frame1time in FTE

Discuss CSQC related programming.
Post Reply
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

frame1time in FTE

Post by toneddu2000 »

This issue really makes me nervous.. let's imagine a player with a skeleton with two set of bones. spineupper bone to head bone(including shoulders,arms and weapon bones) control upper movement and hips bone to left foot bone control lower movement.
The hierarchy of bones is

Code: Select all

root,spine,hips,thigh_r,leg_r,foot_r,thigh_l,leg_l,foot_l,spine2,neck,head,clavicle_r,armupper_r,armlower_r,hand_r,weapon_r,thumb1_r,thumb2_r,finger1_r,finger2_r,holster_r,clavicle_l,armupper_l,armlower_l,hand_l,weapon_l,thumb1_l,thumb2_l,finger1_l,finger2_l,holster_l
So, first lower bones, than upper bones, just like skeletal.txt said

Now, creating PlayerLowerAnims (used in predraw function) function for playing lower running/idle animation it's simple

Code: Select all

void PlayerLowerAnims()
{
if(self.velocity_x > 20||self.velocity_y > 20||self.velocity_x < -20||self.velocity_y < -20){
		self.frame = animRun;
		self.frame1time += frametime;
		skel_build(self.skeletonindex, self, self.modelindex, 0, bHips, bFootL, 1);
	}
	else{
		self.frame = animIdle;
		self.frame1time += frametime;
		skel_build(self.skeletonindex, self, self.modelindex, 0, bHips, bFootL, 1);//build lower part
	}
}
Both idle and running animation are looped animations, so self.frame1time += frametime is ok. Now comes the problem.

Imagine that I want to add a PlayerUpperAnims to control fire animations from spineupper and up. Fire animations are non-looped, so self.frame1time += frametime it's not good, because frame1time animation, after the shot impulse, must be set to 0 just like this

Code: Select all

if(self.weaponFpvShot == TRUE){
	self.frame = animFirePistol;
	self.frame1time += frametime;
	skel_build(self.skeletonindex, self, self.modelindex, 0, bSpine2, bHolsterL, 1);
}
else{
	self.frame = 0;
	self.frame1time = 0;
	skel_build(self.skeletonindex, self, self.modelindex, 0, bSpine2, bHolsterL, 1);
}
to reset the animation, but it will break self.frame1time for legs because both upper bones and lower bones share same frame1time!
Just note that the above method works beautifully for weapon view models in CSQC but just because weapon entity doesn't share frame1time with anyone else!!

Spike once told me, to play animation discarding looping, to do like this

Code: Select all

duration = frameduration(self.modelindex, animFirePistol);

if(self.weaponFpvShot == TRUE){
//non-loop animation code
self.frame = animFirePistol;
t = self.frame1time / duration;
self.frame1time = (t-floor(t))*(duration/0.8);
//build upper bones
skel_build(self.skeletonindex, self, self.modelindex, 0, bSpine2, bHolsterL, 1);//build upper part
}
But animation are not synced well, sometimes they start some time after the shot impulse or they play twice or more for one single shoot impulse.
This code is placed BEFORE lower bones as stated in skeletal.txt
Separation of torso+legs can also be achieved, by calling skel_build multiple times with separate bone ranges.
If your model is arranged with the legs as the early bones, and the torso as the later bones, you can use skel_find_bone to find the lower spine bone, and animate the two bone groups separately by updating the animation fields for torso, then for legs.
Which it's not incorrect: torso + legs separaration WILL work. The problem is frame duration / looping separation for upper bones animations and lower bones animations.

Plus, skeletal.txt said
Note that you can simply pass something other than 'self' for the legs, and it'll use the animation fields from a different (invisible) entity instead.
:?: How can I assign a null entity to .frame?
I mean, to be valid, this invisible entity (for example named legs)should be spawn in CSQC_Init. It should be set same model of player, it should be created a skeleton in Startup player function

Code: Select all

if (!legs.skeletonindex){
	legs.skeletonindex = skel_create(legs.modelindex);
}
Then I should fix leg.origin to player's spineupper origin (and hoping that it centers the right orientation) and leg.angles to player.angles with vectoangles(v_up, v_forward), but, why? I mean, it's invisible, why makes all this effort? :D
And, at last, how can I draw those frames if it's not "self" which it's playing it but, instead, that invisible legs entity?

This last phrase Spike wrote it's really difficult to understand.

I also thought:"well I can use self.frame and self.frame2!" I set self.frame to upper animations and self.frame2 for lower bones and I play them simoultaneously. Yes, but how? self.frame2 implicates the use of self.lerpfrac, wich set to 0.5 plays self.frame and self.frame2 at 50%, but, of course, they appear as "melted" together, which it's not what I need.
It would be great to use, in this case, self.lerpfrac = middlebone; and, for middlebone, use the bone that divides upper animations from lower ones.

Sorry for the long post but this topic is not clearly described anywhere.

Ideas? :D
Thanks A LOT for any input
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: frame1time in FTE

Post by Spike »

the whole point about the skeletal objects extension is that the skeletal object is separate from the entity itself.
you set an entity's skeletonindex to refer to your skeletal object. this COMPLETELY overrides the .frame etc fields for the purposes of rendering. You then use skel_build to _copy_ skeletal data from the model specified into the skeletal object. skel_build only has an entity argument because its the easiest way to specify the full set of frame+frame2+lerpfrac+etc - the specified entity doesn't need a model/modelindex or a skeletonindex set.

just do self.legstime += frametime; self.frame1time = self.legstime; or something.
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: frame1time in FTE

Post by toneddu2000 »

Thanks a lot Spike for your help!
I changed my code to this

Code: Select all

void PlayerAnimation()
{	
//upper part
	if(self.weaponFpvShot == TRUE){
		self.frame = animFirePistol;
		self.armstime += frametime;
		self.frame1time = self.armstime;
		skel_build(self.skeletonindex, self, self.modelindex, 0, bSpine2, bHolsterL, 1);
	}
	else{
		self.frame = animFirePistol;
		self.armstime = 0;
		self.frame1time = self.armstime;
		skel_build(self.skeletonindex, self, self.modelindex, 0, bSpine2, bHolsterL, 1);
	}
//lower part	
	if(self.velocity_x > 20||self.velocity_y > 20||self.velocity_x < -20||self.velocity_y < -20){
		self.frame = animRun;
		self.legstime += frametime;
		self.frame1time = self.legstime;
		skel_build(self.legstime, self, self.modelindex, 0, bHips, bFootL, 1);
	}
	else{
		self.frame = animIdle;
		self.legstime += frametime;
		self.frame1time = self.legstime;
		skel_build(self.legstime, self, self.modelindex, 0, bHips, bFootL, 1);
	}
}
And, now, it works! Player model fire and walk simoultaneously!

Now, can you please explain to me what did I write? :D
I mean, the upper bones skel_build calls used self.skeletonindex, the lower bones call use self.legstime.
If I use self.armstime (instead of self.skeletonindex) in upper bones skel_build calls, arms and head disappear (all the bones called by skel_build), like model couldn't be built right
the whole point about the skeletal objects extension is that the skeletal object is separate from the entity itself.
yeah, I got it. Practically you could have a skeleton object that's completely indipendent from a specific model, so it could be applied to N models that have same skeletal hierarchy and structure, right?
You then use skel_build to _copy_ skeletal data from the model specified into the skeletal object.
ok, that's clear
skel_build only has an entity argument because its the easiest way to specify the full set of frame+frame2+lerpfrac+etc - the specified entity doesn't need a model/modelindex or a skeletonindex set.
That's less clear. I think that this part would need some extended documentation. I could write that for you, no problem. But first I need to understand how skel_build manage entities.

Still no clues about retainfrac and addfrac. I understood that addfrac sum always need to be 1 or scaling issues occur (oh, they do occur! :) ), but, apart from that, I wasn't able to use to mix animations. I also tried to use retainfrac to 0 for first skel_build and 1 for the next ones but I cannot understan how to use it

Thanks again Spike, your help is precious
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: frame1time in FTE

Post by Spike »

skel_build(self.legstime, self, self.modelindex, 0, bHips, bFootL, 1);

NO!

self.legstime is NOT a skeletal object. Why would you do that?

retainfrac and addfrac are quite simple:
objectbonematrix = (objectbonematrix * retainfrac) + (modelbonematrix * addfrac);
this means that you can have multiple animations blending together to update a single bone.

actually, its more complex than that:
objectbonematrix = (objectbonematrix * retainfrac) + (modelbonematrix[ent.frame2] * addfrac * ent.lerpfrac) + (modelbonematrix[ent.frame] * addfrac * (1-ent.lerpfrac));

Actually, its more complex than that:
if (omitted(addfrac)) addfrac = 1-retainfrac;
objectbonematrix *= retainfrac;
f = ent.frame;
pose = floor(ent.frame1time / modelframe[f].framerate);
frac = (ent.frame1time / modelframe[f].framerate) - pose;
objectbonematrix += addfrac * (1-ent.lerpfrac) * (modelframe[f].pose[clampormoduloiflooping(modelframe[f].looping, pose+1, modelframe[f].numposes)] * frac + modelframe[f].pose[clampormoduloiflooping(modelframe[f].looping, pose+0, modelframe[f].numposes) * (1-frac));
f = ent.frame2;
pose = floor(ent.frame2time / modelframe[f].framerate);
frac = (ent.frame2time / modelframe[f].framerate) - pose;
objectbonematrix += addfrac * ent.lerpfrac * (modelframe[f].pose[clampormoduloiflooping(modelframe[f].looping, pose+1, modelframe[f].numposes)] * frac + modelframe[f].pose[clampormoduloiflooping(modelframe[f].looping, pose+0, modelframe[f].numposes) * (1-frac));

Actually, its more complex than that:
... frame3, frame3time, lerpfrac3, frame4, frame4time, lerpfrac4 fields exist. which provide 8-way animation (or 4-way, if your model doesn't use framegroups properly).

with all four added together, scaled by addfrac and their own lerpfrac weights, and then with the existing bone added scaled by retainfrac.
MATHS! just what you always wanted!

Actually, its more complex than that:
baseframe, baseframe2, baseframe1time, baseframe2time, baselerpfrac, basebone exist, which allows you to use different frame fields based upon the bone number (but 8-way is pure overkill). This is some WIP extension I never quite documented properly, in part because the server equivelent of it is more useful due to ssqc not being able to send skeletal objects over the net in combination with the fact that I never made a network protocol for it.
(the basebone field specifies the first bone that is logically no longer part of the 'base' group, with the first bone in the model being the first bone of this group, 0 thus means that the group is empty and thus the other base* fields are irrelevant).

Actually, I'm tempted to mention halflife model formats with its bone controller stuff, but I value my sanity too much.


Oh yeah, this is all per bone(or per-bonerange, as far as skel_build is concerned, for efficiency).
when you have torso and legs, you're updating two bone ranges independantly.
when you have a forwards animation and a sideways animation, and your player is moving diagonally, you want to update the lower bonerange TWICE, once with each animation that is active. this is where your addfrac/retainfrac arguments become useful. with this, you can avoid using frame2+lerpfrac entirely, if you feel that way inclined. or you can have 1024-way animation blending or whatever... if you have that many animations anyway.
if you scale each animation's weight by the total weights currently active, you can avoid any rescaling of your model (but you'll need to ensure that inactive animations still drop to 0 somehow, ideally linearly).
toneddu2000
Posts: 1395
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: frame1time in FTE

Post by toneddu2000 »

skel_build(self.legstime, self, self.modelindex, 0, bHips, bFootL, 1);

NO!

self.legstime is NOT a skeletal object. Why would you do that?
Yeah, writing

Code: Select all

skel_build(self.skeletonindex, self, self.modelindex, 0, bHips, bFootL, 1);
works too, my question is: how is it possible that it works with self.legstime too?

The problem of retainfrac / addfrac is that, at 99% of the times, you'll never blend animation for anything but frame transition (running frame ends - idle frame begins - let's smooth the transition between them to avoid the "gap"),so basically I can't find any situation where I could use a skel_build call where blending 2 animations on a single bone. Probably I'd use self.frame2 when self.velocity get low under 10 units or so

Thanks for the technical very exaustive explanation but you forget that I'm not a genius like you! :lol: Anyway, really appreciated, I'll study it tonight.

I've seen basebone before on csdefs.qc but I always wondered how it works. But if it's just a WIP, nevermind
Meadow Fun!! - my first commercial game, made with FTEQW game engine
Post Reply