/* ** 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.cpp //////////////////////////////////////////////////////////////////////////////// // Particle System implementation // Author: Michael S. Booth, November 2001 /////////////////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_PARTICLE_SYSTEM_NAMES #include "Common/GameState.h" #include "Common/INI.h" #include "Common/PerfTimer.h" #include "Common/ThingFactory.h" #include "Common/GameLOD.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/DebugDisplay.h" #include "GameClient/Display.h" #include "GameClient/GameClient.h" #include "GameClient/InGameUI.h" #include "GameClient/ParticleSys.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/TerrainLogic.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------ Performance Timers //#include "Common/PerfMetrics.h" //#include "Common/PerfTimer.h" //static PerfTimer s_particleSys("ParticleSys::update", false, PERFMETRICS_LOGIC_STARTFRAME, PERFMETRICS_LOGIC_STOPFRAME); //------------------------------------------------------------------------------------------------- // the singleton ParticleSystemManager *TheParticleSystemManager = NULL; /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleInfo::ParticleInfo( void ) { //Added By Sadullah Nader //Initializations inserted m_angleZ = 0.0f; m_angularDamping = 0.0f; m_angularRateZ = 0.0f; m_colorScale =0.0f; m_size = 0.0f; m_sizeRate = 0.0f; m_sizeRateDamping = 0.0f; m_velDamping = 0.0f; m_windRandomness = 0.0f; m_emitterPos.zero(); m_pos.zero(); m_vel.zero(); m_lifetime = 0; m_particleUpTowardsEmitter = FALSE; // } // end ParticleInfo // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ParticleInfo::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void ParticleInfo::xfer( Xfer *xfer ) { Int i; // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // velocity xfer->xferCoord3D( &m_vel ); // position xfer->xferCoord3D( &m_pos ); // emitter position xfer->xferCoord3D( &m_emitterPos ); // velocity damping xfer->xferReal( &m_velDamping ); // angle Real tempAngle=0; //temporary value to save out for backwards compatibility when we supported x,y xfer->xferReal( &tempAngle ); xfer->xferReal( &tempAngle ); xfer->xferReal( &m_angleZ ); // angular rate xfer->xferReal( &tempAngle ); xfer->xferReal( &tempAngle ); xfer->xferReal( &m_angularRateZ ); // lifetime xfer->xferUnsignedInt( &m_lifetime ); // size xfer->xferReal( &m_size ); // size rate xfer->xferReal( &m_sizeRate ); // size rate damping xfer->xferReal( &m_sizeRateDamping ); // alpha keys for( i = 0; i < MAX_KEYFRAMES; ++i ) { xfer->xferReal( &m_alphaKey[ i ].value ); xfer->xferUnsignedInt( &m_alphaKey[ i ].frame ); } // end for, i // color keys for( i = 0; i < MAX_KEYFRAMES; ++i ) { xfer->xferRGBColor( &m_colorKey[ i ].color ); xfer->xferUnsignedInt( &m_colorKey[ i ].frame ); } // end for, i // color scale xfer->xferReal( &m_colorScale ); // particle up towards emitter xfer->xferBool( &m_particleUpTowardsEmitter ); // wind randomness xfer->xferReal( &m_windRandomness ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ParticleInfo::loadPostProcess( void ) { } // end loadPostProcess /** Load post process */ // ------------------------------------------------------------------------------------------------ /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ enum { MAX_SIZE_BONUS = 50 }; //todo move this somewhere more useful. static Real angleBetween(const Coord2D *vecA, const Coord2D *vecB); /////////////////////////////////////////////////////////////////////////////////////////////////// // Particle /////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ /** Compute alpha rate to get to next key on given frame */ // ------------------------------------------------------------------------------------------------ void Particle::computeAlphaRate( void ) { if (m_alphaKey[ m_alphaTargetKey ].frame == 0) { m_alphaRate = 0.0f; 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; m_alphaRate = delta/time; } // ------------------------------------------------------------------------------------------------ /** Compute color rate to get to next key on given frame */ // ------------------------------------------------------------------------------------------------ void Particle::computeColorRate( void ) { if (m_colorKey[ m_colorTargetKey ].frame == 0) { m_colorRate.red = 0.0f; m_colorRate.green = 0.0f; m_colorRate.blue = 0.0f; return; } UnsignedInt time = m_colorKey[ m_colorTargetKey ].frame - m_colorKey[ m_colorTargetKey-1 ].frame; Real delta = m_colorKey[ m_colorTargetKey ].color.red - m_colorKey[ m_colorTargetKey-1 ].color.red; m_colorRate.red = delta/time; delta = m_colorKey[ m_colorTargetKey ].color.green - m_colorKey[ m_colorTargetKey-1 ].color.green; m_colorRate.green = delta/time; delta = m_colorKey[ m_colorTargetKey ].color.blue - m_colorKey[ m_colorTargetKey-1 ].color.blue; m_colorRate.blue = delta/time; } // ------------------------------------------------------------------------------------------------ /** Construct a particle from a particle template */ // ------------------------------------------------------------------------------------------------ Particle::Particle( ParticleSystem *system, const ParticleInfo *info ) { m_system = system; m_isCulled = FALSE; m_accel.x = 0.0f; m_accel.y = 0.0f; m_accel.z = 0.0f; m_vel = info->m_vel; m_pos = info->m_pos; m_angleZ = info->m_angleZ; //Added By Sadullah Nader //Initializations inserted m_lastPos.zero(); // m_windRandomness = info->m_windRandomness; m_particleUpTowardsEmitter = info->m_particleUpTowardsEmitter; m_emitterPos = info->m_emitterPos; m_angularRateZ = info->m_angularRateZ; m_angularDamping = info->m_angularDamping; m_velDamping = info->m_velDamping; m_lifetime = info->m_lifetime; m_lifetimeLeft = info->m_lifetime; m_createTimestamp = TheGameClient->getFrame(); m_personality = 0; m_size = info->m_size; m_sizeRate = info->m_sizeRate; m_sizeRateDamping = info->m_sizeRateDamping; // set up alpha for( int i=0; im_alphaKey[i]; m_alpha = m_alphaKey[0].value; m_alphaTargetKey = 1; computeAlphaRate(); // set up colors for( i=0; im_colorKey[i]; m_color = m_colorKey[0].color; m_colorTargetKey = 1; computeColorRate(); m_colorScale = info->m_colorScale; m_inSystemList = m_inOverallList = FALSE; m_systemPrev = m_systemNext = m_overallPrev = m_overallNext = NULL; // add this particle to the global list, retaining particle creation order TheParticleSystemManager->addParticle(this, system->getPriority() ); // add this particle to the Particle System list, retaining local creation order m_system->addParticle(this); //DEBUG_ASSERTLOG(!(totalParticleCount % 100 == 0), ( "TotalParticleCount = %d\n", m_totalParticleCount )); } // ------------------------------------------------------------------------------------------------ /** Destructor */ // ------------------------------------------------------------------------------------------------ Particle::~Particle() { // tell the particle system that this particle is gone m_system->removeParticle( this ); // if this particle was controlling another particle system, destroy that system if (m_systemUnderControl) { m_systemUnderControl->detachControlParticle( this ); m_systemUnderControl->destroy(); } m_systemUnderControl = NULL; // remove from the global list TheParticleSystemManager->removeParticle(this); //DEBUG_ASSERTLOG(!(totalParticleCount % 100 == 0), ( "TotalParticleCount = %d\n", m_totalParticleCount )); } // ------------------------------------------------------------------------------------------------ /** Add the given acceleration */ // ------------------------------------------------------------------------------------------------ void Particle::applyForce( const Coord3D *force ) { m_accel.x += force->x; m_accel.y += force->y; m_accel.z += force->z; } // ------------------------------------------------------------------------------------------------ /** Update the behavior of an individual particle */ // ------------------------------------------------------------------------------------------------ Bool Particle::update( void ) { // integrate acceleration into velocity m_vel.x += m_accel.x; m_vel.y += m_accel.y; m_vel.z += m_accel.z; m_vel.x *= m_velDamping; 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; // 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(); // update orientation m_angleZ += m_angularRateZ; 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; // // Update alpha (if used) // if (m_system->getShaderType() != ParticleSystemInfo::ADDITIVE) { m_alpha += m_alphaRate; if (m_alphaTargetKey < MAX_KEYFRAMES && m_alphaKey[ m_alphaTargetKey ].frame) { if (TheGameClient->getFrame() - m_createTimestamp >= 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 (TheGameClient->getFrame() - m_createTimestamp >= m_colorKey[ m_colorTargetKey ].frame) { // can't set, because of colorscale // m_color = m_colorKey[ m_colorTargetKey ].color; m_colorTargetKey++; computeColorRate(); } } else { m_colorRate.red = 0.0f; m_colorRate.green = 0.0f; 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 (m_color.red < 0.0f) m_color.red = 0.0f; else if (m_color.red > 1.0f) m_color.red = 1.0f; if (m_color.red < 0.0f) m_color.green = 0.0f; else if (m_color.green > 1.0f) m_color.green = 1.0f; if (m_color.blue < 0.0f) m_color.blue = 0.0f; else if (m_color.blue > 1.0f) m_color.blue = 1.0f; // reset the acceleration for accumulation next frame m_accel.z=m_accel.y=m_accel.x= 0.0f; // monitor lifetime if (m_lifetimeLeft && --m_lifetimeLeft == 0) return false; DEBUG_ASSERTCRASH( m_lifetimeLeft, ( "A particle has an infinite lifetime..." )); // if we've gone totally invisible, destroy ourselves if (isInvisible()) return false; return true; } // ------------------------------------------------------------------------------------------------ /** Do wind motion as specified by the particle system template, if present */ // ------------------------------------------------------------------------------------------------ void Particle::doWindMotion( void ) { // get the angle of the wind Real windAngle = m_system->getWindAngle(); // get the system position Coord3D systemPos; m_system->getPosition( &systemPos ); // when we're attached objects and drawables we offset by that position as well if( ObjectID attachedObj = m_system->getAttachedObject() ) { Object *obj = TheGameLogic->findObjectByID( attachedObj ); if( obj ) { const Coord3D *objPos = obj->getPosition(); systemPos.x += objPos->x; systemPos.y += objPos->y; systemPos.z += objPos->z; } // end if } // end if else if( DrawableID attachedDraw = m_system->getAttachedDrawable() ) { Drawable *draw = TheGameClient->findDrawableByID( attachedDraw ); if( draw ) { const Coord3D *drawPos = draw->getPosition(); systemPos.x += drawPos->x; systemPos.y += drawPos->y; systemPos.z += drawPos->z; } // end if } // end else if // // compute a vector from the system position in the world to the particle ... we will use // this to compute how much force we apply // Coord3D v; v.x = m_pos.x - systemPos.x; v.y = m_pos.y - systemPos.y; v.z = m_pos.z - systemPos.z; // distance amounts for full force from wind and no force at all Real fullForceDistance = 75.0f; Real noForceDistance = 200.0f; // // given the distance from the wind position to the particle ... figure out how much // force we're going to apply to it. When it's further away (outside of the full force // distance) we will apply only a fraction of the force // Real distFromWind = v.length(); if( distFromWind < noForceDistance ) { Real windForceStrength = 2.0f * m_windRandomness; // only apply force if still within the circle of influence if( distFromWind > fullForceDistance ) windForceStrength *= (1.0f - ((distFromWind - fullForceDistance) / (noForceDistance - fullForceDistance))); // integate the wind motion into the position m_pos.x += (Cos( windAngle ) * windForceStrength); m_pos.y += (Sin( windAngle ) * windForceStrength); } // end if } // doWindMotion // ------------------------------------------------------------------------------------------------ /** Get priority of a particle ... which is the priority of it's attached system */ // ------------------------------------------------------------------------------------------------ ParticlePriorityType Particle::getPriority( void ) { return m_system->getPriority(); } // ------------------------------------------------------------------------------------------------ /** Return true if this particle is invisible */ // ------------------------------------------------------------------------------------------------ Bool Particle::isInvisible( void ) { 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_color.red + m_color.green + m_color.blue) <= 0.06f) return true; } return false; case ParticleSystemInfo::ALPHA: // if alpha is zero, this particle is invisible if (m_alpha < 0.02f) return true; return false; case ParticleSystemInfo::ALPHA_TEST: // hmm... assume these particles are never invisible 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_color.red * m_color.green * m_color.blue) > 0.95f) return true; } return false; } // should never get here - if we do, data is incorrect return true; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void Particle::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void Particle::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // base class particle info ParticleInfo::xfer( xfer ); // personality xfer->xferUnsignedInt( &m_personality ); // acceleration xfer->xferCoord3D( &m_accel ); // last position xfer->xferCoord3D( &m_lastPos ); // lifetime left xfer->xferUnsignedInt( &m_lifetimeLeft ); // creation timestamp xfer->xferUnsignedInt( &m_createTimestamp ); // alpha xfer->xferReal( &m_alpha ); // alpha rate xfer->xferReal( &m_alphaRate ); // alpha target key xfer->xferInt( &m_alphaTargetKey ); // color xfer->xferRGBColor( &m_color ); // color rate xfer->xferRGBColor( &m_colorRate ); // color target key xfer->xferInt( &m_colorTargetKey ); // drawable DrawableID drawableID = INVALID_DRAWABLE_ID; xfer->xferDrawableID( &drawableID ); //saving for backwards compatibility when we supported drawables. // system under control as an id ParticleSystemID systemUnderControlID = m_systemUnderControl ? m_systemUnderControl->getSystemID() : INVALID_PARTICLE_SYSTEM_ID; xfer->xferUser( &systemUnderControlID, sizeof( ParticleSystemID ) ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void Particle::loadPostProcess( void ) { // call base class post process ParticleInfo::loadPostProcess(); // tidy up the m_systemUnderControl pointer if( m_systemUnderControlID != INVALID_PARTICLE_SYSTEM_ID ) { ParticleSystem *system; // find system system = TheParticleSystemManager->findParticleSystem( m_systemUnderControlID ); // set us as the control particle for this system system->setControlParticle( this ); controlParticleSystem( system ); // sanity if( m_systemUnderControlID == NULL ) { DEBUG_CRASH(( "Particle::loadPostProcess - Unable to find system under control pointer\n" )); throw SC_INVALID_DATA; } // end if } // end if } // end loadPostProcess /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleSystemInfo::ParticleSystemInfo() { m_priority = PARTICLE_PRIORITY_LOWEST; m_isGroundAligned = false; m_isEmitAboveGroundOnly = false; m_isParticleUpTowardsEmitter = false; //Added By Sadullah Nader //Initializations inserted m_driftVelocity.zero(); m_gravity = 0.0f; m_isEmissionVolumeHollow = FALSE; m_isOneShot = FALSE; m_slavePosOffset.zero(); m_systemLifetime = 0; // // some default values for the wind motion values m_windMotion = WIND_MOTION_NOT_USED; m_windAngle = 0.0f; m_windAngleChange = 0.15f; // higher is ping pong faster m_windAngleChangeMin = 0.15f; m_windAngleChangeMax = 0.45f; m_windMotionStartAngleMin = 0.0f; m_windMotionStartAngleMax = PI / 4.0f; m_windMotionStartAngle = m_windMotionStartAngleMin; m_windMotionEndAngleMin = TWO_PI - (PI / 4.0f); m_windMotionEndAngleMax = TWO_PI; m_windMotionEndAngle = m_windMotionEndAngleMin; m_windMotionMovingToEndAngle = TRUE; m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; } void ParticleSystemInfo::tintAllColors( Color tintColor ) { RGBColor rgb; rgb.setFromInt(tintColor); //This tints all but the first colorKey!!! for (int key = 1; key < MAX_KEYFRAMES; ++key ) { m_colorKey[ key ].color.red *= (Real)(rgb.red ) / 255.0f; m_colorKey[ key ].color.green *= (Real)(rgb.green) / 255.0f; m_colorKey[ key ].color.blue *= (Real)(rgb.blue ) / 255.0f; } } // end loadPostProcess // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ParticleSystemInfo::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void ParticleSystemInfo::xfer( Xfer *xfer ) { Int i; // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // is one shot xfer->xferBool( &m_isOneShot ); // shader type xfer->xferUser( &m_shaderType, sizeof( ParticleShaderType ) ); // particle type xfer->xferUser( &m_particleType, sizeof( ParticleType ) ); // particle type name xfer->xferAsciiString( &m_particleTypeName ); // angles GameClientRandomVariable tempRandom; //for backwards compatibility when we supported x,y xfer->xferUser( &tempRandom, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &tempRandom, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_angleZ, sizeof( GameClientRandomVariable ) ); // angular rate xfer->xferUser( &tempRandom, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &tempRandom, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_angularRateZ, sizeof( GameClientRandomVariable ) ); // angular damping xfer->xferUser( &m_angularDamping, sizeof( GameClientRandomVariable ) ); // velocity damping xfer->xferUser( &m_velDamping, sizeof( GameClientRandomVariable ) ); // lifetime xfer->xferUser( &m_lifetime, sizeof( GameClientRandomVariable ) ); // system lifetime xfer->xferUnsignedInt( &m_systemLifetime ); // start size xfer->xferUser( &m_startSize, sizeof( GameClientRandomVariable ) ); // start size rate xfer->xferUser( &m_startSizeRate, sizeof( GameClientRandomVariable ) ); // size rate xfer->xferUser( &m_sizeRate, sizeof( GameClientRandomVariable ) ); // size rate damping xfer->xferUser( &m_sizeRateDamping, sizeof( GameClientRandomVariable ) ); // alpha keys for( i = 0; i < MAX_KEYFRAMES; ++i ) { xfer->xferUser( &m_alphaKey[ i ].var, sizeof( GameClientRandomVariable ) ); xfer->xferUnsignedInt( &m_alphaKey[ i ].frame ); } // end for, i // color keys for( i = 0; i < MAX_KEYFRAMES; ++i ) { xfer->xferRGBColor( &m_colorKey[ i ].color ); xfer->xferUnsignedInt( &m_colorKey[ i ].frame ); } // end for, i // color scale xfer->xferUser( &m_colorScale, sizeof( GameClientRandomVariable ) ); // burst delay xfer->xferUser( &m_burstDelay, sizeof( GameClientRandomVariable ) ); // burst count xfer->xferUser( &m_burstCount, sizeof( GameClientRandomVariable ) ); // initial delay xfer->xferUser( &m_initialDelay, sizeof( GameClientRandomVariable ) ); // drift velocity xfer->xferCoord3D( &m_driftVelocity ); // gravity xfer->xferReal( &m_gravity ); // slave system name xfer->xferAsciiString( &m_slaveSystemName ); // slave position offset xfer->xferCoord3D( &m_slavePosOffset ); // attached system name xfer->xferAsciiString( &m_attachedSystemName ); // emission velocity type, this must come before m_emissionVelocity xfer->xferUser( &m_emissionVelocityType, sizeof( EmissionVelocityType ) ); // particle priority xfer->xferUser( &m_priority, sizeof( ParticlePriorityType ) ); // emission velocity switch( m_emissionVelocityType ) { // -------------------------------------------------------------------------------------------- case ORTHO: xfer->xferUser( &m_emissionVelocity.ortho.x, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_emissionVelocity.ortho.y, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_emissionVelocity.ortho.z, sizeof( GameClientRandomVariable ) ); break; // -------------------------------------------------------------------------------------------- case SPHERICAL: xfer->xferUser( &m_emissionVelocity.spherical.speed, sizeof( GameClientRandomVariable ) ); break; // -------------------------------------------------------------------------------------------- case HEMISPHERICAL: xfer->xferUser( &m_emissionVelocity.hemispherical.speed, sizeof( GameClientRandomVariable ) ); break; // -------------------------------------------------------------------------------------------- case CYLINDRICAL: xfer->xferUser( &m_emissionVelocity.cylindrical.radial, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_emissionVelocity.cylindrical.normal, sizeof( GameClientRandomVariable ) ); break; // -------------------------------------------------------------------------------------------- case OUTWARD: xfer->xferUser( &m_emissionVelocity.outward.speed, sizeof( GameClientRandomVariable ) ); xfer->xferUser( &m_emissionVelocity.outward.otherSpeed, sizeof( GameClientRandomVariable ) ); break; } // end switch, m_emissionVelocityType // emission volume type xfer->xferUser( &m_emissionVolumeType, sizeof( EmissionVolumeType ) ); // emission volume switch( m_emissionVolumeType ) { // -------------------------------------------------------------------------------------------- case POINT: // point has no data, it uses the systems position break; // -------------------------------------------------------------------------------------------- case LINE: xfer->xferCoord3D( &m_emissionVolume.line.start ); xfer->xferCoord3D( &m_emissionVolume.line.end ); break; // -------------------------------------------------------------------------------------------- case BOX: xfer->xferCoord3D( &m_emissionVolume.box.halfSize ); break; // -------------------------------------------------------------------------------------------- case SPHERE: xfer->xferReal( &m_emissionVolume.sphere.radius ); break; // -------------------------------------------------------------------------------------------- case CYLINDER: xfer->xferReal( &m_emissionVolume.cylinder.radius ); xfer->xferReal( &m_emissionVolume.cylinder.length ); break; } // end switch, m_emissionVolumeType // is emission volume hollow xfer->xferBool( &m_isEmissionVolumeHollow ); // is ground aligned xfer->xferBool( &m_isGroundAligned ); // emit above ground only xfer->xferBool( &m_isEmitAboveGroundOnly ); // is particle up towards emitter xfer->xferBool( &m_isParticleUpTowardsEmitter ); // wind motion xfer->xferUser( &m_windMotion, sizeof( WindMotion ) ); // wind angle xfer->xferReal( &m_windAngle ); // wind angle change xfer->xferReal( &m_windAngleChange ); // wind angle change min xfer->xferReal( &m_windAngleChangeMin ); // wind angle change max xfer->xferReal( &m_windAngleChangeMax ); // wind motion start angle xfer->xferReal( &m_windMotionStartAngle ); // wind motion start angle min xfer->xferReal( &m_windMotionStartAngleMin ); // wind motion start angle max xfer->xferReal( &m_windMotionStartAngleMax ); // wind motion end angle xfer->xferReal( &m_windMotionEndAngle ); // wind motion end angle min xfer->xferReal( &m_windMotionEndAngleMin ); // wind motion end angle max xfer->xferReal( &m_windMotionEndAngleMax ); // wind motion moving to end angle xfer->xferByte( &m_windMotionMovingToEndAngle ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ParticleSystemInfo::loadPostProcess( void ) { } // end loadPostProcess /////////////////////////////////////////////////////////////////////////////////////////////////// // ParticleSystem ///////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ /** Read particle system properties from given file */ // ------------------------------------------------------------------------------------------------ ParticleSystem::ParticleSystem( const ParticleSystemTemplate *sysTemplate, ParticleSystemID id, Bool createSlaves ) { m_systemParticlesHead = m_systemParticlesTail = NULL; m_isFirstPos = true; m_template = sysTemplate; m_systemID = id; //Added By Sadullah Nader //Initializations inserted m_lastPos.zero(); m_pos.zero(); m_velCoeff.zero(); // m_attachedToDrawableID = INVALID_DRAWABLE_ID; m_attachedToObjectID = INVALID_ID; m_isLocalIdentity = true; m_localTransform.Make_Identity(); m_isIdentity = true; m_transform.Make_Identity(); m_skipParentXfrm = false; m_isStopped = false; m_isDestroyed = false; m_isSaveable = true; m_slavePosOffset = sysTemplate->m_slavePosOffset; ///@todo: further formalize this parameter with an UnsignedInt field in the editor m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; m_driftVelocity = sysTemplate->m_driftVelocity; m_velCoeff.x = 1.0f; m_velCoeff.y = 1.0f; m_velCoeff.z = 1.0f; m_countCoeff = 1.0f; m_delayCoeff = 1.0f; m_sizeCoeff = 1.0f; m_gravity = sysTemplate->m_gravity; m_lifetime = sysTemplate->m_lifetime; m_startSize = sysTemplate->m_startSize; m_startSizeRate = sysTemplate->m_startSizeRate; m_sizeRate = sysTemplate->m_sizeRate; m_sizeRateDamping = sysTemplate->m_sizeRateDamping; for( int i=0; im_alphaKey[i]; for( i=0; im_colorKey[i]; /// @todo It is confusing to do this conversion here... Real low = sysTemplate->m_colorScale.getMinimumValue(); Real hi = sysTemplate->m_colorScale.getMaximumValue(); m_colorScale.setRange( low / 255.0f, hi / 255.0f ); m_burstDelay = sysTemplate->m_burstDelay; m_burstDelayLeft = 0; m_burstCount = sysTemplate->m_burstCount; m_isOneShot = sysTemplate->m_isOneShot; m_delayLeft = (UnsignedInt)sysTemplate->m_initialDelay.getValue(); m_startTimestamp = TheGameClient->getFrame(); m_systemLifetimeLeft = sysTemplate->m_systemLifetime; if (sysTemplate->m_systemLifetime) m_isForever = false; else m_isForever = true; m_accumulatedSizeBonus = 0; m_velDamping = sysTemplate->m_velDamping; m_angleZ = sysTemplate->m_angleZ; m_angularRateZ = sysTemplate->m_angularRateZ; m_angularDamping = sysTemplate->m_angularDamping; m_priority = sysTemplate->m_priority; m_emissionVelocityType = sysTemplate->m_emissionVelocityType; m_emissionVelocity = sysTemplate->m_emissionVelocity; m_emissionVolumeType = sysTemplate->m_emissionVolumeType; m_emissionVolume = sysTemplate->m_emissionVolume; m_isEmissionVolumeHollow = sysTemplate->m_isEmissionVolumeHollow; m_isGroundAligned = sysTemplate->m_isGroundAligned; m_isEmitAboveGroundOnly = sysTemplate->m_isEmitAboveGroundOnly; m_isParticleUpTowardsEmitter = sysTemplate->m_isParticleUpTowardsEmitter; m_windMotion = sysTemplate->m_windMotion; m_windAngleChange = sysTemplate->m_windAngleChange; m_windAngleChangeMin = sysTemplate->m_windAngleChangeMin; m_windAngleChangeMax = sysTemplate->m_windAngleChangeMax; m_windMotionStartAngleMin = sysTemplate->m_windMotionStartAngleMin; m_windMotionStartAngleMax = sysTemplate->m_windMotionStartAngleMax; m_windMotionEndAngleMin = sysTemplate->m_windMotionEndAngleMin; m_windMotionEndAngleMax = sysTemplate->m_windMotionEndAngleMax; m_windMotionMovingToEndAngle = sysTemplate->m_windMotionMovingToEndAngle; m_windMotionStartAngle = GameClientRandomValueReal( m_windMotionStartAngleMin, m_windMotionStartAngleMax ); m_windMotionEndAngle = GameClientRandomValueReal( m_windMotionEndAngleMin, m_windMotionEndAngleMax ); m_windAngle = GameClientRandomValueReal( m_windMotionStartAngle, m_windMotionEndAngle ); m_shaderType = sysTemplate->m_shaderType; m_particleType = sysTemplate->m_particleType; m_particleTypeName = sysTemplate->m_particleTypeName; m_isStopped = false; // set up slave particle system, if any m_masterSystemID = INVALID_PARTICLE_SYSTEM_ID; m_slaveSystemID = INVALID_PARTICLE_SYSTEM_ID; m_masterSystem = NULL; m_slaveSystem = NULL; if( createSlaves ) { ParticleSystem *slaveSystem = sysTemplate->createSlaveSystem(); if( slaveSystem ) { setSlave( slaveSystem ); m_slaveSystem->setMaster( this ); } // end if } // end if m_attachedSystemName = sysTemplate->m_attachedSystemName; m_particleCount = 0; m_personalityStore = 0; m_controlParticle = NULL; TheParticleSystemManager->friend_addParticleSystem(this); //DEBUG_ASSERTLOG(!(m_totalParticleSystemCount % 10 == 0), ( "TotalParticleSystemCount = %d\n", m_totalParticleSystemCount )); } // ------------------------------------------------------------------------------------------------ /** Destroy particle system and all of its particles */ // ------------------------------------------------------------------------------------------------ ParticleSystem::~ParticleSystem() { // tell any of our slave systems that we are going away if( m_slaveSystem ) { DEBUG_ASSERTCRASH( m_slaveSystem->getMaster() == this, ("~ParticleSystem: Our slave doesn't have us as a master!\n") ); m_slaveSystem->setMaster( NULL ); setSlave( NULL ); } // end if // tell any master system that *we* are going away if( m_masterSystem ) { DEBUG_ASSERTCRASH( m_masterSystem->getSlave() == this, ("~ParticleSystem: Our master doesn't have us as a slave!\n") ); m_masterSystem->setSlave( NULL ); setMaster( NULL ); } // end if // destroy all particles "in the air" while (m_systemParticlesHead) m_systemParticlesHead->deleteInstance(); m_attachedToDrawableID = INVALID_DRAWABLE_ID; m_attachedToObjectID = INVALID_ID; // if this system was controlled by a particle, detach if (m_controlParticle) m_controlParticle->detachControlledParticleSystem(); m_controlParticle = NULL; TheParticleSystemManager->friend_removeParticleSystem(this); //DEBUG_ASSERTLOG(!(m_totalParticleSystemCount % 10 == 0), ( "TotalParticleSystemCount = %d\n", m_totalParticleSystemCount )); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setMaster( ParticleSystem *master ) { m_masterSystem = master; m_masterSystemID = master ? master->getSystemID() : INVALID_PARTICLE_SYSTEM_ID; } // end set Master // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setSlave( ParticleSystem *slave ) { m_slaveSystem = slave; m_slaveSystemID = slave ? slave->getSystemID() : INVALID_PARTICLE_SYSTEM_ID; } // end setSlave // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setSaveable(Bool b) { m_isSaveable = b; if (m_slaveSystem) m_slaveSystem->setSaveable(b); } // ------------------------------------------------------------------------------------------------ /** (Re)start a stopped particle system */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::start( void ) { m_isStopped = false; } // ------------------------------------------------------------------------------------------------ /** Stop a particle system from emitting */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::stop( void ) { m_isStopped = true; } // ------------------------------------------------------------------------------------------------ /** Stop emitting, wait for all of our particles to die, then destroy self. */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::destroy( void ) { m_isDestroyed = true; if( m_slaveSystem ) { m_slaveSystem->destroy(); // If we don't it will leak forever. We are solely responsible for it. } } // ------------------------------------------------------------------------------------------------ /** Get the position of the particle system */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::getPosition( Coord3D *pos ) { Vector3 vec; m_localTransform.Get_Translation(&vec); if (pos) { pos->x=vec.X; pos->y=vec.Y; pos->z=vec.Z; } } // ------------------------------------------------------------------------------------------------ /** Set the position of the particle system */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setPosition( const Coord3D *pos ) { m_localTransform.Set_X_Translation( pos->x ); m_localTransform.Set_Y_Translation( pos->y ); m_localTransform.Set_Z_Translation( pos->z ); m_isLocalIdentity = false; } // ------------------------------------------------------------------------------------------------ /** Set the system's local transform */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setLocalTransform( const Matrix3D *matrix ) { m_localTransform = *matrix; m_isLocalIdentity = false; } // ------------------------------------------------------------------------------------------------ /** Rotate local transform matrix */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::rotateLocalTransformX( Real x ) { m_localTransform.Rotate_X( x ); m_isLocalIdentity = false; } // ------------------------------------------------------------------------------------------------ /** Rotate local transform matrix */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::rotateLocalTransformY( Real y ) { m_localTransform.Rotate_Y( y ); m_isLocalIdentity = false; } // ------------------------------------------------------------------------------------------------ /** Rotate local transform matrix */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::rotateLocalTransformZ( Real z ) { m_localTransform.Rotate_Z( z ); m_isLocalIdentity = false; } // ------------------------------------------------------------------------------------------------ /** Attach this particle system to a Drawable */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::attachToDrawable( const Drawable *draw ) { if (draw) m_attachedToDrawableID = draw->getID(); else m_attachedToDrawableID = INVALID_DRAWABLE_ID; } // ------------------------------------------------------------------------------------------------ /** Attach this particle system to a Drawable */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::attachToObject( const Object *obj ) { if (obj) m_attachedToObjectID = obj->getID(); else m_attachedToObjectID = INVALID_ID; } // ------------------------------------------------------------------------------------------------ /** Compute a random point on a unit sphere * @todo The density of random points generated is not uniform within the sphere */ // ------------------------------------------------------------------------------------------------ const Coord3D *ParticleSystem::computePointOnUnitSphere( void ) { static Coord3D point; do { point.x = GameClientRandomValueReal( -1.0f, 1.0f ); point.y = GameClientRandomValueReal( -1.0f, 1.0f ); point.z = GameClientRandomValueReal( -1.0f, 1.0f ); } while (point.x == 0.0f && point.y == 0.0f && point.z == 0.0f); point.normalize(); return &point; } // ------------------------------------------------------------------------------------------------ /** Compute a velocity vector based on emission properties */ // ------------------------------------------------------------------------------------------------ const Coord3D *ParticleSystem::computeParticleVelocity( const Coord3D *pos ) { static Coord3D newVel; switch( m_emissionVelocityType ) { case ORTHO: newVel.x = m_emissionVelocity.ortho.x.getValue(); newVel.y = m_emissionVelocity.ortho.y.getValue(); newVel.z = m_emissionVelocity.ortho.z.getValue(); break; case CYLINDRICAL: { Real radialSpeed, angle; radialSpeed = m_emissionVelocity.cylindrical.radial.getValue(); angle = GameClientRandomValueReal( 0, 2.0f*PI ); newVel.x = radialSpeed * cos( angle ); newVel.y = radialSpeed * sin( angle ); newVel.z = m_emissionVelocity.cylindrical.normal.getValue(); break; } // "outward" velocity is directed along the surface normal of the emission volume case OUTWARD: { Real speed = m_emissionVelocity.outward.speed.getValue(); Real otherSpeed = m_emissionVelocity.outward.otherSpeed.getValue(); Coord3D sysPos; /* sysPos.x = m_localTransform.Get_X_Translation(); sysPos.y = m_localTransform.Get_Y_Translation(); sysPos.z = m_localTransform.Get_Z_Translation(); */ sysPos.x = 0.0f; sysPos.y = 0.0f; sysPos.z = 0.0f; switch( m_emissionVolumeType ) { case CYLINDER: Coord2D disk; disk.x = pos->x - sysPos.x; disk.y = pos->y - sysPos.y; disk.normalize(); newVel.x = speed * disk.x; newVel.y = speed * disk.y; newVel.z = otherSpeed; break; case BOX: ///< @todo Implement BOX OUTWARD velocity case SPHERE: { newVel.x = pos->x - sysPos.x; newVel.y = pos->y - sysPos.y; newVel.z = pos->z - sysPos.z; newVel.normalize(); newVel.x *= speed; newVel.y *= speed; newVel.z *= speed; break; } case LINE: { Coord3D along; // unit vector along line direction along.x = m_emissionVolume.line.end.x - m_emissionVolume.line.start.x; along.y = m_emissionVolume.line.end.y - m_emissionVolume.line.start.y; along.z = m_emissionVolume.line.end.z - m_emissionVolume.line.start.z; along.normalize(); Coord3D perp; // unit vector perpendicular to the along/up plane Coord3D up; // unit vector in the up direction (Z) up.x = 0.0; up.y = 0.0; up.z = 1.0; perp.crossProduct( &up, &along, &perp ); up.crossProduct( &along, &perp, &up ); // "speed" is in 'horizontal' plane, and "otherSpeed" is 'vertical' newVel.x = speed * perp.x + otherSpeed * up.x; newVel.y = speed * perp.y + otherSpeed * up.y; newVel.z = speed * perp.z + otherSpeed * up.z; break; } case POINT: { Coord3D vel = *computePointOnUnitSphere(); newVel.x = speed * vel.x; newVel.y = speed * vel.y; newVel.z = speed * vel.z; break; } } break; } case SPHERICAL: { Real speed = m_emissionVelocity.spherical.speed.getValue(); Coord3D vel = *computePointOnUnitSphere(); newVel.x = speed * vel.x; newVel.y = speed * vel.y; newVel.z = speed * vel.z; break; } case HEMISPHERICAL: { Coord3D vel; Real speed = m_emissionVelocity.spherical.speed.getValue(); do { vel.x = GameClientRandomValueReal( -1.0f, 1.0f ); vel.y = GameClientRandomValueReal( -1.0f, 1.0f ); vel.z = GameClientRandomValueReal( 0.0f, 1.0f ); } while (vel.x == 0.0f && vel.y == 0.0f && vel.z == 0.0f); vel.normalize(); newVel.x = speed * vel.x; newVel.y = speed * vel.y; newVel.z = speed * vel.z; break; } default: newVel.x = 0.0f; newVel.y = 0.0f; newVel.z = 0.0f; break; } // scale the velocity by the velocity multiplier newVel.x *= m_velCoeff.x*(0.5f+TheGlobalData->m_particleScale/2.0f); newVel.y *= m_velCoeff.y*(0.5f+TheGlobalData->m_particleScale/2.0f); newVel.z *= m_velCoeff.z*(0.5f+TheGlobalData->m_particleScale/2.0f); return &newVel; } // ------------------------------------------------------------------------------------------------ /** Compute a position based on emission properties */ // ------------------------------------------------------------------------------------------------ const Coord3D *ParticleSystem::computeParticlePosition( void ) { static Coord3D newPos; switch( m_emissionVolumeType ) { case CYLINDER: { Real angle = GameClientRandomValueReal( 0, 2.0f*PI ); Real radius; if (m_isEmissionVolumeHollow) radius = m_emissionVolume.cylinder.radius; else radius = GameClientRandomValueReal( 0.0f, m_emissionVolume.cylinder.radius ); newPos.x = radius * cos( angle ); newPos.y = radius * sin( angle ); Real halfLength = m_emissionVolume.cylinder.length/2.0f; newPos.z = GameClientRandomValueReal( -halfLength, halfLength ); break; } case SPHERE: { Real radius; if (m_isEmissionVolumeHollow) radius = m_emissionVolume.sphere.radius; else radius = GameClientRandomValueReal( 0.0f, m_emissionVolume.sphere.radius ); newPos = *computePointOnUnitSphere(); newPos.x *= radius; newPos.y *= radius; newPos.z *= radius; break; } case BOX: { if (m_isEmissionVolumeHollow) { // determine which side to generate on. // 0 is bottom, 3 is top, // 1 is left , 4 is right // 2 is front, 5 is right back int side = GameClientRandomValue(0, 6); if (side % 3 == 0) { // generate X, Y newPos.x = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.x, m_emissionVolume.box.halfSize.x ); newPos.y = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.y, m_emissionVolume.box.halfSize.y ); if (side == 0) { newPos.z = -m_emissionVolume.box.halfSize.z; } else { newPos.z = m_emissionVolume.box.halfSize.z; } } else if (side % 3 == 1) { // generate Y, Z newPos.y = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.y, m_emissionVolume.box.halfSize.y ); newPos.z = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.z, m_emissionVolume.box.halfSize.z ); if (side == 1) { newPos.x = -m_emissionVolume.box.halfSize.x; } else { newPos.x = m_emissionVolume.box.halfSize.y; } } else if (side % 3 == 2) { // generate X, Z newPos.x = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.x, m_emissionVolume.box.halfSize.x ); newPos.z = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.z, m_emissionVolume.box.halfSize.z ); if (side == 2) { newPos.y = -m_emissionVolume.box.halfSize.y; } else { newPos.y = m_emissionVolume.box.halfSize.y; } } } else { newPos.x = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.x, m_emissionVolume.box.halfSize.x ); newPos.y = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.y, m_emissionVolume.box.halfSize.y ); newPos.z = GameClientRandomValueReal( -m_emissionVolume.box.halfSize.z, m_emissionVolume.box.halfSize.z ); break; } } case LINE: { Coord3D delta, start, end; start = m_emissionVolume.line.start; end = m_emissionVolume.line.end; delta.x = end.x - start.x; delta.y = end.y - start.y; delta.z = end.z - start.z; Real t = GameClientRandomValueReal( 0.0f, 1.0f ); newPos.x = start.x + t * delta.x; newPos.y = start.y + t * delta.y; newPos.z = start.z + t * delta.z; break; } case POINT: default: newPos.x = 0.0f; newPos.y = 0.0f; newPos.z = 0.0f; break; } newPos.x *= (0.5f+TheGlobalData->m_particleScale/2.0f); newPos.y *= (0.5f+TheGlobalData->m_particleScale/2.0f); newPos.z *= (0.5f+TheGlobalData->m_particleScale/2.0f); return &newPos; } // ------------------------------------------------------------------------------------------------ /** Factory method for particles. */ // ------------------------------------------------------------------------------------------------ Particle *ParticleSystem::createParticle( const ParticleInfo *info, ParticlePriorityType priority, Bool forceCreate ) { // // if we aren't absolutely forcing this particle to be created (which is needed when // loading and creating particle systems from the save games) we need to check a few // restrictions before this particle can really be created // if( forceCreate == FALSE ) { if (TheGlobalData->m_useFX == FALSE) return NULL; // // Enforce particle limit. // If we are at the limit, destroy the oldest particle in order // to make room for this one. // // // 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 // getMinDynamicParticleSkipPriority()) // if( priority < TheGameLODManager->getMinDynamicParticlePriority() || (priority < TheGameLODManager->getMinDynamicParticleSkipPriority() && TheGameLODManager->isParticleSkipped()) ) return NULL; if ( getParticleCount() > 0 && priority == AREA_EFFECT && m_isGroundAligned && TheParticleSystemManager->getFieldParticleCount() > (UnsignedInt)TheGlobalData->m_maxFieldParticleCount ) return NULL; // ALWAYS_RENDER particles are exempt from all count limits, and are always created, regardless of LOD issues. if (priority != ALWAYS_RENDER) { int numInExcess = TheParticleSystemManager->getParticleCount() - (UnsignedInt)TheGlobalData->m_maxParticleCount; if ( numInExcess > 0) { if( TheParticleSystemManager->removeOldestParticles((UnsignedInt) numInExcess, priority) != numInExcess ) return NULL; // could not remove enough particles, don't create new stuff } if (TheGlobalData->m_maxParticleCount == 0) return NULL; } } // end if Particle *p = newInstance(Particle)( this, info ); return p; } // ------------------------------------------------------------------------------------------------ /** Generate a new, random set of ParticleInfo * particleNum and particleCount are used to get 'tween frame particles emitted in the correct * place. (jkmcd) */ // ------------------------------------------------------------------------------------------------ const ParticleInfo *ParticleSystem::generateParticleInfo( Int particleNum, Int particleCount ) { static ParticleInfo info; if (particleCount == 0) { DEBUG_CRASH(("particleCount must NOT be 0. Set to 1 or greater.")); return &info; } // NOTE: position MUST be computed before velocity, in case OUTWARD velocity is // specified, which must know where the particle is in space. info.m_pos = *computeParticlePosition(); info.m_vel = *computeParticleVelocity( &info.m_pos ); // transform the position and velocity, if necessary /// @todo Avoid conversion from Coord3D to Vector3 somehow if (m_isIdentity == false) { // 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) { m_lastPos = m_pos; 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); p.X = info.m_pos.x; p.Y = info.m_pos.y; p.Z = info.m_pos.z; #ifdef ALLOW_TEMPORARIES pr = m_transform * p; #else m_transform.mulVector3(p, pr); #endif info.m_pos.x = pr.X - emissionAdjustment.x; info.m_pos.y = pr.Y - emissionAdjustment.y; info.m_pos.z = pr.Z - emissionAdjustment.z; // transform particle velocity to world coordinates Vector3 v, vr; v.X = info.m_vel.x; v.Y = info.m_vel.y; v.Z = info.m_vel.z; Matrix3D::Rotate_Vector( m_transform, v, &vr ); info.m_vel.x = vr.X; info.m_vel.y = vr.Y; info.m_vel.z = vr.Z; } info.m_velDamping = m_velDamping.getValue(); info.m_angularDamping = m_angularDamping.getValue(); info.m_angleZ = m_angleZ.getValue(); info.m_angularRateZ = m_angularRateZ.getValue(); info.m_lifetime = (UnsignedInt)m_lifetime.getValue(); info.m_size = m_startSize.getValue()*m_sizeCoeff*TheGlobalData->m_particleScale; info.m_sizeRate = m_sizeRate.getValue()*m_sizeCoeff*TheGlobalData->m_particleScale; info.m_sizeRateDamping = m_sizeRateDamping.getValue(); // Keeping a running tally makes each successive particle spawned start a bit bigger (or smaller). info.m_size += m_accumulatedSizeBonus; m_accumulatedSizeBonus += m_startSizeRate.getValue(); if( m_accumulatedSizeBonus ) m_accumulatedSizeBonus = min( m_accumulatedSizeBonus, (float)MAX_SIZE_BONUS ); for( int i=0; im_useFX == FALSE) return false; // do initial delay ... note, this currently delays the lifetime if (m_delayLeft) { --m_delayLeft; // system actually "starts" once initial delay is over /// @todo reset start time when system is stopped/started if (m_delayLeft == 0) m_startTimestamp = TheGameClient->getFrame(); return true; } // update the wind motion 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 = NULL; 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; } // // Generate new particles if the system hasn't been 'stopped' or 'destroyed' // If we are a slave system, do not generate particles ourselves - our master will force us to // if (m_isDestroyed == false) { if (m_isForever || (m_isForever == false && m_systemLifetimeLeft > 0)) { if (!isShrouded && m_isStopped == false && m_masterSystem == NULL) { if (m_burstDelayLeft == 0) { ParticlePriorityType priority = getPriority(); // emit a burst of particles Int count = REAL_TO_INT(m_burstCount.getValue()); count *= m_countCoeff; for( Int i=0; im_pos.z >= TheTerrainLogic->getGroundHeight(info->m_pos.x, info->m_pos.y))) { // actually create a particle Particle *p = createParticle( info, priority ); if (p == NULL) continue; if (m_attachedSystemName.isEmpty() == false) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( m_attachedSystemName ); if (tmp) { ParticleSystem *sys = TheParticleSystemManager->createParticleSystem( tmp, TRUE ); sys->setControlParticle( p ); p->controlParticleSystem( sys ); } } // create a slave particle, if necessary if (m_slaveSystem) { ParticleInfo mergeInfo = ParticleSystem::mergeRelatedParticleSystems(this, m_slaveSystem, false); // create slaved particle m_slaveSystem->createParticle( &mergeInfo, priority ); } } } // compute next burst delay m_burstDelayLeft = (UnsignedInt)m_burstDelay.getValue(); m_burstDelayLeft *= m_delayCoeff; } else { m_burstDelayLeft--; } } // end if stopped check } // end if system lifetime check } // end if is destroyed // // Update all particles in the system // Particle *p = m_systemParticlesHead; Particle *oldParticle; while (p) { // apply 'gravity' force if (m_gravity != 0.0f) { Coord3D force; force.x = 0.0f; force.y = 0.0f; force.z = m_gravity; p->applyForce( &force ); } if (p->update() == false) { oldParticle = p; p = p->m_systemNext; oldParticle->deleteInstance(); } else { p = p->m_systemNext; } } // // If we have been "destroyed", wait for all of our particles to die off, // then destroy ourselves (return false). // if (m_isDestroyed && !m_systemParticlesHead) return false; // monitor particle system lifetime if (m_isForever == false) { // decrement lifetime if not zero if (m_systemLifetimeLeft) m_systemLifetimeLeft--; // if there are still particles "in the air", don't destroy yet if (getParticleCount()) return true; // check if time is up if (m_systemLifetimeLeft == 0) return false; } return true; } // ------------------------------------------------------------------------------------------------ /** Update the wind motion */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::updateWindMotion( void ) { switch( m_windMotion ) { // -------------------------------------------------------------------------------------------- case ParticleSystemInfo::WIND_MOTION_PING_PONG: { Real startAngle = m_windMotionStartAngle; Real endAngle = m_windMotionEndAngle; // this only works when start angle is less than end angle DEBUG_ASSERTCRASH( startAngle < endAngle, ("updateWindMotion: startAngle must be < endAngle\n") ); // how big is the total angle span Real totalSpan = endAngle - startAngle; Real halfSpan = totalSpan / 2.0f; // given our current angle ... how far away from the "center" of the span are we Real diffFromCenter = fabs( halfSpan - m_windAngle + startAngle ); // // given our distance from the center ... we need to compute how much we will change // the angle. When we are closer to the center we change it faster (more), and when // we are near the edges we change is slower (less) // Real change = (1.0f - (diffFromCenter / halfSpan)) * m_windAngleChange; // we will always change a little bit #define MINIMUM_CHANGE 0.005f // lower #'s have softer swings at the edge angles if( change < MINIMUM_CHANGE ) change = MINIMUM_CHANGE; // // if we are moving toward the end angle we add the change, if we're moving away // from it we subtract it // if( m_windMotionMovingToEndAngle ) { // add angle m_windAngle += change; // see if we're at the end and should switch directions if( m_windAngle >= endAngle ) { // change directions m_windMotionMovingToEndAngle = FALSE; // pick a new change delta m_windAngleChange = GameClientRandomValueReal( m_windAngleChangeMin, m_windAngleChangeMax ); // pick new start and end angles m_windMotionStartAngle = GameClientRandomValueReal( m_windMotionStartAngleMin, m_windMotionStartAngleMax ); m_windMotionEndAngle = GameClientRandomValueReal( m_windMotionEndAngleMin, m_windMotionEndAngleMax ); } // end if } // end if else { // subtract angle m_windAngle -= change; // see if we're at the end and should switch directions if( m_windAngle <= startAngle ) { // change directions m_windMotionMovingToEndAngle = TRUE; // pick a new change delta m_windAngleChange = GameClientRandomValueReal( m_windAngleChangeMin, m_windAngleChangeMax ); // pick new start and end angles m_windMotionStartAngle = GameClientRandomValueReal( m_windMotionStartAngleMin, m_windMotionStartAngleMax ); m_windMotionEndAngle = GameClientRandomValueReal( m_windMotionEndAngleMin, m_windMotionEndAngleMax ); } // end if } // end else break; } // end case // -------------------------------------------------------------------------------------------- case ParticleSystemInfo::WIND_MOTION_CIRCULAR: { // give us a wind angle change if one hasn't been specifed (this plays nice with the particle editor) if( m_windAngleChange == 0.0f ) m_windAngleChange = GameClientRandomValueReal( m_windAngleChangeMin, m_windAngleChangeMax ); // add to our wind angle m_windAngle += m_windAngleChange; // keep in 0 to 2PI range just to keep the numbers safe and sane if( m_windAngle > TWO_PI ) m_windAngle -= TWO_PI; else if( m_windAngle < 0.0f ) m_windAngle += TWO_PI; break; } // end case // --------------------------------------------------------------------------------------------- default: { break; } // end default } // end if } // end updateWindMotion // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::addParticle( Particle *particleToAdd ) { if (particleToAdd->m_inSystemList) return; if (!m_systemParticlesHead) { m_systemParticlesHead = particleToAdd; } if (m_systemParticlesTail) { m_systemParticlesTail->m_systemNext = particleToAdd; particleToAdd->m_systemPrev = m_systemParticlesTail; } else { particleToAdd->m_systemPrev = NULL; } m_systemParticlesTail = particleToAdd; particleToAdd->m_systemNext = NULL; particleToAdd->m_inSystemList = TRUE; ++m_particleCount; particleToAdd->setPersonality( m_personalityStore++ ); } // ------------------------------------------------------------------------------------------------ /** Remove given particle from the list - ONLY FOR USE BY PARTICLE */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::removeParticle( Particle *particleToRemove ) { if (!particleToRemove->m_inSystemList) return; // remove links from prev & next objs if (particleToRemove->m_systemNext) particleToRemove->m_systemNext->m_systemPrev = particleToRemove->m_systemPrev; if (particleToRemove->m_systemPrev) particleToRemove->m_systemPrev->m_systemNext = particleToRemove->m_systemNext; // update head & tail if neccessary if (particleToRemove == m_systemParticlesHead) m_systemParticlesHead = particleToRemove->m_systemNext; if (particleToRemove == m_systemParticlesTail) m_systemParticlesTail = particleToRemove->m_systemPrev; particleToRemove->m_systemNext = particleToRemove->m_systemPrev = NULL; particleToRemove->m_inSystemList = FALSE; --m_particleCount; } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleInfo ParticleSystem::mergeRelatedParticleSystems( ParticleSystem *masterParticleSystem, ParticleSystem *slaveParticleSystem, Bool slaveNeedsFullPromotion) { if (!masterParticleSystem || !slaveParticleSystem) { DEBUG_CRASH(("masterParticleSystem or slaveParticleSystem was NULL. Should not happen. JKMCD")); ParticleInfo bogus; memset( &bogus, 0, sizeof( bogus ) ); return bogus; } // copy info ParticleInfo mergeInfo = *masterParticleSystem->generateParticleInfo(1, 1); // generate one from the slave system const ParticleInfo *info = slaveParticleSystem->generateParticleInfo(1, 1); // override unique attributes of slave particle mergeInfo.m_lifetime = info->m_lifetime; // size becomes a scale factor of master's particles mergeInfo.m_size *= info->m_size; mergeInfo.m_sizeRate *= info->m_sizeRate; mergeInfo.m_sizeRateDamping *= info->m_sizeRateDamping; mergeInfo.m_angleZ = info->m_angleZ; mergeInfo.m_angularRateZ = info->m_angularRateZ; mergeInfo.m_angularDamping = info->m_angularDamping; for( int i=0; im_alphaKey[i]; for( i=0; im_colorKey[i]; mergeInfo.m_colorScale = info->m_colorScale; // offset slave's position relative to master's const Coord3D *offset = slaveParticleSystem->getSlavePositionOffset(); mergeInfo.m_pos.x += offset->x; mergeInfo.m_pos.y += offset->y; mergeInfo.m_pos.z += offset->z; if (slaveNeedsFullPromotion) { slaveParticleSystem->m_burstCount = masterParticleSystem->m_burstCount; slaveParticleSystem->m_burstDelay = masterParticleSystem->m_burstDelay; slaveParticleSystem->m_priority = masterParticleSystem->m_priority; slaveParticleSystem->m_emissionVelocity = masterParticleSystem->m_emissionVelocity; slaveParticleSystem->m_emissionVelocityType = masterParticleSystem->m_emissionVelocityType; slaveParticleSystem->m_emissionVolume = masterParticleSystem->m_emissionVolume; slaveParticleSystem->m_emissionVolumeType = masterParticleSystem->m_emissionVolumeType; slaveParticleSystem->m_isEmissionVolumeHollow = masterParticleSystem->m_isEmissionVolumeHollow; slaveParticleSystem->m_startSize.setRange(masterParticleSystem->m_startSize.getMinimumValue() * slaveParticleSystem->m_startSize.getMinimumValue(), masterParticleSystem->m_startSize.getMaximumValue() * slaveParticleSystem->m_startSize.getMaximumValue(), masterParticleSystem->m_startSize.getDistributionType()); slaveParticleSystem->m_sizeRate.setRange(masterParticleSystem->m_sizeRate.getMinimumValue() * slaveParticleSystem->m_sizeRate.getMinimumValue(), masterParticleSystem->m_sizeRate.getMaximumValue() * slaveParticleSystem->m_sizeRate.getMaximumValue(), masterParticleSystem->m_sizeRate.getDistributionType()); slaveParticleSystem->m_sizeRateDamping.setRange(masterParticleSystem->m_sizeRateDamping.getMinimumValue() * slaveParticleSystem->m_sizeRateDamping.getMinimumValue(), masterParticleSystem->m_sizeRateDamping.getMaximumValue() * slaveParticleSystem->m_sizeRateDamping.getMaximumValue(), masterParticleSystem->m_sizeRateDamping.getDistributionType()); // slaveParticleSystem->m_burstCount.setRange(masterParticleSystem->m_burstCount.getMinimumValue() / 2, // masterParticleSystem->m_burstCount.getMaximumValue() / 2, // masterParticleSystem->m_burstCount.getDistributionType()); } return mergeInfo; } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setLifetimeRange( Real min, Real max ) { m_lifetime.setRange( min, max ); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void ParticleSystem::setControlParticle( Particle *p ) { m_controlParticle = p; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // base class info ParticleSystemInfo::xfer( xfer ); // particle system ID xfer->xferUser( &m_systemID, sizeof( ParticleSystemID ) ); // attached to drawable id xfer->xferDrawableID( &m_attachedToDrawableID ); // attached to object id xfer->xferObjectID( &m_attachedToObjectID ); // is local identity xfer->xferBool( &m_isLocalIdentity ); // local transform xfer->xferUser( &m_localTransform, sizeof( Matrix3D ) ); // is identity xfer->xferBool( &m_isIdentity ); // transform xfer->xferUser( &m_transform, sizeof( Matrix3D ) ); // burst delay left xfer->xferUnsignedInt( &m_burstDelayLeft ); // delay left xfer->xferUnsignedInt( &m_delayLeft ); // start timestamp xfer->xferUnsignedInt( &m_startTimestamp ); // system lifetime left xfer->xferUnsignedInt( &m_systemLifetimeLeft ); // personality store xfer->xferUnsignedInt( &m_personalityStore ); // is forever xfer->xferBool( &m_isForever ); // accumulated size bonus xfer->xferReal( &m_accumulatedSizeBonus ); // is stopped xfer->xferBool( &m_isStopped ); // we never save destroyed particle systems so there is no need to consider m_isDestroyed // m_isDestroyed <-- do nothing with me // ditto for m_isSaveable // m_isSaveable <-- do nothing with me // velCoeff xfer->xferCoord3D( &m_velCoeff ); // count coeff xfer->xferReal( &m_countCoeff ); // delay coeff xfer->xferReal( &m_delayCoeff ); // size coeff xfer->xferReal( &m_sizeCoeff ); // position xfer->xferCoord3D( &m_pos ); // last position xfer->xferCoord3D( &m_lastPos ); // is first pos xfer->xferBool( &m_isFirstPos ); // slave system id xfer->xferUser( &m_slaveSystemID, sizeof( ParticleSystemID ) ); // master system xfer->xferUser( &m_masterSystemID, sizeof( ParticleSystemID ) ); // particle count UnsignedInt particleCount = m_particleCount; xfer->xferUnsignedInt( &particleCount ); // particles if( xfer->getXferMode() == XFER_SAVE ) { Particle *particle; // go through all particles in this system for( particle = m_systemParticlesHead; particle; particle = particle->m_systemNext ) { // write particle information xfer->xferSnapshot( particle ); } // end for, particle } // end if, save else { ParticlePriorityType priority = getPriority(); const ParticleInfo *info = generateParticleInfo( 0, 1 ); Particle *particle; // read each particle data block for( UnsignedInt i = 0; i < particleCount; ++i ) { // create a new particle particle = createParticle( info, priority, TRUE ); // sanity DEBUG_ASSERTCRASH( particle, ("ParticleSyste::xfer - Unable to create particle for loading\n") ); // read in the particle data xfer->xferSnapshot( particle ); } // end for i } // end else, load } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ParticleSystem::loadPostProcess( void ) { // call base class post process ParticleSystemInfo::loadPostProcess(); // reconnect slave pointers if needed if( m_slaveSystemID != INVALID_PARTICLE_SYSTEM_ID ) { // sanity if( m_slaveSystem != NULL ) { DEBUG_CRASH(( "ParticleSystem::loadPostProcess - m_slaveSystem is not NULL but should be\n" )); throw SC_INVALID_DATA; } // end if // assign system m_slaveSystem = TheParticleSystemManager->findParticleSystem( m_slaveSystemID ); // sanity if( m_slaveSystem == NULL || m_slaveSystem->isDestroyed() == TRUE ) { DEBUG_CRASH(( "ParticleSystem::loadPostProcess - m_slaveSystem is NULL or destroyed\n" )); throw SC_INVALID_DATA; } // end if } // end if // reconnect master pointers if needed if( m_masterSystemID != INVALID_PARTICLE_SYSTEM_ID ) { // sanity if( m_masterSystem != NULL ) { DEBUG_CRASH(( "ParticleSystem::loadPostProcess - m_masterSystem is not NULL but should be\n" )); throw SC_INVALID_DATA; } // end if // assign system m_masterSystem = TheParticleSystemManager->findParticleSystem( m_masterSystemID ); // sanity if( m_masterSystem == NULL || m_masterSystem->isDestroyed() == TRUE ) { DEBUG_CRASH(( "ParticleSystem::loadPostProcess - m_masterSystem is NULL or destroyed\n" )); throw SC_INVALID_DATA; } // end if } // end if } // end loadPostProcess /////////////////////////////////////////////////////////////////////////////////////////////////// // ParticleSystemTemplate ///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ /** INI parse data */ // ------------------------------------------------------------------------------------------------ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] = { { "Priority", INI::parseIndexList, ParticlePriorityNames, offsetof( ParticleSystemTemplate, m_priority ) }, { "IsOneShot", INI::parseBool, NULL, offsetof( ParticleSystemTemplate, m_isOneShot ) }, { "Shader", INI::parseIndexList, ParticleShaderTypeNames, offsetof( ParticleSystemTemplate, m_shaderType ) }, { "Type", INI::parseIndexList, ParticleTypeNames, offsetof( ParticleSystemTemplate, m_particleType ) }, { "ParticleName", INI::parseAsciiString, NULL, offsetof( ParticleSystemTemplate, m_particleTypeName ) }, { "AngleZ", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_angleZ ) }, { "AngularRateZ", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_angularRateZ ) }, { "AngularDamping", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_angularDamping ) }, { "VelocityDamping", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_velDamping ) }, { "Gravity", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_gravity ) }, { "SlaveSystem", INI::parseAsciiString, NULL, offsetof( ParticleSystemTemplate, m_slaveSystemName ) }, { "SlavePosOffset", INI::parseCoord3D, NULL, offsetof( ParticleSystemTemplate, m_slavePosOffset ) }, { "PerParticleAttachedSystem", INI::parseAsciiString, NULL, offsetof( ParticleSystemTemplate, m_attachedSystemName ) }, { "Lifetime", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_lifetime ) }, { "SystemLifetime", INI::parseUnsignedInt, NULL, offsetof( ParticleSystemTemplate, m_systemLifetime ) }, { "Size", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_startSize ) }, { "StartSizeRate", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_startSizeRate ) }, { "SizeRate", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_sizeRate ) }, { "SizeRateDamping", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_sizeRateDamping ) }, { "Alpha1", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[0] ) }, { "Alpha2", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[1] ) }, { "Alpha3", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[2] ) }, { "Alpha4", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[3] ) }, { "Alpha5", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[4] ) }, { "Alpha6", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[5] ) }, { "Alpha7", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[6] ) }, { "Alpha8", ParticleSystemTemplate::parseRandomKeyframe, NULL, offsetof( ParticleSystemTemplate, m_alphaKey[7] ) }, { "Color1", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[0] ) }, { "Color2", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[1] ) }, { "Color3", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[2] ) }, { "Color4", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[3] ) }, { "Color5", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[4] ) }, { "Color6", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[5] ) }, { "Color7", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[6] ) }, { "Color8", ParticleSystemTemplate::parseRGBColorKeyframe,NULL, offsetof( ParticleSystemTemplate, m_colorKey[7] ) }, // { "COLOR", ParticleSystemTemplate::parseRandomRGBColor, NULL, offsetof( ParticleSystemTemplate, m_color ) }, { "ColorScale", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_colorScale ) }, { "BurstDelay", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_burstDelay ) }, { "BurstCount", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_burstCount ) }, { "InitialDelay", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_initialDelay ) }, { "DriftVelocity", INI::parseCoord3D, NULL, offsetof( ParticleSystemTemplate, m_driftVelocity ) }, { "VelocityType", INI::parseIndexList, EmissionVelocityTypeNames, offsetof( ParticleSystemTemplate, m_emissionVelocityType ) }, { "VelOrthoX", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.ortho.x ) }, { "VelOrthoY", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.ortho.y ) }, { "VelOrthoZ", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.ortho.z ) }, { "VelSpherical", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.spherical.speed ) }, { "VelHemispherical", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.hemispherical.speed ) }, { "VelCylindricalRadial", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.cylindrical.radial ) }, { "VelCylindricalNormal", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.cylindrical.normal ) }, { "VelOutward", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.outward.speed ) }, { "VelOutwardOther", INI::parseGameClientRandomVariable, NULL, offsetof( ParticleSystemTemplate, m_emissionVelocity.outward.otherSpeed ) }, { "VolumeType", INI::parseIndexList, EmissionVolumeTypeNames, offsetof( ParticleSystemTemplate, m_emissionVolumeType ) }, { "VolLineStart", INI::parseCoord3D, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.line.start ) }, { "VolLineEnd", INI::parseCoord3D, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.line.end ) }, { "VolBoxHalfSize", INI::parseCoord3D, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.box.halfSize ) }, { "VolSphereRadius", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.sphere.radius ) }, { "VolCylinderRadius", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.cylinder.radius ) }, { "VolCylinderLength", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_emissionVolume.cylinder.length ) }, { "IsHollow", INI::parseBool, NULL, offsetof( ParticleSystemTemplate, m_isEmissionVolumeHollow ) }, { "IsGroundAligned", INI::parseBool, NULL, offsetof( ParticleSystemTemplate, m_isGroundAligned ) }, { "IsEmitAboveGroundOnly", INI::parseBool, NULL, offsetof( ParticleSystemTemplate, m_isEmitAboveGroundOnly) }, { "IsParticleUpTowardsEmitter", INI::parseBool, NULL, offsetof( ParticleSystemTemplate, m_isParticleUpTowardsEmitter) }, { "WindMotion", INI::parseIndexList, WindMotionNames, offsetof( ParticleSystemTemplate, m_windMotion ) }, { "WindAngleChangeMin", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windAngleChangeMin ) }, { "WindAngleChangeMax", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windAngleChangeMax ) }, { "WindPingPongStartAngleMin", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windMotionStartAngleMin ) }, { "WindPingPongStartAngleMax", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windMotionStartAngleMax ) }, { "WindPingPongEndAngleMin", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windMotionEndAngleMin ) }, { "WindPingPongEndAngleMax", INI::parseReal, NULL, offsetof( ParticleSystemTemplate, m_windMotionEndAngleMax ) }, { NULL, NULL, NULL, 0 }, }; // ------------------------------------------------------------------------------------------------ /** Parse a "random keyframe". * The format is "FIELD = low high frame". */ // ------------------------------------------------------------------------------------------------ void ParticleSystemTemplate::parseRandomKeyframe( INI* ini, void *instance, void *store, const void* /*userData*/ ) { RandomKeyframe *key = static_cast(store); Real low = ini->scanReal(ini->getNextToken()); Real high = ini->scanReal(ini->getNextToken()); key->frame = ini->scanUnsignedInt(ini->getNextToken()); // set the range of the random variable key->var.setRange( low, high ); } // ------------------------------------------------------------------------------------------------ /** Parse a "color keyframe". * The format is "FIELD = R:r G:g B:b frame". */ // ------------------------------------------------------------------------------------------------ void ParticleSystemTemplate::parseRGBColorKeyframe( INI* ini, void *instance, void *store, const void* /*userData*/ ) { RGBColorKeyframe *key = static_cast(store); INI::parseRGBColor( ini, instance, &key->color, NULL ); INI::parseUnsignedInt( ini, instance, &key->frame, NULL ); } // ------------------------------------------------------------------------------------------------ /** Parse a RandomVariable RGB color. * Note that the components may be negative, as this is used for rates, as well. */ // ------------------------------------------------------------------------------------------------ void ParticleSystemTemplate::parseRandomRGBColor( INI* ini, void *instance, void *store, const void* /*userData*/ ) { #if 0 char seps[] = " \n\r\t=:RGB,"; const char *token; Int colors[2][3]; Int result; enum { LO = 0, HI = 1 }; enum { RED = 0, GREEN = 1, BLUE = 2 }; // initialize to invalid values colors[ LO ][ RED ] = -1; colors[ LO ][ GREEN ] = -1; colors[ LO ][ BLUE ] = -1; colors[ HI ][ RED ] = -1; colors[ HI ][ GREEN ] = -1; colors[ HI ][ BLUE ] = -1; // do each color part for( Int i = 0; i < 3; i++ ) { for( Int j = 0; j < 2; j++ ) { // get the color number token = ini->getNextToken(seps); // convert to number colors[j][i] = ini->scanInt(token); // check to see if it's within range if( colors[j][i] < -255 || colors[j][i] > 255 ) throw INI_INVALID_DATA; } // end for i } // assign the color components to the "RGBColor" pointer at 'store' ParticleSystemInfo::RandomRGBColor *theColor = (ParticleSystemInfo::RandomRGBColor *)store; theColor->red.setRange( (Real)colors[ LO ][ RED ] / 255.0f, (Real)colors[ HI ][ RED ] / 255.0f ); theColor->green.setRange( (Real)colors[ LO ][ GREEN ] / 255.0f, (Real)colors[ HI ][ GREEN ] / 255.0f ); theColor->blue.setRange( (Real)colors[ LO ][ BLUE ] / 255.0f, (Real)colors[ HI ][ BLUE ] / 255.0f ); #endif } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleSystemTemplate::ParticleSystemTemplate( const AsciiString &name ) : m_name(name) { //Added By Sadullah Nader //Initializations inserted m_slaveTemplate = NULL; // } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleSystemTemplate::~ParticleSystemTemplate() { } // ------------------------------------------------------------------------------------------------ /** If returns non-NULL, it is a slave system for use ... the create slaves parameter * tells *this* slave system whether or not it should create any slaves itself * automatically during its own constructor */ // ------------------------------------------------------------------------------------------------ ParticleSystem *ParticleSystemTemplate::createSlaveSystem( Bool createSlaves ) const { if (m_slaveTemplate == NULL && m_slaveSystemName.isEmpty() == false) m_slaveTemplate = TheParticleSystemManager->findTemplate( m_slaveSystemName ); ParticleSystem *slave = NULL; if (m_slaveTemplate) slave = TheParticleSystemManager->createParticleSystem( m_slaveTemplate, createSlaves ); return slave; } /////////////////////////////////////////////////////////////////////////////////////////////////// // ParticleSystemManager ////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleSystemManager::ParticleSystemManager( void ) { m_uniqueSystemID = INVALID_PARTICLE_SYSTEM_ID; m_onScreenParticleCount = 0; m_localPlayerIndex = 0; //Added By Sadullah Nader //Initializations inserted m_lastLogicFrameUpdate = 0; m_particleCount = 0; m_fieldParticleCount = 0; m_particleSystemCount = 0; // for( Int i = 0; i < NUM_PARTICLE_PRIORITIES; ++i ) { m_allParticlesHead[ i ] = NULL; m_allParticlesTail[ i ] = NULL; } // end for, i } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ParticleSystemManager::~ParticleSystemManager() { reset(); TemplateMap::iterator begin(m_templateMap.begin()); TemplateMap::iterator end(m_templateMap.end()); for (; begin != end; ++begin) { (*begin).second->deleteInstance(); } } // ------------------------------------------------------------------------------------------------ /** Initialize the manager */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::init( void ) { /// Read INI data and build templates INI ini; ini.load( AsciiString( "Data\\INI\\ParticleSystem.ini" ), INI_LOAD_OVERWRITE, NULL ); // sanity, our lists must be empty!! for( Int i = 0; i < NUM_PARTICLE_PRIORITIES; ++i ) { // sanity DEBUG_ASSERTCRASH( m_allParticlesHead[ i ] == NULL, ("INIT: ParticleSystem all particles head[%d] is not NULL!\n", i) ); DEBUG_ASSERTCRASH( m_allParticlesTail[ i ] == NULL, ("INIT: ParticleSystem all particles tail[%d] is not NULL!\n", i) ); // just to be clean set them to NULL m_allParticlesHead[ i ] = NULL; m_allParticlesTail[ i ] = NULL; } // end for, i } // ------------------------------------------------------------------------------------------------ /** Reset the manager and all particle systems */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::reset( void ) { while (getParticleSystemCount()) { if (m_allParticleSystemList.front()) { m_allParticleSystemList.front()->deleteInstance(); } } // sanity, our lists must be empty!! for( Int i = 0; i < NUM_PARTICLE_PRIORITIES; ++i ) { // sanity DEBUG_ASSERTCRASH( m_allParticlesHead[ i ] == NULL, ("RESET: ParticleSystem all particles head[%d] is not NULL!\n", i) ); DEBUG_ASSERTCRASH( m_allParticlesTail[ i ] == NULL, ("RESET: ParticleSystem all particles tail[%d] is not NULL!\n", i) ); // just to be clean set them to NULL m_allParticlesHead[ i ] = NULL; m_allParticlesTail[ i ] = NULL; } // end for, i m_particleCount = 0; m_fieldParticleCount = 0; m_particleSystemCount = 0; m_uniqueSystemID = INVALID_PARTICLE_SYSTEM_ID; m_lastLogicFrameUpdate = -1; // leave templates as-is } // ------------------------------------------------------------------------------------------------ /** Update all particle systems */ // ------------------------------------------------------------------------------------------------ //DECLARE_PERF_TIMER(ParticleSystemManager) void ParticleSystemManager::update( void ) { if (m_lastLogicFrameUpdate == TheGameLogic->getFrame()) { return; } // update the last logic frame. m_lastLogicFrameUpdate = TheGameLogic->getFrame(); //USE_PERF_TIMER(ParticleSystemManager) ParticleSystem *sys; for(ParticleSystemListIt it = m_allParticleSystemList.begin(); it != m_allParticleSystemList.end();) { sys = (*it); if (!sys) { continue; } if (sys->update(m_localPlayerIndex) == false) { ++it; sys->deleteInstance(); } else { ++it; } } } // ------------------------------------------------------------------------------------------------ /** sets the count of the particles on screen after each frame */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::setOnScreenParticleCount(int count) { m_onScreenParticleCount = count; } // ------------------------------------------------------------------------------------------------ /** Given a file containing particle system properties, create a new instance of it */ // ------------------------------------------------------------------------------------------------ ParticleSystem *ParticleSystemManager::createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves ) { // sanity if (sysTemplate == NULL) return NULL; m_uniqueSystemID = (ParticleSystemID)((UnsignedInt)m_uniqueSystemID + 1); ParticleSystem *sys = newInstance(ParticleSystem)( sysTemplate, m_uniqueSystemID, createSlaves ); return sys; } // ------------------------------------------------------------------------------------------------ /// given a template, instantiate a particle system attached to the given object, and return its ID // ------------------------------------------------------------------------------------------------ ParticleSystemID ParticleSystemManager::createAttachedParticleSystemID( const ParticleSystemTemplate *sysTemplate, Object* attachTo, Bool createSlaves ) { ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem(sysTemplate, createSlaves); if (pSystem && attachTo) pSystem->attachToObject(attachTo); return pSystem ? pSystem->getSystemID() : INVALID_PARTICLE_SYSTEM_ID; } // ------------------------------------------------------------------------------------------------ /** Find a particle system with the matching system id */ // ------------------------------------------------------------------------------------------------ ParticleSystem *ParticleSystemManager::findParticleSystem( ParticleSystemID id ) { if (id == INVALID_PARTICLE_SYSTEM_ID) return NULL; // my, that was easy ParticleSystem *system = NULL; for( ParticleSystemListIt it = m_allParticleSystemList.begin(); it != m_allParticleSystemList.end(); ++it ) { system = *it; if (!system) { continue; } if( system->getSystemID() == id ) { return system; } } return NULL; } // end findParticleSystem // ------------------------------------------------------------------------------------------------ /** destroy the particle system with the given id (if it still exists) */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::destroyParticleSystemByID(ParticleSystemID id) { ParticleSystem* pSystem = findParticleSystem(id); if( pSystem ) pSystem->destroy(); } // ------------------------------------------------------------------------------------------------ /** Locate an existing ParticleSystemTemplate */ // ------------------------------------------------------------------------------------------------ ParticleSystemTemplate *ParticleSystemManager::findTemplate( const AsciiString &name ) const { ParticleSystemTemplate *sysTemplate = NULL; TemplateMap::const_iterator find(m_templateMap.find(name)); if (find != m_templateMap.end()) { sysTemplate = (*find).second; } return sysTemplate; } // ------------------------------------------------------------------------------------------------ /** Create a new ParticleSystemTemplate */ // ------------------------------------------------------------------------------------------------ ParticleSystemTemplate *ParticleSystemManager::newTemplate( const AsciiString &name ) { ParticleSystemTemplate *sysTemplate = findTemplate(name); if (sysTemplate == NULL) { sysTemplate = newInstance(ParticleSystemTemplate)( name ); if (! m_templateMap.insert(std::make_pair(name, sysTemplate)).second) { sysTemplate->deleteInstance(); sysTemplate = NULL; } } return sysTemplate; } // ------------------------------------------------------------------------------------------------ /** Find a particle system's parent. Should really only be called by TheScriptEngine */ // ------------------------------------------------------------------------------------------------ ParticleSystemTemplate *ParticleSystemManager::findParentTemplate( const AsciiString &name, Int parentNum ) const { if (name.isEmpty()) { return NULL; } TemplateMap::const_iterator begin(m_templateMap.begin()); TemplateMap::const_iterator end(m_templateMap.end()); for(; begin != end; ++begin) { ParticleSystemTemplate *sysTemplate = (*begin).second; if (name.compare(sysTemplate->m_slaveSystemName) == 0) { if (! parentNum--) { return sysTemplate; } } } return NULL; } // ------------------------------------------------------------------------------------------------ /** Destroy any particle systems that are attached to this object */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::destroyAttachedSystems( Object *obj ) { // sanity if( obj == NULL ) return; // iterate through all systems ParticleSystem *system = NULL; for( ParticleSystemListIt it = m_allParticleSystemList.begin(); it != m_allParticleSystemList.end(); ++it ) { system = *it; if( system == NULL ) continue; if( system->getAttachedObject() == obj->getID() ) system->destroy(); } } // ------------------------------------------------------------------------------------------------ /** Add a particle to the global particle list. */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::addParticle( Particle *particleToAdd, ParticlePriorityType priority ) { if (particleToAdd->m_inOverallList) return; if (!m_allParticlesHead[ priority ]) { m_allParticlesHead[ priority ] = particleToAdd; } if (m_allParticlesTail[ priority ]) { m_allParticlesTail[ priority ]->m_overallNext = particleToAdd; particleToAdd->m_overallPrev = m_allParticlesTail[ priority ]; } else { particleToAdd->m_overallPrev = NULL; } m_allParticlesTail[ priority ] = particleToAdd; particleToAdd->m_overallNext = NULL; particleToAdd->m_inOverallList = TRUE; ++m_particleCount; } // ------------------------------------------------------------------------------------------------ /** Remove a particle from the global particle list. */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::removeParticle( Particle *particleToRemove) { if (!particleToRemove->m_inOverallList) return; // get priority of the particle we're removing ParticlePriorityType priority = particleToRemove->getPriority(); // remove links from prev & next objs if (particleToRemove->m_overallNext) particleToRemove->m_overallNext->m_overallPrev = particleToRemove->m_overallPrev; if (particleToRemove->m_overallPrev) particleToRemove->m_overallPrev->m_overallNext = particleToRemove->m_overallNext; // update head & tail if neccessary if (particleToRemove == m_allParticlesHead[ priority ]) m_allParticlesHead[ priority ] = particleToRemove->m_overallNext; if (particleToRemove == m_allParticlesTail[ priority ]) m_allParticlesTail[ priority ] = particleToRemove->m_overallPrev; particleToRemove->m_overallNext = particleToRemove->m_overallPrev = NULL; particleToRemove->m_inOverallList = FALSE; --m_particleCount; } // ------------------------------------------------------------------------------------------------ /** Add a particle system to the master particle system list. */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::friend_addParticleSystem( ParticleSystem *particleSystemToAdd ) { m_allParticleSystemList.push_back(particleSystemToAdd); ++m_particleSystemCount; } // ------------------------------------------------------------------------------------------------ /** Remove a particle system from the master particle system list. */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::friend_removeParticleSystem( ParticleSystem *particleSystemToRemove ) { ParticleSystemListIt it = std::find(m_allParticleSystemList.begin(), m_allParticleSystemList.end(), particleSystemToRemove); if (it != m_allParticleSystemList.end()) { m_allParticleSystemList.erase(it); --m_particleSystemCount; } } // ------------------------------------------------------------------------------------------------ /** Remove the oldest N number of particles from the lowest priority lists first. We will * not remove particles from any priorities higher or equal to the priorityCap parameter. */ // ------------------------------------------------------------------------------------------------ Int ParticleSystemManager::removeOldestParticles( UnsignedInt count, ParticlePriorityType priorityCap ) { Int countToRemove = count; while (count-- && getParticleCount()) { for( Int i = PARTICLE_PRIORITY_LOWEST; i < priorityCap; ++i ) { if( m_allParticlesHead[ i ] ) { m_allParticlesHead[ i ]->deleteInstance(); break; // exit for } } } // return the number of particles actually removed return countToRemove - count; } // ------------------------------------------------------------------------------------------------ /** Preload particle system textures */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::preloadAssets( TimeOfDay timeOfDay ) { TemplateMap::iterator begin(m_templateMap.begin()); TemplateMap::iterator end(m_templateMap.end()); for (; begin != end; ++begin) { const ParticleSystemTemplate *tmplate = (*begin).second; if (tmplate->m_particleType == ParticleSystemInfo::PARTICLE && (! tmplate->m_particleTypeName.isEmpty())) { TheDisplay->preloadTextureAssets(tmplate->m_particleTypeName); } } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // unique system ID counter xfer->xferUser( &m_uniqueSystemID, sizeof( ParticleSystemID ) ); // count of particle systems in the world UnsignedInt systemCount = m_particleSystemCount; xfer->xferUnsignedInt( &systemCount ); // particle systems data AsciiString systemName; ParticleSystem *system; if( xfer->getXferMode() == XFER_SAVE ) { // iterate each particle system ParticleSystemListIt it; for( it = m_allParticleSystemList.begin(); it != m_allParticleSystemList.end(); ++it ) { systemCount--; // get system system = *it; // ignore destroyed systems and non-saveable systems if( system->isDestroyed() == TRUE || system->isSaveable() == FALSE ) { AsciiString mtString = ""; xfer->xferAsciiString(&mtString); // write null string as key for destroyed system. continue; } // write template name systemName = system->getTemplate()->getName(); xfer->xferAsciiString( &systemName ); // write system data xfer->xferSnapshot( system ); } // end for, it DEBUG_ASSERTCRASH(systemCount==0, ("Mismatch in write count.")); } // end if, save else { const ParticleSystemTemplate *systemTemplate; // read each particle system for( UnsignedInt i = 0; i < systemCount; ++i ) { // read system name and find template xfer->xferAsciiString( &systemName ); if (systemName.isEmpty()) { continue; // destroyed particle system. } systemTemplate = findTemplate( systemName ); // sanity if( systemTemplate == NULL ) { DEBUG_CRASH(( "ParticleSystemManager::xfer - Unknown particle system template '%s'\n", systemName.str() )); throw SC_INVALID_DATA; } // end if // create system system = createParticleSystem( systemTemplate, FALSE ); if( system == NULL ) { DEBUG_CRASH(( "ParticleSystemManager::xfer - Unable to allocate particle system '%s'\n", systemName.str() )); throw SC_INVALID_DATA; } // end if // read system data xfer->xferSnapshot( system ); } // end for, i } // end else, load } // end particleSystemManager // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ParticleSystemManager::loadPostProcess( void ) { } // end loadPostProcess // ------------------------------------------------------------------------------------------------ /** Output particle system statistics to the screen * @todo Implement a real console (MSB) */ // ------------------------------------------------------------------------------------------------ void ParticleSystemDebugDisplay( DebugDisplayInterface *dd, void *, FILE *fp ) { if (!dd) return; dd->setCursorPos( 0, 0 ); dd->setRightMargin( 2 ); dd->printf( "Total Particles: %d\n", TheParticleSystemManager->getParticleCount() ); dd->printf( "Total Particles (On Screen): %d\n", TheParticleSystemManager->getOnScreenParticleCount()); dd->printf( "Total Particle Systems: %d\n", TheParticleSystemManager->getParticleSystemCount() ); ParticleSystemManager::ParticleSystemList list = TheParticleSystemManager->getAllParticleSystems(); ParticleSystemManager::ParticleSystemList::iterator it; std::map templateMap; std::map templateMapParticleCount; std::map::iterator templateMapIt; std::map::iterator templateMapParticleCountIt; for ( it = list.begin(); it != list.end(); ++it ) { AsciiString templateName = (*it)->getTemplate()->getName(); templateMapIt = templateMap.find(templateName); if (templateMapIt == templateMap.end()) { templateMap.insert(std::make_pair(templateName, 1)); templateMapParticleCount.insert(std::make_pair(templateName, (*it)->getParticleCount())); } else { ++templateMapIt->second; templateMapParticleCountIt = templateMapParticleCount.find(templateName); if (templateMapParticleCountIt != templateMapParticleCount.end()) templateMapParticleCountIt->second += (*it)->getParticleCount(); } } for (templateMapIt = templateMap.begin(); templateMapIt != templateMap.end(); ++templateMapIt) { templateMapParticleCountIt = templateMapParticleCount.find(templateMapIt->first); dd->printf(" %s: %d instances", templateMapIt->first.str(), templateMapIt->second); if (templateMapParticleCountIt != templateMapParticleCount.end()) dd->printf(" (Avg per system %.2f)", INT_TO_REAL(templateMapParticleCountIt->second) / templateMapIt->second); dd->printf("\n"); } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ static Real angleBetween(const Coord2D *vecA, const Coord2D *vecB) { if (!(vecA && vecA->length() && vecB && vecB->length())) { return 0.0; } Real lengthA = vecA->length(); Real lengthB = vecB->length(); Real dotProduct = (vecA->x * vecB->x + vecA->y * vecB->y); Real cosTheta = dotProduct / (lengthA * lengthB); // If the dotproduct is 0.0, then they are orthogonal if (dotProduct == 0.0f) { if (vecB->x > 0) { return PI; } return 0.0f; } Real theta = ACos( cosTheta ); if (vecB->x > 0) { return theta; } return -theta; }