diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 60de88bfb66..e8cc3a81e65 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -43,6 +43,7 @@ * HLodDefClass::Save_Header -- writes the HLodDef header * * HLodDefClass::Save_Lod_Array -- Saves the lod array * * HLodDefClass::Save_Aggregate_Array -- Save the array of aggregate models * + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * HLodDefClass::read_header -- loads the HLodDef header from a W3d file * * HLodDefClass::read_proxy_array -- load the proxy names * @@ -51,6 +52,7 @@ * HLodDefClass::SubObjectArrayClass::Save_W3D -- saves a w3d file for this HLodDef * * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * HLodDefClass::SubObjectArrayClass::Reset -- release the contents of this array * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * HLodClass::HLodClass -- Constructor * * HLodClass::HLodClass -- copy constructor * * HLodClass::HLodClass -- Constructor * @@ -344,6 +346,7 @@ void HLodDefClass::Free() Lod = nullptr; LodCount = 0; + Aggregates.Reset(); REF_PTR_RELEASE(ProxyArray); } @@ -408,6 +411,9 @@ void HLodDefClass::Initialize(HLodClass &src_lod) Lod[index].BoneIndex = bone_indicies; } } + + Aggregates.Copy_Additional_Models(src_lod); + REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -433,7 +439,9 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) // Attempt to save the different sections of the aggregate definition if ((Save_Header (csave) == WW3D_ERROR_OK) && - (Save_Lod_Array (csave) == WW3D_ERROR_OK)) { + (Save_Lod_Array (csave) == WW3D_ERROR_OK) && + (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && + (Save_Proxy_Array (csave) == WW3D_ERROR_OK)) { // Success! ret_val = WW3D_ERROR_OK; @@ -516,7 +524,11 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = Lod[lod_index].Save_W3D (csave); + success &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + if (success) { + success &= Lod[lod_index].Save_W3D (csave); + success &= csave.End_Chunk (); + } } // Return the WW3DErrorType return code @@ -541,12 +553,68 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) { + bool success = true; if (Aggregates.ModelCount > 0) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); - Aggregates.Save_W3D(csave); - csave.End_Chunk(); + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); + if (success) { + success &= Aggregates.Save_W3D(csave); + success &= csave.End_Chunk(); + } } - return WW3D_ERROR_OK; + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; +} + + +/*********************************************************************************************** + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * + * * + * The screen size in the array header is 0. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) +{ + bool success = true; + + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + + if (success) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + + if (success) { + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + success &= (csave.Write(&header,sizeof(header)) == sizeof(header)); + success &= csave.End_Chunk(); + } + + for (int imodel=0; success && imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); + } + } + success &= csave.End_Chunk(); + } + } + + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } @@ -772,6 +840,35 @@ void HLodDefClass::SubObjectArrayClass::Reset() } +/*********************************************************************************************** + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +void HLodDefClass::SubObjectArrayClass::Copy_Additional_Models(const HLodClass & hlod) +{ + Reset(); + + ModelCount = hlod.Get_Additional_Model_Count (); + if (ModelCount > 0) { + MaxScreenSize = 0.0f; + ModelName = W3DNEWARRAY char *[ModelCount]; + BoneIndex = W3DNEWARRAY int[ModelCount]; + for (int model_index = 0; model_index < ModelCount; model_index ++) { + ModelName[model_index] = ::strdup (hlod.Peek_Additional_Model (model_index)->Get_Name ()); + BoneIndex[model_index] = hlod.Get_Additional_Model_Bone (model_index); + } + } +} + + /*********************************************************************************************** * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * * @@ -834,58 +931,54 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * * * HISTORY: * * 1/26/00 gth : Created. * + * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * + * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY. * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { // Assume error bool ret_val = false; - // Begin a chunk that identifies the LOD array - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE) { - - // Begin a chunk that identifies the LOD header - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { + // Begin a chunk that identifies the LOD header + if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ModelCount; - header.MaxScreenSize = MaxScreenSize; + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ModelCount; + header.MaxScreenSize = MaxScreenSize; - // Write the LOD header structure out to the chunk - ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); + // Write the LOD header structure out to the chunk + ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); - // End the header chunk - csave.End_Chunk (); - } + // End the header chunk + csave.End_Chunk (); + } - if (ret_val) { + if (ret_val) { - // Write all of this LOD's models to the file - for (int index = 0; - (index < ModelCount) && ret_val; - index ++) { + // Write all of this LOD's models to the file + for (int index = 0; + (index < ModelCount) && ret_val; + index ++) { - // Save this LOD sub-obj to the chunk - ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); - if (ret_val) { + // Save this LOD sub-obj to the chunk + ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); + if (ret_val) { - W3dHLodSubObjectStruct info = { 0 }; - info.BoneIndex = BoneIndex[index]; + W3dHLodSubObjectStruct info = { 0 }; + info.BoneIndex = BoneIndex[index]; - // Copy this model name into the structure - ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); - info.Name[sizeof (info.Name) - 1] = 0; + // Copy this model name into the structure + ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); + info.Name[sizeof (info.Name) - 1] = 0; - // Write the LOD sub-obj structure out to the chunk - ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); + // Write the LOD sub-obj structure out to the chunk + ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); - // End the sub-obj chunk - csave.End_Chunk (); - } + // End the sub-obj chunk + csave.End_Chunk (); } } - - // End the HLOD-Array chunk - csave.End_Chunk (); } // Return the true/false result code diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 02cc810ec9c..d2dc923ba30 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -257,6 +257,8 @@ class HLodClass : public Animatable3DObjClass // Current LOD Bias (affects recalculation of the Value array) float LODBias; + + friend class HLodDefClass; }; @@ -298,6 +300,7 @@ class HLodDefClass WW3DErrorType Save_Header (ChunkSaveClass &csave); WW3DErrorType Save_Lod_Array (ChunkSaveClass &csave); WW3DErrorType Save_Aggregate_Array(ChunkSaveClass & csave); + WW3DErrorType Save_Proxy_Array(ChunkSaveClass & csave); private: @@ -313,6 +316,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); + void Copy_Additional_Models(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 9705de5602f..901bc4b2401 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -43,6 +43,7 @@ * HLodDefClass::Save_Header -- writes the HLodDef header * * HLodDefClass::Save_Lod_Array -- Saves the lod array * * HLodDefClass::Save_Aggregate_Array -- Save the array of aggregate models * + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * HLodDefClass::read_header -- loads the HLodDef header from a W3d file * * HLodDefClass::read_proxy_array -- load the proxy names * @@ -51,6 +52,7 @@ * HLodDefClass::SubObjectArrayClass::Save_W3D -- saves a w3d file for this HLodDef * * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * HLodDefClass::SubObjectArrayClass::Reset -- release the contents of this array * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * HLodClass::HLodClass -- Constructor * * HLodClass::HLodClass -- copy constructor * * HLodClass::HLodClass -- Constructor * @@ -344,6 +346,7 @@ void HLodDefClass::Free() Lod = nullptr; LodCount = 0; + Aggregates.Reset(); REF_PTR_RELEASE(ProxyArray); } @@ -408,6 +411,9 @@ void HLodDefClass::Initialize(HLodClass &src_lod) Lod[index].BoneIndex = bone_indicies; } } + + Aggregates.Copy_Additional_Models(src_lod); + REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -433,7 +439,9 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) // Attempt to save the different sections of the aggregate definition if ((Save_Header (csave) == WW3D_ERROR_OK) && - (Save_Lod_Array (csave) == WW3D_ERROR_OK)) { + (Save_Lod_Array (csave) == WW3D_ERROR_OK) && + (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && + (Save_Proxy_Array (csave) == WW3D_ERROR_OK)) { // Success! ret_val = WW3D_ERROR_OK; @@ -516,7 +524,11 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = Lod[lod_index].Save_W3D (csave); + success &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + if (success) { + success &= Lod[lod_index].Save_W3D (csave); + success &= csave.End_Chunk (); + } } // Return the WW3DErrorType return code @@ -541,12 +553,68 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) { + bool success = true; if (Aggregates.ModelCount > 0) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); - Aggregates.Save_W3D(csave); - csave.End_Chunk(); + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); + if (success) { + success &= Aggregates.Save_W3D(csave); + success &= csave.End_Chunk(); + } } - return WW3D_ERROR_OK; + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; +} + + +/*********************************************************************************************** + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * + * * + * The screen size in the array header is 0. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) +{ + bool success = true; + + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + + if (success) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + + if (success) { + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + success &= (csave.Write(&header,sizeof(header)) == sizeof(header)); + success &= csave.End_Chunk(); + } + + for (int imodel=0; success && imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); + } + } + success &= csave.End_Chunk(); + } + } + + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } @@ -772,6 +840,35 @@ void HLodDefClass::SubObjectArrayClass::Reset() } +/*********************************************************************************************** + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +void HLodDefClass::SubObjectArrayClass::Copy_Additional_Models(const HLodClass & hlod) +{ + Reset(); + + ModelCount = hlod.Get_Additional_Model_Count (); + if (ModelCount > 0) { + MaxScreenSize = 0.0f; + ModelName = W3DNEWARRAY char *[ModelCount]; + BoneIndex = W3DNEWARRAY int[ModelCount]; + for (int model_index = 0; model_index < ModelCount; model_index ++) { + ModelName[model_index] = ::strdup (hlod.Peek_Additional_Model (model_index)->Get_Name ()); + BoneIndex[model_index] = hlod.Get_Additional_Model_Bone (model_index); + } + } +} + + /*********************************************************************************************** * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * * @@ -834,58 +931,54 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * * * HISTORY: * * 1/26/00 gth : Created. * + * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * + * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY. * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { // Assume error bool ret_val = false; - // Begin a chunk that identifies the LOD array - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE) { - - // Begin a chunk that identifies the LOD header - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { + // Begin a chunk that identifies the LOD header + if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ModelCount; - header.MaxScreenSize = MaxScreenSize; + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ModelCount; + header.MaxScreenSize = MaxScreenSize; - // Write the LOD header structure out to the chunk - ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); + // Write the LOD header structure out to the chunk + ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); - // End the header chunk - csave.End_Chunk (); - } + // End the header chunk + csave.End_Chunk (); + } - if (ret_val) { + if (ret_val) { - // Write all of this LOD's models to the file - for (int index = 0; - (index < ModelCount) && ret_val; - index ++) { + // Write all of this LOD's models to the file + for (int index = 0; + (index < ModelCount) && ret_val; + index ++) { - // Save this LOD sub-obj to the chunk - ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); - if (ret_val) { + // Save this LOD sub-obj to the chunk + ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); + if (ret_val) { - W3dHLodSubObjectStruct info = { 0 }; - info.BoneIndex = BoneIndex[index]; + W3dHLodSubObjectStruct info = { 0 }; + info.BoneIndex = BoneIndex[index]; - // Copy this model name into the structure - ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); - info.Name[sizeof (info.Name) - 1] = 0; + // Copy this model name into the structure + ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); + info.Name[sizeof (info.Name) - 1] = 0; - // Write the LOD sub-obj structure out to the chunk - ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); + // Write the LOD sub-obj structure out to the chunk + ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); - // End the sub-obj chunk - csave.End_Chunk (); - } + // End the sub-obj chunk + csave.End_Chunk (); } } - - // End the HLOD-Array chunk - csave.End_Chunk (); } // Return the true/false result code diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index f48d74d0f2a..4c61a85a149 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -258,6 +258,8 @@ class HLodClass : public Animatable3DObjClass // Current LOD Bias (affects recalculation of the Value array) float LODBias; + + friend class HLodDefClass; }; @@ -299,6 +301,7 @@ class HLodDefClass WW3DErrorType Save_Header (ChunkSaveClass &csave); WW3DErrorType Save_Lod_Array (ChunkSaveClass &csave); WW3DErrorType Save_Aggregate_Array(ChunkSaveClass & csave); + WW3DErrorType Save_Proxy_Array(ChunkSaveClass & csave); private: @@ -314,6 +317,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); + void Copy_Additional_Models(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); diff --git a/Tests/Google/Core/CMakeLists.txt b/Tests/Google/Core/CMakeLists.txt index 8681be68f0f..b6b929b805e 100644 --- a/Tests/Google/Core/CMakeLists.txt +++ b/Tests/Google/Core/CMakeLists.txt @@ -5,6 +5,7 @@ set(COREI_GOOGLETEST_SRC GameEngine/Common/INITest.cpp GameEngineDevice/StdDevice/Common/StdLocalFileSystemTest.cpp Libraries/Compression/CompressionManagerTest.cpp + Libraries/WWVegas/WW3D2/hlod_test.cpp Libraries/WWVegas/WW3D2/soundrobj_test.cpp Libraries/WWVegas/WWMath/WWMathCalls.h Libraries/WWVegas/WWMath/WWMathDumps.h diff --git a/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp new file mode 100644 index 00000000000..d858f6837fe --- /dev/null +++ b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp @@ -0,0 +1,204 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** 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 . +*/ + +#include +#include + +#include "WW3D2/hlod.h" +#include "WW3D2/nullrobj.h" +#include "WWLib/RAMFILE.h" +#include "WWLib/chunkio.h" + +// W3DView's Export LOD saves HLod definitions as W3D_CHUNK_HLOD. These arrays hold the bytes that ChunkSaveClass writes: +// a chunk header is the uint32 ID and the uint32 size, whose top bit marks a chunk that contains chunks. +namespace +{ +// clang-format off + +// Two levels of detail: an aggregate and a proxy. +const unsigned char FullChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0x6C, 0x01, 0x00, 0x80, // W3D_CHUNK_HLOD, 364 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 2 LODs, name "ProbeHLod", hierarchy "ProbeTree" + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 300 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x43, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 1, name "ProbeTree.Low" + 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x4C, 0x6F, + 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x68, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 104 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 2 models, max screen size FLT_MAX + 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 1, name "ProbeTree.High" + 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x48, 0x69, + 0x67, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 2, name "ProbeTree.Turret" + 0x02, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x54, 0x75, + 0x72, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_AGGREGATE_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 3, name "ProbeEmitter" + 0x03, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x45, 0x6D, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_PROXY_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 4, name "ProbeProxy" + 0x04, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x50, 0x72, 0x6F, 0x78, 0x79, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +// One level of detail and nothing else, like every HLod in the retail archives. +const unsigned char LodOnlyChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0x74, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD, 116 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 1 LOD, name "ProbeHLod", hierarchy "ProbeTree" + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size FLT_MAX + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeTree.Mesh" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x4D, 0x65, + 0x73, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +// An HLod built from one null object with a second null object on its root bone. +const unsigned char InitializedChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD, 184 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 1 LOD, name "ProbeHLod", hierarchy "" + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size FLT_MAX + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeLod" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x4C, 0x6F, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_AGGREGATE_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeAggregate" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +// clang-format on + +template +std::vector toBytes(const unsigned char (&data)[Size]) +{ + return std::vector(data, data + Size); +} + +std::vector saveDefinition(HLodDefClass &definition) +{ + std::vector buffer(1024); + RAMFileClass file(buffer.data(), static_cast(buffer.size())); + file.Open(FileClass::WRITE); + ChunkSaveClass csave(&file); + EXPECT_EQ(definition.Save(csave), WW3D_ERROR_OK); + buffer.resize(file.Size()); + file.Close(); + return buffer; +} + +// Loads the chunk with the loader that the asset manager uses and saves the loaded definition again. +std::vector loadAndSave(std::vector input) +{ + RAMFileClass file(input.data(), static_cast(input.size())); + file.Open(FileClass::READ); + ChunkLoadClass cload(&file); + + std::vector output; + if (cload.Open_Chunk()) + { + EXPECT_EQ(cload.Cur_Chunk_ID(), static_cast(W3D_CHUNK_HLOD)); + PrototypeClass *prototype = _HLodLoader.Load_W3D(cload); + cload.Close_Chunk(); + EXPECT_NE(prototype, nullptr); + if (prototype != nullptr) + { + output = saveDefinition(*static_cast(prototype)->Get_Definition()); + prototype->DeleteSelf(); + } + } + file.Close(); + return output; +} + +} // namespace + +TEST(HLodDefClass, SavesLoadedChunkUnchanged) +{ + EXPECT_EQ(loadAndSave(toBytes(FullChunk)), toBytes(FullChunk)); +} + +TEST(HLodDefClass, SavesLodOnlyChunkUnchanged) +{ + EXPECT_EQ(loadAndSave(toBytes(LodOnlyChunk)), toBytes(LodOnlyChunk)); +} + +// W3DView rebuilds the definition from the displayed HLod after Record Screen Area and Include NULL LOD. +TEST(HLodDefClass, InitializeKeepsAdditionalModels) +{ + RenderObjClass *lod = NEW_REF(Null3DObjClass, ("ProbeLod")); + HLodClass *hlod = NEW_REF(HLodClass, ("ProbeHLod", &lod, 1)); + RenderObjClass *aggregate = NEW_REF(Null3DObjClass, ("ProbeAggregate")); + hlod->Add_Sub_Object_To_Bone(aggregate, 0); + + HLodDefClass definition(*hlod); + EXPECT_EQ(saveDefinition(definition), toBytes(InitializedChunk)); + + REF_PTR_RELEASE(aggregate); + REF_PTR_RELEASE(hlod); + REF_PTR_RELEASE(lod); +}