mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
1448 lines
50 KiB
C++
1448 lines
50 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "T3D/fx/explosion.h"
|
|
|
|
#include "core/resourceManager.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "console/typeValidators.h"
|
|
#include "sfx/sfxSystem.h"
|
|
#include "sfx/sfxTrack.h"
|
|
#include "sfx/sfxTypes.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "lighting/lightInfo.h"
|
|
#include "lighting/lightManager.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "sim/netConnection.h"
|
|
#include "ts/tsShape.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "math/mRandom.h"
|
|
#include "math/mathIO.h"
|
|
#include "math/mathUtils.h"
|
|
#include "T3D/debris.h"
|
|
#include "T3D/gameBase/gameConnection.h"
|
|
#include "T3D/fx/particleEmitter.h"
|
|
#include "T3D/fx/cameraFXMgr.h"
|
|
#include "T3D/debris.h"
|
|
#include "T3D/shapeBase.h"
|
|
#include "T3D/gameBase/gameProcess.h"
|
|
#include "renderInstance/renderPassManager.h"
|
|
#include "console/engineAPI.h"
|
|
|
|
#include "sfx/sfxProfile.h"
|
|
|
|
IMPLEMENT_CONOBJECT(Explosion);
|
|
|
|
ConsoleDocClass( Explosion,
|
|
"@brief The emitter for an explosion effect, with properties defined by a "
|
|
"ExplosionData object.\n\n"
|
|
"@ingroup FX\n"
|
|
"The object will initiate the explosion effects automatically after being "
|
|
"added to the simulation.\n"
|
|
"@tsexample\n"
|
|
"datablock ExplosionData( GrenadeSubExplosion )\n"
|
|
"{\n"
|
|
" offset = 0.25;\n"
|
|
" emitter[0] = GrenadeExpSparkEmitter;\n\n"
|
|
" lightStartRadius = 4.0;\n"
|
|
" lightEndRadius = 0.0;\n"
|
|
" lightStartColor = \"0.9 0.7 0.7\";\n"
|
|
" lightEndColor = \"0.9 0.7 0.7\";\n"
|
|
" lightStartBrightness = 2.0;\n"
|
|
" lightEndBrightness = 0.0;\n"
|
|
"};\n\n"
|
|
"datablock ExplosionData( GrenadeLauncherExplosion )\n"
|
|
"{\n"
|
|
" soundProfile = GrenadeLauncherExplosionSound;\n"
|
|
" lifeTimeMS = 400; // Quick flash, short burn, and moderate dispersal\n\n"
|
|
" // Volume particles\n"
|
|
" particleEmitter = GrenadeExpFireEmitter;\n"
|
|
" particleDensity = 75;\n"
|
|
" particleRadius = 2.25;\n\n"
|
|
" // Point emission\n"
|
|
" emitter[0] = GrenadeExpDustEmitter;\n"
|
|
" emitter[1] = GrenadeExpSparksEmitter;\n"
|
|
" emitter[2] = GrenadeExpSmokeEmitter;\n\n"
|
|
" // Sub explosion objects\n"
|
|
" subExplosion[0] = GrenadeSubExplosion;\n\n"
|
|
" // Camera Shaking\n"
|
|
" shakeCamera = true;\n"
|
|
" camShakeFreq = \"10.0 11.0 9.0\";\n"
|
|
" camShakeAmp = \"15.0 15.0 15.0\";\n"
|
|
" camShakeDuration = 1.5;\n"
|
|
" camShakeRadius = 20;\n\n"
|
|
" // Exploding debris\n"
|
|
" debris = GrenadeDebris;\n"
|
|
" debrisThetaMin = 10;\n"
|
|
" debrisThetaMax = 60;\n"
|
|
" debrisNum = 4;\n"
|
|
" debrisNumVariance = 2;\n"
|
|
" debrisVelocity = 25;\n"
|
|
" debrisVelocityVariance = 5;\n\n"
|
|
" lightStartRadius = 4.0;\n"
|
|
" lightEndRadius = 0.0;\n"
|
|
" lightStartColor = \"1.0 1.0 1.0\";\n"
|
|
" lightEndColor = \"1.0 1.0 1.0\";\n"
|
|
" lightStartBrightness = 4.0;\n"
|
|
" lightEndBrightness = 0.0;\n"
|
|
" lightNormalOffset = 2.0;\n"
|
|
"};\n\n"
|
|
"function ServerPlayExplosion(%position, %datablock)\n"
|
|
"{\n"
|
|
" // Play the given explosion on every client.\n"
|
|
" // The explosion will be transmitted as an event, not attached to any object.\n"
|
|
" for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
|
|
" {\n"
|
|
" %client = ClientGroup.getObject(%idx);\n"
|
|
" commandToClient(%client, 'PlayExplosion', %position, %datablock.getId());\n"
|
|
" }\n"
|
|
"}\n\n"
|
|
"function clientCmdPlayExplosion(%position, %effectDataBlock)\n"
|
|
"{\n"
|
|
" // Play an explosion sent by the server. Make sure this function is defined\n"
|
|
" // on the client.\n"
|
|
" if (isObject(%effectDataBlock))\n"
|
|
" {\n"
|
|
" new Explosion()\n"
|
|
" {\n"
|
|
" position = %position;\n"
|
|
" dataBlock = %effectDataBlock;\n"
|
|
" };\n"
|
|
" }\n"
|
|
"}\n\n"
|
|
"// schedule an explosion\n"
|
|
"schedule(1000, 0, ServerPlayExplosion, \"0 0 0\", GrenadeLauncherExplosion);\n"
|
|
"@endtsexample"
|
|
);
|
|
|
|
#define MaxLightRadius 20
|
|
|
|
MRandomLCG sgRandom(0xdeadbeef);
|
|
|
|
//WLE - Vince - The defaults are bad, the whole point of calling this function\
|
|
//is to determine the explosion coverage on a object. Why would you want them
|
|
//To call this with a null for the ID? In fact, it just returns a 1f if
|
|
//it can't find the object. Seems useless to me. Cause how can I apply
|
|
//damage to a object that doesn't exist?
|
|
|
|
//I could possible see a use with passing in a null covMask, but even that
|
|
//sounds flaky because it will be 100 percent if your saying not to take
|
|
//any thing in consideration for coverage. So I'm removing these defaults they are just bad.
|
|
|
|
DefineEngineFunction(calcExplosionCoverage, F32, (Point3F pos, S32 id, U32 covMask),,
|
|
"@brief Calculates how much an explosion effects a specific object.\n\n"
|
|
"Use this to determine how much damage to apply to objects based on their "
|
|
"distance from the explosion's center point, and whether the explosion is "
|
|
"blocked by other objects.\n\n"
|
|
"@param pos Center position of the explosion.\n"
|
|
"@param id Id of the object of which to check coverage.\n"
|
|
"@param covMask Mask of object types that may block the explosion.\n"
|
|
"@return Coverage value from 0 (not affected by the explosion) to 1 (fully affected)\n\n"
|
|
"@tsexample\n"
|
|
"// Get the position of the explosion.\n"
|
|
"%position = %explosion.getPosition();\n\n"
|
|
"// Set a list of TypeMasks (defined in gameFunctioncs.cpp), seperated by the | character.\n"
|
|
"%TypeMasks = $TypeMasks::StaticObjectType | $TypeMasks::ItemObjectType\n\n"
|
|
"// Acquire the damage value from 0.0f - 1.0f.\n"
|
|
"%coverage = calcExplosionCoverage( %position, %sceneObject, %TypeMasks );\n\n"
|
|
"// Apply damage to object\n"
|
|
"%sceneObject.applyDamage( %coverage * 20 );\n"
|
|
"@endtsexample\n"
|
|
"@ingroup FX")
|
|
{
|
|
Point3F center;
|
|
|
|
SceneObject* sceneObject = NULL;
|
|
if (Sim::findObject(id, sceneObject) == false) {
|
|
Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: couldn't find object: %d", id);
|
|
return 1.0f;
|
|
}
|
|
if (sceneObject->isClientObject() || sceneObject->getContainer() == NULL) {
|
|
Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: object is on the client, or not in the container system");
|
|
return 1.0f;
|
|
}
|
|
|
|
sceneObject->getObjBox().getCenter(¢er);
|
|
center.convolve(sceneObject->getScale());
|
|
sceneObject->getTransform().mulP(center);
|
|
|
|
RayInfo rayInfo;
|
|
sceneObject->disableCollision();
|
|
if (sceneObject->getContainer()->castRay(pos, center, covMask, &rayInfo) == true) {
|
|
// Try casting up and then out
|
|
if (sceneObject->getContainer()->castRay(pos, pos + Point3F(0.0f, 0.0f, 1.0f), covMask, &rayInfo) == false)
|
|
{
|
|
if (sceneObject->getContainer()->castRay(pos + Point3F(0.0f, 0.0f, 1.0f), center, covMask, &rayInfo) == false)
|
|
{
|
|
sceneObject->enableCollision();
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
sceneObject->enableCollision();
|
|
return 0.0f;
|
|
} else {
|
|
sceneObject->enableCollision();
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
IMPLEMENT_CO_DATABLOCK_V1(ExplosionData);
|
|
|
|
ConsoleDocClass( ExplosionData,
|
|
"@brief Defines the attributes of an Explosion: particleEmitters, debris, "
|
|
"lighting and camera shake effects.\n"
|
|
"@ingroup FX\n"
|
|
);
|
|
|
|
ExplosionData::ExplosionData()
|
|
{
|
|
particleDensity = 10;
|
|
particleRadius = 1.0f;
|
|
|
|
faceViewer = false;
|
|
|
|
INIT_ASSET(Sound);
|
|
|
|
//soundProfile = NULL;
|
|
particleEmitter = NULL;
|
|
particleEmitterId = 0;
|
|
|
|
explosionScale.set(1.0f, 1.0f, 1.0f);
|
|
playSpeed = 1.0f;
|
|
|
|
INIT_ASSET(ExplosionShape);
|
|
|
|
explosionAnimation = -1;
|
|
|
|
dMemset( emitterList, 0, sizeof( emitterList ) );
|
|
dMemset( emitterIDList, 0, sizeof( emitterIDList ) );
|
|
dMemset( debrisList, 0, sizeof( debrisList ) );
|
|
dMemset( debrisIDList, 0, sizeof( debrisIDList ) );
|
|
|
|
debrisThetaMin = 0.0f;
|
|
debrisThetaMax = 90.0f;
|
|
debrisPhiMin = 0.0f;
|
|
debrisPhiMax = 360.0f;
|
|
debrisNum = 1;
|
|
debrisNumVariance = 0;
|
|
debrisVelocity = 2.0f;
|
|
debrisVelocityVariance = 0.0f;
|
|
|
|
dMemset( explosionList, 0, sizeof( explosionList ) );
|
|
dMemset( explosionIDList, 0, sizeof( explosionIDList ) );
|
|
|
|
delayMS = 0;
|
|
delayVariance = 0;
|
|
lifetimeMS = 1000;
|
|
lifetimeVariance = 0;
|
|
offset = 0.0f;
|
|
|
|
shakeCamera = false;
|
|
camShakeFreq.set( 10.0f, 10.0f, 10.0f );
|
|
camShakeAmp.set( 1.0f, 1.0f, 1.0f );
|
|
camShakeDuration = 1.5f;
|
|
camShakeRadius = 10.0f;
|
|
camShakeFalloff = 10.0f;
|
|
|
|
for( U32 i=0; i<EC_NUM_TIME_KEYS; i++ )
|
|
{
|
|
times[i] = 1.0f;
|
|
}
|
|
times[0] = 0.0f;
|
|
|
|
for( U32 j=0; j<EC_NUM_TIME_KEYS; j++ )
|
|
{
|
|
sizes[j].set( 1.0f, 1.0f, 1.0f );
|
|
}
|
|
|
|
//
|
|
lightStartRadius = lightEndRadius = 0.0f;
|
|
lightStartColor.set(1.0f,1.0f,1.0f);
|
|
lightEndColor.set(1.0f,1.0f,1.0f);
|
|
lightStartBrightness = 1.0f;
|
|
lightEndBrightness = 1.0f;
|
|
lightNormalOffset = 0.1f;
|
|
}
|
|
|
|
//#define TRACK_EXPLOSION_DATA_CLONES
|
|
|
|
#ifdef TRACK_EXPLOSION_DATA_CLONES
|
|
static int explosion_data_clones = 0;
|
|
#endif
|
|
|
|
ExplosionData::ExplosionData(const ExplosionData& other, bool temp_clone) : GameBaseData(other, temp_clone)
|
|
{
|
|
#ifdef TRACK_EXPLOSION_DATA_CLONES
|
|
explosion_data_clones++;
|
|
if (explosion_data_clones == 1)
|
|
Con::errorf("ExplosionData -- Clones are on the loose!");
|
|
#endif
|
|
|
|
faceViewer = other.faceViewer;
|
|
particleDensity = other.particleDensity;
|
|
particleRadius = other.particleRadius;
|
|
CLONE_ASSET(Sound);
|
|
particleEmitter = other.particleEmitter;
|
|
particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
|
|
explosionScale = other.explosionScale;
|
|
playSpeed = other.playSpeed;
|
|
CLONE_ASSET(ExplosionShape);
|
|
explosionAnimation = other.explosionAnimation; // -- from explosionShape sequence "ambient"
|
|
dMemcpy( emitterList, other.emitterList, sizeof( emitterList ) );
|
|
dMemcpy( emitterIDList, other.emitterIDList, sizeof( emitterIDList ) ); // -- for pack/unpack of emitterList ptrs
|
|
dMemcpy( debrisList, other.debrisList, sizeof( debrisList ) );
|
|
dMemcpy( debrisIDList, other.debrisIDList, sizeof( debrisIDList ) ); // -- for pack/unpack of debrisList ptrs
|
|
debrisThetaMin = other.debrisThetaMin;
|
|
debrisThetaMax = other.debrisThetaMax;
|
|
debrisPhiMin = other.debrisPhiMin;
|
|
debrisPhiMax = other.debrisPhiMax;
|
|
debrisNum = other.debrisNum;
|
|
debrisNumVariance = other.debrisNumVariance;
|
|
debrisVelocity = other.debrisVelocity;
|
|
debrisVelocityVariance = other.debrisVelocityVariance;
|
|
dMemcpy( explosionList, other.explosionList, sizeof( explosionList ) );
|
|
dMemcpy( explosionIDList, other.explosionIDList, sizeof( explosionIDList ) ); // -- for pack/unpack of explosionList ptrs
|
|
delayMS = other.delayMS;
|
|
delayVariance = other.delayVariance;
|
|
lifetimeMS = other.lifetimeMS;
|
|
lifetimeVariance = other.lifetimeVariance;
|
|
offset = other.offset;
|
|
dMemcpy( sizes, other.times, sizeof( sizes ) );
|
|
dMemcpy( times, other.times, sizeof( times ) );
|
|
shakeCamera = other.shakeCamera;
|
|
camShakeFreq = other.camShakeFreq;
|
|
camShakeAmp = other.camShakeAmp;
|
|
camShakeDuration = other.camShakeDuration;
|
|
camShakeRadius = other.camShakeRadius;
|
|
camShakeFalloff = other.camShakeFalloff;
|
|
lightStartRadius = other.lightStartRadius;
|
|
lightEndRadius = other.lightEndRadius;
|
|
lightStartColor = other.lightStartColor;
|
|
lightEndColor = other.lightEndColor;
|
|
lightStartBrightness = other.lightStartBrightness;
|
|
lightEndBrightness = other.lightEndBrightness;
|
|
lightNormalOffset = other.lightNormalOffset;
|
|
// Note - Explosion calls mDataBlock->getName() in warning messages but
|
|
// that should be safe.
|
|
}
|
|
|
|
ExplosionData::~ExplosionData()
|
|
{
|
|
if (!isTempClone())
|
|
return;
|
|
|
|
// particleEmitter, emitterList[*], debrisList[*], explosionList[*] will delete themselves
|
|
|
|
#ifdef TRACK_EXPLOSION_DATA_CLONES
|
|
if (explosion_data_clones > 0)
|
|
{
|
|
explosion_data_clones--;
|
|
if (explosion_data_clones == 0)
|
|
Con::errorf("ExplosionData -- Clones eliminated!");
|
|
}
|
|
else
|
|
Con::errorf("ExplosionData -- Too many clones deleted!");
|
|
#endif
|
|
}
|
|
|
|
ExplosionData* ExplosionData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
|
|
{
|
|
if (!owner || getSubstitutionCount() == 0)
|
|
return this;
|
|
|
|
ExplosionData* sub_explosion_db = new ExplosionData(*this, true);
|
|
performSubstitutions(sub_explosion_db, owner, index);
|
|
|
|
return sub_explosion_db;
|
|
}
|
|
|
|
void ExplosionData::initPersistFields()
|
|
{
|
|
docsURL;
|
|
addGroup("Shapes");
|
|
INITPERSISTFIELD_SHAPEASSET(ExplosionShape, ExplosionData, "@brief Optional shape asset to place at the center of the explosion.\n\n"
|
|
"The <i>ambient</i> animation of this model will be played automatically at the start of the explosion.");
|
|
endGroup("Shapes");
|
|
|
|
addGroup("Sounds");
|
|
INITPERSISTFIELD_SOUNDASSET(Sound, ExplosionData, "Sound to play when this explosion explodes.");
|
|
endGroup("Sounds");
|
|
|
|
addGroup("Particle Effects");
|
|
addField( "faceViewer", TypeBool, Offset(faceViewer, ExplosionData),
|
|
"Controls whether the visual effects of the explosion always face the camera." );
|
|
|
|
addField( "particleEmitter", TYPEID< ParticleEmitterData >(), Offset(particleEmitter, ExplosionData),
|
|
"@brief Emitter used to generate a cloud of particles at the start of the explosion.\n\n"
|
|
"Explosions can generate two different particle effects. The first is a "
|
|
"single burst of particles at the start of the explosion emitted in a "
|
|
"spherical cloud using particleEmitter.\n\n"
|
|
"The second effect spawns the list of ParticleEmitters given by the emitter[] "
|
|
"field. These emitters generate particles in the normal way throughout the "
|
|
"lifetime of the explosion." );
|
|
addField( "particleDensity", TypeS32, Offset(particleDensity, ExplosionData),
|
|
"@brief Density of the particle cloud created at the start of the explosion.\n\n"
|
|
"@see particleEmitter" );
|
|
addField( "particleRadius", TypeF32, Offset(particleRadius, ExplosionData),
|
|
"@brief Radial distance from the explosion center at which cloud particles "
|
|
"are emitted.\n\n"
|
|
"@see particleEmitter" );
|
|
addField( "emitter", TYPEID< ParticleEmitterData >(), Offset(emitterList, ExplosionData), EC_NUM_EMITTERS,
|
|
"@brief List of additional ParticleEmitterData objects to spawn with this "
|
|
"explosion.\n\n"
|
|
"@see particleEmitter" );
|
|
endGroup("Particle Effects");
|
|
|
|
addGroup("Debris");
|
|
addField( "debris", TYPEID< DebrisData >(), Offset(debrisList, ExplosionData), EC_NUM_DEBRIS_TYPES,
|
|
"List of DebrisData objects to spawn with this explosion." );
|
|
addField( "debrisThetaMin", TypeF32, Offset(debrisThetaMin, ExplosionData),
|
|
"Minimum angle, from the horizontal plane, to eject debris from." );
|
|
addField( "debrisThetaMax", TypeF32, Offset(debrisThetaMax, ExplosionData),
|
|
"Maximum angle, from the horizontal plane, to eject debris from." );
|
|
addField( "debrisPhiMin", TypeF32, Offset(debrisPhiMin, ExplosionData),
|
|
"Minimum reference angle, from the vertical plane, to eject debris from." );
|
|
addField( "debrisPhiMax", TypeF32, Offset(debrisPhiMax, ExplosionData),
|
|
"Maximum reference angle, from the vertical plane, to eject debris from." );
|
|
addField( "debrisNum", TypeS32, Offset(debrisNum, ExplosionData),
|
|
"Number of debris objects to create." );
|
|
addField( "debrisNumVariance", TypeS32, Offset(debrisNumVariance, ExplosionData),
|
|
"Variance in the number of debris objects to create (must be from 0 - debrisNum)." );
|
|
addField( "debrisVelocity", TypeF32, Offset(debrisVelocity, ExplosionData),
|
|
"Velocity to toss debris at." );
|
|
addField( "debrisVelocityVariance", TypeF32, Offset(debrisVelocityVariance, ExplosionData),
|
|
"Variance in the debris initial velocity (must be >= 0)." );
|
|
addField( "subExplosion", TYPEID< ExplosionData >(), Offset(explosionList, ExplosionData), EC_MAX_SUB_EXPLOSIONS,
|
|
"List of additional ExplosionData objects to create at the start of the explosion." );
|
|
endGroup("Debris");
|
|
|
|
|
|
addGroup("Animation");
|
|
addField("explosionScale", TypePoint3F, Offset(explosionScale, ExplosionData),
|
|
"\"X Y Z\" scale factor applied to the explosionShape model at the start "
|
|
"of the explosion.");
|
|
addField("playSpeed", TypeF32, Offset(playSpeed, ExplosionData),
|
|
"Time scale at which to play the explosionShape <i>ambient</i> sequence.");
|
|
|
|
addField( "delayMS", TypeS32, Offset(delayMS, ExplosionData),
|
|
"Amount of time, in milliseconds, to delay the start of the explosion effect "
|
|
"from the creation of the Explosion object." );
|
|
addField( "delayVariance", TypeS32, Offset(delayVariance, ExplosionData),
|
|
"Variance, in milliseconds, of delayMS." );
|
|
addField( "lifetimeMS", TypeS32, Offset(lifetimeMS, ExplosionData),
|
|
"@brief Lifetime, in milliseconds, of the Explosion object.\n\n"
|
|
"@note If explosionShape is defined and contains an <i>ambient</i> animation, "
|
|
"this field is ignored, and the playSpeed scaled duration of the animation "
|
|
"is used instead." );
|
|
addField( "lifetimeVariance", TypeS32, Offset(lifetimeVariance, ExplosionData),
|
|
"Variance, in milliseconds, of the lifetimeMS of the Explosion object.\n" );
|
|
addField( "offset", TypeF32, Offset(offset, ExplosionData),
|
|
"@brief Offset distance (in a random direction) of the center of the explosion "
|
|
"from the Explosion object position.\n\n"
|
|
"Most often used to create some variance in position for subExplosion effects." );
|
|
|
|
addField( "times", TypeF32, Offset(times, ExplosionData), EC_NUM_TIME_KEYS,
|
|
"@brief Time keyframes used to scale the explosionShape model.\n\n"
|
|
"Values should be in increasing order from 0.0 - 1.0, and correspond to "
|
|
"the life of the Explosion where 0 is the beginning and 1 is the end of "
|
|
"the explosion lifetime.\n"
|
|
"@see lifetimeMS" );
|
|
addField( "sizes", TypePoint3F, Offset(sizes, ExplosionData), EC_NUM_TIME_KEYS,
|
|
"@brief \"X Y Z\" size keyframes used to scale the explosionShape model.\n\n"
|
|
"The explosionShape (if defined) will be scaled using the times/sizes "
|
|
"keyframes over the lifetime of the explosion.\n"
|
|
"@see lifetimeMS" );
|
|
endGroup("Animation");
|
|
|
|
addGroup("Camera Shake");
|
|
addField( "shakeCamera", TypeBool, Offset(shakeCamera, ExplosionData),
|
|
"Controls whether the camera shakes during this explosion." );
|
|
addField( "camShakeFreq", TypePoint3F, Offset(camShakeFreq, ExplosionData),
|
|
"Frequency of camera shaking, defined in the \"X Y Z\" axes." );
|
|
addField( "camShakeAmp", TypePoint3F, Offset(camShakeAmp, ExplosionData),
|
|
"@brief Amplitude of camera shaking, defined in the \"X Y Z\" axes.\n\n"
|
|
"Set any value to 0 to disable shaking in that axis." );
|
|
addField( "camShakeDuration", TypeF32, Offset(camShakeDuration, ExplosionData),
|
|
"Duration (in seconds) to shake the camera." );
|
|
addField( "camShakeRadius", TypeF32, Offset(camShakeRadius, ExplosionData),
|
|
"Radial distance that a camera's position must be within relative to the "
|
|
"center of the explosion to be shaken." );
|
|
addField( "camShakeFalloff", TypeF32, Offset(camShakeFalloff, ExplosionData),
|
|
"Falloff value for the camera shake." );
|
|
endGroup("Camera Shake");
|
|
|
|
addGroup("Light Emitter");
|
|
addField( "lightStartRadius", TypeF32, Offset(lightStartRadius, ExplosionData),
|
|
"@brief Initial radius of the PointLight created by this explosion.\n\n"
|
|
"Radius is linearly interpolated from lightStartRadius to lightEndRadius "
|
|
"over the lifetime of the explosion.\n"
|
|
"@see lifetimeMS" );
|
|
addField( "lightEndRadius", TypeF32, Offset(lightEndRadius, ExplosionData),
|
|
"@brief Final radius of the PointLight created by this explosion.\n\n"
|
|
"@see lightStartRadius" );
|
|
addField( "lightStartColor", TypeColorF, Offset(lightStartColor, ExplosionData),
|
|
"@brief Initial color of the PointLight created by this explosion.\n\n"
|
|
"Color is linearly interpolated from lightStartColor to lightEndColor "
|
|
"over the lifetime of the explosion.\n"
|
|
"@see lifetimeMS" );
|
|
addField( "lightEndColor", TypeColorF, Offset(lightEndColor, ExplosionData),
|
|
"@brief Final color of the PointLight created by this explosion.\n\n"
|
|
"@see lightStartColor" );
|
|
addField( "lightStartBrightness", TypeF32, Offset(lightStartBrightness, ExplosionData),
|
|
"@brief Initial brightness of the PointLight created by this explosion.\n\n"
|
|
"Brightness is linearly interpolated from lightStartBrightness to "
|
|
"lightEndBrightness over the lifetime of the explosion.\n"
|
|
"@see lifetimeMS" );
|
|
addField("lightEndBrightness", TypeF32, Offset(lightEndBrightness, ExplosionData),
|
|
"@brief Final brightness of the PointLight created by this explosion.\n\n"
|
|
"@see lightStartBrightness" );
|
|
addField( "lightNormalOffset", TypeF32, Offset(lightNormalOffset, ExplosionData),
|
|
"Distance (in the explosion normal direction) of the PointLight position "
|
|
"from the explosion center." );
|
|
endGroup("Light Emitter");
|
|
|
|
// disallow some field substitutions
|
|
onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK
|
|
onlyKeepClearSubstitutions("emitter");
|
|
onlyKeepClearSubstitutions("particleEmitter");
|
|
onlyKeepClearSubstitutions("subExplosion");
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
bool ExplosionData::onAdd()
|
|
{
|
|
if (Parent::onAdd() == false)
|
|
return false;
|
|
|
|
if (explosionScale.x < 0.01f || explosionScale.y < 0.01f || explosionScale.z < 0.01f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s)::onAdd: ExplosionScale components must be >= 0.01", getName());
|
|
explosionScale.x = explosionScale.x < 0.01f ? 0.01f : explosionScale.x;
|
|
explosionScale.y = explosionScale.y < 0.01f ? 0.01f : explosionScale.y;
|
|
explosionScale.z = explosionScale.z < 0.01f ? 0.01f : explosionScale.z;
|
|
}
|
|
|
|
if (debrisThetaMin < 0.0f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMin < 0.0", getName());
|
|
debrisThetaMin = 0.0f;
|
|
}
|
|
if (debrisThetaMax > 180.0f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMax > 180.0", getName());
|
|
debrisThetaMax = 180.0f;
|
|
}
|
|
if (debrisThetaMin > debrisThetaMax) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMin > debrisThetaMax", getName());
|
|
debrisThetaMin = debrisThetaMax;
|
|
}
|
|
if (debrisPhiMin < 0.0f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMin < 0.0", getName());
|
|
debrisPhiMin = 0.0f;
|
|
}
|
|
if (debrisPhiMax > 360.0f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMax > 360.0", getName());
|
|
debrisPhiMax = 360.0f;
|
|
}
|
|
if (debrisPhiMin > debrisPhiMax) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMin > debrisPhiMax", getName());
|
|
debrisPhiMin = debrisPhiMax;
|
|
}
|
|
if (debrisNum > 1000) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisNum > 1000", getName());
|
|
debrisNum = 1000;
|
|
}
|
|
if (debrisNumVariance > 1000) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisNumVariance > 1000", getName());
|
|
debrisNumVariance = 1000;
|
|
}
|
|
if (debrisVelocity < 0.1f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocity < 0.1", getName());
|
|
debrisVelocity = 0.1f;
|
|
}
|
|
if (debrisVelocityVariance > 1000) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocityVariance > 1000", getName());
|
|
debrisVelocityVariance = 1000;
|
|
}
|
|
if (playSpeed < 0.05f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) playSpeed < 0.05", getName());
|
|
playSpeed = 0.05f;
|
|
}
|
|
if (lifetimeMS < 1) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) lifetimeMS < 1", getName());
|
|
lifetimeMS = 1;
|
|
}
|
|
if (lifetimeVariance > lifetimeMS) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) lifetimeVariance > lifetimeMS", getName());
|
|
lifetimeVariance = lifetimeMS;
|
|
}
|
|
if (delayMS < 0) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) delayMS < 0", getName());
|
|
delayMS = 0;
|
|
}
|
|
if (delayVariance > delayMS) {
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) delayVariance > delayMS", getName());
|
|
delayVariance = delayMS;
|
|
}
|
|
if (offset < 0.0f)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) offset < 0.0", getName());
|
|
offset = 0.0f;
|
|
}
|
|
|
|
S32 i;
|
|
for( i=0; i<EC_NUM_DEBRIS_TYPES; i++ )
|
|
{
|
|
if( !debrisList[i] && debrisIDList[i] != 0 )
|
|
{
|
|
if( !Sim::findObject( debrisIDList[i], debrisList[i] ) )
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ExplosionData::onAdd: Invalid packet, bad datablockId(debris): 0x%x", debrisIDList[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
for( i=0; i<EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( !emitterList[i] && emitterIDList[i] != 0 )
|
|
{
|
|
if( Sim::findObject( emitterIDList[i], emitterList[i] ) == false)
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ExplosionData::onAdd: Invalid packet, bad datablockId(particle emitter): 0x%x", emitterIDList[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
for( S32 k=0; k<EC_MAX_SUB_EXPLOSIONS; k++ )
|
|
{
|
|
if( !explosionList[k] && explosionIDList[k] != 0 )
|
|
{
|
|
if( Sim::findObject( explosionIDList[k], explosionList[k] ) == false)
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ExplosionData::onAdd: Invalid packet, bad datablockId(explosion): 0x%x", explosionIDList[k] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ExplosionData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
|
|
PACKDATA_ASSET(ExplosionShape);
|
|
|
|
//PACKDATA_SOUNDASSET(Sound);
|
|
PACKDATA_ASSET(Sound);
|
|
|
|
if (stream->writeFlag(particleEmitter))
|
|
stream->writeRangedU32(particleEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
|
|
stream->writeInt(particleDensity, 14);
|
|
stream->write(particleRadius);
|
|
stream->writeFlag(faceViewer);
|
|
if(stream->writeFlag(explosionScale.x != 1 || explosionScale.y != 1 || explosionScale.z != 1))
|
|
{
|
|
stream->writeInt((S32)(explosionScale.x * 100), 16);
|
|
stream->writeInt((S32)(explosionScale.y * 100), 16);
|
|
stream->writeInt((S32)(explosionScale.z * 100), 16);
|
|
}
|
|
stream->writeInt((S32)(playSpeed * 20), 14);
|
|
stream->writeRangedU32((U32)debrisThetaMin, 0, 180);
|
|
stream->writeRangedU32((U32)debrisThetaMax, 0, 180);
|
|
stream->writeRangedU32((U32)debrisPhiMin, 0, 360);
|
|
stream->writeRangedU32((U32)debrisPhiMax, 0, 360);
|
|
stream->writeRangedU32((U32)debrisNum, 0, 1000);
|
|
stream->writeRangedU32(debrisNumVariance, 0, 1000);
|
|
stream->writeInt((S32)(debrisVelocity * 10), 14);
|
|
stream->writeRangedU32((U32)(debrisVelocityVariance * 10), 0, 10000);
|
|
stream->writeInt(delayMS >> 5, 16);
|
|
stream->writeInt(delayVariance >> 5, 16);
|
|
stream->writeInt(lifetimeMS >> 5, 16);
|
|
stream->writeInt(lifetimeVariance >> 5, 16);
|
|
stream->write(offset);
|
|
|
|
stream->writeFlag( shakeCamera );
|
|
stream->write(camShakeFreq.x);
|
|
stream->write(camShakeFreq.y);
|
|
stream->write(camShakeFreq.z);
|
|
stream->write(camShakeAmp.x);
|
|
stream->write(camShakeAmp.y);
|
|
stream->write(camShakeAmp.z);
|
|
stream->write(camShakeDuration);
|
|
stream->write(camShakeRadius);
|
|
stream->write(camShakeFalloff);
|
|
|
|
for( S32 j=0; j<EC_NUM_DEBRIS_TYPES; j++ )
|
|
{
|
|
if( stream->writeFlag( debrisList[j] ) )
|
|
{
|
|
stream->writeRangedU32( debrisList[j]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
|
|
S32 i;
|
|
for( i=0; i<EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( stream->writeFlag( emitterList[i] != NULL ) )
|
|
{
|
|
stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
|
|
for( i=0; i<EC_MAX_SUB_EXPLOSIONS; i++ )
|
|
{
|
|
if( stream->writeFlag( explosionList[i] != NULL ) )
|
|
{
|
|
stream->writeRangedU32( explosionList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
U32 count;
|
|
for(count = 0; count < EC_NUM_TIME_KEYS; count++)
|
|
if(times[count] >= 1)
|
|
break;
|
|
count++;
|
|
if(count > EC_NUM_TIME_KEYS)
|
|
count = EC_NUM_TIME_KEYS;
|
|
|
|
stream->writeRangedU32(count, 0, EC_NUM_TIME_KEYS);
|
|
|
|
for( i=0; i<count; i++ )
|
|
stream->writeFloat( times[i], 8 );
|
|
|
|
for( i=0; i<count; i++ )
|
|
{
|
|
stream->writeRangedU32((U32)(sizes[i].x * 100), 0, 16000);
|
|
stream->writeRangedU32((U32)(sizes[i].y * 100), 0, 16000);
|
|
stream->writeRangedU32((U32)(sizes[i].z * 100), 0, 16000);
|
|
}
|
|
|
|
// Dynamic light info
|
|
stream->writeFloat(lightStartRadius/MaxLightRadius, 8);
|
|
stream->writeFloat(lightEndRadius/MaxLightRadius, 8);
|
|
stream->writeFloat(lightStartColor.red,7);
|
|
stream->writeFloat(lightStartColor.green,7);
|
|
stream->writeFloat(lightStartColor.blue,7);
|
|
stream->writeFloat(lightEndColor.red,7);
|
|
stream->writeFloat(lightEndColor.green,7);
|
|
stream->writeFloat(lightEndColor.blue,7);
|
|
stream->writeFloat(lightStartBrightness/MaxLightRadius, 8);
|
|
stream->writeFloat(lightEndBrightness/MaxLightRadius, 8);
|
|
stream->write(lightNormalOffset);
|
|
}
|
|
|
|
void ExplosionData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
|
|
UNPACKDATA_ASSET(ExplosionShape);
|
|
|
|
UNPACKDATA_ASSET(Sound);
|
|
|
|
if (stream->readFlag())
|
|
particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
else
|
|
particleEmitterId = 0;
|
|
|
|
particleDensity = stream->readInt(14);
|
|
stream->read(&particleRadius);
|
|
faceViewer = stream->readFlag();
|
|
if(stream->readFlag())
|
|
{
|
|
explosionScale.x = stream->readInt(16) / 100.0f;
|
|
explosionScale.y = stream->readInt(16) / 100.0f;
|
|
explosionScale.z = stream->readInt(16) / 100.0f;
|
|
}
|
|
else
|
|
explosionScale.set(1,1,1);
|
|
playSpeed = stream->readInt(14) / 20.0f;
|
|
debrisThetaMin = stream->readRangedU32(0, 180);
|
|
debrisThetaMax = stream->readRangedU32(0, 180);
|
|
debrisPhiMin = stream->readRangedU32(0, 360);
|
|
debrisPhiMax = stream->readRangedU32(0, 360);
|
|
debrisNum = stream->readRangedU32(0, 1000);
|
|
debrisNumVariance = stream->readRangedU32(0, 1000);
|
|
|
|
debrisVelocity = stream->readInt(14) / 10.0f;
|
|
debrisVelocityVariance = stream->readRangedU32(0, 10000) / 10.0f;
|
|
delayMS = stream->readInt(16) << 5;
|
|
delayVariance = stream->readInt(16) << 5;
|
|
lifetimeMS = stream->readInt(16) << 5;
|
|
lifetimeVariance = stream->readInt(16) << 5;
|
|
|
|
stream->read(&offset);
|
|
|
|
shakeCamera = stream->readFlag();
|
|
stream->read(&camShakeFreq.x);
|
|
stream->read(&camShakeFreq.y);
|
|
stream->read(&camShakeFreq.z);
|
|
stream->read(&camShakeAmp.x);
|
|
stream->read(&camShakeAmp.y);
|
|
stream->read(&camShakeAmp.z);
|
|
stream->read(&camShakeDuration);
|
|
stream->read(&camShakeRadius);
|
|
stream->read(&camShakeFalloff);
|
|
|
|
|
|
for( S32 j=0; j<EC_NUM_DEBRIS_TYPES; j++ )
|
|
{
|
|
if( stream->readFlag() )
|
|
{
|
|
debrisIDList[j] = (S32) stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
|
|
U32 i;
|
|
for( i=0; i<EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( stream->readFlag() )
|
|
{
|
|
emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
|
|
for( S32 k=0; k<EC_MAX_SUB_EXPLOSIONS; k++ )
|
|
{
|
|
if( stream->readFlag() )
|
|
{
|
|
explosionIDList[k] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
}
|
|
|
|
U32 count = stream->readRangedU32(0, EC_NUM_TIME_KEYS);
|
|
|
|
for( i=0; i<count; i++ )
|
|
times[i] = stream->readFloat(8);
|
|
|
|
for( i=0; i<count; i++ )
|
|
{
|
|
sizes[i].x = stream->readRangedU32(0, 16000) / 100.0f;
|
|
sizes[i].y = stream->readRangedU32(0, 16000) / 100.0f;
|
|
sizes[i].z = stream->readRangedU32(0, 16000) / 100.0f;
|
|
}
|
|
|
|
//
|
|
lightStartRadius = stream->readFloat(8) * MaxLightRadius;
|
|
lightEndRadius = stream->readFloat(8) * MaxLightRadius;
|
|
lightStartColor.red = stream->readFloat(7);
|
|
lightStartColor.green = stream->readFloat(7);
|
|
lightStartColor.blue = stream->readFloat(7);
|
|
lightEndColor.red = stream->readFloat(7);
|
|
lightEndColor.green = stream->readFloat(7);
|
|
lightEndColor.blue = stream->readFloat(7);
|
|
lightStartBrightness = stream->readFloat(8) * MaxLightRadius;
|
|
lightEndBrightness = stream->readFloat(8) * MaxLightRadius;
|
|
stream->read( &lightNormalOffset );
|
|
}
|
|
|
|
bool ExplosionData::preload(bool server, String &errorStr)
|
|
{
|
|
if (Parent::preload(server, errorStr) == false)
|
|
return false;
|
|
|
|
if( !server )
|
|
{
|
|
|
|
if (getSound() != StringTable->EmptyString())
|
|
{
|
|
_setSound(getSound());
|
|
|
|
if (!getSoundProfile())
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "SplashData::preload: Cant get an sfxProfile for splash.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!particleEmitter && particleEmitterId != 0)
|
|
if (Sim::findObject(particleEmitterId, particleEmitter) == false)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "Error, unable to load particle emitter for explosion datablock");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mExplosionShapeAsset.notNull()) {
|
|
|
|
// Resolve animations
|
|
explosionAnimation = mExplosionShape->findSequence("ambient");
|
|
|
|
// Preload textures with a dummy instance...
|
|
TSShapeInstance* pDummy = new TSShapeInstance(mExplosionShape, !server);
|
|
delete pDummy;
|
|
|
|
} else {
|
|
explosionAnimation = -1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
//--------------------------------------
|
|
//
|
|
Explosion::Explosion()
|
|
: mDataBlock( NULL )
|
|
{
|
|
mTypeMask |= ExplosionObjectType | LightObjectType;
|
|
|
|
mExplosionInstance = NULL;
|
|
mExplosionThread = NULL;
|
|
|
|
dMemset( mEmitterList, 0, sizeof( mEmitterList ) );
|
|
mMainEmitter = NULL;
|
|
|
|
mFade = 1;
|
|
mDelayMS = 0;
|
|
mCurrMS = 0;
|
|
mEndingMS = 1000;
|
|
mActive = false;
|
|
mCollideType = 0;
|
|
|
|
mInitialNormal.set( 0.0f, 0.0f, 1.0f );
|
|
mRandAngle = sgRandom.randF( 0.0f, 1.0f ) * M_PI_F * 2.0f;
|
|
mLight = LIGHTMGR->createLightInfo();
|
|
|
|
mNetFlags.set( IsGhost );
|
|
ss_object = 0;
|
|
ss_index = 0;
|
|
mDataBlock = 0;
|
|
soundProfile_clone = 0;
|
|
mRandomVal = 0;
|
|
}
|
|
|
|
Explosion::~Explosion()
|
|
{
|
|
if( mExplosionInstance )
|
|
{
|
|
delete mExplosionInstance;
|
|
mExplosionInstance = NULL;
|
|
mExplosionThread = NULL;
|
|
}
|
|
|
|
SAFE_DELETE(mLight);
|
|
|
|
if (soundProfile_clone)
|
|
{
|
|
delete soundProfile_clone;
|
|
soundProfile_clone = 0;
|
|
}
|
|
|
|
if (mDataBlock && mDataBlock->isTempClone())
|
|
{
|
|
delete mDataBlock;
|
|
mDataBlock = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void Explosion::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade)
|
|
{
|
|
setPosition(point);
|
|
mInitialNormal = normal;
|
|
mFade = fade;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void Explosion::initPersistFields()
|
|
{
|
|
docsURL;
|
|
Parent::initPersistFields();
|
|
addField("initialNormal", TypePoint3F, Offset(mInitialNormal, Explosion), "Initial starting Normal.");
|
|
//
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool Explosion::onAdd()
|
|
{
|
|
// first check if we have a server connection, if we dont then this is on the server
|
|
// and we should exit, then check if the parent fails to add the object
|
|
GameConnection *conn = GameConnection::getConnectionToServer();
|
|
if ( !conn || !Parent::onAdd() )
|
|
return false;
|
|
|
|
if( !mDataBlock )
|
|
{
|
|
Con::errorf("Explosion::onAdd - Fail - No datablok");
|
|
return false;
|
|
}
|
|
|
|
mDelayMS = mDataBlock->delayMS + sgRandom.randI( -mDataBlock->delayVariance, mDataBlock->delayVariance );
|
|
mEndingMS = mDataBlock->lifetimeMS + sgRandom.randI( -mDataBlock->lifetimeVariance, mDataBlock->lifetimeVariance );
|
|
|
|
if( mFabs( mDataBlock->offset ) > 0.001f )
|
|
{
|
|
MatrixF axisOrient = MathUtils::createOrientFromDir( mInitialNormal );
|
|
|
|
MatrixF trans = getTransform();
|
|
Point3F randVec;
|
|
randVec.x = sgRandom.randF( -1.0f, 1.0f );
|
|
randVec.y = sgRandom.randF( 0.0f, 1.0f );
|
|
randVec.z = sgRandom.randF( -1.0f, 1.0f );
|
|
randVec.normalize();
|
|
randVec *= mDataBlock->offset;
|
|
axisOrient.mulV( randVec );
|
|
trans.setPosition( trans.getPosition() + randVec );
|
|
setTransform( trans );
|
|
}
|
|
|
|
// shake camera
|
|
if( mDataBlock->shakeCamera )
|
|
{
|
|
// first check if explosion is near player
|
|
GameConnection* connection = GameConnection::getConnectionToServer();
|
|
ShapeBase *obj = dynamic_cast<ShapeBase*>(connection->getControlObject());
|
|
|
|
bool applyShake = true;
|
|
|
|
if( obj )
|
|
{
|
|
ShapeBase* cObj = obj;
|
|
while((cObj = cObj->getControlObject()) != 0)
|
|
{
|
|
if(cObj->useObjsEyePoint())
|
|
{
|
|
applyShake = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( applyShake && obj )
|
|
{
|
|
VectorF diff = obj->getPosition() - getPosition();
|
|
F32 dist = diff.len();
|
|
if( dist < mDataBlock->camShakeRadius )
|
|
{
|
|
CameraShake *camShake = new CameraShake;
|
|
camShake->setDuration( mDataBlock->camShakeDuration );
|
|
camShake->setFrequency( mDataBlock->camShakeFreq );
|
|
|
|
F32 falloff = dist / mDataBlock->camShakeRadius;
|
|
falloff = 1.0f + falloff * 10.0f;
|
|
falloff = 1.0f / (falloff * falloff);
|
|
|
|
VectorF shakeAmp = mDataBlock->camShakeAmp * falloff;
|
|
camShake->setAmplitude( shakeAmp );
|
|
camShake->setFalloff( mDataBlock->camShakeFalloff );
|
|
camShake->init();
|
|
gCamFXMgr.addFX( camShake );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( mDelayMS == 0 )
|
|
{
|
|
if( !explode() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
gClientSceneGraph->addObjectToScene(this);
|
|
|
|
removeFromProcessList();
|
|
ClientProcessList::get()->addObject(this);
|
|
|
|
mRandomVal = sgRandom.randF();
|
|
|
|
NetConnection* pNC = NetConnection::getConnectionToServer();
|
|
AssertFatal(pNC != NULL, "Error, must have a connection to the server!");
|
|
pNC->addObject(this);
|
|
|
|
// Initialize the light structure and register as a dynamic light
|
|
if (mDataBlock->lightStartRadius != 0.0f || mDataBlock->lightEndRadius)
|
|
{
|
|
mLight->setType( LightInfo::Point );
|
|
mLight->setRange( mDataBlock->lightStartRadius );
|
|
mLight->setColor( mDataBlock->lightStartColor );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Explosion::onRemove()
|
|
{
|
|
for( S32 i=0; i<ExplosionData::EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( mEmitterList[i] )
|
|
{
|
|
mEmitterList[i]->deleteWhenEmpty();
|
|
mEmitterList[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if( mMainEmitter )
|
|
{
|
|
mMainEmitter->deleteWhenEmpty();
|
|
mMainEmitter = NULL;
|
|
}
|
|
|
|
removeFromScene();
|
|
|
|
Parent::onRemove();
|
|
}
|
|
|
|
|
|
bool Explosion::onNewDataBlock( GameBaseData *dptr, bool reload )
|
|
{
|
|
mDataBlock = dynamic_cast<ExplosionData*>( dptr );
|
|
if (!mDataBlock || !Parent::onNewDataBlock( dptr, reload ))
|
|
return false;
|
|
|
|
if (mDataBlock->isTempClone())
|
|
return true;
|
|
scriptOnNewDataBlock();
|
|
return true;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void Explosion::prepRenderImage( SceneRenderState* state )
|
|
{
|
|
prepBatchRender( state );
|
|
}
|
|
|
|
void Explosion::setCurrentScale()
|
|
{
|
|
F32 t = F32(mCurrMS) / F32(mEndingMS);
|
|
|
|
for( U32 i = 1; i < ExplosionData::EC_NUM_TIME_KEYS; i++ )
|
|
{
|
|
if( mDataBlock->times[i] >= t )
|
|
{
|
|
F32 firstPart = t - mDataBlock->times[i-1];
|
|
F32 total = mDataBlock->times[i] -
|
|
mDataBlock->times[i-1];
|
|
|
|
firstPart /= total;
|
|
|
|
mObjScale = (mDataBlock->sizes[i-1] * (1.0f - firstPart)) +
|
|
(mDataBlock->sizes[i] * firstPart);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Make the explosion face the viewer (if desired)
|
|
//--------------------------------------------------------------------------
|
|
void Explosion::prepModelView(SceneRenderState* state)
|
|
{
|
|
MatrixF rotMatrix( true );
|
|
Point3F targetVector;
|
|
|
|
if( mDataBlock->faceViewer )
|
|
{
|
|
targetVector = getPosition() - state->getCameraPosition();
|
|
targetVector.normalize();
|
|
|
|
// rotate explosion each time so it's a little different
|
|
rotMatrix.set( EulerF( 0.0f, mRandAngle, 0.0f ) );
|
|
}
|
|
else
|
|
{
|
|
targetVector = mInitialNormal;
|
|
}
|
|
|
|
MatrixF explOrient = MathUtils::createOrientFromDir( targetVector );
|
|
explOrient.mul( rotMatrix );
|
|
explOrient.setPosition( getPosition() );
|
|
|
|
setCurrentScale();
|
|
explOrient.scale( mObjScale );
|
|
GFX->setWorldMatrix( explOrient );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Render object
|
|
//--------------------------------------------------------------------------
|
|
void Explosion::prepBatchRender(SceneRenderState* state)
|
|
{
|
|
if ( !mExplosionInstance )
|
|
return;
|
|
|
|
MatrixF proj = GFX->getProjectionMatrix();
|
|
RectI viewport = GFX->getViewport();
|
|
|
|
// Set up our TS render state here.
|
|
TSRenderState rdata;
|
|
rdata.setSceneState( state );
|
|
|
|
// We might have some forward lit materials
|
|
// so pass down a query to gather lights.
|
|
LightQuery query;
|
|
query.init( getWorldSphere() );
|
|
rdata.setLightQuery( &query );
|
|
|
|
// render mesh
|
|
GFX->pushWorldMatrix();
|
|
|
|
prepModelView( state );
|
|
|
|
mExplosionInstance->animate();
|
|
mExplosionInstance->render( rdata );
|
|
|
|
GFX->popWorldMatrix();
|
|
GFX->setProjectionMatrix( proj );
|
|
GFX->setViewport( viewport );
|
|
}
|
|
|
|
void Explosion::submitLights( LightManager *lm, bool staticLighting )
|
|
{
|
|
if ( staticLighting )
|
|
return;
|
|
|
|
// Update the light's info and add it to the scene, the light will
|
|
// only be visible for this current frame.
|
|
mLight->setPosition( getRenderTransform().getPosition() + mInitialNormal * mDataBlock->lightNormalOffset );
|
|
F32 t = F32(mCurrMS) / F32(mEndingMS);
|
|
mLight->setRange( mDataBlock->lightStartRadius +
|
|
(mDataBlock->lightEndRadius - mDataBlock->lightStartRadius) * t );
|
|
mLight->setColor( mDataBlock->lightStartColor +
|
|
(mDataBlock->lightEndColor - mDataBlock->lightStartColor) * t );
|
|
mLight->setBrightness( mDataBlock->lightStartBrightness +
|
|
(mDataBlock->lightEndBrightness - mDataBlock->lightStartBrightness) * t );
|
|
|
|
lm->registerGlobalLight( mLight, this );
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void Explosion::processTick(const Move*)
|
|
{
|
|
mCurrMS += TickMs;
|
|
|
|
if( mCurrMS >= mEndingMS )
|
|
{
|
|
deleteObject();
|
|
return;
|
|
}
|
|
|
|
if( (mCurrMS > mDelayMS) && !mActive )
|
|
explode();
|
|
}
|
|
|
|
void Explosion::advanceTime(F32 dt)
|
|
{
|
|
if (dt == 0.0f)
|
|
return;
|
|
|
|
GameConnection* conn = GameConnection::getConnectionToServer();
|
|
if(!conn)
|
|
return;
|
|
|
|
updateEmitters( dt );
|
|
|
|
if( mExplosionInstance )
|
|
mExplosionInstance->advanceTime(dt, mExplosionThread);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Update emitters
|
|
//----------------------------------------------------------------------------
|
|
void Explosion::updateEmitters( F32 dt )
|
|
{
|
|
Point3F pos = getPosition();
|
|
|
|
for( S32 i=0; i<ExplosionData::EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( mEmitterList[i] )
|
|
{
|
|
mEmitterList[i]->emitParticles( pos, pos, mInitialNormal, Point3F( 0.0f, 0.0f, 0.0f ), (U32)(dt * 1000));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Launch Debris
|
|
//----------------------------------------------------------------------------
|
|
void Explosion::launchDebris( Point3F &axis )
|
|
{
|
|
GameConnection* conn = GameConnection::getConnectionToServer();
|
|
if(!conn)
|
|
return;
|
|
|
|
bool hasDebris = false;
|
|
for( S32 j=0; j<ExplosionData::EC_NUM_DEBRIS_TYPES; j++ )
|
|
{
|
|
if( mDataBlock->debrisList[j] )
|
|
{
|
|
hasDebris = true;
|
|
break;
|
|
}
|
|
}
|
|
if( !hasDebris )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Point3F axisx;
|
|
if (mFabs(axis.z) < 0.999f)
|
|
mCross(axis, Point3F(0.0f, 0.0f, 1.0f), &axisx);
|
|
else
|
|
mCross(axis, Point3F(0.0f, 1.0f, 0.0f), &axisx);
|
|
axisx.normalize();
|
|
|
|
Point3F pos( 0.0f, 0.0f, 0.5f );
|
|
pos += getPosition();
|
|
|
|
|
|
U32 numDebris = mDataBlock->debrisNum + sgRandom.randI( -mDataBlock->debrisNumVariance, mDataBlock->debrisNumVariance );
|
|
|
|
for( S32 i=0; i<numDebris; i++ )
|
|
{
|
|
|
|
Point3F launchDir = MathUtils::randomDir( axis, mDataBlock->debrisThetaMin, mDataBlock->debrisThetaMax,
|
|
mDataBlock->debrisPhiMin, mDataBlock->debrisPhiMax );
|
|
|
|
F32 debrisVel = mDataBlock->debrisVelocity + mDataBlock->debrisVelocityVariance * sgRandom.randF( -1.0f, 1.0f );
|
|
|
|
launchDir *= debrisVel;
|
|
|
|
Debris *debris = new Debris;
|
|
debris->setSubstitutionData(ss_object, ss_index);
|
|
debris->setDataBlock(mDataBlock->debrisList[0]->cloneAndPerformSubstitutions(ss_object, ss_index));
|
|
debris->setTransform( getTransform() );
|
|
debris->init( pos, launchDir );
|
|
|
|
if( !debris->registerObject() )
|
|
{
|
|
Con::warnf( ConsoleLogEntry::General, "Could not register debris for class: %s", mDataBlock->getName() );
|
|
delete debris;
|
|
debris = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Spawn sub explosions
|
|
//----------------------------------------------------------------------------
|
|
void Explosion::spawnSubExplosions()
|
|
{
|
|
GameConnection* conn = GameConnection::getConnectionToServer();
|
|
if(!conn)
|
|
return;
|
|
|
|
for( S32 i=0; i<ExplosionData::EC_MAX_SUB_EXPLOSIONS; i++ )
|
|
{
|
|
if( mDataBlock->explosionList[i] )
|
|
{
|
|
MatrixF trans = getTransform();
|
|
Explosion* pExplosion = new Explosion;
|
|
pExplosion->setSubstitutionData(ss_object, ss_index);
|
|
pExplosion->setDataBlock(mDataBlock->explosionList[i]->cloneAndPerformSubstitutions(ss_object, ss_index));
|
|
pExplosion->setTransform( trans );
|
|
pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1);
|
|
if (!pExplosion->registerObject())
|
|
delete pExplosion;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Explode
|
|
//----------------------------------------------------------------------------
|
|
bool Explosion::explode()
|
|
{
|
|
mActive = true;
|
|
|
|
GameConnection* conn = GameConnection::getConnectionToServer();
|
|
if(!conn)
|
|
return false;
|
|
|
|
launchDebris( mInitialNormal );
|
|
spawnSubExplosions();
|
|
|
|
if (bool(mDataBlock->mExplosionShape) && mDataBlock->explosionAnimation != -1) {
|
|
mExplosionInstance = new TSShapeInstance(mDataBlock->mExplosionShape, true);
|
|
|
|
mExplosionThread = mExplosionInstance->addThread();
|
|
mExplosionInstance->setSequence(mExplosionThread, mDataBlock->explosionAnimation, 0);
|
|
mExplosionInstance->setTimeScale(mExplosionThread, mDataBlock->playSpeed);
|
|
|
|
mCurrMS = 0;
|
|
mEndingMS = U32(mExplosionInstance->getScaledDuration(mExplosionThread) * 1000.0f);
|
|
|
|
mObjScale.convolve(mDataBlock->explosionScale);
|
|
mObjBox = mDataBlock->mExplosionShape->mBounds;
|
|
resetWorldBox();
|
|
}
|
|
|
|
SFXProfile* sound_prof = mDataBlock->getSoundProfile();
|
|
if (sound_prof)
|
|
{
|
|
soundProfile_clone = sound_prof->cloneAndPerformSubstitutions(ss_object, ss_index);
|
|
SFX->playOnce( soundProfile_clone, &getTransform() );
|
|
if (!soundProfile_clone->isTempClone())
|
|
soundProfile_clone = 0;
|
|
}
|
|
|
|
if (mDataBlock->particleEmitter) {
|
|
mMainEmitter = new ParticleEmitter;
|
|
mMainEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
|
|
mMainEmitter->registerObject();
|
|
|
|
mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius,
|
|
Point3F::Zero, U32(mDataBlock->particleDensity * mFade));
|
|
}
|
|
|
|
for( S32 i=0; i<ExplosionData::EC_NUM_EMITTERS; i++ )
|
|
{
|
|
if( mDataBlock->emitterList[i] != NULL )
|
|
{
|
|
ParticleEmitter * pEmitter = new ParticleEmitter;
|
|
pEmitter->setDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index));
|
|
if( !pEmitter->registerObject() )
|
|
{
|
|
Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
|
|
SAFE_DELETE(pEmitter);
|
|
}
|
|
mEmitterList[i] = pEmitter;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|