hi,
Here is some example code you might find helpful – I'm pretty confident there are no bugs since it's based on code I use, however I might have made some transcription errors.
The basic method (calculate the distance to the closest point on the lineseg, then push the circle away from the lineseg, cancelling inwards velocity) is definitely correct though.
//basic collision detection+response for point-vs-lineseg:
// point(/circle) position p and velocity v
// lineseg(/capsule) positions a,b
// combined circle+capsule radius r (this needs to be >0 !)
//
//based on: //https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
//
//NOTE: the exact same math should work in 3d, just swap vec2 with vec3
function DepenetrateAndSlide(p:vec2, v:vec2, a:vec2, b:vec2, r:float)
{
vec2 pa = p-a;//delta vector from lineseg start to query point
vec2 ba = b-a;//lineseg vector
//"dot(pa,ba)/dot(ba,ba)" projects p onto the line containing ab,
//ie it's the distance from a to p, measured along the line containing ab,
//in units "length of ab".
//this means that if we clamp this parametric position to within [0,1]
//we get a point on the lineseg -- h is the parametric position of the
//closest point on the lineseg to p
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
//note: the position of the closest-point-on-lineseg-to-p in
//worldspace (ie nonparametric) is (a + ba*h)
//(but we don't need that for our purposes)
//
//delta is the vector from the closest point on ab to p
vec2 delta = pa - ba*h;
float dlen = length(delta);
float pen = dlen - r;
if(pen < 0)
{
//if we reached here, p is closer to ab than r
//this means the circle is penetrating the capsule by -pen
//n is the surface normal pointing outward from the lineseg
vec2 n = delta/dlen;
//RESOLVE POSITION ERROR
//push p along the normal, onto the outer surface of the capsule
p -= n*pen;
float vn = dot(v,n);
if(vn < 0)
{
//if we reached here, the circle's velocity is moving
//inward into the collision
float bounce = 0;//0 is totally inelastic, 1 is totally elastic
//RESOLVE VELOCITY ERROR
//cancel the component of v that points along n
v -= n*vn*(1+bounce);
}
}
}
Hopefully this gives you some ideas. If the circle is colliding with multiple linesegs, it's not so trivial to resolve – one solution I've used in the past is:
- record the circle's position
- calculate the penetration for all colliding linesegs
- push the circle out of the closest lineseg (ie correct the position error vs the lineseg with the most-negative pen value)
- go to (2) unless we're not colliding with anything anymore
- the vector from (1) to the circle's current position is an approximate normal; project the velocity onto this normal to cancel inward velocity.
(you could also skip (1) and (5) and instead correct the velocity error during (3), this works too, each process just gives slightly different results in cases with multiple collisions)
Good luck! : )
Raigan