/* ** 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 : LevelEdit * * * * $Archive:: /Commando/Code/Tools/LevelEdit/Node.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 1/18/02 11:54a $* * * * $Revision:: 49 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "node.h" #include "phys.h" #include "sceneeditor.h" #include "utils.h" #include "selectionbox.h" #include "mover.h" #include "chunkio.h" #include "preset.h" #include "wwstring.h" #include "presetmgr.h" #include "vispointgenerator.h" #include "mesh.h" #include "preset.h" #include "staticphys.h" #include "NodeInfoPage.h" #include "EditorPropSheet.h" #include "LevelEditView.h" #include "NodeMgr.h" #include "PositionPage.h" /////////////////////////////////////////////////////////////////////// // Constants /////////////////////////////////////////////////////////////////////// const Vector3 SEL_COLOR_NORMAL = Vector3 (0.8F, 0.8F, 0.8F); const Vector3 SEL_COLOR_LOCKED = Vector3 (0.0F, 0.0F, 0.8F); const Vector3 SEL_COLOR_INVALID = Vector3 (1.0F, 0.0F, 0.0F); enum { CHUNKID_VARIABLES = 0x00000100, CHUNKID_COMMENTS }; enum { VARID_NAME = 0x01, VARID_IS_LOCKED, VARID_PRESETID, VARID_ID, VARID_ROT_RESTRICT, VARID_THISPTR, VARID_TRANSFORM, VARID_VISID, VARID_COMMENTS, VARID_VISSECTORID, VARID_CULLLINK, VARID_NEEDS_SAVE, VARID_CONTAINER_ID, VARID_IS_PROXIED }; ////////////////////////////////////////////////////////////////////// // // NodeClass // ////////////////////////////////////////////////////////////////////// NodeClass::NodeClass (PresetClass *preset) : m_Preset (NULL), m_PresetID (0), m_ID (0), m_SelectionBox (NULL), m_IsLocked (false), m_RotationRestricted (false), m_Orientation (true), m_IsInScene (false), m_SelColor (SEL_COLOR_NORMAL), m_NextNode (NULL), m_PrevNode (NULL), m_HitTestInfo (this), m_NeedsSave (true), m_IsProxied (false), m_Transform (1), m_CullLink (-1), m_ContainerPresetID (0) { Set_Preset (preset); return ; } ////////////////////////////////////////////////////////////////////// // // NodeClass // ////////////////////////////////////////////////////////////////////// NodeClass::NodeClass (const NodeClass &src) : m_Preset (NULL), m_PresetID (0), m_ID (0), m_SelectionBox (NULL), m_IsLocked (false), m_RotationRestricted (false), m_Orientation (true), m_IsInScene (false), m_SelColor (SEL_COLOR_NORMAL), m_NextNode (NULL), m_PrevNode (NULL), m_HitTestInfo (this), m_NeedsSave (true), m_IsProxied (false), m_Transform (1), m_CullLink (-1), m_ContainerPresetID (0) { *this = src; return ; } ////////////////////////////////////////////////////////////////////// // // ~NodeClass // ////////////////////////////////////////////////////////////////////// NodeClass::~NodeClass (void) { SAFE_DELETE (m_SelectionBox); return ; } ////////////////////////////////////////////////////////////////////// // // Add_To_Scene // ////////////////////////////////////////////////////////////////////// void NodeClass::Add_To_Scene (void) { PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL && phys_obj->Get_Culling_System () == NULL) { // // Add the object to either the static or dynamic culling system // SceneEditorClass *scene = ::Get_Scene_Editor (); if (Is_Static ()) { scene->Add_Static_Object ((StaticPhysClass *)phys_obj, Get_Cull_Link ()); } else { scene->Add_Dynamic_Object (phys_obj); } } m_IsInScene = true; return ; } ////////////////////////////////////////////////////////////////////// // // Remove_From_Scene // ////////////////////////////////////////////////////////////////////// void NodeClass::Remove_From_Scene (void) { PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL && phys_obj->Get_Culling_System () != NULL) { // // Record the cull-link index (if necessary) // Update_Cached_Cull_Link (); // // Remove this object from either the static or dynamic culling system // SceneEditorClass *scene = ::Get_Scene_Editor (); if (Is_Static ()) { scene->Remove_Object ((StaticPhysClass *)phys_obj); } else { scene->Remove_Object (phys_obj); } } m_IsInScene = false; return ; } ////////////////////////////////////////////////////////////////////// // // Update_Cached_Cull_Link // ////////////////////////////////////////////////////////////////////// void NodeClass::Update_Cached_Cull_Link (void) { // // We can only do this if the physics object is currently in the scene // PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL && phys_obj->Get_Culling_System () != NULL) { // // Make sure this is a static physics object // StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass (); if (static_phys_obj != NULL) { // // Dig the cull link out from the culling system structure // AABTreeLinkClass *link = (AABTreeLinkClass *)static_phys_obj->Get_Cull_Link (); if (link != NULL) { m_CullLink = link->Node->Index; } } } return ; } ////////////////////////////////////////////////////////////////////// // // Peek_Render_Obj // ////////////////////////////////////////////////////////////////////// RenderObjClass * NodeClass::Peek_Render_Obj (void) const { RenderObjClass *render_obj = NULL; PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL) { render_obj = phys_obj->Peek_Model (); } return render_obj; } ////////////////////////////////////////////////////////////////////// // // Hide // ////////////////////////////////////////////////////////////////////// void NodeClass::Hide (bool hide) { RenderObjClass *render_obj = Peek_Render_Obj (); if (render_obj != NULL) { render_obj->Set_Hidden (hide); } return ; } ////////////////////////////////////////////////////////////////////// // // Is_Hidden // ////////////////////////////////////////////////////////////////////// bool NodeClass::Is_Hidden (void) const { bool is_hidden = false; RenderObjClass *render_obj = Peek_Render_Obj (); if (render_obj != NULL) { is_hidden = (render_obj->Is_Not_Hidden_At_All () == false); } return is_hidden; } ////////////////////////////////////////////////////////////////////// // // Show_Selection_Box // ////////////////////////////////////////////////////////////////////// void NodeClass::Show_Selection_Box (bool show) { if (show) { // // Create the selection box if necessary // if (m_SelectionBox == NULL) { m_SelectionBox = new SelectionBoxClass; } Update_Selection_Color (); m_SelectionBox->Display_Around_Node (*this); } else { // // Hide and destroy the selection box // if (m_SelectionBox != NULL) { m_SelectionBox->Remove_From_Scene (); SAFE_DELETE (m_SelectionBox); } } return ; } ///////////////////////////////////////////////////////////////// // // Update_Selection_Color // ///////////////////////////////////////////////////////////////// void NodeClass::Update_Selection_Color (void) { m_SelColor = m_IsLocked ? SEL_COLOR_LOCKED : SEL_COLOR_NORMAL; // // Only perform the bounding box check if we have a valid phys obj pointer // PhysClass *collision_obj = Peek_Collision_Obj (); if (collision_obj != NULL) { AABoxClass aabox; OBBoxClass obbox; bool is_aabox = ::Get_Collision_Box (collision_obj->Peek_Model (), aabox, obbox); if (is_aabox) { Vector3 move (0, 0, 0); // // See if the AABox intersects anything // collision_obj->Inc_Ignore_Counter(); CastResultStruct res; PhysAABoxCollisionTestClass boxtest (aabox, move, &res, GAME_COLLISION_GROUP); PhysicsSceneClass::Get_Instance()->Cast_AABox (boxtest); collision_obj->Dec_Ignore_Counter(); if (boxtest.Result->StartBad) { m_SelColor = SEL_COLOR_INVALID; } } else { Vector3 move (0, 0, 0); // // See if the OBBox intersects anything // collision_obj->Inc_Ignore_Counter(); CastResultStruct res; PhysOBBoxCollisionTestClass boxtest (obbox, move, &res, GAME_COLLISION_GROUP); PhysicsSceneClass::Get_Instance()->Cast_OBBox (boxtest); collision_obj->Dec_Ignore_Counter(); if (boxtest.Result->StartBad) { m_SelColor = SEL_COLOR_INVALID; } } } // // Update the selection box if it exists // if (m_SelectionBox != NULL) { m_SelectionBox->Set_Color (m_SelColor); } return ; } ///////////////////////////////////////////////////////////////// // // Update_Selection_Box // ///////////////////////////////////////////////////////////////// void NodeClass::Update_Selection_Box (void) { // // If we have a selection box, then update its color and position // if (m_SelectionBox != NULL) { Update_Selection_Color (); m_SelectionBox->Display_Around_Node (*this); } return ; } ///////////////////////////////////////////////////////////////// // // Lock // ///////////////////////////////////////////////////////////////// void NodeClass::Lock (bool lock) { m_IsLocked = lock; Update_Selection_Color (); return ; } ///////////////////////////////////////////////////////////////// // // Is_Locked // ///////////////////////////////////////////////////////////////// bool NodeClass::Is_Locked (void) const { return m_IsLocked; } ///////////////////////////////////////////////////////////////// // // Translate // ///////////////////////////////////////////////////////////////// void NodeClass::Translate (const Vector3 &vector) { MoverClass::Translate_Node (this, vector); return ; } ///////////////////////////////////////////////////////////////// // // Rotate // ///////////////////////////////////////////////////////////////// void NodeClass::Rotate ( const Matrix3D &rotation_matrix, const Matrix3D &coord_system ) { MoverClass::Rotate_Node (this, rotation_matrix, coord_system); return ; } ///////////////////////////////////////////////////////////////// // // On_Rotate // ///////////////////////////////////////////////////////////////// void NodeClass::On_Rotate (void) { m_Orientation = ::Build_Quaternion (Get_Transform ()); return ; } ///////////////////////////////////////////////////////////////// // // On_Translate // ///////////////////////////////////////////////////////////////// void NodeClass::On_Translate (void) { m_Orientation = ::Build_Quaternion (Get_Transform ()); return ; } ///////////////////////////////////////////////////////////////// // // On_Transform // ///////////////////////////////////////////////////////////////// void NodeClass::On_Transform (void) { m_Orientation = ::Build_Quaternion (Get_Transform ()); return ; } ///////////////////////////////////////////////////////////////// // // Save // ///////////////////////////////////////////////////////////////// bool NodeClass::Save (ChunkSaveClass &csave) { // // Update the preset-id // if (m_Preset != NULL) { m_PresetID = m_Preset->Get_ID (); } // // Make sure we've got the latest cull-link information // Update_Cached_Cull_Link (); Update_Cached_Vis_IDs (); // // Save the comments to their own chunk // WRITE_WWSTRING_CHUNK (csave, CHUNKID_COMMENTS, m_Comments); // // Save the variables // csave.Begin_Chunk (CHUNKID_VARIABLES); WRITE_MICRO_CHUNK_STRING (csave, VARID_NAME, (LPCTSTR)m_Name); WRITE_MICRO_CHUNK (csave, VARID_IS_LOCKED, m_IsLocked); WRITE_MICRO_CHUNK (csave, VARID_ID, m_ID); WRITE_MICRO_CHUNK (csave, VARID_ROT_RESTRICT, m_RotationRestricted); WRITE_MICRO_CHUNK (csave, VARID_TRANSFORM, m_Transform); WRITE_MICRO_CHUNK (csave, VARID_PRESETID, m_PresetID); WRITE_MICRO_CHUNK (csave, VARID_CULLLINK, m_CullLink); WRITE_MICRO_CHUNK (csave, VARID_NEEDS_SAVE, m_NeedsSave); WRITE_MICRO_CHUNK (csave, VARID_IS_PROXIED, m_IsProxied); WRITE_MICRO_CHUNK (csave, VARID_CONTAINER_ID, m_ContainerPresetID); // // Save the node's vis ID (if it has one) // PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL) { StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass (); if (static_phys_obj != NULL) { uint32 vis_id = static_phys_obj->Get_Vis_Object_ID (); WRITE_MICRO_CHUNK (csave, VARID_VISID, vis_id); vis_id = static_phys_obj->Get_Vis_Sector_ID(); WRITE_MICRO_CHUNK (csave, VARID_VISSECTORID, vis_id); } } // // For pointer remapping // NodeClass *this_ptr = this; WRITE_MICRO_CHUNK (csave, VARID_THISPTR, this_ptr); csave.End_Chunk (); return true; } ///////////////////////////////////////////////////////////////// // // Load // ///////////////////////////////////////////////////////////////// bool NodeClass::Load (ChunkLoadClass &cload) { while (cload.Open_Chunk ()) { switch (cload.Cur_Chunk_ID ()) { READ_WWSTRING_CHUNK (cload, CHUNKID_COMMENTS, m_Comments); case CHUNKID_VARIABLES: Load_Variables (cload); break; } cload.Close_Chunk (); } return true; } ///////////////////////////////////////////////////////////////// // // Load_Variables // ///////////////////////////////////////////////////////////////// bool NodeClass::Load_Variables (ChunkLoadClass &cload) { PresetClass *old_this_ptr = NULL; while (cload.Open_Micro_Chunk ()) { switch (cload.Cur_Micro_Chunk_ID ()) { READ_MICRO_CHUNK (cload, VARID_IS_LOCKED, m_IsLocked); READ_MICRO_CHUNK (cload, VARID_ID, m_ID); READ_MICRO_CHUNK (cload, VARID_ROT_RESTRICT, m_RotationRestricted); READ_MICRO_CHUNK (cload, VARID_PRESETID, m_PresetID); READ_MICRO_CHUNK (cload, VARID_THISPTR, old_this_ptr) READ_MICRO_CHUNK (cload, VARID_TRANSFORM, m_Transform) READ_MICRO_CHUNK_WWSTRING (cload, VARID_NAME, m_Name); READ_MICRO_CHUNK_WWSTRING (cload, VARID_COMMENTS, m_Comments); READ_MICRO_CHUNK (cload, VARID_CULLLINK, m_CullLink); READ_MICRO_CHUNK (cload, VARID_NEEDS_SAVE, m_NeedsSave); READ_MICRO_CHUNK (cload, VARID_IS_PROXIED, m_IsProxied); READ_MICRO_CHUNK (cload, VARID_CONTAINER_ID, m_ContainerPresetID); case VARID_VISID: { uint32 vis_id = 0; cload.Read (&vis_id, sizeof (vis_id)); // // Pass the vis-object-id onto the physics object (if it needs one) // PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL) { StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass (); if (static_phys_obj != NULL) { static_phys_obj->Set_Vis_Object_ID (vis_id); } } } case VARID_VISSECTORID: { uint32 vis_id = 0; cload.Read (&vis_id, sizeof (vis_id)); // // Pass the vis-sector-id onto the physics object (if it needs one) // PhysClass *phys_obj = Peek_Physics_Obj (); if (phys_obj != NULL) { StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass (); if (static_phys_obj != NULL) { static_phys_obj->Set_Vis_Sector_ID (vis_id); } } } break; } cload.Close_Micro_Chunk (); } // // Handle pointer remapping // WWASSERT (old_this_ptr != NULL); SaveLoadSystemClass::Register_Pointer (old_this_ptr, this); // // Get the preset pointer from its ID // m_Preset = PresetMgrClass::Find_Preset (m_PresetID); return true; } ///////////////////////////////////////////////////////////////// // // Has_Vis_Sectors // ///////////////////////////////////////////////////////////////// bool NodeClass::Has_Vis_Sectors (RenderObjClass *render_obj) { bool retval = false; // // Start with the parent render obj if none is supplied. // if (render_obj == NULL) { render_obj = Peek_Render_Obj (); } if (render_obj != NULL) { // // Loop through all the render objects sub-objects // int count = render_obj->Get_Num_Sub_Objects (); for (int index = 0; (index < count) && !retval; index ++) { // Get a pointer to this subobject RenderObjClass *sub_object = render_obj->Get_Sub_Object (index); if (sub_object != NULL) { retval |= Has_Vis_Sectors (sub_object); MEMBER_RELEASE (sub_object); } } // // Is this render object a mesh? // if ((render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) && (render_obj->Get_Collision_Type () & COLLISION_TYPE_VIS)) { retval = true; } } return retval; } ///////////////////////////////////////////////////////////////// // // Add_Vis_Points // ///////////////////////////////////////////////////////////////// void NodeClass::Add_Vis_Points ( VisPointGeneratorClass & generator, RenderObjClass * render_obj ) { // // Let the generator know which node its processing // generator.Set_Current_Node (this); // // Start with the parent render obj if none is supplied. // if (render_obj == NULL) { render_obj = Peek_Render_Obj (); } // // Loop through all the render objects sub-objects // int count = render_obj->Get_Num_Sub_Objects (); for (int index = 0; index < count; index ++) { // Get a pointer to this subobject RenderObjClass *sub_object = render_obj->Get_Sub_Object (index); if (sub_object != NULL) { Add_Vis_Points (generator, sub_object); MEMBER_RELEASE (sub_object); } } // // Is this render object a mesh? // if ((render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) && (render_obj->Get_Collision_Type () & COLLISION_TYPE_VIS)) { // // Pass this mesh off to the generator // MeshClass *mesh = static_cast (render_obj); generator.Submit_Mesh (*mesh); } return ; } ///////////////////////////////////////////////////////////////// // // Reload // ///////////////////////////////////////////////////////////////// void NodeClass::Reload (void) { CLevelEditView::Allow_Repaint (false); bool in_scene = m_IsInScene; if (in_scene) { Remove_From_Scene (); } // // Reinitialize the node from its preset // //m_Preset = PresetMgrClass::Find_Preset (m_PresetID); Initialize (); if (in_scene) { Add_To_Scene (); } CLevelEditView::Allow_Repaint (true); return ; } ///////////////////////////////////////////////////////////////// // // Show_Settings_Dialog // ///////////////////////////////////////////////////////////////// bool NodeClass::Show_Settings_Dialog (void) { NodeInfoPageClass info_tab (this); PositionPageClass pos_tab (this); EditorPropSheetClass prop_sheet; prop_sheet.Add_Page (&info_tab); prop_sheet.Add_Page (&pos_tab); // Show the property sheet UINT ret_code = prop_sheet.DoModal (); // Return true if the user clicked OK return (ret_code == IDOK); } ///////////////////////////////////////////////////////////////// // // operator= // ///////////////////////////////////////////////////////////////// const NodeClass & NodeClass::operator= (const NodeClass &src) { // // Copy the data that makes sense to copy. // m_Comments = src.m_Comments; m_PresetID = src.m_PresetID; m_IsLocked = src.m_IsLocked; m_RotationRestricted = src.m_RotationRestricted; m_Preset = src.m_Preset; m_Orientation = src.m_Orientation; m_SelColor = src.m_SelColor; m_Transform = src.m_Transform; // // Make sure the node has a unique id and name // NodeMgrClass::Setup_Node_Identity (*this); // // Give the node a chance to create any internal // data (game objects, phys objects, etc) it needs. // Initialize (); return *this; } ///////////////////////////////////////////////////////////////// // // Set_ID // ///////////////////////////////////////////////////////////////// void NodeClass::Set_ID (uint32 id) { m_ID = id; NodeMgrClass::Setup_Node_Identity (*this); return ; } ///////////////////////////////////////////////////////////////// // // Get_Model_Name // ///////////////////////////////////////////////////////////////// LPCTSTR NodeClass::Get_Model_Name (void) const { LPCTSTR name = NULL; RenderObjClass *render_obj = Peek_Render_Obj (); if (render_obj != NULL) { name = render_obj->Get_Name (); } return name; }