Sign in to follow this  
ElectroDruid

Joint constraints for ragdoll physics

Recommended Posts

ElectroDruid    122
Hello, I'm wondering if anyone can point me to any good (preferably readable - I'm a half decent coder but not a hardcore mathematician) resources on ways to implement constraints for joints and hinges in ragdoll physics. Also, to see if anyone can point out the stupid mistakes I'm making with my current code. I'm fooling about with a simple ragdoll physics system based on Thomas Jakobsen's Verlet integration system (I can dig out a link to the paper if anyone hasn't seen it). So far I've been pretty pleased with what the Verlet system can do - I've modelled cloths, ropes, rigid boxes, so I built a ragdoll model. I'm working in 2D for now, to keep things simple. I'm having problems working out joint constraints to keep the model's elbows and knees etc from rotating all the way round and doing other weird stuff. So let's same I've got 3 particles at 2D positions point1, point2 and point3. point2 is a pivot point so I want to make sure point1 and point3 do sensible things relative to it. Every time I iterate through the function to satisfy all the particle constraints, after I've applied all the other constraints to the particles, I test the angle between the sticks:
Vector2f v0 = (point1 - point2).Norm();
Vector2f v1 = (point1 - point2).Norm();
F32 invCos = v0.Dot(v1);
F32 theta = acosf(invCos);
If the angle between them is too small, I can get away with an arbitrary little hack to moves point1 and point3 away from each other. You'll remember that with the Verlet scheme you don't so much apply forces to particles as just move them where you want them and let the integration deal with the wider results.
if (theta < 0.1f)
{
	// Cheesy hack
	Vector2f diff = point1 - point3;
	diff /= 2.0f;
	point1 += diff;
	point3 -= diff;
}
This is clearly not going to work for when theta is too big. point1 and point3 would be far apart and I'd be trying to move them both to somewhere near point2. What I'm trying to do instead is to calculate the angle I need to rotate point1 and point3 by to get theta back down to an acceptible value. In this particular code I'm trying to keep theta less than 2/3 PI (120 degrees).
else if (theta > (PI * 2 / 3))
{
	// Get the angle to rotate each point around
	F32 adjustAngle = (theta - (PI * 2 / 3)) * 0.5f;

	// Adjust point1
	v0 = point1 - point2;
	F32 length = v0.Length();
	F32 phi = asinf(v0.y / length);
	point1.x = point2.x + (length * cosf(phi - adjustAngle));
	point1.y = point2.y + (length * sinf(phi - adjustAngle));

	// Adjust point2
	v1 = point3 - point2;
	length = v1.Length();
	phi = asinf(v1.y / length);
	point3.x = point2.x + (length * cosf(phi + adjustAngle));
	point3.y = point2.y + (length * sinf(phi + adjustAngle));
}
This works, up to a point. The simulation does react when the angle gets too big, but it does it rather violently by snapping the joint closed (rather than the more relaxed behaviour when theta gets too small). This sends shudders all across the rest of the mesh and is often enough to bend the model right out of shape - my ragdoll is made of boxes, hinged at the corners and the shudder seems to be able to turn boxes inside out. Does anyone have any ideas why? Or can someone suggest a better (preferably simple) way to implement these constraints? Gratitude and good karma to anyone who can help. :o)

Share this post


Link to post
Share on other sites
MrRowl    2490
I never managed to get joint limits working well with the verlet-particle approach, so feel free to ignore my advice. However, one thing that you have to watch is that when you move particles in you shouldn't change the centre of gravity (either location or orientation, whatever that means :) of the particle system that you're dealing with. If you do then you introduce momentum into the system, and that's bad.

Also, I can't help feeling that if you start putting functions like asin in your constraint loop you're asking for performance problems...

If you want to constrain point3 so that it is (a) constant distance from point2 and (b) within a range of angles symmetric around the vector point2-point1, that's easy to do by using a minimum-distance constraint between point1 and point3, so long as your angle range is neither too close to 0 nor too close to 360deg.

If the angle range isn't symmetric around point2-point1 (e.g. for creating a hip joint) what _might_ work is to:

1. Create a new point, point4, that is in a position such that the vector point2-point4 bisects the range of allowable angles (so for a hip joint point4 would be up and behind the hip).

2. Attach point4 to point1 and point2 using stick constraints

3. Do the min-distance constraint thing between point4 and point3 to enforce the angle limits

4. Make another point5 and place it where it will bring the ragdoll's centre of gravity back into the correct place (up and in front of the hip), and attach it there with stick constraints to point1 and point2 and maybe point4

5. make point4 and point5 non-collidable

Well, that's the kind of stuff I did a while back in 3D and it gave me all sorts of trouble! Maybe it will work better in 2D....

I believe Steve Bromley, who posts here occasionally, got the verlet-particle method working pretty well in 3D.

I gave up on it.

Share this post


Link to post
Share on other sites
sbroumley    283
Hi ElectroDruid - I too (like many people!) started physics with the verlet paper you mentioned. Before you know it you'll have simple objects working including a ragdoll without rotation constraints. As you have found out - the paper quickly skips over the rotational constraints (I wonder why? [evil] ) which in my opinion are much less intuative than traditional rigid body constraints.

Now I've never tried to do a 2d ragdoll with verlet, but 3d is almost the same as 2d so you might as well just go for it.

For collision with the world just start off with a simple ground plane (ie. just keep the Y co-ordinate from going below zero if Y is your "up" axis). It will also help to code a simple "blast up" function (the paper describes how to do this) so that you can fling your ragdoll around.

The basic principal I used for min/max angle constraints was to use a min dist constraint for min angle (like the paper mentions), and projection onto a plane constraint for max angle. In 2d this would probably translate to a min dist constraint for min angle, and projection onto a line for max angle.

Now instead of projecting 1 particle onto a plane to fix a max angle constraint, compute the plane to be in the middle of 2 connected particles, then project both particles onto that plane (this maintains the center of gravity like MrRowl points out).

Once you understand how to create constraint planes from the particle rig in the paper, you can stop the knee from inverting and the torso from bending backwards etc (the arms are a little tricker). To get to that point you'll need to draw plenty of debugging information (I drew a small grid for each plane, and an arrow for the plane's normal) and do lot's of tweaking.

I found I needed 10 iterations of the human constraints to make the ragdoll be stable and not rubbery looking.

Once you have the basic wireframe ragdoll working you'll spend many hours tweaking the dam thing to get it looking good. You'll have to add friction, bouncyness, and velocity damping to make it look realistic. Be warned - this will suck up many hours of your life!

Another very tricky part that the paper skips over is how to derive the geometry bone matrices from the particle positions in order to display your final mesh character (even in "The Hitman" they didn't fully solve this and you can sometimes get the arms of ragdolls to twist completely around.)

Anyways, I got verlet ragdoll working for Area51 about half way through the dev cycle of the game (it was just published last month from Midway [cool]), but in the end I replaced it with an impulse based rigid body simlulator (with constraints between bodies) very similar to MrRowl's jiggle demo (BTW - Danny and Steve - I gave you both a special thanks credit in the game [smile]).

The big advantage of a true rigid body system is that constraints are much easier to represent and picture in your head. Ragdolls can also be created for any skeleton/creature type (in Area51 we have a boss with 4 arms - try doing that with verlet?!). Don't get me wrong, I believe verlet does have it's place for rope and cloth etc, but for rigid bodies and ragdolls, I'd advise learning a more traditional rigid body system - it will be a much faster, smoother road.

To conclude, here's the source for the 3d constraints I used to get verlet ragdoll working.

hope this helps,
-Steve.



// Adds debug plane for drawing in debug mode
void ragdoll::AddDebugPlane( const vector3& Point, const vector3& Normal, xcolor Color )
{
ASSERT(m_NDebugPlanes < MAX_DEBUG_PLANES) ;
debug_plane& Plane = m_DebugPlanes[m_NDebugPlanes++] ;

Plane.Point = Point ;
Plane.Normal = Normal ;
Plane.Color = Color ;
}

//==============================================================================

// Keeps ParticleA (connected to ParticleB) behind the plane
void ragdoll::KeepBehindPlane( const vector3& Point,
const vector3& Normal,
particle* pA,
particle* pB,
f32 Damp /*= 1.0f*/ )
{
ASSERT( pA );
ASSERT( pB );

// Setup plane
plane Plane ;
Plane.Setup(Point, Normal) ;

// Get distance from plane
f32 D = Plane.Distance(pA->m_Pos) ;
if (D > 0)
{
// Setup masses
f32 InvMass0 = pA->GetInvMass() ;
f32 InvMass1 = pB->GetInvMass() ;
f32 TotalInvMass = InvMass0 + InvMass1 ;
if( TotalInvMass == 0.0f )
return ;

// Move particles towards plane
D *= Damp / TotalInvMass ;
Plane.Normal *= D;
pA->m_Pos -= InvMass0 * Plane.Normal ;
pB->m_Pos += InvMass1 * Plane.Normal ;

#ifdef X_DEBUG
AddDebugPlane(Point, Normal, XCOLOR_YELLOW) ;
#endif
}
else
{
#ifdef X_DEBUG
AddDebugPlane(Point, Normal, XCOLOR_RED) ;
#endif
}
}

//==============================================================================

// Keeps ParticleA (connected to ParticleB) on the plane
void ragdoll::KeepOnPlane( const vector3& Point,
const vector3& Normal,
particle* pA,
particle* pB )
{
ASSERT( pA );
ASSERT( pB );

// Setup plane
plane Plane ;
Plane.Setup(Point, Normal) ;

// Setup masses
f32 InvMass0 = pA->GetInvMass() ;
f32 InvMass1 = pB->GetInvMass() ;
f32 TotalInvMass = InvMass0 + InvMass1 ;
if( TotalInvMass == 0.0f )
return;

// Move particles towards plane
f32 D = Plane.Distance(pA->m_Pos) ;
D *= 1.0f / TotalInvMass ;
Plane.Normal *= D;
pA->m_Pos -= InvMass0 * Plane.Normal ;
pB->m_Pos += InvMass1 * Plane.Normal ;

#ifdef X_DEBUG
AddDebugPlane(Point, Normal, XCOLOR_GREEN) ;
#endif
}

//==============================================================================

void ragdoll::ApplyHumanConstraints( void )
{
CONTEXT("ragdoll::ApplyHumanConstraints") ;

s32 i ;
vector3 Point, Normal, Out ;
vector3 Torso2Torso, Torso2Hip,
Hip2Torso, Hip2Hip, Hip2Knee,
Knee2Hip, Knee2Foot ;
vector3 Shoulder2Torso, Shoulder2Elbow, Shoulder2Shoulder ;

// Do both sides of body
for (i = 0 ; i < 2 ; i++)
{
// Useful info
f32 Side = (i == 0) ? 1.0f : -1.0f ;
joint_side& Joints = m_Joints.m_Side[i] ;
joint_side& OpJoints = m_Joints.m_Side[i^1] ;

// Keep shoulder behind of hip plane
Torso2Torso = OpJoints.m_pTorso->m_Pos - Joints.m_pTorso->m_Pos ;
Torso2Hip = Joints.m_pHip->m_Pos - Joints.m_pTorso->m_Pos ;
Point = (Joints.m_pShoulder->m_Pos + Joints.m_pTorso->m_Pos) * 0.5f ;
Normal = Torso2Torso.Cross(Torso2Hip) * Side ;
KeepBehindPlane(Point, Normal, Joints.m_pShoulder, Joints.m_pTorso, 1.0f) ;

// Stop knee from going too far out
Normal = Joints.m_pHip->m_Pos - OpJoints.m_pHip->m_Pos ;
Point = Joints.m_pHip->m_Pos + (2.0f * Normal) ;
KeepBehindPlane(Point, Normal, Joints.m_pKnee, Joints.m_pHip) ;

// Keep knee behind hip plane
Hip2Torso = Joints.m_pTorso->m_Pos - Joints.m_pHip->m_Pos ;
Torso2Torso = OpJoints.m_pTorso->m_Pos - Joints.m_pTorso->m_Pos ;
Normal = Hip2Torso.Cross(Torso2Torso) * Side ;
Point = (Normal * 0.01f) + ((Joints.m_pKnee->m_Pos + Joints.m_pHip->m_Pos) * 0.5f) ;
KeepBehindPlane(Point, Normal, Joints.m_pKnee, Joints.m_pHip, 1.0f) ;

// Keep foot behind thigh plane
Knee2Hip = Joints.m_pHip->m_Pos - Joints.m_pKnee->m_Pos ;
Knee2Foot = Joints.m_pFoot->m_Pos - Joints.m_pKnee->m_Pos ;
Hip2Hip = OpJoints.m_pHip->m_Pos - Joints.m_pHip->m_Pos ;
Point = (Joints.m_pFoot->m_Pos + Joints.m_pKnee->m_Pos) * 0.5f ;
Normal = Hip2Hip.Cross(Knee2Hip) * Side ;
KeepBehindPlane(Point, Normal, Joints.m_pFoot, Joints.m_pKnee, 1.0f) ;

// Compute leg plane normal
Hip2Hip = OpJoints.m_pHip->m_Pos - Joints.m_pHip->m_Pos ;
Hip2Knee = Joints.m_pKnee->m_Pos - Joints.m_pHip->m_Pos ;
Out = Hip2Hip.Cross(Hip2Knee) ;
Normal = Out.Cross(Hip2Knee) ;

// Keep foot on leg plane without causing any twist - works finally!!!
Point = (Joints.m_pFoot->m_Pos + Joints.m_pKnee->m_Pos) * 0.5f ;
KeepOnPlane(Point, Normal, Joints.m_pFoot, Joints.m_pKnee) ;

// Keep elbow behind side plane (stop it going into the head)
Normal = OpJoints.m_pShoulder->m_Pos - Joints.m_pShoulder->m_Pos ;
Point = (Joints.m_pElbow->m_Pos + Joints.m_pShoulder->m_Pos) * 0.5f ;
KeepBehindPlane(Point, Normal, Joints.m_pElbow, Joints.m_pShoulder, 1.0f) ;

// Keep elbow infront of torso
Shoulder2Shoulder = OpJoints.m_pShoulder->m_Pos - Joints.m_pShoulder->m_Pos ;
Shoulder2Torso = Joints.m_pTorso->m_Pos - Joints.m_pShoulder->m_Pos ;
Point = (Joints.m_pElbow->m_Pos + Joints.m_pShoulder->m_Pos) * 0.5f ;
Normal = Shoulder2Shoulder.Cross(Shoulder2Torso) * Side ;
Point -= Normal * 0.0001f ; // Keep slightly infront so geom arm rotation doesn't flip
KeepBehindPlane(Point, Normal, Joints.m_pElbow, Joints.m_pShoulder, 1.0f) ;

// Keep wrist behind upper arm plane
Shoulder2Torso = Joints.m_pTorso->m_Pos - Joints.m_pShoulder->m_Pos ;
Shoulder2Elbow = Joints.m_pElbow->m_Pos - Joints.m_pShoulder->m_Pos ;
Point = (Joints.m_pWrist->m_Pos + Joints.m_pElbow->m_Pos) * 0.5f ;
Normal = Shoulder2Torso.Cross(Shoulder2Elbow) * Side ;
KeepBehindPlane(Point, Normal, Joints.m_pWrist, Joints.m_pElbow) ;
}
}

Share this post


Link to post
Share on other sites
ElectroDruid    122
MrRowl,

Yes, I realise that testing the angle and using all those trig functions was pretty unneccessary, and I can just move the particles if the distance between point1 and point3 falls outside of minimum/maximum constraints. Basically if they do, I find a point directly between the two endpoints, and get a direction vector between them which I normalise, and scale up by the value represtenting the constraint. point1 is set to the midpoint + 0.5 * the scaled direction, and point3 is set to midpoint - 0.5 * scaledDirection. This works reasonably well for elbows and knees because it doesn't seem to add any momentum to the system. I don't know if moving the particles in this way somehow manages to retain the centre of gravity you were talking about, and if so how it does that. I don't have any explicit centres of gravity for limbs or the model as a whole, just the masses of the particles themselves.

Unfortunately (as you predicted), hip joints etc aren't so easy. The problem I'm having there seems to be not so much that the rotation constraints aren't symmetrical but that I'm having trouble establishing which points of the torso to move, in which direction to move them, and how far. I tried leaving the torso as is, and just moving the leg to be within the constraint, but that very much introduces momentum and the model starts flying around like crazy. I'm afraid I had some trouble visualising your solution of adding extra particles, particularly from a 2D point of view.

Steve,

I'm looking through your code now, and seeing if I can convert the planes to lines and do something similar in my own code. I've been doing 10 iterations on my model too, to get some degree of solidity.

Incidentally, could you elaborate more on what you mean by a traditional impulse based rigid body simlulator? Are there any papers you could point me towards (the simpler the better - I'm new to this physics programming malarkey)? Verlet definitely does nice stuff for rope and cloth, but if it's not the all-purpose saviour of the physics engine that Jakobsen claims it is then it might be worth my while looking elsewhere.

Share this post


Link to post
Share on other sites
sbroumley    283
By a more traditional rigid body system (the impulse method I mention is just one method - there are many others such as penalty, LCP etc) I mean representing the ragdoll as a collection of rigid bodies connected with constraints. Just google and you'll find lot's of info. A good start is Chris Hecker's or David Baraff's papers.

There are also plenty of references to links in this forum. Try this for starters:
http://www.gamedev.net/community/forums/topic.asp?topic_id=324255


hope that helps,
-Steve.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this