Ragdolls in FTE - How to
Introduction
In thies brief post, I put all I know and learned (mostly by trial and error processes, and also with a great help by Spike)about using ODE physics engine in FTEQW game engine to produce ragdoll animations. I consider myself a self-taught developer, so, since my explanations could be not so "professional" and missing some important parts, please consider to step in and fill the gap with your comments.
To be immediately clear, just be sure to understand that an entity A, (with a skeletal model linked, a .skeletonindex, a skel_ragupdate call) and an ODE ragdoll entity,
are two different entities. You could move the entity A from [0,0,0] to [256,0,0] and then call the ragdoll call (we'll see later how to do it) but the entity A is not the one you'll see falling on the ground. It's like FTE erases old entity and replace it with an ODE entity. Infact it's not possible to retrieve it unless you launch a skel_ragupdate("cleardoll") call. I always thought if it was possible to pick a ragdoll character with mouse (via project() function) but it seems that it's not possible, at least not for me.
Pure CSQC
All the examples posted on Github are pure CSQC based. What does it mean? That every line of code happens only on client side, server side is not even considered. In fact FTE permits to launch a game only with csprogs.dat, excluding all the server part:of course this will create single player games only. I use this setup because for me it's cleaner, I can avoid to send continously data from SSQC to CSQC, which is boring and prone to errors, and, most of all, in this case make things extrordinary easier. ODE, infact, doesn't work well in SSQC. When I was creating projectUnknown in 2015, I had to create a ragdoll entity in CSQC everytime a player was killed because, in SSQC, when a player is fragged and the first ragdoll of the game is dropped at, for example, [240,125,45], every other ragdolls, no matter where players get fragged, were spawned precisely at [240,125,45]! So, for this example, will use pure client side code.
Skeleton hierarchy
I will never stop to say it: if you want to make your skeletal code work ok (rotating specific bone,dividing torso / leg animations, ragdolls, etc) you need to
export from Blender skeleton hierarchy exactly as the 3d application does. Unfortunately, IQM exporter for Blender 2.79 by Lee Salzman seems not to import bones order file and bones are exported in a different order, so I made a fix to write the bone order directly in the Blender IQM exporter window.
The fix is linked here.
After exported your own skeletal model from Blender, launch FTE Ragdoll and press Tab key to display bone order on the left. Keep in mind that, I don't know why, FTE adds as bone #0 an empty bone name, so if you have 12 bones, it displays 13.
Doll file
You have exported your skeletal model, with all the animations, now you can create a .doll file that ODE will read and uses as ragdoll file.
The doll files has 3 sections: the preamble for default body/ default joint, the body part and the joint part. For bodies we refer to the exact names of every single bone in the skeletal file, in the exact order as IQM exporter did. Joints are virtual links between 2 or more bodies, I used the "j" prefix to make it clear that we're talking about joints. For example the jhips_r joint links hips joint and thigh_r together.
For the default body / joint I ask Spike's help for clarification. This preamble refers to a default root bone (not the one you created in Blender!) and, after many tests, I learned that it cannot be skipped.
##UPDATE: it seems that this part refers to all bones and to all joints, so, if you want to set erp,cfm,lostop,histop,etc, for all joints just once, set them in updatejoint default
- cfm makes constraints hard(0) or soft(1). Range:cfm 0.0001 cfm2 0.001
- erp is related to error in joints collision, so setting too high will make joints move too fast like springs. Range:erp 0.1 erp2 0.8 (usually erp 0.3 erp2 0.35)
- lostop histop is how joint can bend. I have set lostop -0.08 histop 0.08 lostop2 -0.1 histop2 0.1
- offset (Spike's definition):the pivot point relative to the parent bone, which can be used on the default joint.
Code: Select all
updatebody default
shape box
draw 0
animate 0
mass 1
updatejoint default
type hinge
draw 0
offset 0 0 0
//low hi stop
lostop -0.1
histop 0.1
//axis 0
erp 0.2
cfm 0.001
//low hi stop 2
lostop2 -0.5
histop2 0.5
//axis2
erp2 0.2
cfm2 0.001
The body part is more clear, it asks for every bone which type of collider shape, physical properties you want to set. In this case, indentation, is very useful to distinguish between a body and another without cluttering the file using too much comments. This file is barely minimal and it displays only the bone and its 2 basic properties. The syntax is body bonename bonealias. I preferred to choose for aliases same bone names to avoid confusion, but you could also do body root broot. I used shape box because my model is made of boxes. Of course you could also use body root broot or whatever alias you prefer. But remember that bone alias is the one used by joints, so paying attention to naming them correctly!
Code: Select all
//bodies
body root root
shape box
mass 2
body hips hips
shape box
mass 2
body thigh_r thigh_r
shape box
mass 2
body leg_r leg_r
shape box
mass 2
body foot_r foot_r
shape box
mass 2
body thigh_l thigh_l
shape box
mass 2
body leg_l leg_l
shape box
mass 2
body foot_l foot_l
shape box
mass 2
body spine1 spine1
shape box
mass 2
body spine2 spine2
shape box
mass 2
body armupper_r armupper_r
shape box
mass 2
body armlower_r armlower_r
shape box
mass 2
body hand_r hand_r
shape box
mass 2
body armupper_l armupper_l
shape box
mass 2
body armlower_l armlower_l
shape box
mass 2
body hand_l hand_l
shape box
mass 2
body head head
shape box
mass 2
Joints are links between a body and the next one in the model skel order. I only put type of joint, in this case, universal for every joint, just because it's a simple example project. You could also set type to fixed, point, hinge, slider, universal or hinge2. Every type permits different rotation on different axis (for example, hinge permits rotation on just one axis, like hinges on doors). It's possible to add additional informations like lostop, histop, erp, cfm, etc. that should limit orientation of the joint but, to be sure, take a look at ODE official manual.
Code: Select all
//joints
joint jrootbottom root hips
type universal
joint jhips_r hips thigh_r
type universal
joint jhips_l hips thigh_l
type universal
joint jthigh_r thigh_r leg_r
type universal
joint jleg_r leg_r foot_r
type universal
joint jthigh_l thigh_l leg_l
type universal
joint jleg_l leg_l foot_l
type universal
joint jroottop root spine1
type universal
joint jspine1 spine1 spine2
type universal
joint jclav_r spine2 armupper_r
type universal
joint jarm_r armupper_r armlower_r
type universal
joint jhand_r armlower_r hand_r
type universal
joint jclavl_r spine2 armupper_l
type universal
joint jarm_l armupper_l armlower_l
type universal
joint jhand_l armlower_l hand_l
type universal
joint jspine2 spine2 head
type universal
Call ragdoll from code
Simpy put, an entity with skeletal model and .doll file needs few calls to enable ragdoll animations. In CSQC_UpdateView(), after clearscene and before renderscene
Code: Select all
skel_build(myent.skeletonindex,myent,myent.modelindex,0,0,0);
skel_ragupdate(myent,"",myent.skeletonindex);
or
Code: Select all
skel_build(myent.skeletonindex,myent,myent.modelindex,0,0,0);
skel_ragupdate(myent,"doll pathtomodel/mymodel.iqm.doll",myent.skeletonindex);
Now you could say:"ok, but, if I want to activate ragdoll at certain timeframe, for example at the end of the die animation?". You can do it.
This function is in the project
Code: Select all
void Ragdoll_Execute(entity e, string framename, float speed)
{
e.frame = frameforname(e.modelindex, framename);
local float duratdie = frameduration(e.modelindex,e.frame);
if(!e.ragdollActive && e.ragdollOnGround){
if(e.frame1time < duratdie){
e.frame1time += frametime * speed;
}
else{
e.ragdollActive = TRUE;
}
}
if(e.ragdollActive){
skel_build(e.skeletonindex, e, e.modelindex,0,0,0);
skel_ragupdate(e,"animate 1",e.skeletonindex);
skel_ragupdate(e,strcat("doll ",e.model,".doll"),e.skeletonindex);
}
else{
skel_build(e.skeletonindex, e, e.modelindex,0,0,0);
skel_ragupdate(e,"animate 0",e.skeletonindex);
}
}
Now if entity is myent.ragdollActive = TRUE and myent.ragdollOnGround (just a flag I used to be sure ent is on the ground but you could also disable it), at the end of the die animation, ragdoll will animation code will be executed.
Bugs
- Dismemberment. Calling skel_build(e.skeletonindex, e, e.modelindex,0,0,0); skel_ragupdate(e,"enablejoint jhead 0",e.skeletonindex); to simulate an head shot, will make FTE crash
- Setting animate 0 or 1 inside .doll file in body section it doesn't make any difference. It's not taking in consideration. using skel_ragupdate(myent,"animatebody mybone whatever",myent.skeletonindex)will make FTE crash
- Using skelbuild with a different range of bones (for example from hips to legs) before skel_ragupdate will make FTE crash. So, for example, as far as I know, rope physics is impossible because you cannot exclude root bone from ragdoll
I post here some words by Spike on the argument, you can find them
here:
There's a few noteworthy things in the code above.
Setting the animate value to 1 means the doll will begin animating the doll as normal. This will give some sort of inverse kinematics (BUG: the current version forces positions rather than updating velocities, meaning the bodies will just spaz out as the joints break).
Setting the animate value to 0 means that the doll will ignore all animation values, thus the current info in the skeletal object is ignored and completely overwritten, thus removing the need to call skel_build. You can still call it, it just won't do anything useful.
skel_ragupdate will change the skeletal object to an absolute-skeletal-object (the bones are specified in modelspace rather than relative to their parents).
skel_build can only update relative-skeletal-objects, and will throw an error if given an absolute skeleton. If retainfrac=0 then it'll just silently update the skeletal object to relative as all data will be overwritten anyway.
The third argument to skel_ragupdate can be used as an animation source. This allows you to retain a set of animation data without destroying it. Specifically, if this is set to 0, the bones without bodies will snap back to their base pose, which can be rather ugly when it dies if the preliminary death animations do not revert them first. This animation source argument must be a relative-skeletal-object (ie: filled in with skel_build).
For portability/sanity reasons, don't call skel_get_bonerel+skel_set_bone+skel_mul_bone if ragupdate was called more recently than build.
Personally none of these tips work for me