|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "tier0/dbg.h"
#include "mathlib/mathlib.h"
#include "bone_setup.h"
#include <string.h>
#include "collisionutils.h"
#include "vstdlib/random.h"
#include "tier0/vprof.h"
#include "bone_accessor.h"
#include "mathlib/ssequaternion.h"
#include "bitvec.h"
#include "datamanager.h"
#include "convar.h"
#include "tier0/tslist.h"
#include "vphysics_interface.h"
#include "datacache/idatacache.h"
#include "posedebugger.h"
#include "mathlib/softbody.h"
#include "tier0/miniprofiler.h"
#ifdef CLIENT_DLL
#include "posedebugger.h"
#endif
#include "bone_utils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
//-----------------------------------------------------------------------------
void InitPose( const CStudioHdr *pStudioHdr, BoneVector pos[], BoneQuaternionAligned q[], int boneMask ) { // const fltx4 zeroQ = Four_Origin;
BONE_PROFILE_FUNC(); SNPROF_ANIM("InitPose");
if( mstudiolinearbone_t *pLinearBones = pStudioHdr->pLinearBones() ) { int numBones = pStudioHdr->numbones();
Assert( sizeof(Quaternion) == sizeof(BoneQuaternion) ); memcpy( q, (((byte *)pLinearBones) + pLinearBones->quatindex), sizeof( Quaternion ) * numBones );
if( sizeof(Vector) == sizeof(BoneVector) ) { memcpy( pos, (((byte *)pLinearBones) + pLinearBones->posindex), sizeof( Vector ) * numBones ); } else { Vector *pSrcPos = (Vector *)(((byte *)pLinearBones) + pLinearBones->posindex); for( int i = 0; i < pStudioHdr->numbones(); i++ ) { //if( pStudioHdr->boneFlags( i ) & boneMask )
{ pos[i] = pSrcPos[i]; } } } } else { for( int i = 0; i < pStudioHdr->numbones(); i++ ) { if( pStudioHdr->boneFlags( i ) & boneMask ) { const mstudiobone_t *pbone = pStudioHdr->pBone( i ); pos[i] = pbone->pos; q[i] = pbone->quat; } /* // unnecessary to initialize unused bones since they are ignored downstream.
else { pos[i].Zero(); // q[i] = zeroQ;
StoreAlignedSIMD(q[i].Base(), zeroQ); } */ } } }
inline bool PoseIsAllZeros( const CStudioHdr *pStudioHdr, int sequence, mstudioseqdesc_t &seqdesc, int i0, int i1 ) { int baseanim; // remove "zero" positional blends
baseanim = pStudioHdr->iRelativeAnim( sequence, seqdesc.anim(i0 ,i1 ) ); mstudioanimdesc_t &anim = ((CStudioHdr *)pStudioHdr)->pAnimdesc( baseanim ); return (anim.flags & STUDIO_ALLZEROS) != 0; }
//-----------------------------------------------------------------------------
// Purpose: turn a 2x2 blend into a 3 way triangle blend
// Returns: returns the animination indices and barycentric coordinates of a triangle
// the triangle is a right triangle, and the diagonal is between elements [0] and [2]
//-----------------------------------------------------------------------------
static ConVar anim_3wayblend( "anim_3wayblend", "1", FCVAR_REPLICATED, "Toggle the 3-way animation blending code." );
void Calc3WayBlendIndices( int i0, int i1, float s0, float s1, const mstudioseqdesc_t &seqdesc, int *pAnimIndices, float *pWeight ) { BONE_PROFILE_FUNC(); // Figure out which bi-section direction we are using to make triangles.
bool bEven = ( ( ( i0 + i1 ) & 0x1 ) == 0 );
int x1, y1; int x2, y2; int x3, y3;
// diagonal is between elements 1 & 3
// TL to BR
if ( bEven ) { if ( s0 > s1 ) { // B
x1 = 0; y1 = 0; x2 = 1; y2 = 0; x3 = 1; y3 = 1; pWeight[0] = (1.0f - s0); pWeight[1] = s0 - s1; } else { // C
x1 = 1; y1 = 1; x2 = 0; y2 = 1; x3 = 0; y3 = 0; pWeight[0] = s0; pWeight[1] = s1 - s0; } } // BL to TR
else { float flTotal = s0 + s1;
if( flTotal > 1.0f ) { // D
x1 = 1; y1 = 0; x2 = 1; y2 = 1; x3 = 0; y3 = 1; pWeight[0] = (1.0f - s1); pWeight[1] = s0 - 1.0f + s1; } else { // A
x1 = 0; y1 = 1; x2 = 0; y2 = 0; x3 = 1; y3 = 0; pWeight[0] = s1; pWeight[1] = 1.0f - s0 - s1; } }
pAnimIndices[0] = seqdesc.anim( i0 + x1, i1 + y1 ); pAnimIndices[1] = seqdesc.anim( i0 + x2, i1 + y2 ); pAnimIndices[2] = seqdesc.anim( i0 + x3, i1 + y3 );
/*
float w0 = ((x2-x3)*(y3-s1) - (x3-s0)*(y2-y3)) / ((x1-x3)*(y2-y3) - (x2-x3)*(y1-y3)); float w1 = ((x1-x3)*(y3-s1) - (x3-s0)*(y1-y3)) / ((x2-x3)*(y1-y3) - (x1-x3)*(y2-y3)); Assert( pWeight[0] == w0 && pWeight[1] == w1 ); */
// clamp the diagonal
if (pWeight[1] < 0.001f) pWeight[1] = 0.0f; pWeight[2] = 1.0f - pWeight[0] - pWeight[1];
Assert( pWeight[0] >= 0.0f && pWeight[0] <= 1.0f ); Assert( pWeight[1] >= 0.0f && pWeight[1] <= 1.0f ); Assert( pWeight[2] >= 0.0f && pWeight[2] <= 1.0f ); }
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
//-----------------------------------------------------------------------------
bool CalcPoseSingle( const CStudioHdr *pStudioHdr, BoneVector pos[], BoneQuaternionAligned q[], mstudioseqdesc_t &seqdesc, int sequence, float cycle, const float poseParameter[], int boneMask, float flTime ) { BONE_PROFILE_FUNC(); // ex: x360: up to 1.3ms
SNPROF_ANIM("CalcPoseSingle");
bool bResult = true; BoneVector *pos2 = g_VectorPool.Alloc(); BoneQuaternionAligned *q2 = g_QuaternionPool.Alloc(); BoneVector *pos3 = g_VectorPool.Alloc(); BoneQuaternionAligned *q3 = g_QuaternionPool.Alloc();
if ( sequence < 0 || sequence >= pStudioHdr->GetNumSeq()) { AssertMsg( false, "Trying to CalcPoseSingle with an out-of-range sequence!\n" ); return false;
//sequence = 0;
//seqdesc = ((CStudioHdr *)pStudioHdr)->pSeqdesc( sequence );
}
float s0 = 0, s1 = 0;
int i0 = Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 0, s0 ); int i1 = Studio_LocalPoseParameter( pStudioHdr, poseParameter, seqdesc, sequence, 1, s1 );
if (seqdesc.flags & STUDIO_REALTIME) { float cps = Studio_CPS( pStudioHdr, seqdesc, sequence, poseParameter ); cycle = flTime * cps; cycle = cycle - (int)cycle; } else if (seqdesc.flags & STUDIO_CYCLEPOSE) { int iPose = pStudioHdr->GetSharedPoseParameter( sequence, seqdesc.cycleposeindex ); if (iPose != -1) { /*
const mstudioposeparamdesc_t &Pose = pStudioHdr->pPoseParameter( iPose ); cycle = poseParameter[ iPose ] * (Pose.end - Pose.start) + Pose.start; */ cycle = poseParameter[ iPose ]; } else { cycle = 0.0f; } } else if (cycle < 0 || cycle >= 1) { if (seqdesc.flags & STUDIO_LOOPING) { cycle = cycle - (int)cycle; if (cycle < 0) cycle += 1; } else { cycle = clamp( cycle, 0.0f, 1.0f ); } }
if (s0 < 0.001) { if (s1 < 0.001) { if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) { bResult = false; } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); } } else if (s1 > 0.999) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 , i1 ), cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1 ), cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); } } else if (s0 > 0.999) { if (s1 < 0.001) { if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) { bResult = false; } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); } } else if (s1 > 0.999) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1, i1 ), cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1, i1+1 ), cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); } } else { if (s1 < 0.001) { if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0+1, i1 )) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); ScaleBones( pStudioHdr, q, pos, sequence, 1.0 - s0, boneMask ); } else if (PoseIsAllZeros( pStudioHdr, sequence, seqdesc, i0, i1 )) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0+1 ,i1 ), cycle, boneMask ); ScaleBones( pStudioHdr, q, pos, sequence, s0, boneMask ); } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask );
BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); } } else if (s1 > 0.999) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1+1 ), cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1+1 ), cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask ); } else if ( !anim_3wayblend.GetBool() ) { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, seqdesc.anim( i0 ,i1 ), cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0+1,i1 ), cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s0, boneMask );
CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, seqdesc.anim( i0 , i1+1), cycle, boneMask ); CalcAnimation( pStudioHdr, pos3, q3, seqdesc, sequence, seqdesc.anim( i0+1, i1+1), cycle, boneMask ); BlendBones( pStudioHdr, q2, pos2, seqdesc, sequence, q3, pos3, s0, boneMask );
BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, s1, boneMask ); } else { int iAnimIndices[3]; float weight[3];
Calc3WayBlendIndices( i0, i1, s0, s1, seqdesc, iAnimIndices, weight );
/*
char buf[256]; sprintf( buf, "%d %6.2f %d %6.2f : %6.2f %6.2f %6.2f\n", i0, s0, i1, s1, weight[0], weight[1], weight[2] ); OutputDebugString( buf ); */
if (weight[1] < 0.001) { // on diagonal
CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, iAnimIndices[0], cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, iAnimIndices[2], cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, weight[2] / (weight[0] + weight[2]), boneMask ); } else { CalcAnimation( pStudioHdr, pos, q, seqdesc, sequence, iAnimIndices[0], cycle, boneMask ); CalcAnimation( pStudioHdr, pos2, q2, seqdesc, sequence, iAnimIndices[1], cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, weight[1] / (weight[0] + weight[1]), boneMask );
CalcAnimation( pStudioHdr, pos3, q3, seqdesc, sequence, iAnimIndices[2], cycle, boneMask ); BlendBones( pStudioHdr, q, pos, seqdesc, sequence, q3, pos3, weight[2], boneMask ); } } }
g_VectorPool.Free( pos2 ); g_QuaternionPool.Free( q2 ); g_VectorPool.Free( pos3 ); g_QuaternionPool.Free( q3 );
return bResult; }
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CBoneSetup::AddSequenceLayers( BoneVector pos[], BoneQuaternion q[], mstudioseqdesc_t &seqdesc, int sequence, float cycle, float flWeight, float flTime, CIKContext *pIKContext ) { BONE_PROFILE_FUNC(); // ex: x360: 1.84ms
SNPROF_ANIM("CBoneSetup::AddSequenceLayers");
for (int i = 0; i < seqdesc.numautolayers; i++) { mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i );
if (pLayer->flags & STUDIO_AL_LOCAL) continue;
float layerCycle = cycle; float layerWeight = flWeight;
if (pLayer->start != pLayer->end) { float s = 1.0; float index;
if (!(pLayer->flags & STUDIO_AL_POSE)) { index = cycle; } else { int iSequence = m_pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); int iPose = m_pStudioHdr->GetSharedPoseParameter( iSequence, pLayer->iPose ); if (iPose != -1) { const mstudioposeparamdesc_t &Pose = ((CStudioHdr *)m_pStudioHdr)->pPoseParameter( iPose ); index = m_flPoseParameter[ iPose ] * (Pose.end - Pose.start) + Pose.start; } else { index = 0; } }
if (index < pLayer->start) continue; if (index >= pLayer->end) continue;
if (index < pLayer->peak && pLayer->start != pLayer->peak) { s = (index - pLayer->start) / (pLayer->peak - pLayer->start); } else if (index > pLayer->tail && pLayer->end != pLayer->tail) { s = (pLayer->end - index) / (pLayer->end - pLayer->tail); }
if (pLayer->flags & STUDIO_AL_SPLINE) { s = clamp( SimpleSpline( s ), 0, 1 ); // SimpleSpline imprecision can push some float values outside 0..1
}
if ((pLayer->flags & STUDIO_AL_XFADE) && (index > pLayer->tail)) { layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); } else if (pLayer->flags & STUDIO_AL_NOBLEND) { layerWeight = s; } else { layerWeight = flWeight * s; }
if (!(pLayer->flags & STUDIO_AL_POSE)) { layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); } } else if ( pLayer->start == 0 && pLayer->end == 0 && (pLayer->flags & STUDIO_AL_POSE) ) { int iSequence = m_pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); int iPose = m_pStudioHdr->GetSharedPoseParameter( iSequence, pLayer->iPose ); if (iPose == -1) continue; const mstudioposeparamdesc_t &Pose = ((CStudioHdr *)m_pStudioHdr)->pPoseParameter( iPose ); float s = m_flPoseParameter[ iPose ] * (Pose.end - Pose.start) + Pose.start;
Assert( (pLayer->tail - pLayer->peak) != 0 );
s = clamp( (s - pLayer->peak) / (pLayer->tail - pLayer->peak), 0, 1 );
if (pLayer->flags & STUDIO_AL_SPLINE) { s = clamp( SimpleSpline( s ), 0, 1 ); // SimpleSpline imprecision can push some float values outside 0..1
}
layerWeight = flWeight * s; }
int iSequence = m_pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); AccumulatePose( pos, q, iSequence, layerCycle, layerWeight, flTime, pIKContext ); } }
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CBoneSetup::AddLocalLayers( BoneVector pos[], BoneQuaternion q[], mstudioseqdesc_t &seqdesc, int sequence, float cycle, float flWeight, float flTime, CIKContext *pIKContext ) { BONE_PROFILE_FUNC(); SNPROF_ANIM("CBoneSetup::AddLocalLayers");
if (!(seqdesc.flags & STUDIO_LOCAL)) { return; }
for (int i = 0; i < seqdesc.numautolayers; i++) { mstudioautolayer_t *pLayer = seqdesc.pAutolayer( i );
if (!(pLayer->flags & STUDIO_AL_LOCAL)) continue;
float layerCycle = cycle; float layerWeight = flWeight;
if (pLayer->start != pLayer->end) { float s = 1.0;
if (cycle < pLayer->start) continue; if (cycle >= pLayer->end) continue;
if (cycle < pLayer->peak && pLayer->start != pLayer->peak) { s = (cycle - pLayer->start) / (pLayer->peak - pLayer->start); } else if (cycle > pLayer->tail && pLayer->end != pLayer->tail) { s = (pLayer->end - cycle) / (pLayer->end - pLayer->tail); }
if (pLayer->flags & STUDIO_AL_SPLINE) { s = SimpleSpline( s ); }
if ((pLayer->flags & STUDIO_AL_XFADE) && (cycle > pLayer->tail)) { layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); } else if (pLayer->flags & STUDIO_AL_NOBLEND) { layerWeight = s; } else { layerWeight = flWeight * s; }
layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); }
int iSequence = m_pStudioHdr->iRelativeSeq( sequence, pLayer->iSequence ); AccumulatePose( pos, q, iSequence, layerCycle, layerWeight, flTime, pIKContext ); } }
//-----------------------------------------------------------------------------
// Purpose: my sleezy attempt at an interface only class
//-----------------------------------------------------------------------------
IBoneSetup::IBoneSetup( const CStudioHdr *pStudioHdr, int boneMask, const float poseParameter[], IPoseDebugger *pPoseDebugger ) { m_pBoneSetup = new CBoneSetup( pStudioHdr, boneMask, poseParameter, pPoseDebugger ); }
IBoneSetup::~IBoneSetup( void ) { if ( m_pBoneSetup ) { delete m_pBoneSetup; } }
void IBoneSetup::InitPose( BoneVector pos[], BoneQuaternionAligned q[] ) { ::InitPose( m_pBoneSetup->m_pStudioHdr, pos, q, m_pBoneSetup->m_boneMask ); }
void IBoneSetup::AccumulatePose( BoneVector pos[], BoneQuaternion q[], int sequence, float cycle, float flWeight, float flTime, CIKContext *pIKContext ) { m_pBoneSetup->AccumulatePose( pos, q, sequence, cycle, flWeight, flTime, pIKContext ); }
void IBoneSetup::CalcAutoplaySequences( BoneVector pos[], BoneQuaternion q[], float flRealTime, CIKContext *pIKContext ) { m_pBoneSetup->CalcAutoplaySequences( pos, q, flRealTime, pIKContext ); }
// takes a "controllers[]" array normalized to 0..1 and adds in the adjustments to pos[], and q[].
void IBoneSetup::CalcBoneAdj( BoneVector pos[], BoneQuaternion q[], const float controllers[] ) { ::CalcBoneAdj( m_pBoneSetup->m_pStudioHdr, pos, q, controllers, m_pBoneSetup->m_boneMask ); }
CStudioHdr *IBoneSetup::GetStudioHdr() { return (CStudioHdr *)m_pBoneSetup->m_pStudioHdr; }
CBoneSetup::CBoneSetup( const CStudioHdr *pStudioHdr, int boneMask, const float poseParameter[], IPoseDebugger *pPoseDebugger ) { m_pStudioHdr = pStudioHdr; m_boneMask = boneMask; m_flPoseParameter = poseParameter; m_pPoseDebugger = pPoseDebugger; }
#if 0
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CalcPose( const CStudioHdr *pStudioHdr, CIKContext *pIKContext, BoneVector pos[], BoneQuaternionAligned q[], int sequence, float cycle, const float poseParameter[], int boneMask, float flWeight, float flTime ) { BONE_PROFILE_FUNC(); mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( sequence );
Assert( flWeight >= 0.0f && flWeight <= 1.0f ); // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up
flWeight = clamp( flWeight, 0.0f, 1.0f );
// add any IK locks to prevent numautolayers from moving extremities
CIKContext seq_ik; if (seqdesc.numiklocks) { seq_ik.Init( pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, boneMask ); // local space relative so absolute position doesn't mater
seq_ik.AddSequenceLocks( seqdesc, pos, q ); }
CalcPoseSingle( pStudioHdr, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flTime );
if ( pIKContext ) { pIKContext->AddDependencies( seqdesc, sequence, cycle, poseParameter, flWeight ); } AddSequenceLayers( pStudioHdr, pIKContext, pos, q, seqdesc, sequence, cycle, poseParameter, boneMask, flWeight, flTime );
if (seqdesc.numiklocks) { seq_ik.SolveSequenceLocks( seqdesc, pos, q ); } } #endif
extern ConVar cl_use_simd_bones; //-----------------------------------------------------------------------------
// Purpose: accumulate a pose for a single sequence on top of existing animation
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CBoneSetup::AccumulatePose( BoneVector pos[], BoneQuaternion q[], int sequence, float cycle, float flWeight, float flTime, CIKContext *pIKContext ) { BONE_PROFILE_FUNC(); // ex: x360: up to 3.6ms
#if _DEBUG
VPROF_INCREMENT_COUNTER("AccumulatePose",1); #endif
VPROF( "AccumulatePose" );
SNPROF_ANIM( "CBoneSetup::AccumulatePose" );
// Check alignment.
if ( cl_use_simd_bones.GetBool() && (! (reinterpret_cast<uintp>(q) & 0x0F) == 0 ) ) { DebuggerBreakIfDebugging(); AssertMsg(false, "Arguments to AccumulatePose are unaligned. Disaster will result.\n" ); }
Assert( flWeight >= 0.0f && flWeight <= 1.0f ); // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up
flWeight = clamp( flWeight, 0.0f, 1.0f );
if ( sequence < 0 || sequence >= m_pStudioHdr->GetNumSeq() ) { AssertMsg( false, "Trying to AccumulatePose with an out-of-range sequence!\n" ); return; }
// This should help re-use the memory for vectors/quaternions
// BoneVector pos2[MAXSTUDIOBONES];
// BoneQuaternion q2[MAXSTUDIOBONES];
BoneVector *pos2 = g_VectorPool.Alloc(); BoneQuaternionAligned * q2 = ( BoneQuaternionAligned * ) g_QuaternionPool.Alloc();
PREFETCH360( pos2, 0 ); PREFETCH360( q2, 0 );
// Trigger pose debugger
if (m_pPoseDebugger) { m_pPoseDebugger->AccumulatePose( m_pStudioHdr, pIKContext, pos, q, sequence, cycle, m_flPoseParameter, m_boneMask, flWeight, flTime ); }
mstudioseqdesc_t &seqdesc = ((CStudioHdr *)m_pStudioHdr)->pSeqdesc( sequence );
// add any IK locks to prevent extremities from moving
CIKContext seq_ik; if (seqdesc.numiklocks) { seq_ik.Init( m_pStudioHdr, vec3_angle, vec3_origin, 0.0, 0, m_boneMask ); // local space relative so absolute position doesn't mater
seq_ik.AddSequenceLocks( seqdesc, pos, q ); }
if ((seqdesc.flags & STUDIO_LOCAL) || (seqdesc.flags & STUDIO_ROOTXFORM) || (seqdesc.flags & STUDIO_WORLD_AND_RELATIVE)) { ::InitPose( m_pStudioHdr, pos2, q2, m_boneMask ); }
if (CalcPoseSingle( m_pStudioHdr, pos2, q2, seqdesc, sequence, cycle, m_flPoseParameter, m_boneMask, flTime )) {
if ( (seqdesc.flags & STUDIO_ROOTXFORM) && seqdesc.rootDriverIndex > 0 ) {
// hack: Remap the driver bone if it's coming in from an included virtual model and the indices might not match
// poseparam input is ignored for now
int nRemappedDriverBone = seqdesc.rootDriverIndex; virtualmodel_t *pVModel = m_pStudioHdr->GetVirtualModel(); if (pVModel) { const virtualgroup_t *pAnimGroup; const studiohdr_t *pAnimStudioHdr; int baseanimation = m_pStudioHdr->iRelativeAnim( sequence, 0 ); pAnimGroup = pVModel->pAnimGroup( baseanimation ); pAnimStudioHdr = ((CStudioHdr *)m_pStudioHdr)->pAnimStudioHdr( baseanimation ); nRemappedDriverBone = pAnimGroup->masterBone[nRemappedDriverBone]; }
matrix3x4a_t rootDriverXform; AngleMatrix( RadianEuler(q2[nRemappedDriverBone]), pos2[nRemappedDriverBone], rootDriverXform );
matrix3x4a_t rootToMove; AngleMatrix( RadianEuler(q[0]), pos[0], rootToMove );
matrix3x4a_t rootMoved; ConcatTransforms_Aligned( rootDriverXform, rootToMove, rootMoved );
MatrixAngles( rootMoved, q2[0], pos2[0] ); }
// this weight is wrong, the IK rules won't composite at the correct intensity
AddLocalLayers( pos2, q2, seqdesc, sequence, cycle, 1.0, flTime, pIKContext ); SlerpBones( m_pStudioHdr, q, pos, seqdesc, sequence, q2, pos2, flWeight, m_boneMask ); }
g_VectorPool.Free( pos2 ); g_QuaternionPool.Free( q2 );
if ( pIKContext ) { pIKContext->AddDependencies( seqdesc, sequence, cycle, m_flPoseParameter, flWeight ); }
AddSequenceLayers( pos, q, seqdesc, sequence, cycle, flWeight, flTime, pIKContext );
if (seqdesc.numiklocks) { seq_ik.SolveSequenceLocks( seqdesc, pos, q ); } }
//-----------------------------------------------------------------------------
// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1.
// 0 returns q1, pos1. 1 returns q2, pos2
//-----------------------------------------------------------------------------
void CalcBoneAdj( const CStudioHdr *pStudioHdr, BoneVector pos[], BoneQuaternion q[], const float controllers[], int boneMask ) { BONE_PROFILE_FUNC(); int i, j, k; float value; mstudiobonecontroller_t *pbonecontroller; Vector p0; RadianEuler a0; Quaternion q0; for (j = 0; j < pStudioHdr->numbonecontrollers(); j++) { pbonecontroller = pStudioHdr->pBonecontroller( j ); k = pbonecontroller->bone;
if (pStudioHdr->boneFlags( k ) & boneMask) { i = pbonecontroller->inputfield; value = controllers[i]; if (value < 0) value = 0; if (value > 1.0) value = 1.0; value = (1.0 - value) * pbonecontroller->start + value * pbonecontroller->end;
switch(pbonecontroller->type & STUDIO_TYPES) { case STUDIO_XR: a0.Init( value * (M_PI / 180.0), 0, 0 ); AngleQuaternion( a0, q0 ); QuaternionSM( 1.0, q0, q[k], q[k] ); break; case STUDIO_YR: a0.Init( 0, value * (M_PI / 180.0), 0 ); AngleQuaternion( a0, q0 ); QuaternionSM( 1.0, q0, q[k], q[k] ); break; case STUDIO_ZR: a0.Init( 0, 0, value * (M_PI / 180.0) ); AngleQuaternion( a0, q0 ); QuaternionSM( 1.0, q0, q[k], q[k] ); break; case STUDIO_X: pos[k].x += value; break; case STUDIO_Y: pos[k].y += value; break; case STUDIO_Z: pos[k].z += value; break; } } } }
void CalcBoneDerivatives( Vector &velocity, AngularImpulse &angVel, const matrix3x4_t &prev, const matrix3x4_t ¤t, float dt ) { float scale = 1.0; if ( dt > 0 ) { scale = 1.0 / dt; } Vector endPosition, startPosition, deltaAxis; QAngle endAngles, startAngles; float deltaAngle;
MatrixAngles( prev, startAngles, startPosition ); MatrixAngles( current, endAngles, endPosition );
velocity.x = (endPosition.x - startPosition.x) * scale; velocity.y = (endPosition.y - startPosition.y) * scale; velocity.z = (endPosition.z - startPosition.z) * scale; RotationDeltaAxisAngle( startAngles, endAngles, deltaAxis, deltaAngle ); VectorScale( deltaAxis, (deltaAngle * scale), angVel ); }
void CalcBoneVelocityFromDerivative( const QAngle &vecAngles, Vector &velocity, AngularImpulse &angVel, const matrix3x4_t ¤t ) { Vector vecLocalVelocity; AngularImpulse LocalAngVel; Quaternion q; float angle; MatrixAngles( current, q, vecLocalVelocity ); QuaternionAxisAngle( q, LocalAngVel, angle ); LocalAngVel *= angle;
matrix3x4a_t matAngles; AngleMatrix( vecAngles, matAngles ); VectorTransform( vecLocalVelocity, matAngles, velocity ); VectorTransform( LocalAngVel, matAngles, angVel ); }
//-----------------------------------------------------------------------------
// Purpose: run all animations that automatically play and are driven off of poseParameters
//-----------------------------------------------------------------------------
void CBoneSetup::CalcAutoplaySequences( BoneVector pos[], BoneQuaternion q[], float flRealTime, CIKContext *pIKContext ) { BONE_PROFILE_FUNC(); // ASSERT_NO_REENTRY();
SNPROF_ANIM( "CBoneSetup::CalcAutoplaySequences" ); int i; if ( pIKContext ) { pIKContext->AddAutoplayLocks( pos, q ); }
unsigned short *pList = NULL; int count = m_pStudioHdr->GetAutoplayList( &pList ); for (i = 0; i < count; i++) { int sequenceIndex = pList[i]; mstudioseqdesc_t &seqdesc = ((CStudioHdr *)m_pStudioHdr)->pSeqdesc( sequenceIndex ); if (seqdesc.flags & STUDIO_AUTOPLAY) { float cycle = 0; float cps = Studio_CPS( m_pStudioHdr, seqdesc, sequenceIndex, m_flPoseParameter ); cycle = flRealTime * cps; cycle = cycle - (int)cycle;
AccumulatePose( pos, q, sequenceIndex, cycle, 1.0, flRealTime, pIKContext ); } }
if ( pIKContext ) { pIKContext->SolveAutoplayLocks( pos, q ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Studio_BuildMatrices( const CStudioHdr *pStudioHdr, const QAngle& angles, const Vector& origin, const BoneVector pos[], const BoneQuaternion q[], int iBone, float flScale, matrix3x4a_t bonetoworld[MAXSTUDIOBONES], int boneMask ) { BONE_PROFILE_FUNC(); int i, j;
int chain[MAXSTUDIOBONES] = {}; int chainlength = 0;
if (iBone < -1 || iBone >= pStudioHdr->numbones()) iBone = 0;
// build list of what bones to use
if (iBone == -1) { // all bones
chainlength = pStudioHdr->numbones(); for (i = 0; i < pStudioHdr->numbones(); i++) { chain[chainlength - i - 1] = i; } } else { // only the parent bones
i = iBone; while (i != -1) { chain[chainlength++] = i; i = pStudioHdr->boneParent( i ); } }
matrix3x4a_t bonematrix; matrix3x4a_t rotationmatrix; // model to world transformation
AngleMatrix( angles, origin, rotationmatrix );
// Account for a change in scale
if ( flScale < 1.0f-FLT_EPSILON || flScale > 1.0f+FLT_EPSILON ) { Vector vecOffset; MatrixGetColumn( rotationmatrix, 3, vecOffset ); vecOffset -= origin; vecOffset *= flScale; vecOffset += origin; MatrixSetColumn( vecOffset, 3, rotationmatrix );
// Scale it uniformly
VectorScale( rotationmatrix[0], flScale, rotationmatrix[0] ); VectorScale( rotationmatrix[1], flScale, rotationmatrix[1] ); VectorScale( rotationmatrix[2], flScale, rotationmatrix[2] ); }
// check for 16 byte alignment
if ((((size_t)bonetoworld) % 16) != 0) { for (j = chainlength - 1; j >= 0; j--) { i = chain[j]; if (pStudioHdr->boneFlags(i) & boneMask) { QuaternionMatrix( q[i], pos[i], bonematrix );
if (pStudioHdr->boneParent(i) == -1) { ConcatTransforms (rotationmatrix, bonematrix, bonetoworld[i]); } else { ConcatTransforms (bonetoworld[pStudioHdr->boneParent(i)], bonematrix, bonetoworld[i]); } } } } else { for (j = chainlength - 1; j >= 0; j--) { i = chain[j]; if (pStudioHdr->boneFlags(i) & boneMask) { QuaternionMatrix( q[i], pos[i], bonematrix );
if (pStudioHdr->boneParent(i) == -1) { ConcatTransforms_Aligned (rotationmatrix, bonematrix, bonetoworld[i]); } else { ConcatTransforms_Aligned (bonetoworld[pStudioHdr->boneParent(i)], bonematrix, bonetoworld[i]); } } } } }
//-----------------------------------------------------------------------------
// Purpose: look at single column vector of another bones local transformation
// and generate a procedural transformation based on how that column
// points down the 6 cardinal axis (all negative weights are clamped to 0).
//-----------------------------------------------------------------------------
void DoAxisInterpBone( const mstudiobone_t *pbones, int ibone, CBoneAccessor &bonetoworld ) { BONE_PROFILE_FUNC(); matrix3x4a_t bonematrix; Vector control;
mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pbones[ibone].pProcedure( ); const matrix3x4_t &controlBone = bonetoworld.GetBone( pProc->control ); if (pProc && pbones[pProc->control].parent != -1) { Vector tmp; // pull out the control column
tmp.x = controlBone[0][pProc->axis]; tmp.y = controlBone[1][pProc->axis]; tmp.z = controlBone[2][pProc->axis];
// invert it back into parent's space.
VectorIRotate( tmp, bonetoworld.GetBone( pbones[pProc->control].parent ), control ); #if 0
matrix3x4a_t tmpmatrix; matrix3x4a_t controlmatrix; MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent ), tmpmatrix ); ConcatTransforms_Aligned( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix );
// pull out the control column
control.x = controlmatrix[0][pProc->axis]; control.y = controlmatrix[1][pProc->axis]; control.z = controlmatrix[2][pProc->axis]; #endif
} else { // pull out the control column
control.x = controlBone[0][pProc->axis]; control.y = controlBone[1][pProc->axis]; control.z = controlBone[2][pProc->axis]; }
Quaternion *q1, *q2, *q3; Vector *p1, *p2, *p3;
// find axial control inputs
float a1 = control.x; float a2 = control.y; float a3 = control.z; if (a1 >= 0) { q1 = &pProc->quat[0]; p1 = &pProc->pos[0]; } else { a1 = -a1; q1 = &pProc->quat[1]; p1 = &pProc->pos[1]; }
if (a2 >= 0) { q2 = &pProc->quat[2]; p2 = &pProc->pos[2]; } else { a2 = -a2; q2 = &pProc->quat[3]; p2 = &pProc->pos[3]; }
if (a3 >= 0) { q3 = &pProc->quat[4]; p3 = &pProc->pos[4]; } else { a3 = -a3; q3 = &pProc->quat[5]; p3 = &pProc->pos[5]; }
// do a three-way blend
Vector p; Quaternion v, tmp; if (a1 + a2 > 0) { float t = 1.0 / (a1 + a2 + a3); // FIXME: do a proper 3-way Quat blend!
QuaternionSlerp( *q2, *q1, a1 / (a1 + a2), tmp ); QuaternionSlerp( tmp, *q3, a3 * t, v ); VectorScale( *p1, a1 * t, p ); VectorMA( p, a2 * t, *p2, p ); VectorMA( p, a3 * t, *p3, p ); } else { QuaternionSlerp( *q3, *q3, 0, v ); // ??? no quat copy?
p = *p3; }
QuaternionMatrix( v, p, bonematrix );
ConcatTransforms (bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone )); }
//-----------------------------------------------------------------------------
// Purpose: Generate a procedural transformation based on how that another bones
// local transformation matches a set of target orientations.
//-----------------------------------------------------------------------------
void DoQuatInterpBone( const mstudiobone_t *pbones, int ibone, CBoneAccessor &bonetoworld ) { BONE_PROFILE_FUNC(); matrix3x4a_t bonematrix; Vector control;
mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pbones[ibone].pProcedure( ); if (pProc && pbones[pProc->control].parent != -1) { Quaternion src; float weight[32]; float scale = 0.0; Quaternion quat; Vector pos;
matrix3x4a_t tmpmatrix; matrix3x4a_t controlmatrix; MatrixInvert( bonetoworld.GetBone( pbones[pProc->control].parent), tmpmatrix ); ConcatTransforms_Aligned( tmpmatrix, bonetoworld.GetBone( pProc->control ), controlmatrix );
MatrixAngles( controlmatrix, src, pos ); // FIXME: make a version without pos
int i; for (i = 0; i < pProc->numtriggers; i++) { float dot = fabs( QuaternionDotProduct( pProc->pTrigger( i )->trigger, src ) ); // FIXME: a fast acos should be acceptable
dot = clamp( dot, -1, 1 ); weight[i] = 1 - (2 * acos( dot ) * pProc->pTrigger( i )->inv_tolerance ); weight[i] = MAX( 0, weight[i] ); scale += weight[i]; }
if (scale <= 0.001) // EPSILON?
{ AngleMatrix( RadianEuler(pProc->pTrigger( 0 )->quat), pProc->pTrigger( 0 )->pos, bonematrix ); ConcatTransforms ( bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone ) ); return; }
scale = 1.0 / scale;
quat.Init( 0, 0, 0, 0); pos.Init( );
for (i = 0; i < pProc->numtriggers; i++) { if (weight[i]) { float s = weight[i] * scale; mstudioquatinterpinfo_t *pTrigger = pProc->pTrigger( i );
QuaternionAlign( pTrigger->quat, quat, quat );
quat.x = quat.x + s * pTrigger->quat.x; quat.y = quat.y + s * pTrigger->quat.y; quat.z = quat.z + s * pTrigger->quat.z; quat.w = quat.w + s * pTrigger->quat.w; pos.x = pos.x + s * pTrigger->pos.x; pos.y = pos.y + s * pTrigger->pos.y; pos.z = pos.z + s * pTrigger->pos.z; } } Assert( QuaternionNormalize( quat ) != 0); QuaternionMatrix( quat, pos, bonematrix ); }
ConcatTransforms_Aligned( bonetoworld.GetBone( pbones[ibone].parent ), bonematrix, bonetoworld.GetBoneForWrite( ibone ) ); }
/*
* This is for DoAimAtBone below, was just for testing, not needed in general * but to turn it back on, uncomment this and the section in DoAimAtBone() below *
static ConVar aim_constraint( "aim_constraint", "1", FCVAR_REPLICATED, "Toggle <aimconstraint> Helper Bones" );
*/
//-----------------------------------------------------------------------------
// Purpose: Generate a procedural transformation so that one bone points at
// another point on the model
//-----------------------------------------------------------------------------
void DoAimAtBone( const mstudiobone_t *pBones, int iBone, CBoneAccessor &bonetoworld, const CStudioHdr *pStudioHdr ) { BONE_PROFILE_FUNC(); mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pBones[iBone].pProcedure();
if ( !pProc ) { return; }
/*
* Uncomment this if the ConVar above is uncommented *
if ( !aim_constraint.GetBool() ) { // If the aim constraint is turned off then just copy the parent transform
// plus the offset value
matrix3x4a_t boneToWorldSpace; MatrixCopy ( bonetoworld.GetBone( pProc->parent ), boneToWorldSpace ); Vector boneWorldPosition; VectorTransform( pProc->basepos, boneToWorldSpace, boneWorldPosition ); MatrixSetColumn( boneWorldPosition, 3, boneToWorldSpace ); MatrixCopy( boneToWorldSpace, bonetoworld.GetBoneForWrite( iBone ) );
return; }
*/
// The world matrix of the bone to change
matrix3x4a_t boneMatrix;
// Guaranteed to be unit length
const Vector &userAimVector( pProc->aimvector );
// Guaranteed to be unit length
const Vector &userUpVector( pProc->upvector );
// Get to get position of bone but also for up reference
matrix3x4a_t parentSpace; MatrixCopy ( bonetoworld.GetBone( pProc->parent ), parentSpace );
// World space position of the bone to aim
Vector aimWorldPosition; VectorTransform( pProc->basepos, parentSpace, aimWorldPosition );
// The worldspace matrix of the bone to aim at
matrix3x4a_t aimAtSpace; if ( pStudioHdr ) { // This means it's AIMATATTACH
const mstudioattachment_t &attachment( ((CStudioHdr *)pStudioHdr)->pAttachment( pProc->aim ) ); ConcatTransforms( bonetoworld.GetBone( attachment.localbone ), attachment.local, aimAtSpace ); } else { MatrixCopy( bonetoworld.GetBone( pProc->aim ), aimAtSpace ); }
Vector aimAtWorldPosition; MatrixGetColumn( aimAtSpace, 3, aimAtWorldPosition );
// make sure the redundant parent info is correct
Assert( pProc->parent == pBones[iBone].parent ); // make sure the redundant position info is correct
Assert( pProc->basepos.DistToSqr( pBones[iBone].pos ) < 0.1 );
// The aim and up data is relative to this bone, not the parent bone
matrix3x4a_t bonematrix; matrix3x4a_t boneLocalToWorld; AngleMatrix( RadianEuler(pBones[iBone].quat), pProc->basepos, bonematrix ); ConcatTransforms_Aligned( bonetoworld.GetBone( pProc->parent ), bonematrix, boneLocalToWorld );
Vector aimVector; VectorSubtract( aimAtWorldPosition, aimWorldPosition, aimVector ); VectorNormalizeFast( aimVector );
Vector axis; CrossProduct( userAimVector, aimVector, axis ); VectorNormalizeFast( axis ); Assert( 1.0f - fabs( DotProduct( userAimVector, aimVector ) ) > FLT_EPSILON ); float angle( acosf( DotProduct( userAimVector, aimVector ) ) ); Quaternion aimRotation; AxisAngleQuaternion( axis, RAD2DEG( angle ), aimRotation );
if ( ( 1.0f - fabs( DotProduct( userUpVector, userAimVector ) ) ) > FLT_EPSILON ) { matrix3x4a_t aimRotationMatrix; QuaternionMatrix( aimRotation, aimRotationMatrix );
Vector tmpV;
Vector tmp_pUp; VectorRotate( userUpVector, aimRotationMatrix, tmp_pUp ); VectorScale( aimVector, DotProduct( aimVector, tmp_pUp ), tmpV ); Vector pUp; VectorSubtract( tmp_pUp, tmpV, pUp ); VectorNormalizeFast( pUp );
Vector tmp_pParentUp; VectorRotate( userUpVector, boneLocalToWorld, tmp_pParentUp ); VectorScale( aimVector, DotProduct( aimVector, tmp_pParentUp ), tmpV ); Vector pParentUp; VectorSubtract( tmp_pParentUp, tmpV, pParentUp ); VectorNormalizeFast( pParentUp );
Quaternion upRotation; //Assert( 1.0f - fabs( DotProduct( pUp, pParentUp ) ) > FLT_EPSILON );
if( 1.0f - fabs( DotProduct( pUp, pParentUp ) ) > FLT_EPSILON ) { angle = acos( DotProduct( pUp, pParentUp ) ); CrossProduct( pUp, pParentUp, axis ); } else { angle = 0; axis = pUp; }
VectorNormalizeFast( axis ); AxisAngleQuaternion( axis, RAD2DEG( angle ), upRotation );
Quaternion boneRotation; QuaternionMult( upRotation, aimRotation, boneRotation ); QuaternionMatrix( boneRotation, aimWorldPosition, boneMatrix ); } else { QuaternionMatrix( aimRotation, aimWorldPosition, boneMatrix ); }
MatrixCopy( boneMatrix, bonetoworld.GetBoneForWrite( iBone ) ); }
//-----------------------------------------------------------------------------
// Purpose: Run the twist bone constraint code
//-----------------------------------------------------------------------------
static ConVar anim_twistbones_enabled( "anim_twistbones_enabled", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Enable procedural twist bones." ); void DoTwistBones( const mstudiobone_t *pBones, int iBone, CBoneAccessor &bonetoworld, const CStudioHdr *pStudioHdr ) { BONE_PROFILE_FUNC();
mstudiotwistbone_t *pProc = ( mstudiotwistbone_t * )pBones[iBone].pProcedure();
if ( !pProc ) return;
matrix3x4a_t mTmp;
// Compute local space version of parent bone matrix
const matrix3x4a_t &mParentToWorld = bonetoworld.GetBone( pProc->m_nParentBone ); QuaternionAligned qParent; const int nGrandParentBone = pBones[pProc->m_nParentBone].parent; if ( nGrandParentBone >= 0 ) { MatrixInvert( bonetoworld.GetBone( pBones[pProc->m_nParentBone].parent), mTmp ); matrix3x4a_t mParent; ConcatTransforms_Aligned( mTmp, mParentToWorld, mParent ); MatrixQuaternion( mParent, qParent ); } else { MatrixQuaternion( mParentToWorld, qParent ); }
// Compute local space version of child bone matrix
matrix3x4a_t mChild; MatrixInvert( mParentToWorld, mTmp ); ConcatTransforms_Aligned( mTmp, bonetoworld.GetBone( pProc->m_nChildBone ), mChild );
float *pflWeights = ( float * )stackalloc( pProc->m_nTargetCount * sizeof( float ) ); Quaternion *pqTwistBases = ( Quaternion * )stackalloc( pProc->m_nTargetCount * sizeof( Quaternion ) ); Quaternion *pqTwists = ( Quaternion * )stackalloc( pProc->m_nTargetCount * sizeof( Quaternion ) );
for ( int i = 0; i < pProc->m_nTargetCount; ++i ) { const mstudiotwistbonetarget_t *pTwistTarget = pProc->pTarget( i ); pflWeights[i] = pTwistTarget->m_flWeight; pqTwistBases[i] = pTwistTarget->m_qBaseRotation; }
V_memcpy( pqTwists, pqTwistBases, pProc->m_nTargetCount * sizeof( Quaternion ) );
if ( anim_twistbones_enabled.GetBool() ) ComputeTwistBones( pqTwists, pProc->m_nTargetCount, pProc->m_bInverse, pProc->m_vUpVector, qParent, mChild, pProc->m_qBaseInv, pflWeights, pqTwistBases );
for ( int i = 0; i < pProc->m_nTargetCount; ++i ) { const mstudiotwistbonetarget_t *pTwistTarget = pProc->pTarget( i ); AngleMatrix( RadianEuler(pqTwists[i]), pTwistTarget->m_vBaseTranslate, mTmp ); ConcatTransforms_Aligned( mParentToWorld, mTmp, bonetoworld.GetBoneForWrite( pTwistTarget->m_nBone ) ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CalcProceduralBone( const CStudioHdr *pStudioHdr, int iBone, CBoneAccessor &bonetoworld ) { const mstudiobone_t *pbones = pStudioHdr->pBone( 0 );
if ( pStudioHdr->boneFlags( iBone ) & BONE_ALWAYS_PROCEDURAL ) { switch( pbones[iBone].proctype ) { case STUDIO_PROC_AXISINTERP: DoAxisInterpBone( pbones, iBone, bonetoworld ); return true;
case STUDIO_PROC_QUATINTERP: DoQuatInterpBone( pbones, iBone, bonetoworld ); return true;
case STUDIO_PROC_AIMATBONE: DoAimAtBone( pbones, iBone, bonetoworld, NULL ); return true;
case STUDIO_PROC_AIMATATTACH: DoAimAtBone( pbones, iBone, bonetoworld, pStudioHdr ); return true;
case STUDIO_PROC_TWIST_MASTER: DoTwistBones( pbones, iBone, bonetoworld, pStudioHdr ); return true;
case STUDIO_PROC_TWIST_SLAVE: // Twist bones are grouped because many twist boens tend to share
// a large amount of common computation
// There is one TWIST_MASTER per group and any number of TWIST_SLAVE
// TWIST_SLAVE data is computed when their corresponding TWIST_MASTER
// is computed, so they don't need any explicit computation
return true;
default: return false; } } return false; }
|