//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "stdafx.h" #include "GlobalFunctions.h" #include "History.h" #include "MapDefs.h" #include "MapDoc.h" #include "MapFace.h" #include "MapSolid.h" #include "MapView2D.h" #include "MapWorld.h" #include "Options.h" #include "Render2D.h" #include "Render3D.h" #include "RenderUtils.h" #include "StatusBarIDs.h" // dvs: remove #include "ToolClipper.h" #include "ToolManager.h" #include "vgui/Cursor.h" #include "Selection.h" // memdbgon must be the last include file in a .cpp file!!! #include #pragma warning( disable:4244 ) //============================================================================= // // Friend Function (for MapClass->EnumChildren Callback) // //----------------------------------------------------------------------------- // Purpose: This function creates a new clip group with the given solid as // the original solid. // Input: pSolid - the original solid to put in the clip list // pClipper - the clipper tool // Output: successful?? (true/false) //----------------------------------------------------------------------------- BOOL AddToClipList( CMapSolid *pSolid, Clipper3D *pClipper ) { CClipGroup *pClipGroup = new CClipGroup; if( !pClipGroup ) return false; pClipGroup->SetOrigSolid( pSolid ); pClipper->m_ClipResults.AddToTail( pClipGroup ); return true; } //============================================================================= // // CClipGroup // //----------------------------------------------------------------------------- // Purpose: Destructor. Gets rid of the unnecessary clip solids. //----------------------------------------------------------------------------- CClipGroup::~CClipGroup() { delete m_pClipSolids[0]; delete m_pClipSolids[1]; } //----------------------------------------------------------------------------- // Purpose: constructor - initialize the clipper variables //----------------------------------------------------------------------------- Clipper3D::Clipper3D(void) { m_Mode = FRONT; m_ClipPlane.normal.Init(); m_ClipPlane.dist = 0.0f; m_ClipPoints[0].Init(); m_ClipPoints[1].Init(); m_ClipPointHit = -1; m_pOrigObjects = NULL; m_bDrawMeasurements = false; SetEmpty(); } //----------------------------------------------------------------------------- // Purpose: deconstructor //----------------------------------------------------------------------------- Clipper3D::~Clipper3D(void) { } //----------------------------------------------------------------------------- // Purpose: Called when the tool is activated. // Input : eOldTool - The ID of the previously active tool. //----------------------------------------------------------------------------- void Clipper3D::OnActivate() { if (IsActiveTool()) { // // Already the active tool - toggle the mode. // IterateClipMode(); } } //----------------------------------------------------------------------------- // Purpose: Called when the tool is deactivated. // Input : eNewTool - The ID of the tool that is being activated. //----------------------------------------------------------------------------- void Clipper3D::OnDeactivate() { SetEmpty(); } //----------------------------------------------------------------------------- // Purpose: (virtual imp) This function handles the "dragging" of the mouse // while the left mouse button is depressed. It updates the position // of the clippoing plane point selected in the StartTranslation // function. This function rebuilds the clipping plane and updates // the clipping solids when necessary. // Input: pt - current location of the mouse in the 2DView // uFlags - constrained clipping plane point movement // *dragSize - not used in the virtual implementation // Output: success of translation (TRUE/FALSE) //----------------------------------------------------------------------------- bool Clipper3D::UpdateTranslation( const Vector &vUpdate, UINT uFlags ) { // sanity check if( IsEmpty() ) return false; Vector vNewPos = m_vOrgPos + vUpdate; // snap point if need be if ( uFlags & constrainSnap ) m_pDocument->Snap( vNewPos, uFlags ); // // update clipping point positions // if ( m_ClipPoints[m_ClipPointHit] == vNewPos ) return false; if( uFlags & constrainMoveAll ) { // // calculate the point and delta - to move both clip points simultaneously // Vector delta = vNewPos - m_ClipPoints[m_ClipPointHit]; m_ClipPoints[(m_ClipPointHit+1)%2] += delta; } m_ClipPoints[m_ClipPointHit] = vNewPos; // build the new clip plane and update clip results BuildClipPlane(); GetClipResults(); m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_TOOL ); return true; } //----------------------------------------------------------------------------- // Purpose: (virtual imp) This function defines all finishing functionality // necessary at the end of a clipping action. Nothing really!!! // Input : bSave - passed along the the Tool finish translation call //----------------------------------------------------------------------------- void Clipper3D::FinishTranslation( bool bSave ) { // get the clip results -- in case the update is a click and not a drag GetClipResults(); Tool3D::FinishTranslation( bSave ); } //----------------------------------------------------------------------------- // Purpose: iterate through the types of clipping modes, update after an // iteration takes place to visualize the new clip results //----------------------------------------------------------------------------- void Clipper3D::IterateClipMode( void ) { // // increment the clipping mode (wrap when necessary) // m_Mode++; if( m_Mode > BOTH ) { m_Mode = FRONT; } // update the clipped objects based on the mode GetClipResults(); } //----------------------------------------------------------------------------- // Purpose: This resets the solids to clip (the original list) and calls the // CalcClipResults function to generate new "clip" solids //----------------------------------------------------------------------------- void Clipper3D::GetClipResults( void ) { // reset the clip list to the original solid lsit SetClipObjects( m_pOrigObjects ); // calculate the clipped objects based on the current "clip plane" CalcClipResults(); } //----------------------------------------------------------------------------- // Purpose: This function allows one to specifically set the clipping plane // information, as opposed to building a clip plane during "translation" // Input: pPlane - the plane information used to create the clip plane //----------------------------------------------------------------------------- void Clipper3D::SetClipPlane( PLANE *pPlane ) { // // copy the clipping plane info // m_ClipPlane.normal = pPlane->normal; m_ClipPlane.dist = pPlane->dist; } //----------------------------------------------------------------------------- // Purpose: This function builds a clipping plane based on the clip point // locations manipulated in the "translation" functions and the 2DView //----------------------------------------------------------------------------- void Clipper3D::BuildClipPlane( void ) { // calculate the up vector Vector upVect = m_vPlaneNormal; // calculate the right vector Vector rightVect; VectorSubtract( m_ClipPoints[1], m_ClipPoints[0], rightVect ); // calculate the forward (normal) vector Vector forwardVect; CrossProduct( upVect, rightVect, forwardVect ); VectorNormalize( forwardVect ); // // save the clip plane info // m_ClipPlane.normal = forwardVect; m_ClipPlane.dist = DotProduct( m_ClipPoints[0], forwardVect ); } //----------------------------------------------------------------------------- // Purpose: This functions sets up the list of objects to be clipped. // Initially the list is passed in (typically a Selection set). On // subsequent "translation" updates the list is refreshed from the // m_pOrigObjects list. // Input: pList - the list of objects (solids) to be clipped //----------------------------------------------------------------------------- void Clipper3D::SetClipObjects( const CMapObjectList *pList ) { // check for an empty list if( !pList ) return; // save the original list m_pOrigObjects = pList; // clear the clip results list ResetClipResults(); // // copy solids into the clip list // FOR_EACH_OBJ( *m_pOrigObjects, pos ) { CMapClass *pObject = (CUtlReference< CMapClass >)m_pOrigObjects->Element( pos ); if( !pObject ) continue; if( pObject->IsMapClass( MAPCLASS_TYPE( CMapSolid ) ) ) { AddToClipList( ( CMapSolid* )pObject, this ); } pObject->EnumChildren( ENUMMAPCHILDRENPROC( AddToClipList ), DWORD( this ), MAPCLASS_TYPE( CMapSolid ) ); } // the clipping list is not empty anymore m_bEmpty = false; } //----------------------------------------------------------------------------- // Purpose: This function calculates based on the defined or given clipping // plane and clipping mode the new clip solids. //----------------------------------------------------------------------------- void Clipper3D::CalcClipResults( void ) { // sanity check if( IsEmpty() ) return; // // iterate through and clip all of the solids in the clip list // FOR_EACH_OBJ( m_ClipResults, pos ) { CClipGroup *pClipGroup = m_ClipResults.Element( pos ); CMapSolid *pOrigSolid = pClipGroup->GetOrigSolid(); if( !pOrigSolid ) continue; // // check the modes for which solids to generate // CMapSolid *pFront = NULL; CMapSolid *pBack = NULL; if( m_Mode == FRONT ) { pOrigSolid->Split( &m_ClipPlane, &pFront, NULL ); } else if( m_Mode == BACK ) { pOrigSolid->Split( &m_ClipPlane, NULL, &pBack ); } else if( m_Mode == BOTH ) { pOrigSolid->Split( &m_ClipPlane, &pFront, &pBack ); } if( pFront ) { pFront->SetTemporary(true); pClipGroup->SetClipSolid( pFront, FRONT ); } if( pBack ) { pBack->SetTemporary(true); pClipGroup->SetClipSolid( pBack, BACK ); } } } //----------------------------------------------------------------------------- // Purpose: This function handles the removal of the "original" solid when it // has been clipped into new solid(s) or removed from the world (group // or entity) entirely. It handles this in an undo safe fashion. // Input: pOrigSolid - the solid to remove //----------------------------------------------------------------------------- void Clipper3D::RemoveOrigSolid( CMapSolid *pOrigSolid ) { m_pDocument->DeleteObject(pOrigSolid); // // remove the solid from the selection set if in the seleciton set and // its parent is the world, or set the selection state to none parent is group // or entity in the selection set // CSelection *pSelection = m_pDocument->GetSelection(); if ( pSelection->IsSelected( pOrigSolid ) ) { pSelection->SelectObject( pOrigSolid, scUnselect ); } else { pOrigSolid->SetSelectionState( SELECT_NONE ); } } //----------------------------------------------------------------------------- // Purpose: This function handles the saving of newly clipped solids (derived // from an "original" solid). It handles them in an undo safe fashion. // Input: pSolid - the newly clipped solid // pOrigSolid - the "original" solid or solid the clipped solid was // derived from //----------------------------------------------------------------------------- void Clipper3D::SaveClipSolid( CMapSolid *pSolid, CMapSolid *pOrigSolid ) { // // no longer a temporary solid // pSolid->SetTemporary( FALSE ); // // Add the new solid to the original solid's parent (group, entity, world, etc.). // m_pDocument->AddObjectToWorld(pSolid, pOrigSolid->GetParent()); // // handle linking solid into selection -- via selection set when parent is the world // and selected, or set the selection state if parent is group or entity in selection set // if( m_pDocument->GetSelection()->IsSelected( pOrigSolid ) ) { m_pDocument->SelectObject( pSolid, scSelect ); } else { pSolid->SetSelectionState( SELECT_NORMAL ); } GetHistory()->KeepNew( pSolid ); } //----------------------------------------------------------------------------- // Purpose: This function saves all the clipped solid information. If new solids // were generated from the original, they are saved and the original is // set for desctruciton. Otherwise, the original solid is kept. //----------------------------------------------------------------------------- void Clipper3D::SaveClipResults( void ) { // sanity check! if( IsEmpty() ) return; // mark this place in the history GetHistory()->MarkUndoPosition( NULL, "Clip Objects" ); // // save all new objects into the selection list // FOR_EACH_OBJ( m_ClipResults, pos ) { CClipGroup *pClipGroup = m_ClipResults.Element( pos ); if( !pClipGroup ) continue; CMapSolid *pOrigSolid = pClipGroup->GetOrigSolid(); CMapSolid *pBackSolid = pClipGroup->GetClipSolid( CClipGroup::BACK ); CMapSolid *pFrontSolid = pClipGroup->GetClipSolid( CClipGroup::FRONT ); // // save the front clip solid and clear the clip results list of itself // if( pFrontSolid ) { SaveClipSolid( pFrontSolid, pOrigSolid ); pClipGroup->SetClipSolid( NULL, CClipGroup::FRONT ); } // // save the front clip solid and clear the clip results list of itself // if( pBackSolid ) { SaveClipSolid( pBackSolid, pOrigSolid ); pClipGroup->SetClipSolid( NULL, CClipGroup::BACK ); } // Send the notification that this solid as been clipped. pOrigSolid->PostUpdate( Notify_Clipped ); // remove the original solid RemoveOrigSolid( pOrigSolid ); } // set the the clipping results list as empty ResetClipResults(); // update world and views m_pDocument->SetModifiedFlag(); } //----------------------------------------------------------------------------- // Purpose: Draws the measurements of a brush in the 2D view. // Input : pRender - // pSolid - // nFlags - //----------------------------------------------------------------------------- void Clipper3D::DrawBrushExtents( CRender2D *pRender, CMapSolid *pSolid, int nFlags ) { // // get the bounds of the solid // Vector Mins, Maxs; pSolid->GetRender2DBox( Mins, Maxs ); // // Determine which side of the clipping plane this solid is on in screen // space. This tells us where to draw the extents. // if( ( m_ClipPlane.normal[0] == 0 ) && ( m_ClipPlane.normal[1] == 0 ) && ( m_ClipPlane.normal[2] == 0 ) ) return; Vector normal = m_ClipPlane.normal; if( nFlags & DBT_BACK ) { VectorNegate( normal ); } Vector2D planeNormal; pRender->TransformNormal( planeNormal, normal ); if( planeNormal.x <= 0 ) { nFlags &= ~DBT_RIGHT; nFlags |= DBT_LEFT; } else if( planeNormal.x > 0 ) { nFlags &= ~DBT_LEFT; nFlags |= DBT_RIGHT; } if( planeNormal.y <= 0 ) { nFlags &= ~DBT_BOTTOM; nFlags |= DBT_TOP; } else if( planeNormal.y > 0 ) { nFlags &= ~DBT_TOP; nFlags |= DBT_BOTTOM; } DrawBoundsText(pRender, Mins, Maxs, nFlags); } //----------------------------------------------------------------------------- // Purpose: // Input : *pRender - //----------------------------------------------------------------------------- void Clipper3D::RenderTool2D(CRender2D *pRender) { if ( IsEmpty() ) return; // check flag for rendering vertices bool bDrawVerts = ( bool )( Options.view2d.bDrawVertices == TRUE ); // setup the line to use pRender->SetDrawColor( 255, 255, 255 ); // // render the clipped solids // FOR_EACH_OBJ( m_ClipResults, pos ) { CClipGroup *pClipGroup = m_ClipResults.Element( pos ); CMapSolid *pClipBack = pClipGroup->GetClipSolid( CClipGroup::BACK ); CMapSolid *pClipFront = pClipGroup->GetClipSolid( CClipGroup::FRONT ); if( !pClipBack && !pClipFront ) continue; // // draw clip solids with the extents // if( pClipBack ) { int faceCount = pClipBack->GetFaceCount(); for( int i = 0; i < faceCount; i++ ) { CMapFace *pFace = pClipBack->GetFace( i ); // size 4 pRender->DrawPolyLine( pFace->nPoints, pFace->Points ); if ( bDrawVerts ) { pRender->DrawHandles( pFace->nPoints, pFace->Points ); } if( m_bDrawMeasurements ) { DrawBrushExtents( pRender, pClipBack, DBT_TOP | DBT_LEFT | DBT_BACK ); } } } if( pClipFront ) { int faceCount = pClipFront->GetFaceCount(); for( int i = 0; i < faceCount; i++ ) { CMapFace *pFace = pClipFront->GetFace( i ); pRender->DrawPolyLine( pFace->nPoints, pFace->Points ); if ( bDrawVerts ) { pRender->DrawHandles( pFace->nPoints, pFace->Points ); } if( m_bDrawMeasurements ) { DrawBrushExtents( pRender, pClipFront, DBT_BOTTOM | DBT_RIGHT ); } } } } // // draw the clip-plane // pRender->SetDrawColor( 0, 255, 255 ); pRender->DrawLine( m_ClipPoints[0], m_ClipPoints[1] ); // // draw the clip-plane endpoints // pRender->SetHandleStyle( HANDLE_RADIUS, CRender::HANDLE_SQUARE ); pRender->SetHandleColor( 255, 255, 255 ); pRender->DrawHandle( m_ClipPoints[0] ); pRender->DrawHandle( m_ClipPoints[1] ); } //----------------------------------------------------------------------------- // Purpose: Renders the brushes that will be left by the clipper in white // wireframe. // Input : pRender - Rendering interface. //----------------------------------------------------------------------------- void Clipper3D::RenderTool3D( CRender3D *pRender ) { // is there anything to render? if( m_bEmpty ) return; // // setup the renderer // pRender->PushRenderMode( RENDER_MODE_WIREFRAME ); FOR_EACH_OBJ( m_ClipResults, pos ) { CClipGroup *pClipGroup = m_ClipResults.Element( pos ); CMapSolid *pFrontSolid = pClipGroup->GetClipSolid( CClipGroup::FRONT ); if( pFrontSolid ) { color32 rgbColor = pFrontSolid->GetRenderColor(); pFrontSolid->SetRenderColor(255, 255, 255); pFrontSolid->Render3D(pRender); pFrontSolid->SetRenderColor(rgbColor); } CMapSolid *pBackSolid = pClipGroup->GetClipSolid( CClipGroup::BACK ); if( pBackSolid ) { color32 rgbColor = pBackSolid->GetRenderColor(); pBackSolid->SetRenderColor(255, 255, 255); pBackSolid->Render3D(pRender); pBackSolid->SetRenderColor(rgbColor); } } pRender->PopRenderMode(); } //----------------------------------------------------------------------------- // Purpose: (virtual imp) // Input : pt - // BOOL - // Output : int //----------------------------------------------------------------------------- int Clipper3D::HitTest(CMapView *pView, const Vector2D &ptClient, bool bTestHandles) { // check points for ( int i=0; i<2;i++ ) { if ( HitRect(pView, ptClient, m_ClipPoints[i], HANDLE_RADIUS) ) { return i+1; // return clip point index + 1 } } // neither point hit return 0; } //----------------------------------------------------------------------------- // Purpose: Reset (clear) the clip results. //----------------------------------------------------------------------------- void Clipper3D::ResetClipResults( void ) { // // delete the clip solids held in the list -- originals are just pointers // to pre-existing objects // FOR_EACH_OBJ( m_ClipResults, pos ) { CClipGroup *pClipGroup = m_ClipResults.Element(pos); if( pClipGroup ) { delete pClipGroup; } } m_ClipResults.RemoveAll(); // the clipping list is empty SetEmpty(); } //----------------------------------------------------------------------------- // Purpose: // Input : nChar - // nRepCnt - // nFlags - //----------------------------------------------------------------------------- bool Clipper3D::OnKeyDown2D(CMapView2D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case 'O': { // // Toggle the rendering of measurements. // ToggleMeasurements(); return true; } case VK_RETURN: { // // Do the clip. // if (!IsEmpty() ) { SaveClipResults(); } return true; } case VK_ESCAPE: { OnEscape(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Handles left mouse button down events in the 2D view. // Input : Per CWnd::OnLButtonDown. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool Clipper3D::OnLMouseDown2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { Tool3D::OnLMouseDown2D(pView, nFlags, vPoint); unsigned int uConstraints = GetConstraints( nFlags ); // // Convert point to world coords. // Vector vecWorld; pView->ClientToWorld(vecWorld, vPoint); vecWorld[pView->axThird] = COORD_NOTINIT; // getvisiblepoint fills in any coord that's still set to COORD_NOTINIT: m_pDocument->GetBestVisiblePoint(vecWorld); // snap starting position to grid if ( uConstraints & constrainSnap ) m_pDocument->Snap(vecWorld, uConstraints); bool bStarting = false; // if the tool is not empty, and shift is not held down (to // start a new camera), don't do anything. if(!IsEmpty()) { // test for clip point hit (result = {0, 1, 2} int hitPoint = HitTest( pView, vPoint ); if ( hitPoint > 0 ) { // test for clip point hit (result = {0, 1, -1}) m_ClipPointHit = hitPoint-1; // convert back to index m_vOrgPos = m_ClipPoints[m_ClipPointHit]; StartTranslation( pView, vPoint ); } else if ( m_vPlaneNormal != pView->GetViewAxis() ) { SetEmpty(); bStarting = true; } else { if (nFlags & MK_SHIFT) { SetEmpty(); bStarting = true; } else { return true; // do nothing; } } } else { bStarting = true; } SetClipObjects(m_pDocument->GetSelection()->GetList()); if (bStarting) { // start the tools translation functionality StartTranslation( pView, vPoint ); // set the initial clip points m_ClipPointHit = 0; m_ClipPoints[0] = vecWorld; m_ClipPoints[1] = vecWorld; m_vOrgPos = vecWorld; } return true; } //----------------------------------------------------------------------------- // Purpose: Handles left mouse button up events in the 2D view. // Input : Per CWnd::OnLButtonUp. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool Clipper3D::OnLMouseUp2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { Tool3D::OnLMouseUp2D(pView, nFlags, vPoint); if ( IsTranslating() ) { FinishTranslation(true); } m_pDocument->UpdateStatusbar(); return true; } unsigned int Clipper3D::GetConstraints(unsigned int nKeyFlags) { unsigned int uConstraints = Tool3D::GetConstraints( nKeyFlags ); if(nKeyFlags & MK_CONTROL) { uConstraints |= constrainMoveAll; } return uConstraints; } //----------------------------------------------------------------------------- // Purpose: Handles mouse move events in the 2D view. // Input : Per CWnd::OnMouseMove. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool Clipper3D::OnMouseMove2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { vgui::HCursor hCursor = vgui::dc_arrow; unsigned int uConstraints = GetConstraints( nFlags ); Tool3D::OnMouseMove2D(pView, nFlags, vPoint); // // Convert to world coords. // Vector vecWorld; pView->ClientToWorld(vecWorld, vPoint); // // Update status bar position display. // char szBuf[128]; if ( uConstraints & constrainSnap ) m_pDocument->Snap(vecWorld,uConstraints); sprintf(szBuf, " @%.0f, %.0f ", vecWorld[pView->axHorz], vecWorld[pView->axVert]); SetStatusText(SBI_COORDS, szBuf); if (IsTranslating()) { // cursor is cross here Tool3D::UpdateTranslation( pView, vPoint, uConstraints); hCursor = vgui::dc_none; } else if (!IsEmpty()) { // // If the cursor is on a handle, set it to a cross. // if (HitTest( pView, vPoint, true)) { hCursor = vgui::dc_crosshair; } } if ( hCursor != vgui::dc_none ) pView->SetCursor( hCursor ); return true; } //----------------------------------------------------------------------------- // Purpose: Handles character events. // Input : Per CWnd::OnKeyDown. // Output : Returns true if the message was handled, false if not. //----------------------------------------------------------------------------- bool Clipper3D::OnKeyDown3D(CMapView3D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_RETURN: { if (!IsEmpty()) // dvs: what does isempty mean for the clipper? { SaveClipResults(); } return true; } case VK_ESCAPE: { OnEscape(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Handles the escape key in the 2D or 3D views. //----------------------------------------------------------------------------- void Clipper3D::OnEscape(void) { // If we're clipping, clear it if (!IsEmpty()) { SetEmpty(); } else { m_pDocument->GetTools()->SetTool(TOOL_POINTER); } }