5401 lines
150 KiB
C++
5401 lines
150 KiB
C++
/*
|
|
simplify.cpp - studio model simplification
|
|
Copyright (C) 2017 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "cmdlib.h"
|
|
#include "mathlib.h"
|
|
#include "stringlib.h"
|
|
#include "studio.h"
|
|
#include "studiomdl.h"
|
|
#include "iksolver.h"
|
|
|
|
#define ANIM_COMPRESS_THRESHOLD 0 // more values compress animation but can skip frames (optimal range between 0-100)
|
|
#define cmp_animvalue( x, y ) ( abs( value[x] - value[y] ) <= ANIM_COMPRESS_THRESHOLD )
|
|
|
|
void AccumulateSeqLayers( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight );
|
|
int g_rootIndex = 0;
|
|
|
|
//----------------------------------------------------------------------
|
|
// underlay:
|
|
// studiomdl : delta = new_anim * ( -1 * base_anim )
|
|
// engine : result = (w * delta) * base_anim
|
|
//
|
|
// overlay
|
|
//
|
|
// studiomdl : delta = (-1 * base_anim ) * new_anim
|
|
// engine : result = base_anim * (w * delta)
|
|
//
|
|
//----------------------------------------------------------------------
|
|
void QuaternionSMAngles( float s, Vector4D const &p, Vector4D const &q, Radian &angles )
|
|
{
|
|
Vector4D qt;
|
|
QuaternionSM( s, p, q, qt );
|
|
QuaternionAngle( qt, angles );
|
|
}
|
|
|
|
void QuaternionMAAngles( Vector4D const &p, float s, Vector4D const &q, Radian &angles )
|
|
{
|
|
Vector4D qt;
|
|
QuaternionMA( p, s, q, qt );
|
|
QuaternionAngle( qt, angles );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert pBoneToWorld back into rot/pos data
|
|
//-----------------------------------------------------------------------------
|
|
void solveBone( s_animation_t *panim, int iFrame, int iBone, matrix3x4 *pBoneToWorld )
|
|
{
|
|
int iParent = g_bonetable[iBone].parent;
|
|
|
|
if( iParent == -1 )
|
|
{
|
|
pBoneToWorld[iBone].GetStudioTransform( panim->sanim[iFrame][iBone].pos, panim->sanim[iFrame][iBone].rot );
|
|
return;
|
|
}
|
|
|
|
matrix3x4 worldToBone = pBoneToWorld[iParent].Invert();
|
|
matrix3x4 local = worldToBone.ConcatTransforms( pBoneToWorld[iBone] );
|
|
|
|
iFrame = iFrame % panim->numframes;
|
|
|
|
local.GetStudioTransform( panim->sanim[iFrame][iBone].pos, panim->sanim[iFrame][iBone].rot );
|
|
}
|
|
|
|
bool AnimationDifferent( const Vector& startPos, const Radian& startRot, const Vector& pos, const Radian& rot )
|
|
{
|
|
if( !VectorCompareEpsilon( startPos, pos, 0.01 ))
|
|
return true;
|
|
|
|
if( !RadianCompareEpsilon( startRot, rot, 0.01 ))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool BoneHasAnimation( const char *pName )
|
|
{
|
|
bool first = true;
|
|
Vector pos;
|
|
Radian rot;
|
|
|
|
if( !g_numani ) return false;
|
|
|
|
int globalIndex = findGlobalBone( pName );
|
|
|
|
// don't check root bones for animation
|
|
if( globalIndex >= 0 && g_bonetable[globalIndex].parent == -1 )
|
|
return true;
|
|
|
|
// find used bones per g_model
|
|
for( int i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
int boneIndex = findLocalBone( panim, pName );
|
|
if( boneIndex < 0 ) continue; // not in this source?
|
|
|
|
// this is not right, but enough of the bones are moved unintentionally between
|
|
// animations that I put this in to catch them.
|
|
int n = panim->startframe - panim->source.startframe;
|
|
first = true;
|
|
|
|
for( int j = 0; j < panim->numframes; j++ )
|
|
{
|
|
if ( first )
|
|
{
|
|
pos = panim->rawanim[j+n][boneIndex].pos;
|
|
rot = panim->rawanim[j+n][boneIndex].rot;
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
if( AnimationDifferent( pos, rot, panim->rawanim[j+n][boneIndex].pos, panim->rawanim[j+n][boneIndex].rot ))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BoneHasAttachments( char const *pname )
|
|
{
|
|
for( int k = 0; k < g_numattachments; k++ )
|
|
{
|
|
if( !Q_stricmp( g_attachment[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int BoneIsProcedural( char const *pname )
|
|
{
|
|
int k;
|
|
|
|
for( k = 0; k < g_numaxisinterpbones; k++ )
|
|
{
|
|
if( !Q_stricmp( g_axisinterpbones[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
|
|
for( k = 0; k < g_numquatinterpbones; k++ )
|
|
{
|
|
if( IsGlobalBoneXSI( g_quatinterpbones[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
|
|
for( k = 0; k < g_numaimatbones; k++ )
|
|
{
|
|
if( IsGlobalBoneXSI( g_aimatbones[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
|
|
for( k = 0; k < g_numjigglebones; k++ )
|
|
{
|
|
if( !Q_stricmp( g_jigglebones[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BoneIsIK( char const *pname )
|
|
{
|
|
// tag bones used by ikchains
|
|
for( int k = 0; k < g_numikchains; k++ )
|
|
{
|
|
if( !Q_stricmp( g_ikchain[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BoneShouldCollapse( char const *pname )
|
|
{
|
|
for( int k = 0; k < g_collapse.Count(); k++ )
|
|
{
|
|
if( !Q_stricmp( g_collapse[k].bonename, pname ))
|
|
return true;
|
|
}
|
|
|
|
return ( !BoneHasAnimation( pname ) && !BoneIsProcedural( pname ) && !BoneIsIK( pname ) /* && !BoneHasAttachments( pname ) */);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Collapse vertex assignments up to parent on bones that are not needed
|
|
// This can optimize a model substantially if the animator is using
|
|
// lots of helper bones with no animation.
|
|
//-----------------------------------------------------------------------------
|
|
void CollapseBones( void )
|
|
{
|
|
int count = 0;
|
|
int j, k;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].bDontCollapse )
|
|
continue;
|
|
|
|
if(( g_bonetable[k].flags != 0 || g_bonetable[k].bPreDefined ) && !BoneShouldCollapse( g_bonetable[k].name ))
|
|
continue;
|
|
count++;
|
|
|
|
MsgDev( D_NOTE, "collapsing %s\n", g_bonetable[k].name );
|
|
|
|
g_numbones--;
|
|
int m = g_bonetable[k].parent;
|
|
|
|
for( j = k; j < g_numbones; j++ )
|
|
{
|
|
g_bonetable[j] = g_bonetable[j+1];
|
|
|
|
if( g_bonetable[j].parent == k )
|
|
g_bonetable[j].parent = m;
|
|
else if( g_bonetable[j].parent >= k )
|
|
g_bonetable[j].parent = g_bonetable[j].parent - 1;
|
|
}
|
|
k--;
|
|
}
|
|
|
|
if( count ) MsgDev( D_REPORT, "Collapsed %d bones\n", count );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: replace all animation, rotation and translation, etc. with a single bone
|
|
//-----------------------------------------------------------------------------
|
|
void MakeStaticProp( void )
|
|
{
|
|
matrix3x4 rotated = matrix3x4( g_vecZero, g_defaultrotation );
|
|
Vector centerOffset;
|
|
int i, j, k;
|
|
|
|
// FIXME: missing attachment point recalcs!
|
|
// replace bone 0 with "static_prop" bone and attach everything to it.
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
s_model_t *pmodel = g_model[i];
|
|
|
|
Q_strncpy( pmodel->localBone[0].name, "kHED", sizeof( pmodel->localBone[0].name )); // g-cont :-)
|
|
pmodel->localBone[0].parent = -1;
|
|
|
|
for( k = 1; k < pmodel->numbones; k++ )
|
|
{
|
|
pmodel->localBone[k].parent = -1;
|
|
}
|
|
|
|
rotated.SetOrigin( g_defaultadjust );
|
|
|
|
Vector mins, maxs;
|
|
ClearBounds( mins, maxs );
|
|
|
|
for( j = 0; j < pmodel->numsrcverts; j++ )
|
|
{
|
|
for( k = 0; k < pmodel->srcvert[j].localWeight.numbones; k++ )
|
|
{
|
|
// attach everything to root
|
|
pmodel->srcvert[j].localWeight.bone[k] = 0;
|
|
}
|
|
|
|
// **shift everything into identity space**
|
|
pmodel->srcvert[j].vert = rotated.VectorTransform( pmodel->srcvert[j].vert );
|
|
|
|
// normal
|
|
pmodel->srcvert[j].norm = rotated.VectorRotate( pmodel->srcvert[j].norm );
|
|
|
|
// incrementally compute identity space bbox
|
|
AddPointToBounds( pmodel->srcvert[j].vert, mins, maxs );
|
|
}
|
|
|
|
if( g_centerstaticprop )
|
|
{
|
|
const char *pAttachmentName = "placementOrigin";
|
|
bool bFound = false;
|
|
|
|
for( k = 0; k < g_numattachments; k++ )
|
|
{
|
|
if( !Q_stricmp( g_attachment[k].name, pAttachmentName ))
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bFound )
|
|
{
|
|
centerOffset = -0.5f * (mins + maxs);
|
|
}
|
|
|
|
for( j = 0; j < pmodel->numsrcverts; j++ )
|
|
{
|
|
pmodel->srcvert[j].vert += centerOffset;
|
|
}
|
|
|
|
if ( !bFound )
|
|
{
|
|
// now add an attachment point to store this offset
|
|
Q_strncpy( g_attachment[g_numattachments].name, pAttachmentName, sizeof( g_attachment[0].name ));
|
|
Q_strncpy( g_attachment[g_numattachments].bonename, "kHED", sizeof( g_attachment[0].name ));
|
|
g_attachment[g_numattachments].local = matrix3x4( centerOffset, g_vecZero );
|
|
g_attachment[g_numattachments].bone = 0;
|
|
g_attachment[g_numattachments].type = 0;
|
|
g_numattachments++;
|
|
}
|
|
}
|
|
|
|
// force the bone to be identity
|
|
pmodel->skeleton[0].pos = Vector( 0, 0, 0 );
|
|
pmodel->skeleton[0].rot = Radian( 0, 0, 0 );
|
|
|
|
// make an identity boneToPose transform
|
|
pmodel->boneToPose[0].Identity();
|
|
}
|
|
|
|
// throw all specified hitboxes
|
|
g_numbonecontrollers = 0;
|
|
allow_boneweights = 0;
|
|
g_numimportbones = 0;
|
|
has_boneweights = 0;
|
|
g_hitboxsets.Purge();
|
|
g_BoneMerge.Purge();
|
|
|
|
// throw away all animations
|
|
g_panimation[0]->numframes = 1;
|
|
g_panimation[0]->startframe = 0;
|
|
g_panimation[0]->endframe = 1;
|
|
g_panimation[0]->source.numframes = 1;
|
|
g_panimation[0]->source.startframe = 0;
|
|
g_panimation[0]->source.endframe = 1;
|
|
Q_strncpy( g_panimation[0]->name, "seq-name", sizeof( g_panimation[0]->name ));
|
|
g_panimation[0]->rotation = Radian( 0, 0, 0 );
|
|
g_panimation[0]->adjust = Vector( 0, 0, 0 );
|
|
g_panimation[0]->fps = 30.0f;
|
|
|
|
g_numani = 1;
|
|
|
|
// recalc attachment points:
|
|
for( i = 0; i < g_numattachments; i++ )
|
|
{
|
|
if( g_centerstaticprop && ( i == g_numattachments - 1 ))
|
|
continue;
|
|
|
|
Q_strncpy( g_attachment[i].bonename, "kHED", sizeof( g_attachment[i].name ));
|
|
g_attachment[i].local = rotated.ConcatTransforms( g_attachment[i].local );
|
|
g_attachment[i].bone = 0;
|
|
g_attachment[i].type = 0;
|
|
}
|
|
|
|
// throw away all sequences
|
|
Q_strncpy( g_sequence[0].name, "seq-name", sizeof( g_sequence[0].name ));
|
|
g_sequence[0].panim[0] = g_panimation[0];
|
|
g_sequence[0].fps = g_panimation[0]->fps;
|
|
g_sequence[0].numautolayers = 0;
|
|
g_sequence[0].activity = 0;
|
|
g_sequence[0].numframes = 1;
|
|
g_sequence[0].numblends = 1;
|
|
g_sequence[0].numevents = 0;
|
|
g_sequence[0].seqgroup = 0;
|
|
g_sequence[0].paramindex[0] = -1;
|
|
g_sequence[0].paramindex[1] = -1;
|
|
g_sequence[0].groupsize[0] = 0;
|
|
g_sequence[0].groupsize[1] = 0;
|
|
g_sequence[0].fadeintime = 0.2f;
|
|
g_sequence[0].fadeouttime = 0.2f;
|
|
g_sequence[0].numiklocks = 0;
|
|
g_sequence[0].numikrules = 0;
|
|
g_sequence[0].flags = 0;
|
|
g_numseq = 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: set "boneref" for all the source bones used by vertices, attachments, etc.
|
|
//-----------------------------------------------------------------------------
|
|
void TagUsedBones( void )
|
|
{
|
|
int i, j, k, n;
|
|
|
|
// find used bones
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
s_model_t *pmodel = g_model[i];
|
|
|
|
for( k = 0; k < MAXSTUDIOSRCBONES; k++ )
|
|
{
|
|
pmodel->boneflags[k] = 0;
|
|
pmodel->boneref[k] = 0;
|
|
}
|
|
|
|
for( j = 0; j < pmodel->numsrcverts; j++ )
|
|
{
|
|
for( k = 0; k < pmodel->srcvert[j].localWeight.numbones; k++ )
|
|
{
|
|
SetBits( pmodel->boneflags[pmodel->srcvert[j].localWeight.bone[k]], BONE_USED_BY_VERTEX );
|
|
}
|
|
}
|
|
|
|
for( k = 0; k < g_numattachments; k++ )
|
|
{
|
|
for( j = 0; j < pmodel->numbones; j++ )
|
|
{
|
|
if( !Q_stricmp( g_attachment[k].bonename, pmodel->localBone[j].name ))
|
|
{
|
|
// this bone is a keeper with or without associated vertices
|
|
// because an attachment point depends on it.
|
|
if( FBitSet( g_attachment[k].type, IS_RIGID ))
|
|
{
|
|
for( n = j; n != -1; n = pmodel->localBone[n].parent )
|
|
{
|
|
if( FBitSet( pmodel->boneflags[n], BONE_USED_BY_VERTEX ))
|
|
{
|
|
SetBits( pmodel->boneflags[n], BONE_USED_BY_ATTACHMENT );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetBits( pmodel->boneflags[j], BONE_USED_BY_ATTACHMENT );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for( k = 0; k < g_numikchains; k++ )
|
|
{
|
|
for( j = 0; j < pmodel->numbones; j++ )
|
|
{
|
|
if( !Q_stricmp( g_ikchain[k].bonename, pmodel->localBone[j].name ))
|
|
{
|
|
// this bone is a keeper with or without associated vertices
|
|
// because a ikchain depends on it.
|
|
SetBits( pmodel->boneflags[j], BONE_USED_BY_ATTACHMENT );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tag all bones marked as being used by bonemerge
|
|
int nBoneMergeCount = g_BoneMerge.Count();
|
|
for( k = 0; k < nBoneMergeCount; ++k )
|
|
{
|
|
for( j = 0; j < pmodel->numbones; j++ )
|
|
{
|
|
if( !Q_stricmp( g_BoneMerge[k].bonename, pmodel->localBone[j].name ))
|
|
continue;
|
|
|
|
SetBits( pmodel->boneflags[j], BONE_USED_BY_BONE_MERGE );
|
|
}
|
|
}
|
|
|
|
// NOTE: This must come last; after all flags have been set!
|
|
// tag bonerefs as being used the union of the boneflags all their children
|
|
for( k = 0; k < MAXSTUDIOSRCBONES; k++ )
|
|
{
|
|
if( !pmodel->boneflags[k] )
|
|
continue;
|
|
|
|
// tag parent bones as used if child has been used
|
|
pmodel->boneref[k] |= pmodel->boneflags[k];
|
|
|
|
n = g_model[i]->localBone[k].parent;
|
|
while( n != -1 )
|
|
{
|
|
pmodel->boneref[n] |= pmodel->boneref[k];
|
|
n = g_model[i]->localBone[n].parent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: change the names in the source files for bones that max auto-renamed on us
|
|
//-----------------------------------------------------------------------------
|
|
void RenameBones( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
// rename model bones if needed
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
for( j = 0; j < g_model[i]->numbones; j++ )
|
|
{
|
|
for( k = 0; k < g_numrenamedbones; k++ )
|
|
{
|
|
if( !Q_strcmp( g_model[i]->localBone[j].name, g_renamedbone[k].from ))
|
|
{
|
|
Q_strncpy( g_model[i]->localBone[j].name, g_renamedbone[k].to, MAXSRCSTUDIONAME );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rename sequence bones if needed
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
for( j = 0; j < g_panimation[i]->numbones; j++ )
|
|
{
|
|
for( k = 0; k < g_numrenamedbones; k++ )
|
|
{
|
|
if( !Q_strcmp( g_panimation[i]->localBone[j].name, g_renamedbone[k].from ))
|
|
{
|
|
Q_strncpy( g_panimation[i]->localBone[j].name, g_renamedbone[k].to, MAXSRCSTUDIONAME );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuildGlobalBoneToPose( void )
|
|
{
|
|
// build reference pose
|
|
for( int j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].parent == -1 ) g_bonetable[j].boneToPose = g_bonetable[j].rawLocal;
|
|
else g_bonetable[j].boneToPose = g_bonetable[g_bonetable[j].parent].boneToPose.ConcatTransforms( g_bonetable[j].rawLocal );
|
|
}
|
|
}
|
|
|
|
void EnforceHierarchy( void )
|
|
{
|
|
bool bSort = true;
|
|
int i, j, k, count = 0;
|
|
|
|
// force changes to hierarchy
|
|
for( i = 0; i < g_numforcedhierarchy; i++ )
|
|
{
|
|
j = findGlobalBone( g_forcedhierarchy[i].parentname );
|
|
k = findGlobalBone( g_forcedhierarchy[i].childname );
|
|
|
|
if( j == -1 && Q_strlen( g_forcedhierarchy[i].parentname ) > 0 )
|
|
{
|
|
COM_FatalError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].parentname );
|
|
}
|
|
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].childname );
|
|
}
|
|
|
|
g_bonetable[k].parent = j;
|
|
}
|
|
|
|
while( bSort )
|
|
{
|
|
count++;
|
|
bSort = false;
|
|
for( i = 0; i < g_numbones; i++ )
|
|
{
|
|
if( g_bonetable[i].parent > i )
|
|
{
|
|
// swap
|
|
j = g_bonetable[i].parent;
|
|
s_bonetable_t tmp;
|
|
tmp = g_bonetable[i];
|
|
g_bonetable[i] = g_bonetable[j];
|
|
g_bonetable[j] = tmp;
|
|
|
|
// relink parents
|
|
for( k = i; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == i )
|
|
g_bonetable[k].parent = j;
|
|
else if( g_bonetable[k].parent == j )
|
|
g_bonetable[k].parent = i;
|
|
}
|
|
bSort = true;
|
|
}
|
|
}
|
|
|
|
if( count > 1000 )
|
|
COM_FatalError( "circular bone heirarchy\n" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: build transforms in source space, assuming source bones
|
|
//-----------------------------------------------------------------------------
|
|
void BuildRawTransforms( s_model_t const *pmodel, Vector const &shift, Radian const &rotate, matrix3x4 *boneToWorld )
|
|
{
|
|
matrix3x4 rootxform = matrix3x4( g_vecZero, rotate );
|
|
matrix3x4 bonematrix;
|
|
|
|
// build source space local to world transforms
|
|
for( int k = 0; k < pmodel->numbones; k++ )
|
|
{
|
|
Radian rot = pmodel->skeleton[k].rot;
|
|
Vector pos = pmodel->skeleton[k].pos;
|
|
|
|
if( pmodel->localBone[k].parent == -1 )
|
|
{
|
|
// translate
|
|
Vector tmp = pos - shift;
|
|
|
|
// rotate
|
|
pos = rootxform.VectorRotate( tmp );
|
|
bonematrix = rootxform.ConcatTransforms( matrix3x4( g_vecZero, rot ));
|
|
bonematrix.GetStudioTransform( tmp, rot );
|
|
clip_rotations( rot );
|
|
}
|
|
|
|
bonematrix = matrix3x4( pos, rot );
|
|
|
|
if( pmodel->localBone[k].parent == -1 ) boneToWorld[k] = bonematrix;
|
|
else boneToWorld[k] = boneToWorld[pmodel->localBone[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: build transforms in source space, assuming source bones
|
|
//-----------------------------------------------------------------------------
|
|
void BuildRawTransforms( s_animation_t const *panim, int frame, Vector const &shift, Radian const &rotate, matrix3x4 *boneToWorld )
|
|
{
|
|
matrix3x4 rootxform = matrix3x4( g_vecZero, rotate );
|
|
matrix3x4 bonematrix;
|
|
|
|
if( FBitSet( panim->flags, STUDIO_LOOPING ))
|
|
{
|
|
if( frame )
|
|
{
|
|
while( frame < 0)
|
|
frame += panim->source.numframes;
|
|
frame = frame % panim->source.numframes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
frame = bound( 0, frame, panim->source.numframes - 1 );
|
|
}
|
|
|
|
// build source space local to world transforms
|
|
for( int k = 0; k < panim->numbones; k++ )
|
|
{
|
|
Radian rot = panim->rawanim[frame][k].rot;
|
|
Vector pos = panim->rawanim[frame][k].pos;
|
|
|
|
if( panim->localBone[k].parent == -1)
|
|
{
|
|
// translate
|
|
Vector tmp = pos - shift;
|
|
|
|
// rotate
|
|
pos = rootxform.VectorRotate( tmp );
|
|
bonematrix = rootxform.ConcatTransforms( matrix3x4( g_vecZero, rot ));
|
|
bonematrix.GetStudioTransform( tmp, rot );
|
|
clip_rotations( rot );
|
|
}
|
|
|
|
bonematrix = matrix3x4( pos, rot );
|
|
|
|
if( panim->localBone[k].parent == -1 ) boneToWorld[k] = bonematrix;
|
|
else boneToWorld[k] = boneToWorld[panim->localBone[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
void RebuildLocalPose( void )
|
|
{
|
|
matrix3x4 boneToPose[MAXSTUDIOBONES];
|
|
matrix3x4 poseToBone[MAXSTUDIOBONES];
|
|
int j;
|
|
|
|
// build reference pose
|
|
for( j = 0; j < g_numbones; j++ )
|
|
boneToPose[j] = g_bonetable[j].boneToPose;
|
|
|
|
// rebuild local pose
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].parent == -1 ) g_bonetable[j].rawLocal = boneToPose[j];
|
|
else g_bonetable[j].rawLocal = poseToBone[g_bonetable[j].parent].ConcatTransforms( boneToPose[j] );
|
|
|
|
g_bonetable[j].rawLocal.GetStudioTransform( g_bonetable[j].pos, g_bonetable[j].rot );
|
|
g_bonetable[j].boneToPose = boneToPose[j];
|
|
poseToBone[j] = boneToPose[j].Invert();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Tags bones in the global bone table
|
|
//-----------------------------------------------------------------------------
|
|
void TagUsedImportedBones( void )
|
|
{
|
|
// NOTE: This has to happen because some bones referenced by bonemerge
|
|
// can be set up using the importbones feature
|
|
int k, j;
|
|
|
|
// Tag all bones marked as being used by bonemerge
|
|
int nBoneMergeCount = g_BoneMerge.Count();
|
|
for( k = 0; k < nBoneMergeCount; k++ )
|
|
{
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( Q_stricmp( g_BoneMerge[k].bonename, g_bonetable[j].name ))
|
|
continue;
|
|
g_bonetable[j].flags |= BONE_USED_BY_BONE_MERGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: look through all the sources and build a table of used bones
|
|
//-----------------------------------------------------------------------------
|
|
void BuildGlobalBonetable( void )
|
|
{
|
|
int i, j, k, n;
|
|
|
|
g_numbones = 0;
|
|
|
|
for( i = 0; i < MAXSTUDIOSRCBONES; i++ )
|
|
{
|
|
g_bonetable[i].srcRealign.Identity();
|
|
}
|
|
|
|
// insert predefined bones first
|
|
for( i = 0; i < g_numimportbones; i++ )
|
|
{
|
|
k = findGlobalBone( g_importbone[i].name );
|
|
|
|
if( k == -1 )
|
|
{
|
|
k = g_numbones;
|
|
Q_strncpy( g_bonetable[k].name, g_importbone[i].name, sizeof( g_bonetable[0].name ));
|
|
|
|
if( Q_strlen( g_importbone[i].parent ) == 0 )
|
|
{
|
|
g_bonetable[k].parent = -1;
|
|
}
|
|
else
|
|
{
|
|
// FIXME: This won't work if the imported bone refers to
|
|
// another imported bone which is further along in the list
|
|
g_bonetable[k].parent = findGlobalBone( g_importbone[i].parent );
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
MsgDev( D_WARN, "Imported bone %s tried to access parent bone %s and failed!\n",
|
|
g_importbone[i].name, g_importbone[i].parent );
|
|
}
|
|
}
|
|
|
|
g_bonetable[k].bPreDefined = true;
|
|
g_bonetable[k].rawLocal = g_importbone[i].rawLocal;
|
|
g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal;
|
|
g_numbones++;
|
|
}
|
|
|
|
g_bonetable[k].bDontCollapse = true;
|
|
g_bonetable[k].srcRealign = g_importbone[i].srcRealign;
|
|
g_bonetable[k].bPreAligned = true;
|
|
}
|
|
|
|
TagUsedImportedBones();
|
|
|
|
// union of all used bones
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
s_model_t *pmodel = g_model[i];
|
|
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
BuildRawTransforms( pmodel, g_vecZero, g_radZero, srcBoneToWorld );
|
|
|
|
for( j = 0; j < pmodel->numbones; j++ )
|
|
{
|
|
if( g_collapse_bones_aggressive )
|
|
{
|
|
if( pmodel->boneflags[j] == 0 )
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( pmodel->boneref[j] == 0 )
|
|
continue;
|
|
}
|
|
|
|
k = findGlobalBone( pmodel->localBone[j].name );
|
|
|
|
if( k == -1 )
|
|
{
|
|
// create new bone
|
|
k = g_numbones;
|
|
Q_strncpy( g_bonetable[k].name, pmodel->localBone[j].name, sizeof( g_bonetable[0].name ));
|
|
|
|
if(( n = pmodel->localBone[j].parent ) != -1 )
|
|
g_bonetable[k].parent = findGlobalBone( pmodel->localBone[n].name );
|
|
else g_bonetable[k].parent = -1;
|
|
|
|
g_bonetable[k].bonecontroller = 0;
|
|
g_bonetable[k].flags = pmodel->boneflags[j];
|
|
|
|
if( g_bonetable[k].parent == -1 || !g_bonetable[g_bonetable[k].parent].bPreAligned )
|
|
{
|
|
g_bonetable[k].rawLocal = matrix3x4( pmodel->skeleton[j].pos, pmodel->skeleton[j].rot );
|
|
g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal;
|
|
}
|
|
else
|
|
{
|
|
// convert the local relative position into a realigned relative position
|
|
matrix3x4 srcParentBoneToWorld;
|
|
srcParentBoneToWorld = srcBoneToWorld[n].ConcatTransforms( g_bonetable[g_bonetable[k].parent].srcRealign );
|
|
matrix3x4 invSrcParentBoneToWorld = srcParentBoneToWorld.Invert();
|
|
g_bonetable[k].rawLocal = invSrcParentBoneToWorld.ConcatTransforms( srcBoneToWorld[j] );
|
|
}
|
|
|
|
g_bonetable[k].rawLocal = matrix3x4( pmodel->skeleton[j].pos, pmodel->skeleton[j].rot );
|
|
g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal;
|
|
|
|
g_bonetable[k].boneToPose.Identity(); // in original code this was Invalidate (write NAN at each section)
|
|
g_numbones++;
|
|
continue;
|
|
}
|
|
|
|
if( g_overridebones && g_bonetable[k].bPreDefined )
|
|
{
|
|
g_bonetable[k].flags |= pmodel->boneflags[j];
|
|
|
|
g_bonetable[k].boneToPose = srcBoneToWorld[j].ConcatTransforms( g_bonetable[k].srcRealign );
|
|
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
g_bonetable[k].rawLocal = g_bonetable[k].boneToPose;
|
|
}
|
|
else
|
|
{
|
|
matrix3x4 tmp = g_bonetable[g_bonetable[k].parent].boneToPose.Invert();
|
|
g_bonetable[k].rawLocal = tmp.ConcatTransforms( g_bonetable[k].boneToPose );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// accumlate flags
|
|
g_bonetable[k].flags |= pmodel->boneflags[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find procedural bones and tag for inclusion even if they don't animate
|
|
//-----------------------------------------------------------------------------
|
|
void TagProceduralBones( void )
|
|
{
|
|
int numaxisinterpbones = 0;
|
|
int numquatinterpbones = 0;
|
|
int numaimatbones = 0;
|
|
int numjigglebones = 0;
|
|
int j;
|
|
|
|
// look for AxisInterp bone definitions
|
|
for( j = 0; j < g_numaxisinterpbones; j++ )
|
|
{
|
|
g_axisinterpbones[j].bone = findGlobalBone( g_axisinterpbones[j].bonename );
|
|
g_axisinterpbones[j].control = findGlobalBone( g_axisinterpbones[j].controlname );
|
|
|
|
if( g_axisinterpbones[j].bone == -1 )
|
|
{
|
|
MsgDev( D_WARN, "axisinterpbone \"%s\" unused\n", g_axisinterpbones[j].bonename );
|
|
continue; // optimized out, don't complain
|
|
}
|
|
|
|
if( g_axisinterpbones[j].control == -1 )
|
|
{
|
|
COM_FatalError( "Missing control bone \"%s\" for procedural bone \"%s\"\n",
|
|
g_axisinterpbones[j].bonename, g_axisinterpbones[j].controlname );
|
|
}
|
|
|
|
g_bonetable[g_axisinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
|
|
g_axisinterpbonemap[numaxisinterpbones++] = j;
|
|
}
|
|
g_numaxisinterpbones = numaxisinterpbones;
|
|
|
|
// look for QuatInterp bone definitions
|
|
for( j = 0; j < g_numquatinterpbones; j++ )
|
|
{
|
|
g_quatinterpbones[j].bone = findGlobalBoneXSI( g_quatinterpbones[j].bonename );
|
|
g_quatinterpbones[j].control = findGlobalBoneXSI( g_quatinterpbones[j].controlname );
|
|
|
|
if( g_quatinterpbones[j].bone == -1 )
|
|
{
|
|
MsgDev( D_WARN, "quatinterpbone \"%s\" unused\n", g_quatinterpbones[j].bonename );
|
|
continue; // optimized out, don't complain
|
|
}
|
|
|
|
if( g_quatinterpbones[j].control == -1 )
|
|
{
|
|
COM_FatalError( "Missing control bone \"%s\" for procedural bone \"%s\"\n",
|
|
g_quatinterpbones[j].bonename, g_quatinterpbones[j].controlname );
|
|
}
|
|
|
|
g_bonetable[g_quatinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
|
|
g_quatinterpbonemap[numquatinterpbones++] = j;
|
|
}
|
|
g_numquatinterpbones = numquatinterpbones;
|
|
|
|
// look for AimAt bone definitions
|
|
for( j = 0; j < g_numaimatbones; j++ )
|
|
{
|
|
g_aimatbones[j].bone = findGlobalBoneXSI( g_aimatbones[j].bonename );
|
|
|
|
if( g_aimatbones[j].bone == -1 )
|
|
{
|
|
MsgDev( D_WARN, "<aimconstraint> \"%s\" unused\n", g_aimatbones[j].bonename );
|
|
continue; // optimized out, don't complain
|
|
}
|
|
|
|
g_aimatbones[j].parent = findGlobalBoneXSI( g_aimatbones[j].parentname );
|
|
|
|
if( g_aimatbones[j].parent == -1 )
|
|
{
|
|
COM_FatalError( "Missing parent control bone \"%s\" for procedural bone \"%s\"\n",
|
|
g_aimatbones[j].parentname, g_aimatbones[j].bonename );
|
|
}
|
|
|
|
// Look for the aim bone as an attachment first
|
|
g_aimatbones[j].aimAttach = -1;
|
|
|
|
for( int ai = 0; ai < g_numattachments; ai++ )
|
|
{
|
|
if( !Q_strcmp( g_attachment[ai].name, g_aimatbones[j].aimname ))
|
|
{
|
|
g_aimatbones[j].aimAttach = ai;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( g_aimatbones[j].aimAttach == -1 )
|
|
{
|
|
g_aimatbones[j].aimBone = findGlobalBoneXSI( g_aimatbones[j].aimname );
|
|
|
|
if( g_aimatbones[j].aimBone == -1 )
|
|
{
|
|
COM_FatalError( "Missing aim control attachment or bone \"%s\" for procedural bone \"%s\"\n",
|
|
g_aimatbones[j].aimname, g_aimatbones[j].bonename );
|
|
}
|
|
}
|
|
|
|
g_bonetable[g_aimatbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules
|
|
g_aimatbonemap[numaimatbones++] = j;
|
|
}
|
|
g_numaimatbones = numaimatbones;
|
|
|
|
// look for Jiggle bone definitions
|
|
for( j = 0; j < g_numjigglebones; j++ )
|
|
{
|
|
g_jigglebones[j].bone = findGlobalBone( g_jigglebones[j].bonename );
|
|
|
|
if( g_jigglebones[j].bone == -1 )
|
|
{
|
|
MsgDev( D_WARN, "jigglebone \"%s\" unused\n", g_jigglebones[j].bonename );
|
|
continue; // optimized out, don't complain
|
|
}
|
|
|
|
g_bonetable[g_jigglebones[j].bone].flags |= BONE_JIGGLE_PROCEDURAL; // ??? what about physics rules
|
|
g_jigglebonemap[numjigglebones++] = j;
|
|
}
|
|
g_numjigglebones = numjigglebones;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert original procedural bone info into correct values for existing skeleton
|
|
//-----------------------------------------------------------------------------
|
|
void RemapProceduralBones( void )
|
|
{
|
|
int j;
|
|
|
|
// look for QuatInterp bone definitions
|
|
for( j = 0; j < g_numquatinterpbones; j++ )
|
|
{
|
|
s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]];
|
|
int origParent = findGlobalBoneXSI( pInterp->parentname );
|
|
int origControlParent = findGlobalBoneXSI( pInterp->controlparentname );
|
|
|
|
if( origParent == -1 )
|
|
{
|
|
COM_FatalError( "procedural bone \"%s\", can't find orig parent \"%s\"\n\n", pInterp->bonename, pInterp->parentname );
|
|
}
|
|
|
|
if( origControlParent == -1 )
|
|
{
|
|
COM_FatalError( "procedural bone \"%s\", can't find control parent \"%s\n\n", pInterp->bonename, pInterp->controlparentname );
|
|
}
|
|
|
|
if( g_bonetable[pInterp->bone].parent != origParent )
|
|
{
|
|
COM_FatalError( "unknown procedural bone parent remapping\n" );
|
|
}
|
|
|
|
if( g_bonetable[pInterp->control].parent != origControlParent )
|
|
{
|
|
COM_FatalError( "procedural bone \"%s\", parent remapping error, control parent was \"%s\", is now \"%s\"\n",
|
|
pInterp->bonename, pInterp->controlparentname, g_bonetable[g_bonetable[pInterp->control].parent].name );
|
|
}
|
|
|
|
// remap triggers and movements/rotations due to skeleton changes and realignment
|
|
for( int k = 0; k < pInterp->numtriggers; k++ )
|
|
{
|
|
int parent = g_bonetable[pInterp->control].parent;
|
|
|
|
// triggers are the "control" bone relative to the control's parent bone
|
|
if( parent != -1 )
|
|
{
|
|
matrix3x4 invControlParentRealign = g_bonetable[parent].srcRealign.Invert();
|
|
matrix3x4 srcControlParentBoneToPose = g_bonetable[parent].boneToPose.ConcatTransforms( invControlParentRealign );
|
|
matrix3x4 srcControlRelative = matrix3x4( g_vecZero, pInterp->trigger[k] );
|
|
matrix3x4 srcControlBoneToPose = srcControlParentBoneToPose.ConcatTransforms( srcControlRelative );
|
|
matrix3x4 destControlParentBoneToPose = srcControlParentBoneToPose.ConcatTransforms( g_bonetable[parent].srcRealign );
|
|
matrix3x4 destControlBoneToPose = srcControlBoneToPose.ConcatTransforms( g_bonetable[pInterp->control].srcRealign );
|
|
matrix3x4 invDestControlParentBoneToPose = destControlParentBoneToPose.Invert();
|
|
matrix3x4 destControlRelative = invDestControlParentBoneToPose.ConcatTransforms( destControlBoneToPose );
|
|
|
|
// FIXME: do revision
|
|
pInterp->trigger[k] = destControlRelative.GetQuaternion();
|
|
#if 0
|
|
Vector pos = srcControlRelative.GetOrigin();
|
|
Vector rot = srcControlRelative.GetAngles();
|
|
|
|
Msg( "srcControlRelative : %7.2f %7.2f %7.2f\n", rot.x, rot.y, rot.z );
|
|
|
|
pos = destControlRelative.GetOrigin();
|
|
rot = destControlRelative.GetAngles();
|
|
|
|
Msg( "destControlRelative : %7.2f %7.2f %7.2f\n", rot.x, rot.y, rot.z );
|
|
|
|
Msg( "\n" );
|
|
#endif
|
|
}
|
|
|
|
// movements are relative to the bone's parent
|
|
parent = g_bonetable[pInterp->bone].parent;
|
|
|
|
if( parent != -1 )
|
|
{
|
|
// Msg( "procedural bone \"%s\"\n", pInterp->bonename );
|
|
// Msg( "pre : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z );
|
|
// get local transform
|
|
matrix3x4 srcParentRelative = matrix3x4( pInterp->pos[k] + pInterp->basepos, pInterp->quat[k] );
|
|
|
|
// get original boneToPose
|
|
matrix3x4 invSrcRealign = g_bonetable[parent].srcRealign.Invert();
|
|
matrix3x4 origParentBoneToPose = g_bonetable[parent].boneToPose.ConcatTransforms( invSrcRealign );
|
|
|
|
// move bone adjustment into world position
|
|
matrix3x4 srcBoneToWorld = origParentBoneToPose.ConcatTransforms( srcParentRelative );
|
|
|
|
// calculate local transform
|
|
matrix3x4 parentPoseToBone = g_bonetable[parent].boneToPose.Invert();
|
|
matrix3x4 destBoneToWorld =parentPoseToBone.ConcatTransforms( srcBoneToWorld );
|
|
|
|
// save out the local transform
|
|
pInterp->quat[k] = destBoneToWorld.GetQuaternion();
|
|
pInterp->pos[k] = destBoneToWorld.GetOrigin();
|
|
|
|
pInterp->pos[k] += g_bonetable[pInterp->control].pos * pInterp->percentage;
|
|
|
|
// Msg("post : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// look for aimatbones
|
|
for( j = 0; j < g_numaimatbones; j++ )
|
|
{
|
|
s_aimatbone_t *pAimAtBone = &g_aimatbones[g_aimatbonemap[j]];
|
|
|
|
int origParent = findGlobalBoneXSI( pAimAtBone->parentname );
|
|
|
|
if( origParent == -1 )
|
|
{
|
|
COM_FatalError( "<aimconstraint> bone \"%s\", can't find parent bone \"%s\"\n\n", pAimAtBone->bonename, pAimAtBone->parentname );
|
|
}
|
|
|
|
int origAim = -1;
|
|
|
|
for( int ai = 0; ai < g_numattachments; ++ai )
|
|
{
|
|
if( !Q_strcmp( g_attachment[ai].name, pAimAtBone->aimname ))
|
|
{
|
|
origAim = ai;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( origAim == -1 )
|
|
{
|
|
COM_FatalError( "<aimconstraint> bone \"%s\", can't find aim bone \"%s\n\n", pAimAtBone->bonename, pAimAtBone->aimname );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: propogate procedural bone usage up its chain
|
|
//-----------------------------------------------------------------------------
|
|
void MarkProceduralBoneChain( void )
|
|
{
|
|
int k, fBoneFlags;
|
|
|
|
// look for QuatInterp bone definitions
|
|
for( int j = 0; j < g_numquatinterpbones; j++ )
|
|
{
|
|
s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]];
|
|
|
|
fBoneFlags = g_bonetable[pInterp->bone].flags & BONE_USED_MASK;
|
|
|
|
// propogate the procedural bone usage up its hierarchy
|
|
k = pInterp->control;
|
|
while( k != -1 )
|
|
{
|
|
g_bonetable[k].flags |= fBoneFlags;
|
|
k = g_bonetable[k].parent;
|
|
}
|
|
|
|
// propogate the procedural bone usage up its hierarchy
|
|
k = pInterp->bone;
|
|
while( k != -1 )
|
|
{
|
|
g_bonetable[k].flags |= fBoneFlags;
|
|
k = g_bonetable[k].parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MapSourcesToGlobalBonetable( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
// map each source bone list to master list
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
for( k = 0; k < MAXSTUDIOSRCBONES; k++ )
|
|
{
|
|
g_model[i]->boneGlobalToLocal[k] = -1;
|
|
g_model[i]->boneLocalToGlobal[k] = -1;
|
|
}
|
|
|
|
for( j = 0; j < g_model[i]->numbones; j++ )
|
|
{
|
|
k = findGlobalBone( g_model[i]->localBone[j].name );
|
|
|
|
if( k == -1 )
|
|
{
|
|
int m = g_model[i]->localBone[j].parent;
|
|
while( m != -1 && ( k = findGlobalBone( g_model[i]->localBone[m].name )) == -1 )
|
|
{
|
|
m = g_model[i]->localBone[m].parent;
|
|
}
|
|
|
|
if( k == -1 ) k = 0;
|
|
|
|
g_model[i]->boneLocalToGlobal[j] = k;
|
|
}
|
|
else
|
|
{
|
|
g_model[i]->boneLocalToGlobal[j] = k;
|
|
g_model[i]->boneGlobalToLocal[k] = j;
|
|
}
|
|
}
|
|
}
|
|
|
|
// map each sequences bone list to master list
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
for( k = 0; k < MAXSTUDIOSRCBONES; k++ )
|
|
{
|
|
panim->boneGlobalToLocal[k] = -1;
|
|
panim->boneLocalToGlobal[k] = -1;
|
|
}
|
|
|
|
for( j = 0; j < panim->numbones; j++ )
|
|
{
|
|
k = findGlobalBone( panim->localBone[j].name );
|
|
|
|
if( k == -1 )
|
|
{
|
|
int m = panim->localBone[j].parent;
|
|
while( m != -1 && ( k = findGlobalBone( panim->localBone[m].name )) == -1 )
|
|
{
|
|
m = panim->localBone[m].parent;
|
|
}
|
|
|
|
if( k == -1 ) k = 0;
|
|
|
|
panim->boneLocalToGlobal[j] = k;
|
|
}
|
|
else
|
|
{
|
|
panim->boneLocalToGlobal[j] = k;
|
|
panim->boneGlobalToLocal[k] = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: go through bone and find any that arent aligned on the X axis
|
|
//-----------------------------------------------------------------------------
|
|
void RealignBones( void )
|
|
{
|
|
matrix3x4 boneToPose[MAXSTUDIOBONES];
|
|
int childbone[MAXSTUDIOBONES];
|
|
matrix3x4 poseToBone, bonematrix;
|
|
int i, j, k;
|
|
|
|
for( j = 0; j < g_numbones; j++ )
|
|
childbone[j] = -1;
|
|
|
|
// force bones with IK rules to realign themselves
|
|
for( i = 0; i < g_numikchains; i++ )
|
|
{
|
|
k = g_ikchain[i].link[0].bone;
|
|
if( childbone[k] == -1 || childbone[k] == g_ikchain[i].link[1].bone )
|
|
{
|
|
childbone[k] = g_ikchain[i].link[1].bone;
|
|
}
|
|
else
|
|
{
|
|
COM_FatalError( "Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n",
|
|
g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[1].bone].name );
|
|
}
|
|
|
|
k = g_ikchain[i].link[1].bone;
|
|
if( childbone[k] == -1 || childbone[k] == g_ikchain[i].link[2].bone )
|
|
{
|
|
childbone[k] = g_ikchain[i].link[2].bone;
|
|
}
|
|
else
|
|
{
|
|
COM_FatalError( "Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n",
|
|
g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[2].bone].name );
|
|
}
|
|
}
|
|
|
|
if( g_realignbones )
|
|
{
|
|
int children[MAXSTUDIOBONES];
|
|
|
|
// count children
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
children[k] = 0;
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent != -1 )
|
|
children[g_bonetable[k].parent]++;
|
|
}
|
|
|
|
// if my parent bone only has one child, then tell it to align to me
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent != -1 && children[g_bonetable[k].parent] == 1 )
|
|
childbone[g_bonetable[k].parent] = k;
|
|
}
|
|
}
|
|
|
|
for( j = 0; j < g_numbones; j++ )
|
|
boneToPose[j] = g_bonetable[j].boneToPose;
|
|
|
|
// look for bones that aren't on a primary X axis
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( !g_bonetable[k].bPreAligned && childbone[k] != -1 )
|
|
{
|
|
float d = g_bonetable[childbone[k]].pos.Length();
|
|
|
|
// check to see that it's on positive X
|
|
if( d - g_bonetable[childbone[k]].pos.x > 0.01 )
|
|
{
|
|
Vector forward, left, up;
|
|
Vector v2, v3;
|
|
|
|
// calc X axis
|
|
g_bonetable[childbone[k]].boneToPose.GetOrigin( v2 );
|
|
g_bonetable[k].boneToPose.GetOrigin( v3 );
|
|
forward = (v2 - v3).Normalize();
|
|
|
|
// try to align to existing bone/boundingbox by finding most perpendicular
|
|
// existing axis and aligning the new Z axis to it.
|
|
Vector forward2 = boneToPose[k].GetForward();
|
|
Vector left2 = boneToPose[k].GetRight(); // FIXME: wrong name
|
|
Vector up2 = boneToPose[k].GetUp();
|
|
|
|
float d1 = fabs( DotProduct( forward, forward2 ));
|
|
float d2 = fabs( DotProduct( forward, left2 ));
|
|
float d3 = fabs( DotProduct( forward, up2 ));
|
|
|
|
if( d1 <= d2 && d1 <= d3 )
|
|
{
|
|
up = CrossProduct( forward, forward2 ).Normalize();
|
|
}
|
|
else if( d2 <= d1 && d2 <= d3 )
|
|
{
|
|
up = CrossProduct( forward, left2 ).Normalize();
|
|
}
|
|
else
|
|
{
|
|
up = CrossProduct( forward, up2 ).Normalize();
|
|
}
|
|
|
|
left = CrossProduct( up, forward );
|
|
|
|
// setup matrix
|
|
boneToPose[k].SetForward( forward );
|
|
boneToPose[k].SetRight( left );
|
|
boneToPose[k].SetUp( up );
|
|
|
|
// check orthonormality of matrix
|
|
d = fabs( DotProduct( forward, left ))
|
|
+ fabs( DotProduct( left, up ))
|
|
+ fabs( DotProduct( up, forward ))
|
|
+ fabs( DotProduct( boneToPose[k].GetForward(), boneToPose[k].GetRight() ))
|
|
+ fabs( DotProduct( boneToPose[k].GetRight(), boneToPose[k].GetUp() ))
|
|
+ fabs( DotProduct( boneToPose[k].GetUp(), boneToPose[k].GetForward() ));
|
|
|
|
if( d > 0.0001 )
|
|
COM_FatalError( "error with realigning bone %s\n", g_bonetable[k].name );
|
|
boneToPose[k].SetOrigin( v3 );
|
|
}
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < g_numforcedrealign; i++ )
|
|
{
|
|
k = findGlobalBone( g_forcedrealign[i].name );
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone %s in $forcedrealign\n", g_forcedrealign[i].name );
|
|
}
|
|
|
|
matrix3x4 local = matrix3x4( g_vecZero, g_forcedrealign[i].rot );
|
|
boneToPose[k] = boneToPose[k].ConcatTransforms( local );
|
|
}
|
|
|
|
// build realignment transforms
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].bPreAligned )
|
|
continue;
|
|
|
|
poseToBone = g_bonetable[j].boneToPose.Invert();
|
|
g_bonetable[j].srcRealign = poseToBone.ConcatTransforms( boneToPose[j] );
|
|
g_bonetable[j].boneToPose = boneToPose[j];
|
|
}
|
|
|
|
// rebuild default angles, position, etc.
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].bPreAligned )
|
|
continue;
|
|
|
|
if( g_bonetable[j].parent == -1 )
|
|
{
|
|
bonematrix = g_bonetable[j].boneToPose;
|
|
}
|
|
else
|
|
{
|
|
// convert my transform into parent relative space
|
|
poseToBone = g_bonetable[g_bonetable[j].parent].boneToPose.Invert();
|
|
bonematrix = poseToBone.ConcatTransforms( g_bonetable[j].boneToPose );
|
|
}
|
|
|
|
bonematrix.GetStudioTransform( g_bonetable[j].pos, g_bonetable[j].rot );
|
|
}
|
|
|
|
// build reference pose
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
bonematrix = matrix3x4( g_bonetable[j].pos, g_bonetable[j].rot );
|
|
|
|
if( g_bonetable[j].parent == -1 ) g_bonetable[j].boneToPose = bonematrix;
|
|
else g_bonetable[j].boneToPose = g_bonetable[g_bonetable[j].parent].boneToPose.ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find all the different bones used in all the source files and map everything
|
|
// to a common bonetable.
|
|
//-----------------------------------------------------------------------------
|
|
void RemapBones( void )
|
|
{
|
|
g_real_numbones = g_numbones;
|
|
g_real_numseq = g_numseq;
|
|
g_real_numani = g_numani;
|
|
|
|
if( g_staticprop )
|
|
{
|
|
MakeStaticProp( );
|
|
}
|
|
else if ( g_centerstaticprop )
|
|
{
|
|
MsgDev( D_WARN, "Ignoring option $autocenter. Only supported on $staticprop models!\n" );
|
|
}
|
|
|
|
TagUsedBones( );
|
|
|
|
RenameBones( );
|
|
|
|
BuildGlobalBonetable( );
|
|
|
|
BuildGlobalBoneToPose( );
|
|
|
|
EnforceHierarchy( );
|
|
|
|
// tag parent bones as being in the same way as their children
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
int n = g_bonetable[k].parent;
|
|
while( n != -1 )
|
|
{
|
|
g_bonetable[n].flags |= g_bonetable[k].flags;
|
|
n = g_bonetable[n].parent;
|
|
}
|
|
}
|
|
|
|
if( g_collapse_bones || g_numimportbones )
|
|
{
|
|
CollapseBones( );
|
|
}
|
|
|
|
if( g_numbones >= MAXSTUDIOBONES )
|
|
COM_FatalError( "Too many bones used in model, used %d, max %d\n", g_numbones, MAXSTUDIOBONES );
|
|
|
|
RebuildLocalPose( );
|
|
|
|
TagProceduralBones( );
|
|
|
|
MapSourcesToGlobalBonetable( );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert source bone animation into global bone animation
|
|
//-----------------------------------------------------------------------------
|
|
void TranslateAnimations( const int boneGlobalToLocal[MAXSTUDIOSRCBONES], const matrix3x4 *srcBoneToWorld, matrix3x4 *destBoneToWorld )
|
|
{
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
int q = boneGlobalToLocal[k];
|
|
matrix3x4 bonematrix;
|
|
|
|
if( q == -1 )
|
|
{
|
|
// unknown bone, copy over defaults
|
|
if( g_bonetable[k].parent >= 0 )
|
|
{
|
|
bonematrix = matrix3x4( g_bonetable[k].pos, g_bonetable[k].rot );
|
|
destBoneToWorld[k] = destBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
else
|
|
{
|
|
destBoneToWorld[k] = matrix3x4( g_bonetable[k].pos, g_bonetable[k].rot );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
destBoneToWorld[k] = srcBoneToWorld[q].ConcatTransforms( g_bonetable[k].srcRealign );
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemapVertices( void )
|
|
{
|
|
int i, j;
|
|
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
matrix3x4 destBoneToWorld[MAXSTUDIOSRCBONES];
|
|
Vector tmp1, tmp2, vdest, ndest;
|
|
s_model_t *pmodel = g_model[i];
|
|
|
|
BuildRawTransforms( pmodel, g_vecZero, g_radZero, srcBoneToWorld );
|
|
TranslateAnimations( pmodel->boneGlobalToLocal, srcBoneToWorld, destBoneToWorld );
|
|
|
|
for( j = 0; j < pmodel->numsrcverts; j++ )
|
|
{
|
|
vdest.Init();
|
|
ndest.Init();
|
|
|
|
for( int n = 0; n < pmodel->srcvert[j].localWeight.numbones; n++ )
|
|
{
|
|
|
|
int q = pmodel->srcvert[j].localWeight.bone[n]; // src bone
|
|
int k = pmodel->boneLocalToGlobal[q]; // mapping to global bone
|
|
|
|
if( k == -1 )
|
|
{
|
|
vdest = pmodel->srcvert[j].vert;
|
|
ndest = pmodel->srcvert[j].norm;
|
|
break; // staticprop
|
|
}
|
|
|
|
for( int m = 0; m < pmodel->srcvert[j].globalWeight.numbones; m++ )
|
|
{
|
|
if( k == pmodel->srcvert[j].globalWeight.bone[m] )
|
|
{
|
|
// bone got collapsed out
|
|
pmodel->srcvert[j].globalWeight.weight[m] += pmodel->srcvert[j].localWeight.weight[n];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( m == pmodel->srcvert[j].globalWeight.numbones )
|
|
{
|
|
// add new bone
|
|
pmodel->srcvert[j].globalWeight.bone[m] = k;
|
|
pmodel->srcvert[j].globalWeight.weight[m] = pmodel->srcvert[j].localWeight.weight[n];
|
|
pmodel->srcvert[j].globalWeight.numbones++;
|
|
}
|
|
|
|
if( has_boneweights )
|
|
{
|
|
// convert vertex into original models' bone local space
|
|
tmp1 = destBoneToWorld[k].VectorITransform( pmodel->srcvert[j].vert );
|
|
// convert that into global world space using stardard pose
|
|
tmp2 = g_bonetable[k].boneToPose.VectorTransform( tmp1 );
|
|
// accumulate
|
|
vdest += tmp2 * pmodel->srcvert[j].localWeight.weight[n];
|
|
|
|
// convert vertex into original models' bone local space
|
|
tmp1 = destBoneToWorld[k].VectorIRotate( pmodel->srcvert[j].norm );
|
|
// convert that into global world space using stardard pose
|
|
tmp2 = g_bonetable[k].boneToPose.VectorRotate( tmp1 );
|
|
// accumulate
|
|
ndest += tmp2 * pmodel->srcvert[j].localWeight.weight[n];
|
|
}
|
|
else
|
|
{
|
|
vdest = g_bonetable[k].boneToPose.VectorITransform( pmodel->srcvert[j].vert );
|
|
ndest = g_bonetable[k].boneToPose.VectorIRotate( pmodel->srcvert[j].norm );
|
|
}
|
|
}
|
|
|
|
pmodel->srcvert[j].vert = vdest;
|
|
pmodel->srcvert[j].norm = ndest.Normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: make indexed vertex and normal arrays
|
|
//-----------------------------------------------------------------------------
|
|
void BuildVertexArrays( void )
|
|
{
|
|
for( int i = 0; i < g_nummodels; i++ )
|
|
{
|
|
s_model_t *pmodel = g_model[i];
|
|
|
|
for( int j = 0; j < pmodel->nummesh; j++ )
|
|
{
|
|
s_mesh_t *pmesh = pmodel->pmesh[j];
|
|
|
|
for( int k = 0; k < pmesh->numtris; k++ )
|
|
{
|
|
s_trianglevert_t *ptriv = pmesh->triangle[k];
|
|
|
|
for( int q = 0; q < 3; q++ )
|
|
{
|
|
ptriv[q].vertindex = LookupVertex( pmodel, &pmodel->srcvert[ptriv[q].vertindex] );
|
|
ptriv[q].normindex = LookupNormal( pmodel, &pmodel->srcvert[ptriv[q].normindex] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: convert source bone animation into global bone animation
|
|
//-----------------------------------------------------------------------------
|
|
void ConvertAnimation( s_animation_t const *panim, int frame, s_bone_t *dest )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
matrix3x4 destBoneToWorld[MAXSTUDIOSRCBONES];
|
|
matrix3x4 destWorldToBone[MAXSTUDIOSRCBONES];
|
|
matrix3x4 bonematrix;
|
|
int k;
|
|
|
|
BuildRawTransforms( panim, frame, panim->adjust, panim->rotation, srcBoneToWorld );
|
|
TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, destBoneToWorld );
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
destWorldToBone[k] = destBoneToWorld[k].Invert();
|
|
}
|
|
|
|
// convert source_space_local_to_world transforms to shared_space_local_to_world transforms
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
bonematrix = destBoneToWorld[k];
|
|
}
|
|
else
|
|
{
|
|
// convert my transform into parent relative space
|
|
bonematrix = destWorldToBone[g_bonetable[k].parent].ConcatTransforms( destBoneToWorld[k] );
|
|
}
|
|
|
|
bonematrix.GetStudioTransform( dest[k].pos, dest[k].rot );
|
|
clip_rotations( dest[k].rot );
|
|
}
|
|
}
|
|
|
|
void RemapAnimations( void )
|
|
{
|
|
int size = g_numbones * sizeof( s_bone_t );
|
|
s_animation_t *panim;
|
|
int i, j, n;
|
|
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
panim = g_panimation[i];
|
|
n = panim->startframe - panim->source.startframe;
|
|
for( j = 0; j < panim->numframes; j++ )
|
|
{
|
|
panim->sanim[j] = (s_bone_t *)Mem_Alloc( size );
|
|
ConvertAnimation( panim, n + j, panim->sanim[j] );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calculate the bone to world transforms for a processed animation
|
|
//-----------------------------------------------------------------------------
|
|
void CalcBoneTransforms( s_animation_t *panimation, s_animation_t *pbaseanimation, int frame, matrix3x4 *pBoneToWorld )
|
|
{
|
|
if( FBitSet( panimation->flags, STUDIO_LOOPING ) && panimation->numframes > 1 )
|
|
{
|
|
while( frame >= ( panimation->numframes - 1 ))
|
|
frame = frame - (panimation->numframes - 1);
|
|
}
|
|
|
|
if( frame < 0 || frame >= panimation->numframes )
|
|
{
|
|
COM_FatalError( "requested out of range frame on animation \"%s\" : %d (%d)\n", panimation->name, frame, panimation->numframes );
|
|
}
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
matrix3x4 bonematrix;
|
|
Vector angle;
|
|
|
|
if( !FBitSet( panimation->flags, STUDIO_DELTA ))
|
|
{
|
|
bonematrix = matrix3x4( panimation->sanim[frame][k].pos, panimation->sanim[frame][k].rot );
|
|
}
|
|
else if( pbaseanimation )
|
|
{
|
|
Vector4D q1, q2, q3;
|
|
Vector p3;
|
|
|
|
AngleQuaternion( pbaseanimation->sanim[0][k].rot, q1 );
|
|
AngleQuaternion( panimation->sanim[frame][k].rot, q2 );
|
|
|
|
float s = panimation->weight[k];
|
|
|
|
QuaternionMA( q1, s, q2, q3 );
|
|
p3 = pbaseanimation->sanim[0][k].pos + s * panimation->sanim[frame][k].pos;
|
|
bonematrix = matrix3x4( p3, q3 );
|
|
}
|
|
else
|
|
{
|
|
Vector4D q1, q2, q3;
|
|
Vector p3;
|
|
|
|
AngleQuaternion( g_bonetable[k].rot, q1 );
|
|
AngleQuaternion( panimation->sanim[frame][k].rot, q2 );
|
|
|
|
float s = panimation->weight[k];
|
|
|
|
QuaternionMA( q1, s, q2, q3 );
|
|
p3 = pbaseanimation->sanim[0][k].pos + s * g_bonetable[k].pos;
|
|
|
|
bonematrix = matrix3x4( p3, q3 );
|
|
}
|
|
|
|
if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix;
|
|
else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
void CalcBoneTransforms( s_animation_t *panimation, int frame, matrix3x4 *pBoneToWorld )
|
|
{
|
|
CalcBoneTransforms( panimation, g_panimation[0], frame, pBoneToWorld );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calculate the bone to world transforms for a processed animation
|
|
//-----------------------------------------------------------------------------
|
|
void CalcBoneTransformsCycle( s_animation_t *panimation, s_animation_t *pbaseanimation, float flCycle, matrix3x4 *pBoneToWorld )
|
|
{
|
|
float fFrame = flCycle * (panimation->numframes - 1);
|
|
int iFrame = (int)fFrame;
|
|
float s = (fFrame - iFrame);
|
|
|
|
int iFrame1 = iFrame % (panimation->numframes - 1);
|
|
int iFrame2 = (iFrame + 1) % (panimation->numframes - 1);
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
Vector4D q1, q2, q3;
|
|
matrix3x4 bonematrix;
|
|
Vector p3;
|
|
|
|
AngleQuaternion( panimation->sanim[iFrame1][k].rot, q1 );
|
|
AngleQuaternion( panimation->sanim[iFrame2][k].rot, q2 );
|
|
QuaternionSlerp( q1, q2, s, q3 );
|
|
|
|
VectorLerp( panimation->sanim[iFrame1][k].pos, s, panimation->sanim[iFrame2][k].pos, p3 );
|
|
|
|
bonematrix = matrix3x4( p3, q3 );
|
|
|
|
if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix;
|
|
else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calculate the bone to world transforms for a processed sequence
|
|
//-----------------------------------------------------------------------------
|
|
void SlerpBones( Vector4D q1[MAXSTUDIOBONES], Vector pos1[MAXSTUDIOBONES], int sequence, const Vector4D q2[MAXSTUDIOBONES], const Vector pos2[MAXSTUDIOBONES], float s )
|
|
{
|
|
int i;
|
|
Vector4D q3, q4;
|
|
float s1, s2;
|
|
|
|
s_sequence_t *pseqdesc = &g_sequence[sequence];
|
|
|
|
if( s <= 0.0f )
|
|
{
|
|
return;
|
|
}
|
|
else if( s > 1.0f )
|
|
{
|
|
s = 1.0f;
|
|
}
|
|
|
|
if( pseqdesc->flags & STUDIO_DELTA )
|
|
{
|
|
for( i = 0; i < g_numbones; i++ )
|
|
{
|
|
|
|
s2 = s * pseqdesc->weight[i]; // blend in based on this bones weight
|
|
if( s2 > 0.0 )
|
|
{
|
|
if( pseqdesc->flags & STUDIO_POST )
|
|
{
|
|
QuaternionMA( q1[i], s2, q2[i], q1[i] );
|
|
// FIXME: are these correct?
|
|
pos1[i] = pos1[i] + pos2[i] * s2;
|
|
}
|
|
else
|
|
{
|
|
QuaternionSM( s2, q2[i], q1[i], q1[i] );
|
|
// FIXME: are these correct?
|
|
pos1[i] = pos1[i] + pos2[i] * s2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < g_numbones; i++ )
|
|
{
|
|
s2 = s * pseqdesc->weight[i]; // blend in based on this animations weights
|
|
|
|
if( s2 > 0.0f )
|
|
{
|
|
s1 = 1.0f - s2;
|
|
|
|
if( g_bonetable[i].flags & BONE_FIXED_ALIGNMENT )
|
|
QuaternionSlerpNoAlign( q2[i], q1[i], s1, q3 );
|
|
else QuaternionSlerp( q2[i], q1[i], s1, q3 );
|
|
|
|
pos1[i] = pos1[i] * s1 + pos2[i] * s2;
|
|
q1[i] = q3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CalcPoseSingle( Vector pos[], Vector4D q[], int sequence, float frame )
|
|
{
|
|
s_sequence_t *pseqdesc = &g_sequence[sequence];
|
|
s_animation_t *panim = pseqdesc->panim[0];
|
|
|
|
// FIXME: is this modulo correct?
|
|
int iframe = ((int)frame) % panim->numframes;
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
// FIXME: this isn't doing a fractional frame
|
|
AngleQuaternion( panim->sanim[iframe][k].rot, q[k] );
|
|
pos[k] = panim->sanim[iframe][k].pos;
|
|
}
|
|
}
|
|
|
|
void AccumulatePose( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight )
|
|
{
|
|
Vector pos2[MAXSTUDIOBONES];
|
|
Vector4D q2[MAXSTUDIOBONES];
|
|
|
|
CalcPoseSingle( pos2, q2, sequence, frame );
|
|
SlerpBones( q, pos, sequence, q2, pos2, flWeight );
|
|
AccumulateSeqLayers( pos, q, sequence, frame, flWeight );
|
|
}
|
|
|
|
void AccumulateSeqLayers( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight )
|
|
{
|
|
s_sequence_t *pseqdesc = &g_sequence[sequence];
|
|
|
|
for( int i = 0; i < pseqdesc->numautolayers; i++ )
|
|
{
|
|
s_autolayer_t *pLayer = &pseqdesc->autolayer[i];
|
|
|
|
float layerFrame = frame;
|
|
float layerWeight = flWeight;
|
|
|
|
if( pLayer->start != pLayer->end )
|
|
{
|
|
float s = 1.0;
|
|
float index;
|
|
|
|
if( !FBitSet( pLayer->flags, STUDIO_AL_POSE ))
|
|
{
|
|
index = frame;
|
|
}
|
|
else
|
|
{
|
|
int iPose = pLayer->pose;
|
|
if( iPose != -1 )
|
|
{
|
|
index = 0; // undefined?
|
|
}
|
|
else
|
|
{
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
if( index < pLayer->start )
|
|
continue;
|
|
if( index >= pLayer->end )
|
|
continue;
|
|
|
|
if( index < pLayer->peak && pLayer->start != pLayer->peak )
|
|
{
|
|
s = (index - pLayer->start) / (pLayer->peak - pLayer->start);
|
|
}
|
|
else if( index > pLayer->tail && pLayer->end != pLayer->tail )
|
|
{
|
|
s = (pLayer->end - index) / (pLayer->end - pLayer->tail);
|
|
}
|
|
|
|
if( pLayer->flags & STUDIO_AL_SPLINE )
|
|
{
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
}
|
|
|
|
if(( pLayer->flags & STUDIO_AL_XFADE ) && ( frame > pLayer->tail ))
|
|
{
|
|
layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight );
|
|
}
|
|
else if( pLayer->flags & STUDIO_AL_NOBLEND )
|
|
{
|
|
layerWeight = s;
|
|
}
|
|
else
|
|
{
|
|
layerWeight = flWeight * s;
|
|
}
|
|
|
|
if( !FBitSet( pLayer->flags, STUDIO_AL_POSE ))
|
|
{
|
|
layerFrame = ((frame - pLayer->start) / (pLayer->end - pLayer->start)) * (g_sequence[pLayer->sequence].panim[0]->numframes - 1);
|
|
}
|
|
else
|
|
{
|
|
layerFrame = (frame / g_sequence[sequence].panim[0]->numframes - 1) * (g_sequence[pLayer->sequence].panim[0]->numframes - 1);
|
|
}
|
|
}
|
|
|
|
AccumulatePose( pos, q, pLayer->sequence, layerFrame, layerWeight );
|
|
}
|
|
}
|
|
|
|
void CalcSeqTransforms( int sequence, int frame, matrix3x4 *pBoneToWorld )
|
|
{
|
|
Vector pos[MAXSTUDIOBONES];
|
|
Vector4D q[MAXSTUDIOBONES];
|
|
int k;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
AngleQuaternion( g_bonetable[k].rot, q[k] );
|
|
pos[k] = g_bonetable[k].pos;
|
|
}
|
|
|
|
AccumulatePose( pos, q, sequence, frame, 1.0 );
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
matrix3x4 bonematrix = matrix3x4( pos[k], q[k] );
|
|
|
|
if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix;
|
|
else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix );
|
|
}
|
|
}
|
|
|
|
void buildAnimationWeights( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
// relink animation weights
|
|
for( i = 0; i < g_numweightlist; i++ )
|
|
{
|
|
if( i == 0 )
|
|
{
|
|
// initialize weights
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].parent != -1 )
|
|
{
|
|
// set child bones to uninitialized
|
|
g_weightlist[i].weight[j] = -1.0f;
|
|
}
|
|
else if( i == 0 )
|
|
{
|
|
// set root bones to 1
|
|
g_weightlist[i].weight[j] = 1.0f;
|
|
g_weightlist[i].posweight[j] = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// initialize weights
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_bonetable[j].parent != -1 )
|
|
{
|
|
// set child bones to uninitialized
|
|
g_weightlist[i].weight[j] = g_weightlist[0].weight[j];
|
|
g_weightlist[i].posweight[j] = g_weightlist[0].posweight[j];
|
|
}
|
|
else
|
|
{
|
|
// set root bones to 0
|
|
g_weightlist[i].weight[j] = 0.0f;
|
|
g_weightlist[i].posweight[j] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// match up weights
|
|
for( j = 0; j < g_weightlist[i].numbones; j++ )
|
|
{
|
|
k = findGlobalBone( g_weightlist[i].bonename[j] );
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone reference '%s' in weightlist '%s'\n",
|
|
g_weightlist[i].bonename[j], g_weightlist[i].name );
|
|
}
|
|
g_weightlist[i].weight[k] = g_weightlist[i].boneweight[j];
|
|
g_weightlist[i].posweight[k] = g_weightlist[i].boneposweight[j];
|
|
}
|
|
}
|
|
|
|
for( i = 0; i < g_numweightlist; i++ )
|
|
{
|
|
// copy weights forward
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( g_weightlist[i].weight[j] < 0.0f )
|
|
{
|
|
if( g_bonetable[j].parent != -1 )
|
|
{
|
|
g_weightlist[i].weight[j] = g_weightlist[i].weight[g_bonetable[j].parent];
|
|
g_weightlist[i].posweight[j] = g_weightlist[i].posweight[g_bonetable[j].parent];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: subtract each frame running interpolation of the first frame to the last frame
|
|
//-----------------------------------------------------------------------------
|
|
void linearDelta( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
|
|
{
|
|
// create delta animations
|
|
s_bone_t src0[MAXSTUDIOSRCBONES];
|
|
s_bone_t src1[MAXSTUDIOSRCBONES];
|
|
int j, k;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
src0[k].pos = psrc->sanim[0][k].pos;
|
|
src0[k].rot = psrc->sanim[0][k].rot;
|
|
src1[k].pos = psrc->sanim[srcframe][k].pos;
|
|
src1[k].rot = psrc->sanim[srcframe][k].rot;
|
|
}
|
|
|
|
if( pdest->numframes == 1 )
|
|
{
|
|
MsgDev( D_WARN, "%s too short for splinedelta\n", pdest->name );
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
for( j = 0; j < pdest->numframes; j++ )
|
|
{
|
|
float s = 1;
|
|
if( pdest->numframes > 1 )
|
|
{
|
|
s = (float)j / (pdest->numframes - 1);
|
|
}
|
|
|
|
// make it a spline curve
|
|
if( flags & STUDIO_AL_SPLINE )
|
|
{
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
}
|
|
|
|
if( pdest->weight[k] > 0.0f )
|
|
{
|
|
s_bone_t src;
|
|
|
|
src.pos = src0[k].pos * (1 - s) + src1[k].pos * s;
|
|
QuaternionSlerp( src0[k].rot, src1[k].rot, s, src.rot );
|
|
|
|
// calc differences between two rotations
|
|
if( flags & STUDIO_AL_POST )
|
|
{
|
|
// find pdest in src's reference frame
|
|
QuaternionSMAngles( -1.0f, src.rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot );
|
|
pdest->sanim[j][k].pos = pdest->sanim[j][k].pos - src.pos;
|
|
}
|
|
else
|
|
{
|
|
// find src in pdest's reference frame?
|
|
QuaternionMAAngles( pdest->sanim[j][k].rot, -1.0f, src.rot, pdest->sanim[j][k].rot );
|
|
pdest->sanim[j][k].pos = src.pos - pdest->sanim[j][k].pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: turn the animation into a lower fps encoded version
|
|
//-----------------------------------------------------------------------------
|
|
void reencodeAnimation( s_animation_t *panim, int frameskip )
|
|
{
|
|
int j, k, n;
|
|
|
|
for( n = 1, j = frameskip; j < panim->numframes; j += frameskip )
|
|
{
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
panim->sanim[n][k] = panim->sanim[j][k];
|
|
}
|
|
n++;
|
|
}
|
|
|
|
panim->numframes = n;
|
|
panim->fps = panim->fps / frameskip;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: clip or pad the animation as nessesary to be a specified number of frames
|
|
//-----------------------------------------------------------------------------
|
|
void forceNumframes( s_animation_t *panim, int numframes )
|
|
{
|
|
int size = g_numbones * sizeof( s_bone_t );
|
|
|
|
// copy
|
|
for( int j = panim->numframes; j < numframes; j++ )
|
|
{
|
|
panim->sanim[j] = (s_bone_t *)Mem_Alloc( size );
|
|
memcpy( panim->sanim[j], panim->sanim[panim->numframes-1], size );
|
|
}
|
|
panim->numframes = numframes;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: subtract each frame from the previous to calculate the animations derivative
|
|
//-----------------------------------------------------------------------------
|
|
void createDerivative( s_animation_t *panim, float scale )
|
|
{
|
|
s_bone_t orig[MAXSTUDIOSRCBONES];
|
|
int j, k;
|
|
|
|
j = panim->numframes - 1;
|
|
|
|
if( FBitSet( panim->flags, STUDIO_LOOPING ))
|
|
j--;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
orig[k].pos = panim->sanim[j][k].pos;
|
|
orig[k].rot = panim->sanim[j][k].rot;
|
|
}
|
|
|
|
for( j = panim->numframes - 1; j >= 0; j-- )
|
|
{
|
|
s_bone_t *psrc, *pdest;
|
|
Vector4D q;
|
|
|
|
if( j - 1 >= 0 )
|
|
psrc = panim->sanim[j-1];
|
|
else psrc = orig;
|
|
pdest = panim->sanim[j];
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( panim->weight[k] > 0.0f )
|
|
{
|
|
// find pdest in src's reference frame
|
|
QuaternionSMAngles( -1, psrc[k].rot, pdest[k].rot, pdest[k].rot );
|
|
pdest[k].pos = pdest[k].pos - psrc[k].pos;
|
|
|
|
// rescale results (not sure what basis physics system is expecting)
|
|
AngleQuaternion( pdest[k].rot, q );
|
|
QuaternionScale( q, scale, q );
|
|
QuaternionAngle( q, pdest[k].rot );
|
|
pdest[k].pos *= scale;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: subtract each frame from the previous to calculate the animations derivative
|
|
//-----------------------------------------------------------------------------
|
|
void clearAnimations( s_animation_t *panim )
|
|
{
|
|
panim->flags |= STUDIO_DELTA;
|
|
panim->flags |= STUDIO_ALLZEROS;
|
|
|
|
panim->numframes = 1;
|
|
panim->startframe = 0;
|
|
panim->endframe = 1;
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
panim->sanim[0][k].pos = Vector( 0, 0, 0 );
|
|
panim->sanim[0][k].rot = Radian( 0, 0, 0 );
|
|
panim->posweight[k] = 0.0f;
|
|
panim->weight[k] = 0.0f;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: remove all world rotation from a bone
|
|
//-----------------------------------------------------------------------------
|
|
void counterRotateBone( s_animation_t *panim, int iBone, Vector target )
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 defaultBoneToWorld;
|
|
Vector pos;
|
|
|
|
defaultBoneToWorld = matrix3x4( g_vecZero, target );
|
|
|
|
for( int j = 0; j < panim->numframes; j++ )
|
|
{
|
|
CalcBoneTransforms( panim, j, boneToWorld );
|
|
|
|
boneToWorld[iBone].GetOrigin( pos );
|
|
defaultBoneToWorld.SetOrigin( pos );
|
|
boneToWorld[iBone] = defaultBoneToWorld;
|
|
solveBone( panim, j, iBone, boneToWorld );
|
|
}
|
|
}
|
|
|
|
void setAnimationWeight( s_animation_t *panim, int index )
|
|
{
|
|
// copy weightlists to animations
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
panim->weight[k] = g_weightlist[index].weight[k];
|
|
panim->posweight[k] = g_weightlist[index].posweight[k];
|
|
}
|
|
}
|
|
|
|
void addDeltas( s_animation_t *panim, int frame, float s, Vector delta_pos[], Vector4D delta_q[] )
|
|
{
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( panim->weight[k] > 0.0f )
|
|
{
|
|
QuaternionSMAngles( s, delta_q[k], panim->sanim[frame][k].rot, panim->sanim[frame][k].rot );
|
|
panim->sanim[frame][k].pos += delta_pos[k] * s;
|
|
}
|
|
}
|
|
}
|
|
|
|
void extractUnusedMotion( s_animation_t *panim )
|
|
{
|
|
float motion[6];
|
|
int type, j, k;
|
|
|
|
type = panim->motiontype;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
motion[0] = panim->sanim[0][k].pos.x;
|
|
motion[1] = panim->sanim[0][k].pos.y;
|
|
motion[2] = panim->sanim[0][k].pos.z;
|
|
motion[3] = panim->sanim[0][k].rot.x;
|
|
motion[4] = panim->sanim[0][k].rot.y;
|
|
motion[5] = panim->sanim[0][k].rot.z;
|
|
|
|
for( j = 0; j < panim->numframes; j++ )
|
|
{
|
|
if( type & STUDIO_X )
|
|
panim->sanim[j][k].pos.x = motion[0];
|
|
if( type & STUDIO_Y )
|
|
panim->sanim[j][k].pos.y = motion[1];
|
|
if( type & STUDIO_Z )
|
|
panim->sanim[j][k].pos.z = motion[2];
|
|
if( type & STUDIO_XR )
|
|
panim->sanim[j][k].rot.x = motion[3];
|
|
if( type & STUDIO_YR )
|
|
panim->sanim[j][k].rot.y = motion[4];
|
|
if( type & STUDIO_ZR )
|
|
panim->sanim[j][k].rot.z = motion[5];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find the difference between the src and dest animations, then add that
|
|
// difference to all the frames of the dest animation.
|
|
//-----------------------------------------------------------------------------
|
|
void processMatch( s_animation_t *psrc, s_animation_t *pdest, int flags )
|
|
{
|
|
// process "match"
|
|
Vector delta_pos[MAXSTUDIOSRCBONES];
|
|
Vector4D delta_q[MAXSTUDIOSRCBONES];
|
|
int j, k;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( flags ) delta_pos[k] = psrc->sanim[0][k].pos - pdest->sanim[0][k].pos;
|
|
QuaternionSM( -1.0f, pdest->sanim[0][k].rot, psrc->sanim[0][k].rot, delta_q[k] );
|
|
}
|
|
|
|
for( j = 0; j < pdest->numframes; j++ )
|
|
{
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( pdest->weight[k] > 0.0f )
|
|
{
|
|
if( flags ) pdest->sanim[j][k].pos += delta_pos[k];
|
|
QuaternionMAAngles( pdest->sanim[j][k].rot, 1.0f, delta_q[k], pdest->sanim[j][k].rot );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: blend the psrc animation overtop the pdest animation, but blend the
|
|
// quaternions in world space instead of parent bone space.
|
|
// Also, blend bone lengths, but only for non root animations.
|
|
//-----------------------------------------------------------------------------
|
|
void worldspaceBlend( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
|
|
{
|
|
int j, k, n;
|
|
|
|
// process "match"
|
|
Vector4D srcQ[MAXSTUDIOSRCBONES];
|
|
Vector srcPos[MAXSTUDIOSRCBONES];
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 destBoneToWorld[MAXSTUDIOBONES];
|
|
Vector tmp;
|
|
|
|
if( !flags )
|
|
{
|
|
CalcBoneTransforms( psrc, srcframe, srcBoneToWorld );
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
srcQ[k] = srcBoneToWorld[k].GetQuaternion();
|
|
srcPos[k] = psrc->sanim[srcframe][k].pos;
|
|
}
|
|
}
|
|
|
|
Vector4D targetQ, destQ;
|
|
|
|
for( j = 0; j < pdest->numframes; j++ )
|
|
{
|
|
if( flags )
|
|
{
|
|
// pull from a looping source
|
|
float flCycle = (float)j / (pdest->numframes - 1);
|
|
flCycle += (float)srcframe / (psrc->numframes - 1);
|
|
CalcBoneTransformsCycle( psrc, psrc, flCycle, srcBoneToWorld );
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
srcQ[k] = srcBoneToWorld[k].GetQuaternion();
|
|
|
|
n = g_bonetable[k].parent;
|
|
if( n == -1 )
|
|
{
|
|
srcPos[k] = srcBoneToWorld[k].GetOrigin();
|
|
}
|
|
else
|
|
{
|
|
matrix3x4 worldToBone = srcBoneToWorld[n].Invert();
|
|
matrix3x4 local = worldToBone.ConcatTransforms( srcBoneToWorld[k] );
|
|
srcPos[k] = local.GetOrigin();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CalcBoneTransforms( pdest, j, destBoneToWorld );
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( pdest->weight[k] > 0 )
|
|
{
|
|
// blend the boneToWorld transforms in world space
|
|
destQ = destBoneToWorld[k].GetQuaternion();
|
|
tmp = destBoneToWorld[k].GetOrigin();
|
|
|
|
QuaternionSlerp( destQ, srcQ[k], pdest->weight[k], targetQ );
|
|
destBoneToWorld[k] = matrix3x4( tmp, targetQ );
|
|
}
|
|
|
|
// back solve
|
|
n = g_bonetable[k].parent;
|
|
if( n == -1 )
|
|
{
|
|
destBoneToWorld[k].GetAngles( pdest->sanim[j][k].rot );
|
|
|
|
// FIXME: it's not clear if this should blend position or not....it'd be
|
|
// better if weight lists could do quat and pos independently.
|
|
}
|
|
else
|
|
{
|
|
matrix3x4 worldToBone = destBoneToWorld[n].Invert();
|
|
matrix3x4 local = worldToBone.ConcatTransforms( destBoneToWorld[k] );
|
|
local.GetAngles( pdest->sanim[j][k].rot );
|
|
|
|
// blend bone lengths (local space)
|
|
VectorLerp( pdest->sanim[j][k].pos, pdest->posweight[k], srcPos[k], pdest->sanim[j][k].pos );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: match one animations position/orientation to another animations position/orientation
|
|
//-----------------------------------------------------------------------------
|
|
void processAutoorigin( s_animation_t *psrc, s_animation_t *pdest, int motiontype, int srcframe, int destframe, int bone )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 destBoneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 adjmatrix;
|
|
int j, k;
|
|
|
|
CalcBoneTransforms( psrc, srcframe, srcBoneToWorld );
|
|
CalcBoneTransforms( pdest, destframe, destBoneToWorld );
|
|
|
|
// find rotation
|
|
Radian rot( 0, 0, 0 );
|
|
Vector srcPos = srcBoneToWorld[bone].GetOrigin();
|
|
Vector destPos = destBoneToWorld[bone].GetOrigin();
|
|
Vector4D q0 = srcBoneToWorld[bone].GetQuaternion();
|
|
Vector4D q2 = destBoneToWorld[bone].GetQuaternion();
|
|
|
|
if( FBitSet( motiontype, STUDIO_LXR | STUDIO_LYR | STUDIO_LZR | STUDIO_XR | STUDIO_YR | STUDIO_ZR ))
|
|
{
|
|
Vector4D deltaQ2;
|
|
Vector4D q4;
|
|
Radian a3;
|
|
|
|
QuaternionMA( q2, -1.0f, q0, deltaQ2 );
|
|
|
|
if( FBitSet( motiontype, STUDIO_LXR | STUDIO_XR ))
|
|
{
|
|
q4.Init( deltaQ2.x, 0, 0, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
rot.x = a3.x;
|
|
}
|
|
|
|
if( FBitSet( motiontype, STUDIO_LYR | STUDIO_YR ))
|
|
{
|
|
q4.Init( 0, deltaQ2.y, 0, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
rot.y = a3.y;
|
|
}
|
|
|
|
if( FBitSet( motiontype, STUDIO_LZR | STUDIO_ZR ))
|
|
{
|
|
q4.Init( 0, 0, deltaQ2.z, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
rot.z = a3.z;
|
|
}
|
|
|
|
if( FBitSet( motiontype, STUDIO_XR ) && FBitSet( motiontype, STUDIO_YR ) && FBitSet( motiontype, STUDIO_ZR ))
|
|
{
|
|
QuaternionAngle( deltaQ2, rot );
|
|
}
|
|
}
|
|
|
|
// find movement
|
|
Vector p0 = srcPos;
|
|
adjmatrix = matrix3x4( g_vecZero, rot ).Invert();
|
|
Vector p2 = adjmatrix.VectorRotate( destPos );
|
|
|
|
Vector adj = p0 - p2;
|
|
|
|
if( !FBitSet( motiontype, STUDIO_X | STUDIO_LX ))
|
|
adj.x = 0;
|
|
if( !FBitSet( motiontype, STUDIO_Y | STUDIO_LY ))
|
|
adj.y = 0;
|
|
if( !FBitSet( motiontype, STUDIO_Z | STUDIO_LZ ))
|
|
adj.z = 0;
|
|
|
|
adjmatrix.SetOrigin( adj );
|
|
|
|
if( bone != g_rootIndex )
|
|
{
|
|
MsgDev( D_REPORT, "%s aligning to %s - %.2f %.2f %.2f\n", pdest->name, g_bonetable[bone].name, adj.x, adj.y, adj.z );
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
for( j = 0; j < pdest->numframes; j++ )
|
|
{
|
|
matrix3x4 bonematrix = matrix3x4( pdest->sanim[j][k].pos, pdest->sanim[j][k].rot );
|
|
bonematrix = adjmatrix.ConcatTransforms( bonematrix );
|
|
bonematrix.GetStudioTransform( pdest->sanim[j][k].pos, pdest->sanim[j][k].rot );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: subtract one animaiton from animation to create an animation of the "difference"
|
|
//-----------------------------------------------------------------------------
|
|
void subtractBaseAnimations( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags )
|
|
{
|
|
int j, k;
|
|
|
|
// create delta animations
|
|
s_bone_t src[MAXSTUDIOSRCBONES];
|
|
|
|
if( srcframe >= psrc->numframes )
|
|
{
|
|
COM_FatalError( "subtract frame %d out of range for %s\n", srcframe, psrc->name );
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
src[k].pos = psrc->sanim[srcframe][k].pos;
|
|
src[k].rot = psrc->sanim[srcframe][k].rot;
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
for( j = 0; j < pdest->numframes; j++ )
|
|
{
|
|
if( pdest->weight[k] > 0.0f )
|
|
{
|
|
// calc differences between two rotations
|
|
if( FBitSet( flags, STUDIO_POST ))
|
|
{
|
|
// find pdest in src's reference frame
|
|
QuaternionSMAngles( -1.0f, src[k].rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot );
|
|
pdest->sanim[j][k].pos = pdest->sanim[j][k].pos - src[k].pos;
|
|
}
|
|
else
|
|
{
|
|
// find src in pdest's reference frame?
|
|
QuaternionMAAngles( pdest->sanim[j][k].rot, -1.0f, src[k].rot, pdest->sanim[j][k].rot );
|
|
pdest->sanim[j][k].pos = src[k].pos - pdest->sanim[j][k].pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: rotate the animation so that it's moving in the specified angle
|
|
//-----------------------------------------------------------------------------
|
|
void makeAngle( s_animation_t *panim, float angle )
|
|
{
|
|
float da = 0.0f;
|
|
|
|
if( panim->numpiecewisekeys != 0 )
|
|
{
|
|
// look for movement in total piecewise movement
|
|
Vector pos = panim->piecewisemove[panim->numpiecewisekeys-1].pos;
|
|
|
|
if( pos[0] != 0 || pos[1] != 0 )
|
|
{
|
|
float a = atan2( pos[1], pos[0] ) * (180 / M_PI);
|
|
da = angle - a;
|
|
}
|
|
|
|
for( int i = 0; i < panim->numpiecewisekeys; i++ )
|
|
{
|
|
panim->piecewisemove[i].pos = VectorYawRotate( panim->piecewisemove[i].pos, da );
|
|
panim->piecewisemove[i].vector = VectorYawRotate( panim->piecewisemove[i].vector, da );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// look for movement in root bone
|
|
Vector pos = panim->sanim[(panim->numframes - 1)][g_rootIndex].pos - panim->sanim[0][g_rootIndex].pos;
|
|
|
|
if( pos[0] != 0 || pos[1] != 0 )
|
|
{
|
|
float a = atan2( pos[1], pos[0] ) * (180 / M_PI);
|
|
da = angle - a;
|
|
}
|
|
}
|
|
|
|
matrix3x4 rootxform;
|
|
matrix3x4 src, dest;
|
|
|
|
rootxform = matrix3x4( g_vecZero, Vector( 0.0f, da, 0.0f ));
|
|
|
|
for( int j = 0; j < panim->numframes; j++ )
|
|
{
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
src = matrix3x4( panim->sanim[j][k].pos, panim->sanim[j][k].rot );
|
|
dest = rootxform.ConcatTransforms( src );
|
|
dest.GetStudioTransform( panim->sanim[j][k].pos, panim->sanim[j][k].rot );
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: not finished
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find the difference between the overlapping frames and spread out
|
|
// the difference over multiple frames.
|
|
// start: negative number, specifies how far back from the end to start blending
|
|
// end: positive number, specifies how many frames from the beginning to blend
|
|
//-----------------------------------------------------------------------------
|
|
void fixupLoopingDiscontinuities( s_animation_t *panim, int start, int end )
|
|
{
|
|
int j, k, m, n;
|
|
|
|
// fix C0 errors on looping animations
|
|
m = panim->numframes - 1;
|
|
|
|
Vector delta_pos[MAXSTUDIOSRCBONES];
|
|
Vector4D delta_q[MAXSTUDIOSRCBONES];
|
|
|
|
// skip if there's nothing to smooth
|
|
if( m == 0 ) return;
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
delta_pos[k] = panim->sanim[m][k].pos - panim->sanim[0][k].pos;
|
|
QuaternionMA( panim->sanim[m][k].rot, -1, panim->sanim[0][k].rot, delta_q[k] );
|
|
}
|
|
|
|
// HACK: skip fixup for motion that'll be matched with linear extraction
|
|
// FIXME: remove when "global" extraction moved into normal ordered processing loop
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
if( FBitSet( panim->motiontype, STUDIO_LX ))
|
|
delta_pos[k].x = 0.0f;
|
|
if( FBitSet( panim->motiontype, STUDIO_LY ))
|
|
delta_pos[k].y = 0.0f;
|
|
if( FBitSet( panim->motiontype, STUDIO_LZ ))
|
|
delta_pos[k].z = 0.0f;
|
|
// FIXME: add rotation
|
|
}
|
|
}
|
|
|
|
// make sure loop doesn't exceed animation length
|
|
if(( end - start ) > panim->numframes )
|
|
{
|
|
end = panim->numframes + start;
|
|
if( end < 0 )
|
|
{
|
|
end = 0;
|
|
start = -(panim->numframes - 1);
|
|
}
|
|
}
|
|
|
|
// FIXME: figure out S
|
|
float s = 0;
|
|
float nf = end - start;
|
|
|
|
for( j = start + 1; j <= 0; j++ )
|
|
{
|
|
n = j - start;
|
|
s = (n / nf);
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
addDeltas( panim, m+j, -s, delta_pos, delta_q );
|
|
}
|
|
|
|
for( j = 0; j < end; j++ )
|
|
{
|
|
n = end - j;
|
|
s = (n / nf);
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
addDeltas( panim, j, s, delta_pos, delta_q );
|
|
}
|
|
}
|
|
|
|
void matchBlend( s_animation_t *pDestAnim, s_animation_t *pSrcAnimation, int iSrcFrame, int iDestFrame, int iPre, int iPost )
|
|
{
|
|
int j, k;
|
|
|
|
if( FBitSet( pDestAnim->flags, STUDIO_LOOPING ))
|
|
{
|
|
iPre = Q_max( iPre, -pDestAnim->numframes );
|
|
iPost = Q_min( iPost, pDestAnim->numframes );
|
|
}
|
|
else
|
|
{
|
|
iPre = Q_max( iPre, -iDestFrame );
|
|
iPost = Q_min( iPost, pDestAnim->numframes - iDestFrame );
|
|
}
|
|
|
|
Vector delta_pos[MAXSTUDIOSRCBONES];
|
|
Vector4D delta_q[MAXSTUDIOSRCBONES];
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
delta_pos[k] = pSrcAnimation->sanim[iSrcFrame][k].pos - pDestAnim->sanim[iDestFrame][k].pos;
|
|
QuaternionMA( pSrcAnimation->sanim[iSrcFrame][k].rot, -1, pDestAnim->sanim[iDestFrame][k].rot, delta_q[k] );
|
|
}
|
|
|
|
// HACK: skip fixup for motion that'll be matched with linear extraction
|
|
// FIXME: remove when "global" extraction moved into normal ordered processing loop
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
if( FBitSet( pDestAnim->motiontype, STUDIO_LX ))
|
|
delta_pos[k].x = 0.0f;
|
|
if( FBitSet( pDestAnim->motiontype, STUDIO_LY ))
|
|
delta_pos[k].y = 0.0f;
|
|
if( FBitSet( pDestAnim->motiontype, STUDIO_LZ ))
|
|
delta_pos[k].z = 0.0f;
|
|
// FIXME: add rotation
|
|
}
|
|
}
|
|
|
|
// FIXME: figure out S
|
|
float s = 0;
|
|
|
|
for( j = iPre; j <= iPost; j++ )
|
|
{
|
|
if( j < 0 )
|
|
{
|
|
s = j / (float)(iPre - 1);
|
|
}
|
|
else
|
|
{
|
|
s = j / (float)(iPost + 1);
|
|
}
|
|
|
|
s = SimpleSpline( 1 - s );
|
|
k = iDestFrame + j;
|
|
|
|
if( k < 0 )
|
|
{
|
|
k += (pDestAnim->numframes - 1);
|
|
}
|
|
else
|
|
{
|
|
k = k % (pDestAnim->numframes - 1);
|
|
}
|
|
|
|
addDeltas( pDestAnim, k, s, delta_pos, delta_q );
|
|
|
|
// make sure final frame of a looping animation matches frame 0
|
|
if( FBitSet( pDestAnim->flags, STUDIO_LOOPING ) && k == 0 )
|
|
{
|
|
addDeltas( pDestAnim, pDestAnim->numframes - 1, s, delta_pos, delta_q );
|
|
}
|
|
}
|
|
}
|
|
|
|
void forceAnimationLoop( s_animation_t *panim )
|
|
{
|
|
// force looping animations to be looping
|
|
if( FBitSet( panim->flags, STUDIO_LOOPING ))
|
|
{
|
|
int n = 0;
|
|
int m = panim->numframes - 1;
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
int type = panim->motiontype;
|
|
|
|
if( !FBitSet( type, STUDIO_LX ))
|
|
panim->sanim[m][k].pos[0] = panim->sanim[n][k].pos[0];
|
|
if( !FBitSet( type, STUDIO_LY ))
|
|
panim->sanim[m][k].pos[1] = panim->sanim[n][k].pos[1];
|
|
if( !FBitSet( type, STUDIO_LZ ))
|
|
panim->sanim[m][k].pos[2] = panim->sanim[n][k].pos[2];
|
|
|
|
if( !FBitSet( type, STUDIO_LXR ))
|
|
panim->sanim[m][k].rot[0] = panim->sanim[n][k].rot[0];
|
|
if( !FBitSet( type, STUDIO_LYR ))
|
|
panim->sanim[m][k].rot[1] = panim->sanim[n][k].rot[1];
|
|
if( !FBitSet( type, STUDIO_LZR ))
|
|
panim->sanim[m][k].rot[2] = panim->sanim[n][k].rot[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: find the linear movement/rotation between two frames, subtract that
|
|
// out of the animation and add it back on as a "piecewise movement" command
|
|
// panim - current animation
|
|
// motiontype - what to extract
|
|
// iStartFrame - first frame to apply motion over
|
|
// iEndFrame - last end frame to apply motion over
|
|
// iSrcFrame - match refFrame against what frame of the current animation
|
|
// pRefAnim - reference animtion
|
|
// iRefFrame - frame of reference animation to match
|
|
//-----------------------------------------------------------------------------
|
|
void extractLinearMotion( s_animation_t *panim, int motiontype, int iStartFrame, int iEndFrame, int iSrcFrame, s_animation_t *pRefAnim, int iRefFrame )
|
|
{
|
|
matrix3x4 adjmatrix;
|
|
int j, k;
|
|
|
|
// Can't extract motion with only 1 frame of animation!
|
|
if( panim->numframes <= 1 )
|
|
{
|
|
COM_FatalError( "Can't extract motion from sequence %s (%s). Check your QC options!\n", panim->name, panim->filename );
|
|
}
|
|
|
|
if( panim->numpiecewisekeys >= MAXSTUDIOMOVEKEYS )
|
|
{
|
|
COM_FatalError( "Too many piecewise movement keys in %s (%s)\n", panim->name, panim->filename );
|
|
}
|
|
|
|
if( iEndFrame > panim->numframes - 1 )
|
|
iEndFrame = panim->numframes - 1;
|
|
|
|
if( iSrcFrame > panim->numframes - 1 )
|
|
iSrcFrame = panim->numframes - 1;
|
|
|
|
if( iStartFrame >= iEndFrame )
|
|
{
|
|
MsgDev( D_WARN, "Motion extraction ignored, no frames remaining in %s (%s)\n", panim->name, panim->filename );
|
|
return;
|
|
}
|
|
|
|
float fFrame = (iStartFrame + iSrcFrame) / 2.0f;
|
|
int iMidFrame = (int)fFrame;
|
|
float s = fFrame - iMidFrame;
|
|
|
|
// find rotation
|
|
Radian rot( 0, 0, 0 );
|
|
|
|
if( FBitSet( motiontype, STUDIO_LXR | STUDIO_LYR | STUDIO_LZR ))
|
|
{
|
|
Vector4D q0, q1, q2, q4, q5;
|
|
|
|
AngleQuaternion( pRefAnim->sanim[iRefFrame][g_rootIndex].rot, q0 );
|
|
AngleQuaternion( panim->sanim[iMidFrame][g_rootIndex].rot, q1 ); // only used for rotation checking
|
|
AngleQuaternion( panim->sanim[iSrcFrame][g_rootIndex].rot, q2 );
|
|
|
|
Vector4D deltaQ1, deltaQ2;
|
|
|
|
QuaternionMA( q1, -1.0f, q0, deltaQ1 );
|
|
QuaternionMA( q2, -1.0f, q0, deltaQ2 );
|
|
|
|
// FIXME: this is still wrong, but it should be slightly more robust
|
|
Radian a3, a5;
|
|
|
|
if( FBitSet( motiontype, STUDIO_LXR ))
|
|
{
|
|
q4.Init( deltaQ2.x, 0, 0, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
rot.x = a3.x;
|
|
}
|
|
|
|
if( FBitSet( motiontype, STUDIO_LYR ))
|
|
{
|
|
q4.Init( 0, deltaQ2.y, 0, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
rot.y = a3.y;
|
|
}
|
|
|
|
if( FBitSet( motiontype, STUDIO_LZR ))
|
|
{
|
|
q4.Init( 0, 0, deltaQ2.z, deltaQ2.w );
|
|
q4 = q4.Normalize();
|
|
QuaternionAngle( q4, a3 );
|
|
|
|
// check for possible rotations >180 degrees by looking at the
|
|
// halfway point and seeing if it's rotating a different direction
|
|
// than the shortest path to the end point
|
|
Radian a5;
|
|
q5.Init( 0, 0, deltaQ1.z, deltaQ1.w );
|
|
q5 = q5.Normalize();
|
|
QuaternionAngle( q5, a5 );
|
|
if( a3.z > M_PI ) a5.z -= 2 * M_PI;
|
|
if( a3.z < -M_PI ) a5.z += 2 * M_PI;
|
|
if( a5.z > M_PI ) a5.z -= 2 * M_PI;
|
|
if( a5.z < -M_PI ) a5.z += 2 * M_PI;
|
|
if( a5.z > M_PI / 4 && a3.z < 0 )
|
|
a3.z += 2 * M_PI;
|
|
if( a5.z < -M_PI / 4 && a3.z > 0 )
|
|
a3.z -= 2*M_PI;
|
|
rot.z = a3.z;
|
|
}
|
|
}
|
|
|
|
// find movement
|
|
Vector p0;
|
|
|
|
adjmatrix = matrix3x4( g_vecZero, rot );
|
|
p0 = adjmatrix.VectorRotate( pRefAnim->sanim[iRefFrame][g_rootIndex].pos );
|
|
|
|
Vector p2 = panim->sanim[iSrcFrame][g_rootIndex].pos;
|
|
Vector p1 = panim->sanim[iMidFrame][g_rootIndex].pos * (1.0f - s) + panim->sanim[iMidFrame+1][g_rootIndex].pos * s;
|
|
|
|
p2 = p2 - p0;
|
|
p1 = p1 - p0;
|
|
|
|
if( !FBitSet( motiontype, STUDIO_LX ))
|
|
{
|
|
p2.x = 0;
|
|
p1.x = 0;
|
|
}
|
|
|
|
if( !FBitSet( motiontype, STUDIO_LY ))
|
|
{
|
|
p2.y = 0;
|
|
p1.y = 0;
|
|
}
|
|
|
|
if( !FBitSet( motiontype, STUDIO_LZ ))
|
|
{
|
|
p2.z = 0;
|
|
p1.z = 0;
|
|
}
|
|
|
|
float d1 = p1.Length();
|
|
float d2 = p2.Length();
|
|
|
|
float v0 = -1.0f * d2 + 4 * d1;
|
|
float v1 = 3 * d2 - 4 * d1;
|
|
|
|
MsgDev( D_REPORT, "%s : %d - %d : %.1f %.1f %.1f\n", panim->name, iStartFrame, iEndFrame, p2.x, p2.y, RAD2DEG( rot[2] ));
|
|
|
|
int numframes = iEndFrame - iStartFrame + 1;
|
|
if( numframes < 1 ) return;
|
|
|
|
float n = numframes - 1;
|
|
|
|
if( FBitSet( motiontype, STUDIO_LINEAR ))
|
|
{
|
|
v0 = v1 = p2.Length();
|
|
}
|
|
else if( v0 < 0.0f )
|
|
{
|
|
v0 = 0.0;
|
|
v1 = p2.Length() * 2.0f;
|
|
}
|
|
else if( v1 < 0.0f )
|
|
{
|
|
v0 = p2.Length() * 2.0f;
|
|
v1 = 0.0;
|
|
}
|
|
else if(( v0 + v1 ) > 0.01f && (fabs( v0 - v1 ) / ( v0 + v1 )) < 0.2f )
|
|
{
|
|
// if they're within 10% of each other, assume no acceleration
|
|
v0 = v1 = p2.Length();
|
|
}
|
|
|
|
Vector v = p2.Normalize();
|
|
Vector A, B, C;
|
|
|
|
if( FBitSet( motiontype, STUDIO_QUADRATIC_MOTION ))
|
|
{
|
|
SolveInverseQuadratic( 0, 0, 0.5, p1.x, 1.0, p2.x, A.x, B.x, C.x );
|
|
SolveInverseQuadratic( 0, 0, 0.5, p1.y, 1.0, p2.y, A.y, B.y, C.y );
|
|
SolveInverseQuadratic( 0, 0, 0.5, p1.z, 1.0, p2.z, A.z, B.z, C.z );
|
|
}
|
|
|
|
Vector adjpos;
|
|
Radian adjangle;
|
|
matrix3x4 bonematrix;
|
|
|
|
for( j = 0; j < numframes; j++ )
|
|
{
|
|
float t = (j / n);
|
|
|
|
if( FBitSet( motiontype, STUDIO_QUADRATIC_MOTION ))
|
|
{
|
|
adjpos.x = t * t * A.x + t * B.x + C.x;
|
|
adjpos.y = t * t * A.y + t * B.y + C.y;
|
|
adjpos.z = t * t * A.z + t * B.z + C.z;
|
|
}
|
|
else
|
|
{
|
|
adjpos = v * ( v0 * t + 0.5f * (v1 - v0) * t * t );
|
|
}
|
|
|
|
adjangle = rot * t;
|
|
adjmatrix = matrix3x4( adjpos, adjangle ).Invert();
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
bonematrix = matrix3x4( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot );
|
|
bonematrix = adjmatrix.ConcatTransforms( bonematrix );
|
|
bonematrix.GetStudioTransform( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot );
|
|
}
|
|
}
|
|
}
|
|
|
|
// use adjmatrix form last frame
|
|
for( ; j + iStartFrame < panim->numframes; j++ )
|
|
{
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].parent == -1 )
|
|
{
|
|
bonematrix = matrix3x4( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot );
|
|
bonematrix = adjmatrix.ConcatTransforms( bonematrix );
|
|
bonematrix.GetStudioTransform( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot );
|
|
}
|
|
}
|
|
}
|
|
|
|
// create piecewise motion paths
|
|
s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys++];
|
|
|
|
pmove->endframe = iEndFrame;
|
|
pmove->flags = motiontype;
|
|
|
|
// concatinate xforms
|
|
if( panim->numpiecewisekeys > 1 )
|
|
{
|
|
bonematrix = matrix3x4( adjpos, adjangle );
|
|
adjmatrix = matrix3x4( pmove[-1].pos, pmove[-1].rot );
|
|
bonematrix = adjmatrix.ConcatTransforms( bonematrix );
|
|
bonematrix.GetStudioTransform( pmove[0].pos, pmove[0].rot );
|
|
pmove->vector = pmove[0].pos - pmove[-1].pos;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( adjpos, pmove[0].pos );
|
|
VectorCopy( adjangle, pmove[0].rot );
|
|
pmove->vector = pmove[0].pos;
|
|
}
|
|
|
|
pmove->vector = pmove->vector.Normalize();
|
|
pmove->v0 = v0;
|
|
pmove->v1 = v1;
|
|
|
|
if( iStartFrame == 0 && iSrcFrame == ( panim->numframes - 1 ))
|
|
panim->linearmovement = pmove[0].pos; // for goldsource movement
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: process the "piecewise movement" commands and return where the animation
|
|
// would move to on a given frame (assuming frame 0 is at the origin)
|
|
//-----------------------------------------------------------------------------
|
|
Vector calcPosition( s_animation_t *panim, int iFrame )
|
|
{
|
|
Vector vecPos = g_vecZero;
|
|
|
|
if( panim->numpiecewisekeys == 0 )
|
|
return vecPos;
|
|
|
|
if( panim->numframes == 1 )
|
|
return vecPos;
|
|
|
|
int iLoops = 0;
|
|
while( iFrame >= ( panim->numframes - 1 ))
|
|
{
|
|
iLoops++;
|
|
iFrame = iFrame - (panim->numframes - 1);
|
|
}
|
|
|
|
float prevframe = 0.0f;
|
|
|
|
for( int i = 0; i < panim->numpiecewisekeys; i++ )
|
|
{
|
|
s_linearmove_t *pmove = &panim->piecewisemove[i];
|
|
|
|
if( pmove->endframe >= iFrame )
|
|
{
|
|
float f = (iFrame - prevframe) / (pmove->endframe - prevframe);
|
|
float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f;
|
|
|
|
vecPos = vecPos + d * pmove->vector;
|
|
|
|
if( iLoops != 0 )
|
|
{
|
|
s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys - 1];
|
|
vecPos = vecPos + iLoops * pmove->pos;
|
|
}
|
|
return vecPos;
|
|
}
|
|
else
|
|
{
|
|
prevframe = pmove->endframe;
|
|
vecPos = pmove->pos;
|
|
}
|
|
}
|
|
|
|
return vecPos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calculate how far an animation travels between two frames
|
|
//-----------------------------------------------------------------------------
|
|
Vector calcMovement( s_animation_t *panim, int iFrom, int iTo )
|
|
{
|
|
Vector p1 = calcPosition( panim, iFrom );
|
|
Vector p2 = calcPosition( panim, iTo );
|
|
|
|
return p2 - p1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: try to calculate a "missing" frame of animation, i.e the overlapping frame
|
|
//-----------------------------------------------------------------------------
|
|
void fixupMissingFrame( s_animation_t *panim )
|
|
{
|
|
// the animations DIDN'T have the end frame the same as the start frame, so fudge it
|
|
int size = g_numbones * sizeof( s_bone_t );
|
|
int j = panim->numframes;
|
|
|
|
float scale = 1.0f / (j - 1.0f);
|
|
|
|
panim->sanim[j] = (s_bone_t *)Mem_Alloc( size );
|
|
|
|
Vector deltapos;
|
|
|
|
for( int k = 0; k < g_numbones; k++ )
|
|
{
|
|
deltapos = panim->sanim[j-1][k].pos - panim->sanim[0][k].pos;
|
|
panim->sanim[j-1][k].pos += deltapos * scale;
|
|
panim->sanim[j][k].rot = panim->sanim[0][k].rot;
|
|
}
|
|
|
|
panim->numframes = j + 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: shift the frames of the animation so that it starts on the desired frame
|
|
//-----------------------------------------------------------------------------
|
|
void realignLooping( s_animation_t *panim )
|
|
{
|
|
int j, k;
|
|
|
|
// realign looping animations
|
|
if( panim->numframes > 1 && panim->looprestart )
|
|
{
|
|
if( panim->looprestart >= panim->numframes )
|
|
{
|
|
COM_FatalError( "loopstart (%d) out of range for animation %s (%d)", panim->looprestart, panim->name, panim->numframes );
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
Vector shiftpos[MAXSTUDIOANIMATIONS];
|
|
Radian shiftrot[MAXSTUDIOANIMATIONS];
|
|
int n;
|
|
|
|
// printf("%f %f %f\n", motion[0], motion[1], motion[2] );
|
|
for( j = 0; j < panim->numframes - 1; j++ )
|
|
{
|
|
n = (j + panim->looprestart) % (panim->numframes - 1);
|
|
shiftpos[j] = panim->sanim[n][k].pos;
|
|
shiftrot[j] = panim->sanim[n][k].rot;
|
|
}
|
|
|
|
n = panim->looprestart;
|
|
j = panim->numframes - 1;
|
|
shiftpos[j] = panim->sanim[n][k].pos;
|
|
shiftrot[j] = panim->sanim[n][k].rot;
|
|
|
|
for( j = 0; j < panim->numframes; j++ )
|
|
{
|
|
panim->sanim[j][k].pos = shiftpos[j];
|
|
panim->sanim[j][k].rot = shiftrot[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OptimizeAnimations( void )
|
|
{
|
|
int i, j;
|
|
|
|
// optimize animations
|
|
for (i = 0; i < g_numseq; i++)
|
|
{
|
|
for( j = 0; j < g_sequence[i].numevents; j++ )
|
|
{
|
|
if( g_sequence[i].event[j].frame < g_sequence[i].panim[0]->startframe )
|
|
{
|
|
MsgDev( D_WARN, "sequence %s has event (%d) before first frame (%d)\n",
|
|
g_sequence[i].name, g_sequence[i].event[j].frame, g_sequence[i].panim[0]->startframe );
|
|
g_sequence[i].event[j].frame = g_sequence[i].panim[0]->startframe;
|
|
}
|
|
|
|
if( g_sequence[i].event[j].frame > g_sequence[i].panim[0]->endframe )
|
|
{
|
|
MsgDev( D_WARN, "sequence %s has event (%d) after last frame (%d)\n",
|
|
g_sequence[i].name, g_sequence[i].event[j].frame, g_sequence[i].panim[0]->endframe );
|
|
g_sequence[i].event[j].frame = g_sequence[i].panim[0]->endframe;
|
|
}
|
|
}
|
|
|
|
g_sequence[i].linearmovement = g_sequence[i].panim[0]->linearmovement;
|
|
g_sequence[i].frameoffset = g_sequence[i].panim[0]->startframe;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Acculumate quaternions and try to find the swept area of rotation
|
|
// so that a "midpoint" of the rotation area can be found
|
|
//-----------------------------------------------------------------------------
|
|
void findAnimQuaternionAlignment( int k, int i, Vector4D &qBase, Vector4D &qMin, Vector4D &qMax )
|
|
{
|
|
int j;
|
|
|
|
AngleQuaternion( g_panimation[i]->sanim[0][k].rot, qBase );
|
|
|
|
qMin = qBase;
|
|
float dMin = 1.0;
|
|
qMax = qBase;
|
|
float dMax = 1.0;
|
|
|
|
for( j = 1; j < g_panimation[i]->numframes; j++ )
|
|
{
|
|
Vector4D q;
|
|
|
|
AngleQuaternion( g_panimation[i]->sanim[j][k].rot, q );
|
|
QuaternionAlign( qBase, q, q );
|
|
|
|
float d0 = DotProduct( q, qBase );
|
|
float d1 = DotProduct( q, qMin );
|
|
float d2 = DotProduct( q, qMax );
|
|
|
|
if( d1 >= d0 )
|
|
{
|
|
if( d0 < dMin )
|
|
{
|
|
qMin = q;
|
|
dMin = d0;
|
|
if( dMax == 1.0 )
|
|
{
|
|
QuaternionMA( qBase, -0.01f, qMin, qMax );
|
|
QuaternionAlign( qBase, qMax, qMax );
|
|
}
|
|
}
|
|
}
|
|
else if( d2 >= d0 )
|
|
{
|
|
if( d0 < dMax )
|
|
{
|
|
qMax = q;
|
|
dMax = d0;
|
|
}
|
|
}
|
|
|
|
QuaternionSlerpNoAlign( qMin, qMax, 0.5f, qBase );
|
|
|
|
dMin = DotProduct( qBase, qMin );
|
|
dMax = DotProduct( qBase, qMax );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For specific bones, try to find the total valid area of rotation so
|
|
// that their mid point of rotation can be used at run time to "pre-align"
|
|
// the quaternions so that rotations > 180 degrees don't get blended the
|
|
// "short way round".
|
|
//-----------------------------------------------------------------------------
|
|
void limitBoneRotations( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
for( i = 0; i < g_numlimitrotation; i++ )
|
|
{
|
|
Vector4D qBase;
|
|
|
|
k = findGlobalBone( g_limitrotation[i].name );
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone \"%s\" in $limitrotation\n", g_limitrotation[i].name );
|
|
}
|
|
|
|
AngleQuaternion( g_bonetable[k].rot, qBase );
|
|
|
|
if( g_limitrotation[i].numseq == 0 )
|
|
{
|
|
for( j = 0; j < g_numani; j++ )
|
|
{
|
|
if( !FBitSet( g_panimation[j]->flags, STUDIO_DELTA ) && g_panimation[j]->numframes > 3 )
|
|
{
|
|
Vector4D qBase2, qMin2, qMax2;
|
|
|
|
findAnimQuaternionAlignment( k, j, qBase2, qMin2, qMax2 );
|
|
QuaternionAdd( qBase, qBase2, qBase );
|
|
}
|
|
}
|
|
|
|
qBase = qBase.Normalize();
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < g_limitrotation[i].numseq; j++)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
g_bonetable[k].qAlignment = qBase;
|
|
g_bonetable[k].flags |= BONE_FIXED_ALIGNMENT;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Realign the matrix so that its X axis points along the desired axis.
|
|
//-----------------------------------------------------------------------------
|
|
void AlignIKMatrix( matrix3x4 &mMat, const Vector &vAlignTo )
|
|
{
|
|
Vector tmp1, tmp2, tmp3;
|
|
|
|
// Column 0 (X) becomes the vector.
|
|
tmp1 = vAlignTo.Normalize();
|
|
mMat.SetForward( tmp1 );
|
|
|
|
// Column 1 (Y) is the cross of the vector and column 2 (Z).
|
|
tmp3 = mMat.GetUp();
|
|
tmp2 = CrossProduct( tmp3, tmp1 ).Normalize();
|
|
|
|
// FIXME: check for X being too near to Z
|
|
mMat.SetRight( tmp2 );
|
|
|
|
// Column 2 (Z) is the cross of columns 0 (X) and 1 (Y).
|
|
tmp3 = CrossProduct( tmp1, tmp2 );
|
|
mMat.SetUp( tmp3 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction
|
|
//-----------------------------------------------------------------------------
|
|
bool solveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4 *pBoneToWorld )
|
|
{
|
|
Vector worldFoot, worldKnee, worldThigh;
|
|
|
|
worldThigh = pBoneToWorld[iThigh].GetOrigin();
|
|
worldKnee = pBoneToWorld[iKnee].GetOrigin();
|
|
worldFoot = pBoneToWorld[iFoot].GetOrigin();
|
|
|
|
Vector ikFoot, ikTargetKnee, ikKnee;
|
|
|
|
ikFoot = targetFoot - worldThigh;
|
|
ikKnee = targetKneePos - worldThigh;
|
|
|
|
float l1 = (worldKnee - worldThigh).Length();
|
|
float l2 = (worldFoot - worldKnee).Length();
|
|
|
|
// exaggerate knee targets for legs that are nearly straight
|
|
// FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed
|
|
float d = (targetFoot - worldThigh).Length() - Q_min( l1, l2 );
|
|
d = Q_max( l1 + l2, d );
|
|
|
|
// FIXME: too short knee directions cause trouble
|
|
d = d * 100.0f;
|
|
|
|
ikTargetKnee = ikKnee + targetKneeDir * d;
|
|
|
|
// too far away? (0.9998 is about 1 degree)
|
|
if( ikFoot.Length() > ( l1 + l2 ) * KNEEMAX_EPSILON )
|
|
{
|
|
ikFoot = ikFoot.Normalize();
|
|
ikFoot *= (l1 + l2) * KNEEMAX_EPSILON;
|
|
}
|
|
|
|
// too close?
|
|
// limit distance to about an 80 degree knee bend
|
|
float minDist = Q_max( fabs( l1 - l2 ) * 1.15f, Q_min( l1, l2 ) * 0.15f );
|
|
|
|
if( ikFoot.Length() < minDist )
|
|
{
|
|
// too close to get an accurate vector, just use original vector
|
|
ikFoot = (worldFoot - worldThigh);
|
|
ikFoot = ikFoot.Normalize();
|
|
ikFoot *= minDist;
|
|
}
|
|
|
|
CIKSolver ik;
|
|
|
|
if( ik.solve( l1, l2, ikFoot, ikTargetKnee, ikKnee ))
|
|
{
|
|
matrix3x4 &mWorldThigh = pBoneToWorld[ iThigh ];
|
|
matrix3x4 &mWorldKnee = pBoneToWorld[ iKnee ];
|
|
matrix3x4 &mWorldFoot = pBoneToWorld[ iFoot ];
|
|
|
|
// build transformation matrix for thigh
|
|
AlignIKMatrix( mWorldThigh, ikKnee );
|
|
AlignIKMatrix( mWorldKnee, ikFoot - ikKnee );
|
|
|
|
mWorldKnee[3][0] = ikKnee.x + worldThigh.x;
|
|
mWorldKnee[3][1] = ikKnee.y + worldThigh.y;
|
|
mWorldKnee[3][2] = ikKnee.z + worldThigh.z;
|
|
|
|
mWorldFoot[3][0] = ikFoot.x + worldThigh.x;
|
|
mWorldFoot[3][1] = ikFoot.y + worldThigh.y;
|
|
mWorldFoot[3][2] = ikFoot.z + worldThigh.z;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference
|
|
//-----------------------------------------------------------------------------
|
|
bool solveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4 *pBoneToWorld )
|
|
{
|
|
Vector worldFoot, worldKnee, worldThigh;
|
|
|
|
worldThigh = pBoneToWorld[iThigh].GetOrigin();
|
|
worldKnee = pBoneToWorld[iKnee].GetOrigin();
|
|
worldFoot = pBoneToWorld[iFoot].GetOrigin();
|
|
|
|
Vector ikFoot, ikKnee;
|
|
|
|
ikFoot = targetFoot - worldThigh;
|
|
ikKnee = worldKnee - worldThigh;
|
|
|
|
float l1 = (worldKnee - worldThigh).Length();
|
|
float l2 = (worldFoot - worldKnee).Length();
|
|
float l3 = (worldFoot - worldThigh).Length();
|
|
|
|
// leg too straight to figure out knee?
|
|
if( l3 > (l1 + l2) * KNEEMAX_EPSILON )
|
|
return false;
|
|
|
|
Vector ikHalf = (worldFoot - worldThigh) * (l1 / l3);
|
|
|
|
// FIXME: what to do when the knee completely straight?
|
|
Vector ikKneeDir = (ikKnee - ikHalf).Normalize();
|
|
|
|
return solveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: calc the influence of a ik rule for a specific point in the animation cycle
|
|
//-----------------------------------------------------------------------------
|
|
float IKRuleWeight( s_ikrule_t *pRule, float flCycle )
|
|
{
|
|
if( pRule->end > 1.0f && flCycle < pRule->start )
|
|
{
|
|
flCycle = flCycle + 1.0f;
|
|
}
|
|
|
|
float value = 0.0f;
|
|
if( flCycle < pRule->start )
|
|
{
|
|
return 0.0f;
|
|
}
|
|
else if( flCycle < pRule->peak )
|
|
{
|
|
value = (flCycle - pRule->start) / (pRule->peak - pRule->start);
|
|
}
|
|
else if( flCycle < pRule->tail )
|
|
{
|
|
return 1.0f;
|
|
}
|
|
else if( flCycle < pRule->end )
|
|
{
|
|
value = 1.0f - ((flCycle - pRule->tail) / (pRule->end - pRule->tail));
|
|
}
|
|
|
|
return 3.0f * value * value - 2.0f * value * value * value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Lock the ik target to a specific location in order to clean up bad animations (shouldn't be needed).
|
|
//-----------------------------------------------------------------------------
|
|
void fixupIKErrors( s_animation_t *panim, s_ikrule_t *pRule )
|
|
{
|
|
int k;
|
|
|
|
if( pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0 )
|
|
{
|
|
pRule->tail = panim->numframes - 1;
|
|
pRule->end = panim->numframes - 1;
|
|
}
|
|
|
|
// check for wrapping
|
|
if( pRule->peak < pRule->start )
|
|
{
|
|
pRule->peak += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->tail < pRule->peak )
|
|
{
|
|
pRule->tail += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->end < pRule->tail )
|
|
{
|
|
pRule->end += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->contact == -1 )
|
|
{
|
|
pRule->contact = pRule->peak;
|
|
}
|
|
|
|
if( panim->numframes <= 1 )
|
|
return;
|
|
|
|
pRule->errorData.numerror = pRule->end - pRule->start + 1;
|
|
|
|
switch( pRule->type )
|
|
{
|
|
case IK_SELF:
|
|
break;
|
|
case IK_WORLD:
|
|
case IK_GROUND:
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
|
|
int bone = g_ikchain[pRule->chain].link[2].bone;
|
|
CalcBoneTransforms( panim, pRule->contact, boneToWorld );
|
|
// FIXME: add in motion
|
|
|
|
Vector footfall = boneToWorld[bone].GetOrigin();
|
|
|
|
for( k = 0; k < pRule->errorData.numerror; k++ )
|
|
{
|
|
CalcBoneTransforms( panim, k + pRule->start, boneToWorld );
|
|
|
|
float cycle = (panim->numframes <= 1) ? 0 : (float)(k + pRule->start) / (panim->numframes - 1);
|
|
float s = IKRuleWeight( pRule, cycle );
|
|
s = 1.0f; // FIXME - the weight rule is wrong
|
|
|
|
Vector orig = boneToWorld[g_ikchain[pRule->chain].link[2].bone].GetOrigin();
|
|
|
|
Vector pos = (footfall + calcMovement( panim, k + pRule->start, pRule->contact )) * s + orig * (1.0 - s);
|
|
|
|
solveIK( g_ikchain[pRule->chain].link[0].bone, g_ikchain[pRule->chain].link[1].bone, g_ikchain[pRule->chain].link[2].bone, pos, boneToWorld );
|
|
solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[0].bone, boneToWorld );
|
|
solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[1].bone, boneToWorld );
|
|
solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[2].bone, boneToWorld );
|
|
}
|
|
}
|
|
}
|
|
|
|
forceAnimationLoop( panim ); // !!!
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For specific bones, try to find the total valid area of rotation so
|
|
// that their mid point of rotation can be used at run time to "pre-align"
|
|
// the quaternions so that rotations > 180 degrees don't get blended the
|
|
// "short way round".
|
|
//-----------------------------------------------------------------------------
|
|
void limitIKChainLength( void )
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix
|
|
int i, j, k;
|
|
|
|
for( k = 0; k < g_numikchains; k++ )
|
|
{
|
|
Vector kneeDir = g_ikchain[k].link[0].kneeDir;
|
|
bool needsFixup = false;
|
|
bool hasKnees = false;
|
|
|
|
if( kneeDir.Length() > 0.0f )
|
|
{
|
|
hasKnees = true;
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
if( FBitSet( panim->flags, STUDIO_DELTA ))
|
|
continue;
|
|
|
|
if( FBitSet( panim->flags, STUDIO_HIDDEN ))
|
|
continue;
|
|
|
|
for( j = 0; j < panim->numframes; j++ )
|
|
{
|
|
CalcBoneTransforms( panim, j, boneToWorld );
|
|
|
|
Vector worldThigh;
|
|
Vector worldKnee;
|
|
Vector worldFoot;
|
|
|
|
boneToWorld[g_ikchain[k].link[0].bone].GetOrigin( worldThigh );
|
|
boneToWorld[g_ikchain[k].link[1].bone].GetOrigin( worldKnee );
|
|
boneToWorld[g_ikchain[k].link[2].bone].GetOrigin( worldFoot );
|
|
|
|
float l1 = (worldKnee-worldThigh).Length();
|
|
float l2 = (worldFoot-worldKnee).Length();
|
|
float l3 = (worldFoot-worldThigh).Length();
|
|
|
|
Vector ikHalf = (worldFoot + worldThigh) * 0.5;
|
|
|
|
// FIXME: what to do when the knee completely straight?
|
|
Vector ikKneeDir = (worldKnee - ikHalf).Normalize();
|
|
// ikTargetKnee = ikKnee + ikKneeDir * l1;
|
|
|
|
// leg too straight to figure out knee?
|
|
if( l3 > ( l1 + l2 ) * 0.999f )
|
|
{
|
|
needsFixup = true;
|
|
}
|
|
else
|
|
{
|
|
// rotate knee into local space
|
|
Vector tmp = boneToWorld[g_ikchain[k].link[0].bone].VectorIRotate( ikKneeDir );
|
|
float bend = (((DotProduct( worldThigh - worldKnee, worldFoot - worldKnee )) / (l1 * l3)) + 1.0f ) / 2.0f;
|
|
kneeDir += tmp * bend;
|
|
hasKnees = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !needsFixup )
|
|
continue;
|
|
|
|
if( !hasKnees )
|
|
{
|
|
MsgDev( D_WARN, "ik rules for %s but no clear knee direction\n", g_ikchain[k].name );
|
|
continue;
|
|
}
|
|
|
|
kneeDir = kneeDir.Normalize();
|
|
g_ikchain[k].link[0].kneeDir = kneeDir;
|
|
MsgDev( D_REPORT, "knee %s %f %f %f\n", g_ikchain[k].name, kneeDir.x, kneeDir.y, kneeDir.z );
|
|
}
|
|
}
|
|
|
|
void MakeTransitions( void )
|
|
{
|
|
bool iHit = g_multistagegraph;
|
|
int i, j, k;
|
|
|
|
// add in direct node transitions
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
if( g_sequence[i].entrynode != g_sequence[i].exitnode )
|
|
{
|
|
g_xnode[g_sequence[i].entrynode-1][g_sequence[i].exitnode-1] = g_sequence[i].exitnode;
|
|
if( g_sequence[i].nodeflags )
|
|
g_xnode[g_sequence[i].exitnode-1][g_sequence[i].entrynode-1] = g_sequence[i].entrynode;
|
|
}
|
|
|
|
if( g_sequence[i].entrynode > g_numxnodes )
|
|
g_numxnodes = g_sequence[i].entrynode;
|
|
}
|
|
|
|
// calculate multi-stage transitions
|
|
while( iHit )
|
|
{
|
|
iHit = false;
|
|
for( i = 1; i <= g_numxnodes; i++ )
|
|
{
|
|
for( j = 1; j <= g_numxnodes; j++ )
|
|
{
|
|
// if I can't go there directly
|
|
if( i != j && g_xnode[i-1][j-1] == 0 )
|
|
{
|
|
for( k = 1; k <= g_numxnodes; k++ )
|
|
{
|
|
// but I found someone who knows how that I can get to
|
|
if( g_xnode[k-1][j-1] > 0 && g_xnode[i-1][k-1] > 0 )
|
|
{
|
|
// then go to them
|
|
g_xnode[i-1][j-1] = -g_xnode[i-1][k-1];
|
|
iHit = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// reset previous pass so the links can be used in the next pass
|
|
for( i = 1; i <= g_numxnodes; i++ )
|
|
{
|
|
for( j = 1; j <= g_numxnodes; j++ )
|
|
{
|
|
g_xnode[i-1][j-1] = abs( g_xnode[i-1][j-1] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// add in allowed "skips"
|
|
for( i = 0; i < g_numxnodeskips; i++ )
|
|
{
|
|
g_xnode[g_xnodeskip[i][0]-1][g_xnodeskip[i][1]-1] = 0;
|
|
}
|
|
|
|
if( g_dump_graph )
|
|
{
|
|
for( j = 1; j <= g_numxnodes; j++ )
|
|
{
|
|
Msg( "%2d : %s\n", j, g_xnodename[j] );
|
|
}
|
|
Msg( " " );
|
|
for( j = 1; j <= g_numxnodes; j++ )
|
|
{
|
|
Msg( "%2d ", j );
|
|
}
|
|
Msg( "\n" );
|
|
|
|
for( i = 1; i <= g_numxnodes; i++ )
|
|
{
|
|
Msg( "%2d: ", i );
|
|
for( j = 1; j <= g_numxnodes; j++ )
|
|
{
|
|
Msg( "%2d ", g_xnode[i-1][j-1] );
|
|
}
|
|
Msg( "\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
void processAnimations( void )
|
|
{
|
|
int i, j;
|
|
|
|
// find global root bone.
|
|
if( Q_strlen( rootname ))
|
|
{
|
|
g_rootIndex = findGlobalBone( rootname );
|
|
if( g_rootIndex == -1 ) g_rootIndex = 0;
|
|
}
|
|
|
|
buildAnimationWeights( );
|
|
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
extractUnusedMotion( panim ); // FIXME: this should be part of LinearMotion()
|
|
|
|
setAnimationWeight( panim, 0 );
|
|
|
|
int startframe = 0;
|
|
|
|
if( panim->fudgeloop )
|
|
{
|
|
fixupMissingFrame( panim );
|
|
}
|
|
|
|
for( j = 0; j < panim->numcmds; j++ )
|
|
{
|
|
s_animcmd_t *pcmd = &panim->cmds[j];
|
|
|
|
switch( pcmd->cmd )
|
|
{
|
|
case CMD_WEIGHTS:
|
|
setAnimationWeight( panim, pcmd->weightlist.index );
|
|
break;
|
|
case CMD_SUBTRACT:
|
|
panim->flags |= STUDIO_DELTA;
|
|
subtractBaseAnimations( pcmd->subtract.ref, panim, pcmd->subtract.frame, pcmd->subtract.flags );
|
|
break;
|
|
case CMD_AO:
|
|
{
|
|
int bone = g_rootIndex;
|
|
if( pcmd->ao.pBonename != NULL )
|
|
{
|
|
bone = findGlobalBone( pcmd->ao.pBonename );
|
|
if( bone == -1 )
|
|
{
|
|
COM_FatalError("unable to find bone %s to alignbone\n", pcmd->ao.pBonename );
|
|
}
|
|
}
|
|
processAutoorigin( pcmd->ao.ref, panim, pcmd->ao.motiontype, pcmd->ao.srcframe, pcmd->ao.destframe, bone );
|
|
}
|
|
break;
|
|
case CMD_MATCH:
|
|
processMatch( pcmd->match.ref, panim, false );
|
|
break;
|
|
case CMD_FIXUP:
|
|
fixupLoopingDiscontinuities( panim, pcmd->fixuploop.start, pcmd->fixuploop.end );
|
|
break;
|
|
case CMD_ANGLE:
|
|
makeAngle( panim, pcmd->angle.angle );
|
|
break;
|
|
case CMD_IKFIXUP:
|
|
break;
|
|
case CMD_IKRULE:
|
|
// processed later
|
|
break;
|
|
case CMD_MOTION:
|
|
extractLinearMotion( panim, pcmd->motion.motiontype, startframe, pcmd->motion.iEndFrame, pcmd->motion.iEndFrame, panim, startframe );
|
|
startframe = pcmd->motion.iEndFrame;
|
|
break;
|
|
case CMD_REFMOTION:
|
|
extractLinearMotion( panim, pcmd->motion.motiontype, startframe, pcmd->motion.iEndFrame, pcmd->motion.iSrcFrame, pcmd->motion.pRefAnim, pcmd->motion.iRefFrame );
|
|
startframe = pcmd->motion.iEndFrame;
|
|
break;
|
|
case CMD_DERIVATIVE:
|
|
createDerivative( panim, pcmd->derivative.scale );
|
|
break;
|
|
case CMD_NOANIMATION:
|
|
clearAnimations( panim );
|
|
break;
|
|
case CMD_LINEARDELTA:
|
|
panim->flags |= STUDIO_DELTA;
|
|
linearDelta( panim, panim, panim->numframes - 1, pcmd->linear.flags );
|
|
break;
|
|
case CMD_COMPRESS:
|
|
reencodeAnimation( panim, pcmd->compress.frames );
|
|
break;
|
|
case CMD_NUMFRAMES:
|
|
forceNumframes( panim, pcmd->numframes.frames );
|
|
break;
|
|
case CMD_COUNTERROTATE:
|
|
{
|
|
int bone = findGlobalBone( pcmd->counterrotate.pBonename );
|
|
if( bone != -1 )
|
|
{
|
|
Vector target;
|
|
|
|
if( !pcmd->counterrotate.bHasTarget )
|
|
{
|
|
matrix3x4 rootxform = matrix3x4( g_vecZero, panim->rotation );
|
|
matrix3x4 defaultBoneToWorld;
|
|
defaultBoneToWorld = rootxform.ConcatTransforms( g_bonetable[bone].boneToPose );
|
|
target = defaultBoneToWorld.GetAngles();
|
|
}
|
|
else
|
|
{
|
|
target = Vector( pcmd->counterrotate.targetAngle );
|
|
}
|
|
|
|
counterRotateBone( panim, bone, target );
|
|
}
|
|
else
|
|
{
|
|
COM_FatalError( "unable to find bone %s to counterrotate\n", pcmd->counterrotate.pBonename );
|
|
}
|
|
}
|
|
break;
|
|
case CMD_WORLDSPACEBLEND:
|
|
worldspaceBlend( pcmd->world.ref, panim, pcmd->world.startframe, pcmd->world.loops );
|
|
break;
|
|
case CMD_MATCHBLEND:
|
|
matchBlend( panim, pcmd->match.ref, pcmd->match.srcframe, pcmd->match.destframe, pcmd->match.destpre, pcmd->match.destpost );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( panim->motiontype )
|
|
{
|
|
int lastframe;
|
|
|
|
if( !FBitSet( panim->flags, STUDIO_LOOPING ))
|
|
{
|
|
// roll back 0.2 seconds to try to prevent popping
|
|
int frames = panim->fps * panim->motionrollback;
|
|
lastframe = Q_max( Q_min( startframe + 1, panim->numframes - 1 ), panim->numframes - frames - 1 );
|
|
}
|
|
else
|
|
{
|
|
lastframe = panim->numframes - 1;
|
|
}
|
|
|
|
extractLinearMotion( panim, panim->motiontype, startframe, lastframe, panim->numframes - 1, panim, startframe );
|
|
startframe = panim->numframes - 1;
|
|
}
|
|
|
|
realignLooping( panim );
|
|
forceAnimationLoop( panim );
|
|
}
|
|
|
|
// merge weightlists
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
for( int n = 0; n < g_numbones; n++ )
|
|
{
|
|
g_sequence[i].weight[n] = 0.0f;
|
|
|
|
for( int j = 0; j < g_sequence[i].groupsize[0]; j++ )
|
|
{
|
|
for( int k = 0; k < g_sequence[i].groupsize[1]; k++ )
|
|
{
|
|
int q = j + g_sequence[i].groupsize[0] * k;
|
|
g_sequence[i].weight[n] = Q_max( g_sequence[i].weight[n], g_sequence[i].panim[q]->weight[n] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CompressAnimations
|
|
//-----------------------------------------------------------------------------
|
|
static void CompressAnimations( void )
|
|
{
|
|
int i, j, k, n, m;
|
|
float v;
|
|
|
|
// find scales for all bones
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
for( k = 0; k < 6; k++ )
|
|
{
|
|
float minv, maxv, scale;
|
|
|
|
if( k < 3 )
|
|
{
|
|
minv = -128.0f;
|
|
maxv = 128.0f;
|
|
}
|
|
else
|
|
{
|
|
minv = -M_PI / 8.0;
|
|
maxv = M_PI / 8.0;
|
|
}
|
|
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
for( n = 0; n < panim->numframes; n++ )
|
|
{
|
|
switch( k )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
if( panim->flags & STUDIO_DELTA ) v = panim->sanim[n][j].pos[k];
|
|
else v = ( panim->sanim[n][j].pos[k] - g_bonetable[j].pos[k] );
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
if( panim->flags & STUDIO_DELTA ) v = panim->sanim[n][j].rot[k-3];
|
|
else v = ( panim->sanim[n][j].rot[k-3] - g_bonetable[j].rot[k-3] );
|
|
clip_rotations( v );
|
|
break;
|
|
}
|
|
|
|
minv = Q_min( v, minv );
|
|
maxv = Q_max( v, maxv );
|
|
}
|
|
}
|
|
|
|
if( minv < maxv )
|
|
{
|
|
if( -minv > maxv )
|
|
scale = minv / -32768.0f;
|
|
else scale = maxv / 32767.0f;
|
|
}
|
|
else
|
|
{
|
|
scale = 1.0f / 32.0f;
|
|
}
|
|
|
|
switch( k )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
g_bonetable[j].posscale[k] = scale;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
g_bonetable[j].rotscale[k-3] = scale;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int changes = 0;
|
|
int total = 0;
|
|
|
|
// reduce animations
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
if( FBitSet( g_bonetable[j].flags, BONE_ALWAYS_PROCEDURAL ))
|
|
continue;
|
|
|
|
// skip bones that have no influence
|
|
if( panim->weight[j] < 0.001f )
|
|
continue;
|
|
|
|
for( k = 0; k < 6; k++ )
|
|
{
|
|
mstudioanimvalue_t data[MAXSTUDIOANIMATIONS];
|
|
mstudioanimvalue_t *pcount, *pvalue;
|
|
short value[MAXSTUDIOANIMATIONS];
|
|
|
|
if( panim->numframes <= 0 )
|
|
COM_FatalError( "no animation frames: \"%s\"\n", panim->name );
|
|
|
|
// find deltas from default pose
|
|
for( n = 0; n < panim->numframes; n++ )
|
|
{
|
|
s_bone_t *psrcdata = &panim->sanim[n][j];
|
|
|
|
switch( k )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
if( panim->flags & STUDIO_DELTA )
|
|
{
|
|
value[n] = psrcdata->pos[k] / g_bonetable[j].posscale[k];
|
|
// pre-scale pos delta since format only has room for "overall" weight
|
|
float r = panim->posweight[j] / panim->weight[j];
|
|
value[n] *= r;
|
|
}
|
|
else
|
|
{
|
|
v = ( psrcdata->pos[k] - g_bonetable[j].pos[k] );
|
|
value[n] = v / g_bonetable[j].posscale[k];
|
|
}
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
if( panim->flags & STUDIO_DELTA ) v = psrcdata->rot[k-3];
|
|
else v = ( psrcdata->rot[k-3] - g_bonetable[j].rot[k-3] );
|
|
clip_rotations( v );
|
|
value[n] = v / g_bonetable[j].rotscale[k-3];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// FIXME: this compression algorithm needs work
|
|
|
|
// initialize animation RLE block
|
|
panim->numanim[j][k] = 0;
|
|
|
|
memset( data, 0, sizeof( data ));
|
|
pcount = data;
|
|
pvalue = pcount + 1;
|
|
|
|
pcount->num.valid = 1;
|
|
pcount->num.total = 1;
|
|
pvalue->value = value[0];
|
|
pvalue++;
|
|
changes++;
|
|
total++;
|
|
|
|
for( m = 1; m < n; m++ )
|
|
{
|
|
if( pcount->num.total == 255 )
|
|
{
|
|
// chain too long, force a new entry
|
|
pcount = pvalue;
|
|
pvalue = pcount + 1;
|
|
pcount->num.valid++;
|
|
pvalue->value = value[m];
|
|
pvalue++;
|
|
changes++;
|
|
}
|
|
|
|
// insert value if they're not equal,
|
|
// or if we're not on a run and the run is less than 3 units
|
|
else if( !cmp_animvalue( m, m - 1 ) || (( pcount->num.total == pcount->num.valid )
|
|
&& (( m < n - 1 ) && !cmp_animvalue( m, m + 1 ))))
|
|
{
|
|
if( pcount->num.total != pcount->num.valid )
|
|
{
|
|
pcount = pvalue;
|
|
pvalue = pcount + 1;
|
|
}
|
|
pcount->num.valid++;
|
|
pvalue->value = value[m];
|
|
pvalue++;
|
|
changes++;
|
|
}
|
|
pcount->num.total++;
|
|
total++;
|
|
}
|
|
|
|
panim->numanim[j][k] = pvalue - data;
|
|
if( panim->numanim[j][k] == 2 && value[0] == 0 )
|
|
{
|
|
panim->numanim[j][k] = 0;
|
|
}
|
|
else
|
|
{
|
|
size_t anim_size = ( pvalue - data ) * sizeof( mstudioanimvalue_t );
|
|
panim->anim[j][k] = (mstudioanimvalue_t *)Mem_Alloc( anim_size );
|
|
memmove( panim->anim[j][k], data, anim_size );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( total != 0 )
|
|
MsgDev( D_INFO, "animation compressed of %.1f%c at original size\n", ((float)changes / (float)total ) * 100.0f, '%' );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compress a single animation stream
|
|
//-----------------------------------------------------------------------------
|
|
static void CompressSingle( s_animationstream_t *pStream )
|
|
{
|
|
int k, n, m;
|
|
|
|
if( pStream->numerror == 0 )
|
|
return;
|
|
|
|
for( k = 0; k < 6; k++ )
|
|
{
|
|
float minv, maxv, scale;
|
|
Radian ang;
|
|
|
|
if( k < 3 )
|
|
{
|
|
minv = -128.0f;
|
|
maxv = 128.0f;
|
|
}
|
|
else
|
|
{
|
|
minv = -M_PI / 8.0;
|
|
maxv = M_PI / 8.0;
|
|
}
|
|
|
|
for( n = 0; n < pStream->numerror; n++ )
|
|
{
|
|
float v = 0.0f;
|
|
switch( k )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
v = pStream->pError[n].pos[k];
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
QuaternionAngle( pStream->pError[n].q, ang );
|
|
v = ang[k-3];
|
|
clip_rotations( v );
|
|
break;
|
|
}
|
|
|
|
minv = Q_min( v, minv );
|
|
maxv = Q_max( v, maxv );
|
|
}
|
|
|
|
if( minv < maxv )
|
|
{
|
|
if( -minv > maxv )
|
|
scale = minv / -32768.0f;
|
|
else scale = maxv / 32767.0f;
|
|
}
|
|
else
|
|
{
|
|
scale = 1.0f / 32.0f;
|
|
}
|
|
|
|
pStream->scale[k] = scale;
|
|
|
|
mstudioanimvalue_t *pcount, *pvalue;
|
|
short value[MAXSTUDIOANIMATIONS];
|
|
mstudioanimvalue_t data[MAXSTUDIOANIMATIONS];
|
|
float v;
|
|
|
|
// find deltas from default pose
|
|
for( n = 0; n < pStream->numerror; n++ )
|
|
{
|
|
switch( k )
|
|
{
|
|
case 0: // X Position
|
|
case 1: // Y Position
|
|
case 2: // Z Position
|
|
value[n] = pStream->pError[n].pos[k] / pStream->scale[k];
|
|
break;
|
|
case 3: // X Rotation
|
|
case 4: // Y Rotation
|
|
case 5: // Z Rotation
|
|
QuaternionAngle( pStream->pError[n].q, ang );
|
|
v = ang[k-3];
|
|
clip_rotations( v );
|
|
value[n] = v / pStream->scale[k];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// initialize animation RLE block
|
|
pStream->numanim[k] = 0;
|
|
|
|
memset( data, 0, sizeof( data ));
|
|
pcount = data;
|
|
pvalue = pcount + 1;
|
|
|
|
pcount->num.valid = 1;
|
|
pcount->num.total = 1;
|
|
pvalue->value = value[0];
|
|
pvalue++;
|
|
|
|
// build a RLE of deltas from the default pose
|
|
for( m = 1; m < n; m++ )
|
|
{
|
|
if( pcount->num.total == 255 )
|
|
{
|
|
// chain too long, force a new entry
|
|
pcount = pvalue;
|
|
pvalue = pcount + 1;
|
|
pcount->num.valid++;
|
|
pvalue->value = value[m];
|
|
pvalue++;
|
|
}
|
|
|
|
// insert value if they're not equal,
|
|
// or if we're not on a run and the run is less than 3 units
|
|
else if( !cmp_animvalue( m, m - 1 ) || (( pcount->num.total == pcount->num.valid )
|
|
&& (( m < n - 1 ) && !cmp_animvalue( m, m + 1 ))))
|
|
{
|
|
if( pcount->num.total != pcount->num.valid )
|
|
{
|
|
pcount = pvalue;
|
|
pvalue = pcount + 1;
|
|
}
|
|
pcount->num.valid++;
|
|
pvalue->value = value[m];
|
|
pvalue++;
|
|
}
|
|
pcount->num.total++;
|
|
}
|
|
|
|
pStream->numanim[k] = pvalue - data;
|
|
pStream->anim[k] = (mstudioanimvalue_t *)Mem_Alloc(( pvalue - data ) * sizeof( mstudioanimvalue_t ));
|
|
memmove( pStream->anim[k], data, (pvalue - data) * sizeof( mstudioanimvalue_t ));
|
|
}
|
|
}
|
|
|
|
static void CalcSequenceBoundingBoxes( void )
|
|
{
|
|
int i, j, k;
|
|
int n, m;
|
|
|
|
// find bounding box for each sequence
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
Vector bmin, bmax;
|
|
|
|
// find intersection box volume for each bone
|
|
ClearBounds( bmin, bmax );
|
|
|
|
s_animation_t *panim = g_panimation[i];
|
|
|
|
for( n = 0; n < panim->numframes; n++ )
|
|
{
|
|
matrix3x4 bonetransform[MAXSTUDIOBONES]; // bone transformation matrix
|
|
matrix3x4 posetransform[MAXSTUDIOBONES]; // bone transformation matrix
|
|
matrix3x4 bonematrix; // local transformation matrix
|
|
Vector pos, tmp;
|
|
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
bonematrix = matrix3x4( panim->sanim[n][j].pos, panim->sanim[n][j].rot );
|
|
if( g_bonetable[j].parent == -1 ) bonetransform[j] = bonematrix;
|
|
else bonetransform[j] = bonetransform[g_bonetable[j].parent].ConcatTransforms( bonematrix );
|
|
|
|
bonematrix = g_bonetable[j].boneToPose.Invert();
|
|
posetransform[j] = bonetransform[j].ConcatTransforms( bonematrix );
|
|
}
|
|
|
|
// include bones as well.
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
Vector tmpMin, tmpMax;
|
|
TransformAABB( bonetransform[k], g_bonetable[k].bmin, g_bonetable[k].bmax, tmpMin, tmpMax );
|
|
AddPointToBounds( tmpMin, bmin, bmax );
|
|
AddPointToBounds( tmpMax, bmin, bmax );
|
|
}
|
|
|
|
// include vertices
|
|
for( k = 0; k < g_nummodels; k++ )
|
|
{
|
|
for( j = 0; j < g_model[k]->numsrcverts; j++ )
|
|
{
|
|
s_srcvertex_t *v = &g_model[k]->srcvert[j];
|
|
pos = g_vecZero;
|
|
|
|
for( m = 0; m < v->globalWeight.numbones; m++ )
|
|
{
|
|
if( has_boneweights )
|
|
tmp = posetransform[v->globalWeight.bone[m]].VectorTransform( v->vert );
|
|
else tmp = bonetransform[v->globalWeight.bone[m]].VectorTransform( v->vert );
|
|
pos += tmp * v->globalWeight.weight[m];
|
|
}
|
|
AddPointToBounds( pos, bmin, bmax );
|
|
}
|
|
}
|
|
}
|
|
|
|
panim->bmin = bmin;
|
|
panim->bmax = bmax;
|
|
}
|
|
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
Vector bmin, bmax;
|
|
|
|
// find intersection box volume for each bone
|
|
ClearBounds( bmin, bmax );
|
|
|
|
for( j = 0; j < g_sequence[i].numblends; j++ )
|
|
{
|
|
s_animation_t *panim = g_sequence[i].panim[j];
|
|
AddPointToBounds( panim->bmin, bmin, bmax );
|
|
AddPointToBounds( panim->bmax, bmin, bmax );
|
|
}
|
|
|
|
g_sequence[i].bmin = bmin;
|
|
g_sequence[i].bmax = bmax;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Links bone controllers
|
|
//-----------------------------------------------------------------------------
|
|
static void LinkBoneControllers( void )
|
|
{
|
|
for( int i = 0; i < g_numbonecontrollers; i++ )
|
|
{
|
|
int j = findGlobalBone( g_bonecontroller[i].name );
|
|
if( j == -1 )
|
|
COM_FatalError( "unknown g_bonecontroller link '%s'\n", g_bonecontroller[i].name );
|
|
g_bonecontroller[i].bone = j;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Find autolayers
|
|
//-----------------------------------------------------------------------------
|
|
static void FindAutolayers( void )
|
|
{
|
|
for( int i = 0; i < g_numseq; i++ )
|
|
{
|
|
for( int k = 0; k < g_sequence[i].numautolayers; k++ )
|
|
{
|
|
int j;
|
|
|
|
for( j = 0; j < g_numseq; j++)
|
|
{
|
|
if( !Q_stricmp( g_sequence[i].autolayer[k].name, g_sequence[j].name ))
|
|
{
|
|
g_sequence[i].autolayer[k].sequence = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( j == g_numseq )
|
|
{
|
|
COM_FatalError( "sequence \"%s\" cannot find autolayer sequence \"%s\"\n", g_sequence[i].name, g_sequence[i].autolayer[k].name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Links screen aligned bones
|
|
//-----------------------------------------------------------------------------
|
|
static void TagScreenAlignedBones( void )
|
|
{
|
|
for( int i = 0; i < g_numscreenalignedbones; i++ )
|
|
{
|
|
int j = findGlobalBone( g_screenalignedbone[i].name );
|
|
if( j == -1 )
|
|
{
|
|
COM_FatalError( "unknown screenaligned bone link '%s'\n", g_screenalignedbone[i].name );
|
|
}
|
|
|
|
g_bonetable[j].flags |= g_screenalignedbone[i].flags;
|
|
MsgDev( D_REPORT, "tagging bone: %s as screen aligned (index %i, flags:%x)\n", g_bonetable[j].name, j, g_bonetable[j].flags );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Links attachments
|
|
//-----------------------------------------------------------------------------
|
|
static void LinkAttachments( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
matrix3x4 boneToPose;
|
|
matrix3x4 world;
|
|
matrix3x4 poseToBone;
|
|
|
|
// attachments may be connected to bones that can be optimized out
|
|
// so search through all the sources and move to a valid location
|
|
for( i = 0; i < g_numattachments; i++ )
|
|
{
|
|
bool found = false;
|
|
|
|
// search through known bones
|
|
k = findGlobalBone( g_attachment[i].bonename );
|
|
if( k != -1 )
|
|
{
|
|
g_attachment[i].bone = k;
|
|
boneToPose = g_bonetable[k].boneToPose;
|
|
poseToBone = boneToPose.Invert();
|
|
found = true;
|
|
}
|
|
|
|
if( !found )
|
|
{
|
|
// search all the loaded sources for the first occurance of the named bone
|
|
for( j = 0; j < g_nummodels && !found; j++ )
|
|
{
|
|
for( k = 0; k < g_model[j]->numbones && !found; k++ )
|
|
{
|
|
if( !Q_stricmp( g_attachment[i].bonename, g_model[j]->localBone[k].name ) )
|
|
{
|
|
boneToPose = g_model[j]->boneToPose[k];
|
|
|
|
// check to make sure that this bone is actually referenced in the output model
|
|
// if not, try parent bone until we find a referenced bone in this chain
|
|
while( k != -1 && g_model[j]->boneGlobalToLocal[g_model[j]->boneLocalToGlobal[k]] != k )
|
|
{
|
|
k = g_model[j]->localBone[k].parent;
|
|
}
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unable to find valid bone for attachment %s:%s\n",
|
|
g_attachment[i].name, g_attachment[i].bonename );
|
|
}
|
|
|
|
poseToBone = g_model[j]->boneToPose[k].Invert();
|
|
g_attachment[i].bone = g_model[j]->boneLocalToGlobal[k];
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !found ) COM_FatalError( "unknown attachment link '%s'\n", g_attachment[i].bonename );
|
|
|
|
if( g_attachment[i].type & IS_ABSOLUTE ) world = g_attachment[i].local;
|
|
else world = boneToPose.ConcatTransforms( g_attachment[i].local );
|
|
|
|
g_attachment[i].local = poseToBone.ConcatTransforms( world );
|
|
}
|
|
|
|
// flag all bones used by attachments
|
|
for( i = 0; i < g_numattachments; i++ )
|
|
{
|
|
j = g_attachment[i].bone;
|
|
while( j != -1 )
|
|
{
|
|
g_bonetable[j].flags |= BONE_USED_BY_ATTACHMENT;
|
|
j = g_bonetable[j].parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SetupHitBoxes( void )
|
|
{
|
|
int i, j, k, n;
|
|
|
|
// set hitgroups
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
g_bonetable[k].group = -9999;
|
|
}
|
|
|
|
for( j = 0; j < g_numhitgroups; j++ )
|
|
{
|
|
k = findGlobalBone( g_hitgroup[j].name );
|
|
if( k != -1 )
|
|
{
|
|
g_bonetable[k].group = g_hitgroup[j].group;
|
|
}
|
|
else
|
|
{
|
|
COM_FatalError( "cannot find bone %s for hitgroup %d\n", g_hitgroup[j].name, g_hitgroup[j].group );
|
|
}
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].group == -9999 )
|
|
{
|
|
if( g_bonetable[k].parent != -1 )
|
|
g_bonetable[k].group = g_bonetable[g_bonetable[k].parent].group;
|
|
else g_bonetable[k].group = 0;
|
|
}
|
|
}
|
|
|
|
if( g_hitboxsets.Size() == 0 )
|
|
{
|
|
int index = g_hitboxsets.AddToTail();
|
|
s_hitboxset_t *set = &g_hitboxsets[index];
|
|
memset( set, 0, sizeof( *set ));
|
|
Q_strncpy( set->hitboxsetname, "default", sizeof( set->hitboxsetname ));
|
|
|
|
// find intersection box volume for each bone
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
g_bonetable[k].bmin = g_vecZero;
|
|
g_bonetable[k].bmax = g_vecZero;
|
|
}
|
|
|
|
// try all the connect vertices
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
Vector p;
|
|
|
|
for( j = 0; j < g_model[i]->numsrcverts; j++ )
|
|
{
|
|
for( n = 0; n < g_model[i]->srcvert[j].globalWeight.numbones; n++ )
|
|
{
|
|
k = g_model[i]->srcvert[j].globalWeight.bone[n];
|
|
|
|
if( has_boneweights )
|
|
p = g_bonetable[k].boneToPose.VectorITransform( g_model[i]->srcvert[j].vert );
|
|
else p = g_model[i]->srcvert[j].vert;
|
|
|
|
if( p[0] < g_bonetable[k].bmin[0] ) g_bonetable[k].bmin[0] = p[0];
|
|
if( p[1] < g_bonetable[k].bmin[1] ) g_bonetable[k].bmin[1] = p[1];
|
|
if( p[2] < g_bonetable[k].bmin[2] ) g_bonetable[k].bmin[2] = p[2];
|
|
if( p[0] > g_bonetable[k].bmax[0] ) g_bonetable[k].bmax[0] = p[0];
|
|
if( p[1] > g_bonetable[k].bmax[1] ) g_bonetable[k].bmax[1] = p[1];
|
|
if( p[2] > g_bonetable[k].bmax[2] ) g_bonetable[k].bmax[2] = p[2];
|
|
}
|
|
}
|
|
}
|
|
// add in all your children as well
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if(( j = g_bonetable[k].parent ) != -1 )
|
|
{
|
|
if( g_bonetable[k].pos[0] < g_bonetable[j].bmin[0] ) g_bonetable[j].bmin[0] = g_bonetable[k].pos[0];
|
|
if( g_bonetable[k].pos[1] < g_bonetable[j].bmin[1] ) g_bonetable[j].bmin[1] = g_bonetable[k].pos[1];
|
|
if( g_bonetable[k].pos[2] < g_bonetable[j].bmin[2] ) g_bonetable[j].bmin[2] = g_bonetable[k].pos[2];
|
|
if( g_bonetable[k].pos[0] > g_bonetable[j].bmax[0] ) g_bonetable[j].bmax[0] = g_bonetable[k].pos[0];
|
|
if( g_bonetable[k].pos[1] > g_bonetable[j].bmax[1] ) g_bonetable[j].bmax[1] = g_bonetable[k].pos[1];
|
|
if( g_bonetable[k].pos[2] > g_bonetable[j].bmax[2] ) g_bonetable[j].bmax[2] = g_bonetable[k].pos[2];
|
|
}
|
|
}
|
|
|
|
for( k = 0; k < g_numbones; k++ )
|
|
{
|
|
if( g_bonetable[k].bmin[0] < g_bonetable[k].bmax[0] - 1.0f
|
|
&& g_bonetable[k].bmin[1] < g_bonetable[k].bmax[1] - 1.0f
|
|
&& g_bonetable[k].bmin[2] < g_bonetable[k].bmax[2] - 1.0f )
|
|
{
|
|
set->hitbox[set->numhitboxes].bone = k;
|
|
set->hitbox[set->numhitboxes].group = g_bonetable[k].group;
|
|
set->hitbox[set->numhitboxes].bmin = g_bonetable[k].bmin;
|
|
set->hitbox[set->numhitboxes].bmax = g_bonetable[k].bmax;
|
|
|
|
if( g_dump_hboxes )
|
|
{
|
|
Msg( "$hbox %d \"%s\" %.2f %.2f %.2f %.2f %.2f %.2f\n",
|
|
set->hitbox[set->numhitboxes].group,
|
|
g_bonetable[set->hitbox[set->numhitboxes].bone].name,
|
|
set->hitbox[set->numhitboxes].bmin[0], set->hitbox[set->numhitboxes].bmin[1], set->hitbox[set->numhitboxes].bmin[2],
|
|
set->hitbox[set->numhitboxes].bmax[0], set->hitbox[set->numhitboxes].bmax[1], set->hitbox[set->numhitboxes].bmax[2] );
|
|
|
|
}
|
|
set->numhitboxes++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( int s = 0; s < g_hitboxsets.Size(); s++ )
|
|
{
|
|
s_hitboxset_t *set = &g_hitboxsets[s];
|
|
|
|
for( j = 0; j < set->numhitboxes; j++ )
|
|
{
|
|
k = findGlobalBone( set->hitbox[j].name );
|
|
|
|
if( k != -1 )
|
|
{
|
|
set->hitbox[j].bone = k;
|
|
}
|
|
else
|
|
{
|
|
COM_FatalError( "cannot find bone %s for bbox\n", set->hitbox[j].name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
static float CalcPoseParameterValue( int control, Radian &angle, Vector &pos )
|
|
{
|
|
switch( control )
|
|
{
|
|
case STUDIO_X:
|
|
return pos.x;
|
|
case STUDIO_Y:
|
|
return pos.y;
|
|
case STUDIO_Z:
|
|
return pos.z;
|
|
case STUDIO_XR:
|
|
return RAD2DEG( angle.x );
|
|
case STUDIO_YR:
|
|
return RAD2DEG( angle.y );
|
|
case STUDIO_ZR:
|
|
return RAD2DEG( angle.z );
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
static void CalcPoseParameters( void )
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
Radian angles;
|
|
Vector pos;
|
|
|
|
for( int i = 0; i < g_numseq; i++ )
|
|
{
|
|
s_sequence_t *pseq = &g_sequence[i];
|
|
|
|
for( int iPose = 0; iPose < 2; iPose++ )
|
|
{
|
|
if( pseq->groupsize[iPose] > 1 )
|
|
{
|
|
if( pseq->paramattachment[iPose] != -1 )
|
|
{
|
|
int j0 = pseq->paramindex[iPose];
|
|
int n0 = pseq->paramattachment[iPose];
|
|
int k0 = g_attachment[n0].bone;
|
|
|
|
matrix3x4 boneToWorldRel;
|
|
matrix3x4 boneToWorldMid;
|
|
matrix3x4 worldToBoneMid;
|
|
matrix3x4 boneRel;
|
|
|
|
if( pseq->paramanim == NULL )
|
|
{
|
|
pseq->paramanim = g_panimation[0];
|
|
}
|
|
|
|
if( pseq->paramcompanim == NULL )
|
|
{
|
|
pseq->paramcompanim = pseq->paramanim;
|
|
}
|
|
|
|
// calculate what "zero" looks like to the attachment
|
|
CalcBoneTransforms( pseq->paramanim, 0, boneToWorld );
|
|
boneToWorldMid = boneToWorld[k0].ConcatTransforms( g_attachment[n0].local );
|
|
boneToWorldMid.GetStudioTransform( pos, angles );
|
|
worldToBoneMid = boneToWorldMid.Invert();
|
|
|
|
MsgDev( D_REPORT, "%s : %s", pseq->name, g_pose[j0].name );
|
|
|
|
// for 2D animation, figure out what opposite row/column to use
|
|
// FIXME: make these 2D instead of 2 1D!
|
|
bool found = false;
|
|
int m[2];
|
|
|
|
if( pseq->paramcenter != NULL )
|
|
{
|
|
for( int i0 = 0; !found && i0 < pseq->groupsize[0]; i0++ )
|
|
{
|
|
for( int i1 = 0; !found && i1 < pseq->groupsize[1]; i1++ )
|
|
{
|
|
int q = i0 + pseq->groupsize[0] * i1;
|
|
if( pseq->panim[q] == pseq->paramcenter )
|
|
{
|
|
m[0] = i0;
|
|
m[1] = i1;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !found )
|
|
{
|
|
m[1-iPose] = (pseq->groupsize[1-iPose]) / 2;
|
|
}
|
|
|
|
// find changes to attachment
|
|
for( m[iPose] = 0; m[iPose] < pseq->groupsize[iPose]; m[iPose]++ )
|
|
{
|
|
int q = m[0] + pseq->groupsize[0] * m[1];
|
|
CalcBoneTransforms( pseq->panim[q], pseq->paramcompanim, 0, boneToWorld );
|
|
boneToWorldRel = boneToWorld[k0].ConcatTransforms( g_attachment[n0].local );
|
|
boneRel = worldToBoneMid.ConcatTransforms( boneToWorldRel );
|
|
boneRel.GetStudioTransform( pos, angles );
|
|
|
|
float v = CalcPoseParameterValue( pseq->paramcontrol[iPose], angles, pos );
|
|
|
|
MsgDev( D_REPORT, " %6.2f", v );
|
|
|
|
if( iPose == 0 )
|
|
{
|
|
pseq->param0[m[iPose]] = v;
|
|
}
|
|
else
|
|
{
|
|
pseq->param1[m[iPose]] = v;
|
|
}
|
|
|
|
if( m[iPose] == 0 )
|
|
{
|
|
pseq->paramstart[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]];
|
|
}
|
|
|
|
if( m[iPose] == pseq->groupsize[iPose] - 1 )
|
|
{
|
|
pseq->paramend[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]];
|
|
}
|
|
}
|
|
|
|
MsgDev( D_REPORT, "\n" );
|
|
|
|
if( fabs( pseq->paramstart[iPose] - pseq->paramend[iPose] ) < 0.01 )
|
|
{
|
|
COM_FatalError( "calcblend failed in %s\n", pseq->name );
|
|
}
|
|
|
|
g_pose[j0].min = Q_min( g_pose[j0].min, pseq->paramstart[iPose] );
|
|
g_pose[j0].max = Q_max( g_pose[j0].max, pseq->paramstart[iPose] );
|
|
g_pose[j0].min = Q_min( g_pose[j0].min, pseq->paramend[iPose] );
|
|
g_pose[j0].max = Q_max( g_pose[j0].max, pseq->paramend[iPose] );
|
|
}
|
|
else
|
|
{
|
|
for( int m = 0; m < pseq->groupsize[iPose]; m++ )
|
|
{
|
|
float f = (m / (float)(pseq->groupsize[iPose] - 1.0));
|
|
if( iPose == 0 )
|
|
{
|
|
pseq->param0[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f;
|
|
}
|
|
else
|
|
{
|
|
pseq->param1[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Process IK links
|
|
//-----------------------------------------------------------------------------
|
|
s_ikrule_t *FindPrevIKRule( s_animation_t *panim, int iRule )
|
|
{
|
|
int i, j;
|
|
|
|
s_ikrule_t *pRule = &panim->ikrule[iRule];
|
|
|
|
for( i = 1; i < panim->numikrules; i++ )
|
|
{
|
|
j = ( iRule - i + panim->numikrules) % panim->numikrules;
|
|
if( panim->ikrule[j].chain == pRule->chain )
|
|
return &panim->ikrule[j];
|
|
}
|
|
|
|
return pRule;
|
|
}
|
|
|
|
s_ikrule_t *FindNextIKRule( s_animation_t *panim, int iRule )
|
|
{
|
|
int i, j;
|
|
|
|
s_ikrule_t *pRule = &panim->ikrule[iRule];
|
|
|
|
for( i = 1; i < panim->numikrules; i++ )
|
|
{
|
|
j = (iRule + i ) % panim->numikrules;
|
|
if( panim->ikrule[j].chain == pRule->chain )
|
|
return &panim->ikrule[j];
|
|
}
|
|
|
|
return pRule;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Link ikchains
|
|
//-----------------------------------------------------------------------------
|
|
static void LinkIKChains( void )
|
|
{
|
|
int i, k;
|
|
|
|
// create IK links
|
|
for( i = 0; i < g_numikchains; i++ )
|
|
{
|
|
g_ikchain[i].numlinks = 3;
|
|
|
|
k = findGlobalBone( g_ikchain[i].bonename );
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone '%s' in ikchain '%s'\n", g_ikchain[i].bonename, g_ikchain[i].name );
|
|
}
|
|
|
|
g_ikchain[i].link[2].bone = k;
|
|
g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
|
|
|
|
k = g_bonetable[k].parent;
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError("ikchain '%s' too close to root, no parent knee/elbow\n", g_ikchain[i].name );
|
|
}
|
|
|
|
g_ikchain[i].link[1].bone = k;
|
|
g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
|
|
|
|
k = g_bonetable[k].parent;
|
|
if( k == -1 )
|
|
{
|
|
COM_FatalError("ikchain '%s' too close to root, no parent hip/shoulder\n", g_ikchain[i].name );
|
|
}
|
|
|
|
g_ikchain[i].link[0].bone = k;
|
|
g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT;
|
|
|
|
// FIXME: search for toes
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Link ikchains
|
|
//-----------------------------------------------------------------------------
|
|
static void LinkIKLocks( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
// create IK links
|
|
for( i = 0; i < g_numikautoplaylocks; i++ )
|
|
{
|
|
for( j = 0; j < g_numikchains; j++ )
|
|
{
|
|
if( !Q_stricmp( g_ikchain[j].name, g_ikautoplaylock[i].name ))
|
|
break;
|
|
}
|
|
|
|
if( j == g_numikchains )
|
|
{
|
|
COM_FatalError( "unknown chain '%s' in ikautoplaylock\n", g_ikautoplaylock[i].name );
|
|
}
|
|
g_ikautoplaylock[i].chain = j;
|
|
}
|
|
|
|
for( k = 0; k < g_numseq; k++ )
|
|
{
|
|
for( i = 0; i < g_sequence[k].numiklocks; i++ )
|
|
{
|
|
for( j = 0; j < g_numikchains; j++ )
|
|
{
|
|
if( !Q_stricmp( g_ikchain[j].name, g_sequence[k].iklock[i].name ))
|
|
break;
|
|
}
|
|
|
|
if( j == g_numikchains )
|
|
{
|
|
COM_FatalError( "unknown chain '%s' in sequence iklock\n", g_sequence[k].iklock[i].name );
|
|
}
|
|
g_sequence[k].iklock[i].chain = j;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: go through all the IK rules and calculate the animated path the IK'd
|
|
// end point moves relative to its IK target.
|
|
//-----------------------------------------------------------------------------
|
|
static void ProcessIKRules( void )
|
|
{
|
|
int i, j, k;
|
|
|
|
// copy source animations
|
|
for( i = 0; i < g_numani; i++ )
|
|
{
|
|
s_animation_t *panim = g_panimation[i];
|
|
const char *pAnimationName = g_panimation[i]->name;
|
|
|
|
for( j = 0; j < panim->numcmds; j++ )
|
|
{
|
|
if( panim->cmds[j].cmd == CMD_IKFIXUP )
|
|
{
|
|
fixupIKErrors( panim, panim->cmds[j].ikfixup.pRule );
|
|
}
|
|
|
|
if( panim->cmds[j].cmd != CMD_IKRULE )
|
|
continue;
|
|
|
|
if( panim->numikrules >= MAXSTUDIOIKRULES )
|
|
{
|
|
COM_FatalError("Too many IK rules in %s (%s)\n", panim->name, panim->filename );
|
|
}
|
|
|
|
s_ikrule_t *pRule = &panim->ikrule[panim->numikrules++];
|
|
|
|
// make a copy of the rule;
|
|
*pRule = *panim->cmds[j].ikrule.pRule;
|
|
}
|
|
|
|
for( j = 0; j < panim->numikrules; j++ )
|
|
{
|
|
s_ikrule_t *pRule = &panim->ikrule[j];
|
|
|
|
if( pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0 )
|
|
{
|
|
pRule->tail = panim->numframes - 1;
|
|
pRule->end = panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->start != -1 && pRule->peak == -1 && pRule->tail == -1 && pRule->end != -1 )
|
|
{
|
|
pRule->peak = (pRule->start + pRule->end) / 2;
|
|
pRule->tail = (pRule->start + pRule->end) / 2;
|
|
}
|
|
|
|
if( pRule->start != -1 && pRule->peak == -1 && pRule->tail != -1 )
|
|
{
|
|
pRule->peak = (pRule->start + pRule->tail) / 2;
|
|
}
|
|
|
|
if( pRule->peak != -1 && pRule->tail == -1 && pRule->end != -1 )
|
|
{
|
|
pRule->tail = (pRule->peak + pRule->end) / 2;
|
|
}
|
|
|
|
if( pRule->peak == -1 )
|
|
{
|
|
pRule->start = 0;
|
|
pRule->peak = 0;
|
|
}
|
|
|
|
if( pRule->tail == -1 )
|
|
{
|
|
pRule->tail = panim->numframes - 1;
|
|
pRule->end = panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->contact == -1 )
|
|
{
|
|
pRule->contact = pRule->peak;
|
|
}
|
|
|
|
// huh, make up start and end numbers
|
|
if( pRule->start == -1 )
|
|
{
|
|
s_ikrule_t *pPrev = FindPrevIKRule( panim, j );
|
|
|
|
if( pPrev->slot == pRule->slot )
|
|
{
|
|
if( pRule->peak < pPrev->tail )
|
|
{
|
|
pRule->start = pRule->peak + (pPrev->tail - pRule->peak) / 2;
|
|
}
|
|
else
|
|
{
|
|
pRule->start = pRule->peak + (pPrev->tail - pRule->peak + panim->numframes - 1) / 2;
|
|
}
|
|
|
|
pRule->start = (pRule->start + panim->numframes / 2) % (panim->numframes - 1);
|
|
pPrev->end = (pRule->start + panim->numframes - 1) % (panim->numframes - 1);
|
|
}
|
|
else
|
|
{
|
|
pRule->start = pPrev->tail;
|
|
pPrev->end = pRule->peak;
|
|
}
|
|
}
|
|
|
|
// huh, make up start and end numbers
|
|
if( pRule->end == -1 )
|
|
{
|
|
s_ikrule_t *pNext = FindNextIKRule( panim, j );
|
|
|
|
if( pNext->slot == pRule->slot )
|
|
{
|
|
if( pNext->peak < pRule->tail )
|
|
{
|
|
pNext->start = pNext->peak + (pRule->tail - pNext->peak) / 2;
|
|
}
|
|
else
|
|
{
|
|
pNext->start = pNext->peak + (pRule->tail - pNext->peak + panim->numframes - 1) / 2;
|
|
}
|
|
|
|
pNext->start = (pNext->start + panim->numframes / 2) % (panim->numframes - 1);
|
|
pRule->end = (pNext->start + panim->numframes - 1) % (panim->numframes - 1);
|
|
}
|
|
else
|
|
{
|
|
pNext->start = pRule->tail;
|
|
pRule->end = pNext->peak;
|
|
}
|
|
}
|
|
|
|
// check for wrapping
|
|
if( pRule->peak < pRule->start )
|
|
{
|
|
pRule->peak += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->tail < pRule->peak )
|
|
{
|
|
pRule->tail += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->end < pRule->tail )
|
|
{
|
|
pRule->end += panim->numframes - 1;
|
|
}
|
|
|
|
if( pRule->contact < pRule->start )
|
|
{
|
|
pRule->contact += panim->numframes - 1;
|
|
}
|
|
|
|
pRule->errorData.numerror = pRule->end - pRule->start + 1;
|
|
if( pRule->end >= panim->numframes )
|
|
pRule->errorData.numerror = pRule->errorData.numerror + 2;
|
|
|
|
pRule->errorData.pError = (s_streamdata_t *)Mem_Alloc( pRule->errorData.numerror * sizeof( s_streamdata_t ));
|
|
|
|
int n = 0;
|
|
|
|
if( pRule->usesequence )
|
|
{
|
|
// FIXME: bah, this is horrendously hacky, add a damn back pointer
|
|
for( n = 0; n < g_numseq; n++ )
|
|
{
|
|
if( g_sequence[n].panim[0] == panim )
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch( pRule->type )
|
|
{
|
|
case IK_SELF:
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 worldToBone;
|
|
matrix3x4 local;
|
|
|
|
if( !Q_strlen( pRule->bonename ))
|
|
{
|
|
pRule->bone = -1;
|
|
}
|
|
else
|
|
{
|
|
pRule->bone = findGlobalBone( pRule->bonename );
|
|
|
|
if( pRule->bone == -1 )
|
|
COM_FatalError( "unknown bone '%s' in ikrule\n", pRule->bonename );
|
|
}
|
|
|
|
for( k = 0; k < pRule->errorData.numerror; k++ )
|
|
{
|
|
if( pRule->usesequence )
|
|
{
|
|
CalcSeqTransforms( n, k + pRule->start, boneToWorld );
|
|
}
|
|
else if( pRule->usesource )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
BuildRawTransforms( panim, k + pRule->start + panim->startframe - panim->source.startframe, panim->adjust, panim->rotation, srcBoneToWorld );
|
|
TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld );
|
|
}
|
|
else
|
|
{
|
|
CalcBoneTransforms( panim, k + pRule->start, boneToWorld );
|
|
}
|
|
|
|
if( pRule->bone != -1 )
|
|
{
|
|
worldToBone = boneToWorld[pRule->bone].Invert();
|
|
local = worldToBone.ConcatTransforms( boneToWorld[g_ikchain[pRule->chain].link[2].bone] );
|
|
}
|
|
else
|
|
{
|
|
local = boneToWorld[g_ikchain[pRule->chain].link[2].bone];
|
|
}
|
|
|
|
pRule->errorData.pError[k].q = local.GetQuaternion();
|
|
pRule->errorData.pError[k].pos = local.GetOrigin();
|
|
}
|
|
}
|
|
break;
|
|
case IK_WORLD:
|
|
break;
|
|
case IK_ATTACHMENT:
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 worldToBone;
|
|
matrix3x4 local;
|
|
|
|
int bone = g_ikchain[pRule->chain].link[2].bone;
|
|
CalcBoneTransforms( panim, pRule->contact, boneToWorld );
|
|
// FIXME: add in motion
|
|
|
|
if( !Q_strlen( pRule->bonename ))
|
|
{
|
|
if( pRule->bone != -1 )
|
|
{
|
|
pRule->bone = bone;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pRule->bone = findGlobalBone( pRule->bonename );
|
|
if( pRule->bone == -1 )
|
|
{
|
|
COM_FatalError( "unknown bone '%s' in ikrule\n", pRule->bonename );
|
|
}
|
|
}
|
|
|
|
if( pRule->bone != -1 )
|
|
{
|
|
// FIXME: look for local bones...
|
|
CalcBoneTransforms( panim, pRule->contact, boneToWorld );
|
|
pRule->q = boneToWorld[pRule->bone].GetQuaternion();
|
|
pRule->pos = boneToWorld[pRule->bone].GetOrigin();
|
|
}
|
|
|
|
for( k = 0; k < pRule->errorData.numerror; k++ )
|
|
{
|
|
int t = k + pRule->start;
|
|
|
|
if( pRule->usesequence )
|
|
{
|
|
CalcSeqTransforms( n, t, boneToWorld );
|
|
}
|
|
else if( pRule->usesource )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
BuildRawTransforms( panim, t + panim->startframe - panim->source.startframe, g_vecZero, g_radZero, srcBoneToWorld );
|
|
TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld );
|
|
}
|
|
else
|
|
{
|
|
CalcBoneTransforms( panim, t, boneToWorld );
|
|
}
|
|
|
|
Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact );
|
|
|
|
local = matrix3x4( pos, pRule->q );
|
|
worldToBone = local.Invert();
|
|
|
|
// calc position error
|
|
local = worldToBone.ConcatTransforms( boneToWorld[bone] );
|
|
pRule->errorData.pError[k].q = local.GetQuaternion();
|
|
pRule->errorData.pError[k].pos = local.GetOrigin();
|
|
}
|
|
}
|
|
break;
|
|
case IK_GROUND:
|
|
{
|
|
matrix3x4 boneToWorld[MAXSTUDIOBONES];
|
|
matrix3x4 worldToBone;
|
|
matrix3x4 local;
|
|
|
|
int bone = g_ikchain[pRule->chain].link[2].bone;
|
|
|
|
if( pRule->usesequence )
|
|
{
|
|
CalcSeqTransforms( n, pRule->contact, boneToWorld );
|
|
}
|
|
else if (pRule->usesource)
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
BuildRawTransforms( panim, pRule->contact + panim->startframe, panim->adjust, panim->rotation, srcBoneToWorld );
|
|
TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld );
|
|
}
|
|
else
|
|
{
|
|
CalcBoneTransforms( panim, pRule->contact, boneToWorld );
|
|
}
|
|
|
|
// FIXME: add in motion
|
|
|
|
Vector footfall = boneToWorld[bone].VectorTransform( g_ikchain[pRule->chain].center );
|
|
footfall.z = pRule->floor;
|
|
|
|
local = matrix3x4( footfall, g_radZero );
|
|
worldToBone = local.Invert();
|
|
|
|
pRule->pos = footfall;
|
|
pRule->q = g_radZero; // auto conversion Radian->Quaternion
|
|
|
|
float s;
|
|
|
|
for( k = 0; k < pRule->errorData.numerror; k++ )
|
|
{
|
|
int t = k + pRule->start;
|
|
|
|
if( pRule->usesequence )
|
|
{
|
|
CalcSeqTransforms( n, t, boneToWorld );
|
|
}
|
|
else if( pRule->usesource )
|
|
{
|
|
matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES];
|
|
BuildRawTransforms( panim, pRule->contact + panim->startframe, panim->adjust, panim->rotation, srcBoneToWorld );
|
|
TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld );
|
|
}
|
|
else
|
|
{
|
|
CalcBoneTransforms( panim, t, boneToWorld );
|
|
}
|
|
|
|
Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact );
|
|
s = 0.0;
|
|
|
|
Vector cur = boneToWorld[bone].VectorTransform( g_ikchain[pRule->chain].center );
|
|
cur.z = pos.z;
|
|
|
|
if( t < pRule->start || t >= pRule->end )
|
|
{
|
|
pos = cur;
|
|
}
|
|
else if( t < pRule->peak )
|
|
{
|
|
s = (float)(pRule->peak - t) / (pRule->peak - pRule->start);
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
pos = pos * (1 - s) + cur * s;
|
|
}
|
|
else if( t > pRule->tail )
|
|
{
|
|
s = (float)(t - pRule->tail) / (pRule->end - pRule->tail);
|
|
s = 3 * s * s - 2 * s * s * s;
|
|
pos = pos * (1 - s) + cur * s;
|
|
}
|
|
|
|
local = matrix3x4( pos, pRule->q );
|
|
worldToBone = local.Invert();
|
|
|
|
// calc position error
|
|
local = worldToBone.ConcatTransforms( boneToWorld[bone] );
|
|
pRule->errorData.pError[k].q = local.GetQuaternion();
|
|
pRule->errorData.pError[k].pos = local.GetOrigin();
|
|
}
|
|
}
|
|
break;
|
|
case IK_RELEASE:
|
|
case IK_UNLATCH:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( FBitSet( panim->flags, STUDIO_DELTA ) || panim->noAutoIK )
|
|
continue;
|
|
|
|
// auto release ik chains that are moved but not referenced and have no explicit rules
|
|
int count[16];
|
|
|
|
for( j = 0; j < g_numikchains; j++ )
|
|
{
|
|
count[j] = 0;
|
|
}
|
|
|
|
for( j = 0; j < panim->numikrules; j++ )
|
|
{
|
|
count[panim->ikrule[j].chain]++;
|
|
}
|
|
|
|
for( j = 0; j < g_numikchains; j++ )
|
|
{
|
|
if( count[j] == 0 && panim->weight[g_ikchain[j].link[2].bone] > 0.0f )
|
|
{
|
|
k = panim->numikrules++;
|
|
panim->ikrule[k].chain = j;
|
|
panim->ikrule[k].slot = j;
|
|
panim->ikrule[k].type = IK_RELEASE;
|
|
panim->ikrule[k].start = 0;
|
|
panim->ikrule[k].peak = 0;
|
|
panim->ikrule[k].tail = panim->numframes - 1;
|
|
panim->ikrule[k].end = panim->numframes - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// realign IK across multiple animations
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
for( j = 0; j < g_sequence[i].groupsize[0]; j++ )
|
|
{
|
|
for( k = 0; k < g_sequence[i].groupsize[1]; k++ )
|
|
{
|
|
int q = j + g_sequence[i].groupsize[0] * k;
|
|
g_sequence[i].numikrules = Q_max( g_sequence[i].numikrules, g_sequence[i].panim[q]->numikrules );
|
|
}
|
|
}
|
|
|
|
// check for mismatched ik rules
|
|
s_animation_t *panim1 = g_sequence[i].panim[0];
|
|
for( j = 0; j < g_sequence[i].groupsize[0]; j++ )
|
|
{
|
|
for( k = 0; k < g_sequence[i].groupsize[1]; k++ )
|
|
{
|
|
int q = j + g_sequence[i].groupsize[0] * k;
|
|
s_animation_t *panim2 = g_sequence[i].panim[q];
|
|
if( panim1->numikrules != panim2->numikrules )
|
|
{
|
|
COM_FatalError( "%s - mismatched number of IK rules: \"%s\" \"%s\"\n", g_sequence[i].name, panim1->name, panim2->name );
|
|
}
|
|
|
|
for( int n = 0; n < panim1->numikrules; n++ )
|
|
{
|
|
if(( panim1->ikrule[n].type != panim2->ikrule[n].type ) ||
|
|
( panim1->ikrule[n].chain != panim2->ikrule[n].chain ) ||
|
|
( panim1->ikrule[n].slot != panim2->ikrule[n].slot ))
|
|
{
|
|
COM_FatalError( "%s - mismatched IK rule %d: \n\"%s\" : %d %d %d\n\"%s\" : %d %d %d\n",
|
|
g_sequence[i].name, n,
|
|
panim1->name, panim1->ikrule[n].type, panim1->ikrule[n].chain, panim1->ikrule[n].slot,
|
|
panim2->name, panim2->ikrule[n].type, panim2->ikrule[n].chain, panim2->ikrule[n].slot );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: this doesn't check alignment!!!
|
|
for( j = 0; j < g_sequence[i].groupsize[0]; j++ )
|
|
{
|
|
for( k = 0; k < g_sequence[i].groupsize[1]; k++ )
|
|
{
|
|
int q = j + g_sequence[i].groupsize[0] * k;
|
|
for( int n = 0; n < g_sequence[i].panim[q]->numikrules; n++ )
|
|
{
|
|
g_sequence[i].panim[q]->ikrule[n].index = n;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compress all the IK data
|
|
//-----------------------------------------------------------------------------
|
|
static void CompressIKErrors( void )
|
|
{
|
|
// find scales for all bones
|
|
for( int i = 0; i < g_numani; i++ )
|
|
{
|
|
for( int j = 0; j < g_panimation[i]->numikrules; j++ )
|
|
{
|
|
s_ikrule_t *pRule = &g_panimation[i]->ikrule[j];
|
|
|
|
if( pRule->errorData.numerror == 0 )
|
|
continue;
|
|
|
|
CompressSingle( &pRule->errorData );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SimplifyModel( void )
|
|
{
|
|
if( g_numseq == 0 )
|
|
COM_FatalError( "model has no sequences\n" );
|
|
|
|
RemapBones( );
|
|
|
|
LinkIKChains();
|
|
|
|
LinkIKLocks();
|
|
|
|
RealignBones( );
|
|
|
|
RemapVertices( );
|
|
|
|
BuildVertexArrays( );
|
|
|
|
RemapAnimations( );
|
|
|
|
processAnimations( );
|
|
|
|
OptimizeAnimations( ); // FIXME: remove
|
|
|
|
limitBoneRotations();
|
|
|
|
limitIKChainLength();
|
|
|
|
RemapProceduralBones( );
|
|
|
|
MakeTransitions( );
|
|
|
|
FindAutolayers();
|
|
|
|
LinkBoneControllers( );
|
|
|
|
// link screen aligned bones
|
|
TagScreenAlignedBones( );
|
|
|
|
LinkAttachments( );
|
|
|
|
// procedural bone needs to propagate its bone usage up its chain
|
|
// ensures runtime sets up dependent bone hierarchy
|
|
MarkProceduralBoneChain( );
|
|
|
|
ProcessIKRules();
|
|
|
|
CompressIKErrors( );
|
|
|
|
CalcPoseParameters();
|
|
|
|
SetupHitBoxes( );
|
|
|
|
CompressAnimations( );
|
|
|
|
CalcSequenceBoundingBoxes( );
|
|
|
|
// auto groups
|
|
if( g_numseqgroups == 1 && maxseqgroupsize < 1024 * 1024 )
|
|
{
|
|
int i, j, k, q;
|
|
int current = 0;
|
|
|
|
g_numseqgroups = 2;
|
|
|
|
for( i = 0; i < g_numseq; i++ )
|
|
{
|
|
int accum = 0;
|
|
|
|
if( g_sequence[i].activity == 0 )
|
|
{
|
|
for( q = 0; q < g_sequence[i].numblends; q++ )
|
|
{
|
|
for( j = 0; j < g_numbones; j++ )
|
|
{
|
|
for( k = 0; k < 6; k++ )
|
|
{
|
|
accum += g_sequence[i].panim[q]->numanim[j][k] * sizeof( mstudioanimvalue_t );
|
|
}
|
|
}
|
|
}
|
|
|
|
accum += g_sequence[i].numblends * g_numbones * sizeof( mstudioanim_t );
|
|
|
|
if( current && current + accum > maxseqgroupsize )
|
|
{
|
|
g_numseqgroups++;
|
|
current = accum;
|
|
}
|
|
else
|
|
{
|
|
current += accum;
|
|
}
|
|
// printf("%d %d %d\n", g_numseqgroups, current, accum );
|
|
g_sequence[i].seqgroup = g_numseqgroups - 1;
|
|
}
|
|
else
|
|
{
|
|
g_sequence[i].seqgroup = 0;
|
|
}
|
|
}
|
|
}
|
|
} |