|
|
//============ Copyright (c) Valve Corporation, All rights reserved. ============
//
// Routines used for various prodcedural bones but meant to be called from
// datamodel or maya as well
//
// In a separate source file so linking bonesetup.lib doesn't get more than
// needed
//
//===============================================================================
// Valve includes
#include "mathlib/mathlib.h"
#include "mathlib/vector.h"
#include "tier1/strtools.h"
#include "bone_setup.h"
#include "bone_constraints.h"
#include "bone_accessor.h"
#include "studio.h"
#include "tier0/tslist.h"
#include "tier0/miniprofiler.h"
#include "bone_utils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//=============================================================================
//=============================================================================
//
// CConstraintBones
//
//=============================================================================
//=============================================================================
//-----------------------------------------------------------------------------
// Compute the aggregate target position and orientation from the weighted target list and return
// the resulting position and orientation in addition to updating the target dag.
// All passed arrays must be nTargetCount in length
//-----------------------------------------------------------------------------
float CConstraintBones::ComputeTargetPosition( Vector &vTargetPosition, int nTargetCount, float *flTargetWeights, Vector *vTargetPositions, Vector *vTargetOffsets ) { vTargetPosition = vec3_origin; float flWeightSum = 0.0;
for ( int i = 0; i < nTargetCount; ++i ) { vTargetPosition += ( flTargetWeights[i] * ( vTargetPositions[i] + vTargetOffsets[i] ) ); flWeightSum += flTargetWeights[i]; }
if ( flWeightSum > 0.0f ) { vTargetPosition *= 1.0f / flWeightSum; }
return MIN( 1.0f, flWeightSum ); }
//-----------------------------------------------------------------------------
// Compute the aggregate target orientation from the weighted target list and
// return the total weight
// All passed arrays must be nTargetCount in length
//-----------------------------------------------------------------------------
float CConstraintBones::ComputeTargetOrientation( Quaternion &qTargetOrientation, int nTargetCount, float *pflTargetWeights, Quaternion *pqTargetOrientations, Quaternion *pqTargetOffsets ) { // If there is only one target, for efficiency don't bother with the weighting
if ( nTargetCount == 1 ) { QuaternionMult( pqTargetOrientations[0], pqTargetOffsets[0], qTargetOrientation );
return MIN( 1.0f, pflTargetWeights[0] ); }
qTargetOrientation = quat_identity;
// If no targets, return identity quaternion and weight of 0
if ( nTargetCount <= 0 ) return 0.0f;
Quaternion *pQuats = reinterpret_cast< Quaternion * >( stackalloc( nTargetCount * sizeof( Quaternion ) ) );
float flWeightSum = 0.0f;
for ( int i = 0; i < nTargetCount; ++i ) { QuaternionMult( pqTargetOrientations[i], pqTargetOffsets[i], pQuats[i] ); flWeightSum += pflTargetWeights[i]; }
QuaternionAverageExponential( qTargetOrientation, nTargetCount, pQuats, pflTargetWeights );
return MIN( 1.0f, flWeightSum ); }
//-----------------------------------------------------------------------------
// Compute the aggregate target position and orientation from the weighted
// target list and return the total weight
// All passed arrays must be nTargetCount in length
//-----------------------------------------------------------------------------
float CConstraintBones::ComputeTargetPositionOrientation( Vector &vTargetPosition, Quaternion &qTargetOrientation, int nTargetCount, float *pflTargetWeights, Vector *pvTargetPositions, Vector *pvTargetOffsets, Quaternion *pqTargetOrientations, Quaternion *pqTargetOffsets ) { Quaternion *pQuats = reinterpret_cast< Quaternion *>( stackalloc( nTargetCount * sizeof( Quaternion ) ) );
float flWeightSum = 0.0f;
vTargetPosition = vec3_origin; qTargetOrientation = quat_identity;
for ( int i = 0; i < nTargetCount; ++i ) { float flWeight = pflTargetWeights[i];
matrix3x4a_t mTarget; AngleMatrix( RadianEuler(pqTargetOrientations[i]), pvTargetPositions[i], mTarget );
matrix3x4a_t mOffset; AngleMatrix( RadianEuler(pqTargetOffsets[i]), pvTargetOffsets[i], mOffset );
matrix3x4a_t mAbs; ConcatTransforms( mTarget, mOffset, mAbs );
Vector vPos; MatrixAngles( mAbs, pQuats[i], vPos );
vTargetPosition += ( flWeight * vPos );
// For normalization
flWeightSum += flWeight; }
if ( flWeightSum > 0.0f ) { vTargetPosition *= 1.0f / flWeightSum; }
QuaternionAverageExponential( qTargetOrientation, nTargetCount, pQuats, pflTargetWeights );
return MIN( 1.0f, flWeightSum ); }
//-----------------------------------------------------------------------------
// Compute the aggregate target position and orientation from the weighted
// target list and return the total weight
// All passed arrays must be nTargetCount in length
//-----------------------------------------------------------------------------
float CConstraintBones::ComputeTargetPositionOrientation( Vector &vTargetPosition, Quaternion &qTargetOrientation, int nTargetCount, float *pflTargetWeights, matrix3x4a_t *pmTargets, matrix3x4a_t *pmOffsets ) { Quaternion *pQuats = reinterpret_cast< Quaternion *>( stackalloc( nTargetCount * sizeof( Quaternion ) ) );
float flWeightSum = 0.0f;
vTargetPosition = vec3_origin; qTargetOrientation = quat_identity;
matrix3x4a_t mAbs; Vector vPos;
for ( int i = 0; i < nTargetCount; ++i ) { float flWeight = pflTargetWeights[i];
ConcatTransforms( pmTargets[i], pmOffsets[i], mAbs );
MatrixAngles( mAbs, pQuats[i], vPos );
vTargetPosition += ( flWeight * vPos );
// For normalization
flWeightSum += flWeight; }
if ( flWeightSum > 0.0f ) { vTargetPosition *= 1.0f / flWeightSum; }
QuaternionAverageExponential( qTargetOrientation, nTargetCount, pQuats, pflTargetWeights );
return MIN( 1.0f, flWeightSum ); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CConstraintBones::ComputeAimConstraintOffset( Quaternion &qAimOffset, bool bPreserveOffset, const Vector &vTargetWorldPos, const matrix3x4_t &mSlaveParentToWorld, const Vector &vUp, const Vector &vSlaveLocalPos, const Quaternion &qSlaveLocal, matrix3x4_t *pmUpToWorld, AimConstraintUpType_t eUpType ) { if ( !bPreserveOffset ) { qAimOffset = quat_identity; return; }
// Calculate the desired orientation based the target position
Quaternion qAim; ComputeAimConstraint( qAim, vTargetWorldPos, mSlaveParentToWorld, vUp, vSlaveLocalPos, pmUpToWorld, eUpType );
// Compute the difference between the slave's current orientation and the target orientation
Quaternion qAimInv; QuaternionInvert( qAim, qAimInv );
QuaternionMult( qAimInv, qSlaveLocal, qAimOffset );
RadianEuler eAim(qAim); RadianEuler eSlaveLocal(qSlaveLocal); RadianEuler eAimOffset(qAimOffset); }
//-----------------------------------------------------------------------------
// Calculate the orientation needed to make a transform where the y
// vector of the transform matches the forward vector and the z vector matches
// the up reference vector as closely as possible. The x vector will be in the
// plane defined by using the forward vector as the normal.
//-----------------------------------------------------------------------------
void CConstraintBones::ComputeAimConstraintAimAt( Quaternion &qAim, const Vector &vForward, const Vector &vReferenceUp ) { Vector vFwd = vForward; vFwd.NormalizeInPlace(); const float flRatio = DotProduct( vFwd, vReferenceUp ); Vector vUp = vReferenceUp - ( vFwd * flRatio ); vUp.NormalizeInPlace();
Vector vRight = vFwd.Cross( vUp ); vRight.NormalizeInPlace();
const Vector &vX = vRight; const Vector &vY = vFwd; const Vector &vZ = vUp;
const float flTr = vX.x + vY.y + vZ.z; qAim.Init( vY.z - vZ.y , vZ.x - vX.z, vX.y - vY.x, flTr + 1.0f ); const float flRadius = qAim[0] * qAim[0] + qAim[1] * qAim[1] + qAim[2] * qAim[2] + qAim[3] * qAim[3]; if ( flRadius > FLT_EPSILON ) { QuaternionNormalize( qAim ); } else { matrix3x4_t mRot; MatrixSetColumn( vX, 0, mRot ); MatrixSetColumn( vY, 1, mRot ); MatrixSetColumn( vZ, 2, mRot ); MatrixQuaternion( mRot, qAim ); } }
//-----------------------------------------------------------------------------
// Given the various parameters, computes the local vForward & vReferenceUp
// and calls ComputeAimConstraintAimAt
//-----------------------------------------------------------------------------
void CConstraintBones::ComputeAimConstraint( Quaternion &qAim, const Vector &vTargetWorldPos, const matrix3x4_t &mParentToWorld, const Vector &vUp, const Vector &vSlaveLocalPos, const matrix3x4_t *pmUpToWorld, AimConstraintUpType_t eUpType ) { matrix3x4_t mWorldToParent; MatrixInvert( mParentToWorld, mWorldToParent );
// If the up vector is in world space, convert it into local space
Vector vWorldUp;
ComputeWorldUpVector( &vWorldUp, mParentToWorld, vUp, vSlaveLocalPos, pmUpToWorld, eUpType );
Vector vLocalUp; VectorRotate( vWorldUp, mWorldToParent, vLocalUp );
// Convert the target's world space position into the local space of the slave.
Vector vTargetLocalPos; VectorTransform( vTargetWorldPos, mWorldToParent, vTargetLocalPos );
// Compute the local space forward vector
Vector vLocalForward = vTargetLocalPos - vSlaveLocalPos; vLocalForward.NormalizeInPlace();
// Compute the orientation
CConstraintBones::ComputeAimConstraintAimAt( qAim, vLocalForward, vLocalUp ); RadianEuler e(qAim); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CConstraintBones::ComputeWorldUpVector( Vector *pvWorldUp, const matrix3x4_t & mParentToWorld, const Vector &vUp, const Vector &vSlaveLocalPos, const matrix3x4_t *pmUpToWorld, AimConstraintUpType_t eUpType ) { switch ( eUpType ) { case AC_UP_TYPE_VECTOR: VectorCopy( vUp, *pvWorldUp ); break; case AC_UP_TYPE_OBJECT: if ( pmUpToWorld ) { Vector vUpObjectWorldPos; MatrixPosition( *pmUpToWorld, vUpObjectWorldPos ); Vector vSlaveWorldPos; VectorTransform( vSlaveLocalPos, mParentToWorld, vSlaveWorldPos ); VectorSubtract( vUpObjectWorldPos, vSlaveWorldPos, *pvWorldUp ); VectorNormalize( *pvWorldUp ); } else { VectorCopy( vUp, *pvWorldUp ); } break; case AC_UP_TYPE_PARENT_ROTATION: VectorRotate( vUp, mParentToWorld, *pvWorldUp ); break; default: case AC_UP_TYPE_OBJECT_ROTATION: if ( pmUpToWorld ) { VectorRotate( vUp, *pmUpToWorld, *pvWorldUp ); } else { VectorCopy( vUp, *pvWorldUp ); } break; } }
//=============================================================================
//=============================================================================
//
// CStudioConstraintBones
//
//=============================================================================
//=============================================================================
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
float CStudioConstraintBones::ComputeTargetPosition( Vector &vTargetPosition, mstudioconstrainttarget_t *pTargets, int nTargetCount, CBoneAccessor &boneToWorld ) { float *pflTargetWeights = reinterpret_cast< float * >( stackalloc( nTargetCount * sizeof( float ) ) ); Vector *pvTargetPositions = reinterpret_cast< Vector * >( stackalloc( nTargetCount * sizeof( Vector ) ) ); Vector *pvTargetOffsets = reinterpret_cast< Vector * >( stackalloc( nTargetCount * sizeof( Vector ) ) );
mstudioconstrainttarget_t *pTarget;
for ( int i = 0; i < nTargetCount; ++i ) { pTarget = pTargets + i; pflTargetWeights[i] = pTarget->m_flWeight; pvTargetOffsets[i] = pTarget->m_vOffset; MatrixPosition( boneToWorld.GetBone( pTarget->m_nBone ), pvTargetPositions[i] ); }
return CConstraintBones::ComputeTargetPosition( vTargetPosition, nTargetCount, pflTargetWeights, pvTargetPositions, pvTargetOffsets ); }
//=============================================================================
// CStudioConstraintBones : Studio Interface Functions
//=============================================================================
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
float CStudioConstraintBones::ComputeTargetOrientation( Quaternion &qTargetOrientation, mstudioconstrainttarget_t *pTargets, int nTargetCount, CBoneAccessor &boneToWorld ) { float *pflTargetWeights = reinterpret_cast< float * >( stackalloc( nTargetCount * sizeof( float ) ) ); Quaternion *pqTargetOrientations = reinterpret_cast< Quaternion * >( stackalloc( nTargetCount * sizeof( Quaternion ) ) ); Quaternion *pqTargetOffsets = reinterpret_cast< Quaternion * >( stackalloc( nTargetCount * sizeof( Quaternion ) ) );
mstudioconstrainttarget_t *pTarget;
for ( int i = 0; i < nTargetCount; ++i ) { pTarget = pTargets + i; pflTargetWeights[i] = pTarget->m_flWeight; pqTargetOffsets[i] = pTarget->m_qOffset; MatrixQuaternion( boneToWorld.GetBone( pTarget->m_nBone ), pqTargetOrientations[i] ); }
return CConstraintBones::ComputeTargetOrientation( qTargetOrientation, nTargetCount, pflTargetWeights, pqTargetOrientations, pqTargetOffsets ); }
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
float CStudioConstraintBones::ComputeTargetPositionOrientation( Vector &vTargetPosition, Quaternion &qTargetOrientation, mstudioconstrainttarget_t *pTargets, int nTargetCount, CBoneAccessor &boneToWorld ) { float *pflTargetWeights = reinterpret_cast< float * >( stackalloc( nTargetCount * sizeof( float ) ) ); matrix3x4a_t *pmTargets = reinterpret_cast< matrix3x4a_t * >( stackalloc( nTargetCount * sizeof( matrix3x4a_t ) ) ); matrix3x4a_t *pmOffsets = reinterpret_cast< matrix3x4a_t * >( stackalloc( nTargetCount * sizeof( matrix3x4a_t ) ) );
mstudioconstrainttarget_t *pTarget = pTargets;
for ( int i = 0; i < nTargetCount; ++i, ++pTarget ) { pflTargetWeights[i] = pTarget->m_flWeight; QuaternionMatrix( pTarget->m_qOffset, pTarget->m_vOffset, pmOffsets[i] ); pmTargets[i] = boneToWorld.GetBone( pTarget->m_nBone ); }
return CConstraintBones::ComputeTargetPositionOrientation( vTargetPosition, qTargetOrientation, nTargetCount, pflTargetWeights, pmTargets, pmOffsets ); }
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
void CStudioConstraintBones::ComputeBaseWorldMatrix( matrix3x4a_t &mBaseWorldMatrix, mstudioconstraintslave_t *pSlave, CBoneAccessor &boneToWorld, const CStudioHdr *pStudioHdr, const matrix3x4_t *pmViewTransform /* = NULL */ ) { // studiomdl shouldn't create mstudioconstraintslave_t's with invalid bone indices
Assert( pSlave->m_nBone >= 0 && pSlave->m_nBone < MAXSTUDIOBONES );
const int nBoneParent = pStudioHdr->boneParent( pSlave->m_nBone ); if ( nBoneParent < 0 ) { if ( pmViewTransform ) { matrix3x4a_t mTmp; QuaternionMatrix( pSlave->m_qBaseOrientation, pSlave->m_vBasePosition, mTmp ); ConcatTransforms( *pmViewTransform, mTmp, mBaseWorldMatrix ); } else { QuaternionMatrix( pSlave->m_qBaseOrientation, pSlave->m_vBasePosition, mBaseWorldMatrix ); } } else { matrix3x4a_t mTmp; QuaternionMatrix( pSlave->m_qBaseOrientation, pSlave->m_vBasePosition, mTmp ); ConcatTransforms( boneToWorld.GetBone( nBoneParent ), mTmp, mBaseWorldMatrix ); } }
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
void CStudioConstraintBones::ComputePointConstraint( const mstudiobone_t *pBones, int nBone, CBoneAccessor &boneToWorld, const CStudioHdr *pStudioHdr ) { BONE_PROFILE_FUNC();
mstudiopointconstraint_t *pProc = ( mstudiopointconstraint_t * )pBones[nBone].pProcedure();
// Calculate the current target position and the total weight
// of the the targets contributing to the target position.
Vector vTargetPosition; const float flWeight = CStudioConstraintBones::ComputeTargetPosition( vTargetPosition, pProc->pTarget( 0 ), pProc->m_nTargetCount, boneToWorld );
Vector vFinalPosition;
matrix3x4a_t &mBaseWorldMatrix = boneToWorld.GetBoneForWrite( nBone ); CStudioConstraintBones::ComputeBaseWorldMatrix( mBaseWorldMatrix, &( pProc->m_slave ), boneToWorld, pStudioHdr );
// Blend between the target position and the base position using the target weight
if ( flWeight < 1.0f ) { Vector vBasePosition; MatrixPosition( mBaseWorldMatrix, vBasePosition );
vFinalPosition = Lerp( flWeight, vBasePosition, vTargetPosition ); } else { vFinalPosition = vTargetPosition; }
// Update the bone the new position.
PositionMatrix( vFinalPosition, mBaseWorldMatrix ); }
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
void CStudioConstraintBones::ComputeOrientConstraint( const mstudiobone_t *pBones, int nBone, CBoneAccessor &boneToWorld, const CStudioHdr *pStudioHdr, const matrix3x4_t *pmViewTransform ) { BONE_PROFILE_FUNC();
mstudioorientconstraint_t *pProc = ( mstudioorientconstraint_t * )pBones[nBone].pProcedure();
// Calculate the current target position and the total weight
// of the the targets contributing to the target position.
Quaternion qTargetOrientation; const float flWeight = CStudioConstraintBones::ComputeTargetOrientation( qTargetOrientation, pProc->pTarget( 0 ), pProc->m_nTargetCount, boneToWorld );
// Blend between the target orientation and the base orientation using the target weight
Quaternion qFinalOrientation;
matrix3x4a_t &mBaseWorldMatrix = boneToWorld.GetBoneForWrite( nBone ); CStudioConstraintBones::ComputeBaseWorldMatrix( mBaseWorldMatrix, &( pProc->m_slave ), boneToWorld, pStudioHdr, pmViewTransform );
if ( flWeight < 1.0f ) { Quaternion qBaseOrientation; MatrixQuaternion( mBaseWorldMatrix, qBaseOrientation );
QuaternionSlerp( qBaseOrientation, qTargetOrientation, flWeight, qFinalOrientation ); } else { qFinalOrientation = qTargetOrientation; }
// Quaternion matrix wipes out the translate component
Vector vTmpPosition; MatrixPosition( mBaseWorldMatrix, vTmpPosition ); QuaternionMatrix( qFinalOrientation, vTmpPosition, mBaseWorldMatrix ); }
//-----------------------------------------------------------------------------
// studio interface
//
// pmViewTransform is for hlmv which modifies the actual parentless bones
// to position things in the viewer
//
// TODO: Split into hlmv and "normal" versions, i.e. hlmv needs ViewTransform
//-----------------------------------------------------------------------------
void CStudioConstraintBones::ComputeAimConstraint( const mstudiobone_t *pBones, int nBone, CBoneAccessor &boneToWorld, const CStudioHdr *pStudioHdr, const matrix3x4_t *pmViewTransform, AimConstraintUpType_t eType ) { BONE_PROFILE_FUNC();
mstudioaimconstraint_t *pProc = ( mstudioaimconstraint_t * )pBones[nBone].pProcedure();
// Calculate the current target position and the total weight
// of the the targets contributing to the target position.
Vector vTargetPos; const float flWeight = CStudioConstraintBones::ComputeTargetPosition( vTargetPos, pProc->pTarget( 0 ), pProc->m_nTargetCount, boneToWorld ); Vector vTargetWorldPos;
matrix3x4a_t mSlaveParentToWorld; const int nParentBone = pBones[nBone].parent;
if ( pmViewTransform ) { matrix3x4_t mInv; MatrixInvert( *pmViewTransform, mInv );
VectorTransform( vTargetPos, mInv, vTargetWorldPos );
if ( nParentBone >= 0 ) { ConcatTransforms( mInv, boneToWorld[nParentBone], mSlaveParentToWorld ); } else { SetIdentityMatrix( mSlaveParentToWorld ); } } else { VectorCopy( vTargetPos, vTargetWorldPos );
if ( nParentBone >= 0 ) { MatrixCopy( boneToWorld[nParentBone], mSlaveParentToWorld ); } else { SetIdentityMatrix( mSlaveParentToWorld ); } }
Quaternion qTargetOrientation; CConstraintBones::ComputeAimConstraint( qTargetOrientation, vTargetWorldPos, mSlaveParentToWorld, pProc->m_vUp, pProc->m_slave.m_vBasePosition, pProc->m_nUpSpaceTarget >= 0 ? &boneToWorld[ pProc->m_nUpSpaceTarget ] : NULL, eType );
// Add in initial offset
Quaternion qOffsetOrientation; QuaternionMult( qTargetOrientation, pProc->m_qAimOffset, qOffsetOrientation );
// Add in parent matrix
Quaternion qParentToWorld; MatrixQuaternion( mSlaveParentToWorld, qParentToWorld ); Quaternion qTmp; QuaternionMult( qParentToWorld, qOffsetOrientation, qTmp ); qOffsetOrientation = qTmp;
// Blend between the target orientation and the base orientation using the target weight
Quaternion qFinalOrientation;
matrix3x4a_t &mBaseWorldMatrix = boneToWorld.GetBoneForWrite( nBone ); CStudioConstraintBones::ComputeBaseWorldMatrix( mBaseWorldMatrix, &( pProc->m_slave ), boneToWorld, pStudioHdr, pmViewTransform );
if ( flWeight < 1.0f ) { Quaternion qBaseOrientation; MatrixQuaternion( mBaseWorldMatrix, qBaseOrientation );
QuaternionSlerp( qBaseOrientation, qOffsetOrientation, flWeight, qFinalOrientation ); } else { qFinalOrientation = qOffsetOrientation; }
if ( pmViewTransform ) { Quaternion qTmp0; Quaternion qTmp1; MatrixQuaternion( *pmViewTransform, qTmp0 ); QuaternionMult( qTmp0, qFinalOrientation, qTmp1 ); qFinalOrientation = qTmp1; }
// Quaternion matrix wipes out the translate component
Vector vTmpPosition; MatrixPosition( mBaseWorldMatrix, vTmpPosition ); QuaternionMatrix( qFinalOrientation, vTmpPosition, mBaseWorldMatrix ); }
//-----------------------------------------------------------------------------
// studio interface
//-----------------------------------------------------------------------------
void CStudioConstraintBones::ComputeParentConstraint( const mstudiobone_t *pBones, int nBone, CBoneAccessor &boneToWorld, const CStudioHdr *pStudioHdr ) { BONE_PROFILE_FUNC();
mstudioorientconstraint_t *pProc = ( mstudioorientconstraint_t * )pBones[nBone].pProcedure();
// Calculate the current target position and the total weight
// of the the targets contributing to the target position.
Vector vTargetPosition; Quaternion qTargetOrientation; const float flWeight = CStudioConstraintBones::ComputeTargetPositionOrientation( vTargetPosition, qTargetOrientation, pProc->pTarget( 0 ), pProc->m_nTargetCount, boneToWorld );
// Blend between the target orientation and the base orientation using the target weight
Quaternion qFinalOrientation; Vector vFinalPosition;
matrix3x4a_t &mBaseWorldMatrix = boneToWorld.GetBoneForWrite( nBone ); CStudioConstraintBones::ComputeBaseWorldMatrix( mBaseWorldMatrix, &( pProc->m_slave ), boneToWorld, pStudioHdr );
if ( flWeight < 1.0f ) { Vector vBasePosition; Quaternion qBaseOrientation; MatrixAngles( mBaseWorldMatrix, qBaseOrientation, vBasePosition );
QuaternionSlerp( qBaseOrientation, qTargetOrientation, flWeight, qFinalOrientation ); VectorLerp( vBasePosition, vTargetPosition, flWeight, vFinalPosition ); } else { qFinalOrientation = qTargetOrientation; vFinalPosition = vTargetPosition; }
QuaternionMatrix( qFinalOrientation, vFinalPosition, mBaseWorldMatrix ); }
//-----------------------------------------------------------------------------
//
// Twist bones are bones which take a portion of the rotation around a specified
// axis.
//
// The axis is defined as the vector between a parent and child bone
// The twist bones must also be children of the parent
//
// + parent
// |
// +--+ twist 0.25
// |
// +--+ twist 0.5
// |
// +--+ twist 0.75
// |
// +--+ child
//
// If inverse is false each twist takes a portion of the child rotation around
// the specified axis
//
// If inverse is true each twist takes a portion of the parent rotation around
// the specified axis from a specified reference orientation
//
// All specified matrices & Quaternions are local to the bone, they are not
// worldToBone transformations
//
// pqTwists, pflWeights, pqTwistBinds are all pointers to arrays which must be
// at least nCount in size
//
// This code is called directly from:
// maya, datamodel & CalcProceduralBone/DoTwistBones
//-----------------------------------------------------------------------------
void ComputeTwistBones( Quaternion *pqTwists, int nCount, bool bInverse, const Vector &vUp, const Quaternion &qParent, const matrix3x4_t &mChild, const Quaternion &qBaseInv, const float *pflWeights, const Quaternion *pqTwistBinds ) { const float flEps = FLT_EPSILON * 10.0f; const float flEpsSq = flEps * flEps;
Vector vUpRotate; Vector vLocalTranslation; Vector vRotatedTranslation; Quaternion qTmp0; Quaternion qTmp1;
{ Quaternion qChild; MatrixAngles( mChild, qChild, vLocalTranslation );
// Check for 0 length translation - perhaps use Vector::IsZero?
if ( vLocalTranslation.LengthSqr() < flEpsSq ) { // No translation, can't compute rotation axis, do nothing
V_memcpy( pqTwists, pqTwistBinds, nCount * sizeof( Quaternion ) ); return; }
VectorNormalize( vLocalTranslation );
if ( bInverse ) { QuaternionMult( qBaseInv, qParent, qTmp0 ); VectorRotate( vUp, qTmp0, vUpRotate ); VectorRotate( vLocalTranslation, qTmp0, vRotatedTranslation ); } else { QuaternionMult( qBaseInv, qChild, qTmp0 ); VectorRotate( vUp, qTmp0, vUpRotate ); VectorRotate( vLocalTranslation, qBaseInv, vRotatedTranslation ); } }
// If the specified up axis and the rotated translation vector are parallel then quit
if ( 1.0f - FloatMakePositive( DotProduct( vRotatedTranslation, vUp ) ) < flEps ) { V_memcpy( pqTwists, pqTwistBinds, nCount * sizeof( Quaternion ) ); return; }
// If the rotated up axis and the rotated translation vector are parallel then quit
if ( 1.0f - FloatMakePositive( DotProduct( vRotatedTranslation, vUpRotate ) ) < flEps ) { V_memcpy( pqTwists, pqTwistBinds, nCount * sizeof( Quaternion ) ); return; }
// Project Up (V) & Rotated Up (V) into the plane defined by the
// rotated up vector (N)
//
// U = V - ( V dot N ) N;
//
// U is V projected into plane with normal N
Vector vTmp0; vTmp0 = vRotatedTranslation; Vector vUpProject; vTmp0 *= DotProduct( vUp, vRotatedTranslation ); VectorSubtract( vUp, vTmp0, vUpProject ); VectorNormalize( vUpProject );
vTmp0 = vRotatedTranslation; Vector vUpRotateProject; vTmp0 *= DotProduct( vUpRotate, vRotatedTranslation ); VectorSubtract( vUpRotate, vTmp0, vUpRotateProject ); VectorNormalize( vUpRotateProject );
if ( VectorsAreEqual( vUpProject, vUpRotateProject, 0.001 ) ) { V_memcpy( pqTwists, pqTwistBinds, nCount * sizeof( Quaternion ) ); } else { CrossProduct( vUpProject, vUpRotateProject, vTmp0 ); VectorNormalize( vTmp0 ); const float flDot = DotProduct( vUpProject, vUpRotateProject ); const float flAngle = DotProduct( vTmp0, vRotatedTranslation ) < 0.0f ? -acos( flDot ) : acos( flDot );
AxisAngleQuaternion( vLocalTranslation, RAD2DEG( flAngle ), qTmp0 );
if ( bInverse ) { for ( int i = 0; i < nCount; ++i ) { QuaternionScale( qTmp0, pflWeights[i] - 1.0f, qTmp1 ); QuaternionMult( qTmp1, pqTwistBinds[i], pqTwists[i] ); } } else { for ( int i = 0; i < nCount; ++i ) { QuaternionScale( qTmp0, pflWeights[i], qTmp1 ); QuaternionMult( qTmp1, pqTwistBinds[i], pqTwists[i] ); } } } }
|