/* ** Command & Conquer Generals Zero Hour(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) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // PhysicsUpdate.h // Simple rigid body physics // Author: Michael S. Booth, November 2001 #pragma once #ifndef _PHYSICSUPDATE_H_ #define _PHYSICSUPDATE_H_ #include "Common/AudioEventRTS.h" #include "Common/GameAudio.h" #include "GameLogic/Module/BehaviorModule.h" #include "GameLogic/Module/UpdateModule.h" #include "GameLogic/Module/CollideModule.h" enum ObjectID; enum PhysicsTurningType { TURN_NEGATIVE = -1, TURN_NONE = 0, TURN_POSITIVE = 1 }; //------------------------------------------------------------------------------------------------- class PhysicsBehaviorModuleData : public UpdateModuleData { public: Real m_mass; Real m_shockResistance; Real m_shockMaxYaw; Real m_shockMaxPitch; Real m_shockMaxRoll; Real m_forwardFriction; Real m_lateralFriction; Real m_ZFriction; Real m_aerodynamicFriction; // The percent of the wind resistance effect you suffer from Real m_centerOfMassOffset; // Distance the center of mass is from the center of geometry, to control pitch rate Bool m_killWhenRestingOnGround; // when airborne==false and vel==0, kill it. Bool m_allowBouncing; Bool m_allowCollideForce; Real m_minFallSpeedForDamage; Real m_fallHeightDamageFactor; Real m_pitchRollYawFactor; const WeaponTemplate* m_vehicleCrashesIntoBuildingWeaponTemplate; const WeaponTemplate* m_vehicleCrashesIntoNonBuildingWeaponTemplate; PhysicsBehaviorModuleData(); static void buildFieldParse(MultiIniFieldParse& p); }; //------------------------------------------------------------------------------------------------- /** * Simple rigid body physics update module */ class PhysicsBehavior : public UpdateModule, public CollideModuleInterface { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( PhysicsBehavior, "PhysicsBehavior" ) MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( PhysicsBehavior, PhysicsBehaviorModuleData ) public: PhysicsBehavior( Thing *thing, const ModuleData* moduleData ); // virtual destructor prototype defined by MemoryPoolObject static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_COLLIDE); } virtual void onObjectCreated(); // BehaviorModule virtual CollideModuleInterface* getCollide() { return this; } // CollideModuleInterface virtual void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal ); virtual Bool wouldLikeToCollideWith(const Object* other) const { return false; } virtual Bool isCarBombCrateCollide() const { return false; } virtual Bool isHijackedVehicleCrateCollide() const { return false; } virtual Bool isRailroad() const { return false;} virtual Bool isSalvageCrateCollide() const { return false; } virtual Bool isSabotageBuildingCrateCollide() const { return FALSE; } // UpdateModuleInterface virtual UpdateSleepTime update(); // Disabled conditions to process -- all virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } void applyForce( const Coord3D *force ); ///< apply a force at the object's CG void applyShock( const Coord3D *force ); ///< apply a shockwave force against the object's CG void applyRandomRotation(); ///< apply a random rotation at the object's CG void addVelocityTo(const Coord3D* vel) ; /** identical to applyForce, except that no forward friction will be applied to the physics this frame. (lateral and z-friction will still be applied.) */ void applyMotiveForce( const Coord3D *force ); /** This is a force clear for when objects are going out of bounds, so the locomotor can push them back into legal space. */ void clearAcceleration() { m_accel.zero(); } /** add the velocity of 'this' to 'that'... useful when a unit disgorges another unit and you want to maintain relative velocities */ void transferVelocityTo(PhysicsBehavior* that) const; /// @todo Rotations should be handled by this system as well (MSB) void setAngles( Real yaw, Real pitch, Real roll ); Real getMass() const; void setMass( Real mass ) { m_mass = mass; } Real getCenterOfMassOffset() const { return getPhysicsBehaviorModuleData()->m_centerOfMassOffset; } const Coord3D *getAcceleration() const { return &m_prevAccel; } ///< get last frame's acceleration const Coord3D *getVelocity() const { return &m_vel; } ///< get current velocity Real getVelocityMagnitude() const; ///< return velocity magnitude (speed) Real getForwardSpeed2D() const; ///< compute speed along object's 2d direction vector Real getForwardSpeed3D() const; ///< compute speed along object's 3d direction vector ObjectID getCurrentOverlap() const; ///< return object(s) being overlapped ObjectID getPreviousOverlap() const; ///< return object(s) that were overlapped last frame ObjectID getLastCollidee() const; ///< return object that was last collided with... can be quite old Bool isCurrentlyOverlapped(Object *obj) const; Bool wasPreviouslyOverlapped(Object *obj) const; void addOverlap(Object *obj); Bool isMotive() const; PhysicsTurningType getTurning(void) const { return m_turning; } ///< 0 = not turning, -1 = turn negative, 1 = turn positive. void setTurning(PhysicsTurningType turning) { m_turning = turning; } /** This is a force scrub for velocity when ai objects are colliding. */ void scrubVelocity2D( Real desiredVelocity ); // note that, unlike scrubVelocity2D(), this is a signed limit void scrubVelocityZ( Real desiredVelocity ); void setPitchRate(Real pitch); void setRollRate(Real roll); void setYawRate(Real yaw); /* stickToGround and allowToFall seem contradictory... here's the deal. if stickToGround is true, you stick to the ground, otherwise your z isn't messed with. HOWEVER, if allowToFall is true, stickToGround is ignored, and z is never messed with. Also, allowToFall is reset to "false" when you hit the ground, so you can set it with (relatively) little fear. */ void setAllowToFall(Bool allow) { setFlag(ALLOW_TO_FALL, allow); } void setStickToGround(Bool allow) { setFlag(STICK_TO_GROUND, allow); } void setAllowBouncing(Bool allow) { setFlag(ALLOW_BOUNCE, allow); } void setAllowCollideForce(Bool allow) { setFlag(ALLOW_COLLIDE_FORCE, allow); } void setAllowAirborneFriction(Bool allow) { setFlag(APPLY_FRICTION2D_WHEN_AIRBORNE, allow); } void setImmuneToFallingDamage(Bool allow) { setFlag(IMMUNE_TO_FALLING_DAMAGE, allow); } void setStunned(Bool allow) { setFlag(IS_STUNNED, allow); } Bool getAllowToFall() const { return getFlag(ALLOW_TO_FALL); } void setIsInFreeFall(Bool allow) { setFlag(IS_IN_FREEFALL, allow); } Bool getIsInFreeFall() const { return getFlag(IS_IN_FREEFALL); } Bool getIsStunned() const { return getFlag(IS_STUNNED); } void setExtraBounciness(Real b) { m_extraBounciness = b; } void setExtraFriction(Real b) { m_extraFriction = b; } void setBounceSound(const AudioEventRTS* bounceSound); const AudioEventRTS* getBounceSound() { return m_bounceSound ? &m_bounceSound->m_event : TheAudio->getValidSilentAudioEvent(); } /** Reset all values (vel, accel, etc) to starting values. You should ALMOST NEVER use this; it's intended for cases where you need to nuke everything, like when an existing object goes off the map and you want to bring it back on with all-new values, and want to start with a clean slate. Rule of thumb: if the object was in existence on the map in the previous frame, don't call this... apply forces instead! */ void resetDynamicPhysics(); void setIgnoreCollisionsWith(const Object* obj); Bool isIgnoringCollisionsWith(ObjectID id) const; inline Bool getAllowCollideForce() const { return getFlag(ALLOW_COLLIDE_FORCE); } protected: /* Physics runs in its own phase, after AI, but before all others. It's actually quite important that AI (the thing that drives Locomotors) and Physics run in the same order, relative to each other, for a given object; otherwise, interesting oscillations can occur in some situations, with friction being applied either before or after the locomotive force, making for huge stuttery messes. (srj) */ virtual SleepyUpdatePhase getUpdatePhase() const { return PHASE_PHYSICS; } Real getAerodynamicFriction() const; Real getForwardFriction() const; Real getLateralFriction() const; Real getZFriction() const; void applyGravitationalForces(); void applyFrictionalForces(); Bool handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* bounceForce); void applyYPRDamping(Real factor); UpdateSleepTime calcSleepTime() const; void doBounceSound(const Coord3D& prevPos); Bool checkForOverlapCollision(Object *other); void testStunnedUnitForDestruction(void); private: enum PhysicsFlagsType { // Note - written out in save/load xfer; don't change these numbers. STICK_TO_GROUND = 0x0001, ALLOW_BOUNCE = 0x0002, APPLY_FRICTION2D_WHEN_AIRBORNE = 0x0004, UPDATE_EVER_RUN = 0x0008, WAS_AIRBORNE_LAST_FRAME = 0x0010, ALLOW_COLLIDE_FORCE = 0x0020, ALLOW_TO_FALL = 0x0040, HAS_PITCHROLLYAW = 0x0080, IMMUNE_TO_FALLING_DAMAGE = 0x0100, IS_IN_FREEFALL = 0x0200, IS_IN_UPDATE = 0x0400, IS_STUNNED = 0x0800, }; /* Note: these are private because you should never manipulate these directly, even if you are a subclass... if you want to change the acceleration, you MUST call applyForce(). */ Real m_yawRate; ///< rate of rotation around up vector Real m_rollRate; ///< rate of rotation around forward vector Real m_pitchRate; ///< rate or rotation around side vector DynamicAudioEventRTS* m_bounceSound; ///< The sound for when this thing bounces, or NULL Coord3D m_accel; ///< current acceleration Coord3D m_prevAccel; ///< last frame's acceleration Coord3D m_vel; ///< current velocity PhysicsTurningType m_turning; ///< 0 = not turning, -1 = turn negative, 1 = turn positive. ObjectID m_ignoreCollisionsWith; Int m_flags; Real m_mass; ObjectID m_currentOverlap; ///< object(s) being overlapped, if any ObjectID m_previousOverlap; ///< last frame's object(s) being overlapped ObjectID m_lastCollidee; ///< ID of the last object I collided with, can be quite old. UnsignedInt m_motiveForceExpires; ///< frames at which "recent" applyMotiveForce is no longer considered Real m_extraBounciness; ///< modifier to ground stiffness Real m_extraFriction; ///< modifier to friction(s) ProjectileUpdateInterface* m_pui; mutable Real m_velMag; ///< magnitude of cur vel (recalced when m_vel changes) Bool m_originalAllowBounce; ///< orignal state of allow bounce inline void setFlag(PhysicsFlagsType f, Bool set) { if (set) m_flags |= f; else m_flags &= ~f; } inline Bool getFlag(PhysicsFlagsType f) const { return (m_flags & f) != 0; } }; //------------------------------------------------------------------------------------------------- inline ObjectID PhysicsBehavior::getCurrentOverlap() const { return m_currentOverlap; } //------------------------------------------------------------------------------------------------- inline ObjectID PhysicsBehavior::getPreviousOverlap() const { return m_previousOverlap; } inline ObjectID PhysicsBehavior::getLastCollidee() const { return m_lastCollidee; } #endif // _PHYSICSUPDATE_H_