C# / XNA - 2D collision engine glitch? - c#

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).

Related

Get points x distance inside a polygon

I can't figure out a way to do this. I have a list of vector2 points and I need all the points which are inside that polygon with a x distance.
So I have a List of Green points and looking for a List of Red points that have a x distance from respective green points.
I am thinking of getting 2 imaginary points, 1 unity towards the previous and next point.
Then moving towards the center of that 2 points by x distance. But then if the inter angle is not 90 then it will move outside of the polygon.
Vector2 me = point; // point
Vector2 next = n_point; // on left
Vector2 previous = p_point; // on right
//Debug.DrawLine(me, (me - next), Color.green);
// 2 points ep1 & ep2
Vector2 center = Vector2.Lerp(ep1,ep2, 0.5f);
Vector2 finalpoint = Vector2.Lerp(me,center,0.1f); //move towards center
I think I am overthinking this. Is there a super-easy way to do this?
Assuming that all the edges are either horizontal or vertical I would simply consider each possible case separately.
Get the direction vectors.
Vector2 from = me - previous;
Vector2 to = next - me;
I also assume that there is always a turn. I.e., if from is horizontal, then to is vertical and vice versa. Either x or y is 0.0f and the other coordinate is not zero.
I also assume that the x-axis points to the right and the y-axis upwards.
Assuming points are listed clock-wise.
float x, y;
if (from.x > 0.0f) { // from points to the right
y = me.y - distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.x < 0.0f) { // from points to the left
y = me.y + distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.y > 0.0f) { // from points upwards
x = me.x + distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
} else { // from.y < 0.0f, points downwards
x = me.x - distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
}
Vector2 me_inner = new Vector2(x, y);
I hope I got all the signs right.
There are two methods that spring to mind
Option1:
For each line define a normal, i.e. a perpendicular line pointing outward
Define a normal for each vertex as the average of the normals of the lines the vertex is part of.
Move the vertex X units along the normal.
This is fairly easy to implement, but may have problems with self-intersection for some kinds of geometry.
Option2:
For each line define a normal, i.e. a perpendicular line pointing outward
Move each line-segment X Units along the normal.
for each sequential pair of line segments determine if:
the two line segments intersect, if so, use the intersection point as the vertex. i.e. add the intersection point into your point-list.
If they do not intersect, insert a new line segment between the start and end point of the lines. i.e. Insert both start and end vertex to your point-list.
This should handle self-intersection better, but there might still be problem-cases. And it a bit more cumbersome to implement. It somewhat depend on how exact you need the new line positioned, and well it should handle different kinds of geometry.

C# XNA Calculate distance between rectangles (rotation-friendly)

Here's 2 methods available;
if(rectangle.Intersects(otherRectangle))
{
//collision stuff
}
Catch: Only works with non-rotating rectangles.
if(Vector2.Distance(player.pos, enemy.pos) < 50)
{
//collision stuff
}
Catch: Only works with circles.
What I want is to calculate x and y in this image:
Facts
The width and length of both rectangles is defined, along with their rotations.
I can calculate D using the Pythagorean theorem.
But the TRUE distance is D - (X + Y).
General approach
Evidently x and y can be calculated using the Cosine rule.
But I only have the width or length and the angle between the two shapes.
Complication
Plus, this needs to work for any rotation.
The rectangle on the left could be rotated in any direction, and x would be different depending on said rotation.
Question
How would I calculate x and y?
I just want an effective collision detection method more complex than bounding boxes and Pythagoras' theorem.
One approach is to rotate the line with the inverse angle and check with the axis-aligned box:
class RotatedBox
{
...
float CalcIntersectionLength(Vector2 lineTo) //assume that the line starts at the box' origin
{
Matrix myTransform = Matrix.CreateRotationZ(-this.RotationAngle);
var lineDirection = Vector2.Transform(lineTo -this.Center, myTransform);
lineDirection.Normalize();
var distanceToHitLeftOrRight = this.Width / 2 / Math.Abs(lineDirection.X);
var distanceToHitTopOrBottom = this.Height / 2 / Math.Abbs(lineDirection.Y);
return Math.Min(distanceToHitLeftOrRight, distanceToHitTopOrBottom);
}
}
Now you can calculate the actual distance with
var distance = (box1.Center - box2.Center).Length
- box1.CalcIntersectionLength(box2.Center)
- box2.CalcIntersectionLength(box1.Center);
Be sure that the rotation direction matches your visualization.

Drawing an envelope around a curve

In my C# WinForms application I have a picturebox that hosts 2 curves (Resulted from a voltage/current measurement). The X axis is voltage and Y axis is current. The voltage axis is ranged from -5 to 5 but the current axis is a much smaller scale ranged from -10 uA to 10 uA. The task is to see if the second curve is within 10% of the first curve.
For visual inspection I am trying to draw an envelope around the first curve (Blue one). The curve is just a PointF array. At the moment since I have no idea how to draw a correct envelope around the blue curve, I just draw two other curves that are result of X points of the actual curve added and subtracted by 10% of the original curve. Of course this is a bad approach, but atleast for the section of the curve that is noticably vertical, it works. But as soon as the curve is on its non vertical section, this trick does not work anymore, as you can see in the picture below:
Here is the code that I am using to draw the envelope:
public Bitmap DrawEnvelope(double[,] pinData, float vLimit, float iLimit)
{
g = Graphics.FromImage(box);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
PointF[] u = new PointF[pinData.GetLength(0)]; //Up line
PointF[] d = new PointF[pinData.GetLength(0)]; //Down Line
List<PointF> joinedCurves = new List<PointF>();
float posX = xMaxValue * (vLimit / 100);
float minX = posX * -1;
for (int i = 0; i < pinData.GetLength(0); i++)
{
u[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + minX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
for (int i = 0; i < pinData.GetLength(0); i++)
{
d[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + posX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
}
Pen pengraph = new Pen(Color.FromArgb(50, 0 ,0 ,200), 1F);
pengraph.Alignment = PenAlignment.Center;
joinedCurves.AddRange(u);
joinedCurves.AddRange(d.Reverse());
PointF[] fillPoints = joinedCurves.ToArray();
SolidBrush fillBrush = new SolidBrush(Color.FromArgb(40, 0, 0, 250));
FillMode newFillMode = FillMode.Alternate;
g.FillClosedCurve(fillBrush, fillPoints, newFillMode, 0);
g.Dispose();
return box;
}
The green circles are added by myself, and they indicate the region that the second curve (Red one) is potentially has a difference bigger than 10% from the orginal curve.
Would be nice if someone put me in the right way, what should I look to to achive a nice envelope around original curve?
UPDATE
Because I am so noob I cant find a way to implement the answers given to this question until now, So put a bounty to see if somone can kindly show me atleast a coding approach to this problem.
You could try finding the gradient between each pair of points and calculating two points either side that are on the orthogonal that passes through the midpoint.
You would then have two more lines defined as a set of points that you could use to draw the envelope.
Your best bet is to iterate your point array and to calculate a perpendicular vector to two consecutive points each time (see Calculating a 2D Vector's Cross Product for implementation clues). Project in either direction along these perpendicular vectors to generate the two point arrays of your envelope.
This function generates them roughly using segment midpoints (as long as the point count is high and your offset is not too small it should look ok when plotted):
private void GetEnvelope(PointF[] curve, out PointF[] left, out PointF[] right, float offset)
{
left = new PointF[curve.Length - 1];
right = new PointF[curve.Length - 1];
for (int i = 1; i < curve.Length; i++)
{
PointF normal = new PointF(curve[i].Y - curve[i - 1].Y, curve[i - 1].X - curve[i].X);
float length = (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y);
normal.X /= length;
normal.Y /= length;
PointF midpoint = new PointF((curve[i - 1].X + curve[i].X) / 2F, (curve[i - 1].Y + curve[i].Y) / 2F);
left[i - 1] = new PointF(midpoint.X - (normal.X * offset), midpoint.Y - (normal.Y * offset));
right[i - 1] = new PointF(midpoint.X + (normal.X * offset), midpoint.Y + (normal.Y * offset));
}
}
It all depends on the way you want the envelop to be sized.
You could calculate/guestimate the slope of the curve in each point by calculating the slope to the next point and the slope to the previous point, average these and then calculate a perpendicular vector to the slope.
Add this vector to the point of the curve; this gives you the right-hand edge of the envelop.
Subtract this vector from the point of the curve; this gives you the left-hand edge of the envelop.
This method will fail if the points are too far apart or very sudden changes in the points appear.
This is probably a dumb suggestion. Perhaps instead of drawing the envelope yourself, maybe you could let winforms do it for you. Try drawing the envelope as a line with a pen that has a larger width. Perhaps it might work.
If you look at this msdn example on varying the pen width, you might see what I mean.
http://msdn.microsoft.com/en-us/library/3bssbs7z.aspx
2 (probably incorrect) possibilities.
Do what you did originally to get the pale blue wide area, but also do it in the vertical direction (not just the horizontal)
Do what Dan suggested with a REALLY thick line (in pale blue) then draw it again, then draw the original (thin) line on top of it.

Basic (Fake) Raycasting on a 2D Heightmap

Basically what I'm trying to do is shade a 2D heightmap using a very very basic raycasting system that basically just checks if the ray is intercepted before it should be to shade it. However it's not working correctly and I've been banging my head for several hours now on this so I figured it couldn't hurt to turn it over to you guys, because I think it's probably something either so blindingly obvious that I won't see it or so complex that I'll never wrap my head around it.
I have a map like this:
And the raycasting is giving me this (keep in mind it's just debug colors; red is ray interception, but before intended position (so shading), blue would be ray interception in the correct place (so highlights or just as-is), and yellow means that point had no ray interaction at all before the while loop cut-out).
The result should be with red on backfacing slopes and areas behind large mountains (shadows) and blue on sun-facing slopes (highlights). There should not be any yellow. So this image indicates that either all of the rays are hitting the wrong place, or the rays are being intersected ALWAYS somewhere else before they reach their target, which is impossible.
At this point I highly suspect the problem is with my trig.
Here's the Ray class:
class Ray
{
public Vector2 Position;
public Vector2 Direction; // Think in XZ coordinates for these (they are on a perpendicular plane to the heightmap)
// Angle is angle from horizon (I think), and height is height above zero (arbitrary)
public float Angle, Height;
private TerrainUnit[,] Terrainmap;
private float U, V;
public Ray(ref TerrainUnit[,] Terrainmap, float height, float angle)
{
this.Terrainmap = Terrainmap;
this.Angle = angle;
this.Height = this.V = height;
// Create new straight vector
this.Direction = new Vector2(0, 1);
// Rotate it to the values determined by the angle
this.Direction = Vector2.Transform(Direction, Matrix.CreateRotationX(Angle));
//this.Direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));
// Find the horizontal distance of the origin-destination triangle
this.U = V / (float)Math.Tan(Angle);
// Bleh just initialize the vector to something
this.Position = new Vector2(U, V);
}
public void CastTo(int x, int y)
{
// Get the height of the target terrain unit
float H = (float)Terrainmap[x, y].Height;
// Find where the ray would have to be to intersect that terrain unit based on its angle and height
Position = new Vector2(x - U, H + V);
float Z = 1000 * (float)Terrainmap[0, y].Height;
// As long as the ray is not below the terrain and not past the destination point
while (Position.Y > Z && Position.X <= x)
{
// If the ray has passed into terrain bounds update Z every step
if (Position.X > 0) Z = 1000 * (float)Terrainmap[(int)Position.X, y].Height;
Position.X += Direction.X;
Position.Y += Direction.Y;
}
Terrainmap[x, y].TypeColor = Color.Yellow;
if ((int)Position.X == x) Terrainmap[x, y].TypeColor = Color.Blue;
else Terrainmap[x, y].TypeColor = Color.Red;
}
}
Also just as a formality, the function that is casting each ray and how I am calling that:
if (lighting) CastSunRays(1f, MathHelper.PiOver4);
private void CastSunRays(float height, float angle)
{
Ray ray = new Ray(ref Terrainmap, height, angle);
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
ray.CastTo(x, y);
}
I ended up using a much simpler approach with Bresenham's Line Algorithm to find the intercept point; I imagine it's much faster and more efficient than the way I was trying to do it would have been.
My guess is that when your Direction vector is applied to Position, it oversteps the lower limit (Position.Y > -1) before it has a chance to hit the surface (Position.Y <= Terrainmap[(int)Position.X, y].Height).
You could try to decrease the lower limit, or re-order your if/while tests.
Another problem might be that the Direction Vector is too large in comparison to your height-range. The distance between two neighboring pixels is 1, while the whole range of height differences is contained in the range (-1,1). This gives a very flat surface from the ray-casters point of view. When the Direction vector is applied to the Position vector is takes a relatively small step over the length, and a relatively large step over the height.
#Maltor: I actually wanted to comment your own answer, but due to my reputation am not currently able to.
I also used the bresenham's line approach and decreased calculation time to 1/10!
A running example of that can be viewed at my github project TextureGenerator-Online.
The terrain tool uses this approach.
See function setTerrainShadow() at tex_terrain.js

Reversing the direction of an object after a collision

Edit2: See below for new problem.
I'm creating a Windows Phone 7 game and have run into some difficulty. What I'm trying to do is use touchscreen coordinates converted to radians to fire an object upwards at an angle. That part works fine.
However, I cannot figure out how to make the object bounce off the side walls. I had it working in an earlier implementation, and it was simply a matter of negating the Y value when colliding with a wall. Unfortunately, it's not so easy this time. When the object hits the wall, it bounces off the wall for a short distance before changing direction towards the wall again.
How can I structure my code so it stops getting pushed into the wall?
Thank you.
//Move bubble
public void MoveBubble(Bubble b, double radians)
{
if (b.stop == false)
{
{
//Move bubble 'up'
Y = (float)(speed * Math.Cos(radians));
X = (float)(speed * Math.Sin(radians));
}
b.bubblePosX += X;
b.bubblePosY += Y;
//Problem area
if(b.bubblePosY <= 0)
Y-=Y;
if(b.bubblePosY >= 350)
Y-=Y;
}
//Calculate Angle
private void CalculateAngle(int touchX, int touchY)
{
radians = (Math.Atan2(touchX - bubbleStart.X, touchY - bubbleStart.Y));
}
Edit2: Just wanted to add the latest code. Now the ball is bouncing off the walls perfectly, but I'm having an issue with the direction bool being initialized to true. Sometimes, when firing the object, the angle is reversed. How can set the initial bool to the appropriate true/false value?
//Move bubble
public void MoveBubble(Bubble bubble, Vector2 distance)
{
if (bubble.stop == false)
{
float velocityX = speed * distance.X;
float velocityY = speed * distance.Y;
bubble.X += velocityX;
if (direction == true)
bubble.Y += velocityY;
if (direction == false)
bubble.Y -= velocityY;
if (bubble.Y <= 0)
direction = !direction;
if (bubble.Y >= 350)
direction = !direction;
}
}
You're not negating the Y value, you're just subtracting its value from itself. Perhaps you meant to write:
Y = -Y;
Or am I missing something?
You aren't reversing the direction of the bubble movement. the most suitable solution for changing at that moment the direction would be:
public void MoveBubble(Bubble b, double radians)
{
if (b.stop == false)
{
{
//Move bubble 'up'
Y = (float)(speed * Math.Cos(radians));
X = (float)(speed * Math.Sin(radians));
}
b.bubblePosX += X;
//Problem area
if(b.bubblePosY <= 0 || b.bubblePosY >= 350)
b.bubblePosY -= Y;
else
b.bubblePosY += Y;
}
}
That portion of code would move backwards the bubble just for the next frame, to get the bubble change absolutely the direction of its movement, you would have to recalculate the radians getting the point where the bubble finds a collision and the old touch points.
In resume, in each point of collision, you would have to recalculate the direction is getting your bubble, if you goal this, you can have the whole problem solved, reflect direction in case of collision.
I was looking for some code I had, but I found I've lost it, but I hope mi explanation could help.
See you
You have to change the location and the velocity.
If you have a position of (0, -1) and a velocity of (1, -1) and you can't have negative values, you have to switch the y-value (as to make up for the velocity) to (0, 1) and change the velocity to it's negative (1, 1).
so, loc(ation) and vel(ocity) has to change for instance like:
loc(0, 1), vel(0, -1) // OK
loc(0, 0), vel(0, -1) // switch vel to y=-y; (so -1 becomes 1)
loc(0, -2), vel(1, -3) // switch loc to it's positive value, loc(0, 2)
// and the vel to it's opposite (1, 3)
loc(-1, -3), vel(-2, -4) // loc is set to (1, 3) an vel to (2, 4)
// since both X and Y have been breached
Do this (assuming X and Y are velocity):
if(b.bubblePosY <= 0 && Y < 0)
Y = -Y;
if(b.bubblePosY >= 350 && Y > 0)
Y = -Y;
What is happening in your code is that your bubbles are getting stuck in your wall. The code above only reflects the bubble's direction if it is moving into the wall. Once it is moving out of the wall, it ignores that wall.
Additionally, rather than calculating the angle from the difference in positions and then converting it back to a direction vector, try simply normalizing difference in positions to get a direction vector (a unit vector; length = 1):
Vector2.Normalize(new Vector2(touchX - bubbleStart.X, touchY - bubbleStart.Y))
You should be storing either velocity vector or the direction vector between frames, so that when you reflect your bubble it stays going in that direction until it is reflected again.

Categories

Resources