603 lines
17 KiB
C++
603 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 : 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<MeshClass *> (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;
|
|
}
|