Przeglądaj źródła

Add all new AFX files

Marc Chapman 8 lat temu
rodzic
commit
ace877b409
100 zmienionych plików z 30755 dodań i 0 usunięć
  1. 1189 0
      Engine/source/afx/afxCamera.cpp
  2. 189 0
      Engine/source/afx/afxCamera.h
  3. 1084 0
      Engine/source/afx/afxChoreographer.cpp
  4. 222 0
      Engine/source/afx/afxChoreographer.h
  5. 2613 0
      Engine/source/afx/afxConstraint.cpp
  6. 677 0
      Engine/source/afx/afxConstraint.h
  7. 114 0
      Engine/source/afx/afxEffectDefs.h
  8. 270 0
      Engine/source/afx/afxEffectGroup.cpp
  9. 103 0
      Engine/source/afx/afxEffectGroup.h
  10. 358 0
      Engine/source/afx/afxEffectVector.cpp
  11. 86 0
      Engine/source/afx/afxEffectVector.h
  12. 1193 0
      Engine/source/afx/afxEffectWrapper.cpp
  13. 392 0
      Engine/source/afx/afxEffectWrapper.h
  14. 1119 0
      Engine/source/afx/afxEffectron.cpp
  15. 216 0
      Engine/source/afx/afxEffectron.h
  16. 2116 0
      Engine/source/afx/afxMagicMissile.cpp
  17. 435 0
      Engine/source/afx/afxMagicMissile.h
  18. 2709 0
      Engine/source/afx/afxMagicSpell.cpp
  19. 390 0
      Engine/source/afx/afxMagicSpell.h
  20. 196 0
      Engine/source/afx/afxPhrase.cpp
  21. 87 0
      Engine/source/afx/afxPhrase.h
  22. 176 0
      Engine/source/afx/afxRenderHighlightMgr.cpp
  23. 76 0
      Engine/source/afx/afxRenderHighlightMgr.h
  24. 469 0
      Engine/source/afx/afxResidueMgr.cpp
  25. 179 0
      Engine/source/afx/afxResidueMgr.h
  26. 1173 0
      Engine/source/afx/afxSelectron.cpp
  27. 258 0
      Engine/source/afx/afxSelectron.h
  28. 357 0
      Engine/source/afx/afxSpellBook.cpp
  29. 142 0
      Engine/source/afx/afxSpellBook.h
  30. 311 0
      Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp
  31. 91 0
      Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h
  32. 302 0
      Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp
  33. 92 0
      Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h
  34. 420 0
      Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp
  35. 92 0
      Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h
  36. 344 0
      Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp
  37. 92 0
      Engine/source/afx/afxZodiacTerrainRenderer_T3D.h
  38. 959 0
      Engine/source/afx/arcaneFX.cpp
  39. 214 0
      Engine/source/afx/arcaneFX.h
  40. 217 0
      Engine/source/afx/ce/afxAnimClip.cpp
  41. 81 0
      Engine/source/afx/ce/afxAnimClip.h
  42. 95 0
      Engine/source/afx/ce/afxAnimLock.cpp
  43. 48 0
      Engine/source/afx/ce/afxAnimLock.h
  44. 121 0
      Engine/source/afx/ce/afxAreaDamage.cpp
  45. 62 0
      Engine/source/afx/ce/afxAreaDamage.h
  46. 246 0
      Engine/source/afx/ce/afxAudioBank.cpp
  47. 67 0
      Engine/source/afx/ce/afxAudioBank.h
  48. 294 0
      Engine/source/afx/ce/afxBillboard.cpp
  49. 131 0
      Engine/source/afx/ce/afxBillboard.h
  50. 145 0
      Engine/source/afx/ce/afxBillboard_T3D.cpp
  51. 132 0
      Engine/source/afx/ce/afxCameraPuppet.cpp
  52. 64 0
      Engine/source/afx/ce/afxCameraPuppet.h
  53. 118 0
      Engine/source/afx/ce/afxCameraShake.cpp
  54. 57 0
      Engine/source/afx/ce/afxCameraShake.h
  55. 103 0
      Engine/source/afx/ce/afxCollisionEvent.cpp
  56. 59 0
      Engine/source/afx/ce/afxCollisionEvent.h
  57. 41 0
      Engine/source/afx/ce/afxComponentEffect.h
  58. 93 0
      Engine/source/afx/ce/afxConsoleMessage.cpp
  59. 54 0
      Engine/source/afx/ce/afxConsoleMessage.h
  60. 140 0
      Engine/source/afx/ce/afxDamage.cpp
  61. 63 0
      Engine/source/afx/ce/afxDamage.h
  62. 116 0
      Engine/source/afx/ce/afxFootSwitch.cpp
  63. 56 0
      Engine/source/afx/ce/afxFootSwitch.h
  64. 109 0
      Engine/source/afx/ce/afxGuiController.cpp
  65. 58 0
      Engine/source/afx/ce/afxGuiController.h
  66. 103 0
      Engine/source/afx/ce/afxGuiText.cpp
  67. 57 0
      Engine/source/afx/ce/afxGuiText.h
  68. 41 0
      Engine/source/afx/ce/afxLight.cpp
  69. 38 0
      Engine/source/afx/ce/afxLight.h
  70. 243 0
      Engine/source/afx/ce/afxLightBase_T3D.cpp
  71. 73 0
      Engine/source/afx/ce/afxLightBase_T3D.h
  72. 127 0
      Engine/source/afx/ce/afxMachineGun.cpp
  73. 59 0
      Engine/source/afx/ce/afxMachineGun.h
  74. 770 0
      Engine/source/afx/ce/afxModel.cpp
  75. 166 0
      Engine/source/afx/ce/afxModel.h
  76. 71 0
      Engine/source/afx/ce/afxModel_T3D.cpp
  77. 278 0
      Engine/source/afx/ce/afxMooring.cpp
  78. 102 0
      Engine/source/afx/ce/afxMooring.h
  79. 88 0
      Engine/source/afx/ce/afxMooring_T3D.cpp
  80. 40 0
      Engine/source/afx/ce/afxMultiLight.cpp
  81. 38 0
      Engine/source/afx/ce/afxMultiLight.h
  82. 1617 0
      Engine/source/afx/ce/afxParticleEmitter.cpp
  83. 350 0
      Engine/source/afx/ce/afxParticleEmitter.h
  84. 312 0
      Engine/source/afx/ce/afxPhraseEffect.cpp
  85. 120 0
      Engine/source/afx/ce/afxPhraseEffect.h
  86. 114 0
      Engine/source/afx/ce/afxPhysicalZone.cpp
  87. 65 0
      Engine/source/afx/ce/afxPhysicalZone.h
  88. 140 0
      Engine/source/afx/ce/afxPlayerMovement.cpp
  89. 73 0
      Engine/source/afx/ce/afxPlayerMovement.h
  90. 122 0
      Engine/source/afx/ce/afxPlayerPuppet.cpp
  91. 64 0
      Engine/source/afx/ce/afxPlayerPuppet.h
  92. 103 0
      Engine/source/afx/ce/afxPointLight_T3D.cpp
  93. 56 0
      Engine/source/afx/ce/afxPointLight_T3D.h
  94. 371 0
      Engine/source/afx/ce/afxProjectile.cpp
  95. 117 0
      Engine/source/afx/ce/afxProjectile.h
  96. 94 0
      Engine/source/afx/ce/afxScriptEvent.cpp
  97. 57 0
      Engine/source/afx/ce/afxScriptEvent.h
  98. 112 0
      Engine/source/afx/ce/afxSpotLight_T3D.cpp
  99. 58 0
      Engine/source/afx/ce/afxSpotLight_T3D.h
  100. 241 0
      Engine/source/afx/ce/afxStaticShape.cpp

+ 1189 - 0
Engine/source/afx/afxCamera.cpp

@@ -0,0 +1,1189 @@
+//-----------------------------------------------------------------------------
+// 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.
+//
+// afxCamera implements a modified camera for demonstrating a third person camera style
+// which is more common to RPG games than the standard FPS style camera. For the most part,
+// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera
+// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum
+// required for demonstrating an RPG style camera and leaves tons of room for improvement. 
+// It should be replaced with a better camera if possible.
+//
+// Advanced Camera Resource by Thomas "Man of Ice" Lund:
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "math/mathUtils.h"
+#include "math/mathIO.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/camera.h"
+#include "T3D/player.h"
+#include "T3D/sfx/sfx3DWorld.h"
+
+#include "afx/afxCamera.h"
+
+#define MaxPitch      1.3962f
+#define CameraRadius  0.05f;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraData);
+
+ConsoleDocClass( afxCameraData,
+   "@brief A datablock that describes an afxCamera.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+U32 afxCameraData::sCameraCollisionMask = TerrainObjectType | InteriorLikeObjectType | TerrainLikeObjectType;
+
+void afxCameraData::initPersistFields()
+{
+  Con::addVariable("pref::afxCamera::collisionMask", TypeS32, &sCameraCollisionMask);
+
+  Parent::initPersistFields();
+}
+
+void afxCameraData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+}
+
+void afxCameraData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCamera
+
+IMPLEMENT_CO_NETOBJECT_V1(afxCamera);
+
+ConsoleDocClass( afxCamera,
+   "@brief A 3rd person camera object.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+);
+
+afxCamera::afxCamera()
+{
+  mNetFlags.clear(Ghostable);
+  mTypeMask |= CameraObjectType;
+  delta.pos = Point3F(0,0,100);
+  delta.rot = Point3F(0,0,0);
+  delta.posVec = delta.rotVec = VectorF(0,0,0);
+  mObjToWorld.setColumn(3,delta.pos);
+  mRot = delta.rot;
+  
+  mMinOrbitDist = 0;
+  mMaxOrbitDist = 0;
+  mCurOrbitDist = 0;
+  mOrbitObject = NULL;
+  mPosition.set(0.f, 0.f, 0.f);
+  mObservingClientObject = false;
+  mode = FlyMode;
+  
+  cam_subject = NULL;
+  coi_offset.set(0, 0, 2);
+  cam_offset.set(0, 0, 0);
+  cam_distance = 0.0f;
+  cam_angle = 0.0f;
+  cam_dirty = false;
+      
+  flymode_saved = false;
+  third_person_snap_s = 1;
+  third_person_snap_c = 1;
+  flymode_saved_pos.zero();
+
+  mDamageState = Disabled;
+}
+
+afxCamera::~afxCamera()
+{
+}
+
+//----------------------------------------------------------------------------
+
+void afxCamera::cam_update(F32 dt, bool on_server) 
+{
+  if (mode == ThirdPersonMode && cam_subject)
+    cam_update_3pov(dt, on_server);
+}
+
+void afxCamera::set_cam_pos(const Point3F& pos,const Point3F& rot)
+{
+   MatrixF xRot, zRot;
+   xRot.set(EulerF(rot.x, 0, 0));
+   zRot.set(EulerF(0, 0, rot.z));
+   MatrixF temp;
+   temp.mul(zRot, xRot);
+   temp.setColumn(3, pos);
+   Parent::setTransform(temp);
+   mRot = rot;
+}
+
+
+//----------------------------------------------------------------------------
+
+
+
+//----------------------------------------------------------------------------
+
+Point3F &afxCamera::getPosition()
+{
+   static Point3F position;
+   mObjToWorld.getColumn(3, &position);
+   return position;
+}
+
+//----------------------------------------------------------------------------
+
+
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+//    NEW Observer Code
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+void afxCamera::setFlyMode()
+{
+  mode = FlyMode;
+  if (flymode_saved)
+    snapToPosition(flymode_saved_pos);
+  
+  if (bool(mOrbitObject)) 
+  {
+    clearProcessAfter();
+    clearNotify(mOrbitObject);
+  }
+  mOrbitObject = NULL;
+}
+
+void afxCamera::setOrbitMode(GameBase *obj, Point3F &pos, AngAxisF &rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject)
+{
+   mObservingClientObject = ownClientObject;
+
+   if(bool(mOrbitObject)) {
+      clearProcessAfter();
+      clearNotify(mOrbitObject);
+   }
+   mOrbitObject = obj;
+   if(bool(mOrbitObject))
+   {
+      processAfter(mOrbitObject);
+      deleteNotify(mOrbitObject);
+      mOrbitObject->getWorldBox().getCenter(&mPosition);
+      mode = OrbitObjectMode;
+   }
+   else
+   {
+      mode = OrbitPointMode;
+      mPosition = pos;
+   }
+
+   QuatF q(rot);
+   MatrixF tempMat(true);
+   q.setMatrix(&tempMat);
+   Point3F dir;
+   tempMat.getColumn(1, &dir);
+
+   set_cam_pos(mPosition, dir);
+
+   mMinOrbitDist = minDist;
+   mMaxOrbitDist = maxDist;
+   mCurOrbitDist = curDist;
+}
+
+
+void afxCamera::validateEyePoint(F32 pos, MatrixF *mat)
+{
+   if (pos != 0) {
+      // Use the eye transform to orient the camera
+      Point3F dir;
+      mat->getColumn(1, &dir);
+      pos *= mMaxOrbitDist - mMinOrbitDist;
+      // Use the camera node's pos.
+      Point3F startPos;
+      Point3F endPos;
+      mObjToWorld.getColumn(3,&startPos);
+
+      // Make sure we don't extend the camera into anything solid
+      if(mOrbitObject)
+         mOrbitObject->disableCollision();
+      disableCollision();
+      RayInfo collision;
+
+      SceneContainer* pContainer = isServerObject() ? &gServerContainer : &gClientContainer;
+      if (!pContainer->castRay(startPos, startPos - dir * 2.5 * pos, afxCameraData::sCameraCollisionMask, &collision))
+         endPos = startPos - dir * pos;
+      else
+      {
+         float dot = mDot(dir, collision.normal);
+         if(dot > 0.01)
+         {
+            float colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius;
+            if(colDist > pos)
+               colDist = pos;
+            if(colDist < 0)
+               colDist = 0;
+            endPos = startPos - dir * colDist;
+         }
+         else
+            endPos = startPos - dir * pos;
+      }
+      mat->setColumn(3,endPos);
+      enableCollision();
+      if(mOrbitObject)
+         mOrbitObject->enableCollision();
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+
+
+
+// Sets the position and calculates rotation
+void afxCamera::snapToPosition(const Point3F& tPos) 
+{
+  MatrixF transMat;
+
+  if (cam_subject) 
+  {
+    // get the subject's transform
+    MatrixF objToWorld = cam_subject->getRenderTransform();
+
+    // transform the center-of-interest to world-space
+    Point3F objPos;
+    objToWorld.mulP(coi_offset, &objPos);
+
+    // find normalized direction vector looking from camera to coi
+    VectorF dirVec = objPos - tPos;
+    dirVec.normalize();
+
+    MathUtils::getAnglesFromVector(dirVec, mRot.z, mRot.x);
+    mRot.x = 0 - mRot.x;
+
+    transMat = MathUtils::createOrientFromDir(dirVec);
+  } 
+  else
+  {
+    transMat.identity();
+  }
+
+  transMat.setColumn(3, tPos);
+  Parent::setTransform(transMat);
+}
+
+void afxCamera::setCameraSubject(SceneObject* new_subject)
+{
+  // cleanup any existing chase subject
+  if (cam_subject) 
+  {
+    if (dynamic_cast<GameBase*>(cam_subject))
+      clearProcessAfter();
+    clearNotify(cam_subject);
+  }
+  
+  cam_subject = new_subject;
+  
+  // set associations with new chase subject 
+  if (cam_subject) 
+  {
+    if (dynamic_cast<GameBase*>(cam_subject))
+      processAfter((GameBase*)cam_subject);
+    deleteNotify(cam_subject);
+  }
+
+  mode = (cam_subject) ? ThirdPersonMode : FlyMode;
+  setMaskBits(SubjectMask);
+}
+
+void afxCamera::setThirdPersonOffset(const Point3F& offset) 
+{
+  // new method
+  if (cam_distance > 0.0f)
+  {
+    if (isClientObject())
+    {
+      GameConnection* conn = GameConnection::getConnectionToServer();
+      if (conn)
+      {
+        // this auto switches to/from first person 
+        if (conn->isFirstPerson())
+        {
+          if (cam_distance >= 1.0f)
+            conn->setFirstPerson(false);
+        }
+        else
+        {
+          if (cam_distance < 1.0f)
+            conn->setFirstPerson(true);
+        }
+      }
+    }
+
+    cam_offset = offset;
+    cam_dirty = true;
+
+    return;
+  }
+
+  // old backwards-compatible method
+  if (offset.y != cam_offset.y && isClientObject())
+  {
+    GameConnection* conn = GameConnection::getConnectionToServer();
+    if (conn)
+    {
+      // this auto switches to/from first person 
+      if (conn->isFirstPerson())
+      {
+        if (offset.y <= -1.0f)
+          conn->setFirstPerson(false);
+      }
+      else
+      {
+        if (offset.y > -1.0f)
+          conn->setFirstPerson(true);
+      }
+    }
+  }
+
+  cam_offset = offset;
+  cam_dirty = true;
+}
+
+void afxCamera::setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset) 
+{
+  this->coi_offset = coi_offset;
+  setThirdPersonOffset(offset);
+}
+
+void afxCamera::setThirdPersonDistance(F32 distance) 
+{
+  cam_distance = distance;
+  cam_dirty = true;
+}
+
+F32 afxCamera::getThirdPersonDistance() 
+{
+  return cam_distance;
+}
+
+void afxCamera::setThirdPersonAngle(F32 angle) 
+{
+  cam_angle = angle;
+  cam_dirty = true;
+}
+
+F32 afxCamera::getThirdPersonAngle() 
+{
+  return cam_angle;
+}
+
+void afxCamera::setThirdPersonMode()
+{
+  mode = ThirdPersonMode;
+  flymode_saved_pos = getPosition();
+  flymode_saved = true;
+  cam_dirty = true;
+  third_person_snap_s++;
+}
+
+void afxCamera::setThirdPersonSnap()
+{
+  if (mode == ThirdPersonMode)
+    third_person_snap_s += 2;
+}
+
+void afxCamera::setThirdPersonSnapClient()
+{
+  if (mode == ThirdPersonMode)
+    third_person_snap_c++;
+}
+
+const char* afxCamera::getMode()
+{
+  switch (mode)
+  {
+  case ThirdPersonMode:
+    return "ThirdPerson";
+  case FlyMode:
+    return "Fly";
+  case OrbitObjectMode:
+    return "Orbit";
+  }
+
+  return "Unknown";
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Console Methods
+
+static char buffer[100];
+
+ConsoleMethod(afxCamera, setOrbitMode, void, 7, 8, 
+  "(GameBase orbitObject, transform mat, float minDistance, float maxDistance, float curDistance, bool ownClientObject)"
+  "Set the camera to orbit around some given object.\n\n"
+  "@param   orbitObject  Object we want to orbit.\n"
+  "@param   mat          A set of fields: posX posY posZ aaX aaY aaZ aaTheta\n"
+  "@param   minDistance  Minimum distance to keep from object.\n"
+  "@param   maxDistance  Maximum distance to keep from object.\n"
+  "@param   curDistance  Distance to set initially from object.\n"
+  "@param   ownClientObj Are we observing an object owned by us?")
+{
+  Point3F pos;
+  AngAxisF aa;
+  F32 minDis, maxDis, curDis;
+  
+  GameBase *orbitObject = NULL;
+  if(Sim::findObject(argv[2],orbitObject) == false)
+  {
+    Con::warnf("Cannot orbit non-existing object.");
+    object->setFlyMode();
+    return;
+  }
+  
+  dSscanf(argv[3],"%f %f %f %f %f %f %f",
+    &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle);
+  minDis = dAtof(argv[4]);
+  maxDis = dAtof(argv[5]);
+  curDis = dAtof(argv[6]);
+  
+  object->setOrbitMode(orbitObject, pos, aa, minDis, maxDis, curDis, (argc == 8) ? dAtob(argv[7]) : false);
+}
+
+ConsoleMethod( afxCamera, setFlyMode, void, 2, 2, "()" "Set the camera to be able to fly freely.")
+{
+  object->setFlyMode();
+}
+
+ConsoleMethod( afxCamera, getPosition, const char *, 2, 2, "()"
+              "Get the position of the camera.\n\n"
+              "@returns A string of form \"x y z\".")
+{ 
+  Point3F& pos = object->getPosition();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, setCameraSubject, bool, 3, 3, "") 
+{   
+  SceneObject* subject;
+  if (!Sim::findObject(argv[2], subject))
+  {
+    Con::errorf("Camera subject \"%s\" not found.", argv[2].getStringValue());
+    return false;
+  }
+  
+  object->setCameraSubject(subject);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, setThirdPersonDistance, bool, 3, 3, "") 
+{   
+  F32 distance; 
+  dSscanf(argv[2], "%f", &distance);
+
+  object->setThirdPersonDistance(distance);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonDistance, F32, 2, 2, "")
+{
+   return object->getThirdPersonDistance();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonAngle, bool, 3, 3, "") 
+{   
+  F32 angle; 
+  dSscanf(argv[2], "%f", &angle);
+
+  object->setThirdPersonAngle(angle);
+  
+  return true;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonAngle, F32, 2, 2, "")
+{
+   return object->getThirdPersonAngle();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonOffset, void, 3, 4, "(Point3F offset [, Point3f coi_offset])") 
+{
+  Point3F offset; 
+  dSscanf(argv[2], "%f %f %f", &offset.x, &offset.y, &offset.z);
+  if (argc > 3)
+  {
+    Point3F coi_offset; 
+    dSscanf(argv[3], "%f %f %f", &coi_offset.x, &coi_offset.y, &coi_offset.z);
+    object->setThirdPersonOffset(offset, coi_offset);
+  }
+  else
+    object->setThirdPersonOffset(offset);
+}
+
+ConsoleMethod(afxCamera, getThirdPersonOffset, const char *, 2, 2, "()")
+{
+  const Point3F& pos = object->getThirdPersonOffset();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, getThirdPersonCOIOffset, const char *, 2, 2, "()")
+{
+  const Point3F& pos = object->getThirdPersonCOIOffset();
+  dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z);
+  return buffer;
+}
+
+ConsoleMethod(afxCamera, setThirdPersonMode, void, 2, 2, "()")
+{
+  object->setThirdPersonMode();
+}
+
+ConsoleMethod(afxCamera, setThirdPersonSnap, void, 2, 2, "()")
+{
+  object->setThirdPersonSnap();
+}
+
+ConsoleMethod(afxCamera, getMode, const char *, 2, 2, "()")
+{
+  return object->getMode();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// 3POV SECTION
+
+void afxCamera::cam_update_3pov(F32 dt, bool on_server) 
+{
+  Point3F	goal_pos;
+  Point3F curr_pos = getRenderPosition();
+  MatrixF	xfm = cam_subject->getRenderTransform();
+  Point3F coi = cam_subject->getRenderPosition() + coi_offset;
+
+  // for player subjects, pitch is adjusted
+  Player*	player_subj =	dynamic_cast<Player*>(cam_subject);
+  if (player_subj) 
+  {
+    if (cam_distance > 0.0f)
+    {
+      // rotate xfm by amount of cam_angle
+      F32	look_yaw = player_subj->getHeadRotation().z + mDegToRad(-cam_angle);
+      MatrixF	look_yaw_mtx(EulerF(0,0,look_yaw));
+      xfm.mul(look_yaw_mtx);
+
+      // rotate xfm by amount of head pitch in player
+      F32	head_pitch = player_subj->getHeadRotation().x;
+      MatrixF	head_pitch_mtx(EulerF(head_pitch,0,0));
+      xfm.mul(head_pitch_mtx);
+
+      VectorF	behind_vec(0, -cam_distance, 0);
+      xfm.mulP(behind_vec, &goal_pos);
+      goal_pos += cam_offset;
+    }
+    else // old backwards-compatible method
+    {
+      // rotate xfm by amount of head pitch in player
+      F32	head_pitch = player_subj->getHeadRotation().x;
+      MatrixF	head_pitch_mtx(EulerF(head_pitch,0,0));
+      xfm.mul(head_pitch_mtx);
+
+      VectorF	behind_vec(0, cam_offset.y, 0);
+      xfm.mulP(behind_vec, &goal_pos);
+      goal_pos.z += cam_offset.z;
+    }
+  }
+  // for non-player subjects, camera will follow, but pitch won't adjust.
+  else 
+  {
+    xfm.mulP(cam_offset, &goal_pos);
+  }
+
+  // avoid view occlusion
+  if (avoid_blocked_view(coi, goal_pos, goal_pos) && !on_server)
+  {
+    // snap to final position if path to goal is blocked
+    if (test_blocked_line(curr_pos, goal_pos))
+      third_person_snap_c++;
+  }
+
+  // place camera into its final position	
+
+  // speed factor values
+  //   15 -- tight
+  //   10 -- normal
+  //    5 -- loose
+  //    1 -- very loose
+  F32 speed_factor = 8.0f;
+  F32 time_inc = 1.0f/speed_factor;
+
+  // snap to final position
+  if (on_server || (third_person_snap_c > 0 || dt > time_inc))
+  {
+    snapToPosition(goal_pos);
+    if (!on_server && third_person_snap_c > 0)
+      third_person_snap_c--;
+    return;
+  }
+  // interpolate to final position
+  else
+  {
+    // interpretation: always move a proportion of the distance
+    // from current location to destination that would cover the
+    // entire distance in time_inc duration at constant velocity.
+    F32 t = (dt >= time_inc) ? 1.0f : dt*speed_factor;
+    snapToPosition(goal_pos*t + curr_pos*(1.0-t));
+  }
+}
+
+// See if the camera view is occluded by certain objects, 
+// and move the camera closer to the subject in that case
+bool afxCamera::avoid_blocked_view(const Point3F& startpos, const Point3F& endpos, Point3F& newpos) 
+{ 
+  // cast ray to check for intersection with potential blocker objects
+  RayInfo hit_info;
+  if (!getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info)) 
+  {
+    // no hit: just return original endpos
+    newpos = endpos;
+    return false;
+  }
+
+	// did hit: return the hit location nudged forward slightly
+  // to avoid seeing clipped portions of blocking object.
+	Point3F sight_line = startpos - hit_info.point;
+  sight_line.normalize();
+  newpos = hit_info.point + sight_line*0.4f;
+
+  return true;
+}
+
+bool afxCamera::test_blocked_line(const Point3F& startpos, const Point3F& endpos) 
+{ 
+  RayInfo hit_info;
+  return getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STD OVERRIDES SECTION
+
+bool afxCamera::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  mObjBox.maxExtents = mObjScale;
+  mObjBox.minExtents = mObjScale;
+  mObjBox.minExtents.neg();
+
+  resetWorldBox();
+
+  addToScene();
+
+  return true;
+}
+
+void afxCamera::onRemove()
+{
+  removeFromScene();
+  Parent::onRemove();
+}
+
+void afxCamera::onDeleteNotify(SimObject *obj)
+{
+  Parent::onDeleteNotify(obj);
+
+  if (obj == (SimObject*)mOrbitObject)
+  {
+    mOrbitObject = NULL;
+    if (mode == OrbitObjectMode)
+      mode = OrbitPointMode;
+  }
+
+  if (obj == cam_subject)
+  {
+    cam_subject = NULL;
+  }
+}
+
+void afxCamera::advanceTime(F32 dt) 
+{
+  Parent::advanceTime(dt);
+
+  if (gSFX3DWorld)
+  {
+     if (mode == ThirdPersonMode && cam_subject)
+     {
+        if (gSFX3DWorld->getListener() != cam_subject)
+           gSFX3DWorld->setListener(cam_subject);
+     }
+     else if (mode == FlyMode)
+     {
+        if (gSFX3DWorld->getListener() != this)
+           gSFX3DWorld->setListener(this);
+     }
+  }
+
+  cam_update(dt, false);
+}
+
+void afxCamera::processTick(const Move* move)
+{
+  Parent::processTick(move);
+  Point3F vec,pos;
+
+  // move will be NULL unless camera becomes the control object as in FlyMode
+  if (move) 
+  {
+    // UPDATE ORIENTATION //
+    delta.rotVec = mRot;
+    mObjToWorld.getColumn(3, &delta.posVec);
+    mRot.x = mClampF(mRot.x + move->pitch, -MaxPitch, MaxPitch);
+    mRot.z += move->yaw;
+
+    // ORBIT MODE // 
+    if (mode == OrbitObjectMode || mode == OrbitPointMode)
+    {
+      if(mode == OrbitObjectMode && bool(mOrbitObject)) 
+      {
+        // If this is a shapebase, use its render eye transform
+        // to avoid jittering.
+        GameBase *castObj = mOrbitObject;
+        ShapeBase* shape = dynamic_cast<ShapeBase*>(castObj);
+        if( shape != NULL ) {
+          MatrixF ret;
+          shape->getRenderEyeTransform( &ret );
+          mPosition = ret.getPosition();
+        } 
+        else 
+        {
+          // Hopefully this is a static object that doesn't move,
+          // because the worldbox doesn't get updated between ticks.
+          mOrbitObject->getWorldBox().getCenter(&mPosition);
+        }
+      }
+      set_cam_pos(mPosition, mRot);
+      validateEyePoint(1.0f, &mObjToWorld);
+      pos = mPosition;
+    }
+
+    // NON-ORBIT MODE (FLY MODE) //
+    else // if (mode == FlyMode)
+    {
+      // Update pos
+      bool faster = move->trigger[0] || move->trigger[1];
+      F32 scale = Camera::getMovementSpeed() * (faster + 1);
+
+      mObjToWorld.getColumn(3,&pos);
+      mObjToWorld.getColumn(0,&vec);
+      pos += vec * move->x * TickSec * scale;
+      mObjToWorld.getColumn(1,&vec);
+      pos += vec * move->y * TickSec * scale;
+      mObjToWorld.getColumn(2,&vec);
+      pos += vec * move->z * TickSec * scale;
+      set_cam_pos(pos,mRot);
+    }
+
+    // If on the client, calc delta for backstepping
+    if (isClientObject()) 
+    {
+      delta.pos = pos;
+      delta.rot = mRot;
+      delta.posVec = delta.posVec - delta.pos;
+      delta.rotVec = delta.rotVec - delta.rot;
+    }
+    else
+    {
+      setMaskBits(MoveMask);
+    }
+  }
+  else // if (!move)
+  {
+    if (isServerObject())
+      cam_update(1.0/32.0, true);
+  }
+
+  if (getControllingClient() && mContainer)
+    updateContainer();
+}
+
+void afxCamera::interpolateTick(F32 dt)
+{
+  Parent::interpolateTick(dt);
+
+  if (mode == ThirdPersonMode)
+    return;
+
+  Point3F rot = delta.rot + delta.rotVec * dt;
+
+  if(mode == OrbitObjectMode || mode == OrbitPointMode)
+  {
+    if(mode == OrbitObjectMode && bool(mOrbitObject))
+    {
+      // If this is a shapebase, use its render eye transform
+      // to avoid jittering.
+      GameBase *castObj = mOrbitObject;
+      ShapeBase* shape = dynamic_cast<ShapeBase*>(castObj);
+      if( shape != NULL ) 
+      {
+        MatrixF ret;
+        shape->getRenderEyeTransform( &ret );
+        mPosition = ret.getPosition();
+      } 
+      else 
+      {
+        // Hopefully this is a static object that doesn't move,
+        // because the worldbox doesn't get updated between ticks.
+        mOrbitObject->getWorldBox().getCenter(&mPosition);
+      }
+    }
+    set_cam_pos(mPosition, rot);
+    validateEyePoint(1.0f, &mObjToWorld);
+  }
+  else 
+  {
+    // NOTE - posVec is 0,0,0 unless cam is control-object and process tick is
+    // updating the delta
+    Point3F pos = delta.pos + delta.posVec * dt;
+    set_cam_pos(pos,rot);
+  }
+}
+
+void afxCamera::writePacketData(GameConnection *connection, BitStream *bstream)
+{
+  // Update client regardless of status flags.
+  Parent::writePacketData(connection, bstream);
+
+  Point3F pos; mObjToWorld.getColumn(3, &pos);
+  bstream->setCompressionPoint(pos);                                      // SET COMPRESSION POINT
+  mathWrite(*bstream, pos);                                               // SND POS
+  bstream->write(mRot.x);                                                 // SND X ROT
+  bstream->write(mRot.z);                                                 // SND Z ROT
+
+  if (bstream->writeFlag(cam_dirty))
+  {
+    mathWrite(*bstream, cam_offset);                                        // SND CAM_OFFSET
+    mathWrite(*bstream, coi_offset);                                        // SND COI_OFFSET
+    bstream->write(cam_distance); 
+    bstream->write(cam_angle);
+    cam_dirty = false;
+  }
+
+  U32 writeMode = mode;
+  Point3F writePos = mPosition;
+  S32 gIndex = -1;
+  if (mode == OrbitObjectMode)
+  {
+    gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1;
+    if(gIndex == -1)
+    {
+      writeMode = OrbitPointMode;
+      mOrbitObject->getWorldBox().getCenter(&writePos);
+    }
+  }
+
+  bstream->writeRangedU32(writeMode, CameraFirstMode, CameraLastMode);    // SND MODE
+  if (writeMode == ThirdPersonMode)
+  {
+    bstream->write(third_person_snap_s > 0);                              // SND SNAP
+    if (third_person_snap_s > 0)
+      third_person_snap_s--;
+  }
+
+  if (writeMode == OrbitObjectMode || writeMode == OrbitPointMode)
+  {
+    bstream->write(mMinOrbitDist);                                        // SND ORBIT MIN DIST
+    bstream->write(mMaxOrbitDist);                                        // SND ORBIT MAX DIST
+    bstream->write(mCurOrbitDist);                                        // SND ORBIT CURR DIST
+    if(writeMode == OrbitObjectMode)
+    {
+      bstream->writeFlag(mObservingClientObject);                         // SND OBSERVING CLIENT OBJ
+      bstream->writeInt(gIndex, NetConnection::GhostIdBitSize);           // SND ORBIT OBJ
+    }
+    if (writeMode == OrbitPointMode)
+      bstream->writeCompressedPoint(writePos);                            // WRITE COMPRESSION POINT
+  }
+}
+
+void afxCamera::readPacketData(GameConnection *connection, BitStream *bstream)
+{
+  Parent::readPacketData(connection, bstream);
+
+  Point3F pos,rot;
+  mathRead(*bstream, &pos);                                               // RCV POS
+  bstream->setCompressionPoint(pos);
+  bstream->read(&rot.x);                                                  // RCV X ROT
+  bstream->read(&rot.z);                                                  // RCV Z ROT
+
+  if (bstream->readFlag())
+  {
+    Point3F new_cam_offset, new_coi_offset;
+    mathRead(*bstream, &new_cam_offset);                                    // RCV CAM_OFFSET
+    mathRead(*bstream, &new_coi_offset);                                    // RCV COI_OFFSET
+    bstream->read(&cam_distance);
+    bstream->read(&cam_angle);
+    setThirdPersonOffset(new_cam_offset, new_coi_offset);
+  }
+
+  GameBase* obj = 0;
+  mode = bstream->readRangedU32(CameraFirstMode,                          // RCV MODE
+    CameraLastMode);
+  if (mode == ThirdPersonMode)
+  {
+    bool snap; bstream->read(&snap);
+    if (snap)
+      third_person_snap_c++;
+  }
+
+  mObservingClientObject = false;
+  if (mode == OrbitObjectMode || mode == OrbitPointMode) {
+    bstream->read(&mMinOrbitDist);
+    bstream->read(&mMaxOrbitDist);
+    bstream->read(&mCurOrbitDist);
+
+    if(mode == OrbitObjectMode)
+    {
+      mObservingClientObject = bstream->readFlag();
+      S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize);
+      obj = static_cast<GameBase*>(connection->resolveGhost(gIndex));
+    }
+    if (mode == OrbitPointMode)
+      bstream->readCompressedPoint(&mPosition);
+  }
+  if (obj != (GameBase*)mOrbitObject) {
+    if (mOrbitObject) {
+      clearProcessAfter();
+      clearNotify(mOrbitObject);
+    }
+    mOrbitObject = obj;
+    if (mOrbitObject) {
+      processAfter(mOrbitObject);
+      deleteNotify(mOrbitObject);
+    }
+  }
+
+  if (mode == ThirdPersonMode)
+    return;
+
+  set_cam_pos(pos,rot);
+  delta.pos = pos;
+  delta.rot = rot;
+  delta.rotVec.set(0,0,0);
+  delta.posVec.set(0,0,0);
+}
+
+U32 afxCamera::packUpdate(NetConnection* conn, U32 mask, BitStream *bstream)
+{
+  U32 retMask = Parent::packUpdate(conn,mask,bstream);
+
+  // The rest of the data is part of the control object packet update.
+  // If we're controlled by this client, we don't need to send it.
+  //if(bstream->writeFlag(getControllingClient() == conn && !(mask & InitialUpdateMask)))
+  //   return 0;
+
+  if (bstream->writeFlag(mask & MoveMask)) {
+    Point3F pos;
+    mObjToWorld.getColumn(3,&pos);
+    bstream->write(pos.x);
+    bstream->write(pos.y);
+    bstream->write(pos.z);
+    bstream->write(mRot.x);
+    bstream->write(mRot.z);
+  }
+
+  if (bstream->writeFlag(mask & SubjectMask)) 
+  {
+    S32 ghost_id = (cam_subject) ? conn->getGhostIndex(cam_subject) : -1;
+    if (bstream->writeFlag(ghost_id != -1))
+      bstream->writeRangedU32(U32(ghost_id), 0, NetConnection::MaxGhostCount);
+    else if (cam_subject)
+      retMask |= SubjectMask;
+  }
+
+  return retMask;
+}
+
+void afxCamera::unpackUpdate(NetConnection *conn, BitStream *bstream)
+{
+  Parent::unpackUpdate(conn,bstream);
+
+  // controlled by the client?
+  //if(bstream->readFlag())
+  //   return;
+
+  if (bstream->readFlag()) {
+    Point3F pos,rot;
+    bstream->read(&pos.x);
+    bstream->read(&pos.y);
+    bstream->read(&pos.z);
+    bstream->read(&rot.x);
+    bstream->read(&rot.z);
+    set_cam_pos(pos,rot);
+
+    // New delta for client side interpolation
+    delta.pos = pos;
+    delta.rot = rot;
+    delta.posVec = delta.rotVec = VectorF(0,0,0);
+  }
+
+  if (bstream->readFlag()) 
+  {
+    if (bstream->readFlag())
+    {
+      S32 ghost_id = bstream->readRangedU32(0, NetConnection::MaxGhostCount);
+      cam_subject = dynamic_cast<GameBase*>(conn->resolveGhost(ghost_id));
+    }
+    else
+      cam_subject = NULL;
+  }
+}
+
+// Override to ensure both are kept in scope
+void afxCamera::onCameraScopeQuery(NetConnection* conn, CameraScopeQuery* query) 
+{
+  if (cam_subject)
+    conn->objectInScope(cam_subject);
+  Parent::onCameraScopeQuery(conn, query);
+}
+
+//----------------------------------------------------------------------------
+// check if the object needs to be observed through its own camera...
+void afxCamera::getCameraTransform(F32* pos, MatrixF* mat)
+{
+  // The camera doesn't support a third person mode,
+  // so we want to override the default ShapeBase behavior.
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if (obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    obj->getCameraTransform(pos, mat);
+  else
+    getEyeTransform(mat);
+}
+
+void afxCamera::setTransform(const MatrixF& mat)
+{
+  // This method should never be called on the client.
+
+  // This currently converts all rotation in the mat into
+  // rotations around the z and x axis.
+  Point3F pos,vec;
+  mat.getColumn(1,&vec);
+  mat.getColumn(3,&pos);
+  Point3F rot(-mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan2(-vec.x,vec.y));
+  set_cam_pos(pos,rot);
+}
+
+void afxCamera::onEditorEnable()
+{
+  mNetFlags.set(Ghostable);
+}
+
+void afxCamera::onEditorDisable()
+{
+  mNetFlags.clear(Ghostable);
+}
+
+F32 afxCamera::getCameraFov()
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->getCameraFov());
+  else
+    return(Parent::getCameraFov());
+}
+
+F32 afxCamera::getDefaultCameraFov()
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->getDefaultCameraFov());
+  else
+    return(Parent::getDefaultCameraFov());
+}
+
+bool afxCamera::isValidCameraFov(F32 fov)
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    return(obj->isValidCameraFov(fov));
+  else
+    return(Parent::isValidCameraFov(fov));
+}
+
+void afxCamera::setCameraFov(F32 fov)
+{
+  ShapeBase * obj = dynamic_cast<ShapeBase*>(static_cast<SimObject*>(mOrbitObject));
+  if(obj && static_cast<ShapeBaseData*>(obj->getDataBlock())->observeThroughObject)
+    obj->setCameraFov(fov);
+  else
+    Parent::setCameraFov(fov);
+}
+
+F32 afxCamera::getDamageFlash() const
+{
+  if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject))
+  {
+    const GameBase *castObj = mOrbitObject;
+    const ShapeBase* psb = dynamic_cast<const ShapeBase*>(castObj);
+    if (psb)
+      return psb->getDamageFlash();
+  }
+
+  return mDamageFlash;
+}
+
+F32 afxCamera::getWhiteOut() const
+{
+  if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject))
+  {
+    const GameBase *castObj = mOrbitObject;
+    const ShapeBase* psb = dynamic_cast<const ShapeBase*>(castObj);
+    if (psb)
+      return psb->getWhiteOut();
+  }
+
+  return mWhiteOut;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxCamera::setControllingClient( GameConnection* client )
+{
+   GameBase::setControllingClient( client );
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 189 - 0
Engine/source/afx/afxCamera.h

@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// 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.
+//
+// afxCamera implements a modified camera for demonstrating a third person camera style
+// which is more common to RPG games than the standard FPS style camera. For the most part,
+// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera
+// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum
+// required for demonstrating an RPG style camera and leaves tons of room for improvement. 
+// It should be replaced with a better camera if possible.
+//
+// Advanced Camera Resource by Thomas "Man of Ice" Lund:
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_H_
+#define _AFX_CAMERA_H_
+
+#ifndef _SHAPEBASE_H_
+#include "game/shapeBase.h"
+#endif
+
+//----------------------------------------------------------------------------
+struct afxCameraData: public ShapeBaseData {
+  typedef ShapeBaseData Parent;
+
+  static U32    sCameraCollisionMask;
+
+  //
+  DECLARE_CONOBJECT(afxCameraData);
+  DECLARE_CATEGORY("AFX");
+  static void initPersistFields();
+  virtual void packData(BitStream* stream);
+  virtual void unpackData(BitStream* stream);
+};
+
+
+//----------------------------------------------------------------------------
+// Implements a basic camera object.
+class afxCamera: public ShapeBase
+{
+  typedef ShapeBase Parent;
+
+  enum MaskBits {
+    MoveMask     = Parent::NextFreeMask,
+    SubjectMask  = Parent::NextFreeMask << 1,
+    NextFreeMask = Parent::NextFreeMask << 2
+  };
+
+  struct StateDelta {
+    Point3F pos;
+    Point3F rot;
+    VectorF posVec;
+    VectorF rotVec;
+  };
+
+  enum 
+  {
+    ThirdPersonMode = 1,
+    FlyMode         = 2,
+    OrbitObjectMode = 3,
+    OrbitPointMode  = 4,
+    CameraFirstMode = 0,
+    CameraLastMode  = 4
+  };
+
+private:
+  int             mode;
+  Point3F         mRot;
+  StateDelta      delta;
+
+  SimObjectPtr<GameBase> mOrbitObject;
+  F32             mMinOrbitDist;
+  F32             mMaxOrbitDist;
+  F32             mCurOrbitDist;
+  Point3F         mPosition;
+  bool            mObservingClientObject;
+
+  SceneObject*    cam_subject;
+  Point3F         cam_offset;
+  Point3F         coi_offset;
+  F32             cam_distance;
+  F32             cam_angle;
+  bool            cam_dirty;
+
+  bool            flymode_saved;
+  Point3F         flymode_saved_pos;
+  S8              third_person_snap_c;
+  S8              third_person_snap_s;
+
+  void            set_cam_pos(const Point3F& pos, const Point3F& viewRot);
+  void            cam_update(F32 dt, bool on_server);
+
+public:
+  /*C*/           afxCamera();
+  /*D*/           ~afxCamera();
+
+  Point3F&        getPosition();
+  void            setFlyMode();
+  void            setOrbitMode(GameBase* obj, Point3F& pos, AngAxisF& rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject);
+  void            validateEyePoint(F32 pos, MatrixF *mat);
+
+  GameBase*       getOrbitObject() { return(mOrbitObject); }
+  bool            isObservingClientObject() { return(mObservingClientObject); }
+
+  void            snapToPosition(const Point3F& pos);
+  void            setCameraSubject(SceneObject* subject);
+  void            setThirdPersonOffset(const Point3F& offset);
+  void            setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset);
+  const Point3F&  getThirdPersonOffset() const { return cam_offset; }
+  const Point3F&  getThirdPersonCOIOffset() const { return coi_offset; }
+  void            setThirdPersonDistance(F32 distance);
+  F32             getThirdPersonDistance();
+  void            setThirdPersonAngle(F32 angle);
+  F32             getThirdPersonAngle();
+  void            setThirdPersonMode();
+  void            setThirdPersonSnap();
+  void            setThirdPersonSnapClient();
+  const char*     getMode();
+
+  bool            isCamera() const { return true; }
+
+  DECLARE_CONOBJECT(afxCamera);
+  DECLARE_CATEGORY("AFX");
+
+private:          // 3POV SECTION
+  U32             blockers_mask_3pov;
+
+  void            cam_update_3pov(F32 dt, bool on_server);
+  bool            avoid_blocked_view(const Point3F& start, const Point3F& end, Point3F& newpos);
+  bool            test_blocked_line(const Point3F& start, const Point3F& end);
+
+public:           // STD OVERRIDES SECTION
+  virtual bool    onAdd();
+  virtual void    onRemove();
+  virtual void    onDeleteNotify(SimObject *obj);
+
+  virtual void    advanceTime(F32 dt);
+  virtual void    processTick(const Move* move);
+  virtual void    interpolateTick(F32 delta);
+
+  virtual void    writePacketData(GameConnection *conn, BitStream *stream);
+  virtual void    readPacketData(GameConnection *conn, BitStream *stream);
+  virtual U32     packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+  virtual void    unpackUpdate(NetConnection *conn, BitStream *stream);
+
+  virtual void    onCameraScopeQuery(NetConnection* cr, CameraScopeQuery*);
+  virtual void    getCameraTransform(F32* pos,MatrixF* mat);
+  virtual void    setTransform(const MatrixF& mat);
+
+  virtual void    onEditorEnable();
+  virtual void    onEditorDisable();
+
+  virtual F32     getCameraFov();
+  virtual F32     getDefaultCameraFov();
+  virtual bool    isValidCameraFov(F32 fov);
+  virtual void    setCameraFov(F32 fov);
+
+  virtual F32     getDamageFlash() const;
+  virtual F32     getWhiteOut() const;
+
+  virtual void setControllingClient( GameConnection* connection );
+};
+
+
+#endif // _AFX_CAMERA_H_

+ 1084 - 0
Engine/source/afx/afxChoreographer.cpp

@@ -0,0 +1,1084 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "math/mathIO.h"
+#include "console/compiler.h"
+
+#include "afx/afxConstraint.h"
+#include "afx/afxChoreographer.h"
+#include "afx/afxEffectWrapper.h"
+#include "afx/util/afxParticlePool.h"
+#include "afx/forces/afxForceSet.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxChoreographerData);
+
+ConsoleDocClass( afxChoreographerData,
+   "@brief Datablock base class used by choreographers.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxChoreographerData::afxChoreographerData()
+{
+  exec_on_new_clients = false;
+  echo_packet_usage = 100;
+  client_script_file = ST_NULLSTRING;
+  client_init_func = ST_NULLSTRING;
+}
+
+afxChoreographerData::afxChoreographerData(const afxChoreographerData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  exec_on_new_clients = other.exec_on_new_clients;
+  echo_packet_usage = other.echo_packet_usage;
+  client_script_file = other.client_script_file;
+  client_init_func = other.client_init_func;
+}
+
+#define myOffset(field) Offset(field, afxChoreographerData)
+
+void afxChoreographerData::initPersistFields()
+{
+  addField("execOnNewClients",    TypeBool,       myOffset(exec_on_new_clients),
+    "...");
+  addField("echoPacketUsage",     TypeS8,         myOffset(echo_packet_usage),
+    "...");
+  addField("clientScriptFile",    TypeFilename,   myOffset(client_script_file),
+    "...");
+  addField("clientInitFunction",  TypeString,     myOffset(client_init_func),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxChoreographerData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(exec_on_new_clients);
+  stream->write(echo_packet_usage);
+  stream->writeString(client_script_file);
+  stream->writeString(client_init_func);
+}
+
+void afxChoreographerData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&exec_on_new_clients);
+  stream->read(&echo_packet_usage);
+  client_script_file = stream->readSTString();
+  client_init_func = stream->readSTString();
+}
+
+bool afxChoreographerData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  if (!server && client_script_file != ST_NULLSTRING)
+  {
+    Compiler::gSyntaxError = false;
+    Con::evaluate(avar("exec(\"%s\");", client_script_file), false, 0);
+    if (Compiler::gSyntaxError)
+    {
+      Con::errorf("afxChoreographerData: failed to exec clientScriptFile \"%s\" -- syntax error", client_script_file);
+      Compiler::gSyntaxError = false;
+    }
+  }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxChoreographer);
+
+ConsoleDocClass( afxChoreographer,
+   "@brief Base class used by choreographers.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+afxChoreographer::afxChoreographer()
+{
+  datablock = 0;  // datablock initializer (union these?)
+  exeblock = 0;   // object to use in executef callouts
+
+  // create the constraint manager
+  constraint_mgr = new afxConstraintMgr();
+
+  ranking = 0;
+  lod = 0;
+  exec_conds_mask = 0;
+  choreographer_id = 0;
+  extra = 0;
+  started_with_newop = false;
+  postpone_activation = false;
+  remapped_cons_sent = false; // CONSTRAINT REMAPPING
+
+  dyn_cons_defs = &dc_defs_a;
+  dyn_cons_defs2 = &dc_defs_b;
+
+  proc_after_obj = 0;
+  trigger_mask = 0;
+
+  force_set_mgr = 0;
+
+  mOrderGUID = 0x0FFFFFFF;
+}
+
+afxChoreographer::~afxChoreographer()
+{
+  explicit_clients.clear();
+
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL)
+      delete (*dyn_cons_defs)[i].cons_obj.point;
+    else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL)
+      delete (*dyn_cons_defs)[i].cons_obj.xfm;
+  }
+
+  constraint_mgr->clearAllScopeableObjs();
+  delete constraint_mgr;
+
+  delete force_set_mgr;
+}
+
+void afxChoreographer::initPersistFields()
+{
+  // conditionals
+  addField("extra",              TYPEID<SimObject>(), Offset(extra, afxChoreographer),
+    "...");
+  addField("postponeActivation", TypeBool,            Offset(postpone_activation, afxChoreographer),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+bool afxChoreographer::onAdd()
+{
+  if (!Parent::onAdd()) 
+    return(false);
+
+  if (isServerObject())
+    choreographer_id = arcaneFX::registerChoreographer(this);
+  else
+  {
+    if (proc_after_obj)
+    {
+      processAfter(proc_after_obj);
+      proc_after_obj = 0;
+    }
+
+    force_set_mgr = new afxForceSetMgr();
+
+    if (datablock && datablock->client_init_func != ST_NULLSTRING)
+      Con::executef(datablock->client_init_func, getIdString());
+  }
+
+  return(true);
+}
+
+void afxChoreographer::onRemove()
+{
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i])
+      particle_pools[i]->setChoreographer(0);
+  particle_pools.clear();
+
+  if (isServerObject())
+    arcaneFX::unregisterChoreographer(this);
+  else
+    arcaneFX::unregisterClientChoreographer(this);
+
+  choreographer_id = 0;
+  Parent::onRemove();
+}
+
+void afxChoreographer::onDeleteNotify(SimObject* obj)
+{
+  if (dynamic_cast<SceneObject*>(obj))
+  { 
+    SceneObject* scn_obj = (SceneObject*)(obj);
+    for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+      if ((*dyn_cons_defs)[i].cons_type != OBJECT_CONSTRAINT || (*dyn_cons_defs)[i].cons_obj.object != scn_obj)
+        dyn_cons_defs2->push_back((*dyn_cons_defs)[i]);
+
+    Vector<dynConstraintDef>* tmp = dyn_cons_defs;
+    dyn_cons_defs = dyn_cons_defs2;
+    dyn_cons_defs2 = tmp;
+    dyn_cons_defs2->clear();
+
+    if (isServerObject() && scn_obj->isScopeable())
+        constraint_mgr->removeScopeableObject(scn_obj);
+  }
+  else if (dynamic_cast<NetConnection*>(obj))
+  {
+    removeExplicitClient((NetConnection*)obj);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+bool afxChoreographer::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxChoreographerData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  exeblock = datablock;
+
+  return true;
+}
+
+void afxChoreographer::pack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  stream->write(dyn_cons_defs->size());
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_type == OBJECT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.object != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      SceneObject* object = (*dyn_cons_defs)[i].cons_obj.object;
+      S32 ghost_idx = conn->getGhostIndex(object); 
+      if (stream->writeFlag(ghost_idx != -1))
+      {
+        stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      }
+      else
+      {
+        if (stream->writeFlag(object->getScopeId() > 0))
+        {
+          stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS);
+          stream->writeFlag(dynamic_cast<ShapeBase*>(object) != NULL);
+        }
+      }
+    }
+    else if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.point);
+    }
+    else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL)
+    {
+      stream->writeString((*dyn_cons_defs)[i].cons_name);
+      stream->write((*dyn_cons_defs)[i].cons_type);
+      mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.xfm);
+    }
+  }
+      
+  constraint_mgr->packConstraintNames(conn, stream);
+}
+
+void afxChoreographer::unpack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  S32 n_defs;
+  stream->read(&n_defs);
+  dyn_cons_defs->clear();
+  for (S32 i = 0; i < n_defs; i++)
+  {
+    StringTableEntry cons_name = stream->readSTString();
+    U8 cons_type; stream->read(&cons_type);
+    if (cons_type == OBJECT_CONSTRAINT)
+    {
+      SceneObject* scn_obj = NULL;
+      if (stream->readFlag())
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        scn_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+        if (scn_obj)
+        {
+          addObjectConstraint(scn_obj, cons_name); 
+        }
+        else
+          Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name);
+      }
+      else
+      {
+        if (stream->readFlag())
+        {
+          U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          bool is_shape = stream->readFlag();
+          addObjectConstraint(scope_id, cons_name, is_shape);                                                                   
+        }
+      }
+    }
+    else if (cons_type == POINT_CONSTRAINT)
+    {
+      Point3F point;
+      mathRead(*stream, &point);
+      addPointConstraint(point, cons_name);
+    }    
+    else if (cons_type == TRANSFORM_CONSTRAINT)
+    {
+      MatrixF xfm;
+      mathRead(*stream, &xfm);
+      addTransformConstraint(xfm, cons_name);
+    }    
+  }
+  
+  constraint_mgr->unpackConstraintNames(stream);
+}
+
+void afxChoreographer::setup_dynamic_constraints()
+{
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    switch ((*dyn_cons_defs)[i].cons_type)
+    {
+    case OBJECT_CONSTRAINT:
+      constraint_mgr->setReferenceObject((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.object);
+      break;
+    case POINT_CONSTRAINT:
+      constraint_mgr->setReferencePoint((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.point);
+      break;
+    case TRANSFORM_CONSTRAINT:
+      constraint_mgr->setReferenceTransform((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.xfm);
+      break;
+    case OBJECT_CONSTRAINT_SANS_OBJ:
+      constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, false);
+      break;
+    case OBJECT_CONSTRAINT_SANS_SHAPE:
+      constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, true);
+      break;
+    }
+  }
+}
+
+afxChoreographer::dynConstraintDef* afxChoreographer::find_cons_def_by_name(const char* cons_name)
+{
+  StringTableEntry cons_name_ste = StringTable->insert(cons_name);
+
+  for (S32 i = 0; i < dyn_cons_defs->size(); i++)
+  {
+    if ((*dyn_cons_defs)[i].cons_name == cons_name_ste)
+      return &((*dyn_cons_defs)[i]);
+  }
+
+  return 0;
+}
+
+void afxChoreographer::check_packet_usage(NetConnection* conn, BitStream* stream, S32 mark_stream_pos, const char* msg_tag)
+{
+
+  S32 packed_size = stream->getCurPos() - mark_stream_pos;
+  S32 current_headroom = stream->getMaxWriteBitNum()-(stream->getStreamSize()<<3);
+  S32 max_headroom = stream->getMaxWriteBitNum()-(conn->getMaxRatePacketSize()<<3);
+
+  if (packed_size > max_headroom)
+  {
+    Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for max PacketSize settings. [%s]",
+                msg_tag, datablock->getName(), packed_size, max_headroom, "PACKET OVERRUN POSSIBLE");
+    Con::errorf("getMaxRatePacketSize()=%d getCurRatePacketSize()=%d", conn->getMaxRatePacketSize(), conn->getCurRatePacketSize());
+  }
+  // JTF Note: this current_headroom > 0 check is odd and occurs when object is created real early
+  // in the startup, such as when the dead orc gets fatal damage and we try to post a text effect.
+  else if (packed_size > current_headroom && current_headroom > 0)
+  {
+    Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for current PacketSize settings. [%s]",
+                msg_tag, datablock->getName(), packed_size, current_headroom, "PACKET OVERRUN POSSIBLE");
+  }
+  else
+  {
+    F32 percentage = 100.0f*((F32)packed_size/(F32)max_headroom);
+    if (percentage >= datablock->echo_packet_usage)
+    {
+        Con::warnf("%s [%s] -- packed-bits (%d) < limit (%d). [%.1f%% full]", msg_tag, datablock->getName(),
+                  packed_size, max_headroom, percentage);
+    }
+  }
+}
+
+SceneObject* afxChoreographer::get_camera(Point3F* cam_pos) const
+{
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  if (!conn)
+  {
+    if (cam_pos)
+      cam_pos->zero();
+    return 0;
+  }
+
+  SceneObject* cam_obj = conn->getCameraObject();
+  if (cam_pos)
+  {
+    if (cam_obj)
+    {
+      MatrixF cam_mtx;
+      conn->getControlCameraTransform(0, &cam_mtx);
+      cam_mtx.getColumn(3, cam_pos);
+    }
+    else
+      cam_pos->zero();
+  }
+
+  return cam_obj;
+}
+
+U32 afxChoreographer::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  if (stream->writeFlag(mask & InitialUpdateMask))      //-- INITIAL UPDATE ?
+  {
+    stream->write(ranking);
+    stream->write(lod);
+    stream->write(exec_conds_mask);
+    stream->write(choreographer_id);
+
+    // write dynamic fields beginning with arcaneFX::sParameterFieldPrefix
+    SimFieldDictionaryIterator itr(getFieldDictionary());
+    SimFieldDictionary::Entry* entry;
+    U32 prefix_len = dStrlen(arcaneFX::sParameterFieldPrefix);
+    if (prefix_len > 0)
+    {
+      while ((entry = *itr) != NULL)
+      {
+        if (dStrncmp(entry->slotName, arcaneFX::sParameterFieldPrefix, prefix_len) == 0)
+        {
+          stream->writeFlag(true);
+          stream->writeString(entry->slotName);
+          stream->writeLongString(1023, entry->value);
+        }
+        ++itr;
+      }
+    }
+    stream->writeFlag(false);
+  }
+
+  if (stream->writeFlag(mask & TriggerMask))
+  {
+    stream->write(trigger_mask);
+  }
+
+  // CONSTRAINT REMAPPING <<
+  if (stream->writeFlag((mask & RemapConstraintMask) && !remapped_cons_defs.empty()))
+  {
+    remapped_cons_sent = true;
+    //Con::errorf("PACKING CONS REMAP %d conn:%d", remapped_cons_defs.size(), (U32) conn);
+
+    stream->write(remapped_cons_defs.size());
+    for (S32 i = 0; i < remapped_cons_defs.size(); i++)
+    {
+      if (remapped_cons_defs[i]->cons_type == OBJECT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.object != NULL)
+      {
+        //Con::errorf("PACKING CONS REMAP: name %s", remapped_cons_defs[i]->cons_name);
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        //Con::errorf("PACKING CONS REMAP: type %d", remapped_cons_defs[i]->cons_type);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        SceneObject* object = remapped_cons_defs[i]->cons_obj.object;
+        S32 ghost_idx = conn->getGhostIndex(object);
+        //Con::errorf("PACKING CONS REMAP: ghost %d", ghost_idx);
+        if (stream->writeFlag(ghost_idx != -1))
+        {
+          stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+        }
+        else
+        {
+          if (stream->writeFlag(object->getScopeId() > 0))
+          {
+            stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS);
+            stream->writeFlag(dynamic_cast<ShapeBase*>(object) != NULL);
+          }
+        }
+      }
+      else if (remapped_cons_defs[i]->cons_type == POINT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.point != NULL)
+      {
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.point);
+      }
+      else if (remapped_cons_defs[i]->cons_type == TRANSFORM_CONSTRAINT && remapped_cons_defs[i]->cons_obj.xfm != NULL)
+      {
+        stream->writeString(remapped_cons_defs[i]->cons_name);
+        stream->write(remapped_cons_defs[i]->cons_type);
+        mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.xfm);
+      }
+    }
+  }
+  // CONSTRAINT REMAPPING >>
+
+  AssertISV(stream->isValid(), "afxChoreographer::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); 
+
+  return retMask;
+}
+
+void afxChoreographer::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {
+    stream->read(&ranking);
+    stream->read(&lod);
+    stream->read(&exec_conds_mask);
+    stream->read(&choreographer_id);
+
+    // read dynamic fields
+    while(stream->readFlag())
+    {
+      char slotName[256];
+      char value[1024];
+      stream->readString(slotName);
+      stream->readLongString(1023, value);
+      setDataField(StringTable->insert(slotName), 0, value);
+    }
+
+    arcaneFX::registerClientChoreographer(this);
+  }
+
+  if (stream->readFlag()) // TriggerMask
+  {
+    stream->read(&trigger_mask);
+  }
+
+  // CONSTRAINT REMAPPING <<
+  if (stream->readFlag()) // RemapConstraintMask
+  {
+    S32 n_defs;
+    stream->read(&n_defs);
+    for (S32 i = 0; i < n_defs; i++)
+    {
+      StringTableEntry cons_name = stream->readSTString();
+      U8 cons_type; stream->read(&cons_type);
+      if (cons_type == OBJECT_CONSTRAINT)
+      {
+        SceneObject* scn_obj = NULL;
+        if (stream->readFlag())
+        {
+          S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+          scn_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+          if (scn_obj)
+          {
+            remapObjectConstraint(scn_obj, cons_name); 
+          }
+          else
+            Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name);
+        }
+        else
+        {
+          if (stream->readFlag())
+          {
+            U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+            bool is_shape = stream->readFlag();
+            remapObjectConstraint(scope_id, cons_name, is_shape);
+          }
+        }
+      }
+      else if (cons_type == POINT_CONSTRAINT)
+      {
+        Point3F point;
+        mathRead(*stream, &point);
+        remapPointConstraint(point, cons_name);
+      }    
+      else if (cons_type == TRANSFORM_CONSTRAINT)
+      {
+        MatrixF xfm;
+        mathRead(*stream, &xfm);
+        remapTransformConstraint(xfm, cons_name);
+      }    
+    }
+  }
+  // CONSTRAINT REMAPPING >>
+}
+
+void afxChoreographer::executeScriptEvent(const char* method, afxConstraint* cons, 
+                                          const MatrixF& xfm, const char* data)
+{
+  SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL;
+
+  char *arg_buf = Con::getArgBuffer(256);
+  Point3F pos;
+  xfm.getColumn(3,&pos);
+  AngAxisF aa(xfm);
+  dSprintf(arg_buf,256,"%g %g %g %g %g %g %g",
+           pos.x, pos.y, pos.z,
+           aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
+
+  // CALL SCRIPT afxChoreographerData::method(%choreographer, %constraint, %transform, %data)
+  Con::executef(exeblock, method, 
+                getIdString(),
+                (cons_obj) ? cons_obj->getIdString() : "",
+                arg_buf,
+                data);
+}
+
+void afxChoreographer::addObjectConstraint(SceneObject* object, const char* cons_name)
+{
+  if (!object || !cons_name)
+    return;
+
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = OBJECT_CONSTRAINT;
+  dyn_def.cons_obj.object = object;
+  dyn_cons_defs->push_back(dyn_def);
+
+#if defined(AFX_CAP_SCOPE_TRACKING)
+  if (isServerObject() && object->isScopeable())
+    constraint_mgr->addScopeableObject(object);
+#endif
+
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name);
+  deleteNotify(object);
+}
+
+void afxChoreographer::addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape)
+{
+  if (!cons_name)
+    return;
+
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = (is_shape) ? OBJECT_CONSTRAINT_SANS_SHAPE : OBJECT_CONSTRAINT_SANS_OBJ;
+  dyn_def.cons_obj.scope_id = scope_id;
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name);
+}
+
+void afxChoreographer::addPointConstraint(Point3F& point, const char* cons_name)
+{
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = POINT_CONSTRAINT;
+  dyn_def.cons_obj.point = new Point3F(point);
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT, dyn_def.cons_name);
+}
+
+void afxChoreographer::addTransformConstraint(MatrixF& xfm, const char* cons_name)
+{
+  dynConstraintDef dyn_def;
+  dyn_def.cons_name = StringTable->insert(cons_name);
+  dyn_def.cons_type = TRANSFORM_CONSTRAINT;
+  dyn_def.cons_obj.xfm = new MatrixF(xfm);
+  dyn_cons_defs->push_back(dyn_def);
+
+  constraint_mgr->defineConstraint(TRANSFORM_CONSTRAINT, dyn_def.cons_name);
+}
+
+// CONSTRAINT REMAPPING <<
+static inline U32 resolve_cons_spec(const char* source_spec, Point3F& pos, MatrixF& xfm, SceneObject** scn_obj)
+{
+  AngAxisF aa;
+
+  S32 args_n = dSscanf(source_spec, "%g %g %g %g %g %g %g", 
+                       &pos.x, &pos.y, &pos.z,
+                       &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle);
+
+  // TRANSFORM CONSTRAINT SRC
+  if (args_n == 7)
+  {
+     aa.setMatrix(&xfm);
+     xfm.setColumn(3,pos);
+     return afxEffectDefs::TRANSFORM_CONSTRAINT;
+  }
+
+  // POINT CONSTRAINT SRC 
+  if (args_n == 3)
+  {
+    return afxEffectDefs::POINT_CONSTRAINT;
+  }
+
+  SimObject* cons_sim_obj = Sim::findObject(source_spec);
+  *scn_obj = dynamic_cast<SceneObject*>(cons_sim_obj);
+  if (*scn_obj)
+  {
+    return afxEffectDefs::OBJECT_CONSTRAINT;
+  }
+
+  return afxEffectDefs::UNDEFINED_CONSTRAINT_TYPE;
+}
+// CONSTRAINT REMAPPING >>
+
+bool afxChoreographer::addConstraint(const char* source_spec, const char* cons_name)
+{
+  VectorF pos;
+  MatrixF xfm;
+  SceneObject* scn_obj;
+
+  switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj))
+  {
+  case TRANSFORM_CONSTRAINT:
+    addTransformConstraint(xfm, cons_name);
+    return true;
+  case POINT_CONSTRAINT:
+    addPointConstraint(pos, cons_name);
+    return true;
+  case OBJECT_CONSTRAINT:
+    addObjectConstraint(scn_obj, cons_name);
+    return true;
+  }
+
+  return false;
+}
+
+void afxChoreographer::addNamedEffect(afxEffectWrapper* ew)
+{
+  named_effects.addObject(ew);
+}
+
+void afxChoreographer::removeNamedEffect(afxEffectWrapper* ew)
+{
+  named_effects.removeObject(ew);
+}
+
+afxEffectWrapper* afxChoreographer::findNamedEffect(StringTableEntry name)
+{
+  return (afxEffectWrapper*) named_effects.findObject(name);
+}
+
+void afxChoreographer::setGhostConstraintObject(SceneObject* obj, StringTableEntry cons_name)
+{
+  if (constraint_mgr)
+    constraint_mgr->setReferenceObject(cons_name, obj);
+}
+
+void afxChoreographer::restoreScopedObject(SceneObject* obj)
+{
+  constraint_mgr->restoreScopedObject(obj, this);
+  constraint_mgr->adjustProcessOrdering(this);
+}
+
+void afxChoreographer::addExplicitClient(NetConnection* conn) 
+{ 
+  if (!conn)
+    return;
+
+  for (S32 i = 0; i < explicit_clients.size(); i++)
+  {
+    if (explicit_clients[i] == conn)
+      return;
+  }
+
+  explicit_clients.push_back(conn);
+  deleteNotify(conn);
+}
+
+void afxChoreographer::removeExplicitClient(NetConnection* conn) 
+{ 
+  if (!conn)
+    return;
+
+  for (S32 i = 0; i < explicit_clients.size(); i++)
+  {
+    if (explicit_clients[i] == conn)
+    {
+      clearNotify(conn);
+      explicit_clients.erase_fast(i);
+      return;
+    }
+  }
+}
+
+void afxChoreographer::postProcessAfterObject(GameBase* obj)
+{
+  proc_after_obj = obj;
+}
+
+void afxChoreographer::setTriggerMask(U32 mask)
+{
+  if (mask != trigger_mask)
+  {
+    trigger_mask = mask;
+    setMaskBits(TriggerMask);
+  }
+}
+
+afxParticlePool* afxChoreographer::findParticlePool(afxParticlePoolData* key_block, U32 key_index) 
+{ 
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i] && particle_pools[i]->hasMatchingKeyBlock(key_block, key_index))
+      return particle_pools[i];
+
+  return 0; 
+}
+
+void afxChoreographer::registerParticlePool(afxParticlePool* pool) 
+{ 
+  particle_pools.push_back(pool);
+}
+
+void afxChoreographer::unregisterParticlePool(afxParticlePool* pool) 
+{ 
+  for (S32 i = 0; i < particle_pools.size(); i++)
+    if (particle_pools[i] == pool)
+    {
+      particle_pools.erase_fast(i);
+      return;
+    }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod( afxChoreographer, setRanking, void, ( unsigned int ranking ),,
+   "Set a ranking value (0-255) for the choreographer.\n" )
+{
+  object->setRanking((U8)ranking);
+}
+
+DefineEngineMethod( afxChoreographer, setLevelOfDetail, void, ( unsigned int lod ),,
+   "Set a level-of-detail value (0-255) for the choreographer.\n" )
+{
+  object->setLevelOfDetail((U8)lod);
+}
+
+DefineEngineMethod( afxChoreographer, setExecConditions, void, ( U32 mask ),,
+   "Set a bitmask to specifiy the state of exec-conditions.\n" )
+{
+  object->setExecConditions(afxChoreographer::USER_EXEC_CONDS_MASK & mask);
+}
+
+DefineEngineMethod( afxChoreographer, addConstraint, void, ( const char* source, const char* name),,
+   "Add a dynamic constraint consistiing of a source and name. The source can be a SceneObject, a 3-valued position, or a 7-valued transform.\n" )
+{
+  if (!object->addConstraint(source, name))
+    Con::errorf("afxChoreographer::addConstraint() -- failed to resolve constraint source [%s].", source);
+}
+
+DefineEngineMethod( afxChoreographer, addExplicitClient, void, ( NetConnection* client ),,
+   "Add an explicit client.\n" )
+{
+   if (!client)
+   {
+      Con::errorf(ConsoleLogEntry::General, "afxChoreographer::addExplicitClient: Failed to resolve client connection");
+      return;
+   }
+
+   object->addExplicitClient(client);
+}
+
+DefineEngineMethod( afxChoreographer, setTriggerBit, void, ( U32 bit_num ),,
+   "Set a bit of the trigger-mask.\n" )
+{
+  U32 set_bit = 1 << bit_num;
+  object->setTriggerMask(set_bit | object->getTriggerMask());
+}
+
+DefineEngineMethod( afxChoreographer, clearTriggerBit, void, ( U32 bit_num ),,
+   "Unset a bit of the trigger-mask.\n" )
+{
+  U32 clear_bit = 1 << bit_num;
+  object->setTriggerMask(~clear_bit & object->getTriggerMask());
+}
+
+DefineEngineMethod( afxChoreographer, testTriggerBit, bool, ( U32 bit_num ),,
+   "Test state of a trigger-mask bit.\n" )
+{
+  U32 test_bit = 1 << bit_num;
+  return ((test_bit & object->getTriggerMask()) != 0);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// CONSTRAINT REMAPPING <<
+
+void afxChoreographer::remapObjectConstraint(SceneObject* object, const char* cons_name)
+{
+  if (!object || !cons_name)
+    return;
+
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != OBJECT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  // nothing to do if new object is same as old 
+  if (dyn_def->cons_obj.object == object)
+  {
+#ifdef TORQUE_DEBUG
+    Con::warnf("afxChoreographer::remapObjectConstraint() -- remapped contraint object is same as existing object.");
+#endif
+    return;
+  }
+
+  dyn_def->cons_obj.object = object;
+  deleteNotify(object);
+
+  constraint_mgr->setReferenceObject(StringTable->insert(cons_name), object);
+
+#if defined(AFX_CAP_SCOPE_TRACKING)
+  if (isServerObject() && object->isScopeable())
+    constraint_mgr->addScopeableObject(object);
+#endif
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+void afxChoreographer::remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape)
+{
+  if (!cons_name)
+    return;
+
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != OBJECT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  constraint_mgr->setReferenceObjectByScopeId(StringTable->insert(cons_name), scope_id, is_shape);
+}
+
+void afxChoreographer::remapPointConstraint(Point3F& point, const char* cons_name)
+{
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapPointConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != POINT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapPointConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  *dyn_def->cons_obj.point = point;
+
+  constraint_mgr->setReferencePoint(StringTable->insert(cons_name), point);
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+void afxChoreographer::remapTransformConstraint(MatrixF& xfm, const char* cons_name)
+{
+  // must be a constraint-def with a matching name in the list
+  dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name);
+  if (!dyn_def)
+  {
+    Con::errorf("afxChoreographer::remapTransformConstraint() -- failed to find constraint name [%s].", cons_name);
+    return;
+  }
+
+  // constraint-def must have matching name constraint-type
+  if (dyn_def->cons_type != POINT_CONSTRAINT)
+  {
+    Con::errorf("afxChoreographer::remapTransformConstraint() -- remapped contraint type does not match existing constraint type.");
+    return;
+  }
+
+  *dyn_def->cons_obj.xfm = xfm;
+
+  constraint_mgr->setReferenceTransform(StringTable->insert(cons_name), xfm);
+
+  if (isServerObject())
+  {
+    if (remapped_cons_sent)
+    {
+      remapped_cons_defs.clear();
+      remapped_cons_sent = false;
+    }
+    remapped_cons_defs.push_back(dyn_def);
+    setMaskBits(RemapConstraintMask);
+  }
+}
+
+bool afxChoreographer::remapConstraint(const char* source_spec, const char* cons_name)
+{
+  SceneObject* scn_obj;
+  Point3F pos;
+  MatrixF xfm;
+
+  switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj))
+  {
+  case TRANSFORM_CONSTRAINT:
+    //addTransformConstraint(xfm, cons_name);
+    return true;
+  case POINT_CONSTRAINT:
+    //addPointConstraint(pos, cons_name);
+    return true;
+  case OBJECT_CONSTRAINT:
+    remapObjectConstraint(scn_obj, cons_name);
+    return true;
+  }
+
+  return false;
+}
+
+DefineEngineMethod( afxChoreographer, remapConstraint, void, ( const char* source, const char* name),,
+   "Remap a dynamic constraint to use a new source. The source can be a SceneObject, a 3-valued position, or a 7-valued transform. but must match type of existing source.\n" )
+{
+  if (!object->remapConstraint(source, name))
+    Con::errorf("afxChoreographer::remapConstraint() -- failed to resolve constraint source [%s].", source);
+}
+
+// CONSTRAINT REMAPPING >>
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 222 - 0
Engine/source/afx/afxChoreographer.h

@@ -0,0 +1,222 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CHOREOGRAPHER_H_
+#define _AFX_CHOREOGRAPHER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afxEffectDefs.h"
+#include "afxEffectWrapper.h"
+#include "afxMagicMissile.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxChoreographerData
+
+class afxChoreographerData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  bool              exec_on_new_clients;
+  U8                echo_packet_usage;
+  StringTableEntry  client_script_file;
+  StringTableEntry  client_init_func;
+
+public:
+  /*C*/         afxChoreographerData();
+  /*C*/         afxChoreographerData(const afxChoreographerData&, bool = false);
+
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxChoreographerData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxChoreographer
+
+class afxConstraint;
+class afxConstraintMgr;
+class afxEffectWrapper;
+class afxParticlePool;
+class afxParticlePoolData;
+class SimSet;
+class afxForceSetMgr;
+
+class afxChoreographer : public GameBase, public afxEffectDefs, public afxMagicMissileCallback
+{
+  typedef GameBase  Parent;
+
+public:
+  enum MaskBits 
+  {
+    TriggerMask         = Parent::NextFreeMask << 0,
+    RemapConstraintMask = Parent::NextFreeMask << 1, // CONSTRAINT REMAPPING
+    NextFreeMask        = Parent::NextFreeMask << 2
+  };
+
+  enum 
+  { 
+    USER_EXEC_CONDS_MASK = 0x00ffffff
+  };
+
+protected:
+  struct dynConstraintDef
+  {
+    StringTableEntry  cons_name;
+    U8                cons_type;
+    union
+    {
+      SceneObject* object;
+      Point3F*     point;
+      MatrixF*     xfm;
+      U16          scope_id;
+    } cons_obj;
+  };
+
+private:
+  afxChoreographerData*    datablock;
+  SimSet                   named_effects;
+  SimObject*               exeblock;
+  afxForceSetMgr*          force_set_mgr;
+  Vector<afxParticlePool*> particle_pools;
+  Vector<dynConstraintDef> dc_defs_a;
+  Vector<dynConstraintDef> dc_defs_b;
+  GameBase*                proc_after_obj;
+  U32                      trigger_mask;
+
+protected:
+  Vector<dynConstraintDef>* dyn_cons_defs;
+  Vector<dynConstraintDef>* dyn_cons_defs2;
+  afxConstraintMgr* constraint_mgr;
+  U32               choreographer_id;
+  U8                ranking;
+  U8                lod;
+  U32               exec_conds_mask;
+  SimObject*        extra;
+  Vector<NetConnection*> explicit_clients;
+  bool              started_with_newop;
+  bool              postpone_activation;
+
+  virtual void      pack_constraint_info(NetConnection* conn, BitStream* stream);
+  virtual void      unpack_constraint_info(NetConnection* conn, BitStream* stream);
+  void              setup_dynamic_constraints();
+  void              check_packet_usage(NetConnection*, BitStream*, S32 mark_stream_pos, const char* msg_tag);
+  SceneObject*      get_camera(Point3F* cam_pos=0) const;
+
+public:
+  /*C*/             afxChoreographer();
+  virtual           ~afxChoreographer();
+
+  static void       initPersistFields();
+
+  virtual bool      onAdd();
+  virtual void      onRemove();
+  virtual void      onDeleteNotify(SimObject*);
+  virtual bool      onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual U32       packUpdate(NetConnection*, U32, BitStream*);
+  virtual void      unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void      sync_with_clients() { }
+  
+  afxConstraintMgr* getConstraintMgr() { return constraint_mgr; }
+  afxForceSetMgr*   getForceSetMgr() { return force_set_mgr; }
+
+  afxParticlePool*  findParticlePool(afxParticlePoolData* key_block, U32 key_index);
+  void              registerParticlePool(afxParticlePool*);
+  void              unregisterParticlePool(afxParticlePool*);
+
+  void              setRanking(U8 value) { ranking = value; }
+  U8                getRanking() const { return ranking; }
+  bool              testRanking(U8 low, U8 high) { return (ranking <= high && ranking >= low); }
+  void              setLevelOfDetail(U8 value) { lod = value; }
+  U8                getLevelOfDetail() const { return lod; }
+  bool              testLevelOfDetail(U8 low, U8 high) { return (lod <= high && lod >= low); }
+  void              setExecConditions(U32 mask) { exec_conds_mask = mask; }
+  U32               getExecConditions() const { return exec_conds_mask; }
+
+  virtual void      executeScriptEvent(const char* method, afxConstraint*, 
+                                       const MatrixF& xfm, const char* data);
+
+  virtual void      inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                                  F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp) { }
+
+  void              addObjectConstraint(SceneObject*, const char* cons_name);
+  void              addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape);
+  void              addPointConstraint(Point3F&, const char* cons_name);
+  void              addTransformConstraint(MatrixF&, const char* cons_name);
+  bool              addConstraint(const char* source_spec, const char* cons_name);
+
+  void              addNamedEffect(afxEffectWrapper*);
+  void              removeNamedEffect(afxEffectWrapper*);
+  afxEffectWrapper* findNamedEffect(StringTableEntry);
+
+  void              clearChoreographerId() { choreographer_id = 0; }
+  U32               getChoreographerId() { return choreographer_id; }
+  void              setGhostConstraintObject(SceneObject*, StringTableEntry cons_name);
+  void              setExtra(SimObject* extra) { this->extra = extra; }
+  void              addExplicitClient(NetConnection* conn);
+  void              removeExplicitClient(NetConnection* conn);
+  U32               getExplicitClientCount() { return explicit_clients.size(); }
+
+  void              restoreScopedObject(SceneObject* obj);
+  virtual void      restoreObject(SceneObject*) { };
+
+  void              postProcessAfterObject(GameBase* obj);
+  U32               getTriggerMask() const { return trigger_mask; }
+  void              setTriggerMask(U32 trigger_mask);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// missile watcher callbacks
+public:
+  virtual void  impactNotify(const Point3F& p, const Point3F& n, SceneObject*) { }
+
+  DECLARE_CONOBJECT(afxChoreographer);
+  DECLARE_CATEGORY("AFX");
+  
+  // CONSTRAINT REMAPPING <<
+protected:
+  Vector<dynConstraintDef*> remapped_cons_defs;
+  bool              remapped_cons_sent;
+  virtual bool      remap_builtin_constraint(SceneObject*, const char* cons_name) { return false; }     
+  dynConstraintDef* find_cons_def_by_name(const char* cons_name);
+public:
+  void              remapObjectConstraint(SceneObject*, const char* cons_name);
+  void              remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape);
+  void              remapPointConstraint(Point3F&, const char* cons_name);
+  void              remapTransformConstraint(MatrixF&, const char* cons_name);
+  bool              remapConstraint(const char* source_spec, const char* cons_name);
+  // CONSTRAINT REMAPPING >> 
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CHOREOGRAPHER_H_

+ 2613 - 0
Engine/source/afx/afxConstraint.cpp

@@ -0,0 +1,2613 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "arcaneFX.h"
+
+#include "T3D/aiPlayer.h"
+#include "T3D/tsStatic.h"
+#include "sim/netConnection.h"
+#include "ts/tsShapeInstance.h"
+
+#include "afxConstraint.h"
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintDef
+
+// static
+StringTableEntry  afxConstraintDef::SCENE_CONS_KEY;
+StringTableEntry  afxConstraintDef::EFFECT_CONS_KEY;
+StringTableEntry  afxConstraintDef::GHOST_CONS_KEY;
+
+afxConstraintDef::afxConstraintDef()
+{
+  if (SCENE_CONS_KEY == 0)
+  {
+    SCENE_CONS_KEY = StringTable->insert("#scene");
+    EFFECT_CONS_KEY = StringTable->insert("#effect");
+    GHOST_CONS_KEY = StringTable->insert("#ghost");
+  }
+
+  reset();
+}
+
+bool afxConstraintDef::isDefined() 
+{
+  return (def_type != CONS_UNDEFINED); 
+}
+
+bool afxConstraintDef::isArbitraryObject() 
+{ 
+  return ((cons_src_name != ST_NULLSTRING) && (def_type == CONS_SCENE)); 
+}
+
+void afxConstraintDef::reset()
+{
+  cons_src_name = ST_NULLSTRING;
+  cons_node_name = ST_NULLSTRING;
+  def_type = CONS_UNDEFINED;
+  history_time = 0;
+  sample_rate = 30;
+  runs_on_server = false;
+  runs_on_client = false;
+  pos_at_box_center = false;
+  treat_as_camera = false;
+}
+
+bool afxConstraintDef::parseSpec(const char* spec, bool runs_on_server, 
+                                 bool runs_on_client)
+{
+  reset();
+
+  if (spec == 0 || spec[0] == '\0')
+    return false;
+
+  history_time = 0.0f;
+  sample_rate = 30;
+
+  this->runs_on_server = runs_on_server;
+  this->runs_on_client = runs_on_client;
+
+  // spec should be in one of these forms:
+  //    CONSTRAINT_NAME (only)
+  //    CONSTRAINT_NAME.NODE (shapeBase objects only)
+  //    CONSTRAINT_NAME.#center
+  //    object.OBJECT_NAME
+  //    object.OBJECT_NAME.NODE (shapeBase objects only)
+  //    object.OBJECT_NAME.#center
+  //    effect.EFFECT_NAME
+  //    effect.EFFECT_NAME.NODE
+  //    effect.EFFECT_NAME.#center
+  //    #ghost.EFFECT_NAME
+  //    #ghost.EFFECT_NAME.NODE
+  //    #ghost.EFFECT_NAME.#center
+  //
+
+  // create scratch buffer by duplicating spec.
+  char special = '\b';
+  char* buffer = dStrdup(spec);
+
+  // substitute a dots not inside parens with special character
+  S32 n_nested = 0;
+  for (char* b = buffer; (*b) != '\0'; b++)
+  {
+    if ((*b) == '(')
+      n_nested++;
+    else if ((*b) == ')')
+      n_nested--;
+    else if ((*b) == '.' && n_nested == 0)
+      (*b) = special;
+  }
+
+  // divide name into '.' separated tokens (up to 8)
+  char* words[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  char* dot = buffer;
+  int wdx = 0;
+  while (wdx < 8)
+  {
+    words[wdx] = dot;
+    dot = dStrchr(words[wdx++], special);
+    if (!dot)
+      break;
+    *(dot++) = '\0';
+    if ((*dot) == '\0')
+      break;
+  }
+
+  int n_words = wdx;
+
+  // at this point the spec has been split into words. 
+  // n_words indicates how many words we have.
+
+  // no words found (must have been all whitespace)
+  if (n_words < 1)
+  {
+    dFree(buffer);
+    return false;
+  }
+
+  char* hist_spec = 0;
+  char* words2[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+  int n_words2 = 0;
+
+  // move words to words2 while extracting #center and #history
+  for (S32 i = 0; i < n_words; i++)
+  {
+    if (dStrcmp(words[i], "#center") == 0)
+      pos_at_box_center = true;
+    else if (dStrncmp(words[i], "#history(", 9) == 0)
+      hist_spec = words[i];
+    else
+      words2[n_words2++] = words[i];
+  }
+
+  // words2[] now contains just the constraint part
+
+  // no words found (must have been all #center and #history)
+  if (n_words2 < 1)
+  {
+    dFree(buffer);
+    return false;
+  }
+
+  if (hist_spec)
+  {
+    char* open_paren = dStrchr(hist_spec, '(');
+    if (open_paren)
+    {
+      hist_spec = open_paren+1;
+      if ((*hist_spec) != '\0')
+      {
+        char* close_paren = dStrchr(hist_spec, ')');
+        if (close_paren)
+          (*close_paren) = '\0';
+        char* slash = dStrchr(hist_spec, '/');
+        if (slash)
+          (*slash) = ' ';
+
+        F32 hist_age = 0.0;
+        U32 hist_rate = 30;
+        S32 args = dSscanf(hist_spec,"%g %d", &hist_age, &hist_rate);
+
+        if (args > 0)
+          history_time = hist_age;
+        if (args > 1)
+          sample_rate = hist_rate;
+      }
+    }
+  }
+
+  StringTableEntry cons_name_key = StringTable->insert(words2[0]);
+
+  // must be in CONSTRAINT_NAME (only) form
+  if (n_words2 == 1)
+  {
+    // arbitrary object/effect constraints must have a name
+    if (cons_name_key == SCENE_CONS_KEY || cons_name_key == EFFECT_CONS_KEY)
+    {
+      dFree(buffer);
+      return false;
+    }
+
+    cons_src_name = cons_name_key;
+    def_type = CONS_PREDEFINED;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#scene.NAME" or "#scene.NAME.NODE""
+  if (cons_name_key == SCENE_CONS_KEY)
+  {
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_SCENE;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#effect.NAME" or "#effect.NAME.NODE"
+  if (cons_name_key == EFFECT_CONS_KEY)
+  {
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_EFFECT;
+    dFree(buffer);
+    return true;
+  }
+
+  // "#ghost.NAME" or "#ghost.NAME.NODE"
+  if (cons_name_key == GHOST_CONS_KEY)
+  {
+    if (runs_on_server)
+    {
+      dFree(buffer);
+      return false;
+    }
+
+    cons_src_name = StringTable->insert(words2[1]);
+    if (n_words2 > 2)
+      cons_node_name = StringTable->insert(words2[2]);
+    def_type = CONS_GHOST;
+    dFree(buffer);
+    return true;
+  }
+
+  // "CONSTRAINT_NAME.NODE"
+  if (n_words2 == 2)
+  {
+    cons_src_name = cons_name_key;
+    cons_node_name = StringTable->insert(words2[1]);
+    def_type = CONS_PREDEFINED;
+    dFree(buffer);
+    return true;
+  }
+
+  // must be in unsupported form
+  dFree(buffer); 
+  return false;
+}
+
+void afxConstraintDef::gather_cons_defs(Vector<afxConstraintDef>& defs, afxEffectList& fx)
+{
+  for (S32 i = 0; i <  fx.size(); i++)
+  {
+    if (fx[i])
+      fx[i]->gather_cons_defs(defs);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraint
+
+afxConstraint::afxConstraint(afxConstraintMgr* mgr)
+{
+  this->mgr = mgr;
+  is_defined = false;
+  is_valid = false;
+  last_pos.zero();
+  last_xfm.identity();
+  history_time = 0.0f;
+  is_alive = true;
+  gone_missing = false;
+  change_code = 0;
+}
+
+afxConstraint::~afxConstraint()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+inline afxPointConstraint* newPointCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxPointHistConstraint(mgr) : new afxPointConstraint(mgr);
+}
+
+inline afxTransformConstraint* newTransformCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxTransformHistConstraint(mgr) : new afxTransformConstraint(mgr);
+}
+
+inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxShapeHistConstraint(mgr) : new afxShapeConstraint(mgr);
+}
+
+inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist)
+{
+  return (hist) ? new afxShapeHistConstraint(mgr, name) : new afxShapeConstraint(mgr, name);
+}
+
+inline afxShapeNodeConstraint* newShapeNodeCons(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node, bool hist)
+{
+  return (hist) ? new afxShapeNodeHistConstraint(mgr, name, node) : new afxShapeNodeConstraint(mgr, name, node);
+}
+
+inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, bool hist)
+{
+  return (hist) ? new afxObjectHistConstraint(mgr) : new afxObjectConstraint(mgr);
+}
+
+inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist)
+{
+  return (hist) ? new afxObjectHistConstraint(mgr, name) : new afxObjectConstraint(mgr, name);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintMgr
+
+#define CONS_BY_ID(id) ((*constraints_v[(id).index])[(id).sub_index])
+#define CONS_BY_IJ(i,j) ((*constraints_v[(i)])[(j)])
+
+afxConstraintMgr::afxConstraintMgr()
+{
+  starttime = 0;
+  on_server = false;
+  initialized = false;
+  scoping_dist_sq = 1000.0f*1000.0f;
+  missing_objs = &missing_objs_a;
+  missing_objs2 = &missing_objs_b;
+}
+
+afxConstraintMgr::~afxConstraintMgr()
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    for (S32 j = 0; j < (*constraints_v[i]).size(); j++)
+      delete CONS_BY_IJ(i,j);
+    delete constraints_v[i];
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+S32 afxConstraintMgr::find_cons_idx_from_name(StringTableEntry which)
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(i,0);
+    if (cons && afxConstraintDef::CONS_EFFECT != cons->cons_def.def_type && 
+        which == cons->cons_def.cons_src_name)
+    {
+        return i;
+    }
+  }
+
+  return -1;
+}
+
+S32 afxConstraintMgr::find_effect_cons_idx_from_name(StringTableEntry which)
+{
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(i,0);
+    if (cons && afxConstraintDef::CONS_EFFECT == cons->cons_def.def_type && 
+        which == cons->cons_def.cons_src_name)
+    {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+// Defines a predefined constraint with given name and type
+void afxConstraintMgr::defineConstraint(U32 type, StringTableEntry name)
+{
+  preDef predef = { name, type };
+  predefs.push_back(predef);
+}
+
+afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point, 
+                                                    Point3F vector)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferencePoint(id, point, vector); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::setReferenceTransform(StringTableEntry which, MatrixF& xfm)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceTransform(id, xfm); 
+
+  return id;
+}
+
+// Assigns an existing scene-object to the named constraint
+afxConstraintID afxConstraintMgr::setReferenceObject(StringTableEntry which, SceneObject* obj)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceObject(id, obj); 
+
+  return id;
+}
+
+// Assigns an un-scoped scene-object by scope_id to the named constraint
+afxConstraintID afxConstraintMgr::setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape)
+{
+  S32 idx = find_cons_idx_from_name(which);
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceObjectByScopeId(id, scope_id, is_shape); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::setReferenceEffect(StringTableEntry which, afxEffectWrapper* ew)
+{
+  S32 idx = find_effect_cons_idx_from_name(which);
+
+  if (idx < 0)
+    return afxConstraintID();
+
+  afxConstraintID id = afxConstraintID(idx);
+  setReferenceEffect(id, ew); 
+
+  return id;
+}
+
+afxConstraintID afxConstraintMgr::createReferenceEffect(StringTableEntry which, afxEffectWrapper* ew)
+{
+  afxEffectConstraint* cons = new afxEffectConstraint(this, which);
+  //cons->cons_def = def;
+  cons->cons_def.def_type = afxConstraintDef::CONS_EFFECT;
+  cons->cons_def.cons_src_name = which;
+  afxConstraintList* list = new afxConstraintList();
+  list->push_back(cons);
+  constraints_v.push_back(list);
+
+  return setReferenceEffect(which, ew);
+}
+
+void afxConstraintMgr::setReferencePoint(afxConstraintID id, Point3F point, Point3F vector)
+{
+  afxPointConstraint* pt_cons = dynamic_cast<afxPointConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!pt_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    pt_cons = newPointCons(this, cons->cons_def.history_time > 0.0f);
+    pt_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = pt_cons;
+    delete cons;
+  }
+
+  pt_cons->set(point, vector);
+
+  // nullify all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->unset();
+  }
+}
+
+void afxConstraintMgr::setReferenceTransform(afxConstraintID id, MatrixF& xfm)
+{
+  afxTransformConstraint* xfm_cons = dynamic_cast<afxTransformConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!xfm_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    xfm_cons = newTransformCons(this, cons->cons_def.history_time > 0.0f);
+    xfm_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = xfm_cons;
+    delete cons;
+  }
+
+  xfm_cons->set(xfm);
+
+  // nullify all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->unset();
+  }
+}
+
+void afxConstraintMgr::set_ref_shape(afxConstraintID id, ShapeBase* shape)
+{
+  id.sub_index = 0;
+
+  afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!shape_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f);
+    shape_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = shape_cons;
+    delete cons;
+  }
+
+  // set new shape on root 
+  shape_cons->set(shape);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxShapeNodeConstraint*>(cons))
+        ((afxShapeNodeConstraint*)cons)->set(shape);
+      else if (dynamic_cast<afxShapeConstraint*>(cons))
+        ((afxShapeConstraint*)cons)->set(shape);
+      else if (dynamic_cast<afxObjectConstraint*>(cons))
+        ((afxObjectConstraint*)cons)->set(shape);
+      else
+        cons->unset();
+    }
+  }
+}
+
+void afxConstraintMgr::set_ref_shape(afxConstraintID id, U16 scope_id)
+{
+  id.sub_index = 0;
+
+  afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!shape_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f);
+    shape_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = shape_cons;
+    delete cons;
+  }
+
+  // set new shape on root 
+  shape_cons->set_scope_id(scope_id);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->set_scope_id(scope_id);
+  }
+}
+
+// Assigns an existing scene-object to the constraint matching the given constraint-id.
+void afxConstraintMgr::setReferenceObject(afxConstraintID id, SceneObject* obj)
+{
+  if (!initialized)
+    Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized");
+
+  if (!CONS_BY_ID(id)->cons_def.treat_as_camera)
+  {
+    ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+    if (shape)
+    {
+      set_ref_shape(id, shape);
+      return;
+    }
+  }
+
+  afxObjectConstraint* obj_cons = dynamic_cast<afxObjectConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!obj_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f);
+    obj_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = obj_cons;
+    delete cons;
+  }
+
+  obj_cons->set(obj);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxObjectConstraint*>(cons))
+          ((afxObjectConstraint*)cons)->set(obj);
+      else
+        cons->unset();
+    }
+  }
+}
+
+// Assigns an un-scoped scene-object by scope_id to the constraint matching the 
+// given constraint-id.
+void afxConstraintMgr::setReferenceObjectByScopeId(afxConstraintID id, U16 scope_id, bool is_shape)
+{
+  if (!initialized)
+    Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized");
+
+  if (is_shape)
+  {
+    set_ref_shape(id, scope_id);
+    return;
+  }
+
+  afxObjectConstraint* obj_cons = dynamic_cast<afxObjectConstraint*>(CONS_BY_ID(id));
+
+  // need to change type
+  if (!obj_cons)
+  {
+    afxConstraint* cons = CONS_BY_ID(id);
+    obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f);
+    obj_cons->cons_def = cons->cons_def;
+    CONS_BY_ID(id) = obj_cons;
+    delete cons;
+  }
+
+  obj_cons->set_scope_id(scope_id);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+      cons->set_scope_id(scope_id);
+  }
+}
+
+void afxConstraintMgr::setReferenceEffect(afxConstraintID id, afxEffectWrapper* ew)
+{
+  afxEffectConstraint* eff_cons = dynamic_cast<afxEffectConstraint*>(CONS_BY_ID(id));
+  if (!eff_cons)
+    return;
+
+  eff_cons->set(ew);
+
+  // update all subnodes
+  for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++)
+  {
+    afxConstraint* cons = CONS_BY_IJ(id.index,j);
+    if (cons)
+    {
+      if (dynamic_cast<afxEffectNodeConstraint*>(cons))
+        ((afxEffectNodeConstraint*)cons)->set(ew);
+      else if (dynamic_cast<afxEffectConstraint*>(cons))
+        ((afxEffectConstraint*)cons)->set(ew);
+      else
+        cons->unset();
+    }
+  }
+}
+
+void afxConstraintMgr::invalidateReference(afxConstraintID id)
+{
+  afxConstraint* cons = CONS_BY_ID(id);
+  if (cons)
+    cons->is_valid = false;
+}
+
+void afxConstraintMgr::create_constraint(const afxConstraintDef& def)
+{
+  if (def.def_type == afxConstraintDef::CONS_UNDEFINED)
+    return;
+
+  //Con::printf("CON - %s [%s] [%s] h=%g", def.cons_type_name, def.cons_src_name, def.cons_node_name, def.history_time);
+
+  bool want_history = (def.history_time > 0.0f);
+
+  // constraint is an arbitrary named scene object
+  //
+  if (def.def_type == afxConstraintDef::CONS_SCENE)
+  {
+    if (def.cons_src_name == ST_NULLSTRING)
+      return;
+
+    // find the arbitrary object by name
+    SceneObject* arb_obj;
+    if (on_server)
+    {
+      arb_obj = dynamic_cast<SceneObject*>(Sim::findObject(def.cons_src_name));
+      if (!arb_obj)
+         Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on server.", 
+                     def.cons_src_name);
+    }
+    else
+    {
+      arb_obj = find_object_from_name(def.cons_src_name);
+      if (!arb_obj)
+         Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on client.", 
+                     def.cons_src_name);
+    }
+
+    // if it's a shapeBase object, create a Shape or ShapeNode constraint
+    if (dynamic_cast<ShapeBase*>(arb_obj))
+    {
+      if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+      {
+        afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = new afxConstraintList();
+        list->push_back(cons);
+        constraints_v.push_back(list);
+      }
+      else if (def.pos_at_box_center)
+      {
+        afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1]; // SHAPE-NODE CONS-LIST (#scene)(#center)
+        if (list && (*list)[0])
+          list->push_back(cons);
+      }
+      else
+      {
+        afxShapeNodeConstraint* sub = newShapeNodeCons(this, def.cons_src_name, def.cons_node_name, want_history);
+        sub->cons_def = def;
+        sub->set((ShapeBase*)arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1];
+        if (list && (*list)[0])
+          list->push_back(sub);
+      }
+    }
+    // if it's not a shapeBase object, create an Object constraint
+    else if (arb_obj)
+    {
+      if (!def.pos_at_box_center)
+      {
+        afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set(arb_obj);
+        afxConstraintList* list = new afxConstraintList(); // OBJECT CONS-LIST (#scene)
+        list->push_back(cons);
+        constraints_v.push_back(list);
+      }
+      else // if (def.pos_at_box_center)
+      {
+        afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history);
+        cons->cons_def = def;
+        cons->set(arb_obj); 
+        afxConstraintList* list = constraints_v[constraints_v.size()-1]; // OBJECT CONS-LIST (#scene)(#center)
+        if (list && (*list)[0])
+          list->push_back(cons);
+      }
+    }
+  }
+
+  // constraint is an arbitrary named effect
+  //
+  else if (def.def_type == afxConstraintDef::CONS_EFFECT)
+  {
+    if (def.cons_src_name == ST_NULLSTRING)
+      return;
+
+    // create an Effect constraint
+    if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+    {
+      afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name);
+      cons->cons_def = def;
+      afxConstraintList* list = new afxConstraintList();
+      list->push_back(cons);
+      constraints_v.push_back(list);
+    }
+    // create an Effect #center constraint
+    else if (def.pos_at_box_center)
+    {
+      afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name);
+      cons->cons_def = def;
+      afxConstraintList* list = constraints_v[constraints_v.size()-1]; // EFFECT-NODE CONS-LIST (#effect)
+      if (list && (*list)[0])
+        list->push_back(cons);
+    }
+    // create an EffectNode constraint
+    else
+    {
+      afxEffectNodeConstraint* sub = new afxEffectNodeConstraint(this, def.cons_src_name, def.cons_node_name);
+      sub->cons_def = def;
+      afxConstraintList* list = constraints_v[constraints_v.size()-1];
+      if (list && (*list)[0])
+        list->push_back(sub);
+    }
+  }
+
+  // constraint is a predefined constraint
+  //
+  else
+  {
+    afxConstraint* cons = 0;
+    afxConstraint* cons_ctr = 0;
+    afxConstraint* sub = 0;
+
+    if (def.def_type == afxConstraintDef::CONS_GHOST)
+    {
+      for (S32 i = 0; i < predefs.size(); i++)
+      {
+        if (predefs[i].name == def.cons_src_name)
+        {
+          if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+          {
+            cons = newShapeCons(this, want_history);
+            cons->cons_def = def;
+          }
+          else if (def.pos_at_box_center)
+          {
+            cons_ctr = newShapeCons(this, want_history);
+            cons_ctr->cons_def = def;
+          }
+          else
+          {
+            sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+            sub->cons_def = def;
+          }
+          break;
+        }
+      }
+    }
+    else
+    {
+      for (S32 i = 0; i < predefs.size(); i++)
+      {
+        if (predefs[i].name == def.cons_src_name)
+        {
+          switch (predefs[i].type)
+          {
+          case POINT_CONSTRAINT:
+            cons = newPointCons(this, want_history);
+            cons->cons_def = def;
+            break;
+          case TRANSFORM_CONSTRAINT:
+            cons = newTransformCons(this, want_history);
+            cons->cons_def = def;
+            break;
+          case OBJECT_CONSTRAINT:
+            if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center)
+            {
+              cons = newShapeCons(this, want_history);
+              cons->cons_def = def;
+            }
+            else if (def.pos_at_box_center)
+            {
+              cons_ctr = newShapeCons(this, want_history);
+              cons_ctr->cons_def = def;
+            }
+            else
+            {
+              sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+              sub->cons_def = def;
+            }
+            break;
+          case CAMERA_CONSTRAINT:
+            cons = newObjectCons(this, want_history);
+            cons->cons_def = def;
+            cons->cons_def.treat_as_camera = true;
+            break;
+          }
+          break;
+        }
+      }
+    }
+
+    if (cons)
+    {
+      afxConstraintList* list = new afxConstraintList();
+      list->push_back(cons);
+      constraints_v.push_back(list);
+    }
+    else if (cons_ctr && constraints_v.size() > 0)
+    {
+      afxConstraintList* list = constraints_v[constraints_v.size()-1]; // PREDEF-NODE CONS-LIST
+      if (list && (*list)[0])
+        list->push_back(cons_ctr);
+    }
+    else if (sub && constraints_v.size() > 0)
+    {
+      afxConstraintList* list = constraints_v[constraints_v.size()-1];
+      if (list && (*list)[0])
+        list->push_back(sub);
+    }
+    else
+      Con::printf("predef not found %s", def.cons_src_name);
+  }
+}
+
+afxConstraintID afxConstraintMgr::getConstraintId(const afxConstraintDef& def)
+{
+  if (def.def_type == afxConstraintDef::CONS_UNDEFINED)
+    return afxConstraintID();
+
+  if (def.cons_src_name != ST_NULLSTRING)
+  {
+    for (S32 i = 0; i < constraints_v.size(); i++)
+    {
+      afxConstraintList* list = constraints_v[i];
+      afxConstraint* cons = (*list)[0];
+      if (def.cons_src_name == cons->cons_def.cons_src_name)
+      {
+        for (S32 j = 0; j < list->size(); j++)
+        {
+          afxConstraint* sub = (*list)[j];
+          if (def.cons_node_name == sub->cons_def.cons_node_name && 
+              def.pos_at_box_center == sub->cons_def.pos_at_box_center && 
+              def.cons_src_name == sub->cons_def.cons_src_name)
+          {
+            return afxConstraintID(i, j);
+          }
+        }
+
+        // if we're here, it means the root object name matched but the node name
+        // did not.
+        if (def.def_type == afxConstraintDef::CONS_PREDEFINED && !def.pos_at_box_center)
+        {
+          afxShapeConstraint* shape_cons = dynamic_cast<afxShapeConstraint*>(cons);
+          if (shape_cons)
+          {
+             //Con::errorf("Append a Node constraint [%s.%s] [%d,%d]", def.cons_src_name, def.cons_node_name, i, list->size());
+             bool want_history = (def.history_time > 0.0f);
+             afxConstraint* sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history);
+             sub->cons_def = def;
+             ((afxShapeConstraint*)sub)->set(shape_cons->shape);
+             list->push_back(sub);
+
+             return afxConstraintID(i, list->size()-1);
+          }
+        }
+
+        break;
+      }
+    }
+  }
+
+  return afxConstraintID();
+}
+
+afxConstraint* afxConstraintMgr::getConstraint(afxConstraintID id)
+{
+  if (id.undefined())
+    return 0;
+
+  afxConstraint* cons = CONS_BY_IJ(id.index,id.sub_index);
+  if (cons && !cons->isDefined())
+    return NULL;
+
+  return cons;
+}
+
+void afxConstraintMgr::sample(F32 dt, U32 now, const Point3F* cam_pos)
+{
+  U32 elapsed = now - starttime;
+
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraintList* list = constraints_v[i];
+    for (S32 j = 0; j < list->size(); j++)
+      (*list)[j]->sample(dt, elapsed, cam_pos);
+  }
+}
+
+S32 QSORT_CALLBACK cmp_cons_defs(const void* a, const void* b)
+{
+  afxConstraintDef* def_a = (afxConstraintDef*) a;
+  afxConstraintDef* def_b = (afxConstraintDef*) b;
+
+  if (def_a->def_type == def_b->def_type)
+  {
+    if (def_a->cons_src_name == def_b->cons_src_name)
+    {
+      if (def_a->pos_at_box_center == def_b->pos_at_box_center)
+        return (def_a->cons_node_name - def_b->cons_node_name);
+      else
+        return (def_a->pos_at_box_center) ? 1 : -1;
+    }
+    return (def_a->cons_src_name - def_b->cons_src_name);
+  }
+
+  return (def_a->def_type - def_b->def_type);
+}
+
+void afxConstraintMgr::initConstraintDefs(Vector<afxConstraintDef>& all_defs, bool on_server, F32 scoping_dist)
+{
+  initialized = true;
+  this->on_server = on_server;
+
+  if (scoping_dist > 0.0)
+    scoping_dist_sq = scoping_dist*scoping_dist;
+  else
+  {
+    SceneManager* sg = (on_server) ? gServerSceneGraph : gClientSceneGraph;
+    F32 vis_dist = (sg) ? sg->getVisibleDistance() : 1000.0f;
+    scoping_dist_sq = vis_dist*vis_dist;
+  }
+
+  if (all_defs.size() < 1)
+    return;
+
+  // find effect ghost constraints
+  if (!on_server)
+  {
+    Vector<afxConstraintDef> ghost_defs;
+
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].def_type == afxConstraintDef::CONS_GHOST && all_defs[i].cons_src_name != ST_NULLSTRING)
+        ghost_defs.push_back(all_defs[i]);
+    
+    if (ghost_defs.size() > 0)
+    {
+      // sort the defs
+      if (ghost_defs.size() > 1)
+        dQsort(ghost_defs.address(), ghost_defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+      
+      S32 last = 0;
+      defineConstraint(OBJECT_CONSTRAINT, ghost_defs[0].cons_src_name);
+
+      for (S32 i = 1; i < ghost_defs.size(); i++)
+      {
+        if (ghost_defs[last].cons_src_name != ghost_defs[i].cons_src_name)
+        {
+          defineConstraint(OBJECT_CONSTRAINT, ghost_defs[i].cons_src_name);
+          last++;
+        }
+      }
+    }
+  }
+
+  Vector<afxConstraintDef> defs;
+
+  // collect defs that run here (server or client)
+  if (on_server)
+  {
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_server)
+        defs.push_back(all_defs[i]);
+  }
+  else
+  {
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_client)
+        defs.push_back(all_defs[i]);
+  }
+
+  // create unique set of constraints.
+  //
+  if (defs.size() > 0)
+  {
+    // sort the defs
+    if (defs.size() > 1)
+      dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+    
+    Vector<afxConstraintDef> unique_defs;
+    S32 last = 0;
+    
+    // manufacture root-object def if absent
+    if (defs[0].cons_node_name != ST_NULLSTRING)
+    {
+      afxConstraintDef root_def = defs[0];
+      root_def.cons_node_name = ST_NULLSTRING;
+      unique_defs.push_back(root_def);
+      last++;
+    }
+    else if (defs[0].pos_at_box_center)
+    {
+      afxConstraintDef root_def = defs[0];
+      root_def.pos_at_box_center = false;
+      unique_defs.push_back(root_def);
+      last++;
+    }
+
+    unique_defs.push_back(defs[0]);
+    
+    for (S32 i = 1; i < defs.size(); i++)
+    {
+      if (unique_defs[last].cons_node_name != defs[i].cons_node_name ||
+          unique_defs[last].cons_src_name != defs[i].cons_src_name || 
+          unique_defs[last].pos_at_box_center != defs[i].pos_at_box_center || 
+          unique_defs[last].def_type != defs[i].def_type)
+      {
+        // manufacture root-object def if absent
+        if (defs[i].cons_src_name != ST_NULLSTRING && unique_defs[last].cons_src_name != defs[i].cons_src_name)
+        {
+          if (defs[i].cons_node_name != ST_NULLSTRING || defs[i].pos_at_box_center)
+          {
+            afxConstraintDef root_def = defs[i];
+            root_def.cons_node_name = ST_NULLSTRING;
+            root_def.pos_at_box_center = false;
+            unique_defs.push_back(root_def);
+            last++;
+          }
+        }
+        unique_defs.push_back(defs[i]);
+        last++;
+      }
+      else
+      {
+        if (defs[i].history_time > unique_defs[last].history_time)
+          unique_defs[last].history_time = defs[i].history_time;
+        if (defs[i].sample_rate > unique_defs[last].sample_rate)
+          unique_defs[last].sample_rate = defs[i].sample_rate;
+      }
+    }
+    
+    //Con::printf("\nConstraints on %s", (on_server) ? "server" : "client");
+    for (S32 i = 0; i < unique_defs.size(); i++)
+      create_constraint(unique_defs[i]);
+  }
+
+  // collect the names of all the arbitrary object constraints
+  // that run on clients and store in names_on_server array.
+  //
+  if (on_server)
+  {
+    names_on_server.clear();
+    defs.clear();
+
+    for (S32 i = 0; i < all_defs.size(); i++)
+      if (all_defs[i].runs_on_client && all_defs[i].isArbitraryObject())
+        defs.push_back(all_defs[i]);
+
+    if (defs.size() < 1)
+      return;
+
+    // sort the defs
+    if (defs.size() > 1)
+      dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs);
+
+    S32 last = 0;
+    names_on_server.push_back(defs[0].cons_src_name);
+
+    for (S32 i = 1; i < defs.size(); i++)
+    {
+      if (names_on_server[last] != defs[i].cons_src_name)
+      {
+        names_on_server.push_back(defs[i].cons_src_name);
+        last++;
+      }
+    }
+  }
+}
+
+void afxConstraintMgr::packConstraintNames(NetConnection* conn, BitStream* stream)
+{
+  // pack any named constraint names and ghost indices
+  if (stream->writeFlag(names_on_server.size() > 0)) //-- ANY NAMED CONS_BY_ID?
+  {
+    stream->write(names_on_server.size());
+    for (S32 i = 0; i < names_on_server.size(); i++)
+    {
+      stream->writeString(names_on_server[i]);
+      NetObject* obj = dynamic_cast<NetObject*>(Sim::findObject(names_on_server[i]));
+      if (!obj)
+      {
+        //Con::printf("CONSTRAINT-OBJECT %s does not exist.", names_on_server[i]);
+        stream->write((S32)-1);
+      }
+      else
+      {
+        S32 ghost_id = conn->getGhostIndex(obj);
+        /*
+        if (ghost_id == -1)
+          Con::printf("CONSTRAINT-OBJECT %s does not have a ghost.", names_on_server[i]);
+        else
+          Con::printf("CONSTRAINT-OBJECT %s name to server.", names_on_server[i]);
+         */
+        stream->write(ghost_id);
+      }
+    }
+  }
+}
+
+void afxConstraintMgr::unpackConstraintNames(BitStream* stream)
+{
+  if (stream->readFlag())                                         //-- ANY NAMED CONS_BY_ID?
+  {
+    names_on_server.clear();
+    S32 sz; stream->read(&sz);
+    for (S32 i = 0; i < sz; i++)
+    {
+      names_on_server.push_back(stream->readSTString());
+      S32 ghost_id; stream->read(&ghost_id);
+      ghost_ids.push_back(ghost_id);
+    }
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+SceneObject* afxConstraintMgr::find_object_from_name(StringTableEntry name)
+{
+  if (names_on_server.size() > 0)
+  {
+    for (S32 i = 0; i < names_on_server.size(); i++)
+      if (names_on_server[i] == name)
+      {
+        if (ghost_ids[i] == -1)
+          return 0;
+        NetConnection* conn = NetConnection::getConnectionToServer();
+        if (!conn)
+          return 0;
+        return dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_ids[i]));
+      }
+  }
+
+  return dynamic_cast<SceneObject*>(Sim::findObject(name));
+}
+
+void afxConstraintMgr::addScopeableObject(SceneObject* object)
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+  {
+    if (scopeable_objs[i] == object)
+      return;
+  }
+
+  object->addScopeRef();
+  scopeable_objs.push_back(object);
+}
+
+void afxConstraintMgr::removeScopeableObject(SceneObject* object)
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+    if (scopeable_objs[i] == object)
+    {
+      object->removeScopeRef();
+      scopeable_objs.erase_fast(i);
+      return;
+    }
+}
+
+void afxConstraintMgr::clearAllScopeableObjs()
+{
+  for (S32 i = 0; i < scopeable_objs.size(); i++)
+    scopeable_objs[i]->removeScopeRef();
+  scopeable_objs.clear();
+}
+
+void afxConstraintMgr::postMissingConstraintObject(afxConstraint* cons, bool is_deleting)
+{
+  if (cons->gone_missing)
+    return;
+
+  if (!is_deleting)
+  {
+    SceneObject* obj = arcaneFX::findScopedObject(cons->getScopeId());
+    if (obj)
+    {
+      cons->restoreObject(obj);
+      return;
+    }
+  }
+
+  cons->gone_missing = true;
+  missing_objs->push_back(cons);
+}
+
+void afxConstraintMgr::restoreScopedObject(SceneObject* obj, afxChoreographer* ch)
+{
+  for (S32 i = 0; i < missing_objs->size(); i++)
+  {
+    if ((*missing_objs)[i]->getScopeId() == obj->getScopeId())
+    {
+      (*missing_objs)[i]->gone_missing = false;
+      (*missing_objs)[i]->restoreObject(obj);
+      if (ch)
+        ch->restoreObject(obj);
+    }
+    else
+      missing_objs2->push_back((*missing_objs)[i]);
+  }
+
+  Vector<afxConstraint*>* tmp = missing_objs;
+  missing_objs = missing_objs2;
+  missing_objs2 = tmp;
+  missing_objs2->clear();
+}
+
+void afxConstraintMgr::adjustProcessOrdering(afxChoreographer* ch)
+{
+  Vector<ProcessObject*> cons_sources;
+
+  // add choreographer to the list
+  cons_sources.push_back(ch);
+
+  // collect all the ProcessObject related constraint sources
+  for (S32 i = 0; i < constraints_v.size(); i++)
+  {
+    afxConstraintList* list = constraints_v[i];
+    afxConstraint* cons = (*list)[0];
+    if (cons)
+    {
+      ProcessObject* pobj = dynamic_cast<ProcessObject*>(cons->getSceneObject());
+      if (pobj)
+        cons_sources.push_back(pobj);
+    }
+  }
+
+  ProcessList* proc_list;
+  if (ch->isServerObject())
+    proc_list = ServerProcessList::get();
+  else
+    proc_list = ClientProcessList::get();
+  if (!proc_list)
+    return;
+
+  GameBase* nearest_to_end = dynamic_cast<GameBase*>(proc_list->findNearestToEnd(cons_sources));
+  GameBase* chor = (GameBase*) ch;
+
+  // choreographer needs to be processed after the latest process object
+  if (chor != nearest_to_end && nearest_to_end != 0)
+  {
+    //Con::printf("Choreographer processing BEFORE some of its constraints... fixing. [%s] -- %s",
+    //   (ch->isServerObject()) ? "S" : "C", nearest_to_end->getClassName());
+    if (chor->isProperlyAdded())
+      ch->processAfter(nearest_to_end);
+    else
+      ch->postProcessAfterObject(nearest_to_end);
+  }
+  else
+  {
+    //Con::printf("Choreographer processing AFTER its constraints... fine. [%s]",
+    //   (ch->isServerObject()) ? "S" : "C");
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointConstraint
+
+afxPointConstraint::afxPointConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  point.zero();
+  vector.set(0,0,1);
+}
+
+afxPointConstraint::~afxPointConstraint()
+{
+}
+
+void afxPointConstraint::set(Point3F point, Point3F vector)
+{
+  this->point = point;
+  this->vector = vector;
+  is_defined = true;
+  is_valid = true;
+  change_code++;
+  sample(0.0f, 0, 0);
+}
+
+void afxPointConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (cam_pos)
+  {
+    Point3F dir = (*cam_pos) - point;
+    F32 dist_sq = dir.lenSquared();
+    if (dist_sq > mgr->getScopingDistanceSquared())
+    {
+      is_valid = false;
+      return;
+    }
+    is_valid = true;
+  }
+
+  last_pos = point;
+  last_xfm.identity();
+  last_xfm.setPosition(point);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxTransformConstraint
+
+afxTransformConstraint::afxTransformConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  xfm.identity();
+}
+
+afxTransformConstraint::~afxTransformConstraint()
+{
+}
+
+void afxTransformConstraint::set(const MatrixF& xfm)
+{
+  this->xfm = xfm;
+  is_defined = true;
+  is_valid = true;
+  change_code++;
+  sample(0.0f, 0, 0);
+}
+
+void afxTransformConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (cam_pos)
+  {
+    Point3F dir = (*cam_pos) - xfm.getPosition();
+    F32 dist_sq = dir.lenSquared();
+    if (dist_sq > mgr->getScopingDistanceSquared())
+    {
+      is_valid = false;
+      return;
+    }
+    is_valid = true;
+  }
+
+  last_xfm = xfm;
+  last_pos = xfm.getPosition();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeConstraint
+
+afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  arb_name = ST_NULLSTRING;
+  shape = 0;
+  scope_id = 0;
+  clip_tag = 0;
+  lock_tag = 0;
+}
+
+afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) 
+  : afxConstraint(mgr)
+{
+  this->arb_name = arb_name;
+  shape = 0;
+  scope_id = 0;
+  clip_tag = 0;
+  lock_tag = 0;
+}
+
+afxShapeConstraint::~afxShapeConstraint()
+{
+  if (shape)
+    clearNotify(shape);
+}
+
+void afxShapeConstraint::set(ShapeBase* shape)
+{
+  if (this->shape)
+  {
+    scope_id = 0;
+    clearNotify(this->shape);
+    if (clip_tag > 0)
+      remapAnimation(clip_tag, shape);
+    if (lock_tag > 0)
+      unlockAnimation(lock_tag);
+  }
+
+  this->shape = shape;
+
+  if (this->shape)
+  {
+    deleteNotify(this->shape);
+    scope_id = this->shape->getScopeId();
+  }
+
+  if (this->shape != NULL)
+  {
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+    sample(0.0f, 0, 0);
+  }
+  else
+    is_valid = false;
+}
+
+void afxShapeConstraint::set_scope_id(U16 scope_id)
+{
+  if (shape)
+    clearNotify(shape);
+
+  shape = 0;
+  this->scope_id = scope_id;
+
+  is_defined = (this->scope_id > 0);
+  is_valid = false;
+  mgr->postMissingConstraintObject(this);
+}
+
+void afxShapeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (gone_missing)
+    return;
+
+  if (shape)
+  {
+    last_xfm = shape->getRenderTransform();
+    if (cons_def.pos_at_box_center)
+      last_pos = shape->getBoxCenter();
+    else
+      last_pos = shape->getRenderPosition();
+  }
+}
+
+void afxShapeConstraint::restoreObject(SceneObject* obj) 
+{ 
+  if (this->shape)
+  {
+    scope_id = 0;
+    clearNotify(this->shape);
+  }
+
+  this->shape = (ShapeBase* )obj;
+
+  if (this->shape)
+  {
+    deleteNotify(this->shape);
+    scope_id = this->shape->getScopeId();
+  }
+
+  is_valid = (this->shape != NULL);
+}
+
+void afxShapeConstraint::onDeleteNotify(SimObject* obj)
+{
+  if (shape == dynamic_cast<ShapeBase*>(obj))
+  {
+    shape = 0;
+    is_valid = false;
+    if (scope_id > 0)
+      mgr->postMissingConstraintObject(this, true);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+U32 afxShapeConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim)
+{
+  if (!shape)
+    return 0;
+
+  if (shape->isServerObject())
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player && !ai_player->isBlendAnimation(clip))
+    {
+      ai_player->saveMoveState();
+      ai_player->stopMove();
+    }
+  }
+
+  clip_tag = shape->playAnimation(clip, pos, rate, trans, false/*hold*/, true/*wait*/, is_death_anim);
+  return clip_tag;
+}
+
+void afxShapeConstraint::remapAnimation(U32 tag, ShapeBase* other_shape)
+{
+  if (clip_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+
+  if (!other_shape)
+  {
+    resetAnimation(tag);
+    return;
+  }
+
+  Con::errorf("remapAnimation -- Clip name, %s.", shape->getLastClipName(tag));
+
+  if (shape->isClientObject())
+  {
+    shape->restoreAnimation(tag);
+  }
+  else
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player)
+      ai_player->restartMove(tag);
+    else
+      shape->restoreAnimation(tag);
+  }
+
+  clip_tag = 0;
+}
+
+void afxShapeConstraint::resetAnimation(U32 tag)
+{
+  if (clip_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+  
+  if (shape->isClientObject())
+  {
+    shape->restoreAnimation(tag);
+  }
+  else
+  {
+    AIPlayer* ai_player = dynamic_cast<AIPlayer*>(shape);
+    if (ai_player)
+      ai_player->restartMove(tag);
+    else
+      shape->restoreAnimation(tag);
+  }
+
+  if ((tag & 0x80000000) == 0 && tag == clip_tag)
+    clip_tag = 0;
+}
+
+U32 afxShapeConstraint::lockAnimation()
+{
+  if (!shape)
+    return 0;
+
+  lock_tag = shape->lockAnimation();
+  return lock_tag;
+}
+
+void afxShapeConstraint::unlockAnimation(U32 tag)
+{
+  if (lock_tag == 0)
+    return;
+
+  if (!shape)
+    return;
+  
+  shape->unlockAnimation(tag);
+  lock_tag = 0;
+}
+
+F32 afxShapeConstraint::getAnimClipDuration(const char* clip)
+{
+  return (shape) ? shape->getAnimationDuration(clip) : 0.0f;
+}
+
+S32 afxShapeConstraint::getDamageState()
+{
+  return (shape) ? shape->getDamageState() : -1;
+}
+
+U32 afxShapeConstraint::getTriggers()
+{
+  return (shape) ? shape->getShapeInstance()->getTriggerStateMask() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeConstraint
+
+afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr)  
+  : afxShapeConstraint(mgr)
+{
+  arb_node = ST_NULLSTRING;
+  shape_node_ID = -1;
+}
+
+afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name, StringTableEntry arb_node)
+  : afxShapeConstraint(mgr, arb_name)
+{
+  this->arb_node = arb_node;
+  shape_node_ID = -1;
+}
+
+void afxShapeNodeConstraint::set(ShapeBase* shape)
+{
+  if (shape)
+  {
+    shape_node_ID = shape->getShape()->findNode(arb_node);
+    if (shape_node_ID == -1)
+      Con::errorf("Failed to find node [%s]", arb_node);
+  }
+  else
+    shape_node_ID = -1;
+
+  Parent::set(shape);
+}
+
+void afxShapeNodeConstraint::set_scope_id(U16 scope_id)
+{
+  shape_node_ID = -1;
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeNodeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (shape && shape_node_ID != -1)
+  {
+    last_xfm = shape->getRenderTransform();
+    last_xfm.scale(shape->getScale());
+    last_xfm.mul(shape->getShapeInstance()->mNodeTransforms[shape_node_ID]);
+    last_pos = last_xfm.getPosition();
+  }
+}
+
+void afxShapeNodeConstraint::restoreObject(SceneObject* obj) 
+{ 
+  ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+  if (shape)
+  {
+    shape_node_ID = shape->getShape()->findNode(arb_node);
+    if (shape_node_ID == -1)
+      Con::errorf("Failed to find node [%s]", arb_node);
+  }
+  else
+    shape_node_ID = -1;
+  Parent::restoreObject(obj);
+}
+
+void afxShapeNodeConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectConstraint
+
+afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  arb_name = ST_NULLSTRING;
+  obj = 0;
+  scope_id = 0;
+  is_camera = false;
+}
+
+afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) 
+  : afxConstraint(mgr)
+{
+  this->arb_name = arb_name;
+  obj = 0;
+  scope_id = 0;
+  is_camera = false;
+}
+
+afxObjectConstraint::~afxObjectConstraint()
+{
+  if (obj)
+    clearNotify(obj);
+}
+
+void afxObjectConstraint::set(SceneObject* obj)
+{
+  if (this->obj)
+  {
+    scope_id = 0;
+    clearNotify(this->obj);
+  }
+
+  this->obj = obj;
+
+  if (this->obj)
+  {
+    deleteNotify(this->obj);
+    scope_id = this->obj->getScopeId();
+  }
+
+  if (this->obj != NULL)
+  {
+    is_camera = this->obj->isCamera();
+
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+    sample(0.0f, 0, 0);
+  }
+  else
+    is_valid = false;
+}
+
+void afxObjectConstraint::set_scope_id(U16 scope_id)
+{
+  if (obj)
+    clearNotify(obj);
+
+  obj = 0;
+  this->scope_id = scope_id;
+
+  is_defined = (scope_id > 0);
+  is_valid = false;
+  mgr->postMissingConstraintObject(this);
+}
+
+void afxObjectConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  if (gone_missing)
+    return;
+
+  if (obj)
+  {
+    if (!is_camera && cons_def.treat_as_camera && dynamic_cast<ShapeBase*>(obj))
+    {
+      ShapeBase* cam_obj = (ShapeBase*) obj;
+      F32 pov = 1.0f;
+      cam_obj->getCameraTransform(&pov, &last_xfm);
+      last_xfm.getColumn(3, &last_pos);
+    }
+    else
+    {
+      last_xfm = obj->getRenderTransform();
+      if (cons_def.pos_at_box_center)
+        last_pos = obj->getBoxCenter();
+      else
+        last_pos = obj->getRenderPosition();
+    }
+  }
+}
+
+void afxObjectConstraint::restoreObject(SceneObject* obj)
+{
+  if (this->obj)
+  {
+    scope_id = 0;
+    clearNotify(this->obj);
+  }
+
+  this->obj = obj;
+
+  if (this->obj)
+  {
+    deleteNotify(this->obj);
+    scope_id = this->obj->getScopeId();
+  }
+
+  is_valid = (this->obj != NULL);
+}
+
+void afxObjectConstraint::onDeleteNotify(SimObject* obj)
+{
+  if (this->obj == dynamic_cast<SceneObject*>(obj))
+  {
+    this->obj = 0;
+    is_valid = false;
+    if (scope_id > 0)
+      mgr->postMissingConstraintObject(this, true);
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+U32 afxObjectConstraint::getTriggers()
+{
+  TSStatic* ts_static = dynamic_cast<TSStatic*>(obj);
+  if (ts_static)
+  {
+    TSShapeInstance* obj_inst = ts_static->getShapeInstance();
+    return (obj_inst) ? obj_inst->getTriggerStateMask() : 0;
+  }
+
+  ShapeBase* shape_base = dynamic_cast<ShapeBase*>(obj);
+  if (shape_base)
+  {
+    TSShapeInstance* obj_inst = shape_base->getShapeInstance();
+    return (obj_inst) ? obj_inst->getTriggerStateMask() : 0;
+  }
+
+  return 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectConstraint
+
+afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr) 
+  : afxConstraint(mgr)
+{
+  effect_name = ST_NULLSTRING;
+  effect = 0;
+}
+
+afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr, StringTableEntry effect_name) 
+  : afxConstraint(mgr)
+{
+  this->effect_name = effect_name;
+  effect = 0;
+}
+
+afxEffectConstraint::~afxEffectConstraint()
+{
+}
+
+bool afxEffectConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+ 
+  if (cons_def.pos_at_box_center)
+    effect->getUpdatedBoxCenter(pos);
+  else
+    effect->getUpdatedPosition(pos);
+  
+  return true;
+}
+
+bool afxEffectConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  effect->getUpdatedTransform(xfm);
+  return true;
+}
+
+bool afxEffectConstraint::getAltitudes(F32& terrain_alt, F32& interior_alt) 
+{ 
+  if (!effect)
+    return false;
+  
+  effect->getAltitudes(terrain_alt, interior_alt);
+  return true;
+}
+
+void afxEffectConstraint::set(afxEffectWrapper* effect)
+{
+  this->effect = effect;
+
+  if (this->effect != NULL)
+  {
+    is_defined = true;
+    is_valid = true;
+    change_code++;
+  }
+  else
+    is_valid = false;
+}
+
+U32 afxEffectConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim)
+{
+  return (effect) ? effect->setAnimClip(clip, pos, rate, trans) : 0;
+}
+
+void afxEffectConstraint::resetAnimation(U32 tag)
+{
+  if (effect)
+    effect->resetAnimation(tag);
+}
+
+F32 afxEffectConstraint::getAnimClipDuration(const char* clip)
+{
+  return (effect) ? getAnimClipDuration(clip) : 0;
+}
+
+U32 afxEffectConstraint::getTriggers()
+{
+  return (effect) ? effect->getTriggers() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectNodeConstraint
+
+afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr) 
+  : afxEffectConstraint(mgr)
+{
+  effect_node = ST_NULLSTRING;
+  effect_node_ID = -1;
+}
+
+afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node)
+: afxEffectConstraint(mgr, name)
+{
+  this->effect_node = node;
+  effect_node_ID = -1;
+}
+
+
+
+bool afxEffectNodeConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance();
+  if (!ts_shape_inst)
+    return false;
+
+  if (effect_node_ID == -1)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+
+  if (effect_node_ID == -1)
+    return false;
+
+  effect->getUpdatedTransform(last_xfm);
+
+  Point3F scale;
+  effect->getUpdatedScale(scale);
+
+  MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID];
+  gag.setPosition( gag.getPosition()*scale );
+
+  MatrixF xfm;
+  xfm.mul(last_xfm, gag);
+  //
+  pos = xfm.getPosition();
+
+  return true;
+}
+
+bool afxEffectNodeConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  if (!effect || !effect->inScope())
+    return false;
+  
+  TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance();
+  if (!ts_shape_inst)
+    return false;
+
+  if (effect_node_ID == -1)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+
+  if (effect_node_ID == -1)
+    return false;
+
+  effect->getUpdatedTransform(last_xfm);
+
+  Point3F scale;
+  effect->getUpdatedScale(scale);
+
+  MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID];
+  gag.setPosition( gag.getPosition()*scale );
+
+  xfm.mul(last_xfm, gag);
+
+  return true;
+}
+
+void afxEffectNodeConstraint::set(afxEffectWrapper* effect)
+{
+  if (effect)
+  {
+    TSShape* ts_shape = effect->getTSShape();
+    effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1;
+  }
+  else
+    effect_node_ID = -1;
+
+  Parent::set(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleBuffer
+
+afxSampleBuffer::afxSampleBuffer()
+{
+  buffer_sz = 0;
+  buffer_ms = 0;
+  ms_per_sample = 33;
+  elapsed_ms = 0;
+  last_sample_ms = 0;
+  next_sample_num = 0;
+  n_samples = 0;
+}
+
+afxSampleBuffer::~afxSampleBuffer()
+{
+}
+
+void afxSampleBuffer::configHistory(F32 hist_len, U8 sample_rate)
+{
+  buffer_sz = mCeil(hist_len*sample_rate) + 1;
+  ms_per_sample = mCeil(1000.0f/sample_rate);
+  buffer_ms = buffer_sz*ms_per_sample;
+}
+
+void afxSampleBuffer::recordSample(F32 dt, U32 elapsed_ms, void* data)
+{
+  this->elapsed_ms = elapsed_ms;
+
+  if (!data)
+    return;
+
+  U32 now_sample_num = elapsed_ms/ms_per_sample;
+  if (next_sample_num <= now_sample_num)
+  {
+    last_sample_ms = elapsed_ms;
+    while (next_sample_num <= now_sample_num)
+    {
+      recSample(next_sample_num % buffer_sz, data);
+      next_sample_num++;
+      n_samples++;
+    }
+  }
+}
+
+inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx) 
+{ 
+  bool in_bounds = true;
+
+  U32 lag_ms = lag*1000.0f;
+  U32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms;
+  if (lag_ms > rec_ms)
+  {
+    // hasn't produced enough history
+    lag_ms = rec_ms;
+    in_bounds = false;
+  }
+
+  U32 latest_sample_num = last_sample_ms/ms_per_sample;
+  U32 then_sample_num = (elapsed_ms - lag_ms)/ms_per_sample;
+
+  if (then_sample_num > latest_sample_num)
+  {
+    // latest sample is older than lag
+    then_sample_num = latest_sample_num;
+    in_bounds = false;
+  }
+
+  idx = then_sample_num % buffer_sz;
+  return in_bounds;
+}
+
+inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t) 
+{ 
+  bool in_bounds = true;
+
+  F32 lag_ms = lag*1000.0f;
+  F32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms;
+  if (lag_ms > rec_ms)
+  {
+    // hasn't produced enough history
+    lag_ms = rec_ms;
+    in_bounds = false;
+  }
+
+  F32 per_samp = ms_per_sample;
+  F32 latest_sample_num = last_sample_ms/per_samp;
+  F32 then_sample_num = (elapsed_ms - lag_ms)/per_samp;
+
+  U32 latest_sample_num_i = latest_sample_num;
+  U32 then_sample_num_i = then_sample_num;
+
+  if (then_sample_num_i >= latest_sample_num_i)
+  {
+    if (latest_sample_num_i < then_sample_num_i)
+      in_bounds = false;
+    t = 0.0;
+    idx1 = then_sample_num_i % buffer_sz;
+    idx2 = idx1;
+  }
+  else
+  {
+    t = then_sample_num - then_sample_num_i;
+    idx1 = then_sample_num_i % buffer_sz;
+    idx2 = (then_sample_num_i+1) % buffer_sz;
+  }
+
+  return in_bounds;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleXfmBuffer
+
+afxSampleXfmBuffer::afxSampleXfmBuffer()
+{
+  xfm_buffer = 0;
+}
+
+afxSampleXfmBuffer::~afxSampleXfmBuffer()
+{
+  delete [] xfm_buffer;
+}
+
+void afxSampleXfmBuffer::configHistory(F32 hist_len, U8 sample_rate)
+{
+  if (!xfm_buffer)
+  {
+    afxSampleBuffer::configHistory(hist_len, sample_rate);
+    if (buffer_sz > 0)
+      xfm_buffer = new MatrixF[buffer_sz];
+  }  
+}
+
+void afxSampleXfmBuffer::recSample(U32 idx, void* data)
+{
+  xfm_buffer[idx] = *((MatrixF*)data);
+}
+
+void afxSampleXfmBuffer::getSample(F32 lag, void* data, bool& in_bounds) 
+{ 
+  U32 idx1, idx2;
+  F32 t;
+  in_bounds = compute_idx_from_lag(lag, idx1, idx2, t);
+
+  if (idx1 == idx2)
+  {
+    MatrixF* m1 = &xfm_buffer[idx1];
+    *((MatrixF*)data) = *m1;
+  }
+  else
+  {
+    MatrixF* m1 = &xfm_buffer[idx1];
+    MatrixF* m2 = &xfm_buffer[idx2];
+
+    Point3F p1 = m1->getPosition();
+    Point3F p2 = m2->getPosition();
+    Point3F p; p.interpolate(p1, p2, t);
+
+    if (t < 0.5f)
+      *((MatrixF*)data) = *m1;
+    else
+      *((MatrixF*)data) = *m2;
+
+    ((MatrixF*)data)->setPosition(p);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+
+afxPointHistConstraint::afxPointHistConstraint(afxConstraintMgr* mgr)
+  : afxPointConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxPointHistConstraint::~afxPointHistConstraint()
+{
+  delete samples;
+}
+
+void afxPointHistConstraint::set(Point3F point, Point3F vector)
+{
+  if (!samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(point, vector);
+}
+
+void afxPointHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxPointHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxPointHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+
+afxTransformHistConstraint::afxTransformHistConstraint(afxConstraintMgr* mgr)
+  : afxTransformConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxTransformHistConstraint::~afxTransformHistConstraint()
+{
+  delete samples;
+}
+
+void afxTransformHistConstraint::set(const MatrixF& xfm)
+{
+  if (!samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(xfm);
+}
+
+void afxTransformHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxTransformHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxTransformHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeHistConstraint
+
+afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr)
+  : afxShapeConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name)
+  : afxShapeConstraint(mgr, arb_name)
+{
+  samples = 0;
+}
+
+afxShapeHistConstraint::~afxShapeHistConstraint()
+{
+  delete samples;
+}
+
+void afxShapeHistConstraint::set(ShapeBase* shape)
+{
+  if (shape && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(shape);
+}
+
+void afxShapeHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxShapeHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxShapeHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxShapeHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeHistConstraint
+
+afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr)
+  : afxShapeNodeConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name,
+                                                       StringTableEntry arb_node)
+  : afxShapeNodeConstraint(mgr, arb_name, arb_node)
+{
+  samples = 0;
+}
+
+afxShapeNodeHistConstraint::~afxShapeNodeHistConstraint()
+{
+  delete samples;
+}
+
+void afxShapeNodeHistConstraint::set(ShapeBase* shape)
+{
+  if (shape && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(shape);
+}
+
+void afxShapeNodeHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxShapeNodeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxShapeNodeHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxShapeNodeHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxShapeNodeHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectHistConstraint
+
+afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr)
+  : afxObjectConstraint(mgr)
+{
+  samples = 0;
+}
+
+afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name)
+  : afxObjectConstraint(mgr, arb_name)
+{
+  samples = 0;
+}
+
+afxObjectHistConstraint::~afxObjectHistConstraint()
+{
+  delete samples;
+}
+
+void afxObjectHistConstraint::set(SceneObject* obj)
+{
+  if (obj && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set(obj);
+}
+
+void afxObjectHistConstraint::set_scope_id(U16 scope_id)
+{
+  if (scope_id > 0 && !samples)
+  {
+    samples = new afxSampleXfmBuffer;
+    samples->configHistory(cons_def.history_time, cons_def.sample_rate);
+  }
+  
+  Parent::set_scope_id(scope_id);
+}
+
+void afxObjectHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)
+{
+  Parent::sample(dt, elapsed_ms, cam_pos);
+
+  if (isDefined())
+  {
+    if (isValid())
+      samples->recordSample(dt, elapsed_ms, &last_xfm);
+    else
+      samples->recordSample(dt, elapsed_ms, 0);
+  }
+}
+
+bool afxObjectHistConstraint::getPosition(Point3F& pos, F32 hist) 
+{ 
+  bool in_bounds;
+
+  MatrixF xfm;
+  samples->getSample(hist, &xfm, in_bounds);
+
+  pos = xfm.getPosition();
+
+  return in_bounds;
+}
+
+bool afxObjectHistConstraint::getTransform(MatrixF& xfm, F32 hist) 
+{ 
+  bool in_bounds;
+
+  samples->getSample(hist, &xfm, in_bounds);
+
+  return in_bounds; 
+}
+
+void afxObjectHistConstraint::onDeleteNotify(SimObject* obj)
+{
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 677 - 0
Engine/source/afx/afxConstraint.h

@@ -0,0 +1,677 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CONSTRAINT_H_
+#define _AFX_CONSTRAINT_H_
+
+#include "core/util/tVector.h"
+#include "T3D/shapeBase.h"
+
+#include "afxEffectDefs.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintDef
+
+class afxEffectBaseData;
+
+struct afxConstraintDef : public afxEffectDefs
+{
+  enum DefType
+  {
+    CONS_UNDEFINED,
+    CONS_PREDEFINED,
+    CONS_SCENE,
+    CONS_EFFECT,
+    CONS_GHOST
+  };
+
+  DefType           def_type;
+
+  StringTableEntry  cons_src_name;
+  StringTableEntry  cons_node_name;
+  F32               history_time;
+  U8                sample_rate;
+
+  bool              runs_on_server;
+  bool              runs_on_client;
+  bool              pos_at_box_center;
+  bool              treat_as_camera;
+
+  /*C*/             afxConstraintDef();
+
+  bool              isDefined();
+
+  bool              isArbitraryObject();
+  void              reset();
+  bool              parseSpec(const char* spec, bool runs_on_server, bool runs_on_client);
+
+  static void       gather_cons_defs(Vector<afxConstraintDef>& defs, Vector<afxEffectBaseData*>& fx);
+
+  static StringTableEntry  SCENE_CONS_KEY;
+  static StringTableEntry  EFFECT_CONS_KEY;
+  static StringTableEntry  GHOST_CONS_KEY;
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraint
+//  Abstract base-class for a simple constraint mechanism used to constrain
+//  special effects to spell related objects such as the spellcaster, target,
+//  projectile, or impact location.
+//
+//  note -- the direction vectors don't really fit... should probably consider separate
+//    constraint types for position, orientation, and possibly a look-at constraint.
+//
+
+class SceneObject;
+class afxConstraintMgr;
+  
+class afxConstraint : public SimObject, public afxEffectDefs
+{
+  friend class afxConstraintMgr;
+  typedef SimObject Parent;
+
+protected:
+  afxConstraintMgr* mgr;
+  afxConstraintDef  cons_def;
+  bool              is_defined;
+  bool              is_valid;
+  Point3F           last_pos;
+  MatrixF           last_xfm;
+  F32               history_time;
+  bool              is_alive;
+  bool              gone_missing;
+  U32               change_code;
+
+public:
+  /*C*/             afxConstraint(afxConstraintMgr*);
+  virtual           ~afxConstraint();
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f) 
+                      { pos = last_pos; return is_valid; }
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f) 
+                      { xfm = last_xfm; return is_valid;}
+  virtual bool      getAltitudes(F32& terrain_alt, F32& interior_alt) { return false; }
+
+  virtual bool      isDefined() { return is_defined; }
+  virtual bool      isValid() { return is_valid; }
+  virtual U32       getChangeCode() { return change_code; }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim) 
+                                  { return 0; };
+  virtual void      resetAnimation(U32 tag) { };
+  virtual U32       lockAnimation() { return 0; }
+  virtual void      unlockAnimation(U32 tag) { }
+  virtual F32       getAnimClipDuration(const char* clip) { return 0.0f; }
+
+  virtual S32       getDamageState() { return -1; }
+  virtual void      setLivingState(bool state) { is_alive = state; };
+  virtual bool      getLivingState() { return is_alive; };
+
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)=0;
+
+  virtual SceneObject* getSceneObject()=0;
+  virtual void      restoreObject(SceneObject*)=0;
+  virtual U16       getScopeId()=0;
+
+  virtual U32       getTriggers()=0;
+
+  virtual void      set_scope_id(U16 scope_id) { }
+  virtual void      unset() { }
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConstraintMgr
+
+class ShapeBase;
+class afxEffectWrapper;
+class afxShapeConstraint;
+class afxObjectConstraint;
+class BitStream;
+class NetConnection;
+
+struct afxConstraintID
+{
+  S16 index;
+  S16 sub_index;
+
+  afxConstraintID() { index = -1; sub_index = 0; }
+  afxConstraintID(S16 idx, S16 sub=0) { index = idx; sub_index = sub; }
+  bool undefined() const { return (index < 0); }
+};
+ 
+typedef Vector<afxConstraint*> afxConstraintList;
+
+class afxConstraintMgr : public afxEffectDefs
+{
+  typedef SimObject Parent;
+
+  struct preDef
+  {
+    StringTableEntry  name;
+    U32               type;
+  };
+
+  Vector<afxConstraintList*>  constraints_v;
+
+  Vector<StringTableEntry>  names_on_server;
+  Vector<S32>               ghost_ids;
+  Vector<preDef>            predefs;
+  U32                       starttime;
+  bool                      on_server;
+  bool                      initialized;
+  F32                       scoping_dist_sq;
+
+  SceneObject*        find_object_from_name(StringTableEntry);
+  S32                 find_cons_idx_from_name(StringTableEntry);
+  S32                 find_effect_cons_idx_from_name(StringTableEntry);
+
+  void                create_constraint(const afxConstraintDef&);    
+  void                set_ref_shape(afxConstraintID which_id, ShapeBase*);
+  void                set_ref_shape(afxConstraintID which_id, U16 scope_id);
+
+public:
+  /*C*/               afxConstraintMgr();
+  /*D*/               ~afxConstraintMgr();
+
+  void                defineConstraint(U32 type, StringTableEntry);
+
+  afxConstraintID     setReferencePoint(StringTableEntry which, Point3F point);
+  afxConstraintID     setReferencePoint(StringTableEntry which, Point3F point, Point3F vector);
+  afxConstraintID     setReferenceTransform(StringTableEntry which, MatrixF& xfm);
+  afxConstraintID     setReferenceObject(StringTableEntry which, SceneObject*);
+  afxConstraintID     setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape);
+  afxConstraintID     setReferenceEffect(StringTableEntry which, afxEffectWrapper*);
+  afxConstraintID     createReferenceEffect(StringTableEntry which, afxEffectWrapper*);
+
+  void                setReferencePoint(afxConstraintID which_id, Point3F point);
+  void                setReferencePoint(afxConstraintID which_id, Point3F point, Point3F vector);
+  void                setReferenceTransform(afxConstraintID which_id, MatrixF& xfm);
+  void                setReferenceObject(afxConstraintID which_id, SceneObject*);
+  void                setReferenceObjectByScopeId(afxConstraintID which_id, U16 scope_id, bool is_shape);
+  void                setReferenceEffect(afxConstraintID which_id, afxEffectWrapper*);
+
+  void                invalidateReference(afxConstraintID which_id);
+                      
+  afxConstraintID     getConstraintId(const afxConstraintDef&);
+  afxConstraint*      getConstraint(afxConstraintID cons_id);
+
+  void                sample(F32 dt, U32 now, const Point3F* cam_pos=0);
+
+  void                setStartTime(U32 timestamp) { starttime = timestamp; }
+  void                initConstraintDefs(Vector<afxConstraintDef>&, bool on_server, F32 scoping_dist=-1.0f); 
+  void                packConstraintNames(NetConnection* conn, BitStream* stream);
+  void                unpackConstraintNames(BitStream* stream);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// scope-tracking
+private:
+  Vector<SceneObject*>  scopeable_objs;
+  Vector<U16>           scopeable_ids;
+  Vector<afxConstraint*>* missing_objs;
+  Vector<afxConstraint*>* missing_objs2;
+  Vector<afxConstraint*> missing_objs_a;
+  Vector<afxConstraint*> missing_objs_b;
+
+public:
+  void                  addScopeableObject(SceneObject*);
+  void                  removeScopeableObject(SceneObject*);
+  void                  clearAllScopeableObjs();
+
+  void                  postMissingConstraintObject(afxConstraint*, bool is_deleting=false);
+  void                  restoreScopedObject(SceneObject*, afxChoreographer* ch);
+  void                  adjustProcessOrdering(afxChoreographer*);
+
+  F32                   getScopingDistanceSquared() const { return scoping_dist_sq; }
+};
+
+inline afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point)
+{
+  return setReferencePoint(which, point, Point3F(0,0,1));
+}
+
+inline void afxConstraintMgr::setReferencePoint(afxConstraintID which, Point3F point) 
+{
+  setReferencePoint(which, point, Point3F(0,0,1));
+}
+
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointConstraint
+//  This constrains to a specific 3D position such as an impact location.
+//
+
+class afxPointConstraint : public afxConstraint
+{
+  typedef afxConstraint  Parent;
+
+protected:
+  Point3F           point;
+  Point3F           vector;
+
+public:
+  /*C*/             afxPointConstraint(afxConstraintMgr*);
+  virtual           ~afxPointConstraint();
+
+  virtual void      set(Point3F point, Point3F vector);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers() { return 0; }
+
+  virtual void      unset() { set(Point3F::Zero, Point3F(0,0,1)); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxTransformConstraint
+//  This constrains to a specific 3D transformation.
+//
+
+class afxTransformConstraint : public afxConstraint
+{
+  typedef afxConstraint  Parent;
+
+protected:
+  MatrixF           xfm;
+
+public:
+  /*C*/             afxTransformConstraint(afxConstraintMgr*);
+  virtual           ~afxTransformConstraint();
+
+  virtual void      set(const MatrixF& xfm);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers() { return 0; }
+
+  virtual void      unset() { set(MatrixF::Identity); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeConstraint
+//  This constrains to a hierarchical shape (subclasses of ShapeBase), such as a 
+//  Player or a Vehicle. You can also constrain to named sub-nodes of a shape.
+
+class ShapeBase;
+class SceneObject;
+
+class afxShapeConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_name;
+  ShapeBase*        shape;
+  U16               scope_id;
+  U32               clip_tag;
+  U32               lock_tag;
+
+public:
+  /*C*/             afxShapeConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxShapeConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim);
+  virtual void      resetAnimation(U32 tag);
+  virtual U32       lockAnimation();
+  virtual void      unlockAnimation(U32 tag);
+  virtual F32       getAnimClipDuration(const char* clip);
+
+  void              remapAnimation(U32 tag, ShapeBase* other_shape);
+
+  virtual S32       getDamageState();
+
+  virtual SceneObject* getSceneObject() { return shape; }
+  virtual void      restoreObject(SceneObject*);
+  virtual U16       getScopeId() { return scope_id; }
+  virtual U32       getTriggers();
+
+  virtual void      onDeleteNotify(SimObject*);
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeConstraint
+
+class afxShapeNodeConstraint : public afxShapeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_node;
+  S32               shape_node_ID;
+
+public:
+  /*C*/             afxShapeNodeConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeNodeConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node);
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+  virtual void      restoreObject(SceneObject*);
+
+  S32               getNodeID() const { return shape_node_ID; }
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectConstraint
+//  This constrains to a simple 3D object (subclasses of SceneObject), such as an 
+//  afxMagicMissile or a Projectile. You cannot constrain to sub-nodes with an
+//  afxObjectConstraint, use afxShapeConstraint instead.
+
+class SceneObject;
+
+class afxObjectConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  arb_name;
+  SceneObject*      obj;
+  U16               scope_id;
+  bool              is_camera;
+
+public:
+                    afxObjectConstraint(afxConstraintMgr*);
+                    afxObjectConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxObjectConstraint();
+
+  virtual void      set(SceneObject* obj);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual SceneObject* getSceneObject() { return obj; }
+  virtual void      restoreObject(SceneObject*);
+  virtual U16       getScopeId() { return scope_id; }
+  virtual U32       getTriggers();
+
+  virtual void      onDeleteNotify(SimObject*);
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectConstraint
+//  This constrains to a hierarchical shape (subclasses of ShapeBase), such as a 
+//  Player or a Vehicle. You can also constrain to named sub-nodes of a shape.
+
+class afxEffectWrapper;
+
+class afxEffectConstraint : public afxConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxConstraint  Parent;
+
+protected:
+  StringTableEntry  effect_name;
+  afxEffectWrapper* effect;
+  U32               clip_tag;
+  bool              is_death_clip;
+  U32               lock_tag;
+
+public:
+  /*C*/             afxEffectConstraint(afxConstraintMgr*);
+  /*C*/             afxEffectConstraint(afxConstraintMgr*, StringTableEntry effect_name);
+  virtual           ~afxEffectConstraint();
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f); 
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+  virtual bool      getAltitudes(F32& terrain_alt, F32& interior_alt);
+
+  virtual void      set(afxEffectWrapper* effect);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) { }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim);
+  virtual void      resetAnimation(U32 tag);
+  virtual F32       getAnimClipDuration(const char* clip);
+
+  virtual SceneObject* getSceneObject() { return 0; }
+  virtual void      restoreObject(SceneObject*) { }
+  virtual U16       getScopeId() { return 0; }
+  virtual U32       getTriggers();
+
+  virtual void      unset() { set(0); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectNodeConstraint
+
+class afxEffectNodeConstraint : public afxEffectConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxEffectConstraint  Parent;
+
+protected:
+  StringTableEntry  effect_node;
+  S32               effect_node_ID;
+
+public:
+  /*C*/             afxEffectNodeConstraint(afxConstraintMgr*);
+  /*C*/             afxEffectNodeConstraint(afxConstraintMgr*, StringTableEntry name, StringTableEntry node);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f); 
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      set(afxEffectWrapper* effect);
+
+  S32               getNodeID() const { return effect_node_ID; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleBuffer
+
+class afxSampleBuffer
+{
+protected:
+  U32               buffer_sz;
+  U32               buffer_ms;
+  U32               ms_per_sample;
+  U32               elapsed_ms;
+  U32               last_sample_ms;
+  U32               next_sample_num;
+  U32               n_samples;
+
+  virtual void      recSample(U32 idx, void* data) = 0;
+  bool              compute_idx_from_lag(F32 lag, U32& idx);
+  bool              compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t);
+
+public:
+  /*C*/             afxSampleBuffer();
+  virtual           ~afxSampleBuffer();
+
+  virtual void      configHistory(F32 hist_len, U8 sample_rate);
+  void              recordSample(F32 dt, U32 elapsed_ms, void* data);
+  virtual void      getSample(F32 lag, void* data, bool& oob) = 0;
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSampleXfmBuffer
+
+class afxSampleXfmBuffer : public afxSampleBuffer
+{
+  typedef afxSampleBuffer  Parent;
+
+protected:
+  MatrixF*          xfm_buffer;
+
+  virtual void      recSample(U32 idx, void* data);
+
+public:
+  /*C*/             afxSampleXfmBuffer();
+  virtual           ~afxSampleXfmBuffer();
+
+  virtual void      configHistory(F32 hist_len, U8 sample_rate);
+  virtual void      getSample(F32 lag, void* data, bool& oob);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+//  This class extends afxPointConstraint to remember its values for a period of time.
+
+class afxPointHistConstraint : public afxPointConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxPointConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxPointHistConstraint(afxConstraintMgr*);
+  virtual           ~afxPointHistConstraint();
+
+  virtual void      set(Point3F point, Point3F vector);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPointHistConstraint
+//  This class extends afxTransformConstraint to remember its values for a period of time.
+
+class afxTransformHistConstraint : public afxTransformConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxTransformConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxTransformHistConstraint(afxConstraintMgr*);
+  virtual           ~afxTransformHistConstraint();
+
+  virtual void      set(const MatrixF& xfm);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeHistConstraint
+//  This class extends afxShapeConstraint to remember its values for a period of time.
+
+class afxShapeHistConstraint : public afxShapeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxShapeHistConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxShapeHistConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxShapeNodeHistConstraint
+//  This class extends afxShapeConstraint to remember its values for a period of time.
+
+class afxShapeNodeHistConstraint : public afxShapeNodeConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxShapeNodeConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+  /*C*/             afxShapeNodeHistConstraint(afxConstraintMgr*);
+  /*C*/             afxShapeNodeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node);
+  virtual           ~afxShapeNodeHistConstraint();
+
+  virtual void      set(ShapeBase* shape);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxObjectHistConstraint
+//  This class extends afxObjectConstraint to remember its values for a period of time.
+
+class SceneObject;
+
+class afxObjectHistConstraint : public afxObjectConstraint
+{
+  friend class afxConstraintMgr;
+  typedef afxObjectConstraint  Parent;
+
+protected:
+  afxSampleBuffer*  samples;
+
+public:
+                    afxObjectHistConstraint(afxConstraintMgr*);
+                    afxObjectHistConstraint(afxConstraintMgr*, StringTableEntry arb_name);
+  virtual           ~afxObjectHistConstraint();
+
+  virtual void      set(SceneObject* obj);
+  virtual void      set_scope_id(U16 scope_id);
+  virtual void      sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos);
+
+  virtual bool      getPosition(Point3F& pos, F32 hist=0.0f);
+  virtual bool      getTransform(MatrixF& xfm, F32 hist=0.0f);
+
+  virtual void      onDeleteNotify(SimObject*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CONSTRAINT_H_
+

+ 114 - 0
Engine/source/afx/afxEffectDefs.h

@@ -0,0 +1,114 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_DEFS_H_
+#define _AFX_EFFECT_DEFS_H_
+
+#include "afx/arcaneFX.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectBASE
+
+class afxEffectDefs
+{
+public:
+
+  enum
+  {
+    MAX_EFFECTS_PER_PHRASE  = 1023,
+    EFFECTS_PER_PHRASE_BITS = 10
+  };
+
+  // effect networking
+  enum
+  {
+    SERVER_ONLY       = BIT(0),
+    SCOPE_ALWAYS      = BIT(1),
+    GHOSTABLE         = BIT(2),
+    CLIENT_ONLY       = BIT(3),
+    SERVER_AND_CLIENT = BIT(4)
+  };
+  
+  // effect condititons
+  enum 
+  {
+    DISABLED = BIT(0),
+    ENABLED = BIT(1),
+    FAILING = BIT(2),
+    ALIVE = ENABLED,
+    DEAD = DISABLED,
+    DYING = FAILING,
+    //
+    IMPACTED_SOMETHING  = BIT(31),
+    IMPACTED_TARGET     = BIT(30),
+    IMPACTED_PRIMARY    = BIT(29),
+    IMPACT_IN_WATER     = BIT(28),
+    CASTER_IN_WATER     = BIT(27),
+  };
+
+  enum
+  {
+    REQUIRES_STOP     = BIT(0),
+    RUNS_ON_SERVER    = BIT(1),
+    RUNS_ON_CLIENT    = BIT(2),
+  };
+
+  enum 
+  {
+    MAX_XFM_MODIFIERS = 32,
+    INFINITE_LIFETIME = (24*60*60)
+  };
+
+  enum
+  {
+    POINT_CONSTRAINT,
+    TRANSFORM_CONSTRAINT,
+    OBJECT_CONSTRAINT,
+    CAMERA_CONSTRAINT,
+    OBJECT_CONSTRAINT_SANS_OBJ,
+    OBJECT_CONSTRAINT_SANS_SHAPE,
+    UNDEFINED_CONSTRAINT_TYPE
+  };
+
+  enum
+  {
+    DIRECT_DAMAGE,
+    DAMAGE_OVER_TIME,
+    AREA_DAMAGE
+  };
+
+  enum
+  {
+    TIMING_DELAY      = BIT(0), 
+    TIMING_LIFETIME   = BIT(1),
+    TIMING_FADE_IN    = BIT(2),
+    TIMING_FADE_OUT   = BIT(3),
+    TIMING_BITS       = 2
+  };
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_DEFS_H_

+ 270 - 0
Engine/source/afx/afxEffectGroup.cpp

@@ -0,0 +1,270 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+
+#include "afx/afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectGroupData::egValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list. 
+//
+void afxEffectGroupData::egValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxEffectGroupData* eff_data = dynamic_cast<afxEffectGroupData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (eff_data && ew)
+  {
+    eff_data->fx_list.push_back(*ew);
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectGroupData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectGroupData);
+
+ConsoleDocClass( afxEffectGroupData,
+   "@brief A datablock that describes an Effect Group.\n\n"
+
+   "afxEffectGroupData provides a way for adding several effects to a choreographer as a "
+   "group and can be used wherever an afxEffectWrapperData is used. Basically, an "
+   "effect-group is a simple list of effect-wrappers. When an effect-group is added to a "
+   "choreographer, the end result is almost the same as adding all of the group's "
+   "effect-wrappers directly to the choreographer. The main difference is that the "
+   "grouped effects can be turned on and off collectively and created in multiples. "
+   "Effect-groups can also contain other effect-groups, forming a hierarchy of effects.\n\n"
+
+   "A great strength of effect-groups is that they have a count setting that multiplies "
+   "the number of times the effects in the group are added to the owning choreographer "
+   "and this doesn't happen until the choreographer instance is created and launched. "
+   "This makes a big difference for certain kinds of effects, such as fireworks, that "
+   "tend to consist of small groupings of effects that are repeated many times with "
+   "slight variations. With groups, an effect like this has a very compact representation "
+   "for transmitting from server to clients, that only expands when actually used.\n\n"
+
+   "Effect-groups with a count greater than one are extremely useful when some of the "
+   "effects use field substitutions. When an effect-group is expanded, it essentially runs "
+   "through a for-loop from 0 to count-1 and creates a new set of effect instances each "
+   "time through the loop. For each new set of effects, their group-index is set to the "
+   "index of this for-loop, which in turn replaces the ## token used in any field "
+   "substitutions in the child effects. In essence, the for-loop index becomes a parameter "
+   "of the child effects which can be used to vary the effects created in each loop.\n\n"
+
+   "@see afxEffectBaseData\n\n"
+   "@see afxEffectWrapperData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectGroupData::afxEffectGroupData()
+{
+  group_enabled = true;
+  group_count = 1;
+  idx_offset = 0;
+  assign_idx = false;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxEffectGroupData::afxEffectGroupData(const afxEffectGroupData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone)
+{
+  group_enabled = other.group_enabled;
+  group_count = other.group_count;
+  idx_offset = other.idx_offset;
+  assign_idx = other.assign_idx;
+  timing = other.timing;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert; // --
+  fx_list = other.fx_list; // -- 
+}
+
+void afxEffectGroupData::reloadReset()
+{
+  fx_list.clear();
+}
+
+void afxEffectGroupData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxEffectGroupData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+#define myOffset(field) Offset(field, afxEffectGroupData)
+
+void afxEffectGroupData::initPersistFields()
+{
+  addField("groupEnabled",   TypeBool,    myOffset(group_enabled),
+    "...");
+  addField("count",          TypeS32,     myOffset(group_count),
+    "...");
+  addField("indexOffset",    TypeS8,      myOffset(idx_offset),
+    "...");
+  addField("assignIndices",  TypeBool,    myOffset(assign_idx),
+    "...");
+
+  addField("delay",          TypeF32,     myOffset(timing.delay),
+    "...");
+  addField("lifetime",       TypeF32,     myOffset(timing.lifetime),
+    "...");
+  addField("fadeInTime",     TypeF32,     myOffset(timing.fade_in_time),
+    "...");
+  addField("fadeOutTime",    TypeF32,     myOffset(timing.fade_out_time),
+    "...");
+
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
+  static egValidator emptyValidator(0);
+  
+  addFieldV("addEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry), &emptyValidator,
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addEffect");
+}
+
+void afxEffectGroupData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeFlag(group_enabled);
+  stream->write(group_count);
+  stream->write(idx_offset);
+  stream->writeFlag(assign_idx);
+  stream->write(timing.delay);
+  stream->write(timing.lifetime);
+  stream->write(timing.fade_in_time);
+  stream->write(timing.fade_out_time);
+
+  pack_fx(stream, fx_list, packed);
+}
+
+void afxEffectGroupData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  group_enabled = stream->readFlag();
+  stream->read(&group_count);
+  stream->read(&idx_offset);
+  assign_idx = stream->readFlag();
+  stream->read(&timing.delay);
+  stream->read(&timing.lifetime);
+  stream->read(&timing.fade_in_time);
+  stream->read(&timing.fade_out_time);
+
+  do_id_convert = true;
+  unpack_fx(stream, fx_list);
+}
+
+bool afxEffectGroupData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < fx_list.size(); i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, fx_list[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General, 
+              "afxEffectGroupData::preload() -- bad datablockId: 0x%x", 
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxEffectGroupData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    if (fx_list[i])
+      fx_list[i]->gather_cons_defs(defs);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxEffectGroupData, reset, void, (),,
+                   "Resets an effect-group datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxEffectGroupData, addEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to an effect-group.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect) 
+  {
+    Con::errorf("afxEffectGroupData::addEffect() -- missing afxEffectWrapperData.");
+    return;
+  }
+  
+  object->fx_list.push_back(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 103 - 0
Engine/source/afx/afxEffectGroup.h

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_GROUP_H_
+#define _AFX_EFFECT_GROUP_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afx/afxEffectDefs.h"
+#include "afx/afxEffectWrapper.h"
+
+class afxEffectWrapperData;
+
+struct afxGroupTimingData
+{
+  F32     delay;
+  F32     lifetime;
+  F32     fade_in_time;
+  F32     fade_out_time;
+
+  afxGroupTimingData()
+  {
+    delay = 0.0f;
+    lifetime = 0.0f;
+    fade_in_time = 0.0f;
+    fade_out_time = 0.0f;
+  }
+};
+
+class afxEffectGroupData : public afxEffectBaseData
+{
+  typedef afxEffectBaseData Parent;
+
+  class egValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    egValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  afxEffectList       fx_list;
+  bool                group_enabled;
+  S32                 group_count;
+  U8                  idx_offset;
+  bool                assign_idx;
+  afxGroupTimingData  timing;
+  afxEffectBaseData*  dummy_fx_entry;
+
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+  
+public:
+  /*C*/         afxEffectGroupData();
+  /*C*/         afxEffectGroupData(const afxEffectGroupData&, bool = false);
+
+  virtual void  reloadReset();
+
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  virtual void  gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectGroupData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_EFFECT_GROUP_H_

+ 358 - 0
Engine/source/afx/afxEffectVector.cpp

@@ -0,0 +1,358 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afxChoreographer.h"
+#include "afxEffectVector.h"
+#include "afxConstraint.h"
+#include "afxEffectWrapper.h"
+#include "afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectVector::filter_client_server()
+{
+  if (empty())
+    return;
+
+  for (S32 i = 0; i < fx_v->size(); i++)
+  {
+    if ((*fx_v)[i]->datablock->runsHere(on_server))
+      fx_v2->push_back((*fx_v)[i]);
+    else
+    {
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+}
+
+void afxEffectVector::calc_fx_dur_and_afterlife()
+{
+  total_fx_dur = 0.0f;
+  after_life = 0.0f;
+
+  if (empty())
+    return;
+
+  for (S32 i = 0; i < fx_v->size(); i++)
+  {
+    afxEffectWrapper* ew = (*fx_v)[i];
+    if (ew)
+    {
+      F32 ew_dur;
+      if (ew->ew_timing.lifetime < 0)
+      {
+        if (phrase_dur > ew->ew_timing.delay)
+          ew_dur = phrase_dur + ew->afterStopTime();
+        else
+          ew_dur = ew->ew_timing.delay + ew->afterStopTime();
+      }
+      else
+        ew_dur = ew->ew_timing.delay + ew->ew_timing.lifetime + ew->ew_timing.fade_out_time;
+
+      if (ew_dur > total_fx_dur)
+        total_fx_dur = ew_dur;
+
+      F32 after = ew->afterStopTime();
+      if (after > after_life)
+        after_life = after;
+    }
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+afxEffectVector::afxEffectVector()
+{
+  fx_v = 0;
+  fx_v2 = 0;
+  active = false;
+  on_server = false;
+  total_fx_dur = 0;
+  after_life = 0;
+}
+
+afxEffectVector::~afxEffectVector()
+{
+  stop(true);
+  delete fx_v;
+  delete fx_v2;
+}
+
+void afxEffectVector::effects_init(afxChoreographer* chor, afxEffectList& effects, bool will_stop, F32 time_factor, 
+                                   S32 group_index, const afxGroupTimingData* group_timing)
+{ 
+  afxConstraintMgr* cons_mgr = chor->getConstraintMgr();
+
+  for (S32 i = 0; i < effects.size(); i++)
+  {
+    if (dynamic_cast<afxEffectGroupData*>(effects[i]))
+    {
+      afxEffectGroupData* eg = (afxEffectGroupData*)effects[i];
+      if (eg->getSubstitutionCount() > 0)
+      {
+        // clone the datablock and perform substitutions
+        afxEffectGroupData* orig_db = eg;
+        eg = new afxEffectGroupData(*orig_db, true);
+        orig_db->performSubstitutions(eg, chor, group_index);
+      }
+
+      if (eg->group_enabled)
+      {
+        if (eg->assign_idx)
+        {
+          for (S32 j = 0; j < eg->group_count; j++)
+            effects_init(chor, eg->fx_list, will_stop, time_factor, j+eg->idx_offset, &eg->timing);
+        }
+        else
+        {
+          for (S32 j = 0; j < eg->group_count; j++)
+            effects_init(chor, eg->fx_list, will_stop, time_factor, group_index, &eg->timing);
+        }
+      }
+
+      if (eg->isTempClone())
+        delete eg;
+    }
+    else if (dynamic_cast<afxEffectWrapperData*>(effects[i]))
+    {
+      afxEffectWrapperData* ewd = (afxEffectWrapperData*)effects[i];
+
+      if (ewd->getSubstitutionCount() > 0)
+      {
+        // clone the ewd and perform substitutions
+        afxEffectWrapperData* orig_db = ewd;
+        ewd = new afxEffectWrapperData(*orig_db, true);
+        orig_db->performSubstitutions(ewd, chor, group_index);
+      }
+
+      if (ewd->effect_enabled)
+      {
+        static afxEffectTimingData inherited_timing;
+        bool use_inherited_timing = false;
+        if (ewd->inherit_timing != 0)
+        {
+          if (group_timing)
+          {
+            inherited_timing = ewd->ewd_timing;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_DELAY) != 0)
+              inherited_timing.delay = group_timing->delay;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_LIFETIME) != 0)
+              inherited_timing.lifetime = group_timing->lifetime;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_IN) != 0)
+              inherited_timing.fade_in_time = group_timing->fade_in_time;
+            if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_OUT) != 0)
+              inherited_timing.fade_out_time = group_timing->fade_out_time;
+          }
+          else
+          {
+            Con::warnf("afxEffectVector::effects_init() -- %s::inheritGroupTiming is non-zero but wrapper is not in a group.");
+          }
+        }
+
+        const afxEffectTimingData& timing = (use_inherited_timing) ? inherited_timing : ewd->ewd_timing;
+
+        if ( (will_stop || !ewd->requiresStop(timing)) && 
+             (chor->testRanking(ewd->ranking_range.low, ewd->ranking_range.high)) &&
+             (chor->testLevelOfDetail(ewd->lod_range.low, ewd->lod_range.high)) && 
+             (ewd->testExecConditions(chor->getExecConditions()))
+            )
+        {
+          afxEffectWrapper* effect;
+          effect = afxEffectWrapper::ew_create(chor, ewd, cons_mgr, time_factor, group_index);
+          if (effect)
+            fx_v->push_back(effect);
+        }      
+      }
+      else
+      {
+        if (ewd->isTempClone())
+          delete ewd;
+      }
+
+    }
+  }
+}
+
+void afxEffectVector::ev_init(afxChoreographer* chor, afxEffectList& effects, bool on_server, 
+                              bool will_stop, F32 time_factor, F32 phrase_dur, S32 group_index)
+{
+  this->on_server = on_server;
+  this->phrase_dur = phrase_dur;
+
+  fx_v = new Vector<afxEffectWrapper*>;
+
+  effects_init(chor, effects, will_stop, time_factor, group_index);
+
+  fx_v2 = new Vector<afxEffectWrapper*>(fx_v->size());
+}
+
+void afxEffectVector::start(F32 timestamp)
+{
+  if (empty())
+    return;
+
+  // At this point both client and server effects are in the list.
+  // Timing adjustments are made during prestart().
+  for (S32 i = 0; i < fx_v->size(); i++)
+    (*fx_v)[i]->prestart();
+
+  // duration and afterlife values are pre-calculated here
+  calc_fx_dur_and_afterlife();
+
+  // now we filter out client-only or server-only effects that
+  // don't belong here,
+  filter_client_server();
+
+  active = true;
+
+  for (S32 j = 0; j < fx_v->size(); j++)
+  {
+    if ((*fx_v)[j]->start(timestamp))
+      fx_v2->push_back((*fx_v)[j]);
+    else
+    {
+      delete (*fx_v)[j];
+      (*fx_v)[j] = 0;
+    }
+  }
+
+  swap_vecs();
+  fx_v2->clear();
+}
+
+void afxEffectVector::update(F32 dt)
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->update(dt);
+
+    if ((*fx_v)[i]->isDone() || (*fx_v)[i]->isAborted())
+    {
+      // effect has ended, cleanup and delete
+      (*fx_v)[i]->cleanup();
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+    else
+    {
+      // effect is still going, so keep it around
+      fx_v2->push_back((*fx_v)[i]);
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+void afxEffectVector::stop(bool force_cleanup)
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->stop();
+
+    if (force_cleanup || (*fx_v)[i]->deleteWhenStopped())
+    {
+      // effect is over when stopped, cleanup and delete 
+      (*fx_v)[i]->cleanup();
+      delete (*fx_v)[i];
+      (*fx_v)[i] = 0;
+    }
+    else
+    {
+      // effect needs to fadeout or something, so keep it around
+      fx_v2->push_back((*fx_v)[i]);
+    }
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+void afxEffectVector::interrupt()
+{
+  if (empty())
+  {
+    active = false;
+    return;
+  }
+
+  for (int i = 0; i < fx_v->size(); i++)
+  {
+    (*fx_v)[i]->stop();
+    (*fx_v)[i]->cleanup();
+    delete (*fx_v)[i];
+    (*fx_v)[i] = 0;
+  }
+
+  swap_vecs();
+
+  fx_v2->clear();
+
+  if (empty())
+  {
+    active = false;
+    delete fx_v; fx_v =0;
+    delete fx_v2; fx_v2 = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+

+ 86 - 0
Engine/source/afx/afxEffectVector.h

@@ -0,0 +1,86 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_VECTOR_H_
+#define _AFX_EFFECT_VECTOR_H_
+
+#include "afx/afxEffectWrapper.h"
+#include "afx/afxEffectGroup.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectVector
+
+class afxEffectWrapper;
+class afxChoreographer;
+
+class afxEffectVector
+{
+  Vector<afxEffectWrapper*>*  fx_v;
+  Vector<afxEffectWrapper*>*  fx_v2;
+
+  bool          active;
+  bool          on_server;
+  F32           phrase_dur;
+  F32           total_fx_dur;
+  F32           after_life;
+
+  void          swap_vecs();
+  void          filter_client_server();
+  void          calc_fx_dur_and_afterlife();
+
+  void          effects_init(afxChoreographer*, afxEffectList&, bool will_stop, F32 time_factor, 
+                             S32 group_index, const afxGroupTimingData* group_timing=0);
+
+public:
+  /*C*/         afxEffectVector();
+  /*D*/         ~afxEffectVector();
+
+  void          ev_init(afxChoreographer*, afxEffectList&, bool on_server, bool will_stop, 
+                        F32 time_factor, F32 phrase_dur, S32 group_index=0);
+
+  void          start(F32 timestamp);
+  void          update(F32 dt);
+  void          stop(bool force_cleanup=false);
+  void          interrupt();
+  bool          empty() { return (!fx_v || fx_v->empty()); }
+  bool          isActive() { return active; }
+  S32           count() { return (fx_v) ? fx_v->size() : 0; }
+
+  F32           getTotalDur() { return total_fx_dur; }
+  F32           getAfterLife() { return after_life; }
+
+  Vector<afxEffectWrapper*>* getFX() { return fx_v; }
+};
+
+inline void afxEffectVector::swap_vecs()
+{
+  Vector<afxEffectWrapper*>* tmp = fx_v;
+  fx_v = fx_v2;
+  fx_v2 = tmp;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_VECTOR_H_

+ 1193 - 0
Engine/source/afx/afxEffectWrapper.cpp

@@ -0,0 +1,1193 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/ce/afxComponentEffect.h"
+#include "afx/afxResidueMgr.h"
+#include "afx/afxChoreographer.h"
+#include "afx/afxConstraint.h"
+#include "afx/xm/afxXfmMod.h"
+#include "afx/afxEffectWrapper.h"
+#include "afx/util/afxAnimCurve.h"
+#include "afx/util/afxEase.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapperData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectBaseData);
+
+ConsoleDocClass( afxEffectBaseData,
+   "@brief A datablock baseclass for afxEffectWrapperData and afxEffectGroupData.\n\n"
+
+   "Not intended to be used directly, afxEffectBaseData exists to provide base member "
+   "variables and generic functionality for the derived classes afxEffectWrapperData and "
+   "afxEffectGroupData.\n\n"
+
+   "@see afxEffectWrapperData\n\n"
+   "@see afxEffectGroupData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectWrapperData);
+
+ConsoleDocClass( afxEffectWrapperData,
+   "@brief A datablock that describes an Effect Wrapper.\n\n"
+
+   "Conceptually an effect wrapper encloses a building-block effect and acts "
+   "as a handle for adding the effect to a choreographer. Effect wrapper fields "
+   "primarily deal with effect timing, constraints, and conditional effect execution.\n\n"
+
+   "@see afxEffectBaseData\n\n"
+   "@see afxEffectGroupData\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectWrapperData::afxEffectWrapperData()
+{
+  effect_name = ST_NULLSTRING;
+  effect_data = 0;
+  effect_desc = 0;
+  data_ID = 0;
+  use_as_cons_obj = false;
+  use_ghost_as_cons_obj = false;
+
+  // constraint data
+  cons_spec = ST_NULLSTRING;
+  pos_cons_spec = ST_NULLSTRING;
+  orient_cons_spec = ST_NULLSTRING;
+  aim_cons_spec = StringTable->insert("camera");
+  life_cons_spec = ST_NULLSTRING;
+
+  // conditional execution flags
+  effect_enabled = true;
+  ranking_range.set(0,255);
+  lod_range.set(0,255);
+  life_conds = 0;
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  { 
+    exec_cond_on_bits[i] = 0;
+    exec_cond_off_bits[i] = 0;
+    exec_cond_bitmasks[i] = 0;
+  }
+
+  ewd_timing.lifetime = -1;
+
+  user_fade_out_time = 0.0;
+
+  is_looping = false;
+  n_loops = 0;
+  loop_gap_time = 0.0f;
+
+  ignore_time_factor = false;
+  propagate_time_factor = false;
+
+  // residue settings
+
+  // scaling factors
+  rate_factor = 1.0f;
+  scale_factor = 1.0f;
+
+  dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers));
+
+  forced_bbox.minExtents.set(1,1,1);
+  forced_bbox.maxExtents.set(-1,-1,-1);
+
+  update_forced_bbox = false;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+
+  sort_priority = 0;
+  direction.set(0,1,0);
+  speed = 0.0f;
+  mass = 1.0f;
+
+  borrow_altitudes = false;
+  vis_keys_spec = ST_NULLSTRING;
+  vis_keys = 0;
+
+  group_index = -1;
+  inherit_timing = 0;
+}
+
+afxEffectWrapperData::afxEffectWrapperData(const afxEffectWrapperData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone)
+{
+  effect_name = other.effect_name;
+  effect_data = other.effect_data;
+  effect_desc = other.effect_desc;
+  data_ID = other.data_ID;
+  use_as_cons_obj = other.use_as_cons_obj;
+  use_ghost_as_cons_obj = other.use_ghost_as_cons_obj;
+  cons_spec = other.cons_spec;
+  pos_cons_spec = other.pos_cons_spec;
+  orient_cons_spec = other.orient_cons_spec;
+  aim_cons_spec = other.aim_cons_spec;
+  life_cons_spec = other.life_cons_spec;
+  cons_def = other.cons_def;
+  pos_cons_def = other.pos_cons_def;
+  orient_cons_def = other.orient_cons_def;
+  aim_cons_def = other.aim_cons_def;
+  life_cons_def = other.life_cons_def;
+  effect_enabled = other.effect_enabled;
+  ranking_range = other.ranking_range;
+  lod_range = other.lod_range;
+  life_conds = other.life_conds;
+  dMemcpy(exec_cond_on_bits, other.exec_cond_on_bits, sizeof(exec_cond_on_bits));
+  dMemcpy(exec_cond_off_bits, other.exec_cond_off_bits, sizeof(exec_cond_off_bits));
+  dMemcpy(exec_cond_bitmasks, other.exec_cond_bitmasks, sizeof(exec_cond_bitmasks));
+  ewd_timing = other.ewd_timing;
+  user_fade_out_time = other.user_fade_out_time;
+  is_looping = other.is_looping;
+  n_loops = other.n_loops;
+  loop_gap_time = other.loop_gap_time;
+  ignore_time_factor = other.ignore_time_factor;
+  propagate_time_factor = other.propagate_time_factor;
+  rate_factor = other.rate_factor;
+  scale_factor = other.scale_factor;
+  dMemcpy(xfm_modifiers, other.xfm_modifiers, sizeof(xfm_modifiers));
+  forced_bbox = other.forced_bbox;
+  update_forced_bbox = other.update_forced_bbox;
+  do_id_convert = other.do_id_convert;
+  sort_priority = other.sort_priority;
+  direction = other.direction;
+  speed = other.speed;
+  mass = other.mass;
+  borrow_altitudes = other.borrow_altitudes;
+  vis_keys_spec = other.vis_keys_spec;
+  vis_keys = other.vis_keys;
+  if (other.vis_keys)
+  {
+    vis_keys = new afxAnimCurve();
+    for (S32 i = 0; i < other.vis_keys->numKeys(); i++)
+    {
+      F32 when = other.vis_keys->getKeyTime(i);
+      F32 what = other.vis_keys->getKeyValue(i);
+      vis_keys->addKey(when, what);
+    }
+  }
+  else
+    vis_keys = 0;
+  group_index = other.group_index;
+  inherit_timing = other.inherit_timing;
+}
+
+afxEffectWrapperData::~afxEffectWrapperData()
+{
+  if (vis_keys)
+    delete vis_keys;
+}
+
+#define myOffset(field) Offset(field, afxEffectWrapperData)
+
+void afxEffectWrapperData::initPersistFields()
+{
+  // the wrapped effect
+  addField("effect",       TYPEID<SimDataBlock>(),    myOffset(effect_data),
+    "...");
+  addField("effectName",   TypeString,                myOffset(effect_name),
+    "...");
+
+  // constraints
+  addField("constraint",              TypeString,   myOffset(cons_spec),
+    "...");
+  addField("posConstraint",           TypeString,   myOffset(pos_cons_spec),
+    "...");
+  addField("posConstraint2",          TypeString,   myOffset(aim_cons_spec),
+    "...");
+  addField("orientConstraint",        TypeString,   myOffset(orient_cons_spec),
+    "...");
+  addField("lifeConstraint",          TypeString,   myOffset(life_cons_spec),
+    "...");
+  //
+  addField("isConstraintSrc",         TypeBool,     myOffset(use_as_cons_obj),
+    "...");
+  addField("ghostIsConstraintSrc",    TypeBool,     myOffset(use_ghost_as_cons_obj),
+    "...");
+
+  addField("delay",             TypeF32,          myOffset(ewd_timing.delay),
+    "...");
+  addField("lifetime",          TypeF32,          myOffset(ewd_timing.lifetime),
+    "...");
+  addField("fadeInTime",        TypeF32,          myOffset(ewd_timing.fade_in_time),
+    "...");
+  addField("residueLifetime",   TypeF32,          myOffset(ewd_timing.residue_lifetime),
+    "...");
+  addField("fadeInEase",        TypePoint2F,      myOffset(ewd_timing.fadein_ease),
+    "...");
+  addField("fadeOutEase",       TypePoint2F,      myOffset(ewd_timing.fadeout_ease),
+    "...");
+  addField("lifetimeBias",      TypeF32,          myOffset(ewd_timing.life_bias),
+    "...");
+  addField("fadeOutTime",       TypeF32,          myOffset(user_fade_out_time),
+    "...");
+
+  addField("rateFactor",        TypeF32,          myOffset(rate_factor),
+    "...");
+  addField("scaleFactor",       TypeF32,          myOffset(scale_factor),
+    "...");
+
+  addField("isLooping",         TypeBool,         myOffset(is_looping),
+    "...");
+  addField("loopCount",         TypeS32,          myOffset(n_loops),
+    "...");
+  addField("loopGapTime",       TypeF32,          myOffset(loop_gap_time),
+    "...");
+
+  addField("ignoreTimeFactor",    TypeBool,       myOffset(ignore_time_factor),
+    "...");
+  addField("propagateTimeFactor", TypeBool,       myOffset(propagate_time_factor),
+    "...");
+
+  addField("effectEnabled",         TypeBool,         myOffset(effect_enabled),
+    "...");
+  addField("rankingRange",          TypeByteRange,    myOffset(ranking_range),
+    "...");
+  addField("levelOfDetailRange",    TypeByteRange,    myOffset(lod_range),
+    "...");
+  addField("lifeConditions",        TypeS32,      myOffset(life_conds),
+    "...");
+  addField("execConditions",        TypeS32,      myOffset(exec_cond_on_bits),  MAX_CONDITION_STATES,
+    "...");
+  addField("execOffConditions",     TypeS32,      myOffset(exec_cond_off_bits), MAX_CONDITION_STATES,
+    "...");
+
+  addField("xfmModifiers",    TYPEID<afxXM_BaseData>(),  myOffset(xfm_modifiers),  MAX_XFM_MODIFIERS,
+    "...");
+
+  addField("forcedBBox",        TypeBox3F,        myOffset(forced_bbox),
+    "...");
+  addField("updateForcedBBox",  TypeBool,         myOffset(update_forced_bbox),
+    "...");
+
+  addField("sortPriority",      TypeS8,           myOffset(sort_priority),
+    "...");
+  addField("direction",         TypePoint3F,      myOffset(direction),
+    "...");
+  addField("speed",             TypeF32,          myOffset(speed),
+    "...");
+  addField("mass",              TypeF32,          myOffset(mass),
+    "...");
+
+  addField("borrowAltitudes",   TypeBool,         myOffset(borrow_altitudes),
+    "...");
+  addField("visibilityKeys",    TypeString,       myOffset(vis_keys_spec),
+    "...");
+
+  addField("groupIndex",          TypeS32,        myOffset(group_index),
+    "...");
+  addField("inheritGroupTiming",  TypeS32,        myOffset(inherit_timing),
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("effect");
+  onlyKeepClearSubstitutions("xfmModifiers"); // subs resolving to "~~", or "~0" are OK
+
+  // Conditional Execution Flags
+  Con::setIntVariable("$afx::DISABLED", DISABLED);
+  Con::setIntVariable("$afx::ENABLED", ENABLED);
+  Con::setIntVariable("$afx::FAILING", FAILING);
+  Con::setIntVariable("$afx::DEAD", DEAD);
+  Con::setIntVariable("$afx::ALIVE", ALIVE);
+  Con::setIntVariable("$afx::DYING", DYING);
+}
+
+bool afxEffectWrapperData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  if (!effect_data)
+  {
+    if (!Sim::findObject((SimObjectId)data_ID, effect_data))
+    {
+      Con::errorf("afxEffectWrapperData::onAdd() -- bad datablockId: 0x%x", data_ID);
+      return false;
+    }
+  }
+
+  if (effect_data)
+  {
+    if (!afxEffectAdapterDesc::identifyEffect(this))
+    {
+      Con::errorf("afxEffectWrapperData::onAdd() -- unknown effect type.");
+      return false;
+    }
+  }
+
+  parse_cons_specs();
+  parse_vis_keys();
+
+  // figure out if fade-out is for effect of residue
+  if (ewd_timing.residue_lifetime > 0)
+  {
+    ewd_timing.residue_fadetime = user_fade_out_time;
+    ewd_timing.fade_out_time = 0.0f;
+  }
+  else
+  {
+    ewd_timing.residue_fadetime = 0.0f;
+    ewd_timing.fade_out_time = user_fade_out_time;
+  }
+
+  // adjust fade-in time
+  if (ewd_timing.lifetime >= 0)
+  {
+    ewd_timing.fade_in_time = getMin(ewd_timing.lifetime, ewd_timing.fade_in_time);
+  }
+
+  // adjust exec-conditions
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+    exec_cond_bitmasks[i] = exec_cond_on_bits[i] | exec_cond_off_bits[i];
+
+  return true;
+}
+
+void afxEffectWrapperData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  writeDatablockID(stream, effect_data, packed);
+
+  stream->writeString(effect_name);
+
+  stream->writeString(cons_spec);
+  stream->writeString(pos_cons_spec);
+  stream->writeString(orient_cons_spec);
+  stream->writeString(aim_cons_spec);
+  stream->writeString(life_cons_spec);
+  //
+  stream->write(use_as_cons_obj);
+  //stream->write(use_ghost_as_cons_obj);
+
+  stream->writeFlag(effect_enabled);
+  stream->write(ranking_range.low);
+  stream->write(ranking_range.high);
+  stream->write(lod_range.low);
+  stream->write(lod_range.high);
+
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  {
+    stream->write(exec_cond_on_bits[i]);
+    stream->write(exec_cond_off_bits[i]);
+  }
+  stream->write(life_conds);
+  stream->write(ewd_timing.delay);
+  stream->write(ewd_timing.lifetime);
+  stream->write(ewd_timing.fade_in_time);
+  stream->write(user_fade_out_time);
+  stream->write(is_looping);
+  stream->write(n_loops);
+  stream->write(loop_gap_time);
+  stream->write(ignore_time_factor);
+  stream->write(propagate_time_factor);
+  stream->write(ewd_timing.residue_lifetime);
+  stream->write(rate_factor);
+  stream->write(scale_factor);
+
+  // modifiers
+  pack_mods(stream, xfm_modifiers, packed);
+
+  mathWrite(*stream, forced_bbox);
+  stream->write(update_forced_bbox);
+
+  stream->write(sort_priority);
+  mathWrite(*stream, direction);
+  stream->write(speed);
+  stream->write(mass);
+
+  stream->write(borrow_altitudes);
+  if (stream->writeFlag(vis_keys_spec != ST_NULLSTRING))
+    stream->writeLongString(1023, vis_keys_spec);
+
+  if (stream->writeFlag(group_index != -1))
+    stream->write(group_index);
+
+  stream->writeInt(inherit_timing, TIMING_BITS);
+}
+
+void afxEffectWrapperData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  data_ID = readDatablockID(stream);
+
+  effect_name = stream->readSTString();
+
+  cons_spec = stream->readSTString();
+  pos_cons_spec = stream->readSTString();
+  orient_cons_spec = stream->readSTString();
+  aim_cons_spec = stream->readSTString();
+  life_cons_spec = stream->readSTString();
+  //
+  stream->read(&use_as_cons_obj);
+  //stream->read(&use_ghost_as_cons_obj);
+
+  effect_enabled = stream->readFlag();
+  stream->read(&ranking_range.low);
+  stream->read(&ranking_range.high);
+  stream->read(&lod_range.low);
+  stream->read(&lod_range.high);
+
+  for (S32 i = 0; i < MAX_CONDITION_STATES; i++)
+  {
+    stream->read(&exec_cond_on_bits[i]);
+    stream->read(&exec_cond_off_bits[i]);
+  }
+  stream->read(&life_conds);
+  stream->read(&ewd_timing.delay);
+  stream->read(&ewd_timing.lifetime);
+  stream->read(&ewd_timing.fade_in_time);
+  stream->read(&user_fade_out_time);
+  stream->read(&is_looping);
+  stream->read(&n_loops);
+  stream->read(&loop_gap_time);
+  stream->read(&ignore_time_factor);
+  stream->read(&propagate_time_factor);
+  stream->read(&ewd_timing.residue_lifetime);
+  stream->read(&rate_factor);
+  stream->read(&scale_factor);
+
+  // modifiers
+  do_id_convert = true;
+  unpack_mods(stream, xfm_modifiers);
+
+  mathRead(*stream, &forced_bbox);
+  stream->read(&update_forced_bbox);
+
+  stream->read(&sort_priority);
+  mathRead(*stream, &direction);
+  stream->read(&speed);
+  stream->read(&mass);
+
+  stream->read(&borrow_altitudes);
+  if (stream->readFlag())
+  {
+    char buf[1024];
+    stream->readLongString(1023, buf);
+    vis_keys_spec = StringTable->insert(buf);
+  }
+  else
+    vis_keys_spec = ST_NULLSTRING;
+
+  if (stream->readFlag())
+    stream->read(&group_index);
+  else
+    group_index = -1;
+
+  inherit_timing = stream->readInt(TIMING_BITS);
+}
+
+/* static*/ 
+S32 num_modifiers(afxXM_BaseData* mods[])
+{
+  S32 n_mods = 0;
+  for (int i = 0; i < afxEffectDefs::MAX_XFM_MODIFIERS; i++)
+  {
+    if (mods[i])
+    {
+      if (i != n_mods)
+      {
+        mods[n_mods] = mods[i];
+        mods[i] = 0;
+      }
+      n_mods++;
+    }
+  }
+
+  return n_mods;
+}
+
+void afxEffectWrapperData::parse_cons_specs()
+{
+  // parse the constraint specifications
+  bool runs_on_s = runsOnServer();
+  bool runs_on_c = runsOnClient();
+  cons_def.parseSpec(cons_spec, runs_on_s, runs_on_c);
+  pos_cons_def.parseSpec(pos_cons_spec, runs_on_s, runs_on_c);
+  orient_cons_def.parseSpec(orient_cons_spec, runs_on_s, runs_on_c);
+  aim_cons_def.parseSpec(aim_cons_spec, runs_on_s, runs_on_c);
+  life_cons_def.parseSpec(life_cons_spec, runs_on_s, runs_on_c);
+  if (cons_def.isDefined())
+  {
+    pos_cons_def = cons_def;
+    if (!orient_cons_def.isDefined())
+      orient_cons_def = cons_def;
+  }
+}
+
+void afxEffectWrapperData::parse_vis_keys()
+{
+  if (vis_keys_spec != ST_NULLSTRING)
+  {
+    if (vis_keys)
+      delete vis_keys;
+    vis_keys = new afxAnimCurve();
+
+    char* keys_buffer = dStrdup(vis_keys_spec);
+
+    char* key_token = dStrtok(keys_buffer, " \t");
+    while (key_token != NULL)
+    {
+      char* colon = dStrchr(key_token, ':');
+      if (colon)
+      {
+        *colon = '\0';
+
+        F32 when = dAtof(key_token);
+        F32 what = dAtof(colon+1);
+
+        vis_keys->addKey(when, what);
+      }
+      key_token = dStrtok(NULL, " \t");
+    }
+
+    dFree(keys_buffer);
+    vis_keys->sort();
+  }
+}
+
+void afxEffectWrapperData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{
+  if (pos_cons_def.isDefined())
+    defs.push_back(pos_cons_def);
+  if (orient_cons_def.isDefined())
+    defs.push_back(orient_cons_def);
+  if (aim_cons_def.isDefined())
+    defs.push_back(aim_cons_def);
+  if (life_cons_def.isDefined())
+    defs.push_back(life_cons_def);
+
+  afxComponentEffectData* ce_data = dynamic_cast<afxComponentEffectData*>(effect_data);
+  if (ce_data)
+    ce_data->gather_cons_defs(defs);
+}
+
+void afxEffectWrapperData::pack_mods(BitStream* stream, afxXM_BaseData* mods[], bool packed)
+{
+  S32 n_mods = num_modifiers(mods);
+  stream->writeInt(n_mods, 6);
+  for (int i = 0; i < n_mods; i++)
+    writeDatablockID(stream, mods[i], packed);
+}
+
+void afxEffectWrapperData::unpack_mods(BitStream* stream, afxXM_BaseData* mods[])
+{
+  S32 n_mods = stream->readInt(6);
+  for (int i = 0; i < n_mods; i++)
+    mods[i] = (afxXM_BaseData*) readDatablockID(stream);
+}
+
+bool afxEffectWrapperData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+  
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (int i = 0; i < MAX_XFM_MODIFIERS; i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)xfm_modifiers[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, xfm_modifiers[i]))
+          {
+            Con::errorf("afxEffectWrapperData::preload() -- bad datablockId: 0x%x (xfm_modifiers[%d])",
+              db_id, i);
+          }
+        }
+        do_id_convert = false;
+      }
+    }
+  }
+  
+  return true;
+}
+
+void afxEffectWrapperData::onPerformSubstitutions()
+{
+  Parent::onPerformSubstitutions();
+
+  parse_cons_specs();
+  parse_vis_keys();
+
+  if (ewd_timing.residue_lifetime > 0)
+  {
+    ewd_timing.residue_fadetime = user_fade_out_time;
+    ewd_timing.fade_out_time = 0.0f;
+  }
+  else
+  {
+    ewd_timing.residue_fadetime = 0.0f;
+    ewd_timing.fade_out_time = user_fade_out_time;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapper
+
+IMPLEMENT_CONOBJECT(afxEffectWrapper);
+
+ConsoleDocClass( afxEffectWrapper,
+   "@brief An Effect Wrapper as defined by an afxEffectWrapperData datablock.\n\n"
+
+   "Conceptually an effect wrapper encloses a building-block effect and acts "
+   "as a handle for adding the effect to a choreographer. Effect wrapper fields "
+   "primarily deal with effect timing, constraints, and conditional effect execution.\n\n"
+
+   "Not intended to be used directly, afxEffectWrapper is an internal baseclass used to "
+   "implement effect-specific adapter classes.\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxEffectWrapper::afxEffectWrapper()
+{
+  choreographer = 0;
+  datablock = 0;
+  cons_mgr = 0;
+
+  cond_alive = true;
+  elapsed = 0;
+  life_end = 0;
+  life_elapsed = 0;
+  stopped = false;
+  n_updates = 0;
+  fade_value = 1.0f;
+  last_fade_value = 0.0f;
+  fade_in_end = 0.0;
+  fade_out_start = 0.0f;
+  in_scope = true;
+  is_aborted = false;
+  do_fade_inout = false;
+  do_fades = false;
+  full_lifetime = 0;
+
+  time_factor = 1.0f;
+  prop_time_factor = 1.0f;
+
+  live_scale_factor = 1.0f;
+  live_fade_factor = 1.0f;
+  terrain_altitude = -1.0f;
+  interior_altitude = -1.0f;
+
+  group_index = 0;
+
+  dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers));
+}
+
+afxEffectWrapper::~afxEffectWrapper()
+{
+  for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++)
+    if (xfm_modifiers[i])
+      delete xfm_modifiers[i];
+
+  if (datablock && datablock->effect_name != ST_NULLSTRING)
+  {
+    choreographer->removeNamedEffect(this);
+    if (datablock->use_as_cons_obj && !effect_cons_id.undefined())
+      cons_mgr->setReferenceEffect(effect_cons_id, 0);
+  }
+
+  if (datablock && datablock->isTempClone())
+    delete datablock;
+  datablock = 0;
+}
+
+#undef myOffset
+#define myOffset(field) Offset(field, afxEffectWrapper)
+
+void afxEffectWrapper::initPersistFields()
+{
+  addField("liveScaleFactor",     TypeF32,    myOffset(live_scale_factor),
+    "...");
+  addField("liveFadeFactor",      TypeF32,    myOffset(live_fade_factor),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxEffectWrapper::ew_init(afxChoreographer*     choreographer, 
+                               afxEffectWrapperData* datablock, 
+                               afxConstraintMgr*     cons_mgr,
+                               F32                   time_factor)
+{
+  AssertFatal(choreographer != NULL, "Choreographer is missing.");
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+  AssertFatal(cons_mgr != NULL, "Constraint manager is missing.");
+
+  this->choreographer = choreographer;
+  this->datablock = datablock;
+  this->cons_mgr = cons_mgr;
+  ea_set_datablock(datablock->effect_data);
+
+  ew_timing = datablock->ewd_timing;
+  if (ew_timing.life_bias != 1.0f)
+  {
+    if (ew_timing.lifetime > 0)
+      ew_timing.lifetime *= ew_timing.life_bias;
+    ew_timing.fade_in_time *= ew_timing.life_bias;
+    ew_timing.fade_out_time *= ew_timing.life_bias;
+  }
+
+  pos_cons_id = cons_mgr->getConstraintId(datablock->pos_cons_def);
+  orient_cons_id = cons_mgr->getConstraintId(datablock->orient_cons_def);
+  aim_cons_id = cons_mgr->getConstraintId(datablock->aim_cons_def);
+  life_cons_id = cons_mgr->getConstraintId(datablock->life_cons_def);
+
+  this->time_factor = (datablock->ignore_time_factor) ? 1.0f : time_factor;
+
+  if (datablock->propagate_time_factor)
+    prop_time_factor = time_factor;
+
+  if (datablock->runsHere(choreographer->isServerObject()))
+  {
+    for (int i = 0; i < MAX_XFM_MODIFIERS && datablock->xfm_modifiers[i] != 0; i++)
+    {
+      xfm_modifiers[i] = datablock->xfm_modifiers[i]->create(this, choreographer->isServerObject());
+      AssertFatal(xfm_modifiers[i] != 0, avar("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName()));
+      if (xfm_modifiers[i] == 0)
+        Con::errorf("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName());
+    }
+  }
+
+  if (datablock->effect_name != ST_NULLSTRING)
+  {
+    assignName(datablock->effect_name);
+    choreographer->addNamedEffect(this);
+    if (datablock->use_as_cons_obj)
+    {
+      effect_cons_id = cons_mgr->setReferenceEffect(datablock->effect_name, this);
+      if (effect_cons_id.undefined() && datablock->isTempClone() && datablock->runsHere(choreographer->isServerObject()))
+        effect_cons_id = cons_mgr->createReferenceEffect(datablock->effect_name, this);
+    }
+  }
+}
+
+void afxEffectWrapper::prestart() 
+{
+  // modify timing values by time_factor
+  if (ew_timing.lifetime > 0)
+    ew_timing.lifetime *= time_factor;
+  ew_timing.delay *= time_factor;
+  ew_timing.fade_in_time *= time_factor;
+  ew_timing.fade_out_time *= time_factor;
+
+  if (ew_timing.lifetime < 0)
+  {
+    full_lifetime = INFINITE_LIFETIME;
+    life_end = INFINITE_LIFETIME;
+  }
+  else
+  {
+    full_lifetime = ew_timing.lifetime + ew_timing.fade_out_time;
+    life_end = ew_timing.delay + ew_timing.lifetime;
+  }
+
+  if ((ew_timing.fade_in_time + ew_timing.fade_out_time) > 0.0f)
+  {
+    fade_in_end = ew_timing.delay + ew_timing.fade_in_time;
+    if (full_lifetime == INFINITE_LIFETIME)
+      fade_out_start = INFINITE_LIFETIME;
+    else
+      fade_out_start = ew_timing.delay + ew_timing.lifetime;
+    do_fade_inout = true;
+  }
+
+  if (!do_fade_inout && datablock->vis_keys != NULL && datablock->vis_keys->numKeys() > 0)
+  {
+    //do_fades = true;
+    fade_out_start = ew_timing.delay + ew_timing.lifetime;
+  }
+}
+
+bool afxEffectWrapper::start(F32 timestamp) 
+{ 
+  if (!ea_is_enabled())
+  {
+    Con::warnf("afxEffectWrapper::start() -- effect type of %s is currently disabled.", datablock->getName());
+    return false;
+  }
+
+  afxConstraint* life_constraint = getLifeConstraint();
+  if (life_constraint)
+    cond_alive = life_constraint->getLivingState();
+
+  elapsed = timestamp; 
+
+  for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++)
+  {
+    if (!xfm_modifiers[i])
+      break;
+    else
+      xfm_modifiers[i]->start(timestamp);
+  }
+
+  if (!ea_start())
+  {
+    // subclass should print error message if applicable, so no message here.
+    return false;
+  }
+
+  update(0.0f);
+  return !isAborted();
+}
+
+bool afxEffectWrapper::test_life_conds()
+{
+  afxConstraint* life_constraint = getLifeConstraint();
+  if (!life_constraint || datablock->life_conds == 0)
+    return true;
+
+  S32 now_state = life_constraint->getDamageState();
+  if ((datablock->life_conds & DEAD) != 0 && now_state == ShapeBase::Disabled)
+    return true;
+  if ((datablock->life_conds & ALIVE) != 0 && now_state == ShapeBase::Enabled)
+    return true;
+  if ((datablock->life_conds & DYING) != 0)
+    return (cond_alive && now_state == ShapeBase::Disabled);
+
+  return false;
+}
+
+bool afxEffectWrapper::update(F32 dt) 
+{ 
+  elapsed += dt; 
+
+  // life_elapsed won't exceed full_lifetime
+  life_elapsed = getMin(elapsed - ew_timing.delay, full_lifetime);
+
+  // update() returns early if elapsed is outside of active timing range 
+  //     (delay <= elapsed <= delay+lifetime)
+  // note: execution is always allowed beyond this point at least once, 
+  //       even if elapsed exceeds the lifetime.
+  if (elapsed < ew_timing.delay)
+  {
+    setScopeStatus(false);
+    return false;
+  }
+  
+  if (!datablock->requiresStop(ew_timing) && ew_timing.lifetime < 0)
+  {
+    F32 afterlife = elapsed - ew_timing.delay;
+    if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0)))
+    {
+      setScopeStatus(ew_timing.residue_lifetime > 0.0f);
+      return false;
+    }
+  }
+  else
+  {
+    F32 afterlife = elapsed - (full_lifetime + ew_timing.delay);
+    if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0)))
+    {
+      setScopeStatus(ew_timing.residue_lifetime > 0.0f);
+      return false;
+    }
+  }
+
+  // first time here, test if required conditions for effect are met
+  if (n_updates == 0)
+  {
+    if (!test_life_conds())
+    {
+      elapsed = full_lifetime + ew_timing.delay;
+      setScopeStatus(false);
+      n_updates++;
+      return false;
+    }
+  }
+
+  setScopeStatus(true);
+  n_updates++;
+
+
+  // calculate current fade value if enabled
+  if (do_fade_inout)
+  {
+    if (ew_timing.fade_in_time > 0 && elapsed <= fade_in_end)
+    {
+      F32 t = mClampF((elapsed-ew_timing.delay)/ew_timing.fade_in_time, 0.0f, 1.0f);
+      fade_value = afxEase::t(t, ew_timing.fadein_ease.x,ew_timing.fadein_ease.y);
+      do_fades = true;
+    }
+    else if (elapsed > fade_out_start)
+    {
+      if (ew_timing.fade_out_time == 0)
+        fade_value = 0.0f;
+      else
+      {
+        F32 t = mClampF(1.0f-(elapsed-fade_out_start)/ew_timing.fade_out_time, 0.0f, 1.0f);
+        fade_value = afxEase::t(t, ew_timing.fadeout_ease.x,ew_timing.fadeout_ease.y);
+      }
+      do_fades = true;
+    }
+    else
+    {
+      fade_value = 1.0f;
+      do_fades = false;
+    }
+  }
+  else
+  {
+    fade_value = 1.0;
+    do_fades = false;
+  }
+
+  if (datablock->vis_keys && datablock->vis_keys->numKeys() > 0)
+  {
+    F32 vis = datablock->vis_keys->evaluate(elapsed-ew_timing.delay);
+    fade_value *= mClampF(vis, 0.0f, 1.0f);
+    do_fades = (fade_value < 1.0f);
+  }
+
+  // DEAL WITH CONSTRAINTS
+  afxXM_Params params;
+  Point3F& CONS_POS = params.pos;
+  MatrixF& CONS_XFM = params.ori;
+  Point3F& CONS_AIM = params.pos2;
+  Point3F& CONS_SCALE = params.scale;
+  LinearColorF& CONS_COLOR = params.color;
+
+  afxConstraint* pos_constraint = getPosConstraint();
+  if (pos_constraint)
+  {
+    bool valid = pos_constraint->getPosition(CONS_POS, datablock->pos_cons_def.history_time);
+    if (!valid)
+      getUnconstrainedPosition(CONS_POS);
+    setScopeStatus(valid);
+    if (valid && datablock->borrow_altitudes)
+    {
+      F32 terr_alt, inter_alt;
+      if (pos_constraint->getAltitudes(terr_alt, inter_alt))
+      {
+        terrain_altitude = terr_alt;
+        interior_altitude = inter_alt;
+      }
+    }
+  }
+  else
+  {
+    getUnconstrainedPosition(CONS_POS);
+    setScopeStatus(false);
+  }
+
+  afxConstraint* orient_constraint = getOrientConstraint();
+  if (orient_constraint) 
+  {
+    orient_constraint->getTransform(CONS_XFM, datablock->pos_cons_def.history_time);
+  }
+  else
+  {
+    getUnconstrainedTransform(CONS_XFM);
+  }
+
+  afxConstraint* aim_constraint = getAimConstraint();
+  if (aim_constraint)
+    aim_constraint->getPosition(CONS_AIM, datablock->pos_cons_def.history_time);
+  else
+    CONS_AIM.zero();
+
+  CONS_SCALE.set(datablock->scale_factor, datablock->scale_factor, datablock->scale_factor);
+
+  /*
+  if (datablock->isPositional() && CONS_POS.isZero() && in_scope)
+    Con::errorf("#EFFECT AT ORIGIN [%s] time=%g", datablock->getName(), dt);
+  */
+
+  getBaseColor(CONS_COLOR);
+
+  params.vis = fade_value;
+
+  // apply modifiers
+  for (int i = 0; i < MAX_XFM_MODIFIERS; i++)
+  {
+    if (!xfm_modifiers[i])
+      break;
+    else
+      xfm_modifiers[i]->updateParams(dt, life_elapsed, params);
+  }
+
+  // final pos/orient is determined
+  updated_xfm = CONS_XFM;  
+  updated_pos = CONS_POS;
+  updated_aim = CONS_AIM;
+  updated_xfm.setPosition(updated_pos);
+  updated_scale = CONS_SCALE;
+  updated_color = CONS_COLOR;
+
+  if (params.vis > 1.0f)
+    fade_value = 1.0f;
+  else
+    fade_value = params.vis;
+
+  if (last_fade_value != fade_value)
+  {
+    do_fades = true;
+    last_fade_value = fade_value;
+  }
+  else
+  {
+    do_fades = (fade_value < 1.0f);
+  }
+
+  if (!ea_update(dt))
+  {
+    is_aborted = true;
+    Con::errorf("afxEffectWrapper::update() -- effect %s ended unexpectedly.", datablock->getName());
+  }
+
+  return true;
+}
+
+void afxEffectWrapper::stop() 
+{ 
+  if (!datablock->requiresStop(ew_timing))
+    return;
+
+  stopped = true; 
+
+  // this resets full_lifetime so it starts to shrink or fade
+  if (full_lifetime == INFINITE_LIFETIME)
+  {
+    full_lifetime = (elapsed - ew_timing.delay) + afterStopTime();
+    life_end = elapsed; 
+    if (ew_timing.fade_out_time > 0)
+      fade_out_start = elapsed;
+  }
+}
+
+void afxEffectWrapper::cleanup(bool was_stopped)
+{ 
+  ea_finish(was_stopped);
+  if (!effect_cons_id.undefined())
+  {
+    cons_mgr->setReferenceEffect(effect_cons_id, 0);
+    effect_cons_id = afxConstraintID();
+  }
+}
+
+void afxEffectWrapper::setScopeStatus(bool in_scope)
+{ 
+  if (this->in_scope != in_scope)
+  {
+    this->in_scope = in_scope;
+    ea_set_scope_status(in_scope);
+  }
+}
+
+bool afxEffectWrapper::isDone() 
+{ 
+  if (!datablock->is_looping)
+    return (elapsed >= (life_end + ew_timing.fade_out_time));
+
+  return false;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// static 
+afxEffectWrapper* afxEffectWrapper::ew_create(afxChoreographer*      choreographer, 
+                                              afxEffectWrapperData*  datablock, 
+                                              afxConstraintMgr*      cons_mgr,
+                                              F32                    time_factor,
+                                              S32                    group_index)
+{
+  afxEffectWrapper* adapter = datablock->effect_desc->create();
+
+  if (adapter)
+  {
+    adapter->group_index = (datablock->group_index != -1) ? datablock->group_index : group_index;
+    adapter->ew_init(choreographer, datablock, cons_mgr, time_factor); 
+  }
+
+  return adapter;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+Vector<afxEffectAdapterDesc*>* afxEffectAdapterDesc::adapters = 0;
+
+afxEffectAdapterDesc::afxEffectAdapterDesc() 
+{ 
+  if (!adapters)
+    adapters = new Vector<afxEffectAdapterDesc*>;
+
+  adapters->push_back(this);
+}
+
+bool afxEffectAdapterDesc::identifyEffect(afxEffectWrapperData* ew)
+{
+  if (!ew || !ew->effect_data)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect datablock was not specified.");
+    return false;
+  }
+
+  if (!adapters)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- adapter registration list has not been allocated.");
+    return false;
+  }
+
+  if (adapters->size() == 0)
+  {
+    Con::errorf("afxEffectAdapterDesc::identifyEffect() -- no effect adapters have been registered.");
+    return false;
+  }
+
+  for (S32 i = 0; i < adapters->size(); i++)
+  {
+    if ((*adapters)[i]->testEffectType(ew->effect_data))
+    {
+      ew->effect_desc = (*adapters)[i];
+      (*adapters)[i]->prepEffect(ew);
+      return true;
+    }
+  }
+
+  Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect %s has an undefined type. -- %d", 
+    ew->effect_data->getName(), adapters->size());
+  return false;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 392 - 0
Engine/source/afx/afxEffectWrapper.h

@@ -0,0 +1,392 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EFFECT_WRAPPER_H_
+#define _AFX_EFFECT_WRAPPER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+#include "afxEffectDefs.h"
+#include "afxConstraint.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+struct afxEffectTimingData
+{
+  F32     delay;
+  F32     lifetime;
+  F32     fade_in_time;
+  F32     fade_out_time;
+  F32     residue_lifetime;   
+  F32     residue_fadetime;
+  F32     life_bias;
+  Point2F fadein_ease;
+  Point2F fadeout_ease;
+
+  afxEffectTimingData()
+  {
+    delay = 0.0f;
+    lifetime = 0.0f;
+    fade_in_time = 0.0f;
+    fade_out_time = 0.0f;
+    residue_lifetime = 0.0f;
+    residue_fadetime = 0.0f;
+    life_bias = 1.0f;
+    fadein_ease.set(0.0f, 1.0f);
+    fadeout_ease.set(0.0f, 1.0f);
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxEffectWrapperData;
+class afxAnimCurve;
+
+class afxEffectAdapterDesc
+{
+private:
+  static Vector<afxEffectAdapterDesc*>* adapters;
+
+public:
+  /*C*/         afxEffectAdapterDesc();
+
+  virtual bool  testEffectType(const SimDataBlock*) const=0;
+  virtual bool  requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const=0;
+  virtual bool  runsOnServer(const afxEffectWrapperData*) const=0;
+  virtual bool  runsOnClient(const afxEffectWrapperData*) const=0;
+  virtual bool  isPositional(const afxEffectWrapperData*) const { return true; }
+  virtual void  prepEffect(afxEffectWrapperData*) const { }
+
+  virtual afxEffectWrapper* create() const=0;
+
+  static bool   identifyEffect(afxEffectWrapperData*);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxXM_BaseData;
+
+class afxEffectBaseData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  /*C*/           afxEffectBaseData() { }
+  /*C*/           afxEffectBaseData(const afxEffectBaseData& other, bool temp=false)  : GameBaseData(other, temp){ }
+
+  virtual void    gather_cons_defs(Vector<afxConstraintDef>& defs) { };
+
+  DECLARE_CONOBJECT(afxEffectBaseData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//class afxEffectWrapperData : public GameBaseData, public afxEffectDefs
+class afxEffectWrapperData : public afxEffectBaseData
+{
+  //typedef GameBaseData  Parent;
+  typedef afxEffectBaseData  Parent;
+
+  bool                  do_id_convert;
+
+public:
+  enum  { MAX_CONDITION_STATES = 4 };
+
+public:
+  StringTableEntry      effect_name; 
+  bool                  use_as_cons_obj;    
+  bool                  use_ghost_as_cons_obj;    
+
+  StringTableEntry      cons_spec; 
+  StringTableEntry      pos_cons_spec;
+  StringTableEntry      orient_cons_spec;
+  StringTableEntry      aim_cons_spec;
+  StringTableEntry      life_cons_spec;
+  //
+  afxConstraintDef      cons_def;
+  afxConstraintDef      pos_cons_def;
+  afxConstraintDef      orient_cons_def;
+  afxConstraintDef      aim_cons_def;
+  afxConstraintDef      life_cons_def;
+
+  afxEffectTimingData   ewd_timing;
+  U32                   inherit_timing;
+  
+  F32                   scale_factor;       // scale size if applicable
+  F32                   rate_factor;        // scale rate if applicable
+  F32                   user_fade_out_time;
+
+  bool                  is_looping;
+  U32                   n_loops;
+  F32                   loop_gap_time;
+
+  bool                  ignore_time_factor;
+  bool                  propagate_time_factor;
+
+  ByteRange             ranking_range;
+  ByteRange             lod_range;
+  S32                   life_conds;
+  bool                  effect_enabled;
+  U32                   exec_cond_on_bits[MAX_CONDITION_STATES];
+  U32                   exec_cond_off_bits[MAX_CONDITION_STATES];
+  U32                   exec_cond_bitmasks[MAX_CONDITION_STATES];
+
+  S32                   data_ID;
+
+  afxXM_BaseData*       xfm_modifiers[MAX_XFM_MODIFIERS];
+
+  Box3F                 forced_bbox;
+  bool                  update_forced_bbox;
+
+  S8                    sort_priority;
+  Point3F               direction;
+  F32                   speed;
+  F32                   mass;
+
+  bool                  borrow_altitudes;
+  StringTableEntry      vis_keys_spec;
+  afxAnimCurve*         vis_keys;
+
+  SimDataBlock*         effect_data;
+  afxEffectAdapterDesc* effect_desc;
+
+  S32                   group_index;
+
+  void                  parse_cons_specs();
+  void                  parse_vis_keys();
+  void                  gather_cons_defs(Vector<afxConstraintDef>& defs);
+  void                  pack_mods(BitStream*, afxXM_BaseData* mods[], bool packed);
+  void                  unpack_mods(BitStream*, afxXM_BaseData* mods[]);
+
+public:
+  /*C*/             afxEffectWrapperData();
+  /*C*/             afxEffectWrapperData(const afxEffectWrapperData&, bool = false);
+  /*D*/             ~afxEffectWrapperData();
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  bool              preload(bool server, String &errorStr);
+
+  virtual void      onPerformSubstitutions();
+
+  bool              requiresStop(const afxEffectTimingData& timing) { return effect_desc->requiresStop(this, timing); }
+  bool              runsOnServer() { return effect_desc->runsOnServer(this); }
+  bool              runsOnClient() { return effect_desc->runsOnClient(this); }
+  bool              runsHere(bool server_here) { return (server_here) ? runsOnServer() : runsOnClient(); }
+  bool              isPositional() { return effect_desc->isPositional(this); }
+  bool              testExecConditions(U32 conditions);
+
+  F32               afterStopTime() { return ewd_timing.fade_out_time; }
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectWrapperData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxEffectWrapperData::testExecConditions(U32 conditions)
+{
+  if (exec_cond_bitmasks[0] == 0)
+    return true;
+
+  if ((exec_cond_bitmasks[0] & conditions) == exec_cond_on_bits[0])
+    return true;
+
+  for (S32 i = 1; i < MAX_CONDITION_STATES; i++)
+  {
+    if (exec_cond_bitmasks[i] == 0)
+      return false;
+    if ((exec_cond_bitmasks[i] & conditions) == exec_cond_on_bits[i])
+      return true;
+  }
+  return false;
+}
+
+typedef Vector<afxEffectBaseData*> afxEffectList;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectWrapper 
+//
+//  NOTE -- this not a subclass of GameBase... it is only meant to exist on
+//    the client-side.
+
+class ShapeBase;
+class GameBase;
+class TSShape;
+class TSShapeInstance;
+class SceneObject;
+class afxConstraint;
+class afxConstraintMgr;
+class afxChoreographer;
+class afxXM_Base;
+
+class afxEffectWrapper : public SimObject,  public afxEffectDefs
+{
+  typedef SimObject Parent;
+  friend class afxEffectVector;
+
+private:
+  bool              test_life_conds();
+
+protected:
+  afxEffectWrapperData* datablock;
+
+  afxEffectTimingData   ew_timing;
+
+  F32               fade_in_end;
+  F32               fade_out_start;
+  F32               full_lifetime;
+
+  F32               time_factor;
+  F32               prop_time_factor;
+
+  afxChoreographer* choreographer;
+  afxConstraintMgr* cons_mgr;
+
+  afxConstraintID   pos_cons_id;
+  afxConstraintID   orient_cons_id;
+  afxConstraintID   aim_cons_id;
+  afxConstraintID   life_cons_id;
+
+  afxConstraintID   effect_cons_id;
+
+  F32               elapsed;
+  F32               life_elapsed;
+  F32               life_end;
+  bool              stopped;
+  bool              cond_alive;
+
+  U32               n_updates;
+
+  MatrixF           updated_xfm;
+  Point3F           updated_pos;
+  Point3F           updated_aim;
+  Point3F           updated_scale;
+  LinearColorF            updated_color;
+
+  F32               fade_value;
+  F32               last_fade_value;
+
+  bool              do_fade_inout;
+  bool              do_fades;
+  bool              in_scope;
+  bool              is_aborted;
+
+  U8                effect_flags;
+
+  afxXM_Base*       xfm_modifiers[MAX_XFM_MODIFIERS];
+
+  F32               live_scale_factor;
+  F32               live_fade_factor;
+  F32               terrain_altitude;
+  F32               interior_altitude;
+
+  S32               group_index;
+
+public:
+  /*C*/             afxEffectWrapper();
+  virtual           ~afxEffectWrapper();
+
+  void              ew_init(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, 
+                            F32 time_factor);
+
+  F32               getFullLifetime() { return ew_timing.lifetime + ew_timing.fade_out_time; }
+  F32               getTimeFactor() { return time_factor; }
+  afxConstraint*    getPosConstraint() { return cons_mgr->getConstraint(pos_cons_id); }
+  afxConstraint*    getOrientConstraint() { return cons_mgr->getConstraint(orient_cons_id); }
+  afxConstraint*    getAimConstraint() { return cons_mgr->getConstraint(aim_cons_id); }
+  afxConstraint*    getLifeConstraint() { return cons_mgr->getConstraint(life_cons_id); }
+  afxChoreographer* getChoreographer() { return choreographer; }
+
+  virtual bool      isDone();
+  virtual bool      deleteWhenStopped() { return false; }
+  F32               afterStopTime() { return ew_timing.fade_out_time; } 
+  bool              isAborted() const { return is_aborted; }
+
+  void              prestart();
+  bool              start(F32 timestamp);
+  bool              update(F32 dt);
+  void              stop();
+  void              cleanup(bool was_stopped=false);
+  void              setScopeStatus(bool flag);
+
+  virtual void      ea_set_datablock(SimDataBlock*) { }
+  virtual bool      ea_start() { return true; }
+  virtual bool      ea_update(F32 dt) { return true; }
+  virtual void      ea_finish(bool was_stopped) { }
+  virtual void      ea_set_scope_status(bool flag) { }
+  virtual bool      ea_is_enabled() { return true; }
+  virtual SceneObject* ea_get_scene_object() const { return 0; }
+  U32               ea_get_triggers() const { return 0; }
+
+  void              getUpdatedPosition(Point3F& pos) { pos = updated_pos;}
+  void              getUpdatedTransform(MatrixF& xfm) { xfm = updated_xfm; }
+  void              getUpdatedScale(Point3F& scale) { scale = updated_scale; }
+  void              getUpdatedColor(LinearColorF& color) { color = updated_color; }
+  virtual void      getUpdatedBoxCenter(Point3F& pos) { pos = updated_pos;}
+
+  virtual void      getUnconstrainedPosition(Point3F& pos) { pos.zero();}
+  virtual void      getUnconstrainedTransform(MatrixF& xfm) { xfm.identity(); }
+  virtual void      getBaseColor(LinearColorF& color) { color.set(1.0f, 1.0f, 1.0f, 1.0f); }
+
+  SceneObject*      getSceneObject() const { return ea_get_scene_object(); }
+  U32               getTriggers() const { return ea_get_triggers(); }
+
+  F32               getMass() { return datablock->mass; }
+  Point3F           getDirection() { return datablock->direction; }
+  F32               getSpeed() { return datablock->speed; }
+
+  virtual TSShape*          getTSShape() { return 0; }
+  virtual TSShapeInstance*  getTSShapeInstance() { return 0; }
+
+  virtual U32       setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans) { return 0; }
+  virtual void      resetAnimation(U32 tag) { }
+  virtual F32       getAnimClipDuration(const char* clip) { return 0.0f; }
+
+  void              setTerrainAltitude(F32 alt) { terrain_altitude = alt; }
+  void              setInteriorAltitude(F32 alt) { interior_altitude = alt; }
+  void              getAltitudes(F32& terr_alt, F32& inter_alt) const { terr_alt = terrain_altitude; inter_alt = interior_altitude; }
+
+  void              setGroupIndex(S32 idx) { group_index = idx; }
+  S32               getGroupIndex() const { return group_index; }
+
+  bool              inScope() const { return in_scope; }
+
+public:
+  static void       initPersistFields();
+
+  static afxEffectWrapper* ew_create(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, F32 time_factor, S32 group_index=0);
+
+  DECLARE_CONOBJECT(afxEffectWrapper);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EFFECT_WRAPPER_H_

+ 1119 - 0
Engine/source/afx/afxEffectron.cpp

@@ -0,0 +1,1119 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxEffectron.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectronData::ewValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list. 
+//
+void afxEffectronData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxEffectronData* eff_data = dynamic_cast<afxEffectronData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (eff_data && ew)
+  {
+    eff_data->fx_list.push_back(*ew);
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class EffectronFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj) 
+  { 
+     afxEffectron* eff = dynamic_cast<afxEffectron*>(obj);
+     if (eff)
+       eff->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectronData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxEffectronData);
+
+ConsoleDocClass( afxEffectronData,
+   "@brief Defines the properties of an afxEffectron.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxEffectronData::afxEffectronData()
+{
+  duration = 0.0f;
+  n_loops = 1;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxEffectronData::afxEffectronData(const afxEffectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  duration = other.duration;
+  n_loops = other.n_loops;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+  fx_list = other.fx_list;
+}
+
+void afxEffectronData::reloadReset()
+{
+  fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxEffectronData)
+
+void afxEffectronData::initPersistFields()
+{
+  addField("duration",    TypeF32,      myOffset(duration),
+    "...");
+  addField("numLoops",    TypeS32,      myOffset(n_loops),
+    "...");
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
+  static ewValidator emptyValidator(0);
+  
+  addFieldV("addEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry), &emptyValidator,
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addEffect");
+}
+
+bool afxEffectronData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxEffectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxEffectronData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxEffectronData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(duration);
+  stream->write(n_loops);
+
+  pack_fx(stream, fx_list, packed);
+}
+
+void afxEffectronData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&duration);
+  stream->read(&n_loops);
+
+  do_id_convert = true;
+  unpack_fx(stream, fx_list);
+}
+
+bool afxEffectronData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < fx_list.size(); i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, fx_list[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General, 
+              "afxEffectronData::preload() -- bad datablockId: 0x%x", 
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxEffectronData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, fx_list);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxEffectronData, reset, void, (),,
+                   "Resets an effectron datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxEffectronData, addEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to an effectron's phase.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect) 
+  {
+    Con::errorf("afxEffectronData::addEffect() -- missing afxEffectWrapperData.");
+    return;
+  }
+  
+  object->fx_list.push_back(effect);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectron
+
+IMPLEMENT_CO_NETOBJECT_V1(afxEffectron);
+
+ConsoleDocClass( afxEffectron,
+   "@brief A basic effects choreographer.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+StringTableEntry  afxEffectron::CAMERA_CONS;
+StringTableEntry  afxEffectron::LISTENER_CONS;
+
+void afxEffectron::init()
+{
+  // setup static predefined constraint names
+  if (CAMERA_CONS == 0)
+  {
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+  }
+
+  // afxEffectron is always in scope, however the effects used 
+  // do their own scoping in that they will shut off if their 
+  // position constraint leaves scope.
+  //
+  //   note -- ghosting is delayed until constraint 
+  //           initialization is done.
+  //
+  //mNetFlags.set(Ghostable | ScopeAlways);
+  mNetFlags.clear(Ghostable | ScopeAlways);
+
+  datablock = NULL;
+  exeblock = NULL;
+
+  constraints_initialized = false;
+  scoping_initialized = false;
+
+  effect_state = (U8) INACTIVE_STATE;
+  effect_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+
+  active_phrase = NULL;
+  time_factor = 1.0f;
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+}
+
+afxEffectron::afxEffectron()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxEffectron::afxEffectron(bool not_default)
+{
+  started_with_newop = false;
+  init();
+}
+
+afxEffectron::~afxEffectron()
+{
+  delete active_phrase;
+
+  if (datablock && datablock->isTempClone())
+  {
+    delete datablock;
+    datablock = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxEffectron::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxEffectronData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  if (isServerObject() && started_with_newop)
+  {
+    // copy dynamic fields from the datablock but
+    // don't replace fields with a value
+    assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true);
+  }
+
+  exeblock = datablock;
+
+  if (isClientObject())
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxEffectronData* orig_db = datablock;
+      datablock = new afxEffectronData(*orig_db, true);
+      exeblock = orig_db;
+      // Don't perform substitutions yet, the effectrons's dynamic fields haven't
+      // arrived yet and the substitutions may refer to them. Hold off and do
+      // in in the onAdd() method.
+    }  
+  }
+  else if (started_with_newop)
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxEffectronData* orig_db = datablock;
+      datablock = new afxEffectronData(*orig_db, true);
+      exeblock = orig_db;
+      orig_db->performSubstitutions(datablock, this, ranking);
+    }
+  }
+
+  return true;
+}
+
+void afxEffectron::processTick(const Move* m)
+{
+	Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxEffectron::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxEffectron::onAdd()
+{
+  if (!Parent::onAdd()) 
+    return false;
+
+  if (isClientObject())
+  {
+    if (datablock->isTempClone())
+    {
+      afxEffectronData* orig_db = (afxEffectronData*)exeblock;
+      orig_db->performSubstitutions(datablock, this, ranking);
+    }  
+  }
+  else if (started_with_newop && !postpone_activation)
+  {
+    if (!activationCallInit())
+      return false;
+    activate();
+  }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxEffectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  S32 mark_stream_pos = stream->getCurPos();
+
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    // pack extra object's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(dynamic_cast<NetObject*>(extra) != 0))
+    {
+      NetObject* net_extra = (NetObject*)extra;
+      S32 ghost_idx = conn->getGhostIndex(net_extra);
+      if (stream->writeFlag(ghost_idx != -1))
+         stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(net_extra->getScopeId() > 0))
+        {
+          stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    stream->write(time_factor);
+
+    GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+    bool zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(effect_state);
+    stream->write(effect_elapsed);
+  }
+
+  // SyncEvent
+  bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask));
+  if (stream->writeFlag(do_sync_event))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  check_packet_usage(conn, stream, mark_stream_pos, "afxEffectron:");
+  AssertISV(stream->isValid(), "afxEffectron::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); 
+  
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectron::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U8 new_marks_mask = 0;
+  U8 new_state = INACTIVE_STATE;
+  F32 new_elapsed = 0;
+
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {    
+    initial_update = true;
+
+    // extra sent
+    if (stream->readFlag())
+    {
+      // cleanup?
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        extra = dynamic_cast<SimObject*>(conn->resolveGhost(ghost_idx));
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          stream->readInt(NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    stream->read(&time_factor);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_state);
+    stream->read(&new_elapsed);
+
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  do_sync_event = stream->readFlag();
+  if (do_sync_event)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    effect_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_state, new_elapsed);
+}
+
+void afxEffectron::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxEffectron::state_expired()
+{
+  afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? active_phrase : NULL;
+
+  if (phrase)
+  {
+    if (phrase->expired(effect_elapsed))
+      return (!phrase->recycle(effect_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+void afxEffectron::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isServerObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+  }
+  else // if (isClientObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxEffectron::init_scoping()
+{
+  if (scoping_initialized)
+  {
+    //Con::printf("SCOPING ALREADY INITIALIZED");
+    return;
+  }
+
+  if (isServerObject())
+  {
+    if (explicit_clients.size() > 0)
+    {
+      for (U32 i = 0; i < explicit_clients.size(); i++)
+        explicit_clients[i]->objectLocalScopeAlways(this);
+    }
+    else
+    {
+      mNetFlags.set(Ghostable);
+      setScopeAlways();
+    }
+    scoping_initialized = true;
+  }
+}
+
+void afxEffectron::setup_active_fx()
+{
+  active_phrase = new afxPhrase(isServerObject(), /*willStop=*/true);
+
+  if (active_phrase)
+  {
+    active_phrase->init(datablock->fx_list, datablock->duration, this, time_factor, datablock->n_loops);
+  }
+}
+
+bool afxEffectron::cleanup_over()
+{
+  if (active_phrase && !active_phrase->isEmpty())
+    return false;
+
+  return true;
+}
+
+void afxEffectron::inflictDamage(const char * label, const char* flavor, SimObjectId target_id,
+                                   F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse)
+{ 
+  char *posArg = Con::getArgBuffer(64);
+  dSprintf(posArg, 64, "%f %f %f", pos.x, pos.y, pos.z);
+  Con::executef(exeblock, "onDamage", 
+                              getIdString(), 
+                              label,
+                              flavor,
+                              Con::getIntArg(target_id),
+                              Con::getFloatArg(amount), 
+                              Con::getIntArg(n),
+                              posArg,
+                              Con::getFloatArg(ad_amount), 
+                              Con::getFloatArg(radius),
+                              Con::getFloatArg(impulse));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxEffectron::process_server()
+{
+  if (effect_state != INACTIVE_STATE)
+    effect_elapsed += TickSec;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_s(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  if (active_phrase)
+    active_phrase->update(TickSec, effect_elapsed);
+}
+
+void afxEffectron::change_state_s(U8 pending_state)
+{ 
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_s();
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxEffectron::enter_done_state_s()
+{ 
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  if (active_phrase)
+  {
+    F32 phrase_done;
+    if (active_phrase->willStop() && active_phrase->isInfinite())
+      phrase_done = effect_elapsed + active_phrase->calcAfterLife();
+    else
+      phrase_done = active_phrase->calcDoneTime();
+    if (phrase_done > done_time)
+      done_time = phrase_done;
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxEffectronData::onDeactivate(%eff)
+  Con::executef(exeblock, "onDeactivate", getIdString());
+}
+
+void afxEffectron::enter_active_state_s()
+{ 
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_active_fx();
+  if (active_phrase)
+    active_phrase->start(effect_elapsed, effect_elapsed);
+}
+
+void afxEffectron::leave_active_state_s()
+{ 
+  if (active_phrase)
+    active_phrase->stop(effect_elapsed);
+}
+
+void afxEffectron::enter_cleanup_state_s()
+{ 
+  postEvent(SHUTDOWN_EVENT);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxEffectron::process_client(F32 dt)
+{
+  effect_elapsed += dt;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_c(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  if (active_phrase)
+    active_phrase->update(dt, effect_elapsed);
+}
+
+void afxEffectron::change_state_c(U8 pending_state)
+{ 
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_c(effect_elapsed);
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+}
+
+void afxEffectron::enter_active_state_c(F32 starttime)
+{ 
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000));
+  ///effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  setup_active_fx();
+  if (active_phrase)
+    active_phrase->start(starttime, effect_elapsed);
+}
+
+void afxEffectron::leave_active_state_c()
+{ 
+  if (active_phrase)
+    active_phrase->stop(effect_elapsed);
+}
+
+void afxEffectron::sync_client(U16 marks, U8 state, F32 elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g", 
+  //            marks, effect_state, state, elapsed);
+
+  if (effect_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN))
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the active state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    effect_state = ACTIVE_STATE;
+    effect_elapsed = elapsed;
+    enter_active_state_c(0.0);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxEffectron::postEvent(U8 event) 
+{ 
+  setMaskBits(StateEventMask);
+  
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_EVENT:
+    marks_mask |= MARK_INTERRUPT;
+    break;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxEffectron::finish_startup()
+{
+  init_constraints();
+  init_scoping();
+  postEvent(afxEffectron::ACTIVATE_EVENT);
+}
+
+// static
+afxEffectron* 
+afxEffectron::start_effect(afxEffectronData* datablock, SimObject* extra) 
+{
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+
+  afxEffectronData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxEffectron: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  // CALL SCRIPT afxEffectronData::onPreactivate(%params, %extra)
+  const char* result = Con::executef(datablock, "onPreactivate", 
+                                     Con::getIntArg(param_holder->getId()), 
+                                     (extra) ? Con::getIntArg(extra->getId()) : "");
+  if (result && result[0] != '\0' && !dAtob(result))
+  {
+#if defined(TORQUE_DEBUG)
+    Con::warnf("afxEffectron: onPreactivate() returned false, effect aborted.");
+#endif
+    Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxEffectronData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new effectron instance
+  afxEffectron* eff = new afxEffectron(true);
+  eff->setDataBlock(datablock);
+  eff->exeblock = exeblock;
+  eff->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the effectron
+  eff->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!eff->registerObject())
+  {
+    Con::errorf("afxEffectron: failed to register effectron instance.");
+    Sim::postEvent(eff, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+  registerForCleanup(eff);
+
+  eff->activate();
+
+  return eff;
+}
+
+bool afxEffectron::activationCallInit(bool postponed) 
+{
+  if (postponed && (!started_with_newop || !postpone_activation))
+  {
+    Con::errorf("afxEffectron::activate() -- activate() is only required when creating an effectron with the \"new\" operator "
+                "and the postponeActivation field is set to \"true\".");
+    return false;
+  }
+
+  return true;
+}
+
+void afxEffectron::activate() 
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned effectron that  
+  // need to happen prior to constraint initialization.
+  Sim::postEvent(this, new EffectronFinishStartupEvent, Sim::getCurrentTime());
+
+  // CALL SCRIPT afxEffectronData::onActivate(%eff)
+  Con::executef(exeblock, "onActivate", getIdString());
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console functions
+
+DefineEngineMethod(afxEffectron, setTimeFactor, void, (float factor),,
+                   "Sets the time-factor for the effectron.\n\n"
+                   "@ingroup AFX") 
+{
+  object->setTimeFactor(factor);
+}
+
+DefineEngineMethod(afxEffectron, interrupt, void, (),,
+                   "Interrupts and deletes a running effectron.\n\n"
+                   "@ingroup AFX") 
+{
+  object->postEvent(afxEffectron::INTERRUPT_EVENT);
+}
+
+DefineEngineMethod(afxEffectron, activate, void, (),,
+                   "Activates an effectron that was started with postponeActivation=true.\n\n"
+                   "@ingroup AFX") 
+{
+  if (object->activationCallInit(true))
+    object->activate();
+}
+
+DefineEngineFunction(startEffectron, S32,	(afxEffectronData* datablock, const char* constraintSource, const char* constraintName, SimObject* extra),
+											(nullAsType<afxEffectronData*>(), nullAsType<const char*>(), nullAsType<const char*>(), nullAsType<SimObject*>()),
+
+                     "Instantiates the effectron defined by datablock.\n\n"
+                     "@ingroup AFX")
+{
+  if (!datablock)
+  {
+    Con::errorf("startEffectron() -- missing valid datablock.");
+    return 0;
+  }
+
+  //
+  // Start the Effectron
+  //
+  afxEffectron* eff = afxEffectron::start_effect(datablock, extra);
+
+  //
+  // Create a constraint from arguments (if specified).
+  //
+  if (eff)
+  {
+    if (constraintSource && constraintName)
+    {
+      if (!eff->addConstraint(constraintSource, constraintName))
+        Con::errorf("startEffectron() -- failed to find constraint object [%s].", constraintSource);
+    }
+  }
+
+  //
+  // Return the ID (or 0 if start failed).
+  //
+  return (eff) ? eff->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 216 - 0
Engine/source/afx/afxEffectron.h

@@ -0,0 +1,216 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COMPOSITE_EFFECT_H_
+#define _AFX_COMPOSITE_EFFECT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+#include "afxPhrase.h"
+
+class afxChoreographerData;
+class afxEffectWrapperData;
+
+class afxEffectronData : public afxChoreographerData
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  F32           duration;
+  S32           n_loops;
+
+  afxEffectBaseData* dummy_fx_entry;
+
+  afxEffectList fx_list;
+  
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/         afxEffectronData();
+  /*C*/         afxEffectronData(const afxEffectronData&, bool = false);
+
+  virtual void  reloadReset();
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  void          gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxEffectronData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxEffectron
+
+class afxEffectron : public afxChoreographer
+{
+  typedef afxChoreographer Parent;
+
+public:
+  enum MaskBits 
+  {
+    StateEventMask    = Parent::NextFreeMask << 0,
+    SyncEventMask     = Parent::NextFreeMask << 1,
+    NextFreeMask      = Parent::NextFreeMask << 2
+  };
+
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    ACTIVE_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE   = BIT(0),
+    MARK_SHUTDOWN   = BIT(1),
+    MARK_DEACTIVATE = BIT(2),
+    MARK_INTERRUPT  = BIT(3),
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+
+private:
+  afxEffectronData*  datablock;
+  SimObject*         exeblock;
+
+  bool          constraints_initialized;
+  bool          scoping_initialized;
+
+  U8            effect_state;
+  F32           effect_elapsed;
+  U8            marks_mask;
+  afxConstraintID listener_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+  afxPhrase*    active_phrase;
+  F32           time_factor;
+
+private:
+  void          init();
+  bool          state_expired();
+  void          init_constraints();
+  void          init_scoping();
+  void          setup_active_fx();
+  bool          cleanup_over();
+
+public:
+  /*C*/         afxEffectron();
+  /*C*/         afxEffectron(bool not_default);
+  /*D*/         ~afxEffectron();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                              F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp);
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  DECLARE_CONOBJECT(afxEffectron);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_active_state_s();
+  void          leave_active_state_s();
+  void          enter_cleanup_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_active_state_c(F32 starttime);
+  void          leave_active_state_c();
+
+  void          sync_client(U16 marks, U8 state, F32 elapsed);
+
+public:
+  void          postEvent(U8 event);
+  void          setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return time_factor; }
+
+  bool          activationCallInit(bool postponed=false);
+  void          activate();
+
+public:
+  static afxEffectron*  start_effect(afxEffectronData*, SimObject* extra);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_EFFECTRON_H_

+ 2116 - 0
Engine/source/afx/afxMagicMissile.cpp

@@ -0,0 +1,2116 @@
+//-----------------------------------------------------------------------------
+// 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.
+//
+// afxMagicMissile is a heavily modified variation of the stock Projectile class. In 
+// addition to numerous AFX customizations, it also incorporates functionality based on
+// the following TGE resources:
+//
+// Guided or Seeker Projectiles by Derk Adams
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
+//
+// Projectile Ballistic Coefficients (drag factors) by Mark Owen
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "afx/arcaneFX.h"
+
+#include "scene/sceneRenderState.h"
+#include "scene/sceneManager.h"
+#include "core/resourceManager.h"
+#include "ts/tsShapeInstance.h"
+#include "sfx/sfxTrack.h"
+#include "sfx/sfxSource.h"
+#include "sfx/sfxSystem.h"
+#include "sfx/sfxTypes.h"
+#include "math/mathUtils.h"
+#include "math/mathIO.h"
+#include "sim/netConnection.h"
+#include "T3D/fx/particleEmitter.h"
+#include "T3D/fx/splash.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/physics/physicsWorld.h"
+#include "gfx/gfxTransformSaver.h"
+#include "T3D/containerQuery.h"
+#include "T3D/lightDescription.h"
+#include "console/engineAPI.h"
+#include "lighting/lightManager.h"
+
+#include "afx/util/afxEase.h"
+#include "afx/afxMagicMissile.h"
+#include "afx/afxMagicSpell.h"
+#include "afx/afxChoreographer.h"
+
+class ObjectDeleteEvent : public SimEvent
+{
+public:
+  void process(SimObject *object)
+  {
+    object->deleteObject();
+  }
+};
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData);
+
+ConsoleDocClass( afxMagicMissileData,
+   "@brief Defines a particular magic-missile type. (Use with afxMagicSpellData.)\n"
+   "@tsexample\n"
+   "datablock afxMagicMissileData(Fireball_MM)\n"
+   "{\n"
+   "  muzzleVelocity = 50;\n"
+   "  velInheritFactor = 0;\n"
+   "  lifetime = 20000;\n"
+   "  isBallistic = true;\n"
+   "  ballisticCoefficient = 0.85;\n"
+   "  gravityMod = 0.05;\n"
+   "  isGuided = true;\n"
+   "  precision = 30;\n"
+   "  trackDelay = 7;\n"
+   "  launchOffset = \"0 0 43.7965\";\n"
+   "  launchOnServerSignal = true;\n"
+   "};\n"
+   "@endtsexample\n"
+   "@ingroup AFX\n"
+);
+
+IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile);
+
+ConsoleDocClass( afxMagicMissile,
+   "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using afxMagicMissileData.\n"
+   "@ingroup AFX\n"
+);
+
+/* From stock Projectile code...
+IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ), 
+                   ( proj, pos, fade ),
+					"Called when a projectile explodes.\n"
+               "@param proj The projectile exploding.\n"
+					"@param pos The position of the explosion.\n"
+					"@param fade The currently fadeValue of the projectile, affects its visibility.\n"
+					"@see Projectile, ProjectileData\n"
+				  );
+
+IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ),
+                   ( proj, col, fade, pos, normal ),
+					"Called when a projectile collides with another object.\n"
+					"@param proj The projectile colliding.\n"
+					"@param col The object hit by the projectile.\n"
+					"@param fade The current fadeValue of the projectile, affects its visibility.\n"
+					"@param pos The collision position.\n"
+               "@param normal The collision normal.\n"
+					"@see Projectile, ProjectileData\n"
+				  );
+
+const U32 Projectile::csmStaticCollisionMask =  TerrainObjectType    |
+                                                InteriorObjectType   |
+                                                StaticObjectType;
+
+const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType        |
+                                                VehicleObjectType       |
+                                                DamagableItemObjectType;
+
+const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask;
+
+U32 Projectile::smProjectileWarpTicks = 5;
+*/
+
+//--------------------------------------------------------------------------
+//
+afxMagicMissileData::afxMagicMissileData()
+{
+   projectileShapeName = ST_NULLSTRING;
+
+   sound = NULL;
+
+   /* From stock Projectile code...
+   explosion = NULL;
+   explosionId = 0;
+
+   waterExplosion = NULL;
+   waterExplosionId = 0;
+   */
+
+   /* From stock Projectile code...
+   faceViewer = false;
+   */
+   scale.set( 1.0f, 1.0f, 1.0f );
+
+   isBallistic = false;
+
+   /* From stock Projectile code...
+	velInheritFactor = 1.0f;
+   */
+	muzzleVelocity = 50;
+   /* From stock Projectile code...
+   impactForce = 0.0f;
+
+	armingDelay = 0;
+   fadeDelay = 20000 / 32;
+   lifetime = 20000 / 32;
+
+   activateSeq = -1;
+   maintainSeq = -1;
+   */
+
+   gravityMod = 1.0;
+   /* From stock Projectile code...
+   bounceElasticity = 0.999f;
+   bounceFriction = 0.3f;
+   */
+
+   particleEmitter = NULL;
+   particleEmitterId = 0;
+
+   particleWaterEmitter = NULL;
+   particleWaterEmitterId = 0;
+
+   splash = NULL;
+   splashId = 0;
+
+   /* From stock Projectile code...
+   decal = NULL;
+   decalId = 0;
+   */
+
+   lightDesc = NULL;
+   lightDescId = 0;
+   
+  starting_vel_vec.zero();
+
+  isGuided = false;
+  precision = 0; 
+  trackDelay = 0;
+  ballisticCoefficient = 1.0f; 
+
+  followTerrain = false;
+  followTerrainHeight = 0.1f;
+  followTerrainAdjustRate = 20.0f;
+  followTerrainAdjustDelay = 0;
+
+  lifetime = MaxLifetimeTicks;
+  collision_mask = arcaneFX::sMissileCollisionMask;
+
+  acceleration = 0;
+  accelDelay = 0;
+  accelLifetime = 0;
+
+  launch_node = ST_NULLSTRING;
+  launch_offset.zero();
+  launch_offset_server.zero();
+  launch_offset_client.zero();
+  launch_node_offset.zero();
+  launch_pitch = 0;
+  launch_pan = 0;
+  launch_cons_s_spec = ST_NULLSTRING;
+  launch_cons_c_spec = ST_NULLSTRING;
+
+  echo_launch_offset = false;
+
+  wiggle_axis_string = ST_NULLSTRING;
+  wiggle_num_axis = 0;
+  wiggle_axis = 0;
+
+  hover_altitude = 0;
+  hover_attack_distance = 0;
+  hover_attack_gradient = 0;
+  hover_time = 0;
+
+  reverse_targeting = false;
+
+  caster_safety_time = U32_MAX;
+}
+
+afxMagicMissileData::afxMagicMissileData(const afxMagicMissileData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  projectileShapeName = other.projectileShapeName;
+  projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName
+  sound = other.sound;
+  splash = other.splash;
+  splashId = other.splashId; // -- for pack/unpack of splash ptr
+  lightDesc = other.lightDesc;
+  lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr
+  scale = other.scale;
+  isBallistic = other.isBallistic;
+  muzzleVelocity = other.muzzleVelocity;
+  gravityMod = other.gravityMod;
+  particleEmitter = other.particleEmitter;
+  particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr
+  particleWaterEmitter = other.particleWaterEmitter;
+  particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr
+
+  collision_mask = other.collision_mask;
+  starting_vel_vec = other.starting_vel_vec;
+  isGuided = other.isGuided;
+  precision = other.precision;
+  trackDelay = other.trackDelay;
+  ballisticCoefficient = other.ballisticCoefficient;
+  followTerrain  = other.followTerrain;
+  followTerrainHeight = other.followTerrainHeight;
+  followTerrainAdjustRate = other.followTerrainAdjustRate;
+  followTerrainAdjustDelay = other.followTerrainAdjustDelay;
+  lifetime = other.lifetime;
+  fadeDelay = other.fadeDelay;
+  acceleration = other.acceleration;
+  accelDelay = other.accelDelay;
+  accelLifetime = other.accelLifetime;
+  launch_node  = other.launch_node;
+  launch_offset = other.launch_offset;
+  launch_offset_server = other.launch_offset_server;
+  launch_offset_client = other.launch_offset_client;
+  launch_node_offset = other.launch_node_offset;
+  launch_pitch = other.launch_pitch;
+  launch_pan = other.launch_pan;
+  launch_cons_s_spec = other.launch_cons_s_spec;
+  launch_cons_c_spec = other.launch_cons_c_spec;
+  launch_cons_s_def = other.launch_cons_s_def;
+  launch_cons_c_def = other.launch_cons_c_def;
+  echo_launch_offset = other.echo_launch_offset;
+  wiggle_magnitudes = other.wiggle_magnitudes;
+  wiggle_speeds = other.wiggle_speeds;
+  wiggle_axis_string = other.wiggle_axis_string;
+  wiggle_num_axis = other.wiggle_num_axis;
+  wiggle_axis = other.wiggle_axis;
+  hover_altitude = other.hover_altitude;
+  hover_attack_distance = other.hover_attack_distance;
+  hover_attack_gradient = other.hover_attack_gradient;
+  hover_time = other.hover_time;
+  reverse_targeting = other.reverse_targeting;
+  caster_safety_time = other.caster_safety_time;
+}
+
+afxMagicMissileData::~afxMagicMissileData()
+{
+  if (wiggle_axis)
+    delete [] wiggle_axis;
+}
+
+afxMagicMissileData* afxMagicMissileData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+  if (!owner || getSubstitutionCount() == 0)
+    return this;
+
+  afxMagicMissileData* sub_missile_db = new afxMagicMissileData(*this, true);
+  performSubstitutions(sub_missile_db, owner, index);
+
+  return sub_missile_db;
+}
+
+//--------------------------------------------------------------------------
+
+#define myOffset(field) Offset(field, afxMagicMissileData)
+
+FRangeValidator muzzleVelocityValidator(0, 10000);
+FRangeValidator missilePrecisionValidator(0.f, 100.f);
+FRangeValidator missileTrackDelayValidator(0, 100000);
+FRangeValidator missileBallisticCoefficientValidator(0, 1);
+
+void afxMagicMissileData::initPersistFields()
+{
+   static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks);
+
+   addField("particleEmitter", TYPEID<ParticleEmitterData>(), Offset(particleEmitter, afxMagicMissileData));
+   addField("particleWaterEmitter", TYPEID<ParticleEmitterData>(), Offset(particleWaterEmitter, afxMagicMissileData));
+
+   addField("projectileShapeName", TypeFilename, Offset(projectileShapeName, afxMagicMissileData));
+   addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData));
+
+   addField("sound", TypeSFXTrackName, Offset(sound, afxMagicMissileData));
+
+   /* From stock Projectile code...
+   addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData));
+   addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData));
+   */
+
+   addField("splash", TYPEID<SplashData>(), Offset(splash, afxMagicMissileData));
+   /* From stock Projectile code...
+   addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData));
+   */
+
+   addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData));
+
+   addField("isBallistic", TypeBool,   Offset(isBallistic, afxMagicMissileData));
+   /* From stock Projectile code...
+   addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData));
+   */
+   addNamedFieldV(muzzleVelocity,    TypeF32,      afxMagicMissileData,  &muzzleVelocityValidator);
+   /* From stock Projectile code...
+   addField("impactForce", TypeF32, Offset(impactForce, ProjectileData));
+   */
+   addNamedFieldV(lifetime,    TypeS32,              afxMagicMissileData,  &ticksFromMS);
+   /* From stock Projectile code...
+   addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, 
+      "The time in milliseconds before the projectile is armed and will cause damage or explode on impact." );
+
+   addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue,
+      "The time in milliseconds when the projectile begins to fade out.  Must be less than the lifetime to have an effect." );
+
+   addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData));
+   addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData));
+   */
+   addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData));
+
+   // FIELDS ADDED BY MAGIC-MISSILE
+
+   addField("missileShapeName",    TypeFilename, myOffset(projectileShapeName));
+   addField("missileShapeScale",   TypePoint3F,  myOffset(scale));
+
+   addField("startingVelocityVector",TypePoint3F,  myOffset(starting_vel_vec));
+
+   addNamedField(isGuided,               TypeBool,   afxMagicMissileData);
+   addNamedFieldV(precision,             TypeF32,    afxMagicMissileData,  &missilePrecisionValidator); 
+   addNamedFieldV(trackDelay,            TypeS32,    afxMagicMissileData,  &missileTrackDelayValidator); 
+   addNamedFieldV(ballisticCoefficient,  TypeF32,    afxMagicMissileData,  &missileBallisticCoefficientValidator);
+
+   addField("collisionMask",         TypeS32,      myOffset(collision_mask));
+
+   addField("followTerrain",             TypeBool, myOffset(followTerrain));
+   addField("followTerrainHeight",       TypeF32,  myOffset(followTerrainHeight));
+   addField("followTerrainAdjustRate",   TypeF32,  myOffset(followTerrainAdjustRate));
+   addFieldV("followTerrainAdjustDelay", TypeS32,  myOffset(followTerrainAdjustDelay), &ticksFromMS); 
+
+   addNamedField(acceleration,     TypeF32,  afxMagicMissileData);
+   addNamedFieldV(accelDelay,      TypeS32,  afxMagicMissileData,  &ticksFromMS);
+   addNamedFieldV(accelLifetime,   TypeS32,  afxMagicMissileData,  &ticksFromMS);
+
+   addField("launchNode",        TypeString,   myOffset(launch_node));
+   addField("launchOffset",      TypePoint3F,  myOffset(launch_offset));
+   addField("launchOffsetServer",TypePoint3F,  myOffset(launch_offset_server));
+   addField("launchOffsetClient",TypePoint3F,  myOffset(launch_offset_client));
+   addField("launchNodeOffset",  TypePoint3F,  myOffset(launch_node_offset));
+   addField("launchAimPitch",    TypeF32,      myOffset(launch_pitch));
+   addField("launchAimPan",      TypeF32,      myOffset(launch_pan));
+   addField("launchConstraintServer",  TypeString,   myOffset(launch_cons_s_spec));
+   addField("launchConstraintClient",  TypeString,   myOffset(launch_cons_c_spec));
+   //
+   addField("echoLaunchOffset",  TypeBool,     myOffset(echo_launch_offset));
+
+   addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes));
+   addField("wiggleSpeeds",     TypeF32Vector, myOffset(wiggle_speeds));
+   addField("wiggleAxis",       TypeString,    myOffset(wiggle_axis_string));
+
+   addField("hoverAltitude",       TypeF32,    myOffset(hover_altitude));
+   addField("hoverAttackDistance", TypeF32,    myOffset(hover_attack_distance));
+   addField("hoverAttackGradient", TypeF32,    myOffset(hover_attack_gradient));
+   addFieldV("hoverTime",          TypeS32,    myOffset(hover_time), &ticksFromMS); 
+
+   addField("reverseTargeting",  TypeBool,     myOffset(reverse_targeting));
+
+   addFieldV("casterSafetyTime", TypeS32,      myOffset(caster_safety_time), &ticksFromMS); 
+
+   Parent::initPersistFields();
+
+   // disallow some field substitutions
+   onlyKeepClearSubstitutions("particleEmitter"); // subs resolving to "~~", or "~0" are OK
+   onlyKeepClearSubstitutions("particleWaterEmitter");
+   onlyKeepClearSubstitutions("sound");
+   onlyKeepClearSubstitutions("splash");
+}
+
+
+//--------------------------------------------------------------------------
+bool afxMagicMissileData::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   // ADDED BY MAGIC-MISSILE
+
+   // Wiggle axes ////////////////////////////////////////////////////////////  
+   if (wiggle_axis_string != ST_NULLSTRING && wiggle_num_axis == 0)   
+   {
+      // Tokenize input string and convert to Point3F array
+      //
+      Vector<char*> dataBlocks(__FILE__, __LINE__);
+
+      // make a copy of points_string
+      char* tokCopy = new char[dStrlen(wiggle_axis_string) + 1];
+      dStrcpy(tokCopy, wiggle_axis_string);
+
+      // extract tokens one by one, adding them to dataBlocks
+      char* currTok = dStrtok(tokCopy, " \t");
+      while (currTok != NULL) 
+      {
+         dataBlocks.push_back(currTok);
+         currTok = dStrtok(NULL, " \t");
+      }
+
+      // bail if there were no tokens in the string
+      if (dataBlocks.size() == 0) 
+      {
+         Con::warnf(ConsoleLogEntry::General, "afxMagicMissileData(%s) invalid wiggle axis string. No tokens found", getName());
+         delete [] tokCopy;
+         return false;
+      }
+
+      // Find wiggle_num_axis (round up to multiple of 3) // WARNING here if not multiple of 3?
+      for (U32 i = 0; i < dataBlocks.size()%3; i++) 
+      {
+         dataBlocks.push_back("0.0");
+      }
+
+      wiggle_num_axis = dataBlocks.size()/3;
+      wiggle_axis = new Point3F[wiggle_num_axis];
+
+      U32 p_i = 0;
+      for (U32 i = 0; i < dataBlocks.size(); i+=3, p_i++)
+      {
+         F32 x,y,z;
+         x = dAtof(dataBlocks[i]);  // What about overflow?
+         y = dAtof(dataBlocks[i+1]);
+         z = dAtof(dataBlocks[i+2]);
+         wiggle_axis[p_i].set(x,y,z);
+
+         wiggle_axis[p_i].normalizeSafe(); // sufficient????
+      }
+
+      delete [] tokCopy; 
+   }
+
+   launch_cons_s_def.parseSpec(launch_cons_s_spec, true, false);
+   launch_cons_c_def.parseSpec(launch_cons_c_spec, false, true);
+
+   return true;
+}
+
+
+bool afxMagicMissileData::preload(bool server, String &errorStr)
+{
+   if (Parent::preload(server, errorStr) == false)
+      return false;
+      
+   if( !server )
+   {
+      if (!particleEmitter && particleEmitterId != 0)
+         if (Sim::findObject(particleEmitterId, particleEmitter) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId);
+
+      if (!particleWaterEmitter && particleWaterEmitterId != 0)
+         if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId);
+
+      /* From stock Projectile code...
+      if (!explosion && explosionId != 0)
+         if (Sim::findObject(explosionId, explosion) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
+
+      if (!waterExplosion && waterExplosionId != 0)
+         if (Sim::findObject(waterExplosionId, waterExplosion) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId);
+      */
+
+      if (!splash && splashId != 0)
+         if (Sim::findObject(splashId, splash) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(splash): %d", splashId);
+
+      /* From stock Projectile code...
+      if (!decal && decalId != 0)
+         if (Sim::findObject(decalId, decal) == false)
+            Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId);
+      */
+
+      String errorStr;
+      if( !sfxResolve( &sound, errorStr ) )
+         Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", errorStr.c_str());
+
+      if (!lightDesc && lightDescId != 0)
+         if (Sim::findObject(lightDescId, lightDesc) == false)
+            Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId);   
+   }
+
+   if (projectileShapeName != ST_NULLSTRING) 
+   {
+      projectileShape = ResourceManager::get().load(projectileShapeName);
+      if (bool(projectileShape) == false)
+      {
+         errorStr = String::ToString("afxMagicMissileData::load: Couldn't load shape \"%s\"", projectileShapeName);
+         return false;
+      }
+      /* From stock Projectile code...
+      activateSeq = projectileShape->findSequence("activate");
+      maintainSeq = projectileShape->findSequence("maintain");
+      */
+   }
+
+   if (bool(projectileShape)) // create an instance to preload shape data
+   {
+      TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server);
+      delete pDummy;
+   }
+
+   return true;
+}
+
+//--------------------------------------------------------------------------
+// Modified from floorPlanRes.cc
+// Read a vector of items
+template <class T>
+bool readVector(Vector<T> & vec, Stream & stream, const char * msg)
+{
+   U32   num, i;
+   bool  Ok = true;
+   stream.read( & num );
+   vec.setSize( num );
+   for( i = 0; i < num && Ok; i++ ){
+      Ok = stream.read(& vec[i]);
+      AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) );
+   }
+   return Ok;
+}
+// Write a vector of items
+template <class T>
+bool writeVector(const Vector<T> & vec, Stream & stream, const char * msg)
+{
+   bool  Ok = true;
+   stream.write( vec.size() );
+   for( U32 i = 0; i < vec.size() && Ok; i++ ) {
+      Ok = stream.write(vec[i]);
+      AssertISV( Ok, avar("vec write error (%s) on elem %d", msg, i) );
+   }
+   return Ok;
+}
+//--------------------------------------------------------------------------
+
+void afxMagicMissileData::packData(BitStream* stream)
+{
+   Parent::packData(stream);
+
+   stream->writeString(projectileShapeName);
+   /* From stock Projectile code...
+   stream->writeFlag(faceViewer);
+   */
+   if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1))
+   {
+      stream->write(scale.x);
+      stream->write(scale.y);
+      stream->write(scale.z);
+   }
+
+   stream->write(collision_mask);
+
+   if (stream->writeFlag(particleEmitter != NULL))
+      stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst,
+                                                   DataBlockObjectIdLast);
+
+   if (stream->writeFlag(particleWaterEmitter != NULL))
+      stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst,
+                                                   DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->writeFlag(explosion != NULL))
+      stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst,
+                                                 DataBlockObjectIdLast);
+
+   if (stream->writeFlag(waterExplosion != NULL))
+      stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst,
+                                                      DataBlockObjectIdLast);
+   */
+
+   if (stream->writeFlag(splash != NULL))
+      stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst,
+                                              DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->writeFlag(decal != NULL))
+      stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst,
+                                              DataBlockObjectIdLast);
+   */
+
+   sfxWrite( stream, sound );
+
+   if ( stream->writeFlag(lightDesc != NULL))
+      stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst,
+                                                 DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   stream->write(impactForce);
+   */
+
+   stream->write(lifetime);
+   /* From stock Projectile code...
+   stream->write(armingDelay);
+   stream->write(fadeDelay);
+   */
+
+   if(stream->writeFlag(isBallistic))
+   {
+      stream->write(gravityMod);
+      /* From stock Projectile code...
+      stream->write(bounceElasticity);
+      stream->write(bounceFriction);
+      */
+      stream->write(ballisticCoefficient);
+   }
+
+   if(stream->writeFlag(isGuided))
+   {
+      stream->write(precision);
+      stream->write(trackDelay);
+   }
+
+   stream->write(muzzleVelocity);
+   mathWrite(*stream, starting_vel_vec);
+   stream->write(acceleration);
+   stream->write(accelDelay);
+   stream->write(accelLifetime);
+
+   stream->writeString(launch_node);
+   mathWrite(*stream, launch_offset);
+   mathWrite(*stream, launch_offset_server);
+   mathWrite(*stream, launch_offset_client);
+   mathWrite(*stream, launch_node_offset);
+   stream->write(launch_pitch);
+   stream->write(launch_pan);
+   stream->writeString(launch_cons_c_spec);
+   stream->writeFlag(echo_launch_offset);
+
+   writeVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
+   writeVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
+
+   stream->write(wiggle_num_axis);
+   for (U32 i = 0; i < wiggle_num_axis; i++)
+      mathWrite(*stream, wiggle_axis[i]);
+
+   stream->write(hover_altitude);
+   stream->write(hover_attack_distance);
+   stream->write(hover_attack_gradient);
+   stream->writeRangedU32(hover_time, 0, MaxLifetimeTicks);
+
+   stream->writeFlag(reverse_targeting);
+
+   stream->write(caster_safety_time);
+}
+
+void afxMagicMissileData::unpackData(BitStream* stream)
+{
+   Parent::unpackData(stream);
+
+   projectileShapeName = stream->readSTString();
+   /* From stock Projectile code...
+   faceViewer = stream->readFlag();
+   */
+   if(stream->readFlag())
+   {
+      stream->read(&scale.x);
+      stream->read(&scale.y);
+      stream->read(&scale.z);
+   }
+   else
+      scale.set(1,1,1);
+
+   stream->read(&collision_mask);
+
+   if (stream->readFlag())
+      particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   if (stream->readFlag())
+      particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->readFlag())
+      explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   if (stream->readFlag())
+      waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+   */
+   
+   if (stream->readFlag())
+      splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   if (stream->readFlag())
+      decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+   */
+   
+   sfxRead( stream, &sound );
+
+   if (stream->readFlag())
+      lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+   /* From stock Projectile code...
+   stream->read(&impactForce);
+   */
+
+   stream->read(&lifetime);
+   /* From stock Projectile code...
+   stream->read(&armingDelay);
+   stream->read(&fadeDelay);
+   */
+
+   isBallistic = stream->readFlag();
+   if(isBallistic)
+   {
+      stream->read(&gravityMod);
+      /* From stock Projectile code...
+      stream->read(&bounceElasticity);
+      stream->read(&bounceFriction);
+      */
+      stream->read(&ballisticCoefficient);
+   }
+
+   isGuided = stream->readFlag();
+   if(isGuided)
+   {
+      stream->read(&precision);
+      stream->read(&trackDelay);
+   }
+
+   stream->read(&muzzleVelocity);
+   mathRead(*stream, &starting_vel_vec);
+   stream->read(&acceleration);
+   stream->read(&accelDelay);
+   stream->read(&accelLifetime);
+
+   launch_node = stream->readSTString();
+   mathRead(*stream, &launch_offset);
+   mathRead(*stream, &launch_offset_server);
+   mathRead(*stream, &launch_offset_client);
+   mathRead(*stream, &launch_node_offset);
+   stream->read(&launch_pitch);
+   stream->read(&launch_pan);
+   launch_cons_c_spec = stream->readSTString();
+   echo_launch_offset = stream->readFlag();
+
+   readVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes");
+   readVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds");
+
+   if (wiggle_axis)
+      delete [] wiggle_axis;
+   wiggle_axis = 0;
+   wiggle_num_axis = 0;
+
+   stream->read(&wiggle_num_axis);
+   if (wiggle_num_axis > 0)
+   {
+      wiggle_axis = new Point3F[wiggle_num_axis];
+      for (U32 i = 0; i < wiggle_num_axis; i++)
+         mathRead(*stream, &wiggle_axis[i]);
+   }
+
+   stream->read(&hover_altitude);
+   stream->read(&hover_attack_distance);
+   stream->read(&hover_attack_gradient);
+   hover_time = stream->readRangedU32(0, MaxLifetimeTicks);
+
+   reverse_targeting = stream->readFlag();
+
+   stream->read(&caster_safety_time);
+}
+
+/* From stock Projectile code...
+bool ProjectileData::setLifetime( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+   
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->lifetime = value;
+
+   return false;
+}
+
+bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->armingDelay = value;
+
+   return false;
+}
+
+bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data )
+{
+	S32 value = dAtoi(data);
+   value = scaleValue(value);
+
+   ProjectileData *object = static_cast<ProjectileData*>(obj);
+   object->fadeDelay = value;
+
+   return false;
+}
+
+const char *ProjectileData::getScaledValue( void *obj, const char *data)
+{
+
+	S32 value = dAtoi(data);
+   value = scaleValue(value, false);
+
+   String stringData = String::ToString(value);
+   char *strBuffer = Con::getReturnBuffer(stringData.size());
+   dMemcpy( strBuffer, stringData, stringData.size() );
+
+   return strBuffer;
+}
+
+S32 ProjectileData::scaleValue( S32 value, bool down )
+{
+   S32 minV = 0;
+   S32 maxV = Projectile::MaxLivingTicks;
+   
+   // scale down to ticks before we validate
+   if( down )
+      value /= TickMs;
+   
+   if(value < minV || value > maxV)
+	{
+      Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV);
+		if(value < minV)
+			value = minV;
+		else if(value > maxV)
+			value = maxV;
+	}
+
+   // scale up from ticks after we validate
+   if( !down )
+      value *= TickMs;
+
+   return value;
+}
+*/
+
+void afxMagicMissileData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (launch_cons_s_def.isDefined())
+    defs.push_back(launch_cons_s_def);
+  if (launch_cons_c_def.isDefined())
+    defs.push_back(launch_cons_c_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissile
+
+afxMagicMissile::afxMagicMissile()
+{
+  init(true, true);
+}
+
+afxMagicMissile::afxMagicMissile(bool on_server, bool on_client)
+{
+  init(on_server, on_client);
+}
+
+//--------------------------------------------------------------------------
+//--------------------------------------
+//
+void afxMagicMissile::init(bool on_server, bool on_client)
+{
+  mPhysicsWorld = NULL;
+
+  mTypeMask |= ProjectileObjectType | LightObjectType;
+
+  mLight = LightManager::createLightInfo();
+  mLight->setType( LightInfo::Point );   
+
+  mLightState.clear();
+  mLightState.setLightInfo( mLight );
+
+  mCurrPosition.zero();
+  mCurrVelocity.set(0, 0, 1);
+
+  mCurrTick = 0;
+
+  mParticleEmitter   = NULL;
+  mParticleWaterEmitter = NULL;
+  mSound = 0;
+
+  mProjectileShape   = NULL;
+
+  mDataBlock = NULL;
+
+  choreographer = NULL;
+
+  if (on_server != on_client)
+  {
+    client_only = on_client;
+    server_only = on_server;
+    mNetFlags.clear(Ghostable | ScopeAlways);
+    if (client_only)
+      mNetFlags.set(IsGhost);
+  }
+  else
+  {
+    // note -- setting neither server or client makes no sense so we
+    // treat as if both are set.
+    mNetFlags.set(Ghostable | ScopeAlways);
+    client_only = server_only = false;
+  }
+
+  mCurrDeltaBase.zero();
+  mCurrBackDelta.zero();
+  collision_mask = 0;
+  prec_inc = 0.0f;
+
+  did_launch = false;
+  did_impact = false;
+
+  missile_target = NULL;
+  collide_exempt = NULL;
+
+  use_accel = false;
+
+  hover_attack_go = false;
+  hover_attack_tick = 0;
+
+  starting_velocity = 0.0;
+  starting_vel_vec.zero();
+
+  ss_object = 0;
+  ss_index = 0;
+}
+
+afxMagicMissile::~afxMagicMissile()
+{
+   SAFE_DELETE(mLight);
+
+   delete mProjectileShape;
+   mProjectileShape = NULL;
+}
+
+//--------------------------------------------------------------------------
+void afxMagicMissile::initPersistFields()
+{
+   addGroup("Physics");
+   addField("initialPosition", TypePoint3F, Offset(mCurrPosition, afxMagicMissile) ,
+     "Initial starting position for this missile.");
+   addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, afxMagicMissile) ,
+     "Initial starting velocity for this missile.");
+   endGroup("Physics");
+
+   /* From stock Projectile code...
+   addGroup("Source");
+   addField("sourceObject",     TypeS32,     Offset(mSourceObjectId, Projectile) ,"The object that fires this projectile. If this projectile was fired by a WeaponImage, it will be the object that owns the WeaponImage, usually the player.");
+   addField("sourceSlot",       TypeS32,     Offset(mSourceObjectSlot, Projectile) ,"Which weapon slot on the sourceObject that this projectile originates from.");
+   endGroup("Source");
+  */
+
+   Parent::initPersistFields();
+}
+
+/* From stock Projectile code...
+bool Projectile::calculateImpact(float,
+                                 Point3F& pointOfImpact,
+                                 float&   impactTime)
+{
+   Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called");
+
+   impactTime = 0;
+   pointOfImpact.set(0, 0, 0);
+   return false;
+}
+
+
+//--------------------------------------------------------------------------
+F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
+{
+   F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
+   // if the camera "owns" this object, it should have a slightly higher priority
+   if(mSourceObject == camInfo->camera)
+      return ret + 0.2;
+   return ret;
+}
+*/
+
+bool afxMagicMissile::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   if (isServerObject())
+   {
+      /* From stock Projectile code...
+      ShapeBase* ptr;
+      if (Sim::findObject(mSourceObjectId, ptr))
+      {
+         mSourceObject = ptr;
+
+         // Since we later do processAfter( mSourceObject ) we must clearProcessAfter
+         // if it is deleted. SceneObject already handles this in onDeleteNotify so
+         // all we need to do is register for the notification.
+         deleteNotify( ptr );
+      }
+      else
+      {
+         if (mSourceObjectId != -1)
+            Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid");
+         mSourceObject = NULL;
+      }
+
+      // If we're on the server, we need to inherit some of our parent's velocity
+      //
+      mCurrTick = 0;
+     */
+   }
+   else
+   {
+      if (bool(mDataBlock->projectileShape))
+      {
+         mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, true);
+         /* From stock Projectile code...
+         if (mDataBlock->activateSeq != -1)
+         {
+            mActivateThread = mProjectileShape->addThread();
+            mProjectileShape->setTimeScale(mActivateThread, 1);
+            mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0);
+         }
+         */
+      }
+      if (mDataBlock->particleEmitter != NULL)
+      {
+         ParticleEmitter* pEmitter = new ParticleEmitter;
+         //pEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index));
+         pEmitter->onNewDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
+         if (pEmitter->registerObject() == false)
+         {
+            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
+            delete pEmitter;
+            pEmitter = NULL;
+         }
+         mParticleEmitter = pEmitter;
+      }
+
+      if (mDataBlock->particleWaterEmitter != NULL)
+      {
+         ParticleEmitter* pEmitter = new ParticleEmitter;
+         pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false);
+         if (pEmitter->registerObject() == false)
+         {
+            Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName());
+            delete pEmitter;
+            pEmitter = NULL;
+         }
+         mParticleWaterEmitter = pEmitter;
+      }
+   }
+   /* From stock Projectile code...
+   if (mSourceObject.isValid())
+      processAfter(mSourceObject);
+  */
+
+   // detect for acceleration
+   use_accel = (mDataBlock->acceleration != 0 && mDataBlock->accelLifetime > 0);
+
+   // Setup our bounding box
+   if (bool(mDataBlock->projectileShape) == true)
+      mObjBox = mDataBlock->projectileShape->bounds;
+   else
+      mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0));
+   resetWorldBox();
+   addToScene();
+
+   if ( PHYSICSMGR )
+      mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
+
+   return true;
+}
+
+
+void afxMagicMissile::onRemove()
+{
+   if(mParticleEmitter)
+   {
+      mParticleEmitter->deleteWhenEmpty();
+      mParticleEmitter = NULL;
+   }
+
+   if(mParticleWaterEmitter)
+   {
+      mParticleWaterEmitter->deleteWhenEmpty();
+      mParticleWaterEmitter = NULL;
+   }
+
+   SFX_DELETE( mSound );
+
+   removeFromScene();
+   Parent::onRemove();
+}
+
+
+bool afxMagicMissile::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+   mDataBlock = dynamic_cast<afxMagicMissileData*>(dptr);
+   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+      return false;
+
+   starting_velocity = mDataBlock->muzzleVelocity;
+   starting_vel_vec = mDataBlock->starting_vel_vec;
+   collision_mask = mDataBlock->collision_mask;
+
+   if ( isGhost() )
+   {
+      // Create the sound ahead of time.  This reduces runtime
+      // costs and makes the system easier to understand.
+
+      SFX_DELETE( mSound );
+
+      if ( mDataBlock->sound )
+         mSound = SFX->createSource( mDataBlock->sound );
+   }
+
+   return true;
+}
+
+
+//--------------------------------------------------------------------------
+
+void afxMagicMissile::submitLights( LightManager *lm, bool staticLighting )
+{
+   if ( staticLighting || isHidden() || !mDataBlock->lightDesc )
+      return;
+   
+   mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this );   
+}
+
+bool afxMagicMissile::pointInWater(const Point3F &point)
+{
+   // This is pretty much a hack so we can use the existing ContainerQueryInfo
+   // and findObject router.
+   
+   // We only care if we intersect with water at all 
+   // so build a box at the point that has only 1 z extent.
+   // And test if water coverage is anything other than zero.
+
+   Box3F boundsBox( point, point );
+   boundsBox.maxExtents.z += 1.0f;
+
+   ContainerQueryInfo info;
+   info.box = boundsBox;
+   info.mass = 0.0f;
+   
+   // Find and retreive physics info from intersecting WaterObject(s)
+   if(mContainer != NULL)
+   {
+      mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info );
+   }
+   else
+   {
+      // Handle special case where the projectile has exploded prior to having
+      // called onAdd() on the client.  This occurs when the projectile on the
+      // server is created and then explodes in the same network update tick.
+      // On the client end in NetConnection::ghostReadPacket() the ghost is
+      // created and then Projectile::unpackUpdate() is called prior to the
+      // projectile being registered.  Within unpackUpdate() the explosion
+      // is triggered, but without being registered onAdd() isn't called and
+      // the container is not set.  As all we're doing is checking if the
+      // given explosion point is within water, we should be able to use the
+      // global container here.  We could likely always get away with this,
+      // but using the actual defined container when possible is the right
+      // thing to do.  DAW
+      AssertFatal(isClientObject(), "Server projectile has not been properly added");
+      gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info );
+   }
+
+   return ( info.waterCoverage > 0.0f );
+}
+
+//----------------------------------------------------------------------------
+
+void afxMagicMissile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms)
+{
+  /* From stock Projectile code...
+   if ( isHidden() )
+      return;
+  */
+
+   Point3F axis = -vel;
+
+   if( axis.isZero() )
+      axis.set( 0.0, 0.0, 1.0 );
+   else
+      axis.normalize();
+
+   bool fromWater = pointInWater(from);
+   bool toWater   = pointInWater(to);
+
+   if (!fromWater && !toWater && bool(mParticleEmitter))                                        // not in water
+      mParticleEmitter->emitParticles(from, to, axis, vel, ms);
+   else if (fromWater && toWater && bool(mParticleWaterEmitter))                                // in water
+      mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms);
+   else if (!fromWater && toWater)     // entering water
+   {
+      // cast the ray to get the surface point of the water
+      RayInfo rInfo;
+      if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo))
+      {
+         create_splash(rInfo.point);
+
+         // create an emitter for the particles out of water and the particles in water
+         if (mParticleEmitter)
+            mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
+
+         if (mParticleWaterEmitter)
+            mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
+      }
+   }
+   else if (fromWater && !toWater)     // leaving water
+   {
+      // cast the ray in the opposite direction since that point is out of the water, otherwise
+      //  we hit water immediately and wont get the appropriate surface point
+      RayInfo rInfo;
+      if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo))
+      {
+      create_splash(rInfo.point);
+
+         // create an emitter for the particles out of water and the particles in water
+         if (mParticleEmitter)
+            mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms);
+
+         if (mParticleWaterEmitter)
+            mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms);
+      }
+   }
+}
+
+void afxMagicMissile::processTick(const Move* move)
+{
+   Parent::processTick( move );
+   
+   // only active from launch to impact
+   if (!is_active())
+      return;
+
+   mCurrTick++;
+
+   // missile fizzles out by exceeding lifetime
+   if ((isServerObject() || client_only) && mCurrTick >= mDataBlock->lifetime)
+   {
+      did_impact = true;
+      setMaskBits(ImpactMask);
+      if (choreographer)
+      {
+         Point3F n = mCurrVelocity; n.normalizeSafe();
+         choreographer->impactNotify(mCurrPosition, n, 0);
+      }
+      Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+      return;
+   }
+
+   static F32 dT = F32(TickMs)*0.001f;
+
+   Point3F old_pos = mCurrPosition;
+
+   // adjust missile velocity from gravity and drag influences
+   if (mDataBlock->isBallistic)
+   {
+      F32 dV = (1 - mDataBlock->ballisticCoefficient)*dT;
+      Point3F d(mCurrVelocity.x*dV, mCurrVelocity.y*dV, 9.81f*mDataBlock->gravityMod*dT);
+      mCurrVelocity -= d;
+   }
+
+   // adjust missile velocity from acceleration
+   if (use_accel)
+   {
+      if (mCurrTick > mDataBlock->accelDelay && 
+         mCurrTick <= mDataBlock->accelDelay + mDataBlock->accelLifetime)
+      {
+         Point3F d = mCurrVelocity; d.normalizeSafe();
+         mCurrVelocity += d*mDataBlock->acceleration*dT;
+      }
+   }
+
+   // adjust mCurrVelocity from guidance system influences
+   if (mDataBlock->isGuided && missile_target && mCurrTick > mDataBlock->trackDelay) 
+   {
+      // get the position tracked by the guidance system
+      Point3F target_pos = missile_target->getBoxCenter(); 
+
+      Point3F target_vec = target_pos - mCurrPosition;
+
+      F32 target_dist_sq = target_vec.lenSquared();
+      if (target_dist_sq < 4.0f)
+         prec_inc += 1.0f;
+
+      // hover
+      if (mDataBlock->hover_altitude > 0.0f)
+      {
+         Point3F target_vec_xy(target_vec.x, target_vec.y, 0);
+         F32 xy_dist = target_vec_xy.len();
+
+         if (xy_dist > mDataBlock->hover_attack_distance)
+         {          
+            hover_attack_go = false;
+
+            if (xy_dist > mDataBlock->hover_attack_distance + mDataBlock->hover_attack_gradient)
+            {
+               target_pos.z += mDataBlock->hover_altitude;          
+            }
+            else
+            {
+               target_pos.z += afxEase::eq( (xy_dist-mDataBlock->hover_attack_distance)/mDataBlock->hover_attack_gradient, 
+                  0.0f, mDataBlock->hover_altitude, 
+                  0.25f, 0.75f);
+            }          			
+            target_vec = target_pos - mCurrPosition;
+         }
+
+         else
+         {
+            if (!hover_attack_go) 
+            {
+               hover_attack_go = true;
+               hover_attack_tick = 0;
+            }
+            hover_attack_tick++;
+
+            if (hover_attack_tick < mDataBlock->hover_time)
+            {
+               target_pos.z += mDataBlock->hover_altitude;
+               target_vec = target_pos - mCurrPosition;
+            }
+         }
+      }
+
+      // apply precision 
+
+      // extract speed
+      F32 speed = mCurrVelocity.len(); 
+
+      // normalize vectors
+      target_vec.normalizeSafe();
+      mCurrVelocity.normalize();
+
+      F32 prec = mDataBlock->precision;
+
+      // fade in precision gradually to avoid sudden turn
+      if (mCurrTick < mDataBlock->trackDelay + 16)
+         prec *= (mCurrTick - mDataBlock->trackDelay)/16.0f;
+
+      prec += prec_inc;
+      if (prec > 100)
+         prec = 100;
+
+      // apply precision weighting
+      target_vec *= prec;
+      mCurrVelocity *= (100 - prec);
+
+      mCurrVelocity += target_vec;
+      mCurrVelocity.normalize();
+      mCurrVelocity *= speed;
+   } 
+
+   // wiggle
+   for (U32 i = 0; i < mDataBlock->wiggle_num_axis; i++)
+   {
+      if (i >= mDataBlock->wiggle_magnitudes.size() || i >= mDataBlock->wiggle_speeds.size()) 
+         break;
+
+      F32 wiggle_mag   = mDataBlock->wiggle_magnitudes[i];
+      F32 wiggle_speed = mDataBlock->wiggle_speeds[i];
+      Point3F wiggle_axis = mDataBlock->wiggle_axis[i];
+      //wiggle_axis.normalizeSafe(); // sufficient????
+
+      F32 theta = wiggle_mag * mSin(wiggle_speed*(mCurrTick*TickSec));
+      //Con::printf( "theta: %f", theta );    
+      AngAxisF thetaRot(wiggle_axis, theta);
+      MatrixF temp(true);
+      thetaRot.setMatrix(&temp);
+      temp.mulP(mCurrVelocity);
+   }
+
+   Point3F new_pos = old_pos + mCurrVelocity*dT;
+
+   // conform to terrain
+   if (mDataBlock->followTerrain && mCurrTick >= mDataBlock->followTerrainAdjustDelay) 
+   {
+      U32 mask = TerrainObjectType | TerrainLikeObjectType; //  | InteriorObjectType;
+
+      F32 ht = mDataBlock->followTerrainHeight;
+      F32 ht_rate = mDataBlock->followTerrainAdjustRate;
+      F32 ht_min = 0.05f;
+      if (ht < ht_min)
+         ht = ht_min;
+
+      SceneContainer* container = (isServerObject()) ? &gServerContainer : &gClientContainer;
+      Point3F above_pos = new_pos; above_pos.z += 10000;
+      Point3F below_pos = new_pos; below_pos.z -= 10000;
+      RayInfo rInfo;
+      if (container && container->castRay(above_pos, below_pos, mask, &rInfo)) 
+      {
+         F32 terrain_z = rInfo.point.z;
+         F32 seek_z = terrain_z + ht;
+         if (new_pos.z < seek_z)
+         {
+            new_pos.z += ht_rate*dT;
+            if (new_pos.z > seek_z)
+               new_pos.z = seek_z;
+         }
+         else if (new_pos.z > seek_z)
+         {
+            new_pos.z -= ht_rate*dT;
+            if (new_pos.z < seek_z)
+               new_pos.z = seek_z;
+         }
+
+         if (new_pos.z < terrain_z + ht_min)
+            new_pos.z = terrain_z + ht_min;
+      }
+   }
+
+   // only check for impacts on server
+   if (isServerObject())
+   {
+      // avoid collision with the spellcaster
+      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
+         collide_exempt->disableCollision();
+
+      // check for collision along ray from old to new position
+      RayInfo rInfo;
+      bool did_hit = false;
+
+      if (mPhysicsWorld)
+      {
+         // did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * mDataBlock->impactForce );
+         // Impulse currently hardcoded for testing purposes
+         did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * 1000.0f );
+      }
+      else
+      {
+         did_hit = getContainer()->castRay(old_pos, new_pos, collision_mask, &rInfo);
+      }
+
+      // restore collisions on spellcaster 
+      if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time)
+         collide_exempt->enableCollision();
+
+      // process impact
+      if (did_hit)
+      {
+         MatrixF xform(true);
+         xform.setColumn(3, rInfo.point);
+         setTransform(xform);
+         mCurrPosition = rInfo.point;
+         mCurrVelocity = Point3F(0, 0, 0);
+         did_impact = true;
+         setMaskBits(ImpactMask);
+         if (choreographer)
+         {
+            choreographer->impactNotify(rInfo.point, rInfo.normal, rInfo.object);
+            Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+         }
+      }
+   }
+   else // if (isClientObject())
+   {
+      emitParticles(mCurrPosition, new_pos, mCurrVelocity, TickMs);
+      updateSound();
+   }
+
+   // interp values used in interpolateTick()
+   mCurrDeltaBase = new_pos;
+   mCurrBackDelta = mCurrPosition - new_pos;
+
+   mCurrPosition = new_pos;
+
+   MatrixF xform(true);
+   xform.setColumn(3, mCurrPosition);
+   setTransform(xform);
+}
+
+void afxMagicMissile::interpolateTick(F32 delta)
+{
+   Parent::interpolateTick(delta);
+
+   // only active from launch to impact
+   if (!is_active())
+      return;
+
+   Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta;
+   Point3F dir = mCurrVelocity;
+   if(dir.isZero())
+      dir.set(0,0,1);
+   else
+      dir.normalize();
+
+   MatrixF xform(true);
+	xform = MathUtils::createOrientFromDir(dir);
+   xform.setPosition(interpPos);
+   setRenderTransform(xform);
+
+   updateSound();
+}
+
+//--------------------------------------------------------------------------
+U32 afxMagicMissile::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
+{
+   U32 retMask = Parent::packUpdate( con, mask, stream );
+
+   const bool isInitalUpdate = mask & GameBase::InitialUpdateMask;
+
+   // InitialUpdateMask
+   if ( stream->writeFlag( isInitalUpdate ) )
+   {
+      Point3F pos;
+      getTransform().getColumn( 3, &pos );
+      stream->writeCompressedPoint( pos );
+      F32 len = mCurrVelocity.len();
+
+      if ( stream->writeFlag( len > 0.02 ) )
+      {
+         Point3F outVel = mCurrVelocity;
+         outVel *= 1 / len;
+         stream->writeNormalVector( outVel, 10 );
+         len *= 32.0; // 5 bits for fraction
+
+         if ( len > 8191 )
+             len = 8191;
+
+         stream->writeInt( (S32)len, 13 );
+      }
+
+      stream->writeRangedU32( mCurrTick, 0, afxMagicMissileData::MaxLifetimeTicks );
+
+      if (choreographer)
+      {
+         S32 ghostIndex = con->getGhostIndex( choreographer );
+         if ( stream->writeFlag( ghostIndex != -1 ) )
+         {
+            stream->writeRangedU32( U32(ghostIndex), 
+                                    0, 
+                                    NetConnection::MaxGhostCount );
+         }
+         else 
+            // have not recieved the ghost for the source object yet, try again later
+            retMask |= GameBase::InitialUpdateMask;
+      }
+      else
+         stream->writeFlag( false );
+   }
+
+   // impact update
+   if (stream->writeFlag(mask & ImpactMask))
+   {
+      mathWrite(*stream, mCurrPosition);
+      mathWrite(*stream, mCurrVelocity);
+      stream->writeFlag(did_impact);
+   }
+
+   // guided update
+   if (stream->writeFlag(mask & GuideMask))
+   {
+      mathWrite(*stream, mCurrPosition);
+      mathWrite(*stream, mCurrVelocity);
+   }
+
+   return retMask;
+}
+
+void afxMagicMissile::unpackUpdate(NetConnection* con, BitStream* stream)
+{
+   Parent::unpackUpdate(con, stream);
+   
+   if ( stream->readFlag() ) // InitialUpdateMask
+   {
+      Point3F pos;
+      stream->readCompressedPoint( &pos );
+      if ( stream->readFlag() )
+      {
+         stream->readNormalVector( &mCurrVelocity, 10 );
+         mCurrVelocity *= stream->readInt( 13 ) / 32.0f;
+      }
+      else
+         mCurrVelocity.zero();
+
+      mCurrDeltaBase = pos;
+      mCurrBackDelta = mCurrPosition - pos;
+      mCurrPosition  = pos;
+      setPosition( mCurrPosition );
+
+      mCurrTick = stream->readRangedU32(0, afxMagicMissileData::MaxLifetimeTicks);
+      if ( stream->readFlag() )
+      {
+         U32 id   = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+         choreographer = dynamic_cast<afxChoreographer*>(con->resolveGhost(id));
+         if (choreographer)
+         {
+            deleteNotify(choreographer);
+         }
+      }
+      else
+      {
+         if (choreographer)
+            clearNotify(choreographer);
+         choreographer = 0;
+      }
+   }
+   
+   // impact update
+   if (stream->readFlag())
+   {
+      mathRead(*stream, &mCurrPosition);
+      mathRead(*stream, &mCurrVelocity);
+      did_impact = stream->readFlag();
+
+   }
+
+   if (stream->readFlag()) // guided update
+   {
+      mathRead( *stream, &mCurrPosition );
+      mathRead( *stream, &mCurrVelocity );
+   }
+}
+
+//--------------------------------------------------------------------------
+void afxMagicMissile::prepRenderImage(SceneRenderState* state)
+{
+   if (!is_active())
+      return;
+
+   /*
+   if (isHidden() || mFadeValue <= (1.0/255.0))
+      return;
+   */
+
+   if ( mDataBlock->lightDesc )
+   {
+     mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() );
+   }
+
+   /*
+   if ( mFlareData )
+   {
+     mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness;
+     mFlareState.scale = mFlareScale;
+     mFlareState.lightInfo = mLight;
+     mFlareState.lightMat = getTransform();
+
+     mFlareData->prepRender( state, &mFlareState );
+   }
+   */
+
+   prepBatchRender( state );
+}
+
+void afxMagicMissile::prepBatchRender(SceneRenderState* state)
+{
+   if ( !mProjectileShape )
+      return;
+
+   GFXTransformSaver saver;
+
+   // Set up our TS render state.
+   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 );
+
+   MatrixF mat = getRenderTransform();
+   mat.scale( mObjScale );
+   mat.scale( mDataBlock->scale );
+   GFX->setWorldMatrix( mat );
+
+   mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale );
+   mProjectileShape->animate();
+
+   mProjectileShape->render( rdata );
+}
+
+void afxMagicMissile::onDeleteNotify(SimObject* obj)
+{
+  ShapeBase* shape_test = dynamic_cast<ShapeBase*>(obj);
+  if (shape_test == collide_exempt)
+  {
+    collide_exempt = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  SceneObject* target_test = dynamic_cast<SceneObject*>(obj);
+  if (target_test == missile_target)
+  {
+    missile_target = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  afxChoreographer* ch = dynamic_cast<afxChoreographer*>(obj);
+  if (ch == choreographer)
+  {
+    choreographer = NULL;
+    Parent::onDeleteNotify(obj);
+    return;
+  }
+
+  Parent::onDeleteNotify(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private:
+
+void afxMagicMissile::create_splash(const Point3F& pos)
+{
+  if (!mDataBlock || !mDataBlock->splash)
+    return;
+
+  MatrixF xfm = getTransform();
+  xfm.setPosition(pos);
+
+  Splash* splash = new Splash();
+  splash->onNewDataBlock(mDataBlock->splash, false);
+  splash->setTransform(xfm);
+  splash->setInitialState(xfm.getPosition(), Point3F(0.0, 0.0, 1.0));
+  if (!splash->registerObject())
+  {
+    delete splash;
+    splash = NULL;
+  }
+}
+
+void afxMagicMissile::get_launch_constraint_data(Point3F& pos, Point3F& vel)
+{
+  // need a choreographer
+  if (!choreographer)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing choreographer.");
+    return;
+  }
+
+  // need a constraint manager
+  afxConstraintMgr* cons_mgr = choreographer->getConstraintMgr();
+  if (!cons_mgr)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing constriant manager.");
+    return;
+  }
+
+  // need a valid constraint
+  afxConstraintID launch_cons_id;
+  if (isServerObject())
+    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_s_def);
+  else
+    launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_c_def);
+
+  afxConstraint* launch_cons = cons_mgr->getConstraint(launch_cons_id);
+  if (!launch_cons)
+  {
+    Con::errorf("afxMagicMissile::get_launch_constraint_data(): constraint undefined.");
+    return;
+  }
+
+  MatrixF launch_xfm;
+  launch_cons->getTransform(launch_xfm);
+
+  Point3F launch_pos;
+  launch_cons->getPosition(launch_pos);
+
+  pos = launch_pos;
+
+  // echo the actual launch position to the console
+  if (mDataBlock->echo_launch_offset)
+  {
+    SceneObject* default_launcher = get_default_launcher();
+    if (default_launcher)
+    {
+      MatrixF launcher_xfm_inv = default_launcher->getWorldTransform();
+      VectorF offset = pos - default_launcher->getRenderPosition();
+      launcher_xfm_inv.mulV(offset);
+      if (isServerObject())
+        Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
+      else
+        Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
+    }
+  }
+
+  // setup aiming matrix to straight forward and level
+  MatrixF aim_mtx;
+  AngAxisF aim_aa(Point3F(0,1,0),0);
+  aim_aa.setMatrix(&aim_mtx);
+
+  // calculate final aiming vector
+  MatrixF aim2_mtx;
+  aim2_mtx.mul(launch_xfm, aim_mtx);
+
+  VectorF aim_vec;
+  aim2_mtx.getColumn(1,&aim_vec);
+  aim_vec.normalizeSafe();
+
+  // give velocity vector a magnitude
+  vel = aim_vec*mDataBlock->muzzleVelocity;
+}
+
+// resolve the launch constraint object. normally it's the caster, but for
+// reverse_targeting the target object us used.
+SceneObject* afxMagicMissile::get_default_launcher() const
+{
+  SceneObject* launch_cons_obj = 0;
+  if (mDataBlock->reverse_targeting)
+  {
+    if (dynamic_cast<afxMagicSpell*>(choreographer))
+      launch_cons_obj = ((afxMagicSpell*)choreographer)->target;
+    if (!launch_cons_obj)
+    {
+      Con::errorf("afxMagicMissile::get_launch_data(): missing target constraint object for reverse targeted missile.");
+      return 0;
+    }
+  }
+  else
+  {
+    if (dynamic_cast<afxMagicSpell*>(choreographer))
+      launch_cons_obj = ((afxMagicSpell*)choreographer)->caster;
+    if (!launch_cons_obj)
+    {
+      Con::errorf("afxMagicMissile::get_launch_data(): missing launch constraint object missile.");
+      return 0;
+    }
+  }
+
+  return launch_cons_obj;
+}
+
+void afxMagicMissile::get_launch_data(Point3F& pos, Point3F& vel)
+{
+  bool use_constraint = (isServerObject()) ? mDataBlock->launch_cons_s_def.isDefined() : mDataBlock->launch_cons_c_def.isDefined();
+  if (use_constraint)
+  {
+    get_launch_constraint_data(pos, vel);
+    return;
+  }  
+  
+  // a choreographer pointer is required
+  if (!choreographer)
+  {
+    Con::errorf("afxMagicMissile::get_launch_data(): missing choreographer.");
+    return;
+  }
+
+  SceneObject* launch_cons_obj = get_default_launcher();
+  if (!launch_cons_obj)
+    return;
+
+  MatrixF launch_xfm = launch_cons_obj->getRenderTransform();
+
+  // calculate launch position
+  Point3F offset_override = (isClientObject()) ?  mDataBlock->launch_offset_client : 
+                                                  mDataBlock->launch_offset_server;
+  // override
+  if (!offset_override.isZero())
+  {
+    launch_xfm.mulV(offset_override);
+    pos = launch_cons_obj->getRenderPosition() + offset_override;
+  }
+  // no override 
+  else
+  {
+    // get transformed launch offset
+    VectorF launch_offset = mDataBlock->launch_offset;
+    launch_xfm.mulV(launch_offset);
+    
+    StringTableEntry launch_node = mDataBlock->launch_node;
+    
+    // calculate position of missile at launch
+    if (launch_node != ST_NULLSTRING)
+    {
+      ShapeBase* launch_cons_shape = dynamic_cast<ShapeBase*>(launch_cons_obj);
+      TSShapeInstance* shape_inst = (launch_cons_shape) ? launch_cons_shape->getShapeInstance() : 0;
+      if (!shape_inst || !shape_inst->getShape())
+        launch_node = ST_NULLSTRING;
+      else
+      {
+        S32 node_ID = shape_inst->getShape()->findNode(launch_node);
+        MatrixF node_xfm = launch_cons_obj->getRenderTransform();
+        node_xfm.scale(launch_cons_obj->getScale());
+        if (node_ID >= 0)
+          node_xfm.mul(shape_inst->mNodeTransforms[node_ID]);
+        
+        VectorF node_offset = mDataBlock->launch_node_offset;
+        node_xfm.mulV(node_offset);
+        
+        pos = node_xfm.getPosition() + launch_offset + node_offset;
+      }
+    }   
+    // calculate launch position without launch node
+    else
+      pos = launch_cons_obj->getRenderPosition() + launch_offset;
+  }
+
+  // echo the actual launch position to the console
+  if (mDataBlock->echo_launch_offset)
+  {
+    VectorF offset = pos - launch_cons_obj->getRenderPosition();
+    MatrixF caster_xfm_inv = launch_xfm;
+    caster_xfm_inv.affineInverse();
+    caster_xfm_inv.mulV(offset);
+    if (isServerObject())
+      Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z);
+    else
+      Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z);
+  }
+
+  // calculate launch velocity vector
+  if (starting_vel_vec.isZero())
+  {
+    // setup aiming matrix to straight forward and level
+    MatrixF aim_mtx;
+    AngAxisF aim_aa(Point3F(0,1,0),0);
+    aim_aa.setMatrix(&aim_mtx);
+    
+    // setup pitch matrix
+    MatrixF pitch_mtx;
+    AngAxisF pitch_aa(Point3F(1,0,0),mDegToRad(mDataBlock->launch_pitch));
+    pitch_aa.setMatrix(&pitch_mtx);
+    
+    // setup pan matrix
+    MatrixF pan_mtx;
+    AngAxisF pan_aa(Point3F(0,0,1),mDegToRad(mDataBlock->launch_pan));
+    pan_aa.setMatrix(&pan_mtx);
+    
+    // calculate adjusted aiming matrix
+    aim_mtx.mul(pitch_mtx);
+    aim_mtx.mul(pan_mtx);
+    
+    // calculate final aiming vector
+    MatrixF aim2_mtx;
+    aim2_mtx.mul(launch_xfm, aim_mtx);
+    VectorF aim_vec;
+    aim2_mtx.getColumn(1,&aim_vec);
+    aim_vec.normalizeSafe();
+    
+    // give velocity vector a magnitude
+    vel = aim_vec*mDataBlock->muzzleVelocity;
+  }
+  else
+  {
+    vel = starting_vel_vec*starting_velocity;
+  }
+}
+
+void afxMagicMissile::updateSound()
+{
+  if (!mDataBlock->sound)
+    return;
+
+  if ( mSound )
+  {
+    if ( !mSound->isPlaying() )
+      mSound->play();
+
+    mSound->setVelocity( getVelocity() );
+    mSound->setTransform( getRenderTransform() );
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxMagicMissile::launch()
+{
+  get_launch_data(mCurrPosition, mCurrVelocity);
+
+  mCurrDeltaBase = mCurrPosition;
+  mCurrBackDelta.zero();
+
+  did_launch = true;
+
+  setPosition(mCurrPosition);
+
+  afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(choreographer);
+  if (spell)
+  {
+    if (mDataBlock->reverse_targeting)
+    {
+      missile_target = spell->caster;
+      collide_exempt = spell->target;
+    }
+    else
+    {
+      missile_target = spell->target;
+      collide_exempt = spell->caster;
+    }
+
+    if (spell->caster)
+      processAfter(spell->caster);
+    if (missile_target)
+      deleteNotify(missile_target);
+    if (collide_exempt)
+      deleteNotify(collide_exempt);
+  }
+  else
+  {
+    missile_target = 0;
+    collide_exempt = 0;
+  }
+}
+
+void afxMagicMissile::setChoreographer(afxChoreographer* chor)
+{
+  if (choreographer)
+    clearNotify(choreographer);
+  choreographer = chor;
+  if (choreographer)
+    deleteNotify(choreographer);
+}
+
+void afxMagicMissile::setStartingVelocityVector(const Point3F& vel_vec)
+{
+  starting_vel_vec = vel_vec;
+}
+
+void afxMagicMissile::setStartingVelocity(const F32 vel)
+{
+  starting_velocity = vel;
+}
+
+void afxMagicMissile::getStartingVelocityValues(F32& vel, Point3F& vel_vec)
+{
+  vel = starting_velocity;
+  vel_vec = starting_vel_vec;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+/* From stock Projectile code...
+DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), "Updates velocity and position, and performs collision testing.\n"
+													"@param seconds Amount of time, in seconds, since the simulation began, to start the simulation at.\n"
+													"@tsexample\n"
+														"// Tell the projectile object to process a simulation event, and provide the amount of time\n"
+														"   in seconds that has passed since the simulation began.\n"
+														"%seconds = 2000;\n"
+														"%projectile.presimulate(%seconds);\n"
+													"@endtsexample\n")
+{
+	object->simulate( seconds );
+}
+*/
+
+DefineEngineMethod(afxMagicMissile, setStartingVelocityVector, void, (Point3F velocityVec),,
+                   "Set the starting velocity-vector for a magic-missile.\n\n"
+                   "@ingroup AFX")
+{
+  object->setStartingVelocityVector(velocityVec);
+}
+
+DefineEngineMethod(afxMagicMissile, setStartingVelocity, void, (float velocity),,
+                   "Set the starting velocity for a magic-missile.\n\n"
+                   "@ingroup AFX")
+{
+  object->setStartingVelocity(velocity);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 435 - 0
Engine/source/afx/afxMagicMissile.h

@@ -0,0 +1,435 @@
+//-----------------------------------------------------------------------------
+// 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.
+//
+// afxMagicMissile is a heavily modified variation of the stock Projectile class. In 
+// addition to numerous AFX customizations, it also incorporates functionality based on
+// the following TGE resources:
+//
+// Guided or Seeker Projectiles by Derk Adams
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778
+//
+// Projectile Ballistic Coefficients (drag factors) by Mark Owen
+//   http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MAGIC_MISSILE_H_
+#define _AFX_MAGIC_MISSILE_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "T3D/lightDescription.h"
+#include "T3D/fx/particleEmitter.h"
+
+#include "afx/afxConstraint.h"
+
+class SplashData;
+class ShapeBase;
+class TSShapeInstance;
+class PhysicsWorld;
+class SFXTrack;
+class SFXSource;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissileData
+
+class afxMagicMissileData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+  
+protected:
+  bool onAdd();
+
+public:
+  enum { MaxLifetimeTicks = 4095 };
+  
+public:
+   // variables set in datablock definition:
+   // Shape related
+  StringTableEntry      projectileShapeName;
+
+  //bool                  hasLight;
+  //F32                   lightRadius;
+  //LinearColorF                lightColor;
+
+  //bool                  hasWaterLight;
+  //LinearColorF                waterLightColor;
+
+  /*
+  /// Set to true if it is a billboard and want it to always face the viewer, false otherwise
+  bool faceViewer;
+  */
+  Point3F               scale;
+
+  /*
+  /// [0,1] scale of how much velocity should be inherited from the parent object
+  F32 velInheritFactor;
+  /// Speed of the projectile when fired
+  */
+  F32                   muzzleVelocity;
+
+   /// Should it arc?
+  bool                  isBallistic;
+
+  /*
+  /// How HIGH should it bounce (parallel to normal), [0,1]
+  F32 bounceElasticity;
+  /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1]
+  F32 bounceFriction;
+  */
+
+   /// Should this projectile fall/rise different than a default object?
+  F32                   gravityMod;
+
+   /// How long the projectile should exist before deleting itself
+  U32                   lifetime;     // ticks
+  /*
+  /// How long it should not detonate on impact
+  S32 armingDelay;  // the values are converted on initialization with
+  */
+  S32                   fadeDelay;    // ticks
+
+  /*
+  ExplosionData* explosion;           // Explosion Datablock
+  S32 explosionId;                    // Explosion ID
+  ExplosionData* waterExplosion;      // Water Explosion Datablock
+  S32 waterExplosionId;               // Water Explosion ID
+  */
+
+  SplashData* splash;                 // Water Splash Datablock
+  S32 splashId;                       // Water splash ID
+
+  SFXTrack* sound;                    // Projectile Sound
+
+  LightDescription *lightDesc;
+  S32 lightDescId;   
+
+  /*
+  enum DecalConstants {               // Number of decals constant
+    NumDecals = 6,
+  };
+  DecalData* decals[NumDecals];       // Decal Datablocks
+  S32 decalId[NumDecals];             // Decal IDs
+  U32 decalCount;                     // # of loaded Decal Datablocks
+  */
+
+   // variables set on preload:
+  Resource<TSShape>     projectileShape;
+  /*
+  S32 activateSeq;
+  S32 maintainSeq;
+  */
+
+  ParticleEmitterData*  particleEmitter;
+  S32                   particleEmitterId;
+  ParticleEmitterData*  particleWaterEmitter;
+  S32                   particleWaterEmitterId;
+
+  U32                   collision_mask;
+
+  Point3F               starting_vel_vec;
+
+                        // guidance behavior
+  bool                  isGuided;
+  F32                   precision;
+  S32                   trackDelay;
+
+                        // simple physics
+  F32                   ballisticCoefficient;
+
+                        // terrain following
+  bool                  followTerrain;
+  F32                   followTerrainHeight;
+  F32                   followTerrainAdjustRate;
+  S32                   followTerrainAdjustDelay;
+
+  F32                   acceleration;
+  S32                   accelDelay;
+  U32                   accelLifetime;
+
+  StringTableEntry      launch_node;
+  Point3F               launch_offset;
+  Point3F               launch_offset_server;
+  Point3F               launch_offset_client;
+  Point3F               launch_node_offset;
+  F32                   launch_pitch;
+  F32                   launch_pan;
+  bool                  echo_launch_offset;
+
+  StringTableEntry      launch_cons_s_spec;
+  afxConstraintDef      launch_cons_s_def;
+  StringTableEntry      launch_cons_c_spec;
+  afxConstraintDef      launch_cons_c_def;
+
+                        // wiggle behavior
+  Vector<F32>           wiggle_magnitudes;
+  Vector<F32>           wiggle_speeds;
+  StringTableEntry      wiggle_axis_string;
+  Point3F*              wiggle_axis;
+  U32                   wiggle_num_axis;
+
+                        // hover behavior
+  F32                   hover_altitude;
+  F32                   hover_attack_distance;
+  F32                   hover_attack_gradient;
+  U32                   hover_time;
+
+  bool                  reverse_targeting;
+
+  U32                   caster_safety_time;
+
+public:
+  /*C*/                 afxMagicMissileData();
+  /*D*/                 ~afxMagicMissileData();
+  
+  void                  packData(BitStream*);
+  void                  unpackData(BitStream*);
+
+  bool                  preload(bool server, String &errorStr);
+  
+  static void           initPersistFields();
+  
+  DECLARE_CONOBJECT(afxMagicMissileData);
+  DECLARE_CATEGORY("AFX");
+
+public:
+  /*C*/                 afxMagicMissileData(const afxMagicMissileData&, bool = false);
+
+  afxMagicMissileData*  cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+  virtual bool          allowSubstitutions() const { return true; }
+  void                  gather_cons_defs(Vector<afxConstraintDef>& defs);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissile
+
+//class afxMagicSpell;
+class afxChoreographer;
+
+class afxMagicMissile : public GameBase, public ISceneLight
+{
+  typedef GameBase Parent;
+
+public:
+  /*
+  // Initial conditions
+  enum ProjectileConstants {
+    SourceIdTimeoutTicks = 7,   // = 231 ms
+    DeleteWaitTime       = 500, ///< 500 ms delete timeout (for network transmission delays)
+    ExcessVelDirBits     = 7,
+    MaxLivingTicks       = 4095,
+  };
+  */
+  enum UpdateMasks {
+    /*
+    BounceMask    = Parent::NextFreeMask,
+    ExplosionMask = Parent::NextFreeMask << 1,
+    */
+    GuideMask     = Parent::NextFreeMask << 0,
+    LaunchMask    = Parent::NextFreeMask << 1,
+    ImpactMask    = Parent::NextFreeMask << 2,
+    NextFreeMask  = Parent::NextFreeMask << 3
+  }; 
+protected:
+  PhysicsWorld *mPhysicsWorld;
+
+  afxMagicMissileData* mDataBlock;
+
+  ParticleEmitter*  mParticleEmitter;
+  ParticleEmitter*  mParticleWaterEmitter;
+  SFXSource* mSound;
+
+  Point3F  mCurrPosition;
+  Point3F  mCurrVelocity;
+  /*
+  S32      mSourceObjectId;
+  S32      mSourceObjectSlot;
+  */
+
+   // Time related variables common to all projectiles, managed by processTick
+
+  U32 mCurrTick;                         ///< Current time in ticks
+  /*
+  SimObjectPtr<ShapeBase> mSourceObject; ///< Actual pointer to the source object, times out after SourceIdTimeoutTicks
+  */
+
+   // Rendering related variables
+  TSShapeInstance* mProjectileShape;
+  /*
+  TSThread*        mActivateThread;
+  TSThread*        mMaintainThread;
+
+  Point3F          mLastRenderPos;
+  */
+
+  // ISceneLight
+  virtual void submitLights( LightManager *lm, bool staticLighting );
+  virtual LightInfo* getLight() { return mLight; }
+
+  LightInfo *mLight;
+  LightState mLightState;
+
+  /*
+  bool             mHidden;        ///< set by the derived class, if true, projectile doesn't render
+  F32              mFadeValue;     ///< set in processTick, interpolation between fadeDelay and lifetime
+                                 ///< in data block
+  */
+
+  /*
+  // Warping and back delta variables.  Only valid on the client
+  //
+  Point3F mWarpStart;
+  Point3F mWarpEnd;
+  U32     mWarpTicksRemaining;
+  */
+
+  Point3F mCurrDeltaBase;
+  Point3F mCurrBackDelta;
+
+  /*
+  Point3F mExplosionPosition;
+  Point3F mExplosionNormal;
+  U32     mCollideHitType;
+  */
+  
+  bool onAdd();
+  void onRemove();
+  bool onNewDataBlock(GameBaseData *dptr, bool reload);
+
+  // Rendering
+  virtual void prepRenderImage(SceneRenderState*);
+  void prepBatchRender( SceneRenderState *state); 
+
+  void processTick(const Move *move);
+  /*
+  void advanceTime(F32 dt);
+  */
+  void interpolateTick(F32 delta);
+
+  /*
+  /// What to do once this projectile collides with something
+  virtual void onCollision(const Point3F& p, const Point3F& n, SceneObject*);
+
+  /// What to do when this projectile explodes
+  virtual void explode(const Point3F& p, const Point3F& n, const U32 collideType );
+
+  /// Returns the velocity of the projectile
+  Point3F getVelocity() const;
+  */
+
+  void              emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32);
+  void              updateSound();
+
+  // Rendering
+  /*
+  void prepModelView    ( SceneRenderState *state);
+  */
+
+   // These are stolen from the player class ..
+  bool              pointInWater(const Point3F &point);
+
+  U32  packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+  void unpackUpdate(NetConnection *conn, BitStream *stream);
+
+  afxChoreographer* choreographer;
+
+  bool              client_only;
+  bool              server_only;
+  bool              use_accel;
+  U32               collision_mask;
+  F32               prec_inc;
+
+  bool              did_launch;
+  bool              did_impact; 
+  
+  SceneObject*      missile_target;
+  SceneObject*      collide_exempt;
+
+  bool              hover_attack_go;
+  U32               hover_attack_tick;
+  
+  F32               starting_velocity;
+  Point3F           starting_vel_vec;
+
+  SimObject*        ss_object;
+  S32               ss_index;
+
+private:
+  void              init(bool on_server, bool on_client);
+  void              create_splash(const Point3F& pos);
+  SceneObject*      get_default_launcher() const;
+  void              get_launch_constraint_data(Point3F& pos, Point3F& vel);
+  void              get_launch_data(Point3F& pos, Point3F& vel);
+  bool              is_active() const { return (did_launch && !did_impact); }
+
+public:
+  /*
+  F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
+  */
+  /*C*/             afxMagicMissile();
+  /*C*/             afxMagicMissile(bool on_server, bool on_client);
+  /*D*/             ~afxMagicMissile();
+  virtual void      onDeleteNotify(SimObject*);
+
+  DECLARE_CONOBJECT(afxMagicMissile);
+  DECLARE_CATEGORY("AFX");
+
+  static void       initPersistFields();  
+
+  /*
+  virtual bool calculateImpact(float    simTime,
+                               Point3F& pointOfImpact,
+                               float&   impactTime);
+
+  static U32 smProjectileWarpTicks;
+
+protected:
+  static const U32 csmStaticCollisionMask;
+  static const U32 csmDynamicCollisionMask;
+  static const U32 csmDamageableMask;
+  */
+  
+  void              launch();
+  void              setChoreographer(afxChoreographer*); 
+  void              setStartingVelocityVector(const Point3F& vel_vec);
+  void              setStartingVelocity(const F32 vel);
+  void              getStartingVelocityValues(F32& vel, Point3F& vel_vec); 
+  void              setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicMissileCallback
+
+class afxMagicMissileCallback
+{
+public:
+  virtual void impactNotify(const Point3F& p, const Point3F& n, SceneObject*)=0;
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MAGIC_MISSILE_H_
+

+ 2709 - 0
Engine/source/afx/afxMagicSpell.cpp

@@ -0,0 +1,2709 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+#include "math/mathIO.h"
+#include "T3D/containerQuery.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxPhrase.h"
+#include "afx/afxMagicSpell.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpellData::ewValidator
+//
+// When any of the effect list fields (addCastingEffect, etc.) are set, this validator
+// intercepts the value and adds it to the appropriate effects list. One validator is
+// created for each effect list and an id is used to identify which list to add the effect
+// to.
+//
+void afxMagicSpellData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxMagicSpellData* spelldata = dynamic_cast<afxMagicSpellData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (spelldata && ew)
+  {
+    switch (id)
+    {
+    case CASTING_PHRASE:
+      spelldata->casting_fx_list.push_back(*ew);
+      break;
+    case LAUNCH_PHRASE:
+      spelldata->launch_fx_list.push_back(*ew);
+      break;
+    case DELIVERY_PHRASE:
+      spelldata->delivery_fx_list.push_back(*ew);
+      break;
+    case IMPACT_PHRASE:
+      spelldata->impact_fx_list.push_back(*ew);
+      break;
+    case LINGER_PHRASE:
+      spelldata->linger_fx_list.push_back(*ew);
+      break;
+    }
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class SpellFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj)
+  {
+     afxMagicSpell* spell = dynamic_cast<afxMagicSpell*>(obj);
+     if (spell)
+       spell->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpellData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMagicSpellData);
+
+ConsoleDocClass( afxMagicSpellData,
+   "@brief Defines the properties of an afxMagicSpell.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onDamage, void,
+   (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse),
+   (spell, label, flaver, target_id, amount, n, pos, ad_amount, radius, impulse),
+   "Called when the spell deals damage.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onDeactivate, void, (afxMagicSpell* spell), (spell),
+   "Called when the spell ends naturally.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onInterrupt, void, (afxMagicSpell* spell, ShapeBase* caster), (spell, caster),
+   "Called when the spell ends unnaturally due to an interruption.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onLaunch, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile),
+   (spell, caster, target, missile),
+   "Called when the spell's casting stage ends and the delivery stage begins.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onImpact, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal),
+   (spell, caster, impacted, pos, normal),
+   "Called at the spell's missile impact marking the end of the deliver stage and the start of the linger stage.\n"
+   "@param spell the spell object\n" );
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onPreactivate, bool,
+   (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra),
+   (param_holder, caster, target, extra),
+   "Called during spell casting before spell instance is fully created.\n");
+
+IMPLEMENT_CALLBACK( afxMagicSpellData, onActivate, void,
+   (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target),
+   (spell, caster, target),
+   "Called when the spell starts.\n"
+   "@param spell the spell object\n" );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxMagicSpellData::afxMagicSpellData()
+{
+  casting_dur = 0.0f;
+  delivery_dur = 0.0f;
+  linger_dur = 0.0f;
+
+  n_casting_loops = 1;
+  n_delivery_loops = 1;
+  n_linger_loops = 1;
+
+  extra_casting_time = 0.0f;
+  extra_delivery_time = 0.0f;
+  extra_linger_time = 0.0f;
+
+  // interrupt flags
+  do_move_interrupts = true;
+  move_interrupt_speed = 2.0f;
+
+  // delivers projectile spells
+  missile_db = 0;
+  launch_on_server_signal = false;
+  primary_target_types = PlayerObjectType;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxMagicSpellData::afxMagicSpellData(const afxMagicSpellData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  casting_dur = other.casting_dur;
+  delivery_dur = other.delivery_dur;
+  linger_dur = other.linger_dur;
+  n_casting_loops = other.n_casting_loops;
+  n_delivery_loops = other.n_delivery_loops;
+  n_linger_loops = other.n_linger_loops;
+  extra_casting_time = other.extra_casting_time;
+  extra_delivery_time = other.extra_delivery_time;
+  extra_linger_time = other.extra_linger_time;
+  do_move_interrupts = other.do_move_interrupts;
+  move_interrupt_speed = other.move_interrupt_speed;
+  missile_db = other.missile_db;
+  launch_on_server_signal = other.launch_on_server_signal;
+  primary_target_types = other.primary_target_types;
+
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+
+  casting_fx_list = other.casting_fx_list;
+  launch_fx_list = other.launch_fx_list;
+  delivery_fx_list = other.delivery_fx_list;
+  impact_fx_list = other.impact_fx_list;
+  linger_fx_list = other.linger_fx_list;
+}
+
+void afxMagicSpellData::reloadReset()
+{
+  casting_fx_list.clear();
+  launch_fx_list.clear();
+  delivery_fx_list.clear();
+  impact_fx_list.clear();
+  linger_fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxMagicSpellData)
+
+void afxMagicSpellData::initPersistFields()
+{
+  static ewValidator _castingPhrase(CASTING_PHRASE);
+  static ewValidator _launchPhrase(LAUNCH_PHRASE);
+  static ewValidator _deliveryPhrase(DELIVERY_PHRASE);
+  static ewValidator _impactPhrase(IMPACT_PHRASE);
+  static ewValidator _lingerPhrase(LINGER_PHRASE);
+
+  // for each effect list, dummy_fx_entry is set and then a validator adds it to the appropriate effects list
+
+  addGroup("Casting Stage");
+  addField("castingDur",            TypeF32,        myOffset(casting_dur),
+    "...");
+  addField("numCastingLoops",       TypeS32,        myOffset(n_casting_loops),
+    "...");
+  addField("extraCastingTime",      TypeF32,        myOffset(extra_casting_time),
+    "...");
+  addFieldV("addCastingEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_castingPhrase,
+    "...");
+  endGroup("Casting Stage");
+
+  addGroup("Delivery Stage");
+  addField("deliveryDur",           TypeF32,        myOffset(delivery_dur),
+    "...");
+  addField("numDeliveryLoops",      TypeS32,        myOffset(n_delivery_loops),
+    "...");
+  addField("extraDeliveryTime",     TypeF32,        myOffset(extra_delivery_time),
+    "...");
+  addFieldV("addLaunchEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_launchPhrase,
+    "...");
+  addFieldV("addDeliveryEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_deliveryPhrase,
+    "...");
+  endGroup("Delivery Stage");
+
+  addGroup("Linger Stage");
+  addField("lingerDur",             TypeF32,        myOffset(linger_dur),
+    "...");
+  addField("numLingerLoops",        TypeS32,        myOffset(n_linger_loops),
+    "...");
+  addField("extraLingerTime",       TypeF32,        myOffset(extra_linger_time),
+    "...");
+  addFieldV("addImpactEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_impactPhrase,
+    "...");
+  addFieldV("addLingerEffect", TYPEID<afxEffectBaseData>(), Offset(dummy_fx_entry, afxMagicSpellData), &_lingerPhrase,
+    "...");
+  endGroup("Linger Stage");
+
+  // interrupt flags
+  addField("allowMovementInterrupts", TypeBool,     myOffset(do_move_interrupts),
+    "...");
+  addField("movementInterruptSpeed",  TypeF32,      myOffset(move_interrupt_speed),
+    "...");
+
+  // delivers projectile spells
+  addField("missile",               TYPEID<afxMagicMissileData>(), myOffset(missile_db),
+    "...");
+  addField("launchOnServerSignal",  TypeBool,                   myOffset(launch_on_server_signal),
+    "...");
+  addField("primaryTargetTypes",    TypeS32,                    myOffset(primary_target_types),
+    "...");
+
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  onlyKeepClearSubstitutions("missile"); // subs resolving to "~~", or "~0" are OK
+  disableFieldSubstitutions("addCastingEffect");
+  disableFieldSubstitutions("addLaunchEffect");
+  disableFieldSubstitutions("addDeliveryEffect");
+  disableFieldSubstitutions("addImpactEffect");
+  disableFieldSubstitutions("addLingerEffect");
+}
+
+bool afxMagicSpellData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  if (missile_db != NULL && delivery_dur == 0.0)
+    delivery_dur = -1;
+
+  return true;
+}
+
+void afxMagicSpellData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxMagicSpellData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxMagicSpellData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(casting_dur);
+  stream->write(delivery_dur);
+  stream->write(linger_dur);
+  //
+  stream->write(n_casting_loops);
+  stream->write(n_delivery_loops);
+  stream->write(n_linger_loops);
+  //
+  stream->write(extra_casting_time);
+  stream->write(extra_delivery_time);
+  stream->write(extra_linger_time);
+
+  stream->writeFlag(do_move_interrupts);
+  stream->write(move_interrupt_speed);
+
+  writeDatablockID(stream, missile_db, packed);
+  stream->write(launch_on_server_signal);
+  stream->write(primary_target_types);
+
+  pack_fx(stream, casting_fx_list, packed);
+  pack_fx(stream, launch_fx_list, packed);
+  pack_fx(stream, delivery_fx_list, packed);
+  pack_fx(stream, impact_fx_list, packed);
+  pack_fx(stream, linger_fx_list, packed);
+}
+
+void afxMagicSpellData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&casting_dur);
+  stream->read(&delivery_dur);
+  stream->read(&linger_dur);
+  //
+  stream->read(&n_casting_loops);
+  stream->read(&n_delivery_loops);
+  stream->read(&n_linger_loops);
+  //
+  stream->read(&extra_casting_time);
+  stream->read(&extra_delivery_time);
+  stream->read(&extra_linger_time);
+
+  do_move_interrupts = stream->readFlag();
+  stream->read(&move_interrupt_speed);
+
+  missile_db = (afxMagicMissileData*) readDatablockID(stream);
+  stream->read(&launch_on_server_signal);
+  stream->read(&primary_target_types);
+
+  do_id_convert = true;
+  unpack_fx(stream, casting_fx_list);
+  unpack_fx(stream, launch_fx_list);
+  unpack_fx(stream, delivery_fx_list);
+  unpack_fx(stream, impact_fx_list);
+  unpack_fx(stream, linger_fx_list);
+}
+
+bool afxMagicSpellData::writeField(StringTableEntry fieldname, const char* value)
+{
+   if (!Parent::writeField(fieldname, value))
+      return false;
+
+   // don't write the dynamic array fields
+   if( fieldname == StringTable->insert("addCastingEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addLaunchEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addDeliveryEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addImpactEffect") )
+      return false;
+   if( fieldname == StringTable->insert("addLingerEffect") )
+      return false;
+
+   return true;
+}
+
+inline void expand_fx_list(afxEffectList& fx_list, const char* tag)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+    if (db_id != 0)
+    {
+      // try to convert id to pointer
+      if (!Sim::findObject(db_id, fx_list[i]))
+      {
+        Con::errorf(ConsoleLogEntry::General,
+          "afxMagicSpellData::preload() -- bad datablockId: 0x%x (%s)",
+          db_id, tag);
+      }
+    }
+  }
+}
+
+bool afxMagicSpellData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      SimObjectId missile_id = SimObjectId((uintptr_t)missile_db);
+      if (missile_id != 0)
+      {
+        // try to convert id to pointer
+        if (!Sim::findObject(missile_id, missile_db))
+        {
+          Con::errorf(ConsoleLogEntry::General,
+            "afxMagicSpellData::preload() -- bad datablockId: 0x%x (missile)",
+            missile_id);
+        }
+      }
+      expand_fx_list(casting_fx_list, "casting");
+      expand_fx_list(launch_fx_list, "launch");
+      expand_fx_list(delivery_fx_list, "delivery");
+      expand_fx_list(impact_fx_list, "impact");
+      expand_fx_list(linger_fx_list, "linger");
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxMagicSpellData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, casting_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, launch_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, delivery_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, impact_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, linger_fx_list);
+
+  if (missile_db)
+    missile_db->gather_cons_defs(defs);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxMagicSpellData, reset, void, (),,
+                   "Resets a spell datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+DefineEngineMethod(afxMagicSpellData, addCastingEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's casting phase.\n\n"
+                   "@ingroup AFX")
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addCastingEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->casting_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addLaunchEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's launch phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addLaunchEffect() -- "
+                "failed to find afxEffectWrapperData.");
+    return;
+  }
+
+  object->launch_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addDeliveryEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's delivery phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addDeliveryEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->delivery_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addImpactEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's impact phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addImpactEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->impact_fx_list.push_back(effect);
+}
+
+DefineEngineMethod(afxMagicSpellData, addLingerEffect, void, (afxEffectBaseData* effect),,
+                   "Adds an effect (wrapper or group) to a spell's linger phase.\n\n"
+                   "@ingroup AFX")
+
+{
+  if (!effect)
+  {
+    Con::errorf(ConsoleLogEntry::General,
+                "afxMagicSpellData::addLingerEffect() -- "
+                "missing afxEffectWrapperData.");
+    return;
+  }
+
+  object->linger_fx_list.push_back(effect);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingStart, void, (), (),
+   "A callout called on clients by spells when the casting stage begins.\n"
+   "@ingroup AFX\n" );
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingProgressUpdate, void, (F32 frac), (frac),
+   "A callout called periodically on clients by spells to indicate casting progress.\n"
+   "@ingroup AFX\n" );
+
+IMPLEMENT_GLOBAL_CALLBACK( onCastingEnd, void, (), (),
+   "A callout called on clients by spells when the casting stage ends.\n"
+   "@ingroup AFX\n" );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// CastingPhrase_C
+//    Subclass of afxPhrase for the client casting phrase.
+//    This subclass adds handling of the casting progress
+//    bar in cases where the caster is the client's control
+//    object.
+//
+
+class CastingPhrase_C : public afxPhrase
+{
+  typedef afxPhrase Parent;
+  ShapeBase*    caster;
+  bool          notify_castbar;
+  F32           castbar_progress;
+public:
+  /*C*/         CastingPhrase_C(ShapeBase* caster, bool notify_castbar);
+  virtual void  start(F32 startstamp, F32 timestamp);
+  virtual void  update(F32 dt, F32 timestamp);
+  virtual void  stop(F32 timestamp);
+  virtual void  interrupt(F32 timestamp);
+};
+
+CastingPhrase_C::CastingPhrase_C(ShapeBase* c, bool notify)
+  : afxPhrase(false, true)
+{
+  caster = c;
+  notify_castbar = notify;
+  castbar_progress = 0.0f;
+}
+
+void CastingPhrase_C::start(F32 startstamp, F32 timestamp)
+{
+  Parent::start(startstamp, timestamp); //START
+  if (notify_castbar)
+  {
+    castbar_progress = 0.0f;
+    onCastingStart_callback();
+  }
+}
+
+void CastingPhrase_C::update(F32 dt, F32 timestamp)
+{
+  Parent::update(dt, timestamp);
+
+  if (!notify_castbar)
+    return;
+
+  if (dur > 0 && n_loops > 0)
+  {
+    F32 nfrac = (timestamp - starttime)/(dur*n_loops);
+    if (nfrac - castbar_progress > 1.0f/200.0f)
+    {
+      castbar_progress = (nfrac < 1.0f) ? nfrac : 1.0f;
+      onCastingProgressUpdate_callback(castbar_progress);
+    }
+  }
+}
+
+void CastingPhrase_C::stop(F32 timestamp)
+{
+  Parent::stop(timestamp);
+  if (notify_castbar)
+  {
+    onCastingEnd_callback();
+    notify_castbar = false;
+  }
+}
+
+void CastingPhrase_C::interrupt(F32 timestamp)
+{
+  Parent::interrupt(timestamp);
+  if (notify_castbar)
+  {
+    onCastingEnd_callback();
+    notify_castbar = false;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// some enum to name converters for debugging purposes
+
+#ifdef USE_FOR_DEBUG_MESSAGES
+static char* name_from_state(U8 s)
+{
+  switch (s)
+  {
+  case afxMagicSpell::INACTIVE_STATE:
+    return "inactive";
+  case afxMagicSpell::CASTING_STATE:
+    return "casting";
+  case afxMagicSpell::DELIVERY_STATE:
+    return "delivery";
+  case afxMagicSpell::LINGER_STATE:
+    return "linger";
+  case afxMagicSpell::CLEANUP_STATE:
+    return "cleanup";
+  case afxMagicSpell::DONE_STATE:
+    return "done";
+  }
+
+  return "unknown";
+}
+
+static char* name_from_event(U8 e)
+{
+  switch (e)
+  {
+  case afxMagicSpell::NULL_EVENT:
+    return "null";
+  case afxMagicSpell::ACTIVATE_EVENT:
+    return "activate";
+  case afxMagicSpell::LAUNCH_EVENT:
+    return "launch";
+  case afxMagicSpell::IMPACT_EVENT:
+    return "impact";
+  case afxMagicSpell::SHUTDOWN_EVENT:
+    return "shutdown";
+  case afxMagicSpell::DEACTIVATE_EVENT:
+    return "deactivate";
+  case afxMagicSpell::INTERRUPT_PHASE_EVENT:
+    return "interrupt_phase";
+  case afxMagicSpell::INTERRUPT_SPELL_EVENT:
+    return "interrupt_spell";
+  }
+
+  return "unknown";
+}
+#endif
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+IMPLEMENT_CO_NETOBJECT_V1(afxMagicSpell);
+
+ConsoleDocClass( afxMagicSpell,
+   "@brief A magic spell effects choreographer.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+// static
+StringTableEntry  afxMagicSpell::CASTER_CONS;
+StringTableEntry  afxMagicSpell::TARGET_CONS;
+StringTableEntry  afxMagicSpell::MISSILE_CONS;
+StringTableEntry  afxMagicSpell::CAMERA_CONS;
+StringTableEntry  afxMagicSpell::LISTENER_CONS;
+StringTableEntry  afxMagicSpell::IMPACT_POINT_CONS;
+StringTableEntry  afxMagicSpell::IMPACTED_OBJECT_CONS;
+
+void afxMagicSpell::init()
+{
+  // setup static predefined constraint names
+  if (CASTER_CONS == 0)
+  {
+    CASTER_CONS = StringTable->insert("caster");
+    TARGET_CONS = StringTable->insert("target");
+    MISSILE_CONS = StringTable->insert("missile");
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+    IMPACT_POINT_CONS = StringTable->insert("impactPoint");
+    IMPACTED_OBJECT_CONS = StringTable->insert("impactedObject");
+  }
+
+  // afxMagicSpell is always in scope, however effects
+  // do their own scoping in that they will shut off if
+  // their position constraint leaves scope.
+  //
+  //   note -- ghosting is delayed until constraint
+  //           initialization is done.
+  //
+  //mNetFlags.set(Ghostable | ScopeAlways);
+  mNetFlags.clear(Ghostable | ScopeAlways);
+
+  datablock = NULL;
+  exeblock = NULL;
+  missile_db = NULL;
+
+  caster = NULL;
+  target = NULL;
+
+  caster_field = NULL;
+  target_field = NULL;
+
+  caster_scope_id = 0;
+  target_scope_id = 0;
+  target_is_shape = false;
+
+  constraints_initialized = false;
+  scoping_initialized = false;
+
+  spell_state = (U8) INACTIVE_STATE;
+  spell_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  CASTER_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  TARGET_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, MISSILE_CONS);
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  IMPACT_POINT_CONS);
+  constraint_mgr->defineConstraint(OBJECT_CONSTRAINT,  IMPACTED_OBJECT_CONS);
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    phrases[i] = NULL;
+    tfactors[i] = 1.0f;
+  }
+
+  notify_castbar = false;
+  overall_time_factor = 1.0f;
+
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+
+  missile = NULL;
+  missile_is_armed = false;
+  impacted_obj = NULL;
+  impact_pos.zero();
+  impact_norm.set(0,0,1);
+  impacted_scope_id = 0;
+  impacted_is_shape = false;
+}
+
+afxMagicSpell::afxMagicSpell()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxMagicSpell::afxMagicSpell(ShapeBase* caster, SceneObject* target)
+{
+  started_with_newop = false;
+  init();
+
+  this->caster = caster;
+  if (caster)
+  {
+    caster_field = caster;
+    deleteNotify(caster);
+    processAfter(caster);
+  }
+
+  this->target = target;
+  if (target)
+  {
+    target_field = target;
+    deleteNotify(target);
+  }
+}
+
+afxMagicSpell::~afxMagicSpell()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      phrases[i]->interrupt(spell_elapsed);
+      delete phrases[i];
+    }
+  }
+
+  if (missile)
+    missile->deleteObject();
+
+  if (missile_db && missile_db->isTempClone())
+  {
+    delete missile_db;
+    missile_db = 0;
+  }
+
+   if (datablock && datablock->isTempClone())
+   {
+     delete datablock;
+     datablock = 0;
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxMagicSpell::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxMagicSpellData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  if (isServerObject() && started_with_newop)
+  {
+    // copy dynamic fields from the datablock but
+    // don't replace fields with a value
+    assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true);
+  }
+
+  exeblock = datablock;
+  missile_db = datablock->missile_db;
+
+  if (isClientObject())
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxMagicSpellData* orig_db = datablock;
+      datablock = new afxMagicSpellData(*orig_db, true);
+      exeblock = orig_db;
+      missile_db = datablock->missile_db;
+      // Don't perform substitutions yet, the spell's dynamic fields haven't
+      // arrived yet and the substitutions may refer to them. Hold off and do
+      // in in the onAdd() method.
+    }
+  }
+  else if (started_with_newop)
+  {
+    // make a temp datablock clone if there are substitutions
+    if (datablock->getSubstitutionCount() > 0)
+    {
+      afxMagicSpellData* orig_db = datablock;
+      datablock = new afxMagicSpellData(*orig_db, true);
+      exeblock = orig_db;
+      orig_db->performSubstitutions(datablock, this, ranking);
+      missile_db = datablock->missile_db;
+    }
+  }
+
+  return true;
+}
+
+void afxMagicSpell::processTick(const Move* m)
+{
+  Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxMagicSpell::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxMagicSpell::onAdd()
+{
+  if (!Parent::onAdd())
+    return false ;
+
+  if (isClientObject())
+  {
+    if (datablock->isTempClone())
+    {
+      afxMagicSpellData* orig_db = (afxMagicSpellData*)exeblock;
+      orig_db->performSubstitutions(datablock, this, ranking);
+      missile_db = datablock->missile_db;
+      notify_castbar = (notify_castbar && (datablock->casting_dur > 0.0f));
+    }
+  }
+  else if (started_with_newop && !postpone_activation)
+  {
+    if (!activationCallInit())
+      return false;
+    activate();
+  }
+
+  return true ;
+}
+
+void afxMagicSpell::onRemove()
+{
+  Parent::onRemove();
+}
+
+void afxMagicSpell::onDeleteNotify(SimObject* obj)
+{
+  // caster deleted?
+  ShapeBase* shape = dynamic_cast<ShapeBase*>(obj);
+  if (shape == caster)
+  {
+    clearProcessAfter();
+    caster = NULL;
+    caster_field = NULL;
+    caster_scope_id = 0;
+  }
+
+  // target deleted?
+  SceneObject* scene_obj = dynamic_cast<SceneObject*>(obj);
+  if (scene_obj == target)
+  {
+    target = NULL;
+    target_field = NULL;
+    target_scope_id = 0;
+    target_is_shape = false;
+  }
+
+  // impacted_obj deleted?
+  if (scene_obj == impacted_obj)
+  {
+    impacted_obj = NULL;
+    impacted_scope_id = 0;
+    impacted_is_shape = false;
+  }
+
+  // missile deleted?
+  afxMagicMissile* missile = dynamic_cast<afxMagicMissile*>(obj);
+  if (missile != NULL && missile == this->missile)
+  {
+    this->missile = NULL;
+  }
+
+  // something else
+  Parent::onDeleteNotify(obj);
+}
+
+// static
+void afxMagicSpell::initPersistFields()
+{
+  addField("caster", TYPEID<SimObject>(), Offset(caster_field, afxMagicSpell),
+    "...");
+  addField("target", TYPEID<SimObject>(), Offset(target_field, afxMagicSpell),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxMagicSpell::pack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  // pack caster's ghost index or scope id if not yet ghosted
+  if (stream->writeFlag(caster != NULL))
+  {
+    S32 ghost_idx = conn->getGhostIndex(caster);
+    if (stream->writeFlag(ghost_idx != -1))
+      stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+    else
+    {
+      bool bit = (caster) ? (caster->getScopeId() > 0) : false;
+      if (stream->writeFlag(bit))
+        stream->writeInt(caster->getScopeId(), NetObject::SCOPE_ID_BITS);
+    }
+  }
+
+  // pack target's ghost index or scope id if not yet ghosted
+  if (stream->writeFlag(target != NULL))
+  {
+    S32 ghost_idx = conn->getGhostIndex(target);
+    if (stream->writeFlag(ghost_idx != -1))
+      stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+    else
+    {
+      if (stream->writeFlag(target->getScopeId() > 0))
+      {
+        stream->writeInt(target->getScopeId(), NetObject::SCOPE_ID_BITS);
+        stream->writeFlag(dynamic_cast<ShapeBase*>(target) != NULL); // is shape?
+      }
+    }
+  }
+
+  Parent::pack_constraint_info(conn, stream);
+}
+
+void afxMagicSpell::unpack_constraint_info(NetConnection* conn, BitStream* stream)
+{
+  caster = NULL;
+  caster_field = NULL;
+  caster_scope_id = 0;
+  if (stream->readFlag()) // has caster
+  {
+    if (stream->readFlag()) // has ghost_idx
+    {
+      S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+      caster = dynamic_cast<ShapeBase*>(conn->resolveGhost(ghost_idx));
+      if (caster)
+      {
+        caster_field = caster;
+        deleteNotify(caster);
+        processAfter(caster);
+      }
+    }
+    else
+    {
+      if (stream->readFlag()) // has scope_id (is always a shape)
+        caster_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+    }
+  }
+
+  target = NULL;
+  target_field = NULL;
+  target_scope_id = 0;
+  target_is_shape = false;
+  if (stream->readFlag()) // has target
+  {
+    if (stream->readFlag()) // has ghost_idx
+    {
+      S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+      target = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+      if (target)
+      {
+        target_field = target;
+        deleteNotify(target);
+      }
+    }
+    else
+    {
+      if (stream->readFlag()) // has scope_id
+      {
+        target_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+        target_is_shape = stream->readFlag(); // is shape?
+      }
+    }
+  }
+
+  Parent::unpack_constraint_info(conn, stream);
+}
+
+U32 afxMagicSpell::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  S32 mark_stream_pos = stream->getCurPos();
+
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    // pack extra object's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(dynamic_cast<NetObject*>(extra) != 0))
+    {
+      NetObject* net_extra = (NetObject*)extra;
+      S32 ghost_idx = conn->getGhostIndex(net_extra);
+      if (stream->writeFlag(ghost_idx != -1))
+         stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(net_extra->getScopeId() > 0))
+        {
+          stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    // pack initial exec conditions
+    stream->write(exec_conds_mask);
+
+    // flag if this client owns the spellcaster
+    bool client_owns_caster = is_caster_client(caster, dynamic_cast<GameConnection*>(conn));
+    stream->writeFlag(client_owns_caster);
+
+    // pack per-phrase time-factor values
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+      stream->write(tfactors[i]);
+
+    // flag if this conn is zoned-in yet
+    bool zoned_in = client_owns_caster;
+    if (!zoned_in)
+    {
+      GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+      zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    }
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(spell_state);
+    stream->write(state_elapsed());
+    stream->write(spell_elapsed);
+  }
+
+  // SyncEvent
+  if (stream->writeFlag((mask & SyncEventMask) && !(mask & InitialUpdateMask)))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  // LaunchEvent
+  if (stream->writeFlag((mask & LaunchEventMask) && (marks_mask & MARK_LAUNCH) && missile))
+  {
+    F32 vel; Point3F vel_vec;
+    missile->getStartingVelocityValues(vel, vel_vec);
+    // pack launch vector and velocity
+    stream->write(vel);
+    mathWrite(*stream, vel_vec);
+  }
+
+  // ImpactEvent
+  if (stream->writeFlag(((mask & ImpactEventMask) || (mask & SyncEventMask)) && (marks_mask & MARK_IMPACT)))
+  {
+    // pack impact objects's ghost index or scope id if not yet ghosted
+    if (stream->writeFlag(impacted_obj != NULL))
+    {
+      S32 ghost_idx = conn->getGhostIndex(impacted_obj);
+      if (stream->writeFlag(ghost_idx != -1))
+        stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount);
+      else
+      {
+        if (stream->writeFlag(impacted_obj->getScopeId() > 0))
+        {
+          stream->writeInt(impacted_obj->getScopeId(), NetObject::SCOPE_ID_BITS);
+          stream->writeFlag(dynamic_cast<ShapeBase*>(impacted_obj) != NULL);
+        }
+      }
+    }
+
+    // pack impact position and normal
+    mathWrite(*stream, impact_pos);
+    mathWrite(*stream, impact_norm);
+    stream->write(exec_conds_mask);
+
+    ShapeBase* temp_shape;
+    stream->writeFlag(caster != 0 && caster->getDamageState() == ShapeBase::Enabled);
+    temp_shape = dynamic_cast<ShapeBase*>(target);
+    stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
+    temp_shape = dynamic_cast<ShapeBase*>(impacted_obj);
+    stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled);
+  }
+
+  check_packet_usage(conn, stream, mark_stream_pos, "afxMagicSpell:");
+
+  AssertISV(stream->isValid(), "afxMagicSpell::packUpdate(): write failure occurred, possibly caused by packet-size overrun.");
+
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+ // CONSTRAINT REMAPPING <<
+bool afxMagicSpell::remap_builtin_constraint(SceneObject* obj, const char* cons_name)
+{
+  StringTableEntry cons_name_ste = StringTable->insert(cons_name);
+
+  if (cons_name_ste == CASTER_CONS)
+    return true;
+  if (cons_name_ste == TARGET_CONS)
+  {
+    if (obj && target && obj != target && !target_cons_id.undefined())
+    {
+      target = obj;
+      constraint_mgr->setReferenceObject(target_cons_id, target);
+      if (isServerObject())
+      {
+        if (target->isScopeable())
+          constraint_mgr->addScopeableObject(target);
+      }
+    }
+    return true;
+  }
+  if (cons_name_ste == MISSILE_CONS)
+    return true;
+  if (cons_name_ste == CAMERA_CONS)
+    return true;
+  if (cons_name_ste == LISTENER_CONS)
+    return true;
+  if (cons_name_ste == IMPACT_POINT_CONS)
+    return true;
+  if (cons_name_ste == IMPACTED_OBJECT_CONS)
+    return true;
+
+  return false;
+}
+ // CONSTRAINT REMAPPING >>
+
+void afxMagicSpell::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U16 new_marks_mask = 0;
+  U8 new_spell_state = INACTIVE_STATE;
+  F32 new_state_elapsed = 0;
+  F32 new_spell_elapsed = 0;;
+
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+    initial_update = true;
+
+    // extra sent
+    if (stream->readFlag())
+    {
+      // cleanup?
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        extra = dynamic_cast<SimObject*>(conn->resolveGhost(ghost_idx));
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          stream->readInt(NetObject::SCOPE_ID_BITS);
+        }
+      }
+    }
+
+    // unpack initial exec conditions
+    stream->read(&exec_conds_mask);
+
+    // if this is controlling client for the caster,
+    // enable castbar updates
+    bool client_owns_caster = stream->readFlag();
+    if (client_owns_caster)
+      notify_castbar = Con::isFunction("onCastingStart");
+
+    // unpack per-phrase time-factor values
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+      stream->read(&tfactors[i]);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_spell_state);
+    stream->read(&new_state_elapsed);
+    stream->read(&new_spell_elapsed);
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  if ((do_sync_event = stream->readFlag()) == true)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  // LaunchEvent
+  if (stream->readFlag())
+  {
+    F32 vel; Point3F vel_vec;
+    stream->read(&vel);
+    mathRead(*stream, &vel_vec);
+    if (missile)
+    {
+      missile->setStartingVelocity(vel);
+      missile->setStartingVelocityVector(vel_vec);
+    }
+  }
+
+  // ImpactEvent
+  if (stream->readFlag())
+  {
+    if (impacted_obj)
+      clearNotify(impacted_obj);
+    impacted_obj = NULL;
+    impacted_scope_id = 0;
+    impacted_is_shape = false;
+    if (stream->readFlag()) // is impacted_obj
+    {
+      if (stream->readFlag()) // is ghost_idx
+      {
+        S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount);
+        impacted_obj = dynamic_cast<SceneObject*>(conn->resolveGhost(ghost_idx));
+        if (impacted_obj)
+          deleteNotify(impacted_obj);
+      }
+      else
+      {
+        if (stream->readFlag()) // has scope_id
+        {
+          impacted_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS);
+          impacted_is_shape = stream->readFlag(); // is shape?
+        }
+      }
+    }
+
+    mathRead(*stream, &impact_pos);
+    mathRead(*stream, &impact_norm);
+    stream->read(&exec_conds_mask);
+
+    bool caster_alive = stream->readFlag();
+    bool target_alive = stream->readFlag();
+    bool impacted_alive = stream->readFlag();
+
+    afxConstraint* cons;
+    if ((cons = constraint_mgr->getConstraint(caster_cons_id)) != 0)
+      cons->setLivingState(caster_alive);
+    if ((cons = constraint_mgr->getConstraint(target_cons_id)) != 0)
+      cons->setLivingState(target_alive);
+    if ((cons = constraint_mgr->getConstraint(impacted_cons_id)) != 0)
+      cons->setLivingState(impacted_alive);
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    spell_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_spell_state, new_state_elapsed, new_spell_elapsed);
+}
+
+void afxMagicSpell::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxMagicSpell::state_expired()
+{
+  afxPhrase* phrase = NULL;
+
+  switch (spell_state)
+  {
+  case CASTING_STATE:
+    phrase = phrases[CASTING_PHRASE];
+    break;
+  case DELIVERY_STATE:
+    phrase = phrases[DELIVERY_PHRASE];
+    break;
+  case LINGER_STATE:
+    phrase = phrases[LINGER_PHRASE];
+    break;
+  }
+
+  if (phrase)
+  {
+    if (phrase->expired(spell_elapsed))
+      return (!phrase->recycle(spell_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+F32 afxMagicSpell::state_elapsed()
+{
+  afxPhrase* phrase = NULL;
+
+  switch (spell_state)
+  {
+  case CASTING_STATE:
+    phrase = phrases[CASTING_PHRASE];
+    break;
+  case DELIVERY_STATE:
+    phrase = phrases[DELIVERY_PHRASE];
+    break;
+  case LINGER_STATE:
+    phrase = phrases[LINGER_PHRASE];
+    break;
+  }
+
+  return (phrase) ? phrase->elapsed(spell_elapsed) : 0.0f;
+}
+
+void afxMagicSpell::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isServerObject())
+  {
+    caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster);
+    target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target);
+#if defined(AFX_CAP_SCOPE_TRACKING)
+    if (caster && caster->isScopeable())
+      constraint_mgr->addScopeableObject(caster);
+
+    if (target && target->isScopeable())
+      constraint_mgr->addScopeableObject(target);
+#endif
+
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+  }
+  else // if (isClientObject())
+  {
+    if (caster)
+      caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster);
+    else if (caster_scope_id > 0)
+      caster_cons_id = constraint_mgr->setReferenceObjectByScopeId(CASTER_CONS, caster_scope_id, true);
+
+    if (target)
+      target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target);
+    else if (target_scope_id > 0)
+      target_cons_id = constraint_mgr->setReferenceObjectByScopeId(TARGET_CONS, target_scope_id, target_is_shape);
+
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxMagicSpell::init_scoping()
+{
+  if (scoping_initialized)
+  {
+    //Con::printf("SCOPING ALREADY INITIALIZED");
+    return;
+  }
+
+  if (isServerObject())
+  {
+    if (explicit_clients.size() > 0)
+    {
+      for (U32 i = 0; i < explicit_clients.size(); i++)
+        explicit_clients[i]->objectLocalScopeAlways(this);
+    }
+    else
+    {
+      mNetFlags.set(Ghostable);
+      setScopeAlways();
+    }
+    scoping_initialized = true;
+  }
+}
+
+void afxMagicSpell::setup_casting_fx()
+{
+  if (isServerObject())
+    phrases[CASTING_PHRASE] = new afxPhrase(isServerObject(), true);
+  else
+    phrases[CASTING_PHRASE] = new CastingPhrase_C(caster, notify_castbar);
+
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->init(datablock->casting_fx_list, datablock->casting_dur, this,
+                                  tfactors[CASTING_PHRASE], datablock->n_casting_loops, 0,
+                                  datablock->extra_casting_time);
+}
+
+void afxMagicSpell::setup_launch_fx()
+{
+  phrases[LAUNCH_PHRASE] = new afxPhrase(isServerObject(), false);
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->init(datablock->launch_fx_list, -1, this,
+                                 tfactors[LAUNCH_PHRASE], 1);
+}
+
+void afxMagicSpell::setup_delivery_fx()
+{
+  phrases[DELIVERY_PHRASE] = new afxPhrase(isServerObject(), true);
+  if (phrases[DELIVERY_PHRASE])
+  {
+    phrases[DELIVERY_PHRASE]->init(datablock->delivery_fx_list, datablock->delivery_dur, this,
+                                   tfactors[DELIVERY_PHRASE], datablock->n_delivery_loops, 0,
+                                   datablock->extra_delivery_time);
+  }
+}
+
+void afxMagicSpell::setup_impact_fx()
+{
+  phrases[IMPACT_PHRASE] = new afxPhrase(isServerObject(), false);
+  if (phrases[IMPACT_PHRASE])
+  {
+    phrases[IMPACT_PHRASE]->init(datablock->impact_fx_list, -1, this,
+                                 tfactors[IMPACT_PHRASE], 1);
+  }
+}
+
+void afxMagicSpell::setup_linger_fx()
+{
+  phrases[LINGER_PHRASE] = new afxPhrase(isServerObject(), true);
+  if (phrases[LINGER_PHRASE])
+    phrases[LINGER_PHRASE]->init(datablock->linger_fx_list, datablock->linger_dur, this,
+                                 tfactors[LINGER_PHRASE], datablock->n_linger_loops, 0,
+                                 datablock->extra_linger_time);
+}
+
+bool afxMagicSpell::cleanup_over()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i] && !phrases[i]->isEmpty())
+      return false;
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+//
+// MISSILE STUFF
+//
+
+void afxMagicSpell::init_missile_s(afxMagicMissileData* mm_db)
+{
+  if (missile)
+    clearNotify(missile);
+
+  // create the missile
+  missile = new afxMagicMissile(true, false);
+  missile->setSubstitutionData(this, ranking);
+  missile->setDataBlock(mm_db);
+  missile->setChoreographer(this);
+  if (!missile->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register missile instance.");
+    delete missile;
+    missile = NULL;
+  }
+
+  if (missile)
+  {
+    deleteNotify(missile);
+    registerForCleanup(missile);
+  }
+}
+
+void afxMagicSpell::launch_missile_s()
+{
+  if (missile)
+  {
+    missile->launch();
+    constraint_mgr->setReferenceObject(MISSILE_CONS, missile);
+  }
+}
+
+void afxMagicSpell::init_missile_c(afxMagicMissileData* mm_db)
+{
+  if (missile)
+    clearNotify(missile);
+
+  // create the missile
+  missile = new afxMagicMissile(false, true);
+  missile->setSubstitutionData(this, ranking);
+  missile->setDataBlock(mm_db);
+  missile->setChoreographer(this);
+  if (!missile->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register missile instance.");
+    delete missile;
+    missile = NULL;
+  }
+
+  if (missile)
+  {
+    deleteNotify(missile);
+    registerForCleanup(missile);
+  }
+}
+
+void afxMagicSpell::launch_missile_c()
+{
+  if (missile)
+  {
+    missile->launch();
+    constraint_mgr->setReferenceObject(MISSILE_CONS, missile);
+  }
+}
+
+bool afxMagicSpell::is_impact_in_water(SceneObject* obj, const Point3F& p)
+{
+  // AFX_T3D_BROKEN -- water impact detection is disabled. Look at projectile.
+  return false;
+}
+
+void afxMagicSpell::impactNotify(const Point3F& p, const Point3F& n, SceneObject* obj)
+{
+  if (isClientObject())
+    return;
+
+  ///impact_time_ms = spell_elapsed_ms;
+  if (impacted_obj)
+      clearNotify(impacted_obj);
+  impacted_obj = obj;
+  impact_pos = p;
+  impact_norm = n;
+
+  if (impacted_obj != NULL)
+  {
+    deleteNotify(impacted_obj);
+    exec_conds_mask |= IMPACTED_SOMETHING;
+    if (impacted_obj == target)
+      exec_conds_mask |= IMPACTED_TARGET;
+    if (impacted_obj->getTypeMask() & datablock->primary_target_types)
+      exec_conds_mask |= IMPACTED_PRIMARY;
+  }
+
+  if (is_impact_in_water(obj, p))
+    exec_conds_mask |= IMPACT_IN_WATER;
+
+  postSpellEvent(IMPACT_EVENT);
+
+  if (missile)
+    clearNotify(missile);
+  missile = NULL;
+}
+
+void afxMagicSpell::executeScriptEvent(const char* method, afxConstraint* cons,
+                                        const MatrixF& xfm, const char* data)
+{
+  SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL;
+
+  char *arg_buf = Con::getArgBuffer(256);
+  Point3F pos;
+  xfm.getColumn(3,&pos);
+  AngAxisF aa(xfm);
+  dSprintf(arg_buf,256,"%g %g %g %g %g %g %g",
+           pos.x, pos.y, pos.z,
+           aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
+
+  // CALL SCRIPT afxChoreographerData::method(%spell, %caster, %constraint, %transform, %data)
+  Con::executef(exeblock, method,
+                getIdString(),
+                (caster) ? caster->getIdString() : "",
+                (cons_obj) ? cons_obj->getIdString() : "",
+                arg_buf,
+                data);
+}
+
+void afxMagicSpell::inflictDamage(const char * label, const char* flavor, SimObjectId target_id,
+                                   F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse)
+{
+ // Con::printf("INFLICT-DAMAGE label=%s flav=%s id=%d amt=%g n=%d rad=%g pos=(%g %g %g) imp=%g",
+ //             label, flavor, target_id, amount, n, radius, pos.x, pos.y, pos.z, impulse);
+
+  // CALL SCRIPT afxMagicSpellData::onDamage()
+  //    onDamage(%spell, %label, %type, %damaged_obj, %amount, %count, %pos, %ad_amount,
+  //             %radius, %impulse)
+  datablock->onDamage_callback(this, label, flavor, target_id, amount, n, pos, ad_amount, radius, impulse);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxMagicSpell::process_server()
+{
+  if (spell_state != INACTIVE_STATE)
+    spell_elapsed += TickSec;
+
+  U8 pending_state = spell_state;
+
+  // check for state changes
+  switch (spell_state)
+  {
+
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = CASTING_STATE;
+    break;
+
+  case CASTING_STATE:
+    if (datablock->casting_dur > 0.0f && datablock->do_move_interrupts && is_caster_moving())
+    {
+      displayScreenMessage(caster, "SPELL INTERRUPTED.");
+      postSpellEvent(INTERRUPT_SPELL_EVENT);
+    }
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_CASTING)
+      pending_state = DELIVERY_STATE;
+    else if (marks_mask & MARK_LAUNCH)
+      pending_state = DELIVERY_STATE;
+    else if (state_expired())
+      pending_state = DELIVERY_STATE;
+    break;
+
+  case DELIVERY_STATE:
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_DELIVERY)
+      pending_state = LINGER_STATE;
+    else if (marks_mask & MARK_IMPACT)
+      pending_state = LINGER_STATE;
+    else if (state_expired())
+      pending_state = LINGER_STATE;
+    break;
+
+  case LINGER_STATE:
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+
+  case CLEANUP_STATE:
+    if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (spell_state != pending_state)
+    change_state_s(pending_state);
+
+  if (spell_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(TickSec, spell_elapsed);
+
+  if (missile_is_armed)
+  {
+    launch_missile_s();
+    missile_is_armed = false;
+  }
+}
+
+void afxMagicSpell::change_state_s(U8 pending_state)
+{
+  if (spell_state == pending_state)
+    return;
+
+  // LEAVING THIS STATE
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    leave_casting_state_s();
+    break;
+  case DELIVERY_STATE:
+    leave_delivery_state_s();
+    break;
+  case LINGER_STATE:
+    leave_linger_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  spell_state = pending_state;
+
+  // ENTERING THIS STATE
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    enter_casting_state_s();
+    break;
+  case DELIVERY_STATE:
+    enter_delivery_state_s();
+    break;
+  case LINGER_STATE:
+    enter_linger_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxMagicSpell::enter_done_state_s()
+{
+  postSpellEvent(DEACTIVATE_EVENT);
+
+  if (marks_mask & MARK_INTERRUPTS)
+  {
+    Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500);
+  }
+  else
+  {
+    F32 done_time = spell_elapsed;
+
+    for (S32 i = 0; i < NUM_PHRASES; i++)
+    {
+      if (phrases[i])
+      {
+        F32 phrase_done;
+        if (phrases[i]->willStop() && phrases[i]->isInfinite())
+          phrase_done = spell_elapsed + phrases[i]->calcAfterLife();
+        else
+          phrase_done = phrases[i]->calcDoneTime();
+        if (phrase_done > done_time)
+          done_time = phrase_done;
+      }
+    }
+
+    F32 time_left = done_time - spell_elapsed;
+    if (time_left < 0)
+      time_left = 0;
+
+    Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+  }
+
+  // CALL SCRIPT afxMagicSpellData::onDeactivate(%spell)
+  datablock->onDeactivate_callback(this);
+}
+
+void afxMagicSpell::enter_casting_state_s()
+{
+  // note - onActivate() is called in cast_spell() instead of here to make sure any
+  // new time-factor settings resolve before they are sent off to the clients.
+
+  // stamp constraint-mgr starting time and reset spell timer
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  spell_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_casting_fx();
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->start(spell_elapsed, spell_elapsed);
+
+  // initialize missile
+  if (missile_db)
+  {
+    missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking);
+    init_missile_s(missile_db);
+  }
+}
+
+void afxMagicSpell::leave_casting_state_s()
+{
+  if (phrases[CASTING_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+    {
+      //Con::printf("INTERRUPT CASTING (S)");
+      phrases[CASTING_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING CASTING (S)");
+      phrases[CASTING_PHRASE]->stop(spell_elapsed);
+    }
+  }
+
+  if (marks_mask & MARK_INTERRUPT_CASTING)
+  {
+    // CALL SCRIPT afxMagicSpellData::onInterrupt(%spell, %caster)
+    datablock->onInterrupt_callback(this, caster);
+  }
+}
+
+void afxMagicSpell::enter_delivery_state_s()
+{
+  // CALL SCRIPT afxMagicSpellData::onLaunch(%spell, %caster, %target, %missile)
+  datablock->onLaunch_callback(this, caster, target, missile);
+
+  if (datablock->launch_on_server_signal)
+    postSpellEvent(LAUNCH_EVENT);
+
+  missile_is_armed = true;
+
+  // start launch effects
+  setup_launch_fx();
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+  // start delivery effects
+  setup_delivery_fx();
+  if (phrases[DELIVERY_PHRASE])
+    phrases[DELIVERY_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+}
+
+void afxMagicSpell::leave_delivery_state_s()
+{
+  if (phrases[DELIVERY_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+    {
+      //Con::printf("INTERRUPT DELIVERY (S)");
+      phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING DELIVERY (S)");
+      phrases[DELIVERY_PHRASE]->stop(spell_elapsed);
+    }
+  }
+
+  if (!missile && !(marks_mask & MARK_IMPACT))
+  {
+    if (target)
+    {
+      Point3F p = afxMagicSpell::getShapeImpactPos(target);
+      Point3F n = Point3F(0,0,1);
+      impactNotify(p, n, target);
+    }
+    else
+    {
+      Point3F p = Point3F(0,0,0);
+      Point3F n = Point3F(0,0,1);
+      impactNotify(p, n, 0);
+    }
+  }
+}
+
+void afxMagicSpell::enter_linger_state_s()
+{
+  if (impacted_obj)
+  {
+    impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj);
+#if defined(AFX_CAP_SCOPE_TRACKING)
+    if (impacted_obj->isScopeable())
+      constraint_mgr->addScopeableObject(impacted_obj);
+#endif
+  }
+  else
+    constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
+
+  // start impact effects
+  setup_impact_fx();
+  if (phrases[IMPACT_PHRASE])
+    phrases[IMPACT_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+  // start linger effects
+  setup_linger_fx();
+  if (phrases[LINGER_PHRASE])
+    phrases[LINGER_PHRASE]->start(spell_elapsed, spell_elapsed); //START
+
+#if 0 // code temporarily replaced with old callback technique in order to avoid engine bug.
+  // CALL SCRIPT afxMagicSpellData::onImpact(%spell, %caster, %impactedObj, %impactedPos, %impactedNorm)
+  datablock->onImpact_callback(this, caster, impacted_obj, impact_pos, impact_norm);
+#else
+  char pos_buf[128];
+  dSprintf(pos_buf, sizeof(pos_buf), "%g %g %g", impact_pos.x, impact_pos.y, impact_pos.z);
+  char norm_buf[128];
+  dSprintf(norm_buf, sizeof(norm_buf), "%g %g %g", impact_norm.x, impact_norm.y, impact_norm.z);
+  Con::executef(exeblock, "onImpact", getIdString(),
+     (caster) ? caster->getIdString(): "",
+     (impacted_obj) ? impacted_obj->getIdString(): "",
+     pos_buf, norm_buf);
+#endif
+}
+
+void afxMagicSpell::leave_linger_state_s()
+{
+  if (phrases[LINGER_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+    {
+      //Con::printf("INTERRUPT LINGER (S)");
+      phrases[LINGER_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING LINGER (S)");
+      phrases[LINGER_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxMagicSpell::process_client(F32 dt)
+{
+  spell_elapsed += dt; //SPELL_ELAPSED
+
+  U8 pending_state = spell_state;
+
+  // check for state changes
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = CASTING_STATE;
+    break;
+  case CASTING_STATE:
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_CASTING)
+      pending_state = DELIVERY_STATE;
+    else if (datablock->launch_on_server_signal)
+    {
+      if (marks_mask & MARK_LAUNCH)
+        pending_state = DELIVERY_STATE;
+    }
+    else
+    {
+      if (state_expired())
+        pending_state = DELIVERY_STATE;
+    }
+    break;
+  case DELIVERY_STATE:
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_DELIVERY)
+      pending_state = LINGER_STATE;
+    else if (marks_mask & MARK_IMPACT)
+      pending_state = LINGER_STATE;
+    else
+      state_expired();
+    break;
+  case LINGER_STATE:
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_END_LINGER)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (spell_state != pending_state)
+    change_state_c(pending_state);
+
+  if (spell_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(dt, spell_elapsed);
+
+  if (missile_is_armed)
+  {
+    launch_missile_c();
+    missile_is_armed = false;
+  }
+}
+
+void afxMagicSpell::change_state_c(U8 pending_state)
+{
+  if (spell_state == pending_state)
+    return;
+
+  // LEAVING THIS STATE
+  switch (spell_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    leave_casting_state_c();
+    break;
+  case DELIVERY_STATE:
+    leave_delivery_state_c();
+    break;
+  case LINGER_STATE:
+    leave_linger_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  spell_state = pending_state;
+
+  // ENTERING THIS STATE
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case CASTING_STATE:
+    enter_casting_state_c(spell_elapsed);
+    break;
+  case DELIVERY_STATE:
+    enter_delivery_state_c(spell_elapsed);
+    break;
+  case LINGER_STATE:
+    enter_linger_state_c(spell_elapsed);
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+}
+
+void afxMagicSpell::enter_casting_state_c(F32 starttime)
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(spell_elapsed*1000));
+  //spell_elapsed = 0; //SPELL_ELAPSED
+
+  setup_dynamic_constraints();
+
+  // start casting effects and castbar
+  setup_casting_fx();
+  if (phrases[CASTING_PHRASE])
+    phrases[CASTING_PHRASE]->start(starttime, spell_elapsed); //START
+
+  // initialize missile
+  if (missile_db)
+  {
+    missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking);
+    init_missile_c(missile_db);
+  }
+}
+
+void afxMagicSpell::leave_casting_state_c()
+{
+  if (phrases[CASTING_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_CASTING)
+    {
+      //Con::printf("INTERRUPT CASTING (C)");
+      phrases[CASTING_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING CASTING (C)");
+      phrases[CASTING_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::enter_delivery_state_c(F32 starttime)
+{
+  missile_is_armed = true;
+
+  setup_launch_fx();
+  if (phrases[LAUNCH_PHRASE])
+    phrases[LAUNCH_PHRASE]->start(starttime, spell_elapsed); //START
+
+  setup_delivery_fx();
+  if (phrases[DELIVERY_PHRASE])
+    phrases[DELIVERY_PHRASE]->start(starttime, spell_elapsed); //START
+}
+
+void afxMagicSpell::leave_delivery_state_c()
+{
+  if (missile)
+  {
+    clearNotify(missile);
+    missile->deleteObject();
+    missile = NULL;
+  }
+
+  if (phrases[DELIVERY_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_DELIVERY)
+    {
+      //Con::printf("INTERRUPT DELIVERY (C)");
+      phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING DELIVERY (C)");
+      phrases[DELIVERY_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::enter_linger_state_c(F32 starttime)
+{
+  if (impacted_obj)
+    impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj);
+  else if (impacted_scope_id > 0)
+    impacted_cons_id = constraint_mgr->setReferenceObjectByScopeId(IMPACTED_OBJECT_CONS, impacted_scope_id, impacted_is_shape);
+  else
+    constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm);
+  constraint_mgr->setReferenceObject(MISSILE_CONS, 0);
+
+  setup_impact_fx();
+  if (phrases[IMPACT_PHRASE])
+    phrases[IMPACT_PHRASE]->start(starttime, spell_elapsed); //START
+
+  setup_linger_fx();
+  if (phrases[LINGER_PHRASE])
+  {
+    phrases[LINGER_PHRASE]->start(starttime, spell_elapsed); //START
+  }
+}
+
+void afxMagicSpell::leave_linger_state_c()
+{
+  if (phrases[LINGER_PHRASE])
+  {
+    if (marks_mask & MARK_INTERRUPT_LINGER)
+    {
+      //Con::printf("INTERRUPT LINGER (C)");
+      phrases[LINGER_PHRASE]->interrupt(spell_elapsed);
+    }
+    else
+    {
+      //Con::printf("LEAVING LINGER (C)");
+      phrases[LINGER_PHRASE]->stop(spell_elapsed);
+    }
+  }
+}
+
+void afxMagicSpell::sync_client(U16 marks, U8 state, F32 elapsed, F32 spell_elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%s state=%s elapsed=%g spell_elapsed=%g",
+  //            marks, name_from_state(spell_state), name_from_state(state), elapsed,
+  //            spell_elapsed);
+
+  if (spell_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    spell_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if ((marks & (MARK_INTERRUPTS | MARK_DEACTIVATE | MARK_SHUTDOWN)) ||
+           (((marks & MARK_IMPACT) || (marks & MARK_END_DELIVERY)) && (marks & MARK_END_LINGER)))
+  {
+    spell_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the linger state.
+  else if ((marks & MARK_IMPACT) ||
+           (((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING)) && (marks & MARK_END_DELIVERY)))
+  {
+    spell_state = LINGER_STATE;
+    this->spell_elapsed = spell_elapsed;
+    enter_linger_state_c(spell_elapsed-elapsed);
+  }
+
+  // it looks like we should be in the delivery state.
+  else if ((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING))
+  {
+    spell_state = DELIVERY_STATE;
+    this->spell_elapsed = spell_elapsed;
+    enter_delivery_state_c(spell_elapsed-elapsed);
+  }
+
+  // it looks like we should be in the casting state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    spell_state = CASTING_STATE; //SPELL_STATE
+    this->spell_elapsed = spell_elapsed;
+    enter_casting_state_c(spell_elapsed-elapsed);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxMagicSpell::postSpellEvent(U8 event)
+{
+  setMaskBits(StateEventMask);
+
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case LAUNCH_EVENT:
+    marks_mask |= MARK_LAUNCH;
+    setMaskBits(LaunchEventMask);
+    break;
+  case IMPACT_EVENT:
+    marks_mask |= MARK_IMPACT;
+    setMaskBits(ImpactEventMask);
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_PHASE_EVENT:
+    if (spell_state == CASTING_STATE)
+      marks_mask |= MARK_END_CASTING;
+    else if (spell_state == DELIVERY_STATE)
+      marks_mask |= MARK_END_DELIVERY;
+    else if (spell_state == LINGER_STATE)
+      marks_mask |= MARK_END_LINGER;
+    break;
+  case INTERRUPT_SPELL_EVENT:
+    if (spell_state == CASTING_STATE)
+      marks_mask |= MARK_INTERRUPT_CASTING;
+    else if (spell_state == DELIVERY_STATE)
+      marks_mask |= MARK_INTERRUPT_DELIVERY;
+    else if (spell_state == LINGER_STATE)
+      marks_mask |= MARK_INTERRUPT_LINGER;
+    else if (spell_state == CLEANUP_STATE)
+      marks_mask |= MARK_INTERRUPT_CLEANUP;
+    break;
+  }
+}
+
+void afxMagicSpell::resolveTimeFactors()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    tfactors[i] *= overall_time_factor;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxMagicSpell::finish_startup()
+{
+#if !defined(BROKEN_POINT_IN_WATER)
+  // test if caster is in water
+  if (caster)
+  {
+    Point3F pos = caster->getPosition();
+    if (caster->pointInWater(pos))
+      exec_conds_mask |= CASTER_IN_WATER;
+  }
+#endif
+
+  resolveTimeFactors();
+
+  init_constraints();
+  init_scoping();
+
+  postSpellEvent(afxMagicSpell::ACTIVATE_EVENT);
+}
+
+// static
+afxMagicSpell*
+afxMagicSpell::cast_spell(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra)
+{
+  AssertFatal(datablock != NULL, "Datablock is missing.");
+  AssertFatal(caster != NULL, "Caster is missing.");
+
+  afxMagicSpellData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  if (datablock->isMethod("onPreactivate"))
+  {
+     // CALL SCRIPT afxMagicSpellData::onPreactivate(%params, %caster, %target, %extra)
+     bool result = datablock->onPreactivate_callback(param_holder, caster, target, extra);
+     if (!result)
+     {
+   #if defined(TORQUE_DEBUG)
+       Con::warnf("afxMagicSpell: onPreactivate() returned false, spell aborted.");
+   #endif
+       Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+       return 0;
+     }
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxMagicSpellData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new spell instance
+  afxMagicSpell* spell = new afxMagicSpell(caster, target);
+  spell->setDataBlock(datablock);
+  spell->exeblock = exeblock;
+  spell->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the spell
+  spell->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!spell->registerObject())
+  {
+    Con::errorf("afxMagicSpell: failed to register spell instance.");
+    Sim::postEvent(spell, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+  registerForCleanup(spell);
+
+  spell->activate();
+
+  return spell;
+}
+
+IMPLEMENT_GLOBAL_CALLBACK( DisplayScreenMessage, void, (GameConnection* client, const char* message), (client, message),
+   "Called to display a screen message.\n"
+   "@ingroup AFX\n" );
+
+void afxMagicSpell::displayScreenMessage(ShapeBase* caster, const char* msg)
+{
+  if (!caster)
+    return;
+
+  GameConnection* client = caster->getControllingClient();
+  if (client)
+    DisplayScreenMessage_callback(client, msg);
+}
+
+Point3F afxMagicSpell::getShapeImpactPos(SceneObject* obj)
+{
+  Point3F pos = obj->getRenderPosition();
+  if (obj->getTypeMask() & CorpseObjectType)
+    pos.z += 0.5f;
+  else
+    pos.z += (obj->getObjBox().len_z()/2);
+  return pos;
+}
+
+void afxMagicSpell::restoreObject(SceneObject* obj)
+{
+  if (obj->getScopeId() == caster_scope_id && dynamic_cast<ShapeBase*>(obj) != NULL)
+  {
+    caster_scope_id = 0;
+    caster = (ShapeBase*)obj;
+    caster_field = caster;
+    deleteNotify(caster);
+    processAfter(caster);
+  }
+
+  if (obj->getScopeId() == target_scope_id)
+  {
+    target_scope_id = 0;
+    target = obj;
+    target_field = target;
+    deleteNotify(target);
+  }
+
+  if (obj->getScopeId() == impacted_scope_id)
+  {
+    impacted_scope_id = 0;
+    impacted_obj = obj;
+    deleteNotify(impacted_obj);
+  }
+}
+
+bool afxMagicSpell::activationCallInit(bool postponed)
+{
+  if (postponed && (!started_with_newop || !postpone_activation))
+  {
+    Con::errorf("afxMagicSpell::activate() -- activate() is only required when creating a spell with the \"new\" operator "
+                "and the postponeActivation field is set to \"true\".");
+    return false;
+  }
+
+  if (!caster_field)
+  {
+    Con::errorf("afxMagicSpell::activate() -- no spellcaster specified.");
+    return false;
+  }
+
+  caster = dynamic_cast<ShapeBase*>(caster_field);
+  if (!caster)
+  {
+    Con::errorf("afxMagicSpell::activate() -- spellcaster is not a ShapeBase derived object.");
+    return false;
+  }
+
+  if (target_field)
+  {
+    target = dynamic_cast<SceneObject*>(target_field);
+    if (!target)
+      Con::warnf("afxMagicSpell::activate() -- target is not a SceneObject derived object.");
+  }
+
+  return true;
+}
+
+void afxMagicSpell::activate()
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned spell that need
+  // to happen prior to object registration.
+  Sim::postEvent(this, new SpellFinishStartupEvent, Sim::getCurrentTime());
+
+  caster_field = caster;
+  target_field = target;
+
+  // CALL SCRIPT afxMagicSpellData::onActivate(%spell, %caster, %target)
+  datablock->onActivate_callback(this, caster, target);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console methods/functions
+
+DefineEngineMethod(afxMagicSpell, getCaster, S32, (),,
+                   "Returns ID of the spell's caster object.\n\n"
+                   "@ingroup AFX")
+{
+  ShapeBase* caster = object->getCaster();
+  return (caster) ? caster->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getTarget, S32, (),,
+                   "Returns ID of the spell's target object.\n\n"
+                   "@ingroup AFX")
+{
+  SceneObject* target = object->getTarget();
+  return (target) ? target->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getMissile, S32, (),,
+                   "Returns ID of the spell's magic-missile object.\n\n"
+                   "@ingroup AFX")
+{
+  afxMagicMissile* missile = object->getMissile();
+  return (missile) ? missile->getId() : -1;
+}
+
+DefineEngineMethod(afxMagicSpell, getImpactedObject, S32, (),,
+                   "Returns ID of impacted-object for the spell.\n\n"
+                   "@ingroup AFX")
+{
+  SceneObject* imp_obj = object->getImpactedObject();
+  return (imp_obj) ? imp_obj->getId() : -1;
+}
+
+ConsoleMethod(afxMagicSpell, setTimeFactor, void, 3, 4, "(F32 factor) or (string phase, F32 factor)"
+              "Sets the time-factor for the spell, either overall or for a specific phrase.\n\n"
+              "@ingroup AFX")
+{
+  if (argc == 3)
+    object->setTimeFactor(dAtof(argv[2]));
+  else
+  {
+    if (dStricmp(argv[2], "overall") == 0)
+      object->setTimeFactor(dAtof(argv[3]));
+    else if (dStricmp(argv[2], "casting") == 0)
+      object->setTimeFactor(afxMagicSpell::CASTING_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "launch") == 0)
+      object->setTimeFactor(afxMagicSpell::LAUNCH_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "delivery") == 0)
+      object->setTimeFactor(afxMagicSpell::DELIVERY_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "impact") == 0)
+      object->setTimeFactor(afxMagicSpell::IMPACT_PHRASE, dAtof(argv[3]));
+    else if (dStricmp(argv[2], "linger") == 0)
+      object->setTimeFactor(afxMagicSpell::LINGER_PHRASE, dAtof(argv[3]));
+    else
+      Con::errorf("afxMagicSpell::setTimeFactor() -- unknown spell phrase [%s].", argv[2].getStringValue());
+  }
+}
+
+DefineEngineMethod(afxMagicSpell, interruptStage, void, (),,
+                   "Interrupts the current stage of a magic spell causing it to move onto the next one.\n\n"
+                   "@ingroup AFX")
+{
+  object->postSpellEvent(afxMagicSpell::INTERRUPT_PHASE_EVENT);
+}
+
+DefineEngineMethod(afxMagicSpell, interrupt, void, (),,
+                   "Interrupts and deletes a running magic spell.\n\n"
+                   "@ingroup AFX")
+{
+  object->postSpellEvent(afxMagicSpell::INTERRUPT_SPELL_EVENT);
+}
+
+DefineEngineMethod(afxMagicSpell, activate, void, (),,
+                   "Activates a magic spell that was started with postponeActivation=true.\n\n"
+                   "@ingroup AFX")
+{
+  if (object->activationCallInit(true))
+    object->activate();
+}
+
+DefineEngineFunction(castSpell, S32,	(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra),
+										(nullAsType<afxMagicSpellData*>(), nullAsType<ShapeBase*>(), nullAsType<SceneObject*>(), nullAsType<SimObject*>()),		
+                     "Instantiates the magic spell defined by datablock and cast by caster.\n\n"
+                     "@ingroup AFX")
+{
+  if (!datablock)
+  {
+    Con::errorf("castSpell() -- missing valid spell datablock.");
+    return 0;
+  }
+
+  if (!caster)
+  {
+    Con::errorf("castSpell() -- missing valid spellcaster.");
+    return 0;
+  }
+
+  // target is optional (depends on spell)
+
+  // note -- we must examine all arguments prior to calling cast_spell because
+  // it calls Con::executef() which will overwrite the argument array.
+  afxMagicSpell* spell = afxMagicSpell::cast_spell(datablock, caster, target, extra);
+
+  return (spell) ? spell->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 390 - 0
Engine/source/afx/afxMagicSpell.h

@@ -0,0 +1,390 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MAGIC_SPELL_H_
+#define _AFX_MAGIC_SPELL_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "core/util/tVector.h"
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectDefs.h"
+#include "afxEffectWrapper.h"
+#include "afxMagicMissile.h"
+
+class afxChoreographerData;
+class afxMagicMissileData;
+class afxEffectWrapperData;
+class SceneObject;
+class afxMagicSpell;
+
+class afxMagicSpellDefs
+{
+public:
+  enum
+  {
+    CASTING_PHRASE,
+    LAUNCH_PHRASE,
+    DELIVERY_PHRASE,
+    IMPACT_PHRASE,
+    LINGER_PHRASE,
+    NUM_PHRASES
+  };
+};
+
+class afxMagicSpellData : public afxChoreographerData, public afxMagicSpellDefs
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool                  do_id_convert;
+
+public:
+  F32                   casting_dur;
+  F32                   delivery_dur;
+  F32                   linger_dur;
+  //
+  S32                   n_casting_loops;
+  S32                   n_delivery_loops;
+  S32                   n_linger_loops;
+  //
+  F32                   extra_casting_time;
+  F32                   extra_delivery_time;
+  F32                   extra_linger_time;
+  //
+  bool                  do_move_interrupts;
+  F32                   move_interrupt_speed;
+  //
+  afxMagicMissileData*  missile_db;
+  bool                  launch_on_server_signal;
+  U32                   primary_target_types;
+  //
+  afxEffectWrapperData* dummy_fx_entry;
+
+                        // various effects lists
+  afxEffectList         casting_fx_list;
+  afxEffectList         launch_fx_list;
+  afxEffectList         delivery_fx_list;
+  afxEffectList         impact_fx_list;
+  afxEffectList         linger_fx_list;
+
+  void                  pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void                  unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/                 afxMagicSpellData();
+  /*C*/                 afxMagicSpellData(const afxMagicSpellData&, bool = false);
+
+  virtual void          reloadReset();
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+  virtual bool          writeField(StringTableEntry fieldname, const char* value);
+
+  bool                  preload(bool server, String &errorStr);
+
+  void                  gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxMagicSpellData);
+  DECLARE_CATEGORY("AFX");
+
+  /// @name Callbacks
+  /// @{
+  DECLARE_CALLBACK( void, onDamage, (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse) );
+  DECLARE_CALLBACK( void, onDeactivate, (afxMagicSpell* spell) );
+  DECLARE_CALLBACK( void, onInterrupt, (afxMagicSpell* spell, ShapeBase* caster) );
+  DECLARE_CALLBACK( void, onLaunch, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile) );
+  DECLARE_CALLBACK( void, onImpact, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal) );
+  DECLARE_CALLBACK( bool, onPreactivate, (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra) );
+  DECLARE_CALLBACK( void, onActivate, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target) );
+  /// @}
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMagicSpell
+
+class ShapeBase;
+class GameConnection;
+class afxEffectVector;
+class afxConstraint;
+class afxConstraintMgr;
+class afxMagicMissile;
+class afxChoreographer;
+class afxPhrase;
+
+class afxMagicSpell : public afxChoreographer, public afxMagicSpellDefs
+{
+  typedef afxChoreographer Parent;
+  friend class afxMagicMissile;
+
+  enum MaskBits 
+  {
+    MagicMissileMask      = Parent::NextFreeMask << 0,
+    StateEventMask        = Parent::NextFreeMask << 1,
+    LaunchEventMask       = Parent::NextFreeMask << 2,
+    ImpactEventMask       = Parent::NextFreeMask << 3,
+    SyncEventMask         = Parent::NextFreeMask << 4,
+    RemapConstraintMask   = Parent::NextFreeMask << 5, // CONSTRAINT REMAPPING
+    NextFreeMask          = Parent::NextFreeMask << 6
+  };
+
+public:
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    LAUNCH_EVENT,
+    IMPACT_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_PHASE_EVENT,
+    INTERRUPT_SPELL_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    CASTING_STATE,
+    DELIVERY_STATE,
+    LINGER_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE           = BIT(0),
+    MARK_LAUNCH             = BIT(1),
+    MARK_IMPACT             = BIT(2),
+    MARK_SHUTDOWN           = BIT(3),
+    MARK_DEACTIVATE         = BIT(4),
+    MARK_END_CASTING        = BIT(5),
+    MARK_END_DELIVERY       = BIT(6),
+    MARK_END_LINGER         = BIT(7),
+    MARK_INTERRUPT_CASTING  = BIT(8),
+    MARK_INTERRUPT_DELIVERY = BIT(9),
+    MARK_INTERRUPT_LINGER   = BIT(10),
+    MARK_INTERRUPT_CLEANUP  = BIT(11),
+    //
+    MARK_ENDINGS = MARK_END_CASTING | MARK_END_DELIVERY | MARK_END_LINGER,
+    MARK_INTERRUPTS = MARK_INTERRUPT_CASTING | MARK_INTERRUPT_DELIVERY | MARK_INTERRUPT_LINGER | MARK_INTERRUPT_CLEANUP
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CASTER_CONS;
+  static StringTableEntry  TARGET_CONS;
+  static StringTableEntry  MISSILE_CONS;
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+  static StringTableEntry  IMPACT_POINT_CONS;
+  static StringTableEntry  IMPACTED_OBJECT_CONS;
+
+private:
+  afxMagicSpellData*   datablock;
+  SimObject*           exeblock;
+  afxMagicMissileData* missile_db;
+
+  ShapeBase*    caster;
+  SceneObject*  target;
+  SimObject*    caster_field;
+  SimObject*    target_field;
+
+  U16           caster_scope_id;
+  U16           target_scope_id;
+  bool          target_is_shape;
+
+  bool          constraints_initialized;
+  bool          scoping_initialized;
+
+  U8            spell_state;
+  F32           spell_elapsed;
+
+  afxConstraintID listener_cons_id;
+  afxConstraintID caster_cons_id;
+  afxConstraintID target_cons_id;
+  afxConstraintID impacted_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+
+  afxPhrase*    phrases[NUM_PHRASES];
+  F32           tfactors[NUM_PHRASES];
+
+  bool          notify_castbar;
+  F32           overall_time_factor;
+
+  U16           marks_mask;
+
+private:
+  void          init();
+  bool          state_expired();
+  F32           state_elapsed();
+  void          init_constraints();
+  void          init_scoping();
+  void          setup_casting_fx();
+  void          setup_launch_fx();
+  void          setup_delivery_fx();
+  void          setup_impact_fx();
+  void          setup_linger_fx();
+  bool          cleanup_over();
+  bool          is_caster_moving();
+  bool          is_caster_client(ShapeBase* caster, GameConnection* conn);
+  bool          is_impact_in_water(SceneObject* obj, const Point3F& p);
+
+protected:
+  virtual bool  remap_builtin_constraint(SceneObject*, const char* cons_name); // CONSTRAINT REMAPPING
+  virtual void  pack_constraint_info(NetConnection* conn, BitStream* stream);
+  virtual void  unpack_constraint_info(NetConnection* conn, BitStream* stream);
+
+private:
+  afxMagicMissile*  missile;
+  bool              missile_is_armed;
+  SceneObject*      impacted_obj;
+  Point3F           impact_pos;
+  Point3F           impact_norm;
+  U16               impacted_scope_id;
+  bool              impacted_is_shape;
+
+  void          init_missile_s(afxMagicMissileData* mm);
+  void          launch_missile_s();
+
+  void          init_missile_c(afxMagicMissileData* mm);
+  void          launch_missile_c();
+
+public:
+  virtual void  impactNotify(const Point3F& p, const Point3F& n, SceneObject*);
+  virtual void  executeScriptEvent(const char* method, afxConstraint*, 
+                                   const MatrixF& pos, const char* data);
+  virtual void  inflictDamage(const char * label, const char* flavor, SimObjectId target,
+                              F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp);
+
+public:
+  /*C*/         afxMagicSpell();
+  /*C*/         afxMagicSpell(ShapeBase* caster, SceneObject* target);
+  /*D*/         ~afxMagicSpell();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual void  onRemove();
+  virtual void  onDeleteNotify(SimObject*);
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxMagicSpell);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_casting_state_s();
+  void          leave_casting_state_s();
+  void          enter_delivery_state_s();
+  void          leave_delivery_state_s();
+  void          enter_linger_state_s();
+  void          leave_linger_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_casting_state_c(F32 starttime);
+  void          leave_casting_state_c();
+  void          enter_delivery_state_c(F32 starttime);
+  void          leave_delivery_state_c();
+  void          enter_linger_state_c(F32 starttime);
+  void          leave_linger_state_c();
+  //
+  void          sync_client(U16 marks, U8 state, F32 state_elapsed, F32 spell_elapsed);
+
+public:
+  void          postSpellEvent(U8 event);
+  void          resolveTimeFactors();
+
+  void          setTimeFactor(F32 f) { overall_time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return overall_time_factor; }
+  void          setTimeFactor(U8 phase, F32 f) { tfactors[phase] = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor(U8 phase) { return tfactors[phase]; }
+
+  ShapeBase*        getCaster() const { return caster; }
+  SceneObject*      getTarget() const { return target; }
+  afxMagicMissile*  getMissile() const { return missile; }
+  SceneObject*      getImpactedObject() const { return impacted_obj; }
+
+  virtual void      restoreObject(SceneObject*);
+
+  bool              activationCallInit(bool postponed=false);
+  void              activate();
+
+public:
+  static afxMagicSpell* cast_spell(afxMagicSpellData*, ShapeBase* caster, SceneObject* target, SimObject* extra);
+  
+  static void     displayScreenMessage(ShapeBase* caster, const char* msg);
+  static Point3F  getShapeImpactPos(SceneObject*);
+};
+
+inline bool afxMagicSpell::is_caster_moving()
+{
+  return (caster) ? (caster->getVelocity().len() > datablock->move_interrupt_speed) : false;
+}
+
+inline bool afxMagicSpell::is_caster_client(ShapeBase* caster, GameConnection* conn)
+{
+  return (caster) ? (caster->getControllingClient() == conn) : false;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MAGIC_SPELL_H_

+ 196 - 0
Engine/source/afx/afxPhrase.cpp

@@ -0,0 +1,196 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/afxEffectVector.h"
+#include "afx/afxPhrase.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhrase
+
+void
+afxPhrase::init_fx(S32 group_index)
+{
+  fx->ev_init(init_chor, *init_fx_list, on_server, will_stop, init_time_factor, init_dur, group_index); 
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+afxPhrase::afxPhrase(bool on_server, bool will_stop)
+{
+  this->on_server = on_server;
+  this->will_stop = will_stop;
+
+  init_fx_list = NULL;
+  init_dur = 0.0f;
+  init_chor = NULL;
+  init_time_factor = 1.0f;
+
+  fx = new afxEffectVector;
+  fx2 = NULL;
+  starttime = 0;
+  dur = 0;
+
+  n_loops = 1;
+  loop_cnt = 1;
+
+  extra_time = 0.0f;
+  extra_stoptime = 0.0f;
+}
+
+afxPhrase::~afxPhrase()
+{
+  delete fx;
+  delete fx2;
+};
+
+void 
+afxPhrase::init(afxEffectList& fx_list, F32 dur, afxChoreographer* chor, F32 time_factor, 
+                S32 n_loops, S32 group_index, F32 extra_time)
+{
+  init_fx_list = &fx_list;
+  init_dur = dur;
+  init_chor = chor;
+  init_time_factor = time_factor;
+
+  this->n_loops = n_loops;
+  this->extra_time = extra_time;
+  this->dur = (init_dur < 0) ? init_dur : init_dur*init_time_factor;
+
+  init_fx(group_index);
+}
+
+void
+afxPhrase::start(F32 startstamp, F32 timestamp)
+{
+  starttime = startstamp;
+
+  F32 loopstart = timestamp - startstamp;
+
+  if (dur > 0 && loopstart > dur)
+  {
+    loop_cnt += (S32) (loopstart/dur);
+    loopstart = mFmod(loopstart, dur);
+  }
+
+  if (!fx->empty())
+    fx->start(loopstart);
+}
+
+void
+afxPhrase::update(F32 dt, F32 timestamp)
+{
+  if (fx->isActive())
+    fx->update(dt);
+
+  if (fx2 && fx2->isActive())
+    fx2->update(dt);
+
+  if (extra_stoptime > 0 && timestamp > extra_stoptime)
+  {
+    stop(timestamp);
+  }
+}
+
+void
+afxPhrase::stop(F32 timestamp)
+{
+  if (extra_time > 0 && !(extra_stoptime > 0))
+  {
+    extra_stoptime = timestamp + extra_time;
+    return;
+  }
+
+  if (fx->isActive())
+    fx->stop();
+
+  if (fx2 && fx2->isActive())
+    fx2->stop();
+}
+
+bool
+afxPhrase::expired(F32 timestamp)
+{
+  if (dur < 0)
+    return false;
+
+  return ((timestamp - starttime) > loop_cnt*dur);
+}
+
+F32
+afxPhrase::elapsed(F32 timestamp)
+{
+  return (timestamp - starttime);
+}
+
+bool
+afxPhrase::recycle(F32 timestamp)
+{
+  if (n_loops < 0 || loop_cnt < n_loops)
+  {
+    if (fx2)
+      delete fx2;
+
+    fx2 = fx;
+
+    fx = new afxEffectVector;
+    init_fx();
+
+    if (fx2 && !fx2->empty())
+      fx2->stop();
+
+    if (!fx->empty())
+      fx->start(0.0F);
+
+    loop_cnt++;
+    return true;
+  }
+
+  return false;
+}
+
+void
+afxPhrase::interrupt(F32 timestamp)
+{
+  if (fx->isActive())
+    fx->interrupt();
+
+  if (fx2 && fx2->isActive())
+    fx2->interrupt();
+}
+
+F32 afxPhrase::calcDoneTime()
+{
+  return starttime + fx->getTotalDur();
+}
+
+F32 afxPhrase::calcAfterLife()
+{
+  return fx->getAfterLife();
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 87 - 0
Engine/source/afx/afxPhrase.h

@@ -0,0 +1,87 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PHRASE_H_
+#define _AFX_PHRASE_H_
+
+#include "afxEffectVector.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhrase
+
+class afxChoreographer;
+class afxConstraintMgr;
+class afxEffectVector;
+
+class afxPhrase
+{
+protected:
+  afxEffectList*    init_fx_list;
+  F32               init_dur;
+  afxChoreographer* init_chor;
+  F32               init_time_factor;
+  F32               extra_time;
+
+  afxEffectVector*  fx;
+  afxEffectVector*  fx2;
+
+  bool              on_server;
+  bool              will_stop;
+
+  F32               starttime;
+  F32               dur;
+  S32               n_loops;
+  S32               loop_cnt;
+  F32               extra_stoptime;
+
+  void              init_fx(S32 group_index=0);
+
+public:
+  /*C*/             afxPhrase(bool on_server, bool will_stop);
+  virtual           ~afxPhrase();
+
+  virtual void      init(afxEffectList&, F32 dur, afxChoreographer*, F32 time_factor, 
+                         S32 n_loops, S32 group_index=0, F32 extra_time=0.0f);
+
+  virtual void      start(F32 startstamp, F32 timestamp);
+  virtual void      update(F32 dt, F32 timestamp);
+  virtual void      stop(F32 timestamp);
+  virtual void      interrupt(F32 timestamp);
+  virtual bool      expired(F32 timestamp);
+  virtual bool      recycle(F32 timestamp);
+  virtual F32       elapsed(F32 timestamp);
+
+  bool              isEmpty() { return fx->empty(); }
+  bool              isInfinite() { return (init_dur < 0); }
+  F32               calcDoneTime();
+  F32               calcAfterLife();
+  bool              willStop() { return will_stop; }
+  bool              onServer() { return on_server; }
+  S32               count() { return fx->count(); }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PHRASE_H_

+ 176 - 0
Engine/source/afx/afxRenderHighlightMgr.cpp

@@ -0,0 +1,176 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// The afxRenderHighlightMgr class is adapted from the resource,
+// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss.
+// http://www.garagegames.com/community/resources/view/17821
+// Supporting code mods in other areas of the engine are marked as
+// "(selection-highlight)".
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "platform/platform.h"
+#include "afxRenderHighlightMgr.h"
+
+#include "scene/sceneManager.h"
+#include "scene/sceneRenderState.h"
+#include "materials/sceneData.h"
+#include "materials/matInstance.h"
+//#include "materials/materialFeatureTypes.h"
+#include "materials/processedMaterial.h"
+#include "postFx/postEffect.h"
+#include "gfx/gfxTransformSaver.h"
+#include "gfx/gfxDebugEvent.h"
+#include "math/util/matrixSet.h"
+
+IMPLEMENT_CONOBJECT( afxRenderHighlightMgr );
+
+afxRenderHighlightMgr::afxRenderHighlightMgr()
+   : RenderTexTargetBinManager(  RenderPassManager::RIT_Mesh, 
+                                 1.0f, 
+                                 1.0f,
+                                 GFXFormatR8G8B8A8,
+                                 Point2I( 512, 512 ) )
+{
+   mNamedTarget.registerWithName( "highlight" );
+   mTargetSizeType = WindowSize;
+}
+
+afxRenderHighlightMgr::~afxRenderHighlightMgr()
+{
+}
+
+PostEffect* afxRenderHighlightMgr::getSelectionEffect()
+{
+   if ( !mSelectionEffect )
+      mSelectionEffect = dynamic_cast<PostEffect*>( Sim::findObject( "afxHighlightPostFX" ) );
+   
+   return mSelectionEffect;
+}
+
+bool afxRenderHighlightMgr::isSelectionEnabled()
+{
+   return getSelectionEffect() && getSelectionEffect()->isEnabled();
+}
+
+void afxRenderHighlightMgr::addElement( RenderInst *inst )
+{
+   // Skip out if we don't have the selection post 
+   // effect enabled at this time.
+   if ( !isSelectionEnabled() )  
+      return;
+
+   // Skip it if we don't have a selection material.
+   BaseMatInstance *matInst = getMaterial( inst );
+   if ( !matInst || !matInst->needsSelectionHighlighting() )   
+      return;
+
+   internalAddElement(inst);
+}
+
+void afxRenderHighlightMgr::render( SceneRenderState *state )  
+{
+   PROFILE_SCOPE( RenderSelectionMgr_Render );
+   
+   if ( !isSelectionEnabled() )
+      return;
+
+   const U32 binSize = mElementList.size();
+
+   // If this is a non-diffuse pass or we have no objects to
+   // render then tell the effect to skip rendering.
+   if ( !state->isDiffusePass() || binSize == 0 )
+   {
+      getSelectionEffect()->setSkip( true );
+      return;
+   }
+
+   GFXDEBUGEVENT_SCOPE( RenderSelectionMgr_Render, ColorI::GREEN );
+
+   GFXTransformSaver saver;
+
+   // Tell the superclass we're about to render, preserve contents
+   const bool isRenderingToTarget = _onPreRender( state, true );
+
+   // Clear all the buffers to black.
+   //GFX->clear( GFXClearTarget, ColorI::BLACK, 1.0f, 0);
+   GFX->clear( GFXClearTarget, ColorI::ZERO, 1.0f, 0);
+
+   // Restore transforms
+   MatrixSet &matrixSet = getRenderPass()->getMatrixSet();
+   matrixSet.restoreSceneViewProjection();
+
+   // init loop data
+   SceneData sgData;
+   sgData.init( state, SceneData::HighlightBin );
+
+   for( U32 j=0; j<binSize; )
+   {
+      MeshRenderInst *ri = static_cast<MeshRenderInst*>(mElementList[j].inst);
+
+      setupSGData( ri, sgData );
+
+      BaseMatInstance *mat = ri->matInst;
+
+      U32 matListEnd = j;
+
+      while( mat && mat->setupPass( state, sgData ) )
+      {
+         U32 a;
+         for( a=j; a<binSize; a++ )
+         {
+            MeshRenderInst *passRI = static_cast<MeshRenderInst*>(mElementList[a].inst);
+
+            if ( newPassNeeded( ri, passRI ) )
+               break;
+
+            matrixSet.setWorld(*passRI->objectToWorld);
+            matrixSet.setView(*passRI->worldToCamera);
+            matrixSet.setProjection(*passRI->projection);
+            mat->setTransforms(matrixSet, state);
+
+            mat->setSceneInfo(state, sgData);
+            mat->setBuffers(passRI->vertBuff, passRI->primBuff);
+
+            if ( passRI->prim )
+               GFX->drawPrimitive( *passRI->prim );
+            else
+               GFX->drawPrimitive( passRI->primBuffIndex );
+         }
+         matListEnd = a;
+         setupSGData( ri, sgData );
+      }
+
+      // force increment if none happened, otherwise go to end of batch
+      j = ( j == matListEnd ) ? j+1 : matListEnd;
+   }
+
+   // Finish up.
+   if ( isRenderingToTarget )
+      _onPostRender();
+
+   // Make sure the effect is gonna render.
+   getSelectionEffect()->setSkip( false );
+}

+ 76 - 0
Engine/source/afx/afxRenderHighlightMgr.h

@@ -0,0 +1,76 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// The afxRenderHighlightMgr class is adapted from the resource,
+// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss.
+// http://www.garagegames.com/community/resources/view/17821
+// Supporting code mods in other areas of the engine are marked as
+// "(selection-highlight)".
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _afxRENDERHIGHLIGHTMGR_H_
+#define _afxRENDERHIGHLIGHTMGR_H_
+
+#ifndef _TEXTARGETBIN_MGR_H_
+#include "renderInstance/renderTexTargetBinManager.h"
+#endif
+
+
+class PostEffect;
+
+
+///
+class afxRenderHighlightMgr : public RenderTexTargetBinManager
+{  
+   typedef RenderTexTargetBinManager Parent;
+
+public:
+
+   afxRenderHighlightMgr();
+   virtual ~afxRenderHighlightMgr();
+
+   /// Returns the selection post effect.
+   PostEffect* getSelectionEffect();
+
+   /// Returns true if the highlight post effect is
+   /// enabled and the selection buffer should be updated.
+   bool isSelectionEnabled();
+
+   // RenderBinManager
+   virtual void addElement( RenderInst *inst );
+   virtual void render( SceneRenderState *state );
+
+   // ConsoleObject
+   DECLARE_CONOBJECT( afxRenderHighlightMgr );
+
+protected:
+
+   SimObjectPtr<PostEffect> mSelectionEffect;  
+
+};
+
+
+#endif // _afxRENDERHIGHLIGHTMGR_H_

+ 469 - 0
Engine/source/afx/afxResidueMgr.cpp

@@ -0,0 +1,469 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "ts/tsShapeInstance.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/ce/afxModel.h"
+#include "afx/afxResidueMgr.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+int QSORT_CALLBACK afxResidueMgr::ResidueList::compare_residue(const void* p1, const void* p2)
+{
+  const afxResidueMgr::Residue** pd1 = (const afxResidueMgr::Residue**)p1;
+  const afxResidueMgr::Residue** pd2 = (const afxResidueMgr::Residue**)p2;
+  
+  return int(((char*)(*pd1)->data.simobject) - ((char*)(*pd2)->data.simobject));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+inline void afxResidueMgr::ResidueList::swap_array_ptrs()
+{
+  Vector<Residue*>* tmp = m_array;
+  m_array = m_scratch_array;
+  m_scratch_array = tmp;
+}
+
+void afxResidueMgr::ResidueList::free_residue(Residue* residue)
+{
+  if (the_mgr == NULL)
+    return;
+
+  if (the_mgr->requires_delete_tracking(residue))
+    the_mgr->disable_delete_tracking(residue);
+  the_mgr->free_residue(residue);
+}
+
+afxResidueMgr::ResidueList::ResidueList()
+{
+  VECTOR_SET_ASSOCIATION(m_array_a);
+  VECTOR_SET_ASSOCIATION(m_array_b);
+
+  m_array = &m_array_a;
+  m_scratch_array = &m_array_b ;
+
+  m_dirty = false;
+  m_pending = -1;
+}
+
+afxResidueMgr::ResidueList::~ResidueList()
+{
+  clear();
+}
+
+void afxResidueMgr::ResidueList::clear()
+{
+  if (the_mgr)
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      the_mgr->free_residue(r);
+    }
+  }
+
+  m_array_a.clear();
+  m_array_b.clear();
+}
+
+void afxResidueMgr::ResidueList::sort()
+{
+  dQsort(m_array->address(), m_array->size(), sizeof(Residue*), compare_residue);
+  m_dirty = false;
+}
+
+void afxResidueMgr::ResidueList::fadeAndCull(U32 now)
+{
+  for (S32 i = 0; i < m_array->size(); i++)
+  {
+    Residue* r = (*m_array)[i];
+
+    // done
+    if (now >= r->stop_time)
+    {
+      free_residue(r);
+    }
+    // fading
+    else if (now >= r->fade_time)
+    {
+      r->fade = 1.0f - ((F32)(now - r->fade_time))/((F32)(r->stop_time - r->fade_time));
+      m_scratch_array->push_back(r);
+    }
+    // opaque
+    else
+    {
+      r->fade = 1.0f;
+      m_scratch_array->push_back(r);
+    }
+  }
+
+  m_array->clear();
+  swap_array_ptrs();
+}
+
+// removes all residue with datablock matching obj
+void afxResidueMgr::ResidueList::stripMatchingObjects(SimObject* db, bool del_notify)
+{
+  if (del_notify)
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      if (db == r->data.simobject && the_mgr != NULL)
+        the_mgr->free_residue(r);
+      else
+        m_scratch_array->push_back(r);
+    }
+  }
+  else
+  {
+    for (S32 i = 0; i < m_array->size(); i++)
+    {
+      Residue* r = (*m_array)[i];
+      if (db == r->data.simobject)
+        free_residue(r);
+      else
+        m_scratch_array->push_back(r);
+    }
+  }
+
+  m_array->clear();
+  swap_array_ptrs();
+}
+
+void afxResidueMgr::ResidueList::add(Residue* residue)
+{
+  m_array->push_back(residue);
+  m_dirty = true;
+}
+
+void afxResidueMgr::manage_residue(const Residue* r)
+{
+  if (r == NULL || r->fade < 0.01f)
+    return;
+
+  if (r->type == ZODIAC)
+  {
+    LinearColorF zode_color = ColorI(r->params.zodiac.r, r->params.zodiac.g, r->params.zodiac.b, r->params.zodiac.a);
+
+    afxZodiacData* zd = (afxZodiacData*) r->data.zodiac;
+    if (zd->blend_flags == afxZodiacDefs::BLEND_SUBTRACTIVE)
+       zode_color *= r->fade;
+    else
+       zode_color.alpha *= r->fade;
+
+    Point3F zode_pos(r->params.zodiac.pos_x, r->params.zodiac.pos_y, r->params.zodiac.pos_z);
+    Point2F zode_vrange(r->params.zodiac.vrange_dn, r->params.zodiac.vrange_dn);
+    if (r->params.zodiac.on_terrain)
+    {
+      afxZodiacMgr::addTerrainZodiac(zode_pos, r->params.zodiac.rad, zode_color, r->params.zodiac.ang, zd);
+    }
+    else
+    {
+      afxZodiacMgr::addInteriorZodiac(zode_pos, r->params.zodiac.rad, zode_vrange, zode_color, r->params.zodiac.ang, zd);
+    }
+  }
+  else if (r->type == MODEL)
+  {
+    r->data.model->setFadeAmount(r->fade);
+  }
+}
+
+void afxResidueMgr::ResidueList::manage()
+{
+  if (the_mgr == NULL)
+    return;
+
+  S32 n_residue = m_array->size(); 
+
+  for (S32 x = 0; x < n_residue; x++)
+    the_mgr->manage_residue((*m_array)[x]);
+}
+
+U32 afxResidueMgr::ResidueList::findPendingBestBump(U32 look_max)
+{
+  U32 soonest = 1000*60*60*24;
+  m_pending = -1;
+
+  U32 n = m_array->size();
+  for (U32 i = 0; i < n && i < look_max; i++)
+  {
+    Residue* r = (*m_array)[i];
+    if (r->stop_time < soonest)
+    {
+      soonest = r->stop_time;
+      m_pending = i;
+    }
+  }
+
+  return soonest;
+}
+
+void afxResidueMgr::ResidueList::bumpPending()
+{
+  if (m_pending >= 0 && m_pending < m_array->size())
+  {
+    Residue* r = (*m_array)[m_pending];
+    m_array->erase(m_pending);
+    free_residue(r);
+  }
+
+  m_pending = -1;
+}
+
+bool afxResidueMgr::requires_delete_tracking(Residue* r)
+{
+  return (r->type == MODEL);
+}
+
+void afxResidueMgr::enable_delete_tracking(Residue* r)
+{
+  deleteNotify(r->data.simobject);
+}
+
+void afxResidueMgr::disable_delete_tracking(Residue* r)
+{
+  clearNotify(r->data.simobject);
+  r->data.simobject->deleteObject();
+  r->data.simobject = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxResidueMgr*  afxResidueMgr::the_mgr = NULL;
+U32             afxResidueMgr::m_max_residue_objs = 256;
+bool            afxResidueMgr::enabled = true;
+
+IMPLEMENT_CONOBJECT(afxResidueMgr);
+
+ConsoleDocClass( afxResidueMgr,
+   "@brief A class that manages certain AFX effects that can persist for long durations.\n\n"
+
+   "A class that manages certain AFX effects that can persist much longer than the duration of choreographers.\n"
+
+   "@ingroup AFX\n"
+);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// free-list management
+
+afxResidueMgr::Residue* afxResidueMgr::alloc_free_pool_block()
+{
+  // allocate new block for the free-list
+  m_free_pool_blocks.push_back(new Residue[FREE_POOL_BLOCK_SIZE]);
+  
+  // link them onto the free-list
+  Residue* new_block = m_free_pool_blocks.last();
+  for (U32 i = 0; i < FREE_POOL_BLOCK_SIZE - 1; i++)
+    new_block[i].next = &new_block[i + 1];
+  
+  // tail of free-list points to NULL
+  new_block[FREE_POOL_BLOCK_SIZE - 1].next = NULL;
+
+  return new_block;
+}
+
+afxResidueMgr::Residue* afxResidueMgr::alloc_residue()
+{
+  // need new free-list-block if m_next_free is null
+  if (!m_next_free)
+    m_next_free = alloc_free_pool_block();
+  
+  // pop new residue from head of free-list
+  Residue* residue = m_next_free;
+  m_next_free = residue->next;
+  residue->next = NULL;
+
+  return residue;
+}
+
+void afxResidueMgr::free_residue(Residue* residue)
+{
+  if (residue && residue->type == ZODIAC)
+  {
+    if (residue->data.zodiac && residue->data.zodiac->isTempClone())
+    {
+      delete residue->data.zodiac;
+      residue->data.zodiac = 0;
+    }
+  }
+
+  // push residue onto head of free-list
+  residue->next = m_next_free;
+  m_next_free = residue;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxResidueMgr::deleteResidueObject(SimObject* obj, bool del_notify)
+{
+  m_managed.stripMatchingObjects(obj, del_notify);
+}
+
+void afxResidueMgr::bump_residue()
+{
+  if (m_managed.findPendingBestBump())
+    m_managed.bumpPending();
+}
+
+void afxResidueMgr::add_residue(Residue* residue)
+{
+  AssertFatal(residue != NULL, "residue pointer is NULL.");
+
+  if (m_managed.size() >= m_max_residue_objs)
+    bump_residue();
+
+  m_managed.add(residue);
+  manage_residue(residue);
+
+  if (requires_delete_tracking(residue))
+    enable_delete_tracking(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+afxResidueMgr::afxResidueMgr()
+{ 
+  mObjBox.minExtents.set(-1e7, -1e7, -1e7);
+  mObjBox.maxExtents.set( 1e7,  1e7,  1e7);
+  mWorldBox.minExtents.set(-1e7, -1e7, -1e7);
+  mWorldBox.maxExtents.set( 1e7,  1e7,  1e7);
+  
+  m_next_free = NULL;
+
+  VECTOR_SET_ASSOCIATION(m_free_pool_blocks);
+}
+
+afxResidueMgr::~afxResidueMgr()
+{
+  cleanup();
+}
+
+void afxResidueMgr::cleanup()
+{
+  m_managed.clear();
+
+  m_next_free = NULL;
+
+  for (S32 i = 0; i < m_free_pool_blocks.size(); i++)
+    delete [] m_free_pool_blocks[i];
+
+  m_free_pool_blocks.clear();
+}
+
+void afxResidueMgr::onDeleteNotify(SimObject* obj)
+{
+  deleteResidueObject(obj, true);
+  Parent::onDeleteNotify(obj);
+}
+
+void afxResidueMgr::residueAdvanceTime()
+{
+  U32 now = Platform::getVirtualMilliseconds();
+  m_managed.fadeAndCull(now);
+  m_managed.sortIfDirty();
+  m_managed.manage();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// add ZODIAC residue
+void afxResidueMgr::add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                                       F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang)
+{
+  add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, false);
+}
+
+void afxResidueMgr::add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                                       F32 rad, const LinearColorF& col, F32 ang)
+{
+  static Point2F vrange(0.0, 0.0);
+  add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, true);
+}
+
+void afxResidueMgr::add_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, 
+                               F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain)
+{
+  if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL)
+    return;
+
+  ColorI col_i = LinearColorF(col).toColorI();
+  U32 now = Platform::getVirtualMilliseconds();
+
+  Residue* residue = the_mgr->alloc_residue();
+  //
+  residue->type = ZODIAC;
+  residue->data.zodiac = zode;
+  residue->fade_time = now + (U32)(dur*1000);
+  residue->stop_time = residue->fade_time + (U32)(fade_dur*1000);
+  residue->fade = 1.0f;
+  //
+  residue->params.zodiac.pos_x = pos.x;
+  residue->params.zodiac.pos_y = pos.y;
+  residue->params.zodiac.pos_z = pos.z;
+  residue->params.zodiac.rad = rad;
+  residue->params.zodiac.vrange_dn = vrange.x;
+  residue->params.zodiac.vrange_up = vrange.y;
+  residue->params.zodiac.r = col_i.red;
+  residue->params.zodiac.g = col_i.green;
+  residue->params.zodiac.b = col_i.blue;
+  residue->params.zodiac.a = col_i.alpha;
+  residue->params.zodiac.ang = ang;
+  residue->params.zodiac.on_terrain = on_terrain;
+  //
+  residue->next = 0;
+  
+  the_mgr->add_residue(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// add MODEL residue
+
+void afxResidueMgr::add(F32 dur, F32 fade_dur, afxModel* model)
+{
+  if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL)
+    return;
+
+  U32 now = Platform::getVirtualMilliseconds();
+  
+  Residue* residue = the_mgr->alloc_residue();
+  //
+  residue->type = MODEL;
+  residue->data.model = model;
+  residue->fade_time = now + (U32)(dur*1000);
+  residue->stop_time = residue->fade_time + (U32)(fade_dur*1000);
+  residue->fade = 1.0f;
+  //
+  residue->next = 0;
+
+  the_mgr->add_residue(residue);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 179 - 0
Engine/source/afx/afxResidueMgr.h

@@ -0,0 +1,179 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_RESIDUE_MGR_H_
+#define _AFX_RESIDUE_MGR_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxZodiacData;
+class afxModel;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxResidueMgr
+//
+//    Manage transient objects in the world.
+
+class afxResidueMgr : public GameBase
+{
+
+  typedef GameBase Parent;
+ 
+  enum { 
+    ZODIAC,
+    MODEL
+  };
+
+  struct Residue
+  {
+    struct ZodiacParams
+    {
+      F32   pos_x, pos_y, pos_z;
+      F32   rad, vrange_dn, vrange_up;
+      U8    r,g,b,a;
+      F32   ang;
+      bool  on_terrain;
+    };  
+    
+    union ResidueParams
+    {
+      ZodiacParams  zodiac;
+    };
+    
+    union ResidueData
+    {
+      afxZodiacData*  zodiac;
+      afxModel*       model;
+      SimObject*      simobject;
+    };
+
+    
+    U32           type;
+    ResidueData   data;
+    ResidueParams params;
+    U32           fade_time;
+    U32           stop_time;
+    F32           fade;
+        
+    Residue*      next;
+  };
+
+  class ResidueList
+  {
+    Vector<Residue*>  m_array_a;
+    Vector<Residue*>  m_array_b;
+
+    Vector<Residue*>* m_array;
+    Vector<Residue*>* m_scratch_array;
+    bool              m_dirty;
+    S32               m_pending;
+
+    void              swap_array_ptrs();
+    void              free_residue(Residue*);
+
+  public:
+    /*C*/             ResidueList();
+    /*D*/             ~ResidueList();
+
+    void              clear();
+    S32               size() { return m_array->size(); }
+    bool              empty() { return m_array->empty(); }
+    void              sortIfDirty() { if (m_dirty) sort(); }
+
+    void              sort();
+    void              fadeAndCull(U32 now);
+    void              stripMatchingObjects(SimObject* db, bool del_notify=false);
+    void              add(Residue*);
+
+    void              manage();
+
+    U32               findPendingBestBump(U32 look_max=256);
+    void              bumpPending();
+
+    static int QSORT_CALLBACK compare_residue(const void* p1, const void* p2);
+  };
+
+  friend class ResidueList;
+
+private:
+  enum { FREE_POOL_BLOCK_SIZE = 256 };
+
+  static afxResidueMgr* the_mgr;
+
+  static U32        m_max_residue_objs;
+  static bool       enabled;
+
+  ResidueList       m_managed;
+  
+  Vector<Residue*>  m_free_pool_blocks;
+  Residue*          m_next_free;
+
+  Residue*          alloc_free_pool_block();
+  Residue*          alloc_residue();
+  void              free_residue(Residue*);
+
+  void              bump_residue();
+  void              add_residue(Residue*);
+  static void       add_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad, 
+                               const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain);
+ 
+protected:
+  void              deleteResidueObject(SimObject* obj, bool del_notify=false); 
+
+  void              manage_residue(const Residue* r);
+
+  bool              requires_delete_tracking(Residue*);
+  void              enable_delete_tracking(Residue*);
+  void              disable_delete_tracking(Residue*);
+   
+public:                     
+  /*C*/             afxResidueMgr();
+  /*D*/             ~afxResidueMgr();
+
+  void              cleanup();
+  virtual void      onDeleteNotify(SimObject *obj);
+    
+public:
+  void              residueAdvanceTime();
+
+                    // ZODIAC
+  static void       add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad,
+                                       const LinearColorF& col, F32 ang);
+  static void       add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad,
+                                        const Point2F& vrange, const LinearColorF& col, F32 ang);
+
+                    // MODEL
+  static void       add(F32 dur, F32 fade_dur, afxModel*);
+
+  static afxResidueMgr* getMaster() { return the_mgr; }
+  static void           setMaster(afxResidueMgr* m) { the_mgr = m; }
+
+  DECLARE_CONOBJECT(afxResidueMgr);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_RESIDUE_MGR_H_

+ 1173 - 0
Engine/source/afx/afxSelectron.cpp

@@ -0,0 +1,1173 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "sfx/sfxSystem.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxSelectron.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectronData::ewValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list.
+//
+void afxSelectronData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxSelectronData* sele_data = dynamic_cast<afxSelectronData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (sele_data && ew)
+  {
+    switch (id)
+    {
+    case MAIN_PHRASE:
+      sele_data->main_fx_list.push_back(*ew);
+      break;
+    case SELECT_PHRASE:
+      sele_data->select_fx_list.push_back(*ew);
+      break;
+    case DESELECT_PHRASE:
+      sele_data->deselect_fx_list.push_back(*ew);
+      break;
+    }
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class SelectronFinishStartupEvent : public SimEvent
+{
+public:
+  void process(SimObject* obj)
+  {
+     afxSelectron* selectron = dynamic_cast<afxSelectron*>(obj);
+     if (selectron)
+       selectron->finish_startup();
+  }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectronData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxSelectronData);
+
+ConsoleDocClass( afxSelectronData,
+   "@brief Defines the properties of an afxSelectronData.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxSelectronData::afxSelectronData()
+{
+  main_dur = 0.0f;
+  select_dur = 0.0f;
+  deselect_dur = 0.0f;
+
+  n_main_loops = 1;
+  n_select_loops = 1;
+  n_deselect_loops = 1;
+
+  registered = false;
+
+  obj_type_style = 0;
+  obj_type_mask = 0;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxSelectronData::afxSelectronData(const afxSelectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone)
+{
+  main_dur = other.main_dur;
+  select_dur = other.select_dur;
+  deselect_dur = other.deselect_dur;
+  n_main_loops = other.n_main_loops;
+  n_select_loops = other.n_select_loops;
+  n_deselect_loops = other.n_deselect_loops;
+  registered = false;
+  obj_type_style = other.obj_type_style;
+  obj_type_mask = other.obj_type_mask;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert;
+
+  main_fx_list = other.main_fx_list;
+  select_fx_list = other.select_fx_list;
+  deselect_fx_list = other.deselect_fx_list;
+}
+
+afxSelectronData::~afxSelectronData()
+{
+  if (registered && !isTempClone())
+    arcaneFX::unregisterSelectronData(this);
+}
+
+void afxSelectronData::reloadReset()
+{
+  main_fx_list.clear();
+  select_fx_list.clear();
+  deselect_fx_list.clear();
+}
+
+#define myOffset(field) Offset(field, afxSelectronData)
+
+void afxSelectronData::initPersistFields()
+{
+   static ewValidator _mainPhrase(MAIN_PHRASE);
+   static ewValidator _selectPhrase(SELECT_PHRASE);
+   static ewValidator _deselectPhrase(DESELECT_PHRASE);
+
+  addField("mainDur",                   TypeF32,    myOffset(main_dur),
+    "...");
+  addField("selectDur",                 TypeF32,    myOffset(select_dur),
+    "...");
+  addField("deselectDur",               TypeF32,    myOffset(deselect_dur),
+    "...");
+  addField("mainRepeats",               TypeS32,    myOffset(n_main_loops),
+    "...");
+  addField("selectRepeats",             TypeS32,    myOffset(n_select_loops),
+    "...");
+  addField("deselectRepeats",           TypeS32,    myOffset(n_deselect_loops),
+    "...");
+  addField("selectionTypeMask",         TypeS32,    myOffset(obj_type_mask),
+    "...");
+  addField("selectionTypeStyle",        TypeS8,     myOffset(obj_type_style),
+    "...");
+
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list
+  addFieldV("addMainEffect",      TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_mainPhrase,
+    "...");
+  addFieldV("addSelectEffect",    TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_selectPhrase,
+    "...");
+  addFieldV("addDeselectEffect",  TYPEID<afxEffectBaseData>(),  myOffset(dummy_fx_entry),  &_deselectPhrase,
+    "...");
+
+  // deprecated
+  addField("numMainLoops",      TypeS32,      myOffset(n_main_loops),
+    "...");
+  addField("numSelectLoops",    TypeS32,      myOffset(n_select_loops),
+    "...");
+  addField("numDeselectLoops",  TypeS32,      myOffset(n_deselect_loops),
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addMainEffect");
+  disableFieldSubstitutions("addSelectEffect");
+  disableFieldSubstitutions("addDeselectEffect");
+}
+
+bool afxSelectronData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxSelectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxSelectronData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxSelectronData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(main_dur);
+  stream->write(select_dur);
+  stream->write(deselect_dur);
+  stream->write(n_main_loops);
+  stream->write(n_select_loops);
+  stream->write(n_deselect_loops);
+  stream->write(obj_type_style);
+  stream->write(obj_type_mask);
+
+  pack_fx(stream, main_fx_list, packed);
+  pack_fx(stream, select_fx_list, packed);
+  pack_fx(stream, deselect_fx_list, packed);
+}
+
+void afxSelectronData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&main_dur);
+  stream->read(&select_dur);
+  stream->read(&deselect_dur);
+  stream->read(&n_main_loops);
+  stream->read(&n_select_loops);
+  stream->read(&n_deselect_loops);
+  stream->read(&obj_type_style);
+  stream->read(&obj_type_mask);
+
+  do_id_convert = true;
+  unpack_fx(stream, main_fx_list);
+  unpack_fx(stream, select_fx_list);
+  unpack_fx(stream, deselect_fx_list);
+}
+
+inline void expand_fx_list(afxEffectList& fx_list, const char* tag)
+{
+  for (S32 i = 0; i < fx_list.size(); i++)
+  {
+    SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+    if (db_id != 0)
+    {
+      // try to convert id to pointer
+      if (!Sim::findObject(db_id, fx_list[i]))
+      {
+        Con::errorf(ConsoleLogEntry::General,
+          "afxSelectronData::preload() -- bad datablockId: 0x%x (%s)",
+          db_id, tag);
+      }
+    }
+  }
+}
+
+bool afxSelectronData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      expand_fx_list(main_fx_list, "main");
+      expand_fx_list(select_fx_list, "select");
+      expand_fx_list(deselect_fx_list, "deselect");
+      do_id_convert = false;
+    }
+
+    // this is where a selectron registers itself with the rest of AFX
+    if (!registered)
+    {
+      arcaneFX::registerSelectronData(this);
+      registered = true;
+    }
+  }
+
+  return true;
+}
+
+void afxSelectronData::gatherConstraintDefs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, main_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, select_fx_list);
+  afxConstraintDef::gather_cons_defs(defs, deselect_fx_list);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxSelectronData, reset, void, (),,
+                   "Resets a selectron datablock during reload.\n\n"
+                   "@ingroup AFX")
+{
+  object->reloadReset();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectron
+
+IMPLEMENT_CO_NETOBJECT_V1(afxSelectron);
+
+ConsoleDocClass( afxSelectron,
+   "@brief A choreographer for selection effects.\n\n"
+
+   "@ingroup afxChoreographers\n"
+   "@ingroup AFX\n"
+);
+
+StringTableEntry  afxSelectron::CAMERA_CONS;
+StringTableEntry  afxSelectron::LISTENER_CONS;
+StringTableEntry  afxSelectron::FREE_TARGET_CONS;
+
+void afxSelectron::init()
+{
+  client_only = true;
+  mNetFlags.clear(Ghostable | ScopeAlways);
+  mNetFlags.set(IsGhost);
+
+  // setup static predefined constraint names
+  if (CAMERA_CONS == 0)
+  {
+    CAMERA_CONS = StringTable->insert("camera");
+    LISTENER_CONS = StringTable->insert("listener");
+    FREE_TARGET_CONS = StringTable->insert("freeTarget");
+  }
+
+  datablock = NULL;
+  exeblock = NULL;
+
+  constraints_initialized = false;
+
+  effect_state = (U8) INACTIVE_STATE;
+  effect_elapsed = 0;
+
+  // define named constraints
+  constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  LISTENER_CONS);
+  constraint_mgr->defineConstraint(POINT_CONSTRAINT,  FREE_TARGET_CONS);
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    phrases[i] = NULL;
+
+  time_factor = 1.0f;
+  camera_cons_obj = 0;
+
+  marks_mask = 0;
+}
+
+afxSelectron::afxSelectron()
+{
+  started_with_newop = true;
+  init();
+}
+
+afxSelectron::afxSelectron(bool not_default)
+{
+  started_with_newop = false;
+  init();
+}
+
+afxSelectron::~afxSelectron()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      phrases[i]->interrupt(effect_elapsed);
+      delete phrases[i];
+    }
+  }
+
+  if (datablock && datablock->isTempClone())
+  {
+    delete datablock;
+    datablock = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+// STANDARD OVERLOADED METHODS //
+
+bool afxSelectron::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  datablock = dynamic_cast<afxSelectronData*>(dptr);
+  if (!datablock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  exeblock = datablock;
+
+  return true;
+}
+
+void afxSelectron::processTick(const Move* m)
+{
+	Parent::processTick(m);
+
+  // don't process moves or client ticks
+  if (m != 0 || isClientObject())
+    return;
+
+  process_server();
+}
+
+void afxSelectron::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  process_client(dt);
+}
+
+bool afxSelectron::onAdd()
+{
+  if (started_with_newop)
+  {
+    Con::errorf("afxSelectron::onAdd() -- selectrons cannot be created with the \"new\" operator. Use startSelectron() instead.");
+    return false;
+  }
+
+  NetConnection* conn = NetConnection::getConnectionToServer();
+  if (!conn || !Parent::onAdd())
+    return false;
+
+  conn->addObject(this);
+
+  return true;
+}
+
+void afxSelectron::onRemove()
+{
+  getContainer()->removeObject(this);
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxSelectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+    stream->write(time_factor);
+
+    GameConnection* gconn = dynamic_cast<GameConnection*>(conn);
+    bool zoned_in = (gconn) ? gconn->isZonedIn() : false;
+    if (stream->writeFlag(zoned_in))
+      pack_constraint_info(conn, stream);
+  }
+
+  // StateEvent or SyncEvent
+  if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask)))
+  {
+    stream->write(marks_mask);
+    stream->write(effect_state);
+    stream->write(effect_elapsed);
+  }
+
+  // SyncEvent
+  bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask));
+  if (stream->writeFlag(do_sync_event))
+  {
+    pack_constraint_info(conn, stream);
+  }
+
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSelectron::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+
+  bool initial_update = false;
+  bool zoned_in = true;
+  bool do_sync_event = false;
+  U8 new_marks_mask = 0;
+  U8 new_state = INACTIVE_STATE;
+  F32 new_elapsed = 0;
+
+  // InitialUpdate Only
+  if (stream->readFlag())
+  {
+    initial_update = true;
+
+    stream->read(&time_factor);
+
+    // if client is marked as fully zoned in
+    if ((zoned_in = stream->readFlag()) == true)
+    {
+      unpack_constraint_info(conn, stream);
+      init_constraints();
+    }
+  }
+
+  // StateEvent or SyncEvent
+  // this state data is sent for both state-events and
+  // sync-events
+  if (stream->readFlag())
+  {
+    stream->read(&new_marks_mask);
+    stream->read(&new_state);
+    stream->read(&new_elapsed);
+
+    marks_mask = new_marks_mask;
+  }
+
+  // SyncEvent
+  do_sync_event = stream->readFlag();
+  if (do_sync_event)
+  {
+    unpack_constraint_info(conn, stream);
+    init_constraints();
+  }
+
+  //~~~~~~~~~~~~~~~~~~~~//
+
+  if (!zoned_in)
+    effect_state = LATE_STATE;
+
+  // need to adjust state info to get all synced up with spell on server
+  if (do_sync_event && !initial_update)
+    sync_client(new_marks_mask, new_state, new_elapsed);
+}
+
+void afxSelectron::sync_with_clients()
+{
+  setMaskBits(SyncEventMask);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+bool afxSelectron::state_expired()
+{
+  afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? phrases[MAIN_PHRASE] : NULL;
+
+  if (phrase)
+  {
+    if (phrase->expired(effect_elapsed))
+      return (!phrase->recycle(effect_elapsed));
+    return false;
+  }
+
+  return true;
+}
+
+void afxSelectron::init_constraints()
+{
+  if (constraints_initialized)
+  {
+    //Con::printf("CONSTRAINTS ALREADY INITIALIZED");
+    return;
+  }
+
+  Vector<afxConstraintDef> defs;
+  datablock->gatherConstraintDefs(defs);
+
+  constraint_mgr->initConstraintDefs(defs, isServerObject());
+
+  if (isClientObject())
+  {
+    // find local camera
+    camera_cons_obj = get_camera();
+    if (camera_cons_obj)
+      camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj);
+
+    // find local listener
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos);
+
+    // find free target
+    free_target_cons_id = constraint_mgr->setReferencePoint(FREE_TARGET_CONS, arcaneFX::sFreeTargetPos);
+  }
+
+  constraint_mgr->adjustProcessOrdering(this);
+
+  constraints_initialized = true;
+}
+
+void afxSelectron::setup_main_fx()
+{
+  phrases[MAIN_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->init(datablock->main_fx_list, datablock->main_dur, this, time_factor,
+                               datablock->n_main_loops);
+}
+
+void afxSelectron::setup_select_fx()
+{
+  phrases[SELECT_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->init(datablock->select_fx_list, -1, this, time_factor, 1);
+}
+
+void afxSelectron::setup_deselect_fx()
+{
+  phrases[DESELECT_PHRASE] = new afxPhrase(isServerObject(), true);
+
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->init(datablock->deselect_fx_list, -1, this, time_factor, 1);
+}
+
+bool afxSelectron::cleanup_over()
+{
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i] && !phrases[i]->isEmpty())
+      return false;
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxSelectron::process_server()
+{
+  if (effect_state != INACTIVE_STATE)
+    effect_elapsed += TickSec;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_s(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // sample the constraints
+  constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds());
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(TickSec, effect_elapsed);
+}
+
+void afxSelectron::change_state_s(U8 pending_state)
+{
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_s();
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_s();
+    break;
+  case DONE_STATE:
+    enter_done_state_s();
+    break;
+  }
+}
+
+void afxSelectron::enter_done_state_s()
+{
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      F32 phrase_done;
+      if (phrases[i]->willStop() && phrases[i]->isInfinite())
+        phrase_done = effect_elapsed + phrases[i]->calcAfterLife();
+      else
+        phrase_done = phrases[i]->calcDoneTime();
+      if (phrase_done > done_time)
+        done_time = phrase_done;
+    }
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxSelectronData::onDeactivate(%sele)
+  Con::executef(datablock, "onDeactivate", getIdString());
+}
+
+void afxSelectron::enter_active_state_s()
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds());
+  effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  // start casting effects
+  setup_main_fx();
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  setup_select_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+}
+
+void afxSelectron::leave_active_state_s()
+{
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->stop(effect_elapsed);
+}
+
+void afxSelectron::enter_cleanup_state_s()
+{
+  // start deselect effects
+  setup_deselect_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->interrupt(effect_elapsed);
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  postEvent(SHUTDOWN_EVENT);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// private
+
+void afxSelectron::process_client(F32 dt)
+{
+  effect_elapsed += dt;
+
+  U8 pending_state = effect_state;
+
+  // check for state changes
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    if (marks_mask & MARK_ACTIVATE)
+      pending_state = ACTIVE_STATE;
+    break;
+  case ACTIVE_STATE:
+    if (marks_mask & MARK_INTERRUPT)
+      pending_state = CLEANUP_STATE;
+    else if (marks_mask & MARK_SHUTDOWN)
+      pending_state = CLEANUP_STATE;
+    else if (state_expired())
+      pending_state = CLEANUP_STATE;
+    break;
+  case CLEANUP_STATE:
+    if (cleanup_over())
+      pending_state = DONE_STATE;
+    break;
+  }
+
+  if (effect_state != pending_state)
+    change_state_c(pending_state);
+
+  if (effect_state == INACTIVE_STATE)
+    return;
+
+  //--------------------------//
+
+  // update the listener constraint position
+  if (!listener_cons_id.undefined())
+  {
+    Point3F listener_pos;
+    listener_pos = SFX->getListener().getTransform().getPosition();
+    constraint_mgr->setReferencePoint(listener_cons_id, listener_pos);
+  }
+
+  // update the free target constraint position
+  if (!free_target_cons_id.undefined())
+  {
+    if (!arcaneFX::sFreeTargetPosValid)
+      constraint_mgr->invalidateReference(free_target_cons_id);
+    else
+      constraint_mgr->setReferencePoint(free_target_cons_id, arcaneFX::sFreeTargetPos);
+  }
+
+  // find local camera position
+  Point3F cam_pos;
+  SceneObject* current_cam = get_camera(&cam_pos);
+
+  // detect camera changes
+  if (!camera_cons_id.undefined() && current_cam != camera_cons_obj)
+  {
+    constraint_mgr->setReferenceObject(camera_cons_id, current_cam);
+    camera_cons_obj = current_cam;
+  }
+
+  // sample the constraints
+  constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0);
+
+  // update active effects lists
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+    if (phrases[i])
+      phrases[i]->update(dt, effect_elapsed);
+
+}
+
+void afxSelectron::change_state_c(U8 pending_state)
+{
+  if (effect_state == pending_state)
+    return;
+
+  switch (effect_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    leave_active_state_c();
+    break;
+  case CLEANUP_STATE:
+    break;
+  case DONE_STATE:
+    break;
+  }
+
+  effect_state = pending_state;
+
+  switch (pending_state)
+  {
+  case INACTIVE_STATE:
+    break;
+  case ACTIVE_STATE:
+    enter_active_state_c(effect_elapsed);
+    break;
+  case CLEANUP_STATE:
+    enter_cleanup_state_c();
+    break;
+  case DONE_STATE:
+    enter_done_state_c();
+    break;
+  }
+}
+
+void afxSelectron::enter_active_state_c(F32 starttime)
+{
+  // stamp constraint-mgr starting time
+  constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000));
+  ///effect_elapsed = 0;
+
+  setup_dynamic_constraints();
+
+  setup_main_fx();
+  if (phrases[MAIN_PHRASE])
+    phrases[MAIN_PHRASE]->start(starttime, effect_elapsed);
+
+  setup_select_fx();
+  if (phrases[SELECT_PHRASE])
+    phrases[SELECT_PHRASE]->start(starttime, effect_elapsed);
+}
+
+void afxSelectron::leave_active_state_c()
+{
+  if (phrases[MAIN_PHRASE])
+  {
+    //if (marks_mask & MARK_INTERRUPT)
+    //  active_phrase->interrupt(effect_elapsed);
+    //else
+      phrases[MAIN_PHRASE]->stop(effect_elapsed);
+  }
+}
+
+void afxSelectron::enter_cleanup_state_c()
+{
+  if (!client_only)
+    return;
+
+  // start deselect effects
+  setup_deselect_fx();
+  if (phrases[DESELECT_PHRASE])
+    phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed);
+
+  postEvent(SHUTDOWN_EVENT);
+}
+
+void afxSelectron::enter_done_state_c()
+{
+  if (!client_only)
+    return;
+
+  postEvent(DEACTIVATE_EVENT);
+
+  F32 done_time = effect_elapsed;
+
+  for (S32 i = 0; i < NUM_PHRASES; i++)
+  {
+    if (phrases[i])
+    {
+      F32 phrase_done;
+      if (phrases[i]->willStop() && phrases[i]->isInfinite())
+        phrase_done = effect_elapsed + phrases[i]->calcAfterLife();
+      else
+        phrase_done = phrases[i]->calcDoneTime();
+      if (phrase_done > done_time)
+        done_time = phrase_done;
+    }
+  }
+
+  F32 time_left = done_time - effect_elapsed;
+  if (time_left < 0)
+    time_left = 0;
+
+  Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500);
+
+  // CALL SCRIPT afxSelectronData::onDeactivate(%selectron)
+  Con::executef(datablock, "onDeactivate", getIdString());
+}
+
+void afxSelectron::sync_client(U16 marks, U8 state, F32 elapsed)
+{
+  //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g",
+  //            marks, effect_state, state, elapsed);
+
+  if (effect_state != LATE_STATE)
+    return;
+
+  marks_mask = marks;
+
+  // don't want to be started on late zoning clients
+  if (!datablock->exec_on_new_clients)
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we're ghosting pretty late and
+  // should just return to the inactive state.
+  else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN))
+  {
+    effect_state = DONE_STATE;
+  }
+
+  // it looks like we should be in the active state.
+  else if (marks & MARK_ACTIVATE)
+  {
+    effect_state = ACTIVE_STATE;
+    effect_elapsed = elapsed;
+    enter_active_state_c(0.0);
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// public:
+
+void afxSelectron::postEvent(U8 event)
+{
+  setMaskBits(StateEventMask);
+
+  switch (event)
+  {
+  case ACTIVATE_EVENT:
+    marks_mask |= MARK_ACTIVATE;
+    break;
+  case SHUTDOWN_EVENT:
+    marks_mask |= MARK_SHUTDOWN;
+    break;
+  case DEACTIVATE_EVENT:
+    marks_mask |= MARK_DEACTIVATE;
+    break;
+  case INTERRUPT_EVENT:
+    marks_mask |= MARK_INTERRUPT;
+    break;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxSelectron::finish_startup()
+{
+  init_constraints();
+  postEvent(afxSelectron::ACTIVATE_EVENT);
+}
+
+// static
+afxSelectron*
+afxSelectron::start_selectron(SceneObject* picked, U8 subcode, SimObject* extra)
+{
+  U32 picked_type = (picked) ? picked->getTypeMask() : 0;
+
+  afxSelectronData* datablock = arcaneFX::findSelectronData(picked_type, subcode);
+  if (!datablock)
+  {
+    Con::errorf("startSelectron() -- failed to match object-type (%x/%d) to a selection-effect.",
+                picked_type, subcode);
+    return 0;
+  }
+
+  afxSelectronData* exeblock = datablock;
+
+  SimObject* param_holder = new SimObject();
+  if (!param_holder->registerObject())
+  {
+    Con::errorf("afxSelectron: failed to register parameter object.");
+    delete param_holder;
+    return 0;
+  }
+
+  param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix);
+  if (extra)
+  {
+    // copy dynamic fields from the extra object to the param holder
+    param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix);
+  }
+
+  // CALL SCRIPT afxSelectronData::onPreactivate(%params, %extra)
+  const char* result = Con::executef(datablock, "onPreactivate",
+                                     Con::getIntArg(param_holder->getId()),
+                                     (extra) ? Con::getIntArg(extra->getId()) : "");
+  if (result && result[0] != '\0' && !dAtob(result))
+  {
+#if defined(TORQUE_DEBUG)
+    Con::warnf("afxSelectron: onPreactivate() returned false, effect aborted.");
+#endif
+    Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  // make a temp datablock clone if there are substitutions
+  if (datablock->getSubstitutionCount() > 0)
+  {
+    datablock = new afxSelectronData(*exeblock, true);
+    exeblock->performSubstitutions(datablock, param_holder);
+  }
+
+  // create a new selectron instance
+  afxSelectron* selectron = new afxSelectron(true);
+  selectron->setDataBlock(datablock);
+  selectron->exeblock = exeblock;
+  selectron->setExtra(extra);
+
+  // copy dynamic fields from the param holder to the selectron
+  selectron->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix);
+  Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime());
+
+  // register
+  if (!selectron->registerObject())
+  {
+    Con::errorf("afxSelectron: failed to register selectron instance.");
+    Sim::postEvent(selectron, new ObjectDeleteEvent, Sim::getCurrentTime());
+    return 0;
+  }
+
+  selectron->activate();
+
+  return selectron;
+}
+
+void afxSelectron::activate()
+{
+  // separating the final part of startup allows the calling script
+  // to make certain types of calls on the returned effectron that
+  // need to happen prior to constraint initialization.
+  Sim::postEvent(this, new SelectronFinishStartupEvent, Sim::getCurrentTime());
+
+  // CALL SCRIPT afxEffectronData::onActivate(%eff)
+  Con::executef(exeblock, "onActivate", getIdString());
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// console functions
+
+DefineEngineMethod(afxSelectron, setTimeFactor, void, (float factor), (1.0f),
+                   "Sets the time factor of the selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->setTimeFactor(factor);
+}
+
+DefineEngineMethod(afxSelectron, interrupt, void, (),,
+                   "Interrupts and deletes a running selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->postEvent(afxSelectron::INTERRUPT_EVENT);
+}
+
+DefineEngineMethod(afxSelectron, stopSelectron, void, (),,
+                   "Stops and deletes a running selectron.\n\n"
+                   "@ingroup AFX")
+{
+  object->postEvent(afxSelectron::INTERRUPT_EVENT);
+}
+
+DefineEngineFunction(startSelectron, S32, (SceneObject* selectedObj, unsigned int subcode, SimObject* extra),
+                     (nullAsType<SceneObject*>(), 0, nullAsType<SimObject*>()),
+                     "Instantiates a selectron.\n\n"
+                     "@ingroup AFX")
+{
+  //
+  // Start the Selectron
+  //
+  afxSelectron* selectron = afxSelectron::start_selectron(selectedObj, (U8)subcode, extra);
+
+  //
+  // Return the ID (or 0 if start failed).
+  //
+  return (selectron) ? selectron->getId() : 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+
+
+
+
+
+
+

+ 258 - 0
Engine/source/afx/afxSelectron.h

@@ -0,0 +1,258 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_SELECTION_EFFECT_H_
+#define _AFX_SELECTION_EFFECT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "console/typeValidators.h"
+
+#include "afxChoreographer.h"
+#include "afxEffectWrapper.h"
+#include "afxPhrase.h"
+
+class afxChoreographerData;
+class afxEffectBaseData;
+
+class  afxSelectronDefs
+{
+public:
+  enum {
+    MAIN_PHRASE,
+    SELECT_PHRASE,
+    DESELECT_PHRASE,
+    NUM_PHRASES
+  };
+};
+
+class afxSelectronData : public afxChoreographerData, public afxSelectronDefs
+{
+  typedef afxChoreographerData Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool          do_id_convert;
+
+public:
+  F32           main_dur;
+  F32           select_dur;
+  F32           deselect_dur;
+
+  S32           n_main_loops;
+  S32           n_select_loops;
+  S32           n_deselect_loops;
+
+  bool          registered;
+  U8            obj_type_style;
+  U32           obj_type_mask;
+
+  afxEffectBaseData* dummy_fx_entry;
+
+  afxEffectList main_fx_list;
+  afxEffectList select_fx_list;
+  afxEffectList deselect_fx_list;
+  
+private:
+  void          pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void          unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/         afxSelectronData();
+  /*C*/         afxSelectronData(const afxSelectronData&, bool = false);
+  /*D*/         ~afxSelectronData();
+
+  virtual void  reloadReset();
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  bool          matches(U32 mask, U8 style);
+  void          gatherConstraintDefs(Vector<afxConstraintDef>&); 
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxSelectronData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxSelectronData::matches(U32 mask, U8 style)
+{
+  if (obj_type_style != style)
+    return false;
+
+  if (obj_type_mask == 0 && mask == 0)
+    return true;
+
+  return ((obj_type_mask & mask) != 0);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSelectron
+
+class afxSelectron : public afxChoreographer, public afxSelectronDefs
+{
+  typedef afxChoreographer Parent;
+  friend class arcaneFX;
+
+public:
+  enum MaskBits 
+  {
+    StateEventMask    = Parent::NextFreeMask << 0,
+    SyncEventMask     = Parent::NextFreeMask << 1,
+    NextFreeMask      = Parent::NextFreeMask << 2
+  };
+
+  enum
+  {
+    NULL_EVENT,
+    ACTIVATE_EVENT,
+    SHUTDOWN_EVENT,
+    DEACTIVATE_EVENT,
+    INTERRUPT_EVENT
+  };
+
+  enum
+  {
+    INACTIVE_STATE,
+    ACTIVE_STATE,
+    CLEANUP_STATE,
+    DONE_STATE,
+    LATE_STATE
+  };
+
+  enum {
+    MARK_ACTIVATE   = BIT(0),
+    MARK_SHUTDOWN   = BIT(1),
+    MARK_DEACTIVATE = BIT(2),
+    MARK_INTERRUPT  = BIT(3),
+  };
+
+  class ObjectDeleteEvent : public SimEvent
+  {
+  public:
+    void process(SimObject *obj) { if (obj) obj->deleteObject(); }
+  };
+
+private:
+  static StringTableEntry  CAMERA_CONS;
+  static StringTableEntry  LISTENER_CONS;
+  static StringTableEntry  FREE_TARGET_CONS;
+
+private:
+  afxSelectronData*  datablock;
+  SimObject*         exeblock;
+
+  bool          constraints_initialized;
+  bool          client_only;
+
+  U8            effect_state;
+  F32           effect_elapsed;
+
+  afxConstraintID listener_cons_id;
+  afxConstraintID free_target_cons_id;
+  afxConstraintID camera_cons_id;
+  SceneObject*  camera_cons_obj;
+
+  afxPhrase*    phrases[NUM_PHRASES];
+
+  F32           time_factor;
+  U8            marks_mask;
+
+private:
+  void          init();
+  bool          state_expired();
+  void          init_constraints();
+  void          setup_main_fx();
+  void          setup_select_fx();
+  void          setup_deselect_fx();
+  bool          cleanup_over();
+
+public:
+  /*C*/         afxSelectron();
+  /*C*/         afxSelectron(bool not_default);
+  /*D*/         ~afxSelectron();
+
+    // STANDARD OVERLOADED METHODS //
+  virtual bool  onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void  processTick(const Move*);
+  virtual void  advanceTime(F32 dt);
+  virtual bool  onAdd();
+  virtual void  onRemove();
+  virtual U32   packUpdate(NetConnection*, U32, BitStream*);
+  virtual void  unpackUpdate(NetConnection*, BitStream*);
+
+  virtual void  sync_with_clients();
+  void          finish_startup();
+
+  DECLARE_CONOBJECT(afxSelectron);
+  DECLARE_CATEGORY("AFX");
+
+private:
+  void          process_server();
+  //
+  void          change_state_s(U8 pending_state);
+  //
+  void          enter_active_state_s();
+  void          leave_active_state_s();
+  void          enter_cleanup_state_s();
+  void          enter_done_state_s();
+
+private:
+  void          process_client(F32 dt);
+  //
+  void          change_state_c(U8 pending_state);
+  //
+  void          enter_active_state_c(F32 starttime);
+  void          enter_cleanup_state_c();
+  void          enter_done_state_c();
+  void          leave_active_state_c();
+
+  void          sync_client(U16 marks, U8 state, F32 elapsed);
+
+public:
+  void          postEvent(U8 event);
+  void          setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; }
+  F32           getTimeFactor() { return time_factor; }
+
+  void          activate();
+
+public:
+  static afxSelectron*  start_selectron(SceneObject* picked, U8 subcode, SimObject* extra);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+#endif // _AFX_SELECTION_EFFECT_H_

+ 357 - 0
Engine/source/afx/afxSpellBook.cpp

@@ -0,0 +1,357 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "T3D/gameBase/gameBase.h"
+
+#include "afx/afxSpellBook.h"
+#include "afx/afxMagicSpell.h"
+#include "afx/rpg/afxRPGMagicSpell.h"
+#include "afx/ui/afxSpellButton.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSpellBookData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxSpellBookData);
+
+ConsoleDocClass( afxSpellBookData,
+   "@brief A spellbook datablock.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxSpellBookData::afxSpellBookData()
+{
+  spells_per_page = 12;
+  pages_per_book = 12;
+  dMemset(spells, 0, sizeof(spells));
+  dMemset(rpg_spells, 0, sizeof(rpg_spells));
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+#define myOffset(field) Offset(field, afxSpellBookData)
+
+void afxSpellBookData::initPersistFields()
+{
+  addField("spellsPerPage",  TypeS8,                myOffset(spells_per_page),
+    "...");
+  addField("pagesPerBook",   TypeS8,                myOffset(pages_per_book),
+    "...");
+
+  addField("spells",       TYPEID<GameBaseData>(),   myOffset(spells),     MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE,
+    "...");
+  addField("rpgSpells",    TYPEID<GameBaseData>(),   myOffset(rpg_spells), MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE,
+    "...");
+
+  Parent::initPersistFields();
+}
+
+bool afxSpellBookData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)rpg_spells[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, rpg_spells[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General,
+              "afxSpellBookData::preload() -- bad datablockId: 0x%x (afxRPGMagicSpellData)",
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxSpellBookData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(spells_per_page);
+  stream->write(pages_per_book);
+
+  for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+    writeDatablockID(stream, rpg_spells[i], packed);
+}
+
+void afxSpellBookData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&spells_per_page);
+  stream->read(&pages_per_book);
+
+  do_id_convert = true;
+  for (S32 i = 0; i < pages_per_book*spells_per_page; i++)
+    rpg_spells[i] = (afxRPGMagicSpellData*) readDatablockID(stream);
+}
+
+DefineEngineMethod(afxSpellBookData, getPageSlotIndex, S32, (Point2I bookSlot),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  return object->getPageSlotIndex(bookSlot.x, bookSlot.y);
+}
+
+DefineEngineMethod(afxSpellBookData, getCapacity, S32, (),,
+                   "Get the capacity (total number of spell slots) in a spellbook.\n\n"
+                   "@ingroup AFX")
+{
+  return object->spells_per_page*object->pages_per_book;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxSpellBook
+
+IMPLEMENT_CO_NETOBJECT_V1(afxSpellBook);
+
+ConsoleDocClass( afxSpellBook,
+   "@brief A spellbook object.\n\n"
+
+   "@ingroup afxMisc\n"
+   "@ingroup AFX\n"
+);
+
+afxSpellBook::afxSpellBook()
+{
+	mNetFlags.set(Ghostable | ScopeAlways);
+	mDataBlock = NULL;
+  all_spell_cooldown = 1.0f;
+}
+
+afxSpellBook::~afxSpellBook()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::initPersistFields()
+{
+	Parent::initPersistFields();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::processTick(const Move* m)
+{
+	Parent::processTick(m);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  if (all_spell_cooldown < 1.0f)
+  {
+    all_spell_cooldown += dt/2.0f;
+    if (all_spell_cooldown > 1.0f)
+      all_spell_cooldown = 1.0f;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxSpellBook::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxSpellBookData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  scriptOnNewDataBlock();
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxSpellBook::onAdd()
+{
+	if (!Parent::onAdd())
+    return(false);
+
+	return(true);
+}
+
+void afxSpellBook::onRemove()
+{
+	Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+U32 afxSpellBook::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
+{
+  U32 retMask = Parent::packUpdate(con, mask, stream);
+
+  if (stream->writeFlag(mask & InitialUpdateMask))
+  {
+  }
+
+  // AllSpellCooldown
+  if (stream->writeFlag(mask & AllSpellCooldownMask))
+  {
+  }
+
+	return(retMask);
+}
+
+void afxSpellBook::unpackUpdate(NetConnection * con, BitStream * stream)
+{
+	Parent::unpackUpdate(con, stream);
+
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+  }
+
+  // AllSpellCooldown
+  if (stream->readFlag())
+  {
+    all_spell_cooldown = 0.0f;
+  }
+}
+
+#define SPELL_DATA_NOT_FOUND "\n<just:center><font:Arial:20><color:FF0000>** Spell data not found **\n\n\n\n"
+
+char* afxSpellBook::formatDesc(char* buffer, int len, S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return SPELL_DATA_NOT_FOUND;
+
+  return mDataBlock->rpg_spells[idx]->formatDesc(buffer, len);
+}
+
+const char* afxSpellBook::getSpellIcon(S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return 0;
+
+  return mDataBlock->rpg_spells[idx]->icon_name;
+}
+
+bool afxSpellBook::isPlaceholder(S32 page, S32 slot) const
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return false;
+
+  return mDataBlock->rpg_spells[idx]->is_placeholder;
+}
+
+
+afxMagicSpellData* afxSpellBook::getSpellData(S32 page, S32 slot)
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->spells[idx])
+    return 0;
+
+  return mDataBlock->spells[idx];
+}
+
+afxRPGMagicSpellData* afxSpellBook::getSpellRPGData(S32 page, S32 slot)
+{
+  S32 idx = mDataBlock->getPageSlotIndex(page, slot);
+  if (idx < 0 || !mDataBlock->rpg_spells[idx])
+    return 0;
+
+  return mDataBlock->rpg_spells[idx];
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxSpellBook::startAllSpellCooldown()
+{
+  //all_spell_cooldown = 0.0f;
+  setMaskBits(AllSpellCooldownMask);
+}
+
+F32 afxSpellBook::getCooldownFactor(S32 page, S32 slot)
+{
+  return all_spell_cooldown;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod(afxSpellBook, getPageSlotIndex, S32, (Point2I bookSlot),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  return object->getPageSlotIndex(bookSlot.x, bookSlot.y);
+}
+
+DefineEngineMethod(afxSpellBook, getSpellData, S32, (Point2I bookSlot),,
+                   "Get spell datablock for spell stored at spellbook index, (page, slot).\n\n"
+                   "@ingroup AFX")
+{
+  afxMagicSpellData* spell_data = object->getSpellData(bookSlot.x, bookSlot.y);
+  return (spell_data) ? spell_data->getId() : 0;
+}
+
+DefineEngineMethod(afxSpellBook, getSpellRPGData, S32, (Point2I bookSlot),,
+                   "Get spell RPG datablock for spell stored at spellbook index, (page, slot).\n\n"
+                   "@ingroup AFX")
+{
+  afxRPGMagicSpellData* spell_data = object->getSpellRPGData(bookSlot.x, bookSlot.y);
+  return (spell_data) ? spell_data->getId() : 0;
+}
+
+DefineEngineMethod(afxSpellBook, startAllSpellCooldown, void, (),,
+                   "...\n\n"
+                   "@ingroup AFX")
+{
+  object->startAllSpellCooldown();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+
+

+ 142 - 0
Engine/source/afx/afxSpellBook.h

@@ -0,0 +1,142 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_SPELL_BOOK_H_
+#define _AFX_SPELL_BOOK_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "T3D/gameBase/gameBase.h"
+
+class afxSpellBookDefs
+{
+public:
+  enum {
+    MAX_SPELLS_PER_PAGE = 12,
+    MAX_PAGES_PER_BOOK = 12
+  };
+};
+
+class afxMagicSpellData;
+class afxRPGMagicSpellData;
+
+class afxSpellBookData : public GameBaseData, public afxSpellBookDefs
+{
+  typedef GameBaseData  Parent;
+
+  bool                  do_id_convert;
+
+public:
+  U8                    spells_per_page;
+  U8                    pages_per_book;
+  afxMagicSpellData*    spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE];
+  afxRPGMagicSpellData* rpg_spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE];
+
+public:
+  /*C*/                 afxSpellBookData();
+
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  bool                  preload(bool server, String &errorStr);
+
+  bool                  verifyPageSlot(S32 page, S32 slot);
+  S32                   getPageSlotIndex(S32 page, S32 slot);
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxSpellBookData);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline bool afxSpellBookData::verifyPageSlot(S32 page, S32 slot)
+{
+  return (page >= 0 && page < pages_per_book && slot >= 0 && slot < spells_per_page);
+}
+
+inline S32 afxSpellBookData::getPageSlotIndex(S32 page, S32 slot)
+{
+  return (verifyPageSlot(page, slot)) ? page*spells_per_page + slot : -1;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxMagicSpellData;
+class afxSpellButton;
+
+class afxSpellBook : public GameBase, public afxSpellBookDefs
+{
+  typedef GameBase        Parent;
+
+  enum MaskBits 
+  {
+    AllSpellCooldownMask  = Parent::NextFreeMask << 0,
+    NextFreeMask          = Parent::NextFreeMask << 1
+  };
+
+private:
+  afxSpellBookData*       mDataBlock;
+  F32                     all_spell_cooldown;
+
+public:
+  /*C*/                   afxSpellBook();
+  /*D*/                   ~afxSpellBook();
+
+  virtual bool            onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void            processTick(const Move*);
+  virtual void            advanceTime(F32 dt);
+
+  virtual bool            onAdd();
+  virtual void            onRemove();
+
+  virtual U32             packUpdate(NetConnection*, U32, BitStream*);
+  virtual void            unpackUpdate(NetConnection*, BitStream*);
+
+  static void             initPersistFields();
+
+  S32                     getPageSlotIndex(S32 page, S32 slot);
+  char*                   formatDesc(char* buffer, int len, S32 page, S32 slot) const;
+  const char*             getSpellIcon(S32 page, S32 slot) const;
+  bool                    isPlaceholder(S32 page, S32 slot) const;
+  afxMagicSpellData*      getSpellData(S32 page, S32 slot);
+  afxRPGMagicSpellData*   getSpellRPGData(S32 page, S32 slot);
+
+  void                    startAllSpellCooldown();
+  F32                     getCooldownFactor(S32 page, S32 slot);
+
+  DECLARE_CONOBJECT(afxSpellBook);
+  DECLARE_CATEGORY("AFX");
+};
+
+inline S32 afxSpellBook::getPageSlotIndex(S32 page, S32 slot)
+{
+  return (mDataBlock) ? mDataBlock->getPageSlotIndex(page, slot) : -1;
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_SPELL_BOOK_H_

+ 311 - 0
Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp

@@ -0,0 +1,311 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacGroundPlaneRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacGroundPlaneRenderer::RIT_GroundPlaneZodiac("GroundPlaneZodiac");
+
+afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacGroundPlaneRenderer);
+
+ConsoleDocClass( afxZodiacGroundPlaneRenderer, 
+   "@brief A render bin for zodiac rendering on GroundPlane objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto GroundPlane surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer()
+: RenderBinManager(RIT_GroundPlaneZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_GroundPlaneZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacGroundPlaneRenderer::~afxZodiacGroundPlaneRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacGroundPlaneRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getGroundPlaneZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacGroundPlaneRenderer::clear()
+{
+  Parent::clear();
+  groundPlane_zodiacs.clear();
+}
+
+void afxZodiacGroundPlaneRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane* gp, F32 camDist)
+{
+  groundPlane_zodiacs.increment();
+  GroundPlaneZodiacElem& elem = groundPlane_zodiacs.last();
+
+  elem.gp = gp;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacGroundPlaneRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacGroundPlaneRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacGroundPlaneRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacGroundPlaneMgr_render);
+
+   // Early out if no ground-plane zodiacs to draw.
+   if (groundPlane_zodiacs.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < groundPlane_zodiacs.size(); zz++)
+   {
+      GroundPlaneZodiacElem& elem = groundPlane_zodiacs[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         //if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 rad_xy = zode->radius_xy;
+      F32 inv_radius = 1.0f/rad_xy;
+      F32 offset_xy = mSqrt(2*rad_xy*rad_xy);
+
+      F32 zx = zode->pos.x;
+      F32 zy = zode->pos.y;
+      F32 z = 0.00001f;
+
+      Point3F verts[4];
+      verts[0].set(zx+offset_xy, zy+offset_xy, z);
+      verts[1].set(zx-offset_xy, zy+offset_xy, z);
+      verts[2].set(zx-offset_xy, zy-offset_xy, z);
+      verts[3].set(zx+offset_xy, zy-offset_xy, z);
+
+      S32 vertind[6];
+      vertind[0] = 2;
+      vertind[1] = 1;
+      vertind[2] = 0;
+      vertind[3] = 3;
+      vertind[4] = 2;
+      vertind[5] = 0;
+
+      PrimBuild::begin(GFXTriangleList, 6);
+      for (U32 i = 0; i < 2; i++) 
+      {
+        for (U32 j = 0; j < 3; j++) 
+        {
+          const Point3F& vtx = verts[vertind[i*3+j]];
+
+          // compute UV
+          F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+          F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(vtx);
+        }
+      }
+      PrimBuild::end(false);
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 91 - 0
Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h

@@ -0,0 +1,91 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_
+#define _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class GroundPlane;
+
+class afxZodiacGroundPlaneRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct GroundPlaneZodiacElem
+   {
+     const GroundPlane* gp;
+     U32                zode_idx;
+     F32                ang;
+     F32                camDist;
+   };
+
+   Vector<GroundPlaneZodiacElem> groundPlane_zodiacs;
+   static afxZodiacGroundPlaneRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_GroundPlaneZodiac;
+
+   /*C*/          afxZodiacGroundPlaneRenderer();
+   /*C*/          afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacGroundPlaneRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane*, F32 camDist);
+
+   virtual void   render(SceneRenderState* state);
+
+   static afxZodiacGroundPlaneRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacGroundPlaneRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_

+ 302 - 0
Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp

@@ -0,0 +1,302 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacMeshRoadRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacMeshRoadRenderer::RIT_MeshRoadZodiac("MeshRoadZodiac");
+
+afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacMeshRoadRenderer);
+
+ConsoleDocClass( afxZodiacMeshRoadRenderer, 
+   "@brief A render bin for zodiac rendering on MeshRoad objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto MeshRoad surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer()
+: RenderBinManager(RIT_MeshRoadZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_MeshRoadZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacMeshRoadRenderer::~afxZodiacMeshRoadRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacMeshRoadRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getMeshRoadZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacMeshRoadRenderer::clear()
+{
+  Parent::clear();
+  for (S32 i = 0; i < meshRoad_zodiacs.size(); i++)
+    if (meshRoad_zodiacs[i].polys)
+      delete meshRoad_zodiacs[i].polys;
+  meshRoad_zodiacs.clear();
+}
+
+void afxZodiacMeshRoadRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const MeshRoad* road, F32 camDist)
+{
+  meshRoad_zodiacs.increment();
+  MeshRoadZodiacElem& elem = meshRoad_zodiacs.last();
+
+  elem.road = road;
+  elem.polys = polys;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacMeshRoadRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacMeshRoadRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacMeshRoadRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacMeshRoadMgr_render);
+
+   // Early out if no ground-plane zodiacs to draw.
+   if (meshRoad_zodiacs.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < meshRoad_zodiacs.size(); zz++)
+   {
+      MeshRoadZodiacElem& elem = meshRoad_zodiacs[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+      for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) 
+      {
+        ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+        S32 vertind[3];
+        vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+        vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+        vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+        for (U32 j = 0; j < 3; j++) 
+        {
+          Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+          // compute UV
+          F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+          F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(vtx);
+        }
+      }
+      PrimBuild::end(false);
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_MESHROAD_RENDERER_H_
+#define _AFX_ZODIAC_MESHROAD_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class MeshRoad;
+
+class afxZodiacMeshRoadRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct MeshRoadZodiacElem
+   {
+     const MeshRoad*    road;
+     U32                zode_idx;
+     ConcretePolyList*  polys;
+     F32                ang;
+     F32                camDist;
+   };
+
+   Vector<MeshRoadZodiacElem> meshRoad_zodiacs;
+   static afxZodiacMeshRoadRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_MeshRoadZodiac;
+
+   /*C*/          afxZodiacMeshRoadRenderer();
+   /*C*/          afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacMeshRoadRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const MeshRoad*, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacMeshRoadRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacMeshRoadRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_MESHROAD_RENDERER_H_

+ 420 - 0
Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp

@@ -0,0 +1,420 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "collision/concretePolyList.h"
+#include "T3D/tsStatic.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacPolysoupRenderer_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacPolysoupRenderer::RIT_PolysoupZodiac("PolysoupZodiac");
+
+afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacPolysoupRenderer);
+
+ConsoleDocClass( afxZodiacPolysoupRenderer, 
+   "@brief A render bin for zodiac rendering on polysoup TSStatic objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto polysoup TSStatic surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer()
+: RenderBinManager(RIT_PolysoupZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_PolysoupZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacPolysoupRenderer::~afxZodiacPolysoupRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacPolysoupRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getPolysoupZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sPolysoupZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacPolysoupRenderer::clear()
+{
+  Parent::clear();
+  for (S32 i = 0; i < polysoup_zodes.size(); i++)
+    if (polysoup_zodes[i].polys)
+      delete polysoup_zodes[i].polys;
+  polysoup_zodes.clear();
+}
+
+void afxZodiacPolysoupRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const TSStatic* tss, F32 camDist)
+{
+  polysoup_zodes.increment();
+  PolysoupZodiacElem& elem = polysoup_zodes.last();
+
+  elem.tss = tss;
+  elem.polys = polys;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.camDist = camDist;
+}
+
+afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacPolysoupRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacPolysoupRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacPolysoupRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacPolysoupMgr_render);
+
+   // Early out if no polysoup zodiacs to draw.
+   if (polysoup_zodes.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < polysoup_zodes.size(); zz++)
+   {
+      PolysoupZodiacElem& elem = polysoup_zodes[zz];
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::inter_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      // FILTER USING GRADIENT RANGE
+      if ((zode->zflags & afxZodiacData::USE_GRADE_RANGE) != 0)
+      {
+        bool skip_oob;
+        F32 grade_min, grade_max;
+        if (elem.tss->mHasGradients && ((zode->zflags & afxZodiacData::PREFER_DEST_GRADE) != 0))
+        {
+          skip_oob = (elem.tss->mInvertGradientRange == false);
+          grade_min = elem.tss->mGradientRange.x;
+          grade_max = elem.tss->mGradientRange.y;
+        }
+        else
+        {
+          skip_oob = ((zode->zflags & afxZodiacData::INVERT_GRADE_RANGE) == 0);
+          grade_min = zode->grade_range.x;
+          grade_max = zode->grade_range.y;
+        }
+
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++)
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          const PlaneF& plane = poly->plane;
+
+          bool oob = (plane.z > grade_max || plane.z < grade_min);          
+          if (oob == skip_oob)
+            continue;
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+
+      // FILTER USING OTHER FILTERS
+      else if (zode->zflags & afxZodiacData::INTERIOR_FILTERS)
+      {
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++)
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          const PlaneF& plane = poly->plane;
+          if (zode->zflags & afxZodiacData::INTERIOR_HORIZ_ONLY)
+          {
+            if (!plane.isHorizontal())
+              continue;
+
+            if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE)
+            {
+              if (plane.whichSide(zode->pos) == PlaneF::Back)
+                continue;
+            }
+          }
+          else
+          {
+            if (zode->zflags & afxZodiacData::INTERIOR_VERT_IGNORE)
+            {
+              if (plane.isVertical())
+                continue;
+            }
+
+            if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE)
+            {
+              if (plane.whichSide(zode->pos) == PlaneF::Back)
+                continue;
+            }
+          }
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+
+      // NO FILTERING
+      else
+      {
+        PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size());
+        for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) 
+        {
+          ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i];
+
+          S32 vertind[3];
+          vertind[0] = elem.polys->mIndexList[poly->vertexStart];
+          vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1];
+          vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2];
+
+          for (U32 j = 0; j < 3; j++) 
+          {
+            Point3F vtx = elem.polys->mVertexList[vertind[j]];
+
+            // compute UV
+            F32 u1 = (vtx.x - zode->pos.x)*inv_radius;
+            F32 v1 = (vtx.y - zode->pos.y)*inv_radius;
+            F32 ru1 = u1*cos_ang - v1*sin_ang;
+            F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+            F32 uu = (ru1 + 1.0f)/2.0f;
+            F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+            PrimBuild::texCoord2f(uu, vv);
+            PrimBuild::vertex3fv(vtx);
+          }
+        }
+        PrimBuild::end(false);
+      }
+   }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_POLYSOUP_RENDERER_H_
+#define _AFX_ZODIAC_POLYSOUP_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ConcretePolyList;
+class TSStatic;
+
+class afxZodiacPolysoupRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct PolysoupZodiacElem
+   {
+     const TSStatic*   tss;
+     U32               zode_idx;
+     ConcretePolyList* polys;
+     F32               ang;
+     F32               camDist;
+   };
+
+   Vector<PolysoupZodiacElem> polysoup_zodes;
+   static afxZodiacPolysoupRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_PolysoupZodiac;
+
+   /*C*/          afxZodiacPolysoupRenderer();
+   /*C*/          afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacPolysoupRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const TSStatic*, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacPolysoupRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacPolysoupRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_POLYSOUP_RENDERER_H_

+ 344 - 0
Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp

@@ -0,0 +1,344 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "materials/shaderData.h"
+#include "gfx/gfxTransformSaver.h"
+#include "scene/sceneRenderState.h"
+#include "terrain/terrData.h"
+#include "terrain/terrCell.h"
+
+#include "gfx/primBuilder.h"
+
+#include "afx/ce/afxZodiacMgr.h"
+#include "afx/afxZodiacTerrainRenderer_T3D.h"
+#include "afx/util/afxTriBoxCheck2D_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class TerrCellSpy : public TerrCell
+{
+public:
+  static const U32 getMinCellSize() { return smMinCellSize; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+const RenderInstType afxZodiacTerrainRenderer::RIT_TerrainZodiac("TerrainZodiac");
+
+afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::master = 0;
+
+IMPLEMENT_CONOBJECT(afxZodiacTerrainRenderer);
+
+ConsoleDocClass( afxZodiacTerrainRenderer, 
+   "@brief A render bin for zodiac rendering on Terrain objects.\n\n"
+
+   "This bin renders instances of AFX zodiac effects onto Terrain surfaces.\n\n"
+
+   "@ingroup RenderBin\n"
+   "@ingroup AFX\n"
+);
+
+afxZodiacTerrainRenderer::afxZodiacTerrainRenderer()
+: RenderBinManager(RIT_TerrainZodiac, 1.0f, 1.0f)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacTerrainRenderer::afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder)
+: RenderBinManager(RIT_TerrainZodiac, renderOrder, processAddOrder)
+{
+   if (!master)
+     master = this;
+   shader_initialized = false;
+}
+
+afxZodiacTerrainRenderer::~afxZodiacTerrainRenderer()
+{
+  if (this == master)
+    master = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxZodiacTerrainRenderer::initShader()
+{
+   if (shader_initialized)
+     return;
+
+   shader_initialized = true;
+
+   shader_consts = 0;
+   norm_norefl_zb_SB = norm_refl_zb_SB;
+   add_norefl_zb_SB = add_refl_zb_SB;
+   sub_norefl_zb_SB = sub_refl_zb_SB;
+
+   zodiac_shader = afxZodiacMgr::getTerrainZodiacShader();
+   if (!zodiac_shader)
+     return;
+
+   GFXStateBlockDesc d;
+
+   d.cullDefined = true;
+   d.ffLighting = false;
+   d.blendDefined = true;
+   d.blendEnable = true;
+   d.zDefined = false;
+   d.zEnable = true;
+   d.zWriteEnable = false;
+   d.zFunc = GFXCmpLessEqual;
+   d.zSlopeBias = 0;
+   d.alphaDefined = true;
+   d.alphaTestEnable = true; 
+   d.alphaTestRef = 0;
+   d.alphaTestFunc = GFXCmpGreater;
+   d.samplersDefined = true;
+   d.samplers[0] = GFXSamplerStateDesc::getClampLinear();
+
+   // normal
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendInvSrcAlpha;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   norm_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   norm_refl_zb_SB = GFX->createStateBlock(d);
+
+   // additive
+   d.blendSrc = GFXBlendSrcAlpha;
+   d.blendDest = GFXBlendOne;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   add_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   add_refl_zb_SB = GFX->createStateBlock(d);
+
+   // subtractive
+   d.blendSrc = GFXBlendZero;
+   d.blendDest = GFXBlendInvSrcColor;
+   //
+   d.cullMode = GFXCullCCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   sub_norefl_zb_SB = GFX->createStateBlock(d);
+   //
+   d.cullMode = GFXCullCW;
+   d.zBias = arcaneFX::sTerrainZodiacZBias;
+   sub_refl_zb_SB = GFX->createStateBlock(d);
+
+   shader_consts = zodiac_shader->getShader()->allocConstBuffer();
+   projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView");
+   color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor");
+}
+
+void afxZodiacTerrainRenderer::clear()
+{
+  Parent::clear();
+
+  terrain_zodes.clear();
+}
+
+void afxZodiacTerrainRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, 
+                                         const TerrainBlock* block, const TerrCell* cell, 
+                                         const MatrixF& mRenderObjToWorld, F32 camDist)
+{
+  terrain_zodes.increment();
+  TerrainZodiacElem& elem = terrain_zodes.last();
+
+  elem.block = block;
+  elem.cell = cell;
+  elem.zode_idx = zode_idx;
+  elem.ang = ang;
+  elem.mRenderObjToWorld = mRenderObjToWorld;
+  elem.camDist = camDist;
+}
+
+afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::getMaster()
+{
+  if (!master)
+    master = new afxZodiacTerrainRenderer;
+  return master;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+GFXStateBlock* afxZodiacTerrainRenderer::chooseStateBlock(U32 blend, bool isReflectPass)
+{
+  GFXStateBlock* sb = 0;
+  
+  switch (blend)
+  {
+  case afxZodiacData::BLEND_ADDITIVE:
+    sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB;
+    break;
+  case afxZodiacData::BLEND_SUBTRACTIVE:
+    sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB;
+    break;
+  default: // afxZodiacData::BLEND_NORMAL:
+    sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB;
+    break;
+  }
+
+  return sb;
+}
+
+void afxZodiacTerrainRenderer::render(SceneRenderState* state)
+{
+   PROFILE_SCOPE(afxRenderZodiacTerrainMgr_render);
+
+   // Early out if no terrain zodiacs to draw.
+   if (terrain_zodes.size() == 0)
+     return;
+
+   initShader();
+   if (!zodiac_shader)
+     return;
+
+   bool is_reflect_pass = state->isReflectPass();
+
+   // Automagically save & restore our viewport and transforms.
+   GFXTransformSaver saver;
+
+   MatrixF proj = GFX->getProjectionMatrix();
+
+   // Set up world transform
+   MatrixF world = GFX->getWorldMatrix();
+   proj.mul(world);
+   shader_consts->set(projection_sc, proj);
+
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+   // RENDER EACH ZODIAC
+   //
+   for (S32 zz = 0; zz < terrain_zodes.size(); zz++)
+   {
+      TerrainZodiacElem& elem = terrain_zodes[zz];
+
+      TerrainBlock* block = (TerrainBlock*) elem.block;
+
+      afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx];
+      if (!zode)
+         continue;
+
+      if (is_reflect_pass)
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0)
+            continue;
+      }
+      else
+      {
+         if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0)
+            continue;
+      }
+
+      F32 fadebias = zode->calcDistanceFadeBias(elem.camDist);
+      if (fadebias < 0.01f)
+        continue;
+
+      F32 cos_ang = mCos(elem.ang);
+      F32 sin_ang = mSin(elem.ang);
+
+      GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass);
+
+      GFX->setShader(zodiac_shader->getShader());
+      GFX->setStateBlock(sb);
+      GFX->setShaderConstBuffer(shader_consts);
+
+      // set the texture
+      GFX->setTexture(0, *zode->txr);
+      LinearColorF zode_color = (LinearColorF)zode->color;
+      zode_color.alpha *= fadebias;
+      shader_consts->set(color_sc, zode_color);
+
+      Point3F half_size(zode->radius_xy,zode->radius_xy,zode->radius_xy);
+
+      F32 inv_radius = 1.0f/zode->radius_xy;
+
+      GFXPrimitive cell_prim;
+      GFXVertexBufferHandle<TerrVertex> cell_verts;
+      GFXPrimitiveBufferHandle  primBuff;
+      elem.cell->getRenderPrimitive(&cell_prim, &cell_verts, &primBuff);
+
+      U32 n_nonskirt_tris = TerrCellSpy::getMinCellSize()*TerrCellSpy::getMinCellSize()*2;
+
+      const Point3F* verts = ((TerrCell*)elem.cell)->getZodiacVertexBuffer();
+      const U16 *tris = block->getZodiacPrimitiveBuffer();
+      if (!tris)
+         continue; 
+
+      PrimBuild::begin(GFXTriangleList, 3*n_nonskirt_tris);
+
+      /////////////////////////////////
+      U32 n_overlapping_tris = 0;
+      U32 idx = 0;
+      for (U32 i = 0; i < n_nonskirt_tris; i++)
+      {
+        Point3F tri_v[3];
+        tri_v[0] = verts[tris[idx++]];
+        tri_v[1] = verts[tris[idx++]];
+        tri_v[2] = verts[tris[idx++]];
+
+        elem.mRenderObjToWorld.mulP(tri_v[0]);
+        elem.mRenderObjToWorld.mulP(tri_v[1]);
+        elem.mRenderObjToWorld.mulP(tri_v[2]);
+
+        if (!afxTriBoxOverlap2D(zode->pos, half_size, tri_v[0], tri_v[1], tri_v[2]))
+          continue;
+
+        n_overlapping_tris++;
+
+        for (U32 j = 0; j < 3; j++)
+        {
+          // compute UV
+          F32 u1 = (tri_v[j].x - zode->pos.x)*inv_radius;
+          F32 v1 = (tri_v[j].y - zode->pos.y)*inv_radius;
+          F32 ru1 = u1*cos_ang - v1*sin_ang;
+          F32 rv1 = u1*sin_ang + v1*cos_ang;
+
+          F32 uu = (ru1 + 1.0f)/2.0f;
+          F32 vv = 1.0f - (rv1 + 1.0f)/2.0f;
+
+          PrimBuild::texCoord2f(uu, vv);
+          PrimBuild::vertex3fv(tri_v[j]);
+        }
+      }
+
+      /////////////////////////////////
+
+      PrimBuild::end(false);
+   }
+   //
+   // RENDER EACH ZODIAC
+   //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 92 - 0
Engine/source/afx/afxZodiacTerrainRenderer_T3D.h

@@ -0,0 +1,92 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ZODIAC_TERRAIN_RENDERER_H_
+#define _AFX_ZODIAC_TERRAIN_RENDERER_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#include "renderInstance/renderBinManager.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class TerrCell;
+
+class afxZodiacTerrainRenderer : public RenderBinManager
+{
+   typedef RenderBinManager Parent;
+
+   struct TerrainZodiacElem
+   {
+     const TerrainBlock* block;
+     const TerrCell*   cell;
+     U32               zode_idx;
+     F32               ang;
+     MatrixF           mRenderObjToWorld;
+     F32               camDist;
+   };
+
+   Vector<TerrainZodiacElem> terrain_zodes;
+   static afxZodiacTerrainRenderer* master;
+
+   GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB;
+   GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB;
+   GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB;
+
+   ShaderData*    zodiac_shader;
+   GFXShaderConstBufferRef shader_consts;
+   GFXShaderConstHandle* projection_sc;
+   GFXShaderConstHandle* color_sc;
+
+   bool           shader_initialized;
+
+   GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass);
+
+public:
+   static const RenderInstType RIT_TerrainZodiac;
+
+   /*C*/          afxZodiacTerrainRenderer();
+   /*C*/          afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder);
+   /*D*/          ~afxZodiacTerrainRenderer();
+
+   // RenderBinManager
+   virtual void   sort(){}  // don't sort them
+   virtual void   clear();
+
+   void           initShader();
+   void           addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const TerrainBlock*, const TerrCell*, const MatrixF& mRenderObjToWorld, F32 camDist);
+
+   virtual void   render(SceneRenderState*);
+
+   static afxZodiacTerrainRenderer* getMaster();
+
+   // ConsoleObject
+   DECLARE_CONOBJECT(afxZodiacTerrainRenderer);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ZODIAC_TERRAIN_RENDERER_H_

+ 959 - 0
Engine/source/afx/arcaneFX.cpp

@@ -0,0 +1,959 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "scene/sceneObject.h"
+#include "scene/sceneManager.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/gameBase/gameProcess.h"
+#include "T3D/player.h"
+#include "math/mathUtils.h"
+#include "console/compiler.h"
+#include "console/engineAPI.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/afxSelectron.h"
+#include "afx/afxResidueMgr.h"
+#include "afx/ce/afxZodiacMgr.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#define N_LIGHTING_MODELS 6
+//
+// "SG - Original Advanced (Lighting Pack)"
+// "SG - Original Stock (Lighting Pack)"
+// "SG - Inverse Square (Lighting Pack)"
+// "SG - Inverse Square Fast Falloff (Lighting Pack)"
+// "SG - Near Linear (Lighting Pack)"
+// "SG - Near Linear Fast Falloff (Lighting Pack)"
+static StringTableEntry lm_old_names[N_LIGHTING_MODELS];
+//
+// "Original Advanced"
+// "Original Stock"
+// "Inverse Square"
+// "Inverse Square Fast Falloff"
+// "Near Linear"
+// "Near Linear Fast Falloff"
+static StringTableEntry lm_new_names[N_LIGHTING_MODELS];
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class ClientZoneInEvent : public NetEvent
+{
+  typedef NetEvent Parent;
+public:
+  ClientZoneInEvent() { mGuaranteeType = Guaranteed; }
+  ~ClientZoneInEvent() { }
+
+  virtual void pack(NetConnection*, BitStream*bstream) { }
+  virtual void write(NetConnection*, BitStream *bstream) { }
+  virtual void unpack(NetConnection* /*ps*/, BitStream *bstream) { }
+
+  virtual void process(NetConnection* conn)
+  {
+    GameConnection* game_conn = dynamic_cast<GameConnection*>(conn);
+    if (game_conn && !game_conn->isZonedIn())
+    {
+      arcaneFX::syncToNewConnection(game_conn);
+    }
+  }
+
+  DECLARE_CONOBJECT(ClientZoneInEvent);
+  DECLARE_CATEGORY("AFX");
+};
+IMPLEMENT_CO_SERVEREVENT_V1(ClientZoneInEvent);
+
+ConsoleDocClass( ClientZoneInEvent,
+				"@brief Event posted when player is fully loaded into the game and ready for interaction.\n\n"
+				"@internal");
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+Vector<afxChoreographer*> arcaneFX::active_choreographers;
+Vector<afxChoreographer*> arcaneFX::client_choreographers;
+Vector<afxSelectronData*> arcaneFX::selectrons;
+Vector<SceneObject*>      arcaneFX::scoped_objs;
+
+StringTableEntry arcaneFX::NULLSTRING = 0;
+U32              arcaneFX::sTargetSelectionMask = 0;
+U32              arcaneFX::sFreeTargetSelectionMask = 0;
+bool             arcaneFX::sIsFreeTargeting = false;
+Point3F          arcaneFX::sFreeTargetPos = Point3F(0.0f, 0.0f, 0.0f);
+bool             arcaneFX::sFreeTargetPosValid = false;
+F32              arcaneFX::sTargetSelectionRange = 200.0f;
+U32              arcaneFX::sTargetSelectionTimeoutMS = 500;
+bool             arcaneFX::sClickToTargetSelf = false;
+U32              arcaneFX::sMissileCollisionMask = 0;
+StringTableEntry arcaneFX::sParameterFieldPrefix = 0;
+F32              arcaneFX::sTerrainZodiacZBias = -0.00025f;
+F32              arcaneFX::sInteriorZodiacZBias = -0.0001f;
+F32              arcaneFX::sPolysoupZodiacZBias = -0.0001f;
+U32              arcaneFX::master_choreographer_id = 1;
+U16              arcaneFX::master_scope_id = 1;
+bool             arcaneFX::is_shutdown = true;
+
+bool             arcaneFX::sUsePlayerCentricListener = false;
+
+void arcaneFX::init()
+{
+  NULLSTRING = StringTable->insert("");
+  sParameterFieldPrefix = StringTable->insert("_");
+
+#if defined(TORQUE_OS_MAC)
+  arcaneFX::sTerrainZodiacZBias = -0.00025f;
+  arcaneFX::sInteriorZodiacZBias = -0.00025f;
+  arcaneFX::sPolysoupZodiacZBias = -0.00025f;
+#endif
+
+  Con::addVariable(  "pref::AFX::targetSelectionMask",      TypeS32,    &sTargetSelectionMask);
+  Con::addVariable(  "pref::AFX::freeTargetSelectionMask",  TypeS32,    &sFreeTargetSelectionMask);
+  Con::addVariable(  "pref::AFX::targetSelectionRange",     TypeF32,    &sTargetSelectionRange);
+  Con::addVariable(  "pref::AFX::targetSelectionTimeoutMS", TypeS32,    &sTargetSelectionTimeoutMS);
+  Con::addVariable(  "pref::AFX::missileCollisionMask",     TypeS32,    &sMissileCollisionMask);
+  Con::addVariable(  "pref::AFX::clickToTargetSelf",        TypeBool,   &sClickToTargetSelf);
+  Con::addVariable(  "Pref::Server::AFX::parameterFieldPrefix",     TypeString,  &sParameterFieldPrefix);
+
+  Con::addVariable(  "pref::AFX::terrainZodiacZBias",       TypeF32,    &sTerrainZodiacZBias);
+  Con::addVariable(  "pref::AFX::interiorZodiacZBias",      TypeF32,    &sInteriorZodiacZBias);
+  Con::addVariable(  "pref::AFX::polysoupZodiacZBias",      TypeF32,    &sPolysoupZodiacZBias);
+
+  Con::setIntVariable(    "$AFX::TARGETING_OFF",                TARGETING_OFF);
+  Con::setIntVariable(    "$AFX::TARGETING_STANDARD",           TARGETING_STANDARD);
+  Con::setIntVariable(    "$AFX::TARGETING_FREE",               TARGETING_FREE);
+  Con::setIntVariable(    "$AFX::TARGET_CHECK_POLL",            TARGET_CHECK_POLL);
+  Con::setIntVariable(    "$AFX::TARGET_CHECK_ON_MOUSE_MOVE",   TARGET_CHECK_ON_MOUSE_MOVE);
+
+  Con::setIntVariable(    "$AFX::IMPACTED_SOMETHING", afxEffectDefs::IMPACTED_SOMETHING);
+  Con::setIntVariable(    "$AFX::IMPACTED_TARGET",    afxEffectDefs::IMPACTED_TARGET);
+  Con::setIntVariable(    "$AFX::IMPACTED_PRIMARY",   afxEffectDefs::IMPACTED_PRIMARY);
+  Con::setIntVariable(    "$AFX::IMPACT_IN_WATER",    afxEffectDefs::IMPACT_IN_WATER);
+  Con::setIntVariable(    "$AFX::CASTER_IN_WATER",    afxEffectDefs::CASTER_IN_WATER);
+
+  Con::setIntVariable(    "$AFX::SERVER_ONLY",        afxEffectDefs::SERVER_ONLY);
+  Con::setIntVariable(    "$AFX::SCOPE_ALWAYS",       afxEffectDefs::SCOPE_ALWAYS);
+  Con::setIntVariable(    "$AFX::GHOSTABLE",          afxEffectDefs::GHOSTABLE);
+  Con::setIntVariable(    "$AFX::CLIENT_ONLY",        afxEffectDefs::CLIENT_ONLY);
+  Con::setIntVariable(    "$AFX::SERVER_AND_CLIENT",  afxEffectDefs::SERVER_AND_CLIENT);
+
+  Con::setIntVariable(    "$AFX::DELAY",              afxEffectDefs::TIMING_DELAY);
+  Con::setIntVariable(    "$AFX::LIFETIME",           afxEffectDefs::TIMING_LIFETIME);
+  Con::setIntVariable(    "$AFX::FADE_IN_TIME",       afxEffectDefs::TIMING_FADE_IN);
+  Con::setIntVariable(    "$AFX::FADE_OUT_TIME",      afxEffectDefs::TIMING_FADE_OUT);
+
+  Con::setFloatVariable(  "$AFX::INFINITE_TIME",      -1.0f);
+  Con::setIntVariable(    "$AFX::INFINITE_REPEATS",   -1);
+
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_0",      Player::PLAYER_MOVE_TRIGGER_0);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_1",      Player::PLAYER_MOVE_TRIGGER_1);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_2",      Player::PLAYER_MOVE_TRIGGER_2);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_3",      Player::PLAYER_MOVE_TRIGGER_3);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_4",      Player::PLAYER_MOVE_TRIGGER_4);
+  Con::setIntVariable(    "$AFX::PLAYER_MOVE_TRIGGER_5",      Player::PLAYER_MOVE_TRIGGER_5);
+
+  Con::setIntVariable(    "$AFX::PLAYER_FIRE_S_TRIGGER",      Player::PLAYER_FIRE_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_FIRE_ALT_S_TRIGGER",  Player::PLAYER_FIRE_ALT_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_JUMP_S_TRIGGER",      Player::PLAYER_JUMP_S_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_LANDING_S_TRIGGER",   Player::PLAYER_LANDING_S_TRIGGER);
+
+  Con::setIntVariable(    "$AFX::PLAYER_LF_FOOT_C_TRIGGER",   Player::PLAYER_LF_FOOT_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_RT_FOOT_C_TRIGGER",   Player::PLAYER_RT_FOOT_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_LANDING_C_TRIGGER",   Player::PLAYER_LANDING_C_TRIGGER);
+  Con::setIntVariable(    "$AFX::PLAYER_IDLE_C_TRIGGER",      Player::PLAYER_IDLE_C_TRIGGER);
+
+  Con::setIntVariable(    "$AFX::ILLUM_TERRAIN",      0);
+  Con::setIntVariable(    "$AFX::ILLUM_ATLAS",        0);
+  Con::setIntVariable(    "$AFX::ILLUM_DIF",          0);
+  Con::setIntVariable(    "$AFX::ILLUM_DTS",          0);
+  Con::setIntVariable(    "$AFX::ILLUM_ALL",          0);
+
+  Con::setIntVariable("$TypeMasks::TerrainLikeObjectType", TerrainLikeObjectType);
+  Con::setIntVariable("$TypeMasks::InteriorLikeObjectType", InteriorLikeObjectType);
+  Con::setIntVariable("$TypeMasks::PolysoupObjectType", InteriorLikeObjectType); // deprecated
+
+  Con::addVariable("$pref::Audio::usePlayerCentricListener", TypeBool, &sUsePlayerCentricListener);
+
+  afxResidueMgr* residue_mgr = new afxResidueMgr;
+  afxResidueMgr::setMaster(residue_mgr);
+
+  master_scope_id = 1;
+  master_choreographer_id = 1;
+  is_shutdown = false;
+
+  if (lm_old_names[0] == 0)
+  {
+    lm_old_names[0] = StringTable->insert("SG - Original Advanced (Lighting Pack)");
+    lm_old_names[1] = StringTable->insert("SG - Original Stock (Lighting Pack)");
+    lm_old_names[2] = StringTable->insert("SG - Inverse Square (Lighting Pack)");
+    lm_old_names[3] = StringTable->insert("SG - Inverse Square Fast Falloff (Lighting Pack)");
+    lm_old_names[4] = StringTable->insert("SG - Near Linear (Lighting Pack)");
+    lm_old_names[5] = StringTable->insert("SG - Near Linear Fast Falloff (Lighting Pack)");
+    //
+    lm_new_names[0] = StringTable->insert("Original Advanced");
+    lm_new_names[1] = StringTable->insert("Original Stock");
+    lm_new_names[2] = StringTable->insert("Inverse Square");
+    lm_new_names[3] = StringTable->insert("Inverse Square Fast Falloff");
+    lm_new_names[4] = StringTable->insert("Near Linear");
+    lm_new_names[5] = StringTable->insert("Near Linear Fast Falloff");
+  }
+}
+
+void arcaneFX::shutdown()
+{
+  is_shutdown = true;
+
+  for (S32 i = 0; i < scoped_objs.size(); i++)
+     if (scoped_objs[i])
+       scoped_objs[i]->setScopeRegistered(false);
+  scoped_objs.clear();
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+     if (client_choreographers[i])
+       client_choreographers[i]->clearChoreographerId();
+  client_choreographers.clear();
+
+  for (S32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i])
+       selectrons[i]->registered = false;
+  selectrons.clear();
+
+  afxResidueMgr* residue_mgr = afxResidueMgr::getMaster();
+  delete residue_mgr;
+  afxResidueMgr::setMaster(NULL);
+}
+
+MODULE_BEGIN( arcaneFX )
+
+   MODULE_INIT
+   {
+      arcaneFX::init();
+   }
+
+   MODULE_SHUTDOWN
+   {
+      arcaneFX::shutdown();
+   }
+
+MODULE_END;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void arcaneFX::advanceTime(U32 delta)
+{
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  if (conn && !conn->isZonedIn() && conn->getCameraObject() != 0)
+  {
+    conn->setZonedIn();
+    conn->postNetEvent(new ClientZoneInEvent());
+  }
+
+  afxZodiacMgr::frameReset();
+  afxResidueMgr::getMaster()->residueAdvanceTime();
+}
+
+//
+
+U32 arcaneFX::registerChoreographer(afxChoreographer* ch)
+{
+  if (!ch)
+    return 0;
+
+  active_choreographers.push_back(ch);
+
+  //Con::printf("registerChoreographer() -- size=%d %s", active_choreographers.size(),
+  //  (ch->isServerObject()) ? "server" : "client");
+
+  return master_choreographer_id++;
+}
+
+void arcaneFX::unregisterChoreographer(afxChoreographer* ch)
+{
+  if (!ch)
+    return;
+
+  for (U32 i = 0; i < active_choreographers.size(); i++)
+  {
+    if (ch == active_choreographers[i])
+    {
+      active_choreographers.erase_fast(i);
+      //Con::printf("unregisterChoreographer() -- size=%d %s", active_choreographers.size(),
+      //  (ch->isServerObject()) ? "server" : "client");
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterChoreographer() -- failed to find choreographer in list.");
+}
+
+void arcaneFX::registerClientChoreographer(afxChoreographer* ch)
+{
+  if (!ch || ch->getChoreographerId() == 0)
+    return;
+
+  client_choreographers.push_back(ch);
+}
+
+void arcaneFX::unregisterClientChoreographer(afxChoreographer* ch)
+{
+  if (!ch || ch->getChoreographerId() == 0)
+    return;
+
+  for (U32 i = 0; i < client_choreographers.size(); i++)
+  {
+    if (ch == client_choreographers[i])
+    {
+      client_choreographers.erase_fast(i);
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterClientChoreographer() -- failed to find choreographer in list.");
+}
+
+afxChoreographer* arcaneFX::findClientChoreographer(U32 id)
+{
+  for (U32 i = 0; i < client_choreographers.size(); i++)
+  {
+    if (id == client_choreographers[i]->getChoreographerId())
+      return client_choreographers[i];
+  }
+
+  return 0;
+}
+
+//
+
+void arcaneFX::registerSelectronData(afxSelectronData* selectron)
+{
+  if (!selectron)
+    return;
+
+  selectrons.push_back(selectron);
+}
+
+void arcaneFX::unregisterSelectronData(afxSelectronData* selectron)
+{
+  if (!selectron)
+    return;
+
+  for (U32 i = 0; i < selectrons.size(); i++)
+  {
+    if (selectron == selectrons[i])
+    {
+      selectrons.erase_fast(i);
+      return;
+    }
+  }
+
+  Con::errorf("arcaneFX::unregisterSelectronData() -- failed to find selectron in list.");
+}
+
+afxSelectronData* arcaneFX::findSelectronData(U32 mask, U8 style)
+{
+  for (U32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i]->matches(mask, style))
+      return selectrons[i];
+
+  return 0;
+}
+
+U16 arcaneFX::generateScopeId()
+{
+  U16 ret_id = master_scope_id++;
+  if (master_scope_id >= BIT(GameBase::SCOPE_ID_BITS))
+    master_scope_id = 1;
+  return ret_id;
+}
+
+void arcaneFX::registerScopedObject(SceneObject* object)
+{
+  scoped_objs.push_back(object);
+  object->setScopeRegistered(true);
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+    if (client_choreographers[i])
+      client_choreographers[i]->restoreScopedObject(object);
+}
+
+SceneObject* arcaneFX::findScopedObject(U16 scope_id)
+{
+  if (scoped_objs.size() > 0)
+  {
+    for (S32 i = scoped_objs.size()-1; i >= 0; i--)
+      if (scoped_objs[i] && scoped_objs[i]->getScopeId() == scope_id)
+        return scoped_objs[i];
+  }
+  return 0;
+}
+
+void arcaneFX::unregisterScopedObject(SceneObject* object)
+{
+  if (scoped_objs.size() > 0)
+  {
+    for (S32 i = scoped_objs.size()-1; i >= 0; i--)
+      if (scoped_objs[i] == object)
+      {
+        scoped_objs.erase_fast(i);
+        if (object)
+          object->setScopeRegistered(false);
+        return;
+      }
+  }
+}
+
+void arcaneFX::syncToNewConnection(GameConnection* conn)
+{
+  if (conn)
+    conn->setZonedIn();
+
+  for (U32 i = 0; i < active_choreographers.size(); i++)
+  {
+    if (active_choreographers[i])
+      active_choreographers[i]->sync_with_clients();
+  }
+}
+
+void arcaneFX::endMissionNotify()
+{
+  for (S32 i = 0; i < scoped_objs.size(); i++)
+     if (scoped_objs[i])
+       scoped_objs[i]->setScopeRegistered(false);
+  scoped_objs.clear();
+
+  for (S32 i = 0; i < client_choreographers.size(); i++)
+     if (client_choreographers[i])
+       client_choreographers[i]->clearChoreographerId();
+  client_choreographers.clear();
+
+  for (S32 i = 0; i < selectrons.size(); i++)
+    if (selectrons[i])
+       selectrons[i]->registered = false;
+  selectrons.clear();
+
+  if (afxResidueMgr::getMaster())
+    afxResidueMgr::getMaster()->cleanup();
+  afxZodiacMgr::missionCleanup();
+}
+
+S32 arcaneFX::rolloverRayCast(Point3F start, Point3F end, U32 mask)
+{
+  sIsFreeTargeting = false;
+#if !defined(AFX_CAP_ROLLOVER_RAYCASTS)
+  return -1;
+#else
+  GameConnection* conn = GameConnection::getConnectionToServer();
+  SceneObject* ctrl_obj = NULL;
+
+  if (!arcaneFX::sClickToTargetSelf && conn != NULL)
+    ctrl_obj = conn->getControlObject();
+
+  if (ctrl_obj)
+    ctrl_obj->disableCollision();
+
+  SceneObject* rollover_obj = (conn) ? conn->getRolloverObj() : 0;
+  SceneObject* picked_obj = 0;
+
+  RayInfo hit_info;
+  if (gClientContainer.castRay(start, end, mask, &hit_info))
+    picked_obj = dynamic_cast<SceneObject*>(hit_info.object);
+
+  if (ctrl_obj)
+    ctrl_obj->enableCollision();
+
+  if (picked_obj != rollover_obj)
+  {
+    if (rollover_obj)
+      rollover_obj->setSelectionFlags(rollover_obj->getSelectionFlags() & ~SceneObject::PRE_SELECTED);
+    if (picked_obj)
+      picked_obj->setSelectionFlags(picked_obj->getSelectionFlags() | SceneObject::PRE_SELECTED);
+    rollover_obj = picked_obj;
+
+    if (conn)
+      conn->setRolloverObj(rollover_obj);
+  }
+
+  return (picked_obj) ? picked_obj->getId() : -1;
+#endif
+}
+
+bool arcaneFX::freeTargetingRayCast(Point3F start, Point3F end, U32 mask)
+{
+  sIsFreeTargeting = true;
+
+  RayInfo hit_info;
+  if (!gClientContainer.castRay(start, end, mask, &hit_info))
+  {
+    sFreeTargetPosValid = false;
+    return false;
+  }
+
+  sFreeTargetPosValid = true;
+  sFreeTargetPos = hit_info.point;
+
+  return true;
+}
+
+StringTableEntry arcaneFX::convertLightingModelName(StringTableEntry lm_name)
+{
+  for (U32 i = 0; i < N_LIGHTING_MODELS; i++)
+  {
+    if (lm_name == lm_old_names[i])
+      return lm_new_names[i];
+  }
+
+  return lm_name;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// Console Functions
+
+DefineEngineFunction(afxEndMissionNotify, void, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  arcaneFX::endMissionNotify();
+}
+
+DefineEngineFunction(afxGetVersion, const char*, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  return AFX_VERSION_STRING;
+}
+
+DefineEngineFunction(afxGetEngine, const char*, (),,
+                     "...\n\n"
+                     "@ingroup AFX")
+{
+  return "T3D";
+}
+
+#if defined(AFX_CAP_ROLLOVER_RAYCASTS)
+DefineEngineFunction(rolloverRayCast, S32, (Point3F start, Point3F end, U32 mask),,
+                     "Performs a raycast from points start to end and returns the ID of nearest "
+                     "intersecting object with a type found in the specified mask. "
+                     "Returns -1 if no object is found.\n\n"
+                     "@ingroup AFX")
+{
+  return arcaneFX::rolloverRayCast(start, end, mask);
+}
+#endif
+
+DefineEngineFunction(getRandomF, F32, (float a, float b), (F32_MAX, F32_MAX),
+                "Get a random float number between a and b.\n\n"
+                "@ingroup AFX")
+{
+  if (b == F32_MAX)
+  {
+    if (a == F32_MAX)
+      return gRandGen.randF();
+
+    return gRandGen.randF(0.0f, a);
+  }
+
+  return (a + (b-a)*gRandGen.randF());
+}
+
+DefineEngineFunction(getRandomDir, Point3F, (Point3F axis, float thetaMin, float thetaMax, float phiMin, float phiMax),
+                     (Point3F(0.0f,0.0f,0.0f), 0.0f, 180.0f, 0.0f, 360.0f),
+                     "Get a random direction vector.\n\n"
+                     "@ingroup AFX")
+{
+  return MathUtils::randomDir(axis, thetaMin, thetaMax, phiMin, phiMax);
+}
+
+ConsoleFunction( MatrixInverseMulVector, const char*, 3, 3, "(MatrixF xfrm, Point3F vector)"
+                "@brief Multiply the vector by the affine inverse of the transform.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   MatrixF temp1(true);
+   aa1.setMatrix(&temp1);
+   temp1.setColumn(3, pos1);
+
+   Point3F vec1(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &vec1.x, &vec1.y, &vec1.z);
+
+   temp1.affineInverse();
+
+   Point3F result;
+   temp1.mulV(vec1, &result);
+
+   char* ret = Con::getReturnBuffer(256);
+   dSprintf(ret, 255, "%g %g %g", result.x, result.y, result.z);
+   return ret;
+}
+
+ConsoleFunction(moveTransformAbs, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)"
+                "@brief Move the transform to the new absolute position.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   Point3F pos2(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z);
+
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g",
+            pos2.x, pos2.y, pos2.z,
+            aa1.axis.x, aa1.axis.y, aa1.axis.z,
+            aa1.angle);
+   return returnBuffer;
+}
+
+ConsoleFunction(moveTransformRel, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)"
+                "@brief Move the transform to the new relative position.\n\n"
+                "@ingroup AFX")
+{
+   Point3F pos1(0.0f,0.0f,0.0f);
+   AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f);
+   dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle);
+
+   Point3F pos2(0.0f,0.0f,0.0f);
+   dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z);
+
+   pos2 += pos1;
+
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g",
+            pos2.x, pos2.y, pos2.z,
+            aa1.axis.x, aa1.axis.y, aa1.axis.z,
+            aa1.angle);
+   return returnBuffer;
+}
+
+DefineEngineFunction(getFreeTargetPosition, Point3F, (),,
+                     "@brief Returns the current location of the free target.\n\n"
+                     "@ingroup AFX")
+{
+  if (!arcaneFX::sFreeTargetPosValid)
+    return Point3F(0.0f, 0.0f, 0.0f);
+  return arcaneFX::sFreeTargetPos;
+}
+
+DefineEngineMethod(SceneObject, getSpeed, F32, (),,
+                   "Returns the velocity of a scene-object.\n\n"
+                   "@ingroup AFX")
+{
+   return object->getVelocity().len();
+}
+
+static S32 mark_modkey = -1;
+
+DefineEngineFunction(markDataBlocks, void, (),,
+                     "@brief Called before a series of datablocks are reloaded to "
+                     "help distinguish reloaded datablocks from already loaded ones.\n\n"
+                     "@ingroup AFX")
+{
+  mark_modkey = SimDataBlock::getNextModifiedKey();
+}
+
+DefineEngineFunction(touchDataBlocks, void, (),,
+                     "@brief Called after a series of datablocks are reloaded to "
+                     "trigger some important actions on the reloaded datablocks.\n\n"
+                     "@ingroup AFX")
+{
+  if (mark_modkey < 0)
+    return;
+
+  SimDataBlockGroup* g = Sim::getDataBlockGroup();
+
+  U32 groupCount = g->size();
+  for (S32 i = groupCount-1; i >= 0; i--)
+  {
+    SimDataBlock* simdb = (SimDataBlock*)(*g)[i];
+    if (simdb->getModifiedKey() > mark_modkey)
+    {
+      simdb->unregisterObject();
+      simdb->registerObject();
+    }
+  }
+
+  mark_modkey = -1;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// Syntax Error Checking
+// (for checking eval() and compile() calls)
+
+DefineEngineFunction(wasSyntaxError, bool, (),,
+                     "@brief Returns true if script compiler had a syntax error. Useful "
+                     "for detecting syntax errors after reloading a script.\n\n"
+                     "@ingroup AFX")
+{
+  return Compiler::gSyntaxError;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+// Network Object Identification
+
+//  These useful console methods come from the following code resource:
+//
+//  How to Identify Objects from Client to Server or Server to Client by Nathan Davies
+//    http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4852
+//
+
+DefineEngineMethod(NetConnection, GetGhostIndex, S32, (NetObject* obj),,
+                   "Returns the ghost-index for an object.\n\n"
+                   "@ingroup AFX")
+{
+  if (obj)
+    return object->getGhostIndex(obj);
+  return 0;
+}
+
+DefineEngineMethod(NetConnection, ResolveGhost, S32, (int ghostIndex),,
+                   "Resolves a ghost-index into an object ID.\n\n"
+                   "@ingroup AFX")
+{
+  if (ghostIndex != -1)
+  {
+    NetObject* pObject = NULL;
+    if( object->isGhostingTo())
+      pObject = object->resolveGhost(ghostIndex);
+    else if( object->isGhostingFrom())
+      pObject = object->resolveObjectFromGhostIndex(ghostIndex);
+    if (pObject)
+      return pObject->getId();
+  }
+  return 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+//////////////////////////////////////////////////////////////////////////
+// TypeByteRange
+//////////////////////////////////////////////////////////////////////////
+
+IMPLEMENT_STRUCT( ByteRange, ByteRange,,
+   "" )
+END_IMPLEMENT_STRUCT;
+
+ConsoleType( ByteRange, TypeByteRange, ByteRange, "")
+ConsoleType( ByteRange, TypeByteRange2, ByteRange, "")
+
+ConsoleGetType( TypeByteRange )
+{
+   ByteRange* pt = (ByteRange *) dptr;
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high);
+   return returnBuffer;
+}
+
+ConsoleSetType( TypeByteRange )
+{
+  if(argc == 1)
+  {
+    ByteRange* range = (ByteRange*) dptr;
+    U32 lo, hi;
+    S32 args = dSscanf(argv[0], "%u %u", &lo, &hi);
+    range->low = (args > 0) ? lo : 0;
+    range->high = (args > 1) ? hi : 255;
+  }
+  else
+    Con::printf("ByteRange must be set as \"low\" or \"low high\"");
+}
+
+ConsoleGetType( TypeByteRange2 )
+{
+   ByteRange* pt = (ByteRange *) dptr;
+   char* returnBuffer = Con::getReturnBuffer(256);
+   dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high);
+   return returnBuffer;
+}
+
+ConsoleSetType( TypeByteRange2 )
+{
+  if(argc == 1)
+  {
+    ByteRange* range = (ByteRange*) dptr;
+    U32 lo, hi;
+    S32 args = dSscanf(argv[0], "%u %u", &lo, &hi);
+    range->low = (args > 0) ? lo : 0;
+    range->high = (args > 1) ? hi : lo;
+  }
+  else
+    Con::printf("ByteRange must be set as \"low\" or \"low high\"");
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+static void HSVtoRGB(F32 h, F32 s, F32 v, F32& r, F32& g, F32& b)
+{
+  h = mFmod(h, 360.0f);
+
+  if (v == 0.0f)
+    r = g = b = 0.0f;
+  else if (s == 0.0f)
+    r = g = b = v;
+  else
+  {
+    F32 hf = h/60.0f;
+    S32 i = (S32) mFloor(hf);
+    F32 f = hf - i;
+
+    F32 pv = v*(1.0f - s);
+    F32 qv = v*(1.0f - s*f);
+    F32 tv = v*(1.0f - s*(1.0f - f));
+
+    switch (i)
+    {
+    case 0:
+      r = v;  g = tv; b = pv;
+      break;
+    case 1:
+      r = qv; g = v;  b = pv;
+      break;
+    case 2:
+      r = pv; g = v;  b = tv;
+      break;
+    case 3:
+      r = pv; g = qv; b = v;
+      break;
+    case 4:
+      r = tv; g = pv; b = v;
+      break;
+    case 5:
+      r = v;  g = pv; b = qv;
+      break;
+    default:
+      r = g = b = 0.0f;
+      break;
+    }
+  }
+}
+
+DefineEngineFunction(getColorFromHSV, const char*, (float hue, float sat, float val, float alpha), (0.0, 0.0, 1.0, 1.0),
+                     "Coverts an HSV formatted color into an RBG color.\n\n"
+                     "@param hue The hue of the color (0-360).\n"
+                     "@param sat The saturation of the color (0-1).\n"
+                     "@param val The value of the color (0-1).\n"
+                     "@param alpha The alpha of the color (0-1).\n"
+                     "@ingroup AFX")
+{
+  LinearColorF rgb;
+  HSVtoRGB(hue, sat, val, rgb.red, rgb.green, rgb.blue);
+  rgb.alpha = alpha;
+
+  char* returnBuffer = Con::getReturnBuffer(256);
+  dSprintf(returnBuffer, 256, "%g %g %g %g", rgb.red, rgb.green, rgb.blue, rgb.alpha);
+
+  return returnBuffer;
+}
+
+DefineEngineFunction(ColorScale, const char*, ( LinearColorF color, float scalar ),,
+                     "Returns color scaled by scalar (color*scalar).\n\n"
+                     "@param color The color to be scaled.\n"
+                     "@param scalar The amount to scale the color.\n"
+                     "@ingroup AFX")
+{
+  color *= scalar;
+
+  char* returnBuffer = Con::getReturnBuffer(256);
+  dSprintf(returnBuffer, 256, "%g %g %g %g", color.red, color.green, color.blue, color.alpha);
+
+  return returnBuffer;
+}
+
+DefineEngineFunction(getMinF, F32, (float a, float b),,
+                     "Returns the lesser of the two arguments.\n\n"
+                     "@ingroup AFX")
+{
+   return getMin(a, b);
+}
+
+DefineEngineFunction(getMaxF, F32, (float a, float b),,
+                     "Returns the greater of the two arguments.\n\n"
+                     "@ingroup AFX")
+{
+   return getMax(a, b);
+}
+
+ConsoleFunction(echoThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like echo(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::printf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+ConsoleFunction(warnThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like warn(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::warnf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+ConsoleFunction(errorThru, const char*, 2, 0, "(string passthru, string text...)"
+                "Like error(), but first argument is returned.\n"
+                "@ingroup AFX")
+{
+   U32 len = 0;
+   S32 i;
+   for(i = 2; i < argc; i++)
+      len += dStrlen(argv[i]);
+
+   char *ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for(i = 2; i < argc; i++)
+      dStrcat(ret, argv[i]);
+
+   Con::errorf("%s -- [%s]", ret, argv[1].getStringValue());
+   ret[0] = 0;
+
+   return argv[1];
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 214 - 0
Engine/source/afx/arcaneFX.h

@@ -0,0 +1,214 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _ARCANE_FX_H_
+#define _ARCANE_FX_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _PLATFORM_H_
+#include "platform/platform.h"
+#endif
+
+#define AFX_VERSION_STRING "2.0"
+#define AFX_VERSION         2.0
+
+// #define AFX_CUSTOMIZED_BRANCH
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+#if defined(AFX_CUSTOMIZED_BRANCH)
+
+#elif (TORQUE_GAME_ENGINE == 1100 || TORQUE_GAME_ENGINE >= 3000)
+
+#define AFX_CAP_SCOPE_TRACKING
+#define AFX_CAP_ROLLOVER_RAYCASTS
+//#define AFX_CAP_AFXMODEL_TYPE
+//#define BROKEN_POINT_IN_WATER
+#define BROKEN_DAMAGEFLASH_WHITEOUT_BLACKOUT
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+#else
+
+// This version of AFX source only supports T3D 1.1
+
+#endif
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _CONSOLETYPES_H_
+#include "console/consoleTypes.h"
+#endif
+
+#ifndef _ENGINEAPI_H_
+#include "console/engineAPI.h"
+#endif
+
+#ifndef _SIMBASE_H_
+#include "console/simBase.h"
+#endif
+
+#ifndef _BITSTREAM_H_
+#include "core/stream/bitStream.h"
+#endif
+
+#ifndef _GAMEBASE_H_
+#include "T3D/gameBase/gameBase.h"
+#endif
+
+#if defined(DGL_GRAPHICS_LAYER)
+#ifndef _DGL_H_
+#include "dgl/dgl.h"
+#endif
+#endif
+
+class afxChoreographer;
+class afxSelectronData;
+class GameConnection;
+class SceneObject;
+
+class arcaneFX
+{
+public:
+  enum {
+    TARGETING_OFF,
+    TARGETING_STANDARD,
+    TARGETING_FREE
+  };
+  enum {
+    TARGET_CHECK_POLL,
+    TARGET_CHECK_ON_MOUSE_MOVE
+  };
+
+private:
+  static Vector<afxChoreographer*> active_choreographers;
+  static Vector<afxChoreographer*> client_choreographers;
+  static Vector<afxSelectronData*> selectrons;
+  static Vector<SceneObject*>      scoped_objs;
+  static bool                      is_shutdown;
+
+public:
+  static StringTableEntry   NULLSTRING;
+  static U32                sTargetSelectionMask;
+  static U32                sFreeTargetSelectionMask;
+  static bool               sIsFreeTargeting;
+  static Point3F            sFreeTargetPos;
+  static bool               sFreeTargetPosValid;
+  static F32                sTargetSelectionRange;
+  static U32                sTargetSelectionTimeoutMS;
+  static bool               sClickToTargetSelf;
+  static U32                sMissileCollisionMask;
+  static StringTableEntry   sParameterFieldPrefix;
+  static F32                sTerrainZodiacZBias;
+  static F32                sInteriorZodiacZBias;
+  static F32                sPolysoupZodiacZBias;
+  static U32                master_choreographer_id;
+  static U16                master_scope_id;
+
+public:
+  static void init();
+  static void shutdown();
+  static void advanceTime(U32 delta);
+
+  static U32  registerChoreographer(afxChoreographer*);
+  static void unregisterChoreographer(afxChoreographer*);
+  static void registerClientChoreographer(afxChoreographer*);
+  static void unregisterClientChoreographer(afxChoreographer*);
+  static afxChoreographer* findClientChoreographer(U32 id);
+
+  static void registerSelectronData(afxSelectronData*);
+  static void unregisterSelectronData(afxSelectronData*);
+  static afxSelectronData* findSelectronData(U32 obj_type_mask, U8 code);
+
+  static U16            generateScopeId();
+  static void           registerScopedObject(SceneObject*);
+  static SceneObject*   findScopedObject(U16 scope_id);
+  static void           unregisterScopedObject(SceneObject*);
+
+  static void syncToNewConnection(GameConnection* conn);
+  static void endMissionNotify();
+  static S32  rolloverRayCast(Point3F start, Point3F end, U32 mask);
+  static bool freeTargetingRayCast(Point3F start, Point3F end, U32 mask);
+
+  static bool isShutdown() { return is_shutdown; }
+  static StringTableEntry convertLightingModelName(StringTableEntry lm_name);
+
+private:
+  static bool sUsePlayerCentricListener;
+public:
+  static bool usePlayerCentricListener() { return sUsePlayerCentricListener; }
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+class ByteRange
+{
+public:
+  U8     low;
+  U8     high;
+
+public:
+  /*C*/  ByteRange() { low = 0; high = 255; }
+  /*C*/  ByteRange(U8 l, U8 h=255) { low = l; high = h; }
+
+  void   set(U8 l, U8 h=255) { low = l; high = h; }
+  bool   outOfRange(U8 v) { return (v < low || v > high); }
+  bool   inRange(U8 v) { return !outOfRange(v); }
+  S32    getSpan() const { return high - low; }
+};
+
+DefineConsoleType(TypeByteRange, ByteRange)
+DefineConsoleType(TypeByteRange2, ByteRange)
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+inline void writeDatablockID(BitStream* s, SimObject* simobj, bool packed=false)
+{
+  if (s->writeFlag(simobj))
+    s->writeRangedU32(packed ? SimObjectId((uintptr_t)simobj) : simobj->getId(),
+                      DataBlockObjectIdFirst, DataBlockObjectIdLast);
+}
+
+inline S32 readDatablockID(BitStream* s)
+{
+  return (!s->readFlag()) ? 0 : ((S32)s->readRangedU32(DataBlockObjectIdFirst,
+          DataBlockObjectIdLast));
+}
+
+inline void registerForCleanup(SimObject* obj)
+{
+  SimGroup* cleanup_grp = dynamic_cast<SimGroup*>(Sim::findObject("MissionCleanup"));
+  if (cleanup_grp)
+    cleanup_grp->addObject(obj);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+#define ST_NULLSTRING (arcaneFX::NULLSTRING)
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _ARCANE_FX_H_
+

+ 217 - 0
Engine/source/afx/ce/afxAnimClip.cpp

@@ -0,0 +1,217 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxAnimClip.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAnimClipData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAnimClipData);
+
+ConsoleDocClass( afxAnimClipData,
+   "@brief A datablock that specifies an Animation Clip effect.\n\n"
+
+   "An Animation Clip forces a target ShapeBase-derived object, such as Player or AIPlayer, to perform a particular "
+   "animation sequence. Animation Clip does not supply any new animation data, but simply selects, by name, a "
+   "sequence that is already defined in the target. Animation Clip can also target afxModel effects within the same "
+   "choreographer."
+   "\n\n"
+
+   "The target of an Animation Clip is the constraint source object specified by the posConstraint field of the enclosing "
+   "effect wrapper. The target must be a ShapeBase-derived object, or an afxModel and it must contain an animation "
+   "sequence with the same name as the clipName field."
+   "\n\n"
+
+   "Animation Clip controls the rate of animation playback and can even play a sequence in reverse. When an Animation "
+   "Clip selects a blended animation sequence, it is mixed with the current animation instead of replacing it. Animation "
+   "Clips can be used to activate multiple, overlapping blend sequences."
+   "\n\n"
+
+   "Normally when an Animation Clip is applied to a user-controlled Player, any interactive user actions will override the "
+   "animation selected by the clip, but Animation Clips can be configured to temporarily block out some user actions for "
+   "the duration of the clip."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAnimClipData::afxAnimClipData()
+{
+  clip_name = ST_NULLSTRING;
+  rate = 1.0f;
+  pos_offset = 0.0;
+  trans = 0.12f;
+  flags = 0;
+
+  ignore_disabled = false;
+  ignore_enabled = false;
+  is_death_anim = false;
+  lock_anim = false;
+  ignore_first_person = false;
+  ignore_third_person = false;
+}
+
+afxAnimClipData::afxAnimClipData(const afxAnimClipData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  clip_name = other.clip_name;
+  rate = other.rate;
+  pos_offset = other.pos_offset;
+  trans = other.trans;
+  flags = other.flags;
+
+  expand_flags();
+}
+
+void afxAnimClipData::onStaticModified(const char* slot, const char* newValue)
+{
+  Parent::onStaticModified(slot, newValue);
+  merge_flags();
+}
+
+#define myOffset(field) Offset(field, afxAnimClipData)
+
+void afxAnimClipData::initPersistFields()
+{
+  addField("clipName",          TYPEID< StringTableEntry >(),  myOffset(clip_name),
+    "The name of an animation sequence to be played by a ShapeBase-derived object to which this effect is "
+    "constrained. Also works on afxModel effects.\n"
+    "default: \"\"\n");
+  addField("rate",              TYPEID< F32 >(),               myOffset(rate),                  
+    "The desired playback speed for the sequence. A value of 1.0 indicates forward playback at a normal rate. Negative "
+    "values cause the sequence to play backwards.\n"
+    "default: 1.0\n");
+  addField("posOffset",         TYPEID< F32 >(),               myOffset(pos_offset),
+    "Sets a starting offset for the selected animation clip. It directly specifies an animation thread position in the 0.0 to "
+    "1.0 range as a fraction of the clip's duration.\n"
+    "default: 1.0\n");
+  addField("transitionTime",    TYPEID< F32 >(),               myOffset(trans),
+    "The duration in which the active animation overlaps and blends into the sequence selected by the animation clip.\n"
+    "default: 0.12\n");
+  addField("ignoreCorpse",      TYPEID< bool >(),              myOffset(ignore_disabled),
+    "Specifies if the animation clip should not be applied to corpses or anything else with a disabled damage state.\n"
+    "default: false\n");
+  addField("ignoreLiving",      TYPEID< bool >(),              myOffset(ignore_enabled),
+    "Specifies if the animation clip should not be applied to living objects or anything else with an enabled damage "
+    "state.\n"
+    "default: false\n");
+  addField("treatAsDeathAnim",  TYPEID< bool >(),              myOffset(is_death_anim),
+    "Indicates if the animation clip is a death animation. If the target object dies during the effect, this will prevent "
+    "the object from playing another standard death animation after this clip finishes.\n"
+    "default: false\n");
+  addField("lockAnimation",     TYPEID< bool >(),              myOffset(lock_anim),
+    "Indicates if user control of a Player should be temporarily blocked during the clip. (See afxAnimLockData.)\n"
+    "default: false\n");
+  addField("ignoreFirstPerson", TYPEID< bool >(),              myOffset(ignore_first_person),   
+    "If true, the clip will not be played on targets that are the control object and the camera is in first person mode.\n"
+    "default: false\n");
+  addField("ignoreThirdPerson", TYPEID< bool >(),              myOffset(ignore_third_person),   
+    "If true, the clip will not be played on targets that are the control object and the camera is in third person mode.\n"
+    "default: false\n");
+
+  // synonyms
+  addField("ignoreDisabled",    TYPEID< bool >(),              myOffset(ignore_disabled),
+    "A synonym for ignoreLiving.");
+  addField("ignoreEnabled",     TYPEID< bool >(),              myOffset(ignore_enabled),
+    "A synonym for ignoreCorpse.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAnimClipData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAnimClipData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  merge_flags();
+
+  stream->writeString(clip_name);
+  stream->write(rate);
+  stream->write(pos_offset);
+  stream->write(trans);
+  stream->write(flags);
+}
+
+void afxAnimClipData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  clip_name = stream->readSTString();
+  stream->read(&rate);
+  stream->read(&pos_offset);
+  stream->read(&trans);
+  stream->read(&flags);
+
+  expand_flags();
+}
+
+bool afxAnimClipData::writeField(StringTableEntry fieldname, const char* value)
+{
+   if (!Parent::writeField(fieldname, value))
+      return false;
+
+   // don't write the synonyms
+   if( fieldname == StringTable->insert("ignoreDisabled") )
+      return false;
+   if( fieldname == StringTable->insert("ignoreEnabled") )
+      return false;
+
+   return true;
+}
+
+void afxAnimClipData::expand_flags()
+{
+  ignore_disabled = ((flags & IGNORE_DISABLED) != 0);
+  ignore_enabled = ((flags & IGNORE_ENABLED) != 0);
+  lock_anim = ((flags & BLOCK_USER_CONTROL) != 0);
+  is_death_anim = ((flags & IS_DEATH_ANIM) != 0);
+  ignore_first_person = ((flags & IGNORE_FIRST_PERSON) != 0);
+  ignore_third_person = ((flags & IGNORE_THIRD_PERSON) != 0);
+}
+
+void afxAnimClipData::merge_flags()
+{
+  flags = (((ignore_disabled) ? IGNORE_DISABLED : 0) | 
+           ((ignore_enabled) ? IGNORE_ENABLED : 0) | 
+           ((lock_anim) ? BLOCK_USER_CONTROL : 0) | 
+           ((ignore_first_person) ? IGNORE_FIRST_PERSON : 0) |
+           ((ignore_third_person) ? IGNORE_THIRD_PERSON : 0) |
+           ((is_death_anim) ? IS_DEATH_ANIM : 0));
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 81 - 0
Engine/source/afx/ce/afxAnimClip.h

@@ -0,0 +1,81 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ANIM_CLIP_H_
+#define _AFX_ANIM_CLIP_H_
+
+class afxAnimClipData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+  enum 
+  {
+    IGNORE_DISABLED     = BIT(0),
+    IGNORE_ENABLED      = BIT(1),
+    IS_DEATH_ANIM       = BIT(2),
+    BLOCK_USER_CONTROL  = BIT(3),
+    IGNORE_FIRST_PERSON = BIT(4),
+    IGNORE_THIRD_PERSON = BIT(5)
+  };
+
+public:
+  StringTableEntry      clip_name;
+  F32                   rate;
+  F32                   pos_offset;
+  F32                   trans;
+  U8                    flags;
+
+  bool                  ignore_disabled;
+  bool                  ignore_enabled;
+  bool                  is_death_anim;
+  bool                  lock_anim;
+  bool                  ignore_first_person;
+  bool                  ignore_third_person;
+
+  void                  expand_flags();
+  void                  merge_flags();
+
+public:
+  /*C*/                 afxAnimClipData();
+  /*C*/                 afxAnimClipData(const afxAnimClipData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+  virtual bool          writeField(StringTableEntry fieldname, const char* value);
+
+  virtual void          onStaticModified(const char* slotName, const char* newValue = NULL);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxAnimClipData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ANIM_CLIP_H_

+ 95 - 0
Engine/source/afx/ce/afxAnimLock.cpp

@@ -0,0 +1,95 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxAnimLock.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAnimLockData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAnimLockData);
+
+ConsoleDocClass( afxAnimLockData,
+   "@brief A datablock that specifies an Animation Lock effect.\n\n"
+
+   "Animation Lock is used to temporarily lock out user-controlled Player actions, usually while an Animation Clip is "
+   "concurrently playing. Animation Clips can already do this, but must lock out user actions for the entire clip length. "
+   "Sometimes you only want to block user actions for a short section of a longer playing animation, such as the part where "
+   "the Player is thrown into the air from an impact. With Animation Lock, you can set a specific timespan for when user "
+   "actions are blocked, independent of any Animation Clip timing."
+   "\n\n"
+
+   "The target of an Animation Lock is the constraint source object specified by the posConstraint field of the enclosing effect "
+   "wrapper. The target must be a Player, a subclass of Player, or an afxModel."
+   "\n\n"
+
+   "The timing of the Animation Lock is determined by the timing fields of the enclosing effect wrapper."
+   "\n\n"
+
+   "Locking behavior timing is set by fields of the enclosing effect wrapper, so afxAnimLockData does not require any fields. "
+   "However, TorqueScript syntax disallows the declaration of an empty datablock. Therefore, it is recommended that you set "
+   "a dynamic field named 'priority' to zero in the body of the datablock as a workaround to this limitation."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAnimLockData::afxAnimLockData()
+{
+}
+
+#define myOffset(field) Offset(field, afxAnimLockData)
+
+void afxAnimLockData::initPersistFields()
+{
+  Parent::initPersistFields();
+}
+
+bool afxAnimLockData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAnimLockData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+}
+
+void afxAnimLockData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 48 - 0
Engine/source/afx/ce/afxAnimLock.h

@@ -0,0 +1,48 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_ANIM_LOCK_H_
+#define _AFX_ANIM_LOCK_H_
+
+class afxAnimLockData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  /*C*/                 afxAnimLockData();
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxAnimLockData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_ANIM_LOCK_H_

+ 121 - 0
Engine/source/afx/ce/afxAreaDamage.cpp

@@ -0,0 +1,121 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/ce/afxAreaDamage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxAreaDamageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAreaDamageData);
+
+ConsoleDocClass( afxAreaDamageData,
+   "@brief A datablock that specifies an Area Damage effect.\n\n"
+
+   "An Area Damage effect is useful for assigning area damage with unusual timing that must be synchronized with other "
+   "effects. Negative damage amounts can be used for healing effects."
+   "\n\n"
+
+   "The primary difference between afxAreaDamageData and afxDamageData, which is also capable of inflicting area damage, "
+   "is that afxAreaDamageData effects calculate the area damage in C++ code rather than calling out to the script function "
+   "radiusDamage(). In cases where area damage needs to be inflicted repeatedly or in areas crowded with many targets, "
+   "afxAreaDamageData is likely to get better performance."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAreaDamageData::afxAreaDamageData()
+{
+  flavor = ST_NULLSTRING;
+  amount = 0;
+  radius = 0;
+  impulse = 0;
+  notify_damage_src = false;
+  exclude_cons_obj = false;
+}
+
+afxAreaDamageData::afxAreaDamageData(const afxAreaDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  flavor = other.flavor;
+  amount = other.amount;
+  radius = other.radius;
+  impulse = other.impulse;
+  notify_damage_src = other.notify_damage_src;
+  exclude_cons_obj = other.exclude_cons_obj;
+}
+
+#define myOffset(field) Offset(field, afxAreaDamageData)
+
+void afxAreaDamageData::initPersistFields()
+{
+  addField("flavor",                    TypeString,     myOffset(flavor),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It is used to classify a type of damage such as 'melee', 'magical', or "
+    "'fire'.");
+  addField("damage",                    TypeF32,        myOffset(amount),
+    "An amount of area damage to inflict on a target. Objects within half the radius "
+    "receive full damage which then diminishes out to the full distance of the specified "
+    "radius.");
+  addField("radius",                    TypeF32,        myOffset(radius),
+    "Radius centered at the effect position in which damage will be applied.");
+  addField("impulse",                   TypeF32,        myOffset(impulse),
+    "Specifies an amount of force to apply to damaged objects. Objects within half the "
+    "radius receive full impulse which then diminishes out to the full distance of the "
+    "specified radius.");
+  addField("notifyDamageSource",        TypeBool,       myOffset(notify_damage_src),
+    "When true, the onInflictedAreaDamage() method of the damaged object will be called "
+    "to notify it of the damage. This is useful for starting some effects or action that "
+    "responds to the damage.");
+  addField("excludeConstraintObject",   TypeBool,       myOffset(exclude_cons_obj),
+    "When true, the object specified as the effect's primary position constraint will not "
+    "receive any damage.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAreaDamageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxAreaDamageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+}
+
+void afxAreaDamageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 62 - 0
Engine/source/afx/ce/afxAreaDamage.h

@@ -0,0 +1,62 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_AREA_DAMAGE_H_
+#define _AFX_AREA_DAMAGE_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxAreaDamageData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  flavor;
+
+  F32           amount;
+  F32           radius;
+  F32           impulse;
+  bool          notify_damage_src;
+  bool          exclude_cons_obj;
+
+public:
+  /*C*/         afxAreaDamageData();
+  /*C*/         afxAreaDamageData(const afxAreaDamageData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxAreaDamageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_AREA_DAMAGE_H_

+ 246 - 0
Engine/source/afx/ce/afxAudioBank.cpp

@@ -0,0 +1,246 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "sim/netConnection.h"
+#include "sfx/sfxDescription.h"
+
+#include "afx/ce/afxAudioBank.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+IMPLEMENT_CO_DATABLOCK_V1(afxAudioBank);
+
+ConsoleDocClass( afxAudioBank,
+   "@brief A datablock that specifies an Audio Bank effect.\n\n"
+
+   "afxAudioBank is very similar to the stock Torque SFXProfile datablock but it allows specification of up to 32 different sound "
+   "files. The sound that actually plays is determined by the playIndex field."
+   "\n\n"
+
+   "afxAudioBank is most useful when used in combination with field substitutions, whereby a substitution statement "
+   "assigned to playIndex selects a different sound (perhaps randomly) each time the effect is used."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxAudioBank::afxAudioBank()
+{
+  mPath = ST_NULLSTRING;
+  mDescriptionObjectID = 0;
+  mDescriptionObject = NULL;
+  mPreload = false;
+  play_index = -1;
+
+  for (S32 i = 0; i < 32; i++)
+    mFilenames[i] = ST_NULLSTRING;
+}
+
+afxAudioBank::afxAudioBank(const afxAudioBank& other, bool temp_clone) : SimDataBlock(other, temp_clone)
+{
+  mPath = other.mPath;
+  mDescriptionObject = other.mDescriptionObject;
+  mDescriptionObjectID = other.mDescriptionObjectID; // -- for pack/unpack of mDescriptionObject ptr
+  mPreload = other.mPreload;
+  play_index = other.play_index;
+
+  for (S32 i = 0; i < 32; i++)
+    mFilenames[i] = other.mFilenames[i];
+}
+
+afxAudioBank::~afxAudioBank()
+{
+  if (!isTempClone())
+    return;
+
+  if (mDescriptionObject && mDescriptionObject->isTempClone())
+  {
+    delete mDescriptionObject;
+    mDescriptionObject = 0;
+  }
+}
+
+afxAudioBank* afxAudioBank::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
+{
+  if (!owner)
+    return this;
+
+  afxAudioBank* sub_profile_db = this;
+
+  SFXDescription* desc_db;
+  if (mDescriptionObject && mDescriptionObject->getSubstitutionCount() > 0)
+  {
+    SFXDescription* orig_db = mDescriptionObject;
+    desc_db = new SFXDescription(*orig_db, true);
+    orig_db->performSubstitutions(desc_db, owner, index);
+  }
+  else
+    desc_db = 0;
+
+  if (this->getSubstitutionCount() > 0 || desc_db)
+  {
+    sub_profile_db = new afxAudioBank(*this, true);
+    performSubstitutions(sub_profile_db, owner, index);
+    if (desc_db)
+      sub_profile_db->mDescriptionObject = desc_db;
+  }
+
+  return sub_profile_db;
+}
+
+void afxAudioBank::onPerformSubstitutions() 
+{ 
+}
+
+void afxAudioBank::initPersistFields()
+{
+  addField("path",        TypeFilename,             Offset(mPath, afxAudioBank),
+    "A filesystem path to the folder containing the sound files specified by the "
+    "filenames[] field. All sound files used in a single AudioBank must be located in "
+    "the same folder.");
+  addField("filenames",   TypeString,               Offset(mFilenames, afxAudioBank), 32,
+    "Up to 32 names of sound files found in the path folder. The sound that is actually "
+    "played by an Audio Bank effect is determined by the playIndex field.");
+  addField("description", TYPEID<SFXDescription>(), Offset(mDescriptionObject, afxAudioBank),
+    "SFXDescription datablock to use with this set of sounds.");
+  addField("preload",     TypeBool,                 Offset(mPreload, afxAudioBank),
+    "If set to true, file is pre-loaded, otherwise it is loaded on-demand.");
+  addField("playIndex",   TypeS32,                  Offset(play_index, afxAudioBank),
+    "An array index that selects a sound to play from the filenames[] field. Values "
+    "outside of the range of assigned filename[] entries will not play any sound.");
+
+  Parent::initPersistFields();
+}
+
+bool afxAudioBank::preload(bool server, String &errorStr)
+{
+  if(!Parent::preload(server, errorStr))
+    return false;
+
+  return true;
+}
+
+bool afxAudioBank::onAdd()
+{
+  if (!Parent::onAdd())
+    return false;
+
+  if (!mDescriptionObject && mDescriptionObjectID)
+    Sim::findObject(mDescriptionObjectID , mDescriptionObject);
+
+  // if this is client side, make sure that description is as well
+  if(mDescriptionObject)
+  {  // client side dataBlock id's are not in the dataBlock id range
+    if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
+    {
+      SimObjectId pid = mDescriptionObject->getId();
+      if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
+      {
+        Con::errorf(ConsoleLogEntry::General,"afxAudioBank: data dataBlock not networkable (use datablock to create).");
+        return false;
+      }
+    }
+  }
+
+  return(true);
+}
+
+void afxAudioBank::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  if (stream->writeFlag(mDescriptionObject))
+    stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+  /*
+  char buffer[256];
+  if(!mFilename)
+    buffer[0] = 0;
+  else
+    dStrcpy(buffer, mFilename);
+  stream->writeString(buffer);
+  */
+
+  stream->writeString(mPath);
+
+  for (S32 i = 0; i < 32; i++)
+  {
+    stream->writeString(mFilenames[i]);
+    if (mFilenames[i] == ST_NULLSTRING)
+      break;
+  }
+
+  stream->writeFlag(mPreload);
+
+  if (stream->writeFlag(play_index >= 0 && play_index < 32))
+    stream->writeInt(play_index, 5);
+}
+
+void afxAudioBank::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  if (stream->readFlag()) // AudioDescription
+  {
+    SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+    mDescriptionObjectID = id;
+    Sim::findObject(id, mDescriptionObject);
+  }
+
+  // Filename
+  /*
+  char buffer[256];
+  stream->readString(buffer);
+  mFilename = StringTable->insert(buffer);
+  */
+
+  char buffer[256]; 
+
+  stream->readString(buffer);
+  mPath = StringTable->insert(buffer);
+
+  for (S32 i = 0; i < 32; i++)
+  {
+    stream->readString(buffer);
+    mFilenames[i] = StringTable->insert(buffer);
+    if (mFilenames[i] == ST_NULLSTRING)
+      break;
+  }
+
+  mPreload = stream->readFlag(); // Preload
+
+  if (stream->readFlag())
+    play_index = stream->readInt(5);
+  else
+    play_index = -1;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 67 - 0
Engine/source/afx/ce/afxAudioBank.h

@@ -0,0 +1,67 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_AUDIO_BANK_H_
+#define _AFX_AUDIO_BANK_H_
+
+class SFXDescription;
+
+class afxAudioBank: public SimDataBlock
+{
+private:
+   typedef SimDataBlock Parent;
+
+public:
+   SFXDescription*    mDescriptionObject;
+   U32                mDescriptionObjectID;
+   StringTableEntry   mPath;
+   StringTableEntry   mFilenames[32];
+   bool               mPreload;
+   S32                play_index;
+
+public:
+   /*C*/              afxAudioBank();
+   /*C*/              afxAudioBank(const afxAudioBank&, bool = false);
+   /*D*/              ~afxAudioBank();
+
+   static void        initPersistFields();
+
+   virtual bool       onAdd();
+   virtual void       packData(BitStream* stream);
+   virtual void       unpackData(BitStream* stream);
+
+   bool               preload(bool server, String &errorStr);
+
+   afxAudioBank*      cloneAndPerformSubstitutions(const SimObject*, S32 index=0);
+   virtual void       onPerformSubstitutions();
+   virtual bool       allowSubstitutions() const { return true; }
+
+   DECLARE_CONOBJECT(afxAudioBank);
+   DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif  // _AFX_AUDIO_BANK_H_

+ 294 - 0
Engine/source/afx/ce/afxBillboard.cpp

@@ -0,0 +1,294 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "gfx/gfxAPI.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxBillboard.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboardData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxBillboardData);
+
+ConsoleDocClass( afxBillboardData,
+   "@brief A datablock that specifies a Billboard effect.\n\n"
+
+   "A Billboard effect is a textured quadrangle which is always aligned to face towards the camera. It is much like a single "
+   "static particle and is rendered in a similar fashion."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxBillboardData::afxBillboardData()
+{
+  color.set(1.0f, 1.0f, 1.0f, 1.0f);
+  txr_name = ST_NULLSTRING;
+  dimensions.set(1.0f, 1.0f);
+  texCoords[0].set(0.0f, 0.0f);
+  texCoords[1].set(0.0f, 1.0f);
+  texCoords[2].set(1.0f, 1.0f);
+  texCoords[3].set(1.0f, 0.0f);
+  blendStyle = BlendUndefined;
+  srcBlendFactor = BLEND_UNDEFINED;
+  dstBlendFactor = BLEND_UNDEFINED;
+  texFunc = TexFuncModulate;
+}
+
+afxBillboardData::afxBillboardData(const afxBillboardData& other, bool temp_clone)
+  : GameBaseData(other, temp_clone)
+{
+  color = other.color;
+  txr_name = other.txr_name;
+  txr = other.txr;
+  dimensions = other.dimensions;
+  texCoords[0] = other.texCoords[0];
+  texCoords[1] = other.texCoords[1];
+  texCoords[2] = other.texCoords[2];
+  texCoords[3] = other.texCoords[3];
+  blendStyle = other.blendStyle;
+  srcBlendFactor = other.srcBlendFactor;
+  dstBlendFactor = other.dstBlendFactor;
+  texFunc = other.texFunc;
+}
+
+#define myOffset(field) Offset(field, afxBillboardData)
+
+extern EnumTable srcBlendFactorTable;
+extern EnumTable dstBlendFactorTable;
+
+ImplementEnumType( afxBillboard_BlendStyle, "Possible blending types.\n" "@ingroup afxBillboard\n\n" )
+    { afxBillboardData::BlendNormal,         "NORMAL",         "..." },
+    { afxBillboardData::BlendAdditive,       "ADDITIVE",       "..." },
+    { afxBillboardData::BlendSubtractive,    "SUBTRACTIVE",    "..." },
+    { afxBillboardData::BlendPremultAlpha,   "PREMULTALPHA",   "..." },
+EndImplementEnumType;
+
+ImplementEnumType( afxBillboard_TexFuncType, "Possible texture function types.\n" "@ingroup afxBillboard\n\n" )
+    { afxBillboardData::TexFuncReplace,   "replace",     "..." },
+    { afxBillboardData::TexFuncModulate,  "modulate",    "..." },
+    { afxBillboardData::TexFuncAdd,       "add",         "..." },
+EndImplementEnumType;
+
+void afxBillboardData::initPersistFields()
+{
+  addField("color",           TypeColorF,     myOffset(color),
+    "The color assigned to the quadrangle geometry. The way it combines with the given "
+    "texture varies according to the setting of the textureFunction field.");
+  addField("texture",         TypeFilename,   myOffset(txr_name),
+    "An image to use as the billboard's texture.");
+  addField("dimensions",      TypePoint2F,    myOffset(dimensions),
+    "A value-pair that specifies the horizontal and vertical dimensions of the billboard "
+    "in scene units.");
+  addField("textureCoords",   TypePoint2F,    myOffset(texCoords),  4,
+    "An array of four value-pairs that specify the UV texture coordinates for the four "
+    "corners of the billboard's quadrangle.");
+
+  addField("blendStyle",      TYPEID<afxBillboardData::BlendStyle>(),   myOffset(blendStyle),
+    "Selects a common blend factor preset. When set to 'user', srcBlendFactor and "
+    "dstBlendFactor can be used to set additional blend factor combinations.\n"
+    "Possible values: normal, additive, subtractive, premultalpha, or user.");
+  addField("srcBlendFactor",  TYPEID<GFXBlend>(),   myOffset(srcBlendFactor),
+    "Specifies source blend factor when blendStyle is set to 'user'.\n"
+    "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendDestColor, GFXBlendInvDestColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, GFXBlendInvDestAlpha, or GFXBlendSrcAlphaSat");
+  addField("dstBlendFactor",  TYPEID<GFXBlend>(),   myOffset(dstBlendFactor),
+    "Specifies destination blend factor when blendStyle is set to 'user'.\n"
+    "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendSrcColor, GFXBlendInvSrcColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, or GFXBlendInvDestAlpha");
+
+  addField("textureFunction", TYPEID<afxBillboardData::TexFuncType>(),  myOffset(texFunc),
+    "Selects a texture function that determines how the texture pixels are combined "
+    "with the shaded color of the billboard's quadrangle geometry.\n"
+    "Possible values: replace, modulate, or add.");
+
+  Parent::initPersistFields();
+}
+
+void afxBillboardData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(color);
+  stream->writeString(txr_name);
+  mathWrite(*stream, dimensions);
+  mathWrite(*stream, texCoords[0]);
+  mathWrite(*stream, texCoords[1]);
+  mathWrite(*stream, texCoords[2]);
+  mathWrite(*stream, texCoords[3]);
+
+  stream->writeInt(srcBlendFactor, 4);
+  stream->writeInt(dstBlendFactor, 4);
+  stream->writeInt(texFunc, 4);
+}
+
+void afxBillboardData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&color);
+  txr_name = stream->readSTString();
+  txr = GFXTexHandle();
+  mathRead(*stream, &dimensions);
+  mathRead(*stream, &texCoords[0]);
+  mathRead(*stream, &texCoords[1]);
+  mathRead(*stream, &texCoords[2]);
+  mathRead(*stream, &texCoords[3]);
+
+  srcBlendFactor = (GFXBlend) stream->readInt(4);
+  dstBlendFactor = (GFXBlend) stream->readInt(4);
+  texFunc = stream->readInt(4);
+}
+
+bool afxBillboardData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  if (!server)
+  {
+    if (txr_name && txr_name[0] != '\0')
+    {
+      txr.set(txr_name, &GFXStaticTextureSRGBProfile, "Billboard Texture");
+    }
+  }
+
+   // if blend-style is set to User, check for defined blend-factors
+   if (blendStyle == BlendUser && (srcBlendFactor == BLEND_UNDEFINED || dstBlendFactor == BLEND_UNDEFINED))
+   {
+      blendStyle = BlendUndefined;
+      Con::warnf(ConsoleLogEntry::General, "afxBillboardData(%s) incomplete blend factor specification.", getName());
+   }
+
+   // silently switch Undefined blend-style to User if blend factors are both defined
+   if (blendStyle == BlendUndefined && srcBlendFactor != BLEND_UNDEFINED && dstBlendFactor != BLEND_UNDEFINED)
+   {
+      blendStyle = BlendUser;
+   }
+
+   // set pre-defined blend-factors 
+   switch (blendStyle)
+   {
+   case BlendNormal:
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   case BlendSubtractive:
+      srcBlendFactor = GFXBlendZero;
+      dstBlendFactor = GFXBlendInvSrcColor;
+      break;
+   case BlendPremultAlpha:
+      srcBlendFactor = GFXBlendOne;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   case BlendUser:
+      break;
+   case BlendAdditive:
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendOne;
+      break;
+   case BlendUndefined:
+   default:
+      blendStyle = BlendNormal;
+      srcBlendFactor = GFXBlendSrcAlpha;
+      dstBlendFactor = GFXBlendInvSrcAlpha;
+      break;
+   }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboard
+
+IMPLEMENT_CO_NETOBJECT_V1(afxBillboard);
+
+ConsoleDocClass( afxBillboard,
+   "@brief A Billboard effect as defined by an afxBillboardData datablock.\n\n"
+
+   "A Billboard effect is a textured quadrangle which is always aligned to "
+   "face towards the camera. It is much like a single static particle and is rendered "
+   "in a similar fashion.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxBillboard::afxBillboard()
+{
+  mNetFlags.clear();
+  mNetFlags.set(IsGhost);
+
+  mDataBlock = 0;
+  fade_amt = 1.0f;
+  is_visible = true;
+  sort_priority = 0;
+  live_color.set(1.0f, 1.0f, 1.0f, 1.0f);
+}
+
+afxBillboard::~afxBillboard()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxBillboard::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxBillboardData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  live_color = mDataBlock->color;
+
+  return true;
+}
+
+bool afxBillboard::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  F32 width = mDataBlock->dimensions.x * 0.5f;
+  F32 height = mDataBlock->dimensions.y * 0.5f;
+  mObjBox = Box3F(Point3F(-width, -0.01f, -height), Point3F(width, 0.01f, height));
+  
+  addToScene();
+  
+  return true;
+}
+
+void afxBillboard::onRemove()
+{
+  removeFromScene();
+  
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 131 - 0
Engine/source/afx/ce/afxBillboard.h

@@ -0,0 +1,131 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_BILLBOARD_H_
+#define _AFX_BILLBOARD_H_
+
+#include "afx/afxEffectDefs.h"
+
+#define BLEND_UNDEFINED GFXBlend_COUNT
+
+class afxBillboardData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+   // This enum specifies common blend settings with predefined values
+   // for src/dst blend factors. 
+   enum BlendStyle {
+     BlendUndefined,
+     BlendNormal,
+     BlendAdditive,
+     BlendSubtractive,
+     BlendPremultAlpha,
+     BlendUser,
+   };
+
+   enum TexFuncType {
+     TexFuncReplace,
+     TexFuncModulate,
+     TexFuncAdd,
+   };  
+
+public:
+  StringTableEntry  txr_name;
+  GFXTexHandle      txr;
+
+  LinearColorF            color;
+  Point2F           texCoords[4];
+  Point2F           dimensions;
+  S32               blendStyle; 
+  GFXBlend          srcBlendFactor;
+  GFXBlend          dstBlendFactor;
+  S32               texFunc;
+
+public:
+  /*C*/             afxBillboardData();
+  /*C*/             afxBillboardData(const afxBillboardData&, bool = false);
+
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  bool              preload(bool server, String &errorStr);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxBillboardData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxBillboardData::BlendStyle afxBillboard_BlendStyle;
+DefineEnumType( afxBillboard_BlendStyle );
+
+typedef afxBillboardData::TexFuncType afxBillboard_TexFuncType;
+DefineEnumType( afxBillboard_TexFuncType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxBillboard
+
+class afxBillboard : public GameBase, public afxEffectDefs
+{
+  typedef GameBase Parent;
+  friend class afxEA_Billboard;
+
+private:
+  afxBillboardData* mDataBlock;
+
+  F32               fade_amt;
+  bool              is_visible;
+  S8                sort_priority;
+  LinearColorF            live_color;
+
+  GFXStateBlockRef  normal_sb;
+  GFXStateBlockRef  reflected_sb;
+
+public:
+  /*C*/             afxBillboard();
+  /*D*/             ~afxBillboard();
+
+  virtual bool      onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual bool      onAdd();
+  virtual void      onRemove();
+
+  void              setFadeAmount(F32 amt) { fade_amt = amt; }
+  void              setSortPriority(S8 priority) { sort_priority = priority; }
+  void              setVisibility(bool flag) { is_visible = flag; }
+
+  virtual void      prepRenderImage(SceneRenderState*);
+
+  void              _renderBillboard(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*);
+
+  DECLARE_CONOBJECT(afxBillboard);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_BILLBOARD_H_

+ 145 - 0
Engine/source/afx/ce/afxBillboard_T3D.cpp

@@ -0,0 +1,145 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "gfx/gfxTransformSaver.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxBillboard.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxBillboard::prepRenderImage(SceneRenderState* state)
+{
+  if (!is_visible)
+    return;
+
+  ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+  ri->renderDelegate.bind(this, &afxBillboard::_renderBillboard);
+  ri->type = RenderPassManager::RIT_ObjectTranslucent;
+  ri->translucentSort = true;
+  ri->defaultKey = (U32)(dsize_t)mDataBlock;
+  ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() );      
+  state->getRenderPass()->addInst(ri);
+}
+
+void afxBillboard::_renderBillboard(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat)
+{
+  if (overrideMat)
+    return;
+
+  // predraw
+  if (normal_sb.isNull())
+  {
+    GFXStateBlockDesc desc;
+
+    // Culling -- it's a billboard, so no backfaces
+    desc.setCullMode(GFXCullCW);
+
+    // Blending
+    desc.setBlend(true, mDataBlock->srcBlendFactor, mDataBlock->dstBlendFactor);
+    desc.alphaTestEnable = (desc.blendSrc == GFXBlendSrcAlpha && 
+                            (desc.blendDest == GFXBlendInvSrcAlpha || desc.blendDest == GFXBlendOne));
+    desc.alphaTestRef = 1;
+    desc.alphaTestFunc = GFXCmpGreaterEqual;
+    
+    desc.setZReadWrite(true);
+    desc.zFunc = GFXCmpLessEqual;
+    desc.zWriteEnable = false;
+
+    desc.samplersDefined = true;
+    switch (mDataBlock->texFunc)
+    {
+    case afxBillboardData::TexFuncReplace:
+      desc.samplers[0].textureColorOp = GFXTOPDisable;
+      break;
+    case afxBillboardData::TexFuncModulate:
+      desc.samplers[0].textureColorOp = GFXTOPModulate;
+      break;
+    case afxBillboardData::TexFuncAdd:
+      desc.samplers[0].textureColorOp = GFXTOPAdd;
+      break;
+    }
+
+    desc.samplers[1].textureColorOp = GFXTOPDisable;
+
+    normal_sb = GFX->createStateBlock(desc);
+
+    desc.setCullMode(GFXCullCCW);
+    reflected_sb = GFX->createStateBlock(desc);
+  }
+
+  if (state->isReflectPass())
+    GFX->setStateBlock(reflected_sb);
+  else
+    GFX->setStateBlock(normal_sb);
+
+  GFXTransformSaver saver;
+  GFX->multWorld(getRenderTransform());
+
+  GFX->setTexture(0, mDataBlock->txr);
+
+	MatrixF worldmod = GFX->getWorldMatrix();
+	MatrixF viewmod = GFX->getViewMatrix();
+
+  Point4F Position;
+  MatrixF ModelView;
+	ModelView.mul(viewmod, worldmod);
+	ModelView.getColumn(3, &Position);
+	ModelView.identity();
+	ModelView.setColumn(3, Position);
+
+	GFX->setWorldMatrix(ModelView);
+	MatrixF ident;
+	ident.identity();
+	GFX->setViewMatrix(ident);
+
+  F32 width = mDataBlock->dimensions.x * 0.5f * mObjScale.x;
+  F32 height = mDataBlock->dimensions.y * 0.5f * mObjScale.z;
+
+  Point3F points[4];
+  points[0].set( width, 0.0f, -height);
+  points[1].set(-width, 0.0f, -height);
+  points[2].set(-width, 0.0f,  height);
+  points[3].set( width, 0.0f,  height);
+
+  PrimBuild::begin(GFXTriangleStrip, 4);
+  {
+	  PrimBuild::color4f(live_color.red, live_color.green, live_color.blue, live_color.alpha*fade_amt);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[1].x, mDataBlock->texCoords[1].y);
+    PrimBuild::vertex3fv(points[1]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[2].x, mDataBlock->texCoords[2].y);
+    PrimBuild::vertex3fv(points[0]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[0].x, mDataBlock->texCoords[0].y);
+    PrimBuild::vertex3fv(points[2]);
+    PrimBuild::texCoord2f(mDataBlock->texCoords[3].x, mDataBlock->texCoords[3].y);
+    PrimBuild::vertex3fv(points[3]);
+  }
+  PrimBuild::end();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 132 - 0
Engine/source/afx/ce/afxCameraPuppet.cpp

@@ -0,0 +1,132 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "scene/sceneRenderState.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxCameraPuppet.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraPuppetData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraPuppetData);
+
+ConsoleDocClass( afxCameraPuppetData,
+   "@brief A datablock that specifies a Camera Puppet effect.\n\n"
+
+   "A Camera Puppet effect is used to control the position and orientation of the camera using the AFX constraint system. "
+   "Camera Puppet effects are useful for creating small cut-scenes and can add a lot of visual drama to a spell or effectron "
+   "effect."
+   "\n\n"
+
+   "Effective use of Camera Puppet effects require a fairly advanced understanding of how Torque cameras work in a "
+   "server-client context. Care must be taken to prevent client cameras from drifting too far out of sync from the server camera. "
+   "Otherwise, obvious discontinuities in the motion will result when the Camera Puppet ends and control is restored to the "
+   "server camera. Scoping problems can also result if a client camera is moved to a location that is inconsistent with the "
+   "scene scoping done by the server camera."
+   "\n\n"
+
+   "Often it is useful to manage camera controlling in an isolated effectron rather than directly incorporated into a magic-spell. "
+   "This way the camera controlling effectron can target the specific client associated with the spellcaster. The spellcasting "
+   "player observes the spell in a dramatic cut-scene-like fashion while other players continue to observe from their own "
+   "viewing locations."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCameraPuppetData::afxCameraPuppetData()
+{
+  cam_spec = ST_NULLSTRING;
+  networking = SERVER_AND_CLIENT;
+}
+
+afxCameraPuppetData::afxCameraPuppetData(const afxCameraPuppetData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  cam_spec = other.cam_spec;
+  networking = other.networking;
+}
+
+#define myOffset(field) Offset(field, afxCameraPuppetData)
+
+void afxCameraPuppetData::initPersistFields()
+{
+  addField("cameraSpec",          TypeString,   myOffset(cam_spec),
+    "This field is like the effect-wrapper fields for specifying constraint sources, "
+    "but here it specifies a target for the camera-puppet effect.");
+  addField("networking",          TypeS8,       myOffset(networking),
+    "Specifies the networking model used for the camerapuppet effect. The effect can "
+    "puppet just the server camera, just the client camera, or both.\n"
+    "Possible values: $AFX::SERVER_ONLY, $AFX::CLIENT_ONLY, or $AFX::SERVER_AND_CLIENT.");
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("cameraSpec");
+  disableFieldSubstitutions("networking");
+
+  Parent::initPersistFields();
+}
+
+bool afxCameraPuppetData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0);
+  bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0);
+  cam_def.parseSpec(cam_spec, runs_on_s, runs_on_c);
+
+  return true;
+}
+
+void afxCameraPuppetData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(cam_spec);
+  stream->write(networking);
+}
+
+void afxCameraPuppetData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  cam_spec = stream->readSTString();
+  stream->read(&networking);
+}
+
+void afxCameraPuppetData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (cam_def.isDefined())
+    defs.push_back(cam_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 64 - 0
Engine/source/afx/ce/afxCameraPuppet.h

@@ -0,0 +1,64 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_PUPPET_H_
+#define _AFX_CAMERA_PUPPET_H_
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxEffectDefs.h"
+#include "afx/afxConstraint.h"
+
+class afxCameraPuppetData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  cam_spec;
+  afxConstraintDef  cam_def;
+
+  U8                networking;
+
+  virtual void      gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+public:
+  /*C*/             afxCameraPuppetData();
+  /*C*/             afxCameraPuppetData(const afxCameraPuppetData&, bool = false);
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxCameraPuppetData);
+  DECLARE_CATEGORY("AFX");
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CAMERA_PUPPET_H_

+ 118 - 0
Engine/source/afx/ce/afxCameraShake.cpp

@@ -0,0 +1,118 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxCameraShake.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCameraShakeData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCameraShakeData);
+
+ConsoleDocClass( afxCameraShakeData,
+   "@brief A datablock that specifies a Camera Shake effect.\n\n"
+
+   "Camera Shake internally utilizes the standard Torque CameraShake class to implement a shaken camera effect."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCameraShakeData::afxCameraShakeData()
+{
+  camShakeFreq.set( 10.0, 10.0, 10.0 );
+  camShakeAmp.set( 1.0, 1.0, 1.0 );
+  camShakeRadius = 10.0;
+  camShakeFalloff = 10.0;
+}
+
+afxCameraShakeData::afxCameraShakeData(const afxCameraShakeData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  camShakeFreq = other.camShakeFreq;
+  camShakeAmp = other.camShakeAmp;
+  camShakeRadius = other.camShakeRadius;
+  camShakeFalloff = other.camShakeFalloff;
+}
+
+#define myOffset(field) Offset(field, afxCameraShakeData)
+
+void afxCameraShakeData::initPersistFields()
+{
+  addField("frequency", TypePoint3F,   Offset(camShakeFreq,       afxCameraShakeData),
+    "The camera shake frequencies for all three axes: X, Y, Z.");
+  addField("amplitude", TypePoint3F,   Offset(camShakeAmp,        afxCameraShakeData),
+    "The camera shake amplitudes for all three axes: X, Y, Z.");
+  addField("radius",    TypeF32,       Offset(camShakeRadius,     afxCameraShakeData),
+    "Radius about the effect position in which shaking will be applied.");
+  addField("falloff",   TypeF32,       Offset(camShakeFalloff,    afxCameraShakeData),
+    "Magnitude by which shaking decreases over distance to radius.");
+
+  Parent::initPersistFields();
+}
+
+bool afxCameraShakeData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxCameraShakeData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  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(camShakeRadius);
+  stream->write(camShakeFalloff);
+}
+
+void afxCameraShakeData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  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(&camShakeRadius);
+  stream->read(&camShakeFalloff);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 57 - 0
Engine/source/afx/ce/afxCameraShake.h

@@ -0,0 +1,57 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CAMERA_SHAKE_H_
+#define _AFX_CAMERA_SHAKE_H_
+
+class afxCameraShakeData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  VectorF               camShakeFreq;
+  VectorF               camShakeAmp;
+  F32                   camShakeRadius;
+  F32                   camShakeFalloff;
+
+public:
+  /*C*/                 afxCameraShakeData();
+  /*C*/                 afxCameraShakeData(const afxCameraShakeData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxCameraShakeData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CAMERA_SHAKE_H_

+ 103 - 0
Engine/source/afx/ce/afxCollisionEvent.cpp

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxCollisionEvent.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCollisionEventData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxCollisionEventData);
+
+ConsoleDocClass( afxCollisionEventData,
+   "@brief A datablock that specifies a Collision Event effect.\n\n"
+
+   "MORE NEEDED HERE.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxCollisionEventData::afxCollisionEventData()
+{
+  method_name = ST_NULLSTRING;
+  script_data = ST_NULLSTRING;
+  gen_trigger = false;
+  trigger_bit = 0;
+}
+
+afxCollisionEventData::afxCollisionEventData(const afxCollisionEventData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  method_name = other.method_name;
+  script_data = other.script_data;
+  gen_trigger = other.gen_trigger;
+  trigger_bit = other.trigger_bit;
+}
+
+#define myOffset(field) Offset(field, afxCollisionEventData)
+
+void afxCollisionEventData::initPersistFields()
+{
+  addField("methodName",        TypeString,   myOffset(method_name),
+    "...");
+  addField("scriptData",        TypeString,   myOffset(script_data),
+    "...");
+  addField("generateTrigger",   TypeBool,     myOffset(gen_trigger),
+    "...");
+  addField("triggerBit",        TypeS8,       myOffset(trigger_bit),
+    "...");
+
+  Parent::initPersistFields();
+}
+
+void afxCollisionEventData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->writeString(method_name);
+  stream->writeString(script_data);
+  if (stream->writeFlag(gen_trigger))
+    stream->write(trigger_bit);
+}
+
+void afxCollisionEventData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  method_name = stream->readSTString();
+  script_data = stream->readSTString();
+  gen_trigger = stream->readFlag();
+  if (gen_trigger)
+    stream->read(&trigger_bit);
+  else
+    trigger_bit = 0;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 59 - 0
Engine/source/afx/ce/afxCollisionEvent.h

@@ -0,0 +1,59 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COLLISION_EVENT_H_
+#define _AFX_COLLISION_EVENT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxCollisionEventData
+
+struct afxCollisionEventData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+
+public:
+  StringTableEntry      method_name;
+  StringTableEntry      script_data;
+  bool                  gen_trigger;
+  U8                    trigger_bit;
+
+public:
+  /*C*/                 afxCollisionEventData();
+  /*C*/                 afxCollisionEventData(const afxCollisionEventData&, bool = false);
+
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxCollisionEventData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_COLLISION_EVENT_H_

+ 41 - 0
Engine/source/afx/ce/afxComponentEffect.h

@@ -0,0 +1,41 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_COMPONENT_EFFECT_H_
+#define _AFX_COMPONENT_EFFECT_H_
+
+#include "core/util/tVector.h"
+
+#include "afx/afxConstraint.h"
+
+class afxComponentEffectData
+{
+public:
+  virtual void  gather_cons_defs(Vector<afxConstraintDef>& defs) { };
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_COMPONENT_EFFECT_H_

+ 93 - 0
Engine/source/afx/ce/afxConsoleMessage.cpp

@@ -0,0 +1,93 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxConsoleMessage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxConsoleMessageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxConsoleMessageData);
+
+ConsoleDocClass( afxConsoleMessageData,
+   "@brief A datablock that specifies a Console Message effect.\n\n"
+
+   "Console Message effects are useful for debugging purposes when you want to make sure that an effect with a certain kind "
+   "of timing is actually getting executed and for evaluating some kinds of field substitutions."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxConsoleMessageData::afxConsoleMessageData()
+{
+  message_str = ST_NULLSTRING;
+}
+
+afxConsoleMessageData::afxConsoleMessageData(const afxConsoleMessageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  message_str = other.message_str;
+}
+
+#define myOffset(field) Offset(field, afxConsoleMessageData)
+
+void afxConsoleMessageData::initPersistFields()
+{
+  addField("message",    TypeString,     myOffset(message_str),
+    "A text message to be displayed when the effect is executed.");
+
+  Parent::initPersistFields();
+}
+
+bool afxConsoleMessageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxConsoleMessageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(message_str);
+}
+
+void afxConsoleMessageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  message_str = stream->readSTString();
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 54 - 0
Engine/source/afx/ce/afxConsoleMessage.h

@@ -0,0 +1,54 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_CONSOLE_MESSAGE_H_
+#define _AFX_CONSOLE_MESSAGE_H_
+
+class afxConsoleMessageData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry      message_str;
+
+public:
+  /*C*/                 afxConsoleMessageData();
+  /*C*/                 afxConsoleMessageData(const afxConsoleMessageData&, bool = false);
+
+  virtual bool          onAdd();
+  virtual void          packData(BitStream*);
+  virtual void          unpackData(BitStream*);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxConsoleMessageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_CONSOLE_MESSAGE_H_

+ 140 - 0
Engine/source/afx/ce/afxDamage.cpp

@@ -0,0 +1,140 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxDamage.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxDamageData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxDamageData);
+
+ConsoleDocClass( afxDamageData,
+   "@brief A datablock that specifies a Damage effect.\n\n"
+
+   "A Damage effect is useful for assigning damage with unusual timing that must be synchronized with other effects. They " 
+   "can be used to deal direct damage, radius damage, and damage over time. Negative damage amounts can be used for "
+   "healing effects."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxDamageData::afxDamageData()
+{
+  label = ST_NULLSTRING;
+  flavor = ST_NULLSTRING;
+  amount = 0;
+  repeats = 1;
+  ad_amount = 0;
+  radius = 0;
+  impulse = 0;
+}
+
+afxDamageData::afxDamageData(const afxDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  label = other.label;
+  flavor = other.flavor;
+  amount = other.amount;
+  repeats = other.repeats;
+  ad_amount = other.ad_amount;
+  radius = other.radius;
+  impulse = other.impulse;
+}
+
+#define myOffset(field) Offset(field, afxDamageData)
+
+void afxDamageData::initPersistFields()
+{
+  addField("label",               TypeString,     myOffset(label),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It can be used to identify which damage effect the damage came from in "
+    "cases where more than one damage effect is used in a single spell.");
+  addField("flavor",              TypeString,     myOffset(flavor),
+    "An arbitrary string which is passed as an argument to a spell's onDamage() script "
+    "method. It is used to classify a type of damage such as 'melee', 'magical', or "
+    "'fire'.");
+  addField("directDamage",        TypeF32,        myOffset(amount),
+    "An amount of direct damage to inflict on a target.");
+  addField("directDamageRepeats", TypeS8,         myOffset(repeats),
+    "The number of times to inflict the damage specified by directDamage. Values "
+    "greater than 1 inflict damage over time, with the amount of directDamage "
+    "repeatedly dealt at evenly spaced intervals over the lifetime of the effect.");
+  addField("areaDamage",          TypeF32,        myOffset(ad_amount),
+    "An amount of area damage to inflict on a target. Objects within half the radius "
+    "receive full damage which then diminishes out to the full distance of "
+    "areaDamageRadius.");
+  addField("areaDamageRadius",    TypeF32,        myOffset(radius),
+    "Radius centered at the effect position in which damage will be applied.");
+  addField("areaDamageImpulse",   TypeF32,        myOffset(impulse),
+    "Specifies an amount of force to apply to damaged objects. Objects within half the "
+    "radius receive full impulse which then diminishes out to the full distance of "
+    "areaDamageRadius.");
+
+  Parent::initPersistFields();
+}
+
+bool afxDamageData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxDamageData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(label);
+  stream->writeString(flavor);
+  stream->write(amount);
+  stream->write(repeats);
+  stream->write(ad_amount);
+  stream->write(radius);
+  stream->write(impulse);
+}
+
+void afxDamageData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  label = stream->readSTString();
+  flavor = stream->readSTString();
+  stream->read(&amount);
+  stream->read(&repeats);
+  stream->read(&ad_amount);
+  stream->read(&radius);
+  stream->read(&impulse);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 63 - 0
Engine/source/afx/ce/afxDamage.h

@@ -0,0 +1,63 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_DAMAGE_H_
+#define _AFX_DAMAGE_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxDamageData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  label;
+  StringTableEntry  flavor;
+
+  F32           amount;
+  U8            repeats;
+  F32           ad_amount;
+  F32           radius;
+  F32           impulse;
+
+public:
+  /*C*/         afxDamageData();
+  /*C*/         afxDamageData(const afxDamageData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxDamageData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_DAMAGE_H_

+ 116 - 0
Engine/source/afx/ce/afxFootSwitch.cpp

@@ -0,0 +1,116 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxFootSwitch.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxFootSwitchData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxFootSwitchData);
+
+ConsoleDocClass( afxFootSwitchData,
+   "@brief A datablock that specifies a Foot Switch effect.\n\n"
+
+   "A Foot Switch effect is used to disable some or all of the standard built-in footstep effects generated by Player objects."
+   "\n\n"
+
+   "Stock Player objects generate footprint decals, footstep sounds, and puffs of particle dust in response to special "
+   "animation triggers embedded in the Player's dts model. With the help of Phase Effects, AFX can substitute alternatives for "
+   "these built-in effects. When this is done, it is often preferable to turn off some or all of the built-in footstep effects."
+   "\n\n"
+
+   "Foot Switch effects are only meaningful when the primary position constraint is a Player or Player-derived object."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxFootSwitchData::afxFootSwitchData()
+{
+  override_all = false;
+  override_decals = false;
+  override_sounds = false;
+  override_dust = false;
+}
+
+afxFootSwitchData::afxFootSwitchData(const afxFootSwitchData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  override_all = other.override_all;
+  override_decals = other.override_decals;
+  override_sounds = other.override_sounds;
+  override_dust = other.override_dust;
+}
+
+#define myOffset(field) Offset(field, afxFootSwitchData)
+
+void afxFootSwitchData::initPersistFields()
+{
+  addField("overrideAll",       TypeBool,       myOffset(override_all),
+    "When true, all of a Player's footstep effects are turned off for the duration of "
+    "the foot-switch effect.");
+  addField("overrideDecals",    TypeBool,       myOffset(override_decals),
+    "Specifically selects whether the Player's footprint decals are enabled.");
+  addField("overrideSounds",    TypeBool,       myOffset(override_sounds),
+    "Specifically selects whether the Player's footstep sounds are enabled.");
+  addField("overrideDust",      TypeBool,       myOffset(override_dust),
+    "Specifically selects whether the Player's footstep puffs of dust are enabled.");
+
+  Parent::initPersistFields();
+}
+
+void afxFootSwitchData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  if (!stream->writeFlag(override_all))
+  {
+    stream->writeFlag(override_decals);
+    stream->writeFlag(override_sounds);
+    stream->writeFlag(override_dust);
+  }
+}
+
+void afxFootSwitchData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  override_all = stream->readFlag();
+  if (!override_all)
+  {
+    override_decals = stream->readFlag();
+    override_sounds = stream->readFlag();
+    override_dust = stream->readFlag();
+  }
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 56 - 0
Engine/source/afx/ce/afxFootSwitch.h

@@ -0,0 +1,56 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_FOOT_SWITCH_H_
+#define _AFX_FOOT_SWITCH_H_
+
+class afxFootSwitchData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  bool            override_all;
+  bool            override_decals;
+  bool            override_sounds;
+  bool            override_dust;
+
+public:
+  /*C*/           afxFootSwitchData();
+  /*C*/           afxFootSwitchData(const afxFootSwitchData&, bool = false);
+
+  virtual void    packData(BitStream*);
+  virtual void    unpackData(BitStream*);
+
+  virtual bool    allowSubstitutions() const { return true; }
+
+  static void     initPersistFields();
+
+  DECLARE_CONOBJECT(afxFootSwitchData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_FOOT_SWITCH_H_

+ 109 - 0
Engine/source/afx/ce/afxGuiController.cpp

@@ -0,0 +1,109 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "core/stream/bitStream.h"
+
+#include "afx/ce/afxGuiController.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxGuiControllerData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxGuiControllerData);
+
+ConsoleDocClass( afxGuiControllerData,
+   "@brief A datablock that specifies a Gui Controller effect.\n\n"
+
+   "A Gui Controller enables effect manipulation of pre-existing gui controls. With a Gui Controller effect, a regular gui control "
+   "is located by name, made visible during the lifetime of the effect, and potentially repositioned by projecting 3D constraint "
+   "positions into 2D screen space. In addition, when used with a progress-bar control, (GuiProgressCtrl, afxSpellCastBar, "
+   "afxStatusBar), the progress-bar will continuously reflect the elapsed progress of the effect over its lifetime."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxGuiControllerData::afxGuiControllerData()
+{
+  control_name = ST_NULLSTRING;
+  preserve_pos = false;
+  ctrl_client_only = false;
+}
+
+afxGuiControllerData::afxGuiControllerData(const afxGuiControllerData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  control_name = other.control_name;
+  preserve_pos = other.preserve_pos;
+  ctrl_client_only = other.ctrl_client_only;
+}
+
+#define myOffset(field) Offset(field, afxGuiControllerData)
+
+void afxGuiControllerData::initPersistFields()
+{
+  addField("controlName",           TypeString,     myOffset(control_name),
+    "Specifies the name of an existing gui-control.");
+  addField("preservePosition",      TypeBool,       myOffset(preserve_pos),
+    "When true, the gui-control will retain its initial position, otherwise the "
+    "gui-control position will be continuously updated using a projection of the "
+    "3D constraint position into 2D screen coordinates.");
+  addField("controllingClientOnly", TypeBool,       myOffset(ctrl_client_only),
+    "If true, the effect will only be applied to a gui-control on the client that "
+    "matches the controlling-client of the primary position constraint object.");
+
+  Parent::initPersistFields();
+}
+
+bool afxGuiControllerData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxGuiControllerData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(control_name);
+  stream->writeFlag(preserve_pos);
+  stream->writeFlag(ctrl_client_only);
+}
+
+void afxGuiControllerData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  control_name = stream->readSTString();
+  preserve_pos = stream->readFlag();
+  ctrl_client_only = stream->readFlag();
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 58 - 0
Engine/source/afx/ce/afxGuiController.h

@@ -0,0 +1,58 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_GUI_CONTROLLER_H_
+#define _AFX_GUI_CONTROLLER_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxGuiControllerData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  control_name;
+  bool              preserve_pos;
+  bool              ctrl_client_only;
+
+public:
+  /*C*/         afxGuiControllerData();
+  /*C*/         afxGuiControllerData(const afxGuiControllerData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxGuiControllerData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_GUI_CONTROLLER_H_

+ 103 - 0
Engine/source/afx/ce/afxGuiText.cpp

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "core/stream/bitStream.h"
+
+#include "afx/ce/afxGuiText.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxGuiTextData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxGuiTextData);
+
+ConsoleDocClass( afxGuiTextData,
+   "@brief A datablock that specifies a Gui Text effect.\n\n"
+
+   "A Gui Text effect, with the help of an existing afxGuiTextHud, can be used to display 2D text effects on the Gui Canvas. "
+   "Essentially, using Gui Text effects with an afxGuiTextHud is like using the stock GuiShapeNameHud, but with the ability "
+   "to make additional text elements come and go as effects constrained to the projection of 3D positions onto the 2D screen."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxGuiTextData::afxGuiTextData()
+{
+  text_str = ST_NULLSTRING;
+  text_clr.set(1,1,1,1);
+}
+
+afxGuiTextData::afxGuiTextData(const afxGuiTextData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  text_str = other.text_str;
+  text_clr = other.text_clr;
+}
+
+#define myOffset(field) Offset(field, afxGuiTextData)
+
+void afxGuiTextData::initPersistFields()
+{
+  addField("text",    TypeString,     myOffset(text_str),
+    "The literal text to display on the afxGuiTextHud. The center of the text will be "
+    "placed at the projection of the 3D constraint position into 2D screen space.\n"
+    "If the text field is set to the special string, '#shapeName', the shape name of the "
+    "primary position constraint object will be used. (This is only meaningful if the "
+    "constraint source is a ShapeBase-derived object.)");
+  addField("color",   TypeColorF,     myOffset(text_clr),
+    "A color value for the text label.");
+
+  Parent::initPersistFields();
+}
+
+bool afxGuiTextData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxGuiTextData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(text_str);
+  stream->write(text_clr);
+}
+
+void afxGuiTextData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  text_str = stream->readSTString();
+  stream->read(&text_clr);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 57 - 0
Engine/source/afx/ce/afxGuiText.h

@@ -0,0 +1,57 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_GUI_TEXT_H_
+#define _AFX_GUI_TEXT_H_
+
+#include "afx/afxEffectDefs.h"
+
+class afxGuiTextData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  text_str;
+  LinearColorF            text_clr;
+
+public:
+  /*C*/         afxGuiTextData();
+  /*C*/         afxGuiTextData(const afxGuiTextData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxGuiTextData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_GUI_TEXT_H_

+ 41 - 0
Engine/source/afx/ce/afxLight.cpp

@@ -0,0 +1,41 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/ce/afxLight.h"
+IMPLEMENT_CO_DATABLOCK_V1(afxLightData);
+
+ConsoleDocClass( afxLightData,
+   "@brief afxLightData is a legacy datablock which is not supported for T3D.\n\n"
+
+   "In T3D, instead of afxLightData, use afxT3DPointLightData or afxT3DSpotLightData.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 38 - 0
Engine/source/afx/ce/afxLight.h

@@ -0,0 +1,38 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_LIGHT_H_
+#define _AFX_LIGHT_H_
+
+struct afxLightData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+  DECLARE_CONOBJECT(afxLightData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_LIGHT_H_

+ 243 - 0
Engine/source/afx/ce/afxLightBase_T3D.cpp

@@ -0,0 +1,243 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "T3D/lightAnimData.h"
+
+#include "afx/ce/afxLightBase_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxT3DLightBaseData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxT3DLightBaseData);
+
+ConsoleDocClass( afxT3DLightBaseData,
+   "@brief A datablock baseclass for afxT3DPointLightData and afxT3DSpotLightData.\n\n"
+
+   "Not intended to be used directly, afxT3DLightBaseData exists to provide base member variables and generic functionality "
+   "for the derived classes afxT3DPointLightData and afxT3DSpotLightData."
+   "\n\n"
+
+   "@see afxT3DPointLightData\n\n"
+   "@see afxT3DSpotLightData\n\n"
+   "@see PointLight\n\n"
+   "@see SpotLight\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxT3DLightBaseData::afxT3DLightBaseData()
+   :  mIsEnabled( true ),
+      mColor( LinearColorF::WHITE ),
+      mBrightness( 1.0f ),
+      mCastShadows( false ),
+      mPriority( 1.0f ),
+      mAnimationData( NULL ),
+      mFlareData( NULL ),
+      mFlareScale( 1.0f )
+{
+
+  mLocalRenderViz = false;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+}
+
+afxT3DLightBaseData::afxT3DLightBaseData(const afxT3DLightBaseData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  mIsEnabled = other.mIsEnabled;
+  mColor = other.mColor;
+  mBrightness = other.mBrightness;
+  mCastShadows = other.mCastShadows;
+  mPriority = other.mPriority;
+  mAnimationData = other.mAnimationData;
+  mAnimState = other.mAnimState;
+  mFlareData = other.mFlareData;
+  mFlareScale = other.mFlareScale;
+
+  mLocalRenderViz = other.mLocalRenderViz;
+
+  do_id_convert = other.do_id_convert;
+}
+
+//
+// NOTE: keep this as consistent as possible with LightBase::initPersistFields()
+//
+void afxT3DLightBaseData::initPersistFields()
+{
+   // We only add the basic lighting options that all lighting
+   // systems would use... the specific lighting system options
+   // are injected at runtime by the lighting system itself.
+
+   addGroup( "Light" );
+
+      addField( "isEnabled", TypeBool, Offset( mIsEnabled, afxT3DLightBaseData ),
+        "Enables/Disables the object rendering and functionality in the scene.");
+      addField( "color", TypeColorF, Offset( mColor, afxT3DLightBaseData ),
+        "Changes the base color hue of the light.");
+      addField( "brightness", TypeF32, Offset( mBrightness, afxT3DLightBaseData ),
+        "Adjusts the lights power, 0 being off completely.");
+      addField( "castShadows", TypeBool, Offset( mCastShadows, afxT3DLightBaseData ),
+        "Enables/disables shadow casts by this light.");
+      addField( "priority", TypeF32, Offset( mPriority, afxT3DLightBaseData ),
+        "Used for sorting of lights by the light manager. Priority determines if a light "
+        "has a stronger effect than, those with a lower value");
+      addField( "localRenderViz", TypeBool, Offset( mLocalRenderViz, afxT3DLightBaseData ),
+        "Enables/disables a semi-transparent geometry to help visualize the light's "
+        "range and placement.");
+
+   endGroup( "Light" );
+
+   addGroup( "Light Animation" );
+
+      addField( "animate", TypeBool, Offset( mAnimState.active, afxT3DLightBaseData ),
+        "Toggles animation for the light on and off");
+      addField( "animationType", TYPEID<LightAnimData>(), Offset( mAnimationData, afxT3DLightBaseData ),
+        "Datablock containing light animation information (LightAnimData)");
+      addField( "animationPeriod", TypeF32, Offset( mAnimState.animationPeriod, afxT3DLightBaseData ),
+        "The length of time in seconds for a single playback of the light animation");
+      addField( "animationPhase", TypeF32, Offset( mAnimState.animationPhase, afxT3DLightBaseData ),
+        "The phase used to offset the animation start time to vary the animation of "
+        "nearby lights.");
+
+   endGroup( "Light Animation" );
+
+   addGroup( "Misc" );
+
+      addField( "flareType", TYPEID<LightFlareData>(), Offset( mFlareData, afxT3DLightBaseData ),
+        "Datablock containing light flare information (LightFlareData)");
+      addField( "flareScale", TypeF32, Offset( mFlareScale, afxT3DLightBaseData ),
+        "Globally scales all features of the light flare");
+
+   endGroup( "Misc" );
+
+   /*
+   // Now inject any light manager specific fields.
+   LightManager::initLightFields();
+   */
+
+   // We do the parent fields at the end so that
+   // they show up that way in the inspector.
+   Parent::initPersistFields();
+}
+
+bool afxT3DLightBaseData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxT3DLightBaseData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  // note: BitStream's overloaded write() for LinearColorF will convert
+  // to ColorI for transfer and then back to LinearColorF. This is fine
+  // for most color usage but for lighting colors we want to preserve
+  // "pushed" color values which may be greater than 1.0 so the color
+  // is instead sent as individual color primaries.
+  stream->write( mColor.red );
+  stream->write( mColor.green );
+  stream->write( mColor.blue );
+
+  stream->write( mBrightness );
+  stream->writeFlag( mCastShadows );
+  stream->write( mAnimState.animationPeriod );
+  stream->write( mAnimState.animationPhase );
+  stream->write( mFlareScale );
+
+  writeDatablockID(stream, mAnimationData, packed);
+  writeDatablockID(stream, mFlareData, packed);
+}
+
+void afxT3DLightBaseData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read( &mColor.red );
+  stream->read( &mColor.green );
+  stream->read( &mColor.blue );
+  mColor.alpha = 1.0f;
+
+  stream->read( &mBrightness );
+  mCastShadows = stream->readFlag();
+  stream->read( &mAnimState.animationPeriod );
+  stream->read( &mAnimState.animationPhase );
+  stream->read( &mFlareScale );
+
+  mAnimationData = (LightAnimData*) readDatablockID(stream);
+  mFlareData = (LightFlareData*) readDatablockID(stream);
+
+  do_id_convert = true;
+}
+
+bool afxT3DLightBaseData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server)
+  {
+    if (do_id_convert)
+    {
+      SimObjectId anim_id = SimObjectId((uintptr_t)mAnimationData);
+      if (anim_id != 0)
+      {
+        // try to convert id to pointer
+        if (!Sim::findObject(anim_id, mAnimationData))
+        {
+          Con::errorf(ConsoleLogEntry::General,
+            "afxT3DLightBaseData::preload() -- bad datablockId: 0x%x (animationType)",
+            anim_id);
+        }
+      }
+      SimObjectId flare_id = SimObjectId((uintptr_t)mFlareData);
+      if (flare_id != 0)
+      {
+        // try to convert id to pointer
+        if (!Sim::findObject(flare_id, mFlareData))
+        {
+          Con::errorf(ConsoleLogEntry::General,
+            "afxT3DLightBaseData::preload() -- bad datablockId: 0x%x (flareType)",
+            flare_id);
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 73 - 0
Engine/source/afx/ce/afxLightBase_T3D.h

@@ -0,0 +1,73 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_T3D_LIGHT_BASE_H_
+#define _AFX_T3D_LIGHT_BASE_H_
+
+#include "T3D/lightBase.h"
+
+class LightAnimData;
+class LightFlareData;
+
+class afxT3DLightBaseData : public GameBaseData
+{
+  typedef GameBaseData  Parent;
+
+public:
+   bool         mIsEnabled;
+   LinearColorF       mColor;
+   F32          mBrightness;
+   bool         mCastShadows;
+   F32          mPriority;
+
+   LightAnimData* mAnimationData;
+   LightAnimState mAnimState;
+
+   bool         mLocalRenderViz;
+
+   LightFlareData* mFlareData;
+   F32          mFlareScale;
+
+   bool         do_id_convert;
+
+public:
+  /*C*/         afxT3DLightBaseData();
+  /*C*/         afxT3DLightBaseData(const afxT3DLightBaseData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  bool          preload(bool server, String &errorStr);
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxT3DLightBaseData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_T3D_LIGHT_BASE_H_

+ 127 - 0
Engine/source/afx/ce/afxMachineGun.cpp

@@ -0,0 +1,127 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "lighting/lightInfo.h"
+#include "T3D/projectile.h"
+
+#include "afx/ce/afxMachineGun.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMachineGunData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMachineGunData);
+
+ConsoleDocClass( afxMachineGunData,
+   "@brief A datablock that specifies a Machine Gun effect.\n\n"
+
+   "Machine Gun is a simple but useful effect for rapidly shooting standard Torque Projectile objects. For performance "
+   "reasons, keep in mind that each bullet is a separate Projectile object, which is not a very lightweight object."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxMachineGunData::afxMachineGunData()
+{
+  projectile_data = 0;
+  rounds_per_minute = 60;
+}
+
+afxMachineGunData::afxMachineGunData(const afxMachineGunData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  projectile_data = other.projectile_data;
+  rounds_per_minute = other.rounds_per_minute;
+}
+
+#define myOffset(field) Offset(field, afxMachineGunData)
+
+void afxMachineGunData::initPersistFields()
+{
+  addField("projectile", TYPEID<ProjectileData>(), myOffset(projectile_data),
+    "A ProjectileData datablock describing the projectile to be launched.");
+  addField("roundsPerMinute", TypeS32, myOffset(rounds_per_minute),
+    "Specifies the number of projectiles fired over a minute of time. A value of 1200 "
+    "will create 20 projectiles per second.\n"
+    "Sample values for real machine guns:\n"
+    "    AK-47 = 600, M16 = 750-900, UZI = 600");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("projectile");
+}
+
+bool afxMachineGunData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  if (projectile_data)
+  { 
+    if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
+    {
+      SimObjectId pid = projectile_data->getId();
+      if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
+      {
+        Con::errorf(ConsoleLogEntry::General,"afxMachineGunData: bad ProjectileData datablock.");
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+void afxMachineGunData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  if (stream->writeFlag(projectile_data))
+    stream->writeRangedU32(projectile_data->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
+
+  stream->write(rounds_per_minute);
+}
+
+void afxMachineGunData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  if (stream->readFlag()) 
+  {
+    SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
+    Sim::findObject(id, projectile_data);
+  }
+  
+  stream->read(&rounds_per_minute);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 59 - 0
Engine/source/afx/ce/afxMachineGun.h

@@ -0,0 +1,59 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MACHINE_GUN_H_
+#define _AFX_MACHINE_GUN_H_
+
+#include "afx/afxEffectDefs.h"
+
+class ProjectileData;
+
+class afxMachineGunData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  ProjectileData* projectile_data;
+  S32             rounds_per_minute;
+
+public:
+  /*C*/         afxMachineGunData();
+  /*C*/         afxMachineGunData(const afxMachineGunData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxMachineGunData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MACHINE_GUN_H_

+ 770 - 0
Engine/source/afx/ce/afxModel.cpp

@@ -0,0 +1,770 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "T3D/objectTypes.h"
+#include "T3D/gameBase/gameProcess.h"
+#include "core/resourceManager.h"
+#include "sim/netConnection.h"
+#include "scene/sceneRenderState.h"
+#include "scene/sceneManager.h"
+#include "ts/tsShapeInstance.h"
+#include "ts/tsMaterialList.h"
+
+#include "afx/ce/afxModel.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxModelData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxModelData);
+ 
+ConsoleDocClass( afxModelData,
+   "@brief A datablock that specifies a Model effect.\n\n"
+
+   "A Model effect is a lightweight client-only geometry object useful for effect-driven props."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxModelData::afxModelData()
+{
+  shapeName = ST_NULLSTRING;
+  sequence = ST_NULLSTRING;
+  seq_rate = 1.0f;
+  seq_offset = 0.0f;
+  alpha_mult = 1.0f;
+  use_vertex_alpha = false;
+  force_on_material_flags = 0;
+  force_off_material_flags = 0;
+  texture_filtering = true;
+  fog_mult = 1.0f;
+  remap_txr_tags = ST_NULLSTRING;
+  remap_buffer = 0;
+
+  overrideLightingOptions = false;
+  receiveSunLight = true;
+  receiveLMLighting = true;
+  useAdaptiveSelfIllumination = false;
+  useCustomAmbientLighting = false;
+  customAmbientForSelfIllumination = false;
+  customAmbientLighting = LinearColorF(0.0f, 0.0f, 0.0f);
+  shadowEnable = false;
+
+  shadowSize = 128;
+  shadowMaxVisibleDistance = 80.0f;
+  shadowProjectionDistance = 10.0f;
+  shadowSphereAdjust = 1.0;
+}
+
+afxModelData::afxModelData(const afxModelData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  shapeName = other.shapeName;
+  shape = other.shape; // --
+  sequence = other.sequence;
+  seq_rate = other.seq_rate;
+  seq_offset = other.seq_offset;
+  alpha_mult = other.alpha_mult;
+  use_vertex_alpha = other.use_vertex_alpha;
+  force_on_material_flags = other.force_on_material_flags;
+  force_off_material_flags = other.force_off_material_flags;
+  texture_filtering = other.texture_filtering;
+  fog_mult = other.fog_mult;
+  remap_txr_tags = other.remap_txr_tags;
+  remap_buffer = other.remap_buffer;
+  overrideLightingOptions = other.overrideLightingOptions;
+  receiveSunLight = other.receiveSunLight;
+  receiveLMLighting = other.receiveLMLighting;
+  useAdaptiveSelfIllumination = other.useAdaptiveSelfIllumination;
+  useCustomAmbientLighting = other.useCustomAmbientLighting;
+  customAmbientForSelfIllumination = other.customAmbientForSelfIllumination;
+  customAmbientLighting = other.customAmbientLighting;
+  shadowEnable = other.shadowEnable;
+}
+
+afxModelData::~afxModelData()
+{
+   if (remap_buffer)
+      dFree(remap_buffer);
+}
+
+bool afxModelData::preload(bool server, String &errorStr)
+{
+  if (Parent::preload(server, errorStr) == false)
+    return false;
+  
+  // don't need to do this stuff on the server
+  if (server) 
+    return true;
+  
+  if (shapeName != ST_NULLSTRING && !shape)
+  {
+    shape = ResourceManager::get().load(shapeName);
+    if (!shape)
+    {
+      errorStr = String::ToString("afxModelData::load: Failed to load shape \"%s\"", shapeName);
+      return false;
+    }
+
+    // just parse up the string and collect the remappings in txr_tag_remappings.
+    if (remap_txr_tags != ST_NULLSTRING)
+    {
+       txr_tag_remappings.clear();
+       if (remap_buffer)
+          dFree(remap_buffer);
+
+       remap_buffer = dStrdup(remap_txr_tags);
+
+       char* remap_token = dStrtok(remap_buffer, " \t");
+       while (remap_token != NULL)
+       {
+          char* colon = dStrchr(remap_token, ':');
+          if (colon)
+          {
+             *colon = '\0';
+             txr_tag_remappings.increment();
+             txr_tag_remappings.last().old_tag = remap_token;
+             txr_tag_remappings.last().new_tag = colon+1;
+          }
+          remap_token = dStrtok(NULL, " \t");
+       }
+    }
+
+    // this little hack messes things up when remapping texture tags
+    if (txr_tag_remappings.size() == 0)
+    {
+      // this little hack forces the textures to preload
+      TSShapeInstance* pDummy = new TSShapeInstance(shape);
+      delete pDummy;
+    }
+  }
+
+  return true;
+}
+
+#define myOffset(field) Offset(field, afxModelData)
+
+void afxModelData::initPersistFields()
+{
+  addField("shapeFile",             TypeFilename, myOffset(shapeName),
+    "The name of a .dts format file to use for the model.");
+  addField("sequence",              TypeFilename, myOffset(sequence),
+    "The name of an animation sequence to play in the model.");
+  addField("sequenceRate",          TypeF32,      myOffset(seq_rate),
+    "The rate of playback for the sequence.");
+  addField("sequenceOffset",        TypeF32,      myOffset(seq_offset),
+    "An offset in seconds indicating a starting point for the animation sequence "
+    "specified by the sequence field. A rate of 1.0 (rather than sequenceRate) is used "
+    "to convert from seconds to the thread offset.");
+  addField("alphaMult",             TypeF32,      myOffset(alpha_mult),
+    "An alpha multiplier used to set maximum opacity of the model.");
+
+  addField("fogMult",               TypeF32,      myOffset(fog_mult),
+    "");
+  addField("remapTextureTags",      TypeString,   myOffset(remap_txr_tags),
+    "Rename one or more texture tags in the model. Texture tags are what link a "
+    "model's textures to materials.\n"
+    "Field should be a string containing space-separated remapping tokens. A remapping "
+    "token is two names separated by a colon, ':'. The first name should be a texture-tag "
+    "that exists in the model, while the second is a new name to replace it. The string "
+    "can have any number of remapping tokens as long as the total string length does not "
+    "exceed 255.");
+  addField("shadowEnable",                  TypeBool,   myOffset(shadowEnable),
+    "Sets whether the model casts a shadow.");
+
+  addField("useVertexAlpha",        TypeBool,     myOffset(use_vertex_alpha),
+    "deprecated");
+  addField("forceOnMaterialFlags",  TypeS32,      myOffset(force_on_material_flags),
+    "deprecated");
+  addField("forceOffMaterialFlags", TypeS32,      myOffset(force_off_material_flags),
+    "deprecated");
+  addField("textureFiltering",      TypeBool,     myOffset(texture_filtering),
+    "deprecated");
+  addField("overrideLightingOptions",       TypeBool,   myOffset(overrideLightingOptions),
+    "deprecated");
+  addField("receiveSunLight",               TypeBool,   myOffset(receiveSunLight),
+    "");
+  addField("receiveLMLighting",             TypeBool,   myOffset(receiveLMLighting),
+    "deprecated");
+  addField("useAdaptiveSelfIllumination",   TypeBool,   myOffset(useAdaptiveSelfIllumination),
+    "deprecated");
+  addField("useCustomAmbientLighting",      TypeBool,   myOffset(useCustomAmbientLighting),
+    "deprecated");
+  addField("customAmbientSelfIllumination", TypeBool,   myOffset(customAmbientForSelfIllumination),
+    "deprecated");
+  addField("customAmbientLighting",         TypeColorF, myOffset(customAmbientLighting),
+    "deprecated");
+  addField("shadowSize",                    TypeS32,    myOffset(shadowSize),
+    "deprecated");
+  addField("shadowMaxVisibleDistance",      TypeF32,    myOffset(shadowMaxVisibleDistance),
+    "deprecated");
+  addField("shadowProjectionDistance",      TypeF32,    myOffset(shadowProjectionDistance),
+    "deprecated");
+  addField("shadowSphereAdjust",            TypeF32,    myOffset(shadowSphereAdjust),
+    "deprecated");
+
+  Parent::initPersistFields();
+
+  // Material Flags
+  Con::setIntVariable("$MaterialFlags::S_Wrap",              TSMaterialList::S_Wrap);
+  Con::setIntVariable("$MaterialFlags::T_Wrap",              TSMaterialList::T_Wrap);
+  Con::setIntVariable("$MaterialFlags::Translucent",         TSMaterialList::Translucent);
+  Con::setIntVariable("$MaterialFlags::Additive",            TSMaterialList::Additive);
+  Con::setIntVariable("$MaterialFlags::Subtractive",         TSMaterialList::Subtractive);
+  Con::setIntVariable("$MaterialFlags::SelfIlluminating",    TSMaterialList::SelfIlluminating);
+  Con::setIntVariable("$MaterialFlags::NeverEnvMap",         TSMaterialList::NeverEnvMap);
+  Con::setIntVariable("$MaterialFlags::NoMipMap",            TSMaterialList::NoMipMap);
+  Con::setIntVariable("$MaterialFlags::MipMap_ZeroBorder",   TSMaterialList::MipMap_ZeroBorder);
+  Con::setIntVariable("$MaterialFlags::AuxiliaryMap",        TSMaterialList::AuxiliaryMap);
+
+#if defined(AFX_CAP_AFXMODEL_TYPE)
+  Con::setIntVariable("$TypeMasks::afxModelObjectType",      afxModelObjectType);
+#endif
+}
+
+void afxModelData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->writeString(shapeName);
+  stream->writeString(sequence);
+  stream->write(seq_rate);  
+  stream->write(seq_offset);
+  stream->write(alpha_mult); 
+  stream->write(use_vertex_alpha); 
+  stream->write(force_on_material_flags);
+  stream->write(force_off_material_flags);
+  stream->writeFlag(texture_filtering);
+  stream->write(fog_mult);
+
+  stream->writeString(remap_txr_tags);
+
+  stream->writeFlag(overrideLightingOptions);
+  stream->writeFlag(receiveSunLight);
+  stream->writeFlag(useAdaptiveSelfIllumination);
+  stream->writeFlag(useCustomAmbientLighting);
+  stream->writeFlag(customAmbientForSelfIllumination);
+  stream->write(customAmbientLighting);
+  stream->writeFlag(receiveLMLighting);
+  stream->writeFlag(shadowEnable);
+
+  stream->write(shadowSize);
+  stream->write(shadowMaxVisibleDistance);
+  stream->write(shadowProjectionDistance);
+  stream->write(shadowSphereAdjust);
+}
+
+void afxModelData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  shapeName = stream->readSTString();
+  sequence = stream->readSTString();
+  stream->read(&seq_rate);
+  stream->read(&seq_offset);
+  stream->read(&alpha_mult);
+  stream->read(&use_vertex_alpha);
+  stream->read(&force_on_material_flags);
+  stream->read(&force_off_material_flags);
+  texture_filtering = stream->readFlag();
+  stream->read(&fog_mult);
+
+  remap_txr_tags = stream->readSTString();
+
+  overrideLightingOptions = stream->readFlag();
+  receiveSunLight = stream->readFlag();
+  useAdaptiveSelfIllumination = stream->readFlag();
+  useCustomAmbientLighting = stream->readFlag();
+  customAmbientForSelfIllumination = stream->readFlag();
+  stream->read(&customAmbientLighting);
+  receiveLMLighting = stream->readFlag();
+  shadowEnable = stream->readFlag();
+
+  stream->read(&shadowSize);
+  stream->read(&shadowMaxVisibleDistance);
+  stream->read(&shadowProjectionDistance);
+  stream->read(&shadowSphereAdjust);
+}
+
+void afxModelData::onPerformSubstitutions() 
+{ 
+  if (shapeName != ST_NULLSTRING)
+  {
+    shape = ResourceManager::get().load(shapeName);
+    if (!shape)
+    {
+      Con::errorf("afxModelData::onPerformSubstitutions: Failed to load shape \"%s\"", shapeName);
+      return;
+    }
+
+    // REMAP-TEXTURE-TAGS ISSUES?
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxModel
+
+IMPLEMENT_CO_NETOBJECT_V1(afxModel);
+
+ConsoleDocClass( afxModel,
+   "@brief A Model effect as defined by an afxModelData datablock.\n\n"
+
+   "A Model effect is a lightweight client-only geometry object useful for effect-driven "
+   "props.\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxModel::afxModel()
+{
+  mTypeMask |= DynamicShapeObjectType;
+#if defined(AFX_CAP_AFXMODEL_TYPE)
+  mTypeMask |= afxModelObjectType;
+#endif
+
+  shape_inst = 0;
+
+  main_seq_thread = 0;
+  main_seq_id = -1;
+  seq_rate_factor = 1.0f;
+  last_anim_tag = 0;
+
+  seq_animates_vis = false;
+  fade_amt = 1.0f;
+  is_visible = true;
+  sort_priority = 0;
+
+  mNetFlags.set( IsGhost );
+}
+
+afxModel::~afxModel()
+{
+  delete shape_inst;
+}
+
+void afxModel::setSequenceRateFactor(F32 factor)
+{
+  seq_rate_factor = factor;
+  if (shape_inst != NULL && main_seq_thread != NULL)
+    shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxModel::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxModelData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  return true;
+}
+
+bool afxModel::onAdd()
+{
+  // first check if we have a server connection, if we don't then this is on the server
+  // and we should exit, then check if the parent fails to add the object
+  NetConnection* conn = NetConnection::getConnectionToServer();
+  if (!conn || !Parent::onAdd())
+    return false;
+
+  // setup our bounding box
+  if (mDataBlock->shape)
+    mObjBox = mDataBlock->shape->bounds;
+  else
+    mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1));
+
+  // setup the shape instance and sequence
+  if (mDataBlock->shape)
+  {
+     if (/*isClientObject() && */mDataBlock->txr_tag_remappings.size() > 0)
+     {
+        // temporarily substitute material tags with alternates
+        TSMaterialList* mat_list = mDataBlock->shape->materialList;
+        if (mat_list)
+        {
+           for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
+           {
+              afxModelData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
+              Vector<String> & mat_names = (Vector<String>&) mat_list->getMaterialNameList();
+              for (S32 j = 0; j < mat_names.size(); j++) 
+              {
+                 if (mat_names[j].compare(remap->old_tag, dStrlen(remap->old_tag), String::NoCase) == 0)
+                 {
+                    //Con::printf("REMAP TEXTURE TAG [%s] TO [%s]", remap->old_tag, remap->new_tag);
+                    mat_names[j] = String(remap->new_tag);
+                    mat_names[j].insert(0,'#');
+                    break;
+                 }
+              }
+           }
+        }
+     }
+
+    shape_inst = new TSShapeInstance(mDataBlock->shape);
+
+    if (true) // isClientObject())
+    {
+       shape_inst->cloneMaterialList();
+
+       // restore the material tags to original form
+       if (mDataBlock->txr_tag_remappings.size() > 0)
+       {
+          TSMaterialList* mat_list = mDataBlock->shape->materialList;
+          if (mat_list)
+          {
+             for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
+             {
+                afxModelData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
+                Vector<String> & mat_names = (Vector<String>&) mat_list->getMaterialNameList();
+                for (S32 j = 0; j < mat_names.size(); j++) 
+                {
+                   if (mat_names[j].compare(remap->new_tag, dStrlen(remap->new_tag)) == 0)
+                   {
+                      //Con::printf("UNREMAP TEXTURE TAG [%s] TO [%s]", remap->new_tag, remap->old_tag);
+                      mat_names[j] = String(remap->old_tag);
+                      break;
+                   }
+                }
+             }
+          }
+       }
+    }
+
+    if (mDataBlock->sequence == ST_NULLSTRING)
+    {
+      main_seq_thread = 0;
+      main_seq_id = -1;
+    }
+    else
+    {
+      // here we start the default animation sequence
+      TSShape* shape = shape_inst->getShape();
+      main_seq_id = shape->findSequence(mDataBlock->sequence);
+      if (main_seq_id != -1)
+      {      
+        main_seq_thread = shape_inst->addThread();
+      
+        F32 seq_pos = 0.0f;
+        if (mDataBlock->seq_offset > 0.0f && mDataBlock->seq_offset < shape_inst->getDuration(main_seq_thread))
+          seq_pos = mDataBlock->seq_offset / shape_inst->getDuration(main_seq_thread);
+
+        shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate);
+        shape_inst->setSequence(main_seq_thread, main_seq_id, seq_pos);
+        seq_animates_vis = shape->sequences[main_seq_id].visMatters.testAll();
+      }
+    }
+
+    // deal with material changes
+    if (shape_inst && (mDataBlock->force_on_material_flags | mDataBlock->force_off_material_flags))
+    {
+      shape_inst->cloneMaterialList();
+      TSMaterialList* mats = shape_inst->getMaterialList();
+      if (mDataBlock->force_on_material_flags != 0)
+      {
+        for (U32 i = 0; i < mats->size(); i++)
+          mats->setFlags(i, mats->getFlags(i) | mDataBlock->force_on_material_flags);
+      }
+
+      if (mDataBlock->force_off_material_flags != 0)
+      {
+        for (U32 i = 0; i < mats->size(); i++)
+          mats->setFlags(i, mats->getFlags(i) & ~mDataBlock->force_off_material_flags);
+      }
+    }
+  }
+
+  resetWorldBox();
+
+  if (mDataBlock->shape)
+  {
+    // Scan out the collision hulls...
+    static const String sCollisionStr( "collision-" );
+
+    for (U32 i = 0; i < mDataBlock->shape->details.size(); i++)
+    {
+      const String &name = mDataBlock->shape->names[mDataBlock->shape->details[i].nameIndex];
+
+      if (name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0)
+      {
+        mCollisionDetails.push_back(i);
+
+        // The way LOS works is that it will check to see if there is a LOS detail that matches
+        // the the collision detail + 1 + MaxCollisionShapes (this variable name should change in
+        // the future). If it can't find a matching LOS it will simply use the collision instead.
+        // We check for any "unmatched" LOS's further down
+        mLOSDetails.increment();
+
+        char buff[128];
+        dSprintf(buff, sizeof(buff), "LOS-%d", i + 1 + 8/*MaxCollisionShapes*/);
+        U32 los = mDataBlock->shape->findDetail(buff);
+        if (los == -1)
+          mLOSDetails.last() = i;
+        else
+          mLOSDetails.last() = los;
+      }
+    }
+
+    // Snag any "unmatched" LOS details
+    static const String sLOSStr( "LOS-" );
+
+    for (U32 i = 0; i < mDataBlock->shape->details.size(); i++)
+    {
+      const String &name = mDataBlock->shape->names[mDataBlock->shape->details[i].nameIndex];
+
+      if (name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0)
+      {
+        // See if we already have this LOS
+        bool found = false;
+        for (U32 j = 0; j < mLOSDetails.size(); j++)
+        {
+          if (mLOSDetails[j] == i)
+          {
+            found = true;
+            break;
+          }
+        }
+
+        if (!found)
+          mLOSDetails.push_back(i);
+      }
+    }
+
+    // Compute the hull accelerators (actually, just force the shape to compute them)
+    for (U32 i = 0; i < mCollisionDetails.size(); i++)
+      shape_inst->getShape()->getAccelerator(mCollisionDetails[i]);
+  }
+
+  // tell engine the model exists
+  gClientSceneGraph->addObjectToScene(this);
+  removeFromProcessList();
+  ClientProcessList::get()->addObject(this);
+  conn->addObject(this);
+
+  return true;
+}
+
+void afxModel::onRemove()
+{
+  mSceneManager->removeObjectFromScene(this);
+  getContainer()->removeObject(this);
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxModel::advanceTime(F32 dt)
+{
+  if (main_seq_thread)
+    shape_inst->advanceTime(dt, main_seq_thread);
+
+  for (S32 i = 0; i < blend_clips.size(); i++)
+    shape_inst->advanceTime(dt, blend_clips[i].thread);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxModel::prepRenderImage(SceneRenderState* state)
+{
+  if (!is_visible || !shape_inst)
+    return;
+  
+  // calculate distance to camera
+  Point3F cameraOffset;
+  getRenderTransform().getColumn(3, &cameraOffset);
+  cameraOffset -= state->getCameraPosition();   
+  F32 dist = cameraOffset.len();
+  if (dist < 0.01f)
+    dist = 0.01f;
+
+  F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));
+  shape_inst->setDetailFromDistance(state, dist*invScale);
+  if ( shape_inst->getCurrentDetail() < 0 )
+    return;
+
+  renderObject(state);
+}
+
+bool afxModel::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
+{
+  if (shape_inst)
+  {
+    RayInfo shortest;
+    shortest.t = 1e8;
+
+    info->object = NULL;
+    if (mLOSDetails.size() > 0)
+    {
+      for (U32 i = 0; i < mLOSDetails.size(); i++)
+      {
+        shape_inst->animate(mLOSDetails[i]);
+        if (shape_inst->castRay(start, end, info, mLOSDetails[i]))
+        {
+          info->object = this;
+          if (info->t < shortest.t)
+            shortest = *info;
+        }
+      }
+    }
+    else
+    {
+      if (mCollisionDetails.size() > 0)
+      {
+        for (U32 i = 0; i < mCollisionDetails.size(); i++)
+        {
+          shape_inst->animate(mCollisionDetails[i]);
+          if (shape_inst->castRay(start, end, info, mCollisionDetails[i]))
+          {
+            info->object = this;
+            if (info->t < shortest.t)
+              shortest = *info;
+          }
+        }
+      }
+    }
+
+    if (info->object == this) 
+    {
+      // Copy out the shortest time...
+      *info = shortest;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+U32 afxModel::unique_anim_tag_counter = 1;
+#define BAD_ANIM_ID  999999999
+
+U32 afxModel::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans)
+{
+  if (!shape_inst)
+    return 0;
+
+  TSShape* shape = shape_inst->getShape();
+
+  S32 seq_id = shape->findSequence(clip);
+  if (seq_id == -1)
+  {
+    Con::errorf("afxModel::setAnimClip() -- failed to find a sequence matching the name, \"%s\".", clip);
+    return 0;
+  }
+
+  // JTF Note: test if this blend implementation is working
+  if (shape->sequences[seq_id].isBlend())
+  {
+    BlendThread blend_clip;
+    blend_clip.tag = ((unique_anim_tag_counter++) | 0x80000000);
+
+    blend_clip.thread = shape_inst->addThread();
+    shape_inst->setSequence(blend_clip.thread, seq_id, pos);
+    shape_inst->setTimeScale(blend_clip.thread, rate);
+
+    blend_clips.push_back(blend_clip);
+
+    return blend_clip.tag;
+  }
+
+  if (!main_seq_thread)
+  {
+    main_seq_thread = shape_inst->addThread();
+    shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*rate);
+    shape_inst->setSequence(main_seq_thread, seq_id, pos);
+    seq_animates_vis = shape->sequences[seq_id].visMatters.testAll();
+  }
+  else
+  {
+    shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*rate);
+
+    F32 transTime = (trans < 0) ? 0.25 : trans;
+    if (transTime > 0.0f)
+      shape_inst->transitionToSequence(main_seq_thread, seq_id, pos, transTime, true);
+    else
+      shape_inst->setSequence(main_seq_thread, seq_id, pos);
+
+    seq_animates_vis = shape->sequences[seq_id].visMatters.testAll();
+  }
+
+  last_anim_tag = unique_anim_tag_counter++;
+
+  return last_anim_tag;
+}
+
+void afxModel::resetAnimation(U32 tag)
+{
+  // check if this is a blended clip
+  if ((tag & 0x80000000) != 0)
+  {
+    for (S32 i = 0; i < blend_clips.size(); i++)
+    {
+      if (blend_clips[i].tag == tag)
+      {
+        if (blend_clips[i].thread)
+        {
+          //Con::printf("DESTROY THREAD %d of %d tag=%d" , i, blend_clips.size(), tag & 0x7fffffff);
+          shape_inst->destroyThread(blend_clips[i].thread);
+        }
+        blend_clips.erase_fast(i);
+        break;
+      }
+    }
+    return;  
+  }
+
+  if (tag != 0 && tag == last_anim_tag)
+  {
+    // restore original non-animated state
+    if (main_seq_id == -1)
+    {
+      shape_inst->destroyThread(main_seq_thread);
+      main_seq_thread = 0;
+    }
+    // restore original sequence
+    else
+    {
+      shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate);
+      shape_inst->transitionToSequence(main_seq_thread, main_seq_id , 0.0f, 0.25f, true);
+    }
+    last_anim_tag = 0;
+  }
+}
+
+F32 afxModel::getAnimClipDuration(const char* clip)
+{
+  if (!shape_inst)
+    return 0.0f;
+
+  TSShape* shape = shape_inst->getShape();
+  S32 seq_id = shape->findSequence(clip);
+  return (seq_id != -1) ? shape->sequences[seq_id].duration : 0.0f;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 166 - 0
Engine/source/afx/ce/afxModel.h

@@ -0,0 +1,166 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MODEL_H_
+#define _AFX_MODEL_H_
+
+#include "renderInstance/renderPassManager.h"
+
+class ParticleEmitterData;
+class ParticleEmitter;
+class ExplosionData;
+class TSPartInstance;
+class TSShapeInstance;
+class TSShape;
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxModel Data
+
+struct afxModelData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+
+  StringTableEntry      shapeName;
+  StringTableEntry      sequence;
+  F32                   seq_rate;
+  F32                   seq_offset;
+  F32                   alpha_mult;
+  bool                  use_vertex_alpha;
+  U32                   force_on_material_flags;
+  U32                   force_off_material_flags;
+  bool                  texture_filtering;
+  F32                   fog_mult;
+
+  struct TextureTagRemapping
+  {
+     char* old_tag;
+     char* new_tag;
+  };
+  char*                 remap_buffer;
+  Vector<TextureTagRemapping> txr_tag_remappings;
+
+  StringTableEntry      remap_txr_tags;
+
+  Resource<TSShape>     shape;
+
+  bool                  overrideLightingOptions;
+  bool                  receiveSunLight;
+  bool                  receiveLMLighting;
+  bool                  useAdaptiveSelfIllumination;
+  bool                  useCustomAmbientLighting;
+  bool                  customAmbientForSelfIllumination;
+  LinearColorF                customAmbientLighting;
+  bool                  shadowEnable;
+
+  U32                   shadowSize;
+  F32                   shadowMaxVisibleDistance;
+  F32                   shadowProjectionDistance;
+  F32                   shadowSphereAdjust;
+
+public:
+  /*C*/                 afxModelData();
+  /*C*/                 afxModelData(const afxModelData&, bool = false);
+  /*D*/                 ~afxModelData();
+
+  bool                  preload(bool server, String &errorStr);
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+
+  virtual void          onPerformSubstitutions();
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxModelData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxModel
+
+class afxModel : public GameBase
+{
+  typedef GameBase Parent;
+
+private:
+  afxModelData*         mDataBlock;
+  TSShapeInstance*      shape_inst;
+  TSThread*             main_seq_thread;
+  S32                   main_seq_id;
+  F32                   seq_rate_factor;
+  bool                  seq_animates_vis;
+  U32                   last_anim_tag;
+  F32                   fade_amt;
+  bool                  is_visible;
+  S8                    sort_priority;
+
+  struct BlendThread
+  {
+    TSThread* thread;
+    U32       tag;
+  };
+  Vector<BlendThread>   blend_clips;
+  static U32            unique_anim_tag_counter;
+
+protected:
+  Vector<S32>           mCollisionDetails;
+  Vector<S32>           mLOSDetails;
+  bool                  castRay(const Point3F &start, const Point3F &end, RayInfo* info);
+
+  virtual void          advanceTime(F32 dt);
+
+  virtual void          prepRenderImage(SceneRenderState*);
+
+  void                  renderObject(SceneRenderState*);
+
+  virtual bool          onAdd();
+  virtual void          onRemove();
+
+public:
+  /*C*/                 afxModel();
+  /*D*/                 ~afxModel();
+
+  virtual bool          onNewDataBlock(GameBaseData* dptr, bool reload);
+
+  void                  setFadeAmount(F32 amt) { fade_amt = amt; }
+  void                  setSequenceRateFactor(F32 factor);
+  void                  setSortPriority(S8 priority) { sort_priority = priority; }
+
+  const char*           getShapeFileName() const { return mDataBlock->shapeName; }
+  void                  setVisibility(bool flag) { is_visible = flag; }
+  TSShape*              getTSShape() { return mDataBlock->shape; }
+  TSShapeInstance*      getTSShapeInstance() { return shape_inst; }
+
+  U32                   setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans);
+  void                  resetAnimation(U32 tag);
+  F32                   getAnimClipDuration(const char* clip);
+
+  DECLARE_CONOBJECT(afxModel);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MODEL_H_

+ 71 - 0
Engine/source/afx/ce/afxModel_T3D.cpp

@@ -0,0 +1,71 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "scene/sceneRenderState.h"
+#include "scene/sceneManager.h"
+#include "ts/tsShapeInstance.h"
+#include "lighting/lightQuery.h"
+
+#include "afx/ce/afxModel.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxModel::renderObject(SceneRenderState* state)
+{
+   MatrixF proj = GFX->getProjectionMatrix();
+   RectI viewport = GFX->getViewport();
+
+   MatrixF world = GFX->getWorldMatrix();
+
+   GFX->pushWorldMatrix();
+
+   TSRenderState rdata;
+   rdata.setSceneState( state );
+   rdata.setFadeOverride(fade_amt*mDataBlock->alpha_mult);
+
+   // We might have some forward lit materials
+   // so pass down a query to gather lights.
+   LightQuery query;
+   query.init( getWorldSphere() );
+   rdata.setLightQuery( &query );
+
+   MatrixF mat = getRenderTransform();
+   mat.scale( mObjScale );
+   GFX->setWorldMatrix( mat );
+
+   shape_inst->animate();
+
+   shape_inst->render(rdata);
+
+   GFX->popWorldMatrix();
+
+   GFX->setProjectionMatrix( proj );
+   GFX->setViewport( viewport );
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 278 - 0
Engine/source/afx/ce/afxMooring.cpp

@@ -0,0 +1,278 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/afxChoreographer.h"
+#include "afx/ce/afxMooring.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMooringData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMooringData);
+
+ConsoleDocClass( afxMooringData,
+   "@brief A datablock that specifies a Mooring effect.\n\n"
+
+   "A Mooring is an invisible effect object which can be positioned and oriented within a scene like other objects. Its main "
+   "purpose is to serve as a common mount point for other effects within the same choreographer. Typically one uses AFX "
+   "animation features to create movement for a Mooring and then other effects are bound to it using effect-to-effect "
+   "constraints (#effect)."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxMooringData::afxMooringData()
+{
+  track_pos_only = false;
+  networking = SCOPE_ALWAYS;
+  display_axis_marker = false;
+}
+
+afxMooringData::afxMooringData(const afxMooringData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  track_pos_only = other.track_pos_only;
+  networking = other.networking;
+  display_axis_marker = other.display_axis_marker;
+}
+
+#define myOffset(field) Offset(field, afxMooringData)
+
+void afxMooringData::initPersistFields()
+{
+  addField("displayAxisMarker",   TypeBool,     myOffset(display_axis_marker),
+    "Specifies whether to display an axis to help visualize the position and orientation "
+    "of the mooring.");
+  addField("trackPosOnly",        TypeBool,     myOffset(track_pos_only),
+    "This field is only meaningful for networking settings of SCOPE_ALWAYS and GHOSTABLE. "
+    "In these cases, client moorings are ghosting a mooring on the server, and "
+    "trackPosOnly determines if the client moorings need to be updated with the server "
+    "mooring's complete transform or just its position. If only the position needs to be "
+    "tracked, setting trackPosOnly to true will reduce the network traffic.");
+  addField("networking",          TypeS8,       myOffset(networking),
+    "Specifies the networking model used for the mooring and should be one of: "
+    "$AFX::SCOPE_ALWAYS, $AFX::GHOSTABLE, $AFX::SERVER_ONLY, or $AFX::CLIENT_ONLY");
+
+  Parent::initPersistFields();
+}
+
+bool afxMooringData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxMooringData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+  stream->write(display_axis_marker);
+  stream->write(track_pos_only);
+  stream->write(networking);
+}
+
+void afxMooringData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+  stream->read(&display_axis_marker);
+  stream->read(&track_pos_only);
+  stream->read(&networking);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMooring
+
+IMPLEMENT_CO_NETOBJECT_V1(afxMooring);
+
+ConsoleDocClass( afxMooring,
+   "@brief A Mooring effect as defined by an afxMooringData datablock.\n\n"
+
+   "A Mooring is an invisible effect object which can be positioned and oriented within "
+   "a scene like other objects. Its main purpose is to serve as a common mount point for "
+   "other effects within the same choreographer. Typically one uses AFX animation "
+   "features to create movement for a Mooring and then other effects are bound to it "
+   "using effect-to-effect constraints (#effect).\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxMooring::afxMooring()
+{
+  mNetFlags.set(Ghostable | ScopeAlways);
+
+  chor_id = 0;
+  hookup_with_chor = false;
+  ghost_cons_name = ST_NULLSTRING;
+}
+
+afxMooring::afxMooring(U32 networking, U32 chor_id, StringTableEntry cons_name)
+{
+  if (networking & SCOPE_ALWAYS)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(Ghostable | ScopeAlways);
+  }
+  else if (networking & GHOSTABLE)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(Ghostable);
+  }
+  else if (networking & SERVER_ONLY)
+  {
+    mNetFlags.clear();
+  }
+  else // if (networking & CLIENT_ONLY)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(IsGhost);
+  }
+
+  this->chor_id = chor_id;
+  hookup_with_chor = false;
+  this->ghost_cons_name = cons_name;
+}
+
+afxMooring::~afxMooring()
+{
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxMooring::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<afxMooringData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  return true;
+}
+
+void afxMooring::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  if (hookup_with_chor)
+  {
+    afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id);
+    if (chor)
+    {
+      chor->setGhostConstraintObject(this, ghost_cons_name);
+      hookup_with_chor = false;
+    }
+  }
+
+  Point3F pos = getRenderPosition();
+}
+
+U32 afxMooring::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask)) 
+  {
+    stream->write(chor_id);
+    stream->writeString(ghost_cons_name);
+  }
+  
+  if (stream->writeFlag(mask & PositionMask)) 
+  {
+    if (mDataBlock->track_pos_only)
+      mathWrite(*stream, mObjToWorld.getPosition());
+    else
+      stream->writeAffineTransform(mObjToWorld);
+  } 
+  
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxMooring::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+    stream->read(&chor_id);
+    ghost_cons_name = stream->readSTString();
+    
+    if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING)
+      hookup_with_chor = true;
+  }
+  
+  if (stream->readFlag()) 
+  {
+    if (mDataBlock->track_pos_only)
+    {
+      Point3F pos;
+      mathRead(*stream, &pos);
+      setPosition(pos);
+    }
+    else
+    {
+      MatrixF mat;
+      stream->readAffineTransform(&mat);
+      setTransform(mat);
+      setRenderTransform(mat);
+    }
+  }
+}
+
+void afxMooring::setTransform(const MatrixF& mat)
+{
+   Parent::setTransform(mat);
+   setMaskBits(PositionMask);
+}
+
+bool afxMooring::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  mObjBox = Box3F(Point3F(-0.5, -0.5, -0.5), Point3F(0.5, 0.5, 0.5));
+  
+  addToScene();
+  
+  return true;
+}
+
+void afxMooring::onRemove()
+{
+  removeFromScene();
+  
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 102 - 0
Engine/source/afx/ce/afxMooring.h

@@ -0,0 +1,102 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MOORING_H_
+#define _AFX_MOORING_H_
+
+#include "renderInstance/renderPassManager.h"
+
+#include "afx/afxEffectDefs.h"
+
+class afxMooringData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+
+public:
+  U8            networking;
+  bool          track_pos_only;
+  bool          display_axis_marker;
+
+public:
+  /*C*/         afxMooringData();
+  /*C*/         afxMooringData(const afxMooringData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxMooringData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxMooring
+
+class afxMooring : public GameBase, public afxEffectDefs
+{
+  typedef GameBase Parent;
+
+private:
+  afxMooringData*       mDataBlock;
+  U32                   chor_id;
+  bool                  hookup_with_chor;
+  StringTableEntry      ghost_cons_name;
+
+  GFXStateBlockRef      axis_sb;
+  void                  _renderAxisLines(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*);
+
+protected:
+   enum MaskBits 
+   {
+      PositionMask = Parent::NextFreeMask,
+	    NextFreeMask = Parent::NextFreeMask << 1
+   };
+
+public:
+  /*C*/                 afxMooring();
+  /*C*/                 afxMooring(U32 networking, U32 chor_id, StringTableEntry cons_name);
+  /*D*/                 ~afxMooring();
+
+  virtual bool          onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void          advanceTime(F32 dt);
+  virtual bool          onAdd();
+  virtual void          onRemove();
+  virtual U32           packUpdate(NetConnection*, U32, BitStream*);
+  virtual void          unpackUpdate(NetConnection*, BitStream*);
+  virtual void          setTransform(const MatrixF&);
+
+  virtual void          prepRenderImage(SceneRenderState*);
+
+  DECLARE_CONOBJECT(afxMooring);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MOORING_H_

+ 88 - 0
Engine/source/afx/ce/afxMooring_T3D.cpp

@@ -0,0 +1,88 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "gfx/gfxTransformSaver.h"
+#include "gfx/primBuilder.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxMooring.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+void afxMooring::prepRenderImage(SceneRenderState* state)
+{
+  if (!mDataBlock->display_axis_marker)
+    return;
+
+  ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+  ri->renderDelegate.bind(this, &afxMooring::_renderAxisLines);
+  ri->type = RenderPassManager::RIT_ObjectTranslucent;
+  ri->translucentSort = true;
+  ri->defaultKey = (U32)(dsize_t)mDataBlock;
+  ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() );      
+  state->getRenderPass()->addInst(ri);
+}
+
+void afxMooring::_renderAxisLines(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat)
+{
+  if (overrideMat)
+    return;
+
+  if (axis_sb.isNull())
+  {
+    GFXStateBlockDesc desc;
+
+    desc.blendDefined = true;
+    desc.blendEnable = false;
+    desc.cullDefined = true;
+    desc.cullMode = GFXCullNone;
+    desc.ffLighting = false;
+    desc.zDefined = true;
+    desc.zWriteEnable = false;
+
+    axis_sb = GFX->createStateBlock(desc);
+  }
+
+  GFX->setStateBlock(axis_sb);
+
+  GFXTransformSaver saver;
+  GFX->multWorld(getRenderTransform());
+
+	PrimBuild::begin(GFXLineList, 6);
+	PrimBuild::color(LinearColorF(1.0, 0.0, 0.0));
+	PrimBuild::vertex3f(-0.5,  0.0,  0.0);
+	PrimBuild::vertex3f( 0.5,  0.0,  0.0);
+	PrimBuild::color(LinearColorF(0.0, 1.0, 0.0));
+	PrimBuild::vertex3f( 0.0, -0.5,  0.0);
+	PrimBuild::vertex3f( 0.0,  0.5,  0.0);
+	PrimBuild::color(LinearColorF(0.0, 0.0, 1.0));
+	PrimBuild::vertex3f( 0.0,  0.0, -0.5);
+	PrimBuild::vertex3f( 0.0,  0.0,  0.5);
+	PrimBuild::end();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 40 - 0
Engine/source/afx/ce/afxMultiLight.cpp

@@ -0,0 +1,40 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "afx/ce/afxMultiLight.h"
+
+IMPLEMENT_CO_DATABLOCK_V1(afxMultiLightData);
+
+ConsoleDocClass( afxMultiLightData,
+   "@brief afxMultiLightData is a legacy datablock which is not supported for T3D.\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 38 - 0
Engine/source/afx/ce/afxMultiLight.h

@@ -0,0 +1,38 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_MULTI_LIGHT_H_
+#define _AFX_MULTI_LIGHT_H_
+
+struct afxMultiLightData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+  DECLARE_CONOBJECT(afxMultiLightData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_MULTI_LIGHT_H_

+ 1617 - 0
Engine/source/afx/ce/afxParticleEmitter.cpp

@@ -0,0 +1,1617 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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__);
+    char* tokCopy = new char[dStrlen(tpaths_string) + 1];
+    dStrcpy(tokCopy, tpaths_string);
+    
+    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__);
+    char* tokCopy = new char[dStrlen(epaths_string) + 1];
+    dStrcpy(tokCopy, epaths_string);
+
+    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__);
+    char* tokCopy = new char[dStrlen(epaths_string) + 1];
+    dStrcpy(tokCopy, epaths_string);
+
+    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()
+{
+  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()
+{
+}
+
+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()
+{
+  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()
+{
+  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()
+{
+  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);            
+  }         
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+

+ 350 - 0
Engine/source/afx/ce/afxParticleEmitter.h

@@ -0,0 +1,350 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_EMITTER_PARTICLE_H_
+#define _AFX_EMITTER_PARTICLE_H_
+
+#include "T3D/fx/particleEmitter.h"
+
+class afxPathData;
+class afxPath3D;
+
+class afxParticleEmitterData : public ParticleEmitterData
+{
+  typedef ParticleEmitterData Parent;
+
+public:
+  // The afx enhanced particle emitter allows fading
+  // of particle color, size, velocity, and/or offset.
+  // Fading is controlled by a common value which is
+  // set externally using setFadeAmount().
+  //
+  bool        fade_velocity;
+  bool        fade_offset;
+  Point3F     pe_vector;
+  // new -- consider vector in world space?
+  bool        pe_vector_is_world;
+
+  // new -- transform paths?
+  StringTableEntry      tpaths_string;       // 
+  Vector<afxPathData*>  tPathDataBlocks;     // datablocks for paths
+  Vector<U32>           tPathDataBlockIds;   // datablock IDs which correspond to the pathDataBlocks
+
+public:
+  /*C*/       afxParticleEmitterData();
+  /*C*/       afxParticleEmitterData(const afxParticleEmitterData&, bool = false);
+
+  void        packData(BitStream* stream);
+  void        unpackData(BitStream* stream);
+  bool        onAdd();
+
+  bool        preload(bool server, String &errorStr);
+
+  virtual bool allowSubstitutions() const { return true; }
+
+  static void initPersistFields();
+
+  DECLARE_CONOBJECT(afxParticleEmitterData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// VECTOR
+
+class afxParticleEmitterVectorData : public afxParticleEmitterData
+{
+  typedef afxParticleEmitterData Parent;
+
+public:
+  /*C*/       afxParticleEmitterVectorData();
+  /*C*/       afxParticleEmitterVectorData(const afxParticleEmitterVectorData&, bool = false);
+
+  void        packData(BitStream* stream);
+  void        unpackData(BitStream* stream);
+  bool        onAdd();
+
+  bool        preload(bool server, String &errorStr);
+
+  virtual bool allowSubstitutions() const { return true; }
+
+  static void initPersistFields();
+
+  DECLARE_CONOBJECT(afxParticleEmitterVectorData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// CONE
+
+class afxParticleEmitterConeData : public afxParticleEmitterData
+{
+  typedef afxParticleEmitterData Parent;
+
+public:
+  F32         spread_min;
+  F32         spread_max;
+
+public:
+  /*C*/       afxParticleEmitterConeData();
+  /*C*/       afxParticleEmitterConeData(const afxParticleEmitterConeData&, bool = false);
+
+  void        packData(BitStream* stream);
+  void        unpackData(BitStream* stream);
+  bool        onAdd();
+
+  bool        preload(bool server, String &errorStr);
+
+  virtual bool allowSubstitutions() const { return true; }
+
+  static void initPersistFields();
+
+  DECLARE_CONOBJECT(afxParticleEmitterConeData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// PATH
+
+class afxParticleEmitterPathData : public afxParticleEmitterData
+{
+  typedef afxParticleEmitterData Parent;
+
+public:
+  enum PathOriginType 
+  {
+    PATHEMIT_ORIGIN,
+    PATHEMIT_POINT,
+    PATHEMIT_VECTOR,
+    PATHEMIT_TANGENT
+  };   
+  StringTableEntry      epaths_string;       // 
+  Vector<afxPathData*>  epathDataBlocks;     // datablocks for paths
+  Vector<U32>           epathDataBlockIds;   // datablock IDs which correspond to the pathDataBlocks
+  U32                   path_origin_type;
+
+  bool                  ground_conform;
+  bool                  ground_conform_terrain;
+  bool                  ground_conform_interiors;
+  F32                   ground_conform_height;
+
+public:
+  /*C*/                 afxParticleEmitterPathData();
+  /*C*/                 afxParticleEmitterPathData(const afxParticleEmitterPathData&, bool = false);
+
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+  bool                  onAdd();
+
+  bool                  preload(bool server, String &errorStr);
+
+  virtual void          onPerformSubstitutions();
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxParticleEmitterPathData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxParticleEmitterPathData::PathOriginType afxParticleEmitterPath_OriginType;
+DefineEnumType( afxParticleEmitterPath_OriginType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// DISC
+
+class afxParticleEmitterDiscData : public afxParticleEmitterData
+{
+  typedef afxParticleEmitterData Parent;
+
+public:
+  F32                   pe_radius_min;
+  F32                   pe_radius_max;
+
+public:
+  /*C*/                 afxParticleEmitterDiscData();
+  /*C*/                 afxParticleEmitterDiscData(const afxParticleEmitterDiscData&, bool = false);
+
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+  bool                  onAdd();
+
+  bool                  preload(bool server, String &errorStr);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxParticleEmitterDiscData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+class afxParticleEmitter : public ParticleEmitter
+{
+  typedef ParticleEmitter Parent;
+
+private:
+  afxParticleEmitterData* mDataBlock;
+
+protected:
+  Point3F       pe_vector, pe_vector_norm;
+
+  // these go with the "pathsTransform" field
+  Vector<afxPath3D*> tpaths;
+  Vector<F32>        tpath_mults;
+  U32                n_tpath_points;
+  Point3F**          tpath_points;
+
+  const SimObject*   afx_owner; 
+
+  void          init_paths();
+  void          cleanup_paths();
+
+  Particle*     alloc_particle();
+  ParticleData* pick_particle_type();
+  void          afx_emitParticles(const Point3F& point, const bool useLastPosition, const Point3F& velocity, const U32 numMilliseconds);
+  void          afx_emitParticles(const Point3F& start, const Point3F& end, const Point3F& velocity, const U32 numMilliseconds);
+  void          preCompute(const MatrixF& mat);
+
+  virtual void  sub_particleUpdate(Particle*);
+  virtual void  sub_preCompute(const MatrixF& mat)=0;
+  virtual void  sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)=0;
+
+public:
+  /*C*/         afxParticleEmitter();
+  /*D*/         ~afxParticleEmitter();
+
+  virtual void  emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds);
+
+  afxParticleEmitterData* getDataBlock(){ return mDataBlock; }
+  void          setAFXOwner(const SimObject* owner) { afx_owner = owner; }
+  bool          onNewDataBlock(GameBaseData* dptr, bool reload);
+  bool          onAdd();
+  void          onRemove();
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// VECTOR
+
+class afxParticleEmitterVector : public afxParticleEmitter
+{
+  typedef afxParticleEmitter Parent;
+
+private:
+  afxParticleEmitterVectorData* mDataBlock;
+
+public:
+  /*C*/       afxParticleEmitterVector();
+  /*D*/       ~afxParticleEmitterVector();
+
+  bool        onNewDataBlock(GameBaseData* dptr, bool reload);
+
+protected:
+  void        sub_preCompute(const MatrixF& mat);
+  void        sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offse, S32 part_idxt);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// CONE
+
+class afxParticleEmitterCone : public afxParticleEmitter
+{
+  typedef afxParticleEmitter Parent;
+
+private:
+  afxParticleEmitterData* mDataBlock;
+  Point3F     cone_v, cone_s0, cone_s1;
+
+public:
+  /*C*/       afxParticleEmitterCone();
+  /*D*/       ~afxParticleEmitterCone();
+
+  bool        onNewDataBlock(GameBaseData* dptr, bool reload);
+
+protected:
+  void        sub_preCompute(const MatrixF& mat);
+  void        sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// PATH
+
+class afxParticleEmitterPath : public afxParticleEmitter
+{
+  typedef afxParticleEmitter Parent;
+
+private:
+  afxParticleEmitterPathData* mDataBlock;
+
+  Vector<afxPath3D*> epaths;
+  Vector<F32>        epath_mults;
+  U32                n_epath_points;
+  Point3F**          epath_points;
+
+  void               init_paths();
+  void               cleanup_paths();
+
+  void               groundConformPoint(Point3F& point, const MatrixF& mat);
+
+public:
+  /*C*/       afxParticleEmitterPath();
+  /*D*/       ~afxParticleEmitterPath();
+
+  bool        onNewDataBlock(GameBaseData* dptr, bool reload);
+
+protected:
+  bool        onAdd();
+  void        onRemove();
+  void        sub_preCompute(const MatrixF& mat);
+  void        sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// DISC
+
+class afxParticleEmitterDisc : public afxParticleEmitter
+{
+  typedef afxParticleEmitter Parent;
+
+private:
+  afxParticleEmitterDiscData* mDataBlock;
+  Point3F     disc_v, disc_r;
+
+public:
+  /*C*/       afxParticleEmitterDisc();
+  /*D*/       ~afxParticleEmitterDisc();
+
+  bool        onNewDataBlock(GameBaseData* dptr, bool reload);
+
+protected:
+  void        sub_preCompute(const MatrixF& mat);
+  void        sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_EMITTER_PARTICLE_H_

+ 312 - 0
Engine/source/afx/ce/afxPhraseEffect.cpp

@@ -0,0 +1,312 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/engineAPI.h"
+
+#include "afx/afxEffectWrapper.h"
+#include "afx/ce/afxPhraseEffect.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhraseEffectData::ewValidator
+//
+// When an effect is added using "addEffect", this validator intercepts the value
+// and adds it to the dynamic effects list. 
+//
+void afxPhraseEffectData::ewValidator::validateType(SimObject* object, void* typePtr)
+{
+  afxPhraseEffectData* eff_data = dynamic_cast<afxPhraseEffectData*>(object);
+  afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr);
+
+  if (eff_data && ew)
+  {
+    eff_data->fx_list.push_back(*ew);
+    *ew = 0;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhraseEffectData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxPhraseEffectData);
+
+ConsoleDocClass( afxPhraseEffectData,
+   "@brief A datablock that specifies a Phrase Effect, a grouping of other effects.\n\n"
+
+   "A Phrase Effect is a grouping or phrase of effects that do nothing until certain trigger events occur. It's like having a whole "
+   "Effectron organized as an individual effect."
+   "\n\n"
+
+   "Phrase effects can respond to a number of different kinds of triggers:\n"
+   "  -- Player triggers such as footsteps, jumps, landings, and idle triggers.\n"
+   "  -- Arbitrary animation triggers on dts-based scene objects.\n"
+   "  -- Arbitrary trigger bits assigned to active choreographer objects."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxPhraseEffectData::afxPhraseEffectData()
+{
+  duration = 0.0f;
+  n_loops = 1;
+
+  // dummy entry holds effect-wrapper pointer while a special validator
+  // grabs it and adds it to an appropriate effects list
+  dummy_fx_entry = NULL;
+
+  // marked true if datablock ids need to
+  // be converted into pointers
+  do_id_convert = false;
+
+  trigger_mask = 0;
+  match_type = MATCH_ANY;
+  match_state = STATE_ON;
+  phrase_type = PHRASE_TRIGGERED;
+
+  no_choreographer_trigs = false;
+  no_cons_trigs = false;
+  no_player_trigs = false;
+
+  on_trig_cmd = ST_NULLSTRING;
+}
+
+afxPhraseEffectData::afxPhraseEffectData(const afxPhraseEffectData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  duration = other.duration;
+  n_loops = other.n_loops;
+  dummy_fx_entry = other.dummy_fx_entry;
+  do_id_convert = other.do_id_convert; // --
+  trigger_mask = other.trigger_mask;
+  match_type = other.match_type;
+  match_state = other.match_state;
+  phrase_type = other.phrase_type;
+  no_choreographer_trigs = other.no_choreographer_trigs;
+  no_cons_trigs = other.no_cons_trigs;
+  no_player_trigs = other.no_player_trigs;
+  on_trig_cmd = other.on_trig_cmd;
+
+  // fx_list; // -- ??
+}
+
+void afxPhraseEffectData::reloadReset()
+{
+  fx_list.clear();
+}
+
+ImplementEnumType( afxPhraseEffect_MatchType, "Possible phrase effect match types.\n" "@ingroup afxPhraseEffect\n\n" )
+   { afxPhraseEffectData::MATCH_ANY,   "any",      "..." },
+   { afxPhraseEffectData::MATCH_ALL,   "all",      "..." },
+EndImplementEnumType;
+
+ImplementEnumType( afxPhraseEffect_StateType, "Possible phrase effect state types.\n" "@ingroup afxPhraseEffect\n\n" )
+   { afxPhraseEffectData::STATE_ON,           "on",      "..." },
+   { afxPhraseEffectData::STATE_OFF,          "off",     "..." },
+   { afxPhraseEffectData::STATE_ON_AND_OFF,   "both",    "..." },
+EndImplementEnumType;
+
+ImplementEnumType( afxPhraseEffect_PhraseType, "Possible phrase effect types.\n" "@ingroup afxPhraseEffect\n\n" )
+   { afxPhraseEffectData::PHRASE_TRIGGERED,   "triggered",     "..." },
+   { afxPhraseEffectData::PHRASE_CONTINUOUS,  "continuous",    "..." },
+EndImplementEnumType;
+
+#define myOffset(field) Offset(field, afxPhraseEffectData)
+
+void afxPhraseEffectData::initPersistFields()
+{
+  addField("duration",    TypeF32,      myOffset(duration),
+    "Specifies a duration for the phrase-effect. If set to infinity, the phrase-effect "
+    "needs to have a phraseType of continuous. Set infinite duration using "
+    "$AFX::INFINITE_TIME.");
+  addField("numLoops",    TypeS32,      myOffset(n_loops),
+    "Specifies the number of times the phrase-effect should loop. If set to infinity, "
+    "the phrase-effect needs to have a phraseType of continuous. Set infinite looping "
+    "using $AFX::INFINITE_REPEATS.");
+  addField("triggerMask", TypeS32,      myOffset(trigger_mask),
+    "Sets which bits to consider in the current trigger-state which consists of 32 "
+    "trigger-bits combined from (possibly overlapping) player trigger bits, constraint "
+    "trigger bits, and choreographer trigger bits.");
+
+  addField("matchType", TYPEID<afxPhraseEffectData::MatchType>(), myOffset(match_type),
+    "Selects what combination of bits in triggerMask lead to a trigger. When set to "
+    "'any', any bit in triggerMask matching the current trigger-state will cause a "
+    "trigger. If set to 'all', every bit in triggerMask must match the trigger-state. "
+    "Possible values: any or all.");
+  addField("matchState", TYPEID<afxPhraseEffectData::StateType>(), myOffset(match_state),
+    "Selects which bit-state(s) of bits in the triggerMask to consider when comparing to "
+    "the current trigger-state. Possible values: on, off, or both.");
+  addField("phraseType", TYPEID<afxPhraseEffectData::PhraseType>(), myOffset(phrase_type),
+    "Selects between triggered and continuous types of phrases. When set to 'triggered', "
+    "the phrase-effect is triggered when the relevant trigger-bits change state. When set "
+    "to 'continuous', the phrase-effect will stay active as long as the trigger-bits "
+    "remain in a matching state. Possible values: triggered or continuous.");
+
+  addField("ignoreChoreographerTriggers",   TypeBool,  myOffset(no_choreographer_trigs),
+    "When true, trigger-bits on the choreographer will be ignored.");
+  addField("ignoreConstraintTriggers",      TypeBool,  myOffset(no_cons_trigs),
+    "When true, animation triggers from dts-based constraint source objects will be "
+    "ignored.");
+  addField("ignorePlayerTriggers",          TypeBool,  myOffset(no_player_trigs),
+    "When true, Player-specific triggers from Player-derived constraint source objects "
+    "will be ignored.");
+
+  addField("onTriggerCommand",    TypeString,   myOffset(on_trig_cmd),
+    "Like a field substitution statement without the leading '$$' token, this eval "
+    "statement will be executed when a trigger occurs. Any '%%' and '##'  tokens will be "
+    "substituted.");
+
+  // effect lists
+  // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list 
+  static ewValidator emptyValidator(0);  
+  addFieldV("addEffect",  TYPEID< afxEffectBaseData >(),  myOffset(dummy_fx_entry), &emptyValidator,
+    "A field macro which adds an effect wrapper datablock to a list of effects associated "
+    "with the phrase-effect's single phrase. Unlike other fields, addEffect follows an "
+    "unusual syntax. Order is important since the effects will resolve in the order they "
+    "are added to each list.");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("addEffect");
+}
+
+bool afxPhraseEffectData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxPhraseEffectData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed)
+{
+  stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < fx.size(); i++)
+    writeDatablockID(stream, fx[i], packed);
+}
+
+void afxPhraseEffectData::unpack_fx(BitStream* stream, afxEffectList& fx)
+{
+  fx.clear();
+  S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS);
+  for (int i = 0; i < n_fx; i++)
+    fx.push_back((afxEffectWrapperData*)readDatablockID(stream));
+}
+
+void afxPhraseEffectData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(duration);
+  stream->write(n_loops);
+  stream->write(trigger_mask);
+  stream->writeInt(match_type, 1);
+  stream->writeInt(match_state, 2);
+  stream->writeInt(phrase_type, 1);
+
+  stream->writeFlag(no_choreographer_trigs);
+  stream->writeFlag(no_cons_trigs);
+  stream->writeFlag(no_player_trigs);
+
+  stream->writeString(on_trig_cmd);
+
+  pack_fx(stream, fx_list, packed);
+}
+
+void afxPhraseEffectData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&duration);
+  stream->read(&n_loops);
+  stream->read(&trigger_mask);
+  match_type = stream->readInt(1);
+  match_state = stream->readInt(2);
+  phrase_type = stream->readInt(1);
+
+  no_choreographer_trigs = stream->readFlag();
+  no_cons_trigs = stream->readFlag();
+  no_player_trigs = stream->readFlag();
+
+  on_trig_cmd = stream->readSTString();
+
+  do_id_convert = true;
+  unpack_fx(stream, fx_list);
+}
+
+bool afxPhraseEffectData::preload(bool server, String &errorStr)
+{
+  if (!Parent::preload(server, errorStr))
+    return false;
+
+  // Resolve objects transmitted from server
+  if (!server) 
+  {
+    if (do_id_convert)
+    {
+      for (S32 i = 0; i < fx_list.size(); i++)
+      {
+        SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]);
+        if (db_id != 0)
+        {
+          // try to convert id to pointer
+          if (!Sim::findObject(db_id, fx_list[i]))
+          {
+            Con::errorf(ConsoleLogEntry::General, 
+              "afxPhraseEffectData::preload() -- bad datablockId: 0x%x", 
+              db_id);
+          }
+        }
+      }
+      do_id_convert = false;
+    }
+  }
+
+  return true;
+}
+
+void afxPhraseEffectData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{
+  afxConstraintDef::gather_cons_defs(defs, fx_list);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+DefineEngineMethod( afxPhraseEffectData, addEffect, void, ( afxEffectBaseData* effectData ),,
+   "Add a child effect to a phrase effect datablock. Argument can be an afxEffectWrappperData or an afxEffectGroupData.\n" )
+{
+  if (!effectData) 
+  {
+    Con::errorf("afxPhraseEffectData::addEffect() -- failed to resolve effect datablock.");
+    return;
+  }
+
+  object->fx_list.push_back(effectData);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 120 - 0
Engine/source/afx/ce/afxPhraseEffect.h

@@ -0,0 +1,120 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PHRASE_EFFECT_H_
+#define _AFX_PHRASE_EFFECT_H_
+
+#include "console/typeValidators.h"
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxEffectDefs.h"
+#include "afx/afxEffectWrapper.h"
+#include "afx/afxPhrase.h"
+
+class afxPhraseEffectData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData
+{
+  typedef GameBaseData  Parent;
+
+  class ewValidator : public TypeValidator
+  {
+    U32 id;
+  public:
+    ewValidator(U32 id) { this->id = id; }
+    void validateType(SimObject *object, void *typePtr);
+  };
+
+  bool            do_id_convert;
+
+public:
+  enum MatchType {
+    MATCH_ANY = 0,
+    MATCH_ALL = 1
+  };
+  enum StateType {
+    STATE_ON = 1,
+    STATE_OFF = 2,
+    STATE_ON_AND_OFF = STATE_ON | STATE_OFF
+  };
+  enum PhraseType
+  {
+    PHRASE_TRIGGERED = 0,
+    PHRASE_CONTINUOUS = 1
+  };
+
+public:
+  afxEffectList fx_list;
+  F32           duration;
+  S32           n_loops;
+  U32           trigger_mask;
+  U32           match_type;
+  U32           match_state;
+  U32           phrase_type;
+
+  bool          no_choreographer_trigs;
+  bool          no_cons_trigs;
+  bool          no_player_trigs;
+
+  StringTableEntry   on_trig_cmd;
+
+  afxEffectBaseData* dummy_fx_entry;
+  
+private:
+  void            pack_fx(BitStream* stream, const afxEffectList& fx, bool packed);
+  void            unpack_fx(BitStream* stream, afxEffectList& fx);
+
+public:
+  /*C*/           afxPhraseEffectData();
+  /*C*/           afxPhraseEffectData(const afxPhraseEffectData&, bool = false);
+
+  virtual void    reloadReset();
+
+  virtual bool    onAdd();
+  virtual void    packData(BitStream*);
+  virtual void    unpackData(BitStream*);
+
+  bool            preload(bool server, String &errorStr);
+
+  virtual void    gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+  virtual bool    allowSubstitutions() const { return true; }
+
+  static void     initPersistFields();
+
+  DECLARE_CONOBJECT(afxPhraseEffectData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxPhraseEffectData::MatchType afxPhraseEffect_MatchType;
+DefineEnumType( afxPhraseEffect_MatchType );
+
+typedef afxPhraseEffectData::StateType afxPhraseEffect_StateType;
+DefineEnumType( afxPhraseEffect_StateType );
+
+typedef afxPhraseEffectData::PhraseType afxPhraseEffect_PhraseType;
+DefineEnumType( afxPhraseEffect_PhraseType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PHRASE_EFFECT_H_

+ 114 - 0
Engine/source/afx/ce/afxPhysicalZone.cpp

@@ -0,0 +1,114 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "T3D/physicalZone.h"
+
+#include "afx/ce/afxPhysicalZone.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhysicalZoneData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxPhysicalZoneData);
+
+ConsoleDocClass( afxPhysicalZoneData,
+   "@brief A datablock that specifies a PhysicalZone effect.\n\n"
+
+   "A Physical Zone is a Torque effect that applies physical forces to Players and other movable objects that enter a specific "
+   "region of influence. AFX has enhanced Physical Zones by allowing orientation of vector forces and adding radial forces. "
+   "AFX has also optimized Physical Zone networking so that they can be constrained to moving objects for a variety of "
+   "effects including repelling and flying."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxPhysicalZoneData::afxPhysicalZoneData()
+{
+  mVelocityMod = 1.0f;
+  mGravityMod = 1.0f;
+  mAppliedForce.zero();
+  mPolyhedron = ST_NULLSTRING;
+  force_type = PhysicalZone::VECTOR;
+  orient_force = false;
+  exclude_cons_obj = false;
+}
+
+afxPhysicalZoneData::afxPhysicalZoneData(const afxPhysicalZoneData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  mVelocityMod = other.mVelocityMod;
+  mGravityMod = other.mGravityMod;
+  mAppliedForce = other.mAppliedForce;
+  mPolyhedron = other.mPolyhedron;
+  force_type = other.force_type;
+  orient_force = other.orient_force;
+  exclude_cons_obj = other.exclude_cons_obj;
+}
+
+#define myOffset(field) Offset(field, afxPhysicalZoneData)
+
+void afxPhysicalZoneData::initPersistFields()
+{
+  addField("velocityMod",               TypeF32,         myOffset(mVelocityMod),
+    "A multiplier that biases the velocity of an object every tick it is within the "
+    "zone.");
+  addField("gravityMod",                TypeF32,         myOffset(mGravityMod),
+    "A multiplier that biases the influence of gravity on objects within the zone.");
+  addField("appliedForce",              TypePoint3F,     myOffset(mAppliedForce),
+    "A three-valued vector representing a directional force applied to objects withing "
+    "the zone.");
+  addField("polyhedron",                TypeString,      myOffset(mPolyhedron),
+    "Floating point values describing the outer bounds of the PhysicalZone's region of "
+    "influence.");
+
+  addField("forceType", TYPEID<PhysicalZone::ForceType>(), myOffset(force_type),
+    "This enumerated attribute defines the type of force used in the PhysicalZone. "
+    "Possible values: vector, sphere, or cylinder.");
+
+  addField("orientForce",               TypeBool,        myOffset(orient_force),
+    "Determines if the force can be oriented by the PhysicalZone's transform matrix.");
+  addField("excludeConstraintObject",   TypeBool,        myOffset(exclude_cons_obj),
+    "When true, an object used as the primary position constraint of a physical-zone "
+    "effect will not be influenced by the forces of the zone.");
+
+  Parent::initPersistFields();
+}
+
+void afxPhysicalZoneData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+}
+
+void afxPhysicalZoneData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 65 - 0
Engine/source/afx/ce/afxPhysicalZone.h

@@ -0,0 +1,65 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PHYSICAL_ZONE_H_
+#define _AFX_PHYSICAL_ZONE_H_
+
+#include "T3D/gameBase/gameBase.h"
+#include "T3D/trigger.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPhysicalZoneData
+
+class afxPhysicalZoneData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+
+public:
+   F32              mVelocityMod;
+   F32              mGravityMod;
+   Point3F          mAppliedForce;
+   StringTableEntry mPolyhedron;
+   S32              force_type;
+   bool             orient_force;
+   bool             exclude_cons_obj;
+
+public:
+  /*C*/             afxPhysicalZoneData();
+  /*C*/             afxPhysicalZoneData(const afxPhysicalZoneData&, bool = false);
+
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxPhysicalZoneData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PHYSICAL_ZONE_H_

+ 140 - 0
Engine/source/afx/ce/afxPlayerMovement.cpp

@@ -0,0 +1,140 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "scene/sceneRenderState.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxPlayerMovement.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPlayerMovementData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxPlayerMovementData);
+
+ConsoleDocClass( afxPlayerMovementData,
+   "@brief A datablock that specifies a Player Movement effect.\n\n"
+
+   "Player Movement effects are used to directly alter the speed and/or movement direction of Player objects. The Player "
+   "Movement effect is similar to the Player Puppet effect, but where puppet effects totally take over a Player's transformation "
+   "using the AFX constraint system, Player Movement effects 'steer' the player using the same mechanisms that allow "
+   "Player control from mouse and keyboard input. Another difference is that Player Movement effects only influence the "
+   "server instance of a Player. Puppet effects can influence both the Player's server instance and its client ghosts."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+static Point3F default_movement(F32_MAX, F32_MAX, F32_MAX);
+
+afxPlayerMovementData::afxPlayerMovementData()
+{
+  speed_bias = 1.0f;
+  movement = default_movement;
+  movement_op = OP_MULTIPLY;
+}
+
+afxPlayerMovementData::afxPlayerMovementData(const afxPlayerMovementData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  speed_bias = other.speed_bias;
+  movement = other.movement;
+  movement_op = other.movement_op;
+}
+
+ImplementEnumType( afxPlayerMovement_OpType, "Possible player movement operation types.\n" "@ingroup afxPlayerMovemen\n\n" )  
+  { afxPlayerMovementData::OP_ADD,      "add",        "..." },
+  { afxPlayerMovementData::OP_MULTIPLY, "multiply",   "..." },
+  { afxPlayerMovementData::OP_REPLACE,  "replace",    "..." },
+  { afxPlayerMovementData::OP_MULTIPLY, "mult",       "..." },
+EndImplementEnumType;
+
+#define myOffset(field) Offset(field, afxPlayerMovementData)
+
+void afxPlayerMovementData::initPersistFields()
+{
+  addField("speedBias",     TypeF32,        myOffset(speed_bias),
+    "A floating-point multiplier that scales the constraint Player's movement speed.");
+  addField("movement",      TypePoint3F,    myOffset(movement),
+    "");
+  addField("movementOp", TYPEID<afxPlayerMovementData::OpType>(), myOffset(movement_op),
+    "Possible values: add, multiply, or replace.");
+
+  Parent::initPersistFields();
+}
+
+bool afxPlayerMovementData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxPlayerMovementData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write(speed_bias);
+  if (stream->writeFlag(movement != default_movement))
+  {
+    stream->write(movement.x);
+    stream->write(movement.y);
+    stream->write(movement.z);
+    stream->writeInt(movement_op, OP_BITS);
+  }
+}
+
+void afxPlayerMovementData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&speed_bias);
+  if (stream->readFlag())
+  {
+    stream->read(&movement.x);
+    stream->read(&movement.y);
+    stream->read(&movement.z);
+    movement_op = stream->readInt(OP_BITS);
+  }
+  else
+  {
+    movement = default_movement;
+    movement_op = OP_MULTIPLY;
+  }
+}
+
+bool afxPlayerMovementData::hasMovementOverride()
+{
+  return (movement != default_movement);
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 73 - 0
Engine/source/afx/ce/afxPlayerMovement.h

@@ -0,0 +1,73 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PLAYER_MOVEMENT_H_
+#define _AFX_PLAYER_MOVEMENT_H_
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxEffectDefs.h"
+#include "afx/afxConstraint.h"
+
+class afxPlayerMovementData : public GameBaseData, public afxEffectDefs
+{
+  typedef GameBaseData  Parent;
+public:
+  enum OpType
+  {
+    OP_ADD = 0,
+    OP_MULTIPLY,
+    OP_REPLACE,
+    OP_BITS = 2,
+  };
+
+public:
+  F32               speed_bias;
+  Point3F           movement;
+  U32               movement_op;
+
+public:
+  /*C*/             afxPlayerMovementData();
+  /*C*/             afxPlayerMovementData(const afxPlayerMovementData&, bool = false);
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  bool              hasMovementOverride();
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxPlayerMovementData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxPlayerMovementData::OpType afxPlayerMovement_OpType;
+DefineEnumType( afxPlayerMovement_OpType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PLAYER_MOVEMENT_H_

+ 122 - 0
Engine/source/afx/ce/afxPlayerPuppet.cpp

@@ -0,0 +1,122 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "scene/sceneRenderState.h"
+#include "math/mathIO.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxPlayerPuppet.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxPlayerPuppetData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxPlayerPuppetData);
+
+ConsoleDocClass( afxPlayerPuppetData,
+   "@brief A datablock that specifies a Player Puppet effect.\n\n"
+
+   "Player Puppet effects are defined using the afxPlayerPuppetData datablock and are used to control the movement of "
+   "Player objects with the AFX constraint system. The Player Puppet effect is similar to the Player Movement effect, but "
+   "where movement effects 'steer' the player using the same mechanisms that allow Player control from mouse and keyboard "
+   "input, Player Puppet effects totally take over a Player's transformation using the AFX constraint system."
+   "\n\n"
+
+   "Player Puppet can be configured to directly move a Player's client ghosts as well as its server instance. When doing this, "
+   "it is important to keep the general motion of the Player object and its ghosts somewhat consistent. Otherwise, obvious "
+   "discontinuities in the motion will result when the Player Puppet ends and control is restored to the server Player."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxPlayerPuppetData::afxPlayerPuppetData()
+{
+  obj_spec = ST_NULLSTRING;
+  networking = SERVER_ONLY;
+}
+
+afxPlayerPuppetData::afxPlayerPuppetData(const afxPlayerPuppetData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  obj_spec = other.obj_spec;
+  networking = other.networking;
+}
+
+#define myOffset(field) Offset(field, afxPlayerPuppetData)
+
+void afxPlayerPuppetData::initPersistFields()
+{
+  addField("objectSpec",      TypeString,   myOffset(obj_spec),
+    "...");
+  addField("networking",      TypeS8,       myOffset(networking),
+    "...");
+
+  Parent::initPersistFields();
+
+  // disallow some field substitutions
+  disableFieldSubstitutions("objectSpec");
+  disableFieldSubstitutions("networking");
+}
+
+bool afxPlayerPuppetData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0);
+  bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0);
+  obj_def.parseSpec(obj_spec, runs_on_s, runs_on_c);
+
+  return true;
+}
+
+void afxPlayerPuppetData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->writeString(obj_spec);
+  stream->write(networking);
+}
+
+void afxPlayerPuppetData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  obj_spec = stream->readSTString();
+  stream->read(&networking);
+}
+
+void afxPlayerPuppetData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (obj_def.isDefined())
+    defs.push_back(obj_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 64 - 0
Engine/source/afx/ce/afxPlayerPuppet.h

@@ -0,0 +1,64 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PLAYER_PUPPET_H_
+#define _AFX_PLAYER_PUPPET_H_
+
+#include "afx/ce/afxComponentEffect.h"
+#include "afx/afxEffectDefs.h"
+#include "afx/afxConstraint.h"
+
+class afxPlayerPuppetData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData
+{
+  typedef GameBaseData  Parent;
+
+public:
+  StringTableEntry  obj_spec;
+  afxConstraintDef  obj_def;
+
+  U8                networking;
+
+  virtual void      gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+public:
+  /*C*/             afxPlayerPuppetData();
+  /*C*/             afxPlayerPuppetData(const afxPlayerPuppetData&, bool = false);
+
+  virtual bool      onAdd();
+  virtual void      packData(BitStream*);
+  virtual void      unpackData(BitStream*);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxPlayerPuppetData);
+  DECLARE_CATEGORY("AFX");
+};
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PLAYER_PUPPET_H_

+ 103 - 0
Engine/source/afx/ce/afxPointLight_T3D.cpp

@@ -0,0 +1,103 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxPointLight_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxT3DPointLightData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxT3DPointLightData);
+
+ConsoleDocClass( afxT3DPointLightData,
+   "@brief A datablock that specifies a dynamic Point Light effect.\n\n"
+
+   "A Point Light effect that uses the T3D PointLight object. afxT3DPointLightData has the same fields found in PointLight but "
+   "in a datablock structure."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxT3DPointLightData::afxT3DPointLightData()
+   : mRadius( 5.0f )
+{
+}
+
+afxT3DPointLightData::afxT3DPointLightData(const afxT3DPointLightData& other, bool temp_clone) : afxT3DLightBaseData(other, temp_clone)
+{
+  mRadius = other.mRadius;
+}
+
+//
+// NOTE: keep this as consistent as possible with PointLight::initPersistFields()
+//
+void afxT3DPointLightData::initPersistFields()
+{
+   addGroup( "Light" );
+      
+      addField( "radius", TypeF32, Offset( mRadius, afxT3DPointLightData ),
+        "Controls the falloff of the light emission");
+
+   endGroup( "Light" );
+
+   // We do the parent fields at the end so that
+   // they show up that way in the inspector.
+   Parent::initPersistFields();
+
+   // Remove the scale field... it's already 
+   // defined by the light radius.
+   removeField( "scale" );
+}
+
+bool afxT3DPointLightData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxT3DPointLightData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write( mRadius );
+}
+
+void afxT3DPointLightData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read( &mRadius );
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 56 - 0
Engine/source/afx/ce/afxPointLight_T3D.h

@@ -0,0 +1,56 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_T3D_POINT_LIGHT_H_
+#define _AFX_T3D_POINT_LIGHT_H_
+
+#include "afx/ce/afxLightBase_T3D.h"
+
+class afxT3DPointLightData : public afxT3DLightBaseData
+{
+  typedef afxT3DLightBaseData  Parent;
+
+public:
+  F32           mRadius;
+
+public:
+  /*C*/         afxT3DPointLightData();
+  /*C*/         afxT3DPointLightData(const afxT3DPointLightData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void initPersistFields();
+
+  DECLARE_CONOBJECT(afxT3DPointLightData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_T3D_POINT_LIGHT_H_

+ 371 - 0
Engine/source/afx/ce/afxProjectile.cpp

@@ -0,0 +1,371 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "T3D/shapeBase.h"
+
+#include "afx/ce/afxProjectile.h"
+#include "afx/afxChoreographer.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxProjectileData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxProjectileData);
+
+ConsoleDocClass( afxProjectileData,
+   "@brief A datablock that specifies a Projectile effect.\n\n"
+
+   "afxProjectileData inherits from ProjectileData and adds some AFX specific fields."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxProjectileData::afxProjectileData()
+{
+  networking = GHOSTABLE;
+  launch_pos_spec = ST_NULLSTRING;
+  launch_dir_bias.zero();
+  ignore_src_timeout = false;
+  dynamicCollisionMask = 0;
+  staticCollisionMask = 0;
+  override_collision_masks = false;
+  launch_dir_method = TowardPos2Constraint;
+}
+
+afxProjectileData::afxProjectileData(const afxProjectileData& other, bool temp_clone) : ProjectileData(other, temp_clone)
+{
+  networking = other.networking;
+  launch_pos_spec = other.launch_pos_spec;
+  launch_pos_def = other.launch_pos_def;
+  launch_dir_bias = other.launch_dir_bias;
+  ignore_src_timeout = other.ignore_src_timeout;
+  dynamicCollisionMask = other.dynamicCollisionMask;
+  staticCollisionMask = other.staticCollisionMask;
+  override_collision_masks = other.override_collision_masks;
+  launch_dir_method = other.launch_dir_method;
+}
+
+ImplementEnumType( afxProjectile_LaunchDirType, "Possible projectile launch direction types.\n" "@ingroup afxProjectile\n\n" )
+   { afxProjectileData::TowardPos2Constraint,  "towardPos2Constraint",  "..." },
+   { afxProjectileData::OrientConstraint,      "orientConstraint",      "..." },
+   { afxProjectileData::LaunchDirField,        "launchDirField",        "..." },
+EndImplementEnumType;
+
+#define myOffset(field) Offset(field, afxProjectileData)
+
+void afxProjectileData::initPersistFields()
+{
+  addField("networking",              TypeS8,       myOffset(networking),
+    "...");
+  addField("launchPosSpec",           TypeString,   myOffset(launch_pos_spec),
+    "...");
+  addField("launchDirBias",           TypePoint3F,  myOffset(launch_dir_bias),
+    "...");
+  addField("ignoreSourceTimeout",     TypeBool,     myOffset(ignore_src_timeout),
+    "...");
+  addField("dynamicCollisionMask",    TypeS32,      myOffset(dynamicCollisionMask),
+    "...");
+  addField("staticCollisionMask",     TypeS32,      myOffset(staticCollisionMask),
+    "...");
+  addField("overrideCollisionMasks",  TypeBool,     myOffset(override_collision_masks),
+    "...");
+
+  addField("launchDirMethod", TYPEID<afxProjectileData::LaunchDirType>(), myOffset(launch_dir_method),
+    "Possible values: towardPos2Constraint, orientConstraint, or launchDirField.");
+
+  Parent::initPersistFields();
+}
+
+bool afxProjectileData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0);
+  bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0);
+  launch_pos_def.parseSpec(launch_pos_spec, runs_on_s, runs_on_c);
+
+  return true;
+}
+
+void afxProjectileData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->write(networking);
+  stream->writeString(launch_pos_spec);
+  if (stream->writeFlag(!launch_dir_bias.isZero()))
+  {
+    stream->write(launch_dir_bias.x);  
+    stream->write(launch_dir_bias.y);  
+    stream->write(launch_dir_bias.z);  
+  }
+  stream->writeFlag(ignore_src_timeout);
+  if (stream->writeFlag(override_collision_masks))
+  {
+    stream->write(dynamicCollisionMask);  
+    stream->write(staticCollisionMask);  
+  }
+
+  stream->writeInt(launch_dir_method, 2);
+}
+
+void afxProjectileData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read(&networking);
+  launch_pos_spec = stream->readSTString();
+  if (stream->readFlag())
+  {
+    stream->read(&launch_dir_bias.x);  
+    stream->read(&launch_dir_bias.y);  
+    stream->read(&launch_dir_bias.z);  
+  }
+  else
+    launch_dir_bias.zero();
+  ignore_src_timeout = stream->readFlag();
+  if ((override_collision_masks = stream->readFlag()) == true)
+  {
+    stream->read(&dynamicCollisionMask);  
+    stream->read(&staticCollisionMask);  
+  }
+  else
+  {
+    dynamicCollisionMask = 0;
+    staticCollisionMask = 0;
+  }
+
+  launch_dir_method = (U32) stream->readInt(2);
+}
+
+void afxProjectileData::gather_cons_defs(Vector<afxConstraintDef>& defs)
+{ 
+  if (launch_pos_def.isDefined())
+    defs.push_back(launch_pos_def);
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxProjectile
+
+IMPLEMENT_CO_NETOBJECT_V1(afxProjectile);
+
+ConsoleDocClass( afxProjectile,
+   "@brief A Projectile effect as defined by an afxProjectileData datablock.\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxProjectile::afxProjectile()
+{
+  chor_id = 0;
+  hookup_with_chor = false;
+  ghost_cons_name = ST_NULLSTRING;
+  client_only = false;
+}
+
+afxProjectile::afxProjectile(U32 networking, U32 chor_id, StringTableEntry cons_name)
+{
+  if (networking & SCOPE_ALWAYS)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(Ghostable | ScopeAlways);
+    client_only = false;
+  }
+  else if (networking & GHOSTABLE)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(Ghostable);
+    client_only = false;
+  }
+  else if (networking & SERVER_ONLY)
+  {
+    mNetFlags.clear();
+    client_only = false;
+  }
+  else // if (networking & CLIENT_ONLY)
+  {
+    mNetFlags.clear();
+    mNetFlags.set(IsGhost);
+    client_only = true;
+  }
+
+  this->chor_id = chor_id;
+  hookup_with_chor = false;
+  this->ghost_cons_name = cons_name;
+}
+
+afxProjectile::~afxProjectile()
+{
+}
+
+void afxProjectile::init(Point3F& pos, Point3F& vel, ShapeBase* src_obj)
+{
+  mCurrPosition = pos;
+  mCurrVelocity = vel;
+  if (src_obj)
+  {
+    mSourceObject = src_obj;
+    mSourceObjectId = src_obj->getId();
+    mSourceObjectSlot = 0;
+  }
+
+  setPosition(mCurrPosition);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxProjectile::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  return Parent::onNewDataBlock(dptr, reload);
+}
+
+void afxProjectile::processTick(const Move* move)
+{
+  // note: this deletion test must occur before calling to the parent's
+  // processTick() because if this is a server projectile, the parent
+  // might decide to delete it and then client_only will no longer be
+  // valid after the return.
+  bool do_delete = (client_only && mCurrTick >= mDataBlock->lifetime);
+
+  Parent::processTick(move);
+
+  if (do_delete)
+    deleteObject();
+}
+
+void afxProjectile::interpolateTick(F32 delta)
+{
+  if (client_only)
+    return;
+
+  Parent::interpolateTick(delta);
+}
+
+void afxProjectile::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  if (hookup_with_chor)
+  {
+    afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id);
+    if (chor)
+    {
+      chor->setGhostConstraintObject(this, ghost_cons_name);
+      hookup_with_chor = false;
+    }
+  }
+}
+
+bool afxProjectile::onAdd()
+{
+  if(!Parent::onAdd())
+    return false;
+
+  if (isClientObject())
+  {
+    // add to client side mission cleanup
+    SimGroup *cleanup = dynamic_cast<SimGroup *>( Sim::findObject( "ClientMissionCleanup") );
+    if( cleanup != NULL )
+    {
+      cleanup->addObject( this );
+    }
+    else
+    {
+      AssertFatal( false, "Error, could not find ClientMissionCleanup group" );
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void afxProjectile::onRemove()
+{  
+  Parent::onRemove();
+}
+
+//~~~~~~~~~~~~~~~~~~~~
+
+U32 afxProjectile::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+  
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask)) 
+  {
+    stream->write(chor_id);
+    stream->writeString(ghost_cons_name);
+  }
+  
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxProjectile::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+    stream->read(&chor_id);
+    ghost_cons_name = stream->readSTString();
+    
+    if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING)
+      hookup_with_chor = true;
+  }
+}
+
+class afxProjectileDeleteEvent : public SimEvent
+{
+public:
+   void process(SimObject *object)
+   {
+      object->deleteObject();
+   }
+};
+
+void afxProjectile::explode(const Point3F& p, const Point3F& n, const U32 collideType)
+{
+  // Make sure we don't explode twice...
+  if ( isHidden() )
+    return;
+
+  Parent::explode(p, n, collideType);
+
+  if (isClientObject() && client_only) 
+    Sim::postEvent(this, new afxProjectileDeleteEvent, Sim::getCurrentTime() + DeleteWaitTime);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 117 - 0
Engine/source/afx/ce/afxProjectile.h

@@ -0,0 +1,117 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_PROJECTILE_H_
+#define _AFX_PROJECTILE_H_
+
+#include "lighting/lightInfo.h"
+#include "T3D/projectile.h"
+
+#include "afx/afxEffectDefs.h"
+#include "afx/afxConstraint.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxProjectileData
+
+class afxProjectileData : public ProjectileData, public afxEffectDefs
+{
+  typedef ProjectileData Parent;
+
+public:
+  enum LaunchDirType {
+    TowardPos2Constraint,
+    OrientConstraint,
+    LaunchDirField
+  };
+
+public:
+  U8                networking;
+  StringTableEntry  launch_pos_spec;
+  afxConstraintDef  launch_pos_def;
+  Point3F           launch_dir_bias;
+  bool              ignore_src_timeout;
+  U32               dynamicCollisionMask;
+  U32               staticCollisionMask;
+  bool              override_collision_masks;
+  U32               launch_dir_method;
+
+  virtual void      gather_cons_defs(Vector<afxConstraintDef>& defs);
+
+public:
+  /*C*/             afxProjectileData();
+  /*C*/             afxProjectileData(const afxProjectileData&, bool = false);
+
+  virtual bool      onAdd();
+  void              packData(BitStream* stream);
+  void              unpackData(BitStream* stream);
+
+  virtual bool      allowSubstitutions() const { return true; }
+
+  static void       initPersistFields();
+
+  DECLARE_CONOBJECT(afxProjectileData);
+  DECLARE_CATEGORY("AFX");
+};
+
+typedef afxProjectileData::LaunchDirType afxProjectile_LaunchDirType;
+DefineEnumType( afxProjectile_LaunchDirType );
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxProjectile
+
+class afxProjectile : public Projectile, public afxEffectDefs
+{
+  typedef Projectile Parent;
+
+private:
+  U32                 chor_id;
+  bool                hookup_with_chor;
+  StringTableEntry    ghost_cons_name;
+  bool                client_only;
+
+public:
+  /*C*/               afxProjectile();
+  /*C*/               afxProjectile(U32 networking, U32 chor_id, StringTableEntry cons_name);
+  /*D*/               ~afxProjectile();
+
+  void                init(Point3F& pos, Point3F& vel, ShapeBase* src_obj);
+
+  virtual bool        onNewDataBlock(GameBaseData* dptr, bool reload);
+  virtual void        processTick(const Move *move);
+  virtual void        interpolateTick(F32 delta);
+  virtual void        advanceTime(F32 dt);
+  virtual bool        onAdd();
+  virtual void        onRemove();
+  virtual U32         packUpdate(NetConnection*, U32, BitStream*);
+  virtual void        unpackUpdate(NetConnection*, BitStream*);
+  virtual void        explode(const Point3F& p, const Point3F& n, const U32 collideType);
+
+  DECLARE_CONOBJECT(afxProjectile);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_PROJECTILE_H_

+ 94 - 0
Engine/source/afx/ce/afxScriptEvent.cpp

@@ -0,0 +1,94 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxScriptEvent.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxScriptEventData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxScriptEventData);
+
+ConsoleDocClass( afxScriptEventData,
+   "@brief A datablock that specifies a Script Event effect.\n\n"
+
+   "Arbitrary script functions can be called as an AFX effect using afxScriptEventData. They are useful for implementing "
+   "high-level scripted side-effects such as character resurrection or teleportation."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxScriptEventData::afxScriptEventData()
+{
+  method_name = ST_NULLSTRING;
+  script_data = ST_NULLSTRING;
+}
+
+afxScriptEventData::afxScriptEventData(const afxScriptEventData& other, bool temp_clone) : GameBaseData(other, temp_clone)
+{
+  method_name = other.method_name;
+  script_data = other.script_data;
+}
+
+#define myOffset(field) Offset(field, afxScriptEventData)
+
+void afxScriptEventData::initPersistFields()
+{
+  addField("methodName",  TypeString,   myOffset(method_name),
+    "The name of a script method defined for the instance class of an effects "
+    "choreographer. The arguments used to call this method are determined by the type "
+    "of choreographer.");
+  addField("scriptData",  TypeString,   myOffset(script_data),
+    "An arbitrary blind data value which is passed in as an argument of the script event "
+    "method. The value of scriptData can be used to differentiate uses when handling "
+    "different script event effects with a single method.");
+
+  Parent::initPersistFields();
+}
+
+void afxScriptEventData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->writeString(method_name);
+  stream->writeString(script_data);
+}
+
+void afxScriptEventData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  method_name = stream->readSTString();
+  script_data = stream->readSTString();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 57 - 0
Engine/source/afx/ce/afxScriptEvent.h

@@ -0,0 +1,57 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_SCRIPT_EVENT_H_
+#define _AFX_SCRIPT_EVENT_H_
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxScriptEventData
+
+struct afxScriptEventData : public GameBaseData
+{
+  typedef GameBaseData Parent;
+
+public:
+  StringTableEntry      method_name;
+  StringTableEntry      script_data;
+
+public:
+  /*C*/                 afxScriptEventData();
+  /*C*/                 afxScriptEventData(const afxScriptEventData&, bool = false);
+
+  void                  packData(BitStream* stream);
+  void                  unpackData(BitStream* stream);
+
+  virtual bool          allowSubstitutions() const { return true; }
+
+  static void           initPersistFields();
+
+  DECLARE_CONOBJECT(afxScriptEventData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_SCRIPT_EVENT_H_

+ 112 - 0
Engine/source/afx/ce/afxSpotLight_T3D.cpp

@@ -0,0 +1,112 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+
+#include "afx/ce/afxSpotLight_T3D.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxT3DSpotLightData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxT3DSpotLightData);
+
+ConsoleDocClass( afxT3DSpotLightData,
+   "@brief A datablock that specifies a dynamic Spot Light effect.\n\n"
+
+   "A Spot Light effect that uses the T3D SpotLight object. afxT3DSpotLightData has the same fields found in SpotLight but in "
+   "a datablock structure."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxT3DSpotLightData::afxT3DSpotLightData()
+   :  mRange( 10.0f ),
+      mInnerConeAngle( 40.0f ),
+      mOuterConeAngle( 45.0f )
+{
+}
+
+afxT3DSpotLightData::afxT3DSpotLightData(const afxT3DSpotLightData& other, bool temp_clone) : afxT3DLightBaseData(other, temp_clone)
+{
+  mRange = other.mRange;
+  mInnerConeAngle = other.mInnerConeAngle;
+  mOuterConeAngle = other.mOuterConeAngle;
+}
+
+//
+// NOTE: keep this as consistent as possible with PointLight::initPersistFields()
+//
+void afxT3DSpotLightData::initPersistFields()
+{
+   addGroup( "Light" );
+      
+      addField( "range", TypeF32, Offset( mRange, afxT3DSpotLightData ),
+        "...");
+      addField( "innerAngle", TypeF32, Offset( mInnerConeAngle, afxT3DSpotLightData ),
+        "...");
+      addField( "outerAngle", TypeF32, Offset( mOuterConeAngle, afxT3DSpotLightData ),
+        "...");
+
+   endGroup( "Light" );
+
+   // We do the parent fields at the end so that
+   // they show up that way in the inspector.
+   Parent::initPersistFields();
+}
+
+bool afxT3DSpotLightData::onAdd()
+{
+  if (Parent::onAdd() == false)
+    return false;
+
+  return true;
+}
+
+void afxT3DSpotLightData::packData(BitStream* stream)
+{
+	Parent::packData(stream);
+
+  stream->write( mRange );
+  stream->write( mInnerConeAngle );
+  stream->write( mOuterConeAngle );
+}
+
+void afxT3DSpotLightData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  stream->read( &mRange );
+  stream->read( &mInnerConeAngle );
+  stream->read( &mOuterConeAngle );
+}
+
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

+ 58 - 0
Engine/source/afx/ce/afxSpotLight_T3D.h

@@ -0,0 +1,58 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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.
+//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#ifndef _AFX_T3D_SPOT_LIGHT_H_
+#define _AFX_T3D_SPOT_LIGHT_H_
+
+#include "afx/ce/afxLightBase_T3D.h"
+
+class afxT3DSpotLightData : public afxT3DLightBaseData
+{
+  typedef afxT3DLightBaseData  Parent;
+
+public:
+   F32          mRange;
+   F32          mInnerConeAngle;
+   F32          mOuterConeAngle;
+
+public:
+  /*C*/         afxT3DSpotLightData();
+  /*C*/         afxT3DSpotLightData(const afxT3DSpotLightData&, bool = false);
+
+  virtual bool  onAdd();
+  virtual void  packData(BitStream*);
+  virtual void  unpackData(BitStream*);
+
+  virtual bool  allowSubstitutions() const { return true; }
+
+  static void   initPersistFields();
+
+  DECLARE_CONOBJECT(afxT3DSpotLightData);
+  DECLARE_CATEGORY("AFX");
+};
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+
+#endif // _AFX_T3D_SPOT_LIGHT_H_

+ 241 - 0
Engine/source/afx/ce/afxStaticShape.cpp

@@ -0,0 +1,241 @@
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// 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 "ts/tsShapeInstance.h"
+
+#include "afx/afxChoreographer.h"
+#include "afx/ce/afxStaticShape.h"
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxStaticShapeData
+
+IMPLEMENT_CO_DATABLOCK_V1(afxStaticShapeData);
+
+ConsoleDocClass( afxStaticShapeData,
+   "@brief A datablock that specifies a StaticShape effect.\n\n"
+
+   "afxStaticShapeData inherits from StaticShapeData and adds some AFX specific fields. StaticShape effects should be "
+   "specified using afxStaticShapeData rather than StaticShapeData datablocks."
+   "\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+   "@ingroup Datablocks\n"
+);
+
+afxStaticShapeData::afxStaticShapeData()
+{
+  sequence = ST_NULLSTRING;
+  ignore_scene_amb = false; 
+  use_custom_scene_amb = false;
+  custom_scene_amb.set(0.5f, 0.5f, 0.5f);
+  do_spawn = false;
+}
+
+afxStaticShapeData::afxStaticShapeData(const afxStaticShapeData& other, bool temp_clone) : StaticShapeData(other, temp_clone)
+{
+  sequence = other.sequence;
+  ignore_scene_amb = other.ignore_scene_amb;
+  use_custom_scene_amb = other.use_custom_scene_amb;
+  custom_scene_amb = other.custom_scene_amb;
+  do_spawn = other.do_spawn;
+}
+
+#define myOffset(field) Offset(field, afxStaticShapeData)
+
+void afxStaticShapeData::initPersistFields()
+{
+  addField("sequence",              TypeFilename, myOffset(sequence),
+    "An animation sequence in the StaticShape to play.");
+  addField("ignoreSceneAmbient",    TypeBool,     myOffset(ignore_scene_amb),
+    "...");
+  addField("useCustomSceneAmbient", TypeBool,     myOffset(use_custom_scene_amb),
+    "...");
+  addField("customSceneAmbient",    TypeColorF,   myOffset(custom_scene_amb),
+    "...");
+  addField("doSpawn",               TypeBool,     myOffset(do_spawn),
+    "When true, the StaticShape effect will leave behind the StaticShape object as a "
+    "permanent part of the scene.");
+
+  Parent::initPersistFields();
+}
+
+void afxStaticShapeData::packData(BitStream* stream)
+{
+  Parent::packData(stream);
+
+  stream->writeString(sequence);
+  stream->writeFlag(ignore_scene_amb);
+  if (stream->writeFlag(use_custom_scene_amb))
+    stream->write(custom_scene_amb);
+  stream->writeFlag(do_spawn);
+}
+
+void afxStaticShapeData::unpackData(BitStream* stream)
+{
+  Parent::unpackData(stream);
+
+  sequence = stream->readSTString();
+  ignore_scene_amb = stream->readFlag();
+  if ((use_custom_scene_amb = stream->readFlag()) == true)
+    stream->read(&custom_scene_amb);
+  do_spawn = stream->readFlag();
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
+// afxStaticShape
+
+IMPLEMENT_CO_NETOBJECT_V1(afxStaticShape);
+
+ConsoleDocClass( afxStaticShape,
+   "@brief A StaticShape effect as defined by an afxStaticShapeData datablock.\n\n"
+
+   "@ingroup afxEffects\n"
+   "@ingroup AFX\n"
+);
+
+afxStaticShape::afxStaticShape()
+{
+  afx_data = 0;
+  is_visible = true;
+  chor_id = 0;
+  hookup_with_chor = false;
+  ghost_cons_name = ST_NULLSTRING;
+}
+
+afxStaticShape::~afxStaticShape()
+{
+}
+
+void afxStaticShape::init(U32 chor_id, StringTableEntry cons_name)
+{
+  this->chor_id = chor_id;
+  ghost_cons_name = cons_name;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+bool afxStaticShape::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+  mDataBlock = dynamic_cast<StaticShapeData*>(dptr);
+  if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+    return false;
+
+  afx_data = dynamic_cast<afxStaticShapeData*>(mDataBlock);
+
+  if (!mShapeInstance)
+    return true;
+
+  const char* seq_name = 0;
+
+  // if datablock is afxStaticShapeData we get the sequence setting 
+  // directly from the datablock on the client-side only
+  if (afx_data)
+  {
+    if (isClientObject())
+      seq_name = afx_data->sequence;
+  }
+  // otherwise datablock is stock StaticShapeData and we look for
+  // a sequence name on a dynamic field on the server.
+  else
+  {
+    if (isServerObject())
+      seq_name = mDataBlock->getDataField(StringTable->insert("sequence"),0);
+  } 
+
+  // if we have a sequence name, attempt to start a thread
+  if (seq_name)
+  {
+    TSShape* shape = mShapeInstance->getShape();
+    if (shape) 
+    {
+      S32 seq = shape->findSequence(seq_name);
+      if (seq != -1)
+        setThreadSequence(0,seq);
+    }
+  }
+
+  return true;
+}
+
+void afxStaticShape::advanceTime(F32 dt)
+{
+  Parent::advanceTime(dt);
+
+  if (hookup_with_chor)
+  {
+    afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id);
+    if (chor)
+    {
+      chor->setGhostConstraintObject(this, ghost_cons_name);
+      hookup_with_chor = false;
+    }
+  }
+}
+
+U32 afxStaticShape::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
+{
+  U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+  // InitialUpdate
+  if (stream->writeFlag(mask & InitialUpdateMask)) 
+  {
+    stream->write(chor_id);
+    stream->writeString(ghost_cons_name);
+  }
+
+  return retMask;
+}
+
+//~~~~~~~~~~~~~~~~~~~~//
+
+void afxStaticShape::unpackUpdate(NetConnection * conn, BitStream * stream)
+{
+  Parent::unpackUpdate(conn, stream);
+  
+  // InitialUpdate
+  if (stream->readFlag())
+  {
+    stream->read(&chor_id);
+    ghost_cons_name = stream->readSTString();
+
+    if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING)
+      hookup_with_chor = true;
+  }
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//
+
+void afxStaticShape::prepRenderImage(SceneRenderState* state)
+{
+  if (is_visible) 
+     Parent::prepRenderImage(state);
+}
+
+//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików