//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------

#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"

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 createExplosion()\n"
   "{\n"
   "   // Create a new explosion - it will explode automatically\n"
   "   %pos = \"0 0 100\";\n"
   "   %obj = new Explosion()\n"
   "   {\n"
   "      position = %pos;\n"
   "      dataBlock = GrenadeLauncherExplosion;\n"
   "   };\n"
   "}\n\n"
   "// schedule an explosion\n"
   "schedule(1000, 0, createExplosion);\n"
   "@endtsexample"
);

#define MaxLightRadius 20

MRandomLCG sgRandom(0xdeadbeef);


DefineEngineFunction(calcExplosionCoverage, F32, (Point3F pos, S32 id, U32 covMask),(Point3F(0.0f,0.0f,0.0f), NULL, NULL),
   "@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(&center);
   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()
{
   dtsFileName  = NULL;
   particleDensity = 10;
   particleRadius = 1.0f;

   faceViewer   = false;

   soundProfile      = NULL;
   particleEmitter   = NULL;
   particleEmitterId = 0;

   explosionScale.set(1.0f, 1.0f, 1.0f);
   playSpeed = 1.0f;

   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;

   shockwave = NULL;
   shockwaveID = 0;
   shockwaveOnTerrain = false;

   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;
}

void ExplosionData::initPersistFields()
{
   addField( "explosionShape", TypeShapeFilename, Offset(dtsFileName, ExplosionData),
      "@brief Optional DTS or DAE shape 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." );
   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( "soundProfile", TYPEID< SFXTrack >(), Offset(soundProfile, ExplosionData),
      "Non-looping sound effect that will be played at the start of the explosion." );
   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" );

//   addField( "shockwave", TypeShockwaveDataPtr, Offset(shockwave, ExplosionData) );
//   addField( "shockwaveOnTerrain", TypeBool, Offset(shockwaveOnTerrain, ExplosionData) );

   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." );

   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" );

   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." );

   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." );

   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);

   stream->writeString(dtsFileName);

   sfxWrite( stream, soundProfile );
   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[i] >= 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);

   dtsFileName = stream->readSTString();

   sfxRead( stream, &soundProfile );

   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 )
   {
      String errorStr;
      if( !sfxResolve( &soundProfile, errorStr ) )
         Con::errorf(ConsoleLogEntry::General, "Error, unable to load sound profile for explosion datablock: %s", errorStr.c_str());
      if (!particleEmitter && particleEmitterId != 0)
         if (Sim::findObject(particleEmitterId, particleEmitter) == false)
            Con::errorf(ConsoleLogEntry::General, "Error, unable to load particle emitter for explosion datablock");
   }

   if (dtsFileName && dtsFileName[0]) {
      explosionShape = ResourceManager::get().load(dtsFileName);
      if (!bool(explosionShape)) {
         errorStr = String::ToString("ExplosionData: Couldn't load shape \"%s\"", dtsFileName);
         return false;
      }

      // Resolve animations
      explosionAnimation = explosionShape->findSequence("ambient");

      // Preload textures with a dummy instance...
      TSShapeInstance* pDummy = new TSShapeInstance(explosionShape, !server);
      delete pDummy;

   } else {
      explosionShape     = NULL;
      explosionAnimation = -1;
   }

   return true;
}


//--------------------------------------------------------------------------
//--------------------------------------
//
Explosion::Explosion()
{
   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 );
}

Explosion::~Explosion()
{
   if( mExplosionInstance )
   {
      delete mExplosionInstance;
      mExplosionInstance = NULL;
      mExplosionThread   = NULL;
   }
   
   SAFE_DELETE(mLight);
}


void Explosion::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade)
{
   setPosition(point);
   mInitialNormal   = normal;
   mFade            = fade;
}

//--------------------------------------------------------------------------
void Explosion::initPersistFields()
{
   Parent::initPersistFields();

   //
}

//--------------------------------------------------------------------------
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;

   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( int i=0; i<ExplosionData::EC_NUM_EMITTERS; i++ )
   {
      if( mEmitterList[i] )
      {
         mEmitterList[i]->deleteWhenEmpty();
         mEmitterList[i] = NULL;
      }
   }

   if( mMainEmitter )
   {
      mMainEmitter->deleteWhenEmpty();
      mMainEmitter = NULL;
   }

   if (getSceneManager() != NULL)
      getSceneManager()->removeObjectFromScene(this);
   if (getContainer() != NULL)
      getContainer()->removeObject(this);

   Parent::onRemove();
}


bool Explosion::onNewDataBlock( GameBaseData *dptr, bool reload )
{
   mDataBlock = dynamic_cast<ExplosionData*>( dptr );
   if (!mDataBlock || !Parent::onNewDataBlock( dptr, reload ))
      return false;

   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( int 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( int 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( int 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->setDataBlock( mDataBlock->debrisList[0] );
      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->setDataBlock( mDataBlock->explosionList[i] );
         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->explosionShape) && mDataBlock->explosionAnimation != -1) {
      mExplosionInstance = new TSShapeInstance(mDataBlock->explosionShape, 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->explosionShape->bounds;
      resetWorldBox();
   }

   if (mDataBlock->soundProfile)
      SFX->playOnce( mDataBlock->soundProfile, &getTransform() );

   if (mDataBlock->particleEmitter) {
      mMainEmitter = new ParticleEmitter;
      mMainEmitter->setDataBlock(mDataBlock->particleEmitter);
      mMainEmitter->registerObject();

      mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius,
         Point3F::Zero, U32(mDataBlock->particleDensity * mFade));
   }

   for( int i=0; i<ExplosionData::EC_NUM_EMITTERS; i++ )
   {
      if( mDataBlock->emitterList[i] != NULL )
      {
         ParticleEmitter * pEmitter = new ParticleEmitter;
         pEmitter->setDataBlock( mDataBlock->emitterList[i] );
         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;
}

