I am writing a SharpDx.Toolkit(XNA) Game. I searched in the internet how to program a collision detection, and I've found this:
static bool perPixel(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++)
{
if (dataA[(x - rectangleA.Left) + (y - rectangleA.Top) * rectangleA.Width] != new Color(0, 0, 0, 0) && dataB[(x - rectangleB.Left) + (y - rectangleB.Top) * rectangleB.Width] != new Color(0, 0, 0, 0))
{
// 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;
}
This code works fine, but only on non-rotating sprites. So I tried to add a Matrix Transformation, but it doesn't work.
Does anyone has an idea, how to do this?
Thank you,
Christian
EDIT:The game is an 2D Game.
Per-pixel collision is overkill in most cases and i would not recommend it for anything else than programming practice.
You can almost always apply other methods to find collissions which introduces less bugs and require less performance.
You can either use a physics-engine or find another method.
Some methods i believe would work well in your case:
OBB-OBB collision
Circle-Circle collision
With these methods you just check if the bounds intersect instead of checking every single pixel against each-other.
Related
I have created a mesh in run-time with a special shared. The shared has the following property:
1. When it is colored for the first time the color is blue
2. When it is colored for the second time the color is yellow
3. When it is colored for the third time the color is red
4. When it is colored for the fourth or more the color becomes green
Here's an example of the colored region:
In the above image the green region has many layer of mashes. I cannot calculate the area by summing up the triangles in mesh because there are many overlaps.
EDIT! I am updating question with more information
The mesh is generated using the following code (it's not the whole code by it is enough to give a general idea of how the mesh is generated)
for (int i = start; i<colorUntil; i++)
{
parent.position = currentPosition;
Vector3 relativePos = points[i + 1] - points[i];
if (relativePos.magnitude > 0.5)
{
parent.rotation = Quaternion.LookRotation(relativePos);
}
currentPosition = points[i];
Vector3 offset = parent.right * width / 2f;
vertices.Add(currentPosition - offset);
vertices.Add(currentPosition + offset);
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
if (vertices.Count< 4)
{
return;
}
int c = vertices.Count;
triangles.Add(c - 1);
triangles.Add(c - 2);
triangles.Add(c - 3);
triangles.Add(c - 3);
triangles.Add(c - 2);
triangles.Add(c - 4);
Vector3[] v = new Vector3[c];
Vector2[] uv = new Vector2[c];
int[] t = new int[triangles.Count];
vertices.CopyTo(v, 0);
uvs.CopyTo(uv, 0);
triangles.CopyTo(t, 0);
mesh.vertices = v;
mesh.triangles = t;
mesh.uv = uv;
}
Now I have to calculate the area of each color. Given the mesh above is it possible to calculate area of each color? Any suggestion would be welcome.
EDIT 2!
Apparently, there's no way to calculate the colored area using mash information (at least according to my research). How I am looking for a creative way to achieve what I want. I would welcome and appreciate any suggestions.
A simple script to take screenshot then count the green pixels.
Put the script on the render camera, set m_TakeScreenshot to true, it'll work in the current frame.
public class ScreenshotCamera : MonoBehaviour
{
public bool m_TakeScreenshot;
public Texture2D m_OutputTexture;
void OnPostRender()
{
if (m_TakeScreenshot)
{
m_TakeScreenshot = false;
//Take screenshot
if (m_OutputTexture == null)
m_OutputTexture = new Texture2D(Screen.width, Screen.height);
m_OutputTexture.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
m_OutputTexture.Apply();
//Get all pixels, count green pixels
var pixels = m_OutputTexture.GetPixels(0, 0, m_OutputTexture.width, m_OutputTexture.height);
int greenPixels = 0, otherPixels = 0;
foreach (var color in pixels)
{
//green
if (color.g > 0.8f && color.r < 0.1f && color.b < 0.1f)
greenPixels++;
//not black
else if (color.r > 0.1f || color.g > 0.1f || color.b > 0.1f)
otherPixels++;
}
}
}
}
I would take only set of green vertices, compute its Delaunay triangulation and then find its area. Or, to get more precise result - compute outline of concave hull instead of Delaunay, and then use this pretty formula for area of arbitrary polygon.
Alright, so today I decided to try to further optimize my collision detection code for my tile engine.
This is what I did:
Circle class checks if there are points within range. If there are, then check for collision between player and tile.
Code:
int tileWidth = 128;
int tileHeight = 128;
int[,] Layer3 = { 1, 1, 1, etc... };
int tileMapWidth = Layer3.GetLength(1);
int tileMapHeight = Layer3.GetLength(0);
Rectangle tile, tile2;
for (int x = 0; x < tileMapWidth; x++)
{
for (int y = 0; y < tileMapHeight; y++)
{
int wallIndex = Layer3[y, x];
if (wallIndex == 1) //Full-sized Tile Collision (128 x 128)
{
if (collisionCircle.Contains(new Vector2(x * tileWidth + (tileWidth / 2) + (int)Player.camera.Position.X,
y * tileHeight + (tileHeight / 2) + (int)Player.camera.Position.Y))) //+ tile / 2 is for centering the point
{
tile = new Rectangle(x * tileWidth + (int)Player.camera.Position.X, y * tileHeight + (int)Player.camera.Position.Y, tileWidth, tileHeight);
Collide(tile);
}
}
}
}
This would check throughout layer3 if there is a "1". If there is, assign rectangle and check for collision if point is inside collision radius.
Also, I checked this code(with a draw method), and I know it's working properly, at least the behavior.
I added in about 120,000(32 x 3888) tiles to try to make it lag, and before the code, it lagged a little bit. But after I added in the code, it lagged even more so.
I thought that since it would only check for collision between tiles(points) that are within the radius it wouldn't even remotely lag, but that's not the case...
Any help/ideas on how to optimize this would be great.
Thanks a lot,
Shyy
EDIT:
Cirlce.Contains() code:
public bool Contains(Vector2 Point)
{
return ((Point - position).Length() <= radius);
}
I used a circle because I've heard it's faster than using a rectangle.
Another possible optimization is instead of
return ((Point - position).Length() <= radius);
use
return ((Point - position).LengthSquared() <= radius * radius);
This is faster because Vector2.Length() has to perform a costly square root operation. Vector2.LengthSquared() does not have to perform that slow operation. The radius has to be multiplied by itself to account for the length from the vector being squared.
It sounds like you're trying to determine what tiles you don't need to use for collision with the player. Another optimization you could do is that if a tile at (X=5,Y=5) is above and to the left of the player, then you don't need to check a tile at (X=4,Y=4). Similarly if (X=5,Y=5) is below and to the right, (X=6,Y=6) is guaranteed to be too far as well. Try to determine when you've passed the player and no longer need to check collisions.
I suggest to loop only over visible tiles in screen to check collision using movement offset.
i will try something from my head..
for x as integer = 0 + offSetX to tilesInWidth + offSetX
for y as integer = 0 + offSetY to tilesInHeight + offSetY
if player.insideCircle(player.position, radius) '
object = layer(y,x);
if player.collideWith(object) then Collide()
end if
next
next
I have pixel-perfect collision down, but it only works with the texture rotated at 0 radians. Here is my code for determining the pixel-perfect collision-
public static bool IntersectPixels(Texture2D sprite, Rectangle rectangleA, Color[] dataA, Texture2D sprite2, Rectangle rectangleB, Color[] dataB)
{
sprite.GetData<Color>(dataA);
sprite2.GetData<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 am having trouble with my collision when it is rotated. How would I go about checking pixel collision with a rotated sprite? Thanks, any help is appreciated.
Ideally, you can exress all transformations with a matrix. That shouldn't be a problem with the helper methods. And if you use matrices, you can easily extend the program without having to change the collision code. Up to now, you can represent a translation with the position of the rectangle.
Let's assume object A has the transformation transA and object B has transB. Then you would iterate over all pixels of object A. If the pixel is not transparent, check which pixel of object B is at this very position and check if this pixel is transparent.
The tricky part is determining which pixel of object B is at a given position. This can be achieved with some matrix math.
You know the position in the space of object A. Firstly, we want to transform this local position to a global position on the screen. This is exactly what transA does. After that, we want to transform the global location to a local location in the space of object B. This is the inverse of transB. So, we have to transform the local position in A with the following matrix:
var fromAToB = transA * Matrix.Invert(transB);
//now iterate over each pixel of A
for(int x = 0; x < ...; ++x)
for(int y = 0; y < ...; ++y)
{
//if the pixel is not transparent, then
//calculate the position in B
var posInA = new Vector2(x, y);
var posInB = Vector2.Transform(posInA, fromAToB);
//round posInB.X and posInB.Y to integer values
//check if the position is within the range of texture B
//check if the pixel is transparent
}
This method that draws my tiles seems to be quite slow, Im not sure exactly whats wrong, it belive my culling method isnt working and is drawing stuff offscreen, but im not completeley sure. Here it is:
// Calculate the visible range of tiles.
int left = (int)Math.Floor(cameraPosition.X / 16);
int right = left + spriteBatch.GraphicsDevice.Viewport.Width / 16;
right = Math.Min(right, Width) + 1; // Width -1 originally - didn't look good as tiles drawn on screen
if (right > tiles.GetUpperBound(0))
right = tiles.GetUpperBound(0) + 1; // adding 1 to get the last right tile drawn
int top = (int)Math.Floor(cameraPosition.Y / 16);
int bottom = left + spriteBatch.GraphicsDevice.Viewport.Height/ 16;
bottom = Math.Min(bottom, Height) + 1; // Height -1 originally - didn't look good as tiles drawn on screen
if (bottom > tiles.GetUpperBound(1))
bottom = tiles.GetUpperBound(1) + 1; // adding 1 to get the last bottom tile drawn
// For each tile position
for (int y = top; y < bottom; ++y)
{
for (int x = left; x < right; ++x)
{
// If there is a visible tile in that position, draw it
if (tiles[x, y].BlockType.Name != "Blank")
{
Texture2D texture = tileContent["DirtBlock_" + getTileSetType(tiles,x,y)];
spriteBatch.Draw(texture, new Vector2(x * 16, y * 16), Color.White);
if (isMinimap)
spriteBatch.Draw(pixel, new Vector2(30+x, 30+y), Color.White);
}
}
}
GetTileSetTypes is a function to get what tiles are around it, for different textures, like DirtBlock_North, DirtBlock_Center, etc.
Tile content is just a class with my block textures.
Try changing SpriteBatch.Begin to defered and combining all of the tiles onto one texture.
See this GameDev question for info about why deferred is most likely the fastest option for you.
Also realize that every time you draw a new texture you have to take the old one out of the GPU and put the new one in. This process is called texture swapping and usually isn't an issue but you are swapping textures twice per tile which is likely to impact performance noticeably.
This can be fixed by combining multiple sprites onto one texture and using the source rectangle argument. This allows you to draw multiple sprites without a texture swap. There are a few OSS libraries for this. Sprite Sheet Packer is my personal favorite.
Unfortunantly without the project and a profiler I'm just guessing; however, these are the two biggest gotchas for rendering tilemaps I know of. I can't really see anything wrong from here. Below is the code I use to draw my tile maps and as you see its very similar to yours.
If all else fails I would suggest using a profiler to figure out which bits are running slowly.
//Init the holder
_holder = new Rectangle(0, 0, TileWidth, TileHeight);
//Figure out the min and max tile indices to draw
var minX = Math.Max((int)Math.Floor((float)worldArea.Left / TileWidth), 0);
var maxX = Math.Min((int)Math.Ceiling((float)worldArea.Right / TileWidth), Width);
var minY = Math.Max((int)Math.Floor((float)worldArea.Top / TileHeight), 0);
var maxY = Math.Min((int)Math.Ceiling((float)worldArea.Bottom / TileHeight), Height);
for (var y = minY; y < maxY; y++) {
for (var x = minX; x < maxX; x++) {
_holder.X = x * TileWidth;
_holder.Y = y * TileHeight;
var t = tileLayer[y * Width + x];
spriteBatch.Draw(
t.Texture,
_holder,
t.SourceRectangle,
Color.White,
0,
Vector2.Zero,
t.SpriteEffects,
0);
}
}
I have some problems with collision. I want to ge coords of a sprite that can be rotated scaled or whatever. It's similiar to Riemers guide, but he's getting a collision of two sprites and I only need those points where alpha is zero.
Better see source:
public Color[,] TextureTo2DArray(Texture2D texture) // to get color array
{
Color[] colors1D = new Color[texture.Width * texture.Height];
texture.GetData(colors1D);
Color[,] colors2D = new Color[texture.Width, texture.Height];
for (int x = 0; x < texture.Width; x++)
for (int y = 0; y < texture.Height; y++)
colors2D[x, y] = colors1D[x + y * texture.Width];
return colors2D;
}
With color is pretty easy, but here is the part where I get points:
public Vector2 TexturePos(Color[,] Color, Matrix matrix)
{
int width1 = Color.GetLength(0);
int height1 = Color.GetLength(1);
for (int x = 0; x < width1; x++)
{
for (int y = 0; y < height1; y++)
{
Vector2 pos1 = new Vector2(x, y);
if (Color[x, y].A > 0)
{
Vector2 screenPos = Vector2.Transform(pos1, matrix);
return screenPos;
}
}
}
return new Vector2(-1, -1);
}
And for matrix I'm using this:
Matrix matrix =
Matrix.CreateTranslation(new Vector3(origin, 0)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(rotation))*
Matrix.CreateScale(scale) *
Matrix.CreateTranslation(new Vector3(pos, 0));
Sprite is rectangular but i get circular movement: I'm rotating it (rotation += 0,5), adding gravity and making it collide with some y value:
Pos.Y += 5;
if (Position.Y >= 200)
BoxPos.Y -= 5;
And I get that it rotates as a circle colliding a line, but not as a rectangle.
Is this normal? Maybe I need some fixes in source?
"That method is supposed to get a position of a pixel (in sprite) that is not transperent but is rotated, scaled (depending on sprite)."
You need to have a look at this:
http://create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed
This is a great article about 2D collisions in XNA and has an example method that performs 2D collision detection for a Scaled & Rotated set of sprites.