//========= 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 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( 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( pClone ); Assert( pNewKey != NULL ); if ( !pNewKey ) return; CMapEntity *pEntity = dynamic_cast( m_pParent ); CMapEntity *pNewEntity = dynamic_cast( 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( 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( 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); }