Hy guys, just a simple project I had on my virtual shelf for too long. I just had 2 days of free time and I uploaded it on GitHub, MIT license for code, CC0 for assets (mainly junk). With this setup you could now test ragdolls in FTEQW and take a look at assets source and source code to use it in your projects. I dunno if I'll ever have time to fix the problems but this is my opinion: ragdolls in FTE are quite poor/buggy. If you want just to have fun, go get it, otherwise, if your game DEPENDS on ragdoll physics, just use Unity or Unreal: you won't go too far with FTE.
Controls:
Right mouse to toggle cursor placement on/off. Left click to spawn ragdoll at gizmo location
Space key to swap between scenes and OK to launch chosen scene
Tab to display bone names
A key to toggle animation-driven ragdolls (ragdoll animation will start at the end of animation)
Q key to quit
Known bugs:
1) Dismemberment seems not to work. It doesn't allow to choose a different bone in the hierarchy
2) Scene collider MUST be set to SOLID_BSP which doesn't work well with traceline MOVE_HITMODEL so scene has been split in planes and colliders.
3) After 10 ragdolls spawned, system performance are too low
4) Animation driven ragdolls doesn't copy last frame position/rotation, giving unrealistic animations
5) ODE seems to work only on Windows
Important files:
src folder - where all the ragdolls logic takes places, especially ragdoll.c
models/ragtest2.blend 3d model with animations (Blender file)
models/ragtest2.iqm.doll doll file used by engine to read 3d model bones and perform ragdoll animations. Hopefully my comments will help you
Note that, to use 100% of FTE skeletal features, you need to install my IQM Exporter fix, otherwise bones hierarchy will be exported not as in Blender. Blender 2.79 only
[FTEQW][WIP]FTE Ragdoll
-
- Posts: 1395
- Joined: Tue Feb 24, 2009 4:39 pm
- Location: Italy
[FTEQW][WIP]FTE Ragdoll
Meadow Fun!! - my first commercial game, made with FTEQW game engine
-
- Posts: 1395
- Joined: Tue Feb 24, 2009 4:39 pm
- Location: Italy
Re: [FTEQW][WIP]FTE Ragdoll
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.
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!
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.
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
or
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
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
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
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
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
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);
Code: Select all
skel_build(myent.skeletonindex,myent,myent.modelindex,0,0,0);
skel_ragupdate(myent,"doll pathtomodel/mymodel.iqm.doll",myent.skeletonindex);
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);
}
}
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
Personally none of these tips work for meThere'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.
Meadow Fun!! - my first commercial game, made with FTEQW game engine
Re: [FTEQW][WIP]FTE Ragdoll
if you want to play animations on a model, using ragdoll physics on some of its bodies:
set the individual body properties with animate 0.
note that the above uses TWO skeletal objects.
the animskel contains the source animation data, while the ragdoll skeletal object contains the resulting bone positions. pass 0 if you're fine with using the identity pose, but do NOT pass e.skeletonindex because that will write to the same matricies that its simultaneously trying to read, which is BAD.
`enablejoint jointthatdoesnotexistlikeegaboneorbodyname bool` yes this will read an array at offset -1, which is baaaad. sorry. its not a problem with correct qc code though (assuming there's a .doll that properly matches the code, anyway).
`animatebody badbody frac` will silently ignore bad body names too, but at least won't crash. it will crash if you don't have a ragdoll set up though - check the return result of the ragupdate instanciation to make sure it has an active doll to avoid crashes.
`animate frac` is a multiplier. if you have `animate 0` in one of your bodies, then the global animate multiplier will do nothing. not sure if that's the issue you were having.
5515 should be a little less crashy, hopefully
set the individual body properties with animate 0.
Code: Select all
if (!e.animskel) e.animskel = skel_create(e.modelindex); //for lerped animations.
if (!e.skeletonindex) e.skeletonindex = skel_create(e.modelindex); //for ragdoll (and the one that will actually be visible).
skel_build(e.animskel, e, e.modelindex, 0, 1, 0,0); //read animation data from the model into the animation sko
skel_ragupdate(e, "", e.animskel); //updates e.skeletonindex, copying any animated (ie: non-ragdoll) bodies from e.animskel, using strcat(e.model,".doll") if no specific doll file was attached to the sko.
the animskel contains the source animation data, while the ragdoll skeletal object contains the resulting bone positions. pass 0 if you're fine with using the identity pose, but do NOT pass e.skeletonindex because that will write to the same matricies that its simultaneously trying to read, which is BAD.
`enablejoint jointthatdoesnotexistlikeegaboneorbodyname bool` yes this will read an array at offset -1, which is baaaad. sorry. its not a problem with correct qc code though (assuming there's a .doll that properly matches the code, anyway).
`animatebody badbody frac` will silently ignore bad body names too, but at least won't crash. it will crash if you don't have a ragdoll set up though - check the return result of the ragupdate instanciation to make sure it has an active doll to avoid crashes.
`animate frac` is a multiplier. if you have `animate 0` in one of your bodies, then the global animate multiplier will do nothing. not sure if that's the issue you were having.
5515 should be a little less crashy, hopefully
-
- Posts: 1395
- Joined: Tue Feb 24, 2009 4:39 pm
- Location: Italy
Re: [FTEQW][WIP]FTE Ragdoll
thanks Spike to have come to my rescue
The code you posted makes fte crashes even on v5515. if skel_build is not set like skel_build(e.skeletonindex, e, e.modelindex,0,0,0,1);
are you sure the code is correct? Why skel_create and skel_ragupdate are in the same place? Doesn't skel_create just called once and skel_ragupdate called in the CSQC_UpdateView function?
Were you intending to put all this code in CSQC_UpdateView? Is it not a waste of computational process re-set every time skel_create?
So, if I understood correctly .animskel is for skeletal frame animations (and transforms like skel_mul*)and .skeletonindex is for skeletal ragdoll animations, right?
But why then, it keeps saying
Ragdoll_Create(called once when e is created) I used a define so things are not melted together
Ragdoll_Execute(called by Ragdoll_Update in CSQC_UpdateView)
Now ragdoll work but animations not
Anyway code is online for those who want to test it, I personally don't have time, I guess I'll stick to normal animations
Plus v5514 again makes the "inclination bug", even if these cvars
are set. Unfortunately, I'm using v5432 and this bug is fixed but in v5514 view is not straight
The code you posted makes fte crashes even on v5515. if skel_build is not set like skel_build(e.skeletonindex, e, e.modelindex,0,0,0,1);
are you sure the code is correct? Why skel_create and skel_ragupdate are in the same place? Doesn't skel_create just called once and skel_ragupdate called in the CSQC_UpdateView function?
Were you intending to put all this code in CSQC_UpdateView? Is it not a waste of computational process re-set every time skel_create?
So, if I understood correctly .animskel is for skeletal frame animations (and transforms like skel_mul*)and .skeletonindex is for skeletal ragdoll animations, right?
But why then, it keeps saying
Code: Select all
PF_skel_ragedit: cannot use the same skeleton for animation source
Code: Select all
void Ragdoll_Create(entity e)
{
#ifdef SPIKE_RAGDOLL
if (!e.animSkelSlot) e.animSkelSlot = skel_create(e.modelindex); //for lerped animations.
if (!e.skeletonindex) e.skeletonindex = skel_create(e.modelindex); //for ragdoll (and the one that will actually be visible).
skel_build(e.animSkelSlot, e, e.modelindex, 0, 1, 0,0); //read animation data from the model into the animation sko
#else
if (!e.skeletonindex){
e.skeletonindex = skel_create(e.modelindex);
skel_build(e.skeletonindex, e, e.modelindex,0,0,0);
}
#endif
}
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){
#ifdef SPIKE_RAGDOLL
//updates e.skeletonindex, copying any animated (ie: non-ragdoll) bodies from e.animskel,
//using strcat(e.model,".doll") if no specific doll file was attached to the sko.
skel_build(e.skeletonindex, e, e.modelindex,0,0,0,1);
skel_ragupdate(e,"animate 0",e.skeletonindex);
skel_ragupdate(e,strcat("doll ",e.model,".doll"),0);
#else
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);
#endif
}
else{
#ifdef SPIKE_RAGDOLL
skel_build(e.animSkelSlot, e, e.modelindex,0,0,0);
skel_ragupdate(e,"animate 0",e.animSkelSlot);
#else
skel_build(e.skeletonindex, e, e.modelindex,0,0,0);
skel_ragupdate(e,"animate 0",e.skeletonindex);
#endif
}
}
No, I used enablejoint + a joint that actually is written in .doll file and it keeps crashing`enablejoint jointthatdoesnotexistlikeegaboneorbodyname bool` yes this will read an array at offset -1, which is baaaad. sorry. its not a problem with correct qc code though (assuming there's a .doll that properly matches the code, anyway).
I'll try that`animate frac` is a multiplier. if you have `animate 0` in one of your bodies, then the global animate multiplier will do nothing. not sure if that's the issue you were having.
Anyway code is online for those who want to test it, I personally don't have time, I guess I'll stick to normal animations
Plus v5514 again makes the "inclination bug", even if these cvars
Code: Select all
cl_threadedphysics 0
cl_loopbackprotocol "dpp7"
Meadow Fun!! - my first commercial game, made with FTEQW game engine
Re: [FTEQW][WIP]FTE Ragdoll
That is amazing
Congrats!
Congrats!
-
- Posts: 1395
- Joined: Tue Feb 24, 2009 4:39 pm
- Location: Italy
Re: [FTEQW][WIP]FTE Ragdoll
Thanks JasonX, have you tried It? I'd really like to hear community comments about It.
Unfortunately ODE remains a lot buggy..
Unfortunately ODE remains a lot buggy..
Meadow Fun!! - my first commercial game, made with FTEQW game engine
-
- Posts: 1395
- Joined: Tue Feb 24, 2009 4:39 pm
- Location: Italy
Re: [FTEQW][WIP]FTE Ragdoll
Thanks a lot Spike, v5524 fixed completely the "view inclination" bug and now engine doesn't even need
cl_threadedphysics and cl_loopbackprotocol cvars to be set to make it work! Great!
Thanks for all the efforts you put in FTE
cl_threadedphysics and cl_loopbackprotocol cvars to be set to make it work! Great!
Thanks for all the efforts you put in FTE
Meadow Fun!! - my first commercial game, made with FTEQW game engine