
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

#include "afx/arcaneFX.h"

#include "math/mathIO.h"
#include "scene/sceneManager.h"
#include "T3D/gameBase/gameProcess.h"

#include "afx/util/afxPath.h"
#include "afx/util/afxPath3D.h"
#include "afx/ce/afxParticleEmitter.h"

IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterData);

ConsoleDocClass( afxParticleEmitterData,
   "@brief A base datablock inherited by AFX Particle Emitter effects.\n\n"

   "A base datablock inherited by AFX Particle Emitter effects."
   "\n\n"

   "@ingroup afxEffects\n"
   "@ingroup AFX\n"
   "@ingroup Datablocks\n"
);

afxParticleEmitterData::afxParticleEmitterData()
{
  fade_velocity = false;  // coordinate velocity amount with fade amout
  fade_offset   = false;  // coordinate ejection-offset amount with fade amount
  pe_vector.set(0.0,0.0,0.0);
  pe_vector_is_world = false;
  tpaths_string = ST_NULLSTRING;
  tPathDataBlocks.clear();
  tPathDataBlockIds.clear();
}

afxParticleEmitterData::afxParticleEmitterData(const afxParticleEmitterData& other, bool temp_clone) : ParticleEmitterData(other, temp_clone)
{
  fade_velocity = other.fade_velocity;
  fade_offset = other.fade_offset;
  pe_vector = other.pe_vector;
  pe_vector_is_world = other.pe_vector_is_world;
  tpaths_string = other.tpaths_string;
  tPathDataBlocks = other.tPathDataBlocks;
  //tPathDataBlockIds = other.tPathDataBlockIds;
}

void afxParticleEmitterData::initPersistFields()
{
  addField("fadeVelocity",      TypeBool,    Offset(fade_velocity, afxParticleEmitterData),
    "If true, the initial velocity of emitted particles is multiplied by the fade amount "
    "of the containing effect wrapper. As the effect fades-in and out, so does the "
    "initial velocity of new particles.");
  addField("fadeOffset",        TypeBool,    Offset(fade_offset, afxParticleEmitterData),
    "If true, the ejection offset of emitted particles is multiplied by the fade amount "
    "of the containing effect wrapper. As the effect fades-in and out, so does the "
    "ejection offset of new particles.");
  addField("vector",            TypePoint3F, Offset(pe_vector, afxParticleEmitterData),
    "General direction vector used for emitting particles. Its exact interpretation is "
    "determined by the particle emitter subclass.");
  addField("vectorIsWorld",     TypeBool,    Offset(pe_vector_is_world, afxParticleEmitterData),
    "Sets whether the vector field should be interpreted as a vector in the world "
    "coordinate system.");
  addField("pathsTransform",    TypeString,  Offset(tpaths_string, afxParticleEmitterData),
    "A string of paths to be used as transform paths. Each path name must reference an "
    "afxPathData datablock. Transform paths are used to translate particles along a given "
    "path or series of paths.");

  Parent::initPersistFields();
}

void afxParticleEmitterData::packData(BitStream* stream)
{
  Parent::packData(stream);

  stream->writeFlag(fade_velocity);
  stream->writeFlag(fade_offset);
  mathWrite(*stream, pe_vector);
  stream->writeFlag(pe_vector_is_world);

  stream->write(tPathDataBlockIds.size());
  for (int i = 0; i < tPathDataBlockIds.size(); i++)
    stream->write(tPathDataBlockIds[i]);
}

void afxParticleEmitterData::unpackData(BitStream* stream)
{
  Parent::unpackData(stream);

  fade_velocity = stream->readFlag();
  fade_offset   = stream->readFlag();
  mathRead(*stream, &pe_vector);
  pe_vector_is_world = stream->readFlag();

  U32 n_db; stream->read(&n_db);
  tPathDataBlockIds.setSize(n_db);
  for (U32 i = 0; i < n_db; i++)
    stream->read(&tPathDataBlockIds[i]);
}

bool afxParticleEmitterData::onAdd()
{
  if( Parent::onAdd() == false )
    return false;

  if (tpaths_string != ST_NULLSTRING && tpaths_string[0] == '\0')
  {
    Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) empty transform paths string.", getName());
    return false;
  }
  
  if (tpaths_string != ST_NULLSTRING && dStrlen(tpaths_string) > 255) 
  {
    Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) transform paths string too long [> 255 chars].", getName());
    return false;
  }
  
  if (tpaths_string != ST_NULLSTRING) 
  {
    Vector<char*> dataBlocks(__FILE__, __LINE__);
    dsize_t tokCopyLen = dStrlen(tpaths_string) + 1;
    char* tokCopy = new char[tokCopyLen];
    dStrcpy(tokCopy, tpaths_string, tokCopyLen);
    
    char* currTok = dStrtok(tokCopy, " \t");
    while (currTok != NULL) 
    {
      dataBlocks.push_back(currTok);
      currTok = dStrtok(NULL, " \t");
    }
    if (dataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid transform paths string.  No datablocks found", getName());
      delete [] tokCopy;
      return false;
    }
    tPathDataBlocks.clear();
    tPathDataBlockIds.clear();
    
    for (U32 i = 0; i < dataBlocks.size(); i++) 
    {
      afxPathData* pData = NULL;
      if (Sim::findObject(dataBlocks[i], pData) == false) 
      {
        Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find transform path datablock: %s", getName(), dataBlocks[i]);
      } 
      else 
      {
        tPathDataBlocks.push_back(pData);
        tPathDataBlockIds.push_back(pData->getId());
      }
    }
    delete [] tokCopy;
    if (tPathDataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any transform path datablocks", getName());
      return false;
    }
  }

  return true;
}

bool afxParticleEmitterData::preload(bool server, String &errorStr)
{
  if (Parent::preload(server, errorStr) == false)
    return false;

  tPathDataBlocks.clear();
  for (U32 i = 0; i < tPathDataBlockIds.size(); i++) 
  {
    afxPathData* pData = NULL;
    if (Sim::findObject(tPathDataBlockIds[i], pData) == false)
    {
      Con::warnf(ConsoleLogEntry::General, 
                 "ParticleEmitterData(%s) unable to find transform path datablock: %d", 
                 getName(), tPathDataBlockIds[i]);
    }
    else
      tPathDataBlocks.push_back(pData);
  }

  return true;
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// VECTOR

IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterVectorData);

ConsoleDocClass( afxParticleEmitterVectorData,
   "@brief An AFX customized particle emitter that emits particles along a 3D vector.\n\n"

   "An AFX customized particle emitter that emits particles along a 3D vector."
   "\n\n"

   "@ingroup afxEffects\n"
   "@ingroup AFX\n"
   "@ingroup Datablocks\n"
);

afxParticleEmitterVectorData::afxParticleEmitterVectorData()
{
}

afxParticleEmitterVectorData::afxParticleEmitterVectorData(const afxParticleEmitterVectorData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
{
}

void afxParticleEmitterVectorData::initPersistFields()
{
  Parent::initPersistFields();
}

void afxParticleEmitterVectorData::packData(BitStream* stream)
{
  Parent::packData(stream);
}

void afxParticleEmitterVectorData::unpackData(BitStream* stream)
{
  Parent::unpackData(stream);
}

bool afxParticleEmitterVectorData::onAdd()
{
  if (Parent::onAdd() == false)
    return false;
  return true;
}

bool afxParticleEmitterVectorData::preload(bool server, String &errorStr)
{
  if (Parent::preload(server, errorStr) == false)
    return false;
  return true;
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// CONE

IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterConeData);

ConsoleDocClass( afxParticleEmitterConeData,
   "@brief An AFX customized particle emitter that emits particles within a cone shape.\n\n"

   "An AFX customized particle emitter that emits particles within a cone shape."
   "\n\n"

   "@ingroup afxEffects\n"
   "@ingroup AFX\n"
   "@ingroup Datablocks\n"
);

afxParticleEmitterConeData::afxParticleEmitterConeData()
{
  spread_min = 0.0f;
  spread_max = 90.0f;
}

afxParticleEmitterConeData::afxParticleEmitterConeData(const afxParticleEmitterConeData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
{
  spread_min = other.spread_min;
  spread_max = other.spread_max;
}

void afxParticleEmitterConeData::initPersistFields()
{
  addField("spreadMin",   TypeF32,    Offset(spread_min, afxParticleEmitterConeData),
    "...");
  addField("spreadMax",   TypeF32,    Offset(spread_max, afxParticleEmitterConeData),
    "...");

  Parent::initPersistFields();
}

void afxParticleEmitterConeData::packData(BitStream* stream)
{
  Parent::packData(stream);

  stream->writeRangedU32((U32)spread_min, 0, 180);
  stream->writeRangedU32((U32)spread_max, 0, 180);
}

void afxParticleEmitterConeData::unpackData(BitStream* stream)
{
  Parent::unpackData(stream);

  spread_min = stream->readRangedU32(0, 180);
  spread_max = stream->readRangedU32(0, 180);
}

bool afxParticleEmitterConeData::onAdd()
{
  if( Parent::onAdd() == false )
    return false;

  if (spread_min < 0.0f) 
  {
    Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin < 0.0", getName());
    spread_min = 0.0f;
  }
  if (spread_max > 180.0f) 
  {
    Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMax > 180.0f", getName());
    spread_max = 180.0f;
  }

  if (spread_max > 179.5f) 
    spread_max = 179.5f;
  if (spread_min > 179.5f) 
    spread_min = 179.5f;

  if (spread_min > spread_max) 
  {
    Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin > spreadMax", getName());
    spread_min = spread_max;
  }

  return true;
}

bool afxParticleEmitterConeData::preload(bool server, String &errorStr)
{
  if (Parent::preload(server, errorStr) == false)
    return false;
  return true;
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// PATH

IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterPathData);

ConsoleDocClass( afxParticleEmitterPathData,
   "@brief An AFX customized particle emitter that emits particles along a path.\n\n"

   "An AFX customized particle emitter that emits particles along a path."
   "\n\n"

   "@ingroup afxEffects\n"
   "@ingroup AFX\n"
   "@ingroup Datablocks\n"
);

afxParticleEmitterPathData::afxParticleEmitterPathData()
{
  epaths_string = ST_NULLSTRING;
  epathDataBlocks.clear();
  epathDataBlockIds.clear();
  path_origin_type = PATHEMIT_ORIGIN;
  ground_conform = false;
  ground_conform_terrain = true;
  ground_conform_interiors = true;
  ground_conform_height = 0.0f;
}

afxParticleEmitterPathData::afxParticleEmitterPathData(const afxParticleEmitterPathData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
{
  epaths_string = other.epaths_string;
  epathDataBlocks = other.epathDataBlocks;
  //epathDataBlockIds = other.epathDataBlockIds;
  path_origin_type = other.path_origin_type;
  ground_conform = other.ground_conform;
  ground_conform_terrain = other.ground_conform_terrain;
  ground_conform_interiors = other.ground_conform_interiors;
  ground_conform_height = other.ground_conform_height;
}

ImplementEnumType( afxParticleEmitterPath_OriginType, "Possible particle emitter path origin types.\n" "@ingroup afxParticleEmitterPath\n\n" )
  { afxParticleEmitterPathData::PATHEMIT_ORIGIN,  "origin",      "..." },
  { afxParticleEmitterPathData::PATHEMIT_POINT,   "point",      "..." },
  { afxParticleEmitterPathData::PATHEMIT_VECTOR,  "vector",      "..." },
  { afxParticleEmitterPathData::PATHEMIT_TANGENT, "tangent",      "..." },
EndImplementEnumType;

void afxParticleEmitterPathData::initPersistFields()
{
  addField("paths",         TypeString,  Offset(epaths_string,    afxParticleEmitterPathData),
    "...");

  addField("pathOrigin", TYPEID<afxParticleEmitterPathData::PathOriginType>(), Offset(path_origin_type, afxParticleEmitterPathData),
    "...");

  // JTF Note: take a look at these and make sure they are ok.
  addField("groundConform",           TypeBool, Offset(ground_conform,            afxParticleEmitterPathData),
    "...");
  addField("groundConformTerrain",    TypeBool, Offset(ground_conform_terrain,    afxParticleEmitterPathData),
    "...");
  addField("groundConformInteriors",  TypeBool, Offset(ground_conform_interiors,  afxParticleEmitterPathData),
    "...");
  addField("groundConformHeight",     TypeF32,  Offset(ground_conform_height,     afxParticleEmitterPathData),
    "...");

  Parent::initPersistFields();
}

void afxParticleEmitterPathData::packData(BitStream* stream)
{
  Parent::packData(stream);

  stream->write(epathDataBlockIds.size());
  for (int i = 0; i < epathDataBlockIds.size(); i++)
    stream->write(epathDataBlockIds[i]);
  stream->write(path_origin_type);
  stream->writeFlag(ground_conform);
  stream->writeFlag(ground_conform_terrain);
  stream->writeFlag(ground_conform_interiors);
  stream->write(ground_conform_height);
}

void afxParticleEmitterPathData::unpackData(BitStream* stream)
{
  Parent::unpackData(stream);

  U32 n_db; stream->read(&n_db);
  epathDataBlockIds.setSize(n_db);
  for (U32 i = 0; i < n_db; i++)
    stream->read(&epathDataBlockIds[i]);
  stream->read(&path_origin_type);
  ground_conform = stream->readFlag();
  ground_conform_terrain = stream->readFlag();
  ground_conform_interiors = stream->readFlag();
  stream->read(&ground_conform_height);
}

bool afxParticleEmitterPathData::onAdd()
{
  if( Parent::onAdd() == false )
    return false;

  // path
  if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0')
  {
    Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName());
    return false;
  }

  if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255) 
  {
    Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName());
    return false;
  }

  if (epaths_string != ST_NULLSTRING) 
  {
    Vector<char*> dataBlocks(__FILE__, __LINE__);
    dsize_t tokCopyLen = dStrlen(epaths_string) + 1;
    char* tokCopy = new char[tokCopyLen];
    dStrcpy(tokCopy, epaths_string, tokCopyLen);

    char* currTok = dStrtok(tokCopy, " \t");
    while (currTok != NULL) 
    {
      dataBlocks.push_back(currTok);
      currTok = dStrtok(NULL, " \t");
    }
    if (dataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string.  No datablocks found", getName());
      delete [] tokCopy;
      return false;
    }
    epathDataBlocks.clear();
    epathDataBlockIds.clear();

    for (U32 i = 0; i < dataBlocks.size(); i++) 
    {
      afxPathData* pData = NULL;
      if (Sim::findObject(dataBlocks[i], pData) == false) 
      {
        Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]);
      } 
      else 
      {
        epathDataBlocks.push_back(pData);
        epathDataBlockIds.push_back(pData->getId());
      }
    }
    delete [] tokCopy;
    if (epathDataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName());
      return false;
    }
  }

  return true;
}

bool afxParticleEmitterPathData::preload(bool server, String &errorStr)
{
  if (Parent::preload(server, errorStr) == false)
    return false;

  epathDataBlocks.clear();
  for (U32 i = 0; i < epathDataBlockIds.size(); i++) 
  {
    afxPathData* pData = NULL;
    if (Sim::findObject(epathDataBlockIds[i], pData) == false)
    {
      Con::warnf(ConsoleLogEntry::General, 
                 "afxParticleEmitterPathData(%s) unable to find path datablock: %d", 
                 getName(), epathDataBlockIds[i]);
    }
    else
      epathDataBlocks.push_back(pData);
  }
  parts_per_eject = epathDataBlocks.size();

  return true;
}

void afxParticleEmitterPathData::onPerformSubstitutions()
{
  Parent::onPerformSubstitutions();


  if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0')
  {
    Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName());
    return;// false;
  }

  if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255) 
  {
    Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName());
    return;// false;
  }

  if (epaths_string != ST_NULLSTRING) 
  {
    Vector<char*> dataBlocks(__FILE__, __LINE__);
    dsize_t tokCopyLen = dStrlen(epaths_string) + 1;
    char* tokCopy = new char[tokCopyLen];
    dStrcpy(tokCopy, epaths_string, tokCopyLen);

    char* currTok = dStrtok(tokCopy, " \t");
    while (currTok != NULL) 
    {
      dataBlocks.push_back(currTok);
      currTok = dStrtok(NULL, " \t");
    }
    if (dataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string.  No datablocks found", getName());
      delete [] tokCopy;
      return;// false;
    }
    epathDataBlocks.clear();
    epathDataBlockIds.clear();

    for (U32 i = 0; i < dataBlocks.size(); i++) 
    {
      afxPathData* pData = NULL;
      if (Sim::findObject(dataBlocks[i], pData) == false) 
      {
        Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]);
      } 
      else 
      {
        epathDataBlocks.push_back(pData);
        epathDataBlockIds.push_back(pData->getId());
      }
    }
    delete [] tokCopy;
    if (epathDataBlocks.size() == 0) 
    {
      Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName());
      return;// false;
    }
  }


  /*epathDataBlocks.clear();
  for (U32 i = 0; i < epathDataBlockIds.size(); i++) 
  {
    afxPathData* pData = NULL;
    if (Sim::findObject(epathDataBlockIds[i], pData) == false)
    {
      Con::warnf(ConsoleLogEntry::General, 
                 "afxParticleEmitterPathData(%s) unable to find path datablock: %d", 
                 getName(), epathDataBlockIds[i]);
    }
    else
      epathDataBlocks.push_back(pData);
  }
  */
  parts_per_eject = epathDataBlocks.size();
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// DISC

IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterDiscData);

ConsoleDocClass( afxParticleEmitterDiscData,
   "@brief An AFX customized particle emitter that emits particles within a disc shape.\n\n"

   "An AFX customized particle emitter that emits particles within a disc shape."
   "\n\n"

   "@ingroup afxEffects\n"
   "@ingroup AFX\n"
   "@ingroup Datablocks\n"
);

afxParticleEmitterDiscData::afxParticleEmitterDiscData()
{
  pe_radius_min = 0.0f;
  pe_radius_max = 1.0f;
}

afxParticleEmitterDiscData::afxParticleEmitterDiscData(const afxParticleEmitterDiscData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone)
{
  pe_radius_min = other.pe_radius_min;
  pe_radius_max = other.pe_radius_max;
}

void afxParticleEmitterDiscData::initPersistFields()
{
  addField("radiusMin",   TypeF32,    Offset(pe_radius_min, afxParticleEmitterDiscData),
    "...");
  addField("radiusMax",   TypeF32,    Offset(pe_radius_max, afxParticleEmitterDiscData),
    "...");

  Parent::initPersistFields();
}

void afxParticleEmitterDiscData::packData(BitStream* stream)
{
  Parent::packData(stream);

  stream->writeInt((S32)(pe_radius_min * 100), 16);
  stream->writeInt((S32)(pe_radius_max * 100), 16);
}

void afxParticleEmitterDiscData::unpackData(BitStream* stream)
{
  Parent::unpackData(stream);

  pe_radius_min = stream->readInt(16) / 100.0f;
  pe_radius_max = stream->readInt(16) / 100.0f;
}

bool afxParticleEmitterDiscData::onAdd()
{
  if( Parent::onAdd() == false )
    return false;

  return true;
}

bool afxParticleEmitterDiscData::preload(bool server, String &errorStr)
{
  if (Parent::preload(server, errorStr) == false)
    return false;

  return true;
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

afxParticleEmitter::afxParticleEmitter()
{
  mDataBlock = NULL;
  pe_vector.set(0,0,1);
  pe_vector_norm.set(0,0,1);
  tpaths.clear();
  tpath_mults.clear();
  n_tpath_points = 0;
  tpath_points = NULL;
  afx_owner = 0;
}

afxParticleEmitter::~afxParticleEmitter()
{
}

bool afxParticleEmitter::onAdd()
{
  if( !Parent::onAdd() )
    return false;

  if (dynamic_cast<afxParticleEmitterData*>(mDataBlock))
    init_paths();

  return true;
}

void afxParticleEmitter::onRemove()
{
  if (dynamic_cast<afxParticleEmitterData*>(mDataBlock))
    cleanup_paths();

  Parent::onRemove();
}

void afxParticleEmitter::init_paths()
{
  if (!mDataBlock)
  {
    n_tpath_points = 0;
    tpath_points = NULL;
    return;
  }

  if (mDataBlock->tPathDataBlocks.size() < 1)
  {
    n_tpath_points = 0;
    tpath_points = NULL;
  }
  else
  {
    n_tpath_points = mDataBlock->tPathDataBlocks.size();
    tpath_points = new Point3F*[n_tpath_points];
  
    for (U32 i=0; i < n_tpath_points; i++)
    {
      afxPathData* pd = mDataBlock->tPathDataBlocks[i]; 
      if (!pd)
        continue;

      if (pd->getSubstitutionCount() > 0 && afx_owner)
      {
        afxPathData* orig_db = pd;
        pd = new afxPathData(*orig_db, true);
        orig_db->performSubstitutions(pd, afx_owner);
      }

      if (pd->num_points > 0)
      {
        afxPath3D* path = new afxPath3D();
        if (pd->times)
          path->buildPath( pd->num_points, pd->points, pd->times, pd->delay, 1.0f );
        else if (pd->lifetime == 0)
          path->buildPath( pd->num_points, pd->points, pd->delay, 1.0f );
        else
          path->buildPath( pd->num_points, pd->points, pd->delay, pd->delay+pd->lifetime ); 
        path->setLoopType( pd->loop_type );
        tpaths.push_back(path);  

        tpath_mults.push_back( pd->mult );

        tpath_points[i] = new Point3F[pd->num_points];
        for (U32 j=0; j<pd->num_points; j++)
          tpath_points[i][j] = pd->points[j];
      }
      else
      {
        Con::warnf("afxParticleEmitter::init_paths() -- pathsTransform datablock (%d) has no points.", i);
      }

      if (pd->isTempClone())
        delete pd;
    }
  }
}

void afxParticleEmitter::cleanup_paths()
{
  if (n_tpath_points < 1)
    return;

  for (U32 i=0; i < tpaths.size(); i++)
  {
    if (tpaths[i])
      delete tpaths[i];
  }
  tpaths.clear();
  
  if (tpath_points)
  {
    if (mDataBlock)
    {
      for (U32 i=0; i < n_tpath_points; i++)
      {
        if (tpath_points[i])
          delete [] tpath_points[i];
      }
    }
    
    delete [] tpath_points;
    tpath_points = 0;
  }
}

void afxParticleEmitter::sub_particleUpdate(Particle* part)
{
   if (tpaths.size() < 1)
      return;

   F32 t = ((F32)part->currentAge)/((F32)part->totalLifetime);
   for (U32 i=0; i < tpaths.size(); i++)
   {
      F32 t_last = part->t_last;
      Point3F path_delta = (t_last <= 0.0f) ? tpaths[i]->evaluateAtTime(t) : tpaths[i]->evaluateAtTime(t_last, t);

      if (mDataBlock->tPathDataBlocks[i]->concentric)
      {
         // scale radial vector by x-component of path
         part->pos_local += part->radial_v*path_delta.x;
         // scale axis vector by z-component of path
         part->pos_local += pe_vector_norm*path_delta.z;
         // y-component is ignored
      }
      else
      {
         part->pos_local += path_delta;     
      }
   }

   part->t_last = t;
}

void afxParticleEmitter::preCompute(const MatrixF& mat)
{
   // Put vector into the space of the input matrix
   pe_vector = mDataBlock->pe_vector;
   if (!mDataBlock->pe_vector_is_world)
     mat.mulV(pe_vector);

   pe_vector_norm = pe_vector;
   pe_vector_norm.normalize();

   // Transform Paths: rebuild with current matrix
   for( U32 i=0; i < tpaths.size(); i++ )
   {
      for( U32 j=0; j < tpaths[i]->getNumPoints(); j++ )
      {
         Point3F p = tpath_points[i][j];
         mat.mulV(p);
         tpaths[i]->setPointPosition(j, p);
      }

      tpaths[i]->reBuildPath();
   }

   sub_preCompute(mat);
}

void afxParticleEmitter::afx_emitParticles(const Point3F& point, const bool useLastPosition, const Point3F& velocity, const U32 numMilliseconds)
{
  if (mDead) return;

  // lifetime over - no more particles
  if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS)
    return;

  Point3F realStart;
  if (useLastPosition && mHasLastPosition)
    realStart = mLastPosition;
  else
    realStart = point;

  afx_emitParticles(realStart, point, velocity, numMilliseconds);
}

void afxParticleEmitter::afx_emitParticles(const Point3F& start, const Point3F& end, const Point3F& velocity, const U32 numMilliseconds)
{
  if (mDead) return;

  // lifetime over - no more particles
  if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS)
    return;

  U32 currTime = 0;
  bool particlesAdded = false;

  if (mNextParticleTime != 0) 
  {
    // Need to handle next particle
    //
    if (mNextParticleTime > numMilliseconds) 
    {
      // Defer to next update
      //  (Note that this introduces a potential spatial irregularity if the owning
      //   object is accelerating, and updating at a low frequency)
      //
      mNextParticleTime -= numMilliseconds;
      mInternalClock += numMilliseconds;
      mLastPosition = end;
      mHasLastPosition = true;
      return;
    } 
    else 
    {
      currTime       += mNextParticleTime;
      mInternalClock += mNextParticleTime;
      // Emit particle at curr time

      // Create particle at the correct position
      Point3F pos;
      pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));

      for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
         {
            sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
            particlesAdded = true;
         }
      mNextParticleTime = 0;
    }
  }

  while (currTime < numMilliseconds) 
  {
     S32 nextTime = mDataBlock->ejectionPeriodMS;
     if (mDataBlock->periodVarianceMS != 0) 
     {
        nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) -
           S32(mDataBlock->periodVarianceMS);
     }
     AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0");

     if (currTime + nextTime > numMilliseconds) 
     {
        mNextParticleTime = (currTime + nextTime) - numMilliseconds;
        mInternalClock   += numMilliseconds - currTime;
        AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!");
        break;
     }

     currTime       += nextTime;
     mInternalClock += nextTime;

     // Create particle at the correct position
     Point3F pos;
     pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds));

     U32 advanceMS = numMilliseconds - currTime;
     if (mDataBlock->overrideAdvance == false && advanceMS != 0)
     {
        for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
        {
           sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
           particlesAdded = true;

           Particle* last_part = part_list_head.next;
           if (last_part)
           {
             if (advanceMS > last_part->totalLifetime) 
             {
               part_list_head.next = last_part->next;
               n_parts--;
               last_part->next = part_freelist;
               part_freelist = last_part;
             } 
             else 
             {
               F32 t = F32(advanceMS) / 1000.0;

               Point3F a = last_part->acc;
               a -= last_part->vel*last_part->dataBlock->dragCoefficient;
               a -= mWindVelocity*last_part->dataBlock->windCoefficient;
               //a += Point3F(0, 0, -9.81) * last_part->dataBlock->gravityCoefficient;
               a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster

               last_part->vel += a * t;
               last_part->pos_local += last_part->vel * t;

               // allow subclasses to adjust the particle params here
               sub_particleUpdate(last_part);

               if (last_part->dataBlock->constrain_pos)
                 last_part->pos = last_part->pos_local + this->pos_pe;
               else
                 last_part->pos = last_part->pos_local;

               updateKeyData(last_part);
             }
           }
        }
     }
     else
     {
        for (S32 p = 0; p < mDataBlock->parts_per_eject; p++)
        {
           sub_addParticle(pos, velocity, numMilliseconds-currTime, p);
           particlesAdded = true;
        }
     }
  }

  if( particlesAdded == true )
     updateBBox();

  if( n_parts > 0 && mSceneManager == NULL )
  {
     gClientSceneGraph->addObjectToScene(this);
     ClientProcessList::get()->addObject(this);
  }

  mLastPosition = end;
  mHasLastPosition = true;
}

Particle* afxParticleEmitter::alloc_particle()
{
  n_parts++;

  // this should happen rarely
  if (n_parts > n_part_capacity)
  {
    Particle* store_block = new Particle[16];
    part_store.push_back(store_block);
    n_part_capacity += 16;
    for (S32 i = 0; i < 16; i++)
    {
      store_block[i].next = part_freelist;
      part_freelist = &store_block[i];
    }
    mDataBlock->allocPrimBuffer(n_part_capacity);
  }

  Particle* pNew = part_freelist;
  part_freelist = pNew->next;
  pNew->next = part_list_head.next;
  part_list_head.next = pNew;

  return pNew;
}

ParticleData* afxParticleEmitter::pick_particle_type()
{
  U32 dBlockIndex = (U32)(mCeil(gRandGen.randF() * F32(mDataBlock->particleDataBlocks.size())) - 1);
  return mDataBlock->particleDataBlocks[dBlockIndex];
}

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

  if (mDataBlock->isTempClone())
    return true;

  scriptOnNewDataBlock();
  return true;
}

void afxParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds)
{
   if (mDataBlock->use_emitter_xfm)
   {
      Point3F zero_point(0.0f, 0.0f, 0.0f);
      pos_pe = zero_point;
      setTransform(xfm);

      preCompute(xfm);
      afx_emitParticles(zero_point, true, velocity, numMilliseconds);
   }
   else
   {
      pos_pe = point;
      preCompute(xfm);
      afx_emitParticles(point, true, velocity, numMilliseconds);
   }
}  

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// VECTOR

afxParticleEmitterVector::afxParticleEmitterVector()
{
   mDataBlock = NULL;
}

afxParticleEmitterVector::~afxParticleEmitterVector()
{
}

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

  if (mDataBlock->isTempClone())
    return true;

  scriptOnNewDataBlock();
  return true;
}

void afxParticleEmitterVector::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
{
  Particle* pNew = alloc_particle();
  ParticleData* part_db = pick_particle_type();

  Point3F pos_start = pos;
  if (part_db->constrain_pos)
    pos_start.set(0,0,0);

  F32 initialVel = mDataBlock->ejectionVelocity;
  initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
  if(mDataBlock->fade_velocity)
    initialVel *= fade_amt;

  F32 ejection_offset = mDataBlock->ejectionOffset;
  if(mDataBlock->fade_offset)
    ejection_offset *= fade_amt;

  pNew->pos = pos_start + (pe_vector_norm * ejection_offset);
  pNew->pos_local = pNew->pos;

  pNew->vel = pe_vector_norm * initialVel;
  if (mDataBlock->orientParticles)
    pNew->orientDir = pe_vector_norm;
  else
    // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
    pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
  pNew->acc.set(0, 0, 0);
  pNew->currentAge = age_offset;
  pNew->t_last = 0.0f;

  pNew->radial_v.set(0.0f, 0.0f, 0.0f);

  part_db->initializeParticle(pNew, vel);
  updateKeyData( pNew );
}

void afxParticleEmitterVector::sub_preCompute(const MatrixF& mat)
{
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// CONE

afxParticleEmitterCone::afxParticleEmitterCone()
{
  mDataBlock = NULL;
  cone_v.set(0,0,1);
  cone_s0.set(0,0,1);
  cone_s1.set(0,0,1);
}

afxParticleEmitterCone::~afxParticleEmitterCone()
{
}

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

  if (mDataBlock->isTempClone())
    return true;

  scriptOnNewDataBlock();
  return true;
}

void afxParticleEmitterCone::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
{
  Particle* pNew = alloc_particle();
  ParticleData* part_db = pick_particle_type();

  Point3F pos_start = pos;
  if (part_db->constrain_pos)
    pos_start.set(0,0,0);

  F32 initialVel = mDataBlock->ejectionVelocity;
  initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
  if(mDataBlock->fade_velocity)
    initialVel *= fade_amt;

  // Randomly choose a vector between cone_s0 and cone_s1 and normalize:
  Point3F vec;
  F32 t = mRandF(0.0f, 1.0f);   
  vec.interpolate(cone_s0, cone_s1, t);
  vec.normalize();

  // Randomly rotate about cone_v
  F32 theta = mRandF(0.0f, M_2PI_F);
  AngAxisF thetaRot(cone_v, theta);
  MatrixF temp(true);
  thetaRot.setMatrix(&temp);
  temp.mulP(vec);   

  F32 ejection_offset = mDataBlock->ejectionOffset;
  if(mDataBlock->fade_offset)
    ejection_offset *= fade_amt;

  pNew->pos = pos_start + (vec * ejection_offset);   
  pNew->pos_local = pNew->pos;

  pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel;
  if (mDataBlock->orientParticles)
    pNew->orientDir = vec;
  else
    // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
    pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
  pNew->acc.set(0, 0, 0);
  pNew->currentAge = age_offset;
  pNew->t_last = 0.0f;

  pNew->radial_v.set(0.0f, 0.0f, 0.0f);

  part_db->initializeParticle(pNew, vel);
  updateKeyData( pNew );
}

void afxParticleEmitterCone::sub_preCompute(const MatrixF& mat)
{
  // Find vectors on the XZ plane corresponding to the inner and outer spread angles:
  //    (tan is infinite at PI/4 or 90 degrees)
  cone_v.set( 0.0f, 0.0f, 1.0f );

  cone_s0.x = mTan( mDegToRad( ((afxParticleEmitterConeData*)mDataBlock)->spread_min / 2.0f ));
  cone_s0.y = 0.0f;
  cone_s0.z = 1.0f;

  cone_s1.x = mTan( mDegToRad(((afxParticleEmitterConeData*)mDataBlock)->spread_max / 2.0f ));
  cone_s1.y = 0.0f;
  cone_s1.z = 1.0f;         

  Point3F axis;
  F32 theta = mAcos( mDot(cone_v, pe_vector_norm) );

  if( M_PI_F-theta < POINT_EPSILON )
  {
    cone_v.neg();
    cone_s0.neg();
    cone_s1.neg();
  }
  else if( theta > POINT_EPSILON )
  {
    mCross(pe_vector_norm, cone_v, &axis);
    axis.normalize();

    AngAxisF thetaRot(axis, theta);
    MatrixF temp(true);
    thetaRot.setMatrix(&temp);

    temp.mulP(cone_v);            
    temp.mulP(cone_s0);
    temp.mulP(cone_s1);                      
  }
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// PATH

afxParticleEmitterPath::afxParticleEmitterPath()
{
  mDataBlock = NULL;
  epaths.clear();
  epath_mults.clear();
  n_epath_points = 0;
  epath_points = NULL;
}

afxParticleEmitterPath::~afxParticleEmitterPath()
{
}

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

  if (mDataBlock->isTempClone())
    return true;

  scriptOnNewDataBlock();
  return true;
}

bool afxParticleEmitterPath::onAdd()
{
  if( !Parent::onAdd() )
    return false;

  if (dynamic_cast<afxParticleEmitterPathData*>(mDataBlock))
    init_paths();

  return true;
}

void afxParticleEmitterPath::onRemove()
{
  if (dynamic_cast<afxParticleEmitterPathData*>(mDataBlock))
    cleanup_paths();

  Parent::onRemove();
}

void afxParticleEmitterPath::init_paths()
{
  if (!mDataBlock || ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size() < 1)
  {
    n_epath_points = 0;
    epath_points = NULL;
    return;
  }

  n_epath_points = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size();
  epath_points = new Point3F*[n_epath_points];
  dMemset(epath_points, 0, n_epath_points*sizeof(Point3F*));
  
  for (U32 i=0; i < n_epath_points; i++)
  {
    afxPathData* pd = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks[i];
    if (!pd)
      continue;

    if (pd->getSubstitutionCount() > 0 && afx_owner)
    {
      afxPathData* orig_db = pd;
      pd = new afxPathData(*orig_db, true);
      orig_db->performSubstitutions(pd, afx_owner);
    }
   
    if (pd->num_points > 0)
    {
      afxPath3D* path = new afxPath3D();
      if (pd->times)
        path->buildPath( pd->num_points, pd->points, pd->times, 0.0f, 1.0f );
      else
        path->buildPath( pd->num_points, pd->points, 0.0f, 1.0f );  
      epaths.push_back(path);  

      epath_mults.push_back( pd->mult );

      epath_points[i] = new Point3F[pd->num_points];
      for (U32 j=0; j<pd->num_points; j++)
        epath_points[i][j] = pd->points[j];
    }
    else
    {
      Con::warnf("afxParticleEmitterPath::init_paths() -- paths datablock (%d) has no points.", i);
    }

    if (pd->isTempClone())
      delete pd;
  }
}

void afxParticleEmitterPath::cleanup_paths()
{
  if (n_epath_points < 1)
    return;

  for (U32 i=0; i < epaths.size(); i++)
  {
    if (epaths[i])
      delete epaths[i];
  }
  epaths.clear();
  
  if (epath_points)
  {
    if (mDataBlock)
    {
      for (U32 i=0; i < n_epath_points; i++)
      {
        if (epath_points[i])
          delete [] epath_points[i];
      }
    }
    
    delete [] epath_points;
    epath_points = 0;
  }
}

void afxParticleEmitterPath::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
{
   if (part_idx >= epaths.size() || !epaths[part_idx])
     return;

   Particle* pNew = alloc_particle();
   ParticleData* part_db = pick_particle_type();

   Point3F pos_start = pos;
   if (part_db->constrain_pos)
      pos_start.set(0,0,0);

   F32 initialVel = mDataBlock->ejectionVelocity;
   initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
   if(mDataBlock->fade_velocity)
      initialVel *= fade_amt;

   // Randomly choose a curve parameter between [0.0,1.0] and evaluate the curve there
   F32 param = mRandF(0.0f, 1.0f);

   Point3F curve_pos = epaths[part_idx]->evaluateAtTime(param);

   Point3F vec;
   switch (((afxParticleEmitterPathData*)mDataBlock)->path_origin_type)
   { 
   case afxParticleEmitterPathData::PATHEMIT_ORIGIN :
      vec = curve_pos;
      vec.normalize();
      break;
   case afxParticleEmitterPathData::PATHEMIT_POINT :
      vec = curve_pos-pe_vector;
      vec.normalize();
      break;
   case afxParticleEmitterPathData::PATHEMIT_VECTOR :
      vec = pe_vector_norm;
      break;
   case afxParticleEmitterPathData::PATHEMIT_TANGENT :
      vec = epaths[part_idx]->evaluateTangentAtTime(param);
      vec.normalize();
      break;
   }

   F32 ejection_offset = mDataBlock->ejectionOffset;
   if(mDataBlock->fade_offset)
      ejection_offset *= fade_amt;

   pNew->pos = pos_start + curve_pos + (vec * ejection_offset);
   pNew->pos_local = pNew->pos;

   pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel;
   if (mDataBlock->orientParticles)
     pNew->orientDir = vec;
   else
     // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
     pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
   pNew->acc.set(0, 0, 0);
   pNew->currentAge = age_offset;
   pNew->t_last = 0.0f;

   pNew->radial_v.set(0.0f, 0.0f, 0.0f);

   part_db->initializeParticle(pNew, vel);
   updateKeyData( pNew );
}

void afxParticleEmitterPath::sub_preCompute(const MatrixF& mat)
{
  for( U32 i=0; i < epaths.size(); i++ )
  {
    for( U32 j=0; j < epaths[i]->getNumPoints(); j++ )
    {
      Point3F p = epath_points[i][j];
      mat.mulV(p);

      p *= epath_mults[i];
      if(mDataBlock->ground_conform) {
        groundConformPoint(p, mat);         
      }

      epaths[i]->setPointPosition(j, p);
    }

    epaths[i]->reBuildPath();
  } 
}

void afxParticleEmitterPath::groundConformPoint(Point3F& point, const MatrixF& mat)
{
  point += mat.getPosition();

  RayInfo rInfo;
  bool hit = false;

  if (mDataBlock->ground_conform_interiors)
  {
    U32 mask = InteriorLikeObjectType;
    if (mDataBlock->ground_conform_terrain)
    {
      mask |= TerrainObjectType | TerrainLikeObjectType;
    }
    
    Point3F above_pos(point); above_pos.z += 0.1f;
    Point3F below_pos(point); below_pos.z -= 10000;
    hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo);
    if (!hit)
    {
      above_pos.z = point.z + 10000;
      below_pos.z = point.z - 0.1f;
      hit = gClientContainer.castRay(below_pos, above_pos, mask, &rInfo);
    }
  }
  else if (mDataBlock->ground_conform_terrain)
  {
    U32 mask = TerrainObjectType | TerrainLikeObjectType;
    Point3F above_pos(point); above_pos.z += 10000;
    Point3F below_pos(point); below_pos.z -= 10000;
    hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo);
  }

  if (hit)
  {
    F32 terrain_z = rInfo.point.z;
    F32 new_z = terrain_z + mDataBlock->ground_conform_height;
    point.z = new_z;    
  }

  point -= mat.getPosition();
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// DISC

afxParticleEmitterDisc::afxParticleEmitterDisc()
{
  mDataBlock = NULL;
  disc_v.set(0,0,1);
  disc_r.set(1,0,0);
}

afxParticleEmitterDisc::~afxParticleEmitterDisc()
{
}

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

  if (mDataBlock->isTempClone())
    return true;

  return true;
}

void afxParticleEmitterDisc::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)
{
  Particle* pNew = alloc_particle();
  ParticleData* part_db = pick_particle_type();

  Point3F pos_start = pos;
  if (part_db->constrain_pos)
    pos_start.set(0,0,0);

  F32 initialVel = mDataBlock->ejectionVelocity;
  initialVel    += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance;
  if(mDataBlock->fade_velocity)
    initialVel *= fade_amt;

  // Randomly choose a radius vector
  Point3F r( disc_r );
  F32 radius = mRandF(((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_min, ((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_max);   
  r *= radius;

  // Randomly rotate r about disc_v
  F32 theta = mRandF(0.0f, M_2PI_F);
  AngAxisF thetaRot(disc_v, theta);
  MatrixF temp(true);
  thetaRot.setMatrix(&temp);
  temp.mulP(r);   

  F32 ejection_offset = mDataBlock->ejectionOffset;
  if(mDataBlock->fade_offset)
    ejection_offset *= fade_amt;

  pNew->pos = pos_start + r + (disc_v * ejection_offset);
  pNew->pos_local = pNew->pos;

  pNew->vel = (mDataBlock->ejectionInvert) ? (disc_v * -initialVel) : (disc_v * initialVel);
  if (mDataBlock->orientParticles)
    pNew->orientDir = disc_v;
  else
    // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle.
    pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance);
  pNew->acc.set(0, 0, 0);
  pNew->currentAge = age_offset;
  pNew->t_last = 0.0f;

  pNew->radial_v = r;

  part_db->initializeParticle(pNew, vel);
  updateKeyData( pNew );
}

void afxParticleEmitterDisc::sub_preCompute(const MatrixF& mat)
{
  disc_v.set(0.0f, 0.0f, 1.0f);
  disc_r.set(1.0f, 0.0f, 0.0f);

  Point3F axis;
  F32 theta = mAcos( mDot(disc_v, pe_vector_norm) );

  if( M_PI_F-theta < POINT_EPSILON )
  {
    disc_v.neg();
  }
  else if( theta > POINT_EPSILON )
  {
    mCross(pe_vector_norm, disc_v, &axis);
    axis.normalize();

    AngAxisF thetaRot(axis, theta);
    MatrixF temp(true);
    thetaRot.setMatrix(&temp);

    temp.mulP(disc_v);            
    temp.mulP(disc_r);            
  }         
}

//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

