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/staticphys.cpp

797 lines
38 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 : WWPhys *
* *
* $Archive:: /Commando/Code/wwphys/staticphys.cpp $*
* *
* Author:: Greg Hjelstrom *
* *
* $Modtime:: 3/29/02 2:49p $*
* *
* $Revision:: 49 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* StaticPhysClass::StaticPhysClass -- Constructor *
* StaticPhysClass::~StaticPhysClass -- Destructor *
* StaticPhysClass::Init -- Initialize this object from a definition *
* StaticPhysClass::Set_Vis_Sector_ID -- Sets the vis sector ID for this object *
* StaticPhysClass::Set_Model -- Set the model for this static object *
* StaticPhysClass::Update_Cached_Model_Parameters -- update our state *
* StaticPhysClass::Render_Vis_Meshes -- renders any vis meshes in this model *
* StaticPhysClass::Get_Bounding_Box -- Returns the bounding box of this object *
* StaticPhysClass::Get_Transform -- Returns the transform of this object *
* StaticPhysClass::Set_Transform -- Set the transform for this object *
* StaticPhysClass::Is_Occluder -- Returns whether this static object is an occluder *
* StaticPhysClass::Is_Model_Pre_Lit -- determines if the render object is pre-lit *
* StaticPhysClass::Is_Vis_Sector -- Is_Vis_Sector *
* StaticPhysClass::Update_Sun_Status -- determine if this object is in the sun *
* StaticPhysClass::Get_Factory -- Returns the persist factory for StaticPhysClass *
* StaticPhysClass::Save -- Save method, persistant object support *
* StaticPhysClass::Load -- Load method, persistant object support *
* StaticPhysClass::On_Post_Load -- Post-Load callback *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "staticphys.h"
#include "colmathaabox.h"
#include "vistable.h"
#include "chunkio.h"
#include "assetmgr.h"
#include "progcall.h"
#include "matrix3.h"
#include "persistfactory.h"
#include "simpledefinitionfactory.h"
#include "wwphysids.h"
#include "wwhack.h"
#include "mesh.h"
#include "meshmdl.h"
#include "lightexclude.h"
#include <memory.h>
#include "part_emt.h"
#include "renegadeterrainpatch.h"
#if UMBRASUPPORT
#include <umbra.hpp>
#include "umbrasupport.h"
#endif
DECLARE_FORCE_LINK(staticphys);
/***********************************************************************************************
**
** StaticPhysClass Implementation
**
***********************************************************************************************/
bool StaticPhysClass::_DisableStaticPhysSimulation = false;
bool StaticPhysClass::_DisableStaticPhysRendering = false;
/*
** Declare a PersistFactory for StaticPhysClasses
*/
SimplePersistFactoryClass<StaticPhysClass,PHYSICS_CHUNKID_STATICPHYS> _StaticPhysFactory;
/*
** Chunk-ID's used by StaticPhysClass
*/
enum
{
STATICPHYS_CHUNK_PHYS = 14430100,
STATICPHYS_CHUNK_VARIABLES,
STATICPHYS_VARIABLE_VISOBJECTID = 0x00,
STATICPHYS_VARIABLE_VISSECTORID,
};
/***********************************************************************************************
* StaticPhysClass::StaticPhysClass -- Constructor *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* I'm initializing the VisObjectID to zero, the "always visible" object ID *
* I'm initializing the VisSectorID to -1 to indicate that no table is allocated for this obj *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
StaticPhysClass::StaticPhysClass(void) :
VisSectorID(0xFFFFFFFF)
{
Set_Collision_Group( 15 ); // HACK? All terrain should be group 15?
}
/***********************************************************************************************
* StaticPhysClass::~StaticPhysClass -- Destructor *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
StaticPhysClass::~StaticPhysClass(void)
{
}
/***********************************************************************************************
* StaticPhysClass::Init -- Initialize this object from a definition *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Init(const StaticPhysDefClass & def)
{
PhysClass::Init(def);
if (Model != NULL && Model->Class_ID () == RenderObjClass::CLASSID_PARTICLEEMITTER) {
((ParticleEmitterClass *)Model)->Start ();
}
}
/***********************************************************************************************
* StaticPhysClass::Set_Vis_Sector_ID -- Sets the vis sector ID for this object *
* *
* If a static object contains a VIS mesh, a vis sector ID is allocated for it. This is the *
* ID of the visibility table for this object which tracks what objects are visible when the *
* camera is above one of its VIS meshes. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 6/27/2000 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Set_Vis_Sector_ID(int new_id)
{
VisSectorID = new_id;
}
/***********************************************************************************************
* StaticPhysClass::Set_Model -- Set the model for this static object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Set_Model(RenderObjClass * model)
{
// call parent class
PhysClass::Set_Model(model);
Update_Cached_Model_Parameters();
}
/***********************************************************************************************
* StaticPhysClass::Update_Cached_Model_Parameters -- update our state *
* *
* This function must be called in three places: In the constructor, whenever the model is *
* set, and in the post-load callback *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/14/00 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Update_Cached_Model_Parameters(void)
{
if (Model == NULL) return;
// if the model is a light-mapped mesh, we want to exclude it from the static lights
Enable_Is_Pre_Lit(Is_Model_Pre_Lit());
// if our model is a mesh and its in world space, enable the world-space-mesh optimizations
if ((Model->Class_ID() == RenderObjClass::CLASSID_MESH) && (Get_Transform() == Matrix3D::Identity)) {
Enable_Is_World_Space_Mesh(true);
} else {
Enable_Is_World_Space_Mesh(false);
}
#if (UMBRASUPPORT)
// update the umbra model(s)
Umbra::Model * test_model = NULL;
if (Model->Class_ID() == RenderObjClass::CLASSID_MESH) {
MeshClass & mesh = *((MeshClass *)Model);
test_model = UmbraSupport::Create_Mesh_Model(mesh);
} else {
AABoxClass obj_box;
Model->Get_Obj_Space_Bounding_Box(obj_box);
test_model = UmbraSupport::Create_Box_Model(obj_box);
}
UmbraObject->setTestModel(test_model);
if ((Model->Class_ID() == RenderObjClass::CLASSID_MESH) && (Is_Occluder())) {
test_model->set(Umbra::Model::WRITABLE,true);
UmbraObject->setWriteModel(test_model);
}
//UmbraObject->setCost(100000,100000,5);
#endif //UMBRASUPPORT
}
/***********************************************************************************************
* StaticPhysClass::Render_Vis_Meshes -- renders any vis meshes in this model *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 3/17/2000 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Render_Vis_Meshes(RenderInfoClass & rinfo)
{
if (Model == NULL) return;
// Note, this only works with Mesh vis sectors. Theoretically vis sectors could
// be embedded in a hierarchical model as well...
if (Model->Get_Collision_Type() & COLLISION_TYPE_VIS) {
// Force hidden meshes to render...
int is_hidden = Model->Is_Hidden();
int is_anim_hidden = Model->Is_Animation_Hidden();
Model->Set_Hidden(false);
Model->Set_Animation_Hidden(false);
Model->Render(rinfo);
Model->Set_Hidden(is_hidden);
Model->Set_Animation_Hidden(is_anim_hidden);
}
}
/***********************************************************************************************
* StaticPhysClass::Get_Bounding_Box -- Returns the bounding box of this object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
const AABoxClass & StaticPhysClass::Get_Bounding_Box(void) const
{
assert(Model);
return Model->Get_Bounding_Box();
}
/***********************************************************************************************
* StaticPhysClass::Get_Transform -- Returns the transform of this object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
const Matrix3D & StaticPhysClass::Get_Transform(void) const
{
assert(Model);
return Model->Get_Transform();
}
/***********************************************************************************************
* StaticPhysClass::Set_Transform -- Set the transform for this object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* The transform should not be changed in-game! *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Set_Transform(const Matrix3D & m)
{
// Note: this kind of object never collides with others so we
// can just warp it to the users desired position. However,
// we do need to tell the scene so that it can update us in
// the culling system
// Note #2: In-Game, these objects should never move!!! this
// feature is used in the editor. Moving one of these will
// invalidate some or all of the nice pre-calculated lighting
// and culling data!
assert(Model);
Model->Set_Transform(m);
Update_Cull_Box();
Invalidate_Static_Lighting_Cache();
// if our model is a mesh and its in world space, enable the world-space-mesh optimizations
if ((Model->Class_ID() == RenderObjClass::CLASSID_MESH) && (m == Matrix3D::Identity)) {
Enable_Is_World_Space_Mesh(true);
} else {
Enable_Is_World_Space_Mesh(false);
}
}
/***********************************************************************************************
* StaticPhysClass::Is_Occluder -- Returns whether this static object is an occluder *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
int StaticPhysClass::Is_Occluder(void)
{
StaticPhysDefClass * def = Get_StaticPhysDef();
if ((def != NULL) && (def->IsNonOccluder)) {
return false;
} else if (Model) {
if (Model->Is_Translucent()) {
return false;
}
if (Model->Class_ID() == RenderObjClass::CLASSID_DAZZLE) {
return false;
}
}
return true;
}
/***********************************************************************************************
* StaticPhysClass::Is_Model_Pre_Lit -- determines if the render object is pre-lit *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 4/20/2000 gth : Created. *
*=============================================================================================*/
bool StaticPhysClass::Is_Model_Pre_Lit(void)
{
// if the model is a light-mapped mesh, we want to exclude it from the static lights
if (Model->Class_ID() == RenderObjClass::CLASSID_MESH) {
MeshModelClass * mesh_model = ((MeshClass *)Model)->Get_Model();
WWASSERT(mesh_model != NULL);
if (mesh_model) {
bool isprelit = (mesh_model->Get_Flag(MeshModelClass::PRELIT_MASK) != 0);
mesh_model->Release_Ref();
return isprelit;
}
} else if (Model->Class_ID() == RenderObjClass::CLASSID_RENEGADE_TERRAIN) {
//return ((RenegadeTerrainPatchClass *)Model)->Is_Prelit ();
}
return false;
}
bool StaticPhysClass::Is_Model_User_Lit(void)
{
if (Model != NULL) {
return !!(Model->Has_User_Lighting());
}
return false;
}
/***********************************************************************************************
* StaticPhysClass::Is_Vis_Sector -- Is_Vis_Sector *
* *
* Searches the model to determine whether it defines a vis sector. I.e. any of its polys *
* are marked as VIS polys *
* *
* INPUT: *
* model - typically leave the default of NULL, this function will be called recursively *
* on sub-objects *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 6/5/2000 gth : Created. *
*=============================================================================================*/
bool StaticPhysClass::Is_Vis_Sector(RenderObjClass * model) const
{
bool retval = false;
/*
** If NULL was passed in, start with the render object for this StaticPhysClass.
*/
if (model == NULL) {
model = Model;
}
/*
** If we have a valid model to check; either recurse into its sub objects or
** check if it is a vis-collideable mesh
*/
if (model != NULL) {
/*
** Check each sub-object
*/
int count = model->Get_Num_Sub_Objects ();
for (int index = 0; (index < count) && !retval; index ++) {
RenderObjClass *sub_object = model->Get_Sub_Object (index);
if (sub_object != NULL) {
retval |= Is_Vis_Sector(sub_object);
REF_PTR_RELEASE(sub_object);
}
}
/*
** Check the model itself
*/
if ((model->Class_ID () == RenderObjClass::CLASSID_MESH) &&
(model->Get_Collision_Type () & COLLISION_TYPE_VIS))
{
retval = true;
}
}
return retval;
}
/***********************************************************************************************
* StaticPhysClass::Update_Sun_Status -- determine if this object is in the sun *
* *
* This updates the internal IS_IN_THE_SUN flag. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/24/2000 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::Update_Sun_Status(void)
{
/*
** Cant do sun occlusion very well for static objects...
** Just let the sun always apply?
*/
Set_Flag(IS_IN_THE_SUN,true);
}
/***********************************************************************************************
* StaticPhysClass::Get_Factory -- Returns the persist factory for StaticPhysClass *
* *
* All persistant object types need a persist factory which ties them to a unique chunk ID *
* and links them into the save-load system *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
const PersistFactoryClass & StaticPhysClass::Get_Factory(void) const
{
return _StaticPhysFactory;
}
/***********************************************************************************************
* StaticPhysClass::Save -- Save method, persistant object support *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
bool StaticPhysClass::Save(ChunkSaveClass &csave)
{
csave.Begin_Chunk(STATICPHYS_CHUNK_PHYS);
PhysClass::Save(csave);
csave.End_Chunk();
csave.Begin_Chunk(STATICPHYS_CHUNK_VARIABLES);
WRITE_MICRO_CHUNK(csave,STATICPHYS_VARIABLE_VISOBJECTID,VisObjectID);
WRITE_MICRO_CHUNK(csave,STATICPHYS_VARIABLE_VISSECTORID,VisSectorID);
csave.End_Chunk();
return true;
}
/***********************************************************************************************
* StaticPhysClass::Load -- Load method, persistant object support *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
bool StaticPhysClass::Load(ChunkLoadClass &cload)
{
while (cload.Open_Chunk()) {
switch(cload.Cur_Chunk_ID())
{
case STATICPHYS_CHUNK_PHYS:
PhysClass::Load(cload);
break;
case STATICPHYS_CHUNK_VARIABLES:
while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
READ_MICRO_CHUNK(cload,STATICPHYS_VARIABLE_VISOBJECTID,VisObjectID);
READ_MICRO_CHUNK(cload,STATICPHYS_VARIABLE_VISSECTORID,VisSectorID);
}
cload.Close_Micro_Chunk();
}
break;
default:
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
break;
}
cload.Close_Chunk();
}
SaveLoadSystemClass::Register_Post_Load_Callback(this);
return true;
}
/***********************************************************************************************
* StaticPhysClass::On_Post_Load -- Post-Load callback *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/10/00 gth : Created. *
*=============================================================================================*/
void StaticPhysClass::On_Post_Load(void)
{
PhysClass::On_Post_Load();
WWASSERT(Model);
if (Model) {
// Set our cull box but don't let the culling system re-insert us.
Set_Cull_Box(Model->Get_Bounding_Box(),true);
// If this is a light-mapped mesh, exclude ourselves from the static lights
Update_Cached_Model_Parameters();
// If this is a particle emitter, start it emitting
if (Model->Class_ID () == RenderObjClass::CLASSID_PARTICLEEMITTER) {
((ParticleEmitterClass *)Model)->Start ();
}
}
}
float StaticPhysClass::Compute_Vis_Mesh_Ram(RenderObjClass * model)
{
float total = 0.0f;
if (model == NULL) {
model = Model;
}
if (model != NULL) {
if (model->Class_ID() == RenderObjClass::CLASSID_MESH) {
if (model->Get_Collision_Type() & COLLISION_TYPE_VIS) {
MeshModelClass * mdl = ((MeshClass *)model)->Get_Model();
if (mdl != NULL) {
total += sizeof(MeshClass) + sizeof(MeshModelClass);
total += sizeof(Vector3) * mdl->Get_Vertex_Count();
total += sizeof(TriIndex) * mdl->Get_Polygon_Count();
total += sizeof(PlaneClass) * mdl->Get_Polygon_Count();
mdl->Release_Ref();
}
}
} else {
for (int i=0; i<model->Get_Num_Sub_Objects(); i++) {
RenderObjClass * sub_obj = model->Get_Sub_Object(i);
if (sub_obj != NULL) {
total += Compute_Vis_Mesh_Ram(sub_obj);
REF_PTR_RELEASE(sub_obj);
}
}
}
}
return total;
}
/**************************************************************************************
**
** StaticPhysDefClass Implementation
**
**************************************************************************************/
/*
** Persist factory for StaticPhysDefClass's
*/
SimplePersistFactoryClass<StaticPhysDefClass,PHYSICS_CHUNKID_STATICPHYSDEF> _StaticPhysDefFactory;
/*
** Definition factory for StaticPhysDefClass. This makes it show up in the editor
*/
DECLARE_DEFINITION_FACTORY(StaticPhysDefClass, CLASSID_STATICPHYSDEF, "StaticPhys") _StaticPhysDefDefFactory;
/*
** Chunk ID's used by StaticPhysDefClass
*/
enum
{
STATICPHYSDEF_CHUNK_PHYSDEF = 0x01070002, // (parent class)
STATICPHYSDEF_CHUNK_VARIABLES,
STATICPHYSDEF_VARIABLE_ISNONOCCLUDER = 0,
};
StaticPhysDefClass::StaticPhysDefClass(void) :
IsNonOccluder(true)
{
EDITABLE_PARAM(StaticPhysDefClass, ParameterClass::TYPE_BOOL, IsNonOccluder);
}
uint32 StaticPhysDefClass::Get_Class_ID (void) const
{
return CLASSID_STATICPHYSDEF;
}
PersistClass * StaticPhysDefClass::Create(void) const
{
StaticPhysClass * new_obj = NEW_REF(StaticPhysClass,());
new_obj->Init(*this);
return new_obj;
}
const char * StaticPhysDefClass::Get_Type_Name(void)
{
return "StaticPhysDef";
}
bool StaticPhysDefClass::Is_Type(const char * type_name)
{
if (stricmp(type_name,StaticPhysDefClass::Get_Type_Name()) == 0) {
return true;
} else {
return PhysDefClass::Is_Type(type_name);
}
}
const PersistFactoryClass & StaticPhysDefClass::Get_Factory (void) const
{
return _StaticPhysDefFactory;
}
bool StaticPhysDefClass::Save(ChunkSaveClass &csave)
{
csave.Begin_Chunk(STATICPHYSDEF_CHUNK_PHYSDEF);
PhysDefClass::Save(csave);
csave.End_Chunk();
csave.Begin_Chunk(STATICPHYSDEF_CHUNK_VARIABLES);
WRITE_MICRO_CHUNK(csave,STATICPHYSDEF_VARIABLE_ISNONOCCLUDER,IsNonOccluder);
csave.End_Chunk();
return true;
}
bool StaticPhysDefClass::Load(ChunkLoadClass &cload)
{
while (cload.Open_Chunk()) {
switch(cload.Cur_Chunk_ID()) {
case STATICPHYSDEF_CHUNK_PHYSDEF:
PhysDefClass::Load(cload);
break;
case STATICPHYSDEF_CHUNK_VARIABLES:
while (cload.Open_Micro_Chunk()) {
switch(cload.Cur_Micro_Chunk_ID()) {
READ_MICRO_CHUNK(cload,STATICPHYSDEF_VARIABLE_ISNONOCCLUDER,IsNonOccluder);
}
cload.Close_Micro_Chunk();
}
break;
default:
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
break;
}
cload.Close_Chunk();
}
return true;
}