| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- /*
- ** 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/projectile.cpp $*
- * *
- * Author:: Greg_h *
- * *
- * $Modtime:: 12/20/01 5:14p $*
- * *
- * $Revision:: 60 $*
- * *
- *---------------------------------------------------------------------------------------------*
- * Functions: *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- #include "projectile.h"
- #include "pscene.h"
- #include "lineseg.h"
- #include "physcon.h"
- #include "physcoltest.h"
- #include "rendobj.h"
- #include "persistfactory.h"
- #include "wwphysids.h"
- #include "simpledefinitionfactory.h"
- #include "wwdebug.h"
- #include "wwhack.h"
- #include "wwprofile.h"
- DECLARE_FORCE_LINK(projectile);
- /***********************************************************************************************
- **
- ** ProjectileClass Implementation
- **
- ***********************************************************************************************/
- /*
- ** Declare a PersistFactory for ProjectileClasses
- */
- SimplePersistFactoryClass<ProjectileClass,PHYSICS_CHUNKID_PROJECTILE> _ProjectileFactory;
- /*
- ** ChunkID's used by ProjectileClass
- */
- enum
- {
- PROJECTILE_CHUNK_MOVEABLEPHYS = 0x03308765, // parent class data
- PROJECTILE_CHUNK_VARIABLES,
- PROJECTILE_VARIABLE_POSITION = 0x00,
- PROJECTILE_VARIABLE_VELOCITY,
- PROJECTILE_VARIABLE_COLLIDESONMOVE,
- PROJECTILE_VARIABLE_ORIENTATIONMODE,
- PROJECTILE_VARIABLE_TUMBLEAXIS,
- PROJECTILE_VARIABLE_TUMBLERATE,
- PROJECTILE_VARIABLE_LIFETIME,
- PROJECTILE_VARIABLE_BOUNCECOUNT,
- };
- ProjectileClass::ProjectileClass(void) :
- CollidesOnMove(true),
- OrientationMode(ORIENTATION_ALIGNED),
- TumbleAxis(1,0,0),
- TumbleRate(DEG_TO_RADF(10.0f)),
- Lifetime(2.0f),
- BounceCount(0)
- {
- State.Position.Set(0,0,0);
- State.Velocity.Set(0,0,0);
- // turn off lighting for all projectiles
- Enable_Is_Pre_Lit(true);
- }
- void ProjectileClass::Init(const ProjectileDefClass & def)
- {
- MoveablePhysClass::Init(def);
- CollidesOnMove = def.CollidesOnMove;
- OrientationMode = def.OrientationMode;
- TumbleAxis = def.TumbleAxis;
- TumbleRate = def.TumbleRate;
- Lifetime = def.Lifetime;
- BounceCount = def.BounceCount;
- State.Position.Set(0,0,0);
- State.Velocity.Set(0,0,0);
- if (TumbleAxis.Length2() == 0.0f) {
- TumbleAxis.Set(0,0,1);
- } else {
- TumbleAxis.Normalize();
- }
- // turn off lighting for all projectiles
- Enable_Is_Pre_Lit(true);
- }
- ProjectileClass::~ProjectileClass(void)
- {
- }
- const AABoxClass & ProjectileClass::Get_Bounding_Box(void) const
- {
- assert(Model);
- return Model->Get_Bounding_Box();
- }
- const Matrix3D & ProjectileClass::Get_Transform(void) const
- {
- assert(Model);
- return Model->Get_Transform();
- }
- void ProjectileClass::Set_Transform(const Matrix3D & tm)
- {
- tm.Get_Translation(&State.Position);
- assert(Model);
- Model->Set_Transform(tm);
- // TODO: don't use the actual bounds? Just use the position and a pre-calced extent
- Update_Cull_Box();
- }
- void ProjectileClass::Set_Orientation_Mode_Tumbling(void)
- {
- OrientationMode = ORIENTATION_TUMBLING;
-
- TumbleAxis.Set(0,0,0);
- while (TumbleAxis.Length() <= 0.001f) {
- TumbleAxis.X = WWMath::Random_Float();
- TumbleAxis.Y = WWMath::Random_Float();
- TumbleAxis.Z = WWMath::Random_Float();
- }
- TumbleAxis.Normalize();
- TumbleRate = WWMath::Random_Float((float)DEG_TO_RAD(2.0f),(float)DEG_TO_RAD(10.0f));
- }
- void ProjectileClass::Set_Orientation_Mode_Tumbling(const Vector3 & axis,float rate)
- {
- OrientationMode = ORIENTATION_TUMBLING;
- TumbleAxis = axis;
- TumbleAxis.Normalize();
- TumbleRate = rate;
- }
- void ProjectileClass::Set_Orientation_Mode_Aligned_Fixed(void)
- {
- OrientationMode = ORIENTATION_ALIGNED_FIXED;
- if (OrientationMode == ORIENTATION_ALIGNED_FIXED) {
- Matrix3D tm;
- tm.Obj_Look_At(State.Position,State.Position + State.Velocity,0.0f);
- Model->Set_Transform(tm);
- }
- }
- void ProjectileClass::Set_Lifetime( float time )
- {
- Lifetime = time;
- }
- float ProjectileClass::Get_Lifetime( void )
- {
- return Lifetime;
- }
- void ProjectileClass::Set_Bounce_Count(int count)
- {
- BounceCount = count;
- }
- int ProjectileClass::Get_Bounce_Count(void)
- {
- return BounceCount;
- }
- void ProjectileClass::Integrate(float dt)
- {
- float accel = PhysicsConstants::GravityAcceleration.Z * GravScale;
- State.Position.X += State.Velocity.X * dt;
- State.Position.Y += State.Velocity.Y * dt;
- State.Position.Z += 0.5f * accel * dt * dt + State.Velocity.Z * dt;
- State.Velocity.Z += accel * dt;
- }
- void ProjectileClass::Timestep(float dt)
- {
- WWPROFILE("Projectile::Timestep");
- const int MAX_BUMPS = 5;
- if (Is_User_Control_Enabled()) {
- return;
- }
- if (dt == 0.0f) {
- return;
- }
- WWASSERT(State.Position.Is_Valid());
- WWASSERT(State.Velocity.Is_Valid());
- if ( CollidesOnMove ) {
- WWPROFILE("Move and Collide");
- /*
- ** Repeat until we eat all of the time
- */
- float remaining_time = dt;
- float timestep;
- int bumps = 0;
- /*
- ** This is that last object we choose to ignore
- */
- PhysClass *blocker = NULL;
- while ((remaining_time > 0.0f) && (bumps < MAX_BUMPS) && (BounceCount >= 0)) {
- timestep = remaining_time;
- bumps++;
- /*
- ** Integrate the state of the object for some amount
- ** of time: timestep
- */
- ProjectileStateStruct oldstate = State;
- Integrate(timestep);
- /*
- ** Check for collisions in the path of the object
- */
- CastResultStruct res;
- LineSegClass ray(oldstate.Position,State.Position);
- PhysRayCollisionTestClass raytest(ray,&res,Get_Collision_Group(),COLLISION_TYPE_PROJECTILE);
- {
- WWPROFILE("Raycast");
- Inc_Ignore_Counter();
- PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);
- Dec_Ignore_Counter();
- }
-
- /*
- ** If the result was a "startbad", just do the whole step
- */
- if (raytest.Result->StartBad) {
- remaining_time -= timestep;
- } else {
- /*
- ** If there was a collision, cut the timestep so that
- ** we integrate right up to the collision
- */
- if (raytest.Result->Fraction < 1.0f) {
-
-
- timestep = raytest.Result->Fraction * timestep;
- remaining_time -= timestep;
- /*
- ** Compute the point of collision, stored in ContactPoint of the CastResStruct
- ** so that observers have it.
- */
- ray.Compute_Point(raytest.Result->Fraction,&(raytest.Result->ContactPoint));
- /*
- ** Reset and integrate up to that point
- */
- State = oldstate;
- Integrate(0.99f * timestep);
-
- /*
- ** Notify the parties involved
- */
- WWASSERT(raytest.CollidedPhysObj != NULL);
- CollisionReactionType reaction = COLLISION_REACTION_DEFAULT;
- CollisionEventClass event;
- event.CollisionResult = raytest.Result;
- event.CollidedRenderObj = raytest.CollidedRenderObj;
- {
- WWPROFILE("Callbacks");
- event.OtherObj = raytest.CollidedPhysObj;
- reaction |= Collision_Occurred(event);
- event.OtherObj = this;
- reaction |= raytest.CollidedPhysObj->Collision_Occurred(event);
- }
- if ( !(reaction & COLLISION_REACTION_NO_BOUNCE) ) {
- /*
- ** Perform collision processing and loop
- */
- float dot = Vector3::Dot_Product(State.Velocity,raytest.Result->Normal);
- State.Velocity -= (1.0f + Elasticity)*dot*raytest.Result->Normal;
- /*
- ** If we are using ORIENTATION_MODE_ALIGNED_FIXED, update the
- ** transform whenever we bounce
- */
- if (OrientationMode == ORIENTATION_ALIGNED_FIXED) {
- Matrix3D tm;
- tm.Obj_Look_At(State.Position,State.Position + State.Velocity,0.0f);
- Model->Set_Transform(tm);
- }
- /*
- ** Decrement the bounce count!
- */
- BounceCount--;
- } else {
- /*
- ** We were requested to fly through. Mark the current blocker as ignore
- ** so we collide with him no more this pass
- */
- Integrate(0.02*timestep);
- if ( blocker ) { // Stop ignoring the last blocker
- blocker->Dec_Ignore_Counter();
- }
- blocker = raytest.CollidedPhysObj;
- if ( blocker ) { // Start ignoring this blocker
- blocker->Inc_Ignore_Counter();
- }
- }
- /*
- ** If requested to stop, progress no more
- */
- if ( reaction & COLLISION_REACTION_STOP_MOTION ) {
- remaining_time = 0;
- State.Velocity = Vector3(0,0,0);
- }
-
- } else {
-
- remaining_time -= timestep;
- }
- }
- }
- if ( blocker ) { // Stop ignoring the last blocker
- blocker->Dec_Ignore_Counter();
- }
- } else {
- Integrate(dt);
- }
- Update_Transform(dt);
- Update_Cull_Box();
-
- DEBUG_RENDER_AXES(Get_Transform(),Vector3(1.0f,1.0f,1.0f));
- /*
- ** Decrement our life, check our bounce count
- */
- Lifetime -= dt;
- if ((Lifetime < 0.0f) || (BounceCount < 0)) {
- Expire();
- }
- }
- bool ProjectileClass::Cast_Ray(PhysRayCollisionTestClass & raytest)
- {
- assert(Model);
- if (Model->Cast_Ray(raytest)) {
- raytest.CollidedPhysObj = this;
- return true;
- }
- return false;
- }
- void ProjectileClass::Update_Transform(float dt)
- {
- WWASSERT(Model);
- // Update the cached transformation matrix
- Matrix3D tm;
- switch(OrientationMode) {
- case ORIENTATION_ALIGNED:
- // compute a matrix aligned to the path
- tm.Obj_Look_At(State.Position,State.Position + State.Velocity,0.0f);
- Model->Set_Transform(tm);
- break;
-
- case ORIENTATION_ALIGNED_FIXED:
- case ORIENTATION_FIXED:
- // just update the position
- Model->Set_Position(State.Position);
- break;
- case ORIENTATION_TUMBLING:
- // update the position, tumble a little
- tm = Model->Get_Transform();
- tm.Set_Translation(State.Position);
- Matrix3D::Multiply(tm,Matrix3D(TumbleAxis,TumbleRate*dt),&tm);
- Model->Set_Transform(tm);
- break;
-
- };
- Update_Visibility_Status();
- }
- const PersistFactoryClass & ProjectileClass::Get_Factory (void) const
- {
- return _ProjectileFactory;
- }
- bool ProjectileClass::Save (ChunkSaveClass &csave)
- {
- csave.Begin_Chunk(PROJECTILE_CHUNK_MOVEABLEPHYS);
- MoveablePhysClass::Save(csave);
- csave.End_Chunk();
- csave.Begin_Chunk(PROJECTILE_CHUNK_VARIABLES);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_POSITION,State.Position);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_VELOCITY,State.Velocity);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_COLLIDESONMOVE,CollidesOnMove);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_ORIENTATIONMODE,OrientationMode);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_TUMBLEAXIS,TumbleAxis);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_TUMBLERATE,TumbleRate);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_LIFETIME,Lifetime);
- WRITE_MICRO_CHUNK(csave,PROJECTILE_VARIABLE_BOUNCECOUNT,BounceCount);
- csave.End_Chunk();
- return true;
- }
- bool ProjectileClass::Load (ChunkLoadClass &cload)
- {
- while (cload.Open_Chunk()) {
-
- switch(cload.Cur_Chunk_ID())
- {
- case PROJECTILE_CHUNK_MOVEABLEPHYS:
- MoveablePhysClass::Load(cload);
- break;
- case PROJECTILE_CHUNK_VARIABLES:
- while (cload.Open_Micro_Chunk()) {
- switch(cload.Cur_Micro_Chunk_ID()) {
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_POSITION,State.Position);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_VELOCITY,State.Velocity);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_COLLIDESONMOVE,CollidesOnMove);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_ORIENTATIONMODE,OrientationMode);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_TUMBLEAXIS,TumbleAxis);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_TUMBLERATE,TumbleRate);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_LIFETIME,Lifetime);
- READ_MICRO_CHUNK(cload,PROJECTILE_VARIABLE_BOUNCECOUNT,BounceCount);
- }
- 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;
- }
- /***********************************************************************************************
- **
- ** ProjectileDefClass Implementation
- **
- ***********************************************************************************************/
- /*
- ** Declare a PersistFactory for ProjectileDefClasses, this allows them to save and load
- */
- SimplePersistFactoryClass<ProjectileDefClass,PHYSICS_CHUNKID_PROJECTILEDEF> _ProjectileDefFactory;
- /*
- ** Declare a definition factory. This exposes ProjectileDef to the editor.
- */
- DECLARE_DEFINITION_FACTORY(ProjectileDefClass, CLASSID_PROJECTILEDEF, "Projectile") _ProjectileDefDefFactory;
- /*
- ** Chunk ID's used by MoveablePhysDefClass
- */
- enum
- {
- PROJECTILEDEF_CHUNK_MOVEABLEPHYSDEF = 0x01210011, // (parent class)
- PROJECTILEDEF_CHUNK_VARIABLES,
- PROJECTILEDEF_VARIABLE_COLLIDESONMOVE = 0x00,
- PROJECTILEDEF_VARIABLE_ORIENTATIONMODE,
- PROJECTILEDEF_VARIABLE_TUMBLEAXIS,
- PROJECTILEDEF_VARIABLE_TUMBLERATE,
- PROJECTILEDEF_VARIABLE_LIFETIME,
- PROJECTILEDEF_VARIABLE_BOUNCECOUNT,
-
- };
- ProjectileDefClass::ProjectileDefClass(void) :
- CollidesOnMove(true),
- OrientationMode(ProjectileClass::ORIENTATION_ALIGNED),
- TumbleAxis(1,2,1),
- TumbleRate(DEG_TO_RADF(10.0f)),
- Lifetime(2.0f),
- BounceCount(0)
- {
- #ifdef PARAM_EDITING_ON
- // make our parameters editable!
- EDITABLE_PARAM(ProjectileDefClass, ParameterClass::TYPE_BOOL, CollidesOnMove);
- EnumParameterClass *param = new EnumParameterClass(&OrientationMode);
- param->Set_Name ("OrientationMode");
- param->Add_Value("ALIGNED",ProjectileClass::ORIENTATION_ALIGNED);
- param->Add_Value("FIXED",ProjectileClass::ORIENTATION_FIXED);
- param->Add_Value("TUMBLE",ProjectileClass::ORIENTATION_TUMBLING);
- GENERIC_EDITABLE_PARAM(ProjectileDefClass,param)
- FLOAT_EDITABLE_PARAM(ProjectileDefClass, TumbleAxis.X, 0.0f, 10.0f);
- FLOAT_EDITABLE_PARAM(ProjectileDefClass, TumbleAxis.Y, 0.0f, 10.0f);
- FLOAT_EDITABLE_PARAM(ProjectileDefClass, TumbleAxis.Z, 0.0f, 10.0f);
- ANGLE_EDITABLE_PARAM(ProjectileDefClass, TumbleRate, DEG_TO_RADF(0.1f), DEG_TO_RADF(90.0f));
- FLOAT_EDITABLE_PARAM(ProjectileDefClass, Lifetime, 0.01f, 100.0f);
- INT_EDITABLE_PARAM(ProjectileDefClass, BounceCount, 0, 32);
- #endif
- }
- const PersistFactoryClass & ProjectileDefClass::Get_Factory (void) const
- {
- return _ProjectileDefFactory;
- }
- uint32 ProjectileDefClass::Get_Class_ID (void) const
- {
- return CLASSID_PROJECTILEDEF;
- }
- PersistClass * ProjectileDefClass::Create(void) const
- {
- ProjectileClass * obj = NEW_REF(ProjectileClass,());
- obj->Init(*this);
- return obj;
- }
- bool ProjectileDefClass::Save(ChunkSaveClass &csave)
- {
- csave.Begin_Chunk(PROJECTILEDEF_CHUNK_MOVEABLEPHYSDEF);
- MoveablePhysDefClass::Save(csave);
- csave.End_Chunk();
- csave.Begin_Chunk(PROJECTILEDEF_CHUNK_VARIABLES);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_COLLIDESONMOVE,CollidesOnMove);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_ORIENTATIONMODE,OrientationMode);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_TUMBLEAXIS,TumbleAxis);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_TUMBLERATE,TumbleRate);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_LIFETIME,Lifetime);
- WRITE_MICRO_CHUNK(csave,PROJECTILEDEF_VARIABLE_BOUNCECOUNT,BounceCount);
- csave.End_Chunk();
- return true;
- }
- bool ProjectileDefClass::Load(ChunkLoadClass &cload)
- {
- while (cload.Open_Chunk()) {
- switch(cload.Cur_Chunk_ID()) {
- case PROJECTILEDEF_CHUNK_MOVEABLEPHYSDEF:
- MoveablePhysDefClass::Load(cload);
- break;
- case PROJECTILEDEF_CHUNK_VARIABLES:
- while (cload.Open_Micro_Chunk()) {
- switch(cload.Cur_Micro_Chunk_ID()) {
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_COLLIDESONMOVE,CollidesOnMove);
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_ORIENTATIONMODE,OrientationMode);
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_TUMBLEAXIS,TumbleAxis);
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_TUMBLERATE,TumbleRate);
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_LIFETIME,Lifetime);
- READ_MICRO_CHUNK(cload,PROJECTILEDEF_VARIABLE_BOUNCECOUNT,BounceCount);
- }
- 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 ProjectileDefClass::Is_Type(const char * type_name)
- {
- if (stricmp(type_name,ProjectileDefClass::Get_Type_Name()) == 0) {
- return true;
- } else {
- return MoveablePhysDefClass::Is_Type(type_name);
- }
- }
|