/* ** 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 . */ // 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 #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 ; }