//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: Rendering and mouse handling in the 2D view. // //===========================================================================// #include "stdafx.h" #include "hammer.h" #include "MapView2D.h" #include "MapView3D.h" #include "MapDoc.h" #include "Render2D.h" #include "ToolManager.h" #include "History.h" #include "TitleWnd.h" #include "mainfrm.h" #include "MapSolid.h" #include "ToolMorph.h" // FIXME: remove #include "MapWorld.h" #include "camera.h" #include "Manifest.h" #include "MapInstance.h" #include "Options.h" #include "..\FoW\FoW.h" // memdbgon must be the last include file in a .cpp file!!! #include extern bool g_bUpdateBones2D; static DrawType_t __eNextViewType = VIEW2D_XY; IMPLEMENT_DYNCREATE(CMapView2D, CMapView2DBase) BEGIN_MESSAGE_MAP(CMapView2D, CMapView2DBase) //{{AFX_MSG_MAP(CMapView2D) ON_WM_KEYDOWN() ON_COMMAND(ID_VIEW_2DXY, OnView2dxy) ON_COMMAND(ID_VIEW_2DYZ, OnView2dyz) ON_COMMAND(ID_VIEW_2DXZ, OnView2dxz) ON_COMMAND_EX(ID_TOOLS_ALIGNTOP, OnToolsAlign) ON_COMMAND_EX(ID_TOOLS_ALIGNBOTTOM, OnToolsAlign) ON_COMMAND_EX(ID_TOOLS_ALIGNLEFT, OnToolsAlign) ON_COMMAND_EX(ID_TOOLS_ALIGNRIGHT, OnToolsAlign) ON_COMMAND_EX(ID_FLIP_HORIZONTAL, OnFlip) ON_COMMAND_EX(ID_FLIP_VERTICAL, OnFlip) ON_UPDATE_COMMAND_UI(ID_FLIP_HORIZONTAL, OnUpdateEditSelection) ON_UPDATE_COMMAND_UI(ID_FLIP_VERTICAL, OnUpdateEditSelection) ON_UPDATE_COMMAND_UI(ID_TOOLS_ALIGNTOP, OnUpdateEditSelection) ON_UPDATE_COMMAND_UI(ID_TOOLS_ALIGNBOTTOM, OnUpdateEditSelection) ON_UPDATE_COMMAND_UI(ID_TOOLS_ALIGNLEFT, OnUpdateEditSelection) ON_UPDATE_COMMAND_UI(ID_TOOLS_ALIGNRIGHT, OnUpdateEditSelection) //}}AFX_MSG_MAP END_MESSAGE_MAP() //----------------------------------------------------------------------------- // Purpose: Allows for iteration of draw types in order. // Input : eDrawType - Current draw type. // Output : Returns the next draw type in the list: XY, YZ, XZ. List wraps. //----------------------------------------------------------------------------- static DrawType_t NextDrawType(DrawType_t eDrawType) { if (eDrawType == VIEW2D_XY) { return(VIEW2D_YZ); } if (eDrawType == VIEW2D_YZ) { return(VIEW2D_XZ); } return(VIEW2D_XY); } //----------------------------------------------------------------------------- // Purpose: Allows for iteration of draw types in reverse order. // Input : eDrawType - Current draw type. // Output : Returns the previous draw type in the list: XY, YZ, XZ. List wraps. //----------------------------------------------------------------------------- static DrawType_t PrevDrawType(DrawType_t eDrawType) { if (eDrawType == VIEW2D_XY) { return(VIEW2D_XZ); } if (eDrawType == VIEW2D_YZ) { return(VIEW2D_XY); } return(VIEW2D_YZ); } //----------------------------------------------------------------------------- // Purpose: Constructor. Initializes data members. // --------------------------------------------------------------------------- CMapView2D::CMapView2D(void) { // // Create next 2d view type. // __eNextViewType = NextDrawType(__eNextViewType); SetDrawType(__eNextViewType); m_bUpdateRenderObjects = true; m_bLastActiveView = false; } //----------------------------------------------------------------------------- // Purpose: Destructor. Frees dynamically allocated resources. //----------------------------------------------------------------------------- CMapView2D::~CMapView2D(void) { if ( GetMapDoc() ) { GetMapDoc()->RemoveMRU(this); } } //----------------------------------------------------------------------------- // Purpose: First-time initialization of this view. //----------------------------------------------------------------------------- void CMapView2D::OnInitialUpdate(void) { // NOTE: This must occur becore OnInitialUpdate // Creates the title window CreateTitleWindow(); // NOTE: This must occur becore OnInitialUpdate // Other initialization. SetDrawType( GetDrawType() ); CMapView2DBase::OnInitialUpdate(); // Add to doc's MRU list CMapDoc *pDoc = GetMapDoc(); pDoc->SetMRU(this); } //----------------------------------------------------------------------------- // Purpose: // Input : *pRender - //----------------------------------------------------------------------------- void CMapView2D::DrawPointFile( CRender2D *pRender ) { pRender->SetDrawColor( 255,0,0 ); int nPFPoints = GetMapDoc()->m_PFPoints.Count(); Vector* pPFPoints = GetMapDoc()->m_PFPoints.Base(); pRender->MoveTo( pPFPoints[0] ); for(int i = 1; i < nPFPoints; i++) { pRender->DrawLineTo( pPFPoints[i] ); } } //----------------------------------------------------------------------------- // Called when the base class wants the render lists to be recomputed //----------------------------------------------------------------------------- void CMapView2D::OnRenderListDirty() { m_bUpdateRenderObjects = true; } //----------------------------------------------------------------------------- // Purpose: Sorts the object to be rendered into one of two groups: normal objects // and selected objects, so that selected objects can be rendered last. // Input : pObject - // pRenderList - // Output : Returns TRUE on success, FALSE on failure. //----------------------------------------------------------------------------- void CMapView2D::AddToRenderLists(CMapClass *pObject) { if ( !pObject->IsVisible() ) return; // Don't render groups, render their children instead. if ( !pObject->IsGroup() ) { if ( !pObject->IsVisible2D() ) return; Vector vecMins, vecMaxs; pObject->GetCullBox( vecMins, vecMaxs ); if ( IsValidBox( vecMins, vecMaxs ) ) { // Make sure the object is in the update region. if ( !IsInClientView(vecMins, vecMaxs) ) return; } m_RenderList.AddToTail(pObject); } // Recurse into children and add them. const CMapObjectList *pChildren = pObject->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { AddToRenderLists((CUtlReference< CMapClass >)pChildren->Element(pos)); } } //----------------------------------------------------------------------------- // Purpose: horribly inefficient rendering mechanism for FoW. Demonstration purposes only! //----------------------------------------------------------------------------- void CMapView2D::RenderFoW( void ) { CRender2D *pRender = GetRender(); CFoW *pFoW = GetMapDoc()->GetFoW(); Vector MinCoord, MaxCoord, MinDrawCoord, MaxDrawCoord; Vector2D DrawMins, DrawMaxs; int GridSize = pFoW->GetHorizontalGridSize(); int MidPoint = GridSize / 2; Color color; pFoW->GetSize( MinCoord, MaxCoord ); MinDrawCoord.z = MaxDrawCoord.z = 0; // pFoW->SnapCoordsToGrid( m_ViewMin, MinCoord, true ); // pFoW->SnapCoordsToGrid( m_ViewMax, MaxCoord, false ); pRender->PushRenderMode( RENDER_MODE_FLAT_NOZ ); bool bPopMode = pRender->BeginClientSpace(); for( int x = MinCoord.x; x < MaxCoord.x; x += GridSize ) { MinDrawCoord.x = x; MaxDrawCoord.x = x + GridSize - 1; for( int y = MinCoord.y; y < MaxCoord.y; y += GridSize ) { float Degree = pFoW->LookupVisibilityDegree( x + MidPoint, y + MidPoint, 0 ); Degree = ( ( Degree * 0.25f ) + 0.0f ) * 255; if ( Degree >= 0.95f ) { continue; } color.SetColor( 255, 0, 255, Degree ); pRender->SetDrawColor( color ); MaxDrawCoord.y = y; MinDrawCoord.y = y + GridSize - 1; pRender->TransformPoint( DrawMins, MinDrawCoord ); pRender->TransformPoint( DrawMaxs, MaxDrawCoord ); pRender->DrawFilledRect( DrawMins, DrawMaxs, ( byte * )&color, false ); } } color.SetColor( 255, 255, 255, 255 ); pRender->SetDrawColor( color ); if ( bPopMode ) { pRender->EndClientSpace(); } pRender->PopRenderMode(); } //----------------------------------------------------------------------------- // Purpose: // Input : rectUpdate - //----------------------------------------------------------------------------- void CMapView2D::Render() { // When in Foundry mode, the engine's vgui loop can cause Hammer's vgui windows to want to be drawn and we don't want that. // In any case, don't draw unless we're inside CVGuiWnd::DrawVGuiPanel or else windows and viewports will be all wrong. if ( !m_bIsDrawing ) return; CMapDoc *pDoc = GetMapDoc(); CMapWorld *pWorld = pDoc->GetMapWorld(); CManifest *pManifest = pDoc->GetManifest(); GetRender()->StartRenderFrame( false ); if ( pManifest ) { pWorld = pManifest->GetManifestWorld(); } if ( Options.general.bRadiusCulling ) { DrawCullingCircleHelper2D( GetRender() ); } // Draw grid if enabled. if (pDoc->m_bShowGrid) { DrawGrid( GetRender(), axHorz, axVert, 0 ); } // // Draw the world if we have one. // if (pWorld == NULL) return; // Traverse the entire world, sorting visible elements into two arrays: // Normal objects and selected objects, so that we can render the selected // objects last. // if ( m_bUpdateRenderObjects ) { m_RenderList.RemoveAll(); // fill render lists with visible objects AddToRenderLists( pWorld ); g_bUpdateBones2D = true; } // // Render normal (nonselected) objects first // GetRender()->PrepareInstanceStencil(); CUtlVector selectedObjects; CUtlVector helperObjects; for (int i = 0; i < m_RenderList.Count(); i++) { CMapClass *pObject = m_RenderList[i]; if ( pObject->IsSelected() ) { // render later if ( pObject->GetToolObject(0,false) ) { helperObjects.AddToTail( pObject ); } else { selectedObjects.AddToTail( pObject ); } } else { // render now pObject->Render2D( GetRender() ); } } // // Render selected objects in second batch, so they overdraw normal object // for (int i = 0; i < selectedObjects.Count(); i++) { selectedObjects[i]->Render2D( GetRender() ); } GetRender()->DrawInstanceStencil(); if ( pDoc->GetFoW() && m_eDrawType == VIEW2D_XY ) { // only render the fog of war on the 2d top down view RenderFoW(); } // // Draw pointfile if enabled. // if (pDoc->m_PFPoints.Count()) { DrawPointFile( GetRender() ); } pDoc->RenderDocument( GetRender() ); m_bUpdateRenderObjects = false; g_bUpdateBones2D = false; // render all tools CBaseTool *pCurTool = m_pToolManager->GetActiveTool(); // render active tool if ( pCurTool ) { pCurTool->RenderTool2D( GetRender() ); } // render map helpers at last for (int i = 0; i < helperObjects.Count(); i++) { helperObjects[i]->Render2D( GetRender() ); } GetRender()->EndRenderFrame(); } //----------------------------------------------------------------------------- // Purpose: this function will render an instance map at the specific offset and rotation // Input : pInstanceClass - the map class of the func_instance // pMapClass - the map class of the world spawn of the instance // InstanceOrigin - the translation offset // InstanceAngles - the axis rotation // Output : none //----------------------------------------------------------------------------- void CMapView2D::RenderInstance( CMapInstance *pInstanceClass, CMapClass *pMapClass, Vector &InstanceOrigin, QAngle &InstanceAngles ) { if ( !pInstanceClass->IsInstanceVisible() ) { return; } GetRender()->PushInstanceData( pInstanceClass, InstanceOrigin, InstanceAngles ); RenderInstanceMapClass_r( pMapClass ); GetRender()->PopInstanceData(); } //----------------------------------------------------------------------------- // Purpose: this function will recursively render an instance and all of its children // Input : pObject - the object to be rendered // Output : none //----------------------------------------------------------------------------- void CMapView2D::RenderInstanceMapClass_r( CMapClass *pObject ) { if ( !pObject->IsVisible() ) { return; } // Don't render groups, render their children instead. if ( !pObject->IsGroup() ) { if ( !pObject->IsVisible2D() ) { return; } Vector vecMins, vecMaxs, vecExpandedMins, vecExpandedMaxs, vecOrigin; pObject->GetCullBox( vecMins, vecMaxs ); GetRender()->TransformInstanceAABB( vecMins, vecMaxs, vecExpandedMins, vecExpandedMaxs ); if ( IsValidBox( vecExpandedMins, vecExpandedMaxs ) ) { // Make sure the object is in the update region. if ( !IsInClientView( vecExpandedMins, vecExpandedMaxs ) ) { return; } } pObject->Render2D( GetRender() ); } // Recurse into children and add them. const CMapObjectList *pChildren = pObject->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { RenderInstanceMapClass_r((CUtlReference< CMapClass >)pChildren->Element(pos)); } } //----------------------------------------------------------------------------- // Purpose: // Input : m_DrawType - // bForceUpdate - //----------------------------------------------------------------------------- void CMapView2D::SetDrawType(DrawType_t drawType) { Vector vOldView; // reset old third axis to selection center level m_pCamera->GetViewPoint( vOldView ); CMapDoc *pDoc = GetMapDoc(); if ( pDoc && !pDoc->GetSelection()->IsEmpty() ) { Vector vCenter; pDoc->GetSelection()->GetBoundsCenter( vCenter ); vOldView[axThird] = vCenter[axThird]; } else { vOldView[axThird] = 0; } switch (drawType) { case VIEW2D_XY: SetAxes(AXIS_X, FALSE, AXIS_Y, TRUE); if ( HasTitleWnd() ) { GetTitleWnd()->SetTitle("top (x/y)"); } break; case VIEW2D_YZ: SetAxes(AXIS_Y, FALSE, AXIS_Z, TRUE); if ( HasTitleWnd() ) { GetTitleWnd()->SetTitle("front (y/z)"); } break; case VIEW2D_XZ: SetAxes(AXIS_X, FALSE, AXIS_Z, TRUE); if ( HasTitleWnd() ) { GetTitleWnd()->SetTitle("side (x/z)"); } break; } m_eDrawType = drawType; m_pCamera->SetViewPoint( vOldView ); UpdateClientView(); if (m_bLastActiveView && GetMapDoc()) { GetMapDoc()->UpdateTitle(this); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapView2D::OnView2dxy(void) { SetDrawType(VIEW2D_XY); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapView2D::OnView2dyz(void) { SetDrawType(VIEW2D_YZ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapView2D::OnView2dxz(void) { SetDrawType(VIEW2D_XZ); } //----------------------------------------------------------------------------- // Purpose: // Input : bActivate - // pActivateView - // pDeactiveView - //----------------------------------------------------------------------------- void CMapView2D::ActivateView(bool bActivate) { CMapView2DBase::ActivateView( bActivate ); if ( bActivate ) { CMapDoc *pDoc = GetMapDoc(); pDoc->SetMRU(this); // tell doc to update title m_bLastActiveView = true; } else { m_bLastActiveView = false; } } //----------------------------------------------------------------------------- // Purpose: // Input : nID - // Output : Returns TRUE on success, FALSE on failure. //----------------------------------------------------------------------------- BOOL CMapView2D::OnToolsAlign(UINT nID) { CMapDoc *pDoc = GetMapDoc(); CSelection *pSelection = pDoc->GetSelection(); const CMapObjectList *pSelList = pSelection->GetList(); GetHistory()->MarkUndoPosition(pSelList, "Align"); GetHistory()->Keep(pSelList); // convert nID into the appropriate ID_TOOLS_ALIGNxxx define // taking into consideration the orientation of the axes if(nID == ID_TOOLS_ALIGNTOP && bInvertVert) nID = ID_TOOLS_ALIGNBOTTOM; else if(nID == ID_TOOLS_ALIGNBOTTOM && bInvertVert) nID = ID_TOOLS_ALIGNTOP; else if(nID == ID_TOOLS_ALIGNLEFT && bInvertHorz) nID = ID_TOOLS_ALIGNRIGHT; else if(nID == ID_TOOLS_ALIGNRIGHT && bInvertHorz) nID = ID_TOOLS_ALIGNLEFT; // use boundbox of selection - move all objects to match extreme // side of all the objects BoundBox box; pSelection->GetBounds(box.bmins, box.bmaxs); Vector ptMove( 0, 0, 0 ); for (int i = 0; i < pSelList->Count(); i++) { CMapClass *pObject = (CUtlReference< CMapClass >)pSelList->Element(i); Vector vecMins; Vector vecMaxs; pObject->GetRender2DBox(vecMins, vecMaxs); // align top if (nID == ID_TOOLS_ALIGNTOP) { ptMove[axVert] = box.bmins[axVert] - vecMins[axVert]; } else if (nID == ID_TOOLS_ALIGNBOTTOM) { ptMove[axVert] = box.bmaxs[axVert] - vecMaxs[axVert]; } else if (nID == ID_TOOLS_ALIGNLEFT) { ptMove[axHorz] = box.bmins[axHorz] - vecMins[axHorz]; } else if (nID == ID_TOOLS_ALIGNRIGHT) { ptMove[axHorz] = box.bmaxs[axHorz] - vecMaxs[axHorz]; } pObject->TransMove(ptMove); } pDoc->SetModifiedFlag(); return TRUE; } //----------------------------------------------------------------------------- // Purpose: Flips the selection horizontally or vertically (with respect to the // view orientation. // Input : nID - // Output : Returns TRUE on success, FALSE on failure. //----------------------------------------------------------------------------- BOOL CMapView2D::OnFlip(UINT nID) { CMapDoc *pDoc = GetMapDoc(); CSelection *pSelection = pDoc->GetSelection(); const CMapObjectList *pSelList = pSelection->GetList(); if ( pSelection->IsEmpty() ) { return TRUE; // no selection } // flip objects from center of selection Vector ptRef, vScale(1,1,1); pSelection->GetBoundsCenter(ptRef); // never about this axis: if (nID == ID_FLIP_HORIZONTAL) { vScale[axHorz] = -1; } else if (nID == ID_FLIP_VERTICAL) { vScale[axVert] = -1; } GetHistory()->MarkUndoPosition( pSelList, "Flip Objects"); GetHistory()->Keep(pSelList); // do flip for (int i = 0; i < pSelList->Count(); i++) { CMapClass *pObject = (CUtlReference< CMapClass >)pSelList->Element(i); pObject->TransScale(ptRef,vScale); } pDoc->SetModifiedFlag(); return TRUE; } //----------------------------------------------------------------------------- // Purpose: Manages the state of the Copy menu item. //----------------------------------------------------------------------------- void CMapView2D::OnUpdateEditSelection(CCmdUI *pCmdUI) { pCmdUI->Enable((!GetMapDoc()->GetSelection()->IsEmpty()) && (m_pToolManager->GetActiveToolID() != TOOL_FACEEDIT_MATERIAL) && !GetMainWnd()->IsShellSessionActive()); } void CMapView2D::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if (nChar == VK_TAB) { // swicth to next draw type SetDrawType( NextDrawType( m_eDrawType ) ); return; } CMapView2DBase::OnKeyDown( nChar, nRepCnt, nFlags ); } void CMapView2D::DrawCullingCircleHelper2D( CRender2D *pRender ) { CMapDoc *pDoc = GetMapDoc(); POSITION viewpos = pDoc->GetFirstViewPosition(); while ( viewpos ) { CMapView3D *pView = dynamic_cast( pDoc->GetNextView( viewpos ) ); if ( pView ) { CCamera *pCamera = pView->GetCamera(); Vector cameraPos; pCamera->GetViewPoint( cameraPos ); int iClipDist = (int)pCamera->GetFarClip(); pRender->SetDrawColor( 255, 0, 0 ); pRender->DrawCircle( cameraPos, iClipDist ); } } }