You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1559 lines
43 KiB
1559 lines
43 KiB
//===== 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;
|
|
}
|