Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2027 lines (1710 sloc)
68.1 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //----------------------------------------------------------------------------- | |
| // Copyright (c) 2012 GarageGames, LLC | |
| // | |
| // Permission is hereby granted, free of charge, to any person obtaining a copy | |
| // of this software and associated documentation files (the "Software"), to | |
| // deal in the Software without restriction, including without limitation the | |
| // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
| // sell copies of the Software, and to permit persons to whom the Software is | |
| // furnished to do so, subject to the following conditions: | |
| // | |
| // The above copyright notice and this permission notice shall be included in | |
| // all copies or substantial portions of the Software. | |
| // | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| // IN THE SOFTWARE. | |
| //----------------------------------------------------------------------------- | |
| #include "platform/platform.h" | |
| #include "T3D/fx/particleEmitter.h" | |
| #include "scene/sceneManager.h" | |
| #include "scene/sceneRenderState.h" | |
| #include "console/consoleTypes.h" | |
| #include "console/typeValidators.h" | |
| #include "core/stream/bitStream.h" | |
| #include "core/strings/stringUnit.h" | |
| #include "math/mRandom.h" | |
| #include "gfx/gfxDevice.h" | |
| #include "gfx/primBuilder.h" | |
| #include "gfx/gfxStringEnumTranslate.h" | |
| #include "renderInstance/renderPassManager.h" | |
| #include "T3D/gameBase/gameProcess.h" | |
| #include "lighting/lightInfo.h" | |
| #include "console/engineAPI.h" | |
| #if defined(TORQUE_OS_XENON) | |
| # include "gfx/D3D9/360/gfx360MemVertexBuffer.h" | |
| #endif | |
| Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 ); | |
| const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f; | |
| IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData); | |
| IMPLEMENT_CONOBJECT(ParticleEmitter); | |
| ConsoleDocClass( ParticleEmitter, | |
| "@brief This object is responsible for spawning particles.\n\n" | |
| "@note This class is not normally instantiated directly - to place a simple " | |
| "particle emitting object in the scene, use a ParticleEmitterNode instead.\n\n" | |
| "This class is the main interface for creating particles - though it is " | |
| "usually only accessed from within another object like ParticleEmitterNode " | |
| "or WheeledVehicle. If using this object class (via C++) directly, be aware " | |
| "that it does <b>not</b> track changes in source axis or velocity over the " | |
| "course of a single update, so emitParticles should be called at a fairly " | |
| "fine grain. The emitter will potentially track the last particle to be " | |
| "created into the next call to this function in order to create a uniformly " | |
| "random time distribution of the particles.\n\n" | |
| "If the object to which the emitter is attached is in motion, it should try " | |
| "to ensure that for call (n+1) to this function, start is equal to the end " | |
| "from call (n). This will ensure a uniform spatial distribution.\n\n" | |
| "@ingroup FX\n" | |
| "@see ParticleEmitterData\n" | |
| "@see ParticleEmitterNode\n" | |
| ); | |
| ConsoleDocClass( ParticleEmitterData, | |
| "@brief Defines particle emission properties such as ejection angle, period " | |
| "and velocity for a ParticleEmitter.\n\n" | |
| "@tsexample\n" | |
| "datablock ParticleEmitterData( GrenadeExpDustEmitter )\n" | |
| "{\n" | |
| " ejectionPeriodMS = 1;\n" | |
| " periodVarianceMS = 0;\n" | |
| " ejectionVelocity = 15;\n" | |
| " velocityVariance = 0.0;\n" | |
| " ejectionOffset = 0.0;\n" | |
| " thetaMin = 85;\n" | |
| " thetaMax = 85;\n" | |
| " phiReferenceVel = 0;\n" | |
| " phiVariance = 360;\n" | |
| " overrideAdvance = false;\n" | |
| " lifetimeMS = 200;\n" | |
| " particles = \"GrenadeExpDust\";\n" | |
| "};\n" | |
| "@endtsexample\n\n" | |
| "@ingroup FX\n" | |
| "@see ParticleEmitter\n" | |
| "@see ParticleData\n" | |
| "@see ParticleEmitterNode\n" | |
| ); | |
| static const F32 sgDefaultEjectionOffset = 0.f; | |
| static const F32 sgDefaultPhiReferenceVel = 0.f; | |
| static const F32 sgDefaultPhiVariance = 360.f; | |
| //----------------------------------------------------------------------------- | |
| // ParticleEmitterData | |
| //----------------------------------------------------------------------------- | |
| ParticleEmitterData::ParticleEmitterData() | |
| { | |
| VECTOR_SET_ASSOCIATION(particleDataBlocks); | |
| VECTOR_SET_ASSOCIATION(dataBlockIds); | |
| ejectionPeriodMS = 100; // 10 Particles Per second | |
| periodVarianceMS = 0; // exactly | |
| ejectionVelocity = 2.0f; // From 1.0 - 3.0 meters per sec | |
| velocityVariance = 1.0f; | |
| ejectionOffset = sgDefaultEjectionOffset; // ejection from the emitter point | |
| ejectionOffsetVariance = 0.0f; | |
| thetaMin = 0.0f; // All heights | |
| thetaMax = 90.0f; | |
| phiReferenceVel = sgDefaultPhiReferenceVel; // All directions | |
| phiVariance = sgDefaultPhiVariance; | |
| softnessDistance = 1.0f; | |
| ambientFactor = 0.0f; | |
| lifetimeMS = 0; | |
| lifetimeVarianceMS = 0; | |
| overrideAdvance = true; | |
| orientParticles = false; | |
| orientOnVelocity = true; | |
| useEmitterSizes = false; | |
| useEmitterColors = false; | |
| particleString = NULL; | |
| partListInitSize = 0; | |
| // These members added for support of user defined blend factors | |
| // and optional particle sorting. | |
| blendStyle = ParticleRenderInst::BlendUndefined; | |
| sortParticles = false; | |
| renderReflection = true; | |
| glow = false; | |
| reverseOrder = false; | |
| textureName = 0; | |
| textureHandle = 0; | |
| highResOnly = true; | |
| alignParticles = false; | |
| alignDirection = Point3F(0.0f, 1.0f, 0.0f); | |
| } | |
| // Enum tables used for fields blendStyle, srcBlendFactor, dstBlendFactor. | |
| // Note that the enums for srcBlendFactor and dstBlendFactor are consistent | |
| // with the blending enums used in Torque Game Builder. | |
| typedef ParticleRenderInst::BlendStyle ParticleBlendStyle; | |
| DefineEnumType( ParticleBlendStyle ); | |
| ImplementEnumType( ParticleBlendStyle, | |
| "The type of visual blending style to apply to the particles.\n" | |
| "@ingroup FX\n\n") | |
| { ParticleRenderInst::BlendNormal, "NORMAL", "No blending style.\n" }, | |
| { ParticleRenderInst::BlendAdditive, "ADDITIVE", "Adds the color of the pixel to the frame buffer with full alpha for each pixel.\n" }, | |
| { ParticleRenderInst::BlendSubtractive, "SUBTRACTIVE", "Subtractive Blending. Reverses the color model, causing dark colors to have a stronger visual effect.\n" }, | |
| { ParticleRenderInst::BlendPremultAlpha, "PREMULTALPHA", "Color blends with the colors of the imagemap rather than the alpha.\n" }, | |
| EndImplementEnumType; | |
| IRangeValidator ejectPeriodIValidator(1, 2047); | |
| IRangeValidator periodVarianceIValidator(0, 2047); | |
| FRangeValidator ejectionFValidator(0.f, 655.35f); | |
| FRangeValidator velVarianceFValidator(0.f, 163.83f); | |
| FRangeValidator thetaFValidator(0.f, 180.f); | |
| FRangeValidator phiFValidator(0.f, 360.f); | |
| //----------------------------------------------------------------------------- | |
| // initPersistFields | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitterData::initPersistFields() | |
| { | |
| addGroup( "ParticleEmitterData" ); | |
| addFieldV("ejectionPeriodMS", TYPEID< S32 >(), Offset(ejectionPeriodMS, ParticleEmitterData), &ejectPeriodIValidator, | |
| "Time (in milliseconds) between each particle ejection." ); | |
| addFieldV("periodVarianceMS", TYPEID< S32 >(), Offset(periodVarianceMS, ParticleEmitterData), &periodVarianceIValidator, | |
| "Variance in ejection period, from 1 - ejectionPeriodMS." ); | |
| addFieldV( "ejectionVelocity", TYPEID< F32 >(), Offset(ejectionVelocity, ParticleEmitterData), &ejectionFValidator, | |
| "Particle ejection velocity." ); | |
| addFieldV( "velocityVariance", TYPEID< F32 >(), Offset(velocityVariance, ParticleEmitterData), &velVarianceFValidator, | |
| "Variance for ejection velocity, from 0 - ejectionVelocity." ); | |
| addFieldV( "ejectionOffset", TYPEID< F32 >(), Offset(ejectionOffset, ParticleEmitterData), &ejectionFValidator, | |
| "Distance along ejection Z axis from which to eject particles." ); | |
| addFieldV( "ejectionOffsetVariance", TYPEID< F32 >(), Offset(ejectionOffsetVariance, ParticleEmitterData), &ejectionFValidator, | |
| "Distance Padding along ejection Z axis from which to eject particles." ); | |
| addFieldV( "thetaMin", TYPEID< F32 >(), Offset(thetaMin, ParticleEmitterData), &thetaFValidator, | |
| "Minimum angle, from the horizontal plane, to eject from." ); | |
| addFieldV( "thetaMax", TYPEID< F32 >(), Offset(thetaMax, ParticleEmitterData), &thetaFValidator, | |
| "Maximum angle, from the horizontal plane, to eject particles from." ); | |
| addFieldV( "phiReferenceVel", TYPEID< F32 >(), Offset(phiReferenceVel, ParticleEmitterData), &phiFValidator, | |
| "Reference angle, from the vertical plane, to eject particles from." ); | |
| addFieldV( "phiVariance", TYPEID< F32 >(), Offset(phiVariance, ParticleEmitterData), &phiFValidator, | |
| "Variance from the reference angle, from 0 - 360." ); | |
| addField( "softnessDistance", TYPEID< F32 >(), Offset(softnessDistance, ParticleEmitterData), | |
| "For soft particles, the distance (in meters) where particles will be " | |
| "faded based on the difference in depth between the particle and the " | |
| "scene geometry." ); | |
| addField( "ambientFactor", TYPEID< F32 >(), Offset(ambientFactor, ParticleEmitterData), | |
| "Used to generate the final particle color by controlling interpolation " | |
| "between the particle color and the particle color multiplied by the " | |
| "ambient light color." ); | |
| addField( "overrideAdvance", TYPEID< bool >(), Offset(overrideAdvance, ParticleEmitterData), | |
| "If false, particles emitted in the same frame have their positions " | |
| "adjusted. If true, adjustment is skipped and particles will clump " | |
| "together." ); | |
| addField( "orientParticles", TYPEID< bool >(), Offset(orientParticles, ParticleEmitterData), | |
| "If true, Particles will always face the camera." ); | |
| addField( "orientOnVelocity", TYPEID< bool >(), Offset(orientOnVelocity, ParticleEmitterData), | |
| "If true, particles will be oriented to face in the direction they are moving." ); | |
| addField( "particles", TYPEID< StringTableEntry >(), Offset(particleString, ParticleEmitterData), | |
| "@brief List of space or TAB delimited ParticleData datablock names.\n\n" | |
| "A random one of these datablocks is selected each time a particle is " | |
| "emitted." ); | |
| addField( "lifetimeMS", TYPEID< S32 >(), Offset(lifetimeMS, ParticleEmitterData), | |
| "Lifetime of emitted particles (in milliseconds)." ); | |
| addField("lifetimeVarianceMS", TYPEID< S32 >(), Offset(lifetimeVarianceMS, ParticleEmitterData), | |
| "Variance in particle lifetime from 0 - lifetimeMS." ); | |
| addField( "useEmitterSizes", TYPEID< bool >(), Offset(useEmitterSizes, ParticleEmitterData), | |
| "@brief If true, use emitter specified sizes instead of datablock sizes.\n" | |
| "Useful for Debris particle emitters that control the particle size." ); | |
| addField( "useEmitterColors", TYPEID< bool >(), Offset(useEmitterColors, ParticleEmitterData), | |
| "@brief If true, use emitter specified colors instead of datablock colors.\n\n" | |
| "Useful for ShapeBase dust and WheeledVehicle wheel particle emitters that use " | |
| "the current material to control particle color." ); | |
| /// These fields added for support of user defined blend factors and optional particle sorting. | |
| //@{ | |
| addField( "blendStyle", TYPEID< ParticleRenderInst::BlendStyle >(), Offset(blendStyle, ParticleEmitterData), | |
| "String value that controls how emitted particles blend with the scene." ); | |
| addField( "sortParticles", TYPEID< bool >(), Offset(sortParticles, ParticleEmitterData), | |
| "If true, particles are sorted furthest to nearest."); | |
| addField( "reverseOrder", TYPEID< bool >(), Offset(reverseOrder, ParticleEmitterData), | |
| "@brief If true, reverses the normal draw order of particles.\n\n" | |
| "Particles are normally drawn from newest to oldest, or in Z order " | |
| "(furthest first) if sortParticles is true. Setting this field to " | |
| "true will reverse that order: oldest first, or nearest first if " | |
| "sortParticles is true." ); | |
| addField( "textureName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleEmitterData), | |
| "Optional texture to override ParticleData::textureName." ); | |
| addField( "alignParticles", TYPEID< bool >(), Offset(alignParticles, ParticleEmitterData), | |
| "If true, particles always face along the axis defined by alignDirection." ); | |
| addProtectedField( "alignDirection", TYPEID< Point3F>(), Offset(alignDirection, ParticleEmitterData), &ParticleEmitterData::_setAlignDirection, &defaultProtectedGetFn, | |
| "The direction aligned particles should face, only valid if alignParticles is true." ); | |
| addField( "highResOnly", TYPEID< bool >(), Offset(highResOnly, ParticleEmitterData), | |
| "This particle system should not use the mixed-resolution renderer. " | |
| "If your particle system has large amounts of overdraw, consider " | |
| "disabling this option." ); | |
| addField( "renderReflection", TYPEID< bool >(), Offset(renderReflection, ParticleEmitterData), | |
| "Controls whether particles are rendered onto reflective surfaces like water." ); | |
| addField("glow", TYPEID< bool >(), Offset(glow, ParticleEmitterData), | |
| "If true, the particles are rendered to the glow buffer as well."); | |
| //@} | |
| endGroup( "ParticleEmitterData" ); | |
| Parent::initPersistFields(); | |
| } | |
| bool ParticleEmitterData::_setAlignDirection( void *object, const char *index, const char *data ) | |
| { | |
| ParticleEmitterData *p = static_cast<ParticleEmitterData*>( object ); | |
| Con::setData( TypePoint3F, &p->alignDirection, 0, 1, &data ); | |
| p->alignDirection.normalizeSafe(); | |
| // we already set the field | |
| return false; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // packData | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitterData::packData(BitStream* stream) | |
| { | |
| Parent::packData(stream); | |
| stream->writeInt(ejectionPeriodMS, 11); // must match limit on valid range in ParticleEmitterData::initPersistFields | |
| stream->writeInt(periodVarianceMS, 11); | |
| stream->writeInt((S32)(ejectionVelocity * 100), 16); | |
| stream->writeInt((S32)(velocityVariance * 100), 14); | |
| if( stream->writeFlag( ejectionOffset != sgDefaultEjectionOffset ) ) | |
| stream->writeInt((S32)(ejectionOffset * 100), 16); | |
| if( stream->writeFlag( ejectionOffsetVariance != 0.0f ) ) | |
| stream->writeInt((S32)(ejectionOffsetVariance * 100), 16); | |
| stream->writeRangedU32((U32)thetaMin, 0, 180); | |
| stream->writeRangedU32((U32)thetaMax, 0, 180); | |
| if( stream->writeFlag( phiReferenceVel != sgDefaultPhiReferenceVel ) ) | |
| stream->writeRangedU32((U32)phiReferenceVel, 0, 360); | |
| if( stream->writeFlag( phiVariance != sgDefaultPhiVariance ) ) | |
| stream->writeRangedU32((U32)phiVariance, 0, 360); | |
| stream->write( softnessDistance ); | |
| stream->write( ambientFactor ); | |
| stream->writeFlag(overrideAdvance); | |
| stream->writeFlag(orientParticles); | |
| stream->writeFlag(orientOnVelocity); | |
| stream->write( lifetimeMS ); | |
| stream->write( lifetimeVarianceMS ); | |
| stream->writeFlag(useEmitterSizes); | |
| stream->writeFlag(useEmitterColors); | |
| stream->write(dataBlockIds.size()); | |
| for (U32 i = 0; i < dataBlockIds.size(); i++) | |
| stream->write(dataBlockIds[i]); | |
| stream->writeFlag(sortParticles); | |
| stream->writeFlag(reverseOrder); | |
| if (stream->writeFlag(textureName != 0)) | |
| stream->writeString(textureName); | |
| if (stream->writeFlag(alignParticles)) | |
| { | |
| stream->write(alignDirection.x); | |
| stream->write(alignDirection.y); | |
| stream->write(alignDirection.z); | |
| } | |
| stream->writeFlag(highResOnly); | |
| stream->writeFlag(renderReflection); | |
| stream->writeFlag(glow); | |
| stream->writeInt( blendStyle, 4 ); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // unpackData | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitterData::unpackData(BitStream* stream) | |
| { | |
| Parent::unpackData(stream); | |
| ejectionPeriodMS = stream->readInt(11); | |
| periodVarianceMS = stream->readInt(11); | |
| ejectionVelocity = stream->readInt(16) / 100.0f; | |
| velocityVariance = stream->readInt(14) / 100.0f; | |
| if( stream->readFlag() ) | |
| ejectionOffset = stream->readInt(16) / 100.0f; | |
| else | |
| ejectionOffset = sgDefaultEjectionOffset; | |
| if( stream->readFlag() ) | |
| ejectionOffsetVariance = stream->readInt(16) / 100.0f; | |
| else | |
| ejectionOffsetVariance = 0.0f; | |
| thetaMin = (F32)stream->readRangedU32(0, 180); | |
| thetaMax = (F32)stream->readRangedU32(0, 180); | |
| if( stream->readFlag() ) | |
| phiReferenceVel = (F32)stream->readRangedU32(0, 360); | |
| else | |
| phiReferenceVel = sgDefaultPhiReferenceVel; | |
| if( stream->readFlag() ) | |
| phiVariance = (F32)stream->readRangedU32(0, 360); | |
| else | |
| phiVariance = sgDefaultPhiVariance; | |
| stream->read( &softnessDistance ); | |
| stream->read( &ambientFactor ); | |
| overrideAdvance = stream->readFlag(); | |
| orientParticles = stream->readFlag(); | |
| orientOnVelocity = stream->readFlag(); | |
| stream->read( &lifetimeMS ); | |
| stream->read( &lifetimeVarianceMS ); | |
| useEmitterSizes = stream->readFlag(); | |
| useEmitterColors = stream->readFlag(); | |
| U32 size; stream->read(&size); | |
| dataBlockIds.setSize(size); | |
| for (U32 i = 0; i < dataBlockIds.size(); i++) | |
| stream->read(&dataBlockIds[i]); | |
| sortParticles = stream->readFlag(); | |
| reverseOrder = stream->readFlag(); | |
| textureName = (stream->readFlag()) ? stream->readSTString() : 0; | |
| alignParticles = stream->readFlag(); | |
| if (alignParticles) | |
| { | |
| stream->read(&alignDirection.x); | |
| stream->read(&alignDirection.y); | |
| stream->read(&alignDirection.z); | |
| } | |
| highResOnly = stream->readFlag(); | |
| renderReflection = stream->readFlag(); | |
| glow = stream->readFlag(); | |
| blendStyle = stream->readInt( 4 ); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // onAdd | |
| //----------------------------------------------------------------------------- | |
| bool ParticleEmitterData::onAdd() | |
| { | |
| if( Parent::onAdd() == false ) | |
| return false; | |
| // if (overrideAdvance == true) { | |
| // Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData: Not going to work. Fix it!"); | |
| // return false; | |
| // } | |
| // Validate the parameters... | |
| // | |
| if( ejectionPeriodMS < 1 ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) period < 1 ms", getName()); | |
| ejectionPeriodMS = 1; | |
| } | |
| if( periodVarianceMS >= ejectionPeriodMS ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) periodVariance >= period", getName()); | |
| periodVarianceMS = ejectionPeriodMS - 1; | |
| } | |
| if( ejectionVelocity < 0.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionVelocity < 0.0f", getName()); | |
| ejectionVelocity = 0.0f; | |
| } | |
| if( velocityVariance < 0.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance < 0.0f", getName()); | |
| velocityVariance = 0.0f; | |
| } | |
| if( velocityVariance > ejectionVelocity ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance > ejectionVelocity", getName()); | |
| velocityVariance = ejectionVelocity; | |
| } | |
| if( ejectionOffset < 0.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName()); | |
| ejectionOffset = 0.0f; | |
| } | |
| if( thetaMin < 0.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin < 0.0", getName()); | |
| thetaMin = 0.0f; | |
| } | |
| if( thetaMax > 180.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMax > 180.0", getName()); | |
| thetaMax = 180.0f; | |
| } | |
| if( thetaMin > thetaMax ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin > thetaMax", getName()); | |
| thetaMin = thetaMax; | |
| } | |
| if( phiVariance < 0.0f || phiVariance > 360.0f ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid phiVariance", getName()); | |
| phiVariance = phiVariance < 0.0f ? 0.0f : 360.0f; | |
| } | |
| if ( softnessDistance < 0.0f ) | |
| { | |
| Con::warnf( ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid softnessDistance", getName() ); | |
| softnessDistance = 0.0f; | |
| } | |
| if (particleString == NULL && dataBlockIds.size() == 0) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); | |
| return false; | |
| } | |
| if (particleString && particleString[0] == '\0') | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); | |
| return false; | |
| } | |
| if (particleString && dStrlen(particleString) > 255) | |
| { | |
| Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particle string too long [> 255 chars]", getName()); | |
| return false; | |
| } | |
| if( lifetimeMS < 0 ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeMS < 0.0f", getName()); | |
| lifetimeMS = 0; | |
| } | |
| if( lifetimeVarianceMS > lifetimeMS ) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeVarianceMS >= lifetimeMS", getName()); | |
| lifetimeVarianceMS = lifetimeMS; | |
| } | |
| // load the particle datablocks... | |
| // | |
| if( particleString != NULL ) | |
| { | |
| // particleString is once again a list of particle datablocks so it | |
| // must be parsed to extract the particle references. | |
| // First we parse particleString into a list of particle name tokens | |
| Vector<char*> dataBlocks(__FILE__, __LINE__); | |
| char* tokCopy = new char[dStrlen(particleString) + 1]; | |
| dStrcpy(tokCopy, particleString); | |
| char* currTok = dStrtok(tokCopy, " \t"); | |
| while (currTok != NULL) | |
| { | |
| dataBlocks.push_back(currTok); | |
| currTok = dStrtok(NULL, " \t"); | |
| } | |
| if (dataBlocks.size() == 0) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName()); | |
| delete [] tokCopy; | |
| return false; | |
| } | |
| // Now we convert the particle name tokens into particle datablocks and IDs | |
| particleDataBlocks.clear(); | |
| dataBlockIds.clear(); | |
| for (U32 i = 0; i < dataBlocks.size(); i++) | |
| { | |
| ParticleData* pData = NULL; | |
| if (Sim::findObject(dataBlocks[i], pData) == false) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]); | |
| } | |
| else | |
| { | |
| particleDataBlocks.push_back(pData); | |
| dataBlockIds.push_back(pData->getId()); | |
| } | |
| } | |
| // cleanup | |
| delete [] tokCopy; | |
| // check that we actually found some particle datablocks | |
| if (particleDataBlocks.size() == 0) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // preload | |
| //----------------------------------------------------------------------------- | |
| bool ParticleEmitterData::preload(bool server, String &errorStr) | |
| { | |
| if( Parent::preload(server, errorStr) == false ) | |
| return false; | |
| particleDataBlocks.clear(); | |
| for (U32 i = 0; i < dataBlockIds.size(); i++) | |
| { | |
| ParticleData* pData = NULL; | |
| if (Sim::findObject(dataBlockIds[i], pData) == false) | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %d", getName(), dataBlockIds[i]); | |
| else | |
| particleDataBlocks.push_back(pData); | |
| } | |
| if (!server) | |
| { | |
| // load emitter texture if specified | |
| if (textureName && textureName[0]) | |
| { | |
| textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); | |
| if (!textureHandle) | |
| { | |
| errorStr = String::ToString("Missing particle emitter texture: %s", textureName); | |
| return false; | |
| } | |
| } | |
| // otherwise, check that all particles refer to the same texture | |
| else if (particleDataBlocks.size() > 1) | |
| { | |
| StringTableEntry txr_name = particleDataBlocks[0]->textureName; | |
| for (S32 i = 1; i < particleDataBlocks.size(); i++) | |
| { | |
| // warn if particle textures are inconsistent | |
| if (particleDataBlocks[i]->textureName != txr_name) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles reference different textures.", getName()); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // if blend-style is undefined check legacy useInvAlpha settings | |
| if (blendStyle == ParticleRenderInst::BlendUndefined && particleDataBlocks.size() > 0) | |
| { | |
| bool useInvAlpha = particleDataBlocks[0]->useInvAlpha; | |
| for (S32 i = 1; i < particleDataBlocks.size(); i++) | |
| { | |
| // warn if blend-style legacy useInvAlpha settings are inconsistent | |
| if (particleDataBlocks[i]->useInvAlpha != useInvAlpha) | |
| { | |
| Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles have inconsistent useInvAlpha settings.", getName()); | |
| break; | |
| } | |
| } | |
| blendStyle = (useInvAlpha) ? ParticleRenderInst::BlendNormal : ParticleRenderInst::BlendAdditive; | |
| } | |
| if( !server ) | |
| { | |
| allocPrimBuffer(); | |
| } | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // alloc PrimitiveBuffer | |
| // The datablock allocates this static index buffer because it's the same | |
| // for all of the emitters - each particle quad uses the same index ordering | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) | |
| { | |
| // calculate particle list size | |
| AssertFatal(particleDataBlocks.size() > 0, "Error, no particles found." ); | |
| U32 maxPartLife = particleDataBlocks[0]->lifetimeMS + particleDataBlocks[0]->lifetimeVarianceMS; | |
| for (S32 i = 1; i < particleDataBlocks.size(); i++) | |
| { | |
| U32 mpl = particleDataBlocks[i]->lifetimeMS + particleDataBlocks[i]->lifetimeVarianceMS; | |
| if (mpl > maxPartLife) | |
| maxPartLife = mpl; | |
| } | |
| partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS); | |
| partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1 | |
| // if override size is specified, then the emitter overran its buffer and needs a larger allocation | |
| if( overrideSize != -1 ) | |
| { | |
| partListInitSize = overrideSize; | |
| } | |
| // create index buffer based on that size | |
| U32 indexListSize = partListInitSize * 6; // 6 indices per particle | |
| U16 *indices = new U16[ indexListSize ]; | |
| for( U32 i=0; i<partListInitSize; i++ ) | |
| { | |
| // this index ordering should be optimal (hopefully) for the vertex cache | |
| U16 *idx = &indices[i*6]; | |
| volatile U32 offset = i * 4; // set to volatile to fix VC6 Release mode compiler bug | |
| idx[0] = 0 + offset; | |
| idx[1] = 1 + offset; | |
| idx[2] = 3 + offset; | |
| idx[3] = 1 + offset; | |
| idx[4] = 3 + offset; | |
| idx[5] = 2 + offset; | |
| } | |
| U16 *ibIndices; | |
| GFXBufferType bufferType = GFXBufferTypeStatic; | |
| #ifdef TORQUE_OS_XENON | |
| // Because of the way the volatile buffers work on Xenon this is the only | |
| // way to do this. | |
| bufferType = GFXBufferTypeVolatile; | |
| #endif | |
| primBuff.set( GFX, indexListSize, 0, bufferType ); | |
| primBuff.lock( &ibIndices ); | |
| dMemcpy( ibIndices, indices, indexListSize * sizeof(U16) ); | |
| primBuff.unlock(); | |
| delete [] indices; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // ParticleEmitter | |
| //----------------------------------------------------------------------------- | |
| ParticleEmitter::ParticleEmitter() | |
| { | |
| mDeleteWhenEmpty = false; | |
| mDeleteOnTick = false; | |
| mInternalClock = 0; | |
| mNextParticleTime = 0; | |
| mLastPosition.set(0, 0, 0); | |
| mHasLastPosition = false; | |
| mLifetimeMS = 0; | |
| mElapsedTimeMS = 0; | |
| part_store = 0; | |
| part_freelist = NULL; | |
| part_list_head.next = NULL; | |
| n_part_capacity = 0; | |
| n_parts = 0; | |
| mCurBuffSize = 0; | |
| mDead = false; | |
| mDataBlock = NULL; | |
| // ParticleEmitter should be allocated on the client only. | |
| mNetFlags.set( IsGhost ); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // destructor | |
| //----------------------------------------------------------------------------- | |
| ParticleEmitter::~ParticleEmitter() | |
| { | |
| for( S32 i = 0; i < part_store.size(); i++ ) | |
| { | |
| delete [] part_store[i]; | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // onAdd | |
| //----------------------------------------------------------------------------- | |
| bool ParticleEmitter::onAdd() | |
| { | |
| if( !Parent::onAdd() ) | |
| return false; | |
| // add to client side mission cleanup | |
| SimGroup *cleanup = dynamic_cast<SimGroup *>( Sim::findObject( "ClientMissionCleanup") ); | |
| if( cleanup != NULL ) | |
| { | |
| cleanup->addObject( this ); | |
| } | |
| removeFromProcessList(); | |
| F32 radius = 5.0; | |
| mObjBox.minExtents = Point3F(-radius, -radius, -radius); | |
| mObjBox.maxExtents = Point3F(radius, radius, radius); | |
| resetWorldBox(); | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // onRemove | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::onRemove() | |
| { | |
| removeFromScene(); | |
| Parent::onRemove(); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // onNewDataBlock | |
| //----------------------------------------------------------------------------- | |
| bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload ) | |
| { | |
| mDataBlock = dynamic_cast<ParticleEmitterData*>( dptr ); | |
| if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) | |
| return false; | |
| mLifetimeMS = mDataBlock->lifetimeMS; | |
| if( mDataBlock->lifetimeVarianceMS ) | |
| { | |
| mLifetimeMS += S32( gRandGen.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); | |
| } | |
| // Allocate particle structures and init the freelist. Member part_store | |
| // is a Vector so that we can allocate more particles if partListInitSize | |
| // turns out to be too small. | |
| // | |
| if (mDataBlock->partListInitSize > 0) | |
| { | |
| for( S32 i = 0; i < part_store.size(); i++ ) | |
| { | |
| delete [] part_store[i]; | |
| } | |
| part_store.clear(); | |
| n_part_capacity = mDataBlock->partListInitSize; | |
| Particle* store_block = new Particle[n_part_capacity]; | |
| part_store.push_back(store_block); | |
| part_freelist = store_block; | |
| Particle* last_part = part_freelist; | |
| Particle* part = last_part+1; | |
| for( S32 i = 1; i < n_part_capacity; i++, part++, last_part++ ) | |
| { | |
| last_part->next = part; | |
| } | |
| store_block[n_part_capacity-1].next = NULL; | |
| part_list_head.next = NULL; | |
| n_parts = 0; | |
| } | |
| scriptOnNewDataBlock(); | |
| return true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // getCollectiveColor | |
| //----------------------------------------------------------------------------- | |
| ColorF ParticleEmitter::getCollectiveColor() | |
| { | |
| U32 count = 0; | |
| ColorF color = ColorF(0.0f, 0.0f, 0.0f); | |
| count = n_parts; | |
| for( Particle* part = part_list_head.next; part != NULL; part = part->next ) | |
| { | |
| color += part->color; | |
| } | |
| if(count > 0) | |
| { | |
| color /= F32(count); | |
| } | |
| //if(color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f) | |
| // color = color; | |
| return color; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // prepRenderImage | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::prepRenderImage(SceneRenderState* state) | |
| { | |
| if( state->isReflectPass() && !getDataBlock()->renderReflection ) | |
| return; | |
| // Never render into shadows. | |
| if (state->isShadowPass()) | |
| return; | |
| PROFILE_SCOPE(ParticleEmitter_prepRenderImage); | |
| if ( mDead || | |
| n_parts == 0 || | |
| part_list_head.next == NULL ) | |
| return; | |
| RenderPassManager *renderManager = state->getRenderPass(); | |
| const Point3F &camPos = state->getCameraPosition(); | |
| copyToVB( camPos, state->getAmbientLightColor() ); | |
| if (!mVertBuff.isValid()) | |
| return; | |
| ParticleRenderInst *ri = renderManager->allocInst<ParticleRenderInst>(); | |
| ri->vertBuff = &mVertBuff; | |
| ri->primBuff = &getDataBlock()->primBuff; | |
| ri->translucentSort = true; | |
| ri->type = RenderPassManager::RIT_Particle; | |
| ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos ); | |
| // Draw the system offscreen unless the highResOnly flag is set on the datablock | |
| ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw ); | |
| ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * | |
| GFX->getViewMatrix() * | |
| GFX->getWorldMatrix() ); | |
| // Update position on the matrix before multiplying it | |
| mBBObjToWorld.setPosition(mLastPosition); | |
| ri->bbModelViewProj = renderManager->allocUniqueXform( *ri->modelViewProj * mBBObjToWorld ); | |
| ri->count = n_parts; | |
| ri->blendStyle = mDataBlock->blendStyle; | |
| ri->glow = mDataBlock->glow; | |
| // use first particle's texture unless there is an emitter texture to override it | |
| if (mDataBlock->textureHandle) | |
| ri->diffuseTex = &*(mDataBlock->textureHandle); | |
| else | |
| ri->diffuseTex = &*(part_list_head.next->dataBlock->textureHandle); | |
| ri->softnessDistance = mDataBlock->softnessDistance; | |
| // Sort by texture too. | |
| ri->defaultKey = ri->diffuseTex ? (uintptr_t)ri->diffuseTex : (uintptr_t)ri->vertBuff; | |
| renderManager->addInst( ri ); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // setSizes | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::setSizes( F32 *sizeList ) | |
| { | |
| for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) | |
| { | |
| sizes[i] = sizeList[i]; | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // setColors | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::setColors( ColorF *colorList ) | |
| { | |
| for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ ) | |
| { | |
| colors[i] = colorList[i]; | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // deleteWhenEmpty | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::deleteWhenEmpty() | |
| { | |
| // if the following asserts fire, there is a reasonable chance that you are trying to delete a particle emitter | |
| // that has already been deleted (possibly by ClientMissionCleanup). If so, use a SimObjectPtr to the emitter and check it | |
| // for null before calling this function. | |
| AssertFatal(isProperlyAdded(), "ParticleEmitter must be registed before calling deleteWhenEmpty"); | |
| AssertFatal(!mDead, "ParticleEmitter already deleted"); | |
| AssertFatal(!isDeleted(), "ParticleEmitter already deleted"); | |
| AssertFatal(!isRemoved(), "ParticleEmitter already removed"); | |
| // this check is for non debug case, so that we don't write in to freed memory | |
| bool okToDelete = !mDead && isProperlyAdded() && !isDeleted() && !isRemoved(); | |
| if (okToDelete) | |
| { | |
| mDeleteWhenEmpty = true; | |
| if( !n_parts ) | |
| { | |
| // We're already empty, so delete us now. | |
| mDead = true; | |
| deleteObject(); | |
| } | |
| else | |
| AssertFatal( getSceneManager() != NULL, "ParticleEmitter not on process list and won't get ticked to death" ); | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // emitParticles | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::emitParticles(const Point3F& point, | |
| const bool useLastPosition, | |
| const Point3F& axis, | |
| const Point3F& velocity, | |
| const U32 numMilliseconds) | |
| { | |
| if( mDead ) return; | |
| // lifetime over - no more particles | |
| if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) | |
| { | |
| return; | |
| } | |
| Point3F realStart; | |
| if( useLastPosition && mHasLastPosition ) | |
| realStart = mLastPosition; | |
| else | |
| realStart = point; | |
| emitParticles(realStart, point, | |
| axis, | |
| velocity, | |
| numMilliseconds); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // emitParticles | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::emitParticles(const Point3F& start, | |
| const Point3F& end, | |
| const Point3F& axis, | |
| const Point3F& velocity, | |
| const U32 numMilliseconds) | |
| { | |
| if( mDead ) return; | |
| if( mDataBlock->particleDataBlocks.empty() ) | |
| return; | |
| // lifetime over - no more particles | |
| if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) | |
| { | |
| return; | |
| } | |
| U32 currTime = 0; | |
| bool particlesAdded = false; | |
| Point3F axisx; | |
| if( mFabs(axis.z) < 0.9f ) | |
| mCross(axis, Point3F(0, 0, 1), &axisx); | |
| else | |
| mCross(axis, Point3F(0, 1, 0), &axisx); | |
| axisx.normalize(); | |
| if( mNextParticleTime != 0 ) | |
| { | |
| // Need to handle next particle | |
| // | |
| if( mNextParticleTime > numMilliseconds ) | |
| { | |
| // Defer to next update | |
| // (Note that this introduces a potential spatial irregularity if the owning | |
| // object is accelerating, and updating at a low frequency) | |
| // | |
| mNextParticleTime -= numMilliseconds; | |
| mInternalClock += numMilliseconds; | |
| mLastPosition = end; | |
| mHasLastPosition = true; | |
| return; | |
| } | |
| else | |
| { | |
| currTime += mNextParticleTime; | |
| mInternalClock += mNextParticleTime; | |
| // Emit particle at curr time | |
| // Create particle at the correct position | |
| Point3F pos; | |
| pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); | |
| addParticle(pos, axis, velocity, axisx); | |
| particlesAdded = true; | |
| mNextParticleTime = 0; | |
| } | |
| } | |
| while( currTime < numMilliseconds ) | |
| { | |
| S32 nextTime = mDataBlock->ejectionPeriodMS; | |
| if( mDataBlock->periodVarianceMS != 0 ) | |
| { | |
| nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) - | |
| S32(mDataBlock->periodVarianceMS); | |
| } | |
| AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0"); | |
| if( currTime + nextTime > numMilliseconds ) | |
| { | |
| mNextParticleTime = (currTime + nextTime) - numMilliseconds; | |
| mInternalClock += numMilliseconds - currTime; | |
| AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!"); | |
| break; | |
| } | |
| currTime += nextTime; | |
| mInternalClock += nextTime; | |
| // Create particle at the correct position | |
| Point3F pos; | |
| pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); | |
| addParticle(pos, axis, velocity, axisx); | |
| particlesAdded = true; | |
| // This override-advance code is restored in order to correctly adjust | |
| // animated parameters of particles allocated within the same frame | |
| // update. Note that ordering is important and this code correctly | |
| // adds particles in the same newest-to-oldest ordering of the link-list. | |
| // | |
| // NOTE: We are assuming that the just added particle is at the head of our | |
| // list. If that changes, so must this... | |
| U32 advanceMS = numMilliseconds - currTime; | |
| if (mDataBlock->overrideAdvance == false && advanceMS != 0) | |
| { | |
| Particle* last_part = part_list_head.next; | |
| if (advanceMS > last_part->totalLifetime) | |
| { | |
| part_list_head.next = last_part->next; | |
| n_parts--; | |
| last_part->next = part_freelist; | |
| part_freelist = last_part; | |
| } | |
| else | |
| { | |
| if (advanceMS != 0) | |
| { | |
| F32 t = F32(advanceMS) / 1000.0; | |
| Point3F a = last_part->acc; | |
| a -= last_part->vel * last_part->dataBlock->dragCoefficient; | |
| a -= mWindVelocity * last_part->dataBlock->windCoefficient; | |
| a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; | |
| last_part->vel += a * t; | |
| last_part->pos += last_part->vel * t; | |
| updateKeyData( last_part ); | |
| } | |
| } | |
| } | |
| } | |
| // DMMFIX: Lame and slow... | |
| if( particlesAdded == true ) | |
| updateBBox(); | |
| if( n_parts > 0 && getSceneManager() == NULL ) | |
| { | |
| gClientSceneGraph->addObjectToScene(this); | |
| ClientProcessList::get()->addObject(this); | |
| } | |
| mLastPosition = end; | |
| mHasLastPosition = true; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // emitParticles | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::emitParticles(const Point3F& rCenter, | |
| const Point3F& rNormal, | |
| const F32 radius, | |
| const Point3F& velocity, | |
| S32 count) | |
| { | |
| if( mDead ) return; | |
| // lifetime over - no more particles | |
| if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) | |
| { | |
| return; | |
| } | |
| Point3F axisx, axisy; | |
| Point3F axisz = rNormal; | |
| if( axisz.isZero() ) | |
| { | |
| axisz.set( 0.0, 0.0, 1.0 ); | |
| } | |
| if( mFabs(axisz.z) < 0.98 ) | |
| { | |
| mCross(axisz, Point3F(0, 0, 1), &axisy); | |
| axisy.normalize(); | |
| } | |
| else | |
| { | |
| mCross(axisz, Point3F(0, 1, 0), &axisy); | |
| axisy.normalize(); | |
| } | |
| mCross(axisz, axisy, &axisx); | |
| axisx.normalize(); | |
| // Should think of a better way to distribute the | |
| // particles within the hemisphere. | |
| for( S32 i = 0; i < count; i++ ) | |
| { | |
| Point3F pos = axisx * (radius * (1 - (2 * gRandGen.randF()))); | |
| pos += axisy * (radius * (1 - (2 * gRandGen.randF()))); | |
| pos += axisz * (radius * gRandGen.randF()); | |
| Point3F axis = pos; | |
| axis.normalize(); | |
| pos += rCenter; | |
| addParticle(pos, axis, velocity, axisz); | |
| } | |
| // Set world bounding box | |
| mObjBox.minExtents = rCenter - Point3F(radius, radius, radius); | |
| mObjBox.maxExtents = rCenter + Point3F(radius, radius, radius); | |
| resetWorldBox(); | |
| // Make sure we're part of the world | |
| if( n_parts > 0 && getSceneManager() == NULL ) | |
| { | |
| gClientSceneGraph->addObjectToScene(this); | |
| ClientProcessList::get()->addObject(this); | |
| } | |
| mHasLastPosition = false; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // updateBBox - SLOW, bad news | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::updateBBox() | |
| { | |
| Point3F minPt(1e10, 1e10, 1e10); | |
| Point3F maxPt(-1e10, -1e10, -1e10); | |
| for (Particle* part = part_list_head.next; part != NULL; part = part->next) | |
| { | |
| Point3F particleSize(part->size * 0.5f, 0.0f, part->size * 0.5f); | |
| minPt.setMin( part->pos - particleSize ); | |
| maxPt.setMax( part->pos + particleSize ); | |
| } | |
| mObjBox = Box3F(minPt, maxPt); | |
| MatrixF temp = getTransform(); | |
| setTransform(temp); | |
| mBBObjToWorld.identity(); | |
| Point3F boxScale = mObjBox.getExtents(); | |
| boxScale.x = getMax(boxScale.x, 1.0f); | |
| boxScale.y = getMax(boxScale.y, 1.0f); | |
| boxScale.z = getMax(boxScale.z, 1.0f); | |
| mBBObjToWorld.scale(boxScale); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // addParticle | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::addParticle(const Point3F& pos, | |
| const Point3F& axis, | |
| const Point3F& vel, | |
| const Point3F& axisx) | |
| { | |
| n_parts++; | |
| if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize) | |
| { | |
| // In an emergency we allocate additional particles in blocks of 16. | |
| // This should happen rarely. | |
| Particle* store_block = new Particle[16]; | |
| part_store.push_back(store_block); | |
| n_part_capacity += 16; | |
| for (S32 i = 0; i < 16; i++) | |
| { | |
| store_block[i].next = part_freelist; | |
| part_freelist = &store_block[i]; | |
| } | |
| mDataBlock->allocPrimBuffer(n_part_capacity); // allocate larger primitive buffer or will crash | |
| } | |
| Particle* pNew = part_freelist; | |
| part_freelist = pNew->next; | |
| pNew->next = part_list_head.next; | |
| part_list_head.next = pNew; | |
| Point3F ejectionAxis = axis; | |
| F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + | |
| mDataBlock->thetaMin; | |
| F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; | |
| F32 phi = ref + gRandGen.randF() * mDataBlock->phiVariance; | |
| // Both phi and theta are in degs. Create axis angles out of them, and create the | |
| // appropriate rotation matrix... | |
| AngAxisF thetaRot(axisx, theta * (M_PI / 180.0)); | |
| AngAxisF phiRot(axis, phi * (M_PI / 180.0)); | |
| MatrixF temp(true); | |
| thetaRot.setMatrix(&temp); | |
| temp.mulP(ejectionAxis); | |
| phiRot.setMatrix(&temp); | |
| temp.mulP(ejectionAxis); | |
| F32 initialVel = mDataBlock->ejectionVelocity; | |
| initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; | |
| pNew->pos = pos + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) ); | |
| pNew->vel = ejectionAxis * initialVel; | |
| pNew->orientDir = ejectionAxis; | |
| pNew->acc.set(0, 0, 0); | |
| pNew->currentAge = 0; | |
| // Choose a new particle datablack randomly from the list | |
| U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); | |
| mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); | |
| updateKeyData( pNew ); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // processTick | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::processTick(const Move*) | |
| { | |
| if( mDeleteOnTick == true ) | |
| { | |
| mDead = true; | |
| deleteObject(); | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // advanceTime | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::advanceTime(F32 dt) | |
| { | |
| if( dt < 0.00001 ) return; | |
| Parent::advanceTime(dt); | |
| if( dt > 0.5 ) dt = 0.5; | |
| if( mDead ) return; | |
| mElapsedTimeMS += (S32)(dt * 1000.0f); | |
| U32 numMSToUpdate = (U32)(dt * 1000.0f); | |
| if( numMSToUpdate == 0 ) return; | |
| // TODO: Prefetch | |
| // remove dead particles | |
| Particle* last_part = &part_list_head; | |
| for (Particle* part = part_list_head.next; part != NULL; part = part->next) | |
| { | |
| part->currentAge += numMSToUpdate; | |
| if (part->currentAge > part->totalLifetime) | |
| { | |
| n_parts--; | |
| last_part->next = part->next; | |
| part->next = part_freelist; | |
| part_freelist = part; | |
| part = last_part; | |
| } | |
| else | |
| { | |
| last_part = part; | |
| } | |
| } | |
| AssertFatal( n_parts >= 0, "ParticleEmitter: negative part count!" ); | |
| if (n_parts < 1 && mDeleteWhenEmpty) | |
| { | |
| mDeleteOnTick = true; | |
| return; | |
| } | |
| if( numMSToUpdate != 0 && n_parts > 0 ) | |
| { | |
| update( numMSToUpdate ); | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Update key related particle data | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::updateKeyData( Particle *part ) | |
| { | |
| //Ensure that our lifetime is never below 0 | |
| if( part->totalLifetime < 1 ) | |
| part->totalLifetime = 1; | |
| F32 t = F32(part->currentAge) / F32(part->totalLifetime); | |
| AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); | |
| for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ ) | |
| { | |
| if( part->dataBlock->times[i] >= t ) | |
| { | |
| F32 firstPart = t - part->dataBlock->times[i-1]; | |
| F32 total = part->dataBlock->times[i] - | |
| part->dataBlock->times[i-1]; | |
| firstPart /= total; | |
| if( mDataBlock->useEmitterColors ) | |
| { | |
| part->color.interpolate(colors[i-1], colors[i], firstPart); | |
| } | |
| else | |
| { | |
| part->color.interpolate(part->dataBlock->colors[i-1], | |
| part->dataBlock->colors[i], | |
| firstPart); | |
| } | |
| if( mDataBlock->useEmitterSizes ) | |
| { | |
| part->size = (sizes[i-1] * (1.0 - firstPart)) + | |
| (sizes[i] * firstPart); | |
| } | |
| else | |
| { | |
| part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) + | |
| (part->dataBlock->sizes[i] * firstPart); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Update particles | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::update( U32 ms ) | |
| { | |
| // TODO: Prefetch | |
| for (Particle* part = part_list_head.next; part != NULL; part = part->next) | |
| { | |
| F32 t = F32(ms) / 1000.0; | |
| Point3F a = part->acc; | |
| a -= part->vel * part->dataBlock->dragCoefficient; | |
| a -= mWindVelocity * part->dataBlock->windCoefficient; | |
| a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient; | |
| part->vel += a * t; | |
| part->pos += part->vel * t; | |
| updateKeyData( part ); | |
| } | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Copy particles to vertex buffer | |
| //----------------------------------------------------------------------------- | |
| // structure used for particle sorting. | |
| struct SortParticle | |
| { | |
| Particle* p; | |
| F32 k; | |
| }; | |
| // qsort callback function for particle sorting | |
| S32 QSORT_CALLBACK cmpSortParticles(const void* p1, const void* p2) | |
| { | |
| const SortParticle* sp1 = (const SortParticle*)p1; | |
| const SortParticle* sp2 = (const SortParticle*)p2; | |
| if (sp2->k > sp1->k) | |
| return 1; | |
| else if (sp2->k == sp1->k) | |
| return 0; | |
| else | |
| return -1; | |
| } | |
| void ParticleEmitter::copyToVB( const Point3F &camPos, const ColorF &ambientColor ) | |
| { | |
| static Vector<SortParticle> orderedVector(__FILE__, __LINE__); | |
| PROFILE_START(ParticleEmitter_copyToVB); | |
| PROFILE_START(ParticleEmitter_copyToVB_Sort); | |
| // build sorted list of particles (far to near) | |
| if (mDataBlock->sortParticles) | |
| { | |
| orderedVector.clear(); | |
| MatrixF modelview = GFX->getWorldMatrix(); | |
| Point3F viewvec; modelview.getRow(1, &viewvec); | |
| // add each particle and a distance based sort key to orderedVector | |
| for (Particle* pp = part_list_head.next; pp != NULL; pp = pp->next) | |
| { | |
| orderedVector.increment(); | |
| orderedVector.last().p = pp; | |
| orderedVector.last().k = mDot(pp->pos, viewvec); | |
| } | |
| // qsort the list into far to near ordering | |
| dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); | |
| } | |
| PROFILE_END(); | |
| #if defined(TORQUE_OS_XENON) | |
| // Allocate writecombined since we don't read back from this buffer (yay!) | |
| if(mVertBuff.isNull()) | |
| mVertBuff = new GFX360MemVertexBuffer(GFX, 1, getGFXVertexFormat<ParticleVertexType>(), sizeof(ParticleVertexType), GFXBufferTypeDynamic, PAGE_WRITECOMBINE); | |
| if( n_parts > mCurBuffSize ) | |
| { | |
| mCurBuffSize = n_parts; | |
| mVertBuff.resize(n_parts * 4); | |
| } | |
| ParticleVertexType *buffPtr = mVertBuff.lock(); | |
| #else | |
| static Vector<ParticleVertexType> tempBuff(2048); | |
| tempBuff.reserve( n_parts*4 + 64); // make sure tempBuff is big enough | |
| ParticleVertexType *buffPtr = tempBuff.address(); // use direct pointer (faster) | |
| #endif | |
| if (mDataBlock->orientParticles) | |
| { | |
| PROFILE_START(ParticleEmitter_copyToVB_Orient); | |
| if (mDataBlock->reverseOrder) | |
| { | |
| buffPtr += 4*(n_parts-1); | |
| // do sorted-oriented particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle* partPtr = orderedVector.address(); | |
| for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) | |
| setupOriented(partPtr->p, camPos, ambientColor, buffPtr); | |
| } | |
| // do unsorted-oriented particles | |
| else | |
| { | |
| for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) | |
| setupOriented(partPtr, camPos, ambientColor, buffPtr); | |
| } | |
| } | |
| else | |
| { | |
| // do sorted-oriented particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle* partPtr = orderedVector.address(); | |
| for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) | |
| setupOriented(partPtr->p, camPos, ambientColor, buffPtr); | |
| } | |
| // do unsorted-oriented particles | |
| else | |
| { | |
| for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) | |
| setupOriented(partPtr, camPos, ambientColor, buffPtr); | |
| } | |
| } | |
| PROFILE_END(); | |
| } | |
| else if (mDataBlock->alignParticles) | |
| { | |
| PROFILE_START(ParticleEmitter_copyToVB_Aligned); | |
| if (mDataBlock->reverseOrder) | |
| { | |
| buffPtr += 4*(n_parts-1); | |
| // do sorted-oriented particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle* partPtr = orderedVector.address(); | |
| for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) | |
| setupAligned(partPtr->p, ambientColor, buffPtr); | |
| } | |
| // do unsorted-oriented particles | |
| else | |
| { | |
| Particle *partPtr = part_list_head.next; | |
| for (; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) | |
| setupAligned(partPtr, ambientColor, buffPtr); | |
| } | |
| } | |
| else | |
| { | |
| // do sorted-oriented particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle* partPtr = orderedVector.address(); | |
| for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) | |
| setupAligned(partPtr->p, ambientColor, buffPtr); | |
| } | |
| // do unsorted-oriented particles | |
| else | |
| { | |
| Particle *partPtr = part_list_head.next; | |
| for (; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) | |
| setupAligned(partPtr, ambientColor, buffPtr); | |
| } | |
| } | |
| PROFILE_END(); | |
| } | |
| else | |
| { | |
| PROFILE_START(ParticleEmitter_copyToVB_NonOriented); | |
| // somewhat odd ordering so that texture coordinates match the oriented | |
| // particles | |
| Point3F basePoints[4]; | |
| basePoints[0] = Point3F(-1.0, 0.0, 1.0); | |
| basePoints[1] = Point3F(-1.0, 0.0, -1.0); | |
| basePoints[2] = Point3F( 1.0, 0.0, -1.0); | |
| basePoints[3] = Point3F( 1.0, 0.0, 1.0); | |
| MatrixF camView = GFX->getWorldMatrix(); | |
| camView.transpose(); // inverse - this gets the particles facing camera | |
| if (mDataBlock->reverseOrder) | |
| { | |
| buffPtr += 4*(n_parts-1); | |
| // do sorted-billboard particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle *partPtr = orderedVector.address(); | |
| for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr-=4 ) | |
| setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); | |
| } | |
| // do unsorted-billboard particles | |
| else | |
| { | |
| for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) | |
| setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); | |
| } | |
| } | |
| else | |
| { | |
| // do sorted-billboard particles | |
| if (mDataBlock->sortParticles) | |
| { | |
| SortParticle *partPtr = orderedVector.address(); | |
| for( U32 i=0; i<n_parts; i++, partPtr++, buffPtr+=4 ) | |
| setupBillboard( partPtr->p, basePoints, camView, ambientColor, buffPtr ); | |
| } | |
| // do unsorted-billboard particles | |
| else | |
| { | |
| for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) | |
| setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); | |
| } | |
| } | |
| PROFILE_END(); | |
| } | |
| #if defined(TORQUE_OS_XENON) | |
| mVertBuff.unlock(); | |
| #else | |
| PROFILE_START(ParticleEmitter_copyToVB_LockCopy); | |
| // create new VB if emitter size grows | |
| if( !mVertBuff || n_parts > mCurBuffSize ) | |
| { | |
| mCurBuffSize = n_parts; | |
| mVertBuff.set( GFX, n_parts * 4, GFXBufferTypeDynamic ); | |
| } | |
| // lock and copy tempBuff to video RAM | |
| ParticleVertexType *verts = mVertBuff.lock(); | |
| dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(ParticleVertexType) ); | |
| mVertBuff.unlock(); | |
| PROFILE_END(); | |
| #endif | |
| PROFILE_END(); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Set up particle for billboard style render | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::setupBillboard( Particle *part, | |
| Point3F *basePts, | |
| const MatrixF &camView, | |
| const ColorF &ambientColor, | |
| ParticleVertexType *lVerts ) | |
| { | |
| F32 width = part->size * 0.5f; | |
| F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; | |
| F32 sy, cy; | |
| mSinCos(spinAngle, sy, cy); | |
| const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); | |
| ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); | |
| // fill four verts, use macro and unroll loop | |
| #define fillVert(){ \ | |
| lVerts->point.x = cy * basePts->x - sy * basePts->z; \ | |
| lVerts->point.y = 0.0f; \ | |
| lVerts->point.z = sy * basePts->x + cy * basePts->z; \ | |
| camView.mulV( lVerts->point ); \ | |
| lVerts->point *= width; \ | |
| lVerts->point += part->pos; \ | |
| lVerts->color = partCol; } \ | |
| // Here we deal with UVs for animated particle (billboard) | |
| if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty()) | |
| { | |
| S32 fm = (S32)(part->currentAge*(1.0/1000.0)*part->dataBlock->framesPerSec); | |
| U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; | |
| S32 uv[4]; | |
| uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; | |
| uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); | |
| uv[2] = uv[1] + 1; | |
| uv[3] = uv[0] + 1; | |
| fillVert(); | |
| // Here and below, we copy UVs from particle datablock's current frame's UVs (billboard) | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; | |
| ++lVerts; | |
| ++basePts; | |
| return; | |
| } | |
| fillVert(); | |
| // Here and below, we copy UVs from particle datablock's texCoords (billboard) | |
| lVerts->texCoord = part->dataBlock->texCoords[0]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->texCoords[1]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->texCoords[2]; | |
| ++lVerts; | |
| ++basePts; | |
| fillVert(); | |
| lVerts->texCoord = part->dataBlock->texCoords[3]; | |
| ++lVerts; | |
| ++basePts; | |
| } | |
| //----------------------------------------------------------------------------- | |
| // Set up oriented particle | |
| //----------------------------------------------------------------------------- | |
| void ParticleEmitter::setupOriented( Particle *part, | |
| const Point3F &camPos, | |
| const ColorF &ambientColor, | |
| ParticleVertexType *lVerts ) | |
| { | |
| Point3F dir; | |
| if( mDataBlock->orientOnVelocity ) | |
| { | |
| // don't render oriented particle if it has no velocity | |
| if( part->vel.magnitudeSafe() == 0.0 ) return; | |
| dir = part->vel; | |
| } | |
| else | |
| { | |
| dir = part->orientDir; | |
| } | |
| Point3F dirFromCam = part->pos - camPos; | |
| Point3F crossDir; | |
| mCross( dirFromCam, dir, &crossDir ); | |
| crossDir.normalize(); | |
| dir.normalize(); | |
| F32 width = part->size * 0.5f; | |
| dir *= width; | |
| crossDir *= width; | |
| Point3F start = part->pos - dir; | |
| Point3F end = part->pos + dir; | |
| const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); | |
| ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); | |
| // Here we deal with UVs for animated particle (oriented) | |
| if (part->dataBlock->animateTexture) | |
| { | |
| // Let particle compute the UV indices for current frame | |
| S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); | |
| U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; | |
| S32 uv[4]; | |
| uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; | |
| uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); | |
| uv[2] = uv[1] + 1; | |
| uv[3] = uv[0] + 1; | |
| lVerts->point = start + crossDir; | |
| lVerts->color = partCol; | |
| // Here and below, we copy UVs from particle datablock's current frame's UVs (oriented) | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; | |
| ++lVerts; | |
| lVerts->point = start - crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; | |
| ++lVerts; | |
| lVerts->point = end - crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; | |
| ++lVerts; | |
| lVerts->point = end + crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; | |
| ++lVerts; | |
| return; | |
| } | |
| lVerts->point = start + crossDir; | |
| lVerts->color = partCol; | |
| // Here and below, we copy UVs from particle datablock's texCoords (oriented) | |
| lVerts->texCoord = part->dataBlock->texCoords[1]; | |
| ++lVerts; | |
| lVerts->point = start - crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[2]; | |
| ++lVerts; | |
| lVerts->point = end - crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[3]; | |
| ++lVerts; | |
| lVerts->point = end + crossDir; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[0]; | |
| ++lVerts; | |
| } | |
| void ParticleEmitter::setupAligned( const Particle *part, | |
| const ColorF &ambientColor, | |
| ParticleVertexType *lVerts ) | |
| { | |
| // The aligned direction will always be normalized. | |
| Point3F dir = mDataBlock->alignDirection; | |
| // Find a right vector for this particle. | |
| Point3F right; | |
| if (mFabs(dir.y) > mFabs(dir.z)) | |
| mCross(Point3F::UnitZ, dir, &right); | |
| else | |
| mCross(Point3F::UnitY, dir, &right); | |
| right.normalize(); | |
| // If we have a spin velocity. | |
| if ( !mIsZero( part->spinSpeed ) ) | |
| { | |
| F32 spinAngle = part->spinSpeed * part->currentAge * AgedSpinToRadians; | |
| // This is an inline quaternion vector rotation which | |
| // is faster that QuatF.mulP(), but generates different | |
| // results and hence cannot replace it right now. | |
| F32 sin, qw; | |
| mSinCos( spinAngle * 0.5f, sin, qw ); | |
| F32 qx = dir.x * sin; | |
| F32 qy = dir.y * sin; | |
| F32 qz = dir.z * sin; | |
| F32 vx = ( right.x * qw ) + ( right.z * qy ) - ( right.y * qz ); | |
| F32 vy = ( right.y * qw ) + ( right.x * qz ) - ( right.z * qx ); | |
| F32 vz = ( right.z * qw ) + ( right.y * qx ) - ( right.x * qy ); | |
| F32 vw = ( right.x * qx ) + ( right.y * qy ) + ( right.z * qz ); | |
| right.x = ( qw * vx ) + ( qx * vw ) + ( qy * vz ) - ( qz * vy ); | |
| right.y = ( qw * vy ) + ( qy * vw ) + ( qz * vx ) - ( qx * vz ); | |
| right.z = ( qw * vz ) + ( qz * vw ) + ( qx * vy ) - ( qy * vx ); | |
| } | |
| // Get the cross vector. | |
| Point3F cross; | |
| mCross(right, dir, &cross); | |
| F32 width = part->size * 0.5f; | |
| right *= width; | |
| cross *= width; | |
| Point3F start = part->pos - right; | |
| Point3F end = part->pos + right; | |
| const F32 ambientLerp = mClampF( mDataBlock->ambientFactor, 0.0f, 1.0f ); | |
| ColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp ); | |
| // Here we deal with UVs for animated particle | |
| if (part->dataBlock->animateTexture) | |
| { | |
| // Let particle compute the UV indices for current frame | |
| S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); | |
| U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; | |
| S32 uv[4]; | |
| uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; | |
| uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); | |
| uv[2] = uv[1] + 1; | |
| uv[3] = uv[0] + 1; | |
| lVerts->point = start + cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; | |
| ++lVerts; | |
| lVerts->point = start - cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; | |
| ++lVerts; | |
| lVerts->point = end - cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; | |
| ++lVerts; | |
| lVerts->point = end + cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; | |
| ++lVerts; | |
| } | |
| else | |
| { | |
| // Here and below, we copy UVs from particle datablock's texCoords | |
| lVerts->point = start + cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[0]; | |
| ++lVerts; | |
| lVerts->point = start - cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[1]; | |
| ++lVerts; | |
| lVerts->point = end - cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[2]; | |
| ++lVerts; | |
| lVerts->point = end + cross; | |
| lVerts->color = partCol; | |
| lVerts->texCoord = part->dataBlock->texCoords[3]; | |
| ++lVerts; | |
| } | |
| } | |
| bool ParticleEmitterData::reload() | |
| { | |
| // Clear out current particle data. | |
| dataBlockIds.clear(); | |
| particleDataBlocks.clear(); | |
| // Parse out particle string. | |
| U32 numUnits = 0; | |
| if( particleString ) | |
| numUnits = StringUnit::getUnitCount( particleString, " \t" ); | |
| if( !particleString || !particleString[ 0 ] || !numUnits ) | |
| { | |
| Con::errorf( "ParticleEmitterData(%s) has an empty particles string.", getName() ); | |
| mReloadSignal.trigger(); | |
| return false; | |
| } | |
| for( U32 i = 0; i < numUnits; ++ i ) | |
| { | |
| const char* dbName = StringUnit::getUnit( particleString, i, " \t" ); | |
| ParticleData* data = NULL; | |
| if( !Sim::findObject( dbName, data ) ) | |
| { | |
| Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dbName ); | |
| continue; | |
| } | |
| particleDataBlocks.push_back( data ); | |
| dataBlockIds.push_back( data->getId() ); | |
| } | |
| // Check that we actually found some particle datablocks. | |
| if( particleDataBlocks.empty() ) | |
| { | |
| Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName() ); | |
| mReloadSignal.trigger(); | |
| return false; | |
| } | |
| // Trigger reload. | |
| mReloadSignal.trigger(); | |
| return true; | |
| } | |
| DefineEngineMethod(ParticleEmitterData, reload, void,(),, | |
| "Reloads the ParticleData datablocks and other fields used by this emitter.\n" | |
| "@tsexample\n" | |
| "// Get the editor's current particle emitter\n" | |
| "%emitter = PE_EmitterEditor.currEmitter\n\n" | |
| "// Change a field value\n" | |
| "%emitter.setFieldValue( %propertyField, %value );\n\n" | |
| "// Reload this emitter\n" | |
| "%emitter.reload();\n" | |
| "@endtsexample\n") | |
| { | |
| object->reload(); | |
| } |