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.
604 lines
17 KiB
604 lines
17 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "stdafx.h" |
|
#include "Box3D.h" |
|
#include "StockSolids.h" |
|
#include "GlobalFunctions.h" |
|
#include "hammer_mathlib.h" |
|
#include "MapDoc.h" |
|
#include "MapEntity.h" |
|
#include "MapWorld.h" |
|
#include "KeyFrame/KeyFrame.h" |
|
#include "MapKeyFrame.h" |
|
#include "MapAnimator.h" |
|
#include "Render3D.h" |
|
#include "TextureSystem.h" |
|
#include "materialsystem/imesh.h" |
|
#include "Material.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
IMPLEMENT_MAPCLASS( CMapKeyFrame ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Factory function. Used for creating a CMapKeyFrame from a set |
|
// of string parameters from the FGD file. |
|
// Input : *pInfo - Pointer to helper info class which gives us information |
|
// about how to create the class. |
|
// Output : Returns a pointer to the class, NULL if an error occurs. |
|
//----------------------------------------------------------------------------- |
|
CMapClass *CMapKeyFrame::CreateMapKeyFrame(CHelperInfo *pHelperInfo, CMapEntity *pParent) |
|
{ |
|
return(new CMapKeyFrame); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CMapKeyFrame::CMapKeyFrame() |
|
{ |
|
m_pAnimator = NULL; |
|
m_pNextKeyFrame = NULL; |
|
m_flMoveTime = 0; |
|
m_flSpeed = 0; |
|
m_bRebuildPath = false; |
|
m_Angles.Init(); |
|
|
|
// setup the quaternion identity |
|
m_qAngles[0] = m_qAngles[1] = m_qAngles[2] = 0; |
|
m_qAngles[3] = 1; |
|
|
|
m_pPositionInterpolator = NULL; |
|
m_iPositionInterpolator = -1; |
|
m_iChangeFrame = -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CMapKeyFrame::~CMapKeyFrame() |
|
{ |
|
if( m_pPositionInterpolator ) |
|
m_pPositionInterpolator->Release(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : bFullUpdate - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::CalcBounds(BOOL bFullUpdate) |
|
{ |
|
CMapClass::CalcBounds(bFullUpdate); |
|
|
|
// |
|
// Calculate the 3D bounds to include all points on our line. |
|
// |
|
m_CullBox.ResetBounds(); |
|
m_CullBox.UpdateBounds(m_Origin); |
|
|
|
if( m_pNextKeyFrame ) |
|
{ |
|
// Expand the bbox by the target entity's origin. |
|
Vector vNextOrigin; |
|
m_pNextKeyFrame->GetOrigin( vNextOrigin ); |
|
m_CullBox.UpdateBounds(vNextOrigin); |
|
|
|
// Expand the bbox by the points on our line. |
|
for ( int i=0; i < MAX_LINE_POINTS; i++ ) |
|
{ |
|
m_CullBox.UpdateBounds(m_LinePoints[i]); |
|
} |
|
} |
|
|
|
m_BoundingBox = m_CullBox; |
|
|
|
// |
|
// Our 2D bounds are just a point, because we don't render in 2D. |
|
// |
|
m_Render2DBox.ResetBounds(); |
|
m_Render2DBox.UpdateBounds(m_Origin, m_Origin); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CMapClass * |
|
//----------------------------------------------------------------------------- |
|
CMapClass *CMapKeyFrame::Copy(bool bUpdateDependencies) |
|
{ |
|
CMapKeyFrame *pNew = new CMapKeyFrame; |
|
pNew->CopyFrom(this, bUpdateDependencies); |
|
return pNew; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pObj - |
|
// Output : CMapClass * |
|
//----------------------------------------------------------------------------- |
|
CMapClass *CMapKeyFrame::CopyFrom(CMapClass *pObj, bool bUpdateDependencies) |
|
{ |
|
CMapClass::CopyFrom(pObj, bUpdateDependencies); |
|
|
|
CMapKeyFrame *pFrom = dynamic_cast<CMapKeyFrame*>( pObj ); |
|
Assert( pFrom != NULL ); |
|
|
|
m_qAngles = pFrom->m_qAngles; |
|
m_Angles = pFrom->m_Angles; |
|
m_flSpeed = pFrom->m_flSpeed; |
|
m_flMoveTime = pFrom->m_flMoveTime; |
|
|
|
if (bUpdateDependencies) |
|
{ |
|
m_pNextKeyFrame = (CMapKeyFrame *)UpdateDependency(m_pNextKeyFrame, pFrom->m_pNextKeyFrame); |
|
} |
|
else |
|
{ |
|
m_pNextKeyFrame = pFrom->m_pNextKeyFrame; |
|
} |
|
|
|
m_bRebuildPath = true; |
|
|
|
return this; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: notifies the keyframe that it has been cloned |
|
// inserts the clone into the correct place in the keyframe list |
|
// Input : *pClone - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::OnClone( CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList ) |
|
{ |
|
CMapClass::OnClone( pClone, pWorld, OriginalList, NewList ); |
|
|
|
CMapKeyFrame *pNewKey = dynamic_cast<CMapKeyFrame*>( pClone ); |
|
Assert( pNewKey != NULL ); |
|
if ( !pNewKey ) |
|
return; |
|
|
|
CMapEntity *pEntity = dynamic_cast<CMapEntity*>( m_pParent ); |
|
CMapEntity *pNewEntity = dynamic_cast<CMapEntity*>( pClone->GetParent() ); |
|
|
|
// insert the newly created keyframe into the sequence |
|
|
|
// point the clone's next at what we were pointing at |
|
const char *nextKey = pEntity->GetKeyValue( "NextKey" ); |
|
if ( nextKey ) |
|
{ |
|
pNewEntity->SetKeyValue( "NextKey", nextKey ); |
|
} |
|
|
|
// create a new targetname for the clone |
|
char newName[128]; |
|
const char *oldName = pEntity->GetKeyValue( "targetname" ); |
|
if ( !oldName || oldName[0] == 0 ) |
|
oldName = "keyframe"; |
|
|
|
pWorld->GenerateNewTargetname( oldName, newName, sizeof( newName ), true, NULL ); |
|
pNewEntity->SetKeyValue( "targetname", newName ); |
|
|
|
// point the current keyframe at the clone |
|
pEntity->SetKeyValue( "NextKey", newName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called just after this object has been removed from the world so |
|
// that it can unlink itself from other objects in the world. |
|
// Input : pWorld - The world that we were just removed from. |
|
// bNotifyChildren - Whether we should forward notification to our children. |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren) |
|
{ |
|
CMapClass::OnRemoveFromWorld(pWorld, bNotifyChildren); |
|
|
|
// |
|
// Detach ourselves from the next keyframe in the path. |
|
// |
|
m_pNextKeyFrame = (CMapKeyFrame *)UpdateDependency(m_pNextKeyFrame, NULL); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *outQuat - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::GetQuatAngles( Quaternion &outQuat ) |
|
{ |
|
outQuat = m_qAngles; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Recalulates timings based on the new position |
|
// Input : *pfOrigin - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::SetOrigin( Vector& pfOrigin ) |
|
{ |
|
CMapClass::SetOrigin(pfOrigin); |
|
m_bRebuildPath = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Renders the connecting lines between the keyframes |
|
// Input : pRender - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::Render3D( CRender3D *pRender ) |
|
{ |
|
if ( m_bRebuildPath ) |
|
{ |
|
if (GetAnimator() != NULL) |
|
{ |
|
GetAnimator()->RebuildPath(); |
|
} |
|
} |
|
|
|
// only draw if we have a valid connection |
|
if ( m_pNextKeyFrame && m_flSpeed > 0 ) |
|
{ |
|
// only draw if we haven't already been drawn this frame |
|
if ( GetRenderFrame() != pRender->GetRenderFrame() ) |
|
{ |
|
pRender->PushRenderMode( RENDER_MODE_WIREFRAME ); |
|
|
|
SetRenderFrame( pRender->GetRenderFrame() ); |
|
|
|
Vector o1, o2; |
|
GetOrigin( o1 ); |
|
m_pNextKeyFrame->GetOrigin( o2 ); |
|
|
|
CMeshBuilder meshBuilder; |
|
CMatRenderContextPtr pRenderContext( MaterialSystemInterface() ); |
|
IMesh *pMesh = pRenderContext->GetDynamicMesh(); |
|
|
|
// draw connecting line going from green to red |
|
meshBuilder.Begin( pMesh, MATERIAL_LINE_STRIP, MAX_LINE_POINTS ); |
|
|
|
// start point |
|
meshBuilder.Color3f( 0, 1.0f, 0 ); |
|
meshBuilder.Position3f( o1[0], o1[1], o1[2] ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
for ( int i = 0; i < MAX_LINE_POINTS; i++ ) |
|
{ |
|
float red = (float)(i+1) / (float)MAX_LINE_POINTS; |
|
meshBuilder.Color3f( red, 1.0f - red, 0 ); |
|
meshBuilder.Position3f( m_LinePoints[i][0], m_LinePoints[i][1], m_LinePoints[i][2] ); |
|
meshBuilder.AdvanceVertex(); |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
|
|
pRender->PopRenderMode(); |
|
} |
|
} |
|
|
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the total time remaining in the animation sequence in seconds. |
|
//----------------------------------------------------------------------------- |
|
float CMapKeyFrame::GetRemainingTime( CMapObjectList *pVisited ) |
|
{ |
|
CMapObjectList Visited; |
|
if ( pVisited == NULL ) |
|
{ |
|
pVisited = &Visited; |
|
} |
|
|
|
// |
|
// Check for circularities. |
|
// |
|
if ( pVisited->Find( this ) != -1 ) |
|
{ |
|
return 0.0f; |
|
} |
|
|
|
pVisited->AddToTail( this ); |
|
|
|
if ( m_pNextKeyFrame ) |
|
{ |
|
return m_flMoveTime + m_pNextKeyFrame->GetRemainingTime( pVisited ); |
|
} |
|
|
|
return 0.0f; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CMapKeyFrame |
|
//----------------------------------------------------------------------------- |
|
CMapKeyFrame *CMapKeyFrame::NextKeyFrame( void ) |
|
{ |
|
if ( !m_pNextKeyFrame ) |
|
return this; |
|
|
|
return m_pNextKeyFrame; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Notifies that the entity this is attached to has had a key change |
|
// Input : key - |
|
// value - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::OnParentKeyChanged( const char* key, const char* value ) |
|
{ |
|
if ( !stricmp(key, "NextKey") ) |
|
{ |
|
m_bRebuildPath = true; |
|
} |
|
else if ( !stricmp(key, "NextTime") ) |
|
{ |
|
m_flMoveTime = atof( value ); |
|
} |
|
else if ( !stricmp(key, "MoveSpeed") ) |
|
{ |
|
m_flSpeed = atof( value ); |
|
m_bRebuildPath = true; |
|
} |
|
else if (!stricmp(key, "angles")) |
|
{ |
|
sscanf(value, "%f %f %f", &m_Angles[PITCH], &m_Angles[YAW], &m_Angles[ROLL]); |
|
AngleQuaternion(m_Angles, m_qAngles); |
|
} |
|
|
|
if( m_pPositionInterpolator ) |
|
{ |
|
if( m_pPositionInterpolator->ProcessKey( key, value ) ) |
|
m_bRebuildPath = true; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: calculates the time the current key frame should take, given |
|
// the movement speed, and the distance to the next keyframe |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::RecalculateTimeFromSpeed( void ) |
|
{ |
|
if ( m_flSpeed <= 0 ) |
|
return; |
|
|
|
if ( !m_pNextKeyFrame ) |
|
return; |
|
|
|
// calculate the distance to the next key |
|
Vector o1; |
|
m_pNextKeyFrame->GetOrigin( o1 ); |
|
|
|
Vector o2 = o1 - m_Origin; |
|
float dist = VectorLength( o2 ); |
|
|
|
// couldn't get time from distance, get it from rotation instead |
|
if ( !dist ) |
|
{ |
|
// speed is in degrees per second |
|
// find the largest rotation component and use that |
|
QAngle ang = m_Angles - m_pNextKeyFrame->m_Angles; |
|
dist = 0; |
|
for ( int i = 0; i < 3; i++ ) |
|
{ |
|
fixang( ang[i] ); |
|
if ( ang[i] > 180 ) |
|
ang[i] = ang[i] - 360; |
|
|
|
if ( abs(ang[i]) > dist ) |
|
{ |
|
dist = abs(ang[i]); |
|
} |
|
} |
|
} |
|
|
|
// time = distance / speed |
|
float newTime = dist / m_flSpeed; |
|
|
|
// set the new speed (99.99% of the time this is the same so don't |
|
// bother forcing it to rebuild the path). |
|
if( m_flMoveTime != newTime ) |
|
{ |
|
m_flMoveTime = newTime; |
|
|
|
// rebuild the path before we next render |
|
m_bRebuildPath = true; |
|
} |
|
|
|
// "NextTime" key removed until we get a real-time updating entity properties dialog |
|
/* |
|
CMapEntity *ent = dynamic_cast<CMapEntity*>( Parent ); |
|
if ( ent ) |
|
{ |
|
char buf[16]; |
|
sprintf( buf, "%.2f", newTime ); |
|
ent->SetKeyValue( "NextTime", buf ); |
|
ent->OnParentKeyChanged( "NextTime", buf ); |
|
} |
|
*/ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Builds the spline points between this keyframe and the previous |
|
// keyframe. |
|
// Input : pPrev - |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::BuildPathSegment( CMapKeyFrame *pPrev ) |
|
{ |
|
RecalculateTimeFromSpeed(); |
|
|
|
CMapAnimator *pAnim = GetAnimator(); |
|
|
|
Quaternion qAngles; |
|
for ( int i = 0; i < MAX_LINE_POINTS; i++ ) |
|
{ |
|
if (pAnim != NULL) |
|
{ |
|
CMapAnimator::GetAnimationAtTime( this, pPrev, MoveTime() * ( float )( i + 1 ) / (float)MAX_LINE_POINTS, m_LinePoints[i], qAngles, pAnim->m_iPositionInterpolator, pAnim->m_iRotationInterpolator ); |
|
} |
|
else |
|
{ |
|
// FIXME: If we aren't connected to an animator yet, just draw straight lines. This code is never hit, because |
|
// BuildPathSegment is only called from CMapAnimator. To make matters worse, we can only reliably find |
|
// pPrev through an animator. |
|
CMapAnimator::GetAnimationAtTime( this, pPrev, MoveTime() * (float)( i + 1) / (float)MAX_LINE_POINTS, m_LinePoints[i], qAngles, 0, 0 ); |
|
} |
|
} |
|
|
|
// HACK: we shouldn't need to do this. CalcBounds alone should work (but it doesn't because of where we |
|
// call RebuildPath from). Make this work more like other objects. |
|
if ( m_pParent ) |
|
{ |
|
GetParent()->CalcBounds( true ); |
|
} |
|
else |
|
{ |
|
CalcBounds(); |
|
} |
|
|
|
m_bRebuildPath = false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when an object that we depend on has changed. |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType) |
|
{ |
|
CMapClass::OnNotifyDependent(pObject, eNotifyType); |
|
|
|
if ((pObject == m_pAnimator) && (eNotifyType == Notify_Removed)) |
|
{ |
|
SetAnimator(NULL); |
|
} |
|
|
|
// |
|
// If our next keyframe was deleted, try to link to the one after it. |
|
// |
|
if ((pObject == m_pNextKeyFrame) && (eNotifyType == Notify_Removed)) |
|
{ |
|
CMapEntity *pNextParent = m_pNextKeyFrame->GetParentEntity(); |
|
CMapEntity *pParent = GetParentEntity(); |
|
|
|
if ( pNextParent && pParent ) |
|
{ |
|
const char *szNext = pNextParent->GetKeyValue("NextKey"); |
|
pParent->SetKeyValue("NextKey", szNext); |
|
} |
|
} |
|
|
|
m_bRebuildPath = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: returns a pointer to our parent entity |
|
// Output : CMapEntity |
|
//----------------------------------------------------------------------------- |
|
CMapEntity *CMapKeyFrame::GetParentEntity( void ) |
|
{ |
|
return dynamic_cast<CMapEntity*>( m_pParent ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CMapKeyFrame::IsAnyKeyInSequenceSelected( void ) |
|
{ |
|
if ( m_pParent && m_pParent->IsSelected() ) |
|
{ |
|
return true; |
|
} |
|
|
|
// search forward |
|
for ( CMapKeyFrame *find = m_pAnimator; find != NULL; find = find->m_pNextKeyFrame ) |
|
{ |
|
if ( find->m_pParent && find->m_pParent->IsSelected() ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
// no selected items found |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : iInterpolator - |
|
// Output : IPositionInterpolator |
|
//----------------------------------------------------------------------------- |
|
IPositionInterpolator* CMapKeyFrame::SetupPositionInterpolator( int iInterpolator ) |
|
{ |
|
if( iInterpolator != m_iPositionInterpolator ) |
|
{ |
|
if( m_pPositionInterpolator ) |
|
m_pPositionInterpolator->Release(); |
|
|
|
m_pPositionInterpolator = Motion_GetPositionInterpolator( iInterpolator ); |
|
m_iPositionInterpolator = iInterpolator; |
|
|
|
// Feed keys.. |
|
CMapEntity *pEnt = GetParentEntity(); |
|
if( pEnt ) |
|
{ |
|
for ( int i=pEnt->GetFirstKeyValue(); i != pEnt->GetInvalidKeyValue(); i=pEnt->GetNextKeyValue( i ) ) |
|
{ |
|
m_pPositionInterpolator->ProcessKey( |
|
pEnt->GetKey( i ), |
|
pEnt->GetKeyValue( i ) ); |
|
} |
|
} |
|
} |
|
|
|
|
|
return m_pPositionInterpolator; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Marks that we need to relink any pointers defined by target/targetname pairs |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::UpdateDependencies(CMapWorld *pWorld, CMapClass *pObject) |
|
{ |
|
CMapClass::UpdateDependencies(pWorld, pObject); |
|
m_bRebuildPath = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::SetAnimator(CMapAnimator *pAnimator) |
|
{ |
|
m_pAnimator = (CMapAnimator *)UpdateDependency(m_pAnimator, pAnimator); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CMapKeyFrame::SetNextKeyFrame(CMapKeyFrame *pNext) |
|
{ |
|
m_pNextKeyFrame = (CMapKeyFrame *)UpdateDependency(m_pNextKeyFrame, pNext); |
|
} |
|
|
|
|
|
|