[FTEQW][WIP]FTE Ragdoll

The home for dedicated threads to specific projects, be they mods, tools, or independent games.
Post Reply
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

[FTEQW][WIP]FTE Ragdoll

Post by toneddu2000 »

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.

Image

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
Meadow Fun!! - my first commercial game, made with FTEQW game engine
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: [FTEQW][WIP]FTE Ragdoll

Post by toneddu2000 »

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
  1. 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
  2. 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
  3. 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
Meadow Fun!! - my first commercial game, made with FTEQW game engine
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: [FTEQW][WIP]FTE Ragdoll

Post by Spike »

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.

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.
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
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: [FTEQW][WIP]FTE Ragdoll

Post by toneddu2000 »

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);
Image

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
Ragdoll_Create(called once when e is created) I used a define so things are not melted together

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
}
Ragdoll_Execute(called by Ragdoll_Update in CSQC_UpdateView)

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
	}
}
Now ragdoll work but animations not
`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).
No, I used enablejoint + a joint that actually is written in .doll file and it keeps crashing
`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.
I'll try that

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"
are set. Unfortunately, I'm using v5432 and this bug is fixed but in v5514 view is not straight
Meadow Fun!! - my first commercial game, made with FTEQW game engine
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Re: [FTEQW][WIP]FTE Ragdoll

Post by JasonX »

That is amazing :shock:

Congrats!
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: [FTEQW][WIP]FTE Ragdoll

Post by toneddu2000 »

Thanks JasonX, have you tried It? I'd really like to hear community comments about It.
Unfortunately ODE remains a lot buggy..
Meadow Fun!! - my first commercial game, made with FTEQW game engine
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: [FTEQW][WIP]FTE Ragdoll

Post by toneddu2000 »

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
Meadow Fun!! - my first commercial game, made with FTEQW game engine
Post Reply