Jump to content
  • Advertisement
Sign in to follow this  
JinJo

simple 2d collision responce (Rectangle)

This topic is 3744 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am wanting to move a square and if it hits or overlaps another square then push the position back so that it doesn't collide, like say something falling on ground or running into a wall. I have the routiine to check for collision, I just can't work out how to handle the response. I store the old position before I move, then obviously I have the new position and the othe objects position, just x, y coords. How do I get the coords of the desired position so that the original object is at rest and not coliding with the other, i may have explained that bad, sorry.

Share this post


Link to post
Share on other sites
Advertisement
It's going to be something like this (code ripped out of my engine and edited):



// Given two rectangles, returns true if collision occured.
// If ptOutPenetration was set to point to some variable,
// writes penetration vector into that variable.
// Penetration can be biased by movement vector, or, if not
// available, the smallest penetration on either axis is chosen.
// Penetration vector can then be added to object1's position to
// unseparate after collision occured.
// Uses Win32 structures for clarity and simplicity

bool Collision(const RECT& rcObject1, const RECT& rcObject2, POINT* pptOutPenetration, const POINT* pptMovement)
{
// First, find out if they overlap

if(rcObject1.left > rcObject2.right) return false;

if(rcObject1.right <= rcObject2.left) return false;

if(rcObject1.top > rcObject.bottom) return false;

if(rcObject1.bottom <= rcObject2.top) return false;

// If they do, calculate penetration if specified

if(pptOutPenetration)
{
// Calculate horizontal penetration with respect to rcObject1

if(rcObject2.left >= rcObject1.left &&
rcObject2.left <= rcObject1.right)
{
pptOutPenetration->x = rcObject2.left - rcObject1.right;
}
else if(rcObject1.left >= rcObject2.left &&
rcObject1.left <= rcObject2.right)
{
pptOutPenetration->x = rcObject2.right - rcObject1.left;
}

// Calculate vertical penetration with respect to rcObject1

if(rcObject2.top >= rcObject1.top &&
rcObject2.top <= rcObject1.bottom)
{
pptOutPenetration->y = rcObject2.top - rcObject1.bottom;
}
else if(rcObject1.top >= rcObject2.top &&
rcObject1.top <= rcObject2.bottom)
{
pptOutPenetration->y = rcObject2.bottom - rcObject1.top;
}

if(pptMovement)
{
// If given movement vector, weight penetration vector in movement direction
// This makes sure that when boxes collide, the collider doesn't get
// pushed out of the wrong side with resulting separation vector

float fMovementDistance = sqrtf(float(pptMovement->x * pptMovement->x +
pptMovement->y * pptMovement->y));

pptOutPenetration->x = int(float(pptOutPenetration->x) *
fabsf(float(pptMovement->x) / fMovementDistance));

pptOutPenetration->y = int(float(pptOutPenetration->y) *
fabsf(float(pptMovement->y) / fMovementDistance));
}
else
{
// Choose the smallest absolute penetration axis as separation

if(abs(pptOutPenetration->x) > abs(pptOutPenetration->y))
pptOutPenetration->x = 0;
else
pptOutPenetration->y = 0;
}
}

return true;
}






You could then use this like so:



// Assuing GetPosition() returns a POINT, GetVelocity() returns a point
// and GetPreviousPosition() returns a POINT.
// Also assuming engine.GetTime() returns float for time on this frame in seconds,
// and engine.GetLastTime() returns a float for time on last frame in seconds.

float fTimeDelta = engine.GetTime() - engine.GetLastTime();

object1.SetPosition(object1.GetPosition().x + int(float(object1.GetVelocity().x) * fTimeDelta),
object1.GetPosition().y + int(float(object1.GetVelocity().y) * fTimeDetla));

POINT ptMovement = { object1.GetPosition().x - object1.GetPreviousPosition().x,
object1.GetPosition().y - object1.GetPreviousPosition().y };

POINT ptPenetration;

if(Collision(object1.GetBounds(), object2.GetBounds(), &ptPenetration, ptMovement))
{
object1.SetPosition(object1.GetPosition().x + ptPenetration.x,
object1.GetPosition().y + ptPenetration.y);

// TODO: fire collision response after unseparating - apply forces, play sounds, etc.
}


Share this post


Link to post
Share on other sites
ValMan: thanks very much, exactly what I was looking for.
I'll implement it tomorrow, enjoy weekend!

Share this post


Link to post
Share on other sites
Couldn't wait to try it but I can't get it working correctly.
The penetration point comes out as 0 or nonsense, like x being away off and y never gets calculated.

heres my code


r1.width = 10;
r1.height = 10;
r2.width = 200;
r2.height = 10;
r1.x = 0;
r1.y = 2;
r2.x = 0;
r2.y = 0;
float oldX = 0;
float oldY = 10;

Point movement = { r1.x - oldX,
r1.y - oldY};
Point ptPenetration = {0,0};

CalculatePenetrationPoint(r1, r2, &ptPenetration, &movement);
r1.x = r1.x + ptPenetration.x;
r1.y = r1.y + ptPenetration.y;

void CalculatePenetrationPoint(Rectangle r1, Rectangle r2, Point* PenetrationPoint, const Point* MoveDistance)
{
float r1Left, r1Right, r1Top, r1Bottom;
float r2Left, r2Right, r2Top, r2Bottom;

r1Left = r1.x - (r1.width / 2);
r1Right = r1.x + (r1.width / 2) ;
r1Top = r1.y + (r1.height / 2);
r1Bottom = r1.y - (r1.height / 2);
r2Left = r2.x - (r2.width / 2);
r2Right = r2.x + (r2.width / 2) ;
r2Top = r2.y + (r2.height / 2);
r2Bottom = r2.y - (r2.height / 2);


if(r2Left >= r1Left &&
r2Left <= r1Right)
{
PenetrationPoint->x = r2Left - r1Right;
}
else if(r1Left >= r2Left &&
r1Left <= r2Right)
{
PenetrationPoint->x = r2Right - r1Left;
}

if(r2Top >= r1Top &&
r2Top <= r1Bottom)
{
PenetrationPoint->y = r2Top - r1Bottom;
}
else if(r1Top >= r2Top &&
r1Top <= r2Bottom)
{
PenetrationPoint->y = r2Bottom - r1Top;
}

if(MoveDistance)
{
float fMovementDistance = sqrtf(float(PenetrationPoint->x * MoveDistance->x +
MoveDistance->y * MoveDistance->y));

PenetrationPoint->x = int(float(PenetrationPoint->x) *
fabsf(float(MoveDistance->x) / fMovementDistance ));

PenetrationPoint->y = int(float(PenetrationPoint->y) *
fabsf(float(MoveDistance->y) / fMovementDistance ));
}
else
{
// Choose the smallest absolute penetration axis as separation

if(abs(PenetrationPoint->x) > abs(PenetrationPoint->y))
PenetrationPoint->x = 0;
else
PenetrationPoint->y = 0;
}



}



can you spot anything wrong with my code?

Share this post


Link to post
Share on other sites
float fMovementDistance = sqrtf(float(PenetrationPoint->x * MoveDistance->x + MoveDistance->y * MoveDistance->y));

This looks wrong... I'm assuming you want to calculate the Euclidean distance here, so what is PenetrationPoint->x doing there?

I just browsed a bit through the code from both of you and I could be wrong but what if one rectangle is completely inside another rectangle?
First you have the condition (r2Left >= r1Left && r2Left <= r1Right) if this is true (which will be if the rectangle is contained in another one, assuming that the inner rectangle is R2) then you won't even test the other side. But if R2 is contained in R1 and the right side of R2 is close to the right side of R1 and the left side of R2 isn't close to the left side of R1 then obviously the shortest penetration is defined by R1.right - R2.left. But you won't find this because the first condition was already true.

Anyway I guess your movement steps won't be so big that one rectangle will be completely contained in another, but it would still be a good idea to comment that in your code. Unless you don't care about the shortest penetration of course.

Share this post


Link to post
Share on other sites
As Kamos pointed out, you made a mistake while interpreting my code. Movement Distance should be square root of x squared plus y squared. In this case,


sqrtf(float(pptMovement->x * pptMovement->x + pptMovement->y * pptMovement->y));


Or, in your code, should be:


float fMovementDistance = sqrtf(float(MoveDistance->x * MoveDistance->x + MoveDistance->y * MoveDistance->y));


I didn't look at anything else, just make sure there are no differences.

Also, at the time of writing that code, I don't think I accounted for two objects being possibly inside one another. If this case is to be considered, I think it may go like this:

If Inside Then
Calculate distance to make A's top/left edge come up o B's right/bottom edge (1st possible penetration)
Calculate distance to make A's bottom/right edge come up to B's top/left edge (2nd possible penetration)
Calculate distance to make A's right/bottom edge come up to B's left/top edge
Else If Overlaps on Right/Bottom
Calculate distance to make A's left/top edge come up to B's right/bottom edge

This can probably be rewritten to be easier to read and more efficient. As can be seen, if two objects are inside one another there is not one but two possible distances that can be used to separate them. You would then have to weigh those additional vectors to determine which to use.

[Edited by - ValMan on July 21, 2008 12:37:24 AM]

Share this post


Link to post
Share on other sites
I've no idea why PenetrationPoint creeped in there, thanks.
No wonder I was getting silly results. I'll amend it tonight.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!