//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= // // Purpose: Implementation of the constraint operator classes. The constraint // operators control the position and / or orientation of a specified slave dag // node based on the position and orientation of a set of weighted target dag // nodes. The relationship between the target dag node and the slave is // determined by the type of the constraint. The Operate() function of each // constraint class is responsible for enforcing its specific relationship. // //============================================================================= #include "movieobjects/dmerigconstraintoperators.h" #include "movieobjects/dmechannel.h" #include "movieobjects/dmeanimationset.h" #include "movieobjects/dmerig.h" #include "datamodel/dmelementfactoryhelper.h" #include "tier1/fmtstr.h" #include "bone_constraints.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //------------------------------------------------------------------------------------------------- // // CDmeConstraintTarget // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Expose the CDmeConstraintTarget class to the scene database //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeConstraintTarget, CDmeConstraintTarget ); //----------------------------------------------------------------------------- // Purpose: Perform construction tasks, initializes attributes //----------------------------------------------------------------------------- void CDmeConstraintTarget::OnConstruction() { m_Handle.Init( this, "target" ); m_flWeight.Init( this, "targetWeight" ); m_vecOffset.InitAndSet( this, "vecOffset", vec3_origin ); m_qOffset.InitAndSet( this, "oOffset", quat_identity ); } //----------------------------------------------------------------------------- // Purpose: Perform destruction tasks //----------------------------------------------------------------------------- void CDmeConstraintTarget::OnDestruction() { } //----------------------------------------------------------------------------- // Purpose: Reset the position and orientation offset values //----------------------------------------------------------------------------- void CDmeConstraintTarget::ClearOffset() { m_vecOffset = vec3_origin; m_qOffset = quat_identity; } //----------------------------------------------------------------------------- // Purpose: Find the channel driving the weight value of the constraint //----------------------------------------------------------------------------- CDmeChannel *CDmeConstraintTarget::FindWeightChannel() const { for ( DmAttributeReferenceIterator_t it = g_pDataModel->FirstAttributeReferencingElement( GetHandle() ); it != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; it = g_pDataModel->NextAttributeReferencingElement( it ) ) { CDmAttribute *pAttr = g_pDataModel->GetAttribute( it ); CDmElement *pElement = pAttr->GetOwner(); if ( pElement == NULL ) continue; CDmeChannel *pChannel = CastElement< CDmeChannel >( pElement ); if ( pChannel ) { if ( pChannel->GetToAttribute() == m_flWeight.GetAttribute() ) return pChannel; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Get the constraint that target belongs to. //----------------------------------------------------------------------------- CDmeRigBaseConstraintOperator *CDmeConstraintTarget::GetConstraint() { return FindAncestorReferencingElement< CDmeRigBaseConstraintOperator >( this ); } //----------------------------------------------------------------------------- // Get the control attached to constraint //----------------------------------------------------------------------------- CDmElement *CDmeConstraintTarget::FindWeightControl() const { CDmElement *pWeightChannel = FindWeightChannel(); CDmElement *pControl = FindReferringElement< CDmElement >( pWeightChannel, "channel" ); return pControl; } //------------------------------------------------------------------------------------------------- // // CDmeConstraintSlave // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Expose the CDmeConstraintSlave class to the scene database //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeConstraintSlave, CDmeConstraintSlave ); //----------------------------------------------------------------------------- // Purpose: Perform construction tasks, initializes attributes //----------------------------------------------------------------------------- void CDmeConstraintSlave::OnConstruction() { m_Dag.Init( this, "target" ); m_BasePosition.Init( this, TRANSFORM_POSITION ); m_BaseOrientation.Init( this, TRANSFORM_ORIENTATION ); } //----------------------------------------------------------------------------- // Purpose: Perform destruction tasks //----------------------------------------------------------------------------- void CDmeConstraintSlave::OnDestruction() { } //----------------------------------------------------------------------------- // Get the constraint to which the slave belongs. //----------------------------------------------------------------------------- CDmeRigBaseConstraintOperator *CDmeConstraintSlave::GetConstraint() const { CDmeRigBaseConstraintOperator *pConstraint = FindAncestorReferencingElement< CDmeRigBaseConstraintOperator >( this ); return pConstraint; } //----------------------------------------------------------------------------- // Purpose: Compute the base world space matrix //----------------------------------------------------------------------------- void CDmeConstraintSlave::GetBaseWorldTransform( matrix3x4_t &worldMatrix ) const { Vector lsBasePosition = m_BasePosition; Quaternion lsBaseOrientation = m_BaseOrientation; CDmeDag *pDag = m_Dag; if ( pDag ) { matrix3x4_t parentToWorld, localMatrix; pDag->GetParentWorldMatrix( parentToWorld ); QuaternionMatrix( lsBaseOrientation, lsBasePosition, localMatrix ); ConcatTransforms( parentToWorld, localMatrix, worldMatrix ); } else { SetIdentityMatrix( worldMatrix ); } } //----------------------------------------------------------------------------- // Purpose: Compute the base position and orientation in world space //----------------------------------------------------------------------------- void CDmeConstraintSlave::ComputeBaseWorldValues( Vector &wsPosition, Quaternion &wsOrientation ) const { matrix3x4_t worldMatrix; GetBaseWorldTransform( worldMatrix ); MatrixAngles( worldMatrix, wsOrientation, wsPosition ); } //------------------------------------------------------------------------------------------------- // // CDmeRigBaseConstraintOperator // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Expose this class to the scene database //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigBaseConstraintOperator, CDmeRigBaseConstraintOperator ); //----------------------------------------------------------------------------- // Purpose: Constructor, initializes attributes, create the embedded target //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::OnConstruction() { m_Targets.Init( this, "targets" ); m_Slave.InitAndCreate( this, "slave" ); m_mode.InitAndSet( this, "mode", (int)RM_FORWARD ); } //----------------------------------------------------------------------------- // Purpose: Perform destruction tasks, destroy the internal elements of the // constraint. //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::OnDestruction() { g_pDataModel->DestroyElement( m_Slave.GetHandle() ); for ( int i = 0 ;i < m_Targets.Count(); ++i ) { if ( m_Targets[ i ] ) { g_pDataModel->DestroyElement( m_Targets[ i ]->GetHandle() ); } } m_Targets.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Determine if the the constraint has slave with the specified name //----------------------------------------------------------------------------- bool CDmeRigBaseConstraintOperator::IsSlaveObject( char const *pchName ) const { if ( !m_Slave->m_Dag ) return false; return !Q_stricmp( m_Slave->m_Dag->GetName(), pchName ); } //----------------------------------------------------------------------------- // Purpose: Get the attributes that the constraint reads data from, Inputs are // CDmeDags (handles usually) //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) { for ( int i = 0; i < m_Targets.Count(); ++i ) { if ( !m_Targets[ i ]) continue; CDmeDag* pDagNode = m_Targets[ i ]->m_Handle; if ( pDagNode == NULL ) continue; AddAttribute( attrs, GetInputAttributeType(), pDagNode->GetTransform() ); AddAncestorAttributes( attrs, pDagNode ); CDmAttribute *pWeightAttr = m_Targets[ i ]->GetWeightAttribute(); if ( pWeightAttr ) { attrs.AddToTail( pWeightAttr ); } } // If the slave dag node is the child of another node the operation of the constraint // is dependent upon the evaluation of the parent because result of the constraint // operation must be converted into the space of the parent of the slave. AddAncestorAttributes( attrs, m_Slave->m_Dag ); CDmAttribute *pSlavePosAttr = m_Slave->m_BasePosition.GetAttribute(); if ( pSlavePosAttr ) { attrs.AddToTail( pSlavePosAttr ); } CDmAttribute *pSlaveRotAttr = m_Slave->m_BaseOrientation.GetAttribute(); if ( pSlaveRotAttr ) { attrs.AddToTail( pSlaveRotAttr ); } } //----------------------------------------------------------------------------- // Purpose: Get the attributes to which the attribute writes, Outputs are // CDmeDags (bones or other CDmeRigHandles usually) //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) { if ( !m_Slave->m_Dag ) return; AddAttribute( attrs, GetOutputAttributeType(), m_Slave->m_Dag->GetTransform() ); } //----------------------------------------------------------------------------- // Purpose: Add the specified type of attributes from the provided transform //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::AddAttribute( CUtlVector< CDmAttribute * > &attrs, enum EAddType type, CDmeTransform *pTxForm ) { if ( !pTxForm ) return; // Get the transform attribute if ( type & AA_TYPE_POSITION ) { CDmAttribute *pAttrib = pTxForm->GetPositionAttribute(); if ( pAttrib ) attrs.AddToTail( pAttrib ); } if ( type & AA_TYPE_ORIENTATION ) { CDmAttribute *pAttrib = pTxForm->GetOrientationAttribute(); if ( pAttrib ) attrs.AddToTail( pAttrib ); } } //----------------------------------------------------------------------------- // Purpose: Add the position and orientation attributes the entire ancestry of // the dag node. //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::AddAncestorAttributes( CUtlVector< CDmAttribute * > &attrs, CDmeDag *pDagNode ) { if ( pDagNode == NULL ) return; CDmeDag* pParent = pDagNode->GetParent(); while ( pParent ) { AddAttribute( attrs, (EAddType)( AA_TYPE_POSITION | AA_TYPE_ORIENTATION ), pParent->GetTransform() ); pParent = pParent->GetParent(); } } //----------------------------------------------------------------------------- // Purpose: Add target handles to the the constraint with the specified // weights. Returns the index of the first target added. //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::AddHandles( int nCount, CDmeDag *const pTargetDags[], const float *pflWeights, bool bPreserveOffset, CUtlVector< CDmeConstraintTarget* > *pTargetList ) { // If a target list is provided, allocate the space to hold all // of the constraint targets for the list of input dag nodes. if ( pTargetList ) { pTargetList->EnsureCapacity( pTargetList->Count() + nCount ); } for ( int i = 0; i < nCount; ++i ) { Assert( pTargetDags[ i ] ); if ( !pTargetDags[ i ] ) continue; // Check to see if the constraint already has a target with the specified dag node CDmeConstraintTarget *pTarget = FindConstraintTargetForDag( pTargetDags[ i ] ); if ( pTarget == NULL ) { pTarget = CreateElement< CDmeConstraintTarget >( CFmtStr( "%s", pTargetDags[ i ]->GetName() ), GetFileId() ); m_Targets.AddToTail( pTarget ); } if ( pTarget != NULL ) { pTarget->m_Handle = pTargetDags[ i ]; pTarget->m_flWeight = ( pflWeights != NULL ) ? pflWeights[ i ] : 1.0f; Vector vOffset = vec3_origin; Quaternion qOffset = quat_identity; ComputeOffset( vOffset, qOffset, pTarget, bPreserveOffset ); pTarget->m_vecOffset = vOffset; pTarget->m_qOffset = qOffset; if ( pTargetList ) { pTargetList->AddToTail( pTarget ); } } } PostHandlesAdded( bPreserveOffset ); } //----------------------------------------------------------------------------- // Purpose: Remove all target handles from the constraint //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::ClearHandles() { m_Targets.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Set the dag node which the constraint is controlling //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::SetSlave( CDmeDag *pSlave ) { m_Slave->m_Dag = pSlave; CDmeTransform *pTransform = pSlave->GetTransform(); if ( pTransform ) { m_Slave->SetBasePosition( pTransform->GetPosition() ); m_Slave->SetBaseOrientation( pTransform->GetOrientation() ); } } //----------------------------------------------------------------------------- // Purpose: Get a pointer to the dag node the constraint is controlling //----------------------------------------------------------------------------- const CDmeDag *CDmeRigBaseConstraintOperator::GetSlave() const { return m_Slave->m_Dag; } //----------------------------------------------------------------------------- // Purpose: Set the operating mode of the constraint //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::SetMode( RigOperatorMode_t mode ) { m_mode = mode; } //----------------------------------------------------------------------------- // Purpose: Compute the position and orientation offset of the specified target // based on the relative transforms of the target and the slave. //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::ComputeOffset( Vector &vOffset, Quaternion &qOffset, CDmeConstraintTarget *pTarget, bool bPreserveOffset ) { vOffset = vec3_origin; qOffset = quat_identity; Assert( m_Slave->m_Dag ); if ( !bPreserveOffset || !m_Slave->m_Dag || !pTarget->m_Handle ) return; matrix3x4_t mSlave, mTarget; m_Slave->m_Dag->GetAbsTransform( mSlave ); pTarget->m_Handle->GetAbsTransform( mTarget ); Vector slavePosition, targetPosition; Quaternion slaveOrientation, targetOrientation; MatrixAngles( mSlave, slaveOrientation, slavePosition ); MatrixAngles( mTarget, targetOrientation, targetPosition ); vOffset = slavePosition - targetPosition; Quaternion invTargetOrientation; QuaternionInvert( targetOrientation, invTargetOrientation ); QuaternionMult( invTargetOrientation, slaveOrientation, qOffset ); } //----------------------------------------------------------------------------- // Compute the aggregate target position from the weighted target list and // return the total weight //----------------------------------------------------------------------------- float CDmeRigBaseConstraintOperator::ComputeTargetPosition( Vector &wsTargetPosition ) { wsTargetPosition = vec3_origin; float flWeightSum = 0.0f; int nTargets = m_Targets.Count(); for ( int i = 0; i < nTargets; ++i ) { CDmeConstraintTarget *pTarget = m_Targets[ i ]; Assert( pTarget ); CDmeDag *pTargetDag = pTarget->m_Handle; if ( pTargetDag ) { Vector targetPos; pTargetDag->GetAbsPosition( targetPos ); wsTargetPosition += ( pTarget->m_flWeight * ( targetPos + pTarget->m_vecOffset ) ); flWeightSum += pTarget->m_flWeight; } } if ( flWeightSum > 0.0f ) { wsTargetPosition *= 1.0f / flWeightSum; } return MIN( 1.0f, flWeightSum ); } static const int MAX_RIG_TARGETS = 4; //----------------------------------------------------------------------------- // Compute the aggregate target orientation from the weighted target list and // return the total weight //----------------------------------------------------------------------------- float CDmeRigBaseConstraintOperator::ComputeTargetOrientation( Quaternion &wsTargetOrientation ) { int nTargets = m_Targets.Count(); // If there is only one target, for efficiency don't bother with the weighting if ( nTargets == 1 ) { CDmeConstraintTarget *pTarget = m_Targets[ 0 ]; CDmeDag *pTargetDag = pTarget->m_Handle; if ( !pTargetDag ) return 0; Quaternion targetOrientation; Quaternion offsetOrientation; pTargetDag->GetAbsOrientation( targetOrientation ); QuaternionMult( targetOrientation, pTarget->m_qOffset, wsTargetOrientation ); return MIN( 1.0f, pTarget->m_flWeight.Get() ); } int nAllocCount = MAX( nTargets, 1 ); Quaternion *pQuats = ( Quaternion* )stackalloc( nAllocCount * sizeof( Quaternion ) ); float *flQuatWeights = ( float* )stackalloc( nAllocCount * sizeof( float ) ); wsTargetOrientation = quat_identity; float flWeightSum = 0.0f; int nQuatCount = 0; for ( int i = 0; i < nTargets; ++i ) { CDmeConstraintTarget *pTarget = m_Targets[ i ]; Assert( pTarget ); CDmeDag *pTargetDag = pTarget->m_Handle; if ( !pTargetDag ) continue; Quaternion targetOrientation; Quaternion offsetOrientation; pTargetDag->GetAbsOrientation( targetOrientation ); QuaternionMult( targetOrientation, pTarget->m_qOffset, offsetOrientation ); pQuats[ nQuatCount ] = offsetOrientation; flQuatWeights[ nQuatCount ] = pTarget->m_flWeight; flWeightSum += pTarget->m_flWeight; ++nQuatCount; } QuaternionAverageExponential( wsTargetOrientation, nQuatCount, pQuats, flQuatWeights ); return MIN( 1.0f, flWeightSum ); } //----------------------------------------------------------------------------- // Purpose: Get a list of all of the operators that the operation of the // constraint depends on. //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::GatherInputOperators( CUtlVector< CDmeOperator * > &operatorList ) { // Iterate through the targets and add the operator // dependencies for the associated dag nodes. int nTargets = m_Targets.Count(); for ( int i = 0; i < nTargets; ++i ) { CDmeConstraintTarget *pTarget = m_Targets[ i ]; Assert( pTarget ); CDmeDag *pHandle = pTarget->m_Handle; if ( !pHandle ) continue; pHandle->FindRelevantOperators( operatorList ); } BaseClass::GatherInputOperators( operatorList ); } //----------------------------------------------------------------------------- // Purpose: Disconnect the channels driving the slave dag nodes from the dag // transforms and connect them to the constraint //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::DisconnectTransformChannels() { DisconnectSlaveChannels( m_Slave.GetElement(), GetOutputAttributeType() ); } //----------------------------------------------------------------------------- // Purpose: Reconnect the base channels of each slave directly to the dag //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::ReconnectTransformChannels() { ReconnectSlaveChannels( m_Slave.GetElement(), GetOutputAttributeType() ); } //----------------------------------------------------------------------------- // Purpose: Disconnect the channels driving the specified slave from the dag // and connect them to the constraint //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::DisconnectSlaveChannels( CDmeConstraintSlave *pSlave, int attributeTypeFlags ) { // Verify that the specified slave is valid and has a dag node connected. if ( pSlave == NULL ) return; if ( pSlave->m_Dag.GetElement() == NULL ) return; CDmeTransform *pTransform = pSlave->m_Dag->GetTransform(); if ( pTransform == NULL ) return; // Make the base position and orientation match the // current position and orientation of the transform. pSlave->SetBasePosition( pTransform->GetPosition() ); pSlave->SetBaseOrientation( pTransform->GetOrientation() ); // Find the channel targeting the position attribute of the transform and // re-connect it to the position attribute of the slave within the constraint. if ( attributeTypeFlags & AA_TYPE_POSITION ) { CDmeChannel *pPosChannel = FindChannelTargetingElement( pTransform, TRANSFORM_POSITION ); if ( pPosChannel ) { pPosChannel->SetOutput( pSlave, TRANSFORM_POSITION ); } } // Find the channel targeting the orientation attribute of the transform and // re-connect it to the orientation attribute of the slave within the constraint. if ( attributeTypeFlags & AA_TYPE_ORIENTATION ) { CDmeChannel *pRotChannel = FindChannelTargetingElement( pTransform, TRANSFORM_ORIENTATION ); if ( pRotChannel ) { pRotChannel->SetOutput( pSlave, TRANSFORM_ORIENTATION ); } } } //----------------------------------------------------------------------------- // Purpose: Reconnect the transform channels associated with the specified // slave directly to the dag //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::ReconnectSlaveChannels( CDmeConstraintSlave *pSlave, int attributeTypeFlags ) { // Verify that the specified slave is valid and has a dag node connected. if ( pSlave == NULL ) return; if ( pSlave->m_Dag.GetElement() == NULL ) return; CDmeTransform *pTransform = pSlave->m_Dag->GetTransform(); if ( pTransform == NULL ) return; // Find the channel targeting the position attribute of the slave and // re-connect it to the position attribute of the transform. if ( attributeTypeFlags & AA_TYPE_POSITION ) { CDmeChannel *pPosChannel = FindChannelTargetingElement( pSlave, TRANSFORM_POSITION ); if ( pPosChannel ) { pPosChannel->SetOutput( pTransform, TRANSFORM_POSITION ); // If the channel is in pass through mode, update the control value with the // transform value so that the result of the constraint will be maintained. if ( pPosChannel->GetMode() == CM_PASS ) { CDmElement *pControl = pPosChannel->GetFromElement(); CDmeTransformControl *pTranformControl = CastElement< CDmeTransformControl >( pControl ); if ( pTranformControl ) { pTranformControl->SetPosition( pTransform->GetPosition() ); } } } } // Find the channel targeting the orientation attribute of the slave and // re-connect it to the orientation attribute of the transform. if ( attributeTypeFlags & AA_TYPE_ORIENTATION ) { CDmeChannel *pRotChannel = FindChannelTargetingElement( pSlave, TRANSFORM_ORIENTATION ); if ( pRotChannel ) { pRotChannel->SetOutput( pTransform, TRANSFORM_ORIENTATION ); // If the channel is in pass through mode, update the control value with the // transform value so that the result of the constraint will be maintained. if ( pRotChannel->GetMode() == CM_PASS ) { CDmElement *pControl = pRotChannel->GetFromElement(); CDmeTransformControl *pTranformControl = CastElement< CDmeTransformControl >( pControl ); if ( pTranformControl ) { pTranformControl->SetOrientation( pTransform->GetOrientation() ); } } } } } //----------------------------------------------------------------------------- // Purpose: Find the constraint target for the specified dag node //----------------------------------------------------------------------------- CDmeConstraintTarget *CDmeRigBaseConstraintOperator::FindConstraintTargetForDag( CDmeDag* pDagNode ) const { if ( pDagNode == NULL ) return NULL; int nTargets = m_Targets.Count(); for ( int iTarget = 0; iTarget < nTargets; ++iTarget ) { CDmeConstraintTarget *pTarget = m_Targets[ iTarget ]; if ( pTarget ) { if ( pTarget->GetDag() == pDagNode ) { return pTarget; } } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Find all of the constraints that target the specified dag node //----------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::FindDagConstraints( const CDmeDag *pDagNode, CUtlVector< CDmeRigBaseConstraintOperator* > &constraintList ) { if ( pDagNode == NULL ) return; // Find the slave instances targeting the specified node CUtlVector< CDmeConstraintSlave* > slaveList; FindAncestorsReferencingElement( pDagNode, slaveList ); // Find the constraints for each slave instance for ( int i = 0; i < slaveList.Count(); ++i ) { FindAncestorsReferencingElement( slaveList[ i ], constraintList ); } } //----------------------------------------------------------------------------- // Purpose: Find the constraint on the dag of the specified type //----------------------------------------------------------------------------- CDmeRigBaseConstraintOperator *CDmeRigBaseConstraintOperator::FindDagConstraint( CDmeDag *pDag, EConstraintType constraintType ) { CUtlVector< CDmeRigBaseConstraintOperator* > constraintList; FindDagConstraints( pDag, constraintList ); int nConstraints = constraintList.Count(); for ( int iConstraint = 0; iConstraint < nConstraints; ++iConstraint ) { CDmeRigBaseConstraintOperator *pConstraint = constraintList[ iConstraint ]; if ( pConstraint ) { if ( pConstraint->GetConstraintType() == constraintType ) { return pConstraint; } } } return NULL; } //------------------------------------------------------------------------------------------------- // Purpose: Remove all of the constraints from the specified dag node. //------------------------------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::RemoveConstraintsFromDag( CDmeDag *pDag ) { if ( pDag == NULL ) return; // Find the constraints associated with the specified dag node. CUtlVector< CDmeConstraintSlave* > constraintSlaves; FindAncestorsReferencingElement( pDag, constraintSlaves ); int nSlaves = constraintSlaves.Count(); for ( int i = 0; i < nSlaves; ++i ) { CDmeConstraintSlave *pSlave = constraintSlaves[ i ]; if ( pSlave == NULL ) continue; CDmeRigBaseConstraintOperator *pConstraint = pSlave->GetConstraint(); if ( pConstraint ) { DestroyConstraint( pConstraint ); } } } //------------------------------------------------------------------------------------------------- // Purpose: Destroy the specified constraint and remove it from the animation set. //------------------------------------------------------------------------------------------------- void CDmeRigBaseConstraintOperator::DestroyConstraint( CDmeRigBaseConstraintOperator *pConstraint ) { if ( pConstraint == NULL ) return; CDmeAnimationSet *pAnimSet = FindAncestorReferencingElement< CDmeAnimationSet >( pConstraint ); // Find the weight channels associated with the constraint targets. const CDmaElementArray< CDmeConstraintTarget > &targetList = pConstraint->GetTargets(); int nTargets = targetList.Count(); for ( int iTarget = 0; iTarget < nTargets; ++iTarget ) { CDmeConstraintTarget *pTarget = targetList[ iTarget ]; if ( pTarget == NULL ) continue; CDmeChannel *pWeightChannel = pTarget->FindWeightChannel(); if ( pWeightChannel == NULL ) continue; // Destroy the control associated with the channel CDmElement* pControl = pWeightChannel->GetFromElement(); if ( pControl ) { if ( pAnimSet ) { pAnimSet->RemoveControl( pControl ); } CDmeRig::RemoveElementFromRig( pControl ); DestroyElement( pControl ); } // Remove the channel from the channels clip it belongs to CDmeChannelsClip *pChannelsClip = FindAncestorReferencingElement< CDmeChannelsClip >( pWeightChannel ); if ( pChannelsClip ) { pChannelsClip->RemoveChannel( pWeightChannel ); } // Remove the channel from the rig. CDmeRig::RemoveElementFromRig( pWeightChannel ); // Destroy the channel DestroyElement( pWeightChannel ); } // Reconnect the original channels of the constrained dag back to the transform. pConstraint->ReconnectTransformChannels(); // Remove the constraint from the animation set's list of operators or // from the shot if the constraint did not belong to an animation set. if ( pAnimSet ) { pAnimSet->RemoveOperator( pConstraint ); } CDmeFilmClip *pClip = FindAncestorReferencingElement< CDmeFilmClip >( pConstraint ); if ( pClip ) { pClip->RemoveOperator( pConstraint ); } // Remove the constraint from the rig it belongs to, if any CDmeRig::RemoveElementFromRig( pConstraint ); // Destroy the constraint element DestroyElement( pConstraint ); } //------------------------------------------------------------------------------------------------- // Purpose: Get the string name associated with the specified constraint type. //------------------------------------------------------------------------------------------------- char const *CDmeRigBaseConstraintOperator::ConstraintTypeName( EConstraintType eType ) { switch ( eType ) { case CT_POINT: return( "pointConstraint" ); case CT_ORIENT: return( "orientConstraint" ); case CT_AIM: return( "aimConstraint" ); case CT_IK: return( "ikConstraint" ); case CT_PARENT: return( "parentConstraint" ); case CT_ROTATION: return( "rotationConstraint" ); default: break; } Assert( 0 ); return "unknown"; } //------------------------------------------------------------------------------------------------- // // CDmeRigPointConstraintOperator // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Point Constraint //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigPointConstraintOperator, CDmeRigPointConstraintOperator ); void CDmeRigPointConstraintOperator::OnConstruction() { } void CDmeRigPointConstraintOperator::OnDestruction() { } void CDmeRigPointConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigPointConstraintOperator::Operate", "SFM" ); CDmeDag *pSlaveDag = m_Slave->GetDag(); if ( !pSlaveDag ) return; // Calculate the current target position and the total weight // of the the targets contributing to the target position. Vector targetPos; float weight = ComputeTargetPosition( targetPos ); // Blend between the target position and the base position using the target weight Vector finalPos = targetPos; if ( weight < 1.0f ) { Vector basePosition; Quaternion baseOrientation; m_Slave->ComputeBaseWorldValues( basePosition, baseOrientation ); finalPos = ( targetPos * weight ) + ( basePosition * ( 1.0f - weight ) ); } // Update the transform of the dag with the new position. pSlaveDag->SetAbsPosition( finalPos ); } //------------------------------------------------------------------------------------------------- // // CDmeRigOrientConstraintOperator // //------------------------------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigOrientConstraintOperator, CDmeRigOrientConstraintOperator ); void CDmeRigOrientConstraintOperator::OnConstruction() { } void CDmeRigOrientConstraintOperator::OnDestruction() { } void CDmeRigOrientConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigOrientConstraintOperator::Operate", "SFM" ); CDmeDag *pSlaveDag = m_Slave->GetDag(); if ( !pSlaveDag ) return; // Compute the target orientation and weight Quaternion qTarget; float weight = ComputeTargetOrientation( qTarget ); // Blend between the target orientation and the base orientation using the target weight Quaternion qFinalWorld = qTarget; if ( weight < 1.0f ) { Vector basePosition; Quaternion baseOrientation; m_Slave->ComputeBaseWorldValues( basePosition, baseOrientation ); QuaternionSlerp( baseOrientation, qTarget, weight, qFinalWorld ); } matrix3x4_t parentToWorld; pSlaveDag->GetParentWorldMatrix( parentToWorld ); Quaternion qParentToWorld; MatrixQuaternion( parentToWorld, qParentToWorld ); Quaternion qParentToWorldInv; QuaternionInvert( qParentToWorld, qParentToWorldInv ); Quaternion qFinalLocal; QuaternionMult( qParentToWorldInv, qFinalWorld, qFinalLocal ); pSlaveDag->GetTransform()->SetOrientation( qFinalLocal ); /* CDmeDag *pSlaveDag = m_Slave->GetDag(); if ( !pSlaveDag ) return; // Compute the target orientation and weight Quaternion qTarget; float weight = ComputeTargetOrientation( qTarget ); // Blend between the target orientation and the base orientation using the target weight Vector basePosition; Quaternion baseOrientation; m_Slave->ComputeBaseWorldValues( basePosition, baseOrientation ); Quaternion qFinal; QuaternionSlerp( baseOrientation, qTarget, weight, qFinal ); // Update the orientation of the slave, but maintain the position. matrix3x4_t absTxForm; pSlaveDag->GetAbsTransform( absTxForm ); Vector worldPos; MatrixGetColumn( absTxForm, 3, worldPos ); AngleMatrix( qFinal, worldPos, absTxForm ); pSlaveDag->SetAbsTransform( absTxForm ); */ } //------------------------------------------------------------------------------------------------- // // CDmeRigAimConstraintOperator // //------------------------------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigAimConstraintOperator, CDmeRigAimConstraintOperator ); void CDmeRigAimConstraintOperator::OnConstruction() { m_AimOffset.InitAndSet( this, "aimOffset", quat_identity ); m_UpVector.InitAndSet( this, "upVector", Vector( 0, 0, 1 ) ); m_UpSpaceTarget.Init( this, "upSpaceTarget" ); m_UpType.InitAndSet( this, "upType", CConstraintBones::AC_UP_TYPE_OBJECT_ROTATION ); } void CDmeRigAimConstraintOperator::OnDestruction() { } //----------------------------------------------------------------------------- // Purpose: Perform an update after targets are added to the constraint. Offset // means something slightly different for aim constraint, it's the rotation // needed to orient the aimVector back at the original slave object. So all // target offsets are simply cleared out. //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::PostHandlesAdded( bool bPreserveOffset ) { for ( int i = 0; i < m_Targets.Count(); ++i ) { m_Targets[ i ]->ClearOffset(); } } //----------------------------------------------------------------------------- // Purpose: Set the up vector to be used when calculating the orientation from // the target position. The up vector may be in world space or in the space of // a specified dag node. //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::SetUpVector( const Vector &upVector, bool bPreserveOffset, const CDmeDag *pUpSpaceTarget ) { CDmeDag *pDag = m_Slave->GetDag(); if ( pDag == NULL ) return; m_UpVector = upVector.Normalized(); m_UpSpaceTarget = pUpSpaceTarget; UpdateOffset( bPreserveOffset ); } //----------------------------------------------------------------------------- // Purpose: Set the up vector to be used when calculating the orientation from // the target position. The up vector may be in world space or in the space of // a specified dag node. //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::SetUpType( int nUpType ) { if ( nUpType < CConstraintBones::AC_UP_TYPE_FIRST || nUpType > CConstraintBones::AC_UP_TYPE_LAST ) { nUpType = CConstraintBones::AC_UP_TYPE_OBJECT_ROTATION; } m_UpType = CConstraintBones::AC_UP_TYPE_OBJECT_ROTATION; } //----------------------------------------------------------------------------- // Purpose: 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 CDmeRigAimConstraintOperator::AimAt( const Vector &vecForward, const Vector &referenceUp, Quaternion &q ) { Vector forward = vecForward; forward.NormalizeInPlace(); float ratio = DotProduct( forward, referenceUp ); Vector up = referenceUp - ( forward * ratio ); up.NormalizeInPlace(); Vector right = forward.Cross( up ); right.NormalizeInPlace(); const Vector &x = right; const Vector &y = forward; const Vector &z = up; float tr = x.x + y.y + z.z; q.Init( y.z - z.y , z.x - x.z, x.y - y.x, tr + 1.0f ); float radius = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; if ( radius > FLT_EPSILON ) { QuaternionNormalize( q ); } else { matrix3x4_t rotMat; MatrixSetColumn( x, 0, rotMat ); MatrixSetColumn( y, 1, rotMat ); MatrixSetColumn( z, 2, rotMat ); MatrixQuaternion( rotMat, q ); } } //----------------------------------------------------------------------------- // Purpose: Calculate the target orientation of the aim constraint based on the // current slave position and the target position, using the up vector as a // reference to try and maintain. //----------------------------------------------------------------------------- float CDmeRigAimConstraintOperator::CalculateOrientation( Quaternion &targetOrientation ) { CDmeDag *pDag = m_Slave->GetDag(); if ( pDag == NULL ) return 0.0f; CDmeTransform *pTransform = pDag->GetTransform(); if ( pTransform == NULL ) return 0.0f; // Compute the world space position of the target Vector wsTargetPos; float weight = ComputeTargetPosition( wsTargetPos ); // Construct the matrix to convert from world space to the parent space of the slave matrix3x4_t parentToWorld; matrix3x4_t worldToParent; pDag->GetParentWorldMatrix( parentToWorld ); MatrixInvert( parentToWorld, worldToParent ); // If the up vector is in world space, convert it into local space Vector worldUp; Vector localUp; const CDmeDag *pUpSpaceDag = m_UpSpaceTarget; switch ( m_UpType ) { case CConstraintBones::AC_UP_TYPE_VECTOR: VectorCopy( m_UpVector, worldUp ); break; case CConstraintBones::AC_UP_TYPE_OBJECT: if ( pUpSpaceDag ) { matrix3x4_t localToWorld; pUpSpaceDag->GetAbsTransform( localToWorld ); Vector vUpObjectWorldPos; MatrixPosition( localToWorld, vUpObjectWorldPos ); Vector vSlaveWorldPos; VectorTransform( pTransform->GetPosition(), parentToWorld, vSlaveWorldPos ); VectorSubtract( vUpObjectWorldPos, vSlaveWorldPos, worldUp ); VectorNormalize( worldUp ); } else { VectorCopy( m_UpVector, worldUp ); } break; case CConstraintBones::AC_UP_TYPE_PARENT_ROTATION: VectorRotate( m_UpVector, parentToWorld, worldUp ); break; default: case CConstraintBones::AC_UP_TYPE_OBJECT_ROTATION: if ( pUpSpaceDag ) { matrix3x4_t localToWorld; pUpSpaceDag->GetAbsTransform( localToWorld ); VectorRotate( m_UpVector, localToWorld, worldUp ); } else { VectorCopy( m_UpVector, worldUp ); } break; } VectorRotate( worldUp, worldToParent, localUp ); // Convert the target's world space position into the local space of the slave. Vector lsTargetPos; VectorTransform( wsTargetPos, worldToParent, lsTargetPos ); // Compute the local space forward vector Vector slavePos = pTransform->GetPosition(); Vector forward = lsTargetPos - slavePos; forward.NormalizeInPlace(); // Compute the orientation AimAt( forward, localUp, targetOrientation ); { Quaternion qAim; matrix3x4_t mUpToWorld; if ( pUpSpaceDag ) { pUpSpaceDag->GetAbsTransform( mUpToWorld ); } else { SetIdentityMatrix( mUpToWorld ); } CConstraintBones::ComputeAimConstraint( qAim, wsTargetPos, parentToWorld, m_UpVector.Get(), slavePos, &mUpToWorld, static_cast< CConstraintBones::AimConstraintUpType_t >( m_UpType.Get() ) ); Assert( QuaternionsAreEqual( qAim, targetOrientation, 1.0e-5 ) ); } return weight; } //----------------------------------------------------------------------------- // Purpose: Calculate the rotational offset which will be applied after aiming // the transform at the target location. The rotational offset essentially // allows the selection of which direction within the slave's space will be // considered the forward direction and will point towards the target. //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::UpdateOffset( bool bPreserveOffset ) { if ( !bPreserveOffset ) { m_AimOffset = quat_identity; return; } CDmeDag *pDag = m_Slave->GetDag(); if ( pDag == NULL ) return; CDmeTransform *pTransform = pDag->GetTransform(); if ( pTransform == NULL ) return; // Calculate the desired orientation based the target position Quaternion targetOrientation; CalculateOrientation( targetOrientation ); // Compute the difference between the slave's current orientation and the target orientation Quaternion slaveOrientation = pTransform->GetOrientation(); Quaternion qInv, aimOffset; QuaternionInvert( targetOrientation, qInv ); QuaternionMult( qInv, slaveOrientation, aimOffset ); m_AimOffset = aimOffset; } //----------------------------------------------------------------------------- // Purpose: Operate the aim constraint, modifying the orientation of the slave // so that it looks at the target position. //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigAimConstraintOperator::Operate", "SFM" ); CDmeDag *pDag = m_Slave->GetDag(); if ( pDag == NULL ) return; CDmeTransform *pTransform = pDag->GetTransform(); if ( pTransform == NULL ) return; // Calculate the desired orientation based the target position Quaternion targetOrientation; float targetWeight = CalculateOrientation( targetOrientation ); // Add in initial offset Quaternion offsetOrientation; QuaternionMult( targetOrientation, m_AimOffset, offsetOrientation ); // Blend between the target orientation and the base orientation using the target weight Quaternion finalOrientation; Quaternion baseOrientation = m_Slave->GetBaseOrientation(); QuaternionSlerp( baseOrientation, offsetOrientation, targetWeight, finalOrientation ); // Update the orientation of the slave pTransform->SetOrientation( finalOrientation ); } //----------------------------------------------------------------------------- // Purpose: Get the attributes that the constraint reads data from, Inputs are // CDmeDags (handles usually) //----------------------------------------------------------------------------- void CDmeRigAimConstraintOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) { CDmeRigBaseConstraintOperator::GetInputAttributes( attrs ); // The position of the slave must be evaluated be CDmeDag *pSlaveDag = m_Slave->GetDag(); if ( pSlaveDag ) { AddAttribute( attrs, AA_TYPE_POSITION, pSlaveDag->GetTransform() ); } } //------------------------------------------------------------------------------------------------- // // CDmeRigRotationConstraintOperator // //------------------------------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigRotationConstraintOperator, CDmeRigRotationConstraintOperator ); //----------------------------------------------------------------------------- // Purpose: Perform post construction operations, including initializing member // attributes. //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::OnConstruction() { m_Rotations.Init( this, "rotations" ); m_Axies.Init( this, "axies" ); } //----------------------------------------------------------------------------- // Purpose: Perform shutdown and cleanup operations. //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::OnDestruction() { } //----------------------------------------------------------------------------- // Purpose: Run the operator, this calculates the resulting quaternion // orientation and stores it in result. //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigRotationConstraintOperator::Operate", "SFM" ); CDmeDag *pDag = m_Slave->GetDag(); if ( pDag == NULL ) return; CDmeTransform *pTransform = pDag->GetTransform(); if ( pTransform == NULL ) return; Quaternion finalOrientation = quat_identity; int nAxies = MIN( m_Axies.Count(), m_Rotations.Count() ); for ( int iAxis = 0; iAxis < nAxies; ++iAxis ) { Vector axis = m_Axies[ iAxis ]; float rotation = m_Rotations[ iAxis ]; Quaternion orientation; AxisAngleQuaternion( axis, rotation, orientation ); QuaternionNormalize( orientation ); QuaternionMult( orientation, finalOrientation, finalOrientation ); QuaternionNormalize( finalOrientation ); } // Update the orientation of the slave pTransform->SetOrientation( finalOrientation ); } //----------------------------------------------------------------------------- // Purpose: Add the input attribute used by the operator to the provided list // of attributes, This is generally used by the evaluation process to find the // attributes an operator is dependent on. //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) { attrs.AddToTail( m_Axies.GetAttribute() ); attrs.AddToTail( m_Rotations.GetAttribute() ); } //----------------------------------------------------------------------------- // Purpose: Add a rotation axis //----------------------------------------------------------------------------- int CDmeRigRotationConstraintOperator::AddAxis( const Vector &axis ) { int index = m_Axies.AddToTail( axis ); m_Rotations.AddToTail( 0.0f ); Assert( m_Rotations.Count() == m_Axies.Count() ); return index; } //----------------------------------------------------------------------------- // Purpose: Set the axis around which the rotation is to occur //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::SetAxis( const Vector &axis, int index ) { if ( index < m_Axies.Count() ) { m_Axies.Set( index, axis ); } } //----------------------------------------------------------------------------- // Purpose: Set current rotation value //----------------------------------------------------------------------------- void CDmeRigRotationConstraintOperator::SetRotation( float rotation, int index ) { if ( index < m_Rotations.Count() ) { m_Rotations.Set( index, rotation ); } } //------------------------------------------------------------------------------------------------- // // Parent Constraint // //------------------------------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigParentConstraintOperator, CDmeRigParentConstraintOperator ); void CDmeRigParentConstraintOperator::OnConstruction() { } void CDmeRigParentConstraintOperator::OnDestruction() { } //------------------------------------------------------------------------------------------------- // Compute the offsets of the specified target based on the relative transforms of the target to // the slave //------------------------------------------------------------------------------------------------- void CDmeRigParentConstraintOperator::ComputeOffset( Vector &vOffset, Quaternion &qOffset, CDmeConstraintTarget *pTarget, bool bPreserveOffset ) { CDmeDag *pSlaveDag = m_Slave->GetDag(); CDmeDag *pTargetDag = pTarget->GetDag(); Assert( pSlaveDag ); if ( !bPreserveOffset || !pSlaveDag|| !pTargetDag ) { vOffset = vec3_origin; qOffset = quat_identity; return; } // Get slave abs info matrix3x4_t mS; m_Slave->GetBaseWorldTransform( mS ); matrix3x4_t mT; pTargetDag->GetAbsTransform( mT ); matrix3x4_t invT; MatrixInvert( mT, invT ); // Compute slave offset local to target transform matrix3x4_t offset; ConcatTransforms( invT, mS, offset ); MatrixAngles( offset, qOffset, vOffset ); } //------------------------------------------------------------------------------------------------- // 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. //------------------------------------------------------------------------------------------------- float CDmeRigParentConstraintOperator::ComputeTargetPositionOrientation( Vector &wsTargetPos, Quaternion &wsTargetOrientation ) { static Quaternion s_Quats[ MAX_RIG_TARGETS ]; static float s_flQuatWeights[ MAX_RIG_TARGETS ]; Quaternion *pQuats = s_Quats; float *flQuatWeights = s_flQuatWeights; int nTargets = m_Targets.Count(); if ( nTargets > MAX_RIG_TARGETS ) { pQuats = (Quaternion *)stackalloc( nTargets * sizeof( Quaternion ) ); flQuatWeights = (float *)stackalloc( nTargets * sizeof( float ) ); } float weightSum = 0.0f; wsTargetPos = vec3_origin; wsTargetOrientation = quat_identity; int nQuatCount = 0; for ( int i = 0; i < nTargets; ++i ) { CDmeConstraintTarget *pTarget = m_Targets[ i ]; Assert( pTarget ); CDmeDag *pTargetDag = pTarget->GetDag(); if ( !pTargetDag ) continue; float flWeight = pTarget->GetWeight(); matrix3x4_t handleM; pTargetDag->GetAbsTransform( handleM ); matrix3x4_t offset; Vector vOffset = pTarget->GetPositionOfffset(); Quaternion qOffset = pTarget->GetOrientationOffset(); AngleMatrix( RadianEuler( qOffset ), vOffset, offset ); matrix3x4_t absTxForm; ConcatTransforms( handleM, offset, absTxForm ); Vector pos; Quaternion q; MatrixAngles( absTxForm, q, pos ); wsTargetPos += ( flWeight * pos ); pQuats[ nQuatCount ] = q; flQuatWeights[ nQuatCount ] = flWeight; ++nQuatCount; // For normalization weightSum += flWeight; } if ( weightSum > 0.0f ) { wsTargetPos *= 1.0f / weightSum; } QuaternionAverageExponential( wsTargetOrientation, nQuatCount, pQuats, flQuatWeights ); return MIN( 1.0f, weightSum ); } void CDmeRigParentConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigParentConstraintOperator::Operate", "SFM" ); CDmeDag *pSlaveDag = m_Slave->GetDag(); if ( !pSlaveDag ) return; // Compute the target orientation and weight Vector targetPosition; Quaternion targetOrientation; float weight = ComputeTargetPositionOrientation( targetPosition, targetOrientation ); Vector finalPosition = targetPosition; Quaternion finalOrientation = targetOrientation; // Blend between the target orientation and the base orientation using the target weight if ( weight < 1.0f ) { Vector basePosition; Quaternion baseOrientation; m_Slave->ComputeBaseWorldValues( basePosition, baseOrientation ); VectorLerp( basePosition, targetPosition, weight, finalPosition ); QuaternionSlerp( baseOrientation, targetOrientation, weight, finalOrientation ); } matrix3x4_t finalTransform; AngleMatrix( RadianEuler( finalOrientation ), finalPosition, finalTransform ); pSlaveDag->SetAbsTransform( finalTransform ); } //----------------------------------------------------------------------------- // 3 joint (2 bone) IK Constraint //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigIKConstraintOperator, CDmeRigIKConstraintOperator ); //----------------------------------------------------------------------------- // Purpose: Perform initialization operations. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::OnConstruction() { m_StartOffsetRotation.InitAndSet( this, "startOffset", quat_identity ); m_MidOffsetRotation.InitAndSet( this, "midOffset", quat_identity ); m_PoleVector.InitAndSet( this, "poleVector", Vector( 0, 0, 1 ) ); m_PoleVectorTarget.Init( this, "pvTarget" ); m_StartJoint.InitAndCreate( this, "startJoint" ); m_MidJoint.InitAndCreate( this, "midJoint" ); m_EndJoint.InitAndCreate( this, "endJoint" ); } //----------------------------------------------------------------------------- // Purpose: Perform shutdown operations //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::OnDestruction() { g_pDataModel->DestroyElement( m_StartJoint ); g_pDataModel->DestroyElement( m_MidJoint ); g_pDataModel->DestroyElement( m_EndJoint ); } //----------------------------------------------------------------------------- // Purpose: Validate that the start and end effector are set up and that there // is only one intermediate bone between them forming a 2 bone ik chain. If // specified calculate the offset of the current position of the bones from the // target position calculated by the ik solution and store as an offset to be // applied upon operation. //----------------------------------------------------------------------------- bool CDmeRigIKConstraintOperator::Setup( bool bPreserveOffset ) { CDmeDag *pStartJoint = m_StartJoint->GetDag(); CDmeDag *pMidJoint = m_MidJoint->GetDag(); CDmeDag *pEndJoint = m_EndJoint->GetDag(); CDmeTransform *pStartTransform = pStartJoint->GetTransform(); CDmeTransform *pMidTransform = pMidJoint->GetTransform(); // Make sure all the joints have been specified. if ( !pStartJoint || !pMidJoint || !pEndJoint ) return false; // Verify the chain if ( ( pEndJoint->GetParent() != pMidJoint ) || ( pMidJoint->GetParent() != pStartJoint ) ) return false; // Make sure there is one and only one target and that it is valid if ( m_Targets.Count() != 1 ) return false; CDmeDag *pTargetDag = m_Targets[ 0 ]->GetDag(); if ( pTargetDag == NULL ) return false; // Calculate the orientation that the start and end joints would be set to if the ik were run, // the calculate the difference between the result and the current orientation. if ( bPreserveOffset ) { Quaternion qStartIK = quat_identity; Quaternion qMidIK = quat_identity; CalculateOrientations( qStartIK, qMidIK, quat_identity, quat_identity ); // Calculate the offset of the start joint Quaternion qStartCurrent = pStartTransform->GetOrientation(); Quaternion qStartInv, qStartOffset; QuaternionInvert( qStartIK, qStartInv ); QuaternionMult( qStartInv, qStartCurrent, qStartOffset ); m_StartOffsetRotation = qStartOffset; // Calculate the offset of the mid joint, note the ik solve has to be re-run // with the offset of the start joint to get the proper offset for the mid joint. CalculateOrientations( qStartIK, qMidIK, m_StartOffsetRotation, quat_identity ); Quaternion qMidCurrent = pMidTransform->GetOrientation(); Quaternion qMidInv, qMidOffset; QuaternionInvert( qMidIK, qMidInv ); QuaternionMult( qMidInv, qMidCurrent, qMidOffset ); m_MidOffsetRotation = qMidOffset; } return true; } //----------------------------------------------------------------------------- // Purpose: Calculate the orientation needed to make a transform where the x // vector of the transform matches the forward vector and the z vector matches // the up reference vector as closely as possible. The y vector will be in the // plane defined by using the forward vector as the normal. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::AimAt( const Vector &vecForward, const Vector &referenceUp, Quaternion &q ) { Vector forward = vecForward; forward.NormalizeInPlace(); float ratio = DotProduct( forward, referenceUp ); Vector up = referenceUp - ( forward * ratio ); up.NormalizeInPlace(); Vector left = up.Cross( forward ); left.NormalizeInPlace(); const Vector &x = forward; const Vector &y = left; const Vector &z = up; float tr = x.x + y.y + z.z; q.Init( y.z - z.y, z.x - x.z, x.y - y.x, tr + 1.0f ); float radius = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; if ( radius > FLT_EPSILON ) { QuaternionNormalize( q ); } else { matrix3x4_t rotMat; MatrixSetColumn( x, 0, rotMat ); MatrixSetColumn( y, 1, rotMat ); MatrixSetColumn( z, 2, rotMat ); MatrixQuaternion( rotMat, q ); } } //----------------------------------------------------------------------------- // Purpose: Perform the 2 bone ik solve to get target orientation for the // start and mid bones and apply the specified offset rotations to get the // final target orientation for each bone. //----------------------------------------------------------------------------- float CDmeRigIKConstraintOperator::CalculateOrientations( Quaternion &startBoneOrientation, Quaternion &midBoneOrientation, const Quaternion &startOffset, const Quaternion &midOffset ) { // Get the pointers to the dag nodes and transforms that participate in the calculations. CDmeDag *pStartJoint = m_StartJoint->GetDag(); CDmeDag *pMidJoint = m_MidJoint->GetDag(); CDmeDag *pEndJoint = m_EndJoint->GetDag(); CDmeTransform *pStartTransform = pStartJoint->GetTransform(); CDmeTransform *pMidTransform = pMidJoint->GetTransform(); CDmeTransform *pEndTransform = pEndJoint->GetTransform(); // Get the transform matrix of the parent of the start joint, this transform defines the // local space of the start joint, which is the space in which the calculations will be done. matrix3x4_t mLocalToWorld; matrix3x4_t mWorldToLocal; pStartJoint->GetParentWorldMatrix( mLocalToWorld ); MatrixInvert( mLocalToWorld, mWorldToLocal ); // Update the target position and get the result in world space Vector wsTargetPos; float weight = ComputeTargetPosition( wsTargetPos ); // Convert the world space target position into the local space Vector lsTargetPos; VectorTransform( wsTargetPos, mWorldToLocal, lsTargetPos ); // Find the axis, which is the vector from the start joint to the end joint and then calculate // the distance of the axis and each of the bones. Additionally calculate vDir, the unit vector // along the direction of the axis. Vector startPos = pStartTransform->GetPosition(); Vector vAxis = lsTargetPos - startPos; Vector vDir = vAxis; float axisLen = VectorNormalize( vDir ); float boneLenA = pMidTransform->GetPosition().Length(); float boneLenB = pEndTransform->GetPosition().Length(); axisLen = MIN( axisLen, boneLenA + boneLenB ); lsTargetPos = startPos + ( vDir * axisLen ); // Find the up vector, this is the vector in plane perpendicular to the axis along which the // mid point will move as a function of the length of the axis. Vector vPole; CDmeDag *pPoleVectorTarget = m_PoleVectorTarget; if ( pPoleVectorTarget ) { Vector wsPVTargetPos; pPoleVectorTarget->GetAbsPosition( wsPVTargetPos ); Vector lsPVTargetPos; VectorTransform( wsPVTargetPos, mWorldToLocal, lsPVTargetPos ); vPole = lsPVTargetPos - startPos; } else { VectorRotate( m_PoleVector, mWorldToLocal, vPole ); } Vector vUp = vPole - ( vDir * DotProduct( vDir, vPole ) ); VectorNormalize( vUp ); // Calculate the distance from the start point to the axis mid point. The axis mid point is the // location on the axis that is the projection of the final mid point position onto the axis. float midAxisLen = ( ( boneLenA * boneLenA ) - ( boneLenB * boneLenB ) + ( axisLen * axisLen ) ) / ( 2.0f * axisLen ); // Calculate the distance of the mid point from the axis float midDist = sqrt( MAX( 0, ( boneLenA * boneLenA ) - ( midAxisLen * midAxisLen ) ) ); // Calculate the vector to the new mid point position, and then calculate the new mid point position. Vector vMid = ( vDir * midAxisLen ) + ( vUp * midDist ); Vector newMidPos = startPos + vMid; // Calculate the orientation of the first bone Quaternion qStartTarget; AimAt( vMid, vUp, qStartTarget ); QuaternionMult( qStartTarget, startOffset, startBoneOrientation ); // Calculate the orientation of the second bone Vector vEnd = lsTargetPos - newMidPos; Quaternion qParentBoneOrientB; Quaternion qInvBoneOrientA; Quaternion qMidTarget; AimAt( vEnd, vUp, qParentBoneOrientB ); QuaternionInvert( startBoneOrientation, qInvBoneOrientA ); QuaternionMult( qInvBoneOrientA, qParentBoneOrientB, qMidTarget ); QuaternionMult( qMidTarget, midOffset, midBoneOrientation ); return weight; } //----------------------------------------------------------------------------- // Purpose: Run the ik solve and apply the results to the constrained bones. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigIKConstraintOperator::Operate", "SFM" ); CDmeDag *pStartJoint = m_StartJoint->GetDag(); CDmeDag *pMidJoint = m_MidJoint->GetDag(); CDmeDag *pEndJoint = m_EndJoint->GetDag(); // Make sure all the joints have been specified. if ( !pStartJoint || !pMidJoint || !pEndJoint ) return; // Calculate the new orientation of the start and mid joints Quaternion qStartTarget = quat_identity; Quaternion qMidTarget = quat_identity; float targetWeight = CalculateOrientations( qStartTarget, qMidTarget, m_StartOffsetRotation, m_MidOffsetRotation ); // Update the orientations of the start and mid point joints CDmeTransform *pStartTransform = m_StartJoint->GetDag()->GetTransform(); CDmeTransform *pMidTransform = m_MidJoint->GetDag()->GetTransform(); Quaternion qStartBase = m_StartJoint->GetBaseOrientation(); Quaternion qMidBase = m_MidJoint->GetBaseOrientation(); Quaternion qStartFinal, qMidFinal; QuaternionSlerp( qStartBase, qStartTarget, targetWeight, qStartFinal ); QuaternionSlerp( qMidBase, qMidTarget, targetWeight, qMidFinal ); pStartTransform->SetOrientation( qStartFinal ); pMidTransform->SetOrientation( qMidFinal ); } //----------------------------------------------------------------------------- // Purpose: Assign the dag nodes that to be controlled by the ik constraint. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::SetJoints( CDmeDag *pStartJoint, CDmeDag *pMidJoint, CDmeDag *pEndJoint ) { m_StartJoint->SetDag( pStartJoint ); m_MidJoint->SetDag( pMidJoint ); m_EndJoint->SetDag( pEndJoint ); } //----------------------------------------------------------------------------- // Purpose: Set the world space pole vector for the ik constraint //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::SetPoleVector( const Vector &poleVector ) { m_PoleVector = poleVector; } //----------------------------------------------------------------------------- // Purpose: Set the pole vector target, a dag node toward which the pole vector // will point, overrides the standard pole vector. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::SetPoleVectorTarget( CDmeDag *pPoleVectorTarget ) { m_PoleVectorTarget = pPoleVectorTarget; } //----------------------------------------------------------------------------- // Purpose: Get the attributes which are written to by the constraint. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) { AddAttribute( attrs, AA_TYPE_ORIENTATION, m_StartJoint->GetTransform() ); AddAttribute( attrs, AA_TYPE_ORIENTATION, m_MidJoint->GetTransform() ); } //----------------------------------------------------------------------------- // Purpose: Get the attributes upon which the constraint is dependent. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::GetInputAttributes( CUtlVector< CDmAttribute * > &attrs ) { BaseClass::GetInputAttributes( attrs ); AddAncestorAttributes( attrs, m_StartJoint->GetDag() ); AddAttribute( attrs, AA_TYPE_POSITION, m_StartJoint->GetTransform() ); AddAttribute( attrs, AA_TYPE_POSITION, m_MidJoint->GetTransform() ); AddAttribute( attrs, AA_TYPE_POSITION, m_EndJoint->GetTransform() ); } //----------------------------------------------------------------------------- // Purpose: Find all of the channels relevant to all of the target handles //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::GatherInputOperators( CUtlVector< CDmeOperator * > &operatorList ) { if ( m_PoleVectorTarget.GetElement() ) { m_PoleVectorTarget->FindRelevantOperators( operatorList ); } BaseClass::GatherInputOperators( operatorList ); } //----------------------------------------------------------------------------- // Purpose: Compute the offset for the specified target. The ik constraint does // not use target offsets, so this function simply clears the offset for the // specified target. //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::ComputeOffset( Vector &vOffset, Quaternion &qOffset, CDmeConstraintTarget *pTarget, bool bPreserveOffset ) { // No target offset allowed vOffset = vec3_origin; qOffset = quat_identity; } //----------------------------------------------------------------------------- // Purpose: Disconnect the channels driving the slave dag nodes from the dag // transforms and connect them to the constraint //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::DisconnectTransformChannels() { DisconnectSlaveChannels( m_StartJoint.GetElement(), AA_TYPE_ORIENTATION ); DisconnectSlaveChannels( m_MidJoint.GetElement(), AA_TYPE_ORIENTATION ); } //----------------------------------------------------------------------------- // Purpose: Reconnect the base channels of each slave directly to the dag //----------------------------------------------------------------------------- void CDmeRigIKConstraintOperator::ReconnectTransformChannels() { ReconnectSlaveChannels( m_StartJoint.GetElement(), AA_TYPE_ORIENTATION ); ReconnectSlaveChannels( m_MidJoint.GetElement(), AA_TYPE_ORIENTATION ); } //----------------------------------------------------------------------------- // Purpose: Get a pointer to the dag node the constraint is controlling. For // the ik constraint this is the start joint, but both the start and the mid // joint are actually 'slaves' of the constraint. //----------------------------------------------------------------------------- const CDmeDag *CDmeRigIKConstraintOperator::GetSlave() const { return m_StartJoint->GetDag(); } //----------------------------------------------------------------------------- // Purpose: Determine if the the constraint has slave with the specified name, // for the ik constraint this is always false. //----------------------------------------------------------------------------- bool CDmeRigIKConstraintOperator::IsSlaveObject( char const *pchName ) const { // IK Handle doesn't have targets that can be added return false; } //------------------------------------------------------------------------------------------------- // // CDmeRigTwistSlave // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Expose the CDmeConstraintTarget class to the scene database //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigTwistSlave, CDmeRigTwistSlave ); //----------------------------------------------------------------------------- // Purpose: Perform initialization operations. //----------------------------------------------------------------------------- void CDmeRigTwistSlave::OnConstruction() { m_flWeight.Init( this, "weight" ); } //----------------------------------------------------------------------------- // Purpose: Perform shutdown operations //----------------------------------------------------------------------------- void CDmeRigTwistSlave::OnDestruction() { } //------------------------------------------------------------------------------------------------- // // CDmeRigTwistConstraintOperator // //------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Expose the CDmeConstraintTarget class to the scene database //----------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeRigTwistConstraintOperator, CDmeRigTwistConstraintOperator ); //----------------------------------------------------------------------------- // Purpose: Perform initialization operations. //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::OnConstruction() { m_bInverse.Init( this, "inverse" ); m_vUpAxis.InitAndSet( this, "upVector", Vector( 0.0f, 1.0f, 0.0f ) ); m_flWeights.Init( this, "weights" ); m_eSlaves.Init( this, "twistJoints" ); m_qParentBindRotation.InitAndSet( this, "parentBindRotation", quat_identity ); m_qChildBindRotation.InitAndSet( this, "childBindRotation", quat_identity ); } //----------------------------------------------------------------------------- // Purpose: Perform shutdown operations //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::OnDestruction() { ClearSlaves(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CDmeRigTwistConstraintOperator::SetTargets( CDmeDag *pDmeDagParent, CDmeDag *pDmeDagChild ) { ClearHandles(); if ( !pDmeDagParent || !pDmeDagChild ) return false; CDmeDag *dmeDags[] = { pDmeDagParent, pDmeDagChild }; float flWeights[] = { 1.0f, 1.0f }; AddHandles( 2, dmeDags, flWeights, false, NULL ); return true; } //----------------------------------------------------------------------------- // Purpose: Return the DmeDag parent target or NULL // m_Targets[0] is the parent target //----------------------------------------------------------------------------- CDmeDag *CDmeRigTwistConstraintOperator::GetParentTarget() const { return m_Targets.Count() >= 1 ? m_Targets[0]->GetDag() : NULL; } //----------------------------------------------------------------------------- // Purpose: Return the DmeDag child target or NULL // m_Targets[0] is the parent target //----------------------------------------------------------------------------- CDmeDag *CDmeRigTwistConstraintOperator::GetChildTarget() const { return m_Targets.Count() >= 2 ? m_Targets[1]->GetDag() : NULL; } //----------------------------------------------------------------------------- // Purpose: Remove all slaves //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::ClearSlaves() { for ( int i = 0; i < m_eSlaves.Count(); ++i ) { CDmeConstraintSlave *pDmeConstraintSlave = m_eSlaves[i]; if ( !pDmeConstraintSlave ) continue; DestroyElement( pDmeConstraintSlave ); } } //----------------------------------------------------------------------------- // Purpose: Add the specified DmeDag as a slave with the specified weight // Returns: The index of the slave, -1 on failure //----------------------------------------------------------------------------- int CDmeRigTwistConstraintOperator::AddSlave( CDmeDag *pDmeDagSlave, float flWeight ) { CDmeRigTwistSlave *pDmeRigTwistSlave = CreateElement< CDmeRigTwistSlave >( pDmeDagSlave->GetName(), GetFileId() ); if ( !pDmeRigTwistSlave ) return -1; // This assume the current rotation of the specified dag is the base/bind rotation matrix3x4_t mBind; pDmeDagSlave->GetLocalMatrix( mBind ); Quaternion qBind; MatrixQuaternion( mBind, qBind ); pDmeRigTwistSlave->SetDag( pDmeDagSlave ); pDmeRigTwistSlave->SetWeight( flWeight ); pDmeRigTwistSlave->SetBaseOrientation( qBind ); return m_eSlaves.AddToTail( pDmeRigTwistSlave ); } //----------------------------------------------------------------------------- // Purpose: Return the DmeDag of the slave at the specified index //----------------------------------------------------------------------------- CDmeDag *CDmeRigTwistConstraintOperator::GetSlaveDag( int i ) const { return m_eSlaves.Count() > i ? m_eSlaves[i]->GetDag() : NULL; } //----------------------------------------------------------------------------- // Purpose: Return the weight of the slave at the specified index //----------------------------------------------------------------------------- float CDmeRigTwistConstraintOperator::GetSlaveWeight( int i ) const { return m_eSlaves.Count() > i ? m_eSlaves[i]->GetWeight() : 0.0f; } //----------------------------------------------------------------------------- // Purpose: Set the bind orientation of the parent //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::SetParentBindRotation( const Quaternion &qBindRotation ) { m_qParentBindRotation = qBindRotation; } //----------------------------------------------------------------------------- // Purpose: Set the bind orientation of the child //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::SetChildBindRotation( const Quaternion &qBindRotation ) { m_qChildBindRotation = qBindRotation; } //----------------------------------------------------------------------------- // Purpose: Return the bind orientation of the slave at the specified index //----------------------------------------------------------------------------- const Quaternion &CDmeRigTwistConstraintOperator::GetSlaveBindOrientation( int i ) const { if ( m_eSlaves.Count() > i ) { return m_eSlaves[i]->GetBaseOrientation(); } else { return quat_identity; } } //----------------------------------------------------------------------------- // Purpose: Set the bind orientation of the slave at the specified index //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::SetSlaveBindOrientation( const Quaternion &qBindRotation, int i ) { if ( m_eSlaves.Count() > i ) { m_eSlaves[i]->SetBaseOrientation( qBindRotation ); } } //----------------------------------------------------------------------------- // Purpose: Compute the twist //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::Operate() { VPROF_BUDGET( "CDmeRigTwistConstraintOperator::Operate", "SFM" ); /* * TODO: Call the Twist constraint code * But it needs to be moved into bonesetup first * Right now this is just a data container */ } //----------------------------------------------------------------------------- // Purpose: Get the attributes which are written to by the constraint. //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::GetOutputAttributes( CUtlVector< CDmAttribute * > &attrs ) { for ( int i = 0; i < m_eSlaves.Count(); ++i ) { CDmeConstraintSlave *pDmeConstraintSlave = m_eSlaves[i]; if ( !pDmeConstraintSlave ) continue; AddAttribute( attrs, AA_TYPE_ORIENTATION, pDmeConstraintSlave->GetTransform() ); } } //----------------------------------------------------------------------------- // Purpose: Compute the offset for the specified target. The twist constraint // does not use target offsets, so this function simply clears the offset for the // specified target. //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::ComputeOffset( Vector &vOffset, Quaternion &qOffset, CDmeConstraintTarget *pTarget, bool bPreserveOffset ) { // No target offset allowed vOffset = vec3_origin; qOffset = quat_identity; } //----------------------------------------------------------------------------- // Purpose: Disconnect the channels driving the slave dag nodes from the dag // transforms and connect them to the constraint //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::DisconnectTransformChannels() { for ( int i = 0; i < m_eSlaves.Count(); ++i ) { CDmeConstraintSlave *pDmeConstraintSlave = m_eSlaves[i]; if ( !pDmeConstraintSlave ) continue; DisconnectSlaveChannels( pDmeConstraintSlave, AA_TYPE_ORIENTATION ); } } //----------------------------------------------------------------------------- // Purpose: Reconnect the base channels of each slave directly to the dag //----------------------------------------------------------------------------- void CDmeRigTwistConstraintOperator::ReconnectTransformChannels() { for ( int i = 0; i < m_eSlaves.Count(); ++i ) { CDmeConstraintSlave *pDmeConstraintSlave = m_eSlaves[i]; if ( !pDmeConstraintSlave ) continue; ReconnectSlaveChannels( pDmeConstraintSlave, AA_TYPE_ORIENTATION ); } } //----------------------------------------------------------------------------- // Purpose: Get a pointer to the dag node the constraint is controlling. // Return the first //----------------------------------------------------------------------------- const CDmeDag *CDmeRigTwistConstraintOperator::GetSlave() const { return m_eSlaves.Count() ? m_eSlaves[0]->GetDag() : NULL; } //----------------------------------------------------------------------------- // Purpose: Determine if the the constraint has slave with the specified name //----------------------------------------------------------------------------- bool CDmeRigTwistConstraintOperator::IsSlaveObject( char const *pchName ) const { for ( int i = 0; i < m_eSlaves.Count(); ++i ) { CDmeConstraintSlave *pDmeConstraintSlave = m_eSlaves[i]; if ( !pDmeConstraintSlave ) continue; if ( !V_stricmp( pchName, pDmeConstraintSlave->GetName() ) ) return true; } return false; }