I'm currently trying to understand per pixel collision detection.
This is the code I don't understand:
static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
Rectangle rectangleB, Color[] dataB)
{
// Find the bounds of the rectangle intersection
int top = Math.Max(rectangleA.Top, rectangleB.Top);
int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
int left = Math.Max(rectangleA.Left, rectangleB.Left);
int right = Math.Min(rectangleA.Right, rectangleB.Right);
// Check every point within the intersection bounds
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
// Get the color of both pixels at this point
Color colorA = dataA[(x - rectangleA.Left) +
(y - rectangleA.Top) * rectangleA.Width];
Color colorB = dataB[(x - rectangleB.Left) +
(y - rectangleB.Top) * rectangleB.Width];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
}
// No intersection found
return false;
}
I really haven't understood the all loop. I'll be glad for some explanation how it works.
First up, it finds the region the two image rectangles intersect, then it iterates through each pixel in that region, and compares the alpha values of each image of each pixel. If neither has an alpha value of 0, they are both considered 'solid' and therefore colliding.
it's not that hard (in this case) - you give the algorithm the two bounding-boxes of your objects (so the hole object is inside this box), and a array with color-information for them.
Tha algorithm assumes that a point belongs to the object IFF it is not transparent - this is important.
The first step is to calculate the intersecting rectangle - if you intersect two rectangles that have sides parallel to the axes like in this case - you will get a rectangle again or an empty set.
The next step is to iterate in this intersecting rectangle for all (x,y) -coordinates insiede - first y, then x -so you get your normal first x, then y inside, but this is minor point and not important.
Then finally the algorithm gets the color for object A and B at the current pixel (x,y) - if both colors are NOT transparent then the pixel is in both objects and the objects have to intersect at this point - so the algorithm terminates with "YES they intersect"
If all pixels in the intersection of the bounding boxes where checked and no common (e.g. not transparent) pixel was found the object don't intersect and so the algorithm terminates with "NO they don't intersect"
I hope this helps.
for (int y = top; y < bottom; y++) loops over the lines of the resulting rectangle from top to bottom, and for (int x = left; x < right; x++) loops over pixels inside each line, left to right.
Related
I'm attempting to check if a pixel in a bitmap is touching another pixel of a certain color but I'm not quite sure how to do it.
So far, I can iterate through the pixels like this:
for (int x = 0; i < bitmap.Width; ++x)
{
for (int y = 0; h < bitmap.Height; ++y)
{
// check if this pixel is touching Color.Blue
}
}
If anyone knows how to do this and could tell me, I'd appreciate it. Thanks in advance.
I tried doing image.GetPixel(x - 1, y) == Color.Blue as a test on the left direction, but it just returned false every time.
Of course it will return false. Few are the pixels that are set to a basic color (red, green, blue, etc. — not something like "beige"). Colors are usually mixed to make them more pleasing to the eye. If a pixel is set to an RGB color (45, 45, 255) — you will see blue, eye-pleasing pixel, but it won't be equal to Color.Blue.
I advise you to check closeness to blue, not equality. Try something like this (it tests on the left direction):
Color color = bitmap.GetPixel(x - 1, y);
if (color.B >= 150 && color.R <= 50 && color.G <= 50) {
// The pixel is touching a color close to blue, do something
}
I want to outline my circle of cells, to do that I need a path that travels from each outer corner of the circle. Ive tried to illustrate it below.
Yellow is the circle, in red I've noted some coordinates the way I store them, and blue are where the path points would need to be:
To get the circle of cells surrounding cell at gridX, gridZ I use the following code:
public List<Cell> GetSurroundingCellsCircle(int gridX, int gridZ, int distance)
{
List<Cell> matches = new List<Cell>();
int EX = distance + gridX;
int EY = distance + gridZ;
int SQ = distance * distance;
for (int x = gridX - distance; x <= EX; x++)
{
for (int z = gridZ - distance; z <= EY; z++)
{
int c = x - gridX;
int d = z - gridZ;
if ((c * c + d * d) < SQ)
{
Cell cell = GetCell(x, z);
if (cell != null)
matches.Add(cell);
}
}
}
return matches;
}
So in this List I have access to all the cells in the circle. Now I need to find the border cells, then find the path of the outer corner of those cells. I do not really even know where to start.
Once you have the list of cells:
For each column find the coordinates of the top cell (the one with the maximum Y coordinate). This gives you the cells at the top, so draw a line above each cell.
For each row find the coordinates of the right-most cell (the one with the maximum X coordinate). This gives you the cells on the right side of the circle, so draw the line on the right of each cell.
For each column find the coordinates of the bottom cell (the one with the minimum Y coordinate). This gives you the cells at the bottom of the circle, so draw the lines below each cell.
For each row find the coordinates of the left-most cell (the one with the minimum X coordinate). This gives you the cells on the left side of the circle, so draw the lines on the left side of each cell.
This isn't the most efficient way of doing this, but if your grid isn't huge it should be quick enough.
As I have known, the correct size for the camera is half of your wanted width, so in my case:
Width: 1920
Height: 1080
Camera Size: 540
Each block represents 64x64 pixels
In unity I was having this problem by start placing blocks in the -960 (half of 1920, which is the camera minimun x):
Since as shown I thought, just add half of the blocks width to its x, and subtract half of its height in its y, so this happen:
After a lots of tries I figured out it was supposed to be placed at -938 and 518, which is 22 units away from the corner, by placing in that position this is my result:
(Ignore the other blocks for now, they are with wrong algorithm now)
So what I ask is:
Why 22? How can I get to that value from my starting values? (Please don't answer something like 2.032%, because probably have something more integer to its math calculation)
Code if needed:
float height = Camera.main.orthographicSize *2f;
float width = height / (float)Screen.height * (float)Screen.width;
Destroy(map);
map = new GameObject();
print("W: "+width+". H: "+height);
lastH=height;
lastW=width;
for (int i=0; i<30; i++) {
for (int j=0; j<16; j++) {
if(mapa[i,j]>0){
GameObject aux = objetos[mapa[i,j]-1];
float wX=aux.transform.localScale.x,hY=aux.transform.localScale.y;
print ("wX: "+wX+", hY: "+hY);
float unidadeW = 2*(float)lastW/(float)(20*wX);
float unidadeH = 2*(float)lastH/(float)(20*hY);
//aux.transform.localScale = new Vector3(unidadeW,unidadeH,0);
GameObject t = (GameObject)Instantiate(aux, new Vector2(j*wX*wX/100-width/2+wX*wX/200,-i*hY*hY/100+height/2-hY*hY/200),Quaternion.identity);
t.transform.parent = map.transform;
}
}
}
The correct way to make a distance between two units is their diagonal, see the image below:
Doesn't matter which is your orientation point, if its top left or center, the distance between the blocks will be √2*side, so the correct distance between mine was around 22, and I can find it by doing this equation:
sqrt(2)/2*32=22.62 (the correct distance between two tiles of 64x64), instead of using 64+ at the last x and 64+ at the last y the right way to do is by adding 22.62 at both multiplications.
I have a set of images of various objects of different shapes and sizes. They have transparent backgrounds set on them but the full dimension of the image is a square. I want to calculate a box of coordinates (upper left x/y, lower right x/y) that encompasses the object in the image while ignoring as much of the transparent background as possible. And I need to do this on the fly in code.
Is there an example, or a library, available for C# that would allow me to do this? I am using these in a website where several objects are dynamically overlaid into a single image and I want to calculate an image map with coordinates for each object in the merged image. Using the full size of the square image creates huge overlaps in the coordinate sets and often the last in coordinates hide the lower object from being clickable.
Well, using System.Drawing.Bitmap this is not too hard (the following certainly is not the most performant way):
// we will store actual bounds in here
int left, right, top, bottom;
using (Bitmap b = ...) // open image here
{
var pixelsX = Enumerable.Range(0, b.Width);
var pixelsY = Enumerable.Range(0, b.Height);
left = pixelsX.FirstOrDefault(
x => pixelsY.Any(y => b.GetPixel(x, y).A != 0));
right = pixelsX.Reverse().FirstOrDefault(
x => pixelsY.Any(y => b.GetPixel(x, y).A != 0));
top = pixelsY.FirstOrDefault(
y => pixelsX.Any(x => b.GetPixel(x, y).A != 0));
bottom = pixelsY.Reverse().FirstOrDefault(
y => pixelsX.Any(x => b.GetPixel(x, y).A != 0));
}
Notice that all these 4 coordinates are "inclusive" bounds (meaning: the row/column of pixels they represent does contain at least one non-transparent pixel), so if you should calculate width and height of your new bounds do it like this:
int width = right - left + 1;
int height = bottom - top + 1;
By the way, for an entirely transparent image, all 4 coordinates should be 0, as a result width and height will both be 1 - I guess this is not a problem for you.
I've got quite a lot of code here, but it's relatively straightforward.
This is all snippets from different classes, all references are right, but I think I've done a math-based error somewhere and I can't find it. It always finds a collision on the y axis a pixel before it should. I haven't tried it with different X axis positions but it seems to fall past blocks next to it fine.
The struct "mapSection" just contains two Vector2s- A top-left block and bottom-left block coordinate.
tileManager.def_ts is the default tile width and height (32). The player's size is 32x64.
The toWorldSpace function does nothing right now other than return so that's not the problem.
When I say block coordinate I mean which index the block is in the tile array (Ex 0, 0 is the first block, 0, 1 is the second block on the Y axis, 1, 3 is 1 block in on the X axis and 3 on the Y axis, I do not mean actual pixels.)
From tile engine class:
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
position = toWorldSpace(position);
position.X = (float)Math.Floor(position.X / tileManager.def_ts);
position.Y = (float)Math.Floor(position.Y / tileManager.def_ts);
size.X = (float)Math.Floor(size.X / tileManager.def_ts);
size.Y = (float)Math.Floor(size.Y / tileManager.def_ts);
return new mapSection(position, position + size);
}
public bool collision(Vector2 screenPosition, Vector2 size)
{
mapSection mapCollisionPossibilities = toMapMinMax(screenPosition, size);
for (int y = (int)mapCollisionPossibilities.topLeft.Y; y <= mapCollisionPossibilities.bottomRight.Y; y++)
{
for (int x = (int)mapCollisionPossibilities.topLeft.X; x <= mapCollisionPossibilities.bottomRight.X; x++)
{
if (x >= 0 && y >= 0 && y < tiles.Count && x < tiles[y].Count)
{
if (tileManager.tileTypes[tiles[y][x]].collideable == true)
{
return true;
}
}
}
}
return false;
}
And this is the code from the player class:
if (!tEngine.collision(position + new Vector2(0, 1), new Vector2(32, 64)))
{
position.Y += 1;
}
I add "Vector2(0, 1)" because I want to see if there's a collision a pixel further down; so that he falls until he hits something. It's very basic right now but it's only to test the collision engine, which isn't working.
There's a picture of the error. You can see the player is a pixel too high.
In the picture, "X:" is the top-left block coordinate on X axis, "X2:" is the bottom-right block coordinate on the X axis, and same with "Y:" and "Y2: except Y axis. They're read from the mapSection directly.
If anyone can notice why this is happening, it would be massively appreciated.
Thanks.
If you cannot understand any section of the code just post in the comments and I'll be happy to explain, or if you think I've been a bit too unspecific in some area of this post.
EDIT: For the tile coordinates issue, your toMapMinMax code should be more like this:
EDIT2: have subtracted (1, 1) from bottomRight, since it is a size we are adding.
public mapSection toMapMinMax(Vector2 position, Vector2 size)
{
Vector2 topLeft = position;
Vector2 bottomRight = position + size - new Vector2(1, 1);
topLeft.X = (float)Math.Floor(topLeft.X / tileManager.def_ts);
topLeft.Y = (float)Math.Floor(topLeft.Y / tileManager.def_ts);
bottomRight.X = (float)Math.Floor(bottomRight.X / tileManager.def_ts);
bottomRight.Y = (float)Math.Floor(bottomRight.Y / tileManager.def_ts);
return new mapSection(topLeft, bottomRight);
}
Also, I was wrong in my above comment; you do want <= signs in your two for loops, because most of the time you will be checking 6 tiles.
for the off-by-one-pixel issue:
In order for you to see the character off by some amount of pixels, the draw code and the collision code must be different. If they were identical, for example if they were both off by 15 pixels (you collide 15 pixels too early, but you are also drawing 15 pixels ahead), you wouldn't see any change.
The 1 pixel gap indicates a 1 pixel difference between the draw coordinate calculation and the collision coordinate calculation. This 1 pixel difference is most likely caused by differences in rounding, probably that you are calling Math.Floor in the collision code, but are not rounding the coordinates in the draw code. (I would guess you are probably just passing the position Vector2 straight to the SpriteBatch.Draw method).