/* ** 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/trackedvehicle.cpp $* * * * Original Author:: Greg Hjelstrom * * * * $Author:: Byon_g $* * * * $Modtime:: 3/15/02 1:44p $* * * * $Revision:: 12 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "trackedvehicle.h" #include "rendobj.h" #include "lineseg.h" #include "lookuptable.h" #include "physcoltest.h" #include "pscene.h" #include "physcontrol.h" #include "wwphysids.h" #include "persistfactory.h" #include "simpledefinitionfactory.h" #include "wwhack.h" #include "wwprofile.h" #include "wheel.h" #include "octbox.h" #include "mesh.h" #include "meshmdl.h" #include "matinfo.h" #include "vertmaterial.h" #include "mapper.h" #include DECLARE_FORCE_LINK(trackedvehicle); static bool _is_left_track_name(const char * name) { const char * LEFT_TRACK_NAME0 = "V_TRACK-L"; const char * LEFT_TRACK_NAME1 = "V_TREAD-L"; const char * LEFT_TRACK_NAME2 = "V_TRACK_L"; const char * LEFT_TRACK_NAME3 = "V_TREAD_L"; /* ** Track skins can only be sub-objects of a hierarchy so check the name ** after the hierarchy name. */ const char * sub_name = strchr(name,'.'); if (sub_name != NULL) { sub_name++; if ( (strnicmp(sub_name,LEFT_TRACK_NAME0,strlen(LEFT_TRACK_NAME0)) == 0) || (strnicmp(sub_name,LEFT_TRACK_NAME1,strlen(LEFT_TRACK_NAME1)) == 0) || (strnicmp(sub_name,LEFT_TRACK_NAME2,strlen(LEFT_TRACK_NAME2)) == 0) || (strnicmp(sub_name,LEFT_TRACK_NAME3,strlen(LEFT_TRACK_NAME3)) == 0)) { return true; } } return false; } static bool _is_right_track_name(const char * name) { const char * RIGHT_TRACK_NAME0 = "V_TRACK-R"; const char * RIGHT_TRACK_NAME1 = "V_TREAD-R"; const char * RIGHT_TRACK_NAME2 = "V_TRACK_R"; const char * RIGHT_TRACK_NAME3 = "V_TREAD_R"; /* ** Track skins can only be sub-objects of a hierarchy so check the name ** after the hierarchy name. */ const char * sub_name = strchr(name,'.'); if (sub_name != NULL) { sub_name++; if ( (strnicmp(sub_name,RIGHT_TRACK_NAME0,strlen(RIGHT_TRACK_NAME0)) == 0) || (strnicmp(sub_name,RIGHT_TRACK_NAME1,strlen(RIGHT_TRACK_NAME1)) == 0) || (strnicmp(sub_name,RIGHT_TRACK_NAME2,strlen(RIGHT_TRACK_NAME2)) == 0) || (strnicmp(sub_name,RIGHT_TRACK_NAME3,strlen(RIGHT_TRACK_NAME3)) == 0)) { return true; } } return false; } /************************************************************************************** ** ** TrackedVehicleClass Implementation ** **************************************************************************************/ /* ** Declare a PersistFactory for TrackedVehicleClasses */ SimplePersistFactoryClass _TrackedVehicleFactory; /* ** Chunk-ID's used by tracked vehicle class */ enum { TRACKEDVEHICLE_CHUNK_VEHICLEPHYS = 0x00119801, TRACKEDVEHICLE_CHUNK_VARIABLES, }; // Debugging colors const Vector3 SPRING_COLOR(1.0f,0.5f,0.0f); const Vector3 SUSPENSION_FORCE_COLOR(1.0f,0.0f,0.0f); TrackedVehicleClass::TrackedVehicleClass(void) : LeftTrackMovement(0), RightTrackMovement(0), LeftTrackLastPosition(0,0,0), RightTrackLastPosition(0,0,0) { } void TrackedVehicleClass::Init(const TrackedVehicleDefClass & def) { VehiclePhysClass::Init(def); } TrackedVehicleClass::~TrackedVehicleClass(void) { } void TrackedVehicleClass::Render(RenderInfoClass & rinfo) { /* ** Compute the track movement */ const Matrix3D & tm = Get_Transform(); float extent_y = ContactBox->InnerBox.Extent.Y; float extent_z = ContactBox->InnerBox.Extent.Z; Vector3 left_track_position = tm * Vector3(0,extent_y,-extent_z); Vector3 right_track_position = tm * Vector3(0,-extent_y,-extent_z); Vector3 forward; tm.Get_X_Vector(&forward); Vector3 move; Vector3::Subtract(left_track_position,LeftTrackLastPosition,&move); LeftTrackMovement = Vector3::Dot_Product(move,forward); Vector3::Subtract(right_track_position,RightTrackLastPosition,&move); RightTrackMovement = Vector3::Dot_Product(move,forward); LeftTrackLastPosition = left_track_position; RightTrackLastPosition = right_track_position; /* ** Update the mappers */ const TrackedVehicleDefClass * def = Get_TrackedVehicleDef(); for (int i=0; iSet_UV_Offset_Delta( Vector2(def->TrackUScaleFactor * movement,def->TrackVScaleFactor * movement) ); } /* ** Parent class render */ VehiclePhysClass::Render(rinfo); } void TrackedVehicleClass::Set_Model(RenderObjClass * model) { VehiclePhysClass::Set_Model(model); Update_Cached_Model_Parameters(); } void TrackedVehicleClass::Update_Cached_Model_Parameters(void) { /* ** Reset our array of mapper pointers */ TrackMappers.Delete_All(false); /* ** Make sure that this model has unique meshes for its tracks ** and grab pointers to the mappers. */ if (Model != NULL) { Grab_Track_Mappers(Model); } } void TrackedVehicleClass::Grab_Track_Mappers(RenderObjClass * model) { for (int i=0; iGet_Num_Sub_Objects(); i++) { RenderObjClass * sub_obj = model->Get_Sub_Object(i); if (sub_obj && (sub_obj->Class_ID() == RenderObjClass::CLASSID_MESH)) { const char * name = sub_obj->Get_Name(); if (_is_left_track_name(name)) { Add_Track_Mappers((MeshClass*)sub_obj,LEFT_TRACK); } if (_is_right_track_name(name)) { Add_Track_Mappers((MeshClass*)sub_obj,RIGHT_TRACK); } } Grab_Track_Mappers(sub_obj); REF_PTR_RELEASE(sub_obj); } } void TrackedVehicleClass::Add_Track_Mappers(MeshClass * mesh,int track_type) { /* ** First check if this model has a linear offset mapper in any of its vertex materials */ bool has_mapper = false; MaterialInfoClass * matinfo = mesh->Get_Material_Info(); if (matinfo != NULL) { for (int i=0; iVertex_Material_Count(); i++) { VertexMaterialClass * vmtl = matinfo->Peek_Vertex_Material(i); if (vmtl != NULL) { TextureMapperClass * mapper = vmtl->Get_Mapper(0); if ((mapper != NULL) && (mapper->Mapper_ID() == TextureMapperClass::MAPPER_ID_LINEAR_OFFSET)) { has_mapper = true; } REF_PTR_RELEASE(mapper); } } REF_PTR_RELEASE(matinfo); } /* ** If it does, then make the mesh unique, re-dig out the mapper, and hang onto its pointer */ if (has_mapper) { mesh->Make_Unique(); MaterialInfoClass * matinfo = mesh->Get_Material_Info(); if (matinfo != NULL) { for (int i=0; iVertex_Material_Count(); i++) { VertexMaterialClass * vmtl = matinfo->Peek_Vertex_Material(i); if (vmtl != NULL) { TextureMapperClass * mapper = vmtl->Get_Mapper(0); if ((mapper != NULL) && (mapper->Mapper_ID() == TextureMapperClass::MAPPER_ID_LINEAR_OFFSET)) { TrackMapperStruct ts; ts.Mapper = (LinearOffsetTextureMapperClass*)mapper; ts.TrackType = track_type; TrackMappers.Add(ts); #if 0 //Paranoid debug logging WWDEBUG_SAY(("Grabbing Track Mesh! \r\n")); WWDEBUG_SAY((" container: 0x%X mesh: 0x%X vmtl: 0x%X mapper 0x%X\r\n", (unsigned int)mesh->Get_Container(), (unsigned int)mesh, (unsigned int)vmtl, (unsigned int)mapper)); WWDEBUG_SAY((" tracked vehicle: 0x%X\r\n",(unsigned int)this)); WWDEBUG_SAY((" Name = %s\r\n",mesh->Get_Name())); #endif } REF_PTR_RELEASE(mapper); } } REF_PTR_RELEASE(matinfo); } } } void TrackedVehicleClass::Compute_Force_And_Torque(Vector3 * force,Vector3 * torque) { { WWPROFILE("TrackedVehicleClass::Compute_Force_And_Torque"); const TrackedVehicleDefClass * def = Get_TrackedVehicleDef(); WWASSERT(def != NULL); /* ** Compute the left and right track torque ** Accept either strafe or turn as a turn command */ LeftTrackTorque = RightTrackTorque = 0.0f; if (Controller) { float forward = Controller->Get_Move_Forward(); float left = 0.0f; if (WWMath::Fabs(Controller->Get_Turn_Left()) > WWMath::Fabs(Controller->Get_Move_Left())) { left = Controller->Get_Turn_Left(); } else { left = Controller->Get_Move_Left(); } float turn_scale = 1.0f - WWMath::Fabs(left) * (1.0f - def->TurnTorqueScaleFactor); #if 0 if (forward != 0.0f) { turn_scale = 1.0f; } LeftTrackTorque = WWMath::Clamp((2.12f * forward - left),-1.0f,1.0f); RightTrackTorque = WWMath::Clamp((2.12f * forward + left),-1.0f,1.0f); LeftTrackTorque *= def->MaxEngineTorque * turn_scale; RightTrackTorque *= def->MaxEngineTorque * turn_scale; #else LeftTrackTorque = (forward - left) * def->MaxEngineTorque * turn_scale; RightTrackTorque = (forward + left) * def->MaxEngineTorque * turn_scale; #endif } /* ** Update each wheel's torque. */ for (int iwheel = 0; iwheelGet_Flag(WheelClass::LEFT_TRACK)) { ((TrackWheelClass *)Wheels[iwheel])->Set_Axle_Torque(LeftTrackTorque); } if (Wheels[iwheel]->Get_Flag(WheelClass::RIGHT_TRACK)) { ((TrackWheelClass *)Wheels[iwheel])->Set_Axle_Torque(RightTrackTorque); } } } /* ** Pass on to the base class */ VehiclePhysClass::Compute_Force_And_Torque(force,torque); } SuspensionElementClass * TrackedVehicleClass::Alloc_Suspension_Element(void) { return new TrackWheelClass; } /* ** Save-Load System */ const PersistFactoryClass & TrackedVehicleClass::Get_Factory (void) const { return _TrackedVehicleFactory; } bool TrackedVehicleClass::Save (ChunkSaveClass &csave) { csave.Begin_Chunk(TRACKEDVEHICLE_CHUNK_VEHICLEPHYS); VehiclePhysClass::Save(csave); csave.End_Chunk(); return true; } bool TrackedVehicleClass::Load (ChunkLoadClass &cload) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case TRACKEDVEHICLE_CHUNK_VEHICLEPHYS: VehiclePhysClass::Load(cload); 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; } void TrackedVehicleClass::On_Post_Load (void) { VehiclePhysClass::On_Post_Load(); } /*********************************************************************************************** ** ** TrackedVehicleDefClass Implementation ** ***********************************************************************************************/ SimplePersistFactoryClass _TrackedVehicleDefFactory; DECLARE_DEFINITION_FACTORY(TrackedVehicleDefClass, CLASSID_TRACKEDVEHICLEDEF, "TrackedVehicle") _TrackedVehicleDefDefFactory; /* ** Chunk ID's used by TrackedVehicleDefClass */ enum { TRACKEDVEHICLEDEF_CHUNK_VEHICLEPHYSDEF = 0406001454, // (parent class) TRACKEDVEHICLEDEF_CHUNK_VARIABLES, TRACKEDVEHICLEDEF_VARIABLE_MAXENGINETORQUE = 0x00, TRACKEDVEHICLEDEF_VARIABLE_TRACKUSCALEFACTOR, TRACKEDVEHICLEDEF_VARIABLE_TRACKVSCALEFACTOR, TRACKEDVEHICLEDEF_VARIABLE_TURNTORQUESCALEFACTOR, }; TrackedVehicleDefClass::TrackedVehicleDefClass(void) : MaxEngineTorque(0.0f), TrackUScaleFactor(25.0f), TrackVScaleFactor(0.0f), TurnTorqueScaleFactor(1.0f) { // make our parameters editable FLOAT_EDITABLE_PARAM(TrackedVehicleDefClass, MaxEngineTorque, 0.0f, 100000.0f); FLOAT_EDITABLE_PARAM(TrackedVehicleDefClass, TrackUScaleFactor, -1000.0f, 1000.0f); FLOAT_EDITABLE_PARAM(TrackedVehicleDefClass, TrackVScaleFactor, -1000.0f, 1000.0f); FLOAT_EDITABLE_PARAM(TrackedVehicleDefClass, TurnTorqueScaleFactor, 0.0f, 1.0f); } uint32 TrackedVehicleDefClass::Get_Class_ID (void) const { return CLASSID_TRACKEDVEHICLEDEF; } const PersistFactoryClass & TrackedVehicleDefClass::Get_Factory (void) const { return _TrackedVehicleDefFactory; } PersistClass * TrackedVehicleDefClass::Create(void) const { TrackedVehicleClass * obj = NEW_REF(TrackedVehicleClass,()); obj->Init(*this); return obj; } bool TrackedVehicleDefClass::Save(ChunkSaveClass &csave) { csave.Begin_Chunk(TRACKEDVEHICLEDEF_CHUNK_VEHICLEPHYSDEF); VehiclePhysDefClass::Save(csave); csave.End_Chunk(); csave.Begin_Chunk(TRACKEDVEHICLEDEF_CHUNK_VARIABLES); WRITE_MICRO_CHUNK(csave,TRACKEDVEHICLEDEF_VARIABLE_MAXENGINETORQUE,MaxEngineTorque); WRITE_MICRO_CHUNK(csave,TRACKEDVEHICLEDEF_VARIABLE_TRACKUSCALEFACTOR,TrackUScaleFactor); WRITE_MICRO_CHUNK(csave,TRACKEDVEHICLEDEF_VARIABLE_TRACKVSCALEFACTOR,TrackVScaleFactor); WRITE_MICRO_CHUNK(csave,TRACKEDVEHICLEDEF_VARIABLE_TURNTORQUESCALEFACTOR,TurnTorqueScaleFactor); csave.End_Chunk(); return true; } bool TrackedVehicleDefClass::Load(ChunkLoadClass &cload) { while (cload.Open_Chunk()) { switch(cload.Cur_Chunk_ID()) { case TRACKEDVEHICLEDEF_CHUNK_VEHICLEPHYSDEF: VehiclePhysDefClass::Load(cload); break; case TRACKEDVEHICLEDEF_CHUNK_VARIABLES: while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK(cload,TRACKEDVEHICLEDEF_VARIABLE_MAXENGINETORQUE,MaxEngineTorque); READ_MICRO_CHUNK(cload,TRACKEDVEHICLEDEF_VARIABLE_TRACKUSCALEFACTOR,TrackUScaleFactor); READ_MICRO_CHUNK(cload,TRACKEDVEHICLEDEF_VARIABLE_TRACKVSCALEFACTOR,TrackVScaleFactor); READ_MICRO_CHUNK(cload,TRACKEDVEHICLEDEF_VARIABLE_TURNTORQUESCALEFACTOR,TurnTorqueScaleFactor); } 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; } bool TrackedVehicleDefClass::Is_Type(const char * type_name) { if (stricmp(type_name,TrackedVehicleDefClass::Get_Type_Name()) == 0) { return true; } else { return VehiclePhysDefClass::Is_Type(type_name); } }