|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
//===========================================================================//
#include "tier0/dbg.h"
#include "mathlib/mathlib.h"
#include "bone_setup.h"
#if defined( _PS3 )
#include "bone_setup_PS3.h"
#endif
#include <string.h>
#include "collisionutils.h"
#include "vstdlib/random.h"
#include "tier0/vprof.h"
#include "bone_accessor.h"
#include "mathlib/ssequaternion.h"
#include "bitvec.h"
#include "datamanager.h"
#include "convar.h"
#include "tier0/tslist.h"
#include "vphysics_interface.h"
#include "datacache/idatacache.h"
#include "tier0/miniprofiler.h"
#include "bone_utils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CIKSolver { public: //-------- SOLVE TWO LINK INVERSE KINEMATICS -------------
// Author: Ken Perlin
//
// Given a two link joint from [0,0,0] to end effector position P,
// let link lengths be a and b, and let norm |P| = c. Clearly a+b <= c.
//
// Problem: find a "knee" position Q such that |Q| = a and |P-Q| = b.
//
// In the case of a point on the x axis R = [c,0,0], there is a
// closed form solution S = [d,e,0], where |S| = a and |R-S| = b:
//
// d2+e2 = a2 -- because |S| = a
// (c-d)2+e2 = b2 -- because |R-S| = b
//
// c2-2cd+d2+e2 = b2 -- combine the two equations
// c2-2cd = b2 - a2
// c-2d = (b2-a2)/c
// d - c/2 = (a2-b2)/c / 2
//
// d = (c + (a2-b2/c) / 2 -- to solve for d and e.
// e = sqrt(a2-d2)
static float findD(float a, float b, float c) { return (c + (a*a-b*b)/c) / 2; } static float findE(float a, float d) { return sqrt(a*a-d*d); }
// This leads to a solution to the more general problem:
//
// (1) R = Mfwd(P) -- rotate P onto the x axis
// (2) Solve for S
// (3) Q = Minv(S) -- rotate back again
float Mfwd[3][3]; float Minv[3][3];
bool solve(float A, float B, float const P[], float const D[], float Q[]) { float R[3]; defineM(P,D); rot(Minv,P,R); float r = length(R); float d = findD(A,B,r); float e = findE(A,d); float S[3] = {d,e,0}; rot(Mfwd,S,Q); return d > (r - B) && d < A; }
// If "knee" position Q needs to be as close as possible to some point D,
// then choose M such that M(D) is in the y>0 half of the z=0 plane.
//
// Given that constraint, define the forward and inverse of M as follows:
void defineM(float const P[], float const D[]) { float *X = Minv[0], *Y = Minv[1], *Z = Minv[2];
// Minv defines a coordinate system whose x axis contains P, so X = unit(P).
int i; for (i = 0 ; i < 3 ; i++) X[i] = P[i]; normalize(X);
// Its y axis is perpendicular to P, so Y = unit( E - X(E�X) ).
float dDOTx = dot(D,X); for (i = 0 ; i < 3 ; i++) Y[i] = D[i] - dDOTx * X[i]; normalize(Y);
// Its z axis is perpendicular to both X and Y, so Z = X�Y.
cross(X,Y,Z);
// Mfwd = (Minv)T, since transposing inverts a rotation matrix.
for (i = 0 ; i < 3 ; i++) { Mfwd[i][0] = Minv[0][i]; Mfwd[i][1] = Minv[1][i]; Mfwd[i][2] = Minv[2][i]; } }
//------------ GENERAL VECTOR MATH SUPPORT -----------
static float dot(float const a[], float const b[]) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
static float length(float const v[]) { return sqrt( dot(v,v) ); }
static void normalize(float v[]) { float norm = length(v); for (int i = 0 ; i < 3 ; i++) v[i] /= norm; }
static void cross(float const a[], float const b[], float c[]) { c[0] = a[1] * b[2] - a[2] * b[1]; c[1] = a[2] * b[0] - a[0] * b[2]; c[2] = a[0] * b[1] - a[1] * b[0]; }
static void rot(float const M[3][3], float const src[], float dst[]) { for (int i = 0 ; i < 3 ; i++) dst[i] = dot(M[i],src); } };
//-----------------------------------------------------------------------------
// Purpose: visual debugging code
//-----------------------------------------------------------------------------
#if 1
inline void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) { }; #else
extern void drawLine( const Vector &p1, const Vector &p2, int r = 0, int g = 0, int b = 1, bool noDepthTest = true, float duration = 0.1 ); void debugLine(const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration) { drawLine( origin, dest, r, g, b, noDepthTest, duration ); } #endif
//-----------------------------------------------------------------------------
// Purpose: for a 2 bone chain, find the IK solution and reset the matrices
//-----------------------------------------------------------------------------
bool Studio_SolveIK( mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4a_t *pBoneToWorld ) { #if 0
// FIXME: something with the CS models breaks this, why?
if (pikchain->pLink(0)->kneeDir.LengthSqr() > 0.0) { Vector targetKneeDir, targetKneePos; // FIXME: knee length should be as long as the legs
Vector tmp = pikchain->pLink( 0 )->kneeDir; VectorRotate( tmp, pBoneToWorld[ pikchain->pLink( 0 )->bone ], targetKneeDir ); MatrixPosition( pBoneToWorld[ pikchain->pLink( 1 )->bone ], targetKneePos ); return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, targetKneePos, targetKneeDir, pBoneToWorld ); } else #endif
{ return Studio_SolveIK( pikchain->pLink( 0 )->bone, pikchain->pLink( 1 )->bone, pikchain->pLink( 2 )->bone, targetFoot, pBoneToWorld ); } }
#define KNEEMAX_EPSILON 0.9998 // (0.9998 is about 1 degree)
//-----------------------------------------------------------------------------
// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference
//-----------------------------------------------------------------------------
bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4a_t *pBoneToWorld ) { BONE_PROFILE_FUNC(); Vector worldFoot, worldKnee, worldThigh;
MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); MatrixPosition( pBoneToWorld[ iFoot ], worldFoot );
//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; }
// If any of the thigh to knee to foot bones are co-positional, then solving ik doesn't make sense.
// We're probably looking at uninitialized bones or something
if ( l1 <= 0 || l2 <= 0 || l3 <= 0 ) { return false; }
Vector ikHalf = (worldFoot-worldThigh) * (l1 / l3);
// FIXME: what to do when the knee completely straight?
Vector ikKneeDir = ikKnee - ikHalf; VectorNormalize( ikKneeDir );
return Studio_SolveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose: Realign the matrix so that its X axis points along the desired axis.
//-----------------------------------------------------------------------------
void Studio_AlignIKMatrix( matrix3x4a_t &mMat, const Vector &vAlignTo ) { BONE_PROFILE_FUNC(); Vector tmp1, tmp2, tmp3;
// Column 0 (X) becomes the vector.
tmp1 = vAlignTo; VectorNormalize( tmp1 ); MatrixSetColumn( tmp1, 0, mMat );
// Column 1 (Y) is the cross of the vector and column 2 (Z).
MatrixGetColumn( mMat, 2, tmp3 ); tmp2 = tmp3.Cross( tmp1 ); VectorNormalize( tmp2 ); // FIXME: check for X being too near to Z
MatrixSetColumn( tmp2, 1, mMat );
// Column 2 (Z) is the cross of columns 0 (X) and 1 (Y).
tmp3 = tmp1.Cross( tmp2 ); MatrixSetColumn( tmp3, 2, mMat ); }
//-----------------------------------------------------------------------------
// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction
//-----------------------------------------------------------------------------
bool Studio_SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4a_t *pBoneToWorld ) { BONE_PROFILE_FUNC(); Vector worldFoot, worldKnee, worldThigh;
MatrixPosition( pBoneToWorld[ iThigh ], worldThigh ); MatrixPosition( pBoneToWorld[ iKnee ], worldKnee ); MatrixPosition( pBoneToWorld[ iFoot ], worldFoot );
//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() - MIN( l1, l2 ); d = MAX( l1 + l2, d ); // FIXME: too short knee directions cause trouble
d = d * 100;
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) { VectorNormalize( ikFoot ); VectorScale( ikFoot, (l1 + l2) * KNEEMAX_EPSILON, ikFoot ); color[0] = 255; color[1] = 0; color[2] = 0; }
// too close?
// limit distance to about an 80 degree knee bend
float minDist = MAX( fabs(l1 - l2) * 1.15, MIN( l1, l2 ) * 0.15 ); if (ikFoot.Length() < minDist) { // too close to get an accurate vector, just use original vector
ikFoot = (worldFoot - worldThigh); VectorNormalize( ikFoot ); VectorScale( ikFoot, minDist, ikFoot ); }
CIKSolver ik; if (ik.solve( l1, l2, ikFoot.Base(), ikTargetKnee.Base(), ikKnee.Base() )) { matrix3x4a_t& mWorldThigh = pBoneToWorld[ iThigh ]; matrix3x4a_t& mWorldKnee = pBoneToWorld[ iKnee ]; matrix3x4a_t& 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
Studio_AlignIKMatrix( mWorldThigh, ikKnee ); Studio_AlignIKMatrix( mWorldKnee, ikFoot - ikKnee );
mWorldKnee[0][3] = ikKnee.x + worldThigh.x; mWorldKnee[1][3] = ikKnee.y + worldThigh.y; mWorldKnee[2][3] = ikKnee.z + worldThigh.z;
mWorldFoot[0][3] = ikFoot.x + worldThigh.x; mWorldFoot[1][3] = ikFoot.y + worldThigh.y; mWorldFoot[2][3] = ikFoot.z + worldThigh.z;
return true; } else { /*
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 ); */ return false; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float Studio_IKRuleWeight( 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 ); }
float Studio_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 3.0f * value * value - 2.0f * value * value * value; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool Studio_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 Studio_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 Studio_IKAnimationError( const CStudioHdr *pStudioHdr, mstudioikrule_t *pRule, const mstudioanimdesc_t *panim, float flCycle, BoneVector &pos, BoneQuaternion &q, float &flWeight ) { float fraq; int iFrame;
if (!pRule) return false;
flWeight = Studio_IKRuleWeight( *pRule, panim, flCycle, iFrame, fraq ); Assert( fraq >= 0.0 && fraq < 1.0 ); Assert( flWeight >= 0.0f && flWeight <= 1.0f );
// This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up
flWeight = clamp( flWeight, 0.0f, 1.0f );
if (pRule->type != IK_GROUND && flWeight < 0.0001) return false;
mstudioikerror_t *pError = pRule->pError( iFrame ); if (pError != NULL) { if (fraq < 0.001) { q = pError[0].q; pos = pError[0].pos; } else { QuaternionBlend( pError[0].q, pError[1].q, fraq, q ); pos = pError[0].pos * (1.0f - fraq) + pError[1].pos * fraq; } return true; }
mstudiocompressedikerror_t *pCompressed = pRule->pCompressedError(); if (pCompressed != NULL) { CalcDecompressedAnimation( pCompressed, iFrame - pRule->iStart, fraq, pos, q ); return true; } // no data, disable IK rule
Assert( 0 ); 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 Studio_IKSequenceError( const CStudioHdr *pStudioHdr, mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, int iRule, const float poseParameter[], mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t &ikRule ) { BONE_PROFILE_FUNC(); int i;
memset( &ikRule, 0, sizeof(ikRule) ); ikRule.start = ikRule.peak = ikRule.tail = ikRule.end = 0;
float prevStart = 0.0f;
// find overall influence
for (i = 0; i < 4; i++) { if (weight[i]) { if (iRule >= panim[i]->numikrules || panim[i]->numikrules != panim[0]->numikrules) { Assert( 0 ); return false; }
mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); if (pRule != NULL) { float dt = 0.0f; if (prevStart != 0.0f) { if (pRule->start - prevStart > 0.5) { dt = -1.0; } else if (pRule->start - prevStart < -0.5) { dt = 1.0; } } else { prevStart = pRule->start; }
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]; } else { mstudioikrulezeroframe_t *pZeroFrameRule = panim[i]->pIKRuleZeroFrame( iRule ); if (pZeroFrameRule) { float dt = 0.0f; if (prevStart != 0.0f) { if (pZeroFrameRule->start.GetFloat() - prevStart > 0.5) { dt = -1.0; } else if (pZeroFrameRule->start.GetFloat() - prevStart < -0.5) { dt = 1.0; } } else { prevStart = pZeroFrameRule->start.GetFloat(); }
ikRule.start += (pZeroFrameRule->start.GetFloat() + dt) * weight[i]; ikRule.peak += (pZeroFrameRule->peak.GetFloat() + dt) * weight[i]; ikRule.tail += (pZeroFrameRule->tail.GetFloat() + dt) * weight[i]; ikRule.end += (pZeroFrameRule->end.GetFloat() + dt) * weight[i]; } else { // Msg("%s %s - IK Stall\n", pStudioHdr->name(), seqdesc.pszLabel() );
return false; } } } } if (ikRule.start > 1.0) { ikRule.start -= 1.0; ikRule.peak -= 1.0; ikRule.tail -= 1.0; ikRule.end -= 1.0; } else if (ikRule.start < 0.0) { ikRule.start += 1.0; ikRule.peak += 1.0; ikRule.tail += 1.0; ikRule.end += 1.0; }
ikRule.flWeight = Studio_IKRuleWeight( ikRule, flCycle ); if (ikRule.flWeight <= 0.001f) { // go ahead and allow IK_GROUND rules a virtual looping section
if ( weight[0] ) { if ( panim[ 0 ]->pIKRule( iRule ) == NULL ) return false; if ( ( panim[ 0 ]->flags & STUDIO_LOOPING ) && panim[ 0 ]->pIKRule( iRule )->type == IK_GROUND && ikRule.end - ikRule.start > 0.75 ) { ikRule.flWeight = 0.001; flCycle = ikRule.end - 0.001; } else { return false; } } else { return false; } }
Assert( ikRule.flWeight > 0.0f );
ikRule.pos.Init(); ikRule.q.Init();
// find target error
float total = 0.0f; for (i = 0; i < 4; i++) { if (weight[i]) { BoneVector pos1; BoneQuaternion q1; float w;
mstudioikrule_t *pRule = panim[i]->pIKRule( iRule ); if (pRule != NULL) { 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]; } else { // look to see if there's a zeroframe version of the rule
mstudioikrulezeroframe_t *pZeroFrameRule = panim[i]->pIKRuleZeroFrame( iRule ); if (pZeroFrameRule) { // zeroframe doesn't contain details, so force a IK_RELEASE
ikRule.type = IK_RELEASE; ikRule.chain = pZeroFrameRule->chain; ikRule.slot = pZeroFrameRule->slot; ikRule.bone = -1; // Msg("IK_RELEASE %d %d : %.2f\n", ikRule.chain, ikRule.slot, ikRule.flWeight );
} else { // Msg("%s %s - IK Stall\n", pStudioHdr->name(), seqdesc.pszLabel() );
return false; } }
// keep track of tail condition
ikRule.release += Studio_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: { int bResult = Studio_IKAnimationError( pStudioHdr, pRule, panim[i], flCycle, pos1, q1, w );
if (bResult) { 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 = Studio_IKShouldLatch( ikRule, flCycle ) * ikRule.flWeight;
if (ikRule.type == IK_ATTACHMENT) { ikRule.szLabel = pRule->pszAttachment(); } } }
if (total <= 0.0001f) { return false; }
if (total < 0.999f) { VectorScale( ikRule.pos, 1.0f / total, ikRule.pos ); QuaternionScale( ikRule.q, 1.0f / total, ikRule.q ); }
if (ikRule.type == IK_SELF && ikRule.bone != -1) { // FIXME: this is anim local, not seq local!
ikRule.bone = pStudioHdr->RemapSeqBone( iSequence, ikRule.bone ); if (ikRule.bone == -1) return false; }
QuaternionNormalize( ikRule.q ); return true; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CIKContext::CIKContext() { m_target.EnsureCapacity( 12 ); // FIXME: this sucks, shouldn't it be grown?
m_iFramecounter = -1; m_pStudioHdr = NULL; m_flTime = -1.0f; m_target.SetSize( 0 ); }
void CIKContext::Init( const CStudioHdr *pStudioHdr, const QAngle &angles, const Vector &pos, float flTime, int iFramecounter, int boneMask ) { BONE_PROFILE_FUNC(); SNPROF_ANIM( "CIKContext::Init" );
m_pStudioHdr = pStudioHdr; m_ikChainRule.RemoveAll(); // m_numikrules = 0;
if (pStudioHdr->numikchains()) { m_ikChainRule.SetSize( pStudioHdr->numikchains() );
// FIXME: Brutal hackery to prevent a crash
if (m_target.Count() == 0) { m_target.SetSize(12); memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); ClearTargets(); }
} else { m_target.SetSize( 0 ); } AngleMatrix( angles, pos, m_rootxform ); m_iFramecounter = iFramecounter; m_flTime = flTime; m_boneMask = boneMask; }
void CIKContext::AddDependencies( mstudioseqdesc_t &seqdesc, int iSequence, float flCycle, const float poseParameters[], float flWeight ) { BONE_PROFILE_FUNC(); // ex: x360: up to 1 ms
SNPROF_ANIM("CIKContext::AddDependencies");
int i;
if ( m_pStudioHdr->numikchains() == 0) return;
if (seqdesc.numikrules == 0) return;
ikcontextikrule_t ikrule;
Assert( flWeight >= 0.0f && flWeight <= 1.0f ); // This shouldn't be necessary, but the Assert should help us catch whoever is screwing this up
flWeight = clamp( flWeight, 0.0f, 1.0f );
// unify this
if (seqdesc.flags & STUDIO_REALTIME) { float cps = Studio_CPS( m_pStudioHdr, seqdesc, iSequence, poseParameters ); flCycle = m_flTime * cps; flCycle = flCycle - (int)flCycle; } else if (flCycle < 0 || flCycle >= 1) { if (seqdesc.flags & STUDIO_LOOPING) { flCycle = flCycle - (int)flCycle; if (flCycle < 0) flCycle += 1; } else { flCycle = MAX( 0.0, MIN( flCycle, 0.9999 ) ); } }
mstudioanimdesc_t *panim[4]; float weight[4];
Studio_SeqAnims( m_pStudioHdr, seqdesc, iSequence, poseParameters, panim, weight );
// FIXME: add proper number of rules!!!
for (i = 0; i < seqdesc.numikrules; i++) { if ( !Studio_IKSequenceError( m_pStudioHdr, seqdesc, iSequence, flCycle, i, poseParameters, panim, weight, ikrule ) ) continue;
// don't add rule if the bone isn't going to be calculated
int bone = m_pStudioHdr->pIKChain( ikrule.chain )->pLink( 2 )->bone; if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) continue;
// or if its relative bone isn't going to be calculated
if ( ikrule.bone >= 0 && !(m_pStudioHdr->boneFlags( ikrule.bone ) & m_boneMask)) continue;
// FIXME: Brutal hackery to prevent a crash
if (m_target.Count() == 0) { m_target.SetSize(12); memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); ClearTargets(); }
ikrule.flRuleWeight = flWeight;
if (ikrule.flRuleWeight * ikrule.flWeight > 0.999) { if ( ikrule.type != IK_UNLATCH) { // clear out chain if rule is 100%
m_ikChainRule.Element( ikrule.chain ).RemoveAll( ); if ( ikrule.type == IK_RELEASE) { continue; } } }
int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; } }
#if defined( _PS3 )
//--------------------------------------------------------------------------------------
// 2nd part of IKContext AddDependencies
//
// 1st part assumed to have run during a PS3 bonejob, building a list of IKRules to potentially add
//--------------------------------------------------------------------------------------
void CIKContext::AddAllDependencies_PS3( ikcontextikrule_t *ikRules, int numRules ) { SNPROF_ANIM("CIKContext::AddAllDependencies_PS3");
int i;
// FIXME: add proper number of rules!!!
for( i = 0; i < numRules; i++ ) { ikcontextikrule_t &ikrule = ikRules[ i ];
// no copy constructors generally allowed
//memcpy( &ikrule, &ikRules[i], sizeof(ikcontextikrule_t) );
// don't add rule if the bone isn't going to be calculated
// int bone = m_pStudioHdr->pIKChain( ikrule.chain )->pLink( 2 )->bone;
// if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask))
// continue;
// or if its relative bone isn't going to be calculated
// if ( ikrule.bone >= 0 && !(m_pStudioHdr->boneFlags( ikrule.bone ) & m_boneMask))
// continue;
// FIXME: Brutal hackery to prevent a crash
if (m_target.Count() == 0) { m_target.SetSize(12); memset( m_target.Base(), 0, sizeof(m_target[0])*m_target.Count() ); ClearTargets(); }
//ikrule.flRuleWeight = flWeight;
if( ikrule.flRuleWeight * ikrule.flWeight > 0.999f ) { if ( ikrule.type != IK_UNLATCH) { // clear out chain if rule is 100%
m_ikChainRule.Element( ikrule.chain ).RemoveAll( ); if ( ikrule.type == IK_RELEASE) { continue; } } }
int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; } }
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::AddAutoplayLocks( BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); // skip all array access if no autoplay locks.
if (m_pStudioHdr->GetNumIKAutoplayLocks() == 0) { return; }
matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed;
int ikOffset = m_ikLock.AddMultipleToTail( m_pStudioHdr->GetNumIKAutoplayLocks() ); memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t)*m_pStudioHdr->GetNumIKAutoplayLocks() );
for (int i = 0; i < m_pStudioHdr->GetNumIKAutoplayLocks(); i++) { const mstudioiklock_t &lock = ((CStudioHdr *)m_pStudioHdr)->pIKAutoplayLock( i ); mstudioikchain_t *pchain = ((CStudioHdr *)m_pStudioHdr)->pIKChain( lock.chain ); int bone = pchain->pLink( 2 )->bone;
// don't bother with iklock if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) continue;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
ikcontextikrule_t &ikrule = m_ikLock[ i + ikOffset ];
ikrule.chain = lock.chain; ikrule.slot = i; ikrule.type = IK_WORLD;
MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos );
// save off current knee direction
if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) { Vector tmp = pchain->pLink( 0 )->kneeDir; VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); MatrixPosition( boneToWorld[ pchain->pLink( 1 )->bone ], ikrule.kneePos ); } else { ikrule.kneeDir.Init( ); } } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::AddSequenceLocks( mstudioseqdesc_t &seqdesc, BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); // ex: x360:up to 0.98 ms
SNPROF_ANIM("CIKContext::AddSequenceLocks");
if ( m_pStudioHdr->numikchains() == 0) { return; }
if ( seqdesc.numiklocks == 0 ) { return; }
matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed;
int ikOffset = m_ikLock.AddMultipleToTail( seqdesc.numiklocks ); memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t) * seqdesc.numiklocks );
for (int i = 0; i < seqdesc.numiklocks; i++) { mstudioiklock_t *plock = seqdesc.pIKLock( i ); mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); int bone = pchain->pLink( 2 )->bone;
// don't bother with iklock if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) continue;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
ikcontextikrule_t &ikrule = m_ikLock[i+ikOffset]; ikrule.chain = i; ikrule.slot = i; ikrule.type = IK_WORLD;
MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos );
// save off current knee direction
if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) { VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); } else { ikrule.kneeDir.Init( ); } } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose: build boneToWorld transforms for a specific bone
//-----------------------------------------------------------------------------
void CIKContext::BuildBoneChain( const BoneVector pos[], const BoneQuaternion q[], int iBone, matrix3x4a_t *pBoneToWorld, CBoneBitList &boneComputed ) { Assert( m_pStudioHdr->boneFlags( iBone ) & m_boneMask ); ::BuildBoneChain( m_pStudioHdr, m_rootxform, pos, q, iBone, pBoneToWorld, boneComputed ); }
//-----------------------------------------------------------------------------
// Purpose: turn a specific bones boneToWorld transform into a pos and q in parents bonespace
//-----------------------------------------------------------------------------
void SolveBone( const CStudioHdr *pStudioHdr, int iBone, matrix3x4a_t *pBoneToWorld, BoneVector pos[], BoneQuaternion q[] ) { int iParent = pStudioHdr->boneParent( iBone );
matrix3x4a_t worldToBone; MatrixInvert( pBoneToWorld[iParent], worldToBone );
matrix3x4a_t local; ConcatTransforms_Aligned( worldToBone, pBoneToWorld[iBone], local );
MatrixAngles( local, q[iBone], pos[iBone] ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKTarget::SetOwner( int entindex, const Vector &pos, const QAngle &angles ) { latched.owner = entindex; latched.absOrigin = pos; latched.absAngles = angles; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKTarget::ClearOwner( void ) { latched.owner = -1; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CIKTarget::GetOwner( void ) { return latched.owner; }
//-----------------------------------------------------------------------------
// Purpose: update the latched IK values that are in a moving frame of reference
//-----------------------------------------------------------------------------
void CIKTarget::UpdateOwner( int entindex, const Vector &pos, const QAngle &angles ) { if (pos == latched.absOrigin && angles == latched.absAngles) return;
matrix3x4a_t in; matrix3x4a_t out; AngleMatrix( angles, pos, in ); AngleIMatrix( latched.absAngles, latched.absOrigin, out );
matrix3x4a_t tmp1; matrix3x4a_t tmp2; QuaternionMatrix( latched.q, latched.pos, tmp1 ); ConcatTransforms_Aligned( out, tmp1, tmp2 ); ConcatTransforms_Aligned( in, tmp2, tmp1 ); MatrixAngles( tmp1, latched.q, latched.pos ); }
//-----------------------------------------------------------------------------
// Purpose: sets the ground position of an ik target
//-----------------------------------------------------------------------------
void CIKTarget::SetPos( const Vector &pos ) { est.pos = pos; }
//-----------------------------------------------------------------------------
// Purpose: sets the ground "identity" orientation of an ik target
//-----------------------------------------------------------------------------
void CIKTarget::SetAngles( const QAngle &angles ) { AngleQuaternion( angles, est.q ); }
//-----------------------------------------------------------------------------
// Purpose: sets the ground "identity" orientation of an ik target
//-----------------------------------------------------------------------------
void CIKTarget::SetQuaternion( const Quaternion &q ) { est.q = q; }
//-----------------------------------------------------------------------------
// Purpose: calculates a ground "identity" orientation based on the surface
// normal of the ground and the desired ground identity orientation
//-----------------------------------------------------------------------------
void CIKTarget::SetNormal( const Vector &normal ) { // recalculate foot angle based on slope of surface
matrix3x4a_t m1; Vector forward, right; QuaternionMatrix( est.q, m1 );
MatrixGetColumn( m1, 1, right ); forward = CrossProduct( right, normal ); right = CrossProduct( normal, forward ); MatrixSetColumn( forward, 0, m1 ); MatrixSetColumn( right, 1, m1 ); MatrixSetColumn( normal, 2, m1 ); QAngle a1; Vector p1; MatrixAngles( m1, est.q, p1 ); }
//-----------------------------------------------------------------------------
// Purpose: estimates the ground impact at the center location assuming a the edge of
// an Z axis aligned disc collided with it the surface.
//-----------------------------------------------------------------------------
void CIKTarget::SetPosWithNormalOffset( const Vector &pos, const Vector &normal ) { // assume it's a disc edge intersecting with the floor, so try to estimate the z location of the center
est.pos = pos; if (normal.z > 0.9999) { return; } // clamp at 45 degrees
else if (normal.z > 0.707) { // tan == sin / cos
float tan = sqrt( 1 - normal.z * normal.z ) / normal.z; est.pos.z = est.pos.z - est.radius * tan; } else { est.pos.z = est.pos.z - est.radius; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKTarget::SetOnWorld( bool bOnWorld ) { est.onWorld = bOnWorld; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CIKTarget::IsActive() { return (est.flWeight > 0.0f); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKTarget::IKFailed( void ) { latched.deltaPos.Init(); latched.deltaQ.Init(); latched.pos = ideal.pos; latched.q = ideal.q; est.latched = 0.0; est.flWeight = 0.0; est.onWorld = false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKTarget::MoveReferenceFrame( Vector &deltaPos, QAngle &deltaAngles ) { est.pos -= deltaPos; latched.pos -= deltaPos; offset.pos -= deltaPos; ideal.pos -= deltaPos; }
//-----------------------------------------------------------------------------
// Purpose: Invalidate any IK locks.
//-----------------------------------------------------------------------------
void CIKContext::ClearTargets( void ) { int i; for (i = 0; i < m_target.Count(); i++) { m_target[i].latched.iFramecounter = -9999; } }
//-----------------------------------------------------------------------------
// Purpose: Run through the rules that survived and turn a specific bones boneToWorld
// transform into a pos and q in parents bonespace
//-----------------------------------------------------------------------------
void CIKContext::UpdateTargets( BoneVector pos[], BoneQuaternion q[], matrix3x4a_t boneToWorld[], CBoneBitList &boneComputed ) { BONE_PROFILE_FUNC();
SNPROF_ANIM( "CIKContext::UpdateTargets" );
int i, j;
for (i = 0; i < m_target.Count(); i++) { m_target[i].est.flWeight = 0.0f; m_target[i].est.latched = 1.0f; m_target[i].est.release = 1.0f; m_target[i].est.height = 0.0f; m_target[i].est.floor = 0.0f; m_target[i].est.radius = 0.0f; m_target[i].offset.pos.Init(); m_target[i].offset.q.Init(); }
AutoIKRelease( );
for (j = 0; j < m_ikChainRule.Count(); j++) { for (i = 0; i < m_ikChainRule.Element( j ).Count(); i++) { ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i );
// ikchainresult_t *pChainRule = &chainRule[ m_ikRule[i].chain ];
switch( pRule->type ) { case IK_ATTACHMENT: case IK_GROUND: // case IK_SELF:
{ matrix3x4a_t footTarget; CIKTarget *pTarget = &m_target[pRule->slot]; pTarget->chain = pRule->chain; pTarget->type = pRule->type;
if (pRule->type == IK_ATTACHMENT) { pTarget->offset.pAttachmentName = pRule->szLabel; } else { pTarget->offset.pAttachmentName = NULL; }
if (pRule->flRuleWeight == 1.0f || pTarget->est.flWeight == 0.0f) { pTarget->offset.q = pRule->q; pTarget->offset.pos = pRule->pos; pTarget->est.height = pRule->height; pTarget->est.floor = pRule->floor; pTarget->est.radius = pRule->radius; pTarget->est.latched = pRule->latched * pRule->flRuleWeight; pTarget->est.release = pRule->release; pTarget->est.flWeight = pRule->flWeight * pRule->flRuleWeight; } else { QuaternionSlerp( pTarget->offset.q, pRule->q, pRule->flRuleWeight, pTarget->offset.q ); pTarget->offset.pos = Lerp( pRule->flRuleWeight, pTarget->offset.pos, pRule->pos ); pTarget->est.height = Lerp( pRule->flRuleWeight, pTarget->est.height, pRule->height ); pTarget->est.floor = Lerp( pRule->flRuleWeight, pTarget->est.floor, pRule->floor ); pTarget->est.radius = Lerp( pRule->flRuleWeight, pTarget->est.radius, pRule->radius ); //pTarget->est.latched = Lerp( pRule->flRuleWeight, pTarget->est.latched, pRule->latched );
pTarget->est.latched = MIN( pTarget->est.latched, pRule->latched ); pTarget->est.release = Lerp( pRule->flRuleWeight, pTarget->est.release, pRule->release ); pTarget->est.flWeight = Lerp( pRule->flRuleWeight, pTarget->est.flWeight, pRule->flWeight ); }
if ( pRule->type == IK_GROUND ) { pTarget->latched.deltaPos.z = 0; pTarget->est.pos.z = pTarget->est.floor + m_rootxform[2][3]; } } break; case IK_UNLATCH: { CIKTarget *pTarget = &m_target[pRule->slot]; if (pRule->latched > 0.0) pTarget->est.latched = 0.0; else pTarget->est.latched = MIN( pTarget->est.latched, 1.0f - pRule->flWeight ); } break; case IK_RELEASE: { CIKTarget *pTarget = &m_target[pRule->slot]; if (pRule->latched > 0.0) pTarget->est.latched = 0.0; else pTarget->est.latched = MIN( pTarget->est.latched, 1.0f - pRule->flWeight );
pTarget->est.flWeight = (pTarget->est.flWeight) * (1 - pRule->flWeight * pRule->flRuleWeight); } break; } } }
for (i = 0; i < m_target.Count(); i++) { CIKTarget *pTarget = &m_target[i]; if (pTarget->est.flWeight > 0.0) { mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); // ikchainresult_t *pChainRule = &chainRule[ i ];
int bone = pchain->pLink( 2 )->bone;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
// xform IK target error into world space
matrix3x4a_t local; matrix3x4a_t worldFootpad; QuaternionMatrix( pTarget->offset.q, pTarget->offset.pos, local ); MatrixInvert( local, local ); ConcatTransforms_Aligned( boneToWorld[bone], local, worldFootpad );
if (pTarget->est.latched == 1.0) { pTarget->latched.bNeedsLatch = true; } else { pTarget->latched.bNeedsLatch = false; }
// disable latched position if it looks invalid
if (m_iFramecounter < 0 || pTarget->latched.iFramecounter < m_iFramecounter - 1 || pTarget->latched.iFramecounter > m_iFramecounter) { pTarget->latched.bHasLatch = false; pTarget->latched.influence = 0.0; } pTarget->latched.iFramecounter = m_iFramecounter;
// find ideal contact position
MatrixAngles( worldFootpad, pTarget->ideal.q, pTarget->ideal.pos ); pTarget->est.q = pTarget->ideal.q; pTarget->est.pos = pTarget->ideal.pos;
float latched = pTarget->est.latched;
if (pTarget->latched.bHasLatch) { if (pTarget->est.latched == 1.0) { // keep track of latch position error from ideal contact position
pTarget->latched.deltaPos = pTarget->latched.pos - pTarget->est.pos; QuaternionSM( -1, pTarget->est.q, pTarget->latched.q, pTarget->latched.deltaQ ); pTarget->est.q = pTarget->latched.q; pTarget->est.pos = pTarget->latched.pos; } else if (pTarget->est.latched > 0.0) { // ramp out latch differences during decay phase of rule
if (latched > 0 && latched < pTarget->latched.influence) { // latching has decreased
float dt = pTarget->latched.influence - latched; if (pTarget->latched.influence > 0.0) dt = dt / pTarget->latched.influence;
VectorScale( pTarget->latched.deltaPos, (1-dt), pTarget->latched.deltaPos ); QuaternionScale( pTarget->latched.deltaQ, (1-dt), pTarget->latched.deltaQ ); }
// move ideal contact position by latched error factor
pTarget->est.pos = pTarget->est.pos + pTarget->latched.deltaPos; QuaternionMA( pTarget->est.q, 1, pTarget->latched.deltaQ, pTarget->est.q ); pTarget->latched.q = pTarget->est.q; pTarget->latched.pos = pTarget->est.pos; } else { pTarget->latched.bHasLatch = false; pTarget->latched.q = pTarget->est.q; pTarget->latched.pos = pTarget->est.pos; pTarget->latched.deltaPos.Init(); pTarget->latched.deltaQ.Init(); } pTarget->latched.influence = latched; }
// check for illegal requests
Vector p1, p2, p3; MatrixPosition( boneToWorld[pchain->pLink( 0 )->bone], p1 ); // hip
MatrixPosition( boneToWorld[pchain->pLink( 1 )->bone], p2 ); // knee
MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], p3 ); // foot
float d1 = (p2 - p1).Length(); float d2 = (p3 - p2).Length();
if (pTarget->latched.bHasLatch) { //float d3 = (p3 - p1).Length();
float d4 = (p3 + pTarget->latched.deltaPos - p1).Length();
// unstick feet when distance is too great
if ((d4 < fabs( d1 - d2 ) || d4 * 0.95 > d1 + d2) && pTarget->est.latched > 0.2) { pTarget->error.flTime = m_flTime; }
// unstick feet when angle is too great
if (pTarget->est.latched > 0.2) { float d = fabs( pTarget->latched.deltaQ.w ) * 2.0f - 1.0f; // QuaternionDotProduct( pTarget->latched.q, pTarget->est.q );
// FIXME: cos(45), make property of chain
if (d < 0.707) { pTarget->error.flTime = m_flTime; } } }
Vector dt = pTarget->est.pos - p1; pTarget->trace.hipToFoot = VectorNormalize( dt ); pTarget->trace.hipToKnee = d1; pTarget->trace.kneeToFoot = d2; pTarget->trace.hip = p1; pTarget->trace.knee = p2; pTarget->trace.closest = p1 + dt * (fabs( d1 - d2 ) * 1.01); pTarget->trace.farthest = p1 + dt * (d1 + d2) * 0.99; pTarget->trace.lowest = p1 + Vector( 0, 0, -1 ) * (d1 + d2) * 0.99; // pTarget->trace.endpos = pTarget->est.pos;
} } }
//-----------------------------------------------------------------------------
// Purpose: insert release rules if the ik rules were in error
//-----------------------------------------------------------------------------
void CIKContext::AutoIKRelease( void ) { BONE_PROFILE_FUNC(); int i;
for (i = 0; i < m_target.Count(); i++) { CIKTarget *pTarget = &m_target[i];
float dt = m_flTime - pTarget->error.flTime; if (pTarget->error.bInError || dt < 0.5) { if (!pTarget->error.bInError) { pTarget->error.ramp = 0.0; pTarget->error.flErrorTime = pTarget->error.flTime; pTarget->error.bInError = true; }
float ft = m_flTime - pTarget->error.flErrorTime; if (dt < 0.25) { pTarget->error.ramp = MIN( pTarget->error.ramp + ft * 4.0, 1.0 ); } else { pTarget->error.ramp = MAX( pTarget->error.ramp - ft * 4.0, 0.0 ); } if (pTarget->error.ramp > 0.0) { ikcontextikrule_t ikrule;
ikrule.chain = pTarget->chain; ikrule.bone = 0; ikrule.type = IK_RELEASE; ikrule.slot = i; ikrule.flWeight = SimpleSpline( pTarget->error.ramp ); ikrule.flRuleWeight = 1.0; ikrule.latched = dt < 0.25 ? 0.0 : ikrule.flWeight;
// don't bother with AutoIKRelease if the bone isn't going to be calculated
// this code is crashing for some unknown reason.
if ( pTarget->chain >= 0 && pTarget->chain < m_pStudioHdr->numikchains()) { mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pTarget->chain ); if (pchain != NULL) { int bone = pchain->pLink( 2 )->bone; if (bone >= 0 && bone < m_pStudioHdr->numbones()) { const mstudiobone_t *pBone = m_pStudioHdr->pBone( bone ); if (pBone != NULL) { if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) { pTarget->error.bInError = false; continue; } /*
char buf[256]; sprintf( buf, "dt %.4f ft %.4f weight %.4f latched %.4f\n", dt, ft, ikrule.flWeight, ikrule.latched ); OutputDebugString( buf ); */
int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; } else { DevWarning( 1, "AutoIKRelease (%s) got a NULL pBone %d\n", m_pStudioHdr->name(), bone ); } } else { DevWarning( 1, "AutoIKRelease (%s) got an out of range bone %d (%d)\n", m_pStudioHdr->name(), bone, m_pStudioHdr->numbones() ); } } else { DevWarning( 1, "AutoIKRelease (%s) got a NULL pchain %d\n", m_pStudioHdr->name(), pTarget->chain ); } } else { DevWarning( 1, "AutoIKRelease (%s) got an out of range chain %d (%d)\n", m_pStudioHdr->name(), pTarget->chain, m_pStudioHdr->numikchains()); } } else { pTarget->error.bInError = false; } pTarget->error.flErrorTime = m_flTime; } } }
void CIKContext::SolveDependencies( BoneVector pos[], BoneQuaternion q[], matrix3x4a_t boneToWorld[], CBoneBitList &boneComputed ) { BONE_PROFILE_FUNC(); // ex: x360: up to 1.16ms
// ASSERT_NO_REENTRY();
SNPROF_ANIM( "CIKContext::SolveDependencies" );
matrix3x4a_t worldTarget; int i, j;
ikchainresult_t chainResult[32]; // allocate!!!
// init chain rules
for( i = 0; i < m_pStudioHdr->numikchains(); i++ ) { mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); ikchainresult_t *pChainResult = &chainResult[ i ]; int bone = pchain->pLink( 2 )->bone;
pChainResult->target = -1; pChainResult->flWeight = 0.0f;
// don't bother with chain if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) continue;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
MatrixAngles( boneToWorld[bone], pChainResult->q, pChainResult->pos ); }
for( j = 0; j < m_ikChainRule.Count(); j++ ) { for( i = 0; i < m_ikChainRule.Element( j ).Count(); i++ ) { ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); ikchainresult_t *pChainResult = &chainResult[ pRule->chain ]; pChainResult->target = -1;
switch( pRule->type ) { case IK_SELF: { // xform IK target error into world space
matrix3x4a_t local; QuaternionMatrix( pRule->q, pRule->pos, local ); // eval target bone space
if (pRule->bone != -1) { BuildBoneChain( pos, q, pRule->bone, boneToWorld, boneComputed ); ConcatTransforms_Aligned( boneToWorld[pRule->bone], local, worldTarget ); } else { ConcatTransforms_Aligned( m_rootxform, local, worldTarget ); } float flWeight = pRule->flWeight * pRule->flRuleWeight; pChainResult->flWeight = pChainResult->flWeight * (1.0f - flWeight) + flWeight;
Vector p2; Quaternion q2; // target p and q
MatrixAngles( worldTarget, q2, p2 );
// debugLine( pChainResult->pos, p2, 0, 0, 255, true, 0.1 );
// blend in position and angles
pChainResult->pos = pChainResult->pos * (1.0f - flWeight) + p2 * flWeight; QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); } break; case IK_WORLD: Assert( 0 ); break;
case IK_ATTACHMENT: break;
case IK_GROUND: break;
case IK_RELEASE: { // move target back towards original location
float flWeight = pRule->flWeight * pRule->flRuleWeight; mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pRule->chain ); int bone = pchain->pLink( 2 )->bone;
Vector p2; Quaternion q2; BuildBoneChain( pos, q, bone, boneToWorld, boneComputed ); MatrixAngles( boneToWorld[bone], q2, p2 );
// blend in position and angles
pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); } break; case IK_UNLATCH: { /*
pChainResult->flWeight = pChainResult->flWeight * (1 - pRule->flWeight) + pRule->flWeight;
pChainResult->pos = pChainResult->pos * (1.0 - pRule->flWeight ) + pChainResult->local.pos * pRule->flWeight; QuaternionSlerp( pChainResult->q, pChainResult->local.q, pRule->flWeight, pChainResult->q ); */ } break; } } }
for (i = 0; i < m_target.Count(); i++) { CIKTarget *pTarget = &m_target[i];
if (m_target[i].est.flWeight > 0.0) { matrix3x4a_t worldFootpad; matrix3x4a_t local; //mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( m_target[i].chain );
ikchainresult_t *pChainResult = &chainResult[ pTarget->chain ];
AngleMatrix( RadianEuler(pTarget->offset.q), pTarget->offset.pos, local );
AngleMatrix( RadianEuler(pTarget->est.q), pTarget->est.pos, worldFootpad );
ConcatTransforms_Aligned( worldFootpad, local, worldTarget );
Vector p2; Quaternion q2; // target p and q
MatrixAngles( worldTarget, q2, p2 ); // MatrixAngles( worldTarget, pChainResult->q, pChainResult->pos );
// blend in position and angles
pChainResult->flWeight = pTarget->est.flWeight; pChainResult->pos = pChainResult->pos * (1.0 - pChainResult->flWeight ) + p2 * pChainResult->flWeight; QuaternionSlerp( pChainResult->q, q2, pChainResult->flWeight, pChainResult->q ); }
if (pTarget->latched.bNeedsLatch) { // keep track of latch position
pTarget->latched.bHasLatch = true; pTarget->latched.q = pTarget->est.q; pTarget->latched.pos = pTarget->est.pos; } }
for (i = 0; i < m_pStudioHdr->numikchains(); i++) { ikchainresult_t *pChainResult = &chainResult[ i ]; mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i );
if (pChainResult->flWeight > 0.0) { Vector tmp; MatrixPosition( boneToWorld[pchain->pLink( 2 )->bone], tmp ); // debugLine( pChainResult->pos, tmp, 255, 255, 255, true, 0.1 );
// do exact IK solution
// FIXME: once per link!
if (Studio_SolveIK(pchain, pChainResult->pos, boneToWorld )) { Vector p3; MatrixGetColumn( boneToWorld[pchain->pLink( 2 )->bone], 3, p3 ); QuaternionMatrix( pChainResult->q, p3, boneToWorld[pchain->pLink( 2 )->bone] );
// rebuild chain
// FIXME: is this needed if everyone past this uses the boneToWorld array?
SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); } else { // FIXME: need to invalidate the targets that forced this...
if (pChainResult->target != -1) { CIKTarget *pTarget = &m_target[pChainResult->target]; VectorScale( pTarget->latched.deltaPos, 0.8, pTarget->latched.deltaPos ); QuaternionScale( pTarget->latched.deltaQ, 0.8, pTarget->latched.deltaQ ); } } } }
#if 0
Vector p1, p2, p3; Quaternion q1, q2, q3;
// current p and q
MatrixAngles( boneToWorld[bone], q1, p1 );
// target p and q
MatrixAngles( worldTarget, q2, p2 );
// blend in position and angles
p3 = p1 * (1.0 - m_ikRule[i].flWeight ) + p2 * m_ikRule[i].flWeight;
// do exact IK solution
// FIXME: once per link!
Studio_SolveIK(pchain, p3, boneToWorld );
// force angle (bad?)
QuaternionSlerp( q1, q2, m_ikRule[i].flWeight, q3 ); MatrixGetColumn( boneToWorld[bone], 3, p3 ); QuaternionMatrix( q3, p3, boneToWorld[bone] );
// rebuild chain
SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); #endif
}
#if 0
//-----------------------------------------------------------------------------------------------------------------------
//
// SolveDependencies path abandoned for now, code here for reference
// in order for this to be efficient (multiple pass bone setup) we need to find a more appropriate
// point at which to perform multiple passes over baseanimating jobs (i.e. after each generation)
//
//-----------------------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------
// Pass1/2 here (PPU) - Pass2/2 done on SPU
// Fill bonejob data
//-----------------------------------------------------------------------------------------------------------------------
void CIKContext::SolveDependencies_PS3( bonejob_SPU_2 *pBonejob, CBoneBitList &boneComputed ) { SNPROF_ANIM( "CIKContext::SolveDependencies_PS3" );
int i, j;
pBonejob->rootxform = m_rootxform;
// copy over computed
pBonejob->boneComputed.ResetMarkedBones( m_pStudioHdr->numbones() );
for( i = 0; i < m_pStudioHdr->numbones(); i++ ) { if( boneComputed.IsBoneMarked( i ) ) { // mark bone
pBonejob->boneComputed.MarkBone( i );
// copy matrix
//pBonejob->boneToWorld[ i ] = boneToWorld[ i ];
// could build a dma list so we only copy over valid matrices?
} }
pBonejob->numikchainElements = 0; pBonejob->studiohdr_numikchains = m_pStudioHdr->numikchains();
// init chain rules
for( i = 0; i < m_pStudioHdr->numikchains(); i++ ) { mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i );
ikChain_SPU *pchain_SPU = &pBonejob->ikChains[ i ]; pchain_SPU->bone0 = pchain->pLink( 0 )->bone; pchain_SPU->bone1 = pchain->pLink( 1 )->bone; pchain_SPU->bone2 = pchain->pLink( 2 )->bone; pchain_SPU->kneeDir0 = pchain->pLink( 0 )->kneeDir;
// don't bother with chain if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( pchain_SPU->bone2 ) & m_boneMask)) pchain_SPU->bone2 = -1; }
for( j = 0; j < m_ikChainRule.Count(); j++ ) { for( i = 0; i < m_ikChainRule.Element( j ).Count(); i++ ) { ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i );
AssertFatal( pBonejob->numikchainElements >= MAX_IKCHAINELEMENTS );
switch( pRule->type ) { case IK_SELF: { pBonejob->ikchainElement_rules[ pBonejob->numikchainElements++ ] = pRule; } break; case IK_WORLD: Assert( 0 ); break;
case IK_ATTACHMENT: break;
case IK_GROUND: break;
case IK_RELEASE: { // move target back towards original location
mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( pRule->chain );
pBonejob->ikchainElement_rules[ pBonejob->numikchainElements ] = pRule; pBonejob->ikchainElement_bones[ pBonejob->numikchainElements ] = pchain->pLink( 2 )->bone; pBonejob->numikchainElements++; } break; case IK_UNLATCH: { /*
pChainResult->flWeight = pChainResult->flWeight * (1 - pRule->flWeight) + pRule->flWeight;
pChainResult->pos = pChainResult->pos * (1.0 - pRule->flWeight ) + pChainResult->local.pos * pRule->flWeight; QuaternionSlerp( pChainResult->q, pChainResult->local.q, pRule->flWeight, pChainResult->q ); */ } break; } } }
pBonejob->iktargetcount = m_target.Count();
for (i = 0; i < m_target.Count(); i++) { CIKTarget *pTarget = &m_target[i];
pBonejob->iktargets[ i ] = pTarget; } }
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::SolveAutoplayLocks( BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); // ex: x360: 2.44ms
matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed; int i;
for (i = 0; i < m_ikLock.Count(); i++) { const mstudioiklock_t &lock = ((CStudioHdr *)m_pStudioHdr)->pIKAutoplayLock( i ); SolveLock( &lock, i, pos, q, boneToWorld, boneComputed ); } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::SolveSequenceLocks( mstudioseqdesc_t &seqdesc, BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); SNPROF_ANIM("CIKContext::SolveSequenceLocks");
matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed; int i;
for (i = 0; i < m_ikLock.Count(); i++) { mstudioiklock_t *plock = seqdesc.pIKLock( i ); SolveLock( plock, i, pos, q, boneToWorld, boneComputed ); } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::AddAllLocks( BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); // skip all array access if no autoplay locks.
if (m_pStudioHdr->GetNumIKChains() == 0) { return; }
matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed;
int ikOffset = m_ikLock.AddMultipleToTail( m_pStudioHdr->GetNumIKChains() ); memset( &m_ikLock[ikOffset], 0, sizeof(ikcontextikrule_t)*m_pStudioHdr->GetNumIKChains() );
for (int i = 0; i < m_pStudioHdr->GetNumIKChains(); i++) { mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( i ); int bone = pchain->pLink( 2 )->bone;
// don't bother with iklock if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) continue;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
ikcontextikrule_t &ikrule = m_ikLock[ i + ikOffset ];
ikrule.chain = i; ikrule.slot = i; ikrule.type = IK_WORLD;
MatrixAngles( boneToWorld[bone], ikrule.q, ikrule.pos );
// save off current knee direction
if (pchain->pLink(0)->kneeDir.LengthSqr() > 0.0) { Vector tmp = pchain->pLink( 0 )->kneeDir; VectorRotate( pchain->pLink( 0 )->kneeDir, boneToWorld[ pchain->pLink( 0 )->bone ], ikrule.kneeDir ); MatrixPosition( boneToWorld[ pchain->pLink( 1 )->bone ], ikrule.kneePos ); } else { ikrule.kneeDir.Init( ); } } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::SolveAllLocks( BoneVector pos[], BoneQuaternion q[] ) { BONE_PROFILE_FUNC(); matrix3x4a_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed; int i;
mstudioiklock_t lock;
for (i = 0; i < m_ikLock.Count(); i++) { lock.chain = i; lock.flPosWeight = 1.0; lock.flLocalQWeight = 0.0; lock.flags = 0;
SolveLock( &lock, i, pos, q, boneToWorld, boneComputed ); } g_MatrixPool.Free( boneToWorld ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CIKContext::SolveLock( const mstudioiklock_t *plock, int i, BoneVector pos[], BoneQuaternion q[], matrix3x4a_t boneToWorld[], CBoneBitList &boneComputed ) { BONE_PROFILE_FUNC(); // ex: x360:up to 1.18 ms
mstudioikchain_t *pchain = m_pStudioHdr->pIKChain( plock->chain ); int bone = pchain->pLink( 2 )->bone;
// don't bother with iklock if the bone isn't going to be calculated
if ( !(m_pStudioHdr->boneFlags( bone ) & m_boneMask)) return;
// eval current ik'd bone
BuildBoneChain( pos, q, bone, boneToWorld, boneComputed );
Vector p1, p2, p3; Quaternion q2, q3;
// current p and q
MatrixPosition( boneToWorld[bone], p1 );
// blend in position
p3 = p1 * (1.0 - plock->flPosWeight ) + m_ikLock[i].pos * plock->flPosWeight;
// do exact IK solution
if (m_ikLock[i].kneeDir.LengthSqr() > 0) { Studio_SolveIK(pchain->pLink( 0 )->bone, pchain->pLink( 1 )->bone, pchain->pLink( 2 )->bone, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); } else { Studio_SolveIK(pchain, p3, boneToWorld ); }
// slam orientation
MatrixPosition( boneToWorld[bone], p3 ); QuaternionMatrix( m_ikLock[i].q, p3, boneToWorld[bone] );
// rebuild chain
q2 = q[ bone ]; SolveBone( m_pStudioHdr, pchain->pLink( 2 )->bone, boneToWorld, pos, q ); QuaternionSlerp( q[bone], q2, plock->flLocalQWeight, q[bone] );
SolveBone( m_pStudioHdr, pchain->pLink( 1 )->bone, boneToWorld, pos, q ); SolveBone( m_pStudioHdr, pchain->pLink( 0 )->bone, boneToWorld, pos, q ); }
void CIKContext::CopyTo( CIKContext* pOther, const unsigned short * iRemapping ) { if ( !pOther ) return;
// replace the ik rules and ik locks on the other ik context, and remap the bone chain indices to match
pOther->m_ikChainRule.RemoveAll(); pOther->m_ikLock.RemoveAll(); FOR_EACH_VEC( m_ikChainRule, n ) { int nIndex = pOther->m_ikChainRule.AddToTail();
FOR_EACH_VEC( m_ikChainRule[n], m ) { int nIKChainBone = m_ikChainRule[n][m].bone; if ( iRemapping != NULL && m_ikChainRule[ n ][ m ].type != IK_RELEASE ) { int nIKChainBoneRemapped = iRemapping[ nIKChainBone ]; if ( nIKChainBoneRemapped < 0 || nIKChainBoneRemapped >= MAXSTUDIOBONES ) continue; // don't copy this chain rule at all
nIKChainBone = nIKChainBoneRemapped; }
int nSubIndex = pOther->m_ikChainRule[nIndex].AddToTail(); pOther->m_ikChainRule[nIndex][nSubIndex] = m_ikChainRule[n][m]; pOther->m_ikChainRule[nIndex][nSubIndex].bone = nIKChainBone; // this can be a remapped bone
} }
FOR_EACH_VEC( m_ikLock, n ) { int nIKChainBone = m_ikLock[n].bone; if ( iRemapping != NULL && m_ikLock[ n ].type != IK_RELEASE ) { int nIKChainBoneRemapped = iRemapping[ nIKChainBone ]; if ( nIKChainBoneRemapped < 0 || nIKChainBoneRemapped >= MAXSTUDIOBONES ) continue; // don't copy this ik lock at all
nIKChainBone = nIKChainBoneRemapped; }
int nIndex = pOther->m_ikLock.AddToTail(); pOther->m_ikLock[nIndex] = m_ikLock[n]; pOther->m_ikLock[ nIndex ].bone = nIKChainBone; // this can be a remapped bone
} }
|