/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // ParticleSys.h ////////////////////////////////////////////////////////////////////////////////// // Particle System type definitions // Author: Michael S. Booth, November 2001 /////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once #ifndef _PARTICLE_SYS_H_ #define _PARTICLE_SYS_H_ #include #include "Common/AsciiString.h" #include "Common/GameMemory.h" #include "Common/GameType.h" #include "Common/Snapshot.h" #include "Common/SubsystemInterface.h" #include "GameClient/ClientRandomValue.h" #include "WWMath/Matrix3D.h" ///< @todo Replace with our own matrix library #include "Common/STLTypedefs.h" /// @todo Once the client framerate is decoupled, the frame counters within will have to become time-based class Particle; class ParticleSystem; class ParticleSystemManager; class Drawable; class Object; struct FieldParse; class INI; class DebugWindowDialog; // really ParticleEditorDialog class RenderInfoClass; // ick enum ParticleSystemID { INVALID_PARTICLE_SYSTEM_ID = 0 }; #define MAX_VOLUME_PARTICLE_DEPTH ( 16 ) #define DEFAULT_VOLUME_PARTICLE_DEPTH ( 0 )//The Default is not to do the volume thing! #define OPTIMUM_VOLUME_PARTICLE_DEPTH ( 6 ) //-------------------------------------------------------------------------------------------------------------- enum { MAX_KEYFRAMES=8 }; struct Keyframe { Real value; UnsignedInt frame; }; struct RGBColorKeyframe { RGBColor color; UnsignedInt frame; }; enum ParticlePriorityType { INVALID_PRIORITY = 0, PARTICLE_PRIORITY_LOWEST = 1, // FLUFF = PARTICLE_PRIORITY_LOWEST, ///< total and absolute fluff // DEBRIS, ///< debris related particles // NATURE, ///< neato effects we just might see in the world // WEAPON, ///< Weapons firing and flying in the air // DAMAGE, ///< taking damage/dying explosions // SPECIAL, ///< super special top priority like a superweapon WEAPON_EXPLOSION = PARTICLE_PRIORITY_LOWEST, SCORCHMARK, DUST_TRAIL, BUILDUP, DEBRIS_TRAIL, UNIT_DAMAGE_FX, DEATH_EXPLOSION, SEMI_CONSTANT, CONSTANT, WEAPON_TRAIL, AREA_EFFECT, CRITICAL, ///< super special top priority like a superweapon ALWAYS_RENDER, ///< used for logically important display (not just fluff), so must never be culled, regardless of particle cap, lod, etc // !!! *Noting* goes here ... special is the top priority !!! PARTICLE_PRIORITY_HIGHEST = ALWAYS_RENDER, NUM_PARTICLE_PRIORITIES ///< Keep this last }; /** * This structure is filled out and passed to the constructor of a Particle to initialize it */ class ParticleInfo : public Snapshot { public: ParticleInfo( void ); Coord3D m_vel; ///< initial velocity Coord3D m_pos; ///< initial position Coord3D m_emitterPos; ///< position of the emitter Real m_velDamping; ///< velocity damping coefficient Real m_angleZ; ///< initial angle around Z axis Real m_angularRateZ; ///< initial angle around Z axis Real m_angularDamping; ///< angular velocity damping coefficient UnsignedInt m_lifetime; ///< lifetime of this particle Real m_size; ///< size of the particle Real m_sizeRate; ///< rate of change of size Real m_sizeRateDamping; ///< damping of size change rate Keyframe m_alphaKey[ MAX_KEYFRAMES ]; RGBColorKeyframe m_colorKey[ MAX_KEYFRAMES ]; Real m_colorScale; ///< color "scaling" coefficient Real m_windRandomness; ///< multiplier for wind randomness per particle Bool m_particleUpTowardsEmitter; ///< if this is true, then the 0.0 Z rotation should actually ///< correspond to the direction of the emitter. protected: // snapshot methods virtual void crc( Xfer *xfer ); virtual void xfer( Xfer *xfer ); virtual void loadPostProcess( void ); }; /** * An individual particle created by a ParticleSystem. * NOTE: Particles cannot exist without a parent particle system. */ class Particle : public MemoryPoolObject, public ParticleInfo { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( Particle, "ParticlePool" ) public: Particle( ParticleSystem *system, const ParticleInfo *data ); inline Bool update( void ); ///< update this particle's behavior - return false if dead void doWindMotion( void ); ///< do wind motion (if present) from particle system void applyForce( const Coord3D *force ); ///< add the given acceleration inline const Coord3D *getPosition( void ) { return &m_pos; } inline Real getSize( void ) { return m_size; } inline Real getAngle( void ) { return m_angleZ; } inline Real getAlpha( void ) { return m_alpha; } inline const RGBColor *getColor( void ) { return &m_color; } inline void setColor( RGBColor *color ) { m_color = *color; } inline Bool isInvisible( void ); ///< return true if this particle is invisible inline Bool isCulled (void) {return m_isCulled;} ///< return true if the particle falls off the edge of the screen inline void setIsCulled (Bool enable) { m_isCulled = enable;} ///< set particle to not visible because it's outside view frustum void controlParticleSystem( ParticleSystem *sys ) { m_systemUnderControl = sys; } void detachControlledParticleSystem( void ) { m_systemUnderControl = NULL; } // get priority of this particle ... which is the priority of the system it belongs to ParticlePriorityType getPriority( void ); UnsignedInt getPersonality(void) { return m_personality; }; void setPersonality(UnsignedInt p) { m_personality = p; }; protected: // snapshot methods virtual void crc( Xfer *xfer ); virtual void xfer( Xfer *xfer ); virtual void loadPostProcess( void ); void computeAlphaRate( void ); ///< compute alpha rate to get to next key void computeColorRate( void ); ///< compute color change to get to next key public: Particle * m_systemNext; Particle * m_systemPrev; Particle * m_overallNext; Particle * m_overallPrev; protected: ParticleSystem * m_system; ///< the particle system this particle belongs to UnsignedInt m_personality; ///< each new particle assigned a number one higher than the previous // most of the particle data is derived from ParticleInfo Coord3D m_accel; ///< current acceleration Coord3D m_lastPos; ///< previous position UnsignedInt m_lifetimeLeft; ///< lifetime remaining, if zero -> destroy UnsignedInt m_createTimestamp; ///< frame this particle was created Real m_alpha; ///< current alpha of this particle Real m_alphaRate; ///< current rate of alpha change Int m_alphaTargetKey; ///< next index into key array RGBColor m_color; ///< current color of this particle RGBColor m_colorRate; ///< current rate of color change Int m_colorTargetKey; ///< next index into key array Bool m_isCulled; ///< status of particle relative to screen bounds public: Bool m_inSystemList; Bool m_inOverallList; union { ParticleSystem * m_systemUnderControl; ///< the particle system attached to this particle (not the system that created us) ParticleSystemID m_systemUnderControlID; ///< id of system attached to this particle (not the system that created us); }; }; //-------------------------------------------------------------------------------------------------------------- #ifdef DEFINE_PARTICLE_SYSTEM_NAMES /**** NOTE: These MUST be kept in sync with the enumerations below *****/ static char *ParticleShaderTypeNames[] = { "NONE", "ADDITIVE", "ALPHA", "ALPHA_TEST", "MULTIPLY", NULL }; static char *ParticleTypeNames[] = { "NONE", "PARTICLE", "DRAWABLE", "STREAK", "VOLUME_PARTICLE","SMUDGE", NULL }; static char *EmissionVelocityTypeNames[] = { "NONE", "ORTHO", "SPHERICAL", "HEMISPHERICAL", "CYLINDRICAL", "OUTWARD", NULL }; static char *EmissionVolumeTypeNames[] = { "NONE", "POINT", "LINE", "BOX", "SPHERE", "CYLINDER", NULL }; //"NONE", "FLUFF", "DEBRIS", "NATURE", "WEAPON", "DAMAGE", "SPECIAL" static char *ParticlePriorityNames[] = { "NONE", "WEAPON_EXPLOSION","SCORCHMARK","DUST_TRAIL","BUILDUP","DEBRIS_TRAIL","UNIT_DAMAGE_FX","DEATH_EXPLOSION","SEMI_CONSTANT","CONSTANT","WEAPON_TRAIL","AREA_EFFECT","CRITICAL", "ALWAYS_RENDER", NULL }; static char *WindMotionNames[] = { "NONE", "Unused", "PingPong", "Circular", NULL }; #endif /** * All of the properties of a particle system, used by both ParticleSystemTemplates * and ParticleSystem classes. */ class ParticleSystemInfo : public Snapshot { public: ParticleSystemInfo(); ///< to set defaults. // snapshot methods virtual void crc( Xfer *xfer ); virtual void xfer( Xfer *xfer ); virtual void loadPostProcess( void ); Bool m_isOneShot; ///< if true, destroy system after one burst has occurred enum ParticleShaderType { INVALID_SHADER=0, ADDITIVE, ALPHA, ALPHA_TEST, MULTIPLY } m_shaderType; ///< how this particle is rendered enum ParticleType { INVALID_TYPE=0, PARTICLE, DRAWABLE, STREAK, VOLUME_PARTICLE, SMUDGE ///< is a particle a 2D-screen-facing particle, or a Drawable, or a Segment in a streak? } m_particleType; AsciiString m_particleTypeName; ///< if PARTICLE, texture filename, if DRAWABLE, Drawable name GameClientRandomVariable m_angleZ; ///< initial angle around Z axis GameClientRandomVariable m_angularRateZ; ///< initial angle around Z axis GameClientRandomVariable m_angularDamping; ///< angular velocity damping coefficient GameClientRandomVariable m_velDamping; ///< velocity damping factor GameClientRandomVariable m_lifetime; ///< lifetime of emitted particles UnsignedInt m_systemLifetime; ///< lifetime of the particle system GameClientRandomVariable m_startSize; ///< initial size of emitted particles GameClientRandomVariable m_startSizeRate; ///< change in start size of emitted particles GameClientRandomVariable m_sizeRate; ///< rate of change of size GameClientRandomVariable m_sizeRateDamping; ///< damping of size change UnsignedInt m_volumeParticleDepth; ///< how many layers deep to draw the particle, if >1 struct RandomKeyframe { GameClientRandomVariable var; ///< the range of values at this keyframe UnsignedInt frame; ///< the frame number }; RandomKeyframe m_alphaKey[ MAX_KEYFRAMES ]; RGBColorKeyframe m_colorKey[ MAX_KEYFRAMES ]; ///< color of particle typedef Int Color; void tintAllColors( Color tintColor ); GameClientRandomVariable m_colorScale; ///< color coefficient GameClientRandomVariable m_burstDelay; ///< time between particle emissions GameClientRandomVariable m_burstCount; ///< number of particles emitted per burst GameClientRandomVariable m_initialDelay; ///< delay before particles begin emitting Coord3D m_driftVelocity; ///< additional velocity added to all particles Real m_gravity; ///< gravity acceleration (global Z) for particles in this system AsciiString m_slaveSystemName; ///< if non-empty, create a system whose particles track this system's Coord3D m_slavePosOffset; ///< positional offset of slave particles relative to master's AsciiString m_attachedSystemName; ///< if non-empty, create a system attached to each particle of this system //------------------------------------------------------- // The direction and speed at which particles are emitted enum EmissionVelocityType { INVALID_VELOCITY=0, ORTHO, SPHERICAL, HEMISPHERICAL, CYLINDRICAL, OUTWARD } m_emissionVelocityType; ParticlePriorityType m_priority; union { struct { GameClientRandomVariable x; ///< initial speed of particle along X axis GameClientRandomVariable y; ///< initial speed of particle along Y axis GameClientRandomVariable z; ///< initial speed of particle along Z axis } ortho; struct { GameClientRandomVariable speed; ///< initial speed of particle along random radial direction } spherical, hemispherical; struct { GameClientRandomVariable radial; ///< initial speed of particle in the disk GameClientRandomVariable normal; ///< initial speed of particle perpendicular to disk } cylindrical; struct { GameClientRandomVariable speed; ///< speed outward from emission volume GameClientRandomVariable otherSpeed; ///< speed along "other" axis, such as cylinder length } outward; } m_emissionVelocity; //---------------------------------------------------------- // The volume of space where particles are initially created // Note that the volume is relative to the system's position and orientation enum EmissionVolumeType { INVALID_VOLUME=0, POINT, LINE, BOX, SPHERE, CYLINDER } m_emissionVolumeType; ///< the type of volume where particles are created union emissionVolumeUnion { // point just uses system's position // line struct { Coord3D start, end; } line; // box struct { Coord3D halfSize; } box; // sphere struct { Real radius; } sphere; // cylinder struct { Real radius; Real length; } cylinder; } m_emissionVolume; ///< the dimensions of the emission volume Bool m_isEmissionVolumeHollow; ///< if true, only create particles at boundary of volume Bool m_isGroundAligned; ///< if true, align with the ground. if false, then do the normal billboarding. Bool m_isEmitAboveGroundOnly; ///< if true, only emit particles when the system is above ground. Bool m_isParticleUpTowardsEmitter; ///< if true, align the up direction to be towards the emitter. enum WindMotion { WIND_MOTION_INVALID = 0, WIND_MOTION_NOT_USED, WIND_MOTION_PING_PONG, WIND_MOTION_CIRCULAR }; WindMotion m_windMotion; ///< motion of the wind angle updating Real m_windAngle; ///< angle of the "wind" associated with this system Real m_windAngleChange; ///< current how fast the angle changes (higher=faster change) Real m_windAngleChangeMin; ///< min for angle change Real m_windAngleChangeMax; ///< max for angle change Real m_windMotionStartAngle; ///< (for ping pong) angle 1 of the ping pong Real m_windMotionStartAngleMin; ///< (for ping pong) min angle for angle 1 Real m_windMotionStartAngleMax; ///< (for ping pong) max angle for angle 1 Real m_windMotionEndAngle; ///< (for ping pong) angle 2 of the ping pong Real m_windMotionEndAngleMin; ///< (for ping pong) min angle for angle 2 Real m_windMotionEndAngleMax; ///< (for ping pong) max angel for angle 2 Byte m_windMotionMovingToEndAngle; ///< (for ping pong) TRUE if we're moving "towards" the end angle }; /** * A ParticleSystemTemplate, used by the ParticleSystemManager to instantiate ParticleSystems. */ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystemInfo { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParticleSystemTemplate, "ParticleSystemTemplatePool" ) public: ParticleSystemTemplate( const AsciiString &name ); AsciiString getName( void ) const { return m_name; } // This function was made const because of update modules' module data being all const. ParticleSystem *createSlaveSystem( Bool createSlaves = TRUE ) const ; ///< if returns non-NULL, it is a slave system for use const FieldParse *getFieldParse( void ) const { return m_fieldParseTable; } ///< Parsing from INI access static void parseRGBColorKeyframe( INI* ini, void *instance, void *store, const void* /*userData*/ ); static void parseRandomKeyframe( INI* ini, void *instance, void *store, const void* /*userData*/ ); static void parseRandomRGBColor( INI* ini, void *instance, void *store, const void* /*userData*/ ); static void parseRandomRGBColorRate( INI* ini, void *instance, void *store, const void* /*userData*/ ); protected: friend class ParticleSystemManager; ///< @todo remove this friendship friend class ParticleSystem; ///< @todo remove this friendship // These friendships are naughty but necessary for particle editing. friend class DebugWindowDialog; ///< @todo remove this friendship when no longer editing particles friend void _updateAsciiStringParmsToSystem(ParticleSystemTemplate *particleTemplate); friend void _updateAsciiStringParmsFromSystem(ParticleSystemTemplate *particleTemplate); friend void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ); static const FieldParse m_fieldParseTable[]; ///< the parse table for INI definition protected: AsciiString m_name; ///< the name of this template // This has to be mutable because of the delayed initialization thing in createSlaveSystem mutable const ParticleSystemTemplate *m_slaveTemplate; ///< if non-NULL, use this to create a slave system // template attribute data inherited from ParticleSystemInfo class }; /** * A particle system, responsible for creating Particles. * If a particle system has finished, but still has particles "in the air", it must wait * before destroying itself in order to ensure everything can be cleaned up if the system * is reset. */ class ParticleSystem : public MemoryPoolObject, public ParticleSystemInfo { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParticleSystem, "ParticleSystemPool" ) public: ParticleSystem( const ParticleSystemTemplate *sysTemplate, ParticleSystemID id, Bool createSlaves ); ///< create a particle system from a template and assign it this ID inline ParticleSystemID getSystemID( void ) const { return m_systemID; } ///< get unique system ID void setPosition( const Coord3D *pos ); ///< set the position of the particle system void getPosition( Coord3D *pos ); ///< get the position of the particle system void setLocalTransform( const Matrix3D *matrix ); ///< set the system's local transform void rotateLocalTransformX( Real x ); ///< rotate local transform matrix void rotateLocalTransformY( Real y ); ///< rotate local transform matrix void rotateLocalTransformZ( Real z ); ///< rotate local transform matrix void setSkipParentXfrm(Bool enable) { m_skipParentXfrm = enable; } /// ParticleSystemList; typedef std::list::iterator ParticleSystemListIt; typedef std::hash_map, rts::equal_to > TemplateMap; ParticleSystemManager( void ); virtual ~ParticleSystemManager(); virtual void init( void ); ///< initialize the manager virtual void reset( void ); ///< reset the manager and all particle systems virtual void update( void ); ///< update all particle systems virtual Int getOnScreenParticleCount( void ) = 0; ///< returns the number of particles on screen virtual void setOnScreenParticleCount(int count); ParticleSystemTemplate *findTemplate( const AsciiString &name ) const; ParticleSystemTemplate *findParentTemplate( const AsciiString &name, int parentNum ) const; ParticleSystemTemplate *newTemplate( const AsciiString &name ); /// given a template, instantiate a particle system ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE ); /** given a template, instantiate a particle system. if attachTo is not null, attach the particle system to the given object. return the particle system's ID, NOT its pointer. */ ParticleSystemID createAttachedParticleSystemID( const ParticleSystemTemplate *sysTemplate, Object* attachTo, Bool createSlaves = TRUE ); /// find a particle system given a unique system identifier ParticleSystem *findParticleSystem( ParticleSystemID id ); /// destroy the particle system with the given id (if it still exists) void destroyParticleSystemByID(ParticleSystemID id); /// return iterators to the particle system template TemplateMap::iterator beginParticleSystemTemplate() { return m_templateMap.begin(); } TemplateMap::iterator endParticleSystemTemplate() { return m_templateMap.end(); } TemplateMap::const_iterator beginParticleSystemTemplate() const { return m_templateMap.begin(); } TemplateMap::const_iterator endParticleSystemTemplate() const { return m_templateMap.end(); } /// destroy attached systems to object void destroyAttachedSystems( Object *obj ); void setLocalPlayerIndex(Int index) {m_localPlayerIndex=index;} void addParticle( Particle *particleToAdd, ParticlePriorityType priority ); void removeParticle( Particle *particleToRemove ); Int removeOldestParticles( UnsignedInt count, ParticlePriorityType priorityCap ); UnsignedInt getParticleCount( void ) const { return m_particleCount; } UnsignedInt getFieldParticleCount( void ) const { return m_fieldParticleCount; } UnsignedInt getParticleSystemCount( void ) const { return m_particleSystemCount; } // @todo const this jkmcd ParticleSystemList &getAllParticleSystems( void ) { return m_allParticleSystemList; } virtual void doParticles(RenderInfoClass &rinfo) = 0; virtual void queueParticleRender() = 0; virtual void preloadAssets( TimeOfDay timeOfDay ); // these are only for use by partcle systems to link and unlink themselves void friend_addParticleSystem( ParticleSystem *particleSystemToAdd ); void friend_removeParticleSystem( ParticleSystem *particleSystemToRemove ); protected: // snapshot methods virtual void crc( Xfer *xfer ); virtual void xfer( Xfer *xfer ); virtual void loadPostProcess( void ); Particle *m_allParticlesHead[ NUM_PARTICLE_PRIORITIES ]; Particle *m_allParticlesTail[ NUM_PARTICLE_PRIORITIES ]; ParticleSystemID m_uniqueSystemID; ///< unique system ID to assign to each system created ParticleSystemList m_allParticleSystemList; UnsignedInt m_particleCount; UnsignedInt m_fieldParticleCount; ///< this does not need to be xfered, since it is evaluated every frame UnsignedInt m_particleSystemCount; Int m_onScreenParticleCount; ///< number of particles displayed on screen per frame UnsignedInt m_lastLogicFrameUpdate; Int m_localPlayerIndex; ///