//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include "MapWorld.h" #include "GlobalFunctions.h" #include "MainFrm.h" #include "ToolOverlay.h" #include "MapDoc.h" #include "History.h" #include "CollisionUtils.h" #include "cmodel.h" #include "MapView3D.h" #include "MapView2D.h" #include "MapSolid.h" #include "Camera.h" #include "ObjectProperties.h" // FIXME: For ObjectProperties::RefreshData #include "Selection.h" // memdbgon must be the last include file in a .cpp file!!! #include #define OVERLAY_TOOL_SNAP_DISTANCE 35.0f //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CToolOverlay::CToolOverlay() { m_bDragging = false; m_pActiveOverlay = NULL; } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CToolOverlay::~CToolOverlay() { } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::OnActivate() { m_bDragging = false; m_pActiveOverlay = NULL; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::OnDeactivate() { } //----------------------------------------------------------------------------- // Purpose: Handles key down events in the 2D view. // Input : Per CWnd::OnKeyDown. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool CToolOverlay::OnKeyDown2D(CMapView2D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_ESCAPE: { ToolManager()->SetTool(TOOL_POINTER); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Handles key down events in the 3D view. // Input : Per CWnd::OnKeyDown. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool CToolOverlay::OnKeyDown3D(CMapView3D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_ESCAPE: { ToolManager()->SetTool(TOOL_POINTER); return true; } } return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::OnLMouseUp3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint ) { // Post drag events. PostDrag(); // Update the entity properties dialog. GetMainWnd()->pObjectProperties->MarkDataDirty(); return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::OnLMouseDown3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint ) { // Handle the overlay "handle" selection. if ( HandleSelection( pView, vPoint ) ) { PreDrag(); return true; } // Handle adding and removing overlay entities from the selection list. OverlaySelection( pView, nFlags, vPoint ); // Handle the overlay creation and placement (if we hit a solid). ULONG ulFace; CMapClass *pObject = NULL; if ( ( pObject = pView->NearestObjectAt( vPoint, ulFace ) ) != NULL ) { CMapSolid *pSolid = dynamic_cast( pObject ); if ( pSolid ) { return CreateOverlay( pSolid, ulFace, pView, vPoint ); } } return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::OnMouseMove3D( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint ) { if ( m_bDragging ) { bool bShift = ( ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0 ); // Build the ray and drag the overlay handle to the impact point. const CCamera *pCamera = pView->GetCamera(); if ( pCamera ) { Vector vecStart, vecEnd; pView->BuildRay( vPoint, vecStart, vecEnd ); OnDrag( vecStart, vecEnd, bShift ); } } return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::CreateOverlay( CMapSolid *pSolid, ULONG iFace, CMapView3D *pView, Vector2D point ) { // Build a ray to trace against the face that they clicked on to // find the point of intersection. Vector vecStart, vecEnd; pView->BuildRay( point, vecStart, vecEnd ); Vector vecHitPos, vecHitNormal; CMapFace *pFace = pSolid->GetFace( iFace ); if( pFace->TraceLine( vecHitPos, vecHitNormal, vecStart, vecEnd ) ) { // Create and initialize the "entity" --> "overlay." CMapEntity *pEntity = new CMapEntity; pEntity->SetKeyValue( "material", GetDefaultTextureName() ); pEntity->SetPlaceholder( TRUE ); pEntity->SetOrigin( vecHitPos ); pEntity->SetClass( "info_overlay" ); // Add the entity to the world. m_pDocument->AddObjectToWorld( pEntity ); // Setup "history." GetHistory()->MarkUndoPosition( NULL, "Create Overlay" ); GetHistory()->KeepNew( pEntity ); // Initialize the overlay. InitOverlay( pEntity, pFace ); pEntity->CalcBounds( TRUE ); // Add to selection list. m_pDocument->SelectObject( pEntity, scSelect ); m_bEmpty = false; // Set modified and update views. m_pDocument->SetModifiedFlag(); m_pShoreline = NULL; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CToolOverlay::InitOverlay( CMapEntity *pEntity, CMapFace *pFace ) { // Valid face? if ( !pFace ) return; const CMapObjectList *pChildren = pEntity->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { CMapClass *pMapClassObj = (CUtlReference< CMapClass >)pChildren->Element(pos); CMapOverlay *pOverlay = dynamic_cast( pMapClassObj ); if ( pOverlay ) { pOverlay->Basis_Init( pFace ); pOverlay->Handles_Init( pFace ); pOverlay->SideList_Init( pFace ); pOverlay->SetOverlayType( OVERLAY_TYPE_GENERIC ); pOverlay->SetLoaded( true ); pOverlay->CalcBounds( true ); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::OverlaySelection( CMapView3D *pView, UINT nFlags, const Vector2D &vPoint ) { CMapObjectList aSelectionList; m_pDocument->GetSelection()->ClearHitList(); // Find out how many (and what) map objects are under the point clicked on. HitInfo_t Objects[MAX_PICK_HITS]; int nHits = pView->ObjectsAt( vPoint, Objects, sizeof( Objects ) / sizeof( Objects[0] ) ); if ( nHits != 0 ) { // We now have an array of pointers to CMapAtoms. Any that can be upcast to CMapClass objects? for ( int iHit = 0; iHit < nHits; ++iHit ) { CMapClass *pMapClass = dynamic_cast( Objects[iHit].pObject ); if ( pMapClass ) { aSelectionList.AddToTail( pMapClass ); } } } // Did we hit anything? if ( !aSelectionList.Count() ) { m_pDocument->SelectFace( NULL, 0, scClear ); m_pDocument->SelectObject( NULL, scClear|scSaveChanges ); SetEmpty(); return; } // Find overlays. bool bUpdateViews = false; SelectMode_t eSelectMode = m_pDocument->GetSelection()->GetMode(); FOR_EACH_OBJ( aSelectionList, pos ) { CMapClass *pObject = aSelectionList.Element( pos ); CMapClass *pHitObject = pObject->PrepareSelection( eSelectMode ); if ( pHitObject ) { if ( pHitObject->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) ) { const CMapObjectList *pChildren = pHitObject->GetChildren(); FOR_EACH_OBJ( *pChildren, pos2 ) { CMapClass *pMapClassObj = (CUtlReference< CMapClass >)pChildren->Element(pos2); CMapOverlay *pOverlay = dynamic_cast( pMapClassObj ); if ( pOverlay ) { m_pDocument->GetSelection()->AddHit( pHitObject ); m_bEmpty = false; UINT cmd = scClear | scSelect | scSaveChanges; if (nFlags & MK_CONTROL) { cmd = scToggle; } m_pDocument->SelectObject( pHitObject, cmd ); bUpdateViews = true; } } } } } // Update the views. if ( bUpdateViews ) { m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::OnContextMenu2D( CMapView2D *pView, UINT nFlags, const Vector2D &vPoint ) { static CMenu menu, menuOverlay; static bool bInit = false; if ( !bInit ) { // Create the menu. menu.LoadMenu( IDR_POPUPS ); menuOverlay.Attach( ::GetSubMenu( menu.m_hMenu, 6 ) ); bInit = true; } if ( !pView->PointInClientRect(vPoint) ) return false; if (!IsEmpty()) { if ( HitTest( pView, vPoint, false ) ) { CPoint ptScreen( vPoint.x,vPoint.y); pView->ClientToScreen(&ptScreen); menuOverlay.TrackPopupMenu( TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_LEFTALIGN, ptScreen.x, ptScreen.y, pView ); } } return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::UpdateTranslation( const Vector &vUpdate, UINT nFlags) { // if( m_bBoxSelecting ) // return Box3D::UpdateTranslation( pt, nFlags ); return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::HandlesReset( void ) { // Go through selection list and reset overlay handles. const CMapObjectList *pSelection = m_pDocument->GetSelection()->GetList(); for( int iSelection = 0; iSelection < pSelection->Count(); ++iSelection ) { CMapClass *pMapClass = (CUtlReference< CMapClass >)pSelection->Element( iSelection ); if ( pMapClass && pMapClass->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) ) { const CMapObjectList *pChildren = pMapClass->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { CMapClass *pMapClassCast = (CUtlReference< CMapClass >)pChildren->Element( pos ); CMapOverlay *pOverlay = dynamic_cast( pMapClassCast ); if ( pOverlay ) { pOverlay->HandlesReset(); break; } } } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::HandleSelection( CMapView *pView, const Vector2D &vPoint ) { // Reset the hit overlay. m_pActiveOverlay = NULL; // Go through selection list and test all overlay's handles and set the // "hit" overlay current. const CMapObjectList *pSelection = m_pDocument->GetSelection()->GetList(); for ( int iSelection = 0; iSelection < pSelection->Count(); ++iSelection ) { CMapClass *pMapClass = (CUtlReference< CMapClass >)pSelection->Element( iSelection ); if ( pMapClass && pMapClass->IsMapClass( MAPCLASS_TYPE( CMapEntity ) ) ) { const CMapObjectList *pChildren = pMapClass->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { CMapClass *pMapClassCast = (CUtlReference< CMapClass >)pChildren->Element(pos); CMapOverlay *pOverlay = dynamic_cast( pMapClassCast ); if ( pOverlay && pOverlay->IsSelected() ) { if ( pOverlay->HandlesHitTest( pView, vPoint ) ) { m_pActiveOverlay = pOverlay; break; } } } } } if ( !m_pActiveOverlay ) return false; return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::HandleSnap( CMapOverlay *pOverlay, Vector &vecHandlePt ) { Vector vecTmp; for ( int i = 0; i < OVERLAY_HANDLES_COUNT; i++ ) { pOverlay->GetHandlePos( i, vecTmp ); vecTmp -= vecHandlePt; float flDist = vecTmp.Length(); if ( flDist < OVERLAY_TOOL_SNAP_DISTANCE ) { // Snap! pOverlay->GetHandlePos( i, vecHandlePt ); return true; } } return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CToolOverlay::HandleInBBox( CMapOverlay *pOverlay, Vector const &vecHandlePt ) { Vector vecMin, vecMax; pOverlay->GetCullBox( vecMin, vecMax ); for ( int iAxis = 0; iAxis < 3; iAxis++ ) { vecMin[iAxis] -= OVERLAY_TOOL_SNAP_DISTANCE; vecMax[iAxis] += OVERLAY_TOOL_SNAP_DISTANCE; if( ( vecHandlePt[iAxis] < vecMin[iAxis] ) || ( vecHandlePt[iAxis] > vecMax[iAxis] ) ) return false; } return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::SnapHandle( Vector &vecHandlePt ) { CMapWorld *pWorld = GetActiveWorld(); if ( !pWorld ) return; EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent( pos ); while ( pChild ) { CMapEntity *pEntity = dynamic_cast( pChild ); if ( pEntity ) { const CMapObjectList *pChildren = pEntity->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { CMapClass *pMapClassCast = (CUtlReference< CMapClass >)pChildren->Element(pos); CMapOverlay *pOverlay = dynamic_cast( pMapClassCast ); if ( pOverlay && pOverlay != m_pActiveOverlay && pOverlay->IsSelected() ) { // Intersection test and attempt to snap if ( HandleInBBox( pOverlay, vecHandlePt ) ) { if ( HandleSnap( pOverlay, vecHandlePt ) ) return; } } } } pChild = pWorld->GetNextDescendent( pos ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::OnDrag( Vector const &vecRayStart, Vector const &vecRayEnd, bool bShift ) { // Get the current overlay. CMapOverlay *pOverlay = m_pActiveOverlay; if ( !pOverlay ) return; // Get a list of faces and test for "impact." Vector vecImpact( 0.0f, 0.0f, 0.0f ); Vector vecImpactNormal( 0.0f, 0.0f, 0.0f ); CMapFace *pFace = NULL; int nFaceCount = pOverlay->GetFaceCount(); int iFace; for ( iFace = 0; iFace < nFaceCount; iFace++ ) { pFace = pOverlay->GetFace( iFace ); if ( pFace ) { if ( pFace->TraceLineInside( vecImpact, vecImpactNormal, vecRayStart, vecRayEnd ) ) break; } } // Test for impact (face index = count mean no impact). if ( iFace == nFaceCount ) return; if ( bShift ) { SnapHandle( vecImpact ); } // Pass the new handle position to the overlay. pOverlay->HandlesDragTo( vecImpact, pFace ); m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_ONLY_3D ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::PreDrag( void ) { m_bDragging = true; SetupHandleDragUndo(); } //----------------------------------------------------------------------------- // Purpose: Renders the cordon tool in the 3D view. //----------------------------------------------------------------------------- void CToolOverlay::RenderTool3D(CRender3D *pRender) { // TODO render tool handles here and not in overlay rendering code } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::PostDrag( void ) { if ( !m_bDragging ) return; m_bDragging = false; // Get the current overlay. CMapOverlay *pOverlay = m_pActiveOverlay; if ( pOverlay ) { pOverlay->DoClip(); pOverlay->CenterEntity(); pOverlay->PostUpdate( Notify_Changed ); m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS ); } // Reset the overlay handles. HandlesReset(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CToolOverlay::SetupHandleDragUndo( void ) { // Get the current overlay. CMapOverlay *pOverlay = m_pActiveOverlay; if ( pOverlay ) { CMapEntity *pEntity = ( CMapEntity* )pOverlay->GetParent(); if ( pEntity ) { // Setup for drag undo. GetHistory()->MarkUndoPosition( m_pDocument->GetSelection()->GetList(), "Drag Overlay Handle" ); GetHistory()->Keep( ( CMapClass* )pEntity ); } } }