Paranoia2/game_shared/bone_setup.cpp

2383 lines
69 KiB
C++

/*
bone_setup.cpp - shared code for setup studio bones
This file is part of XashNT engine
Copyright (C) 2015 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "mathlib.h"
#include "const.h"
#include <studio.h>
#include "com_model.h"
#include "stringlib.h"
#include "bs_defs.h"
#include "ikcontext.h"
#include "iksolver.h"
//-----------------------------------------------------------------------------
// Purpose: return a sub frame rotation for a single bone
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1, float &v2 )
{
if( !panimvalue )
{
v1 = v2 = 0.0f;
return;
}
// avoids a crash reading off the end of the data
// g-cont. this solution is coming from Source 2007 and has no changes in Source 2013
if(( panimvalue->num.total == 1 ) && ( panimvalue->num.valid == 1 ))
{
v1 = v2 = panimvalue[1].value * scale;
return;
}
int k = frame;
// find the data list that has the frame
while( panimvalue->num.total <= k )
{
k -= panimvalue->num.total;
panimvalue += panimvalue->num.valid + 1;
if( panimvalue->num.total == 0 )
{
debugMsg( "^3Error:^7 ExtractAnimValue: num.total == 0!\n" );
v1 = v2 = 0.0f;
return;
}
}
// Bah, missing blend!
if( panimvalue->num.valid > k )
{
// has valid animation data
v1 = panimvalue[k+1].value * scale;
if( panimvalue->num.valid > k + 1 )
{
// has valid animation blend data
v2 = panimvalue[k+2].value * scale;
}
else
{
if( panimvalue->num.total > k + 1 ) v2 = v1; // data repeats, no blend
else v2 = panimvalue[panimvalue->num.valid+2].value * scale; // pull blend from first data block in next list
}
}
else
{
// get last valid data block
v1 = panimvalue[panimvalue->num.valid].value * scale;
if( panimvalue->num.total > k + 1 ) v2 = v1; // data repeats, no blend
else v2 = panimvalue[panimvalue->num.valid + 2].value * scale; // pull blend from first data block in next list
}
}
//-----------------------------------------------------------------------------
// Purpose: return a sub frame rotation for a single bone (usefully to decompress IK errors)
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1 )
{
if( !panimvalue )
{
v1 = 0.0f;
return;
}
int k = frame;
while( panimvalue->num.total <= k )
{
k -= panimvalue->num.total;
panimvalue += panimvalue->num.valid + 1;
if( panimvalue->num.total == 0 )
{
debugMsg( "^3Error:^7 ExtractAnimValue: num.total == 0!\n" );
v1 = 0.0f;
return;
}
}
// Bah, missing blend!
if( panimvalue->num.valid > k )
{
v1 = panimvalue[k+1].value * scale;
}
else
{
// get last valid data block
v1 = panimvalue[panimvalue->num.valid].value * scale;
}
}
void CStudioBoneSetup :: AdjustBoneAngles( mstudiobone_t *pbone, Radian &angles1, Radian &angles2 )
{
if( m_flBoneControllers == NULL )
return;
for( int j = 0; j < 3; j++ )
{
if( pbone->bonecontroller[j+3] != -1 )
{
angles1[j] += m_flBoneControllers[pbone->bonecontroller[j+3]];
angles2[j] += m_flBoneControllers[pbone->bonecontroller[j+3]];
}
}
}
void CStudioBoneSetup :: AdjustBoneOrigin( mstudiobone_t *pbone, Vector &origin )
{
if( m_flBoneControllers == NULL )
return;
for( int j = 0; j < 3; j++ )
{
if( pbone->bonecontroller[j] != -1 )
{
origin[j] += m_flBoneControllers[pbone->bonecontroller[j]];
}
}
}
Vector4D CStudioBoneSetup :: CalcBoneQuaternion( int frame, float s, int flags, mstudiobone_t *pbone, mstudioboneinfo_t *pinfo, mstudioanim_t *panim )
{
Radian angles1, angles2;
Vector4D q1, q2, q;
if( !FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO ))
pinfo = NULL;
if( s > 0.001f )
{
ExtractAnimValue( frame, pAnimvalue( panim, 3 ), pbone->scale[3], angles1.x, angles2.x );
ExtractAnimValue( frame, pAnimvalue( panim, 4 ), pbone->scale[4], angles1.y, angles2.y );
ExtractAnimValue( frame, pAnimvalue( panim, 5 ), pbone->scale[5], angles1.z, angles2.z );
if( !FBitSet( flags, STUDIO_DELTA ))
{
angles1.x = angles1.x + pbone->value[3];
angles1.y = angles1.y + pbone->value[4];
angles1.z = angles1.z + pbone->value[5];
angles2.x = angles2.x + pbone->value[3];
angles2.y = angles2.y + pbone->value[4];
angles2.z = angles2.z + pbone->value[5];
}
AdjustBoneAngles( pbone, angles1, angles2 );
if( angles1 != angles2 )
{
AngleQuaternion( angles1, q1 );
AngleQuaternion( angles2, q2 );
QuaternionBlend( q1, q2, s, q );
}
else AngleQuaternion( angles1, q );
}
else
{
ExtractAnimValue( frame, pAnimvalue( panim, 3 ), pbone->scale[3], angles1.x );
ExtractAnimValue( frame, pAnimvalue( panim, 4 ), pbone->scale[4], angles1.y );
ExtractAnimValue( frame, pAnimvalue( panim, 5 ), pbone->scale[5], angles1.z );
angles2 = g_radZero; // dummy
if( !FBitSet( flags, STUDIO_DELTA ))
{
angles1.x = angles1.x + pbone->value[3];
angles1.y = angles1.y + pbone->value[4];
angles1.z = angles1.z + pbone->value[5];
}
AdjustBoneAngles( pbone, angles1, angles2 );
AngleQuaternion( angles1, q );
}
// align to unified bone
if( !FBitSet( flags, STUDIO_DELTA ) && FBitSet( pbone->flags, BONE_FIXED_ALIGNMENT ) && ( pinfo != NULL ))
{
QuaternionAlign( pinfo->qAlignment, q, q );
}
return q;
}
//-----------------------------------------------------------------------------
// Purpose: return a sub frame position for a single bone
//-----------------------------------------------------------------------------
Vector CStudioBoneSetup :: CalcBonePosition( int frame, float s, int flags, mstudiobone_t *pbone, mstudioanim_t *panim )
{
Vector origin1, origin2;
Vector pos;
if( s > 0.001f )
{
ExtractAnimValue( frame, pAnimvalue( panim, 0 ), pbone->scale[0], origin1.x, origin2.x );
ExtractAnimValue( frame, pAnimvalue( panim, 1 ), pbone->scale[1], origin1.y, origin2.y );
ExtractAnimValue( frame, pAnimvalue( panim, 2 ), pbone->scale[2], origin1.z, origin2.z );
if( origin1 != origin2 )
{
InterpolateOrigin( origin1, origin2, pos, s );
}
else pos = origin1;
}
else
{
ExtractAnimValue( frame, pAnimvalue( panim, 0 ), pbone->scale[0], origin1.x );
ExtractAnimValue( frame, pAnimvalue( panim, 1 ), pbone->scale[1], origin1.y );
ExtractAnimValue( frame, pAnimvalue( panim, 2 ), pbone->scale[2], origin1.z );
pos = origin1;
}
if( !FBitSet( flags, STUDIO_DELTA ))
{
pos.x = pos.x + pbone->value[0];
pos.y = pos.y + pbone->value[1];
pos.z = pos.z + pbone->value[2];
}
AdjustBoneOrigin( pbone, pos );
return pos;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcIKError( const mstudioikerror_t *panim, int frame, float s, Vector &pos, Vector4D &q )
{
Radian angles1, angles2;
Vector origin1, origin2;
Vector4D q1, q2;
if( s > 0.0001f )
{
ExtractAnimValue( frame, pAnimvalue( panim, 0 ), panim->scale[0], origin1.x, origin2.x );
ExtractAnimValue( frame, pAnimvalue( panim, 1 ), panim->scale[1], origin1.y, origin2.y );
ExtractAnimValue( frame, pAnimvalue( panim, 2 ), panim->scale[2], origin1.z, origin2.z );
if( origin1 != origin2 )
{
InterpolateOrigin( origin1, origin2, pos, s );
}
else pos = origin1;
ExtractAnimValue( frame, pAnimvalue( panim, 3 ), panim->scale[3], angles1.x, angles2.x );
ExtractAnimValue( frame, pAnimvalue( panim, 4 ), panim->scale[4], angles1.y, angles2.y );
ExtractAnimValue( frame, pAnimvalue( panim, 5 ), panim->scale[5], angles1.z, angles2.z );
if( angles1 != angles2 )
{
AngleQuaternion( angles1, q1 );
AngleQuaternion( angles2, q2 );
QuaternionBlend( q1, q2, s, q );
}
else AngleQuaternion( angles1, q );
}
else
{
ExtractAnimValue( frame, pAnimvalue( panim, 0 ), panim->scale[0], origin1.x );
ExtractAnimValue( frame, pAnimvalue( panim, 1 ), panim->scale[1], origin1.y );
ExtractAnimValue( frame, pAnimvalue( panim, 2 ), panim->scale[2], origin1.z );
pos = origin1;
ExtractAnimValue( frame, pAnimvalue( panim, 3 ), panim->scale[3], angles1.x );
ExtractAnimValue( frame, pAnimvalue( panim, 4 ), panim->scale[4], angles1.y );
ExtractAnimValue( frame, pAnimvalue( panim, 5 ), panim->scale[5], angles1.z );
AngleQuaternion( angles1, q );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
mstudioanim_t *CStudioBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc )
{
mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup;
if( pseqdesc->seqgroup == 0 )
return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex);
return NULL; // base implementation can't lookup for sequence groups
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
mstudioanim_t *CStudioBoneSetup :: FetchAnimation( mstudioseqdesc_t *pseqdesc, int animation )
{
mstudioanim_t *panim = (mstudioanim_t *)GetAnimSourceData( pseqdesc );
if( animation < 0 || animation > ( pseqdesc->numblends - 1 ))
return panim;
panim += animation * m_pStudioHeader->numbones;
return panim;
}
//-----------------------------------------------------------------------------
// Purpose: returns animation description. Notice: may return NULL
//-----------------------------------------------------------------------------
mstudioanimdesc_t *CStudioBoneSetup :: FetchAnimDesc( mstudioseqdesc_t *pseqdesc, int animation )
{
static mstudioanimdesc_t baseDesc; // for backward compatibility
if( pseqdesc->animdescindex <= 0 || pseqdesc->animdescindex >= m_pStudioHeader->length )
{
Q_strncpy( baseDesc.label, pseqdesc->label, sizeof( baseDesc.label ));
baseDesc.numframes = pseqdesc->numframes;
baseDesc.flags = pseqdesc->flags;
baseDesc.fps = pseqdesc->fps;
return &baseDesc;
}
mstudioanimdesc_t *panimdesc = (mstudioanimdesc_t *)((byte *)m_pStudioHeader + pseqdesc->animdescindex);
if( animation < 0 || animation > ( pseqdesc->numblends - 1 ))
return panimdesc; // pointer to first anim description
panimdesc += animation;
return panimdesc;
}
//-----------------------------------------------------------------------------
// Purpose: Find and decode a sub-frame of animation
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcAnimation( Vector pos[], Vector4D q[], mstudioseqdesc_t *pseqdesc, int animation, float cycle )
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
mstudioanimdesc_t *animdesc = FetchAnimDesc( pseqdesc, animation );
mstudioanim_t *panim = FetchAnimation( pseqdesc, animation );
mstudioboneinfo_t *pboneinfo = NULL;
if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO ))
pboneinfo = (mstudioboneinfo_t *)((byte *)pbone + m_pStudioHeader->numbones * sizeof( mstudiobone_t ));
float fFrame = cycle * (animdesc->numframes - 1);
int iFrame = (int)fFrame;
float s = (fFrame - iFrame); // cut fractional part
const float *pweight = pBoneweight( pseqdesc );
// BUGBUG: the sequence, the anim, and the model can have all different bone mappings.
for( int i = 0; i < m_pStudioHeader->numbones; i++, pbone++, pboneinfo++, panim++ )
{
if( pweight[i] <= 0.0f || !IsBoneUsed( pbone ))
continue;
q[i] = CalcBoneQuaternion( iFrame, s, animdesc->flags, pbone, pboneinfo, panim );
pos[i] = CalcBonePosition( iFrame, s, animdesc->flags, pbone, panim );
}
}
//-----------------------------------------------------------------------------
// Purpose: Inter-animation blend. Assumes both types are identical.
// blend together q1,pos1 with q2,pos2. Return result in q1,pos1.
// 0 returns q1, pos1. 1 returns q2, pos2
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: BlendBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s )
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
const float *pweight = pBoneweight( pseqdesc );
int i;
if( s <= 0.0f )
{
return;
}
else if( s >= 1.0 )
{
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
if( pweight[i] <= 0.0f || !IsBoneUsed( pbone + i ))
continue;
pos1[i] = pos2[i];
q1[i] = q2[i];
}
return;
}
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
if( pweight[i] > 0.0f )
{
if( FBitSet( pbone[i].flags, BONE_FIXED_ALIGNMENT ))
QuaternionBlendNoAlign( q1[i], q2[i], s, q1[i] );
else QuaternionBlend( q1[i], q2[i], s, q1[i] );
InterpolateOrigin( pos1[i], pos2[i], pos1[i], s );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1.
// 0 returns q1, pos1. 1 returns q2, pos2
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: SlerpBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s )
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
const float *pweight = pBoneweight( pseqdesc );
float s2;
if( s <= 0.0f )
{
return;
}
else if( s > 1.0f )
{
s = 1.0f;
}
if( FBitSet( pseqdesc->flags, STUDIO_WORLD ))
{
WorldSpaceSlerp( q1, pos1, pseqdesc, q2, pos2, s );
return;
}
if( FBitSet( pseqdesc->flags, STUDIO_DELTA ))
{
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
{
// skip unused bones
if( !IsBoneUsed( pbone + i ))
continue;
s2 = s * pweight[i]; // blend in based on this bones weight
if( s2 <= 0.0f ) continue;
if( FBitSet( pseqdesc->flags, STUDIO_POST ))
{
QuaternionMA( q1[i], s2, q2[i], q1[i] );
// FIXME: are these correct?
pos1[i] = pos1[i] + pos2[i] * s2;
}
else
{
QuaternionSM( s2, q2[i], q1[i], q1[i] );
// FIXME: are these correct?
pos1[i] = pos1[i] + pos2[i] * s2;
}
}
}
else
{
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
{
// skip unused bones
if( !IsBoneUsed( pbone + i ))
continue;
s2 = s * pweight[i]; // blend in based on this bones weight
if( s2 <= 0.0f ) continue;
if( FBitSet( pbone[i].flags, BONE_FIXED_ALIGNMENT ))
QuaternionSlerpNoAlign( q1[i], q2[i], s2, q1[i] );
else QuaternionSlerp( q1[i], q2[i], s2, q1[i] );
InterpolateOrigin( pos1[i], pos2[i], pos1[i], s2 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: blend together in world space q1,pos1 with q2,pos2. Return result in q1,pos1.
// 0 returns q1, pos1. 1 returns q2, pos2
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: WorldSpaceSlerp( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s )
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
const float *pweight = pBoneweight( pseqdesc );
float s1; // weight of parent for q2, pos2
float s2; // weight for q2, pos2
// make fake root transform
matrix3x4 rootXform;
rootXform.Identity();
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
{
// skip unused bones
if( !IsBoneUsed( pbone + i ))
continue;
int n = pbone[i].parent;
s1 = 0.0f;
s2 = s * pweight[i]; // blend in based on this bones weight
if( n != -1 ) s1 = s * pweight[n];
if( s1 == 1.0f && s2 == 1.0f )
{
pos1[i] = pos2[i];
q1[i] = q2[i];
}
else if( s2 > 0.0f )
{
Vector4D srcQ, dstQ;
Vector srcPos, dstPos;
Vector4D targetQ;
Vector targetPos;
BuildBoneChain( rootXform, pos1, q1, i, dstBoneToWorld );
BuildBoneChain( rootXform, pos2, q2, i, srcBoneToWorld );
srcQ = srcBoneToWorld[i].GetQuaternion();
dstQ = dstBoneToWorld[i].GetQuaternion();
srcPos = srcBoneToWorld[i].GetOrigin();
dstPos = dstBoneToWorld[i].GetOrigin();
QuaternionSlerp( dstQ, srcQ, s2, targetQ );
targetBoneToWorld[i] = matrix3x4( dstPos, targetQ );
// back solve
if( n == -1 )
{
q1[i] = targetBoneToWorld[i].GetQuaternion();
}
else
{
matrix3x4 worldToBone = targetBoneToWorld[n].Invert();
matrix3x4 local = worldToBone.ConcatTransforms( targetBoneToWorld[i] );
q1[i] = local.GetQuaternion();
// blend bone lengths (local space)
InterpolateOrigin( pos1[i], pos2[i], pos1[i], s2 );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: build boneToWorld transforms for a specific bone
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: BuildBoneChain( const matrix3x4 &rootxform, const Vector pos[], const Vector4D q[], int iBone, matrix3x4 *pBoneToWorld, byte *pBoneSet )
{
mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
matrix3x4 bonematrix = matrix3x4( pos[iBone], q[iBone] );
int iParent = pbones[iBone].parent;
if( pBoneSet && pBoneSet[iBone] )
return;
if( iParent == -1 )
{
pBoneToWorld[iBone] = rootxform.ConcatTransforms( bonematrix );
}
else
{
// evil recursive!!!
BuildBoneChain( rootxform, pos, q, iParent, pBoneToWorld, pBoneSet );
pBoneToWorld[iBone] = pBoneToWorld[iParent].ConcatTransforms( bonematrix );
}
if( pBoneSet ) pBoneSet[iBone] = 1;
}
//-----------------------------------------------------------------------------
// Purpose: turn a specific bones boneToWorld transform into a pos and q in parents bonespace
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: SolveBone( int iBone, matrix3x4 *pBoneToWorld, Vector pos[], Vector4D q[] )
{
mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
int iParent = pbones[iBone].parent;
matrix3x4 worldToBone = pBoneToWorld[iParent].Invert();
matrix3x4 local = worldToBone.ConcatTransforms( pBoneToWorld[iBone] );
q[iBone] = local.GetQuaternion();
pos[iBone] = local.GetOrigin();
}
//-----------------------------------------------------------------------------
// Purpose: for a 2 bone chain, find the IK solution and reset the matrices
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: SolveIK( const mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4 *pBoneToWorld )
{
const mstudioiklink_t *link0 = pIKLink( pikchain, 0 );
const mstudioiklink_t *link1 = pIKLink( pikchain, 1 );
const mstudioiklink_t *link2 = pIKLink( pikchain, 2 );
if( link0->kneeDir.LengthSqr() > 0.0f )
{
Vector targetKneeDir, targetKneePos;
// FIXME: knee length should be as long as the legs
targetKneeDir = pBoneToWorld[link0->bone].VectorRotate( link0->kneeDir );
targetKneePos = pBoneToWorld[link1->bone].GetOrigin();
return SolveIK( link0->bone, link1->bone, link2->bone, targetFoot, targetKneePos, targetKneeDir, pBoneToWorld );
}
return SolveIK( link0->bone, link1->bone, link2->bone, targetFoot, pBoneToWorld );
}
//-----------------------------------------------------------------------------
// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4 *pBoneToWorld )
{
Vector worldFoot, worldKnee, worldThigh;
worldThigh = pBoneToWorld[iThigh].GetOrigin();
worldKnee = pBoneToWorld[iKnee].GetOrigin();
worldFoot = pBoneToWorld[iFoot].GetOrigin();
// debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 );
// debugLine( worldKnee, worldFoot, 0, 0, 255, true, 0 );
Vector ikFoot, ikKnee;
ikFoot = targetFoot - worldThigh;
ikKnee = worldKnee - worldThigh;
float l1 = (worldKnee - worldThigh).Length();
float l2 = (worldFoot - worldKnee).Length();
float l3 = (worldFoot - worldThigh).Length();
// leg too straight to figure out knee?
if( l3 > (l1 + l2) * KNEEMAX_EPSILON )
{
return false;
}
Vector ikHalf = (worldFoot-worldThigh) * (l1 / l3);
// FIXME: what to do when the knee completely straight?
Vector ikKneeDir = (ikKnee - ikHalf).Normalize();
return SolveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld );
}
//-----------------------------------------------------------------------------
// Purpose: Realign the matrix so that its X axis points along the desired axis.
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: AlignIKMatrix( matrix3x4 &mMat, const Vector &vAlignTo )
{
Vector tmp1, tmp2, tmp3;
// Column 0 (X) becomes the vector.
tmp1 = vAlignTo.Normalize();
mMat.SetForward( tmp1 );
// Column 1 (Y) is the cross of the vector and column 2 (Z).
tmp3 = mMat.GetUp();
tmp2 = CrossProduct( tmp3, tmp1 ).Normalize();
// FIXME: check for X being too near to Z
mMat.SetRight( tmp2 );
// Column 2 (Z) is the cross of columns 0 (X) and 1 (Y).
tmp3 = CrossProduct( tmp1, tmp2 );
mMat.SetUp( tmp3 );
}
//-----------------------------------------------------------------------------
// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4 *pBoneToWorld )
{
Vector worldFoot, worldKnee, worldThigh;
worldThigh = pBoneToWorld[iThigh].GetOrigin();
worldKnee = pBoneToWorld[iKnee].GetOrigin();
worldFoot = pBoneToWorld[iFoot].GetOrigin();
// debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 );
// debugLine( worldThigh, worldThigh + targetKneeDir, 0, 0, 255, true, 0 );
// debugLine( worldKnee, targetKnee, 0, 0, 255, true, 0 );
Vector ikFoot, ikTargetKnee, ikKnee;
ikFoot = targetFoot - worldThigh;
ikKnee = targetKneePos - worldThigh;
float l1 = (worldKnee-worldThigh).Length();
float l2 = (worldFoot-worldKnee).Length();
// exaggerate knee targets for legs that are nearly straight
// FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed
float d = (targetFoot - worldThigh).Length() - Q_min( l1, l2 );
d = Q_max( l1 + l2, d );
// FIXME: too short knee directions cause trouble
d = d * 100.0f;
ikTargetKnee = ikKnee + targetKneeDir * d;
// debugLine( worldKnee, worldThigh + ikTargetKnee, 0, 0, 255, true, 0 );
int color[3] = { 0, 255, 0 };
// too far away? (0.9998 is about 1 degree)
if( ikFoot.Length() > ( l1 + l2 ) * KNEEMAX_EPSILON )
{
ikFoot = ikFoot.Normalize();
ikFoot *= (l1 + l2) * KNEEMAX_EPSILON;
color[0] = 255; color[1] = 0; color[2] = 0;
}
// too close?
// limit distance to about an 80 degree knee bend
float minDist = Q_max( fabs( l1 - l2 ) * 1.15f, Q_min( l1, l2 ) * 0.15f );
if( ikFoot.Length() < minDist )
{
// too close to get an accurate vector, just use original vector
ikFoot = (worldFoot - worldThigh);
ikFoot = ikFoot.Normalize();
ikFoot *= minDist;
}
CIKSolver ik; // heart of all inverse kinematics
if( ik.solve( l1, l2, ikFoot, ikTargetKnee, ikKnee ))
{
matrix3x4& mWorldThigh = pBoneToWorld[iThigh];
matrix3x4& mWorldKnee = pBoneToWorld[iKnee];
matrix3x4& mWorldFoot = pBoneToWorld[iFoot];
// debugLine( worldThigh, ikKnee + worldThigh, 255, 0, 0, true, 0 );
// debugLine( ikKnee + worldThigh, ikFoot + worldThigh, 255, 0, 0, true,0 );
// debugLine( worldThigh, ikKnee + worldThigh, color[0], color[1], color[2], true, 0 );
// debugLine( ikKnee + worldThigh, ikFoot + worldThigh, color[0], color[1], color[2], true,0 );
// build transformation matrix for thigh
AlignIKMatrix( mWorldThigh, ikKnee );
AlignIKMatrix( mWorldKnee, ikFoot - ikKnee );
mWorldKnee[3][0] = ikKnee.x + worldThigh.x;
mWorldKnee[3][1] = ikKnee.y + worldThigh.y;
mWorldKnee[3][2] = ikKnee.z + worldThigh.z;
mWorldFoot[3][0] = ikFoot.x + worldThigh.x;
mWorldFoot[3][1] = ikFoot.y + worldThigh.y;
mWorldFoot[3][2] = ikFoot.z + worldThigh.z;
return true;
}
else
{
#if 0
debugLine( worldThigh, worldThigh + ikKnee, 255, 0, 0, true, 0 );
debugLine( worldThigh + ikKnee, worldThigh + ikFoot, 255, 0, 0, true, 0 );
debugLine( worldThigh + ikFoot, worldThigh, 255, 0, 0, true, 0 );
debugLine( worldThigh + ikKnee, worldThigh + ikTargetKnee, 255, 0, 0, true, 0 );
#endif
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: InitPose( Vector pos[], Vector4D q[] )
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
mstudioboneinfo_t *pboneinfo = (mstudioboneinfo_t *)((byte *)pbone + m_pStudioHeader->numbones * sizeof( mstudiobone_t ));
for( int i = 0; i < m_pStudioHeader->numbones; i++, pbone++ )
{
// skip unused bones
if( !IsBoneUsed( pbone + i ))
continue;
// check if we can use aligned quaternion instead of euler angles
if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) q[i] = pboneinfo[i].quat;
else AngleQuaternion( Radian( pbone->value[3], pbone->value[4], pbone->value[5] ), q[i] );
pos[i] = Vector( pbone->value ); // grab three first values
}
}
//-----------------------------------------------------------------------------
// 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]
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: Calc9WayBlendIndices( int i0, int i1, float s0, float s1, const mstudioseqdesc_t *pseqdesc, int *pAnimIndices, float *pWeight )
{
// 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
if( bEven )
{
// TL to BR
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;
}
}
else
{
float flTotal = s0 + s1;
// BL to TR
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] = iAnimBlend( pseqdesc, i0 + x1, i1 + y1 );
pAnimIndices[1] = iAnimBlend( pseqdesc, i0 + x2, i1 + y2 );
pAnimIndices[2] = iAnimBlend( pseqdesc, i0 + x3, i1 + y3 );
// clamp the diagonal
if( pWeight[1] < 0.001f ) pWeight[1] = 0.0f;
pWeight[2] = 1.0f - pWeight[0] - pWeight[1];
}
//-----------------------------------------------------------------------------
// Purpose: Calculates default values for the pose parameters
// Output: fills in an array
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcDefaultPoseParameters( float flPoseParams[] )
{
int nPoseCount = CountPoseParameters();
for( int i = 0; i < MAXSTUDIOPOSEPARAM; i++ )
{
// default to middle of the pose parameter range
flPoseParams[i] = 0.5f;
if( i < nPoseCount )
{
const mstudioposeparamdesc_t *pPose = pPoseParameter( i );
// want to try for a zero state. If one doesn't exist set it to .5 by default.
if( pPose->start < 0.0f && pPose->end > 0.0f )
{
float flPoseDelta = pPose->end - pPose->start;
flPoseParams[i] = -pPose->start / flPoseDelta;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: resolve a global pose parameter to the specific setting for this sequence
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: LocalPoseParameter( mstudioseqdesc_t *pseqdesc, int iLocalIndex, float &flSetting, int &index )
{
// first check for traditional GoldSource blenders
if( !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE ))
{
flSetting = m_flPoseParams[iLocalIndex];
index = 0; // unused
return;
}
else
{
int iPose = pseqdesc->blendtype[iLocalIndex];
if( iPose == -1 )
{
flSetting = 0;
index = 0;
return;
}
const mstudioposeparamdesc_t *pPose = pPoseParameter( iPose );
if( pPose == NULL )
{
flSetting = 0;
index = 0;
return;
}
float flValue = m_flPoseParams[iPose];
if( pPose->loop )
{
float wrap = (pPose->start + pPose->end) / 2.0 + pPose->loop / 2.0;
float shift = pPose->loop - wrap;
flValue = flValue - pPose->loop * floor((flValue + shift) / pPose->loop);
}
if( pseqdesc->posekeyindex == 0 )
{
float flLocalStart = ((float)pseqdesc->blendstart[iLocalIndex] - pPose->start) / (pPose->end - pPose->start);
float flLocalEnd = ((float)pseqdesc->blendend[iLocalIndex] - pPose->start) / (pPose->end - pPose->start);
// convert into local range
flSetting = (flValue - flLocalStart) / (flLocalEnd - flLocalStart);
// clamp. This shouldn't ever need to happen if it's looping.
flSetting = bound( 0.0f, flSetting, 1.0f );
index = 0;
if( pseqdesc->groupsize[iLocalIndex] > 2 )
{
// estimate index
index = (int)(flSetting * (pseqdesc->groupsize[iLocalIndex] - 1));
if( index == pseqdesc->groupsize[iLocalIndex] - 1 )
index = pseqdesc->groupsize[iLocalIndex] - 2;
flSetting = flSetting * (pseqdesc->groupsize[iLocalIndex] - 1) - index;
}
}
else
{
flValue = flValue * (pPose->end - pPose->start) + pPose->start;
index = 0;
// FIXME: this shouldn't be a linear search
while( 1 )
{
flSetting = (flValue - flPoseKey( pseqdesc, iLocalIndex, index ));
flSetting /= (flPoseKey( pseqdesc, iLocalIndex, index + 1 ) - flPoseKey( pseqdesc, iLocalIndex, index ));
if( index < pseqdesc->groupsize[iLocalIndex] - 2 && flSetting > 1.0f )
{
index++;
continue;
}
break;
}
// clamp.
flSetting = bound( 0.0f, flSetting, 1.0f );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: returns array of animations and weightings for a sequence based on current pose parameters
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: LocalSeqAnims( int sequence, mstudioanimdesc_t *panim[4], float *weight )
{
if( !m_pStudioHeader || sequence < 0 || sequence >= m_pStudioHeader->numseq )
{
weight[0] = weight[1] = 0.0f;
weight[2] = weight[3] = 0.0f;
return;
}
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
float s0 = 0.0f, s1 = 0.0f;
int i0 = 0, i1 = 0;
LocalPoseParameter( pseqdesc, 0, s0, i0 );
LocalPoseParameter( pseqdesc, 1, s1, i1 );
panim[0] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ));
weight[0] = (1.0f - s0) * (1.0f - s1);
panim[1] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ));
weight[1] = (s0) * (1.0f - s1);
panim[2] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ));
weight[2] = (1.0f - s0) * (s1);
panim[3] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ));
weight[3] = (s0) * (s1);
}
//-----------------------------------------------------------------------------
// Purpose: returns max frame number for a sequence
//-----------------------------------------------------------------------------
int CStudioBoneSetup :: LocalMaxFrame( int sequence )
{
mstudioanimdesc_t *panim[4];
float weight[4];
float maxFrame = 0;
LocalSeqAnims( sequence, panim, weight );
for( int i = 0; i < 4; i++ )
{
if( weight[i] > 0.0f )
{
maxFrame += panim[i]->numframes * weight[i];
}
}
if( maxFrame > 1 )
maxFrame -= 1;
// FIXME: why does the weights sometimes not exactly add it 1.0 and this sometimes rounds down?
return (maxFrame + 0.01);
}
//-----------------------------------------------------------------------------
// Purpose: returns frames per second of a sequence
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: LocalFPS( int sequence )
{
mstudioanimdesc_t *panim[4];
float weight[4];
float t = 0.0f;
LocalSeqAnims( sequence, panim, weight );
for( int i = 0; i < 4; i++ )
{
if( weight[i] > 0.0f )
{
t += panim[i]->fps * weight[i];
}
}
return t;
}
//-----------------------------------------------------------------------------
// Purpose: returns cycles per second of a sequence (cycles/second)
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: LocalCPS( int sequence )
{
mstudioanimdesc_t *panim[4];
float weight[4];
float t = 0.0f;
LocalSeqAnims( sequence, panim, weight );
for( int i = 0; i < 4; i++ )
{
if( weight[i] > 0.0f && panim[i]->numframes > 1 )
{
t += (panim[i]->fps / (panim[i]->numframes - 1)) * weight[i];
}
}
return t;
}
//-----------------------------------------------------------------------------
// Purpose: converts a ranged bone controller value into a 0..1 encoded value
// Output: ctlValue contains 0..1 encoding.
// returns clamped ranged value
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: SetController( int iController, float flValue, float &ctlValue )
{
if( !m_pStudioHeader )
return flValue;
mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex);
int i;
// find first controller that matches the index
for( i = 0; i < m_pStudioHeader->numbonecontrollers; i++, pbonecontroller++ )
{
if( pbonecontroller->index == iController )
break;
}
if( i >= m_pStudioHeader->numbonecontrollers )
{
ctlValue = 0.0f;
return flValue;
}
// wrap 0..360 if it's a rotational controller
if( FBitSet( pbonecontroller->type, STUDIO_XR|STUDIO_YR|STUDIO_ZR ))
{
// ugly hack, invert value if end < start
if( pbonecontroller->end < pbonecontroller->start )
flValue = -flValue;
// does the controller not wrap?
if( pbonecontroller->start + 359.0f >= pbonecontroller->end )
{
if( flValue > (( pbonecontroller->start + pbonecontroller->end) / 2.0f ) + 180.0f )
flValue = flValue - 360.0f;
if( flValue < (( pbonecontroller->start + pbonecontroller->end) / 2.0f ) - 180.0f )
flValue = flValue + 360.0f;
}
else
{
if( flValue > 360.0f )
flValue = flValue - (int)(flValue / 360.0f) * 360.0f;
else if( flValue < 0.0f )
flValue = flValue + (int)((flValue / -360.0f) + 1.0f) * 360.0f;
}
}
ctlValue = (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start);
ctlValue = bound( 0.0f, ctlValue, 1.0f );
float flReturnVal = ((1.0f - ctlValue) * pbonecontroller->start + ctlValue * pbonecontroller->end);
// ugly hack, invert value if a rotational controller and end < start
if( FBitSet( pbonecontroller->type, STUDIO_XR | STUDIO_YR | STUDIO_ZR ) && pbonecontroller->end < pbonecontroller->start )
{
flReturnVal *= -1.0f;
}
return flReturnVal;
}
//-----------------------------------------------------------------------------
// Purpose: converts a 0..1 encoded bone controller value into a ranged value
// Output: returns ranged value
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: GetController( int iController, float ctlValue )
{
if( !m_pStudioHeader )
return 0.0f;
mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex);
int i;
// find first controller that matches the index
for( i = 0; i < m_pStudioHeader->numbonecontrollers; i++, pbonecontroller++ )
{
if( pbonecontroller->index == iController )
break;
}
if( i >= m_pStudioHeader->numbonecontrollers )
return 0.0f;
return ctlValue * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start;
}
//-----------------------------------------------------------------------------
// Purpose: converts a ranged pose parameter value into a 0..1 encoded value
// Output: ctlValue contains 0..1 encoding.
// returns clamped ranged value
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: SetPoseParameter( int iParameter, float flValue, float &ctlValue )
{
if( iParameter < 0 || iParameter >= CountPoseParameters( ))
return 0.0f;
const mstudioposeparamdesc_t *pPose = pPoseParameter( iParameter );
if( pPose->loop )
{
float wrap = (pPose->start + pPose->end) / 2.0f + pPose->loop / 2.0f;
float shift = pPose->loop - wrap;
flValue = flValue - pPose->loop * floor(( flValue + shift ) / pPose->loop );
}
ctlValue = (flValue - pPose->start) / (pPose->end - pPose->start);
ctlValue = bound( 0.0f, ctlValue, 1.0f );
return ctlValue * (pPose->end - pPose->start) + pPose->start;
}
//-----------------------------------------------------------------------------
// Purpose: converts a 0..1 encoded pose parameter value into a ranged value
// Output: returns ranged value
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: GetPoseParameter( int iParameter, float ctlValue )
{
if( iParameter < 0 || iParameter >= CountPoseParameters( ))
return 0.0f;
const mstudioposeparamdesc_t *pPose = pPoseParameter( iParameter );
return ctlValue * (pPose->end - pPose->start) + pPose->start;
}
//-----------------------------------------------------------------------------
// Purpose: returns length (in seconds) of a sequence (seconds/cycle)
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: LocalDuration( int sequence )
{
mstudioanimdesc_t *panim[4];
float weight[4];
float t = 0.0f;
LocalSeqAnims( sequence, panim, weight );
for( int i = 0; i < 4; i++ )
{
if( weight[i] > 0 && panim[i]->fps != 0.0f )
{
t += ((panim[i]->numframes - 1) / panim[i]->fps) * weight[i];
}
}
return t;
}
//-----------------------------------------------------------------------------
// Purpose: calculate changes in position and angle relative to the start of an animations cycle
// Output: updated position and angle, relative to the origin
// returns false if animation is not a movement animation
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPos, Vector &vecAngle )
{
float prevframe = 0;
int iLoops = 0;
vecPos.Init( );
vecAngle.Init( );
if( panim->nummovements == 0 )
return false;
if( flCycle > 1.0f )
iLoops = (int)flCycle;
else if( flCycle < 0.0f )
iLoops = (int)flCycle - 1;
flCycle = flCycle - iLoops;
float flFrame = flCycle * (panim->numframes - 1);
for( int i = 0; i < panim->nummovements; i++ )
{
const mstudiomovement_t *pmove = pMovement( panim, i );
if( pmove->endframe >= flFrame )
{
float f = (flFrame - prevframe) / (pmove->endframe - prevframe);
float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f;
vecPos = vecPos + d * pmove->vector;
vecAngle.y = vecAngle.y * (1.0f - f) + pmove->angle * f;
if( iLoops != 0 )
{
const mstudiomovement_t *pmove = pMovement( panim, panim->nummovements - 1 );
vecPos = vecPos + iLoops * pmove->position;
vecAngle.y = vecAngle.y + iLoops * pmove->angle;
}
return true;
}
else
{
prevframe = pmove->endframe;
vecPos = pmove->position;
vecAngle.y = pmove->angle;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: calculate instantaneous velocity in ips at a given point
// in the animations cycle
// Output: velocity vector, relative to identity orientation
// returns false if animation is not a movement animation
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: AnimVelocity( mstudioanimdesc_t *panim, float flCycle, Vector &vecVelocity )
{
float flFrame = flCycle * (panim->numframes - 1);
float prevframe = 0.0f;
flFrame = flFrame - (int)(flFrame / (panim->numframes - 1));
for( int i = 0; i < panim->nummovements; i++ )
{
const mstudiomovement_t *pmove = pMovement( panim, i );
if( pmove->endframe >= flFrame )
{
float f = (flFrame - prevframe) / (pmove->endframe - prevframe);
float vel = pmove->v0 * (1 - f) + pmove->v1 * f;
// scale from per block to per sec velocity
vel = vel * panim->fps / (pmove->endframe - prevframe);
vecVelocity = pmove->vector * vel;
return true;
}
else
{
prevframe = pmove->endframe;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: calculate changes in position and angle between two points in an animation cycle
// Output: updated position and angle, relative to CycleFrom being at the origin
// returns false if animation is not a movement animation
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: AnimMovement( mstudioanimdesc_t *panim, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngle )
{
if( panim->nummovements == 0 )
return false;
Vector startPos;
Vector startA;
AnimPosition( panim, flCycleFrom, startPos, startA );
Vector endPos;
Vector endA;
AnimPosition( panim, flCycleTo, endPos, endA );
Vector tmp = endPos - startPos;
deltaAngle.y = endA.y - startA.y;
deltaPos = VectorYawRotate( tmp, -startA.y );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: finds how much of an animation to play to move given linear distance
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: FindAnimDistance( mstudioanimdesc_t *panim, float flDist )
{
float prevframe = 0;
if( flDist <= 0 )
return 0.0;
for( int i = 0; i < panim->nummovements; i++ )
{
const mstudiomovement_t *pmove = pMovement( panim, i );
float flMove = (pmove->v0 + pmove->v1) * 0.5f;
if( flMove >= flDist )
{
float root1, root2;
// d = V0 * t + 1/2 (V1-V0) * t^2
if( SolveQuadratic( 0.5f * ( pmove->v1 - pmove->v0 ), pmove->v0, -flDist, root1, root2 ))
{
float cpf = 1.0f / (panim->numframes - 1); // cycles per frame
return (prevframe + root1 * (pmove->endframe - prevframe)) * cpf;
}
return 0.0f;
}
else
{
flDist -= flMove;
prevframe = pmove->endframe;
}
}
return 1.0f;
}
//-----------------------------------------------------------------------------
// Purpose: calculate changes in position and angle between two points in a sequences cycle
// Output: updated position and angle, relative to CycleFrom being at the origin
// returns false if sequence is not a movement sequence
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: SeqMovement( int sequence, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngles )
{
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
mstudioanimdesc_t *panim[4];
float weight[4];
LocalSeqAnims( sequence, panim, weight );
deltaPos = g_vecZero;
deltaAngles = g_vecZero;
bool found = false;
for( int i = 0; i < 4; i++ )
{
if( weight[i] )
{
Vector localPos = g_vecZero;
Vector localAngles = g_vecZero;
const float *pweight = pBoneweight( pseqdesc );
if( AnimMovement( panim[i], flCycleFrom, flCycleTo, localPos, localAngles ))
{
found = true;
deltaPos = deltaPos + localPos * weight[i];
// FIXME: this makes no sense
deltaAngles = deltaAngles + localAngles * weight[i];
}
else if( !FBitSet( panim[i]->flags, STUDIO_DELTA ) && panim[i]->nummovements == 0 && pweight[0] > 0.0f )
{
// found = true;
}
}
}
// simple movement from GoldSource
if( !found && pseqdesc->linearmovement != g_vecZero )
{
Vector startPos = pseqdesc->linearmovement * flCycleFrom;
Vector endPos = pseqdesc->linearmovement * flCycleTo;
deltaPos = endPos - startPos;
found = true;
}
return found;
}
//-----------------------------------------------------------------------------
// Purpose: calculate instantaneous velocity in ips at a given point in the sequence's cycle
// Output: velocity vector, relative to identity orientation
// returns false if sequence is not a movement sequence
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: SeqVelocity( int sequence, float flCycle, Vector &vecVelocity )
{
mstudioanimdesc_t *panim[4];
float weight[4];
LocalSeqAnims( sequence, panim, weight );
vecVelocity = g_vecZero;
bool found = false;
for( int i = 0; i < 4; i++ )
{
if( weight[i] )
{
Vector vecLocalVelocity;
if( AnimVelocity( panim[i], flCycle, vecLocalVelocity ))
{
vecVelocity = vecVelocity + vecLocalVelocity * weight[i];
found = true;
}
}
}
return found;
}
//-----------------------------------------------------------------------------
// Purpose: finds how much of an sequence to play to move given linear distance
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: FindSeqDistance( int sequence, float flDist )
{
mstudioanimdesc_t *panim[4];
float weight[4];
LocalSeqAnims( sequence, panim, weight );
float flCycle = 0;
for( int i = 0; i < 4; i++ )
{
if( weight[i] )
{
float flLocalCycle = FindAnimDistance( panim[i], flDist );
flCycle = flCycle + flLocalCycle * weight[i];
}
}
return flCycle;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: IKRuleWeight( const mstudioikrule_t *ikRule, const mstudioanimdesc_t *panim, float flCycle, int &iFrame, float &fraq )
{
if( ikRule->end > 1.0f && flCycle < ikRule->start )
{
flCycle = flCycle + 1.0f;
}
float value = 0.0f;
fraq = (panim->numframes - 1) * (flCycle - ikRule->start) + ikRule->iStart;
iFrame = (int)fraq;
fraq = fraq - iFrame;
if( flCycle < ikRule->start )
{
iFrame = ikRule->iStart;
fraq = 0.0f;
return 0.0f;
}
else if( flCycle < ikRule->peak )
{
value = ( flCycle - ikRule->start ) / ( ikRule->peak - ikRule->start );
}
else if( flCycle < ikRule->tail )
{
return 1.0f;
}
else if( flCycle < ikRule->end )
{
value = 1.0f - (( flCycle - ikRule->tail ) / ( ikRule->end - ikRule->tail ));
}
else
{
fraq = (panim->numframes - 1) * (ikRule->end - ikRule->start) + ikRule->iStart;
iFrame = (int)fraq;
fraq = fraq - iFrame;
}
return SimpleSpline( value );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: IKRuleWeight( ikcontextikrule_t *ikRule, float flCycle )
{
if( ikRule->end > 1.0f && flCycle < ikRule->start )
{
flCycle = flCycle + 1.0f;
}
float value = 0.0f;
if( flCycle < ikRule->start )
{
return 0.0f;
}
else if( flCycle < ikRule->peak )
{
value = ( flCycle - ikRule->start ) / ( ikRule->peak - ikRule->start );
}
else if( flCycle < ikRule->tail )
{
return 1.0f;
}
else if( flCycle < ikRule->end )
{
value = 1.0f - ((flCycle - ikRule->tail) / (ikRule->end - ikRule->tail));
}
return SimpleSpline( value );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: IKShouldLatch( ikcontextikrule_t *ikRule, float flCycle )
{
if( ikRule->end > 1.0f && flCycle < ikRule->start )
{
flCycle = flCycle + 1.0f;
}
if( flCycle < ikRule->peak )
{
return false;
}
else if( flCycle < ikRule->end )
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CStudioBoneSetup :: IKTail( ikcontextikrule_t *ikRule, float flCycle )
{
if( ikRule->end > 1.0f && flCycle < ikRule->start )
{
flCycle = flCycle + 1.0f;
}
if( flCycle <= ikRule->tail )
{
return 0.0f;
}
else if( flCycle < ikRule->end )
{
return (( flCycle - ikRule->tail ) / ( ikRule->end - ikRule->tail ));
}
return 0.0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: IKAnimError( const mstudioikrule_t *pRule, mstudioanimdesc_t *panim, float flCycle, Vector &pos, Vector4D &q, float &flWeight )
{
float fraq;
int iFrame;
flWeight = IKRuleWeight( pRule, panim, flCycle, iFrame, fraq );
flWeight = bound( 0.0f, flWeight, 1.0f );
if( pRule->type != IK_GROUND && flWeight < 0.0001f )
return false;
const mstudioikerror_t *pError = pCompressedError( pRule );
if( pError != NULL )
{
CalcIKError( pError, iFrame - pRule->iStart, fraq, pos, q );
return true;
}
// no data, disable IK rule
flWeight = 0.0f;
return false;
}
//-----------------------------------------------------------------------------
// Purpose: For a specific sequence:rule, find where it starts, stops, and what
// the estimated offset from the connection point is.
// return true if the rule is within bounds.
//-----------------------------------------------------------------------------
bool CStudioBoneSetup :: IKSequenceError( int iSeq, float flCycle, int iRule, mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t *ikRule )
{
int i;
memset( ikRule, 0, sizeof( ikcontextikrule_t ));
ikRule->start = ikRule->peak = ikRule->tail = ikRule->end = 0;
const mstudioikrule_t *prevRule = NULL;
// find overall influence
for( i = 0; i < 4; i++ )
{
if( weight[i] )
{
if( iRule >= panim[i]->numikrules || panim[i]->numikrules != panim[0]->numikrules )
return false;
const mstudioikrule_t *pRule = pIKRule( panim[i], iRule );
if( pRule == NULL )
return false;
float dt = 0.0f;
if( prevRule != NULL )
{
if( pRule->start - prevRule->start > 0.5f )
{
dt = -1.0f;
}
else if( pRule->start - prevRule->start < -0.5f )
{
dt = 1.0;
}
}
else
{
prevRule = pRule;
}
ikRule->start += (pRule->start + dt) * weight[i];
ikRule->peak += (pRule->peak + dt) * weight[i];
ikRule->tail += (pRule->tail + dt) * weight[i];
ikRule->end += (pRule->end + dt) * weight[i];
}
}
if( ikRule->start > 1.0f )
{
ikRule->start -= 1.0f;
ikRule->peak -= 1.0f;
ikRule->tail -= 1.0f;
ikRule->end -= 1.0f;
}
else if( ikRule->start < 0.0f )
{
ikRule->start += 1.0f;
ikRule->peak += 1.0f;
ikRule->tail += 1.0f;
ikRule->end += 1.0f;
}
ikRule->flWeight = IKRuleWeight( ikRule, flCycle );
if( ikRule->flWeight <= 0.001f )
{
// go ahead and allow IK_GROUND rules a virtual looping section
if( pIKRule( panim[0], iRule ) == NULL )
return false;
if(( panim[0]->flags & STUDIO_LOOPING ) && pIKRule( panim[0], iRule )->type == IK_GROUND && ikRule->end - ikRule->start > 0.75f )
{
ikRule->flWeight = 0.001f;
flCycle = ikRule->end - 0.001f;
}
else
{
return false;
}
}
ikRule->pos.Init();
ikRule->q.Init();
// find target error
float total = 0.0f;
for( i = 0; i < 4; i++ )
{
if( weight[i] )
{
Vector pos1;
Vector4D q1;
float w;
const mstudioikrule_t *pRule = pIKRule( panim[i], iRule );
if( pRule == NULL )
return false;
ikRule->chain = pRule->chain; // FIXME: this is anim local
ikRule->bone = pRule->bone; // FIXME: this is anim local
ikRule->type = pRule->type;
ikRule->slot = pRule->slot;
ikRule->height += pRule->height * weight[i];
ikRule->floor += pRule->floor * weight[i];
ikRule->radius += pRule->radius * weight[i];
ikRule->drop += pRule->drop * weight[i];
ikRule->top += pRule->top * weight[i];
// keep track of tail condition
ikRule->release += IKTail( ikRule, flCycle ) * weight[i];
// only check rules with error values
switch( ikRule->type )
{
case IK_SELF:
case IK_WORLD:
case IK_GROUND:
case IK_ATTACHMENT:
if( IKAnimError( pRule, panim[i], flCycle, pos1, q1, w ))
{
ikRule->pos = ikRule->pos + pos1 * weight[i];
QuaternionAccumulate( ikRule->q, weight[i], q1, ikRule->q );
total += weight[i];
}
break;
default:
total += weight[i];
break;
}
ikRule->latched = IKShouldLatch( ikRule, flCycle ) * ikRule->flWeight;
if( ikRule->type == IK_ATTACHMENT )
{
ikRule->iAttachment = pRule->attachment;
}
}
}
if( total <= 0.0001f )
{
return false;
}
if( total < 0.999f )
{
QuaternionScale( ikRule->q, 1.0f / total, ikRule->q );
ikRule->pos *= ( 1.0f / total );
}
ikRule->q = ikRule->q.Normalize();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcPoseSingle( Vector pos[], Vector4D q[], int sequence, float cycle )
{
static Vector pos2[MAXSTUDIOBONES];
static Vector4D q2[MAXSTUDIOBONES];
static Vector pos3[MAXSTUDIOBONES];
static Vector4D q3[MAXSTUDIOBONES];
static Vector pos4[MAXSTUDIOBONES];
static Vector4D q4[MAXSTUDIOBONES];
bool anim_4wayblend = true; // FIXME: get 9-way for gold-source
mstudioseqdesc_t *pseqdesc;
if( sequence < 0 || sequence >= m_pStudioHeader->numseq )
{
debugMsg( "^2Warning:^7 sequence %i/%i out of range for model %s\n", sequence, m_pStudioHeader->numseq, m_pStudioHeader->name );
sequence = 0;
}
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
int i0 = 0, i1 = 0;
float s0 = 0, s1 = 0;
LocalPoseParameter( pseqdesc, 0, s0, i0 );
LocalPoseParameter( pseqdesc, 1, s1, i1 );
if( FBitSet( pseqdesc->flags, STUDIO_REALTIME ))
{
float cps = LocalCPS( sequence );
cycle = m_flTime * cps;
cycle = cycle - (int)cycle;
}
else if( FBitSet( pseqdesc->flags, STUDIO_CYCLEPOSE ))
{
int iPose = bound( 0, pseqdesc->cycleposeindex, MAXSTUDIOPOSEPARAM - 1 );
cycle = m_flPoseParams[iPose];
}
else if( cycle < 0.0f || cycle >= 1.0f )
{
if( FBitSet( pseqdesc->flags, STUDIO_LOOPING ))
{
cycle = cycle - (int)cycle;
if( cycle < 0.0f ) cycle += 1.0f;
}
else
{
cycle = bound( 0.0f, cycle, 1.0f );
}
}
// GoldSource blending
if( !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE ) && ( pseqdesc->numblends > 1 ))
{
if( pseqdesc->numblends == 9 )
{
// blending is 0 - 0.5 == Left to Middle, 0.5 to 1.0 == Middle to Right
if( s1 <= 0.5f )
{
// Scale 0-0.5 blending up to 0-1.0
s1 = ( s1 * 2.0f );
if( s0 <= 0.5f )
{
// Blending is 0-127 == Top to Middle, 128 to 255 == Middle to Bottom
s0 = ( s0 * 2.0f );
// need to blend 0 - 1 - 3 - 4
CalcAnimation( pos, q, pseqdesc, 0, cycle );
CalcAnimation( pos2, q2, pseqdesc, 1, cycle );
CalcAnimation( pos3, q3, pseqdesc, 3, cycle );
CalcAnimation( pos4, q4, pseqdesc, 4, cycle );
}
else
{
// Scale 0.5-1.0 blending up to 0.0-1.0
s0 = 2.0f * ( s0 - 0.5f );
// need to blend 3 - 4 - 6 - 7
CalcAnimation( pos, q, pseqdesc, 3, cycle );
CalcAnimation( pos2, q2, pseqdesc, 4, cycle );
CalcAnimation( pos3, q3, pseqdesc, 6, cycle );
CalcAnimation( pos4, q4, pseqdesc, 7, cycle );
}
}
else
{
// Scale 0.5-1.0 blending up to 0-1.0
s1 = 2.0f * ( s1 - 0.5f );
if ( s0 <= 0.5f )
{
// Blending is 0-0.5 == Top to Middle, 0.5 to 1.0 == Middle to Bottom
s0 = ( s0 * 2.0f );
// need to blend 1 - 2 - 4 - 5
CalcAnimation( pos, q, pseqdesc, 1, cycle );
CalcAnimation( pos2, q2, pseqdesc, 2, cycle );
CalcAnimation( pos3, q3, pseqdesc, 4, cycle );
CalcAnimation( pos4, q4, pseqdesc, 5, cycle );
}
else
{
// Scale 0.5-1.0 blending up to 0-1.0
s0 = 2.0 * ( s0 - 0.5 );
// need to blend 4 - 5 - 7 - 8
CalcAnimation( pos, q, pseqdesc, 4, cycle );
CalcAnimation( pos2, q2, pseqdesc, 5, cycle );
CalcAnimation( pos3, q3, pseqdesc, 7, cycle );
CalcAnimation( pos4, q4, pseqdesc, 8, cycle );
}
}
// Spherically interpolate the bones
SlerpBones( q, pos, pseqdesc, q2, pos2, s1 );
SlerpBones( q3, pos3, pseqdesc, q4, pos4, s1 );
SlerpBones( q, pos, pseqdesc, q3, pos3, s0 );
}
else
{
CalcAnimation( pos, q, pseqdesc, 0, cycle );
CalcAnimation( pos2, q2, pseqdesc, 1, cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s0 );
if( pseqdesc->numblends == 4 )
{
CalcAnimation( pos3, q3, pseqdesc, 2, cycle );
CalcAnimation( pos4, q4, pseqdesc, 3, cycle );
BlendBones( q3, pos3, pseqdesc, q4, pos4, s0 );
BlendBones( q, pos, pseqdesc, q3, pos3, s1 );
}
}
return;
}
if( s0 < 0.001f )
{
if( s1 < 0.001f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle );
}
else if( s1 > 0.999f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle );
}
else
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s1 );
}
}
else if( s0 > 0.999f )
{
if( s1 < 0.001f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle );
}
else if( s1 > 0.999f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle );
}
else
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s1 );
}
}
else
{
if( s1 < 0.001f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s0 );
}
else if( s1 > 0.999f )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s0 );
}
else if( anim_4wayblend )
{
CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, s0 );
CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle );
CalcAnimation( pos3, q3, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle );
BlendBones( q2, pos2, pseqdesc, q3, pos3, s0 );
BlendBones( q, pos, pseqdesc, q2, pos2, s1 );
}
else
{
int iAnimIndices[3];
float weight[3];
Calc9WayBlendIndices( i0, i1, s0, s1, pseqdesc, iAnimIndices, weight );
if( weight[1] < 0.001f )
{
// on diagonal
CalcAnimation( pos, q, pseqdesc, iAnimIndices[0], cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimIndices[2], cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, weight[2] / ( weight[0] + weight[2] ));
}
else
{
CalcAnimation( pos, q, pseqdesc, iAnimIndices[0], cycle );
CalcAnimation( pos2, q2, pseqdesc, iAnimIndices[1], cycle );
BlendBones( q, pos, pseqdesc, q2, pos2, weight[1] / ( weight[0] + weight[1] ));
CalcAnimation( pos3, q3, pseqdesc, iAnimIndices[2], cycle );
BlendBones( q, pos, pseqdesc, q3, pos3, weight[2] );
}
}
}
// list is cleared
SetBoneControllers( NULL );
}
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: AddSequenceLayers( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight )
{
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
for( int i = 0; i < pseqdesc->numautolayers; i++ )
{
mstudioautolayer_t *pLayer = (mstudioautolayer_t *)((byte *)m_pStudioHeader + pseqdesc->autolayerindex) + i;
if( FBitSet( pLayer->flags, STUDIO_AL_LOCAL ))
continue;
float layerCycle = cycle;
float layerWeight = flWeight;
if( pLayer->start != pLayer->end )
{
float s = 1.0;
float index;
if( !FBitSet( pLayer->flags, STUDIO_AL_POSE ))
{
index = cycle;
}
else
{
int iSequence = pLayer->iSequence;
int iPose = pLayer->iPose;
if( iPose != -1 )
{
const mstudioposeparamdesc_t *pPose = pPoseParameter( iPose );
index = m_flPoseParams[iPose] * (pPose->end - pPose->start) + pPose->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( FBitSet( pLayer->flags, STUDIO_AL_SPLINE ))
{
s = SimpleSpline( s );
}
if( FBitSet( pLayer->flags, STUDIO_AL_XFADE ) && ( index > pLayer->tail ))
{
layerWeight = ( s * flWeight ) / ( 1.0f - flWeight + s * flWeight );
}
else if( FBitSet( pLayer->flags, STUDIO_AL_NOBLEND ))
{
layerWeight = s;
}
else
{
layerWeight = flWeight * s;
}
if( !FBitSet( pLayer->flags, STUDIO_AL_POSE ))
{
layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start);
}
}
AccumulatePose( pIKContext, pos, q, pLayer->iSequence, layerCycle, layerWeight );
}
}
//-----------------------------------------------------------------------------
// Purpose: calculate a pose for a single sequence
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: AddLocalLayers( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight )
{
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
if( !FBitSet( pseqdesc->flags, STUDIO_LOCAL ))
{
return;
}
for( int i = 0; i < pseqdesc->numautolayers; i++ )
{
mstudioautolayer_t *pLayer = (mstudioautolayer_t *)((byte *)m_pStudioHeader + pseqdesc->autolayerindex) + i;
if( !FBitSet( pLayer->flags, STUDIO_AL_LOCAL ))
continue;
float layerCycle = cycle;
float layerWeight = flWeight;
if( pLayer->start != pLayer->end )
{
float s = 1.0f;
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( FBitSet( pLayer->flags, STUDIO_AL_SPLINE ))
{
s = SimpleSpline( s );
}
if( FBitSet( pLayer->flags, STUDIO_AL_XFADE ) && ( cycle > pLayer->tail ))
{
layerWeight = ( s * flWeight ) / ( 1.0f - flWeight + s * flWeight );
}
else if( FBitSet( pLayer->flags, STUDIO_AL_NOBLEND ))
{
layerWeight = s;
}
else
{
layerWeight = flWeight * s;
}
layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start);
}
AccumulatePose( pIKContext, pos, q, pLayer->iSequence, layerCycle, layerWeight );
}
}
//-----------------------------------------------------------------------------
// Purpose: accumulate a pose for a single sequence on top of existing animation
// adds autolayers, runs local ik rukes
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: AccumulatePose( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight )
{
Vector pos2[MAXSTUDIOBONES];
Vector4D q2[MAXSTUDIOBONES];
flWeight = bound( 0.0f, flWeight, 1.0f );
if( sequence < 0 ) return;
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
// add any IK locks to prevent extremities from moving
CIKContext seq_ik;
if( pseqdesc->numiklocks )
{
// local space relative so absolute position doesn't mater
seq_ik.Init( this, g_vecZero, g_vecZero, 0.0f, 0 );
seq_ik.AddSequenceLocks( pseqdesc, pos, q );
}
if( FBitSet( pseqdesc->flags, STUDIO_LOCAL ))
{
InitPose( pos2, q2 );
}
CalcPoseSingle( pos2, q2, sequence, cycle );
// this weight is wrong, the IK rules won't composite at the correct intensity
AddLocalLayers( pIKContext, pos2, q2, sequence, cycle, 1.0 );
SlerpBones( q, pos, pseqdesc, q2, pos2, flWeight );
if( pIKContext )
{
pIKContext->AddDependencies( pseqdesc, sequence, cycle, flWeight );
}
AddSequenceLayers( pIKContext, pos, q, sequence, cycle, flWeight );
if( pseqdesc->numiklocks )
{
seq_ik.SolveSequenceLocks( pseqdesc, pos, q );
}
}
//-----------------------------------------------------------------------------
// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1.
// 0 returns q1, pos1. 1 returns q2, pos2
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcBoneAdj( Vector pos[], Vector4D q[], const byte controllers[], byte mouthopen )
{
mstudiobonecontroller_t *pbonecontroller;
int i, j, k;
float value;
Vector p0;
Radian a0;
Vector4D q0;
for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )
{
pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex) + j;
k = pbonecontroller->bone;
if( IsBoneUsed( k ))
{
i = pbonecontroller->index;
if( i == STUDIO_MOUTH )
value = bound( 0.0f, ( mouthopen / 64.0f ), 1.0f );
else value = bound( 0.0f, (float)controllers[i] / 255.0f, 1.0f );
value = (1.0f - value) * pbonecontroller->start + value * pbonecontroller->end;
switch( pbonecontroller->type & STUDIO_TYPES )
{
case STUDIO_XR:
a0.Init( DEG2RAD( value ), 0.0f, 0.0f );
AngleQuaternion( a0, q0 );
QuaternionSM( 1.0f, q0, q[k], q[k] );
break;
case STUDIO_YR:
a0.Init( 0.0f, DEG2RAD( value ), 0.0f );
AngleQuaternion( a0, q0 );
QuaternionSM( 1.0f, q0, q[k], q[k] );
break;
case STUDIO_ZR:
a0.Init( 0.0f, 0.0f, DEG2RAD( value ));
AngleQuaternion( a0, q0 );
QuaternionSM( 1.0f, 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 CStudioBoneSetup :: CalcBoneAdj( float adj[], const byte controllers[], byte mouthopen )
{
mstudiobonecontroller_t *pbonecontroller;
int i, j, k;
float value;
Vector p0;
Radian a0;
Vector4D q0;
for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )
{
pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex) + j;
k = pbonecontroller->bone;
if( IsBoneUsed( k ))
{
i = pbonecontroller->index;
if( i == STUDIO_MOUTH )
value = bound( 0.0f, ( mouthopen / 64.0f ), 1.0f );
else value = bound( 0.0f, (float)controllers[i] / 255.0f, 1.0f );
value = (1.0f - value) * pbonecontroller->start + value * pbonecontroller->end;
switch( pbonecontroller->type & STUDIO_TYPES )
{
case STUDIO_YR:
case STUDIO_ZR:
case STUDIO_XR:
adj[j] = DEG2RAD( value );
break;
case STUDIO_X:
case STUDIO_Y:
case STUDIO_Z:
adj[j] = value;
break;
}
}
}
// list is installed
SetBoneControllers( adj );
}
//-----------------------------------------------------------------------------
// Purpose: run all animations that automatically play and are driven off of poseParameters
//-----------------------------------------------------------------------------
void CStudioBoneSetup :: CalcAutoplaySequences( CIKContext *pIKContext, Vector pos[], Vector4D q[] )
{
if( pIKContext )
{
pIKContext->AddAutoplayLocks( pos, q );
}
for( int i = 0; i < m_pStudioHeader->numseq; i++ )
{
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + i;
if( !FBitSet( pseqdesc->flags, STUDIO_AUTOPLAY ))
continue;
float cps = LocalCPS( i );
float cycle = m_flTime * cps;
cycle = cycle - (int)cycle;
AccumulatePose( NULL, pos, q, i, cycle, 1.0 );
}
if( pIKContext )
{
pIKContext->SolveAutoplayLocks( pos, q );
}
}