// 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_dropAF" "af def" "dropSkin" "skin name" To drop multiple articulated figures the following key/value pairs can be used: "def_dropAF*" "af def" where * is an aribtrary string. ================ */ void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *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(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(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(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 ); }