I want to detect pixel-perfect collisions between 2 sprites.
I use the following function which I have found online, but makes total sense to me.
static bool PerPixelCollision(Sprite a, Sprite b)
{
// Get Color data of each Texture
Color[] bitsA = new Color[a.Width * a.Height];
a.Texture.GetData(0, a.CurrentFrameRectangle, bitsA, 0, a.Width * a.Height);
Color[] bitsB = new Color[b.Width * b.Height];
b.Texture.GetData(0, b.CurrentFrameRectangle, bitsB, 0, b.Width * b.Height);
// Calculate the intersecting rectangle
int x1 = (int)Math.Floor(Math.Max(a.Bounds.X, b.Bounds.X));
int x2 = (int)Math.Floor(Math.Min(a.Bounds.X + a.Bounds.Width, b.Bounds.X + b.Bounds.Width));
int y1 = (int)Math.Floor(Math.Max(a.Bounds.Y, b.Bounds.Y));
int y2 = (int)Math.Floor(Math.Min(a.Bounds.Y + a.Bounds.Height, b.Bounds.Y + b.Bounds.Height));
// For each single pixel in the intersecting rectangle
for (int y = y1; y < y2; ++y)
{
for (int x = x1; x < x2; ++x)
{
// Get the color from each texture
Color colorA = bitsA[(x - (int)Math.Floor(a.Bounds.X)) + (y - (int)Math.Floor(a.Bounds.Y)) * a.Texture.Width];
Color colorB = bitsB[(x - (int)Math.Floor(b.Bounds.X)) + (y - (int)Math.Floor(b.Bounds.Y)) * b.Texture.Width];
if (colorA.A != 0 && colorB.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
{
return true;
}
}
}
//If no collision occurred by now, we're clear.
return false;
}
(all the Math.floor are useless, I copied this function from my current code where I'm trying to make it work with floats).
It reads the color of the sprites in the rectangle portion that is common to both sprites.
This actually works fine, when I display the sprites at x/y coordinates where x and y are int's (.Bounds.X and .Bounds.Y):
View an example
The problem with displaying sprites at int's coordinates is that it results in a very jaggy movement in diagonals:
View an example
So ultimately I would like to not cast the sprite position to int's when drawing them, which results in a smooth(er) movement:
View an example
The issue is that the PerPixelCollision works with ints, not floats, so that's why I added all those Math.Floor. As is, it works in most cases, but it's missing one line and one row of checking on the bottom and right (I think) of the common Rectangle because of the rounding induced by Math.Floor:
View an example
When I think about it, I think it makes sense. If x1 is 80 and x2 would actually be 81.5 but is 81 because of the cast, then the loop will only work for x = 80, and therefore miss the last column (in the example gif, the fixed sprite has a transparent column on the left of the visible pixels).
The issue is that no matter how hard I think about this, or no matter what I try (I have tried a lot of things) - I cannot make this work properly. I am almost convinced that x2 and y2 should have Math.Ceiling instead of Math.Floor, so as to "include" the last pixel that otherwise is left out, but then it always gets me an index out of the bitsA or bitsB arrays.
Would anyone be able to adjust this function so that it works when Bounds.X and Bounds.Y are floats?
PS - could the issue possibly come from BoxingViewportAdapter? I am using this (from MonoExtended) to "upscale" my game which is actually 144p.
Remember, there is no such thing as a fractional pixel. For movement purposes, it completely makes sense to use floats for the values and cast them to integer pixels when drawn. The problem is not in the fractional values, but in the way that they are drawn.
The main reason the collisions are not appearing to work correctly is the scaling. The colors for the new pixels in between the diagonals get their colors by averaging* the surrounding pixels. The effect makes the image appear larger than the original, especially on the diagonals.
*there are several methods that may be used for the scaling, bi-cubic and linear are the most common.
The only direct(pixel perfect) solution is to compare the actual output after scaling. This requires rendering the entire screen twice, and requires the scale factor more computations. (not recommended)
Since you are comparing the non-scaled images your collisions appear to be off.
The other issue is movement speed. If you are moving faster than one pixel per Update(), detecting per pixel collisions is not enough, if the movement is to be restricted by the obstacle. You must resolve the collision.
For enemies or environmental hazards your original code is sufficient and collision resolution is not required. It will give the player a minor advantage.
A simple resolution algorithm(see below for a mathematical solution) is to unwind the movement by half, check for collision. If it is still colliding, unwind the movement by a quarter, otherwise advance it by a quarter and check for collision. Repeat until the movement is less than 1 pixel. This runs log of Speed times.
As for the top wall not colliding perfectly: If the starting Y value is not a multiple of the vertical movement speed, you will not land perfectly on zero. I prefer to resolve this by setting the Y = 0, when Y is negative. It is the same for X, and also when X and Y > screen bounds - origin, for the bottom and right of the screen.
I prefer to use mathematical solutions for collision resolution. In your example images, you show a box colliding with a diamond, the diamond shape is represented mathematically as the Manhattan distance(Math.Abs(x1-x2) + Math.Abs(y1-y2)). From this fact, it is easy directly calculate the resolution to the collision.
On optimizations:
Be sure to check that the bounding Rectangles are overlapping before calling this method.
As you have stated, remove all Math.Floors, since, the cast is sufficient. Reduce all calculations inside of the loops not dependent on the loop variable outside of the loop.
The (int)a.Bounds.Y * a.Texture.Width and (int)b.Bounds.Y * b.Texture.Width are not dependent on the x or y variables and should be calculated and stored before the loops. The subtractions 'y-[above variable]` should be stored in the "y" loop.
I would recommend using a bitboard(1 bit per 8 by 8 square) for collisions. It reduces the broad(8x8) collision checks to O(1). For a resolution of 144x144, the entire search space becomes 18x18.
you can wrap your sprite with a rectangle and use its function called Intersect,which detedct collistions.
Intersect - XNA
Related
I want to add force to the grenade according to the touch positions of the user.
--this is the code
physics.addBody(grenade1,"dynamic",{density=1,friction=.9,bounce=0})
grenade1:applyForce(event.x,event.y,grenade1.x,grenade1.y)
Here more the x and y positions are the lower the force is. But the force here is too high that the grenade is up in the sky.
You must calibrate the force applied. Remember that F=ma so if x=250 then F=250, if the mass of the display object (set when added body, based on material density * object area) is 1 then acceleration a = 250, which is very large. So try:
local coef = 0.001
grenade1:applyForce(coef*event.x, coef*event.y, grenade1.x, grenade1.y)
and see what you get. If too small, increase coef until the response is what you are looking for. You may find that linear (i.e., constant coef) doesn't give you the effect you want, for example coef=0.01 might be fine for small y but for large y you might find that coef=0.001 works better. In this case you would have to make coef a function of event.y, for example
local coef = event.y < 100 and 0.001 or 0.01
You could also increase the mass of the display object, instead of using coeff.
Recall also that top-level corner is 0,0: force in top level corner will be 0,0. So if you want force to increase as you go up on the screen, you need to use display.contentHeight - event.x.
Suppose you have quaternion that describes the rotation of a 3D Model.
What I want to do is, given an Object (with rotationQuaternion, side vector...), I want to align it to a target point.
For a spaceship, I want the cockpit to point to a target.
Here is some code I have ... It's not doing what I want and I don't know why...
if (_target._ray.Position != _obj._ray.Position)
{
Vector3 vec = Vector3.Normalize(_target._ray.Position - _obj._ray.Position);
float angle = (float)Math.Acos(Vector3.Dot(vec, _obj._ray.Direction));
Vector3 cross = Vector3.Cross(vec, _obj._ray.Direction);
if (cross == Vector3.Zero)
cross = _obj._side;
_obj._rotationQuaternion *= Quaternion.CreateFromAxisAngle(cross,angle);
}
// Updates direction, up, side vectors and model Matrix
_obj.UpdateMatrix();
after some time the rotationQuaternion is filled with almost Zero at X,Y,Z and W
Any help?
Thanks ;-)
This is a shortcut I've used to get the quaternion for lock-on-target rotation:
Matrix rot = Matrix.CreateLookAt(_arrow.Position, _cube.Position, Vector3.Down);
_arrow.Rotation = Quaternion.CreateFromRotationMatrix(rot);
For this example, I'm rendering an arrow and a cube, where the cube is moving around in a circle, and with the above code the arrow is always pointing at the cube. (Though I imagine there are some edge cases when cube is exactly above or below).
Once you get this quaternion (from spaceship to target), you can use Quaternion.Lerp() to interpolate between current ship rotation and the aligned one. This will give your rotation a smooth transition (not just snap to target).
Btw, might be that your rotation gets reduced to zero because you're using *= when assigning to it.
Your code's a bit funky.
if (_target._ray.Position != _obj._ray.Position)
{
This may or may not be correct. Clearly, you've overridden the equals comparator. The correct thing be be doing here would be to ensure that the dot-product between the two (unit-length) rays is close to 1. If the rays have the same origin, then presumably have equal 'positions' means they're the same.
Vector3 vec = Vector3.Normalize(_target._ray.Position - _obj._ray.Position);
This seems particularly wrong. Unless the minus operator has been overridden in a strange way, subtracting this way doesn't make sense.
Here's pseudocode for what I recommend:
normalize3(targetRay);
normalize3(objectRay);
angleDif = acos(dotProduct(targetRay,objectRay));
if (angleDif!=0) {
orthoRay = crossProduct(objectRay,targetRay);
normalize3(orthoRay);
deltaQ = quaternionFromAxisAngle(orthoRay,angleDif);
rotationQuaternion = deltaQ*rotationQuaternion;
normalize4(rotationQuaternion);
}
Two things to note here:
Quaternions are not commutative. I've assumed that your quaternions are rotating column vectors; so I put deltaQ on the left. It's not clear what your *= operator is doing.
It's important to regularly normalize your quaternions after multiplication. Otherwise small errors accumulate and they drift away from unit length causing all manner of grief.
OMG! It worked!!!
Vector3 targetRay = Vector3.Normalize(_target._ray.Position - _obj._ray.Position);
Vector3 objectRay = Vector3.Normalize(_obj._ray.Direction);
float angle = (float)Math.Acos(Vector3.Dot(targetRay, objectRay));
if (angle!=0)
{
Vector3 ortho = Vector3.Normalize(Vector3.Cross(objectRay, targetRay));
_obj._rotationQuaternion = Quaternion.CreateFromAxisAngle(ortho, angle) * _obj._rotationQuaternion;
_obj._rotationQuaternion.Normalize();
}
_obj.UpdateMatrix();
Thank you very much JCooper!!!
And niko I like the idea of Lerp ;-)
I'm quite new to XNA so excuse me if I ask a 'silly' question but I couldn't find an answer.
I have a problem with the terrain rendered from a heightmap: the terrain I get is too small, I need something larger for my game but I'd like to keep the heigh tdata updated - so I can check for collisions later. (height data being a 2 dimensional array which holds the heights of each point - in my program it's called 'dateInaltime').
The problem is that if I modify the scale of the terrain, the collision checker will use the old values (from the original/small terrain) so I'll get wrong collision points.
My terrain class looks like this.
How can I make the terrain larger but also extend the height data array?
Change this part:
vertex[x + y * lungime].Position = new Vector3(x, dateInaltime[x, y], -y);
to:
vertex[x + y * lungime].Position = new Vector3(x, dateInaltime[x, y], -y) * new Vector3(10);
It should separate the vertices by a scale of 10 (or whatever number you choose).
Tell me please what values should I set for D3DRS_POINTSCALE_A, D3DRS_POINTSCALE_B, D3DRS_POINTSCALE_С to point sprites scaled just like other objects in the scene. The parameters A = 0 B = 0 and C = 1 (proposed by F. D. Luna) not suitable because the scale is not accurate enough and the distance between the particles (point sprites) can be greater than it should be. If I replace the point sprites to billboards, the scale of the particles is correct, but the rendering is much slower. Help me please because the speed of rendering particles for my task is very important but the precise of their scale is very important too.
Direct3D computes the screen-space point size according to the following formula:
MSDN - Point Sprites I can not understand what values should be set for A, B, C to scaling was correct
P.S. Sorry for my english I'm from Russia
Directx uses this function to determine scaled size of a point:
out_scale = viewport_height * in_scale * sqrt( 1/( A + B * eye_distance + C * (eye_distance^2) ) )
eye_distance is generated by:
eye_position = sqrt(X^2 + Y^2 + Z^2)
So to answer your question:
D3DRS_POINTSCALE_A is the constant
D3DRS_POINTSCALE_B is the Linear element (scales eye_distance) and
D3DRS_POINTSCALE_C is the quadratic element (scales eye_distance squared).
How to make a 2d world with fixed size, which would repeat itself when reached any side of the map?
When you reach a side of a map you see the opposite side of the map which merged togeather with this one. The idea is that if you didn't have a minimap you would not even notice the transition of map repeating itself.
I have a few ideas how to make it:
1) Keeping total of 3x3 world like these all the time which are exactly the same and updated the same way, just the players exists in only one of them.
2) Another way would be to seperate the map into smaller peaces and add them to required place when asked.
Either way it can be complicated to complete it. I remember that more thatn 10 years ago i played some game like that with soldiers following each other in a repeating wold shooting other AI soldiers.
Mostly waned to hear your thoughts about the idea and how it could be achieved. I'm coding in XNA(C#).
Another alternative is to generate noise using libnoise libraries. The beauty of this is that you can generate noise over a theoretical infinite amount of space.
Take a look at the following:
http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile
There is also an XNA port of the above at: http://bigblackblock.com/tools/libnoisexna
If you end up using the XNA port, you can do something like this:
Perlin perlin = new Perlin();
perlin.Frequency = 0.5f; //height
perlin.Lacunarity = 2f; //frequency increase between octaves
perlin.OctaveCount = 5; //Number of passes
perlin.Persistence = 0.45f; //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;
//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);
//Get a section
_map.GeneratePlanar(left, right, top, down);
GeneratePlanar is the function to call to get the sections in each direction that will connect seamlessly with the rest of your world.
If the game is tile based I think what you should do is:
Keep only one array for the game area.
Determine the visible area using modulo arithmetics over the size of the game area mod w and h where these are the width and height of the table.
E.g. if the table is 80x100 (0,0) top left coordinates with a width of 80 and height of 100 and the rect of the viewport is at (70,90) with a width of 40 and height of 20 you index with [70-79][0-29] for the x coordinate and [90-99][0-9] for the y. This can be achieved by calculating the index with the following formula:
idx = (n+i)%80 (or%100) where n is the top coordinate(x or y) for the rect and i is in the range for the width/height of the viewport.
This assumes that one step of movement moves the camera with non fractional coordinates.
So this is your second alternative in a little bit more detailed way. If you only want to repeat the terrain, you should separate the contents of the tile. In this case the contents will most likely be generated on the fly since you don't store them.
Hope this helped.