Page 1 of 2

FTE ragdoll system

Posted: Fri Jan 17, 2014 10:08 am
by toneddu2000
Hi guys, I've started the read of csqc_for_idiots.txt by Spike (I suggest everyone to read it) and, surprisingly, at the end there's a mini chapter:
Ragdoll (FTE_QC_RAGDOLL / FTE_QC_RAGDOLL_WIP):
Ragdoll support is dependant upon ODE, which requires cvar("physics_ode_enable") to be set in order to work properly.
Once you have a skeletal object, you can add in ragdoll by calling skel_ragupdate(self, "doll foo.doll", 0); at the end of your predraw function (which will cause the ragdoll to update and move based upon an animated model, and then call skel_ragupdate(self, "animate 0", 0); once your entity dies and goes limp (typically, this is done part-way through the death animation, so the ragdoll inherits a certain amount of velocity from said animation).
The .doll file describes the bodies and joints within the model. If a body is set to animate, it will use the bone data your code specifies. If a body is not set to animate, it will be ragdolled even when the player is still alive. This can be used for things like ponytails, cloth, etc.
But in the entire FTE engine I can't find the function skel_ragupdate nor any clue related to ragdoll. Is it just a stub? It would be great to know a little more about it!

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 12:05 pm
by Spike
random unedited code splurge.

csqc defs/builtins:
http://triptohell.info/moodles/fteqcc/fteextensions.qc

csqc code. make some console command to invoke test2 or something.

Code: Select all

//keep the ragdoll object updated.
static void() rag_predraw =
{
#if 1
	if (!self.animobject)
	{
		self.animobject = skel_create(self.modelindex);
		skel_build(self.animobject, self, self.modelindex, 0, 0, 0, 1);
	}
#else
	skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
#endif
	skel_ragupdate(self, "", self.animobject);
};

//spawn a ragdoll
void() test2 =
{
	local entity e;
	e = spawn();
	e.movetype = MOVETYPE_NONE;
	e.solid = SOLID_NOT;
	e.owner = world;
	e.angles = [0, 0, 0];
	e.angles_x *= -1;
	e.mass = 10;
	e.drawmask = MASK_NORMAL;
	e.predraw = rag_predraw;
	e.alpha = 0.9;

	setmodel(e, "models/player/erebus.iqm");
	setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
	setorigin(e, vieworg);

	e.skeletonindex = skel_create(e.modelindex);
	skel_build(e.skeletonindex, e, e.modelindex, 0, 0, 0, 1);
	skel_ragupdate(e, "doll test.doll", e.animobject);
	skel_ragupdate(e, "animate 0", e.animobject);
};
models/player/erebus.iqm is from xonotic. extract it yourself.

models/player/erebus.doll

Code: Select all

//see skeletal.txt (http://fteqw.svn.sourceforge.net/viewvc/fteqw/trunk/specs/skeletal.txt) for info on this file format.

updatejoint default
	type hinge
	draw 0
	offset 0 0 0

	lostop -0.1
	histop 0.1
	axis  0 
	erp  0.2
	cfm  0.001

	lostop2 -0.5
	histop2 0.5
	axis2 
	erp2 0.2
	cfm2 0.001
updatebody default
	shape box
	draw 0
	animate 1
	mass 1



//main body... ish.
body torso		master
	shape sphere
	mass 2

//glue the torso in place.
//joint glue torso
//	type hinge

body head	"head"
	shape sphere
joint head	head  torso
	pivot "neck"
	type universal
	lostop -0.5
	histop 0.5
	lostop2 -2
	histop2 2

//joint glue head
//	type point


//let the legs bend backwards a little
updatejoint default
	lostop -2
	histop 0.1

//left leg
body uleg.l		upperleg_L
//	orient
body lleg.l		lowerleg_L
//	orient
body foot.l		foot_L
//	orient
	size 2

//right leg
body uleg.r		upperleg_R
//	orient
body lleg.r		lowerleg_R
//	orient
body foot.r		foot_R
//	orient
	size 2

joint hip.l torso uleg.l
	type universal
	lostop -1
	histop 1
joint knee.l uleg.l lleg.l
joint ankle.l lleg.l foot.l
	lostop -0.5
	histop 0.5

joint hip.r torso uleg.r
	type universal
	lostop -1
	histop 1
joint knee.r uleg.r lleg.r
joint ankle.r lleg.r foot.r
	lostop -0.5
	histop 0.5


//arms tend to bend forwards...
updatejoint default
	type hinge
	lostop -0.5
	histop 2

//left arm
body uarm.l		upperarm_L
	animate 0
//	orient
body larm.l		forearm_L
	animate 0
//	orient
body hand.l		hand_L
	animate 0
//	orient

//right arm
body uarm.r		upperarm_R
	animate 0
//	orient torso.r
body larm.r		forearm_R
	animate 0
//	orient urarm.r
body hand.r		hand_R
	animate 0
//	orient larm.r

joint shoulder.l torso uarm.l
	type point
	lostop2 -1.6
	histop2 0.1
joint elbow.l uarm.l larm.l
	type point
	lostop -1
	histop 1
	draw 1
joint wrist.l larm.l hand.l
	type point
	lostop -1
	histop 1
//	pivot BONE -32 0 0

joint shoulder.r torso  uarm.r
	type point
	lostop2 -0.1
	histop2 1.6
joint elbow.r uarm.r larm.r
	type point
joint wrist.r larm.r hand.r
	type point
//	pivot BONE 32 0 0

body toe.r toe_R
body toe.l toe_L
updatejoint default
	type hinge
	lostop -0.2
	histop 0.1
	lostop2 -0.2
	histop2 0.1
joint hackfix.l toe.l foot.l
joint hackfix.r toe.r foot.r
if done properly, your console command should make an entity spawn and ragdoll down to the floor.

basically, create a skeletal object, tells it to use a doll file. keep poking your skeletal object each frame to keep it updated (it will will do physics if not poked, but won't update the bone info).
note that the code actually uses two skeletal objects. one contains animation data, the other contains physics+final data. the data in the animation data can be used to tell the 'animated' bodies where to move to (so it can kick things etc), and for the bones without bodies to have some position that looks right. If you don't use a second skeletal object, it'll just use the base pose or something for bones-without-bodies.

it probably doesn't work too well with vid_restart. you should probably spam skel_ragupdate with the doll command rather than the empty command. this ensures the entity resets if the doll became invalid due to being flushed for whatever reason. on the plus side, it does make editing the doll file easier.

its still a Work In Progress extension. I documented it in that document for two reasons:
1) gives people something fun to aim for.
2) needs a bit more use+examples+feedback+etc before it can be more than wip.
its been a while since I touched it...
feel free to suggest lots of fixes/tweaks to make it more robust/versatile/easier.

Sidenote: you can get the same thing working via ssqc by providing a model.iqm.doll file and setting self.frame|=65535. the flag-in-frame thing sets all the dolls 'animate' settings to 0, causing it to go limp and collapse. set that half way through your death animation and it should inherit momentum from the animation. that's the theory anyway.
its still worth using csqc for this stuff, so you can do animation blending properly as per the previous chapter in csqc-for-idiots for when the entity is still alive.

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 12:12 pm
by Spike
here's some more code that might be useful.
it just dumps the bone heirachy, frames, skins of a model.
the output might be handy for determining which bones you need to attach your various bodies to, if you don't have the model in an editor or whatever. I dunno.

you can use frameforname to get a frame number for a frame name on a given model(index). hurrah for animation code not caring if the frames get randomly reordered.

Code: Select all

static void(float skel, float numbones, float start, float depth) boneheirachy =
{
	float i, parent;
	//don't print anything for bone 0, because its not real and always exists anyway.
	if (start)
	{
		for (i = 0; i < depth; i++)
			print("  ");
		print(sprintf("%s  (%g)\n", skel_get_bonename(skel, start), start));
	}
	depth+=1;

	for (i = 1; i <= numbones; i++)
	{
		parent = skel_get_boneparent(skel, i);
		if (parent == start)
		{
			boneheirachy(skel, numbones, i, depth);
		}
	}
};

//print out lots of info about a model
void(string fname) showmodelinfo =
{
	local float modelidx;
	float i;
	float numbones;
	float skel;
	string n;
	entity e;
	precache_model(fname);
	e = spawn();
	setmodel(e, fname);
	modelidx = e.modelindex;
	remove(e);
	skel = skel_create(modelidx);
	numbones = skel_get_numbones(skel);
	if (!modelidx)
	{
		print("no model\n");
		return;
	}

	print(sprintf("bone info for %s:\n", modelnameforindex(modelidx)));
	boneheirachy(skel, numbones, 0, 0);
	skel_delete(skel);

	print(sprintf("frame(group) info for %s:\n", modelnameforindex(modelidx)));
	for (i = 0; ; i+=1)
	{
		//DP - frametoname does not exist.
		n = frametoname(modelidx, i);
		if not (n)
			break;
		print(sprintf("framegroup %g: %s  %g secs\n", i, n, frameduration(modelidx, i)));
	}

	print(sprintf("skin info for %s:\n", modelnameforindex(modelidx)));
	for (i = 0; ; i+=1)
	{
		n = skintoname(modelidx, i);
		if not (n)
			break;
		print(sprintf("skin %g: %s\n", i, n));
	}
};

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 12:18 pm
by toneddu2000
Wow Spike thanks a lot I'll try imediately!

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 5:36 pm
by toneddu2000
Nope. The model remains at middle air.
I tried PHYSICS_ODE_ENABLE 1
i changed the line MOVETYPE_NONE TO MOVETYPE_PHYSICS
i recreated the doll file with SKEL_GENERATERAGDOLL (it creates the file erebus.iqm.doll)
i changed the doll to skel_ragupdate(e, "doll erebus.iqm.doll", e.animobject);
fteqcc yells that rag_predraw should return a float not a void but if a change to float the model disappears
I had to include some costants like MASK_NORMAL because are not present in fteextensions
I compiled ODE realese double dll and I put double_dll in the folder of fte exe (I use the experimental 32 and 64bit because the stable 32bit remains searching "connecting to internal server...")
I tried even with compiled ode_single.dll
Nothing, the model is hanged over there. There is some already-made code to use?
here my files
progs.src

Code: Select all

#pragma TARGET FTE
#pragma progs_dat "../csprogs.dat"
#define CSQC //for the following line
#include "fteextensions.qc" //include the various system fields, globals, constants, and builtins.
#include "ragdoll.qc"

void CSQC_UpdateView (float width, float height, float menushown) 
{
	clearscene(); 		// CSQC builtin to clear the scene of all entities / reset our view properties
	setproperty(VF_DRAWWORLD, 1); 		// we want to draw our world!
	setproperty(VF_DRAWCROSSHAIR, 1);		 // we want to draw our crosshair!
	addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); 		// add entities with these rendermask field var's to our view
	renderscene(); 		// render that puppy to our screen, 
}

float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent =
{
   if (evtype == IE_KEYDOWN)
   {
      test2();
   }
   else
      return FALSE;//not recognised, let the engine do its thing.
};
ragdoll.qc

Code: Select all

.float animobject;
vector ragdoll_org;

const float MASK_ENGINE			= 1;
const float MASK_ENGINEVIEWMODELS	= 2;
const float MASK_NORMAL			= 4;
vector	VEC_HULL_MIN = '-16 -16 -24';
vector	VEC_HULL_MAX = '16 16 32';

//keep the ragdoll object updated.
static void() rag_predraw =
{
#if 1
   if (!self.animobject)
   {
      self.animobject = skel_create(self.modelindex);
      skel_build(self.animobject, self, self.modelindex, 0, 0, 0, 1);
   }
#else
   skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
#endif
   skel_ragupdate(self, "", self.animobject);
};

//spawn a ragdoll
void() test2 =
{
   ragdoll_org = [220,550,160];
   local entity e;
   e = spawn();
   e.movetype = MOVETYPE_PHYSICS;
   e.solid = SOLID_NOT;
   e.owner = world;
   e.angles = [0, 0, 0];
   e.angles_x *= -1;
   e.mass = 10;
   e.drawmask = MASK_NORMAL;
   e.predraw = rag_predraw;
   e.alpha = 0.9;

   setmodel(e, "models/player/erebus.iqm");
   setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
   setorigin(e, ragdoll_org);

   e.skeletonindex = skel_create(e.modelindex);
   skel_build(e.skeletonindex, e, e.modelindex, 0, 0, 0, 1);
   skel_ragupdate(e, "doll erebus.iqm.doll", e.animobject);
   skel_ragupdate(e, "animate 0", e.animobject);
};
Thanks again

PS: if I invoke SKEL_INFO it appears a list DOLL:0, DOLL:1,...why?

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 8:37 pm
by Spike
physics_ode_enable must be 1. the engine will force+lock it to 0 if the dll can't be loaded.
the 'doll' command must be given a full (quake) path. my lame example just used 'test.doll' in a crazy location.
the 'skel_info' command shows that you don't have the doll instanciated properly.

Re: FTE ragdoll system

Posted: Fri Jan 17, 2014 8:53 pm
by toneddu2000
physics_ode_enable must be 1. the engine will force+lock it to 0 if the dll can't be loaded.
There's at least a method to understand if the ode dlls are not correctly loaded? A console command for example?
because the dlls are there. I checked at the FTE engine src and I found that(in the World_Physics_Init() function):

Code: Select all

#ifdef ODE_DYNAMIC
	const char* dllname =
	{
# if defined(WIN64)
		"libode1_64.dll"
# elif defined(WIN32)
		"ode_double"
# elif defined(MACOSX)
		"libode.1.dylib"
# else
		"libode.so.1"
# endif
	};
#endif
I'm ignorant in programming, really, but the second string doesn't miss ".dll" suffix at the end?
And, by the way, I couldn't find an ode solution at 64bit. If I search the net for libode1_64.dl I can't find anything

Re: FTE ragdoll system

Posted: Sun Jan 19, 2014 12:42 am
by toneddu2000
Hurray!!Real Ragdolls on Quake!
Image
Too much tired tonight to make other tests because it's almost 26 hours that I try! :D
I post only temp code. Tomorrow I'll make a detailed guide because it's not so intuitive to setup the environment

You need:
Xonotic erebus model (you can find here the blend model)
fteqw src compiled from here (I couldn't compile on Microsoft Visual Studio 2008/2010 so I used Linux, let me know if someone is able to compile fteqw on visual studio)
fteextensions.qc
ragdoll.qc -just make attention to point to .doll file with FULL path (i noticed that, on Linux, it generates doll file in .fte/models/player/, on Windows it generates in users\X\Documents\My Games\FTE QuakeWorld\fte so i placed there models and .doll files)
I was wrong about the MOVETYPE_PHYSICS, it needs a normal MOVETYPE_STEP

Code: Select all

//keep the ragdoll object updated.
void() rag_predraw =
{
#if 1
   if (!self.animobject)
   {
      self.animobject = skel_create(self.modelindex);
      skel_build(self.animobject, self, self.modelindex, 0, 0, 0, 1);
   }
#else
   skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
#endif
   skel_ragupdate(self, "", self.animobject);
};

//spawn a ragdoll
void RagdollSpawn()
{
   local entity e;
   local float numbones;
   e = spawn();
   e.movetype = MOVETYPE_STEP;
   e.owner = world;
   e.angles = [0, 0, 0];
   e.angles_x *= -1;
   e.mass = 10;
   e.drawmask = MASK_NORMAL;
   e.predraw = rag_predraw;
   e.alpha = 0.9;

   setmodel(e, "models/player/erebus.iqm");
   setsize(e, VEC_HULL_MIN, VEC_HULL_MAX);
   setorigin(e, [465, 620, 180]);

   e.skeletonindex = skel_create(e.modelindex);
   skel_build(e.skeletonindex, e, e.modelindex, 0, 0, 0, 1);
   skel_ragupdate(e, "doll models/player/erebus.iqm.doll", e.modelindex);
   skel_ragupdate(e, "animate 0", e.modelindex);
   numbones = skel_get_numbones(e.skeletonindex);
   print("num bones ",ftos(numbones)," animobject ",ftos(e.modelindex),"\n"); //check if engine has correctly loaded bones from the model skeleton
}
main.qc (almost blank)

Code: Select all

void CSQC_Init(float apilevel, string enginename, float engineversion)
{
	RagdollSpawn ();
}

void CSQC_Ent_Update (float isnew)
{
	rag_predraw();
}

void CSQC_UpdateView (float width, float height, float menushown) 
{
	clearscene(); 
	setproperty(VF_DRAWWORLD, 1); 		// we want to draw our world!
	setproperty(VF_DRAWCROSSHAIR, 1);
	addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); 		// add entities with these rendermask field var's to our view
	renderscene(); 
}
VERY IMPORTANT: ragdoll .doll file (incorrect values make the model INACTIVE to ragdoll)

Code: Select all

updatejoint default
   type hinge
   draw 0
   offset 0 0 0

   lostop -0.1
   histop 0.1
   //axis  0 
   erp  0.2
   cfm  0.001

   lostop2 -0.5
   histop2 0.5
   //axis2 
   erp2 0.2
   cfm2 0.001
updatebody default
   shape box
   draw 0
   animate 0
   mass 1



//main body... ish.
body torso      master
   shape sphere
   mass 2

//glue the torso in place.
//joint glue torso
//   type hinge

body head   "head"
   shape sphere
joint head   head  torso
   pivot "neck"
   type universal
   lostop -0.5
   histop 0.5
   lostop2 -2
   histop2 2

//joint glue head
//   type point


//let the legs bend backwards a little
updatejoint default
   lostop -2
   histop 0.1

//left leg
body uleg.l      upperleg_L
//   orient
body lleg.l      lowerleg_L
//   orient
body foot.l      foot_L
//   orient
   size 2

//right leg
body uleg.r      upperleg_R
//   orient
body lleg.r      lowerleg_R
//   orient
body foot.r      foot_R
//   orient
   size 2

joint hip.l torso uleg.l
   type universal
   lostop -1
   histop 1
joint knee.l uleg.l lleg.l
joint ankle.l lleg.l foot.l
   lostop -0.5
   histop 0.5

joint hip.r torso uleg.r
   type universal
   lostop -1
   histop 1
joint knee.r uleg.r lleg.r
joint ankle.r lleg.r foot.r
   lostop -0.5
   histop 0.5


//arms tend to bend forwards...
updatejoint default
   type hinge
   lostop -0.5
   histop 2

//left arm
body uarm.l      upperarm_L
   animate 0
//   orient
body larm.l      forearm_L
   animate 0
//   orient
body hand.l      hand_L
   animate 0
//   orient

//right arm
body uarm.r      upperarm_R
   animate 0
//   orient torso.r
body larm.r      forearm_R
   animate 0
//   orient urarm.r
body hand.r      hand_R
   animate 0
//   orient larm.r

joint shoulder.l torso uarm.l
   type point
   lostop2 -1.6
   histop2 0.1
joint elbow.l uarm.l larm.l
   type point
   lostop -1
   histop 1
   draw 1
joint wrist.l larm.l hand.l
   type point
   lostop -1
   histop 1
//   pivot BONE -32 0 0

joint shoulder.r torso  uarm.r
   type point
   lostop2 -0.1
   histop2 1.6
joint elbow.r uarm.r larm.r
   type point
joint wrist.r larm.r hand.r
   type point
//   pivot BONE 32 0 0

body toe.r toe_R
body toe.l toe_L
updatejoint default
   type hinge
   lostop -0.2
   histop 0.1
   lostop2 -0.2
   histop2 0.1
joint hackfix.l toe.l foot.l
joint hackfix.r toe.r foot.r
progs.src - usual stuff

Code: Select all

#pragma TARGET FTE
#pragma progs_dat "../csprogs.dat"
#define CSQC
#include "fteextensions.qc"
#include "ragdoll.qc"
#include "main.qc"

With these three files you should be able to see your model fall like a.. uhm .. like a rag doll! :D


PS:Note for Spike. I'm still confused with the implementation of the ragdoll in the fteqw engine. Because I searched for skel_ragupdate and it's present in pr_cmds.c and pr_csqc.c files inside the struct BuiltinList which corresponds to function PF_skel_ragedit() but this function is not defined anywhere!This is the only definition I found:

Code: Select all

void QCBUILTIN PF_skel_ragedit(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
How is it possible? Thanks again for your kindness

Re: FTE ragdoll system

Posted: Sun Jan 19, 2014 3:17 am
by Spike
its in pr_skelobj.c

I'm glad you got it working. :)
it helps prove that I'm not making stuff up on a whim. :D

Re: FTE ragdoll system

Posted: Sun Jan 19, 2014 4:03 am
by ceriux
a video would show more than a screenshot when it comes to screenshots, imo... sorry to not be able to contribute more than that.

Re: FTE ragdoll system

Posted: Sun Jan 19, 2014 9:47 pm
by toneddu2000
ceriux wrote:a video would show more than a screenshot
Here. :)
This is the first video of a (I hope) long series. I would like to implement ragdolls in csqc in a complete manner. In this video simply it's raining dolls!
Spike wrote:its in pr_skelobj.c
Thanks Spike, I still don't get it how the builtin functions work but I'll learn!
Spike wrote:I'm glad you got it working. :)
it helps prove that I'm not making stuff up on a whim. :D
:lol: No, I never thought it! You're a genius!
Thanks again for your help!

PS: Can you please explain which are the devs of FTEQW,beyond you?
And, what is fteqtv?

Re: FTE ragdoll system

Posted: Mon Jan 20, 2014 9:03 am
by Spike
there have not been many direct code contributions for a while, other than from myself.
TimeServ still contributes with server hosting (fteqw.com and triptohell.info are both his).
I do sometimes 'steal' code from DarkPlaces, primarily for compatibility, but also for laziness sometimes. The initial ODE stuff (read: MOVETYPE_PHYSICS) is a relevent example of this.

fteqtv is a proxy system, essentually mvd-over-tcp chains, that can be used to broadcast a live game amoungst a potentially unlimited number of viewers (if there's proxies running on enough hosts).
you can connect to the proxy with either a qw or nq client, and either play on a qw server, or watch a live stream.

Re: FTE ragdoll system

Posted: Tue Jan 21, 2014 5:42 am
by ceriux
thanks for the video, do the bodies react when shot?

Re: FTE ragdoll system

Posted: Tue Jan 21, 2014 11:38 am
by toneddu2000
Still not, next weekend: that task! Wait for another video! :)

Re: FTE ragdoll system

Posted: Tue Jan 21, 2014 3:51 pm
by toneddu2000
Spike wrote:there have not been many direct code contributions for a while, other than from myself.
I've not seen your reply!
Unbelievable. An enormous work to do all by yourself. Compliments. FTE and DP are now more than a quake engine, are game engine on their own.