source-engine/hammer/ToolEntity.cpp

731 lines
18 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= 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();
}