/* ** 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 : WWPhys * * * * $Archive:: /Commando/Code/wwphys/phys.cpp $* * * * Author:: Greg Hjelstrom * * * * $Modtime:: 2/21/02 4:01p $* * * * $Revision:: 79 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "phys.h" #include "colmathaabox.h" #include "rendobj.h" #include "rinfo.h" #include "assetmgr.h" #include "ww3d.h" #include "wwdebug.h" #include "pscene.h" #include "cullsys.h" #include "chunkio.h" #include "saveload.h" #include "persistfactory.h" #include "physcoltest.h" #include "lightenvironment.h" #include "umbrasupport.h" #if (UMBRASUPPORT) #include #endif #include "dx8wrapper.h" #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" const float DEBUG_RENDER_DIST2 = (50.0f*50.0f); const float SUN_CHECK_DISTANCE = 50.0f; //If a ray this long doesn't intersect, you can see the sun... /* ** create_render_obj_from_filename */ RenderObjClass * create_render_obj_from_filename( const char * filename ) { StringClass render_obj_name(filename,true); if ( ::strchr( filename, '\\' ) != 0 ) { render_obj_name = ::strrchr( filename, '\\' ) + 1; } render_obj_name.Erase( render_obj_name.Get_Length() - 4, 4 ); RenderObjClass *model = WW3DAssetManager::Get_Instance()->Create_Render_Obj( render_obj_name ); if ( model == NULL ) { WWDEBUG_SAY(("Failed to create %s from %s\n", (const char *)render_obj_name, filename)); } return model; } /*********************************************************************************************** ** ** PhysClass Implementation ** ***********************************************************************************************/ /* ** Chunk ID's used by PhysClass */ enum { PHYS_CHUNK_VARIABLES = 0x00660055, PHYS_CHUNK_MODEL, PHYS_VARIABLE_CULLABLE_PTR = 0x00, PHYS_VARIABLE_WIDGETUSER_PTR, PHYS_VARIABLE_EDITABLE_PTR, PHYS_VARIABLE_FLAGS, PHYS_VARIABLE_NAME, PHYS_VARIABLE_OBSERVER, PHYS_VARIABLE_DEFID, PHYS_VARIABLE_INSTANCEID, }; PhysClass::PhysClass(void) : Flags(DEFAULT_FLAGS), Model(NULL), Observer(NULL), Definition(NULL), InstanceID(0), VisObjectID(0), LastVisibleFrame(0), // JANI TEMP TEST SunStatusLastUpdated(0), StaticLightingCache(NULL) #if (UMBRASUPPORT) ,UmbraObject(NULL) #endif { #if (UMBRASUPPORT) UmbraObject = new Umbra::Object(UmbraSupport::Peek_Dummy_Sphere()); UmbraObject->setUserPointer(this); UmbraSupport::Install_Umbra_Object(this); #endif } PhysClass::~PhysClass(void) { if (Model) { Model->Release_Ref(); Model = NULL; } if (StaticLightingCache) { delete StaticLightingCache; } #if (UMBRASUPPORT) if (UmbraObject) { UmbraSupport::Remove_Umbra_Object(this); UmbraObject->release(); UmbraObject = NULL; } #endif } void PhysClass::Init(const PhysDefClass & def) { Definition = &def; Flags = DEFAULT_FLAGS; if (!def.ModelName.Is_Empty()) { RenderObjClass * model = NULL; if (::strchr(def.ModelName, '.') != NULL) { model = ::create_render_obj_from_filename(def.ModelName); } else { model = WW3DAssetManager::Get_Instance()->Create_Render_Obj(def.ModelName); } if ( model == NULL ) { WWDEBUG_SAY(( "*** FATAL ERROR : Failed to create model %s\n", def.ModelName )); } Set_Model(model); REF_PTR_RELEASE(model); } } void PhysClass::Set_Model(RenderObjClass * model) { PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance(); bool in_scene = the_scene->Contains(this); if (Model) { // If we had an old model, copy the transform if ( model ) { model->Set_Transform( Model->Get_Transform() ); } if (in_scene) Model->Notify_Removed(the_scene); Model->Release_Ref(); } Model = model; if (Model) { Model->Add_Ref(); if (in_scene) Model->Notify_Added(the_scene); } if ((Definition != NULL) && (Definition->IsPreLit)) { Enable_Is_Pre_Lit(true); } Invalidate_Static_Lighting_Cache (); } void PhysClass::Set_Model_By_Name(const char * model_type_name) { RenderObjClass * model = WW3DAssetManager::Get_Instance()->Create_Render_Obj(model_type_name); if ( model == NULL ) { WWDEBUG_SAY(( "%s failed to load\n", model_type_name )); } WWASSERT(model); // As above, PhysClasses cannot survive without a model... Set_Model(model); if (model) { model->Release_Ref(); } } RenderObjClass * PhysClass::Get_Model(void) { if (Model) Model->Add_Ref(); return Model; } void PhysClass::Set_Name(const char * name) { Name=name; } const char * PhysClass::Get_Name(void) { return Name; } void PhysClass::Get_Shadow_Blob_Box(AABoxClass * set_obj_space_box) { WWASSERT(set_obj_space_box != NULL); if (set_obj_space_box != NULL) { Model->Get_Obj_Space_Bounding_Box(*set_obj_space_box); set_obj_space_box->Extent *= 0.75f; } } void PhysClass::Render(RenderInfoClass & rinfo) { Push_Effects(rinfo); if (Model) { Model->Render(rinfo); } Pop_Effects(rinfo); } void PhysClass::Vis_Render(SpecialRenderInfoClass & rinfo) { if (Model) { Model->Special_Render(rinfo); } } void PhysClass::Invalidate_Static_Lighting_Cache(void) { Set_Flag(STATIC_LIGHTING_DIRTY,true); } LightEnvironmentClass * PhysClass::Get_Static_Lighting_Environment(void) { if (Is_Pre_Lit()) { /* ** This object doesn't need a lighting cache, make sure it doesn't have one */ if (StaticLightingCache != NULL) { WWDEBUG_SAY(("Pre-Lit object %s has a lighting cache!\r\n",Model->Get_Name())); delete StaticLightingCache; StaticLightingCache = NULL; } } else if (Get_Flag(STATIC_LIGHTING_DIRTY)) { /* ** First, ensure that we have an allocated lighting cache */ if (StaticLightingCache == NULL) { StaticLightingCache = new LightEnvironmentClass; } /* ** Next, update our sunlight status */ Update_Sun_Status(); /* ** Finally, ask the physics scene to re-compute our lighting cache */ PhysicsSceneClass::Get_Instance()->Compute_Static_Lighting( StaticLightingCache, Model->Get_Bounding_Sphere().Center, Get_Flag(IS_IN_THE_SUN), Get_Vis_Object_ID() ); Set_Flag(STATIC_LIGHTING_DIRTY,false); } /* ** Return our lighting cache to the caller */ return StaticLightingCache; } void PhysClass::Update_Sun_Status(void) { // Update sun status only four times per second unsigned current_time=WW3D::Get_Sync_Time(); if ((current_time-SunStatusLastUpdated)<250) return; SunStatusLastUpdated=current_time; PhysicsSceneClass * scene = PhysicsSceneClass::Get_Instance(); Vector3 sunlight; scene->Get_Sun_Light_Vector(&sunlight); Vector3 center = Model->Get_Bounding_Sphere().Center; #pragma message ("(gth) Need a collision group for sun-rays") CastResultStruct sunresult; LineSegClass sunray(center,center - sunlight * SUN_CHECK_DISTANCE); PhysRayCollisionTestClass sunraytest(sunray,&sunresult,0,COLLISION_TYPE_PROJECTILE); sunraytest.CheckStaticObjs = true; sunraytest.CheckDynamicObjs = false; Inc_Ignore_Counter(); scene->Cast_Ray(sunraytest); // if the ray hits a static object which is casting a projected shadow, ignore that object // and check again. if ( (sunresult.Fraction < 1.0f) && (sunraytest.CollidedPhysObj != NULL) ) { PhysClass * obj = sunraytest.CollidedPhysObj; if (obj->Is_Casting_Shadow()) { obj->Inc_Ignore_Counter(); sunresult.Reset(); scene->Cast_Ray(sunraytest); obj->Dec_Ignore_Counter(); } } Dec_Ignore_Counter(); Enable_Is_In_The_Sun(sunresult.Fraction == 1.0f); } void PhysClass::Push_Effects(RenderInfoClass & rinfo) { if (!MaterialEffectsOnMe.Is_Empty()) { RefMaterialEffectListIterator iterator(&MaterialEffectsOnMe); for ( ; !iterator.Is_Done() ; iterator.Next()) { iterator.Peek_Obj()->Render_Push(rinfo,this); } } #if 0 if (!ProjectionsOnMe.Is_Empty()) { ShaderClass shader = ShaderClass::_PresetOpaqueShader; VertexMaterialClass * vmtl = VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_NODIFFUSE); DX8Wrapper::Set_Shader(shader); DX8Wrapper::Set_Material(vmtl); Matrix4 view,proj; Matrix4 identity(true); DX8Wrapper::Get_Transform(D3DTS_VIEW,view); DX8Wrapper::Get_Transform(D3DTS_PROJECTION,proj); DX8Wrapper::Set_Transform(D3DTS_WORLD,identity); DX8Wrapper::Set_Transform(D3DTS_VIEW,identity); DX8Wrapper::Set_Transform(D3DTS_PROJECTION,identity); TexProjListIterator iterator(&ProjectionsOnMe); for ( ; !iterator.Is_Done() ; iterator.Next()) { TextureClass * tex = iterator.Peek_Obj()->Peek_Material_Pass()->Peek_Texture(0); if (tex != NULL) { DX8Wrapper::Set_Texture(0,tex); DynamicVBAccessClass vbaccess(BUFFER_TYPE_DYNAMIC_DX8,4); { DynamicVBAccessClass::WriteLockClass lock(&vbaccess); VertexFormatXYZNDUV2 * verts = lock.Get_Formatted_Vertex_Array(); verts[0].x = -1.0f; verts[0].y = 0.8f; verts[0].z = 0.0; verts[0].u1 = 0.0f; verts[0].v1 = 0.0f; verts[0].diffuse = 0xFFFFFFFF; verts[1].x = -1.0f; verts[1].y = 0.3f; verts[1].z = 0.0; verts[1].u1 = 0.0f; verts[1].v1 = 1.0f; verts[1].diffuse = 0xFFFFFFFF; verts[2].x = -0.5f; verts[2].y = 0.3f; verts[2].z = 0.0; verts[2].u1 = 1.0f; verts[2].v1 = 1.0f; verts[2].diffuse = 0xFFFFFFFF; verts[3].x = -0.5f; verts[3].y = 0.8f; verts[3].z = 0.0; verts[3].u1 = 1.0f; verts[3].v1 = 0.0f; verts[3].diffuse = 0xFFFFFFFF; } DynamicIBAccessClass ibaccess(BUFFER_TYPE_DYNAMIC_DX8,2*3); { DynamicIBAccessClass::WriteLockClass lock(&ibaccess); unsigned short * indices = lock.Get_Index_Array(); indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 0; indices[4] = 2; indices[5] = 3; } DX8Wrapper::Set_Vertex_Buffer(vbaccess); DX8Wrapper::Set_Index_Buffer(ibaccess,0); DX8Wrapper::Draw_Triangles(0,2,0,4); } } DX8Wrapper::Set_Transform(D3DTS_VIEW,view); DX8Wrapper::Set_Transform(D3DTS_PROJECTION,proj); REF_PTR_RELEASE(vmtl); } #endif } void PhysClass::Pop_Effects(RenderInfoClass & rinfo) { if (!MaterialEffectsOnMe.Is_Empty()) { RefMaterialEffectListIterator iterator(&MaterialEffectsOnMe); while (!iterator.Is_Done()) { MaterialEffectClass * effect = iterator.Peek_Obj(); effect->Render_Pop(rinfo); if (effect->Is_Auto_Remove_Enabled()) { iterator.Remove_Current_Object(); } else { iterator.Next(); } } } } bool PhysClass::Save (ChunkSaveClass &csave) { CullableClass * cullable_ptr = (CullableClass *)this; WidgetUserClass * widgetuser_ptr = (WidgetUserClass *)this; EditableClass * editable_ptr = (EditableClass *)this; csave.Begin_Chunk(PHYS_CHUNK_VARIABLES); // (gth) not saving observer pointers any more! WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_CULLABLE_PTR,cullable_ptr); WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_WIDGETUSER_PTR,widgetuser_ptr); WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_EDITABLE_PTR,editable_ptr); WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_FLAGS,Flags); WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_INSTANCEID,InstanceID); if (Name.Get_Length() > 0) { csave.Begin_Micro_Chunk(PHYS_VARIABLE_NAME); WWASSERT(Name.Get_Length()+1 < 255); csave.Write(Name,Name.Get_Length() + 1); csave.End_Micro_Chunk(); } if (Definition != NULL) { int defid = Definition->Get_ID(); WRITE_MICRO_CHUNK(csave,PHYS_VARIABLE_DEFID,defid); } csave.End_Chunk(); csave.Begin_Chunk(PHYS_CHUNK_MODEL); csave.Begin_Chunk(Model->Get_Factory().Chunk_ID()); Model->Get_Factory().Save(csave,Model); csave.End_Chunk(); csave.End_Chunk(); return true; } bool PhysClass::Load (ChunkLoadClass &cload) { PersistFactoryClass * factory = NULL; CullableClass * cullable_ptr = NULL; WidgetUserClass * widgetuser_ptr = NULL; EditableClass * editable_ptr = NULL; int defid = -1; char tmpstring[256]; tmpstring[0] = 0; RenderObjClass * render_model = NULL; while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case PHYS_CHUNK_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { // (gth) not saving observer pointers any more! READ_MICRO_CHUNK(cload,PHYS_VARIABLE_CULLABLE_PTR,cullable_ptr); READ_MICRO_CHUNK(cload,PHYS_VARIABLE_WIDGETUSER_PTR,widgetuser_ptr); READ_MICRO_CHUNK(cload,PHYS_VARIABLE_EDITABLE_PTR,editable_ptr); READ_MICRO_CHUNK(cload,PHYS_VARIABLE_FLAGS,Flags); READ_MICRO_CHUNK(cload,PHYS_VARIABLE_DEFID,defid); READ_MICRO_CHUNK(cload,PHYS_VARIABLE_INSTANCEID,InstanceID); case PHYS_VARIABLE_NAME: cload.Read(tmpstring,cload.Cur_Micro_Chunk_Length()); break; } cload.Close_Micro_Chunk(); } break; case PHYS_CHUNK_MODEL: cload.Open_Chunk(); factory = SaveLoadSystemClass::Find_Persist_Factory(cload.Cur_Chunk_ID()); WWASSERT(factory != NULL); if (factory != NULL) { render_model = (RenderObjClass *)factory->Load(cload); SET_REF_OWNER(render_model); } cload.Close_Chunk(); break; default: WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__)); break; } if (cullable_ptr != NULL) { SaveLoadSystemClass::Register_Pointer(cullable_ptr,(CullableClass *)this); } cload.Close_Chunk(); } /* ** Set our name */ if (strlen(tmpstring) > 0) { Set_Name(tmpstring); } /* ** Set our definition pointer */ if (defid != -1) { Definition = (PhysDefClass *)_TheDefinitionMgr.Find_Definition(defid); } else { Definition = NULL; } /* ** Only install the model after our definition is installed */ Set_Model(render_model); REF_PTR_RELEASE(render_model); /* ** Ask that our Observer pointer is re-mapped */ if (Observer != NULL) { REQUEST_POINTER_REMAP((void**)&Observer); } /* ** Register all of the multiple-inheritance versions of our this pointer */ if (cullable_ptr != NULL) { SaveLoadSystemClass::Register_Pointer(cullable_ptr,(CullableClass *)this); } if (widgetuser_ptr != NULL) { SaveLoadSystemClass::Register_Pointer(widgetuser_ptr,(WidgetUserClass *)this); } if (editable_ptr != NULL) { SaveLoadSystemClass::Register_Pointer(editable_ptr,(EditableClass *)this); } /* ** Mark our static lighting cache dirty */ Invalidate_Static_Lighting_Cache(); /* ** Update our Umbra object */ #if (UMBRASUPPORT) UmbraSupport::Update_Umbra_Object(this); #endif return true; } #ifdef WWDEBUG void PhysClass::Add_Debug_Point(const Vector3 & p,const Vector3 & color) { if (Is_Debug_Display_Enabled()) { PhysicsSceneClass::Get_Instance()->Add_Debug_Point(p,color); } } #endif #ifdef WWDEBUG void PhysClass::Add_Debug_Vector(const Vector3 & p,const Vector3 & v,const Vector3 & color) { if (Is_Debug_Display_Enabled() && (v.Length2() > 0.0f)) { PhysicsSceneClass::Get_Instance()->Add_Debug_Vector(p,v,color); } } #endif #ifdef WWDEBUG void PhysClass::Add_Debug_AABox(const AABoxClass & box,const Vector3 & color,float opacity) { if (Is_Debug_Display_Enabled()) { PhysicsSceneClass::Get_Instance()->Add_Debug_AABox(box,color,opacity); } } #endif #ifdef WWDEBUG void PhysClass::Add_Debug_OBBox(const OBBoxClass & box,const Vector3 & color,float opacity) { if (Is_Debug_Display_Enabled()) { PhysicsSceneClass::Get_Instance()->Add_Debug_OBBox(box,color,opacity); } } #endif #ifdef WWDEBUG void PhysClass::Add_Debug_Axes(const Matrix3D & transform,const Vector3 & color) { if (Is_Debug_Display_Enabled()) { PhysicsSceneClass::Get_Instance()->Add_Debug_Axes(transform,color); } } #endif bool PhysClass::Is_Debug_Display_Enabled(void) const { PhysicsSceneClass * the_scene = PhysicsSceneClass::Get_Instance(); Vector3 pos; Get_Position(&pos); float dist = (pos - the_scene->Get_Last_Camera_Position()).Length2(); if (dist > DEBUG_RENDER_DIST2) { return false; } if (the_scene) { return (((Flags & DEBUGDISPLAY) == DEBUGDISPLAY) || the_scene->Is_Debug_Display_Enabled()); } else { return ((Flags & DEBUGDISPLAY) == DEBUGDISPLAY); } } bool PhysClass::Expire(void) { ExpirationReactionType result = EXPIRATION_APPROVED; if (Observer != NULL) { result = Observer->Object_Expired(this); } if (result == EXPIRATION_APPROVED) { PhysicsSceneClass::Get_Instance()->Delayed_Remove_Object(this); return true; } else { return false; } } // // TSS added this... not efficient to use if you are also // setting position // void PhysClass::Set_Facing(float new_facing) { Vector3 pos; Get_Position(&pos); Matrix3D tm(1); tm.Translate(pos); tm.Rotate_Z(new_facing); Set_Transform(tm); } bool PhysClass::Do_Any_Effects_Suppress_Shadows(void) { RefMaterialEffectListIterator iterator(&MaterialEffectsOnMe); for ( ; !iterator.Is_Done() ; iterator.Next()) { if (iterator.Peek_Obj()->Are_Shadows_Suppressed()) { return true; } } return false; } /*********************************************************************************************** ** ** PhysDefClass Implementation ** This holds the description for a PhysClass. Since PhysClass's aren't concrete, this ** definition class isn't either and thus has no persist factory (required by PersistClass) ** or create method (required by DefinitionClass) ** ***********************************************************************************************/ enum { PHYSDEF_CHUNK_DEFINITION = 0x055ffe07, // parent class data. PHYSDEF_CHUNK_VARIABLES, // simple variables PHYSDEF_VARIABLE_FLAGS = 0x00, PHYSDEF_VARIABLE_MODELNAME, PHYSDEF_VARIABLE_ISPRELIT, }; PhysDefClass::PhysDefClass(void) : ModelName ("NULL"), IsPreLit(false) { FILENAME_PARAM(PhysDefClass,ModelName, "Westwood 3D Files", ".w3d"); } bool PhysDefClass::Is_Valid_Config (StringClass &message) { bool retval = true; if (ModelName.Is_Empty ()) { message += "ModelName is invalid!\n"; retval = false; } return retval; } bool PhysDefClass::Save(ChunkSaveClass &csave) { csave.Begin_Chunk(PHYSDEF_CHUNK_DEFINITION); DefinitionClass::Save(csave); csave.End_Chunk(); csave.Begin_Chunk(PHYSDEF_CHUNK_VARIABLES); WRITE_MICRO_CHUNK_WWSTRING(csave,PHYSDEF_VARIABLE_MODELNAME,ModelName); WRITE_MICRO_CHUNK(csave,PHYSDEF_VARIABLE_ISPRELIT,IsPreLit); csave.End_Chunk(); return true; } bool PhysDefClass::Load(ChunkLoadClass &cload) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case PHYSDEF_CHUNK_DEFINITION: DefinitionClass::Load(cload); break; case PHYSDEF_CHUNK_VARIABLES: WWASSERT(cload.Cur_Chunk_ID() == PHYSDEF_CHUNK_VARIABLES); while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { OBSOLETE_MICRO_CHUNK(PHYSDEF_VARIABLE_FLAGS); READ_MICRO_CHUNK_WWSTRING(cload,PHYSDEF_VARIABLE_MODELNAME,ModelName); READ_MICRO_CHUNK(cload,PHYSDEF_VARIABLE_ISPRELIT,IsPreLit); } cload.Close_Micro_Chunk(); } break; } cload.Close_Chunk(); } return true; } bool PhysDefClass::Is_Type(const char * type_name) { if (stricmp(type_name,PhysDefClass::Get_Type_Name()) == 0) { return true; } else { return false; } }