/* ** 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/wwphys/heightdb.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 1/19/01 1:48p $* * * * $Revision:: 5 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "heightdb.h" #include "pscene.h" #include "coltest.h" #include "phys.h" #include "physcoltest.h" #include "mesh.h" #include "meshmdl.h" #include "chunkio.h" ///////////////////////////////////////////////////////////////////////// // Constants ///////////////////////////////////////////////////////////////////////// const float HEIGHT_OFFSET = 2.0F; enum { CHUNKID_VARIABLES = 0x07310202, CHUNKID_HEIGHT_ARRAY }; enum { VARID_NUM_POINTS_X = 0x01, VARID_NUM_POINTS_Y, VARID_PATCH_SIZE, VARID_LEV_MIN, VARID_LEV_MAX }; ///////////////////////////////////////////////////////////////////////// // Static member initialization ///////////////////////////////////////////////////////////////////////// float * HeightDBClass::m_HeightArray = NULL; int HeightDBClass::m_NumPointsX = 0; int HeightDBClass::m_NumPointsY = 0; float HeightDBClass::m_PatchSize = 8; Vector3 HeightDBClass::m_LevelMin (0, 0, 0); Vector3 HeightDBClass::m_LevelMax (0, 0, 0); ///////////////////////////////////////////////////////////////////////// // // HeightDBClass // ///////////////////////////////////////////////////////////////////////// HeightDBClass::HeightDBClass (void) { return ; } ///////////////////////////////////////////////////////////////////////// // // ~HeightDBClass // ///////////////////////////////////////////////////////////////////////// HeightDBClass::~HeightDBClass (void) { return ; } ///////////////////////////////////////////////////////////////////////// // // Initialize // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Initialize (void) { return ; } ///////////////////////////////////////////////////////////////////////// // // Shutdown // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Shutdown (void) { Free_Data (); return ; } ///////////////////////////////////////////////////////////////////////// // // Get_Height // ///////////////////////////////////////////////////////////////////////// float HeightDBClass::Get_Height (const Vector3 &pos) { float height = pos.Z; if (m_HeightArray != NULL && m_NumPointsX > 0 && m_NumPointsY > 0) { float percent_x = (pos.X - m_LevelMin.X) / (m_LevelMax.X - m_LevelMin.X); float percent_y = (pos.Y - m_LevelMin.Y) / (m_LevelMax.Y - m_LevelMin.Y); // // Is the position inside our data set? // if (percent_x >= 0 && percent_x <= 1.0F && percent_y >= 0 && percent_y <= 1.0F) { int entry_ul_x = (m_NumPointsX - 1) * percent_x; int entry_ul_y = (m_NumPointsY - 1) * percent_y; int entry_ur_x = entry_ul_x + 1; int entry_ur_y = entry_ul_y; int entry_lr_x = entry_ul_x + 1; int entry_lr_y = entry_ul_y + 1; int entry_ll_x = entry_ul_x; int entry_ll_y = entry_ul_y + 1; if (entry_ul_x + 1 >= m_NumPointsX) { entry_ur_x = entry_ul_x; entry_lr_x = entry_ul_x; } if (entry_ul_y + 1 >= m_NumPointsY) { entry_lr_y = entry_ul_y; entry_ll_y = entry_ul_y; } float *ul_entry = Get_Height_Entry (entry_ul_y, entry_ul_x); float *ur_entry = Get_Height_Entry (entry_ur_y, entry_ur_x); float *lr_entry = Get_Height_Entry (entry_lr_y, entry_lr_x); float *ll_entry = Get_Height_Entry (entry_ll_y, entry_ll_x); float local_per_x = ((pos.X - m_LevelMin.X) - (entry_ul_x * m_PatchSize)) / m_PatchSize; float local_per_y = ((pos.Y - m_LevelMin.Y) - (entry_ul_y * m_PatchSize)) / m_PatchSize; // // Take the weighted average of the 4 corner values // float h1 = (1 - local_per_x) * (1 - local_per_y) * (*ul_entry); float h2 = (local_per_x) * (1 - local_per_y) * (*ur_entry); float h3 = (local_per_x) * (local_per_y) * (*lr_entry); float h4 = (1 - local_per_x) * (local_per_y) * (*ll_entry); height = h1 + h2 + h3 + h4; } } return height; } ///////////////////////////////////////////////////////////////////////// // // Generate // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Generate (void) { Free_Data (); PhysicsSceneClass::Get_Instance ()->Get_Level_Extents (m_LevelMin, m_LevelMax); float generate_patch_size = m_PatchSize / 2; // // Determine how big a heightmap we will need to store data for this level // m_NumPointsX = ((m_LevelMax.X - m_LevelMin.X) / generate_patch_size) + 1; m_NumPointsY = ((m_LevelMax.Y - m_LevelMin.Y) / generate_patch_size) + 1; int entries = m_NumPointsX * m_NumPointsY; m_HeightArray = new float[entries]; // // Build a heightmap by casting rays through the level vertically at each grid intersection // int row = 0; int col = 0; for (float y_pos = m_LevelMin.Y; y_pos < m_LevelMax.Y; y_pos += generate_patch_size) { col = 0; for (float x_pos = m_LevelMin.X; x_pos < m_LevelMax.X; x_pos += generate_patch_size) { float z_pos = m_LevelMin.Z; float start_z = m_LevelMax.Z + 250.0F; float end_z = m_LevelMin.Z - 250.0F; // // Setup a raycast at this (x,y) position that goes through the world // CastResultStruct result; LineSegClass ray (Vector3 (x_pos, y_pos, start_z), Vector3 (x_pos, y_pos, end_z)); PhysRayCollisionTestClass raytest (ray, &result, 0, COLLISION_TYPE_PROJECTILE); PhysicsSceneClass::Get_Instance ()->Cast_Ray (raytest); // // Return a pointer to the collided physics object if the // cast hit something // if (result.Fraction < 1.0F) { z_pos = start_z + (end_z - start_z) * result.Fraction; } // // Store this height value in our array // float *height_value = Get_Height_Entry (row, col); if (height_value != NULL) { (*height_value) = (z_pos + HEIGHT_OFFSET); } // // Move over one columne // col ++; } // // Move down one row // row ++; } Examine_Level_Geometry (); // // Now average out the height values so we don't get large jumps in height // for (row = 1; row < m_NumPointsY - 1; row ++) { for (col = 1; col < m_NumPointsX - 1; col ++) { // // Lookup the height values of the 9 entries surrounding the current entry // float *z_val1 = Get_Height_Entry (row - 1, col - 1); float *z_val2 = Get_Height_Entry (row - 1, col); float *z_val3 = Get_Height_Entry (row - 1, col + 1); float *z_val4 = Get_Height_Entry (row, col - 1); float *z_val5 = Get_Height_Entry (row, col); float *z_val6 = Get_Height_Entry (row, col + 1); float *z_val7 = Get_Height_Entry (row + 1, col - 1); float *z_val8 = Get_Height_Entry (row + 1, col); float *z_val9 = Get_Height_Entry (row + 1, col + 1); // // Average the 9 height values // float average = ( *z_val1 + *z_val2 + *z_val3 + *z_val4 + *z_val5 + *z_val6 + *z_val7 + *z_val8 + *z_val9) / 9.0F; // // Now store the larger of the old value and the average in each of these // 9 entries // float edge_avg = average * 0.98F; *z_val1 = max (*z_val1, edge_avg); *z_val2 = max (*z_val2, edge_avg); *z_val3 = max (*z_val3, edge_avg); *z_val4 = max (*z_val4, edge_avg); *z_val5 = max (*z_val5, average); *z_val6 = max (*z_val6, edge_avg); *z_val7 = max (*z_val7, edge_avg); *z_val8 = max (*z_val8, edge_avg); *z_val9 = max (*z_val9, edge_avg); } } int temp_points_x = m_NumPointsX / 2; int temp_points_y = m_NumPointsY / 2; float *temp_height_array = new float[temp_points_x * temp_points_y]; for (row = 0; row < temp_points_y; row ++) { for (col = 0; col < temp_points_x; col ++) { float *z_val = Get_Height_Entry (row * 2, col * 2); if (z_val != NULL) { temp_height_array[(row * temp_points_x) + col] = *z_val; } else { temp_height_array[(row * temp_points_x) + col] = 0; } } } Free_Data (); m_HeightArray = temp_height_array; m_NumPointsX = temp_points_x; m_NumPointsY = temp_points_y; return ; } ///////////////////////////////////////////////////////////////////////// // // Free_Data // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Free_Data (void) { if (m_HeightArray != NULL) { delete [] m_HeightArray; m_HeightArray = NULL; } m_NumPointsX = 0; m_NumPointsY = 0; return ; } ///////////////////////////////////////////////////////////////////////// // // Examine_Level_Geometry // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Examine_Level_Geometry (void) { RefPhysListIterator it1 = PhysicsSceneClass::Get_Instance ()->Get_Static_Object_Iterator (); RefPhysListIterator it2 = PhysicsSceneClass::Get_Instance ()->Get_Static_Anim_Object_Iterator (); // // Process the static meshes // for (it1.First (); !it1.Is_Done (); it1.Next ()) { PhysClass *phys_obj = it1.Peek_Obj (); if (phys_obj != NULL) { RenderObjClass *model = phys_obj->Peek_Model (); if (model != NULL) { Process_Render_Obj (model); } } } // // Process the static animated meshes // for (it2.First (); !it2.Is_Done (); it2.Next ()) { PhysClass *phys_obj = it2.Peek_Obj (); if (phys_obj != NULL) { RenderObjClass *model = phys_obj->Peek_Model (); if (model != NULL) { Process_Render_Obj (model); } } } return ; } ///////////////////////////////////////////////////////////////////////// // // Process_Render_Obj // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Process_Render_Obj (RenderObjClass *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) { Process_Render_Obj (sub_object); sub_object->Release_Ref (); } } // // Is this render object a mesh? // if ((render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) && (render_obj->Get_Collision_Type () & COLLISION_TYPE_PHYSICAL)) { // // Pass this mesh off to the generator // MeshClass *mesh = static_cast (render_obj); Submit_Mesh (*mesh); } return ; } ///////////////////////////////////////////////////////////////////////// // // Submit_Mesh // ///////////////////////////////////////////////////////////////////////// void HeightDBClass::Submit_Mesh (MeshClass &mesh) { // // Get this mesh's polygon information // MeshModelClass *model = mesh.Get_Model (); if (model != NULL) { const Vector3 *vertex_array = model->Get_Vertex_Array (); int count = model->Get_Vertex_Count (); // // Loop over all the vertices in the mesh, looking for the // highest z-value in each cell of our grid // for (int index = 0; index < count; index ++) { Vector3 vertex = mesh.Get_Transform () * vertex_array[index]; float percent_x = (vertex.X - m_LevelMin.X) / (m_LevelMax.X - m_LevelMin.X); float percent_y = (vertex.Y - m_LevelMin.Y) / (m_LevelMax.Y - m_LevelMin.Y); // // Is the position inside our data set? // if (percent_x >= 0 && percent_x <= 1.0F && percent_y >= 0 && percent_y <= 1.0F) { int entry_ul_x = (m_NumPointsX - 1) * percent_x; int entry_ul_y = (m_NumPointsY - 1) * percent_y; int entry_ur_x = entry_ul_x + 1; int entry_ur_y = entry_ul_y; int entry_lr_x = entry_ul_x + 1; int entry_lr_y = entry_ul_y + 1; int entry_ll_x = entry_ul_x; int entry_ll_y = entry_ul_y + 1; if (entry_ul_x + 1 >= m_NumPointsX) { entry_ur_x = entry_ul_x; entry_lr_x = entry_ul_x; } if (entry_ul_y + 1 >= m_NumPointsY) { entry_lr_y = entry_ul_y; entry_ll_y = entry_ul_y; } float *ul_entry = Get_Height_Entry (entry_ul_y, entry_ul_x); float *ur_entry = Get_Height_Entry (entry_ur_y, entry_ur_x); float *lr_entry = Get_Height_Entry (entry_lr_y, entry_lr_x); float *ll_entry = Get_Height_Entry (entry_ll_y, entry_ll_x); // // Now, move each of the corner entries up to this new z-value if necessary // (*ul_entry) = max (*ul_entry, vertex.Z + HEIGHT_OFFSET); (*ur_entry) = max (*ur_entry, vertex.Z + HEIGHT_OFFSET); (*lr_entry) = max (*lr_entry, vertex.Z + HEIGHT_OFFSET); (*ll_entry) = max (*ll_entry, vertex.Z + HEIGHT_OFFSET); } } } return ; } /////////////////////////////////////////////////////////////////////// // // Save // /////////////////////////////////////////////////////////////////////// bool HeightDBClass::Save (ChunkSaveClass &csave) { bool retval = true; // // Save the variables to their own chunk // csave.Begin_Chunk (CHUNKID_VARIABLES); WRITE_MICRO_CHUNK (csave, VARID_NUM_POINTS_X, m_NumPointsX); WRITE_MICRO_CHUNK (csave, VARID_NUM_POINTS_Y, m_NumPointsY); WRITE_MICRO_CHUNK (csave, VARID_PATCH_SIZE, m_PatchSize); WRITE_MICRO_CHUNK (csave, VARID_LEV_MIN, m_LevelMin); WRITE_MICRO_CHUNK (csave, VARID_LEV_MAX, m_LevelMax); csave.End_Chunk (); // // Write the height data to a chunk // csave.Begin_Chunk (CHUNKID_HEIGHT_ARRAY); int entries = m_NumPointsX * m_NumPointsY; csave.Write (m_HeightArray, entries * sizeof (float)); csave.End_Chunk (); return retval; } /////////////////////////////////////////////////////////////////////// // // Load // /////////////////////////////////////////////////////////////////////// bool HeightDBClass::Load (ChunkLoadClass &cload) { Free_Data (); bool retval = true; while (cload.Open_Chunk ()) { switch (cload.Cur_Chunk_ID ()) { // // Read the height data from the chunk // case CHUNKID_HEIGHT_ARRAY: { // // Allocate enough entries to hold the height data // int entries = m_NumPointsX * m_NumPointsY; m_HeightArray = new float[entries]; cload.Read (m_HeightArray, entries * sizeof (float)); } break; case CHUNKID_VARIABLES: retval &= Load_Variables (cload); break; } cload.Close_Chunk (); } return retval; } /////////////////////////////////////////////////////////////////////// // // Load_Variables // /////////////////////////////////////////////////////////////////////// bool HeightDBClass::Load_Variables (ChunkLoadClass &cload) { bool retval = true; while (cload.Open_Micro_Chunk ()) { switch (cload.Cur_Micro_Chunk_ID ()) { READ_MICRO_CHUNK (cload, VARID_NUM_POINTS_X, m_NumPointsX); READ_MICRO_CHUNK (cload, VARID_NUM_POINTS_Y, m_NumPointsY); READ_MICRO_CHUNK (cload, VARID_PATCH_SIZE, m_PatchSize); READ_MICRO_CHUNK (cload, VARID_LEV_MIN, m_LevelMin); READ_MICRO_CHUNK (cload, VARID_LEV_MAX, m_LevelMax); } cload.Close_Micro_Chunk (); } return retval; }