StudioModelRenderer.cpp
on the clientside contains a lot of code about bone transformation, blending and controllers. It involves a lot of interpolation and quaternion maths, so much that it deserves its very own article. As such, it won't be covered here.entvars_t
:
int sequence; // animation sequence
int gaitsequence; // movement animation sequence for player (0 for none)
float frame; // % playback position in animation sequences (0..255)
float animtime; // world time when frame was set
float framerate; // animation playback rate (-8x to 8x)
byte controller[4]; // bone controller setting (0..255)
byte blending[2]; // blending amount between sub-sequences (0..255)
sequence
is the animation ID. In a modern engine, you'd typically set animations by name, but here, you set animations by number. It is possible to write a utility function to set animations by name though. You will see how later.gaitsequence
is player-specific, and it controls the leg animation.frame
is the current animation frame. In brushes and sprites, it controls which texture frame to display. In models, it controls the percentage of the animation progress, so for example, if it's 127, it'll be roughly 50% complete.animtime
is the server time when frame
was set. This way, the client will have a reference as to when the animation started playing, and as such, will be able to interpolate it correctly.framerate
is quite self-explanatory, except it doesn't exactly control the animation framerate directly. Instead, it is a multiplier, so a value of 1.0 will mean normal playback speed.controller
is an array of 4 bone controller channels. If you have a controller that goes between -180 and +180 degrees, then setting its value to 127 will effectively set its angle to 0. Setting its value to 0 will mean an angle of -180°.blending
only works if sequence
or gaitsequence
are special blend sequences, and it basically blends between their subsequences.int skin;
int body; // sub-model selection for studiomodels
skin
simply selects the current texture. Let's say we have the following texture groups in some model:
$texturegroup arms
{
{ "newarm.bmp" "handback.bmp" "helmet.bmp" }
{ "newarm(dark).bmp" "handback(dark).bmp" "helmet(dark).bmp" }
}
skin
would basically control which row here is selected.body
works is by simply packing multiple integers into itself. // Get a model pointer for this entity
void* pmodel = GET_MODEL_PTR( pEntity->edict() );
// Interpret that as a studio model header
studiohdr_t* pStudioHeader = (studiohdr_t*)pmodel;
From here onward, you can read all kinds of information about the model. The name, the bounding boxes, flags, number of bones, animations and many more. For example, obtaining information about a group of bodyparts would go like this:
int groupNumber = 2;
mstudiobodyparts_t* pBodyGroup = (mstudiobodyparts_t*)((byte*)pStudioHeader + pStudioHeader->bodypartindex) + groupNumber;
ALERT( at_console, "Bodygroup %i has %i bodyparts\n", groupNumber, pBodyGroup->nummodels );
As you can see, the general pattern for obtaining model data is this:
data_t* pData = (data_t*)((byte*)pStudioHeader + pStudioHeader->dataindex);
This way, we may write a utility function that sets an entity's animation by name. Speaking of which...
LookupSequence
.animation.cpp
, and this is essentially how it works:
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex);
for (int i = 0; i < pstudiohdr->numseq; i++)
{
if (stricmp( pseqdesc[i].label, label ) == 0)
return i;
}
return -1;
Not just that, they wrote an entire base class to handle animated model entities.
CBaseAnimating
CBaseDelay
, with a lot of animation utilities added on top:
float StudioFrameAdvance( float flInterval )
pev->frame += interval
. You would typically call this in think functions.int GetSequenceFlags()
int LookupActivity( int activity )
ACTIVITY_NOT_AVAILABLE
(-1) is returned instead.int LookupActivityHeaviest( int activity )
int LookupSequence( const char* label )
void ResetSequenceInfo()
void DispatchAnimEvents( float flFutureInterval = 0.1 )
flFutureInterval
, and calls HandleAnimEvent
for each caught events. This is typically called every 0.1 seconds in the Think
method.virtual void HandleAnimEvent( MonsterEvent_t* pEvent )
DispatchAnimEvents
when appropriate.float SetBoneController( int iController, float flValue )
flValue
.void InitBoneControllers()
float SetBlending( int iBlender, float flValue )
void GetBonePosition( int iBone, Vector& origin, Vector& angles )
void GetAutomovement( Vector& origin, Vector& angles, float flInterval = 0.1 )
int FindTransition( int iEndingSequence, int iGoalSequence, int* piDir )
void GetAttachment( int iAttachment, Vector& origin, Vector& angles )
void SetBodygroup( int iGroup, int iValue )
iValue
) of a chosen bodygroup (iGroup
).int GetBodygroup( int iGroup )
int ExtractBbox( int sequence, float* mins, float* maxs )
void SetSequenceBox()
ExtractBbox
and sets the bounding box of the entity to that.animation.cpp
.CBaseAnimating
. An example animating entity will be shown on the next page.
You must log in to post a comment. You can login or register a new account.