/* ** 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 : WW3D * * * * $Archive:: /Commando/Code/ww3d2/agg_def.cpp $* * * * Author:: Patrick Smith * * * $Modtime:: 4/05/01 10:21a $* * * * $Revision:: 5 $* * * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "agg_def.h" #include "htree.h" #include "w3derr.h" #include "chunkio.h" #include "wwdebug.h" #include "assetmgr.h" #include "matinfo.h" #include "texture.h" #include "wwstring.h" #include /////////////////////////////////////////////////////////////////////////////////// // // Local constants // const char * const EMPTY_STRING = ""; /////////////////////////////////////////////////////////////////////////////////// // // Global variable initialization // AggregateLoaderClass _AggregateLoader; /////////////////////////////////////////////////////////////////////////////////// // // AggregateDefClass // AggregateDefClass::AggregateDefClass (void) : m_pName (NULL) { // Set our member data to default settings ::memset (&m_Info, 0, sizeof (m_Info)); ::memset (&m_MiscInfo, 0, sizeof (m_MiscInfo)); m_MiscInfo.OriginalClassID = RenderObjClass::CLASSID_HLOD; return ; } /////////////////////////////////////////////////////////////////////////////////// // // AggregateDefClass // AggregateDefClass::AggregateDefClass (const AggregateDefClass &src) : m_pName (NULL) { // Set our member data to default settings ::memset (&m_Info, 0, sizeof (m_Info)); ::memset (&m_MiscInfo, 0, sizeof (m_MiscInfo)); m_MiscInfo.OriginalClassID = RenderObjClass::CLASSID_HLOD; // Invoke the assignment operator (*this) = src; return ; } /////////////////////////////////////////////////////////////////////////////////// // // AggregateDefClass // AggregateDefClass::AggregateDefClass (RenderObjClass &base_model) : m_pName (NULL) { // Set our member data to default settings ::memset (&m_Info, 0, sizeof (m_Info)); ::memset (&m_MiscInfo, 0, sizeof (m_MiscInfo)); m_MiscInfo.OriginalClassID = RenderObjClass::CLASSID_HLOD; Initialize (base_model); return ; } /////////////////////////////////////////////////////////////////////////////////// // // ~AggregateDefClass // AggregateDefClass::~AggregateDefClass (void) { // Free the name buffer if necessary if (m_pName != NULL) { // free() is used because the buffer was allocated with ::_strdup(). ::free (m_pName); m_pName = NULL; } Free_Subobject_List (); return ; } /////////////////////////////////////////////////////////////////////////////////// // // operator= // const AggregateDefClass & AggregateDefClass::operator= (const AggregateDefClass &src) { int index; // Free the name buffer if necessary if (m_pName != NULL) { ::free (m_pName); m_pName = NULL; } // Start with a fresh set of data Free_Subobject_List (); // Copy the src object's name and info struct Set_Name (src.Get_Name ()); ::memcpy (&m_Info, &src.m_Info, sizeof (m_Info)); ::memcpy (&m_MiscInfo, &src.m_MiscInfo, sizeof (m_MiscInfo)); m_Version = src.m_Version; // Loop through all the entries in the src object's subobj list for (index = 0; index < src.m_SubobjectList.Count (); index ++) { W3dAggregateSubobjectStruct *pinfo = src.m_SubobjectList[index]; if (pinfo != NULL) { // Copy the src object's info for this subobj W3dAggregateSubobjectStruct *new_info = new W3dAggregateSubobjectStruct; ::memcpy (new_info, pinfo, sizeof (W3dAggregateSubobjectStruct)); // Add this subobj to our list m_SubobjectList.Add (new_info); } } // Return a reference to ourselves return *this; } /////////////////////////////////////////////////////////////////////////////////// // // Free_Subobject_List // void AggregateDefClass::Free_Subobject_List (void) { // Delete all the stucture pointers contained in the subobject list for (int index = 0; index < m_SubobjectList.Count (); index ++) { W3dAggregateSubobjectStruct *pinfo = m_SubobjectList[index]; if (pinfo) { delete pinfo; } } // Reset the lists contents m_SubobjectList.Delete_All (); return ; } /////////////////////////////////////////////////////////////////////////////////// // // Create // RenderObjClass * AggregateDefClass::Create (void) { // Attempt to create an instance of the hierarchy RenderObjClass *pmodel = Create_Render_Object (m_Info.BaseModelName); if (pmodel != NULL) { // Perform the aggregation Attach_Subobjects (*pmodel); // Let the new object know what its new name and base name are. pmodel->Set_Name (m_pName); pmodel->Set_Base_Model_Name (m_Info.BaseModelName); pmodel->Set_Sub_Objects_Match_LOD ((m_MiscInfo.Flags & W3D_AGGREGATE_FORCE_SUB_OBJ_LOD) == W3D_AGGREGATE_FORCE_SUB_OBJ_LOD); } else { WWDEBUG_SAY (("Unable to load aggregate %s.\r\n", m_Info.BaseModelName)); } // Return a pointer to the new aggregate return pmodel; } /////////////////////////////////////////////////////////////////////////////////// // // Find_Subobject // RenderObjClass * AggregateDefClass::Find_Subobject ( RenderObjClass &model, const char mesh_path[MESH_PATH_ENTRIES][MESH_PATH_ENTRY_LEN], const char bone_path[MESH_PATH_ENTRIES][MESH_PATH_ENTRY_LEN] ) { RenderObjClass *parent_model = &model; parent_model->Add_Ref (); // Loop through all the models in our "path" until we've either failed // or found the exact mesh we were looking for... for (int index = 1; (mesh_path[index][0] != 0) && (parent_model != NULL); index ++) { // Look one level deeper into the subobject chain... RenderObjClass *sub_obj = NULL; if (bone_path[index][0] == 0) { sub_obj = parent_model->Get_Sub_Object_By_Name (mesh_path[index]); } else { int bone_index = parent_model->Get_Bone_Index (bone_path[index]); int subobj_count = parent_model->Get_Num_Sub_Objects_On_Bone (bone_index); // Loop through all the subobjects on this bone for (int subobj_index = 0; (subobj_index < subobj_count) && (sub_obj == NULL); subobj_index ++) { // Is this the subobject we were looking for? RenderObjClass *ptemp_obj = parent_model->Get_Sub_Object_On_Bone (subobj_index, bone_index); if (::lstrcmpi (ptemp_obj->Get_Name (), mesh_path[index]) == 0) { sub_obj = ptemp_obj; } else { REF_PTR_RELEASE (ptemp_obj); } } } REF_PTR_RELEASE (parent_model); // The parent for the next iteration is the subobject on this one. parent_model = sub_obj; } // Return a pointer to the subobject return parent_model; } /////////////////////////////////////////////////////////////////////////////////// // // Attach_Subobjects // void AggregateDefClass::Attach_Subobjects (RenderObjClass &base_model) { // Now loop through all the subobjects and attach them to the appropriate bone for (int index = 0; index < m_SubobjectList.Count (); index ++) { W3dAggregateSubobjectStruct *psubobj_info = m_SubobjectList[index]; if (psubobj_info != NULL) { // Now create this subobject and attach it to its bone. RenderObjClass *prender_obj = Create_Render_Object (psubobj_info->SubobjectName); if (prender_obj != NULL) { // Attach this object to the requested bone if (base_model.Add_Sub_Object_To_Bone (prender_obj, psubobj_info->BoneName) == false) { WWDEBUG_SAY (("Unable to attach %s to %s.\r\n", psubobj_info->SubobjectName, psubobj_info->BoneName)); } // Release our hold on this pointer prender_obj->Release_Ref (); } else { WWDEBUG_SAY (("Unable to load aggregate subobject %s.\r\n", psubobj_info->SubobjectName)); } } } return ; } /////////////////////////////////////////////////////////////////////////////////// // // Create_Render_Object // RenderObjClass * AggregateDefClass::Create_Render_Object (const char *passet_name) { // Assume failure RenderObjClass *prender_obj = NULL; // Attempt to get an instance of the render object from the asset manager prender_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj (passet_name); // If we couldn't find the render object in the asset manager, then attempt to // load it from file if ((prender_obj == NULL) && Load_Assets (passet_name)) { // It should be in the asset manager now, so attempt to get it again. prender_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj (passet_name); } // Return a pointer to the render object return prender_obj; } /////////////////////////////////////////////////////////////////////////////////// // // Load_Assets // bool AggregateDefClass::Load_Assets (const char *passet_name) { // Assume failure bool retval = false; // Param OK? if (passet_name != NULL) { // Determine what the current working directory is char path[MAX_PATH]; ::GetCurrentDirectory (sizeof (path), path); // Ensure the path is directory delimited if (path[::lstrlen(path)-1] != '\\') { ::lstrcat (path, "\\"); } // Assume the filename is simply the "asset name" + the w3d extension ::lstrcat (path, passet_name); ::lstrcat (path, ".w3d"); // If the file exists, then load it into the asset manager. if (::GetFileAttributes (path) != 0xFFFFFFFF) { retval = WW3DAssetManager::Get_Instance()->Load_3D_Assets (path); } } // Return the true/false result code return retval; } /////////////////////////////////////////////////////////////////////////////////// // // Initialize // void AggregateDefClass::Initialize (RenderObjClass &base_model) { // Start with fresh lists Free_Subobject_List (); // Determine what the render objects original name was. const char *orig_model_name = base_model.Get_Base_Model_Name (); orig_model_name = (orig_model_name == NULL) ? base_model.Get_Name () : orig_model_name; // Record information about this base model ::lstrcpy (m_Info.BaseModelName, orig_model_name); m_Info.SubobjectCount = 0; m_MiscInfo.OriginalClassID = base_model.Class_ID (); m_MiscInfo.Flags = 0; m_MiscInfo.Flags |= base_model.Is_Sub_Objects_Match_LOD_Enabled () ? W3D_AGGREGATE_FORCE_SUB_OBJ_LOD : 0; // Pass the aggregate name along Set_Name (base_model.Get_Name ()); // Create a new instance of the model which we can use // to compare with the supplied model and determine // which 'bones-models' and textures are new. RenderObjClass *pvanilla_model = (RenderObjClass *)Create_Render_Object (orig_model_name); // Build lists of changes from the delta between the original model and the provided one Build_Subobject_List (*pvanilla_model, base_model); // Release the model if necessary REF_PTR_RELEASE (pvanilla_model); return ; } /////////////////////////////////////////////////////////////////////////////////// // // Build_Subobject_List // void AggregateDefClass::Build_Subobject_List ( RenderObjClass &original_model, RenderObjClass &model ) { int index; // Loop through all the bones in this render obj int bone_count = model.Get_Num_Bones (); for (int bone_index = 0; bone_index < bone_count; bone_index ++) { const char *pbone_name = model.Get_Bone_Name (bone_index); // Build a list of nodes that are contained in the vanilla model DynamicVectorClass orig_node_list; for (index = 0; index < original_model.Get_Num_Sub_Objects_On_Bone (bone_index); index ++) { RenderObjClass *psubobj = original_model.Get_Sub_Object_On_Bone (index, bone_index); if (psubobj != NULL) { orig_node_list.Add (psubobj); } } // Build a list of nodes that are contained in this bone DynamicVectorClass node_list; for (index = 0; index < model.Get_Num_Sub_Objects_On_Bone (bone_index); index ++) { RenderObjClass *psubobj = model.Get_Sub_Object_On_Bone (index, bone_index); if (psubobj != NULL) { node_list.Add (psubobj); } } int node_count = node_list.Count (); if (node_count > 0) { // Loop through the subobjects and add each one to our internal list W3dAggregateSubobjectStruct subobj_info = { 0 }; for (int node_index = 0; node_index < node_count; node_index ++) { RenderObjClass *psubobject = node_list[node_index]; WWASSERT (psubobject != NULL); // Is this subobject new? (i.e. not in a 'vanilla' instance?) const char *prototype_name = psubobject->Get_Name (); if (psubobject != NULL && (Is_Object_In_List (prototype_name, orig_node_list) == false)) { // Add this subobject to our list ::lstrcpy (subobj_info.SubobjectName, prototype_name); ::lstrcpy (subobj_info.BoneName, pbone_name); Add_Subobject (subobj_info); m_Info.SubobjectCount ++; // Attach this render object to the 'original' model (this is done // so we can do texture compares later) RenderObjClass *prender_obj = WW3DAssetManager::Get_Instance ()->Create_Render_Obj (prototype_name); ((RenderObjClass &)original_model).Add_Sub_Object_To_Bone (prender_obj, pbone_name); REF_PTR_RELEASE (prender_obj); } } } // Free our hold on the render objs in the original node list for (index = 0; index < orig_node_list.Count (); index ++) { REF_PTR_RELEASE (orig_node_list[index]); } orig_node_list.Delete_All (); // Free our hold on the render objs in the node list for (index = 0; index < node_list.Count (); index ++) { REF_PTR_RELEASE (node_list[index]); } node_list.Delete_All (); } return ; } /////////////////////////////////////////////////////////////////////////////////// // // Is_Object_In_List // bool AggregateDefClass::Is_Object_In_List ( const char *passet_name, DynamicVectorClass &node_list ) { // Assume failure bool retval = false; // Loop through the nodes in the list until we've found the one // were are looking for. for (int node_index = 0; (node_index < node_list.Count ()) && (retval == false); node_index ++) { RenderObjClass *prender_obj = node_list[node_index]; // Is this the render object we were looking for? if (prender_obj != NULL && ::lstrcmpi (prender_obj->Get_Name (), passet_name) == 0) { retval = true; } } // Return the true/false result code return retval; } /////////////////////////////////////////////////////////////////////////////////// // // Load // WW3DErrorType AggregateDefClass::Load_W3D (ChunkLoadClass &chunk_load) { W3dTextureReplacerHeaderStruct header = { 0 }; while (chunk_load.Open_Chunk()) { WW3DErrorType error = WW3D_ERROR_OK; switch (chunk_load.Cur_Chunk_ID()) { case W3D_CHUNK_AGGREGATE_HEADER: error = Read_Header(chunk_load); break; case W3D_CHUNK_AGGREGATE_INFO: error = Read_Info(chunk_load); break; case W3D_CHUNK_TEXTURE_REPLACER_INFO: if (chunk_load.Read (&header, sizeof (header)) == sizeof (header)) { if (header.ReplacedTexturesCount > 0) { WWDEBUG_SAY(("Obsolete texture replacement chunk encountered in aggregate: %s\r\n",m_pName)); } } break; case W3D_CHUNK_AGGREGATE_CLASS_INFO: error = Read_Class_Info(chunk_load); break; default: // Unknown chunk. break; } chunk_load.Close_Chunk(); if (error != WW3D_ERROR_OK) return (error); } return WW3D_ERROR_OK; } /////////////////////////////////////////////////////////////////////////////////// // // Read_Header // WW3DErrorType AggregateDefClass::Read_Header (ChunkLoadClass &chunk_load) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_LOAD_FAILED; // Is this the header chunk? W3dAggregateHeaderStruct header = { 0 }; if (chunk_load.Read (&header, sizeof (header)) == sizeof (header)) { // Copy the name from the header structure m_pName = ::_strdup (header.Name); m_Version = header.Version; // Success! ret_val = WW3D_ERROR_OK; } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Read_Info // WW3DErrorType AggregateDefClass::Read_Info (ChunkLoadClass &chunk_load) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_LOAD_FAILED; // Read the chunk straight into our member structure ::memset (&m_Info, 0, sizeof (m_Info)); if (chunk_load.Read (&m_Info, sizeof (m_Info)) == sizeof (m_Info)) { // Success! ret_val = WW3D_ERROR_OK; // Read all the subobjects from the file for (UINT isubobject = 0; (isubobject < m_Info.SubobjectCount) && (ret_val == WW3D_ERROR_OK); isubobject ++) { // Read this subobject's definition from the file ret_val = Read_Subobject (chunk_load); } } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Read_Subobject // WW3DErrorType AggregateDefClass::Read_Subobject (ChunkLoadClass &chunk_load) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_LOAD_FAILED; // Read the subobject information from the file W3dAggregateSubobjectStruct subobj_info = { 0 }; if (chunk_load.Read (&subobj_info, sizeof (subobj_info)) == sizeof (subobj_info)) { // Add this subobject to our list Add_Subobject (subobj_info); // Success! ret_val = WW3D_ERROR_OK; } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Add_Subobject // void AggregateDefClass::Add_Subobject (const W3dAggregateSubobjectStruct &subobj_info) { // Create a new structure and copy the contents of the src W3dAggregateSubobjectStruct *pnew_entry = new W3dAggregateSubobjectStruct; ::lstrcpy (pnew_entry->SubobjectName, subobj_info.SubobjectName); ::lstrcpy (pnew_entry->BoneName, subobj_info.BoneName); // Add this new entry to the list m_SubobjectList.Add (pnew_entry); return ; } /////////////////////////////////////////////////////////////////////////////////// // // Read_Class_Info // WW3DErrorType AggregateDefClass::Read_Class_Info (ChunkLoadClass &chunk_load) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_LOAD_FAILED; // Read the chunk straight into our header structure ::memset (&m_MiscInfo, 0, sizeof (m_MiscInfo)); if (chunk_load.Read (&m_MiscInfo, sizeof (m_MiscInfo)) == sizeof (m_MiscInfo)) { // Success! ret_val = WW3D_ERROR_OK; } // Return the WW3D_ERROR_TYPE return code return ret_val; } ////////////////////////////////////////////////////////////////////////////////// // // Save // WW3DErrorType AggregateDefClass::Save_W3D (ChunkSaveClass &chunk_save) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED; // Begin a chunk that identifies an aggregate if (chunk_save.Begin_Chunk (W3D_CHUNK_AGGREGATE) == TRUE) { // Attempt to save the different sections of the aggregate definition if ((Save_Header (chunk_save) == WW3D_ERROR_OK) && (Save_Info (chunk_save) == WW3D_ERROR_OK) && (Save_Class_Info (chunk_save) == WW3D_ERROR_OK)) { // Success! ret_val = WW3D_ERROR_OK; } // Close the aggregate chunk chunk_save.End_Chunk (); } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Save_Header // WW3DErrorType AggregateDefClass::Save_Header (ChunkSaveClass &chunk_save) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED; // Begin a chunk that identifies the aggregate if (chunk_save.Begin_Chunk (W3D_CHUNK_AGGREGATE_HEADER) == TRUE) { // Fill the header structure W3dAggregateHeaderStruct header = { 0 }; header.Version = W3D_CURRENT_AGGREGATE_VERSION; ::lstrcpyn (header.Name, m_pName, sizeof (header.Name)); header.Name[sizeof (header.Name) - 1] = 0; // Write the header out to the chunk if (chunk_save.Write (&header, sizeof (header)) == sizeof (header)) { // Success! ret_val = WW3D_ERROR_OK; } // End the header chunk chunk_save.End_Chunk (); } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Save_Info // WW3DErrorType AggregateDefClass::Save_Info (ChunkSaveClass &chunk_save) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED; // Begin a chunk that identifies the aggregate settings if (chunk_save.Begin_Chunk (W3D_CHUNK_AGGREGATE_INFO) == TRUE) { // Write the settings structure out to the chunk if (chunk_save.Write (&m_Info, sizeof (m_Info)) == sizeof (m_Info)) { // Success! ret_val = WW3D_ERROR_OK; // Write all the subobjects to the file for (int isubobject = 0; (isubobject < m_SubobjectList.Count ()) && (ret_val == WW3D_ERROR_OK); isubobject ++) { // Write this object to the file ret_val = Save_Subobject (chunk_save, m_SubobjectList[isubobject]); } } // End the settings chunk chunk_save.End_Chunk (); } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Save_Subobject // WW3DErrorType AggregateDefClass::Save_Subobject ( ChunkSaveClass &chunk_save, W3dAggregateSubobjectStruct *psubobject ) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED; // Write the subobj structure out to the chunk if (chunk_save.Write (psubobject, sizeof (W3dAggregateSubobjectStruct)) == sizeof (W3dAggregateSubobjectStruct)) { // Success! ret_val = WW3D_ERROR_OK; } // Return the WW3D_ERROR_TYPE return code return ret_val; } ///////////////////////////////////////////////////////////////////////////////// // // Save_Class_Info // WW3DErrorType AggregateDefClass::Save_Class_Info (ChunkSaveClass &chunk_save) { // Assume error WW3DErrorType ret_val = WW3D_ERROR_SAVE_FAILED; // Begin a chunk that identifies the texture replacer header if (chunk_save.Begin_Chunk (W3D_CHUNK_AGGREGATE_CLASS_INFO) == TRUE) { // Write the class information structure out to the chunk if (chunk_save.Write (&m_MiscInfo, sizeof (m_MiscInfo)) == sizeof (m_MiscInfo)) { // Success! ret_val = WW3D_ERROR_OK; } // End the class info chunk chunk_save.End_Chunk (); } // Return the WW3D_ERROR_TYPE return code return ret_val; } /////////////////////////////////////////////////////////////////////////////////// // // Load // PrototypeClass * AggregateLoaderClass::Load_W3D (ChunkLoadClass &chunk_load) { // Assume failure AggregatePrototypeClass *pprototype = NULL; // Create a definition object AggregateDefClass *pdefinition = new AggregateDefClass; if (pdefinition != NULL) { // Ask the definition object to load the aggregate data if (pdefinition->Load_W3D (chunk_load) != WW3D_ERROR_OK) { // Error! Free the definition delete pdefinition; pdefinition = NULL; } else { // Success! Create a prototype from the definition pprototype = new AggregatePrototypeClass (pdefinition); } } // Return a pointer to the prototype return pprototype; }