forked from FWGS/Paranoia2
2383 lines
69 KiB
C++
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 );
|
|
}
|
|
} |