Torque3D/Engine/source/T3D/fx/particleEmitter.cpp

2662 lines
89 KiB
C++
Raw Normal View History

2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
2012-09-19 15:15:01 +00:00
#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"
2012-09-19 15:15:01 +00:00
#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(AFX_CAP_PARTICLE_POOLS)
#include "afx/util/afxParticlePool.h"
#endif
2012-09-19 15:15:01 +00:00
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"
2021-02-07 19:43:21 +00:00
" thetaVariance = 0;\n"
2012-09-19 15:15:01 +00:00
" 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;
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// 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;
2012-09-19 15:15:01 +00:00
thetaMin = 0.0f; // All heights
thetaMax = 90.0f;
2021-02-07 19:43:21 +00:00
thetaVariance = 0.0f;
2012-09-19 15:15:01 +00:00
phiReferenceVel = sgDefaultPhiReferenceVel; // All directions
phiVariance = sgDefaultPhiVariance;
softnessDistance = 1.0f;
ambientFactor = 0.0f;
lifetimeMS = 0;
lifetimeVarianceMS = 0;
overrideAdvance = true;
orientParticles = false;
orientOnVelocity = true;
2021-02-07 19:43:21 +00:00
ribbonParticles = false;
2012-09-19 15:15:01 +00:00
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;
2012-09-19 15:15:01 +00:00
reverseOrder = false;
textureName = 0;
textureHandle = 0;
highResOnly = true;
alignParticles = false;
alignDirection = Point3F(0.0f, 1.0f, 0.0f);
ejectionInvert = false;
fade_color = false;
fade_alpha = false;
fade_size = false;
parts_per_eject = 1;
use_emitter_xfm = false;
#if defined(AFX_CAP_PARTICLE_POOLS)
pool_datablock = 0;
pool_index = 0;
pool_depth_fade = false;
pool_radial_fade = false;
do_pool_id_convert = false;
#endif
2012-09-19 15:15:01 +00:00
}
// 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);
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// initPersistFields
//-----------------------------------------------------------------------------
void ParticleEmitterData::initPersistFields()
{
docsURL;
2012-09-19 15:15:01 +00:00
addGroup( "ParticleEmitterData" );
2025-03-09 16:53:23 +00:00
addFieldV("ejectionPeriodMS", TypeRangedS32, Offset(ejectionPeriodMS, ParticleEmitterData), &ejectPeriodIValidator,
2012-09-19 15:15:01 +00:00
"Time (in milliseconds) between each particle ejection." );
2025-03-09 16:53:23 +00:00
addFieldV("periodVarianceMS", TypeRangedS32, Offset(periodVarianceMS, ParticleEmitterData), &periodVarianceIValidator,
2012-09-19 15:15:01 +00:00
"Variance in ejection period, from 1 - ejectionPeriodMS." );
2025-03-09 16:53:23 +00:00
addFieldV( "ejectionVelocity", TypeRangedF32, Offset(ejectionVelocity, ParticleEmitterData), &ejectionFValidator,
2012-09-19 15:15:01 +00:00
"Particle ejection velocity." );
2025-03-09 16:53:23 +00:00
addFieldV( "velocityVariance", TypeRangedF32, Offset(velocityVariance, ParticleEmitterData), &velVarianceFValidator,
2012-09-19 15:15:01 +00:00
"Variance for ejection velocity, from 0 - ejectionVelocity." );
2025-03-09 16:53:23 +00:00
addFieldV( "ejectionOffset", TypeRangedF32, Offset(ejectionOffset, ParticleEmitterData), &ejectionFValidator,
2012-09-19 15:15:01 +00:00
"Distance along ejection Z axis from which to eject particles." );
2025-03-09 16:53:23 +00:00
addFieldV( "ejectionOffsetVariance", TypeRangedF32, Offset(ejectionOffsetVariance, ParticleEmitterData), &ejectionFValidator,
"Distance Padding along ejection Z axis from which to eject particles." );
2012-09-19 15:15:01 +00:00
2025-03-09 16:53:23 +00:00
addFieldV( "thetaMin", TypeRangedF32, Offset(thetaMin, ParticleEmitterData), &CommonValidators::PosDegreeRangeHalf,
2012-09-19 15:15:01 +00:00
"Minimum angle, from the horizontal plane, to eject from." );
2025-03-09 16:53:23 +00:00
addFieldV( "thetaMax", TypeRangedF32, Offset(thetaMax, ParticleEmitterData), &CommonValidators::PosDegreeRangeHalf,
2012-09-19 15:15:01 +00:00
"Maximum angle, from the horizontal plane, to eject particles from." );
2025-03-09 16:53:23 +00:00
addFieldV( "thetaVariance", TypeRangedF32, Offset(thetaVariance, ParticleEmitterData), &CommonValidators::PosDegreeRangeHalf,
2021-02-07 19:43:21 +00:00
"Angle variance from the previous particle, from 0 - 180." );
2025-03-09 16:53:23 +00:00
addFieldV( "phiReferenceVel", TypeRangedF32, Offset(phiReferenceVel, ParticleEmitterData), &CommonValidators::PosDegreeRange,
2012-09-19 15:15:01 +00:00
"Reference angle, from the vertical plane, to eject particles from." );
2025-03-09 16:53:23 +00:00
addFieldV( "phiVariance", TypeRangedF32, Offset(phiVariance, ParticleEmitterData), &CommonValidators::PosDegreeRange,
2012-09-19 15:15:01 +00:00
"Variance from the reference angle, from 0 - 360." );
2025-03-09 16:53:23 +00:00
addFieldV( "softnessDistance", TypeRangedF32, Offset(softnessDistance, ParticleEmitterData), &CommonValidators::PositiveFloat,
2012-09-19 15:15:01 +00:00
"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." );
2025-03-09 16:53:23 +00:00
addFieldV( "ambientFactor", TypeRangedF32, Offset(ambientFactor, ParticleEmitterData), &CommonValidators::NormalizedFloat,
2012-09-19 15:15:01 +00:00
"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." );
2021-02-07 19:43:21 +00:00
addField( "ribbonParticles", TYPEID< bool >(), Offset(ribbonParticles, ParticleEmitterData),
"If true, particles are rendered as a continous ribbon." );
2012-09-19 15:15:01 +00:00
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." );
2025-03-09 16:53:23 +00:00
addFieldV( "lifetimeMS", TypeRangedS32, Offset(lifetimeMS, ParticleEmitterData), &CommonValidators::PositiveInt,
2012-09-19 15:15:01 +00:00
"Lifetime of emitted particles (in milliseconds)." );
2025-03-09 16:53:23 +00:00
addFieldV("lifetimeVarianceMS", TypeRangedS32, Offset(lifetimeVarianceMS, ParticleEmitterData), &CommonValidators::PositiveInt,
2012-09-19 15:15:01 +00:00
"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.");
2012-09-19 15:15:01 +00:00
//@}
endGroup( "ParticleEmitterData" );
addGroup("AFX");
addField("ejectionInvert", TypeBool, Offset(ejectionInvert, ParticleEmitterData));
addField("fadeColor", TypeBool, Offset(fade_color, ParticleEmitterData));
addField("fadeAlpha", TypeBool, Offset(fade_alpha, ParticleEmitterData));
addField("fadeSize", TypeBool, Offset(fade_size, ParticleEmitterData));
// useEmitterTransform currently does not work in TGEA or T3D
addField("useEmitterTransform", TypeBool, Offset(use_emitter_xfm, ParticleEmitterData));
endGroup("AFX");
#if defined(AFX_CAP_PARTICLE_POOLS)
addGroup("AFX Pooled Particles");
addField("poolData", TYPEID<afxParticlePoolData>(), Offset(pool_datablock, ParticleEmitterData));
2025-03-09 16:53:23 +00:00
addFieldV("poolIndex", TypeRangedS32, Offset(pool_index, ParticleEmitterData), &CommonValidators::PositiveInt);
addField("poolDepthFade", TypeBool, Offset(pool_depth_fade, ParticleEmitterData));
addField("poolRadialFade", TypeBool, Offset(pool_radial_fade, ParticleEmitterData));
endGroup("AFX Pooled Particles");
#endif
// disallow some field substitutions
disableFieldSubstitutions("particles");
onlyKeepClearSubstitutions("poolData"); // subs resolving to "~~", or "~0" are OK
2012-09-19 15:15:01 +00:00
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);
2012-09-19 15:15:01 +00:00
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);
2012-09-19 15:15:01 +00:00
stream->writeRangedU32((U32)thetaMin, 0, 180);
stream->writeRangedU32((U32)thetaMax, 0, 180);
2021-02-07 19:43:21 +00:00
stream->writeRangedU32((U32)thetaVariance, 0, 180);
2012-09-19 15:15:01 +00:00
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);
2021-02-07 19:43:21 +00:00
stream->writeFlag(ribbonParticles);
2012-09-19 15:15:01 +00:00
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);
2012-09-19 15:15:01 +00:00
stream->writeInt( blendStyle, 4 );
stream->writeFlag(ejectionInvert);
stream->writeFlag(fade_color);
stream->writeFlag(fade_alpha);
stream->writeFlag(fade_size);
stream->writeFlag(use_emitter_xfm);
#if defined(AFX_CAP_PARTICLE_POOLS)
if (stream->writeFlag(pool_datablock))
{
stream->writeRangedU32(mPacked ? SimObjectId((uintptr_t)pool_datablock) : pool_datablock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
stream->write(pool_index);
stream->writeFlag(pool_depth_fade);
stream->writeFlag(pool_radial_fade);
}
#endif
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
// unpackData
//-----------------------------------------------------------------------------
void ParticleEmitterData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
ejectionPeriodMS = stream->readInt(11);
periodVarianceMS = stream->readInt(11);
2012-09-19 15:15:01 +00:00
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;
2012-09-19 15:15:01 +00:00
thetaMin = (F32)stream->readRangedU32(0, 180);
thetaMax = (F32)stream->readRangedU32(0, 180);
2021-02-07 19:43:21 +00:00
thetaVariance = (F32)stream->readRangedU32(0, 180);
2012-09-19 15:15:01 +00:00
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();
2021-02-07 19:43:21 +00:00
ribbonParticles = stream->readFlag();
2012-09-19 15:15:01 +00:00
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();
2012-09-19 15:15:01 +00:00
blendStyle = stream->readInt( 4 );
ejectionInvert = stream->readFlag();
fade_color = stream->readFlag();
fade_alpha = stream->readFlag();
fade_size = stream->readFlag();
use_emitter_xfm = stream->readFlag();
#if defined(AFX_CAP_PARTICLE_POOLS)
if (stream->readFlag())
{
pool_datablock = (afxParticlePoolData*)(uintptr_t)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
stream->read(&pool_index);
pool_depth_fade = stream->readFlag();
pool_radial_fade = stream->readFlag();
do_pool_id_convert = true;
}
#endif
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
// 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;
}
2021-02-07 19:43:21 +00:00
if( ejectionOffsetVariance < 0.0f )
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName());
ejectionOffsetVariance = 0.0f;
}
2012-09-19 15:15:01 +00:00
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;
}
2021-02-07 19:43:21 +00:00
if( thetaVariance > 180.0f )
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaVariance > 180.0", getName());
thetaVariance = 180.0f;
}
if( thetaVariance < 0.0f )
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaVariance < 0.0", getName());
thetaVariance = 0.0f;
}
2012-09-19 15:15:01 +00:00
if( phiVariance < 0.0f || phiVariance > 360.0f )
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid phiVariance", getName());
phiVariance = phiVariance < 0.0f ? 0.0f : 360.0f;
}
2021-02-07 19:43:21 +00:00
if( thetaVariance < 0.0f || thetaVariance > 180.0f )
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid thetaVariance", getName());
thetaVariance = thetaVariance < 0.0f ? 0.0f : 180.0f;
}
2012-09-19 15:15:01 +00:00
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__);
dsize_t tokLen = dStrlen(particleString) + 1;
char* tokCopy = new char[tokLen];
dStrcpy(tokCopy, particleString, tokLen);
2012-09-19 15:15:01 +00:00
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)
{
#if defined(AFX_CAP_PARTICLE_POOLS)
if (do_pool_id_convert)
{
SimObjectId db_id = (SimObjectId)(uintptr_t)pool_datablock;
if (db_id != 0)
{
// try to convert id to pointer
if (!Sim::findObject(db_id, pool_datablock))
{
Con::errorf("ParticleEmitterData::reload() -- bad datablockId: 0x%x (poolData)", db_id);
}
}
do_pool_id_convert = false;
}
#endif
2012-09-19 15:15:01 +00:00
// load emitter texture if specified
if (textureName && textureName[0])
{
textureHandle = GFXTexHandle(textureName, &GFXStaticTextureSRGBProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__));
2012-09-19 15:15:01 +00:00
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]->getTexture();
2012-09-19 15:15:01 +00:00
for (S32 i = 1; i < particleDataBlocks.size(); i++)
{
// warn if particle textures are inconsistent
if (particleDataBlocks[i]->getTexture() != txr_name)
2012-09-19 15:15:01 +00:00
{
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." );
if (particleDataBlocks.empty()) return;
2012-09-19 15:15:01 +00:00
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 (parts_per_eject > 1)
partListInitSize *= parts_per_eject;
2012-09-19 15:15:01 +00:00
// 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;
primBuff.set( GFX, indexListSize, 0, bufferType );
primBuff.lock( &ibIndices );
dMemcpy( ibIndices, indices, indexListSize * sizeof(U16) );
primBuff.unlock();
delete [] indices;
}
//#define TRACK_PARTICLE_EMITTER_DATA_CLONES
#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
static int emitter_data_clones = 0;
#endif
ParticleEmitterData::ParticleEmitterData(const ParticleEmitterData& other, bool temp_clone) : GameBaseData(other, temp_clone)
{
#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
emitter_data_clones++;
if (emitter_data_clones == 1)
Con::errorf("ParticleEmitterData -- Clones are on the loose!");
#endif
ejectionPeriodMS = other.ejectionPeriodMS;
periodVarianceMS = other.periodVarianceMS;
ejectionVelocity = other.ejectionVelocity;
velocityVariance = other.velocityVariance;
ejectionOffset = other.ejectionOffset;
ejectionOffsetVariance = other.ejectionOffsetVariance;
thetaMin = other.thetaMin;
thetaMax = other.thetaMax;
2021-02-07 19:43:21 +00:00
thetaVariance = other.thetaVariance;
phiReferenceVel = other.phiReferenceVel;
phiVariance = other.phiVariance;
softnessDistance = other.softnessDistance;
ambientFactor = other.ambientFactor;
lifetimeMS = other.lifetimeMS;
lifetimeVarianceMS = other.lifetimeVarianceMS;
overrideAdvance = other.overrideAdvance;
orientParticles = other.orientParticles;
orientOnVelocity = other.orientOnVelocity;
2021-02-07 19:43:21 +00:00
ribbonParticles = other.ribbonParticles;
useEmitterSizes = other.useEmitterSizes;
useEmitterColors = other.useEmitterColors;
alignParticles = other.alignParticles;
alignDirection = other.alignDirection;
particleString = other.particleString;
particleDataBlocks = other.particleDataBlocks; // -- derived from particleString
dataBlockIds = other.dataBlockIds; // -- derived from particleString
partListInitSize = other.partListInitSize; // -- approx calc from other fields
primBuff = other.primBuff;
blendStyle = other.blendStyle;
sortParticles = other.sortParticles;
reverseOrder = other.reverseOrder;
textureName = other.textureName;
textureHandle = other.textureHandle; // -- TextureHandle loads using textureName
highResOnly = other.highResOnly;
2020-05-11 20:30:21 +00:00
glow = other.glow;
renderReflection = other.renderReflection;
fade_color = other.fade_color;
fade_size = other.fade_size;
fade_alpha = other.fade_alpha;
ejectionInvert = other.ejectionInvert;
parts_per_eject = other.parts_per_eject; // -- set to 1 (used by subclasses)
use_emitter_xfm = other.use_emitter_xfm;
#if defined(AFX_CAP_PARTICLE_POOLS)
pool_datablock = other.pool_datablock;
pool_index = other.pool_index;
pool_depth_fade = other.pool_depth_fade;
pool_radial_fade = other.pool_radial_fade;
do_pool_id_convert = other.do_pool_id_convert; // -- flags pool id conversion need
#endif
}
ParticleEmitterData::~ParticleEmitterData()
{
if (!isTempClone())
return;
for (S32 i = 0; i < particleDataBlocks.size(); i++)
{
if (particleDataBlocks[i] && particleDataBlocks[i]->isTempClone())
{
delete particleDataBlocks[i];
particleDataBlocks[i] = 0;
}
}
#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES
if (emitter_data_clones > 0)
{
emitter_data_clones--;
if (emitter_data_clones == 0)
Con::errorf("ParticleEmitterData -- Clones eliminated!");
}
else
Con::errorf("ParticleEmitterData -- Too many clones deleted!");
#endif
}
ParticleEmitterData* ParticleEmitterData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
{
if (!owner)
return this;
bool clone_parts_db = false;
// note -- this could be checked when the particle blocks are evaluated
for (S32 i = 0; i < this->particleDataBlocks.size(); i++)
{
if (this->particleDataBlocks[i] && (this->particleDataBlocks[i]->getSubstitutionCount() > 0))
{
clone_parts_db = true;
break;
}
}
ParticleEmitterData* sub_emitter_db = this;
if (this->getSubstitutionCount() > 0 || clone_parts_db)
{
sub_emitter_db = new ParticleEmitterData(*this, true);
performSubstitutions(sub_emitter_db, owner, index);
if (clone_parts_db)
{
for (S32 i = 0; i < sub_emitter_db->particleDataBlocks.size(); i++)
{
if (sub_emitter_db->particleDataBlocks[i] && (sub_emitter_db->particleDataBlocks[i]->getSubstitutionCount() > 0))
{
ParticleData* orig_db = sub_emitter_db->particleDataBlocks[i];
sub_emitter_db->particleDataBlocks[i] = new ParticleData(*orig_db, true);
orig_db->performSubstitutions(sub_emitter_db->particleDataBlocks[i], owner, index);
}
}
}
}
return sub_emitter_db;
}
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// 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;
2021-02-07 19:43:21 +00:00
mThetaOld = 0;
mPhiOld = 0;
2012-09-19 15:15:01 +00:00
mCurBuffSize = 0;
mDead = false;
mDataBlock = NULL;
2012-09-19 15:15:01 +00:00
// ParticleEmitter should be allocated on the client only.
mNetFlags.set( IsGhost );
fade_amt = 1.0f;
forced_bbox = false;
db_temp_clone = false;
pos_pe.set(0,0,0);
sort_priority = 0;
mDataBlock = 0;
2020-05-11 20:30:21 +00:00
std::fill_n(sizes, ParticleData::PDC_NUM_KEYS, 0.0f);
#if defined(AFX_CAP_PARTICLE_POOLS)
pool = 0;
#endif
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
// destructor
//-----------------------------------------------------------------------------
ParticleEmitter::~ParticleEmitter()
{
for( S32 i = 0; i < part_store.size(); i++ )
{
delete [] part_store[i];
}
if (db_temp_clone && mDataBlock && mDataBlock->isTempClone())
{
for (S32 i = 0; i < mDataBlock->particleDataBlocks.size(); i++)
{
if (mDataBlock->particleDataBlocks[i] && mDataBlock->particleDataBlocks[i]->isTempClone())
{
delete mDataBlock->particleDataBlocks[i];
mDataBlock->particleDataBlocks[i] = 0;
}
}
delete mDataBlock;
mDataBlock = 0;
}
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
// 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();
#if defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
pool->addParticleEmitter(this);
#endif
2012-09-19 15:15:01 +00:00
return true;
}
//-----------------------------------------------------------------------------
// onRemove
//-----------------------------------------------------------------------------
void ParticleEmitter::onRemove()
{
#if defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
{
pool->removeParticleEmitter(this);
pool = 0;
}
#endif
2012-09-19 15:15:01 +00:00
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;
}
if (mDataBlock->isTempClone())
{
db_temp_clone = true;
return true;
}
2012-09-19 15:15:01 +00:00
scriptOnNewDataBlock();
return true;
}
//-----------------------------------------------------------------------------
// getCollectiveColor
//-----------------------------------------------------------------------------
LinearColorF ParticleEmitter::getCollectiveColor()
2012-09-19 15:15:01 +00:00
{
U32 count = 0;
LinearColorF color = LinearColorF(0.0f, 0.0f, 0.0f);
2012-09-19 15:15:01 +00:00
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 defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
return;
#endif
2012-09-19 15:15:01 +00:00
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 );
ri->defaultKey = (-sort_priority*100);
2012-09-19 15:15:01 +00:00
// 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 );
Updated Assimp Added initial behavior for ImageAssets to hold a list of GFX resources of different texture profiles to avoid mem leaks with incorrect-typed usages Added function to ImageAsset to get best-fit asset, allowing for fallbacks if the requested assetID is not found Added function to ShapeAsset to get best-fit asset, allowing for fallbacks if the requested assetID is not found Disabled fields for dynamic and static shadowmap refresh rates Moved noShape model to core/rendering/shapes to place it in a more logical module position Added an include to avoid undefined type compile error and removed unneeded semicolon from zone code Added call to reload probe textures when a reloadTextures call is made Adjusted default directional light shadowmap settings to not be as extreme Added utility function to probe manager to allow any class to request a 'best fit' list of probes that would affect a given location, allowing other classes such as fog or particles to utilize IBL. Also updated probeManager's forward rendering to utilize same function to reduce code duplication. Shifted shape loader code to utilize assimp for loader consistency and testing Changed render bin used for SSAO postfx so it runs at the right time Made Core_Rendering module scan for assets Updated loose file references to a number of assets to follow proper formatting Refactored asset import code to follow a more consistent object heirarchy structure on importing assets, allowing more reliable cross-referencing between inbound items Updated asset import logic for materials/images so that they properly utilize ImageType. Images correctly save out the assigned image type, materials reference the images' type to know what map slot they should be used in. Importer logic also updated to better find-and-add associated images based on type. Cleaned up a bunch of old, outdated code in the asset importer Added initial handling for in-place importing of files without needing to process them through the UI. Added ability to edit module script from RMB context menu if torsion path is set Updated list field code for variable inspector to utilize correct ownerObject field
2020-03-19 14:47:38 +00:00
ri->wsPosition = getWorldTransform().getPosition();
2012-09-19 15:15:01 +00:00
ri->count = n_parts;
ri->blendStyle = mDataBlock->blendStyle;
ri->glow = mDataBlock->glow;
2012-09-19 15:15:01 +00:00
// 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->getTextureResource());
2012-09-19 15:15:01 +00:00
ri->softnessDistance = mDataBlock->softnessDistance;
// Sort by texture too.
ri->defaultKey = ri->diffuseTex ? (uintptr_t)ri->diffuseTex : (uintptr_t)ri->vertBuff;
2012-09-19 15:15:01 +00:00
renderManager->addInst( ri );
}
//-----------------------------------------------------------------------------
// setSizes
//-----------------------------------------------------------------------------
void ParticleEmitter::setSizes( F32 *sizeList )
{
for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ )
2012-09-19 15:15:01 +00:00
{
sizes[i] = sizeList[i];
}
}
//-----------------------------------------------------------------------------
// setColors
//-----------------------------------------------------------------------------
void ParticleEmitter::setColors( LinearColorF *colorList )
2012-09-19 15:15:01 +00:00
{
for( S32 i=0; i<ParticleData::PDC_NUM_KEYS; i++ )
2012-09-19 15:15:01 +00:00
{
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;
}
pos_pe = point;
2012-09-19 15:15:01 +00:00
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, numMilliseconds-currTime);
2012-09-19 15:15:01 +00:00
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, numMilliseconds-currTime);
2012-09-19 15:15:01 +00:00
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;
a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster
last_part->vel += a * t;
//last_part->pos += last_part->vel * t;
last_part->pos_local += last_part->vel * t;
2012-09-19 15:15:01 +00:00
// AFX -- allow subclasses to adjust the particle params here
sub_particleUpdate(last_part);
2012-09-19 15:15:01 +00:00
if (last_part->dataBlock->constrain_pos)
last_part->pos = last_part->pos_local + this->pos_pe;
else
last_part->pos = last_part->pos_local;
2012-09-19 15:15:01 +00:00
updateKeyData( last_part );
2012-09-19 15:15:01 +00:00
}
}
}
}
// 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, 0);
2012-09-19 15:15:01 +00:00
}
// 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()
{
if (forced_bbox)
return;
2012-09-19 15:15:01 +00:00
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);
F32 motion = getMax((part->vel.len() * part->totalLifetime / 1000.0f), 1.0f);
minPt.setMin(part->pos - particleSize - Point3F(motion));
maxPt.setMax(part->pos + particleSize + Point3F(motion));
2012-09-19 15:15:01 +00:00
}
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);
#if defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
pool->updatePoolBBox(this);
#endif
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
// addParticle
//-----------------------------------------------------------------------------
void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel,
const Point3F& axisx, const U32 age_offset)
2012-09-19 15:15:01 +00:00
{
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;
// for earlier access to constrain_pos, the ParticleData datablock is chosen here instead
// of later in the method.
U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
ParticleData* part_db = mDataBlock->particleDataBlocks[dBlockIndex];
// set start position to world or local space
Point3F pos_start;
if (part_db->constrain_pos)
pos_start.set(0,0,0);
else
pos_start = pos;
2012-09-19 15:15:01 +00:00
Point3F ejectionAxis = axis;
2021-02-07 19:43:21 +00:00
F32 theta = 0.0f;
F32 thetaTarget = (mDataBlock->thetaMax + mDataBlock->thetaMin) / 2.0f;
if (mDataBlock->thetaVariance <= 0.0f)
theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + mDataBlock->thetaMin;
else
{
F32 thetaDelta = ( gRandGen.randF() - 0.5f) * mDataBlock->thetaVariance * 2.0f;
thetaDelta += ( (thetaTarget - mThetaOld) / mDataBlock->thetaMax ) * mDataBlock->thetaVariance * 0.25f;
theta = mThetaOld + thetaDelta;
}
mThetaOld = theta;
2012-09-19 15:15:01 +00:00
F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel;
2021-02-07 19:43:21 +00:00
F32 phi = 0.0f;
if (mDataBlock->thetaVariance <= 0.0f)
{
phi = ref + gRandGen.randF() * mDataBlock->phiVariance;
}
else
{
F32 phiDelta = (gRandGen.randF() - 0.5f) * mDataBlock->thetaVariance * 2.0f;
phi = ref + mPhiOld + phiDelta;
if (phi > mDataBlock->phiVariance)
phi += fabs(phiDelta) * -2.0f;
if (phi < 0.0f)
phi += fabs(phiDelta) * 2.0f;
}
mPhiOld = phi;
2012-09-19 15:15:01 +00:00
// 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_start + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) );
pNew->pos_local = pNew->pos;
pNew->vel = mDataBlock->ejectionInvert ? ejectionAxis * -initialVel : ejectionAxis * initialVel;
if (mDataBlock->orientParticles)
pNew->orientDir = ejectionAxis;
else
// note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
2012-09-19 15:15:01 +00:00
pNew->acc.set(0, 0, 0);
pNew->currentAge = age_offset;
pNew->t_last = 0.0f;
2021-02-07 19:43:21 +00:00
// ribbon particles only use the first particle
if(mDataBlock->ribbonParticles)
{
mDataBlock->particleDataBlocks[0]->initializeParticle(pNew, vel);
}
else
{
dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size();
mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel);
2021-02-07 19:43:21 +00:00
}
2012-09-19 15:15:01 +00:00
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;
if (part->currentAge > part->totalLifetime)
part->currentAge = part->totalLifetime;
F32 t = (F32)part->currentAge / (F32)part->totalLifetime;
2012-09-19 15:15:01 +00:00
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);
part->size *= part->dataBlock->sizeBias;
2012-09-19 15:15:01 +00:00
}
if (mDataBlock->fade_color)
{
if (mDataBlock->fade_alpha)
part->color *= fade_amt;
else
{
part->color.red *= fade_amt;
part->color.green *= fade_amt;
part->color.blue *= fade_amt;
}
}
else if (mDataBlock->fade_alpha)
part->color.alpha *= fade_amt;
if (mDataBlock->fade_size)
part->size *= fade_amt;
2012-09-19 15:15:01 +00:00
break;
}
}
}
//-----------------------------------------------------------------------------
// Update particles
//-----------------------------------------------------------------------------
2021-02-07 19:43:21 +00:00
// AFX CODE BLOCK (enhanced-emitter) <<
2012-09-19 15:15:01 +00:00
void ParticleEmitter::update( U32 ms )
{
F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle
2012-09-19 15:15:01 +00:00
for (Particle* part = part_list_head.next; part != NULL; part = part->next)
{
Point3F a = part->acc;
a -= part->vel * part->dataBlock->dragCoefficient;
a += mWindVelocity * part->dataBlock->windCoefficient;
a.z += -9.81f*part->dataBlock->gravityCoefficient; // AFX -- as long as gravity is a constant, this is faster
2012-09-19 15:15:01 +00:00
part->vel += a * t;
part->pos_local += part->vel * t;
// AFX -- allow subclasses to adjust the particle params here
sub_particleUpdate(part);
if (part->dataBlock->constrain_pos)
part->pos = part->pos_local + this->pos_pe;
else
part->pos = part->pos_local;
2012-09-19 15:15:01 +00:00
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)
2012-09-19 15:15:01 +00:00
{
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 LinearColorF &ambientColor )
2012-09-19 15:15:01 +00:00
{
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();
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)
2021-02-07 19:43:21 +00:00
if (mDataBlock->ribbonParticles)
{
PROFILE_START(ParticleEmitter_copyToVB_Ribbon);
if (mDataBlock->reverseOrder)
{
Particle* oldPtr = NULL;
2021-10-05 05:23:26 +00:00
for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr -= 4)
{
2021-02-07 19:43:21 +00:00
setupRibbon(partPtr, partPtr->next, oldPtr, camPos, ambientColor, buffPtr);
oldPtr = partPtr;
}
}
else
{
Particle* oldPtr = NULL;
2021-10-05 05:23:26 +00:00
for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr += 4)
{
2021-02-07 19:43:21 +00:00
setupRibbon(partPtr, partPtr->next, oldPtr, camPos, ambientColor, buffPtr);
oldPtr = partPtr;
}
}
PROFILE_END();
}
else if (mDataBlock->orientParticles)
2012-09-19 15:15:01 +00:00
{
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();
}
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();
PROFILE_END();
}
//-----------------------------------------------------------------------------
// Set up particle for billboard style render
//-----------------------------------------------------------------------------
void ParticleEmitter::setupBillboard( Particle *part,
Point3F *basePts,
const MatrixF &camView,
const LinearColorF &ambientColor,
2012-09-19 15:15:01 +00:00
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 );
LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp );
2012-09-19 15:15:01 +00:00
// 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.toColorI(); } \
2012-09-19 15:15:01 +00:00
// Here we deal with UVs for animated particle (billboard)
if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty())
2012-09-19 15:15:01 +00:00
{
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 LinearColorF &ambientColor,
2012-09-19 15:15:01 +00:00
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 );
LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp );
const ColorI color = partCol.toColorI();
2012-09-19 15:15:01 +00:00
// Here we deal with UVs for animated particle (oriented)
if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty())
2012-09-19 15:15:01 +00:00
{
// 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 = color;
// Here and below, we copy UVs from particle datablock's current frame's UVs (oriented)
lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = start - crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = end - crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = end + crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]];
++lVerts;
2012-09-19 15:15:01 +00:00
return;
2012-09-19 15:15:01 +00:00
}
lVerts->point = start + crossDir;
lVerts->color = color;
2012-09-19 15:15:01 +00:00
// Here and below, we copy UVs from particle datablock's texCoords (oriented)
lVerts->texCoord = part->dataBlock->texCoords[1];
2012-09-19 15:15:01 +00:00
++lVerts;
lVerts->point = start - crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->texCoords[2];
2012-09-19 15:15:01 +00:00
++lVerts;
lVerts->point = end - crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->texCoords[3];
2012-09-19 15:15:01 +00:00
++lVerts;
lVerts->point = end + crossDir;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->texCoords[0];
2012-09-19 15:15:01 +00:00
++lVerts;
}
void ParticleEmitter::setupAligned( const Particle *part,
const LinearColorF &ambientColor,
2012-09-19 15:15:01 +00:00
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 );
LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp );
const ColorI color = partCol.toColorI();
2012-09-19 15:15:01 +00:00
// Here we deal with UVs for animated particle
if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty())
2012-09-19 15:15:01 +00:00
{
// 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 = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = start - cross;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = end - cross;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]];
++lVerts;
2012-09-19 15:15:01 +00:00
lVerts->point = end + cross;
lVerts->color = color;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]];
++lVerts;
2012-09-19 15:15:01 +00:00
}
else
{
// Here and below, we copy UVs from particle datablock's texCoords
lVerts->point = start + cross;
lVerts->color = color;
2012-09-19 15:15:01 +00:00
lVerts->texCoord = part->dataBlock->texCoords[0];
++lVerts;
lVerts->point = start - cross;
lVerts->color = color;
2012-09-19 15:15:01 +00:00
lVerts->texCoord = part->dataBlock->texCoords[1];
++lVerts;
lVerts->point = end - cross;
lVerts->color = color;
2012-09-19 15:15:01 +00:00
lVerts->texCoord = part->dataBlock->texCoords[2];
++lVerts;
lVerts->point = end + cross;
lVerts->color = color;
2012-09-19 15:15:01 +00:00
lVerts->texCoord = part->dataBlock->texCoords[3];
++lVerts;
}
}
2021-02-07 19:43:21 +00:00
void ParticleEmitter::setupRibbon(Particle *part,
Particle *next,
Particle *prev,
const Point3F &camPos,
const LinearColorF &ambientColor,
ParticleVertexType *lVerts)
{
Point3F dir, dirFromCam;
Point3F crossDir, crossDirNext;
Point3F start, end;
LinearColorF prevCol;
static Point3F crossDirPrev;
static int position;
static F32 alphaMod, alphaModEnd;
const F32 ambientLerp = mClampF(mDataBlock->ambientFactor, 0.0f, 1.0f);
LinearColorF partCol = mLerp(part->color, (part->color * ambientColor), ambientLerp);
if (part->currentAge > part->totalLifetime)
{
F32 alphaDeath = (part->currentAge - part->totalLifetime) / 200.0f;
if (alphaDeath > 1.0f)
alphaDeath = 1.0f;
alphaDeath = 1.0f - alphaDeath;
partCol.alpha *= alphaDeath;
}
start = part->pos;
position++;
if (next == NULL && prev == NULL) {
// a ribbon of just one particle
position = 0;
if (part->vel.magnitudeSafe() == 0.0)
dir = part->orientDir;
else
dir = part->vel;
dir.normalize();
dirFromCam = part->pos - camPos;
mCross(dirFromCam, dir, &crossDir);
crossDir.normalize();
crossDir = crossDir * part->size * 0.5;
crossDirPrev = crossDir;
partCol.alpha = 0.0f;
prevCol = partCol;
end = part->pos;
}
else if (next == NULL && prev != NULL)
{
// last link in the chain, also the oldest
dir = part->pos - prev->pos;
dir.normalize();
dirFromCam = part->pos - camPos;
mCross(dirFromCam, dir, &crossDir);
crossDir.normalize();
crossDir = crossDir * part->size * 0.5;
end = prev->pos;
partCol.alpha = 0.0f;
prevCol = mLerp(prev->color, (prev->color * ambientColor), ambientLerp);
prevCol.alpha *= alphaModEnd;
}
else if (next != NULL && prev == NULL)
{
// first link in chain, newest particle
// since we draw from current to previous, this one isn't drawn
position = 0;
dir = next->pos - part->pos;
dir.normalize();
dirFromCam = part->pos - camPos;
mCross(dirFromCam, dir, &crossDir);
crossDir.normalize();
crossDir = crossDir * part->size * 0.5f;
crossDirPrev = crossDir;
partCol.alpha = 0.0f;
prevCol = partCol;
alphaModEnd = 0.0f;
end = part->pos;
}
else
{
// middle of chain
dir = next->pos - prev->pos;
dir.normalize();
dirFromCam = part->pos - camPos;
mCross(dirFromCam, dir, &crossDir);
crossDir.normalize();
crossDir = crossDir * part->size * 0.5;
prevCol = mLerp(prev->color, (prev->color * ambientColor), ambientLerp);
if (position == 1)
{
// the second particle has a few tweaks for alpha, to smoothly match the first particle
// we only want to do this once when the particle first fades in, and avoid a strobing effect
alphaMod = (float(part->currentAge) / float(part->currentAge - prev->currentAge)) - 1.0f;
if (alphaMod > 1.0f)
alphaMod = 1.0f;
partCol.alpha *= alphaMod;
prevCol.alpha = 0.0f;
if (next->next == NULL)
alphaModEnd = alphaMod;
//Con::printf("alphaMod: %f", alphaMod );
}
else if (position == 2)
{
prevCol.alpha *= alphaMod;
alphaMod = 0.0f;
}
if (next->next == NULL && position > 1)
{
// next to last particle, start the fade out
alphaModEnd = (float(next->totalLifetime - next->currentAge)) / (float(part->totalLifetime - part->currentAge));
alphaModEnd *= 2.0f;
if (alphaModEnd > 1.0f)
alphaModEnd = 1.0f;
partCol.alpha *= alphaModEnd;
//Con::printf("alphaMod: %f Lifetime: %d Age: %d", alphaMod, part->totalLifetime, part->currentAge );
}
end = prev->pos;
}
ColorI pCol = partCol.toColorI();
// Here we deal with UVs for animated particle (oriented)
if (part->dataBlock->animateTexture && !part->dataBlock->animTexFrames.empty())
2021-02-07 19:43:21 +00:00
{
// 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 = pCol;
// 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 = pCol;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]];
++lVerts;
lVerts->point = end - crossDirPrev;
lVerts->color = pCol;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]];
++lVerts;
lVerts->point = end + crossDirPrev;
lVerts->color = pCol;
lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]];
++lVerts;
crossDirPrev = crossDir;
return;
}
lVerts->point = start + crossDir;
lVerts->color = pCol;
// Here and below, we copy UVs from particle datablock's texCoords (oriented)
lVerts->texCoord = part->dataBlock->texCoords[0];
++lVerts;
lVerts->point = start - crossDir;
lVerts->color = pCol;
lVerts->texCoord = part->dataBlock->texCoords[1];
++lVerts;
lVerts->point = end - crossDirPrev;
lVerts->color = pCol;
lVerts->texCoord = part->dataBlock->texCoords[2];
++lVerts;
lVerts->point = end + crossDirPrev;
lVerts->color = pCol;
lVerts->texCoord = part->dataBlock->texCoords[3];
++lVerts;
crossDirPrev = crossDir;
}
2012-09-19 15:15:01 +00:00
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();
}
void ParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point,
const Point3F& velocity, const U32 numMilliseconds)
{
if (mDataBlock->use_emitter_xfm)
{
Point3F zero_point(0.0f, 0.0f, 0.0f);
this->pos_pe = zero_point;
this->setTransform(xfm);
Point3F axis(0.0,0.0,1.0);
xfm.mulV(axis);
emitParticles(zero_point, true, axis, velocity, numMilliseconds);
}
else
{
this->pos_pe = point;
Point3F axis(0.0,0.0,1.0);
xfm.mulV(axis);
emitParticles(point, true, axis, velocity, numMilliseconds);
}
}
void ParticleEmitter::setForcedObjBox(Box3F& box)
{
mObjBox = box;
forced_bbox = true;
#if defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
pool->updatePoolBBox(this);
#endif
}
void ParticleEmitter::setSortPriority(S8 priority)
{
sort_priority = (priority == 0) ? 1 : priority;
#if defined(AFX_CAP_PARTICLE_POOLS)
if (pool)
pool->setSortPriority(sort_priority);
#endif
}