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/wwui/viewerctrl.cpp

711 lines
17 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/>.
*/
/***********************************************************************************************
*** 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<MeshClass *> (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 ;
}