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

898 lines
28 KiB
C++

//============ 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] );
}
}
}
}