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