So right now for my character solver I compute the time of impact of a character AABB and convex polygons using an algorithm based on "Robust Continuous Collision Detection Between Arbitrary Polyhedra Using Trajectory Parameterization of Polyhedral Features" by J.M.P van Waveren (link)
Doom 3 uses this for all of it's collisions so it never has to deal with penetration. This seems undesireable for a couple of reasons so I've decided I'm going to switch to a position solver instead. After some googling I came across Erin Catto's 2014 GDC presention "Understanding Constraints" (link) where he briefly describes how his character solver works. I've written a simple C program (attached to this post) to play around with these concepts and I have some questions that hopefuly I will be able to get answered here.
Keys for the test program:
- ESC - quit
- r - reset
- s - step
- 1-8 - switch to setup 1-8
Here's how I intepret the algorithm:
- Let's call the current position of the character the 'initial position'. This position never changes during the algorithm.
- Let's call the position we are trying to move the character to the 'target position'. This position also never changes during the algorithm.
- Scan the environment for overlap with the capsule at the initial position and construct a list of contact planes.
- Perform a plane solve on the target position to get the first solve position.
- Perform a shape cast from the initial position to the solve position. Move to the first hit point and collect new contact planes there. Add these new planes to the list.
- Perform a plane solve on the target position to get the next solve point.
- If the distance between the new solve point and the old one is within some epsilon, we are done. Otherwise go back to step 3.
Plane solve code:
vec3_t p = target_position;
vec3_t a = p + local_capsule_a;
vec3_t b = p + local_capsule_b;
for(uint32_t i = 0; i < max_iter; i++)
{
vec3_t last_p = p;
for(uint32_t j = 0; j < plane_count; j++)
{
const plane3_t &plane = planes[j];
float d1 = point_distance(plane, a);
float d2 = point_distance(plane, b);
float dist;
if(d1 < d2)
dist = d1;
else
dist = d2;
dist -= capsule_radius;
if(dist < 0.0f)
{
p -= dist * plane.n;
a = p + local_capsule_a;
b = p + local_capsule_b;
}
}
vec3_t delta = p - last_p;
if(dot(delta, delta) < epsilon)
break;
}
solve_position = p;
Couple of issues:
1. Is this interpretation correct?
2. In the test program I am only adding one new plane per shape cast, is there a reason to collect more (all touching planes)?
3. In the test program I am using the segment planes directly. In 3D I would directly use the polygon planes. If I use the direction from the closest point on the segment to the circle centre I get into situations like this, where the character ends up away from the collision geometry:
4. If I naively add all overlapping planes during step 1 I get into sitations like this:
The green circle is at the source position. Step 1 will find both planes and the character will end up in the corner, not touching either segment. In the test program I solve this problem by clearing the plane list after step 2, but this is insufficient as I also need to select the proper planes after a shape cast. I can think of some approaches to solving this problem, but instead of experimenting right away, I'd like to know how other people have solved this. What is a good way to determine which planes to actually use?