Torque3D/Engine/source/T3D/fx/particle.cpp
AzaezelX 5ffa3b81f1 dial back nullPtr usage
while it still remains a good idea to port as many NULL compares and assignments over to nullPtr as feasable, we do still need to sort out how to better support scripted empty, false, and zero assigns for things like objectIDs.

this means we'll need to both fully convert the backend of the parser to support that kind of thing, but also alter most if not all exisiting NULLs. up to and including things like SAFE_DELETE. while that's certainly feasable, given there's aproximatel 400 nullptr assigns/checks prior to this commit, and roughly 1800 of the prior, if it terminates in a script call and not an aip one direct, we'll be dialing that back until such time as fork fully fopcused on converting and resolving any lingering mismatches is completed.
2025-12-29 17:45:09 -06:00

1128 lines
39 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 "particle.h"
#include "console/consoleTypes.h"
#include "console/typeValidators.h"
#include "core/stream/bitStream.h"
#include "math/mRandom.h"
#include "math/mathIO.h"
#include "console/engineAPI.h"
#include "particleInspectors.h"
IMPLEMENT_CO_DATABLOCK_V1( ParticleData );
ConsoleDocClass( ParticleData,
"@brief Contains information for how specific particles should look and react "
"including particle colors, particle imagemap, acceleration value for individual "
"particles and spin information.\n"
"@tsexample\n"
"datablock ParticleData( GLWaterExpSmoke )\n"
"{\n"
" textureName = \"art/shapes/particles/smoke\";\n"
" dragCoefficient = 0.4;\n"
" gravityCoefficient = -0.25;\n"
" inheritedVelFactor = 0.025;\n"
" constantAcceleration = -1.1;\n"
" lifetimeMS = 1250;\n"
" lifetimeVarianceMS = 0;\n"
" useInvAlpha = false;\n"
" spinSpeed = 1;\n"
" spinRandomMin = -200.0;\n"
" spinRandomMax = 200.0;\n\n"
" colors[0] = \"0.1 0.1 1.0 1.0\";\n"
" colors[1] = \"0.4 0.4 1.0 1.0\";\n"
" colors[2] = \"0.4 0.4 1.0 0.0\";\n\n"
" sizes[0] = 2.0;\n"
" sizes[1] = 6.0;\n"
" sizes[2] = 2.0;\n\n"
" times[0] = 0.0;\n"
" times[1] = 0.5;\n"
" times[2] = 1.0;\n"
"};\n"
"@endtsexample\n"
"@ingroup FX\n"
"@see ParticleEmitter\n"
"@see ParticleEmitterData\n"
"@see ParticleEmitterNode\n"
);
static const F32 sgDefaultWindCoefficient = 0.0f;
static const F32 sgDefaultConstantAcceleration = 0.f;
static const F32 sgDefaultSpinSpeed = 1.f;
static const F32 sgDefaultSpinRandomMin = 0.f;
static const F32 sgDefaultSpinRandomMax = 0.f;
static const F32 sgDefaultSpinBias = 1.0f;
static const F32 sgDefaultSizeBias = 1.0f;
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
ParticleData::ParticleData()
{
dragCoefficient = 0.0f;
windCoefficient = sgDefaultWindCoefficient;
gravityCoefficient = 0.0f;
inheritedVelFactor = 0.0f;
constantAcceleration = sgDefaultConstantAcceleration;
lifetimeMS = 1000;
lifetimeVarianceMS = 0;
spinSpeed = sgDefaultSpinSpeed;
spinRandomMin = sgDefaultSpinRandomMin;
spinRandomMax = sgDefaultSpinRandomMax;
useInvAlpha = false;
animateTexture = false;
numFrames = 1;
framesPerSec = numFrames;
S32 i;
for( i=0; i<PDC_NUM_KEYS; i++ )
{
colors[i].set( 1.0, 1.0, 1.0, 1.0 );
sizes[i] = 1.0;
}
times[0] = 0.0f;
times[1] = 1.0f;
for (i = 2; i < PDC_NUM_KEYS; i++)
times[i] = -1.0f;
texCoords[0].set(0.0,0.0); // texture coords at 4 corners
texCoords[1].set(0.0,1.0); // of particle quad
texCoords[2].set(1.0,1.0); // (defaults to entire particle)
texCoords[3].set(1.0,0.0);
animTexTiling.set(0,0); // tiling dimensions
animTexFramesString = NULL; // string of animation frame indices
animTexUVs = NULL; // array of tile vertex UVs
constrain_pos = false;
start_angle = 0.0f;
angle_variance = 0.0f;
sizeBias = sgDefaultSizeBias;
spinBias = sgDefaultSpinBias;
randomizeSpinDir = false;
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
FRangeValidator dragCoefFValidator(0.f, 5.f, BIT(10));
FRangeValidator gravCoefFValidator(-10.f, 10.f, BIT(12));
FRangeValidator spinRandFValidator(-1000.f, 1000.f, BIT(11));
FRangeValidator particleTimeFValidator(0.0f, 1.0f, BIT(8));
FRangeValidator particleSizeFValidator(0.0f, MaxParticleSize, BIT(16));
//-----------------------------------------------------------------------------
// initPersistFields
//-----------------------------------------------------------------------------
void ParticleData::initPersistFields()
{
docsURL;
addGroup("Basic");
INITPERSISTFIELD_IMAGEASSET(Texture, ParticleData, "Texture to use for this particle.");
addField("useInvAlpha", TYPEID< bool >(), Offset(useInvAlpha, ParticleData),
"@brief Controls how particles blend with the scene.\n\n"
"If true, particles blend like ParticleBlendStyle NORMAL, if false, "
"blend like ParticleBlendStyle ADDITIVE.\n"
"@note If ParticleEmitterData::blendStyle is set, it will override this value.");
addFieldV("lifetimeMS", TypeRangedS32, Offset(lifetimeMS, ParticleData), &CommonValidators::PositiveInt,
"Time in milliseconds before this particle is destroyed.");
addFieldV("lifetimeVarianceMS", TypeRangedS32, Offset(lifetimeVarianceMS, ParticleData), &CommonValidators::PositiveInt,
"Variance in lifetime of particle, from 0 - lifetimeMS.");
endGroup("Basic");
addGroup("Motion");
addFieldV("dragCoefficient", TypeRangedF32, Offset(dragCoefficient, ParticleData), &dragCoefFValidator,
"Particle physics drag amount.");
addFieldV("windCoefficient", TypeRangedF32, Offset(windCoefficient, ParticleData),&CommonValidators::F32Range,
"Strength of wind on the particles.");
addFieldV("gravityCoefficient", TypeRangedF32, Offset(gravityCoefficient, ParticleData), &gravCoefFValidator,
"Strength of gravity on the particles.");
addFieldV("inheritedVelFactor", TypeRangedF32, Offset(inheritedVelFactor, ParticleData), &CommonValidators::NormalizedFloat,
"Amount of emitter velocity to add to particle initial velocity.");
addFieldV("constantAcceleration", TypeRangedF32, Offset(constantAcceleration, ParticleData), &CommonValidators::F32Range,
"Constant acceleration to apply to this particle.");
endGroup("Motion");
addGroup("Spin");
addFieldV("spinSpeed", TypeRangedF32, Offset(spinSpeed, ParticleData), &spinRandFValidator,
"Speed at which to spin the particle.");
addFieldV("spinRandomMin", TypeRangedF32, Offset(spinRandomMin, ParticleData), &spinRandFValidator,
"Minimum allowed spin speed of this particle, between -1000 and spinRandomMax.");
addFieldV("spinRandomMax", TypeRangedF32, Offset(spinRandomMax, ParticleData), &spinRandFValidator,
"Maximum allowed spin speed of this particle, between spinRandomMin and 1000.");
endGroup("Spin");
addGroup("Animation");
addField( "animateTexture", TYPEID< bool >(), Offset(animateTexture, ParticleData),
"If true, allow the particle texture to be an animated sprite." );
addField( "framesPerSec", TYPEID< S32 >(), Offset(framesPerSec, ParticleData),
"If animateTexture is true, this defines the frames per second of the "
"sprite animation." );
addField( "textureCoords", TYPEID< Point2F >(), Offset(texCoords, ParticleData), 4,
"@brief 4 element array defining the UV coords into textureName to use "
"for this particle.\n\n"
"Coords should be set for the first tile only when using animTexTiling; "
"coordinates for other tiles will be calculated automatically. \"0 0\" is "
"top left and \"1 1\" is bottom right." );
addField( "animTexTiling", TYPEID< Point2I >(), Offset(animTexTiling, ParticleData),
"@brief The number of frames, in rows and columns stored in textureName "
"(when animateTexture is true).\n\n"
"A maximum of 256 frames can be stored in a single texture when using "
"animTexTiling. Value should be \"NumColumns NumRows\", for example \"4 4\"." );
addField( "animTexFrames", TYPEID< StringTableEntry >(), Offset(animTexFramesString,ParticleData),
"@brief A list of frames and/or frame ranges to use for particle "
"animation if animateTexture is true.\n\n"
"Each frame token must be separated by whitespace. A frame token must be "
"a positive integer frame number or a range of frame numbers separated "
"with a '-'. The range separator, '-', cannot have any whitspace around "
"it.\n\n"
"Ranges can be specified to move through the frames in reverse as well "
"as forward (eg. 19-14). Frame numbers exceeding the number of tiles will "
"wrap.\n"
"@tsexample\n"
"animTexFrames = \"0-16 20 19 18 17 31-21\";\n"
"@endtsexample\n" );
endGroup("Animation");
// Interpolation variables
addGroup("Over Time");
addProtectedFieldV("times", TypeRangedF32, Offset(times, ParticleData), &protectedSetTimes,
&defaultProtectedGetFn, &particleTimeFValidator, PDC_NUM_KEYS,
"@brief Time keys used with the colors and sizes keyframes.\n\n"
"Values are from 0.0 (particle creation) to 1.0 (end of lifespace).");
addField( "colors", TYPEID< LinearColorF >(), Offset(colors, ParticleData), PDC_NUM_KEYS,
"@brief Particle RGBA color keyframe values.\n\n"
"The particle color will linearly interpolate between the color/time keys "
"over the lifetime of the particle." );
addProtectedFieldV( "sizes", TypeRangedF32, Offset(sizes, ParticleData), &protectedSetSizes,
&defaultProtectedGetFn, &particleSizeFValidator, PDC_NUM_KEYS,
"@brief Particle size keyframe values.\n\n"
"The particle size will linearly interpolate between the size/time keys "
"over the lifetime of the particle." );
endGroup("Over Time");
addGroup("AFX");
INITPERSISTFIELD_IMAGEASSET(TextureExt, ParticleData, "");
addField("constrainPos", TypeBool, Offset(constrain_pos, ParticleData));
addFieldV("angle", TypeRangedF32, Offset(start_angle, ParticleData), &CommonValidators::DegreeRange);
addFieldV("angleVariance", TypeRangedF32, Offset(angle_variance, ParticleData), &CommonValidators::DegreeRange);
addFieldV("sizeBias", TypeRangedF32, Offset(sizeBias, ParticleData), &CommonValidators::F32Range);
addFieldV("spinBias", TypeRangedF32, Offset(spinBias, ParticleData), &CommonValidators::F32Range);
addField("randomizeSpinDir", TypeBool, Offset(randomizeSpinDir, ParticleData));
endGroup("AFX");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
// Pack data
//-----------------------------------------------------------------------------
void ParticleData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFloat(dragCoefficient / 5, 10);
if( stream->writeFlag(windCoefficient != sgDefaultWindCoefficient ) )
stream->write(windCoefficient);
if (stream->writeFlag(gravityCoefficient != 0.0f))
stream->writeSignedFloat(gravityCoefficient / 10, 12);
stream->writeFloat(inheritedVelFactor, 9);
if( stream->writeFlag( constantAcceleration != sgDefaultConstantAcceleration ) )
stream->write(constantAcceleration);
stream->write( lifetimeMS );
stream->write( lifetimeVarianceMS );
if( stream->writeFlag( spinSpeed != sgDefaultSpinSpeed ) )
stream->write(spinSpeed);
if(stream->writeFlag(spinRandomMin != sgDefaultSpinRandomMin || spinRandomMax != sgDefaultSpinRandomMax))
{
stream->writeInt((S32)(spinRandomMin + 1000), 11);
stream->writeInt((S32)(spinRandomMax + 1000), 11);
}
if(stream->writeFlag(spinBias != sgDefaultSpinBias))
stream->write(spinBias);
stream->writeFlag(randomizeSpinDir);
stream->writeFlag(useInvAlpha);
S32 i, count;
// see how many frames there are:
for(count = 0; count < ParticleData::PDC_NUM_KEYS-1; count++)
if(times[count] >= 1)
break;
count++;
// An extra bit is needed for 8 keys.
stream->writeInt(count-1, 3);
for( i=0; i<count; i++ )
{
stream->writeFloat( colors[i].red, 8);
stream->writeFloat( colors[i].green, 8);
stream->writeFloat( colors[i].blue, 8);
stream->writeFloat( colors[i].alpha, 8);
// AFX bits raised from 14 to 16 to allow larger sizes
stream->writeFloat( sizes[i]/MaxParticleSize, 16);
stream->writeFloat( times[i], 8);
}
PACKDATA_ASSET_REFACTOR(Texture);
for (i = 0; i < 4; i++)
mathWrite(*stream, texCoords[i]);
if (stream->writeFlag(animateTexture))
{
if (stream->writeFlag(animTexFramesString && animTexFramesString[0]))
{
stream->writeString(animTexFramesString);
}
mathWrite(*stream, animTexTiling);
stream->writeInt(framesPerSec, 8);
}
PACKDATA_ASSET_REFACTOR(TextureExt);
stream->writeFlag(constrain_pos);
stream->writeFloat(start_angle/360.0f, 11);
stream->writeFloat(angle_variance/180.0f, 10);
if(stream->writeFlag(sizeBias != sgDefaultSizeBias))
stream->write(sizeBias);
}
//-----------------------------------------------------------------------------
// Unpack data
//-----------------------------------------------------------------------------
void ParticleData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
dragCoefficient = stream->readFloat(10) * 5;
if(stream->readFlag())
stream->read(&windCoefficient);
else
windCoefficient = sgDefaultWindCoefficient;
if (stream->readFlag())
gravityCoefficient = stream->readSignedFloat(12)*10;
else
gravityCoefficient = 0.0f;
inheritedVelFactor = stream->readFloat(9);
if(stream->readFlag())
stream->read(&constantAcceleration);
else
constantAcceleration = sgDefaultConstantAcceleration;
stream->read( &lifetimeMS );
stream->read( &lifetimeVarianceMS );
if(stream->readFlag())
stream->read(&spinSpeed);
else
spinSpeed = sgDefaultSpinSpeed;
if(stream->readFlag())
{
spinRandomMin = (F32)(stream->readInt(11) - 1000);
spinRandomMax = (F32)(stream->readInt(11) - 1000);
}
else
{
spinRandomMin = sgDefaultSpinRandomMin;
spinRandomMax = sgDefaultSpinRandomMax;
}
if(stream->readFlag())
stream->read(&spinBias);
else
spinBias = sgDefaultSpinBias;
randomizeSpinDir = stream->readFlag();
useInvAlpha = stream->readFlag();
S32 i;
// An extra bit is needed for 8 keys.
S32 count = stream->readInt(3) + 1;
for(i = 0;i < count; i++)
{
colors[i].red = stream->readFloat(8);
colors[i].green = stream->readFloat(8);
colors[i].blue = stream->readFloat(8);
colors[i].alpha = stream->readFloat(8);
// AFX bits raised from 14 to 16 to allow larger sizes
sizes[i] = stream->readFloat(16) * MaxParticleSize;
times[i] = stream->readFloat(8);
}
UNPACKDATA_ASSET_REFACTOR(Texture);
for (i = 0; i < 4; i++)
mathRead(*stream, &texCoords[i]);
animateTexture = stream->readFlag();
if (animateTexture)
{
animTexFramesString = (stream->readFlag()) ? stream->readSTString() : 0;
mathRead(*stream, &animTexTiling);
framesPerSec = stream->readInt(8);
}
UNPACKDATA_ASSET_REFACTOR(TextureExt);
constrain_pos = stream->readFlag();
start_angle = 360.0f*stream->readFloat(11);
angle_variance = 180.0f*stream->readFloat(10);
if(stream->readFlag())
stream->read(&sizeBias);
else
sizeBias = sgDefaultSizeBias;
}
bool ParticleData::protectedSetSizes( void *object, const char *index, const char *data)
{
ParticleData *pData = static_cast<ParticleData*>( object );
F32 val = dAtof(data);
U32 i;
if (!index)
return (val >= 0.f && val <= MaxParticleSize);
else
i = dAtoui(index);
pData->sizes[i] = mClampF( val, 0.f, MaxParticleSize );
return false;
}
bool ParticleData::protectedSetTimes( void *object, const char *index, const char *data)
{
ParticleData *pData = static_cast<ParticleData*>( object );
F32 val = dAtof(data);
U32 i;
if (!index)
return (val >= 0.f && val <= 1.f);
else
i = dAtoui(index);
pData->times[i] = mClampF( val, 0.f, 1.f );
pData->times[0] = 0.0f;
S32 last = i - 1;
S32 next = i + 1;
if (last >= 0 && next < PDC_NUM_KEYS-1)
{
if ((pData->times[last] != -1.0f) && (pData->times[i] < pData->times[last]))
pData->times[i] = pData->times[last];
else if ((pData->times[next] != -1.0f) && (pData->times[i] > pData->times[next]))
pData->times[i] = pData->times[next];
}
return false;
}
//-----------------------------------------------------------------------------
// onAdd
//-----------------------------------------------------------------------------
bool ParticleData::onAdd()
{
if (Parent::onAdd() == false)
return false;
if (dragCoefficient < 0.0) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) drag coeff less than 0", getName());
dragCoefficient = 0.0f;
}
if (lifetimeMS < 1) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetime < 1 ms", getName());
lifetimeMS = 1;
}
if (lifetimeVarianceMS >= lifetimeMS) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetimeVariance >= lifetime", getName());
lifetimeVarianceMS = lifetimeMS - 1;
}
if (spinSpeed > 1000.f || spinSpeed < -1000.f) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinSpeed invalid", getName());
return false;
}
if (spinRandomMin > 1000.f || spinRandomMin < -1000.f) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin invalid", getName());
spinRandomMin = -360.0;
return false;
}
if (spinRandomMin > spinRandomMax) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin greater than spinRandomMax", getName());
spinRandomMin = spinRandomMax - (spinRandomMin - spinRandomMax );
return false;
}
if (spinRandomMax > 1000.f || spinRandomMax < -1000.f) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMax invalid", getName());
spinRandomMax = 360.0;
return false;
}
if (framesPerSec > 255)
{
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec > 255, too high", getName());
framesPerSec = 255;
return false;
}
times[0] = 0.0f;
for (U32 i = 1; i < PDC_NUM_KEYS; i++)
{
if (times[i] < 0.0f)
break;
if (times[i] < times[i-1])
{
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1);
times[i] = times[i-1];
}
}
times[0] = 0.0f;
U32 last_idx = 0;
for (U32 i = 1; i < PDC_NUM_KEYS; i++)
{
if (times[i] < 0.0f)
break;
else
last_idx = i;
}
for (U32 i = last_idx+1; i < PDC_NUM_KEYS; i++)
{
times[i] = times[last_idx];
colors[i] = colors[last_idx];
sizes[i] = sizes[last_idx];
}
// Here we validate parameters
if (animateTexture)
{
// Tiling dimensions must be positive and non-zero
if (animTexTiling.x <= 0 || animTexTiling.y <= 0)
{
Con::warnf(ConsoleLogEntry::General,
"ParticleData(%s) bad value(s) for animTexTiling [%d or %d <= 0], invalid datablock",
animTexTiling.x, animTexTiling.y, getName());
return false;
}
// Indices must fit into a byte so these are also bad
if (animTexTiling.x * animTexTiling.y > 256)
{
Con::warnf(ConsoleLogEntry::General,
"ParticleData(%s) bad values for animTexTiling [%d*%d > %d], invalid datablock",
animTexTiling.x, animTexTiling.y, 256, getName());
return false;
}
// A list of frames is required
if (!animTexFramesString || !animTexFramesString[0])
{
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) no animTexFrames, invalid datablock", getName());
return false;
}
// The frame list cannot be too long.
if (animTexFramesString && dStrlen(animTexFramesString) > 255)
{
Con::errorf(ConsoleLogEntry::General, "ParticleData(%s) animTexFrames string too long [> 255 chars]", getName());
return false;
}
}
start_angle = mFmod(start_angle, 360.0f);
if (start_angle < 0.0f)
start_angle += 360.0f;
angle_variance = mClampF(angle_variance, -180.0f, 180.0f);
return true;
}
//-----------------------------------------------------------------------------
// preload
//-----------------------------------------------------------------------------
bool ParticleData::preload(bool server, String &errorStr)
{
if (Parent::preload(server, errorStr) == false)
return false;
bool error = false;
if(!server)
{
if (animateTexture)
{
// Here we parse animTexFramesString into byte-size frame numbers in animTexFrames.
// Each frame token must be separated by whitespace.
// A frame token must be a positive integer frame number or a range of frame numbers
// separated with a '-'.
// The range separator, '-', cannot have any whitspace around it.
// Ranges can be specified to move through the frames in reverse as well as forward.
// Frame numbers exceeding the number of tiles will wrap.
// example:
// "0-16 20 19 18 17 31-21"
S32 n_tiles = animTexTiling.x * animTexTiling.y;
AssertFatal(n_tiles > 0 && n_tiles <= 256, "Error, bad animTexTiling setting." );
animTexFrames.clear();
dsize_t tokLen = dStrlen(animTexFramesString) + 1;
char* tokCopy = new char[tokLen];
dStrcpy(tokCopy, animTexFramesString, tokLen);
char* currTok = dStrtok(tokCopy, " \t");
while (currTok != NULL)
{
char* minus = dStrchr(currTok, '-');
if (minus)
{
// add a range of frames
*minus = '\0';
S32 range_a = dAtoi(currTok);
S32 range_b = dAtoi(minus+1);
if (range_b < range_a)
{
// reverse frame range
for (S32 i = range_a; i >= range_b; i--)
animTexFrames.push_back((U8)(i % n_tiles));
}
else
{
// forward frame range
for (S32 i = range_a; i <= range_b; i++)
animTexFrames.push_back((U8)(i % n_tiles));
}
}
else
{
// add one frame
animTexFrames.push_back((U8)(dAtoi(currTok) % n_tiles));
}
currTok = dStrtok(NULL, " \t");
}
// Here we pre-calculate the UVs for each frame tile, which are
// tiled inside the UV region specified by texCoords. Since the
// UVs are calculated using bilinear interpolation, the texCoords
// region does *not* have to be an axis-aligned rectangle.
if (animTexUVs)
delete [] animTexUVs;
animTexUVs = new Point2F[(animTexTiling.x+1)*(animTexTiling.y+1)];
// interpolate points on the left and right edge of the uv quadrangle
Point2F lf_pt = texCoords[0];
Point2F rt_pt = texCoords[3];
// per-row delta for left and right interpolated points
Point2F lf_d = (texCoords[1] - texCoords[0])/(F32)animTexTiling.y;
Point2F rt_d = (texCoords[2] - texCoords[3])/(F32)animTexTiling.y;
S32 idx = 0;
for (S32 yy = 0; yy <= animTexTiling.y; yy++)
{
Point2F p = lf_pt;
Point2F dp = (rt_pt - lf_pt)/(F32)animTexTiling.x;
for (S32 xx = 0; xx <= animTexTiling.x; xx++)
{
animTexUVs[idx++] = p;
p += dp;
}
lf_pt += lf_d;
rt_pt += rt_d;
}
// cleanup
delete [] tokCopy;
numFrames = animTexFrames.size();
}
}
return !error;
}
//-----------------------------------------------------------------------------
// Initialize particle
//-----------------------------------------------------------------------------
void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelocity)
{
init->dataBlock = this;
// Calculate the constant accleration...
init->vel += inheritVelocity * inheritedVelFactor;
init->acc = init->vel * constantAcceleration;
// Calculate this instance's lifetime...
init->totalLifetime = lifetimeMS;
if (lifetimeVarianceMS != 0)
init->totalLifetime += S32(gRandGen.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS);
// assign spin amount
init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax );
// apply spin bias
init->spinSpeed *= spinBias;
// randomize spin direction
if (randomizeSpinDir && (gRandGen.randI( 0, 1 ) == 1))
init->spinSpeed = -init->spinSpeed;
}
bool ParticleData::reload(char errorBuffer[256])
{
bool error = false;
/*
numFrames = 0;
for( S32 i=0; i<PDC_MAX_TEX; i++ )
{
if( textureNameList[i] && textureNameList[i][0] )
{
textureList[i] = TextureHandle( textureNameList[i], MeshTexture );
if (!textureList[i].getName())
{
dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureNameList[i]);
error = true;
}
numFrames++;
}
}
*/
return !error;
}
DefineEngineMethod(ParticleData, reload, void, (),,
"Reloads this particle.\n"
"@tsexample\n"
"// Get the editor's current particle\n"
"%particle = PE_ParticleEditor.currParticle\n\n"
"// Change a particle value\n"
"%particle.setFieldValue( %propertyField, %value );\n\n"
"// Reload it\n"
"%particle.reload();\n"
"@endtsexample\n" )
{
char errorBuffer[256];
object->reload(errorBuffer);
}
//#define TRACK_PARTICLE_DATA_CLONES
#ifdef TRACK_PARTICLE_DATA_CLONES
static int particle_data_clones = 0;
#endif
ParticleData::ParticleData(const ParticleData& other, bool temp_clone) : SimDataBlock(other, temp_clone)
{
#ifdef TRACK_PARTICLE_DATA_CLONES
particle_data_clones++;
if (particle_data_clones == 1)
Con::errorf("ParticleData -- Clones are on the loose!");
#endif
dragCoefficient = other.dragCoefficient;
windCoefficient = other.windCoefficient;
gravityCoefficient = other.gravityCoefficient;
inheritedVelFactor = other.inheritedVelFactor;
constantAcceleration = other.constantAcceleration;
lifetimeMS = other.lifetimeMS;
lifetimeVarianceMS = other.lifetimeVarianceMS;
spinSpeed = other.spinSpeed;
spinRandomMin = other.spinRandomMin;
spinRandomMax = other.spinRandomMax;
useInvAlpha = other.useInvAlpha;
animateTexture = other.animateTexture;
numFrames = other.numFrames; // -- calc from other fields
framesPerSec = other.framesPerSec;
dMemcpy( colors, other.colors, sizeof( colors ) );
dMemcpy( sizes, other.sizes, sizeof( sizes ) );
dMemcpy( times, other.times, sizeof( times ) );
animTexUVs = other.animTexUVs; // -- calc from other fields
dMemcpy( texCoords, other.texCoords, sizeof( texCoords ) );
animTexTiling = other.animTexTiling;
animTexFramesString = other.animTexFramesString;
animTexFrames = other.animTexFrames; // -- parsed from animTexFramesString
CLONE_ASSET_REFACTOR(Texture);
spinBias = other.spinBias;
randomizeSpinDir = other.randomizeSpinDir;
CLONE_ASSET_REFACTOR(TextureExt);
constrain_pos = other.constrain_pos;
start_angle = other.start_angle;
angle_variance = other.angle_variance;
sizeBias = other.sizeBias;
}
ParticleData::~ParticleData()
{
if (animTexUVs)
{
delete [] animTexUVs;
}
if (!isTempClone())
return;
#ifdef TRACK_PARTICLE_DATA_CLONES
if (particle_data_clones > 0)
{
particle_data_clones--;
if (particle_data_clones == 0)
Con::errorf("ParticleData -- Clones eliminated!");
}
else
Con::errorf("ParticleData -- Too many clones deleted!");
#endif
}
void ParticleData::onPerformSubstitutions()
{
char errorBuffer[256];
reload(errorBuffer);
}
DEF_ASSET_BINDS_REFACTOR(ParticleData, Texture);
ConsoleType(stringList, TypeParticleList, Vector<StringTableEntry>, "")
ConsoleGetType(TypeParticleList)
{
Vector<StringTableEntry>* vec = (Vector<StringTableEntry> *)dptr;
S32 buffSize = (vec->size() * 15) + 16;
char* returnBuffer = Con::getReturnBuffer(buffSize);
S32 maxReturn = buffSize;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<const char*>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%s ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType(TypeParticleList)
{
Vector<StringTableEntry>* vec = (Vector<StringTableEntry> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
S32 numUnits = 0;
if (argc == 1)
{
const char* values = argv[0];
numUnits = StringUnit::getUnitCount(values, " ");
if (numUnits > 1)
bool dafgdf = true;
for (U32 i = 0; i < numUnits; i++)
{
const char* value = StringUnit::getUnit(values, i, " ");
vec->push_back(StringTable->insert(value));
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(StringTable->insert(argv[i]));
}
else
Con::printf("TypeParticleList must be set as { a, b, c, ... } or \"a b c ...\"");
if (numUnits > 1)
{
for (U32 x = 0; x < numUnits; x++)
{
Vector<const char*> testVec = *vec;
String test = testVec[x];
Con::printf("TypeParticleList vec results: %s", testVec[x]);
}
}
bool test = false;
}
#ifdef TORQUE_TOOLS
//-----------------------------------------------------------------------------
// GuiInspectorTypeExpandableIdList
//-----------------------------------------------------------------------------
/**/IMPLEMENT_CONOBJECT(GuiInspectorTypeParticleDataList);
ConsoleDocClass(GuiInspectorTypeParticleDataList,
"@brief Inspector field type for ParticleData lists\n\n"
"Editor use only.\n\n"
"@internal"
);
GuiControl* GuiInspectorTypeParticleDataList::constructEditControl()
{
mStack = new GuiStackControl();
if (mStack == NULL)
return mStack;
mStack->registerObject();
mStack->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
mStack->setPadding(3);
mNewParticleBtn = new GuiIconButtonCtrl();
mNewParticleBtn->registerObject();
mNewParticleBtn->_setBitmap(StringTable->insert("ToolsModule:iconAdd_image"));
mNewParticleBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
mNewParticleBtn->setHorizSizing(horizResizeRight);
mNewParticleBtn->mMakeIconSquare = true;
mNewParticleBtn->mFitBitmapToButton = true;
mNewParticleBtn->setExtent(20, 20);
char szBuffer[512];
dSprintf(szBuffer, sizeof(szBuffer), "ParticleEditor.addParticleSlot(%s, %s);",
mNewParticleBtn->getIdString(), mInspector->getInspectObject()->getIdString());
mNewParticleBtn->setField("Command", szBuffer);
GuiContainer* newBtnCtnr = new GuiContainer();
newBtnCtnr->registerObject();
newBtnCtnr->addObject(mNewParticleBtn);
newBtnCtnr->setHorizSizing(horizResizeWidth);
mStack->addObject(newBtnCtnr);
//Particle 0
mParticleSlot0Ctrl = _buildParticleEntryField(0);
mStack->addObject(mParticleSlot0Ctrl);
//Now the non-default entries if we already have some
Parent::updateValue();
const char* data = getData();
if (data != NULL && !String::isEmpty(data))
{
U32 particlesCount = StringUnit::getUnitCount(data, " ");
for (U32 i=1; i < particlesCount; i++)
{
GuiControl* particleSlotCtrl = _buildParticleEntryField(i);
mStack->addObject(particleSlotCtrl);
}
}
_registerEditControl(mStack);
//constructEditControlChildren(retCtrl, getWidth());
//retCtrl->addObject(mScriptValue);
/*char szBuffer[512];
dSprintf(szBuffer, 512, "setClipboard(%d.getText());", mScriptValue->getId());
mCopyButton->setField("Command", szBuffer);
addObject(mCopyButton);*/
mUseHeightOverride = true;
mHeightOverride = (mStack->getCount() * 23) + 6;
return mStack;
}
GuiControl* GuiInspectorTypeParticleDataList::_buildParticleEntryField(const S32& index)
{
Point2I editPos = mStack->getPosition();
Point2I editExtent = mStack->getExtent();
//Particle 0
GuiControl* particleSlotCtrl = new GuiControl();
particleSlotCtrl->registerObject();
particleSlotCtrl->setExtent(editExtent.x, 18);
particleSlotCtrl->setHorizSizing(horizResizeWidth);
GuiButtonCtrl* listBtn = new GuiButtonCtrl();
listBtn->registerObject();
listBtn->setHorizSizing(horizResizeLeft);
listBtn->setInternalName("particleDropdown");
listBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile");
listBtn->setExtent(editExtent.x - 40, 18);
char szBuffer[512];
dSprintf(szBuffer, sizeof(szBuffer), "ParticleEditor.changeParticleSlot(%s, %s, %d);",
listBtn->getIdString(), mInspector->getInspectObject()->getIdString(), index);
listBtn->setField("Command", szBuffer);
if (mField && index != -1)
{
Parent::updateValue();
const char* data = getData();
if (data != NULL && !String::isEmpty(data))
{
const char* particleSlotData = StringUnit::getUnit(data, index, " ");
listBtn->setText(particleSlotData);
}
}
particleSlotCtrl->addObject(listBtn);
GuiButtonCtrl* editSlotBtn = new GuiIconButtonCtrl();
editSlotBtn->registerObject();
editSlotBtn->setText(StringTable->insert("..."));
editSlotBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile");
editSlotBtn->setHorizSizing(horizResizeRight);
editSlotBtn->setInternalName("editBtn");
editSlotBtn->setPosition(editExtent.x - 40, 0);
editSlotBtn->setExtent(20, 20);
char cmdBuffer[512];
dSprintf(cmdBuffer, sizeof(cmdBuffer), "ParticleEditor.editParticleSlot(%s, %s, %d);",
this->getIdString(), mInspector->getInspectObject()->getIdString(), index);
editSlotBtn->setField("Command", cmdBuffer);
particleSlotCtrl->addObject(editSlotBtn);
if (index != 0)
{
GuiIconButtonCtrl* deleteSlotBtn = new GuiIconButtonCtrl();
deleteSlotBtn->registerObject();
deleteSlotBtn->_setBitmap(StringTable->insert("ToolsModule:iconCancel_image"));
deleteSlotBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
deleteSlotBtn->setHorizSizing(horizResizeRight);
deleteSlotBtn->setInternalName("deleteBtn");
deleteSlotBtn->mMakeIconSquare = true;
deleteSlotBtn->mFitBitmapToButton = true;
deleteSlotBtn->setPosition(editExtent.x - 20, 0);
deleteSlotBtn->setExtent(20, 20);
char cmdBuffer[512];
dSprintf(cmdBuffer, sizeof(cmdBuffer), "ParticleEditor.clearParticleSlot(%s, %s, %d);",
this->getIdString(), mInspector->getInspectObject()->getIdString(), index);
deleteSlotBtn->setField("Command", cmdBuffer);
particleSlotCtrl->addObject(deleteSlotBtn);
}
return particleSlotCtrl;
}
void GuiInspectorTypeParticleDataList::_populateMenu(GuiPopUpMenuCtrlEx* menu)
{
// Check whether we should show profiles from the editor category.
const bool showEditorProfiles = Con::getBoolVariable("$pref::GuiEditor::showEditorProfiles", false);
// Add the control profiles to the menu.
SimGroup* grp = Sim::getGuiDataGroup();
SimSetIterator iter(grp);
for (; *iter; ++iter)
{
GuiControlProfile* profile = dynamic_cast<GuiControlProfile*>(*iter);
if (!profile)
continue;
if (!showEditorProfiles && profile->mCategory.compare("Editor", 0, String::NoCase) == 0)
continue;
menu->addEntry(profile->getName(), profile->getId());
}
menu->sort();
}
bool GuiInspectorTypeParticleDataList::updateRects()
{
S32 rowSize = 18;
S32 dividerPos, dividerMargin;
mInspector->getDivider(dividerPos, dividerMargin);
Point2I fieldExtent = getExtent();
Point2I fieldPos = getPosition();
mCaptionRect.set(0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y);
mEditCtrlRect.set(fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin, fieldExtent.y);
S32 cellWidth = mCeil((dividerPos - dividerMargin - 29));
mNewParticleBtn->resize(Point2I(mEditCtrlRect.extent.x - 20, 0), Point2I(20, 20));
//Particle Slot 0 is mandatory
auto c = mStack->getCount();
for (U32 i=0; i < mStack->getCount(); i++)
{
GuiControl* slot = static_cast<GuiControl*>(mStack->getObject(i));
slot->resize(Point2I(0, 0), Point2I(mEditCtrlRect.extent.x, rowSize));
GuiControl* dropdown = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("particleDropdown")));
if (dropdown)
{
dropdown->setPosition(0, 0);
dropdown->setExtent(mEditCtrlRect.extent.x - 40, 20);
}
GuiControl* editBtn = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("editBtn")));
if (editBtn)
{
editBtn->setPosition(mEditCtrlRect.extent.x - 40, 0);
editBtn->setExtent(20, 20);
}
GuiControl* deleteBtn = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("deleteBtn")));
if (deleteBtn)
{
deleteBtn->setPosition(mEditCtrlRect.extent.x - 20, 0);
deleteBtn->setExtent(20, 20);
}
}
mEdit->resize(mEditCtrlRect.point, mEditCtrlRect.extent);
mHeightOverride = (mStack->getCount() * 23) + 6;
//mCopyButton->resize(Point2I(mProfile->mTextOffset.x, rowSize + 3), Point2I(45, 15));
//mPasteButton->resize(Point2I(mProfile->mTextOffset.x, rowSize + rowSize + 6), Point2I(45, 15));
return true;
}
void GuiInspectorTypeParticleDataList::consoleInit()
{
Parent::consoleInit();
ConsoleBaseType::getType(TypeParticleList)->setInspectorFieldType("GuiInspectorTypeParticleDataList");
}
#endif