2017-07-26 08:35:44 +00:00
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
|
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
|
|
|
//
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
// of this software and associated documentation files (the "Software"), to
|
|
|
|
|
// deal in the Software without restriction, including without limitation the
|
|
|
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
|
//
|
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
|
//
|
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
|
// IN THE SOFTWARE.
|
|
|
|
|
//
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
|
|
|
|
|
#include "afx/arcaneFX.h"
|
|
|
|
|
|
|
|
|
|
#include "math/mathIO.h"
|
|
|
|
|
#include "scene/sceneManager.h"
|
|
|
|
|
#include "T3D/gameBase/gameProcess.h"
|
|
|
|
|
|
|
|
|
|
#include "afx/util/afxPath.h"
|
|
|
|
|
#include "afx/util/afxPath3D.h"
|
|
|
|
|
#include "afx/ce/afxParticleEmitter.h"
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterData);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( afxParticleEmitterData,
|
|
|
|
|
"@brief A base datablock inherited by AFX Particle Emitter effects.\n\n"
|
|
|
|
|
|
|
|
|
|
"A base datablock inherited by AFX Particle Emitter effects."
|
|
|
|
|
"\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup afxEffects\n"
|
|
|
|
|
"@ingroup AFX\n"
|
|
|
|
|
"@ingroup Datablocks\n"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterData::afxParticleEmitterData()
|
|
|
|
|
{
|
|
|
|
|
fade_velocity = false; // coordinate velocity amount with fade amout
|
|
|
|
|
fade_offset = false; // coordinate ejection-offset amount with fade amount
|
|
|
|
|
pe_vector.set(0.0,0.0,0.0);
|
|
|
|
|
pe_vector_is_world = false;
|
|
|
|
|
tpaths_string = ST_NULLSTRING;
|
|
|
|
|
tPathDataBlocks.clear();
|
|
|
|
|
tPathDataBlockIds.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterData::afxParticleEmitterData(const afxParticleEmitterData& other, bool temp_clone) : ParticleEmitterData(other, temp_clone)
|
|
|
|
|
{
|
|
|
|
|
fade_velocity = other.fade_velocity;
|
|
|
|
|
fade_offset = other.fade_offset;
|
|
|
|
|
pe_vector = other.pe_vector;
|
|
|
|
|
pe_vector_is_world = other.pe_vector_is_world;
|
|
|
|
|
tpaths_string = other.tpaths_string;
|
|
|
|
|
tPathDataBlocks = other.tPathDataBlocks;
|
|
|
|
|
//tPathDataBlockIds = other.tPathDataBlockIds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterData::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
addField("fadeVelocity", TypeBool, Offset(fade_velocity, afxParticleEmitterData),
|
|
|
|
|
"If true, the initial velocity of emitted particles is multiplied by the fade amount "
|
|
|
|
|
"of the containing effect wrapper. As the effect fades-in and out, so does the "
|
|
|
|
|
"initial velocity of new particles.");
|
|
|
|
|
addField("fadeOffset", TypeBool, Offset(fade_offset, afxParticleEmitterData),
|
|
|
|
|
"If true, the ejection offset of emitted particles is multiplied by the fade amount "
|
|
|
|
|
"of the containing effect wrapper. As the effect fades-in and out, so does the "
|
|
|
|
|
"ejection offset of new particles.");
|
|
|
|
|
addField("vector", TypePoint3F, Offset(pe_vector, afxParticleEmitterData),
|
|
|
|
|
"General direction vector used for emitting particles. Its exact interpretation is "
|
|
|
|
|
"determined by the particle emitter subclass.");
|
|
|
|
|
addField("vectorIsWorld", TypeBool, Offset(pe_vector_is_world, afxParticleEmitterData),
|
|
|
|
|
"Sets whether the vector field should be interpreted as a vector in the world "
|
|
|
|
|
"coordinate system.");
|
|
|
|
|
addField("pathsTransform", TypeString, Offset(tpaths_string, afxParticleEmitterData),
|
|
|
|
|
"A string of paths to be used as transform paths. Each path name must reference an "
|
|
|
|
|
"afxPathData datablock. Transform paths are used to translate particles along a given "
|
|
|
|
|
"path or series of paths.");
|
|
|
|
|
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterData::packData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::packData(stream);
|
|
|
|
|
|
|
|
|
|
stream->writeFlag(fade_velocity);
|
|
|
|
|
stream->writeFlag(fade_offset);
|
|
|
|
|
mathWrite(*stream, pe_vector);
|
|
|
|
|
stream->writeFlag(pe_vector_is_world);
|
|
|
|
|
|
|
|
|
|
stream->write(tPathDataBlockIds.size());
|
|
|
|
|
for (int i = 0; i < tPathDataBlockIds.size(); i++)
|
|
|
|
|
stream->write(tPathDataBlockIds[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterData::unpackData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::unpackData(stream);
|
|
|
|
|
|
|
|
|
|
fade_velocity = stream->readFlag();
|
|
|
|
|
fade_offset = stream->readFlag();
|
|
|
|
|
mathRead(*stream, &pe_vector);
|
|
|
|
|
pe_vector_is_world = stream->readFlag();
|
|
|
|
|
|
|
|
|
|
U32 n_db; stream->read(&n_db);
|
|
|
|
|
tPathDataBlockIds.setSize(n_db);
|
|
|
|
|
for (U32 i = 0; i < n_db; i++)
|
|
|
|
|
stream->read(&tPathDataBlockIds[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterData::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( Parent::onAdd() == false )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (tpaths_string != ST_NULLSTRING && tpaths_string[0] == '\0')
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) empty transform paths string.", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tpaths_string != ST_NULLSTRING && dStrlen(tpaths_string) > 255)
|
|
|
|
|
{
|
|
|
|
|
Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) transform paths string too long [> 255 chars].", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tpaths_string != ST_NULLSTRING)
|
|
|
|
|
{
|
|
|
|
|
Vector<char*> dataBlocks(__FILE__, __LINE__);
|
2018-03-09 01:59:40 +00:00
|
|
|
dsize_t tokCopyLen = dStrlen(tpaths_string) + 1;
|
|
|
|
|
char* tokCopy = new char[tokCopyLen];
|
|
|
|
|
dStrcpy(tokCopy, tpaths_string, tokCopyLen);
|
2017-07-26 08:35:44 +00:00
|
|
|
|
|
|
|
|
char* currTok = dStrtok(tokCopy, " \t");
|
|
|
|
|
while (currTok != NULL)
|
|
|
|
|
{
|
|
|
|
|
dataBlocks.push_back(currTok);
|
|
|
|
|
currTok = dStrtok(NULL, " \t");
|
|
|
|
|
}
|
|
|
|
|
if (dataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid transform paths string. No datablocks found", getName());
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
tPathDataBlocks.clear();
|
|
|
|
|
tPathDataBlockIds.clear();
|
|
|
|
|
|
|
|
|
|
for (U32 i = 0; i < dataBlocks.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(dataBlocks[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find transform path datablock: %s", getName(), dataBlocks[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
tPathDataBlocks.push_back(pData);
|
|
|
|
|
tPathDataBlockIds.push_back(pData->getId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
if (tPathDataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any transform path datablocks", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterData::preload(bool server, String &errorStr)
|
|
|
|
|
{
|
|
|
|
|
if (Parent::preload(server, errorStr) == false)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
tPathDataBlocks.clear();
|
|
|
|
|
for (U32 i = 0; i < tPathDataBlockIds.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(tPathDataBlockIds[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
|
|
|
"ParticleEmitterData(%s) unable to find transform path datablock: %d",
|
|
|
|
|
getName(), tPathDataBlockIds[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
tPathDataBlocks.push_back(pData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// VECTOR
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterVectorData);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( afxParticleEmitterVectorData,
|
|
|
|
|
"@brief An AFX customized particle emitter that emits particles along a 3D vector.\n\n"
|
|
|
|
|
|
|
|
|
|
"An AFX customized particle emitter that emits particles along a 3D vector."
|
|
|
|
|
"\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup afxEffects\n"
|
|
|
|
|
"@ingroup AFX\n"
|
|
|
|
|
"@ingroup Datablocks\n"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterVectorData::afxParticleEmitterVectorData()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterVectorData::afxParticleEmitterVectorData(const afxParticleEmitterVectorData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterVectorData::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterVectorData::packData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::packData(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterVectorData::unpackData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::unpackData(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterVectorData::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if (Parent::onAdd() == false)
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterVectorData::preload(bool server, String &errorStr)
|
|
|
|
|
{
|
|
|
|
|
if (Parent::preload(server, errorStr) == false)
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// CONE
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterConeData);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( afxParticleEmitterConeData,
|
|
|
|
|
"@brief An AFX customized particle emitter that emits particles within a cone shape.\n\n"
|
|
|
|
|
|
|
|
|
|
"An AFX customized particle emitter that emits particles within a cone shape."
|
|
|
|
|
"\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup afxEffects\n"
|
|
|
|
|
"@ingroup AFX\n"
|
|
|
|
|
"@ingroup Datablocks\n"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterConeData::afxParticleEmitterConeData()
|
|
|
|
|
{
|
|
|
|
|
spread_min = 0.0f;
|
|
|
|
|
spread_max = 90.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterConeData::afxParticleEmitterConeData(const afxParticleEmitterConeData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
|
|
|
|
|
{
|
|
|
|
|
spread_min = other.spread_min;
|
|
|
|
|
spread_max = other.spread_max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterConeData::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
addField("spreadMin", TypeF32, Offset(spread_min, afxParticleEmitterConeData),
|
|
|
|
|
"...");
|
|
|
|
|
addField("spreadMax", TypeF32, Offset(spread_max, afxParticleEmitterConeData),
|
|
|
|
|
"...");
|
|
|
|
|
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterConeData::packData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::packData(stream);
|
|
|
|
|
|
|
|
|
|
stream->writeRangedU32((U32)spread_min, 0, 180);
|
|
|
|
|
stream->writeRangedU32((U32)spread_max, 0, 180);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterConeData::unpackData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::unpackData(stream);
|
|
|
|
|
|
|
|
|
|
spread_min = stream->readRangedU32(0, 180);
|
|
|
|
|
spread_max = stream->readRangedU32(0, 180);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterConeData::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( Parent::onAdd() == false )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (spread_min < 0.0f)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin < 0.0", getName());
|
|
|
|
|
spread_min = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
if (spread_max > 180.0f)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMax > 180.0f", getName());
|
|
|
|
|
spread_max = 180.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spread_max > 179.5f)
|
|
|
|
|
spread_max = 179.5f;
|
|
|
|
|
if (spread_min > 179.5f)
|
|
|
|
|
spread_min = 179.5f;
|
|
|
|
|
|
|
|
|
|
if (spread_min > spread_max)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin > spreadMax", getName());
|
|
|
|
|
spread_min = spread_max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterConeData::preload(bool server, String &errorStr)
|
|
|
|
|
{
|
|
|
|
|
if (Parent::preload(server, errorStr) == false)
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// PATH
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterPathData);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( afxParticleEmitterPathData,
|
|
|
|
|
"@brief An AFX customized particle emitter that emits particles along a path.\n\n"
|
|
|
|
|
|
|
|
|
|
"An AFX customized particle emitter that emits particles along a path."
|
|
|
|
|
"\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup afxEffects\n"
|
|
|
|
|
"@ingroup AFX\n"
|
|
|
|
|
"@ingroup Datablocks\n"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterPathData::afxParticleEmitterPathData()
|
|
|
|
|
{
|
|
|
|
|
epaths_string = ST_NULLSTRING;
|
|
|
|
|
epathDataBlocks.clear();
|
|
|
|
|
epathDataBlockIds.clear();
|
|
|
|
|
path_origin_type = PATHEMIT_ORIGIN;
|
|
|
|
|
ground_conform = false;
|
|
|
|
|
ground_conform_terrain = true;
|
|
|
|
|
ground_conform_interiors = true;
|
|
|
|
|
ground_conform_height = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterPathData::afxParticleEmitterPathData(const afxParticleEmitterPathData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
|
|
|
|
|
{
|
|
|
|
|
epaths_string = other.epaths_string;
|
|
|
|
|
epathDataBlocks = other.epathDataBlocks;
|
|
|
|
|
//epathDataBlockIds = other.epathDataBlockIds;
|
|
|
|
|
path_origin_type = other.path_origin_type;
|
|
|
|
|
ground_conform = other.ground_conform;
|
|
|
|
|
ground_conform_terrain = other.ground_conform_terrain;
|
|
|
|
|
ground_conform_interiors = other.ground_conform_interiors;
|
|
|
|
|
ground_conform_height = other.ground_conform_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImplementEnumType( afxParticleEmitterPath_OriginType, "Possible particle emitter path origin types.\n" "@ingroup afxParticleEmitterPath\n\n" )
|
|
|
|
|
{ afxParticleEmitterPathData::PATHEMIT_ORIGIN, "origin", "..." },
|
|
|
|
|
{ afxParticleEmitterPathData::PATHEMIT_POINT, "point", "..." },
|
|
|
|
|
{ afxParticleEmitterPathData::PATHEMIT_VECTOR, "vector", "..." },
|
|
|
|
|
{ afxParticleEmitterPathData::PATHEMIT_TANGENT, "tangent", "..." },
|
|
|
|
|
EndImplementEnumType;
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPathData::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
addField("paths", TypeString, Offset(epaths_string, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
|
|
|
|
|
addField("pathOrigin", TYPEID<afxParticleEmitterPathData::PathOriginType>(), Offset(path_origin_type, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
|
|
|
|
|
// JTF Note: take a look at these and make sure they are ok.
|
|
|
|
|
addField("groundConform", TypeBool, Offset(ground_conform, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
addField("groundConformTerrain", TypeBool, Offset(ground_conform_terrain, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
addField("groundConformInteriors", TypeBool, Offset(ground_conform_interiors, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
addField("groundConformHeight", TypeF32, Offset(ground_conform_height, afxParticleEmitterPathData),
|
|
|
|
|
"...");
|
|
|
|
|
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPathData::packData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::packData(stream);
|
|
|
|
|
|
|
|
|
|
stream->write(epathDataBlockIds.size());
|
|
|
|
|
for (int i = 0; i < epathDataBlockIds.size(); i++)
|
|
|
|
|
stream->write(epathDataBlockIds[i]);
|
|
|
|
|
stream->write(path_origin_type);
|
|
|
|
|
stream->writeFlag(ground_conform);
|
|
|
|
|
stream->writeFlag(ground_conform_terrain);
|
|
|
|
|
stream->writeFlag(ground_conform_interiors);
|
|
|
|
|
stream->write(ground_conform_height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPathData::unpackData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::unpackData(stream);
|
|
|
|
|
|
|
|
|
|
U32 n_db; stream->read(&n_db);
|
|
|
|
|
epathDataBlockIds.setSize(n_db);
|
|
|
|
|
for (U32 i = 0; i < n_db; i++)
|
|
|
|
|
stream->read(&epathDataBlockIds[i]);
|
|
|
|
|
stream->read(&path_origin_type);
|
|
|
|
|
ground_conform = stream->readFlag();
|
|
|
|
|
ground_conform_terrain = stream->readFlag();
|
|
|
|
|
ground_conform_interiors = stream->readFlag();
|
|
|
|
|
stream->read(&ground_conform_height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterPathData::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( Parent::onAdd() == false )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// path
|
|
|
|
|
if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0')
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255)
|
|
|
|
|
{
|
|
|
|
|
Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (epaths_string != ST_NULLSTRING)
|
|
|
|
|
{
|
|
|
|
|
Vector<char*> dataBlocks(__FILE__, __LINE__);
|
2018-03-09 01:59:40 +00:00
|
|
|
dsize_t tokCopyLen = dStrlen(epaths_string) + 1;
|
|
|
|
|
char* tokCopy = new char[tokCopyLen];
|
|
|
|
|
dStrcpy(tokCopy, epaths_string, tokCopyLen);
|
2017-07-26 08:35:44 +00:00
|
|
|
|
|
|
|
|
char* currTok = dStrtok(tokCopy, " \t");
|
|
|
|
|
while (currTok != NULL)
|
|
|
|
|
{
|
|
|
|
|
dataBlocks.push_back(currTok);
|
|
|
|
|
currTok = dStrtok(NULL, " \t");
|
|
|
|
|
}
|
|
|
|
|
if (dataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string. No datablocks found", getName());
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
epathDataBlocks.clear();
|
|
|
|
|
epathDataBlockIds.clear();
|
|
|
|
|
|
|
|
|
|
for (U32 i = 0; i < dataBlocks.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(dataBlocks[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
epathDataBlocks.push_back(pData);
|
|
|
|
|
epathDataBlockIds.push_back(pData->getId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
if (epathDataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterPathData::preload(bool server, String &errorStr)
|
|
|
|
|
{
|
|
|
|
|
if (Parent::preload(server, errorStr) == false)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
epathDataBlocks.clear();
|
|
|
|
|
for (U32 i = 0; i < epathDataBlockIds.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(epathDataBlockIds[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
|
|
|
"afxParticleEmitterPathData(%s) unable to find path datablock: %d",
|
|
|
|
|
getName(), epathDataBlockIds[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
epathDataBlocks.push_back(pData);
|
|
|
|
|
}
|
|
|
|
|
parts_per_eject = epathDataBlocks.size();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPathData::onPerformSubstitutions()
|
|
|
|
|
{
|
|
|
|
|
Parent::onPerformSubstitutions();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0')
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName());
|
|
|
|
|
return;// false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255)
|
|
|
|
|
{
|
|
|
|
|
Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName());
|
|
|
|
|
return;// false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (epaths_string != ST_NULLSTRING)
|
|
|
|
|
{
|
|
|
|
|
Vector<char*> dataBlocks(__FILE__, __LINE__);
|
2018-03-09 01:59:40 +00:00
|
|
|
dsize_t tokCopyLen = dStrlen(epaths_string) + 1;
|
|
|
|
|
char* tokCopy = new char[tokCopyLen];
|
|
|
|
|
dStrcpy(tokCopy, epaths_string, tokCopyLen);
|
2017-07-26 08:35:44 +00:00
|
|
|
|
|
|
|
|
char* currTok = dStrtok(tokCopy, " \t");
|
|
|
|
|
while (currTok != NULL)
|
|
|
|
|
{
|
|
|
|
|
dataBlocks.push_back(currTok);
|
|
|
|
|
currTok = dStrtok(NULL, " \t");
|
|
|
|
|
}
|
|
|
|
|
if (dataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string. No datablocks found", getName());
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
return;// false;
|
|
|
|
|
}
|
|
|
|
|
epathDataBlocks.clear();
|
|
|
|
|
epathDataBlockIds.clear();
|
|
|
|
|
|
|
|
|
|
for (U32 i = 0; i < dataBlocks.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(dataBlocks[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
epathDataBlocks.push_back(pData);
|
|
|
|
|
epathDataBlockIds.push_back(pData->getId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete [] tokCopy;
|
|
|
|
|
if (epathDataBlocks.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName());
|
|
|
|
|
return;// false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*epathDataBlocks.clear();
|
|
|
|
|
for (U32 i = 0; i < epathDataBlockIds.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pData = NULL;
|
|
|
|
|
if (Sim::findObject(epathDataBlockIds[i], pData) == false)
|
|
|
|
|
{
|
|
|
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
|
|
|
"afxParticleEmitterPathData(%s) unable to find path datablock: %d",
|
|
|
|
|
getName(), epathDataBlockIds[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
epathDataBlocks.push_back(pData);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
parts_per_eject = epathDataBlocks.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// DISC
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterDiscData);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( afxParticleEmitterDiscData,
|
|
|
|
|
"@brief An AFX customized particle emitter that emits particles within a disc shape.\n\n"
|
|
|
|
|
|
|
|
|
|
"An AFX customized particle emitter that emits particles within a disc shape."
|
|
|
|
|
"\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup afxEffects\n"
|
|
|
|
|
"@ingroup AFX\n"
|
|
|
|
|
"@ingroup Datablocks\n"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterDiscData::afxParticleEmitterDiscData()
|
|
|
|
|
{
|
|
|
|
|
pe_radius_min = 0.0f;
|
|
|
|
|
pe_radius_max = 1.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterDiscData::afxParticleEmitterDiscData(const afxParticleEmitterDiscData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
|
|
|
|
|
{
|
|
|
|
|
pe_radius_min = other.pe_radius_min;
|
|
|
|
|
pe_radius_max = other.pe_radius_max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterDiscData::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
addField("radiusMin", TypeF32, Offset(pe_radius_min, afxParticleEmitterDiscData),
|
|
|
|
|
"...");
|
|
|
|
|
addField("radiusMax", TypeF32, Offset(pe_radius_max, afxParticleEmitterDiscData),
|
|
|
|
|
"...");
|
|
|
|
|
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterDiscData::packData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::packData(stream);
|
|
|
|
|
|
|
|
|
|
stream->writeInt((S32)(pe_radius_min * 100), 16);
|
|
|
|
|
stream->writeInt((S32)(pe_radius_max * 100), 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterDiscData::unpackData(BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::unpackData(stream);
|
|
|
|
|
|
|
|
|
|
pe_radius_min = stream->readInt(16) / 100.0f;
|
|
|
|
|
pe_radius_max = stream->readInt(16) / 100.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterDiscData::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( Parent::onAdd() == false )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterDiscData::preload(bool server, String &errorStr)
|
|
|
|
|
{
|
|
|
|
|
if (Parent::preload(server, errorStr) == false)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
|
|
|
|
|
afxParticleEmitter::afxParticleEmitter()
|
|
|
|
|
{
|
2020-05-11 18:54:23 +00:00
|
|
|
mDataBlock = NULL;
|
2017-07-26 08:35:44 +00:00
|
|
|
pe_vector.set(0,0,1);
|
|
|
|
|
pe_vector_norm.set(0,0,1);
|
|
|
|
|
tpaths.clear();
|
|
|
|
|
tpath_mults.clear();
|
|
|
|
|
n_tpath_points = 0;
|
|
|
|
|
tpath_points = NULL;
|
|
|
|
|
afx_owner = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitter::~afxParticleEmitter()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitter::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( !Parent::onAdd() )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (dynamic_cast<afxParticleEmitterData*>(mDataBlock))
|
|
|
|
|
init_paths();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::onRemove()
|
|
|
|
|
{
|
|
|
|
|
if (dynamic_cast<afxParticleEmitterData*>(mDataBlock))
|
|
|
|
|
cleanup_paths();
|
|
|
|
|
|
|
|
|
|
Parent::onRemove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::init_paths()
|
|
|
|
|
{
|
|
|
|
|
if (!mDataBlock)
|
|
|
|
|
{
|
|
|
|
|
n_tpath_points = 0;
|
|
|
|
|
tpath_points = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->tPathDataBlocks.size() < 1)
|
|
|
|
|
{
|
|
|
|
|
n_tpath_points = 0;
|
|
|
|
|
tpath_points = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
n_tpath_points = mDataBlock->tPathDataBlocks.size();
|
|
|
|
|
tpath_points = new Point3F*[n_tpath_points];
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i < n_tpath_points; i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pd = mDataBlock->tPathDataBlocks[i];
|
|
|
|
|
if (!pd)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (pd->getSubstitutionCount() > 0 && afx_owner)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* orig_db = pd;
|
|
|
|
|
pd = new afxPathData(*orig_db, true);
|
|
|
|
|
orig_db->performSubstitutions(pd, afx_owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pd->num_points > 0)
|
|
|
|
|
{
|
|
|
|
|
afxPath3D* path = new afxPath3D();
|
|
|
|
|
if (pd->times)
|
|
|
|
|
path->buildPath( pd->num_points, pd->points, pd->times, pd->delay, 1.0f );
|
|
|
|
|
else if (pd->lifetime == 0)
|
|
|
|
|
path->buildPath( pd->num_points, pd->points, pd->delay, 1.0f );
|
|
|
|
|
else
|
|
|
|
|
path->buildPath( pd->num_points, pd->points, pd->delay, pd->delay+pd->lifetime );
|
|
|
|
|
path->setLoopType( pd->loop_type );
|
|
|
|
|
tpaths.push_back(path);
|
|
|
|
|
|
|
|
|
|
tpath_mults.push_back( pd->mult );
|
|
|
|
|
|
|
|
|
|
tpath_points[i] = new Point3F[pd->num_points];
|
|
|
|
|
for (U32 j=0; j<pd->num_points; j++)
|
|
|
|
|
tpath_points[i][j] = pd->points[j];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Con::warnf("afxParticleEmitter::init_paths() -- pathsTransform datablock (%d) has no points.", i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pd->isTempClone())
|
|
|
|
|
delete pd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::cleanup_paths()
|
|
|
|
|
{
|
|
|
|
|
if (n_tpath_points < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i < tpaths.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (tpaths[i])
|
|
|
|
|
delete tpaths[i];
|
|
|
|
|
}
|
|
|
|
|
tpaths.clear();
|
|
|
|
|
|
|
|
|
|
if (tpath_points)
|
|
|
|
|
{
|
|
|
|
|
if (mDataBlock)
|
|
|
|
|
{
|
|
|
|
|
for (U32 i=0; i < n_tpath_points; i++)
|
|
|
|
|
{
|
|
|
|
|
if (tpath_points[i])
|
|
|
|
|
delete [] tpath_points[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete [] tpath_points;
|
|
|
|
|
tpath_points = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::sub_particleUpdate(Particle* part)
|
|
|
|
|
{
|
|
|
|
|
if (tpaths.size() < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
F32 t = ((F32)part->currentAge)/((F32)part->totalLifetime);
|
|
|
|
|
for (U32 i=0; i < tpaths.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
F32 t_last = part->t_last;
|
|
|
|
|
Point3F path_delta = (t_last <= 0.0f) ? tpaths[i]->evaluateAtTime(t) : tpaths[i]->evaluateAtTime(t_last, t);
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->tPathDataBlocks[i]->concentric)
|
|
|
|
|
{
|
|
|
|
|
// scale radial vector by x-component of path
|
|
|
|
|
part->pos_local += part->radial_v*path_delta.x;
|
|
|
|
|
// scale axis vector by z-component of path
|
|
|
|
|
part->pos_local += pe_vector_norm*path_delta.z;
|
|
|
|
|
// y-component is ignored
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
part->pos_local += path_delta;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
part->t_last = t;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::preCompute(const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
// Put vector into the space of the input matrix
|
|
|
|
|
pe_vector = mDataBlock->pe_vector;
|
|
|
|
|
if (!mDataBlock->pe_vector_is_world)
|
|
|
|
|
mat.mulV(pe_vector);
|
|
|
|
|
|
|
|
|
|
pe_vector_norm = pe_vector;
|
|
|
|
|
pe_vector_norm.normalize();
|
|
|
|
|
|
|
|
|
|
// Transform Paths: rebuild with current matrix
|
|
|
|
|
for( U32 i=0; i < tpaths.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
for( U32 j=0; j < tpaths[i]->getNumPoints(); j++ )
|
|
|
|
|
{
|
|
|
|
|
Point3F p = tpath_points[i][j];
|
|
|
|
|
mat.mulV(p);
|
|
|
|
|
tpaths[i]->setPointPosition(j, p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tpaths[i]->reBuildPath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub_preCompute(mat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::afx_emitParticles(const Point3F& point, const bool useLastPosition, const Point3F& velocity, const U32 numMilliseconds)
|
|
|
|
|
{
|
|
|
|
|
if (mDead) return;
|
|
|
|
|
|
|
|
|
|
// lifetime over - no more particles
|
|
|
|
|
if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Point3F realStart;
|
|
|
|
|
if (useLastPosition && mHasLastPosition)
|
|
|
|
|
realStart = mLastPosition;
|
|
|
|
|
else
|
|
|
|
|
realStart = point;
|
|
|
|
|
|
|
|
|
|
afx_emitParticles(realStart, point, velocity, numMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::afx_emitParticles(const Point3F& start, const Point3F& end, const Point3F& velocity, const U32 numMilliseconds)
|
|
|
|
|
{
|
|
|
|
|
if (mDead) return;
|
|
|
|
|
|
|
|
|
|
// lifetime over - no more particles
|
|
|
|
|
if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
U32 currTime = 0;
|
|
|
|
|
bool particlesAdded = false;
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
|
|
|
|
|
{
|
|
|
|
|
sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
U32 advanceMS = numMilliseconds - currTime;
|
|
|
|
|
if (mDataBlock->overrideAdvance == false && advanceMS != 0)
|
|
|
|
|
{
|
|
|
|
|
for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
|
|
|
|
|
{
|
|
|
|
|
sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
|
|
|
|
|
particlesAdded = true;
|
|
|
|
|
|
|
|
|
|
Particle* last_part = part_list_head.next;
|
|
|
|
|
if (last_part)
|
|
|
|
|
{
|
|
|
|
|
if (advanceMS > last_part->totalLifetime)
|
|
|
|
|
{
|
|
|
|
|
part_list_head.next = last_part->next;
|
|
|
|
|
n_parts--;
|
|
|
|
|
last_part->next = part_freelist;
|
|
|
|
|
part_freelist = last_part;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
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, 0, -9.81) * 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_local += last_part->vel * t;
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
|
|
|
|
|
{
|
|
|
|
|
sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
|
|
|
|
|
particlesAdded = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( particlesAdded == true )
|
|
|
|
|
updateBBox();
|
|
|
|
|
|
|
|
|
|
if( n_parts > 0 && mSceneManager == NULL )
|
|
|
|
|
{
|
|
|
|
|
gClientSceneGraph->addObjectToScene(this);
|
|
|
|
|
ClientProcessList::get()->addObject(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mLastPosition = end;
|
|
|
|
|
mHasLastPosition = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Particle* afxParticleEmitter::alloc_particle()
|
|
|
|
|
{
|
|
|
|
|
n_parts++;
|
|
|
|
|
|
|
|
|
|
// this should happen rarely
|
|
|
|
|
if (n_parts > n_part_capacity)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Particle* pNew = part_freelist;
|
|
|
|
|
part_freelist = pNew->next;
|
|
|
|
|
pNew->next = part_list_head.next;
|
|
|
|
|
part_list_head.next = pNew;
|
|
|
|
|
|
|
|
|
|
return pNew;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ParticleData* afxParticleEmitter::pick_particle_type()
|
|
|
|
|
{
|
|
|
|
|
U32 dBlockIndex = (U32)(mCeil(gRandGen.randF() * F32(mDataBlock->particleDataBlocks.size())) - 1);
|
|
|
|
|
return mDataBlock->particleDataBlocks[dBlockIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitter::onNewDataBlock(GameBaseData* dptr, bool reload)
|
|
|
|
|
{
|
|
|
|
|
mDataBlock = dynamic_cast<afxParticleEmitterData*>(dptr);
|
|
|
|
|
if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->isTempClone())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
scriptOnNewDataBlock();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitter::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);
|
|
|
|
|
pos_pe = zero_point;
|
|
|
|
|
setTransform(xfm);
|
|
|
|
|
|
|
|
|
|
preCompute(xfm);
|
|
|
|
|
afx_emitParticles(zero_point, true, velocity, numMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pos_pe = point;
|
|
|
|
|
preCompute(xfm);
|
|
|
|
|
afx_emitParticles(point, true, velocity, numMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// VECTOR
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterVector::afxParticleEmitterVector()
|
|
|
|
|
{
|
2020-05-11 18:54:23 +00:00
|
|
|
mDataBlock = NULL;
|
2017-07-26 08:35:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterVector::~afxParticleEmitterVector()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterVector::onNewDataBlock(GameBaseData* dptr, bool reload)
|
|
|
|
|
{
|
|
|
|
|
mDataBlock = dynamic_cast<afxParticleEmitterVectorData*>(dptr);
|
|
|
|
|
if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->isTempClone())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
scriptOnNewDataBlock();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterVector::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
|
|
|
|
|
{
|
|
|
|
|
Particle* pNew = alloc_particle();
|
|
|
|
|
ParticleData* part_db = pick_particle_type();
|
|
|
|
|
|
|
|
|
|
Point3F pos_start = pos;
|
|
|
|
|
if (part_db->constrain_pos)
|
|
|
|
|
pos_start.set(0,0,0);
|
|
|
|
|
|
|
|
|
|
F32 initialVel = mDataBlock->ejectionVelocity;
|
|
|
|
|
initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
|
|
|
|
|
if(mDataBlock->fade_velocity)
|
|
|
|
|
initialVel *= fade_amt;
|
|
|
|
|
|
|
|
|
|
F32 ejection_offset = mDataBlock->ejectionOffset;
|
|
|
|
|
if(mDataBlock->fade_offset)
|
|
|
|
|
ejection_offset *= fade_amt;
|
|
|
|
|
|
|
|
|
|
pNew->pos = pos_start + (pe_vector_norm * ejection_offset);
|
|
|
|
|
pNew->pos_local = pNew->pos;
|
|
|
|
|
|
|
|
|
|
pNew->vel = pe_vector_norm * initialVel;
|
|
|
|
|
if (mDataBlock->orientParticles)
|
|
|
|
|
pNew->orientDir = pe_vector_norm;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
pNew->radial_v.set(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
part_db->initializeParticle(pNew, vel);
|
|
|
|
|
updateKeyData( pNew );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterVector::sub_preCompute(const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// CONE
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterCone::afxParticleEmitterCone()
|
|
|
|
|
{
|
2020-05-11 18:54:23 +00:00
|
|
|
mDataBlock = NULL;
|
2017-07-26 08:35:44 +00:00
|
|
|
cone_v.set(0,0,1);
|
|
|
|
|
cone_s0.set(0,0,1);
|
|
|
|
|
cone_s1.set(0,0,1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterCone::~afxParticleEmitterCone()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterCone::onNewDataBlock(GameBaseData* dptr, bool reload)
|
|
|
|
|
{
|
|
|
|
|
mDataBlock = dynamic_cast<afxParticleEmitterConeData*>(dptr);
|
|
|
|
|
if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->isTempClone())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
scriptOnNewDataBlock();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterCone::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
|
|
|
|
|
{
|
|
|
|
|
Particle* pNew = alloc_particle();
|
|
|
|
|
ParticleData* part_db = pick_particle_type();
|
|
|
|
|
|
|
|
|
|
Point3F pos_start = pos;
|
|
|
|
|
if (part_db->constrain_pos)
|
|
|
|
|
pos_start.set(0,0,0);
|
|
|
|
|
|
|
|
|
|
F32 initialVel = mDataBlock->ejectionVelocity;
|
|
|
|
|
initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
|
|
|
|
|
if(mDataBlock->fade_velocity)
|
|
|
|
|
initialVel *= fade_amt;
|
|
|
|
|
|
|
|
|
|
// Randomly choose a vector between cone_s0 and cone_s1 and normalize:
|
|
|
|
|
Point3F vec;
|
|
|
|
|
F32 t = mRandF(0.0f, 1.0f);
|
|
|
|
|
vec.interpolate(cone_s0, cone_s1, t);
|
|
|
|
|
vec.normalize();
|
|
|
|
|
|
|
|
|
|
// Randomly rotate about cone_v
|
|
|
|
|
F32 theta = mRandF(0.0f, M_2PI_F);
|
|
|
|
|
AngAxisF thetaRot(cone_v, theta);
|
|
|
|
|
MatrixF temp(true);
|
|
|
|
|
thetaRot.setMatrix(&temp);
|
|
|
|
|
temp.mulP(vec);
|
|
|
|
|
|
|
|
|
|
F32 ejection_offset = mDataBlock->ejectionOffset;
|
|
|
|
|
if(mDataBlock->fade_offset)
|
|
|
|
|
ejection_offset *= fade_amt;
|
|
|
|
|
|
|
|
|
|
pNew->pos = pos_start + (vec * ejection_offset);
|
|
|
|
|
pNew->pos_local = pNew->pos;
|
|
|
|
|
|
|
|
|
|
pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel;
|
|
|
|
|
if (mDataBlock->orientParticles)
|
|
|
|
|
pNew->orientDir = vec;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
pNew->radial_v.set(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
part_db->initializeParticle(pNew, vel);
|
|
|
|
|
updateKeyData( pNew );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterCone::sub_preCompute(const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
// Find vectors on the XZ plane corresponding to the inner and outer spread angles:
|
|
|
|
|
// (tan is infinite at PI/4 or 90 degrees)
|
|
|
|
|
cone_v.set( 0.0f, 0.0f, 1.0f );
|
|
|
|
|
|
|
|
|
|
cone_s0.x = mTan( mDegToRad( ((afxParticleEmitterConeData*)mDataBlock)->spread_min / 2.0f ));
|
|
|
|
|
cone_s0.y = 0.0f;
|
|
|
|
|
cone_s0.z = 1.0f;
|
|
|
|
|
|
|
|
|
|
cone_s1.x = mTan( mDegToRad(((afxParticleEmitterConeData*)mDataBlock)->spread_max / 2.0f ));
|
|
|
|
|
cone_s1.y = 0.0f;
|
|
|
|
|
cone_s1.z = 1.0f;
|
|
|
|
|
|
|
|
|
|
Point3F axis;
|
|
|
|
|
F32 theta = mAcos( mDot(cone_v, pe_vector_norm) );
|
|
|
|
|
|
|
|
|
|
if( M_PI_F-theta < POINT_EPSILON )
|
|
|
|
|
{
|
|
|
|
|
cone_v.neg();
|
|
|
|
|
cone_s0.neg();
|
|
|
|
|
cone_s1.neg();
|
|
|
|
|
}
|
|
|
|
|
else if( theta > POINT_EPSILON )
|
|
|
|
|
{
|
|
|
|
|
mCross(pe_vector_norm, cone_v, &axis);
|
|
|
|
|
axis.normalize();
|
|
|
|
|
|
|
|
|
|
AngAxisF thetaRot(axis, theta);
|
|
|
|
|
MatrixF temp(true);
|
|
|
|
|
thetaRot.setMatrix(&temp);
|
|
|
|
|
|
|
|
|
|
temp.mulP(cone_v);
|
|
|
|
|
temp.mulP(cone_s0);
|
|
|
|
|
temp.mulP(cone_s1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// PATH
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterPath::afxParticleEmitterPath()
|
|
|
|
|
{
|
2020-05-11 18:54:23 +00:00
|
|
|
mDataBlock = NULL;
|
2017-07-26 08:35:44 +00:00
|
|
|
epaths.clear();
|
|
|
|
|
epath_mults.clear();
|
|
|
|
|
n_epath_points = 0;
|
|
|
|
|
epath_points = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterPath::~afxParticleEmitterPath()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterPath::onNewDataBlock(GameBaseData* dptr, bool reload)
|
|
|
|
|
{
|
|
|
|
|
mDataBlock = dynamic_cast<afxParticleEmitterPathData*>(dptr);
|
|
|
|
|
if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->isTempClone())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
scriptOnNewDataBlock();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterPath::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if( !Parent::onAdd() )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (dynamic_cast<afxParticleEmitterPathData*>(mDataBlock))
|
|
|
|
|
init_paths();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::onRemove()
|
|
|
|
|
{
|
|
|
|
|
if (dynamic_cast<afxParticleEmitterPathData*>(mDataBlock))
|
|
|
|
|
cleanup_paths();
|
|
|
|
|
|
|
|
|
|
Parent::onRemove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::init_paths()
|
|
|
|
|
{
|
|
|
|
|
if (!mDataBlock || ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size() < 1)
|
|
|
|
|
{
|
|
|
|
|
n_epath_points = 0;
|
|
|
|
|
epath_points = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n_epath_points = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size();
|
|
|
|
|
epath_points = new Point3F*[n_epath_points];
|
|
|
|
|
dMemset(epath_points, 0, n_epath_points*sizeof(Point3F*));
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i < n_epath_points; i++)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* pd = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks[i];
|
|
|
|
|
if (!pd)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (pd->getSubstitutionCount() > 0 && afx_owner)
|
|
|
|
|
{
|
|
|
|
|
afxPathData* orig_db = pd;
|
|
|
|
|
pd = new afxPathData(*orig_db, true);
|
|
|
|
|
orig_db->performSubstitutions(pd, afx_owner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pd->num_points > 0)
|
|
|
|
|
{
|
|
|
|
|
afxPath3D* path = new afxPath3D();
|
|
|
|
|
if (pd->times)
|
|
|
|
|
path->buildPath( pd->num_points, pd->points, pd->times, 0.0f, 1.0f );
|
|
|
|
|
else
|
|
|
|
|
path->buildPath( pd->num_points, pd->points, 0.0f, 1.0f );
|
|
|
|
|
epaths.push_back(path);
|
|
|
|
|
|
|
|
|
|
epath_mults.push_back( pd->mult );
|
|
|
|
|
|
|
|
|
|
epath_points[i] = new Point3F[pd->num_points];
|
|
|
|
|
for (U32 j=0; j<pd->num_points; j++)
|
|
|
|
|
epath_points[i][j] = pd->points[j];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Con::warnf("afxParticleEmitterPath::init_paths() -- paths datablock (%d) has no points.", i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pd->isTempClone())
|
|
|
|
|
delete pd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::cleanup_paths()
|
|
|
|
|
{
|
|
|
|
|
if (n_epath_points < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i < epaths.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (epaths[i])
|
|
|
|
|
delete epaths[i];
|
|
|
|
|
}
|
|
|
|
|
epaths.clear();
|
|
|
|
|
|
|
|
|
|
if (epath_points)
|
|
|
|
|
{
|
|
|
|
|
if (mDataBlock)
|
|
|
|
|
{
|
|
|
|
|
for (U32 i=0; i < n_epath_points; i++)
|
|
|
|
|
{
|
|
|
|
|
if (epath_points[i])
|
|
|
|
|
delete [] epath_points[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete [] epath_points;
|
|
|
|
|
epath_points = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
|
|
|
|
|
{
|
|
|
|
|
if (part_idx >= epaths.size() || !epaths[part_idx])
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Particle* pNew = alloc_particle();
|
|
|
|
|
ParticleData* part_db = pick_particle_type();
|
|
|
|
|
|
|
|
|
|
Point3F pos_start = pos;
|
|
|
|
|
if (part_db->constrain_pos)
|
|
|
|
|
pos_start.set(0,0,0);
|
|
|
|
|
|
|
|
|
|
F32 initialVel = mDataBlock->ejectionVelocity;
|
|
|
|
|
initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
|
|
|
|
|
if(mDataBlock->fade_velocity)
|
|
|
|
|
initialVel *= fade_amt;
|
|
|
|
|
|
|
|
|
|
// Randomly choose a curve parameter between [0.0,1.0] and evaluate the curve there
|
|
|
|
|
F32 param = mRandF(0.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
Point3F curve_pos = epaths[part_idx]->evaluateAtTime(param);
|
|
|
|
|
|
|
|
|
|
Point3F vec;
|
|
|
|
|
switch (((afxParticleEmitterPathData*)mDataBlock)->path_origin_type)
|
|
|
|
|
{
|
|
|
|
|
case afxParticleEmitterPathData::PATHEMIT_ORIGIN :
|
|
|
|
|
vec = curve_pos;
|
|
|
|
|
vec.normalize();
|
|
|
|
|
break;
|
|
|
|
|
case afxParticleEmitterPathData::PATHEMIT_POINT :
|
|
|
|
|
vec = curve_pos-pe_vector;
|
|
|
|
|
vec.normalize();
|
|
|
|
|
break;
|
|
|
|
|
case afxParticleEmitterPathData::PATHEMIT_VECTOR :
|
|
|
|
|
vec = pe_vector_norm;
|
|
|
|
|
break;
|
|
|
|
|
case afxParticleEmitterPathData::PATHEMIT_TANGENT :
|
|
|
|
|
vec = epaths[part_idx]->evaluateTangentAtTime(param);
|
|
|
|
|
vec.normalize();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 ejection_offset = mDataBlock->ejectionOffset;
|
|
|
|
|
if(mDataBlock->fade_offset)
|
|
|
|
|
ejection_offset *= fade_amt;
|
|
|
|
|
|
|
|
|
|
pNew->pos = pos_start + curve_pos + (vec * ejection_offset);
|
|
|
|
|
pNew->pos_local = pNew->pos;
|
|
|
|
|
|
|
|
|
|
pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel;
|
|
|
|
|
if (mDataBlock->orientParticles)
|
|
|
|
|
pNew->orientDir = vec;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
pNew->radial_v.set(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
part_db->initializeParticle(pNew, vel);
|
|
|
|
|
updateKeyData( pNew );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::sub_preCompute(const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
for( U32 i=0; i < epaths.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
for( U32 j=0; j < epaths[i]->getNumPoints(); j++ )
|
|
|
|
|
{
|
|
|
|
|
Point3F p = epath_points[i][j];
|
|
|
|
|
mat.mulV(p);
|
|
|
|
|
|
|
|
|
|
p *= epath_mults[i];
|
|
|
|
|
if(mDataBlock->ground_conform) {
|
|
|
|
|
groundConformPoint(p, mat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
epaths[i]->setPointPosition(j, p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
epaths[i]->reBuildPath();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterPath::groundConformPoint(Point3F& point, const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
point += mat.getPosition();
|
|
|
|
|
|
|
|
|
|
RayInfo rInfo;
|
|
|
|
|
bool hit = false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->ground_conform_interiors)
|
|
|
|
|
{
|
|
|
|
|
U32 mask = InteriorLikeObjectType;
|
|
|
|
|
if (mDataBlock->ground_conform_terrain)
|
|
|
|
|
{
|
|
|
|
|
mask |= TerrainObjectType | TerrainLikeObjectType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Point3F above_pos(point); above_pos.z += 0.1f;
|
|
|
|
|
Point3F below_pos(point); below_pos.z -= 10000;
|
|
|
|
|
hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo);
|
|
|
|
|
if (!hit)
|
|
|
|
|
{
|
|
|
|
|
above_pos.z = point.z + 10000;
|
|
|
|
|
below_pos.z = point.z - 0.1f;
|
|
|
|
|
hit = gClientContainer.castRay(below_pos, above_pos, mask, &rInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (mDataBlock->ground_conform_terrain)
|
|
|
|
|
{
|
|
|
|
|
U32 mask = TerrainObjectType | TerrainLikeObjectType;
|
|
|
|
|
Point3F above_pos(point); above_pos.z += 10000;
|
|
|
|
|
Point3F below_pos(point); below_pos.z -= 10000;
|
|
|
|
|
hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hit)
|
|
|
|
|
{
|
|
|
|
|
F32 terrain_z = rInfo.point.z;
|
|
|
|
|
F32 new_z = terrain_z + mDataBlock->ground_conform_height;
|
|
|
|
|
point.z = new_z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
point -= mat.getPosition();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
// DISC
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterDisc::afxParticleEmitterDisc()
|
|
|
|
|
{
|
2020-05-11 18:54:23 +00:00
|
|
|
mDataBlock = NULL;
|
2017-07-26 08:35:44 +00:00
|
|
|
disc_v.set(0,0,1);
|
|
|
|
|
disc_r.set(1,0,0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
afxParticleEmitterDisc::~afxParticleEmitterDisc()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool afxParticleEmitterDisc::onNewDataBlock(GameBaseData* dptr, bool reload)
|
|
|
|
|
{
|
|
|
|
|
mDataBlock = dynamic_cast<afxParticleEmitterDiscData*>(dptr);
|
|
|
|
|
if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (mDataBlock->isTempClone())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterDisc::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
|
|
|
|
|
{
|
|
|
|
|
Particle* pNew = alloc_particle();
|
|
|
|
|
ParticleData* part_db = pick_particle_type();
|
|
|
|
|
|
|
|
|
|
Point3F pos_start = pos;
|
|
|
|
|
if (part_db->constrain_pos)
|
|
|
|
|
pos_start.set(0,0,0);
|
|
|
|
|
|
|
|
|
|
F32 initialVel = mDataBlock->ejectionVelocity;
|
|
|
|
|
initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
|
|
|
|
|
if(mDataBlock->fade_velocity)
|
|
|
|
|
initialVel *= fade_amt;
|
|
|
|
|
|
|
|
|
|
// Randomly choose a radius vector
|
|
|
|
|
Point3F r( disc_r );
|
|
|
|
|
F32 radius = mRandF(((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_min, ((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_max);
|
|
|
|
|
r *= radius;
|
|
|
|
|
|
|
|
|
|
// Randomly rotate r about disc_v
|
|
|
|
|
F32 theta = mRandF(0.0f, M_2PI_F);
|
|
|
|
|
AngAxisF thetaRot(disc_v, theta);
|
|
|
|
|
MatrixF temp(true);
|
|
|
|
|
thetaRot.setMatrix(&temp);
|
|
|
|
|
temp.mulP(r);
|
|
|
|
|
|
|
|
|
|
F32 ejection_offset = mDataBlock->ejectionOffset;
|
|
|
|
|
if(mDataBlock->fade_offset)
|
|
|
|
|
ejection_offset *= fade_amt;
|
|
|
|
|
|
|
|
|
|
pNew->pos = pos_start + r + (disc_v * ejection_offset);
|
|
|
|
|
pNew->pos_local = pNew->pos;
|
|
|
|
|
|
|
|
|
|
pNew->vel = (mDataBlock->ejectionInvert) ? (disc_v * -initialVel) : (disc_v * initialVel);
|
|
|
|
|
if (mDataBlock->orientParticles)
|
|
|
|
|
pNew->orientDir = disc_v;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
pNew->radial_v = r;
|
|
|
|
|
|
|
|
|
|
part_db->initializeParticle(pNew, vel);
|
|
|
|
|
updateKeyData( pNew );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void afxParticleEmitterDisc::sub_preCompute(const MatrixF& mat)
|
|
|
|
|
{
|
|
|
|
|
disc_v.set(0.0f, 0.0f, 1.0f);
|
|
|
|
|
disc_r.set(1.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
Point3F axis;
|
|
|
|
|
F32 theta = mAcos( mDot(disc_v, pe_vector_norm) );
|
|
|
|
|
|
|
|
|
|
if( M_PI_F-theta < POINT_EPSILON )
|
|
|
|
|
{
|
|
|
|
|
disc_v.neg();
|
|
|
|
|
}
|
|
|
|
|
else if( theta > POINT_EPSILON )
|
|
|
|
|
{
|
|
|
|
|
mCross(pe_vector_norm, disc_v, &axis);
|
|
|
|
|
axis.normalize();
|
|
|
|
|
|
|
|
|
|
AngAxisF thetaRot(axis, theta);
|
|
|
|
|
MatrixF temp(true);
|
|
|
|
|
thetaRot.setMatrix(&temp);
|
|
|
|
|
|
|
|
|
|
temp.mulP(disc_v);
|
|
|
|
|
temp.mulP(disc_r);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
|
|