|
|
//========= 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 <tier0/memdbgon.h>
//#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 <CMapSolid *> (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(); }
|