/* ** 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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Combat * * * * $Archive:: /Commando/Code/wwui/viewerctrl.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 11/06/01 4:23p $* * * * $Revision:: 9 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // Disable warning about exception handling not being enabled. It's used as part of STL - in a part of STL we don't use. #pragma warning(disable : 4530) #include "viewerctrl.h" #include "assetmgr.h" #include "refcount.h" #include "mousemgr.h" #include "ww3d.h" #include "dialogmgr.h" #include "dialogbase.h" #include "stylemgr.h" #include "scene.h" #include "camera.h" #include "rendobj.h" #include "hanim.h" #include "mesh.h" #include "meshmdl.h" #include "light.h" //////////////////////////////////////////////////////////////// // Local constants //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // ViewerCtrlClass // //////////////////////////////////////////////////////////////// ViewerCtrlClass::ViewerCtrlClass (void) : Scene (NULL), Camera (NULL), Model (NULL), Distance (0), ZRotation (0), MinCameraDist (0), RotationRate (DEG_TO_RADF (0.0f)), BoundingBox (Vector3 (0, 0, 0), Vector3 (0, 0, 0)), IsCameraDirty (true), IsBackgroundVisible (true), InterfaceMode (Z_ROTATION) { Vector3 ambientcolor (0.2f, 0.2f, 0.2f); LastMousePosition = DialogMgrClass::Get_Mouse_Pos(); // // Configure the 2D renderer // StyleMgrClass::Configure_Renderer (&ControlRenderer); // // Create a scene to use to render the model // Scene = new SimpleSceneClass; Scene->Set_Ambient_Light (ambientcolor); // // Create a camera to use in the scene // NOTE: Near and far clip planes are set to values which will suit a large range of object sizes. // However, very small objects may pass thru the near clip plane and large objects may pass // thru the far clip plane (and Z-fighting will result if the ratio is too high). If this // proves to problematic replace the constant settings with a function of object size that // can be set on a per object basis. // Camera = new CameraClass; Camera->Set_Clip_Planes (0.25F, 300.0F); // Create and add a light. Light = NEW_REF (LightClass, ()); Light->Set_Intensity (1.0f); Light->Set_Ambient (Vector3 (0.0f, 0.0f, 0.0f)); Light->Set_Diffuse (Vector3 (1.0f, 1.0f, 1.0f)); Scene->Add_Render_Object (Light); return ; } //////////////////////////////////////////////////////////////// // // ~ViewerCtrlClass // //////////////////////////////////////////////////////////////// ViewerCtrlClass::~ViewerCtrlClass (void) { Free_Model (); REF_PTR_RELEASE (Light); REF_PTR_RELEASE (Camera); REF_PTR_RELEASE (Scene); return ; } //////////////////////////////////////////////////////////////// // // Create_Control_Renderer // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Create_Control_Renderer (void) { Render2DClass &renderer = ControlRenderer; // // Configure this renderer // renderer.Reset (); renderer.Enable_Texturing (false); ShaderClass *shader = renderer.Get_Shader (); renderer.Set_Z_Value (1.0F); shader->Set_Depth_Compare (ShaderClass::PASS_ALWAYS); shader->Set_Depth_Mask (ShaderClass::DEPTH_WRITE_ENABLE); shader->Set_Color_Mask (ShaderClass::COLOR_WRITE_ENABLE); // // Determine which color to draw the outline in // int color = StyleMgrClass::Get_Line_Color (); int bkcolor = StyleMgrClass::Get_Bk_Color (); if (IsEnabled == false) { color = StyleMgrClass::Get_Disabled_Line_Color (); bkcolor = StyleMgrClass::Get_Disabled_Bk_Color (); } // // Draw the window frame // renderer.Add_Rect (Rect, 1.0F, color, bkcolor); return ; } //////////////////////////////////////////////////////////////// // // On_Set_Cursor // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::On_Set_Cursor (const Vector2 &mouse_pos) { // // Change the mouse cursor // MouseMgrClass::Set_Cursor (MouseMgrClass::CURSOR_ROTATE); return ; } //////////////////////////////////////////////////////////////// // // Update_Client_Rect // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Update_Client_Rect (void) { // // Set the client area // ClientRect = Rect; // // Calculate what the horizontal and vertical field of view // should be for this window. // float hfov = 0; float vfov = 0; float cx = Rect.Width (); float cy = Rect.Height (); if (cy > cx) { vfov = DEG_TO_RADF (45.0F); hfov = (cx / cy) * vfov; } else { hfov = DEG_TO_RADF (45.0F); vfov = (cy / cx) * hfov; } // // Set the new view plane for the camera // Camera->Set_View_Plane (hfov, vfov); // // Calculate a viewport that we can use to restraint // the model to our window. // const RectClass &screen_rect = Render2DClass::Get_Screen_Resolution (); Vector2 upper_left = Rect.Upper_Left (); Vector2 lower_right = Rect.Lower_Right (); upper_left.X /= screen_rect.Width (); lower_right.X /= screen_rect.Width (); upper_left.Y /= screen_rect.Height (); lower_right.Y /= screen_rect.Height (); // // Set the viewport to render only to our window // Camera->Set_Viewport (upper_left, lower_right); Set_Dirty (); return ; } //////////////////////////////////////////////////////////////// // // Render // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Render (void) { // // Recreate the renderers (if necessary) // if (IsDirty) { Create_Control_Renderer (); } // // Render the background // if (IsBackgroundVisible) { ControlRenderer.Render (); } // // Render the 3D object // if (Scene != NULL) { WW3D::Render (Scene, Camera); } DialogControlClass::Render (); return ; } //////////////////////////////////////////////////////////////// // // On_LButton_Down // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::On_LButton_Down (const Vector2 &mouse_pos) { // Put the viewer interface into virtual trackball mode. Set_Interface_Mode (ViewerCtrlClass::VIRTUAL_TRACKBALL); return ; } //////////////////////////////////////////////////////////////// // // On_LButton_Up // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::On_LButton_Up (const Vector2 &mouse_pos) { return ; } //////////////////////////////////////////////////////////////// // // Set_Model // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Set_Model (const char *model_name) { // // Load the new model // RenderObjClass *new_model = WW3DAssetManager::Get_Instance ()->Create_Render_Obj (model_name); Set_Model (new_model); return ; } //////////////////////////////////////////////////////////////// // // Set_Model // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Set_Model (RenderObjClass *new_model) { // // Start fresh // Free_Model (); if (new_model != NULL) { Model = new_model; // // Notify any advise sinks // ADVISE_NOTIFY (On_ViewerCtrl_Model_Loaded (this, Get_ID (), Model)); // // Force the high LOD // int count = Model->Get_LOD_Count (); Model->Set_LOD_Level (max (count-1, 0)); Model->Set_Transform (Matrix3D (1)); // // Add the model to the scene // Scene->Add_Render_Object (Model); // // Force a camera update // IsCameraDirty = true; } return ; } //////////////////////////////////////////////////////////////// // // Calculate_Camera_Position // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Calculate_Camera_Position (void) { if (Model == NULL) { return ; } Model->Update_Obj_Space_Bounding_Volumes (); Model->Update_Sub_Object_Transforms (); // // Get the bounds of this object // bool is_first = true; BoundingBox.Center.Set (0, 0, 0); BoundingBox.Extent.Set (0, 0, 0); Get_Visible_Bounding_Box (&BoundingBox, Model, is_first); // // Get information about the camera // Vector2 vpmin, vpmax; Camera->Get_View_Plane (vpmin, vpmax); float znear = 0; float zfar = 0; Camera->Get_Clip_Planes (znear, zfar); // // Calculate the closest distance we can get // without clipping the rotating object // float width = WWMath::Sqrt (BoundingBox.Extent.X * BoundingBox.Extent.X * 4 + BoundingBox.Extent.Y * BoundingBox.Extent.Y * 4); float height = BoundingBox.Extent.Z * 2; float near_width = (vpmax.X - vpmin.X) * znear; float far_width = (vpmax.X - vpmin.X) * zfar; float near_height = (vpmax.Y - vpmin.Y) * znear; float far_height = (vpmax.Y - vpmin.Y) * zfar; float percent1 = (width - near_width) / (far_width - near_width); float percent2 = (height - near_height) / (far_height - near_height); float test_z1 = znear + (zfar - znear) * percent1; float test_z2 = znear + (zfar - znear) * percent2; Distance = (max (test_z1, test_z2) * 1.2F); Distance = max (MinCameraDist, Distance); // // Reposition the camera to be looking at this position // Vector3 center = BoundingBox.Center; Vector3 pos = center + Vector3 (Distance, 0, 0); Matrix3D tm; tm.Look_At (pos, center, 0); Camera->Set_Transform (tm); // // Remember that's we've updated // IsCameraDirty = false; return ; } //////////////////////////////////////////////////////////////// // // Set_Animation // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Set_Animation (const char *anim_name) { if (Model == NULL) { return ; } if (anim_name[0] != 0) { // // Play the animation on the model // HAnimClass *anim = WW3DAssetManager::Get_Instance ()->Get_HAnim (anim_name); if (anim != NULL) { Model->Set_Animation (anim, 0, RenderObjClass::ANIM_MODE_LOOP); REF_PTR_RELEASE (anim); } } else { // // Stop the animation // Model->Set_Animation (); } // // Force a camera update // IsCameraDirty = true; return ; } //////////////////////////////////////////////////////////////// // // Free_Model // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Free_Model (void) { if (Model != NULL) { Model->Remove (); REF_PTR_RELEASE (Model); } return ; } //////////////////////////////////////////////////////////////// // // Set_Interface_Mode // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::Set_Interface_Mode (InterfaceModeEnum mode, float rotationrate) { // If already in the desired mode do not reset the rotation. if (mode != InterfaceMode) { InterfaceMode = mode; ZRotation = DEG_TO_RADF (0.0f); } RotationRate = DEG_TO_RADF (rotationrate); } //////////////////////////////////////////////////////////////// // // On_Frame_Update // //////////////////////////////////////////////////////////////// void ViewerCtrlClass::On_Frame_Update (void) { // // Update the camera's data if necessary // if (IsCameraDirty) { Calculate_Camera_Position (); } switch (InterfaceMode) { case Z_ROTATION: { float delta = ((DialogMgrClass::Get_Frame_Time () / 1000.0F) * RotationRate); // // Adjust our rotation // ZRotation -= delta; if (ZRotation < 0) { ZRotation += DEG_TO_RADF (360); } // // Rotate the camera's position about the object // Matrix3D rotation_tm (1); rotation_tm.Rotate_Z (ZRotation); Vector3 position = rotation_tm.Rotate_Vector (Vector3 (Distance, 0, 0)) + BoundingBox.Center; // // Update the camera's position // Matrix3D tm; tm.Look_At (position, BoundingBox.Center, 0); Camera->Set_Transform (tm); break; } case VIRTUAL_TRACKBALL: if (DialogMgrClass::Is_Button_Down (VK_LBUTTON)) { const RectClass &rect = Get_Window_Rect(); const Vector2 mouseposition (DialogMgrClass::Get_Mouse_Pos().X, DialogMgrClass::Get_Mouse_Pos().Y); if (rect.Contains (mouseposition)) { int width, height, bits; bool windowed; float oow, ooh; Vector2 viewportmin, viewportmax, viewportextent, viewportcenter; float a, b; Vector2 p0, p1; Quaternion rotation; Matrix3D transform, inversetransform; Vector3 objectcenter; WW3D::Get_Render_Target_Resolution (width, height, bits, windowed); oow = 1.0f / width; ooh = 1.0f / height; Camera->Get_Viewport (viewportmin, viewportmax); viewportextent = viewportmax - viewportmin; viewportcenter = 0.5f * (viewportmin + viewportmax); a = 1.0f / viewportextent.X; b = -1.0f / viewportextent.Y; p0.Set (LastMousePosition.X, LastMousePosition.Y); p0.Scale (oow, ooh); p0 -= viewportcenter; p0.Scale (a, b); p1.Set (DialogMgrClass::Get_Mouse_Pos().X, DialogMgrClass::Get_Mouse_Pos().Y); p1.Scale (oow, ooh); p1 -= viewportcenter; p1.Scale (a, b); rotation = ::Trackball (p0.X, p0.Y, p1.X, p1.Y, 0.5f); transform = Camera->Get_Transform(); transform.Get_Orthogonal_Inverse (inversetransform); objectcenter = inversetransform * BoundingBox.Center; transform.Translate (objectcenter); Matrix3D::Multiply (transform, Build_Matrix3D (rotation), &transform); transform.Translate (-objectcenter); Camera->Set_Transform (transform); } } break; } // Place the light source at the camera position. Light->Set_Transform (Camera->Get_Transform()); LastMousePosition = DialogMgrClass::Get_Mouse_Pos(); DialogControlClass::On_Frame_Update (); return ; } /////////////////////////////////////////////////////////////// // // Get_Visible_Bounding_Box // /////////////////////////////////////////////////////////////// void ViewerCtrlClass::Get_Visible_Bounding_Box (AABoxClass *box, RenderObjClass *render_obj, bool &is_first) { if (render_obj == NULL) { return ; } // // Recursively walk through the subobjects // for (int index = 0; index < render_obj->Get_Num_Sub_Objects (); index ++) { RenderObjClass *sub_obj = render_obj->Get_Sub_Object (index); // // Recurse into this sub-object // if (sub_obj != NULL && (sub_obj->Is_Hidden () == false)) { Get_Visible_Bounding_Box (box, sub_obj, is_first); } REF_PTR_RELEASE (sub_obj); } // // Is this a mesh? // if (render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) { MeshClass *mesh = reinterpret_cast (render_obj); // // Dig out the mesh's model // MeshModelClass *mesh_model = mesh->Get_Model (); if (mesh_model != NULL) { // // Get the vertex count // int vertex_count = mesh_model->Get_Vertex_Count (); Vector3 *vertex_array = NULL; // // Get a pointer to the vertices // bool is_skin = (mesh_model->Get_Flag (MeshGeometryClass::SKIN) != 0); if (is_skin) { vertex_array = new Vector3[vertex_count]; mesh->Get_Deformed_Vertices (vertex_array); } else { vertex_array = mesh_model->Get_Vertex_Array (); } // // Loop over all the verts inside this model // for (int index = 0; index < vertex_count; index ++) { // // Get the world-space vertex position // Vector3 vertex_pos = vertex_array[index]; if (is_skin == false) { vertex_pos = mesh->Get_Transform () * vertex_array[index]; } // // Expand our box to account for this vertex // if (is_first) { box->Center = vertex_pos; is_first = false; } else { box->Add_Point (vertex_pos); } } // // Free the vertex array (if necessary) // if (is_skin) { delete [] vertex_array; vertex_array = NULL; } REF_PTR_RELEASE (mesh_model); } } return ; }