csgo-2018-source/bonesetup/bone_ik.cpp
2021-07-24 21:11:47 -07:00

2226 lines
63 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===== 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
}
}