mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-26 15:44:46 +00:00
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
2370 lines
80 KiB
C++
2370 lines
80 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#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(AFX_CAP_PARTICLE_POOLS)
|
|
#include "afx/util/afxParticlePool.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);
|
|
|
|
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
|
|
}
|
|
|
|
|
|
|
|
// 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" );
|
|
|
|
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));
|
|
addField("poolIndex", TypeS32, Offset(pool_index, ParticleEmitterData));
|
|
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
|
|
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 );
|
|
|
|
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
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
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
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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__);
|
|
dsize_t tokLen = dStrlen(particleString) + 1;
|
|
char* tokCopy = new char[tokLen];
|
|
dStrcpy(tokCopy, particleString, tokLen);
|
|
|
|
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
|
|
|
|
// load emitter texture if specified
|
|
if (textureName && textureName[0])
|
|
{
|
|
textureHandle = GFXTexHandle(textureName, &GFXStaticTextureSRGBProfile, 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 (parts_per_eject > 1)
|
|
partListInitSize *= parts_per_eject;
|
|
|
|
// 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;
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
fade_amt = 1.0f;
|
|
forced_bbox = false;
|
|
db_temp_clone = false;
|
|
pos_pe.set(0,0,0);
|
|
sort_priority = 0;
|
|
mDataBlock = 0;
|
|
|
|
#if defined(AFX_CAP_PARTICLE_POOLS)
|
|
pool = 0;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// onRemove
|
|
//-----------------------------------------------------------------------------
|
|
void ParticleEmitter::onRemove()
|
|
{
|
|
#if defined(AFX_CAP_PARTICLE_POOLS)
|
|
if (pool)
|
|
{
|
|
pool->removeParticleEmitter(this);
|
|
pool = 0;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
scriptOnNewDataBlock();
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// getCollectiveColor
|
|
//-----------------------------------------------------------------------------
|
|
LinearColorF ParticleEmitter::getCollectiveColor()
|
|
{
|
|
U32 count = 0;
|
|
LinearColorF color = LinearColorF(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 defined(AFX_CAP_PARTICLE_POOLS)
|
|
if (pool)
|
|
return;
|
|
#endif
|
|
|
|
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);
|
|
|
|
// 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->wsPosition = getWorldTransform().getPosition();
|
|
|
|
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( LinearColorF *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;
|
|
}
|
|
|
|
pos_pe = point;
|
|
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);
|
|
|
|
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);
|
|
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;
|
|
|
|
// AFX -- allow subclasses to adjust the particle params here
|
|
sub_particleUpdate(last_part);
|
|
|
|
if (last_part->dataBlock->constrain_pos)
|
|
last_part->pos = last_part->pos_local + this->pos_pe;
|
|
else
|
|
last_part->pos = last_part->pos_local;
|
|
|
|
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, 0);
|
|
}
|
|
|
|
// 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;
|
|
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);
|
|
|
|
#if defined(AFX_CAP_PARTICLE_POOLS)
|
|
if (pool)
|
|
pool->updatePoolBBox(this);
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// addParticle
|
|
//-----------------------------------------------------------------------------
|
|
void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel,
|
|
const Point3F& axisx, const U32 age_offset)
|
|
{
|
|
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;
|
|
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_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);
|
|
pNew->acc.set(0, 0, 0);
|
|
pNew->currentAge = age_offset;
|
|
pNew->t_last = 0.0f;
|
|
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;
|
|
|
|
if (part->currentAge > part->totalLifetime)
|
|
part->currentAge = part->totalLifetime;
|
|
F32 t = (F32)part->currentAge / (F32)part->totalLifetime;
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Update particles
|
|
//-----------------------------------------------------------------------------
|
|
void ParticleEmitter::update( U32 ms )
|
|
{
|
|
F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
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 LinearColorF &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();
|
|
|
|
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)
|
|
|
|
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();
|
|
}
|
|
|
|
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,
|
|
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 );
|
|
|
|
// 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(); } \
|
|
|
|
// 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 LinearColorF &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 );
|
|
LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp );
|
|
const ColorI color = partCol.toColorI();
|
|
// 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 = color;
|
|
// 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 = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]];
|
|
++lVerts;
|
|
|
|
lVerts->point = end - crossDir;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]];
|
|
++lVerts;
|
|
|
|
lVerts->point = end + crossDir;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]];
|
|
++lVerts;
|
|
|
|
return;
|
|
}
|
|
|
|
lVerts->point = start + crossDir;
|
|
lVerts->color = color;
|
|
// 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 = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[2];
|
|
++lVerts;
|
|
|
|
lVerts->point = end - crossDir;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[3];
|
|
++lVerts;
|
|
|
|
lVerts->point = end + crossDir;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[0];
|
|
++lVerts;
|
|
}
|
|
|
|
void ParticleEmitter::setupAligned( const Particle *part,
|
|
const LinearColorF &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 );
|
|
LinearColorF partCol = mLerp( part->color, ( part->color * ambientColor ), ambientLerp );
|
|
const ColorI color = partCol.toColorI();
|
|
// 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 = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]];
|
|
++lVerts;
|
|
|
|
lVerts->point = start - cross;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]];
|
|
++lVerts;
|
|
|
|
lVerts->point = end - cross;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]];
|
|
++lVerts;
|
|
|
|
lVerts->point = end + cross;
|
|
lVerts->color = color;
|
|
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 = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[0];
|
|
++lVerts;
|
|
|
|
lVerts->point = start - cross;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[1];
|
|
++lVerts;
|
|
|
|
lVerts->point = end - cross;
|
|
lVerts->color = color;
|
|
lVerts->texCoord = part->dataBlock->texCoords[2];
|
|
++lVerts;
|
|
|
|
lVerts->point = end + cross;
|
|
lVerts->color = color;
|
|
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();
|
|
}
|
|
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
|
|
}
|
|
|