You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1787 lines
56 KiB
1787 lines
56 KiB
//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. =======
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================
|
|
#include "movieobjects/dmedag.h"
|
|
#include "movieobjects/dmeshape.h"
|
|
#include "datamodel/dmelementfactoryhelper.h"
|
|
#include "movieobjects/dmetransform.h"
|
|
#include "movieobjects/dmeoverlay.h"
|
|
#include "movieobjects_interfaces.h"
|
|
#include "movieobjects/dmedrawsettings.h"
|
|
#include "movieobjects/dmeclip.h"
|
|
#include "movieobjects/dmelog.h"
|
|
#include "movieobjects/dmechannel.h"
|
|
#include "movieobjects/dmerigconstraintoperators.h"
|
|
#include "movieobjects/dmetransformcontrol.h"
|
|
#include "movieobjects/dmeattributereference.h"
|
|
#include "movieobjects/dmerig.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
static const char OVERRIDE_PARENT[] = "overrideParent";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Expose this class to the scene database
|
|
//-----------------------------------------------------------------------------
|
|
IMPLEMENT_ELEMENT_FACTORY( DmeDag, CDmeDag );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CUtlStack<CDmeDag::TransformInfo_t> CDmeDag::s_TransformStack;
|
|
bool CDmeDag::s_bDrawUsingEngineCoordinates = false;
|
|
bool CDmeDag::s_bDrawZUp = false;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::OnConstruction()
|
|
{
|
|
m_Transform.InitAndCreate( this, "transform" );
|
|
m_Shape.Init( this, "shape" );
|
|
m_Visible.InitAndSet( this, "visible", true, FATTRIB_HAS_CALLBACK );
|
|
m_Children.Init( this, "children" );
|
|
m_bDisableOverrideParent.InitAndSet( this, "disableOverride", false, FATTRIB_DONTSAVE | FATTRIB_HIDDEN );
|
|
}
|
|
|
|
void CDmeDag::OnDestruction()
|
|
{
|
|
g_pDataModel->DestroyElement( m_Transform.GetHandle() );
|
|
}
|
|
|
|
void CDmeDag::Resolve()
|
|
{
|
|
// Since the overrideParent attribute is added dynamically we must update
|
|
// its flags is it present when re-loaded since the flags are not stored.
|
|
CDmAttribute *pAttribute = GetAttribute( OVERRIDE_PARENT );
|
|
if ( pAttribute )
|
|
{
|
|
pAttribute->AddFlag( FATTRIB_NEVERCOPY );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Accessors
|
|
//-----------------------------------------------------------------------------
|
|
CDmeTransform *CDmeDag::GetTransform() const
|
|
{
|
|
return m_Transform.GetElement();
|
|
}
|
|
|
|
CDmeShape *CDmeDag::GetShape()
|
|
{
|
|
return m_Shape.GetElement();
|
|
}
|
|
|
|
void CDmeDag::SetShape( CDmeShape *pShape )
|
|
{
|
|
m_Shape = pShape;
|
|
}
|
|
|
|
|
|
bool CDmeDag::IsVisible() const
|
|
{
|
|
return m_Visible;
|
|
}
|
|
|
|
void CDmeDag::SetVisible( bool bVisible )
|
|
{
|
|
m_Visible = bVisible;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the visibility attribute for DmeRenderable support
|
|
//-----------------------------------------------------------------------------
|
|
CDmAttribute *CDmeDag::GetVisibilityAttribute()
|
|
{
|
|
return m_Visible.GetAttribute();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// child helpers
|
|
//-----------------------------------------------------------------------------
|
|
const CUtlVector< DmElementHandle_t > &CDmeDag::GetChildren() const
|
|
{
|
|
return m_Children.Get();
|
|
}
|
|
|
|
int CDmeDag::GetChildCount() const
|
|
{
|
|
return m_Children.Count();
|
|
}
|
|
|
|
CDmeDag *CDmeDag::GetChild( int i ) const
|
|
{
|
|
if ( i < 0 || i >= m_Children.Count() )
|
|
return NULL;
|
|
|
|
return m_Children.Get( i );
|
|
}
|
|
|
|
bool CDmeDag::AddChild( CDmeDag* pDag )
|
|
{
|
|
if ( !pDag || pDag == this )
|
|
return false;
|
|
|
|
// Don't allow a cycle to be created
|
|
if ( pDag->IsAncestorOfDag( this ) )
|
|
return false;
|
|
|
|
m_Children.AddToTail( pDag );
|
|
return true;
|
|
}
|
|
|
|
void CDmeDag::RemoveChild( int i )
|
|
{
|
|
m_Children.FastRemove( i );
|
|
}
|
|
|
|
void CDmeDag::RemoveChild( const CDmeDag *pChild, bool bRecurse )
|
|
{
|
|
int i = FindChild( pChild );
|
|
if ( i >= 0 )
|
|
{
|
|
RemoveChild( i );
|
|
}
|
|
}
|
|
|
|
void CDmeDag::RemoveAllChildren()
|
|
{
|
|
m_Children.RemoveAll();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the parent of this node to the specified parent. Removes any other
|
|
// parent nodes
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeDag::SetParent( CDmeDag *pDmeDagParent )
|
|
{
|
|
if ( !pDmeDagParent || pDmeDagParent == this )
|
|
return false;
|
|
|
|
CUtlVector< CDmeDag * > parentList;
|
|
FindAncestorsReferencingElement( this, parentList );
|
|
for ( int i = 0; i < parentList.Count(); ++i )
|
|
{
|
|
if ( !parentList[ i ] )
|
|
continue;
|
|
|
|
parentList[ i ]->RemoveChild( this );
|
|
}
|
|
|
|
return pDmeDagParent->AddChild( this );
|
|
}
|
|
|
|
|
|
int CDmeDag::FindChild( const CDmeDag *pChild ) const
|
|
{
|
|
return m_Children.Find( pChild->GetHandle() );
|
|
}
|
|
|
|
// recursive
|
|
int CDmeDag::FindChild( CDmeDag *&pParent, const CDmeDag *pChild )
|
|
{
|
|
int index = FindChild( pChild );
|
|
if ( index >= 0 )
|
|
{
|
|
pParent = this;
|
|
return index;
|
|
}
|
|
|
|
int nChildren = m_Children.Count();
|
|
for ( int ci = 0; ci < nChildren; ++ci )
|
|
{
|
|
index = m_Children[ ci ]->FindChild( pParent, pChild );
|
|
if ( index >= 0 )
|
|
return index;
|
|
}
|
|
|
|
pParent = NULL;
|
|
return -1;
|
|
}
|
|
|
|
int CDmeDag::FindChild( const char *name ) const
|
|
{
|
|
int nChildren = m_Children.Count();
|
|
for ( int ci = 0; ci < nChildren; ++ci )
|
|
{
|
|
if ( V_strcmp( m_Children[ ci ]->GetName(), name ) == 0 )
|
|
return ci;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
CDmeDag *CDmeDag::FindOrAddChild( const char *name )
|
|
{
|
|
int i = FindChild( name );
|
|
if ( i >= 0 )
|
|
return GetChild( i );
|
|
|
|
CDmeDag *pChild = CreateElement< CDmeDag >( name, GetFileId() );
|
|
AddChild( pChild );
|
|
return pChild;
|
|
}
|
|
|
|
CDmeDag *CDmeDag::FindChildByName_R( const char *name ) const
|
|
{
|
|
int i = FindChild( name );
|
|
if ( i >= 0 )
|
|
return GetChild( i );
|
|
|
|
int nChildren = m_Children.Count();
|
|
for ( int ci = 0; ci < nChildren; ++ci )
|
|
{
|
|
CDmeDag *found = m_Children[ ci ]->FindChildByName_R( name );
|
|
if ( found )
|
|
return found;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return the number of steps ( levels, connections, etc...) between
|
|
// the the node a the specified child node. If the provided node is not a
|
|
// child of this node -1 will be returned.
|
|
//-----------------------------------------------------------------------------
|
|
int CDmeDag::StepsToChild( const CDmeDag *pChild ) const
|
|
{
|
|
int nSteps = 0;
|
|
for ( const CDmeDag *pDag = pChild; pDag; pDag = pDag->GetParent(), ++nSteps )
|
|
{
|
|
if ( pDag == this )
|
|
return nSteps;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Recursively render the Dag hierarchy
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::PushDagTransform()
|
|
{
|
|
int i = s_TransformStack.Push();
|
|
TransformInfo_t &info = s_TransformStack[i];
|
|
info.m_pTransform = GetTransform();
|
|
info.m_bComputedDagToWorld = false;
|
|
}
|
|
|
|
void CDmeDag::PopDagTransform()
|
|
{
|
|
Assert( s_TransformStack.Top().m_pTransform == GetTransform() );
|
|
s_TransformStack.Pop();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Transform from DME to engine coordinates
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::DmeToEngineMatrix( matrix3x4_t& dmeToEngine, bool bZUp )
|
|
{
|
|
if ( bZUp )
|
|
{
|
|
VMatrix rotationZ;
|
|
MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 0, 1 ), 90 );
|
|
rotationZ.Set3x4( dmeToEngine );
|
|
}
|
|
else
|
|
{
|
|
VMatrix rotation, rotationZ;
|
|
MatrixBuildRotationAboutAxis( rotation, Vector( 1, 0, 0 ), 90 );
|
|
MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 1, 0 ), 90 );
|
|
ConcatTransforms( rotation.As3x4(), rotationZ.As3x4(), dmeToEngine );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Transform from engine to DME coordinates
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::EngineToDmeMatrix( matrix3x4_t& engineToDme, bool bZUp )
|
|
{
|
|
if ( bZUp )
|
|
{
|
|
VMatrix rotationZ;
|
|
MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 0, 1 ), -90 );
|
|
rotationZ.Set3x4( engineToDme );
|
|
}
|
|
else
|
|
{
|
|
VMatrix rotation, rotationZ;
|
|
MatrixBuildRotationAboutAxis( rotation, Vector( 1, 0, 0 ), -90 );
|
|
MatrixBuildRotationAboutAxis( rotationZ, Vector( 0, 1, 0 ), -90 );
|
|
ConcatTransforms( rotationZ.As3x4(), rotation.As3x4(), engineToDme );
|
|
}
|
|
}
|
|
|
|
|
|
void CDmeDag::GetShapeToWorldTransform( matrix3x4_t &mat )
|
|
{
|
|
int nCount = s_TransformStack.Count();
|
|
if ( nCount == 0 )
|
|
{
|
|
if ( !s_bDrawUsingEngineCoordinates )
|
|
{
|
|
SetIdentityMatrix( mat );
|
|
}
|
|
else
|
|
{
|
|
DmeToEngineMatrix( mat, s_bDrawZUp );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( s_TransformStack.Top().m_bComputedDagToWorld )
|
|
{
|
|
MatrixCopy( s_TransformStack.Top().m_DagToWorld, mat );
|
|
return;
|
|
}
|
|
|
|
// Compute all uncomputed dag to worls
|
|
int i;
|
|
for ( i = 0; i < nCount; ++i )
|
|
{
|
|
TransformInfo_t &info = s_TransformStack[i];
|
|
if ( !info.m_bComputedDagToWorld )
|
|
break;
|
|
}
|
|
|
|
// Set up the initial transform
|
|
if ( i == 0 )
|
|
{
|
|
if ( !s_bDrawUsingEngineCoordinates )
|
|
{
|
|
SetIdentityMatrix( mat );
|
|
}
|
|
else
|
|
{
|
|
DmeToEngineMatrix( mat, s_bDrawZUp );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MatrixCopy( s_TransformStack[i-1].m_DagToWorld, mat );
|
|
}
|
|
|
|
// Compute all transforms
|
|
for ( ; i < nCount; ++i )
|
|
{
|
|
matrix3x4_t localToParent;
|
|
TransformInfo_t &info = s_TransformStack[i];
|
|
info.m_pTransform->GetTransform( localToParent );
|
|
ConcatTransforms( mat, localToParent, info.m_DagToWorld );
|
|
info.m_bComputedDagToWorld = true;
|
|
MatrixCopy( info.m_DagToWorld, mat );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::GetLocalMatrix( matrix3x4_t &m ) const
|
|
{
|
|
CDmeTransform *pTransform = GetTransform();
|
|
if ( pTransform )
|
|
{
|
|
pTransform->GetTransform( m );
|
|
}
|
|
else
|
|
{
|
|
SetIdentityMatrix( m );
|
|
}
|
|
}
|
|
|
|
void CDmeDag::SetLocalMatrix( matrix3x4_t &mat )
|
|
{
|
|
CDmeTransform *pTransform = GetTransform();
|
|
if ( !pTransform )
|
|
return;
|
|
|
|
pTransform->SetTransform( mat );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the transform matrix which converts from the space of the parent of the
|
|
// dag node to world space. If the dag node has an override parent specified
|
|
// it will be accounted for when generating the matrix.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::GetParentWorldMatrix( matrix3x4_t &mParentToWorld ) const
|
|
{
|
|
CDmeDag *pParent = GetParent();
|
|
|
|
if ( HasOverrideParent() )
|
|
{
|
|
bool bOverridePos = false;
|
|
bool bOverrideRot = false;
|
|
const CDmeDag *pOverrideParent = GetOverrideParent( bOverridePos, bOverrideRot );
|
|
|
|
if ( bOverridePos && bOverrideRot )
|
|
{
|
|
pOverrideParent->GetAbsTransform( mParentToWorld );
|
|
}
|
|
else
|
|
{
|
|
if ( pParent )
|
|
{
|
|
pParent->GetAbsTransform( mParentToWorld );
|
|
}
|
|
else
|
|
{
|
|
SetIdentityMatrix( mParentToWorld );
|
|
}
|
|
|
|
Quaternion absOrientation;
|
|
Vector absPosition;
|
|
|
|
if ( bOverridePos )
|
|
{
|
|
// The desired result of a position only parent override is that no changes to the original parent in either
|
|
// position or rotation will have any impact on the position of the dag. Furthermore we prefer rotations of
|
|
// the original parent to pivot around the dag node's center, not the override parent's center. To accomplish
|
|
// this we essentially want to compute the rotation normally, but then apply the local translation of the dag
|
|
// to the position of the override dag in world space. In order to construct the a parent matrix that still
|
|
// allows the local transform of the dag to be concatenated normally, we essentially do the whole transform
|
|
// here when constructing the parent matrix and then apply the inverse of the local matrix, so we know when
|
|
// the local matrix is concatenated later the result will be what we setup before applying the inverse of the
|
|
// local matrix.
|
|
|
|
CDmeTransform *pLocalTransform = GetTransform();
|
|
if ( pLocalTransform )
|
|
{
|
|
Vector localPosition = pLocalTransform->GetPosition();
|
|
Quaternion localOrientation = pLocalTransform->GetOrientation();
|
|
|
|
// Compute the orientation normally as if there is no override parent
|
|
// by concatenating the local orientation with the parent orientation.
|
|
Quaternion parentOrientation;
|
|
Quaternion worldOrientation;
|
|
MatrixQuaternion( mParentToWorld, parentOrientation );
|
|
QuaternionMult( parentOrientation, localOrientation, worldOrientation );
|
|
|
|
// Compute the position by simply adding the local position to the world space
|
|
// position of the override parent, this effectively make the local translation
|
|
// of the dag a world space operation so it is never effected by rotation.
|
|
Vector overridePosition;
|
|
pOverrideParent->GetAbsPosition( overridePosition );
|
|
Vector worldPosition = overridePosition + localPosition;
|
|
|
|
// Compute the final world transform we will want for the dag
|
|
// (i.e. this is the reuslt we want GetAbsTransform() to return)
|
|
matrix3x4_t mWorldTransform;
|
|
QuaternionMatrix( worldOrientation, worldPosition, mWorldTransform );
|
|
|
|
// Now construct an apply the inverse of the local transform in order to get
|
|
// an appropriate parent matrix that will yield the desired mWorldTransfom
|
|
// when the local transform of the dag is applied to it.
|
|
matrix3x4_t mLocalTransform;
|
|
matrix3x4_t mInvLocalTransform;
|
|
QuaternionMatrix( localOrientation, localPosition, mLocalTransform );
|
|
MatrixInvert( mLocalTransform, mInvLocalTransform );
|
|
ConcatTransforms( mWorldTransform, mInvLocalTransform, mParentToWorld );
|
|
}
|
|
}
|
|
else if ( bOverrideRot )
|
|
{
|
|
// Rotation only override is much simpler than position, just construct a matrix with
|
|
// the orientation of the override parent and the position of the original parent.
|
|
pOverrideParent->GetAbsOrientation( absOrientation );
|
|
MatrixPosition( mParentToWorld, absPosition );
|
|
QuaternionMatrix( absOrientation, absPosition, mParentToWorld );
|
|
}
|
|
}
|
|
}
|
|
else if ( pParent )
|
|
{
|
|
pParent->GetAbsTransform( mParentToWorld );
|
|
}
|
|
else
|
|
{
|
|
SetIdentityMatrix( mParentToWorld );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the matrix matrix for computing the position of the dag node. This is
|
|
// the same as GetParentWorldMatrix except in the case where the dag node has
|
|
// a position only override parent. This should be used when trying to apply a
|
|
// translation to the dag and conversion from world space to the space of the
|
|
// parent of the dag is required.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::GetTranslationParentWorldMatrix( matrix3x4_t &mParentToWorld )
|
|
{
|
|
bool bOverridePos = false;
|
|
bool bOverrideRot = false;
|
|
const CDmeDag *pOverrideParent = GetOverrideParent( bOverridePos, bOverrideRot );
|
|
|
|
if ( pOverrideParent && bOverridePos && !bOverrideRot )
|
|
{
|
|
Vector overridePosition;
|
|
pOverrideParent->GetAbsPosition( overridePosition );
|
|
SetIdentityMatrix( mParentToWorld );
|
|
PositionMatrix( overridePosition, mParentToWorld );
|
|
}
|
|
else
|
|
{
|
|
GetParentWorldMatrix( mParentToWorld );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Recursively render the Dag hierarchy
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::DrawUsingEngineCoordinates( bool bEnable )
|
|
{
|
|
s_bDrawUsingEngineCoordinates = bEnable;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Specify if the model is a Z Up Model
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::DrawZUp( bool bZUp )
|
|
{
|
|
s_bDrawZUp = bZUp;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Recursively render the Dag hierarchy
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::Draw( CDmeDrawSettings *pDrawSettings )
|
|
{
|
|
if ( !m_Visible )
|
|
return;
|
|
|
|
PushDagTransform();
|
|
|
|
CDmeShape *pShape = GetShape();
|
|
if ( pShape )
|
|
{
|
|
matrix3x4_t shapeToWorld;
|
|
GetShapeToWorldTransform( shapeToWorld );
|
|
pShape->Draw( shapeToWorld, pDrawSettings );
|
|
}
|
|
|
|
uint cn = m_Children.Count();
|
|
for ( uint ci = 0; ci < cn; ++ci )
|
|
{
|
|
m_Children[ ci ]->Draw( pDrawSettings );
|
|
}
|
|
|
|
PopDagTransform();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::GetBoundingSphere( Vector &c0, float &r0, const matrix3x4_t &pMat ) const
|
|
{
|
|
matrix3x4_t lMat;
|
|
m_Transform.GetElement()->GetTransform( lMat );
|
|
matrix3x4_t wMat;
|
|
ConcatTransforms( pMat, lMat, wMat );
|
|
|
|
c0.Zero();
|
|
r0 = 0.0f;
|
|
|
|
Vector vTemp;
|
|
|
|
const CDmeShape *pShape = m_Shape.GetElement();
|
|
if ( pShape )
|
|
{
|
|
pShape->GetBoundingSphere( c0, r0 );
|
|
VectorTransform( c0, lMat, vTemp );
|
|
c0 = vTemp;
|
|
}
|
|
|
|
// No scale in Dme! :)
|
|
VectorTransform( c0, pMat, vTemp );
|
|
c0 = vTemp;
|
|
|
|
const int nChildren = m_Children.Count();
|
|
if ( nChildren > 0 )
|
|
{
|
|
Vector c1; // Child center
|
|
float r1; // Child radius
|
|
|
|
Vector v01; // c1 - c0
|
|
float l01; // |v01|
|
|
|
|
for ( int i = 0; i < nChildren; ++i )
|
|
{
|
|
m_Children[ i ]->GetBoundingSphere( c1, r1, wMat );
|
|
|
|
if ( r0 == 0.0f )
|
|
{
|
|
c0 = c1;
|
|
r0 = r1;
|
|
continue;
|
|
}
|
|
|
|
v01 = c1 - c0;
|
|
l01 = v01.NormalizeInPlace();
|
|
|
|
if ( r0 < l01 + r1 )
|
|
{
|
|
// Current sphere doesn't contain both spheres
|
|
if ( r1 < l01 + r0 )
|
|
{
|
|
// Child sphere doesn't contain both spheres
|
|
c0 = c0 + 0.5f * ( r1 + l01 - r0 ) * v01;
|
|
r0 = 0.5f * ( r0 + l01 + r1 );
|
|
}
|
|
else
|
|
{
|
|
// Child sphere contains both spheres
|
|
c0 = c1;
|
|
r0 = r1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the channels targeting the transform of the object either
|
|
// directly or through a constraint
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::FindTransformChannels( CUtlVector< CDmeChannel * > &channelList ) const
|
|
{
|
|
// Find and channels targeting the transform of the dag node
|
|
FindAncestorsReferencingElement( GetTransform(), channelList );
|
|
|
|
// Find the slave instances targeting the specified node
|
|
CUtlVector< CDmeConstraintSlave* > slaveList;
|
|
FindAncestorsReferencingElement( this, slaveList );
|
|
|
|
int nSlaves = slaveList.Count();
|
|
for ( int iSlave = 0; iSlave < nSlaves; ++iSlave )
|
|
{
|
|
CDmeConstraintSlave *pSlave = slaveList[ iSlave ];
|
|
if ( pSlave )
|
|
{
|
|
FindAncestorsReferencingElement( pSlave, channelList );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the transform controls driving the dag node
|
|
//-----------------------------------------------------------------------------
|
|
CDmeTransformControl *CDmeDag::FindTransformControl() const
|
|
{
|
|
CUtlVector< CDmeChannel* > channelList( 0, 4 );
|
|
FindTransformChannels( channelList );
|
|
|
|
int nChannels = channelList.Count();
|
|
for ( int iChannel = 0; iChannel < nChannels; ++iChannel )
|
|
{
|
|
CDmeChannel *pChannel = channelList[ iChannel ];
|
|
if ( pChannel )
|
|
{
|
|
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pChannel->GetFromElement() );
|
|
if ( pTransformControl )
|
|
return pTransformControl;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the channels and operators which must be evaluated in order
|
|
// to determine the state of the dag node. Note that this function returns only
|
|
// the operators targeting this dag node, it does not include those targeting
|
|
// the parent of this node, but it does return all operators that this node
|
|
// is dependent on even if they are are connected to this node through other
|
|
// operators.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::FindLocalOperators( CUtlVector< CDmeOperator* > &operatorList ) const
|
|
{
|
|
// Find any operators directly targeting the transform of the dag node
|
|
GatherOperatorsForElement( GetTransform(), operatorList );
|
|
|
|
// Find the slave instances targeting the specified node
|
|
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 ( !g_pDataModel->GetElement( pElement->GetHandle() ) )
|
|
continue;
|
|
|
|
CDmeConstraintSlave *pConstraintSlave = CastElement< CDmeConstraintSlave >( pElement );
|
|
if ( pConstraintSlave )
|
|
{
|
|
CDmeRigBaseConstraintOperator *pConstraint = pConstraintSlave->GetConstraint();
|
|
if ( pConstraint )
|
|
{
|
|
if ( operatorList.Find( pConstraint ) == operatorList.InvalidIndex() )
|
|
{
|
|
pConstraint->GatherInputOperators( operatorList );
|
|
operatorList.AddToTail( pConstraint );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find all of the operators on which dag node is dependent, this
|
|
// recursively finds operators for the parents of the dag. The list of operators
|
|
// that is returned represents all of the operators which must be evaluated in
|
|
// order to determine the current state of the node. The list is returned in
|
|
// the order that the operators should be run.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::FindRelevantOperators( CUtlVector< CDmeOperator * > &operatorList ) const
|
|
{
|
|
const CDmeDag *pOverrideParent = GetOverrideParent();
|
|
if ( pOverrideParent )
|
|
{
|
|
pOverrideParent->FindRelevantOperators( operatorList );
|
|
}
|
|
|
|
CDmeDag *pParent = GetParent();
|
|
if ( pParent )
|
|
{
|
|
pParent->FindRelevantOperators( operatorList );
|
|
}
|
|
|
|
FindLocalOperators( operatorList );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find all of the operators on which the dag node is dependent,
|
|
// splitting the channels into a separate list. This function merely calls
|
|
// the FindRelvantOperators() which returns a single list of operators and
|
|
// then creates a list of channels and a list of other operators. It is
|
|
// preferable to just use the version which returns a single list of operators.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::FindRelevantOperators( CUtlVector< CDmeChannel * > &channelList, CUtlVector< CDmeOperator * > &operatorList ) const
|
|
{
|
|
CUtlVector< CDmeOperator* > allOperatorsList( 0, 128 );
|
|
FindRelevantOperators( allOperatorsList );
|
|
|
|
int nTotalOperators = allOperatorsList.Count();
|
|
channelList.EnsureCapacity( channelList.Count() + nTotalOperators );
|
|
operatorList.EnsureCapacity( operatorList.Count() + nTotalOperators );
|
|
|
|
for ( int iOperator = 0; iOperator < nTotalOperators; ++iOperator )
|
|
{
|
|
CDmeOperator *pOperator = allOperatorsList[ iOperator ];
|
|
if ( pOperator == NULL )
|
|
continue;
|
|
|
|
CDmeChannel *pChannel = CastElement< CDmeChannel >( pOperator );
|
|
if ( pChannel )
|
|
{
|
|
channelList.AddToTail( pChannel );
|
|
}
|
|
else
|
|
{
|
|
operatorList.AddToTail( pOperator );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CDmeDag::GetBoundingBox( Vector &min, Vector &max, const matrix3x4_t &pMat ) const
|
|
{
|
|
matrix3x4_t lMat;
|
|
m_Transform.GetElement()->GetTransform( lMat );
|
|
matrix3x4_t wMat;
|
|
ConcatTransforms( pMat, lMat, wMat );
|
|
|
|
min.Zero();
|
|
max.Zero();
|
|
bool bHasBox = false;
|
|
|
|
const CDmeShape *pShape = m_Shape.GetElement();
|
|
if ( pShape )
|
|
{
|
|
Vector cmin, cmax;
|
|
pShape->GetBoundingBox( cmin, cmax );
|
|
if ( cmin != cmax )
|
|
{
|
|
Vector bmin( cmin ), bmax( cmax );
|
|
VectorTransform( cmin, pMat, bmin );
|
|
VectorTransform( cmax, pMat, bmax );
|
|
for ( int kx = 0; kx < 2; ++ kx )
|
|
for ( int ky = 0; ky < 2; ++ ky )
|
|
for ( int kz = 0; kz < 2; ++ kz )
|
|
{
|
|
Vector tp(
|
|
kx ? cmax.x : cmin.x,
|
|
ky ? cmax.y : cmin.y,
|
|
kz ? cmax.z : cmin.z );
|
|
Vector vTemp;
|
|
VectorTransform( tp, pMat, vTemp );
|
|
|
|
bmin = bmin.Min( vTemp );
|
|
bmax = bmax.Max( vTemp );
|
|
}
|
|
|
|
if ( !bHasBox )
|
|
{
|
|
min = bmin;
|
|
max = bmax;
|
|
}
|
|
min = min.Min( bmin );
|
|
max = max.Max( bmax );
|
|
bHasBox = true;
|
|
}
|
|
}
|
|
|
|
for ( int i = 0, nChildren = m_Children.Count(); i < nChildren; ++ i )
|
|
{
|
|
CDmeDag *pChild = m_Children[i];
|
|
|
|
Vector cmin, cmax;
|
|
pChild->GetBoundingBox( cmin, cmax, wMat );
|
|
if ( cmin != cmax )
|
|
{
|
|
if ( !bHasBox )
|
|
{
|
|
min = cmin;
|
|
max = cmax;
|
|
}
|
|
min = min.Min( cmin );
|
|
max = max.Max( cmax );
|
|
bHasBox = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Quaternion QuaternionTransform( const Quaternion &quat, const matrix3x4_t &mat )
|
|
{
|
|
matrix3x4_t quatmat;
|
|
QuaternionMatrix( quat, quatmat );
|
|
|
|
matrix3x4_t newmat;
|
|
ConcatTransforms( mat, quatmat, newmat );
|
|
|
|
Quaternion result;
|
|
MatrixQuaternion( newmat, result );
|
|
|
|
return result;
|
|
}
|
|
|
|
void CDmeDag::BakeStaticTransforms( bool bRecurse /*= true*/ )
|
|
{
|
|
// determine whether to bake this transform into its children
|
|
|
|
if ( FindAncestorReferencingElement< CDmeChannel >( this ) || FindAncestorReferencingElement< CDmeConstraintSlave >( this ) )
|
|
return; // don't bake, since it's transform isn't static
|
|
|
|
if ( ( GetType() != CDmeDag::GetStaticTypeSymbol() ) && ( GetType() != CDmeRig::GetStaticTypeSymbol() ) )
|
|
return; // don't bake CDmeGameModel's root transform into the child bones, CDmeParticleSystem's root transform into the control points, etc.
|
|
|
|
int nChildrenFound = 0;
|
|
|
|
int nChildren = GetChildCount();
|
|
for ( int i = 0; i < nChildren; ++i )
|
|
{
|
|
CDmeDag *pChild = GetChild( i );
|
|
if ( !pChild )
|
|
continue;
|
|
|
|
if ( FindAncestorReferencingElement< CDmeConstraintSlave >( pChild ) )
|
|
return; // don't bake, since child's transform isn't static
|
|
|
|
// TODO: it probably wouldn't be that hard to make this work with dags with overriden parents
|
|
// the simplest path may be to unparent (and let the unparent do the conversion keeping everything in place), bake, and reparent
|
|
// there are faster and less obtrusive ways, but we don't need this functionality now, so I'm not solving it yet
|
|
if ( pChild->HasOverrideParent() )
|
|
return; // don't bake
|
|
|
|
++nChildrenFound;
|
|
}
|
|
|
|
if ( nChildrenFound == 0 )
|
|
return;
|
|
|
|
|
|
// now, actually bake this transform into its children
|
|
|
|
matrix3x4_t parentMat;
|
|
GetLocalMatrix( parentMat );
|
|
|
|
matrix3x4_t identityMat;
|
|
SetIdentityMatrix( identityMat );
|
|
SetLocalMatrix( identityMat );
|
|
|
|
static CUtlSymbolLarge symToElement = g_pDataModel->GetSymbol( "toElement" );
|
|
|
|
for ( int i = 0; i < nChildren; ++i )
|
|
{
|
|
CDmeDag *pChild = GetChild( i );
|
|
if ( !pChild )
|
|
continue;
|
|
|
|
CDmeTransform *pTransform = pChild->GetTransform();
|
|
if ( !pTransform )
|
|
continue;
|
|
|
|
CDmeVector3Log *pPosLog = NULL;
|
|
CDmeQuaternionLog *pRotLog = NULL;
|
|
CDmeTransformControl *pTransformControl = NULL;
|
|
|
|
for ( CAttributeReferenceIterator it( pTransform ); it; ++it )
|
|
{
|
|
if ( CDmeChannel *pChannel = it.FilterReference< CDmeChannel >( symToElement, true, TD_ALL ) )
|
|
{
|
|
CDmAttribute *pToAttr = pChannel->GetToAttribute();
|
|
if ( pToAttr == pTransform->GetPositionAttribute() )
|
|
{
|
|
pPosLog = CastElement< CDmeVector3Log >( pChannel->GetLog() );
|
|
}
|
|
else if ( pToAttr == pTransform->GetOrientationAttribute() )
|
|
{
|
|
pRotLog = CastElement< CDmeQuaternionLog >( pChannel->GetLog() );
|
|
}
|
|
|
|
if ( !pTransformControl )
|
|
{
|
|
pTransformControl = CastElement< CDmeTransformControl >( pChannel->GetFromElement() );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pPosLog )
|
|
{
|
|
int bestLayer = pPosLog->GetTopmostLayer();
|
|
CDmeVector3LogLayer *pPosLogLayer = bestLayer >= 0 ? pPosLog->GetLayer( bestLayer ) : NULL;
|
|
if ( pPosLogLayer )
|
|
{
|
|
int nPosKeys = pPosLogLayer->GetKeyCount();
|
|
for ( int i = 0; i < nPosKeys; ++i )
|
|
{
|
|
pPosLogLayer->SetKeyValue( i, VectorTransform( pPosLogLayer->GetKeyValue( i ), parentMat ) );
|
|
}
|
|
}
|
|
pPosLog->SetDefaultValue( VectorTransform( pPosLog->GetDefaultValue(), parentMat ) ); // do we really want to transform the default???
|
|
}
|
|
|
|
if ( pRotLog )
|
|
{
|
|
int bestLayer = pRotLog->GetTopmostLayer();
|
|
CDmeQuaternionLogLayer *pRotLogLayer = bestLayer >= 0 ? pRotLog->GetLayer( bestLayer ) : NULL;
|
|
if ( pRotLogLayer )
|
|
{
|
|
int nRotKeys = pRotLogLayer->GetKeyCount();
|
|
for ( int i = 0; i < nRotKeys; ++i )
|
|
{
|
|
pRotLogLayer->SetKeyValue( i, QuaternionTransform( pRotLogLayer->GetKeyValue( i ), parentMat ) );
|
|
}
|
|
}
|
|
pRotLog->SetDefaultValue( QuaternionTransform( pRotLog->GetDefaultValue(), parentMat ) ); // do we really want to transform the default???
|
|
}
|
|
|
|
if ( pTransformControl )
|
|
{
|
|
// bake into transform control
|
|
pTransformControl->SetPosition ( VectorTransform ( pTransformControl->GetPosition (), parentMat ) );
|
|
pTransformControl->SetOrientation( QuaternionTransform( pTransformControl->GetOrientation(), parentMat ) );
|
|
|
|
// we explicitly do NOT set the transform control's defaults, since the root default should still be the origin
|
|
}
|
|
|
|
// bake into transform
|
|
pTransform->SetPosition ( VectorTransform ( pTransform->GetPosition (), parentMat ) );
|
|
pTransform->SetOrientation( QuaternionTransform( pTransform->GetOrientation(), parentMat ) );
|
|
|
|
if ( bRecurse )
|
|
{
|
|
pChild->BakeStaticTransforms( bRecurse );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Attach the dag node to the specified parent node. If the dag node
|
|
// is currently a child of another node it will be removed the from that node.
|
|
// The local space transform of the dag node will be modified so that it will
|
|
// have the same world space transform after being attached as before it was
|
|
// attached. Passing in NULL for the parent effectively moves the dag node into
|
|
// world space without any parents.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::OnAttachToDmeDag( CDmeDag *pParentDag, bool bFixupLogs /*= true*/ )
|
|
{
|
|
if ( GetParent() == pParentDag )
|
|
return;
|
|
|
|
CDmeTransform *pTransform = GetTransform();
|
|
if ( !pTransform )
|
|
return;
|
|
|
|
CUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "CDmeDag::OnAttachToDmeDag" );
|
|
|
|
CDmeFilmClip *pFilmClip = NULL;
|
|
|
|
if ( pParentDag != NULL )
|
|
{
|
|
pFilmClip = FindFilmClipContainingDag( pParentDag );
|
|
}
|
|
else
|
|
{
|
|
pFilmClip = FindFilmClipContainingDag( this );
|
|
}
|
|
|
|
|
|
matrix3x4_t cameraMat, parentMat, invParentMat, newCameraMat;
|
|
GetAbsTransform( cameraMat );
|
|
if ( pParentDag && pFilmClip )
|
|
{
|
|
pParentDag->GetAbsTransform( parentMat );
|
|
}
|
|
else
|
|
{
|
|
SetIdentityMatrix( parentMat );
|
|
}
|
|
|
|
MatrixInvert( parentMat, invParentMat );
|
|
ConcatTransforms( invParentMat, cameraMat, newCameraMat );
|
|
pTransform->SetTransform( newCameraMat );
|
|
|
|
matrix3x4_t cameraParentMat, oldToNewCameraMat;
|
|
GetParentWorldMatrix( cameraParentMat );
|
|
ConcatTransforms( invParentMat, cameraParentMat, oldToNewCameraMat );
|
|
|
|
CUtlVector< CDmeDag* > parents;
|
|
FindAncestorsReferencingElement( this, parents );
|
|
int nParents = parents.Count();
|
|
for ( int i = 0; i < nParents; ++i )
|
|
{
|
|
parents[ i ]->RemoveChild( this );
|
|
}
|
|
|
|
if ( ( bFixupLogs ) && ( pFilmClip != NULL ) )
|
|
{
|
|
CDmeTransform *pTransform = GetTransform();
|
|
CDmeChannel *pPosChannel = FindChannelTargetingElement( pFilmClip, pTransform, TRANSFORM_POSITION, NULL );
|
|
if ( pPosChannel )
|
|
{
|
|
CDmeLog *pLog = pPosChannel->GetLog();
|
|
if ( pLog )
|
|
{
|
|
CDmeLogLayer *pLayer = pLog->GetLayer( pLog->GetTopmostLayer() );
|
|
CDmeTypedLogLayer< Vector > *pVectorLayer = CastElement< CDmeTypedLogLayer< Vector > >( pLayer );
|
|
if ( pVectorLayer )
|
|
{
|
|
int nKeys = pVectorLayer->GetKeyCount();
|
|
for ( int i = 0; i < nKeys; ++i )
|
|
{
|
|
Vector newvec, oldvec = pVectorLayer->GetKeyValue( i );
|
|
VectorTransform( oldvec, oldToNewCameraMat, newvec );
|
|
pVectorLayer->SetKeyValue( i, newvec );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CDmeChannel *pRotChannel = FindChannelTargetingElement( pFilmClip, pTransform, TRANSFORM_ORIENTATION, NULL );
|
|
if ( pRotChannel )
|
|
{
|
|
CDmeLog *pLog = pRotChannel->GetLog();
|
|
if ( pLog )
|
|
{
|
|
CDmeLogLayer *pLayer = pLog->GetLayer( pLog->GetTopmostLayer() );
|
|
CDmeTypedLogLayer< Quaternion > *pQuatLayer = CastElement< CDmeTypedLogLayer< Quaternion > >( pLayer );
|
|
if ( pQuatLayer )
|
|
{
|
|
int nKeys = pQuatLayer->GetKeyCount();
|
|
for ( int i = 0; i < nKeys; ++i )
|
|
{
|
|
matrix3x4_t oldmat, newmat;
|
|
QuaternionMatrix( pQuatLayer->GetKeyValue( i ), oldmat );
|
|
ConcatTransforms( oldToNewCameraMat, oldmat, newmat );
|
|
Quaternion quat;
|
|
MatrixQuaternion( newmat, quat );
|
|
pQuatLayer->SetKeyValue( i, quat );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pParentDag )
|
|
{
|
|
pParentDag->AddChild( this );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Modify the logs controlling the transform of this dag node so that
|
|
// the transform maintains the same relative position and orientation to the
|
|
// target dag node for the duration of the provided time period.
|
|
// Input : pTargetDag - Pointer to the dag node whose transform the transform
|
|
// of this dag node it to be bound to.
|
|
// Input : timeSelection - Time selection for which the modifications are to
|
|
// be applied, including falloffs.
|
|
// Input : pMovie - Pointer to the movie to which the dag belongs
|
|
// Input : offset - Offset relative to the dag where the offset is going to be
|
|
// placed if the reset position flag is true.
|
|
// Input : bPosition - flag indicating that the position of the dag node
|
|
// should be modified.
|
|
// Input : bOrientation - flag indicating that the orientation of the dag
|
|
// node should be modified.
|
|
// Input : bResetPosition - flag indicating that the position of the target
|
|
// dag node should be repositioned.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::BindTransformToDmeDag( const CDmeDag *pTargetDag, const DmeLog_TimeSelection_t &timeSelection, const CDmeClip* pMovie, const Vector& offset, bool bPosition, bool bOrientation, bool bMaintainOffset )
|
|
{
|
|
// Must specify a target dag and it must not be this dag.
|
|
if ( ( pTargetDag == NULL ) || ( pTargetDag == this ) )
|
|
{
|
|
Assert( pTargetDag );
|
|
return;
|
|
}
|
|
|
|
// Find the film clip for this dag and the target dag node and make sure they are the same
|
|
CDmeFilmClip *pFilmClip = FindFilmClipContainingDag( this );
|
|
if ( pFilmClip == NULL )
|
|
{
|
|
pFilmClip = FindReferringElement< CDmeFilmClip >( this, "camera" );
|
|
}
|
|
|
|
// Find the channels which must be updated in order to evaluate
|
|
// transforms of both this dag node and the target dag node.
|
|
CUtlVector< CDmeChannel* > localChannelList, targetChannelList;
|
|
CUtlVector< CDmeOperator* > localOperatorList, targetOperatorList;
|
|
FindRelevantOperators( localChannelList, localOperatorList );
|
|
pTargetDag->FindRelevantOperators( targetChannelList, targetOperatorList );
|
|
|
|
|
|
// Build the clip stacks for the channel lists.
|
|
CUtlVector< DmeClipStack_t > localClipStackList, targetClipStackList;
|
|
CUtlVector< DmeTime_t > localChannelTimeList, targetChannelTimeList;
|
|
BuildClipStackList( localChannelList, localClipStackList, localChannelTimeList, pMovie, pFilmClip );
|
|
BuildClipStackList( targetChannelList, targetClipStackList, targetChannelTimeList, pMovie, pFilmClip );
|
|
|
|
// Construct the offset transform matrix which will be used to maintain the relative
|
|
// offset and orientation of the current dag to the target dag at the current time
|
|
CDmeTransform *pTransform = GetTransform();
|
|
|
|
matrix3x4_t targetMat;
|
|
matrix3x4_t invTargetMat;
|
|
pTargetDag->GetAbsTransform( targetMat );
|
|
MatrixInvert( targetMat, invTargetMat );
|
|
|
|
matrix3x4_t orginalTransform;
|
|
|
|
if ( bMaintainOffset )
|
|
{
|
|
GetAbsTransform( orginalTransform );
|
|
}
|
|
else
|
|
{
|
|
Vector basePosition;
|
|
Vector offsetPosition;
|
|
MatrixPosition( targetMat, basePosition );
|
|
offsetPosition = basePosition + offset;
|
|
orginalTransform = targetMat;
|
|
PositionMatrix( offsetPosition, orginalTransform );
|
|
}
|
|
|
|
matrix3x4_t offsetTransform;
|
|
ConcatTransforms( invTargetMat, orginalTransform, offsetTransform );
|
|
|
|
|
|
// Find the position and orientation channels associated with the transform
|
|
CDmeChannel *pLocalPosChannel = bPosition ? FindChannelTargetingElement( pFilmClip, pTransform, "position", NULL ) : NULL;
|
|
CDmeChannel *pLocalRotChannel = bOrientation ? FindChannelTargetingElement( pFilmClip, pTransform, "orientation", NULL ) : NULL;
|
|
|
|
if ( pLocalPosChannel || pLocalRotChannel )
|
|
{
|
|
// Start a new undo group for the operation
|
|
g_pDataModel->StartUndo( "Attach Transform", "Attach Transform" );
|
|
|
|
CDmeLog *pPosLog = ( pLocalPosChannel != NULL ) ? pLocalPosChannel->GetLog() : NULL;
|
|
CDmeLog *pRotLog = ( pLocalRotChannel != NULL ) ? pLocalRotChannel->GetLog() : NULL;
|
|
CDmeChannelsClip *pPosChannelsClip = ( pLocalPosChannel != NULL ) ? FindAncestorReferencingElement< CDmeChannelsClip >( pLocalPosChannel ) : NULL;
|
|
CDmeChannelsClip *pRotChannelsClip = ( pLocalRotChannel != NULL ) ? FindAncestorReferencingElement< CDmeChannelsClip >( pLocalRotChannel ) : NULL;
|
|
CDmeChannelsClip *pChannelsClip = ( pPosChannelsClip != NULL ) ? pPosChannelsClip : pRotChannelsClip;
|
|
Assert( pPosChannelsClip == pRotChannelsClip || pPosChannelsClip == NULL || pRotChannelsClip == NULL );
|
|
Assert( pPosLog != pRotLog );
|
|
Assert( pChannelsClip );
|
|
|
|
if ( pChannelsClip )
|
|
{
|
|
// Construct the clip stack from the movie down to the channels clip containing the channel
|
|
DmeClipStack_t clipStack;
|
|
if ( pPosChannelsClip )
|
|
{
|
|
pPosChannelsClip->BuildClipStack( &clipStack, pMovie, pFilmClip );
|
|
}
|
|
else
|
|
{
|
|
pRotChannelsClip->BuildClipStack( &clipStack, pMovie, pFilmClip );
|
|
}
|
|
|
|
// Add a new layer to the active logs, the new layer will contain the values
|
|
// required to position and orient the transform relative to the target.
|
|
CDmeLogLayer *pPosLayer = ( pPosLog != NULL ) ? pPosLog->AddNewLayer() : NULL;
|
|
CDmeLogLayer *pRotLayer = ( pRotLog != NULL ) ? pRotLog->AddNewLayer() : NULL;
|
|
CDmeTypedLogLayer< Vector > *pVectorLayer = CastElement< CDmeTypedLogLayer< Vector > >( pPosLayer );
|
|
CDmeTypedLogLayer< Quaternion > *pQuatLayer = CastElement< CDmeTypedLogLayer< Quaternion > >( pRotLayer );
|
|
|
|
// Iterate through the time selection based on the resample interval
|
|
// and generate keys in the active logs at each time time step.
|
|
DmeTime_t time = timeSelection.m_nTimes[ 0 ];
|
|
DmeTime_t endTime = timeSelection.m_nTimes[ 3 ];
|
|
bool done = false;
|
|
|
|
// Disable the undo, the only thing that is actually
|
|
// needed is the add new layer and the flatten layers.
|
|
CDisableUndoScopeGuard undosg;
|
|
|
|
while ( !done )
|
|
{
|
|
// Make sure the time does not pass the end of the time selection and set the
|
|
// done flag if it has. By setting the flag instead of breaking immediately we
|
|
// are guaranteed a sample will be made at the very end of the time selection.
|
|
if ( time >= endTime )
|
|
{
|
|
time = endTime;
|
|
done = true;
|
|
}
|
|
|
|
// Evaluate all of the channels relevant to the target dag and the local dag at the
|
|
// specified time so that the transform evaluations will correspond to the correct time.
|
|
PlayChannelsAtTime( time, targetChannelList, targetOperatorList, targetClipStackList );
|
|
PlayChannelsAtTime( time, localChannelList, localOperatorList, localClipStackList );
|
|
|
|
// Get the world space transform of the target
|
|
matrix3x4_t targetTransform;
|
|
pTargetDag->GetAbsTransform( targetTransform );
|
|
|
|
// Apply the offset transform to the target transform and make the
|
|
// result the world space transform of the current dag node.
|
|
matrix3x4_t finalTransform;
|
|
ConcatTransforms( targetTransform, offsetTransform, finalTransform );
|
|
SetAbsTransform( finalTransform );
|
|
|
|
// Calculate the time relative to the log
|
|
DmeTime_t logTime = clipStack.ToChildMediaTime( time );
|
|
|
|
// Add the position of the transform to the position log of the dag node's transform
|
|
if ( pVectorLayer )
|
|
{
|
|
Vector position = pTransform->GetPosition();
|
|
pVectorLayer->SetKey( logTime, position, SEGMENT_INTERPOLATE, CURVE_DEFAULT, false );
|
|
}
|
|
|
|
// Add the orientation of the transform to the orientation log of the dag node's transform
|
|
if ( pQuatLayer )
|
|
{
|
|
Quaternion rotation = pTransform->GetOrientation();
|
|
pQuatLayer->SetKey( logTime, rotation, SEGMENT_INTERPOLATE, CURVE_DEFAULT, false );
|
|
}
|
|
|
|
// Move the evaluation time up by the resample interval
|
|
time += timeSelection.m_nResampleInterval;
|
|
}
|
|
|
|
// Convert the time selection into the local time space of the log.
|
|
DmeLog_TimeSelection_t logTimeSelection = timeSelection;
|
|
clipStack.ToChildMediaTime( logTimeSelection.m_nTimes );
|
|
|
|
// Blend the top level log layers contain the new position and orientation with the base log
|
|
// layer according to the time selection parameters and then flatten the log layers.
|
|
if ( pPosLog )
|
|
{
|
|
pPosLog->BlendLayersUsingTimeSelection( logTimeSelection );
|
|
|
|
CEnableUndoScopeGuard undosg;
|
|
pPosLog->FlattenLayers( timeSelection.m_flThreshold, 0 );
|
|
}
|
|
|
|
if ( pRotLog )
|
|
{
|
|
pRotLog->BlendLayersUsingTimeSelection( logTimeSelection );
|
|
|
|
CEnableUndoScopeGuard undosg;
|
|
pRotLog->FlattenLayers( timeSelection.m_flThreshold, 0 );
|
|
}
|
|
|
|
// Restore all of the channels to the original times so that this operation does not have side effects.
|
|
PlayChannelsAtLocalTimes( targetChannelTimeList, targetChannelList, targetOperatorList );
|
|
PlayChannelsAtLocalTimes( localChannelTimeList, localChannelList, localOperatorList );
|
|
}
|
|
|
|
g_pDataModel->FinishUndo();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute the absolute and the local transform of the dag node at the
|
|
// specified global time.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::ComputeTransformAtTime( DmeTime_t globalTime, const CDmeClip* pMovie, matrix3x4_t &matAbsTransform, matrix3x4_t &localTransform )
|
|
{
|
|
// This operation should have no side effects and
|
|
// no entries should be added to the undo stack.
|
|
CDisableUndoScopeGuard disableUndoSG;
|
|
|
|
// Find the film clip containing this dag
|
|
CDmeFilmClip *pFilmClip = FindFilmClipContainingDag( this );
|
|
if ( pFilmClip == NULL )
|
|
{
|
|
pFilmClip = FindReferringElement< CDmeFilmClip >( this, "camera" );
|
|
}
|
|
|
|
// Get the transform associated with the dag
|
|
CDmeTransform *pTransform = GetTransform();
|
|
|
|
if ( ( pMovie == NULL ) || ( pFilmClip == NULL ) || ( pTransform == NULL ) )
|
|
return;
|
|
|
|
|
|
// Build a list of channels and other operators which must be
|
|
// evaluated in order to evaluate the transform of this dag node.
|
|
CUtlVector< CDmeChannel* > channelList;
|
|
CUtlVector< CDmeOperator* > operatorList;
|
|
CUtlVector< DmeClipStack_t > clipStackList;
|
|
CUtlVector< DmeTime_t > channelTimeList;
|
|
FindRelevantOperators( channelList, operatorList );
|
|
BuildClipStackList( channelList, clipStackList, channelTimeList, pMovie, pFilmClip );
|
|
|
|
// Evaluate the channels and operators relevant to the dag at the specified time
|
|
PlayChannelsAtTime( globalTime, channelList, operatorList, clipStackList );
|
|
|
|
// Get the absolute world transform and the local transform
|
|
GetAbsTransform( matAbsTransform );
|
|
pTransform->GetTransform( localTransform );
|
|
|
|
// Restore all of the channels to the original times so that this operation does not have side effects.
|
|
PlayChannelsAtLocalTimes( channelTimeList, channelList, operatorList );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Move the the dag node to the position of the specified node at the current
|
|
// time, for the rest of the time selection apply the same world space offset
|
|
// that was required to move the dag to the target position at the current time.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::MoveToTarget( const CDmeDag *pTargetDag, const DmeLog_TimeSelection_t &timeSelection, const CDmeClip *pMovie )
|
|
{
|
|
// Must specify a target dag and it must not be this dag.
|
|
if ( ( pTargetDag == NULL ) || ( pTargetDag == this ) )
|
|
{
|
|
Assert( pTargetDag );
|
|
return;
|
|
}
|
|
|
|
// Find the film clip for the dag
|
|
CDmeFilmClip *pFilmClip = FindFilmClipContainingDag( this );
|
|
if ( pFilmClip == NULL )
|
|
{
|
|
pFilmClip = FindReferringElement< CDmeFilmClip >( this, "camera" );
|
|
}
|
|
|
|
// Find the channels which must be updated in order to evaluate transforms of both this dag node.
|
|
CUtlVector< CDmeChannel* > channelList;
|
|
CUtlVector< CDmeOperator* > operatorList;
|
|
FindRelevantOperators( channelList,operatorList );
|
|
|
|
// Build the clip stacks for the channel lists.
|
|
CUtlVector< DmeClipStack_t > clipStackList;
|
|
CUtlVector< DmeTime_t > channelTimeList;
|
|
BuildClipStackList( channelList, clipStackList, channelTimeList, pMovie, pFilmClip );
|
|
|
|
|
|
// Compute the world space offset between the dag node and the target
|
|
Vector vWorldPos, vTargetPos;
|
|
GetAbsPosition( vWorldPos );
|
|
pTargetDag->GetAbsPosition( vTargetPos );
|
|
Vector vOffset = vTargetPos - vWorldPos;
|
|
|
|
|
|
// Find the position channel of driving the dag node which will be updated.
|
|
CDmeTransform *pTransform = GetTransform();
|
|
CDmeChannel *pLocalPosChannel = FindChannelTargetingElement( pFilmClip, pTransform, "position", NULL );
|
|
|
|
if ( pLocalPosChannel != NULL )
|
|
{
|
|
// Start a new undo group for the operation
|
|
g_pDataModel->StartUndo( "Move dag to target", "Move dag to target" );
|
|
|
|
CDmeLog *pPosLog = ( pLocalPosChannel != NULL ) ? pLocalPosChannel->GetLog() : NULL;
|
|
CDmeChannelsClip *pChannelsClip = FindAncestorReferencingElement< CDmeChannelsClip >( pLocalPosChannel );
|
|
Assert( pChannelsClip );
|
|
|
|
if ( pChannelsClip && pPosLog )
|
|
{
|
|
// Construct the clip stack from the movie down to the channels clip containing the channel
|
|
DmeClipStack_t clipStack;
|
|
pChannelsClip->BuildClipStack( &clipStack, pMovie, pFilmClip );
|
|
|
|
// Add a new layer to the active logs, the new layer will contain the values
|
|
// required to position and orient the transform relative to the target.
|
|
CDmeLogLayer *pPosLayer = pPosLog->AddNewLayer();
|
|
CDmeTypedLogLayer< Vector > *pVectorLayer = CastElement< CDmeTypedLogLayer< Vector > >( pPosLayer );
|
|
|
|
|
|
// Iterate through the time selection based on the resample interval
|
|
// and generate keys in the active logs at each time time step.
|
|
DmeTime_t time = timeSelection.m_nTimes[ 0 ];
|
|
DmeTime_t endTime = timeSelection.m_nTimes[ 3 ];
|
|
bool done = false;
|
|
|
|
// Disable the undo, the only thing that is actually
|
|
// needed is the add new layer and the flatten layers.
|
|
CDisableUndoScopeGuard undosg;
|
|
|
|
int nAproxNumTimes = ( ( endTime - time ) / timeSelection.m_nResampleInterval ) + 2;
|
|
CUtlVector< Vector > samplePositions( 0, nAproxNumTimes );
|
|
CUtlVector< DmeTime_t > sampleTimes( 0, nAproxNumTimes );
|
|
|
|
while ( !done )
|
|
{
|
|
// Make sure the time does not pass the end of the time selection and set the
|
|
// done flag if it has. By setting the flag instead of breaking immediately we
|
|
// are guaranteed a sample will be made at the very end of the time selection.
|
|
if ( time >= endTime )
|
|
{
|
|
time = endTime;
|
|
done = true;
|
|
}
|
|
|
|
// Evaluate all of the channels relevant to the target dag and the local dag at the
|
|
// specified time so that the transform evaluations will correspond to the correct time.
|
|
PlayChannelsAtTime( time, channelList, operatorList, clipStackList );
|
|
|
|
// Get the current world space position of the node
|
|
Vector vOriginalWorldPos;
|
|
GetAbsPosition( vOriginalWorldPos );
|
|
Vector vNewWorldPos = vOriginalWorldPos + vOffset;
|
|
SetAbsPosition( vNewWorldPos );
|
|
Vector vLocalPosition = pTransform->GetPosition();
|
|
|
|
// Store the sample time and the local position
|
|
samplePositions.AddToTail( vLocalPosition );
|
|
sampleTimes.AddToTail( time );
|
|
|
|
// Move the evaluation time up by the resample interval
|
|
time += timeSelection.m_nResampleInterval;
|
|
}
|
|
|
|
|
|
// Iterate through all of the samples and construct
|
|
int nNumSamples = sampleTimes.Count();
|
|
for ( int i = 0; i < nNumSamples; ++i )
|
|
{
|
|
// Calculate the time relative to the log
|
|
DmeTime_t logTime = clipStack.ToChildMediaTime( sampleTimes[ i ] );
|
|
|
|
// Add the position of the transform to the position log of the dag node's transform
|
|
if ( pVectorLayer )
|
|
{
|
|
Vector vLocalPosition = samplePositions[ i ];
|
|
pVectorLayer->SetKey( logTime, vLocalPosition, SEGMENT_INTERPOLATE, CURVE_DEFAULT, false );
|
|
}
|
|
}
|
|
|
|
// Convert the time selection into the local time space of the log.
|
|
DmeLog_TimeSelection_t logTimeSelection = timeSelection;
|
|
clipStack.ToChildMediaTime( logTimeSelection.m_nTimes );
|
|
|
|
// Blend the top level log layers contain the new position and orientation with the base log
|
|
// layer according to the time selection parameters and then flatten the log layers.
|
|
pPosLog->BlendLayersUsingTimeSelection( logTimeSelection );
|
|
|
|
{
|
|
CEnableUndoScopeGuard undosg;
|
|
pPosLog->FlattenLayers( timeSelection.m_flThreshold, 0 );
|
|
}
|
|
|
|
// Restore all of the channels to the original times so that this operation does not have side effects.
|
|
PlayChannelsAtLocalTimes( channelTimeList, channelList, operatorList );
|
|
}
|
|
|
|
g_pDataModel->FinishUndo();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void CDmeDag::GetAbsTransform( matrix3x4_t &matAbsTransform ) const
|
|
{
|
|
matrix3x4_t parentToWorld;
|
|
GetParentWorldMatrix( parentToWorld );
|
|
|
|
matrix3x4_t localMatrix;
|
|
GetLocalMatrix( localMatrix );
|
|
|
|
ConcatTransforms( parentToWorld, localMatrix, matAbsTransform );
|
|
}
|
|
|
|
|
|
void CDmeDag::SetAbsTransform( const matrix3x4_t &matAbsTransform )
|
|
{
|
|
CDmeTransform *pTransform = GetTransform();
|
|
if ( pTransform == NULL )
|
|
return;
|
|
|
|
matrix3x4_t parentToWorld;
|
|
GetParentWorldMatrix( parentToWorld );
|
|
|
|
matrix3x4_t worldToParent;
|
|
MatrixInvert( parentToWorld, worldToParent );
|
|
|
|
matrix3x4_t localSpace;
|
|
ConcatTransforms( worldToParent, matAbsTransform, localSpace );
|
|
|
|
Vector localPos;
|
|
Quaternion localRot;
|
|
MatrixAngles( localSpace, localRot, localPos );
|
|
|
|
// The position only override depends on the local transform when calculating the parent transform,
|
|
// meaning that simply inverting the parent to world matrix and concatenating it with the desired
|
|
// absolute transform does not produce the correct local matrix since changing the local matrix
|
|
// changes the result of GetParentWorldMatrix(). So when the position only override is enabled we
|
|
// must compute the rotation and position separately.
|
|
if ( HasOverrideParent() )
|
|
{
|
|
matrix3x4_t mTranslationParentToWorld;
|
|
GetTranslationParentWorldMatrix( mTranslationParentToWorld );
|
|
|
|
matrix3x4_t mTranslationWorldToParent;
|
|
MatrixInvert( mTranslationParentToWorld, mTranslationWorldToParent );
|
|
|
|
matrix3x4_t mTranslationLocalSpace;
|
|
ConcatTransforms( mTranslationWorldToParent, matAbsTransform, mTranslationLocalSpace );
|
|
|
|
MatrixPosition( mTranslationLocalSpace, localPos);
|
|
}
|
|
|
|
pTransform->SetPosition( localPos );
|
|
pTransform->SetOrientation( localRot );
|
|
}
|
|
|
|
|
|
CDmeDag *CDmeDag::GetParent() const
|
|
{
|
|
const static CUtlSymbolLarge symChildren = g_pDataModel->GetSymbol( "children" );
|
|
CDmeDag *pParent = FindReferringElement< CDmeDag >( this, symChildren, false );
|
|
return pParent;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Determine if the dag has an override parent
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeDag::HasOverrideParent() const
|
|
{
|
|
if ( m_bDisableOverrideParent )
|
|
return false;
|
|
|
|
return ( GetValueElement< CDmeDag >( OVERRIDE_PARENT ) != NULL );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the current override parent, returns NULL if no override parent is set
|
|
//-----------------------------------------------------------------------------
|
|
const CDmeDag *CDmeDag::GetOverrideParent( bool bIgnoreEnable ) const
|
|
{
|
|
if ( m_bDisableOverrideParent && !bIgnoreEnable )
|
|
return NULL;
|
|
|
|
return GetValueElement< CDmeDag >( OVERRIDE_PARENT );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the current override parent, returns NULL if no override parent is set
|
|
//-----------------------------------------------------------------------------
|
|
const CDmeDag *CDmeDag::GetOverrideParent( bool &bPosition, bool &bRotation, bool bIgnoreEnable ) const
|
|
{
|
|
bPosition = false;
|
|
bRotation = false;
|
|
|
|
if ( m_bDisableOverrideParent && !bIgnoreEnable )
|
|
return NULL;
|
|
|
|
CDmeDag *pOverrideParent = GetValueElement< CDmeDag >( OVERRIDE_PARENT );
|
|
|
|
if ( pOverrideParent )
|
|
{
|
|
bPosition = GetValue< bool >( "overridePos", false );
|
|
bRotation = GetValue< bool >( "overrideRot", false );
|
|
}
|
|
|
|
if ( bPosition || bRotation )
|
|
{
|
|
return pOverrideParent;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set the current override parent, if the parameter is NULL the overrider
|
|
// parent will be cleared.
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::SetOverrideParent( const CDmeDag *pParentDag, bool bPosition, bool bRotation )
|
|
{
|
|
if ( ( pParentDag == NULL ) || ( !bPosition && !bRotation ) )
|
|
{
|
|
RemoveAttribute( OVERRIDE_PARENT );
|
|
RemoveAttribute( "overridePos" );
|
|
RemoveAttribute( "overrideRot" );
|
|
}
|
|
else
|
|
{
|
|
CDmAttribute *pOverrideAttr = SetValue( OVERRIDE_PARENT, pParentDag );
|
|
if ( pOverrideAttr )
|
|
{
|
|
// Set the never copy flag so that the reference
|
|
// will not be walked when deleting an animation set.
|
|
pOverrideAttr->AddFlag( FATTRIB_NEVERCOPY );
|
|
}
|
|
|
|
SetValue< bool >( "overridePos", bPosition );
|
|
SetValue< bool >( "overrideRot", bRotation );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set the flag which enables or disables the override parent
|
|
//-----------------------------------------------------------------------------
|
|
void CDmeDag::EnableOverrideParent( bool bEnable )
|
|
{
|
|
m_bDisableOverrideParent = !bEnable;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Determine if the override parent is enabled. This only says if an override
|
|
// parent is allowed, not if the dag has an override parent)
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeDag::IsOverrideParentEnabled() const
|
|
{
|
|
return !m_bDisableOverrideParent;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Determine if this dag node is ancestor of the specified dag
|
|
//-----------------------------------------------------------------------------
|
|
bool CDmeDag::IsAncestorOfDag( const CDmeDag *pDag ) const
|
|
{
|
|
if ( pDag == NULL )
|
|
return false;
|
|
|
|
const CDmeDag *pCurrentDag = pDag;
|
|
const CDmeDag *pParent = pDag->GetParent();
|
|
|
|
while ( pParent )
|
|
{
|
|
if ( pParent == this )
|
|
return true;
|
|
|
|
pCurrentDag = pParent;
|
|
pParent = pParent->GetParent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Find the dag node which is the root of the tree the dag node is in.
|
|
//-----------------------------------------------------------------------------
|
|
CDmeDag *CDmeDag::FindRoot()
|
|
{
|
|
CDmeDag *pRoot = this;
|
|
CDmeDag *pParent = GetParent();
|
|
while ( pParent )
|
|
{
|
|
pRoot = pParent;
|
|
pParent = pParent->GetParent();
|
|
}
|
|
return pRoot;
|
|
}
|
|
|
|
|
|
void CDmeDag::GetAbsPosition( Vector &absPos ) const
|
|
{
|
|
matrix3x4_t abs;
|
|
GetAbsTransform( abs );
|
|
MatrixGetColumn( abs, 3, absPos );
|
|
}
|
|
|
|
void CDmeDag::SetAbsPosition( const Vector &absPos )
|
|
{
|
|
matrix3x4_t abs;
|
|
GetAbsTransform( abs );
|
|
MatrixSetColumn( absPos, 3, abs );
|
|
SetAbsTransform( abs );
|
|
}
|
|
|
|
void CDmeDag::GetAbsOrientation( Quaternion &absOrientation ) const
|
|
{
|
|
matrix3x4_t abs;
|
|
GetAbsTransform( abs );
|
|
Vector absPos;
|
|
MatrixQuaternion( abs, absOrientation );
|
|
}
|
|
|
|
void CDmeDag::SetAbsOrientation( const Quaternion &absOrientation )
|
|
{
|
|
matrix3x4_t abs;
|
|
GetAbsTransform( abs );
|
|
Vector absPos;
|
|
MatrixGetColumn( abs, 3, absPos );
|
|
QuaternionMatrix( absOrientation, absPos, abs );
|
|
SetAbsTransform( abs );
|
|
}
|
|
|
|
void RemoveDagFromParents( CDmeDag *pDag, bool bRecursivelyRemoveEmptyDagsFromParents /*= false*/ )
|
|
{
|
|
DmAttributeReferenceIterator_t hAttr = g_pDataModel->FirstAttributeReferencingElement( pDag->GetHandle() );
|
|
for ( ; hAttr != DMATTRIBUTE_REFERENCE_ITERATOR_INVALID; hAttr = g_pDataModel->NextAttributeReferencingElement( hAttr ) )
|
|
{
|
|
CDmAttribute *pAttr = g_pDataModel->GetAttribute( hAttr );
|
|
if ( !pAttr )
|
|
continue;
|
|
|
|
CDmeDag *pParent = CastElement< CDmeDag >( pAttr->GetOwner() );
|
|
if ( !pParent )
|
|
continue;
|
|
|
|
pParent->RemoveChild( pDag );
|
|
if ( bRecursivelyRemoveEmptyDagsFromParents && pParent->GetChildCount() == 0 )
|
|
{
|
|
RemoveDagFromParents( pParent );
|
|
}
|
|
}
|
|
}
|