etqw-sdk/source/game/AFEntity.cpp

871 lines
22 KiB
C++
Raw Normal View History

2008-05-29 00:00:00 +00:00
// Copyright (C) 2007 Id Software, Inc.
//
#include "precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "AFEntity.h"
#include "ContentMask.h"
#include "Item.h"
#include "Player.h"
/*
===============================================================================
idAFEntity_Base
===============================================================================
*/
CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base )
END_CLASS
static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f;
static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f;
/*
================
idAFEntity_Base::idAFEntity_Base
================
*/
idAFEntity_Base::idAFEntity_Base( void ) {
combatModel = NULL;
combatModelContents = 0;
nextSoundTime = 0;
spawnOrigin.Zero();
spawnAxis.Identity();
}
/*
================
idAFEntity_Base::~idAFEntity_Base
================
*/
idAFEntity_Base::~idAFEntity_Base( void ) {
gameLocal.clip.DeleteClipModel( combatModel );
combatModel = NULL;
}
/*
================
idAFEntity_Base::Spawn
================
*/
void idAFEntity_Base::Spawn( void ) {
spawnOrigin = GetPhysics()->GetOrigin();
spawnAxis = GetPhysics()->GetAxis();
nextSoundTime = 0;
}
/*
================
idAFEntity_Base::LoadAF
================
*/
bool idAFEntity_Base::LoadAF( void ) {
idStr fileName;
if ( !spawnArgs.GetString( "articulatedFigure", "*unknown*", fileName ) ) {
return false;
}
af.SetAnimator( GetAnimator() );
if ( !af.Load( this, fileName ) ) {
gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() );
}
af.Start();
af.GetPhysics()->Rotate( spawnAxis.ToRotation() );
af.GetPhysics()->Translate( spawnOrigin );
LoadState( spawnArgs );
af.UpdateAnimation();
animator.CreateFrame( gameLocal.time, true );
UpdateVisuals();
return true;
}
/*
================
idAFEntity_Base::Think
================
*/
void idAFEntity_Base::Think( void ) {
RunPhysics();
UpdateAnimation();
if ( thinkFlags & TH_UPDATEVISUALS ) {
Present();
LinkCombat();
}
}
/*
================
idAFEntity_Base::BodyForClipModelId
================
*/
int idAFEntity_Base::BodyForClipModelId( int id ) const {
return af.BodyForClipModelId( id );
}
/*
================
idAFEntity_Base::SaveState
================
*/
void idAFEntity_Base::SaveState( idDict &args ) const {
const idKeyValue *kv;
// save the ragdoll pose
af.SaveState( args );
// save all the bind constraints
kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL );
while ( kv ) {
args.Set( kv->GetKey(), kv->GetValue() );
kv = spawnArgs.MatchPrefix( "bindConstraint ", kv );
}
// save the bind if it exists
kv = spawnArgs.FindKey( "bind" );
if ( kv ) {
args.Set( kv->GetKey(), kv->GetValue() );
}
kv = spawnArgs.FindKey( "bindToJoint" );
if ( kv ) {
args.Set( kv->GetKey(), kv->GetValue() );
}
kv = spawnArgs.FindKey( "bindToBody" );
if ( kv ) {
args.Set( kv->GetKey(), kv->GetValue() );
}
}
/*
================
idAFEntity_Base::LoadState
================
*/
void idAFEntity_Base::LoadState( const idDict &args ) {
af.LoadState( args );
}
/*
================
idAFEntity_Base::InitBind
================
*/
bool idAFEntity_Base::InitBind( idEntity *master ) {
bool ret = idEntity::InitBind( master );
if ( !ret ) {
return false;
}
if ( master ) {
AddBindConstraints();
}
return true;
}
/*
================
idAFEntity_Base::Unbind
================
*/
void idAFEntity_Base::Unbind( void ) {
RemoveBindConstraints();
idEntity::Unbind();
}
/*
================
idAFEntity_Base::AddBindConstraints
================
*/
void idAFEntity_Base::AddBindConstraints( void ) {
af.AddBindConstraints();
}
/*
================
idAFEntity_Base::RemoveBindConstraints
================
*/
void idAFEntity_Base::RemoveBindConstraints( void ) {
af.RemoveBindConstraints();
}
/*
================
idAFEntity_Base::GetImpactInfo
================
*/
void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) {
if ( af.IsActive() ) {
af.GetImpactInfo( ent, id, point, info );
} else {
idEntity::GetImpactInfo( ent, id, point, info );
}
}
/*
================
idAFEntity_Base::ApplyImpulse
================
*/
void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
if ( af.IsLoaded() ) {
af.ApplyImpulse( ent, id, point, impulse );
}
if ( !af.IsActive() ) {
idEntity::ApplyImpulse( ent, id, point, impulse );
}
}
/*
================
idAFEntity_Base::AddForce
================
*/
void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
if ( af.IsLoaded() ) {
af.AddForce( ent, id, point, force );
}
if ( !af.IsActive() ) {
idEntity::AddForce( ent, id, point, force );
}
}
/*
================
idAFEntity_Base::Collide
================
*/
bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity, int bodyId ) {
float v, f;
if ( af.IsActive() ) {
v = -( velocity * collision.c.normal );
if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) {
f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
if ( StartSound( "snd_bounce", SND_ANY, 0, NULL ) ) {
SetSoundVolume( f );
}
nextSoundTime = gameLocal.time + 500;
}
}
return false;
}
/*
================
idAFEntity_Base::GetPhysicsToVisualTransform
================
*/
bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
if ( af.IsActive() ) {
af.GetPhysicsToVisualTransform( origin, axis );
return true;
}
return idEntity::GetPhysicsToVisualTransform( origin, axis );
}
/*
================
idAFEntity_Base::UpdateAnimationControllers
================
*/
bool idAFEntity_Base::UpdateAnimationControllers( void ) {
if ( af.IsActive() ) {
if ( af.UpdateAnimation() ) {
return true;
}
}
return false;
}
/*
================
idAFEntity_Base::SetCombatModel
================
*/
void idAFEntity_Base::SetCombatModel( void ) {
gameLocal.clip.DeleteClipModel( combatModel );
combatModel = new idClipModel( modelDefHandle );
}
/*
================
idAFEntity_Base::SetCombatContents
================
*/
void idAFEntity_Base::SetCombatContents( bool enable ) {
assert( combatModel );
if ( enable && combatModelContents ) {
assert( !combatModel->GetContents() );
combatModel->SetContents( combatModelContents );
combatModelContents = 0;
} else if ( !enable && combatModel->GetContents() ) {
assert( !combatModelContents );
combatModelContents = combatModel->GetContents();
combatModel->SetContents( 0 );
}
}
/*
================
idAFEntity_Base::LinkCombat
================
*/
void idAFEntity_Base::LinkCombat( void ) {
if ( fl.hidden ) {
return;
}
if ( combatModel ) {
combatModel->Link( gameLocal.clip, this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle );
}
}
/*
================
idAFEntity_Base::UnLinkCombat
================
*/
void idAFEntity_Base::UnLinkCombat( void ) {
if ( combatModel != NULL ) {
combatModel->Unlink( gameLocal.clip );
}
}
/*
===============
idAFEntity_Base::ShowEditingDialog
===============
*/
void idAFEntity_Base::ShowEditingDialog( void ) {
// FIXME:
// common->InitTool( EDITOR_AF, &spawnArgs );
}
/*
================
idAFEntity_Base::DropAFs
The entity should have the following key/value pairs set:
"def_drop<type>AF" "af def"
"drop<type>Skin" "skin name"
To drop multiple articulated figures the following key/value pairs can be used:
"def_drop<type>AF*" "af def"
where * is an aribtrary string.
================
*/
void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList<idEntity *> *list ) {
const idKeyValue *kv;
const char *skinName;
idEntity *newEnt;
idAFEntity_Base *af;
idDict args;
const idDeclSkin *skin;
// drop the articulated figures
kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL );
while ( kv ) {
args.Set( "classname", kv->GetValue() );
gameLocal.SpawnEntityDef( args, true, &newEnt );
if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) {
af = static_cast<idAFEntity_Base *>(newEnt);
af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() );
af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() );
af->af.SetupPose( ent, gameLocal.time );
if ( list ) {
list->Append( af );
}
}
kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv );
}
// change the skin to hide all the dropped articulated figures
skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) );
if ( skinName[0] ) {
skin = declHolder.declSkinType.LocalFind( skinName );
ent->SetSkin( skin );
}
}
/*
===============================================================================
idAFEntity_Generic
===============================================================================
*/
CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Generic )
EVENT( EV_Activate, idAFEntity_Generic::Event_Activate )
END_CLASS
/*
================
idAFEntity_Generic::idAFEntity_Generic
================
*/
idAFEntity_Generic::idAFEntity_Generic( void ) {
keepRunningPhysics = false;
}
/*
================
idAFEntity_Generic::~idAFEntity_Generic
================
*/
idAFEntity_Generic::~idAFEntity_Generic( void ) {
}
/*
================
idAFEntity_Generic::Think
================
*/
void idAFEntity_Generic::Think( void ) {
idAFEntity_Base::Think();
if ( keepRunningPhysics ) {
BecomeActive( TH_PHYSICS );
}
}
/*
================
idAFEntity_Generic::Spawn
================
*/
void idAFEntity_Generic::Spawn( void ) {
if ( !LoadAF() ) {
gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() );
}
SetCombatModel();
SetPhysics( af.GetPhysics() );
af.GetPhysics()->PutToRest();
if ( !spawnArgs.GetBool( "nodrop", "0" ) ) {
af.GetPhysics()->Activate();
}
fl.takedamage = true;
}
/*
================
idAFEntity_Generic::Event_Activate
================
*/
void idAFEntity_Generic::Event_Activate( idEntity *activator ) {
float delay;
idVec3 init_velocity, init_avelocity;
Show();
af.GetPhysics()->EnableImpact();
af.GetPhysics()->Activate();
spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity );
spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity );
delay = spawnArgs.GetFloat( "init_velocityDelay", "0" );
if ( delay == 0.0f ) {
af.GetPhysics()->SetLinearVelocity( init_velocity );
} else {
PostEventSec( &EV_SetLinearVelocity, delay, init_velocity );
}
delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" );
if ( delay == 0.0f ) {
af.GetPhysics()->SetAngularVelocity( init_avelocity );
} else {
PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity );
}
}
/*
===============================================================================
editor support routines
===============================================================================
*/
/*
================
idGameEdit::AF_SpawnEntity
================
*/
bool idGameEdit::AF_SpawnEntity( const char *fileName ) {
idDict args;
idPlayer *player;
idAFEntity_Generic *ent;
const idDeclAF *af;
idVec3 org;
float yaw;
player = gameLocal.GetLocalPlayer();
if ( !player || !gameLocal.CheatsOk( false ) ) {
return false;
}
af = declHolder.declAFType.LocalFind( fileName, false );
if ( !af ) {
return false;
}
yaw = player->viewAngles.yaw;
args.Set( "angle", va( "%f", yaw + 180 ) );
org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 );
args.Set( "origin", org.ToString() );
args.Set( "spawnclass", "idAFEntity_Generic" );
if ( af->model[0] ) {
args.Set( "model", af->model.c_str() );
} else {
args.Set( "model", fileName );
}
if ( af->skin[0] ) {
args.Set( "skin", af->skin.c_str() );
}
args.Set( "articulatedFigure", fileName );
args.Set( "nodrop", "1" );
ent = static_cast<idAFEntity_Generic *>(gameLocal.SpawnEntityType( idAFEntity_Generic::Type, true, &args));
// always update this entity
ent->BecomeActive( TH_THINK );
ent->KeepRunningPhysics();
// player->dragEntity.SetSelected( ent );
return true;
}
/*
================
idGameEdit::AF_UpdateEntities
================
*/
void idGameEdit::AF_UpdateEntities( const char *fileName ) {
idEntity *ent;
idAFEntity_Base *af;
idStr name;
name = fileName;
name.StripFileExtension();
// reload any idAFEntity_Generic which uses the given articulated figure file
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( ent->IsType( idAFEntity_Base::Type ) ) {
af = reinterpret_cast< idAFEntity_Base* >( ent );
if ( name.Icmp( af->GetAFName() ) == 0 ) {
af->LoadAF();
af->GetAFPhysics()->PutToRest();
}
}
}
}
/*
================
idGameEdit::AF_UndoChanges
================
*/
void idGameEdit::AF_UndoChanges( void ) {
int i, c;
idEntity *ent;
idAFEntity_Base *af;
idDeclAF *decl;
c = declHolder.declAFType.Num();
for ( i = 0; i < c; i++ ) {
decl = const_cast< idDeclAF* >( declHolder.declAFType.LocalFindByIndex( i, false ) );
if ( !decl->modified ) {
continue;
}
decl->Invalidate();
declHolder.declAFType.LocalFind( decl->GetName() );
// reload all AF entities using the file
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if ( ent->IsType( idAFEntity_Base::Type ) ) {
af = static_cast< idAFEntity_Base* >( ent );
if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) {
af->LoadAF();
}
}
}
}
}
/*
================
GetJointTransform
================
*/
typedef struct {
renderEntity_t *ent;
const idMD5Joint *joints;
} jointTransformData_t;
static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) {
int i;
jointTransformData_t *data = reinterpret_cast<jointTransformData_t *>(model);
for ( i = 0; i < data->ent->numJoints; i++ ) {
if ( data->joints[i].name.Icmp( jointName ) == 0 ) {
break;
}
}
if ( i >= data->ent->numJoints ) {
return false;
}
origin = frame[i].ToVec3();
axis = frame[i].ToMat3();
return true;
}
/*
================
GetArgString
================
*/
static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) {
const char *s;
s = args.GetString( key );
if ( !s[0] && defArgs ) {
s = defArgs->GetString( key );
}
return s;
}
/*
================
idGameEdit::AF_CreateMesh
================
*/
idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) {
int i, jointNum;
const idDeclAF *af;
const idDeclAF_Body *fb;
renderEntity_t ent;
idVec3 origin, *bodyOrigin, *newBodyOrigin, *modifiedOrigin;
idMat3 axis, *bodyAxis, *newBodyAxis, *modifiedAxis;
declAFJointMod_t *jointMod;
idAngles angles;
const idDict *defArgs;
const idKeyValue *arg;
idStr name;
jointTransformData_t data;
const char *classname, *afName, *modelName;
idRenderModel *md5;
const idDeclModelDef *modelDef;
const idMD5Anim *MD5anim;
const idMD5Joint *MD5joint;
const idMD5Joint *MD5joints;
int numMD5joints;
idJointMat *originalJoints;
int parentNum;
poseIsSet = false;
meshOrigin.Zero();
meshAxis.Identity();
classname = args.GetString( "classname" );
defArgs = gameLocal.FindEntityDefDict( classname );
// get the articulated figure
afName = GetArgString( args, defArgs, "articulatedFigure" );
af = declHolder.declAFType.LocalFind( afName );
if ( !af ) {
return NULL;
}
// get the md5 model
modelName = GetArgString( args, defArgs, "model" );
modelDef = gameLocal.declModelDefType.LocalFind( modelName, false );
if ( !modelDef ) {
return NULL;
}
// make sure model hasn't been purged
if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) {
modelDef->ModelHandle()->LoadModel();
}
// get the md5
md5 = modelDef->ModelHandle();
if ( !md5 || md5->IsDefaultModel() ) {
return NULL;
}
// get the articulated figure pose anim
int animNum = modelDef->GetAnim( "af_pose" );
if ( !animNum ) {
return NULL;
}
const idAnim *anim = modelDef->GetAnim( animNum );
if ( !anim ) {
return NULL;
}
MD5anim = anim->MD5Anim( 0 );
MD5joints = md5->GetJoints();
numMD5joints = md5->NumJoints();
// setup a render entity
memset( &ent, 0, sizeof( ent ) );
ent.customSkin = modelDef->GetSkin();
ent.bounds.Clear();
ent.numJoints = numMD5joints;
ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) );
// create animation from of the af_pose
ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false );
// buffers to store the initial origin and axis for each body
bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) );
bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) );
newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) );
newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) );
// finish the AF positions
data.ent = &ent;
data.joints = MD5joints;
af->Finish( GetJointTransform, ent.joints, &data );
// get the initial origin and axis for each AF body
for ( i = 0; i < af->bodies.Num(); i++ ) {
fb = af->bodies[i];
if ( fb->modelType == TRM_BONE ) {
// axis of bone trace model
axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3();
axis[2].Normalize();
axis[2].NormalVectors( axis[0], axis[1] );
axis[1] = -axis[1];
} else {
axis = fb->angles.ToMat3();
}
newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3();
newBodyAxis[i] = bodyAxis[i] = axis;
}
// get any new body transforms stored in the key/value pairs
for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) {
name = arg->GetKey();
name.Strip( "body " );
for ( i = 0; i < af->bodies.Num(); i++ ) {
fb = af->bodies[i];
if ( fb->name.Icmp( name ) == 0 ) {
break;
}
}
if ( i >= af->bodies.Num() ) {
continue;
}
sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll );
if ( fb->jointName.Icmp( "origin" ) == 0 ) {
meshAxis = bodyAxis[i].Transpose() * angles.ToMat3();
meshOrigin = origin - bodyOrigin[i] * meshAxis;
poseIsSet = true;
} else {
newBodyOrigin[i] = origin;
newBodyAxis[i] = angles.ToMat3();
}
}
// save the original joints
originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) );
memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) );
// buffer to store the joint mods
jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) );
memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) );
modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) );
memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) );
modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) );
memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) );
// get all the joint modifications
for ( i = 0; i < af->bodies.Num(); i++ ) {
fb = af->bodies[i];
if ( fb->jointName.Icmp( "origin" ) == 0 ) {
continue;
}
for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) {
if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) {
break;
}
}
if ( jointNum >= 0 && jointNum < ent.numJoints ) {
jointMod[ jointNum ] = fb->jointMod;
modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() );
// FIXME: calculate correct modifiedOrigin
modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3();
}
}
// apply joint modifications to the skeleton
MD5joint = MD5joints + 1;
for( i = 1; i < numMD5joints; i++, MD5joint++ ) {
parentNum = MD5joint->parent - MD5joints;
idMat3 parentAxis = originalJoints[ parentNum ].ToMat3();
idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose();
idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose();
switch( jointMod[i] ) {
case DECLAF_JOINTMOD_ORIGIN: {
ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() );
ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] );
break;
}
case DECLAF_JOINTMOD_AXIS: {
ent.joints[ i ].SetRotation( modifiedAxis[ i ] );
ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() );
break;
}
case DECLAF_JOINTMOD_BOTH: {
ent.joints[ i ].SetRotation( modifiedAxis[ i ] );
ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] );
break;
}
default: {
ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() );
ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() );
break;
}
}
}
// instantiate a mesh using the joint information from the render entity
return renderSystem->InstantiateDynamicModel( md5, &ent );
}