This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/LevelEdit/LevelEditView.cpp

888 lines
20 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// LevelEditView.cpp : implementation of the CLevelEditView class
//
#include "stdafx.h"
#include "leveledit.h"
#include "leveleditdoc.h"
#include "leveleditview.h"
#include "camera.h"
#include "cameramgr.h"
#include "mainfrm.h"
#include "ww3d.h"
#include "mousemgr.h"
#include "sceneeditor.h"
#include <process.h>
#include "nodemgr.h"
#include "mmsystem.h"
#include "dynamesh.h"
#include "vertmaterial.h"
#include "win.h"
#include "wwaudio.h"
#include "combat.h"
#include "timemgr.h"
#include "gameobjmanager.h"
#include "gameobjobserver.h"
#include "scripts.h"
#include "backgroundmgr.h"
#include "dazzle.h"
#include "phys.h"
#include "weathermgr.h"
#include "soundenvironment.h"
#include "heightfieldeditor.h"
/////////////////////////////////////////////////////////////////////////
// Local Prototypes
/////////////////////////////////////////////////////////////////////////
void CALLBACK fnTimerCallback (UINT, UINT, DWORD, DWORD, DWORD);
/////////////////////////////////////////////////////////////////////////
// Static member initialization
/////////////////////////////////////////////////////////////////////////
int CLevelEditView::_iPaintingLock = 0;
bool CLevelEditView::_bNeedsRepaint = false;
/////////////////////////////////////////////////////////////////////////
// Globals
/////////////////////////////////////////////////////////////////////////
CRITICAL_SECTION _CriticalSection;
IMPLEMENT_DYNCREATE(CLevelEditView, CView)
BEGIN_MESSAGE_MAP(CLevelEditView, CView)
//{{AFX_MSG_MAP(CLevelEditView)
ON_WM_SIZE()
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_RBUTTONDOWN()
ON_WM_RBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_DESTROY()
ON_WM_LBUTTONDBLCLK()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////
//
// CLevelEditView
//
/////////////////////////////////////////////////////////////////////////
CLevelEditView::CLevelEditView (void)
: m_iDeviceIndex (-1),
m_iBitsPerPixel (16),
m_iWindowed (1),
m_bRenderDeviceInitialized (FALSE),
m_pCameraMgr (NULL),
m_pMouseMgr (NULL),
m_bActive (false),
m_p2DScene (NULL),
m_p2DCamera (NULL),
m_TimerID (0),
m_HorzFOV (0),
m_VertFOV (0),
m_CursorXRatio (1),
m_CursorYRatio (1),
m_hMouseHook (NULL)
{
// Get the windowed mode from the registry
CString string_windowed = theApp.GetProfileString ("Config", "Windowed", "1");
m_iWindowed = ::atoi ((LPCTSTR)string_windowed) || Is_Silent_Mode ();
::InitializeCriticalSection (&_CriticalSection);
return ;
}
/////////////////////////////////////////////////////////////////////////
//
// ~CLevelEditView
//
/////////////////////////////////////////////////////////////////////////
CLevelEditView::~CLevelEditView (void)
{
// If the camera manager exists, free it.
SAFE_DELETE (m_pCameraMgr);
SAFE_DELETE (m_pMouseMgr);
m_pCursorMesh->Remove ();
MEMBER_RELEASE (m_pCursorMesh);
MEMBER_RELEASE (m_p2DScene);
MEMBER_RELEASE (m_p2DCamera);
// Cache this information in the registry
TCHAR temp_string[10];
::itoa (m_iWindowed, temp_string, 10);
theApp.WriteProfileString ("Config", "Windowed", temp_string);
::DeleteCriticalSection (&_CriticalSection);
return ;
}
/////////////////////////////////////////////////////////////////////////
//
// PreCreateWindow
//
/////////////////////////////////////////////////////////////////////////
BOOL
CLevelEditView::PreCreateWindow (CREATESTRUCT& cs)
{
// Allow the base class to process this message
return CView::PreCreateWindow (cs);
}
/////////////////////////////////////////////////////////////////////////
//
// OnDraw
//
void
CLevelEditView::OnDraw (CDC* pDC)
{
CLevelEditDoc* doc = GetDocument();
ASSERT_VALID(doc);
// If we are in fullscreen mode, then erase the window background
if (m_iWindowed == 0) {
// Get the client rectangle of the window
RECT rect;
GetClientRect (&rect);
// Erase the background
::FillRect (*pDC, &rect, (HBRUSH)(COLOR_WINDOW + 1));
}
// Ask the view to repaint itself.
Repaint_View ();
return ;
}
/////////////////////////////////////////////////////////////////////////
//
// MFC Junk
//
/////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
void CLevelEditView::AssertValid() const
{
CView::AssertValid();
}
void CLevelEditView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CLevelEditDoc* CLevelEditView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CLevelEditDoc)));
return (CLevelEditDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////
//
// Initialize_Render_Device
//
/////////////////////////////////////////////////////////////////////////
bool
CLevelEditView::Initialize_Render_Device (void)
{
::EnterCriticalSection (&_CriticalSection);
// Assume failure
m_bRenderDeviceInitialized = FALSE;
// Initialize the rendering engine with the information from
// this window.
RECT rect;
GetClientRect (&rect);
int cx = rect.right-rect.left;
int cy = rect.bottom-rect.top;
int bpp = m_iBitsPerPixel;
if (m_iWindowed == 0) {
cx = 640;
cy = 480;
bpp = 16;
// Determine what our aspect ratio should be to
// constrain the mouse to the 'window' while in full screen mode.
m_CursorXRatio = cx / ((float)rect.right);
m_CursorYRatio = cy / ((float)rect.bottom);
} else {
m_CursorXRatio = 1.0F;
m_CursorYRatio = 1.0F;
}
// Ask W3D to use this rendering device
m_bRenderDeviceInitialized = (WW3D::Set_Render_Device (m_iDeviceIndex,
cx,
cy,
bpp,
m_iWindowed) == WW3D_ERROR_OK);
ASSERT (m_bRenderDeviceInitialized);
// Create a new camera manager object
if (m_pCameraMgr == NULL) {
m_pCameraMgr = new CameraMgr;
}
// Create a new mouse manager object
if (m_pMouseMgr == NULL) {
m_pMouseMgr = new MouseMgrClass;
}
// Reset the camera's FOV
m_pCameraMgr->Get_Camera ()->Set_View_Plane (m_HorzFOV, m_VertFOV);
if (m_p2DCamera == NULL) {
// Create a new instance of the camera class to use
// when rendering the background BMP
m_p2DCamera = new CameraClass ();
// Were we successful in creating the new instance?
ASSERT (m_p2DCamera != NULL);
if (m_p2DCamera != NULL) {
// Set the default values for the new camera
m_p2DCamera->Set_View_Plane (Vector2 (-1.00F, -1.00F), Vector2 (1.00F, 1.00F));
m_p2DCamera->Set_Position (Vector3 (0.00F, 0.00F, 1.00F));
m_p2DCamera->Set_Clip_Planes (0.1F, 10.0F);
//m_p2DCamera->Set_Position (Vector3 (0.00F, 0.00F, 1.00F));
//m_p2DCamera->Set_Transform (Matrix3D (1));
}
}
if (m_p2DScene == NULL) {
// Instantiate a new scene
m_p2DScene = new SimpleSceneClass;
// Allocate a new mesh we can use to represent the mouse
//.cursor while in full screen mode
m_pCursorMesh = new DynamicScreenMeshClass (2, 6);
//m_pCursorMesh->Set_Force_Visible (true);
VertexMaterialClass *vmat = new VertexMaterialClass;
vmat->Set_Emissive (1,1,1);
vmat->Set_Diffuse (0,0,0);
m_pCursorMesh->Set_Vertex_Material (vmat);
MEMBER_RELEASE (vmat);
m_pCursorMesh->Begin_Tri_Strip();
m_pCursorMesh->Set_Vertex_Color (Vector3 (0, 0.384F, 0.5F));
m_pCursorMesh->Vertex (0.0F, 0.0F, 0, 0, 0);
m_pCursorMesh->Set_Vertex_Color (Vector3 (0, 0, 0.5F));
m_pCursorMesh->Vertex (0.026F,0.028F, 0, 0, 0);
m_pCursorMesh->Set_Vertex_Color (Vector3 (0, 0.5F, 1));
m_pCursorMesh->Vertex (0.010F,0.024F, 0, 0, 0);
m_pCursorMesh->End_Tri_Strip();
m_pCursorMesh->Begin_Tri_Strip();
m_pCursorMesh->Set_Vertex_Color (Vector3 (0, 0.5F, 1));
m_pCursorMesh->Vertex (0.010F,0.024F, 0, 0, 0);
m_pCursorMesh->Set_Vertex_Color (Vector3 (0.53F, 0.76F, 1));
m_pCursorMesh->Vertex (0.0F, 0.041F, 0, 0, 0);
m_pCursorMesh->Set_Vertex_Color (Vector3 (0, 0.5F, 1));
m_pCursorMesh->Vertex (0.0F, 0.0F, 0, 0, 0);
m_pCursorMesh->End_Tri_Strip();
// Add the cursor to the 2D scene
m_pCursorMesh->Add (m_p2DScene);
}
LeaveCriticalSection (&_CriticalSection);
// Return the TRUE/FALSE result code
return m_bRenderDeviceInitialized;
}
/////////////////////////////////////////////////////////////////////////
//
// OnSize
//
void
CLevelEditView::OnSize
(
UINT nType,
int cx,
int cy
)
{
// Allow the base class to process this message
CView::OnSize (nType, cx, cy);
int bpp = m_iBitsPerPixel;
if (m_iWindowed == 0) {
// Determine what our aspect ratio should be to
// constrain the mouse to the 'window' while in full screen mode.
m_CursorXRatio = 640 / ((float)cx);
m_CursorYRatio = 480 / ((float)cy);
cx = 640;
cy = 480;
bpp = 16;
}
// update the camera FOV settings
// take the larger of the two dimensions, give it the
// full desired FOV, then give the other dimension an
// FOV proportional to its relative size
m_HorzFOV = 0.0F;
m_VertFOV = 0.0F;
if (cy > cx) {
m_VertFOV = (float)DEG_TO_RAD(45.0f);
m_HorzFOV = (double)cx / (double)cy * m_VertFOV;
}
else {
m_HorzFOV = (float)DEG_TO_RAD(45.0f);
m_VertFOV = (double)cy / (double)cx * m_HorzFOV;
}
if (m_bRenderDeviceInitialized) {
if (m_iWindowed == 1) {
// Change the resolution of the rendering device to
// match that of the view's current dimensions
WW3D::Set_Device_Resolution (cx, cy, bpp, m_iWindowed);
}
// Reset the field of view
m_pCameraMgr->Get_Camera ()->Set_View_Plane (m_HorzFOV, m_VertFOV);
// Force a repaint of the screen
Repaint_View ();
}
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// Allow_Repaint
//
////////////////////////////////////////////////////////////////////////////
void
CLevelEditView::Allow_Repaint (bool active)
{
// Either increment or decrement the lock
_iPaintingLock += (active ? -1 : 1);
//
// Refresh the view if necessary
//
if ((_iPaintingLock == 0) && _bNeedsRepaint) {
::Refresh_Main_View ();
}
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// Repaint_View
//
////////////////////////////////////////////////////////////////////////////
void
CLevelEditView::Repaint_View (void)
{
CLevelEditDoc *doc = (CLevelEditDoc *)GetDocument ();
//
// Make sure separate threads don't try to render at the same time
//
::EnterCriticalSection (&_CriticalSection);
_bNeedsRepaint = false;
//
// Are we in a valid state?
//
WWASSERT (_iPaintingLock >= 0);
if (m_bRenderDeviceInitialized &&
(_iPaintingLock == 0) &&
(doc != NULL) &&
(doc->Get_Scene () != NULL) &&
(::Is_Silent_Mode () == false))
{
_iPaintingLock ++;
//
// Get access to the scene and current render camera
//
SceneEditorClass *scene = ::Get_Scene_Editor ();
CameraClass *camera = scene->Get_Render_Camera ();
//
// Let the game free up anything it needs
//
NetworkObjectMgrClass::Delete_Pending ();
GameObjObserverManager::Delete_Pending ();
ScriptManager::Destroy_Pending ();
//
// Call updates on some of the subsystems
//
WWAudioClass::Get_Instance ()->On_Frame_Update ();
TimeManager::Update ();
((CMainFrame *)::AfxGetMainWnd ())->Update_Poly_Count ();
((CMainFrame *)::AfxGetMainWnd ())->Update_Texture_Mem ();
//
// Let the camera know to update its animation (if any)
//
::Get_Camera_Mgr ()->On_Frame ();
::Get_Scene_Editor ()->On_Frame ();
::Get_Scene_Editor ()->Update_Selection_Boxes ();
// Update some sub-systems.
BackgroundMgrClass::Update (scene, camera);
SoundEnvironmentClass *sound_environment = CombatManager::Get_Sound_Environment ();
if (sound_environment != NULL) sound_environment->Update (scene, camera);
WeatherMgrClass::Update (scene, camera);
//
// Collect visible objects and such...
//
PhysicsSceneClass::Get_Instance ()->Pre_Render_Processing (*camera);
//
// Set-up the rendering process
//
WW3D::Begin_Render (true, true, BackgroundMgrClass::Get_Clear_Color());
//
// Render the background scene from the game (this will give
// us the sky and stuff).
//
WW3D::Render (CombatManager::Get_Background_Scene(), camera);
//
// Now render the main scene
//
WW3D::Render (scene, camera);
//
// Render any heightfield editing gizmos
//
HeightfieldEditorClass::Render ();
//
// Render the dazzle layer (if necessary)
//
DazzleLayerClass *dazzle_layer = CombatManager::Get_Dazzle_Layer ();
if (dazzle_layer != NULL) {
dazzle_layer->Render (camera);
}
//
// Finish out the rendering process
//
WW3D::End_Render ();
//
// Cleanup the rendering processing
//
PhysicsSceneClass::Get_Instance ()->Post_Render_Processing ();
_iPaintingLock --;
} else {
_bNeedsRepaint = true;
}
::LeaveCriticalSection (&_CriticalSection);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnFinalRelease
//
void
CLevelEditView::OnFinalRelease (void)
{
// Allow the base class to process this message
CView::OnFinalRelease ();
return;
}
////////////////////////////////////////////////////////////////////////////
//
// OnInitialUpdate
//
void
CLevelEditView::OnInitialUpdate (void)
{
// Allow the base class to process this message
CView::OnInitialUpdate();
// Initialize the render device before we can update the view
if (m_bRenderDeviceInitialized == false) {
Initialize_Render_Device ();
}
if (m_bRenderDeviceInitialized)
{
Start_Update ();
}
//MainWindow = m_hWnd;
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// Start_Update
//
void
CLevelEditView::Start_Update (bool bstart)
{
if (bstart && (m_TimerID == 0)) {
// Kick off a timer that we can use to update
// the display (kinda like a game loop iterator)
m_TimerID = (UINT)::timeSetEvent (50,
50,
fnTimerCallback,
(DWORD)m_hWnd,
TIME_PERIODIC);
} else if ((bstart == false) && (m_TimerID != 0)) {
// Stop the timer
::timeKillEvent ((UINT)m_TimerID);
// We don't need this anymore
m_TimerID = 0;
}
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// fnMouseHookProc
//
LRESULT CALLBACK
fnMouseHookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
LRESULT result = 0L;
CLevelEditView *pview = ::Get_Main_View ();
if (pview != NULL) {
MOUSEHOOKSTRUCT *pinfo = (MOUSEHOOKSTRUCT *)lParam;
if (pinfo != NULL) {
bool allow_update = pinfo->hwnd == pview->m_hWnd;
if (!allow_update) {
allow_update = (::GetProp (pinfo->hwnd, "ALLOW_UPDATE") != NULL);
if (!allow_update && ::GetParent (pinfo->hwnd)) {
allow_update = (::GetProp (::GetParent (pinfo->hwnd), "ALLOW_UPDATE") != NULL);
}
if ( ::Get_Mouse_Mgr () &&
::Get_Mouse_Mgr ()->Get_Mouse_Mode () == MouseMgrClass::MODE_CAMERA_WALK) {
allow_update = true;
}
}
// Start or stop updating the view
pview->Start_Update (allow_update);
}
// Call the next hook in our chain
result = ::CallNextHookEx (::Get_Main_View ()->Get_Hook (), nCode, wParam, lParam);
}
return result;
}
////////////////////////////////////////////////////////////////////////////
//
// OnCreate
//
int
CLevelEditView::OnCreate (LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// Let the main window know we want to be the active view
CMainFrame *pwnd = (CMainFrame *)::AfxGetMainWnd ();
if (pwnd) {
pwnd->SetActiveView (this, FALSE);
}
m_hMouseHook = ::SetWindowsHookEx (WH_MOUSE,
fnMouseHookProc,
::AfxGetInstanceHandle (),
::GetCurrentThreadId ());
return 0;
}
////////////////////////////////////////////////////////////////////////////
//
// OnLButtonDown
//
void
CLevelEditView::OnLButtonDown
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_LButton_Down (nFlags, point);
// Allow the base class to process this message
CView::OnLButtonDown(nFlags, point);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnLButtonDown
//
void
CLevelEditView::OnLButtonUp
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_LButton_Up (nFlags, point);
// Allow the base class to process this message
CView::OnLButtonUp(nFlags, point);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnLButtonDown
//
void
CLevelEditView::OnRButtonDown
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_RButton_Down (nFlags, point);
// Allow the base class to process this message
CView::OnRButtonDown (nFlags, point);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnRButtonUp
//
void
CLevelEditView::OnRButtonUp
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_RButton_Up (nFlags, point);
// Allow the base class to process this message
CView::OnRButtonUp (nFlags, point);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnMouseMove
//
void
CLevelEditView::OnMouseMove
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_Mouse_Move (nFlags, point);
SetFocus ();
// Allow the base class to process this message
CView::OnMouseMove (nFlags, point);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// WindowProc
//
LRESULT
CLevelEditView::WindowProc
(
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
// Is this the repaint message we are expecting?
if (message == WM_USER+101)
{
Repaint_View ();
::RemoveProp (m_hWnd, "WaitingToProcess");
}
// Allow the base class to process this message
return CView::WindowProc(message, wParam, lParam);
}
////////////////////////////////////////////////////////////////////////////
//
// OnDestroy
//
void
CLevelEditView::OnDestroy (void)
{
// Kill the timer
Start_Update (false);
if (m_hMouseHook != NULL) {
::UnhookWindowsHookEx (m_hMouseHook);
m_hMouseHook = NULL;
}
::RemoveProp (m_hWnd, "WaitingToProcess");
// Allow the base class to process this message
CView::OnDestroy ();
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// Reset_View
//
void
CLevelEditView::Reset_View (void)
{
ASSERT (m_pCameraMgr != NULL);
// Re-initialize the camera
m_pCameraMgr->Init_Camera ();
// Reset the mouse mode
m_pMouseMgr->Set_Mouse_Mode (MouseMgrClass::MODE_OBJECT_MANIPULATE);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// fnTimerCallback
//
void CALLBACK
fnTimerCallback
(
UINT uID,
UINT uMsg,
DWORD dwUser,
DWORD dw1,
DWORD dw2
)
{
HWND hwnd = (HWND)dwUser;
if (hwnd != NULL) {
// Send this event off to the view to process (hackish, but fine for now)
if ((GetProp (hwnd, "WaitingToProcess") == NULL) &&
(CLevelEditView::_iPaintingLock == 0))
{
SetProp (hwnd, "WaitingToProcess", (HANDLE)1);
// Send the message to the view so it will be in the
// same thread (Surrender doesn't seem to be thread-safe)
::PostMessage (hwnd, WM_USER+101, 0, 0L);
}
}
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// OnLButtonDblClk
//
void
CLevelEditView::OnLButtonDblClk
(
UINT nFlags,
CPoint point
)
{
// Pass this onto the mouse manager
m_pMouseMgr->Handle_LButton_Dblclk (nFlags, point);
// Allow the base class to process this message
CView::OnLButtonDown(nFlags, point);
return ;
}