//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implements the entity/prefab placement tool. // //=============================================================================// #include "stdafx.h" #include "History.h" #include "MainFrm.h" #include "MapDefs.h" #include "MapSolid.h" #include "MapDoc.h" #include "MapView2D.h" #include "MapView3D.h" #include "Material.h" #include "materialsystem/imesh.h" #include "Render2D.h" #include "Render3D.h" #include "StatusBarIDs.h" #include "TextureSystem.h" #include "ToolEntity.h" #include "ToolManager.h" #include "hammer.h" #include "vgui/Cursor.h" #include "Selection.h" #include "vstdlib/random.h" // memdbgon must be the last include file in a .cpp file!!! #include //#pragma warning(disable:4244) static HCURSOR s_hcurEntity = NULL; class CToolEntityMessageWnd : public CWnd { public: bool Create(void); void PreMenu2D(CToolEntity *pTool, CMapView2D *pView); protected: //{{AFX_MSG_MAP(CToolEntityMessageWnd) afx_msg void OnCreateObject(); //}}AFX_MSG DECLARE_MESSAGE_MAP() private: CToolEntity *m_pToolEntity; CMapView2D *m_pView2D; }; static CToolEntityMessageWnd s_wndToolMessage; static const char *g_pszClassName = "ValveEditor_EntityToolWnd"; BEGIN_MESSAGE_MAP(CToolEntityMessageWnd, CWnd) //{{AFX_MSG_MAP(CToolMessageWnd) ON_COMMAND(ID_CREATEOBJECT, OnCreateObject) //}}AFX_MSG_MAP END_MESSAGE_MAP() //----------------------------------------------------------------------------- // Purpose: Creates the hidden window that receives context menu commands for the // entity tool. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CToolEntityMessageWnd::Create(void) { WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS)); wndcls.lpfnWndProc = AfxWndProc; wndcls.hInstance = AfxGetInstanceHandle(); wndcls.lpszClassName = g_pszClassName; if (!AfxRegisterClass(&wndcls)) { return(false); } return(CWnd::CreateEx(0, g_pszClassName, g_pszClassName, 0, CRect(0, 0, 10, 10), NULL, 0) == TRUE); } //----------------------------------------------------------------------------- // Purpose: Attaches the entity tool to this window before activating the context // menu. //----------------------------------------------------------------------------- void CToolEntityMessageWnd::PreMenu2D(CToolEntity *pToolEntity, CMapView2D *pView) { Assert(pToolEntity != NULL); m_pToolEntity = pToolEntity; m_pView2D = pView; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CToolEntityMessageWnd::OnCreateObject() { m_pToolEntity->CreateMapObject(m_pView2D); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CToolEntity::CToolEntity(void) { SetEmpty(); m_vecPos.Init(); if (s_hcurEntity == NULL) { s_hcurEntity = LoadCursor(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDC_ENTITY)); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CToolEntity::~CToolEntity(void) { } //----------------------------------------------------------------------------- // Purpose: // Input : pt - // BOOL - // Output : //----------------------------------------------------------------------------- int CToolEntity::HitTest(CMapView *pView, const Vector2D &ptClient, bool bTestHandles) { return HitRect( pView, ptClient, m_vecPos, 8 )?TRUE:FALSE; } //----------------------------------------------------------------------------- // Purpose: // Input : bSave - //----------------------------------------------------------------------------- void CToolEntity::FinishTranslation(bool bSave) { if (bSave) { TranslatePoint( m_vecPos ); m_bEmpty = false; } Tool3D::FinishTranslation(bSave); } //----------------------------------------------------------------------------- // Purpose: // Input : pt - // uFlags - // size - // Output : Returns true if the translation delta was nonzero. //----------------------------------------------------------------------------- bool CToolEntity::UpdateTranslation( const Vector &vUpdate, UINT uFlags) { Vector vOldDelta = m_vTranslation; if ( !Tool3D::UpdateTranslation( vUpdate, uFlags ) ) return false; // apply snap to grid constrain if ( uFlags ) { ProjectOnTranslationPlane( m_vecPos + m_vTranslation, m_vTranslation, uFlags ); m_vTranslation -= m_vecPos; } return true; } //----------------------------------------------------------------------------- // Purpose: // Input : pRender - //----------------------------------------------------------------------------- void CToolEntity::RenderTool2D(CRender2D *pRender) { Vector v = m_vecPos; if ( IsTranslating() ) { TranslatePoint( v ); } else if ( IsEmpty() ) { return; } pRender->SetDrawColor( 35, 255, 75 ); // // Draw center rect. // pRender->DrawRectangle( v, v, false, 6.0f ); // // Draw crosshair // pRender->DrawLine( Vector( g_MIN_MAP_COORD, v.y, v.z), Vector( g_MAX_MAP_COORD, v.y , v.z) ); pRender->DrawLine( Vector( v.x, g_MIN_MAP_COORD, v.z), Vector( v.x, g_MAX_MAP_COORD, v.z) ); pRender->DrawLine( Vector( v.x, v.y, g_MIN_MAP_COORD), Vector( v.x, v.y, g_MAX_MAP_COORD) ); } //----------------------------------------------------------------------------- // Purpose: // Input : pView - // point - // Output : //----------------------------------------------------------------------------- bool CToolEntity::OnContextMenu2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { if (!IsEmpty()) { CMapDoc *pDoc = pView->GetMapDoc(); if (pDoc == NULL) { return true; } if (!pView->PointInClientRect(vPoint)) { return true; } if ( HitTest( pView, vPoint, false) ) { static CMenu menu, menuCreate; static bool bInit = false; if (!bInit) { bInit = true; menu.LoadMenu(IDR_POPUPS); menuCreate.Attach(::GetSubMenu(menu.m_hMenu, 1)); // Create the window that handles menu messages. s_wndToolMessage.Create(); } CPoint ptScreen( vPoint.x,vPoint.y); pView->ClientToScreen(&ptScreen); s_wndToolMessage.PreMenu2D(this, pView); menuCreate.TrackPopupMenu(TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_LEFTALIGN, ptScreen.x, ptScreen.y, &s_wndToolMessage); } } return true; } //----------------------------------------------------------------------------- // Purpose: // Input : *pView - // nChar - // nRepCnt - // nFlags - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CToolEntity::OnKeyDown2D(CMapView2D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_RETURN: { if (!IsEmpty()) { CreateMapObject(pView); } return true; } case VK_ESCAPE: { OnEscape(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : pView - // nFlags - // point - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CToolEntity::OnLMouseDown2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { unsigned int uConstraints = GetConstraints( nFlags ); Tool3D::OnLMouseDown2D(pView, nFlags, vPoint); if ( HitTest( pView, vPoint, false) ) { // translate existing object StartTranslation( pView, vPoint ); } else { Vector vecWorld; pView->ClientToWorld(vecWorld, vPoint ); // // Snap starting position to grid. // if ( uConstraints & constrainSnap ) m_pDocument->Snap(vecWorld, uConstraints); // create new one, keep old third axis m_vecPos[pView->axHorz] = vecWorld[pView->axHorz]; m_vecPos[pView->axVert] = vecWorld[pView->axVert]; m_bEmpty = false; StartTranslation( pView, vPoint ); } return true; } // set temp transformation plane void CToolEntity::StartTranslation( CMapView *pView, const Vector2D &vPoint ) { Vector vOrigin, v1,v2,v3; pView->GetBestTransformPlane( v1,v2,v3 ); SetTransformationPlane(m_vecPos, v1, v2, v3 ); // align translation plane to world origin ProjectOnTranslationPlane( vec3_origin, vOrigin, 0 ); // set transformation plane SetTransformationPlane(vOrigin, v1, v2, v3 ); Tool3D::StartTranslation( pView, vPoint, false ); } //----------------------------------------------------------------------------- // Purpose: // Input : Pre CWnd::OnLButtonUp. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CToolEntity::OnLMouseUp2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { Tool3D::OnLMouseUp2D(pView, nFlags, vPoint); if (IsTranslating()) { FinishTranslation( true ); } m_pDocument->UpdateStatusbar(); return true; } //----------------------------------------------------------------------------- // Returns true if the message was handled, false otherwise. //----------------------------------------------------------------------------- bool CToolEntity::OnMouseMove2D(CMapView2D *pView, UINT nFlags, const Vector2D &vPoint) { Tool3D::OnMouseMove2D(pView, nFlags, vPoint); vgui::HCursor hCursor = vgui::dc_arrow; unsigned int uConstraints = GetConstraints( nFlags ); // 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 we are currently dragging the marker, update that operation based on // the current cursor position and keyboard state. // if (IsTranslating()) { Tool3D::UpdateTranslation( pView, vPoint, uConstraints ); // Don't change the cursor while dragging - it should remain a cross. hCursor = vgui::dc_none; } else if (!IsEmpty()) { // Don't change the cursor while dragging - it should remain a cross. hCursor = vgui::dc_crosshair; } if ( hCursor != vgui::dc_none ) pView->SetCursor( hCursor ); return true; } //----------------------------------------------------------------------------- // Returns true if the message was handled, false otherwise. //----------------------------------------------------------------------------- bool CToolEntity::OnMouseMove3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) { return true; } //----------------------------------------------------------------------------- // Returns true if the message was handled, false otherwise. //----------------------------------------------------------------------------- bool CToolEntity::OnKeyDown3D(CMapView3D *pView, UINT nChar, UINT nRepCnt, UINT nFlags) { CMapDoc *pDoc = pView->GetMapDoc(); if (pDoc == NULL) { return false; } switch (nChar) { case VK_RETURN: { // // Create the entity or prefab. // if (!IsEmpty()) { //CreateMapObject(pView); // TODO: support in 3D } return true; } case VK_ESCAPE: { OnEscape(); return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Handles the escape key in the 2D or 3D views. //----------------------------------------------------------------------------- void CToolEntity::OnEscape(void) { // // Cancel the object creation tool. // if (!IsEmpty()) { SetEmpty(); } else { ToolManager()->SetTool(TOOL_POINTER); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pView - // nFlags - // point - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CToolEntity::OnLMouseDown3D(CMapView3D *pView, UINT nFlags, const Vector2D &vPoint) { ULONG ulFace; VMatrix LocalMatrix, LocalMatrixNeg; CMapClass *pObject = pView->NearestObjectAt( vPoint, ulFace, FLAG_OBJECTS_AT_RESOLVE_INSTANCES, &LocalMatrix ); Tool3D::OnLMouseDown3D(pView, nFlags, vPoint); if (pObject != NULL) { CMapSolid *pSolid = dynamic_cast (pObject); if (pSolid == NULL) { // Clicked on a point entity - do nothing. return true; } LocalMatrix.InverseTR( LocalMatrixNeg ); // Build a ray to trace against the face that they clicked on to // find the point of intersection. Vector Start,End; pView->GetCamera()->BuildRay( vPoint, Start, End); Vector HitPos, HitNormal; CMapFace *pFace = pSolid->GetFace(ulFace); Vector vFinalStart, vFinalEnd; LocalMatrixNeg.V3Mul( Start, vFinalStart ); LocalMatrixNeg.V3Mul( End, vFinalEnd ); if (pFace->TraceLine( HitPos, HitNormal, vFinalStart, vFinalEnd)) { Vector vFinalHitPos, vFinalHitNormal; LocalMatrix.V3Mul( HitPos, vFinalHitPos ); vFinalHitNormal = LocalMatrix.ApplyRotation( HitNormal ); CMapClass *pNewObject = NULL; if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingPrefab()) { // // Prefab creation. // unsigned int uConstraints = GetConstraints( nFlags ); m_pDocument->Snap(vFinalHitPos,uConstraints); GetHistory()->MarkUndoPosition(m_pDocument->GetSelection()->GetList(), "New Prefab"); // Get prefab object CMapClass *pPrefabObject = GetMainWnd()->m_ObjectBar.BuildPrefabObjectAtPoint(vFinalHitPos); // // Add prefab to the world. // CMapWorld *pWorld = m_pDocument->GetMapWorld(); m_pDocument->ExpandObjectKeywords(pPrefabObject, pWorld); pNewObject = pPrefabObject; } else if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingEntity()) { // // Entity creation. // GetHistory()->MarkUndoPosition(m_pDocument->GetSelection()->GetList(), "New Entity"); CMapEntity *pEntity = new CMapEntity; pEntity->SetPlaceholder(TRUE); pEntity->SetOrigin(vFinalHitPos); pEntity->SetClass(CObjectBar::GetDefaultEntityClass()); VPlane BeforeTransform( pFace->plane.normal, pFace->plane.dist ), AfterTransform; LocalMatrix.TransformPlane( BeforeTransform, AfterTransform ); PLANE NewPlane; NewPlane.dist = AfterTransform.m_Dist; NewPlane.normal = AfterTransform.m_Normal; // Align the entity on the plane properly pEntity->AlignOnPlane(vFinalHitPos, &NewPlane, (vFinalHitNormal.z > 0.0f) ? CMapEntity::ALIGN_BOTTOM : CMapEntity::ALIGN_TOP); pNewObject = pEntity; } if ( pNewObject ) { if ( GetMainWnd()->m_ObjectBar.UseRandomYawOnEntityPlacement() ) { // They checked "random yaw" on the object bar, so come up with a random yaw. VMatrix vmRotate, vmT1, vmT2; Vector vOrigin; QAngle angRandom( 0, RandomInt( -180, 180 ), 0 ); pNewObject->GetOrigin( vOrigin ); // Setup a matrix that translates them to the origin, rotates it, then translates back. MatrixFromAngles( angRandom, vmRotate ); MatrixBuildTranslation( vmT1, -vOrigin ); MatrixBuildTranslation( vmT2, vOrigin ); // Transform the object. pNewObject->Transform( vmT2 * vmRotate * vmT1 ); } m_pDocument->AddObjectToWorld( pNewObject ); GetHistory()->KeepNew( pNewObject ); // Select the new object. m_pDocument->SelectObject( pNewObject, scClear|scSelect|scSaveChanges ); m_pDocument->SetModifiedFlag(); } } } return true; } //----------------------------------------------------------------------------- // Purpose: Renders a selection gizmo at our bounds center. // Input : pRender - Rendering interface. //----------------------------------------------------------------------------- void CToolEntity::RenderTool3D(CRender3D *pRender) { Vector pos = m_vecPos; if ( IsTranslating() ) { TranslatePoint( pos ); } else if ( IsEmpty() ) { return; } // // Setup the renderer. // pRender->PushRenderMode( RENDER_MODE_WIREFRAME); CMeshBuilder meshBuilder; CMatRenderContextPtr pRenderContext( MaterialSystemInterface() ); IMesh* pMesh = pRenderContext->GetDynamicMesh(); meshBuilder.Begin(pMesh, MATERIAL_LINES, 3); meshBuilder.Position3f(g_MIN_MAP_COORD, pos.y, pos.z); meshBuilder.Color3ub(255, 0, 0); meshBuilder.AdvanceVertex(); meshBuilder.Position3f(g_MAX_MAP_COORD, pos.y, pos.z); meshBuilder.Color3ub(255, 0, 0); meshBuilder.AdvanceVertex(); meshBuilder.Position3f(pos.x, g_MIN_MAP_COORD, pos.z); meshBuilder.Color3ub(0, 255, 0); meshBuilder.AdvanceVertex(); meshBuilder.Position3f(pos.x, g_MAX_MAP_COORD, pos.z); meshBuilder.Color3ub(0, 255, 0); meshBuilder.AdvanceVertex(); meshBuilder.Position3f(pos.x, pos.y, g_MIN_MAP_COORD); meshBuilder.Color3ub(0, 0, 255); meshBuilder.AdvanceVertex(); meshBuilder.Position3f(pos.x, pos.y, g_MAX_MAP_COORD); meshBuilder.Color3ub(0, 0, 255); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); pRender->PopRenderMode(); } void CToolEntity::CreateMapObject(CMapView2D *pView) { CMapWorld *pWorld = m_pDocument->GetMapWorld(); CMapClass *pobj = NULL; // // Handle prefab creation. // if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingPrefab()) { GetHistory()->MarkUndoPosition(m_pDocument->GetSelection()->GetList(), "New Prefab"); CMapClass *pPrefabObject = GetMainWnd()->m_ObjectBar.BuildPrefabObjectAtPoint(m_vecPos); if (pPrefabObject == NULL) { pView->MessageBox("Unable to load prefab", "Error", MB_OK); SetEmpty(); return; } m_pDocument->ExpandObjectKeywords(pPrefabObject, pWorld); m_pDocument->AddObjectToWorld(pPrefabObject); GetHistory()->KeepNew(pPrefabObject); pobj = pPrefabObject; } // // Handle entity creation. // else if (GetMainWnd()->m_ObjectBar.IsEntityToolCreatingEntity()) { GetHistory()->MarkUndoPosition(m_pDocument->GetSelection()->GetList(), "New Entity"); CMapEntity *pEntity = new CMapEntity; pEntity->SetPlaceholder(TRUE); pEntity->SetOrigin(m_vecPos); pEntity->SetClass(CObjectBar::GetDefaultEntityClass()); m_pDocument->AddObjectToWorld(pEntity); pobj = pEntity; GetHistory()->KeepNew(pEntity); } // // Select the new object. // m_pDocument->SelectObject(pobj, scClear |scSelect|scSaveChanges); SetEmpty(); m_pDocument->SetModifiedFlag(); }