This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/wwphys/heightdb.cpp

604 lines
17 KiB
C++
Raw Permalink Normal View History

/*
** 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;
}