diff --git a/Core/GameEngine/Include/Common/FramePacer.h b/Core/GameEngine/Include/Common/FramePacer.h index c8681cab7a7..4e914432cf9 100644 --- a/Core/GameEngine/Include/Common/FramePacer.h +++ b/Core/GameEngine/Include/Common/FramePacer.h @@ -67,6 +67,8 @@ class FramePacer Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in seconds Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in milliseconds + Real getLogicFramePhase() const; ///< Get how far the current render step reaches into the current logic frame, in [0,1]. Used to interpolate render updates between logic updates. + protected: FrameRateLimit m_frameRateLimit; @@ -75,6 +77,7 @@ class FramePacer Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale Real m_updateTime; ///< Last update delta time in seconds + Real m_logicFramePhase; ///< How far the current render step reaches into the current logic frame, ranging 0 to 1. Bool m_enableFpsLimit; Bool m_enableLogicTimeScale; diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index d894d8b0ebb..dd238a3c224 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -180,8 +180,10 @@ class Particle : public MemoryPoolObject, Particle( ParticleSystem *system, const ParticleInfo *data ); - Bool update(); ///< update this particle's behavior - return false if dead - void doWindMotion(); ///< do wind motion (if present) from particle system + Bool update(); ///< update this particle's behavior - return false if dead + + void draw( Real timeScale ); ///< render update + void doWindMotion( Real timeScale ); ///< do wind motion (if present) from particle system void applyForce( const Coord3D *force ); ///< add the given acceleration @@ -205,6 +207,8 @@ class Particle : public MemoryPoolObject, UnsignedInt getPersonality() { return m_personality; }; void setPersonality(UnsignedInt p) { m_personality = p; }; + UnsignedInt getElapsedFrames() const; + protected: // snapshot methods @@ -228,7 +232,6 @@ class Particle : public MemoryPoolObject, // 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 @@ -270,6 +273,8 @@ class ParticleSystemInfo : public Snapshot virtual void xfer( Xfer *xfer ) override; virtual void loadPostProcess() override; + void validate(); + Bool m_isOneShot; ///< if true, destroy system after one burst has occurred enum ParticleShaderType @@ -320,7 +325,7 @@ class ParticleSystemInfo : public Snapshot }; - RandomKeyframe m_alphaKey[ MAX_KEYFRAMES ]; + RandomKeyframe m_alphaKey[ MAX_KEYFRAMES ]; ///< alpha of particle RGBColorKeyframe m_colorKey[ MAX_KEYFRAMES ]; ///< color of particle typedef Int Color; @@ -596,6 +601,8 @@ class ParticleSystem : public MemoryPoolObject, virtual Bool update( Int localPlayerIndex ); ///< update this particle system, return false if dead void updateWindMotion(); ///< update wind motion + void draw( Real timeScale ); ///< render update + void setControlParticle( Particle *p ); ///< set control particle void start(); ///< (re)start a stopped particle system @@ -680,6 +687,12 @@ class ParticleSystem : public MemoryPoolObject, protected: + struct VisibilityState + { + VisibilityState() : isShrouded(false) {} + Bool isShrouded; + }; + // snapshot methods virtual void crc( Xfer *xfer ) override; virtual void xfer( Xfer *xfer ) override; @@ -689,6 +702,11 @@ class ParticleSystem : public MemoryPoolObject, ParticlePriorityType priority, Bool forceCreate = FALSE ); ///< factory method for particles + void updateTransform(); + void applyParentTransform(const Matrix3D &parentXfrm); + void applyLocalTransform(); + + VisibilityState updateVisibility( Int localPlayerIndex ); const ParticleInfo *generateParticleInfo( Int particleNum, Int particleCount ); ///< generate a new, random set of ParticleInfo const Coord3D *computeParticlePosition(); ///< compute a position based on emission properties @@ -754,6 +772,11 @@ class ParticleSystem : public MemoryPoolObject, /** * The particle system manager, responsible for maintaining all ParticleSystems */ +// TheSuperHackers @tweak The particle render update is now decoupled from the logic step. +// The lifetime management and the velocity and rate changes remain coupled to the logic step. +// The render updates integrate exactly one logic time step per logic frame, regardless of how many render updates +// fall into it, so the particles follow the same course as in the original update. +// class ParticleSystemManager : public SubsystemInterface, public Snapshot { @@ -770,7 +793,8 @@ class ParticleSystemManager : public SubsystemInterface, virtual void init() override; ///< initialize the manager virtual void reset() override; ///< reset the manager and all particle systems - virtual void update() override; ///< update all particle systems + virtual void update() override; ///< logic update for all particle systems + virtual void draw() override; ///< render update for all particle systems virtual Bool isDummy() const { return false; } @@ -840,6 +864,9 @@ class ParticleSystemManager : public SubsystemInterface, virtual void xfer( Xfer *xfer ) override; virtual void loadPostProcess() override; + void completeLogicFrameDrawUpdate(); ///< render update for the rest of the current logic frame + void drawSystems( Real timeScale ); ///< render update for all particle systems + Particle *m_allParticlesHead[ NUM_PARTICLE_PRIORITIES ]; Particle *m_allParticlesTail[ NUM_PARTICLE_PRIORITIES ]; @@ -851,8 +878,8 @@ class ParticleSystemManager : public SubsystemInterface, 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; ///hasUpdated()) + { + m_logicFramePhase = timeScale; + } + else + { + m_logicFramePhase = min(1.0f, m_logicFramePhase + timeScale); + } + } } void FramePacer::reset() { m_frameRateLimit.reset(); m_updateTime = 1.0f / (Real)getActualFramesPerSecondLimit(); + m_logicFramePhase = 1.0f; } void FramePacer::setFramesPerSecondLimit( Int fps ) @@ -204,8 +222,7 @@ Real FramePacer::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) const Real FramePacer::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) const { - // TheSuperHackers @info Clamps ratio to min 1, because the logic - // frame rate is currently capped by the render frame rate. + // Clamps ratio to min 1, because the logic frame rate is currently capped by the render frame rate. return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); } @@ -218,3 +235,8 @@ Real FramePacer::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) const { return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); } + +Real FramePacer::getLogicFramePhase() const +{ + return m_logicFramePhase; +} diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 9fab714f490..81a81a31940 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -31,6 +31,7 @@ #define DEFINE_PARTICLE_SYSTEM_NAMES +#include "Common/FramePacer.h" #include "Common/GameState.h" #include "Common/INI.h" #include "Common/PerfTimer.h" @@ -192,12 +193,41 @@ void ParticleInfo::xfer( Xfer *xfer ) } +// ------------------------------------------------------------------------------------------------ +/** Validate alpha key frames */ +// ------------------------------------------------------------------------------------------------ +template +static void validateAlphaKeyframes(KeyframeType *keys, Int keysSize) +{ + for (Int i = 1; i < keysSize; ++i) + { + UnsignedInt &prevFrame = keys[i - 1].frame; + UnsignedInt &currFrame = keys[i].frame; + + // The key frames behind the first one end the sequence with frame zero. + if (currFrame == 0) + break; + + if (currFrame <= prevFrame) + { + DEBUG_LOG(("validateAlphaKeyframes - Alpha%d is on frame %u and does not follow frame %u. It is moved to frame %u", + i + 1, currFrame, prevFrame, prevFrame + 1)); + + currFrame = prevFrame + 1; + } + } +} + // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ParticleInfo::loadPostProcess() { - + // TheSuperHackers @bugfix A key frame that does not come after its predecessor has no interval to + // ramp over. Its infinite rate would remain in the alpha, because the alpha reaches its key frame + // values without being set to them. Such a key frame is therefore moved to the frame after its + // predecessor. The color key frames are not validated, to keep their original look. + validateAlphaKeyframes(m_alphaKey, ARRAY_SIZE(m_alphaKey)); } /** Load post process */ @@ -231,9 +261,8 @@ void Particle::computeAlphaRate() return; } - Real delta = m_alphaKey[ m_alphaTargetKey ].value - m_alphaKey[ m_alphaTargetKey-1 ].value; UnsignedInt time = m_alphaKey[ m_alphaTargetKey ].frame - m_alphaKey[ m_alphaTargetKey-1 ].frame; - + Real delta = m_alphaKey[ m_alphaTargetKey ].value - m_alphaKey[ m_alphaTargetKey-1 ].value; m_alphaRate = delta/time; } @@ -282,7 +311,6 @@ Particle::Particle( ParticleSystem *system, const ParticleInfo *info ) #endif m_angleZ = info->m_angleZ; - m_lastPos.zero(); m_windRandomness = info->m_windRandomness; m_particleUpTowardsEmitter = info->m_particleUpTowardsEmitter; m_emitterPos = info->m_emitterPos; @@ -311,16 +339,38 @@ Particle::Particle( ParticleSystem *system, const ParticleInfo *info ) m_alphaKey[i] = info->m_alphaKey[i]; m_alpha = m_alphaKey[0].value; +#if PRESERVE_RETAIL_PARTICLES + // The original implementation ramps towards the second key frame from the creation frame, which reaches its value one + // frame early, or runs past it until its frame is reached if the first key frame starts later. m_alphaTargetKey = 1; computeAlphaRate(); +#else + // TheSuperHackers @tweak The first key frame holds its value until its frame has passed, and is therefore the first + // key frame to advance to. + m_alphaTargetKey = 0; + m_alphaRate = 0.0f; +#endif // set up colors for( i=0; im_colorKey[i]; m_color = m_colorKey[0].color; +#if PRESERVE_RETAIL_PARTICLES + // The original implementation ramps towards the second key frame from the creation frame and therefore runs past its + // color. Unlike the alpha, the color is not overwritten with the key frame value, so this offset remains for the rest + // of the life of the particle. This can cause visual glitches, such as greenish flames from Dragon Tanks + // and Inferno Cannons. m_colorTargetKey = 1; computeColorRate(); +#else + // TheSuperHackers @tweak The first key frame holds its color until its frame has passed, like the alpha does, so that + // every key frame interval ends exactly on its key frame. + m_colorTargetKey = 0; + m_colorRate.red = 0.0f; + m_colorRate.green = 0.0f; + m_colorRate.blue = 0.0f; +#endif m_colorScale = info->m_colorScale; @@ -373,6 +423,16 @@ void Particle::applyForce( const Coord3D *force ) // ------------------------------------------------------------------------------------------------ Bool Particle::update() { + // monitor lifetime + if (m_lifetimeLeft && --m_lifetimeLeft == 0) + return false; + + DEBUG_ASSERTCRASH( m_lifetimeLeft, ( "A particle has an infinite lifetime..." )); + + // TheSuperHackers @info The logic update changes the velocity and the rates, and the render update only + // integrates them, because a rate that stays constant over a logic frame splits into any number of render + // steps without changing the result. + // integrate acceleration into velocity m_vel.x += m_accel.x; m_vel.y += m_accel.y; @@ -382,84 +442,70 @@ Bool Particle::update() m_vel.y *= m_velDamping; m_vel.z *= m_velDamping; - // integrate velocity into position - const Coord3D *driftVel = m_system->getDriftVelocity(); - m_pos.x += m_vel.x + driftVel->x; - m_pos.y += m_vel.y + driftVel->y; - m_pos.z += m_vel.z + driftVel->z; + // reset the acceleration for accumulation next frame + m_accel.x = 0.0f; + m_accel.y = 0.0f; + m_accel.z = 0.0f; - // integrate the wind (if specified) into position - ParticleSystemInfo::WindMotion windMotion = m_system->getWindMotion(); + const UnsignedInt frameCount = getElapsedFrames(); - // see if we should even do anything - if( windMotion != ParticleSystemInfo::WIND_MOTION_NOT_USED ) - doWindMotion(); + // TheSuperHackers @info Skip the rest of the logic update on the creation frame. No render update has + // integrated this particle yet, so its rates must stay at their initial values for this frame, and testing + // it for invisibility would delete a particle that the later render update is about to fade in. + if (frameCount == 0) + { + return true; + } - // update orientation -#if PARTICLE_USE_XY_ROTATION - m_angleX += m_angularRateX; - m_angleY += m_angularRateY; -#endif - m_angleZ += m_angularRateZ; #if PARTICLE_USE_XY_ROTATION m_angularRateX *= m_angularDamping; m_angularRateY *= m_angularDamping; #endif m_angularRateZ *= m_angularDamping; - if (m_particleUpTowardsEmitter) { - // adjust the up position back towards the particle - static const Coord2D upVec = { 0.0f, 1.0f }; - Coord2D emitterDir; - emitterDir.x = m_pos.x - m_emitterPos.x; - emitterDir.y = m_pos.y - m_emitterPos.y; - m_angleZ = (angleBetween(&upVec, &emitterDir) + PI); - } - - // update size - m_size += m_sizeRate; m_sizeRate *= m_sizeRateDamping; + // TheSuperHackers @info A key frame is passed in the logic frame after its frame, because the render update after + // this logic update applies the next rate. This ends every key frame interval exactly on its key frame, so the alpha + // reaches its key frame values without being set to them. Only the key frames behind the first one end the sequence + // with frame zero. + // // Update alpha (if used) // - if (m_system->getShaderType() != ParticleSystemInfo::ADDITIVE) { - m_alpha += m_alphaRate; +#if PRESERVE_RETAIL_PARTICLES + // The first key frame interval starts on the creation frame and therefore arrives at its key frame early. + // On the key frame, the alpha is set to the key frame value and held for that frame. + if (m_alphaTargetKey == 1 && frameCount == m_alphaKey[ 1 ].frame) + { + m_alpha = m_alphaKey[ 1 ].value; + m_alphaRate = 0.0f; + } +#endif - if (m_alphaTargetKey < MAX_KEYFRAMES && m_alphaKey[ m_alphaTargetKey ].frame) + if ((m_alphaTargetKey < MAX_KEYFRAMES && m_alphaKey[ m_alphaTargetKey ].frame != 0) || m_alphaTargetKey == 0) { - if (TheGameClient->getFrame() - m_createTimestamp >= m_alphaKey[ m_alphaTargetKey ].frame) + if (frameCount > m_alphaKey[ m_alphaTargetKey ].frame) { - m_alpha = m_alphaKey[ m_alphaTargetKey ].value; m_alphaTargetKey++; computeAlphaRate(); } } else + { m_alphaRate = 0.0f; - - if (m_alpha < 0.0f) - m_alpha = 0.0f; - else if (m_alpha > 1.0f) - m_alpha = 1.0f; + } } - // // Update color // - m_color.red += m_colorRate.red; - m_color.green += m_colorRate.green; - m_color.blue += m_colorRate.blue; - - if (m_colorTargetKey < MAX_KEYFRAMES && m_colorKey[ m_colorTargetKey ].frame) + if ((m_colorTargetKey < MAX_KEYFRAMES && m_colorKey[ m_colorTargetKey ].frame != 0) || m_colorTargetKey == 0) { - if (TheGameClient->getFrame() - m_createTimestamp >= m_colorKey[ m_colorTargetKey ].frame) + if (frameCount > m_colorKey[ m_colorTargetKey ].frame) { - // can't set, because of colorscale - // m_color = m_colorKey[ m_colorTargetKey ].color; m_colorTargetKey++; computeColorRate(); } @@ -471,48 +517,75 @@ Bool Particle::update() m_colorRate.blue = 0.0f; } - /// @todo Rethink this - at least its name - m_color.red += m_colorScale; - m_color.green += m_colorScale; - m_color.blue += m_colorScale; + // if we've gone totally invisible, destroy ourselves + if (isInvisible()) + return false; - if (m_color.red < 0.0f) - m_color.red = 0.0f; - else if (m_color.red > 1.0f) - m_color.red = 1.0f; + return true; +} - if (m_color.red < 0.0f) - m_color.green = 0.0f; - else if (m_color.green > 1.0f) - m_color.green = 1.0f; +// ------------------------------------------------------------------------------------------------ +void Particle::draw(Real timeScale) +{ + // integrate velocity into position + const Coord3D *driftVel = m_system->getDriftVelocity(); + m_pos.x += (m_vel.x + driftVel->x) * timeScale; + m_pos.y += (m_vel.y + driftVel->y) * timeScale; + m_pos.z += (m_vel.z + driftVel->z) * timeScale; - if (m_color.blue < 0.0f) - m_color.blue = 0.0f; - else if (m_color.blue > 1.0f) - m_color.blue = 1.0f; + // integrate the wind (if specified) into position + ParticleSystemInfo::WindMotion windMotion = m_system->getWindMotion(); + // see if we should even do anything + if( windMotion != ParticleSystemInfo::WIND_MOTION_NOT_USED ) + doWindMotion(timeScale); - // reset the acceleration for accumulation next frame - m_accel.x = 0.0f; - m_accel.y = 0.0f; - m_accel.z = 0.0f; + // update orientation +#if PARTICLE_USE_XY_ROTATION + m_angleX += m_angularRateX * timeScale; + m_angleY += m_angularRateY * timeScale; +#endif + m_angleZ += m_angularRateZ * timeScale; - // monitor lifetime - if (m_lifetimeLeft && --m_lifetimeLeft == 0) - return false; + if (m_particleUpTowardsEmitter) + { + // adjust the up position back towards the particle + static const Coord2D upVec = { 0.0f, 1.0f }; + Coord2D emitterDir; + emitterDir.x = m_pos.x - m_emitterPos.x; + emitterDir.y = m_pos.y - m_emitterPos.y; + m_angleZ = (angleBetween(&upVec, &emitterDir) + PI); + } - DEBUG_ASSERTCRASH( m_lifetimeLeft, ( "A particle has an infinite lifetime..." )); + // update size + m_size += m_sizeRate * timeScale; - // if we've gone totally invisible, destroy ourselves - if (isInvisible()) - return false; - return true; + // + // Update alpha (if used) + // + if (m_system->getShaderType() != ParticleSystemInfo::ADDITIVE) + { + m_alpha += m_alphaRate * timeScale; + m_alpha = clamp(0.0f, m_alpha, 1.0f); + } + + // + // Update color + // + m_color += m_colorRate * timeScale; + + /// @todo Rethink this - at least its name + m_color += m_colorScale * timeScale; + + m_color.red = clamp(0.0f, m_color.red, 1.0f); + m_color.green = clamp(0.0f, m_color.green, 1.0f); + m_color.blue = clamp(0.0f, m_color.blue, 1.0f); } // ------------------------------------------------------------------------------------------------ /** Do wind motion as specified by the particle system template, if present */ // ------------------------------------------------------------------------------------------------ -void Particle::doWindMotion() +void Particle::doWindMotion(Real timeScale) { // get the angle of the wind @@ -576,7 +649,7 @@ void Particle::doWindMotion() Real distFromWind = v.length(); if( distFromWind < noForceDistance ) { - Real windForceStrength = 2.0f * m_windRandomness; + Real windForceStrength = 2.0f * m_windRandomness * timeScale; // only apply force if still within the circle of influence if( distFromWind > fullForceDistance ) @@ -599,6 +672,12 @@ ParticlePriorityType Particle::getPriority() return m_system->getPriority(); } +// ------------------------------------------------------------------------------------------------ +UnsignedInt Particle::getElapsedFrames() const +{ + return TheGameClient->getFrame() - m_createTimestamp; +} + // ------------------------------------------------------------------------------------------------ /** Return true if this particle is invisible */ // ------------------------------------------------------------------------------------------------ @@ -607,20 +686,23 @@ Bool Particle::isInvisible() switch (m_system->getShaderType()) { case ParticleSystemInfo::ADDITIVE: - // if color is black, this particle is invisible - // check that we're not in the process of going to another color - if (m_colorKey[ m_colorTargetKey ].frame == 0) + if (m_colorTargetKey >= MAX_KEYFRAMES || m_colorKey[ m_colorTargetKey ].frame == 0) { + // if color is black, this particle is invisible if (m_color.red < 0.01f && m_color.green < 0.01f && m_color.blue < 0.01f) return true; } return false; case ParticleSystemInfo::ALPHA: - // if alpha is zero, this particle is invisible - if (m_alpha < 0.01f) - return true; + // TheSuperHackers @fix Check that we're not in the process of going to another alpha. + if (m_alphaTargetKey >= MAX_KEYFRAMES || m_alphaKey[ m_alphaTargetKey ].frame == 0) + { + // if alpha is zero, this particle is invisible + if (m_alpha < 0.01f) + return true; + } return false; case ParticleSystemInfo::ALPHA_TEST: @@ -628,11 +710,10 @@ Bool Particle::isInvisible() return false; case ParticleSystemInfo::MULTIPLY: - // if color is white, this particle is invisible - // check that we're not in the process of going to another color - if (m_colorKey[ m_colorTargetKey ].frame == 0) + if (m_colorTargetKey >= MAX_KEYFRAMES || m_colorKey[ m_colorTargetKey ].frame == 0) { + // if color is white, this particle is invisible if (m_color.red > 0.99f && m_color.green > 0.99f && m_color.blue > 0.99f) return true; } @@ -654,13 +735,19 @@ void Particle::crc( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: - * 1: Initial version */ + * 1: Initial version + * 2: TheSuperHackers @tweak Removed unused m_lastPos + */ // ------------------------------------------------------------------------------------------------ void Particle::xfer( Xfer *xfer ) { // version +#if RETAIL_COMPATIBLE_XFER_SAVE XferVersion currentVersion = 1; +#else + XferVersion currentVersion = 2; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -674,7 +761,11 @@ void Particle::xfer( Xfer *xfer ) xfer->xferCoord3D( &m_accel ); // last position - xfer->xferCoord3D( &m_lastPos ); + if (version <= 1) + { + Coord3D m_lastPos = {0}; + xfer->xferCoord3D( &m_lastPos ); + } // lifetime left xfer->xferUnsignedInt( &m_lifetimeLeft ); @@ -1070,7 +1161,34 @@ void ParticleSystemInfo::xfer( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ void ParticleSystemInfo::loadPostProcess() { + validate(); +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystemInfo::validate() +{ + // TheSuperHackers @info Initialize all volume particles that lack ini configuration to the optimum depth of 6 + // In retail, volume particle depth was not configurable through ini and was hard coded to a particle depth of 6 + if (m_particleType == ParticleSystemInfo::VOLUME_PARTICLE) + { + if (m_volumeParticleDepth == INVALID_VOLUME_PARTICLE_DEPTH) + m_volumeParticleDepth = OPTIMUM_VOLUME_PARTICLE_DEPTH; + } + else + { + m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; + } + +#if PRESERVE_RETAIL_PARTICLES + // TheSuperHackers @info Hack to allow isUsingSmudge() functionality with retail smudge particles + // The retail data template for smudge particles is not correctly configured with the smudge particle type + if (m_particleType != ParticleSystemInfo::SMUDGE && m_particleTypeName.startsWithNoCase("SMUDGE.")) + { + m_particleType = ParticleSystemInfo::SMUDGE; + } +#endif + validateAlphaKeyframes(m_alphaKey, ARRAY_SIZE(m_alphaKey)); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1160,7 +1278,7 @@ ParticleSystem::ParticleSystem( const ParticleSystemTemplate *sysTemplate, else m_isForever = true; - m_accumulatedSizeBonus = 0; + m_accumulatedSizeBonus = 0.0f; m_velDamping = sysTemplate->m_velDamping; @@ -1412,9 +1530,15 @@ void ParticleSystem::rotateLocalTransformZ( Real z ) void ParticleSystem::attachToDrawable( const Drawable *draw ) { if (draw) + { + DEBUG_ASSERTCRASH( m_attachedToObjectID == INVALID_ID, ( "ParticleSystem is already attached to an Object" ) ); m_attachedToDrawableID = draw->getID(); + m_attachedToObjectID = INVALID_ID; + } else + { m_attachedToDrawableID = INVALID_DRAWABLE_ID; + } } // ------------------------------------------------------------------------------------------------ @@ -1423,9 +1547,15 @@ void ParticleSystem::attachToDrawable( const Drawable *draw ) void ParticleSystem::attachToObject( const Object *obj ) { if (obj) + { + DEBUG_ASSERTCRASH( m_attachedToDrawableID == INVALID_ID, ( "ParticleSystem is already attached to a Drawable" ) ); m_attachedToObjectID = obj->getID(); + m_attachedToDrawableID = INVALID_DRAWABLE_ID; + } else + { m_attachedToObjectID = INVALID_ID; + } } // ------------------------------------------------------------------------------------------------ @@ -1758,7 +1888,7 @@ Particle *ParticleSystem::createParticle( const ParticleInfo *info, // // Check if particle is below priorities we allow for this FPS or if it being skipped because - // all particesl are being skipped (excluding special fps independent particles at + // all particles are being skipped (excluding special fps independent particles at // getMinDynamicParticleSkipPriority()) // if( priority < TheGameLODManager->getMinDynamicParticlePriority() || @@ -1815,7 +1945,6 @@ const ParticleInfo *ParticleSystem::generateParticleInfo( Int particleNum, Int p // transform particle position to world coordinates Vector3 p, pr; - Coord3D emissionAdjustment; // this is the adjustment for inter-frame emission // @todo : This should work, if m_lastPos = m_pos is removed from here but it doesn't. // @todo : Investigate why. jkmcd if (m_isFirstPos) { @@ -1823,9 +1952,13 @@ const ParticleInfo *ParticleSystem::generateParticleInfo( Int particleNum, Int p m_isFirstPos = false; } - emissionAdjustment.x = (1 - (INT_TO_REAL(particleNum) / particleCount)) * (m_pos.x - m_lastPos.x); - emissionAdjustment.y = (1 - (INT_TO_REAL(particleNum) / particleCount)) * (m_pos.y - m_lastPos.y); - emissionAdjustment.z = (1 - (INT_TO_REAL(particleNum) / particleCount)) * (m_pos.z - m_lastPos.z); + const Coord3D frameDeltaPos = m_pos - m_lastPos; + const Real frameStep = 1 - (INT_TO_REAL(particleNum) / particleCount); + + Coord3D emissionAdjustment; // this is the adjustment for inter-frame emission + emissionAdjustment.x = frameStep * frameDeltaPos.x; + emissionAdjustment.y = frameStep * frameDeltaPos.y; + emissionAdjustment.z = frameStep * frameDeltaPos.z; p.X = info.m_pos.x; p.Y = info.m_pos.y; @@ -1936,114 +2069,15 @@ Bool ParticleSystem::update( Int localPlayerIndex ) if (m_windMotion != ParticleSystemInfo::WIND_MOTION_NOT_USED ) updateWindMotion(); - // if this system is attached to a Drawable/Object, update the current transform - // matrix so generated particles' are relative to the parent Drawable's - // position and orientation - Bool transformSet = false; - const Matrix3D *parentXfrm = nullptr; - Bool isShrouded = false; - - if (m_attachedToDrawableID) - { - Drawable *attachedTo = TheGameClient->findDrawableByID( m_attachedToDrawableID ); - - if (attachedTo) - { - if (attachedTo->getFullyObscuredByShroud()) - isShrouded = true; - - parentXfrm = attachedTo->getTransformMatrix(); - m_lastPos = m_pos; - m_pos = *attachedTo->getPosition(); - } - else - { - // Drawable has been destroyed - lose our attachment to it - m_attachedToDrawableID = INVALID_DRAWABLE_ID; - - // destroy ourselves - destroy(); - } - } - else if (m_attachedToObjectID) - { - Object *objectAttachedTo = TheGameLogic->findObjectByID( m_attachedToObjectID ); - - if (objectAttachedTo) - { - if (!isShrouded) - isShrouded = (objectAttachedTo->getShroudedStatus(localPlayerIndex) >= OBJECTSHROUD_FOGGED); - - const Drawable * draw = objectAttachedTo->getDrawable(); - if ( draw ) - parentXfrm = draw->getTransformMatrix(); - else - parentXfrm = objectAttachedTo->getTransformMatrix(); - - m_lastPos = m_pos; - m_pos = *objectAttachedTo->getPosition(); - } - else - { - // Drawable has been destroyed - lose our attachment to it - m_attachedToObjectID = INVALID_ID; - - // destroy ourselves - destroy(); - } - } - - if (parentXfrm) - { - if (m_skipParentXfrm) - { - //this particle system is already in world space so no need to apply parent xform. - m_transform = m_localTransform; - } - else - { - // if system has its own local transform, concatenate them - if (m_isLocalIdentity == false) - #ifdef ALLOW_TEMPORARIES - m_transform = (*parentXfrm) * m_localTransform; - #else - m_transform.mul(*parentXfrm, m_localTransform); - #endif - else - m_transform = *parentXfrm; - } - - m_isIdentity = false; - transformSet = true; - } - - - if (transformSet == false) - { - if (m_isLocalIdentity == false) - { - m_transform = m_localTransform; - m_isIdentity = false; - } - else - { - m_isIdentity = true; - } - } - - // if we are controlled by a particle, its position is local origin - if (m_controlParticle) - { - const Coord3D *controlPos = m_controlParticle->getPosition(); - /// @todo Concatenate this, instead of overriding (MSB) - m_transform.Set_X_Translation( controlPos->x ); - m_transform.Set_Y_Translation( controlPos->y ); - m_transform.Set_Z_Translation( controlPos->z ); - m_isIdentity = false; - m_lastPos = m_pos; - m_pos = *controlPos; - } + // + // Update shrouding and drawable/object lifetime for the particle system + // + VisibilityState visibilityState = updateVisibility(localPlayerIndex); + // + // Update position and rotation of the particle system + // + updateTransform(); // // Generate new particles if the system hasn't been 'stopped' or 'destroyed' @@ -2051,9 +2085,9 @@ Bool ParticleSystem::update( Int localPlayerIndex ) // if (m_isDestroyed == false) { - if (m_isForever || (m_isForever == false && m_systemLifetimeLeft > 0)) + if (m_isForever || m_systemLifetimeLeft > 0) { - if (!isShrouded && m_isStopped == false && m_masterSystem == nullptr) + if (!visibilityState.isShrouded && m_isStopped == false && m_masterSystem == nullptr) { if (m_burstDelayLeft == 0) { @@ -2176,6 +2210,164 @@ Bool ParticleSystem::update( Int localPlayerIndex ) return true; } +// ------------------------------------------------------------------------------------------------ +void ParticleSystem::updateTransform() +{ + // if this system is attached to a Drawable/Object, update the current transform + // matrix so generated particles' are relative to the parent Drawable's + // position and orientation, otherwise use the local transform. + if (m_attachedToDrawableID) + { + if (Drawable *attachedTo = TheGameClient->findDrawableByID( m_attachedToDrawableID )) + { + applyParentTransform( *attachedTo->getTransformMatrix() ); + + m_lastPos = m_pos; + m_pos = *attachedTo->getPosition(); + } + else + { + applyLocalTransform(); + } + } + else if (m_attachedToObjectID) + { + if (Object *objectAttachedTo = TheGameLogic->findObjectByID( m_attachedToObjectID )) + { + if (const Drawable * draw = objectAttachedTo->getDrawable()) + { + applyParentTransform( *draw->getTransformMatrix() ); + } + else + { + applyParentTransform( *objectAttachedTo->getTransformMatrix() ); + } + + m_lastPos = m_pos; + m_pos = *objectAttachedTo->getPosition(); + } + else + { + applyLocalTransform(); + } + } + else + { + applyLocalTransform(); + } + + // if we are controlled by a particle, its position is local origin + if (m_controlParticle) + { + const Coord3D *controlPos = m_controlParticle->getPosition(); + /// @todo Concatenate this, instead of overriding (MSB) + m_transform.Set_X_Translation( controlPos->x ); + m_transform.Set_Y_Translation( controlPos->y ); + m_transform.Set_Z_Translation( controlPos->z ); + m_isIdentity = false; + m_lastPos = m_pos; + m_pos = *controlPos; + } +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystem::applyParentTransform(const Matrix3D &parentXfrm) +{ + if (m_skipParentXfrm) + { + //this particle system is already in world space so no need to apply parent xform. + applyLocalTransform(); + } + else + { + // if system has its own local transform, concatenate them + if (!m_isLocalIdentity) + { +#ifdef ALLOW_TEMPORARIES + m_transform = parentXfrm * m_localTransform; +#else + m_transform.mul(parentXfrm, m_localTransform); +#endif + } + else + { + m_transform = parentXfrm; + } + + m_isIdentity = false; + } +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystem::applyLocalTransform() +{ + if (!m_isLocalIdentity) + { + m_transform = m_localTransform; + m_isIdentity = false; + } + else + { + m_isIdentity = true; + } +} + +// ------------------------------------------------------------------------------------------------ +ParticleSystem::VisibilityState ParticleSystem::updateVisibility( Int localPlayerIndex ) +{ + VisibilityState visibilityState; + + if (m_attachedToDrawableID) + { + if (Drawable *attachedTo = TheGameClient->findDrawableByID( m_attachedToDrawableID )) + { + visibilityState.isShrouded = attachedTo->getFullyObscuredByShroud(); + } + else + { + // Drawable has been destroyed - lose our attachment to it + m_attachedToDrawableID = INVALID_DRAWABLE_ID; + + // destroy ourselves + destroy(); + } + } + else if (m_attachedToObjectID) + { + if (Object *objectAttachedTo = TheGameLogic->findObjectByID( m_attachedToObjectID )) + { + visibilityState.isShrouded = objectAttachedTo->getShroudedStatus(localPlayerIndex) >= OBJECTSHROUD_FOGGED; + } + else + { + // Drawable has been destroyed - lose our attachment to it + m_attachedToObjectID = INVALID_ID; + + // destroy ourselves + destroy(); + } + } + + return visibilityState; +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystem::draw(Real timeScale) +{ + if (TheGlobalData->m_useFX == FALSE) + return; + + if (m_delayLeft != 0) + return; + + Particle *p = m_systemParticlesHead; + while (p) + { + p->draw(timeScale); + p = p->m_systemNext; + } +} + // ------------------------------------------------------------------------------------------------ /** Update the wind motion */ // ------------------------------------------------------------------------------------------------ @@ -2675,6 +2867,12 @@ void ParticleSystem::loadPostProcess() } + if( m_attachedToDrawableID != INVALID_DRAWABLE_ID && m_attachedToObjectID != INVALID_ID ) + { + DEBUG_CRASH(( "ParticleSystem::loadPostProcess - m_attachedToDrawableID and m_attachedToObjectID are both erroneously set" )); + throw SC_INVALID_DATA; + } + } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2891,26 +3089,7 @@ ParticleSystemTemplate::~ParticleSystemTemplate() // ------------------------------------------------------------------------------------------------ void ParticleSystemTemplate::validate() { - // TheSuperHackers @info Initialise all volume particles that lack ini configuration to the optimum depth of 6 - // In retail, volume particle depth was not configurable through ini and was hard coded to a particle depth of 6 - if (m_particleType == ParticleSystemInfo::VOLUME_PARTICLE) - { - if (m_volumeParticleDepth == INVALID_VOLUME_PARTICLE_DEPTH) - m_volumeParticleDepth = OPTIMUM_VOLUME_PARTICLE_DEPTH; - } - else - { - m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; - } - -#if PRESERVE_RETAIL_PARTICLES - // TheSuperHackers @info Hack to allow isUsingSmudge() functionality with retail smudge particles - // The retail data template for smudge particles is not correctly configured with the smudge particle type - if (m_particleType != ParticleSystemInfo::SMUDGE && m_particleTypeName.startsWithNoCase("SMUDGE.")) - { - m_particleType = ParticleSystemInfo::SMUDGE; - } -#endif + ParticleSystemInfo::validate(); } // ------------------------------------------------------------------------------------------------ @@ -2941,8 +3120,8 @@ ParticleSystemManager::ParticleSystemManager() m_onScreenParticleCount = 0; m_localPlayerIndex = 0; + m_drawnLogicFramePhase = 1.0f; - m_lastLogicFrameUpdate = 0; m_particleCount = 0; m_fieldParticleCount = 0; m_particleSystemCount = 0; @@ -3028,7 +3207,8 @@ void ParticleSystemManager::reset() m_uniqueSystemID = INVALID_PARTICLE_SYSTEM_ID; - m_lastLogicFrameUpdate = -1; + m_drawnLogicFramePhase = 1.0f; + // leave templates as-is } @@ -3038,18 +3218,15 @@ void ParticleSystemManager::reset() //DECLARE_PERF_TIMER(ParticleSystemManager) void ParticleSystemManager::update() { - if (m_lastLogicFrameUpdate == TheGameLogic->getFrame()) { - return; - } + //USE_PERF_TIMER(ParticleSystemManager) - // update the last logic frame. - m_lastLogicFrameUpdate = TheGameLogic->getFrame(); + // TheSuperHackers @tweak Complete the render update of the previous logic frame before the logic update + // changes the rates, so that every logic frame integrates exactly one logic time step. + completeLogicFrameDrawUpdate(); - //USE_PERF_TIMER(ParticleSystemManager) ParticleSystemListIt it = m_allParticleSystemList.begin(); while( it != m_allParticleSystemList.end() ) { - // TheSuperHackers @info Must increment the list iterator before potential element erasure from the list. ParticleSystem* sys = *it++; DEBUG_ASSERTCRASH(sys != nullptr, ("ParticleSystemManager::update: ParticleSystem is null")); @@ -3097,6 +3274,46 @@ void ParticleSystemManager::update() } } +// ------------------------------------------------------------------------------------------------ +void ParticleSystemManager::draw() +{ + // TheSuperHackers @tweak Integrate the part of the logic frame that the render time + // has advanced into since the last render update. + const Real logicFramePhase = TheFramePacer->getLogicFramePhase(); + if (logicFramePhase > m_drawnLogicFramePhase) + { + const Real timeScale = logicFramePhase - m_drawnLogicFramePhase; + drawSystems(timeScale); + m_drawnLogicFramePhase = logicFramePhase; + } +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystemManager::completeLogicFrameDrawUpdate() +{ + const Real timeScale = 1.0f - m_drawnLogicFramePhase; + if (timeScale > 0.0f) + { + drawSystems(timeScale); + } + m_drawnLogicFramePhase = 0.0f; +} + +// ------------------------------------------------------------------------------------------------ +void ParticleSystemManager::drawSystems(Real timeScale) +{ + DEBUG_ASSERTCRASH(timeScale > 0.0f, ("ParticleSystemManager::drawSystems: timeScale %f is not greater than zero", timeScale)); + + ParticleSystemListIt it = m_allParticleSystemList.begin(); + while( it != m_allParticleSystemList.end() ) + { + ParticleSystem* sys = *it++; + DEBUG_ASSERTCRASH(sys != nullptr, ("ParticleSystemManager::drawSystems: ParticleSystem is null")); + + sys->draw(timeScale); + } +} + // ------------------------------------------------------------------------------------------------ /** sets the count of the particles on screen after each frame */ // ------------------------------------------------------------------------------------------------ diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index faef27ae3d5..94ace64bd78 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -1042,6 +1042,126 @@ struct RGBColor blue = ((c >> 0) & 0xff) / 255.0f; } + RGBColor& operator+=(const RGBColor& c) + { + red += c.red; + green += c.green; + blue += c.blue; + return *this; + } + + RGBColor& operator-=(const RGBColor& c) + { + red -= c.red; + green -= c.green; + blue -= c.blue; + return *this; + } + + RGBColor& operator*=(const RGBColor& c) + { + red *= c.red; + green *= c.green; + blue *= c.blue; + return *this; + } + + RGBColor& operator/=(const RGBColor& c) + { + red /= c.red; + green /= c.green; + blue /= c.blue; + return *this; + } + + RGBColor operator+(const RGBColor& c) const + { + RGBColor res = *this; + res += c; + return res; + } + + RGBColor operator-(const RGBColor& c) const + { + RGBColor res = *this; + res -= c; + return res; + } + + RGBColor operator*(const RGBColor& c) const + { + RGBColor res = *this; + res *= c; + return res; + } + + RGBColor operator/(const RGBColor& c) const + { + RGBColor res = *this; + res /= c; + return res; + } + + RGBColor& operator+=(Real s) + { + red += s; + green += s; + blue += s; + return *this; + } + + RGBColor& operator-=(Real s) + { + red -= s; + green -= s; + blue -= s; + return *this; + } + + RGBColor& operator*=(Real s) + { + red *= s; + green *= s; + blue *= s; + return *this; + } + + RGBColor& operator/=(Real s) + { + red /= s; + green /= s; + blue /= s; + return *this; + } + + RGBColor operator+(Real s) const + { + RGBColor res = *this; + res += s; + return res; + } + + RGBColor operator-(Real s) const + { + RGBColor res = *this; + res -= s; + return res; + } + + RGBColor operator*(Real s) const + { + RGBColor res = *this; + res *= s; + return res; + } + + RGBColor operator/(Real s) const + { + RGBColor res = *this; + res /= s; + return res; + } + }; struct RGBAColorReal diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 7fc03e4e0ea..8e358f5bcfd 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -665,11 +665,11 @@ void GameClient::update() #endif // update all particle systems - if( !freezeTime ) + if( !freezeTime && TheGameLogic->hasUpdated() ) { // update particle systems TheParticleSystemManager->setLocalPlayerIndex(localPlayerIndex); - TheParticleSystemManager->update(); + TheParticleSystemManager->UPDATE(); } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index 90718063d5c..d39b474b02b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -441,15 +441,14 @@ void BoneFXUpdate::doParticleSystemAtBone(const ParticleSystemTemplate *particle if( lastDamageInfo && getDamageTypeFlag( d->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) == FALSE ) return; - Object *building = getObject(); - ParticleSystem *psys = TheParticleSystemManager->createParticleSystem(particleSystemTemplate); if (psys != nullptr) { + Object *object = getObject(); m_particleSystemIDs.push_back(psys->getSystemID()); psys->setPosition(bonePosition); - psys->attachToObject(building); - Drawable *drawable = building->getDrawable(); + psys->attachToObject(object); + Drawable *drawable = object->getDrawable(); if (drawable && drawable->isDrawableEffectivelyHidden()) { psys->stop(); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 03d3bfe5bff..b90905f11be 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1866,6 +1866,8 @@ void W3DDisplay::draw() // if(TheGlobalData->m_loadScreenRender != TRUE) updateViews(); + TheParticleSystemManager->DRAW(); + if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index e3d83bb7323..354e9bd8b60 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -702,13 +702,7 @@ void GameClient::update() #endif // update all particle systems - if( !freezeTime ) - { - // update particle systems - TheParticleSystemManager->setLocalPlayerIndex(localPlayerIndex); -// TheParticleSystemManager->update(); - - } + // LORENZEN AND WILCZYNSKI MOVED THE PARTICLE UPDATE TO W3DDisplay // update the terrain visuals { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index 0d18b06592f..fa2334b0b44 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -441,15 +441,14 @@ void BoneFXUpdate::doParticleSystemAtBone(const ParticleSystemTemplate *particle if( lastDamageInfo && getDamageTypeFlag( d->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) == FALSE ) return; - Object *building = getObject(); - ParticleSystem *psys = TheParticleSystemManager->createParticleSystem(particleSystemTemplate); if (psys != nullptr) { + Object *object = getObject(); m_particleSystemIDs.push_back(psys->getSystemID()); psys->setPosition(bonePosition); - psys->attachToObject(building); - Drawable *drawable = building->getDrawable(); + psys->attachToObject(object); + Drawable *drawable = object->getDrawable(); if (drawable && drawable->isDrawableEffectivelyHidden()) { psys->stop(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 155e8ac43ec..122996911f4 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -51,6 +51,7 @@ static void drawFramerateBar(); #include "Common/PlayerList.h" #include "Common/ThingTemplate.h" #include "Common/GameLOD.h" +#include "Common/GameUtility.h" #include "Common/DrawModule.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/Module/PhysicsUpdate.h" @@ -1936,6 +1937,8 @@ void W3DDisplay::draw() // limit the framerate, because while fast time is on, the game logic is running as fast as it can. } + Bool updateParticles = TheGameLogic->hasUpdated(); + do { // update all views of the world - recomputes data which will affect drawing @@ -1944,15 +1947,23 @@ void W3DDisplay::draw() //trying to refresh the visible terrain geometry. // if(TheGlobalData->m_loadScreenRender != TRUE) updateViews(); - TheParticleSystemManager->update();//LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE - //FOR THE PURPOSE OF LETTING THE PARTICLE SYSTEM LOOK UP THE RENDER OBJECT"S - //TRANSFORM MATRIX, WHILE IT IS STILL VALID (HAVING DONE ITS CLIENT TRANSFORMS - //BUT NOT YET RESETTING TOT HE LOGICAL TRANSFORM) - //THE RESULT IS THAT PARTICLESYSTEMS LINKED TO BONES IN DRAWABLES.OBJECTS - //MOVE WITH THE CLIENT TRANSFORMS, NOW. - //REVOLUTIONARY! - //-LORENZEN + //LORENZEN AND WILCZYNSKI MOVED THIS FROM ITS NATIVE POSITION, ABOVE + //FOR THE PURPOSE OF LETTING THE PARTICLE SYSTEM LOOK UP THE RENDER OBJECT"S + //TRANSFORM MATRIX, WHILE IT IS STILL VALID (HAVING DONE ITS CLIENT TRANSFORMS + //BUT NOT YET RESETTING TOT HE LOGICAL TRANSFORM) + //THE RESULT IS THAT PARTICLESYSTEMS LINKED TO BONES IN DRAWABLES.OBJECTS + //MOVE WITH THE CLIENT TRANSFORMS, NOW. + //REVOLUTIONARY! + //-LORENZEN + if (updateParticles) + { + const Int localPlayerIndex = rts::getObservedOrLocalPlayer()->getPlayerIndex(); + TheParticleSystemManager->setLocalPlayerIndex(localPlayerIndex); + TheParticleSystemManager->UPDATE(); + updateParticles = false; + } + TheParticleSystemManager->DRAW(); if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture