Advertisement

How do you respond to collision with line segments?

Started by June 19, 2015 05:11 AM
8 comments, last by Dim_Yimma_H 9 years, 7 months ago
Hi, I'm having difficulty performing collision response between a 2d axis aligned box and some line segments.
Given a box with a velocity, a set of segments and a simulation time step, I'm current doing this:
Let d be the distance the box travels in the simulation time given that it collides with no segments.
1. Cast the box against all the line segements in the direction of it's velocity.
2. If the closest segment is a distance d0 away and if d0 is less than d, then move the box a distantance d0. Subtract away the simulation time
used by moving the box a distance d0 with it's current velocity.
If there were no intersections then move the box a distance d and end.
3. Subtract the velocity component normal to the segment that was collided with.
Now go to step 1 and use the updated time, position and velocity for the ray cast.
There are some details left out but that's the basic idea.
When colliding with single line segments there are no problems. However if there are two bordering line segments at an acute enough angle then the box will occasionally tunnel through. I believe the issue has to do with limited floating point accuracy. With small velocities the box is able to move arbitrarily close to a line segment which causes the ray cast in the next iteration to fail.
Here is the algorithm with more details:

while (sim_t > 0)
{
    // cast box against segments 
    Vec2 closest_normal;
    float closest_t = IntersectBoxSegments(box, box_velocity, &closest_normal);
    // IntersectBoxSegments solves for the intersection between: box + box_velocity*t = segment 

    if (closest_t < sim_t)
    {
        // move the box so that it is a distance of box_velocity*FUDGE away from the closest segment
        // the fudge is my attempt at keeping the box at a minimum distance away from the segment.
        box.center += box_velocity*(closest_t - FUDGE);
        sim_t -= closest_t;

        // remove the velocity in the direction of the normal.
        box_velocity -= Dot(box_velocity, closest_normal)*closest_normal;
    }
    else
    {
        box.center += box_velocity*sim_t;
        break;
    }
}

Below is an image showing a case where tunneling might occur.

Dj3gd0d.png

I realize this is not the best method for resolving collisions so suggestions are welcome.

Thanks for the help.

Well first of you should introduce preliminary AABB checks, and only care about possible collisions if 2 or more bounding boxes overlap. To reduce tunneling at high velocities you might want to extend AABBs in direction of velocity, and consider subiteration using timesteps sized in a manner that makes sure that each iteration the AABB of the new position would overlap with the AABB of the old position of the object of interest.

Apart from that decide between discrete and continuous detection. Discrete collision detection being easier to implement but somewhat prone to tunneling and overlapping, continuous detection being harder to implement and more comp. expensive but more solid physically.

As for continuous detection it goes something like this:

When you detect a bunch of overlapping AABBs you calculate the times of impact and recalculate positions and velocities for that point in time, and recalculate times of impact - repeat a bunch of times or until all collisions are stepped through.

Your method seems like a mix of the two, as you seem to move box to line ever so close with decreasing timesteps - i'd meh to that and say either use full timestamps, check for overlapping of objects, calculate overlap and move apart again as response (discrete), or as above using "exact" times of impact.

A good google for keywords from this should take you further.

Advertisement

To reduce tunneling at high velocities you might want to extend AABBs in direction of velocity

I actually am performing continuous detection. I'm raycasting the box against the the line segments before updating its position (in the direction of velocity). The issue isn't with tunneling at high speeds. I believe the tunneling issue is from the AABB becoming arbitrarily close to the line segments at low velocities. One thing I tried was to ensure that the box is a minimal distance from the line segment by adjusting the fudge value according to the velocity. I still had problems with this and it was kind of hard to reason about.

I actually found a better solution. Insead of casting the box against the line segments. I'm now casting a point againts the minkowski difference of the box and the segment. So much more simple. I feel dumb for not thinking of it earlier.

I'm still curious though if there's a way to make the first method more impervious to float precision problems.

I think it's because of the fudge, as you use that code line to move box.center after intersection, so the center will vibrate forth an back between the two tunnel lines. At that point it depends on your IntersectBoxSegments how well it detects the box that has already vibrated a tiny bit through the line. I believe that's not a problem with float precision loss, but rather has to do with your response on the box.center, breaking the box out of your IntersectBoxSegments capability.

The reason your "OK" case may work is because the "vibration" there is almost along both line segments (almost perpendicular lines), instead of against each other as in the "tunneling" case (almost parallel lines)

You may try and remove the box.center code line with fudge. So the response is only on the velocity.

I actually am performing continuous detection.

Good! In my opinion continuous collision detection should only need response on the velocity itself, so the function results in collision avoidance rather than a collision resolving response.

This has nothing to do with floating point accuracy. You are only zeroing velocity along the normal, but are not handling the acute case specifically. What you end up with is A) infinite loop since velocity will never come to zero or B) tunneling because you didn't infinite loop.

When hitting an acute angle and zeroing velocity along each normal, you will still have residual velocity going towards the angle. If you draw a picture with the velocity vector and normals this is easy to see. When we zero velocity in a direction we are saying "only allow movement perpendicular to this vector". So with acute angles there are many directions that are not parallel with both vectors that still allow tunneling velocity.

If we have a 90 degree angle it's easy to see how all velocity is zero'd out, since the vectors are linearly independent. If we have an obtuse angle all tunneling velocity is always zero'd out due to the convex nature of the planes formed by an acute angle.

To detect such a case you need to have code that understands when you do hit an acute angle. Luckily for you the solution is simple in 2D: detect acute angle and zero all velocity (or create a deflection velocity, like a bounce away).

In 3D it becomes much more difficult since any number of planes can form a concave cavity. One idea is to detect pairs of concave planes, compute the cross product of the normals, and this resulting cross is the direction of allowed movement for the plane pair.

An alternative solution is to detect when the player comes into a tunneling position and use a position level Seidel plane solver to gently press the player out of all intersecting planes.

Does this all make sense?

Edit:
Also it's silly to use a box as the character in many cases. I suggest considering a capsule for your 2D character when the character is moving around. When at a stand-still you can (if you want to) use AABB collision to let the player stand on ledges without slipping off. The capsule avoids snagging small gaps in level geometry when running around over a surface represented by multiple shapes.


I believe that's not a problem with float precision loss, but rather has to do with your response on the box.center, breaking the box out of your IntersectBoxSegments capability.

I don't think this is the case. The fudge value only causes correction along the direction of velocity.


box.center += box_velocity*(closest_t - FUDGE);
sim_t -= closest_t;

This only moves the box along its trajectory until it intersects with a segment. The fudge prevents the box from butting up against the segment which would cause tunneling.

There is still a problem though. FUDGE should really depend on velocity so that the box is moved a small distance away from the segment, not a small time.


When hitting an acute angle and zeroing velocity along each normal, you will still have residual velocity going towards the angle.

Sorry I'm not sure what you mean by this. Do you mean there is still velocity towards the vertex where the segments join?

Since the normals are linearly independent, the velocity will eventually become zero by repeatedly subtracting the component of velocity along the normals. This is true of any two linearly independent unit vectors. Thus as the number of iterations goes to infinity the velocity will go to zero. I believe that the problem is with small velocities. With a small velocity, the box is able to get close to the segment without intersecting it. Eventually, if it becomes too close the raycast will fail.

Advertisement

Sure, but you'll get a huge spike in iterations that could who knows how long. This is both a performance and stability problem that needs to be dealt with explicitly.

If you have a problem with failing raycasts, then make sure you keep a small buffer of space between all planes and your character. You can do this with the seidel solver I mentioned.

The fudge value only causes correction along the direction of velocity.

FUDGE should really depend on velocity so that the box is moved a small distance away from the segment, not a small time.

You're right. And when you move the box away from intersection, that response in movement may cause another intersection. Does your IntersectBoxSegments notice that small discrete movement of box.center?

For debugging, what are you're results with the following code?


while (sim_t > 0)
{
    Vec2 closest_normal;
    float closest_t = IntersectBoxSegments(box, box_velocity, &closest_normal);

    if (closest_t <= sim_t)
    {
        box_velocity -= Dot(box_velocity, closest_normal)*closest_normal;
	break;
    }
    else
    {
        box.center += box_velocity*sim_t;
        break;
    }
}


when you move the box away from intersection, that response in movement may cause another intersection

In most cases it shouldn't cause an intersection because the box is being moved back in time essentially. However I believe an intersection can happen when closest_t < FUDGE.


Does your IntersectBoxSegments notice that small discrete movement of box.center?

Sorry I'm not sure what you mean. IntersectBoxSegments is a bad name, it should probably be something like CastBoxAgainstSegments.


For debugging, what are you're results with the following code?

Since the position doesn't update when there is a segment that will be intersected within sim_t, the box stops a little distance away from the segment. Also for the code you posted a loop isn't necessary.

I'm using a new method now. Instead of casting a box against a segment, I cast the boxes center against the minkowski difference of the box and a segment. I also changed FUDGE to depend on the current velocity. This seems to work exactly how I expect it to and I haven't found any issues with tunneling yet.

However I believe an intersection can happen when closest_t < FUDGE.

I thought about that too, but it depends on if fudge is positive or negative. Since you add, box.center += it means closest_t - FUDGE must be < 0 in order to make your expression subtract and "move back in time"

Does your IntersectBoxSegments notice that small discrete movement of box.center?

Sorry I'm not sure what you mean.

What I meant is that box.center += is a discrete non-continuous movement, but IntersectBoxSegments detects "continous" velocity. So I meant to check with you if IntersectBoxSegments handles both continuous velocity and also the non-continous box.center?

Since the position doesn't update when there is a segment that will be intersected within sim_t, the box stops a little distance away from the segment.

Cool, this indicates that IntersectBoxSegments detects velocity correctly, and that you could modify your code upon intersection, so the code moves the box to your fudge distance from the intersected segment using only a temporary "velocity" instead of moving box.center non-continuously.

This topic is closed to new replies.

Advertisement