فهرست منبع

Adds Component, the various main component classes and their interfaces.

Areloch 9 سال پیش
والد
کامیت
fa78a2f354
37فایلهای تغییر یافته به همراه9736 افزوده شده و 0 حذف شده
  1. 715 0
      Engine/source/T3D/components/Animation/animationComponent.cpp
  2. 138 0
      Engine/source/T3D/components/Animation/animationComponent.h
  3. 222 0
      Engine/source/T3D/components/Animation/animationComponent_ScriptBinding.h
  4. 483 0
      Engine/source/T3D/components/Camera/CameraComponent.cpp
  5. 159 0
      Engine/source/T3D/components/Camera/CameraComponent.h
  6. 91 0
      Engine/source/T3D/components/Camera/CameraComponent_ScriptBinding.h
  7. 146 0
      Engine/source/T3D/components/Camera/CameraOrbiterComponent.cpp
  8. 71 0
      Engine/source/T3D/components/Camera/CameraOrbiterComponent.h
  9. 172 0
      Engine/source/T3D/components/Collision/CollisionComponent_ScriptBinding.h
  10. 582 0
      Engine/source/T3D/components/Collision/collisionComponent.cpp
  11. 208 0
      Engine/source/T3D/components/Collision/collisionComponent.h
  12. 258 0
      Engine/source/T3D/components/Collision/collisionInterfaces.cpp
  13. 167 0
      Engine/source/T3D/components/Collision/collisionInterfaces.h
  14. 619 0
      Engine/source/T3D/components/Collision/collisionTrigger.cpp
  15. 145 0
      Engine/source/T3D/components/Collision/collisionTrigger.h
  16. 638 0
      Engine/source/T3D/components/Component.cpp
  17. 197 0
      Engine/source/T3D/components/Component.h
  18. 215 0
      Engine/source/T3D/components/Game/StateMachineComponent.cpp
  19. 81 0
      Engine/source/T3D/components/Game/StateMachineComponent.h
  20. 434 0
      Engine/source/T3D/components/Game/stateMachine.cpp
  21. 259 0
      Engine/source/T3D/components/Game/stateMachine.h
  22. 358 0
      Engine/source/T3D/components/Game/triggerComponent.cpp
  23. 74 0
      Engine/source/T3D/components/Game/triggerComponent.h
  24. 368 0
      Engine/source/T3D/components/Physics/physicsBehavior.cpp
  25. 135 0
      Engine/source/T3D/components/Physics/physicsBehavior.h
  26. 0 0
      Engine/source/T3D/components/Physics/physicsComponentInterface.cpp
  27. 49 0
      Engine/source/T3D/components/Physics/physicsComponentInterface.h
  28. 863 0
      Engine/source/T3D/components/Physics/playerControllerComponent.cpp
  29. 212 0
      Engine/source/T3D/components/Physics/playerControllerComponent.h
  30. 467 0
      Engine/source/T3D/components/Physics/rigidBodyComponent.cpp
  31. 183 0
      Engine/source/T3D/components/Physics/rigidBodyComponent.h
  32. 524 0
      Engine/source/T3D/components/Render/MeshComponent.cpp
  33. 183 0
      Engine/source/T3D/components/Render/MeshComponent.h
  34. 155 0
      Engine/source/T3D/components/Render/MeshComponent_ScriptBinding.h
  35. 62 0
      Engine/source/T3D/components/Render/renderComponentInterface.h
  36. 101 0
      Engine/source/T3D/components/coreInterfaces.h
  37. 2 0
      Engine/source/console/consoleObject.h

+ 715 - 0
Engine/source/T3D/components/Animation/animationComponent.cpp

@@ -0,0 +1,715 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Animation/AnimationComponent.h"
+#include "T3D/Components/Animation/AnimationComponent_ScriptBinding.h"
+#include "T3D/components/Render/MeshComponent.h"
+
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "ts/tsShapeInstance.h"
+#include "core/stream/bitStream.h"
+#include "sim/netConnection.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+#include "gfx/sim/debugDraw.h" 
+
+extern bool gEditingMission;
+
+//////////////////////////////////////////////////////////////////////////
+// Callbacks
+//////////////////////////////////////////////////////////////////////////
+IMPLEMENT_CALLBACK( AnimationComponent, onAnimationStart, void, ( Component* obj, const String& animName ), ( obj, animName ),
+                   "@brief Called when we collide with another object.\n\n"
+                   "@param obj The ShapeBase object\n"
+                   "@param collObj The object we collided with\n"
+                   "@param vec Collision impact vector\n"
+                   "@param len Length of the impact vector\n" );
+
+IMPLEMENT_CALLBACK(AnimationComponent, onAnimationEnd, void, (Component* obj, const char* animName), (obj, animName),
+                   "@brief Called when we collide with another object.\n\n"
+                   "@param obj The ShapeBase object\n"
+                   "@param collObj The object we collided with\n"
+                   "@param vec Collision impact vector\n"
+                   "@param len Length of the impact vector\n" );
+
+IMPLEMENT_CALLBACK(AnimationComponent, onAnimationTrigger, void, (Component* obj, const String& animName, S32 triggerID), (obj, animName, triggerID),
+                   "@brief Called when we collide with another object.\n\n"
+                   "@param obj The ShapeBase object\n"
+                   "@param collObj The object we collided with\n"
+                   "@param vec Collision impact vector\n"
+                   "@param len Length of the impact vector\n" );
+
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+AnimationComponent::AnimationComponent() : Component()
+{
+   mNetworked = true;
+   mNetFlags.set(Ghostable | ScopeAlways);
+
+   mFriendlyName = "Animation(Component)";
+   mComponentType = "Render";
+
+   mDescription = getDescriptionText("Allows a rendered mesh to be animated");
+
+   mOwnerRenderInst = NULL;
+
+   mOwnerShapeInstance = NULL;
+
+   for (U32 i = 0; i < MaxScriptThreads; i++)
+   {
+      mAnimationThreads[i].sequence = -1;
+      mAnimationThreads[i].thread = 0;
+      mAnimationThreads[i].sound = 0;
+      mAnimationThreads[i].state = Thread::Stop;
+      mAnimationThreads[i].atEnd = false;
+      mAnimationThreads[i].timescale = 1.f;
+      mAnimationThreads[i].position = -1.f;
+      mAnimationThreads[i].transition = true;
+   }
+}
+
+AnimationComponent::~AnimationComponent()
+{
+   for(S32 i = 0;i < mFields.size();++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(AnimationComponent);
+
+bool AnimationComponent::onAdd()
+{
+   if (!Parent::onAdd())
+      return false;
+
+   //we need at least one layer
+   for (U32 i = 0; i < MaxScriptThreads; i++) 
+   {
+      Thread& st = mAnimationThreads[i];
+
+      if (st.sequence != -1) 
+      {
+         // TG: Need to see about suppressing non-cyclic sounds
+         // if the sequences were activated before the object was
+         // ghosted.
+         // TG: Cyclic animations need to have a random pos if
+         // they were started before the object was ghosted.
+
+         // If there was something running on the old shape, the thread
+         // needs to be reset. Otherwise we assume that it's been
+         // initialized either by the constructor or from the server.
+         bool reset = st.thread != 0;
+         st.thread = 0;
+
+         if (st.sequence != -1)
+         {
+            setThreadSequence(i, st.sequence, reset);
+         }
+      }
+
+      if (st.thread)
+         updateThread(st);
+   }
+
+   return true;
+}
+
+void AnimationComponent::onRemove()
+{
+   Parent::onRemove();
+}
+
+void AnimationComponent::onComponentAdd()
+{
+   //test if this is a shape component!
+   RenderComponentInterface *shapeInstanceInterface = mOwner->getComponent<RenderComponentInterface>();
+   if (shapeInstanceInterface)
+   {
+      shapeInstanceInterface->onShapeInstanceChanged.notify(this, &AnimationComponent::targetShapeChanged);
+      targetShapeChanged(shapeInstanceInterface);
+   }
+}
+
+void AnimationComponent::componentAddedToOwner(Component *comp)
+{
+   if (comp->getId() == getId())
+      return;
+
+   //test if this is a shape component!
+   RenderComponentInterface *shapeInstanceInterface = dynamic_cast<RenderComponentInterface*>(comp);
+   if (shapeInstanceInterface)
+   {
+      shapeInstanceInterface->onShapeInstanceChanged.notify(this, &AnimationComponent::targetShapeChanged);
+      targetShapeChanged(shapeInstanceInterface);
+   }
+}
+
+void AnimationComponent::componentRemovedFromOwner(Component *comp)
+{
+   if (comp->getId() == getId()) //?????????
+      return;
+
+   //test if this is a shape component!
+   RenderComponentInterface *shapeInstanceInterface = dynamic_cast<RenderComponentInterface*>(comp);
+   if (shapeInstanceInterface)
+   {
+      shapeInstanceInterface->onShapeInstanceChanged.remove(this, &AnimationComponent::targetShapeChanged);
+      mOwnerRenderInst = NULL;
+   }
+}
+
+void AnimationComponent::targetShapeChanged(RenderComponentInterface* instanceInterface)
+{
+   mOwnerRenderInst = instanceInterface;
+
+   if (!mOwnerRenderInst || !getShape())
+      return;
+
+   MeshComponent* meshComp = dynamic_cast<MeshComponent*>(mOwnerRenderInst);
+
+   mOwnerShapeInstance = meshComp->getShapeInstance();
+
+   if (!mOwnerShapeInstance)
+      return;
+
+   for (U32 i = 0; i < MaxScriptThreads; i++)
+   {
+      Thread& st = mAnimationThreads[i];
+
+      st.thread = mOwnerShapeInstance->addThread();
+   }
+}
+
+void AnimationComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+}
+
+U32 AnimationComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   //early test if we lack an owner, ghost-wise
+   //no point in trying, just re-queue the mask and go
+   if (!mOwner || con->getGhostIndex(mOwner) == -1)
+   {
+      stream->writeFlag(false);
+      return retMask |= ThreadMask;
+   }
+   else
+   {
+      stream->writeFlag(true);
+
+      for (int i = 0; i < MaxScriptThreads; i++) 
+      {
+         Thread& st = mAnimationThreads[i];
+         if (stream->writeFlag( (st.sequence != -1 || st.state == Thread::Destroy) && (mask & (ThreadMaskN << i)) ) ) 
+         {
+            stream->writeInt(st.sequence,ThreadSequenceBits);
+            stream->writeInt(st.state,2);
+            stream->write(st.timescale);
+            stream->write(st.position);
+            stream->writeFlag(st.atEnd);
+            stream->writeFlag(st.transition);
+         }
+      }
+   }
+
+   return retMask;
+}
+
+void AnimationComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if (stream->readFlag()) 
+   {
+      for (S32 i = 0; i < MaxScriptThreads; i++) 
+      {
+         if (stream->readFlag()) 
+         {
+            Thread& st = mAnimationThreads[i];
+            U32 seq = stream->readInt(ThreadSequenceBits);
+            st.state = stream->readInt(2);
+            stream->read( &st.timescale );
+            stream->read( &st.position );
+            st.atEnd = stream->readFlag();
+            bool transition = stream->readFlag();
+
+            if (!st.thread || st.sequence != seq && st.state != Thread::Destroy)
+               setThreadSequence(i, seq, false, transition);
+            else
+               updateThread(st);
+
+         }
+      }
+   }
+}
+void AnimationComponent::processTick()
+{
+   Parent::processTick();
+
+   if (!isActive())
+      return;
+
+   if (isServerObject()) 
+   {
+      // Server only...
+      advanceThreads(TickSec);
+   }
+}
+
+void AnimationComponent::advanceTime(F32 dt)
+{
+   Parent::advanceTime(dt);
+
+   // On the client, the shape threads and images are
+   // advanced at framerate.
+   advanceThreads(dt);
+}
+//
+const char *AnimationComponent::getThreadSequenceName(U32 slot)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence == -1)
+   {
+      // Invalid Animation.
+      return "";
+   }
+
+   // Name Index
+   TSShape* shape = getShape();
+
+   if (shape)
+   {
+      const U32 nameIndex = shape->sequences[st.sequence].nameIndex;
+
+      // Return Name.
+      return shape->getName(nameIndex);
+   }
+
+   return "";
+}
+
+bool AnimationComponent::setThreadSequence(U32 slot, S32 seq, bool reset, bool transition, F32 transTime)
+{
+   if (!mOwnerShapeInstance)
+      return false;
+
+   Thread& st = mAnimationThreads[slot];
+   if (st.thread && st.sequence == seq && st.state == Thread::Play && !reset)
+      return true;
+
+   // Handle a -1 sequence, as this may be set when a thread has been destroyed.
+   if (seq == -1)
+      return true;
+
+   if (seq < MaxSequenceIndex)
+   {
+      setMaskBits(-1);
+      setMaskBits(ThreadMaskN << slot);
+      st.sequence = seq;
+      st.transition = transition;
+
+      if (reset)
+      {
+         st.state = Thread::Play;
+         st.atEnd = false;
+         st.timescale = 1.f;
+         st.position = 0.f;
+      }
+
+      if (mOwnerShapeInstance)
+      {
+         if (!st.thread)
+            st.thread = mOwnerShapeInstance->addThread();
+
+         if (transition)
+         {
+            mOwnerShapeInstance->transitionToSequence(st.thread, seq, st.position, transTime, true);
+         }
+         else
+         {
+            mOwnerShapeInstance->setSequence(st.thread, seq, 0);
+            stopThreadSound(st);
+         }
+
+         updateThread(st);
+      }
+      return true;
+   }
+   return false;
+}
+
+S32 AnimationComponent::getThreadSequenceID(S32 slot)
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
+   {
+      return mAnimationThreads[slot].sequence;
+   }
+   else
+   {
+      return -1;
+   }
+}
+
+void AnimationComponent::updateThread(Thread& st)
+{
+   if (!mOwnerShapeInstance)
+      return;
+
+   switch (st.state)
+   {
+      case Thread::Stop:
+      {
+         mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
+         mOwnerShapeInstance->setPos(st.thread, (st.timescale > 0.f) ? 0.0f : 1.0f);
+      } // Drop through to pause state
+
+      case Thread::Pause:
+      {
+         if (st.position != -1.f)
+         {
+            mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
+            mOwnerShapeInstance->setPos(st.thread, st.position);
+         }
+
+         mOwnerShapeInstance->setTimeScale(st.thread, 0.f);
+         stopThreadSound(st);
+      } break;
+
+      case Thread::Play:
+      {
+         if (st.atEnd)
+         {
+            mOwnerShapeInstance->setTimeScale(st.thread, 1);
+            mOwnerShapeInstance->setPos(st.thread, (st.timescale > 0.f) ? 1.0f : 0.0f);
+            mOwnerShapeInstance->setTimeScale(st.thread, 0);
+            stopThreadSound(st);
+            st.state = Thread::Stop;
+         }
+         else
+         {
+            if (st.position != -1.f)
+            {
+               mOwnerShapeInstance->setTimeScale(st.thread, 1.f);
+               mOwnerShapeInstance->setPos(st.thread, st.position);
+            }
+
+            mOwnerShapeInstance->setTimeScale(st.thread, st.timescale);
+            if (!st.sound)
+            {
+               startSequenceSound(st);
+            }
+         }
+      } break;
+
+      case Thread::Destroy:
+      {
+         stopThreadSound(st);
+         st.atEnd = true;
+         st.sequence = -1;
+         if (st.thread)
+         {
+            mOwnerShapeInstance->destroyThread(st.thread);
+            st.thread = 0;
+         }
+      } break;
+   }
+}
+
+bool AnimationComponent::stopThread(U32 slot)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1 && st.state != Thread::Stop) 
+   {
+      setMaskBits(ThreadMaskN << slot);
+      st.state = Thread::Stop;
+      updateThread(st);
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::destroyThread(U32 slot)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1 && st.state != Thread::Destroy) 
+   {
+      setMaskBits(ThreadMaskN << slot);
+      st.state = Thread::Destroy;
+      updateThread(st);
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::pauseThread(U32 slot)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1 && st.state != Thread::Pause) 
+   {
+      setMaskBits(ThreadMaskN << slot);
+      st.state = Thread::Pause;
+      updateThread(st);
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::playThread(U32 slot)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1 && st.state != Thread::Play)
+   {
+      setMaskBits(ThreadMaskN << slot);
+      st.state = Thread::Play;
+      updateThread(st);
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::playThread(U32 slot, const char* name, bool transition, F32 transitionTime)
+{
+   if (slot < AnimationComponent::MaxScriptThreads)
+   {
+      if (!dStrEqual(name, ""))
+      {
+         if (TSShape* shape = getShape())
+         {
+            S32 seq = shape->findSequence(name);
+            if (seq != -1 && setThreadSequence(slot, seq, true, transition, transitionTime))
+            {
+               return true;
+            }
+            else if (seq == -1)
+            {
+               //We tried to play a non-existaint sequence, so stop the thread just in case
+               destroyThread(slot);
+               return false;
+            }
+         }
+      }
+      else
+      {
+         if (playThread(slot))
+            return true;
+      }
+   }
+
+   return false;
+}
+
+bool AnimationComponent::setThreadAnimation(U32 slot, const char* name)
+{
+   if (slot < AnimationComponent::MaxScriptThreads)
+   {
+      if (!dStrEqual(name, ""))
+      {
+         if (TSShape* shape = getShape())
+         {
+            S32 seq = shape->findSequence(name);
+            if (seq != -1 && setThreadSequence(slot, seq, false, false))
+            {
+               Thread& st = mAnimationThreads[slot];
+               if (st.position == -1)
+                  st.position = 0;
+               //st.state = Thread::Pause;
+               return true;
+            }
+            else if (seq == -1)
+            {
+               //We tried to play a non-existaint sequence, so stop the thread just in case
+               destroyThread(slot);
+               return false;
+            }
+         }
+      }
+      else
+      {
+         if (playThread(slot))
+            return true;
+      }
+   }
+
+   return false;
+}
+
+bool AnimationComponent::setThreadPosition(U32 slot, F32 pos)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1)
+   {
+      setMaskBits(ThreadMaskN << slot);
+      st.position = pos;
+      st.atEnd = false;
+      updateThread(st);
+
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::setThreadDir(U32 slot, bool forward)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1)
+   {
+      if ((st.timescale >= 0.f) != forward)
+      {
+         setMaskBits(ThreadMaskN << slot);
+         st.timescale *= -1.f;
+         st.atEnd = false;
+         updateThread(st);
+      }
+      return true;
+   }
+   return false;
+}
+
+bool AnimationComponent::setThreadTimeScale(U32 slot, F32 timeScale)
+{
+   Thread& st = mAnimationThreads[slot];
+   if (st.sequence != -1)
+   {
+      if (st.timescale != timeScale)
+      {
+         setMaskBits(ThreadMaskN << slot);
+         st.timescale = timeScale;
+         updateThread(st);
+      }
+      return true;
+   }
+   return false;
+}
+
+void AnimationComponent::stopThreadSound(Thread& thread)
+{
+   return;
+}
+
+void AnimationComponent::startSequenceSound(Thread& thread)
+{
+   return;
+}
+
+void AnimationComponent::advanceThreads(F32 dt)
+{
+   if (!mOwnerShapeInstance)
+      return;
+
+   for (U32 i = 0; i < MaxScriptThreads; i++)
+   {
+      Thread& st = mAnimationThreads[i];
+      if (st.thread && st.sequence != -1)
+      {
+         bool cyclic = getShape()->sequences[st.sequence].isCyclic();
+
+         if (!getShape()->sequences[st.sequence].isCyclic() &&
+            !st.atEnd &&
+            ((st.timescale > 0.f) ? mOwnerShapeInstance->getPos(st.thread) >= 1.0 : mOwnerShapeInstance->getPos(st.thread) <= 0))
+         {
+            st.atEnd = true;
+            updateThread(st);
+
+            if (!isGhost())
+            {
+               Con::executef(this, "onAnimationEnd", st.thread->getSequenceName());
+            }
+         }
+
+         // Make sure the thread is still valid after the call to onEndSequence_callback().
+         // Someone could have called destroyThread() while in there.
+         if (st.thread)
+         {
+            mOwnerShapeInstance->advanceTime(dt, st.thread);
+         }
+
+         if (mOwnerShapeInstance && !isGhost())
+         {
+            for (U32 i = 1; i < 32; i++)
+            {
+               if (mOwnerShapeInstance->getTriggerState(i))
+               {
+                  const char* animName = st.thread->getSequenceName().c_str();
+                  onAnimationTrigger_callback(this, animName, i);
+               }
+            }
+         }
+
+         if (isGhost())
+            mOwnerShapeInstance->animate();
+      }
+   }
+}
+
+TSShape* AnimationComponent::getShape()
+{
+   if (mOwner == NULL)
+      return NULL;
+
+   if (mOwnerRenderInst == NULL)
+      return NULL;
+
+   return mOwnerRenderInst->getShape();
+}
+
+S32 AnimationComponent::getAnimationCount()
+{
+   if (getShape())
+      return getShape()->sequences.size();
+   else
+      return 0;
+}
+
+S32 AnimationComponent::getAnimationIndex(const char* name)
+{
+   if (getShape())
+      return getShape()->findSequence(name);
+   else
+      return -1;
+}
+
+const char* AnimationComponent::getAnimationName(S32 index)
+{
+   if (getShape())
+   {
+      if (index >= 0 && index < getShape()->sequences.size())
+         return getShape()->getName(getShape()->sequences[index].nameIndex);
+   }
+
+   return "";
+}

+ 138 - 0
Engine/source/T3D/components/Animation/animationComponent.h

@@ -0,0 +1,138 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef ANIMATION_COMPONENT_H
+#define ANIMATION_COMPONENT_H
+
+#ifndef COMPONENT_H
+#include "T3D/Components/Component.h"
+#endif
+#ifndef _TSSHAPE_H_
+#include "ts/tsShapeInstance.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef RENDER_COMPONENT_INTERFACE_H
+#include "T3D/Components/render/renderComponentInterface.h"
+#endif
+
+class SceneRenderState;
+
+class AnimationComponent : public Component
+{
+   typedef Component Parent;
+public:
+   enum PublicConstants {
+      ThreadSequenceBits = 6,
+      MaxSequenceIndex = (1 << ThreadSequenceBits) - 1,
+      MaxScriptThreads = 16,            ///< Should be a power of 2
+   };
+
+   enum MaskBits {
+      ThreadMaskN = Parent::NextFreeMask << 0,
+      ThreadMask = (ThreadMaskN << MaxScriptThreads) - ThreadMaskN,
+      NextFreeMask = ThreadMaskN << MaxScriptThreads
+   };
+
+protected:
+
+   struct Thread 
+   {
+      /// State of the animation thread.
+      enum State 
+      {
+         Play, Stop, Pause, Destroy
+      };
+      TSThread* thread; ///< Pointer to 3space data.
+      U32 state;        ///< State of the thread
+      ///
+      ///  @see Thread::State
+      S32 sequence;     ///< The animation sequence which is running in this thread.
+      F32 timescale;    ///< Timescale
+      U32 sound;        ///< Handle to sound.
+      bool atEnd;       ///< Are we at the end of this thread?
+      F32 position;
+      bool transition;
+   };
+
+   Thread mAnimationThreads[MaxScriptThreads];
+
+protected:
+   RenderComponentInterface * mOwnerRenderInst;
+
+   TSShapeInstance *mOwnerShapeInstance;
+
+public:
+   AnimationComponent();
+   virtual ~AnimationComponent();
+   DECLARE_CONOBJECT(AnimationComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+
+   virtual void componentAddedToOwner(Component *comp);
+   virtual void componentRemovedFromOwner(Component *comp);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   TSShape* getShape();
+
+   void targetShapeChanged(RenderComponentInterface* instanceInterface);
+
+   virtual void processTick();
+   virtual void advanceTime(F32 dt);
+
+   const char *getThreadSequenceName(U32 slot);
+   bool setThreadSequence(U32 slot, S32 seq, bool reset = true, bool transition = true, F32 transitionTime = 0.5);
+   void updateThread(Thread& st);
+   bool stopThread(U32 slot);
+   bool destroyThread(U32 slot);
+   bool pauseThread(U32 slot);
+   bool playThread(U32 slot);
+   bool playThread(U32 slot, const char* name, bool transition, F32 transitionTime);
+   bool setThreadAnimation(U32 slot, const char* name);
+   bool setThreadPosition(U32 slot, F32 pos);
+   bool setThreadDir(U32 slot, bool forward);
+   bool setThreadTimeScale(U32 slot, F32 timeScale);
+   void stopThreadSound(Thread& thread);
+   void startSequenceSound(Thread& thread);
+   void advanceThreads(F32 dt);
+
+   S32 getThreadSequenceID(S32 slot);
+
+   //other helper functions
+   S32 getAnimationCount();
+   S32 getAnimationIndex(const char* name);
+   const char* getAnimationName(S32 index);
+
+   //callbacks
+   DECLARE_CALLBACK(void, onAnimationStart, (Component* obj, const String& animName));
+   DECLARE_CALLBACK(void, onAnimationEnd, (Component* obj, const char* animName));
+   DECLARE_CALLBACK(void, onAnimationTrigger, (Component* obj, const String& animName, S32 triggerID));
+};
+
+#endif //_ANIMATION_COMPONENT_H

+ 222 - 0
Engine/source/T3D/components/Animation/animationComponent_ScriptBinding.h

@@ -0,0 +1,222 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/engineAPI.h"
+#include "T3D/Components/Animation/animationComponent.h"
+
+DefineEngineMethod(AnimationComponent, playThread, bool, (S32 slot, const char* name, bool transition, F32 transitionTime), (-1, "", true, 0.5),
+   "@brief Start a new animation thread, or restart one that has been paused or "
+   "stopped.\n\n"
+
+   "@param slot thread slot to play. Valid range is 0 - 3)\n"  // 3 = AnimationComponent::MaxScriptThreads-1
+   "@param name name of the animation sequence to play in this slot. If not "
+   "specified, the paused or stopped thread in this slot will be resumed.\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@tsexample\n"
+   "%obj.playThread( 0, \"ambient\" );      // Play the ambient sequence in slot 0\n"
+   "%obj.setThreadTimeScale( 0, 0.5 );    // Play at half-speed\n"
+   "%obj.pauseThread( 0 );                // Pause the sequence\n"
+   "%obj.playThread( 0 );                 // Resume playback\n"
+   "%obj.playThread( 0, \"spin\" );         // Replace the sequence in slot 0\n"
+   "@endtsexample\n"
+
+   "@see pauseThread()\n"
+   "@see stopThread()\n"
+   "@see setThreadDir()\n"
+   "@see setThreadTimeScale()\n"
+   "@see destroyThread()\n")
+{
+   return object->playThread(slot, name, transition, transitionTime);
+}
+
+DefineEngineMethod(AnimationComponent, setThreadDir, bool, (S32 slot, bool fwd), ,
+   "@brief Set the playback direction of an animation thread.\n\n"
+
+   "@param slot thread slot to modify\n"
+   "@param fwd true to play the animation forwards, false to play backwards\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread()\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads) 
+   {
+      if (object->setThreadDir(slot, fwd))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, setThreadTimeScale, bool, (S32 slot, F32 scale), ,
+   "@brief Set the playback time scale of an animation thread.\n\n"
+
+   "@param slot thread slot to modify\n"
+   "@param scale new thread time scale (1=normal speed, 0.5=half speed etc)\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
+   {
+      if (object->setThreadTimeScale(slot, scale))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, setThreadPosition, bool, (S32 slot, F32 pos), ,
+   "@brief Set the position within an animation thread.\n\n"
+
+   "@param slot thread slot to modify\n"
+   "@param pos position within thread\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads) 
+   {
+      if (object->setThreadPosition(slot, pos))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, setThreadAnimation, bool, (S32 slot, const char* name), (""),
+   "@brief Force-sets the animation in a particular thread without starting it playing."
+
+   "@param slot thread slot to play. Valid range is 0 - 3)\n"  // 3 = AnimationComponent::MaxScriptThreads-1
+   "@param name name of the animation sequence to play in this slot. If not "
+   "specified, the paused or stopped thread in this slot will be resumed.\n"
+   "@return true if successful, false if failed\n\n")
+{
+   return object->setThreadAnimation(slot, name);
+}
+
+DefineEngineMethod(AnimationComponent, getThreadAnimation, String, (S32 slot), ,
+   "@brief Force-sets the animation in a particular thread without starting it playing."
+
+   "@param slot thread slot to play. Valid range is 0 - 3)\n"  // 3 = AnimationComponent::MaxScriptThreads-1
+   "@param name name of the animation sequence to play in this slot. If not "
+   "specified, the paused or stopped thread in this slot will be resumed.\n"
+   "@return true if successful, false if failed\n\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads)
+   {
+      if (TSShape* shape = object->getShape())
+      {
+         S32 seq = object->getThreadSequenceID(slot);
+         if (seq != -1)
+         {
+            String animationName = object->getAnimationName(seq);
+            return animationName;
+         }
+      }
+   }
+
+   return "";
+}
+
+DefineEngineMethod(AnimationComponent, stopThread, bool, (S32 slot), ,
+   "@brief Stop an animation thread.\n\n"
+
+   "If restarted using playThread, the animation "
+   "will start from the beginning again.\n"
+   "@param slot thread slot to stop\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads) 
+   {
+      if (object->stopThread(slot))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, destroyThread, bool, (S32 slot), ,
+   "@brief Destroy an animation thread, which prevents it from playing.\n\n"
+
+   "@param slot thread slot to destroy\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads) 
+   {
+      if (object->destroyThread(slot))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, pauseThread, bool, (S32 slot), ,
+   "@brief Pause an animation thread.\n\n"
+
+   "If restarted using playThread, the animation "
+   "will resume from the paused position.\n"
+   "@param slot thread slot to stop\n"
+   "@return true if successful, false if failed\n\n"
+
+   "@see playThread\n")
+{
+   if (slot >= 0 && slot < AnimationComponent::MaxScriptThreads) 
+   {
+      if (object->pauseThread(slot))
+         return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(AnimationComponent, getAnimationCount, S32, (), ,
+   "Get the total number of sequences in the shape.\n"
+   "@return the number of sequences in the shape\n\n")
+{
+   return object->getAnimationCount();
+}
+
+DefineEngineMethod(AnimationComponent, getAnimationIndex, S32, (const char* name), ,
+   "Find the index of the sequence with the given name.\n"
+   "@param name name of the sequence to lookup\n"
+   "@return index of the sequence with matching name, or -1 if not found\n\n"
+   "@tsexample\n"
+   "// Check if a given sequence exists in the shape\n"
+   "if ( %this.getSequenceIndex( \"walk\" ) == -1 )\n"
+   "   echo( \"Could not find 'walk' sequence\" );\n"
+   "@endtsexample\n")
+{
+   return object->getAnimationIndex(name);
+}
+
+DefineEngineMethod(AnimationComponent, getAnimationName, const char*, (S32 index), ,
+   "Get the name of the indexed sequence.\n"
+   "@param index index of the sequence to query (valid range is 0 - getSequenceCount()-1)\n"
+   "@return the name of the sequence\n\n"
+   "@tsexample\n"
+   "// print the name of all sequences in the shape\n"
+   "%count = %this.getSequenceCount();\n"
+   "for ( %i = 0; %i < %count; %i++ )\n"
+   "   echo( %i SPC %this.getSequenceName( %i ) );\n"
+   "@endtsexample\n")
+{
+   return object->getAnimationName(index);
+}

+ 483 - 0
Engine/source/T3D/components/Camera/CameraComponent.cpp

@@ -0,0 +1,483 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/camera/CameraComponent.h"
+#include "T3D/Components/Camera/CameraComponent_ScriptBinding.h"
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "ts/tsShapeInstance.h"
+#include "core/stream/bitStream.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/gameFunctions.h"
+#include "math/mathUtils.h"
+#include "T3D/Components/render/renderComponentInterface.h"
+
+IMPLEMENT_CALLBACK( CameraComponent, validateCameraFov, F32, (F32 fov), (fov),
+                   "@brief Called on the server when the client has requested a FOV change.\n\n"
+
+                   "When the client requests that its field of view should be changed (because "
+                   "they want to use a sniper scope, for example) this new FOV needs to be validated "
+                   "by the server.  This method is called if it exists (it is optional) to validate "
+                   "the requested FOV, and modify it if necessary.  This could be as simple as checking "
+                   "that the FOV falls within a correct range, to making sure that the FOV matches the "
+                   "capabilities of the current weapon.\n\n"
+
+                   "Following this method, ShapeBase ensures that the given FOV still falls within "
+                   "the datablock's mCameraMinFov and mCameraMaxFov.  If that is good enough for your "
+                   "purposes, then you do not need to define the validateCameraFov() callback for "
+                   "your ShapeBase.\n\n"
+
+                   "@param fov The FOV that has been requested by the client.\n"
+                   "@return The FOV as validated by the server.\n\n"
+
+                   "@see ShapeBaseData\n\n");
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+
+CameraComponent::CameraComponent() : Component()
+{
+   mClientScreen = Point2F(1, 1);
+
+   mCameraFov = mCameraDefaultFov = 80;
+   mCameraMinFov = 5;
+   mCameraMaxFov = 175;
+
+   mTargetNodeIdx = -1;
+
+   mPosOffset = Point3F(0, 0, 0);
+   mRotOffset = EulerF(0, 0, 0);
+
+   mTargetNode = "";
+
+   mUseParentTransform = true;
+
+   mFriendlyName = "Camera(Component)";
+}
+
+CameraComponent::~CameraComponent()
+{
+   for(S32 i = 0;i < mFields.size();++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(CameraComponent);
+
+bool CameraComponent::onAdd()
+{
+   if(! Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+void CameraComponent::onRemove()
+{
+   Parent::onRemove();
+}
+
+void CameraComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addProtectedField("FOV", TypeF32, Offset(mCameraFov, CameraComponent), &_setCameraFov, defaultProtectedGetFn, "");
+
+   addField("MinFOV", TypeF32, Offset(mCameraMinFov, CameraComponent), "");
+
+   addField("MaxFOV", TypeF32, Offset(mCameraMaxFov, CameraComponent), "");
+
+   addField("ScreenAspect", TypePoint2I, Offset(mClientScreen, CameraComponent), "");
+
+   addProtectedField("targetNode", TypeString, Offset(mTargetNode, CameraComponent), &_setNode, defaultProtectedGetFn, "");
+
+   addProtectedField("positionOffset", TypePoint3F, Offset(mPosOffset, CameraComponent), &_setPosOffset, defaultProtectedGetFn, "");
+
+   addProtectedField("rotationOffset", TypeRotationF, Offset(mRotOffset, CameraComponent), &_setRotOffset, defaultProtectedGetFn, "");
+
+   addField("useParentTransform", TypeBool, Offset(mUseParentTransform, CameraComponent), "");
+}
+
+bool CameraComponent::_setNode(void *object, const char *index, const char *data)
+{
+   CameraComponent *mcc = static_cast<CameraComponent*>(object);
+   
+   mcc->mTargetNode = StringTable->insert(data);
+   mcc->setMaskBits(OffsetMask);
+
+   return true;
+}
+
+bool CameraComponent::_setPosOffset(void *object, const char *index, const char *data)
+{
+   CameraComponent *mcc = static_cast<CameraComponent*>(object);
+   
+   if (mcc)
+   {
+      Point3F pos;
+      Con::setData(TypePoint3F, &pos, 0, 1, &data);
+
+      mcc->mPosOffset = pos;
+      mcc->setMaskBits(OffsetMask);
+
+      return true;
+   }
+
+   return false;
+}
+
+bool CameraComponent::_setRotOffset(void *object, const char *index, const char *data)
+{
+   CameraComponent *mcc = static_cast<CameraComponent*>(object);
+
+   if (mcc)
+   {
+      RotationF rot;
+      Con::setData(TypeRotationF, &rot, 0, 1, &data);
+
+      mcc->mRotOffset = rot;
+      mcc->setMaskBits(OffsetMask);
+
+      return true;
+   }
+
+   return false;
+}
+
+bool CameraComponent::isValidCameraFov(F32 fov)
+{
+   return((fov >= mCameraMinFov) && (fov <= mCameraMaxFov));
+}
+
+bool CameraComponent::_setCameraFov(void *object, const char *index, const char *data)
+{
+   CameraComponent *cCI = static_cast<CameraComponent*>(object);
+   cCI->setCameraFov(dAtof(data));
+   return true;
+}
+
+void CameraComponent::setCameraFov(F32 fov)
+{
+   mCameraFov = mClampF(fov, mCameraMinFov, mCameraMaxFov);
+
+   if (isClientObject())
+      GameSetCameraTargetFov(mCameraFov);
+
+   if (isServerObject())
+      setMaskBits(FOVMask);
+}
+
+void CameraComponent::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)
+{
+   // update the camera query
+   query->camera = this;
+
+   if(GameConnection * con = dynamic_cast<GameConnection*>(cr))
+   {
+      // get the fov from the connection (in deg)
+      F32 fov;
+      if (con->getControlCameraFov(&fov))
+      {
+         query->fov = mDegToRad(fov/2);
+         query->sinFov = mSin(query->fov);
+         query->cosFov = mCos(query->fov);
+      }
+      else
+      {
+         query->fov = mDegToRad(mCameraFov/2);
+         query->sinFov = mSin(query->fov);
+         query->cosFov = mCos(query->fov);
+      }
+   }
+
+   // use eye rather than camera transform (good enough and faster)
+   MatrixF camTransform = mOwner->getTransform();
+   camTransform.getColumn(3, &query->pos);
+   camTransform.getColumn(1, &query->orientation);
+
+   // Get the visible distance.
+   if (mOwner->getSceneManager() != NULL)
+      query->visibleDistance = mOwner->getSceneManager()->getVisibleDistance();
+}
+
+bool CameraComponent::getCameraTransform(F32* pos,MatrixF* mat)
+{
+   // Returns camera to world space transform
+   // Handles first person / third person camera position
+   bool isServer = isServerObject();
+
+   if (mTargetNodeIdx == -1)
+   {
+      if (mUseParentTransform)
+      {
+         MatrixF rMat = mOwner->getRenderTransform();
+
+         rMat.mul(mRotOffset.asMatrixF());
+         
+         mat->set(rMat.toEuler(), rMat.getPosition() + mPosOffset);
+      }
+      else
+      {
+         mat->set(mRotOffset.asEulerF(), mPosOffset);
+      }
+
+      return true;
+   }
+   else
+   {
+      RenderComponentInterface *renderInterface = mOwner->getComponent<RenderComponentInterface>();
+
+      if (!renderInterface)
+         return false;
+
+      if (mUseParentTransform)
+      {
+         MatrixF rMat = mOwner->getRenderTransform();
+
+         Point3F position = rMat.getPosition();
+
+         RotationF rot = mRotOffset;
+
+         if (mTargetNodeIdx != -1)
+         {
+            Point3F nodPos;
+            MatrixF nodeTrans = renderInterface->getNodeTransform(mTargetNodeIdx);
+            nodeTrans.getColumn(3, &nodPos);
+
+            // Scale the camera position before applying the transform
+            const Point3F& scale = mOwner->getScale();
+            nodPos.convolve(scale);
+
+            mOwner->getRenderTransform().mulP(nodPos, &position);
+
+            nodeTrans.mul(rMat);
+
+            rot = nodeTrans;
+         }
+
+         position += mPosOffset;
+
+         MatrixF rotMat = rot.asMatrixF();
+
+         MatrixF rotOffsetMat = mRotOffset.asMatrixF();
+
+         rotMat.mul(rotOffsetMat);
+
+         rot = RotationF(rotMat);
+
+         mat->set(rot.asEulerF(), position);
+      }
+      else
+      {
+         MatrixF rMat = mOwner->getRenderTransform();
+
+         Point3F position = rMat.getPosition();
+
+         RotationF rot = mRotOffset;
+
+         if (mTargetNodeIdx != -1)
+         {
+            Point3F nodPos;
+            MatrixF nodeTrans = renderInterface->getNodeTransform(mTargetNodeIdx);
+            nodeTrans.getColumn(3, &nodPos);
+
+            // Scale the camera position before applying the transform
+            const Point3F& scale = mOwner->getScale();
+            nodPos.convolve(scale);
+
+            position = nodPos;
+         }
+
+         position += mPosOffset;
+
+         mat->set(rot.asEulerF(), position);
+      }
+
+      return true;
+   }
+}
+
+void CameraComponent::getCameraParameters(F32 *min, F32* max, Point3F* off, MatrixF* rot)
+{
+   *min = 0.2f;
+   *max = 0.f;
+   off->set(0, 0, 0);
+   rot->identity();
+}
+
+U32 CameraComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retmask = Parent::packUpdate(con, mask, stream);
+
+   if (stream->writeFlag(mask & FOVMask))
+   {
+      stream->write(mCameraFov);
+   }
+
+   if (stream->writeFlag(mask & OffsetMask))
+   {
+      RenderComponentInterface* renderInterface = getOwner()->getComponent<RenderComponentInterface>();
+
+      if (renderInterface && renderInterface->getShape())
+      {
+         S32 nodeIndex = renderInterface->getShape()->findNode(mTargetNode);
+
+         mTargetNodeIdx = nodeIndex;
+      }
+
+      stream->writeInt(mTargetNodeIdx, 32);
+      //send offsets here
+
+      stream->writeCompressedPoint(mPosOffset);
+      stream->writeCompressedPoint(mRotOffset.asEulerF());
+
+      stream->writeFlag(mUseParentTransform);
+   }
+
+   return retmask;
+}
+
+void CameraComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if (stream->readFlag())
+   {
+      F32 fov;
+      stream->read(&fov);
+      setCameraFov(fov);
+   }
+
+   if(stream->readFlag())
+   {
+      mTargetNodeIdx = stream->readInt(32);
+
+      stream->readCompressedPoint(&mPosOffset);
+
+      EulerF rot;
+      stream->readCompressedPoint(&rot);
+
+      mRotOffset = RotationF(rot);
+
+      mUseParentTransform = stream->readFlag();
+   }
+}
+
+void CameraComponent::setForwardVector(VectorF newForward, VectorF upVector)
+{
+   MatrixF mat;
+   F32 pos = 0;
+   getCameraTransform(&pos, &mat);
+
+   mPosOffset = mat.getPosition();
+
+   VectorF up(0.0f, 0.0f, 1.0f);
+   VectorF axisX;
+   VectorF axisY = newForward;
+   VectorF axisZ;
+
+   if (upVector != VectorF::Zero)
+      up = upVector;
+
+   // Validate and normalize input:  
+   F32 lenSq;
+   lenSq = axisY.lenSquared();
+   if (lenSq < 0.000001f)
+   {
+      axisY.set(0.0f, 1.0f, 0.0f);
+      Con::errorf("Entity::setForwardVector() - degenerate forward vector");
+   }
+   else
+   {
+      axisY /= mSqrt(lenSq);
+   }
+
+   lenSq = up.lenSquared();
+   if (lenSq < 0.000001f)
+   {
+      up.set(0.0f, 0.0f, 1.0f);
+      Con::errorf("SceneObject::setForwardVector() - degenerate up vector - too small");
+   }
+   else
+   {
+      up /= mSqrt(lenSq);
+   }
+
+   if (fabsf(mDot(up, axisY)) > 0.9999f)
+   {
+      Con::errorf("SceneObject::setForwardVector() - degenerate up vector - same as forward");
+      // i haven't really tested this, but i think it generates something which should be not parallel to the previous vector:  
+      F32 tmp = up.x;
+      up.x = -up.y;
+      up.y = up.z;
+      up.z = tmp;
+   }
+
+   // construct the remaining axes:  
+   mCross(axisY, up, &axisX);
+   mCross(axisX, axisY, &axisZ);
+
+   mat.setColumn(0, axisX);
+   mat.setColumn(1, axisY);
+   mat.setColumn(2, axisZ);
+
+   mRotOffset = RotationF(mat.toEuler());
+   mRotOffset.y = 0;
+
+   setMaskBits(OffsetMask);
+}
+
+void CameraComponent::setPosition(Point3F newPos)
+{
+   mPosOffset = newPos;
+   setMaskBits(OffsetMask);
+}
+
+void CameraComponent::setRotation(RotationF newRot)
+{
+   mRotOffset = newRot;
+   setMaskBits(OffsetMask);
+}
+
+Frustum CameraComponent::getFrustum()
+{
+   Frustum visFrustum;
+   F32 left, right, top, bottom;
+   F32 aspectRatio = mClientScreen.x / mClientScreen.y;
+
+   visFrustum.set(false, mDegToRad(mCameraFov), aspectRatio, 0.1f, 1000, mOwner->getTransform());
+
+   return visFrustum;
+}

+ 159 - 0
Engine/source/T3D/components/Camera/CameraComponent.h

@@ -0,0 +1,159 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef CAMERA_COMPONENT_H
+#define CAMERA_COMPONENT_H
+
+#ifndef COMPONENT_H
+#include "T3D/Components/Component.h"
+#endif
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+
+class SceneRenderState;
+struct CameraScopeQuery;
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class CameraComponent : public Component, public CameraInterface
+{
+   typedef Component Parent;
+
+   F32  mCameraFov;           ///< The camera vertical FOV in degrees.
+
+   Point2F mClientScreen;     ///< The dimensions of the client's screen. Used to calculate the aspect ratio.
+
+   F32 mCameraDefaultFov;            ///< Default vertical FOV in degrees.
+   F32 mCameraMinFov;                ///< Min vertical FOV allowed in degrees.
+   F32 mCameraMaxFov;                ///< Max vertical FOV allowed in degrees.
+
+protected:
+   Point3F mPosOffset;
+   RotationF mRotOffset;
+
+   StringTableEntry mTargetNode;
+   S32 mTargetNodeIdx;
+
+   bool mUseParentTransform;
+
+   enum
+   {
+      FOVMask = Parent::NextFreeMask,
+      OffsetMask = Parent::NextFreeMask << 1,
+      NextFreeMask = Parent::NextFreeMask << 2,
+   };
+
+public:
+   CameraComponent();
+   virtual ~CameraComponent();
+   DECLARE_CONOBJECT(CameraComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   static bool _setCameraFov(void *object, const char *index, const char *data);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   static bool _setNode(void *object, const char *index, const char *data);
+   static bool _setPosOffset(void *object, const char *index, const char *data);
+   static bool _setRotOffset(void *object, const char *index, const char *data);
+
+   void setRotOffset(RotationF rot)
+   {
+      mRotOffset = rot;
+      setMaskBits(OffsetMask);
+   }
+
+   RotationF getRotOffset()
+   {
+      return mRotOffset;
+   }
+
+   Point3F getPosOffset()
+   {
+      return mPosOffset;
+   }
+
+   /// Gets the minimum viewing distance, maximum viewing distance, camera offsetand rotation
+   /// for this object, if the world were to be viewed through its eyes
+   /// @param   min   Minimum viewing distance
+   /// @param   max   Maximum viewing distance
+   /// @param   offset Offset of the camera from the origin in local space
+   /// @param   rot   Rotation matrix
+   virtual void getCameraParameters(F32 *min, F32* max, Point3F* offset, MatrixF* rot);
+
+   /// Gets the camera to world space transform matrix
+   /// @todo Find out what pos does
+   /// @param   pos   TODO: Find out what this does
+   /// @param   mat   Camera transform (out)
+   virtual bool getCameraTransform(F32* pos, MatrixF* mat);
+
+   /// Returns the vertical field of view in degrees for 
+   /// this object if used as a camera.
+   virtual F32 getCameraFov() { return mCameraFov; }
+
+   /// Returns the default vertical field of view in degrees
+   /// if this object is used as a camera.
+   virtual F32 getDefaultCameraFov() { return mCameraDefaultFov; }
+
+   /// Sets the vertical field of view in degrees for this 
+   /// object if used as a camera.
+   /// @param   yfov  The vertical FOV in degrees to test.
+   virtual void setCameraFov(F32 fov);
+
+   /// Returns true if the vertical FOV in degrees is within 
+   /// allowable parameters of the datablock.
+   /// @param   yfov  The vertical FOV in degrees to test.
+   /// @see ShapeBaseData::cameraMinFov
+   /// @see ShapeBaseData::cameraMaxFov
+   virtual bool isValidCameraFov(F32 fov);
+   /// @}
+
+   virtual Frustum getFrustum();
+
+   /// Control object scoping
+   void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
+
+   void setForwardVector(VectorF newForward, VectorF upVector = VectorF::Zero);
+   void setPosition(Point3F newPos);
+   void setRotation(RotationF newRot);
+
+protected:
+   DECLARE_CALLBACK(F32, validateCameraFov, (F32 fov));
+};
+
+#endif // CAMERA_BEHAVIOR_H

+ 91 - 0
Engine/source/T3D/components/Camera/CameraComponent_ScriptBinding.h

@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/engineAPI.h"
+#include "T3D/Components/Camera/CameraComponent.h"
+
+//Basically, this only exists for backwards compatibility for parts of the editors
+ConsoleMethod(CameraComponent, getMode, const char*, 2, 2, "() - We get the first behavior of the requested type on our owner object.\n"
+   "@return (string name) The type of the behavior we're requesting")
+{
+   return "fly";
+}
+
+DefineConsoleMethod(CameraComponent, getForwardVector, VectorF, (), ,
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   F32 pos = 0;
+   MatrixF cameraMat;
+   object->getCameraTransform(&pos, &cameraMat);
+
+   VectorF returnVec = cameraMat.getForwardVector();
+   returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
+   returnVec.normalize();
+   return returnVec;
+}
+
+DefineConsoleMethod(CameraComponent, getRightVector, VectorF, (), ,
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   F32 pos = 0;
+   MatrixF cameraMat;
+   object->getCameraTransform(&pos, &cameraMat);
+
+   VectorF returnVec = cameraMat.getRightVector();
+   returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
+   returnVec.normalize();
+   return returnVec;
+}
+
+DefineConsoleMethod(CameraComponent, getUpVector, VectorF, (), ,
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   F32 pos = 0;
+   MatrixF cameraMat;
+   object->getCameraTransform(&pos, &cameraMat);
+
+   VectorF returnVec = cameraMat.getUpVector();
+   returnVec = VectorF(mRadToDeg(returnVec.x), mRadToDeg(returnVec.y), mRadToDeg(returnVec.z));
+   returnVec.normalize();
+   return returnVec;
+}
+
+DefineConsoleMethod(CameraComponent, setForwardVector, void, (VectorF newForward), (VectorF(0, 0, 0)),
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   object->setForwardVector(newForward);
+}
+
+DefineConsoleMethod(CameraComponent, getWorldPosition, Point3F, (), ,
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   F32 pos = 0;
+   MatrixF mat;
+   object->getCameraTransform(&pos, &mat);
+
+   return mat.getPosition();
+}

+ 146 - 0
Engine/source/T3D/components/Camera/CameraOrbiterComponent.cpp

@@ -0,0 +1,146 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Camera/CameraOrbiterComponent.h"
+#include "core/util/safeDelete.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "core/stream/bitStream.h"
+#include "console/engineAPI.h"
+#include "sim/netConnection.h"
+#include "math/mathUtils.h"
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+CameraOrbiterComponent::CameraOrbiterComponent() : Component()
+{
+   mMinOrbitDist = 0.0f;
+   mMaxOrbitDist = 0.0f;
+   mCurOrbitDist = 8.0f;
+   mPosition.set(0.0f, 0.0f, 0.0f);
+
+   mMaxPitchAngle = 70;
+   mMinPitchAngle = -10;
+
+   mRotation.set(0, 0, 0);
+
+   mCamera = NULL;
+}
+
+CameraOrbiterComponent::~CameraOrbiterComponent()
+{
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(CameraOrbiterComponent);
+
+bool CameraOrbiterComponent::onAdd()
+{
+   if (!Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+void CameraOrbiterComponent::onRemove()
+{
+   Parent::onRemove();
+}
+void CameraOrbiterComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addField("orbitDistance", TypeF32, Offset(mCurOrbitDist, CameraOrbiterComponent), "Object world orientation.");
+   addField("Rotation", TypeRotationF, Offset(mRotation, CameraOrbiterComponent), "Object world orientation.");
+   addField("maxPitchAngle", TypeF32, Offset(mMaxPitchAngle, CameraOrbiterComponent), "Object world orientation.");
+   addField("minPitchAngle", TypeF32, Offset(mMinPitchAngle, CameraOrbiterComponent), "Object world orientation.");
+}
+
+//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
+void CameraOrbiterComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   CameraComponent *cam = mOwner->getComponent<CameraComponent>();
+   if (cam)
+   {
+      mCamera = cam;
+   }
+}
+
+void CameraOrbiterComponent::onComponentRemove()
+{
+   Parent::onComponentRemove();
+}
+
+U32 CameraOrbiterComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+   return retMask;
+}
+
+void CameraOrbiterComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+}
+
+void CameraOrbiterComponent::processTick()
+{
+   Parent::processTick();
+
+   if (!mOwner)
+      return;
+
+   if (mCamera)
+   {
+      //Clamp our pitch to whatever range we allow, first.
+      mRotation.x = mClampF(mRotation.x, mDegToRad(mMinPitchAngle), mDegToRad(mMaxPitchAngle));
+
+      MatrixF ownerTrans = mOwner->getRenderTransform();
+      Point3F ownerPos = ownerTrans.getPosition();
+
+      Point3F pos;
+      pos.x = mCurOrbitDist * mSin(mRotation.x + M_HALFPI_F) * mCos(-1.0f * (mRotation.z + M_HALFPI_F));
+      pos.y = mCurOrbitDist * mSin(mRotation.x + M_HALFPI_F) * mSin(-1.0f * (mRotation.z + M_HALFPI_F));
+      pos.z = mCurOrbitDist * mSin(mRotation.x);
+
+      //orient the camera towards the owner
+      VectorF ownerVec = ownerPos - pos;
+      ownerVec.normalize();
+
+      MatrixF xRot, zRot, cameraMatrix;
+      xRot.set(EulerF(mRotation.x, 0.0f, 0.0f));
+      zRot.set(EulerF(0.0f, 0.0f, mRotation.z));
+
+      cameraMatrix.mul(zRot, xRot);
+      cameraMatrix.getColumn(1, &ownerVec);
+      cameraMatrix.setColumn(3, pos - ownerVec * pos);
+
+      RotationF camRot = RotationF(cameraMatrix);
+
+      if (camRot != mCamera->getRotOffset())
+         mCamera->setRotation(camRot);
+
+      if (pos != mCamera->getPosOffset())
+         mCamera->setPosition(pos);
+   }
+}

+ 71 - 0
Engine/source/T3D/components/Camera/CameraOrbiterComponent.h

@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef CAMERA_ORBITER_COMPONENT_H
+#define CAMERA_ORBITER_COMPONENT_H
+
+#ifndef COMPONENT_H
+#include "T3D/Components/Component.h"
+#endif
+#ifndef CAMERA_COMPONENT_H
+#include "T3D/Components/camera/cameraComponent.h"
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class CameraOrbiterComponent : public Component
+{
+   typedef Component Parent;
+
+   F32 mMinOrbitDist;
+   F32 mMaxOrbitDist;
+   F32 mCurOrbitDist;
+   Point3F mPosition;
+
+   F32 mMaxPitchAngle;
+   F32 mMinPitchAngle;
+
+   RotationF mRotation;
+
+   CameraComponent* mCamera;
+
+public:
+   CameraOrbiterComponent();
+   virtual ~CameraOrbiterComponent();
+   DECLARE_CONOBJECT(CameraOrbiterComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+   virtual void onComponentRemove();
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   virtual void processTick();
+};
+
+#endif // EXAMPLEBEHAVIOR_H

+ 172 - 0
Engine/source/T3D/components/Collision/CollisionComponent_ScriptBinding.h

@@ -0,0 +1,172 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/engineAPI.h"
+#include "T3D/Components/Collision/CollisionComponent.h"
+#include "materials/baseMatInstance.h"
+
+DefineConsoleMethod(CollisionComponent, getNumberOfContacts, S32, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   return object->getCollisionList()->getCount();
+}
+
+DefineConsoleMethod(CollisionComponent, getBestContact, S32, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   return 0;
+}
+
+DefineConsoleMethod(CollisionComponent, getContactNormal, Point3F, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   if (object->getContactInfo())
+   {
+      if (object->getContactInfo()->contactObject)
+      {
+         return object->getContactInfo()->contactNormal;
+      }
+   }
+
+   return Point3F::Zero;
+}
+
+DefineConsoleMethod(CollisionComponent, getContactMaterial, S32, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   if (object->getContactInfo())
+   {
+      if (object->getContactInfo()->contactObject)
+      {
+         if (object->getContactInfo()->contactMaterial != NULL)
+            return object->getContactInfo()->contactMaterial->getMaterial()->getId();
+      }
+   }
+
+   return 0;
+}
+
+DefineConsoleMethod(CollisionComponent, getContactObject, S32, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   if (object->getContactInfo())
+   {
+      return object->getContactInfo()->contactObject != NULL ? object->getContactInfo()->contactObject->getId() : 0;
+   }
+
+   return 0;
+}
+
+DefineConsoleMethod(CollisionComponent, getContactPoint, Point3F, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   if (object->getContactInfo())
+   {
+      if (object->getContactInfo()->contactObject)
+      {
+         return object->getContactInfo()->contactPoint;
+      }
+   }
+
+   return Point3F::Zero;
+}
+
+DefineConsoleMethod(CollisionComponent, getContactTime, S32, (), ,
+   "Gets the number of contacts this collider has hit.\n"
+   "@return The number of static fields defined on the object.")
+{
+   if (object->getContactInfo())
+   {
+      if (object->getContactInfo()->contactObject)
+      {
+         return object->getContactInfo()->contactTimer;
+      }
+   }
+
+   return 0;
+}
+
+DefineEngineMethod(CollisionComponent, hasContact, bool, (), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->hasContact();
+}
+
+DefineEngineMethod(CollisionComponent, getCollisionCount, S32, (), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getCollisionCount();
+}
+
+DefineEngineMethod(CollisionComponent, getCollisionNormal, Point3F, (S32 collisionIndex), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getCollisionNormal(collisionIndex);
+}
+
+DefineEngineMethod(CollisionComponent, getCollisionAngle, F32, (S32 collisionIndex, VectorF upVector), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getCollisionAngle(collisionIndex, upVector);
+}
+
+DefineEngineMethod(CollisionComponent, getBestCollisionAngle, F32, (VectorF upVector), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getBestCollisionAngle(upVector);
+}

+ 582 - 0
Engine/source/T3D/components/Collision/collisionComponent.cpp

@@ -0,0 +1,582 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Collision/collisionComponent.h"
+#include "T3D/Components/Collision/collisionComponent_ScriptBinding.h"
+#include "T3D/Components/Physics/physicsBehavior.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "core/stream/bitStream.h"
+#include "scene/sceneRenderState.h"
+#include "gfx/gfxTransformSaver.h"
+#include "gfx/gfxDrawUtil.h"
+#include "console/engineAPI.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/physics/physicsBody.h"
+#include "T3D/physics/physicsCollision.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "collision/extrudedPolyList.h"
+#include "math/mathIO.h"
+#include "gfx/sim/debugDraw.h"  
+#include "collision/concretePolyList.h"
+
+#include "T3D/trigger.h"
+#include "opcode/Opcode.h"
+#include "opcode/Ice/IceAABB.h"
+#include "opcode/Ice/IcePoint.h"
+#include "opcode/OPC_AABBTree.h"
+#include "opcode/OPC_AABBCollider.h"
+
+#include "math/mathUtils.h"
+#include "materials/baseMatInstance.h"
+#include "collision/vertexPolyList.h"
+
+extern bool gEditingMission;
+
+static bool sRenderColliders = false;
+
+//Docs
+ConsoleDocClass(CollisionComponent,
+   "@brief The Box Collider component uses a box or rectangular convex shape for collisions.\n\n"
+
+   "Colliders are individualized components that are similarly based off the CollisionInterface core.\n"
+   "They are basically the entire functionality of how Torque handles collisions compacted into a single component.\n"
+   "A collider will both collide against and be collided with, other entities.\n"
+   "Individual colliders will offer different shapes. This box collider will generate a box/rectangle convex, \n"
+   "while the mesh collider will take the owner Entity's rendered shape and do polysoup collision on it, etc.\n\n"
+
+   "The general flow of operations for how collisions happen is thus:\n"
+   "  -When the component is added(or updated) prepCollision() is called.\n"
+   "    This will set up our initial convex shape for usage later.\n\n"
+
+   "  -When we update via processTick(), we first test if our entity owner is mobile.\n"
+   "    If our owner isn't mobile(as in, they have no components that provide it a velocity to move)\n"
+   "    then we skip doing our active collision checks. Collisions are checked by the things moving, as\n"
+   "    opposed to being reactionary. If we're moving, we call updateWorkingCollisionSet().\n"
+   "    updateWorkingCollisionSet() estimates our bounding space for our current ticket based on our position and velocity.\n"
+   "    If our bounding space has changed since the last tick, we proceed to call updateWorkingList() on our convex.\n"
+   "    This notifies any object in the bounding space that they may be collided with, so they will call buildConvex().\n"
+   "    buildConvex() will set up our ConvexList with our collision convex info.\n\n"
+
+   "  -When the component that is actually causing our movement, such as SimplePhysicsBehavior, updates, it will check collisions.\n"
+   "    It will call checkCollisions() on us. checkCollisions() will first build a bounding shape for our convex, and test\n"
+   "    if we can early out because we won't hit anything based on our starting point, velocity, and tick time.\n"
+   "    If we don't early out, we proceed to call updateCollisions(). This builds an ExtrudePolyList, which is then extruded\n"
+   "    based on our velocity. We then test our extruded polies on our working list of objects we build\n"
+   "    up earlier via updateWorkingCollisionSet. Any collisions that happen here will be added to our mCollisionList.\n"
+   "    Finally, we call handleCollisionList() on our collisionList, which then queues out the colliison notice\n"
+   "    to the object(s) we collided with so they can do callbacks and the like. We also report back on if we did collide\n"
+   "    to the physics component via our bool return in checkCollisions() so it can make the physics react accordingly.\n\n"
+
+   "One interesting point to note is the usage of mBlockColliding.\n"
+   "This is set so that it dictates the return on checkCollisions(). If set to false, it will ensure checkCollisions()\n"
+   "will return false, regardless if we actually collided. This is useful, because even if checkCollisions() returns false,\n"
+   "we still handle the collisions so the callbacks happen. This enables us to apply a collider to an object that doesn't block\n"
+   "objects, but does have callbacks, so it can act as a trigger, allowing for arbitrarily shaped triggers, as any collider can\n"
+   "act as a trigger volume(including MeshCollider).\n\n"
+
+   "@tsexample\n"
+   "new CollisionComponentInstance()\n"
+   "{\n"
+   "   template = CollisionComponentTemplate;\n"
+   "   colliderSize = \"1 1 2\";\n"
+   "   blockColldingObject = \"1\";\n"
+   "};\n"
+   "@endtsexample\n"
+
+   "@see SimplePhysicsBehavior\n"
+   "@ingroup Collision\n"
+   "@ingroup Components\n"
+   );
+//Docs
+
+/////////////////////////////////////////////////////////////////////////
+ImplementEnumType(CollisionMeshMeshType,
+   "Type of mesh data available in a shape.\n"
+   "@ingroup gameObjects")
+{ CollisionComponent::None, "None", "No mesh data." },
+{ CollisionComponent::Bounds, "Bounds", "Bounding box of the shape." },
+{ CollisionComponent::CollisionMesh, "Collision Mesh", "Specifically desingated \"collision\" meshes." },
+{ CollisionComponent::VisibleMesh, "Visible Mesh", "Rendered mesh polygons." },
+EndImplementEnumType;
+
+//
+CollisionComponent::CollisionComponent() : Component()
+{
+   mNetFlags.set(Ghostable | ScopeAlways);
+
+   mFriendlyName = "Collision(Component)";
+
+   mOwnerRenderInterface = NULL;
+   mOwnerPhysicsInterface = NULL;
+
+   mBlockColliding = true;
+
+   mCollisionType = CollisionMesh;
+   mLOSType = CollisionMesh;
+   mDecalType = CollisionMesh;
+
+   colisionMeshPrefix = StringTable->insert("Collision");
+
+   CollisionMoveMask = (TerrainObjectType | PlayerObjectType |
+      StaticShapeObjectType | VehicleObjectType |
+      VehicleBlockerObjectType | DynamicShapeObjectType | StaticObjectType | EntityObjectType | TriggerObjectType);
+
+   mPhysicsRep = NULL;
+   mPhysicsWorld = NULL;
+
+   mTimeoutList = NULL;
+}
+
+CollisionComponent::~CollisionComponent()
+{
+   for (S32 i = 0; i < mFields.size(); ++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(CollisionComponent);
+
+void CollisionComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   RenderComponentInterface *renderInterface = mOwner->getComponent<RenderComponentInterface>();
+   if (renderInterface)
+   {
+      renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged);
+      mOwnerRenderInterface = renderInterface;
+   }
+
+   //physicsInterface
+   PhysicsComponentInterface *physicsInterface = mOwner->getComponent<PhysicsComponentInterface>();
+   if (!physicsInterface)
+   {
+      mPhysicsRep = PHYSICSMGR->createBody();
+   }
+
+   prepCollision();
+}
+
+void CollisionComponent::onComponentRemove()
+{
+   SAFE_DELETE(mPhysicsRep);
+
+   Parent::onComponentRemove();
+}
+
+void CollisionComponent::componentAddedToOwner(Component *comp)
+{
+   if (comp->getId() == getId())
+      return;
+
+   //test if this is a shape component!
+   RenderComponentInterface *renderInterface = dynamic_cast<RenderComponentInterface*>(comp);
+   if (renderInterface)
+   {
+      renderInterface->onShapeInstanceChanged.notify(this, &CollisionComponent::targetShapeChanged);
+      mOwnerRenderInterface = renderInterface;
+      prepCollision();
+   }
+
+   PhysicsComponentInterface *physicsInterface = dynamic_cast<PhysicsComponentInterface*>(comp);
+   if (physicsInterface)
+   {
+      if (mPhysicsRep)
+         SAFE_DELETE(mPhysicsRep);
+
+      prepCollision();
+   }
+}
+
+void CollisionComponent::componentRemovedFromOwner(Component *comp)
+{
+   if (comp->getId() == getId()) //?????????
+      return;
+
+   //test if this is a shape component!
+   RenderComponentInterface *renderInterface = dynamic_cast<RenderComponentInterface*>(comp);
+   if (renderInterface)
+   {
+      renderInterface->onShapeInstanceChanged.remove(this, &CollisionComponent::targetShapeChanged);
+      mOwnerRenderInterface = NULL;
+      prepCollision();
+   }
+
+   //physicsInterface
+   PhysicsComponentInterface *physicsInterface = dynamic_cast<PhysicsComponentInterface*>(comp);
+   if (physicsInterface)
+   {
+      mPhysicsRep = PHYSICSMGR->createBody();
+
+      prepCollision();
+   }
+}
+
+void CollisionComponent::checkDependencies()
+{
+}
+
+void CollisionComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addGroup("Collision");
+
+      addField("CollisionType", TypeCollisionMeshMeshType, Offset(mCollisionType, CollisionComponent),
+         "The type of mesh data to use for collision queries.");
+
+      addField("LineOfSightType", TypeCollisionMeshMeshType, Offset(mLOSType, CollisionComponent),
+         "The type of mesh data to use for collision queries.");
+
+      addField("DecalType", TypeCollisionMeshMeshType, Offset(mDecalType, CollisionComponent),
+         "The type of mesh data to use for collision queries.");
+
+      addField("CollisionMeshPrefix", TypeString, Offset(colisionMeshPrefix, CollisionComponent),
+         "The type of mesh data to use for collision queries.");
+
+      addField("BlockCollisions", TypeBool, Offset(mBlockColliding, CollisionComponent), "");
+
+   endGroup("Collision");
+}
+
+void CollisionComponent::inspectPostApply()
+{
+   // Apply any transformations set in the editor
+   Parent::inspectPostApply();
+
+   if (isServerObject())
+   {
+      setMaskBits(ColliderMask);
+      prepCollision();
+   }
+}
+
+U32 CollisionComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if (stream->writeFlag(mask & (ColliderMask | InitialUpdateMask)))
+   {
+      stream->write((U32)mCollisionType);
+      stream->writeString(colisionMeshPrefix);
+   }
+
+   return retMask;
+}
+
+void CollisionComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if (stream->readFlag()) // UpdateMask
+   {
+      U32 collisionType = CollisionMesh;
+
+      stream->read(&collisionType);
+
+      // Handle it if we have changed CollisionType's
+      if ((MeshType)collisionType != mCollisionType)
+      {
+         mCollisionType = (MeshType)collisionType;
+
+         prepCollision();
+      }
+
+      char readBuffer[1024];
+
+      stream->readString(readBuffer);
+      colisionMeshPrefix = StringTable->insert(readBuffer);
+   }
+}
+
+void CollisionComponent::ownerTransformSet(MatrixF *mat)
+{
+   if (mPhysicsRep)
+      mPhysicsRep->setTransform(mOwner->getTransform());
+}
+
+void CollisionComponent::targetShapeChanged(RenderComponentInterface* instanceInterface)
+{
+   prepCollision();
+}
+
+void CollisionComponent::prepCollision()
+{
+   if (!mOwner)
+      return;
+
+   // Let the client know that the collision was updated
+   setMaskBits(ColliderMask);
+
+   mOwner->disableCollision();
+
+   if ((!PHYSICSMGR || mCollisionType == None) ||
+      (mOwnerRenderInterface == NULL && (mCollisionType == CollisionMesh || mCollisionType == VisibleMesh)))
+      return;
+
+   PhysicsCollision *colShape = NULL;
+
+   if (mCollisionType == Bounds)
+   {
+      MatrixF offset(true);
+
+      if (mOwnerRenderInterface && mOwnerRenderInterface->getShape())
+         offset.setPosition(mOwnerRenderInterface->getShape()->center);
+
+      colShape = PHYSICSMGR->createCollision();
+      colShape->addBox(mOwner->getObjBox().getExtents() * 0.5f * mOwner->getScale(), offset);
+   }
+   else if (mCollisionType == CollisionMesh || (mCollisionType == VisibleMesh /*&& !mOwner->getComponent<AnimatedMesh>()*/))
+   {
+      colShape = buildColShapes();
+   }
+
+   if (colShape)
+   {
+      mPhysicsWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
+
+      if (mPhysicsRep)
+      {
+         if (mBlockColliding)
+            mPhysicsRep->init(colShape, 0, 0, mOwner, mPhysicsWorld);
+         else
+            mPhysicsRep->init(colShape, 0, PhysicsBody::BF_TRIGGER, mOwner, mPhysicsWorld);
+
+         mPhysicsRep->setTransform(mOwner->getTransform());
+      }
+   }
+
+   mOwner->enableCollision();
+
+   onCollisionChanged.trigger(colShape);
+}
+
+void CollisionComponent::processTick()
+{
+   if (!isActive())
+      return;
+
+   //ProcessTick is where our collision testing begins!
+
+   //callback if we have a persisting contact
+   if (mContactInfo.contactObject)
+   {
+      if (mContactInfo.contactTimer > 0)
+      {
+         if (isMethod("updateContact"))
+            Con::executef(this, "updateContact");
+
+         if (mOwner->isMethod("updateContact"))
+            Con::executef(mOwner, "updateContact");
+      }
+
+      ++mContactInfo.contactTimer;
+   }
+   else if (mContactInfo.contactTimer != 0)
+      mContactInfo.clear();
+}
+
+void CollisionComponent::updatePhysics()
+{
+   
+}
+
+PhysicsCollision* CollisionComponent::getCollisionData()
+{
+   if ((!PHYSICSMGR || mCollisionType == None) || mOwnerRenderInterface == NULL)
+      return NULL;
+
+   PhysicsCollision *colShape = NULL;
+   if (mCollisionType == Bounds)
+   {
+      MatrixF offset(true);
+      offset.setPosition(mOwnerRenderInterface->getShape()->center);
+      colShape = PHYSICSMGR->createCollision();
+      colShape->addBox(mOwner->getObjBox().getExtents() * 0.5f * mOwner->getScale(), offset);
+   }
+   else if (mCollisionType == CollisionMesh || (mCollisionType == VisibleMesh/* && !mOwner->getComponent<AnimatedMesh>()*/))
+   {
+      colShape = buildColShapes();
+      //colShape = mOwnerShapeInstance->getShape()->buildColShape(mCollisionType == VisibleMesh, mOwner->getScale());
+   }
+   /*else if (mCollisionType == VisibleMesh && !mOwner->getComponent<AnimatedMesh>())
+   {
+   //We don't have support for visible mesh collisions with animated meshes currently in the physics abstraction layer
+   //so we don't generate anything if we're set to use a visible mesh but have an animated mesh component.
+   colShape = mOwnerShapeInstance->getShape()->buildColShape(mCollisionType == VisibleMesh, mOwner->getScale());
+   }*/
+   else if (mCollisionType == VisibleMesh/* && mOwner->getComponent<AnimatedMesh>()*/)
+   {
+      Con::printf("CollisionComponent::updatePhysics: Cannot use visible mesh collisions with an animated mesh!");
+   }
+
+   return colShape;
+}
+
+bool CollisionComponent::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
+{
+   if (!mCollisionType == None)
+   {
+      if (mPhysicsWorld)
+      {
+         return mPhysicsWorld->castRay(start, end, info, Point3F::Zero);
+      }
+   }
+
+   return false;
+}
+
+PhysicsCollision* CollisionComponent::buildColShapes()
+{
+   PROFILE_SCOPE(CollisionComponent_buildColShapes);
+
+   PhysicsCollision *colShape = NULL;
+   U32 surfaceKey = 0;
+
+   TSShape* shape = mOwnerRenderInterface->getShape();
+
+   if (mCollisionType == VisibleMesh)
+   {
+      // Here we build triangle collision meshes from the
+      // visible detail levels.
+
+      // A negative subshape on the detail means we don't have geometry.
+      const TSShape::Detail &detail = shape->details[0];
+      if (detail.subShapeNum < 0)
+         return NULL;
+
+      // We don't try to optimize the triangles we're given
+      // and assume the art was created properly for collision.
+      ConcretePolyList polyList;
+      polyList.setTransform(&MatrixF::Identity, mOwner->getScale());
+
+      // Create the collision meshes.
+      S32 start = shape->subShapeFirstObject[detail.subShapeNum];
+      S32 end = start + shape->subShapeNumObjects[detail.subShapeNum];
+      for (S32 o = start; o < end; o++)
+      {
+         const TSShape::Object &object = shape->objects[o];
+         if (detail.objectDetailNum >= object.numMeshes)
+            continue;
+
+         // No mesh or no verts.... nothing to do.
+         TSMesh *mesh = shape->meshes[object.startMeshIndex + detail.objectDetailNum];
+         if (!mesh || mesh->mNumVerts == 0)
+            continue;
+
+         // Gather the mesh triangles.
+         polyList.clear();
+         mesh->buildPolyList(0, &polyList, surfaceKey, NULL);
+
+         // Create the collision shape if we haven't already.
+         if (!colShape)
+            colShape = PHYSICSMGR->createCollision();
+
+         // Get the object space mesh transform.
+         MatrixF localXfm;
+         shape->getNodeWorldTransform(object.nodeIndex, &localXfm);
+
+         colShape->addTriangleMesh(polyList.mVertexList.address(),
+            polyList.mVertexList.size(),
+            polyList.mIndexList.address(),
+            polyList.mIndexList.size() / 3,
+            localXfm);
+      }
+
+      // Return what we built... if anything.
+      return colShape;
+   }
+   else if (mCollisionType == CollisionMesh)
+   {
+
+      // Scan out the collision hulls...
+      //
+      // TODO: We need to support LOS collision for physics.
+      //
+      for (U32 i = 0; i < shape->details.size(); i++)
+      {
+         const TSShape::Detail &detail = shape->details[i];
+         const String &name = shape->names[detail.nameIndex];
+
+         // Is this a valid collision detail.
+         if (!dStrStartsWith(name, colisionMeshPrefix) || detail.subShapeNum < 0)
+            continue;
+
+         // Now go thru the meshes for this detail.
+         S32 start = shape->subShapeFirstObject[detail.subShapeNum];
+         S32 end = start + shape->subShapeNumObjects[detail.subShapeNum];
+         if (start >= end)
+            continue;
+
+         for (S32 o = start; o < end; o++)
+         {
+            const TSShape::Object &object = shape->objects[o];
+            const String &meshName = shape->names[object.nameIndex];
+
+            if (object.numMeshes <= detail.objectDetailNum)
+               continue;
+
+            // No mesh, a flat bounds, or no verts.... nothing to do.
+            TSMesh *mesh = shape->meshes[object.startMeshIndex + detail.objectDetailNum];
+            if (!mesh || mesh->getBounds().isEmpty() || mesh->mNumVerts == 0)
+               continue;
+
+            // We need the default mesh transform.
+            MatrixF localXfm;
+            shape->getNodeWorldTransform(object.nodeIndex, &localXfm);
+
+            // We have some sort of collision shape... so allocate it.
+            if (!colShape)
+               colShape = PHYSICSMGR->createCollision();
+
+            // Any other mesh name we assume as a generic convex hull.
+            //
+            // Collect the verts using the vertex polylist which will 
+            // filter out duplicates.  This is importaint as the convex
+            // generators can sometimes fail with duplicate verts.
+            //
+            VertexPolyList polyList;
+            MatrixF meshMat(localXfm);
+
+            Point3F t = meshMat.getPosition();
+            t.convolve(mOwner->getScale());
+            meshMat.setPosition(t);
+
+            polyList.setTransform(&MatrixF::Identity, mOwner->getScale());
+            mesh->buildPolyList(0, &polyList, surfaceKey, NULL);
+            colShape->addConvex(polyList.getVertexList().address(),
+               polyList.getVertexList().size(),
+               meshMat);
+         } // objects
+      } // details
+   }
+
+   return colShape;
+}

+ 208 - 0
Engine/source/T3D/components/Collision/collisionComponent.h

@@ -0,0 +1,208 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef COLLISION_COMPONENT_H
+#define COLLISION_COMPONENT_H
+
+#ifndef __RESOURCE_H__
+#include "core/resource.h"
+#endif
+#ifndef _TSSHAPE_H_
+#include "ts/tsShape.h"
+#endif
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+#ifndef COLLISION_INTERFACES_H
+#include "T3D/Components/collision/collisionInterfaces.h"
+#endif
+#ifndef RENDER_COMPONENT_INTERFACE_H
+#include "T3D/Components/render/renderComponentInterface.h"
+#endif
+#ifndef PHYSICS_COMPONENT_INTERFACE_H
+#include "T3D/Components/physics/physicsComponentInterface.h"
+#endif
+#ifndef _T3D_PHYSICSCOMMON_H_
+#include "T3D/physics/physicsCommon.h"
+#endif
+#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_
+#include "T3D/physics/physicsWorld.h"
+#endif
+
+class TSShapeInstance;
+class SceneRenderState;
+class CollisionComponent;
+class PhysicsBody;
+class PhysicsWorld;
+
+class CollisionComponent : public Component,
+   public CollisionInterface,
+   public CastRayInterface
+{
+   typedef Component Parent;
+public:
+   enum MeshType
+   {
+      None = 0,            ///< No mesh
+      Bounds = 1,          ///< Bounding box of the shape
+      CollisionMesh = 2,   ///< Specifically designated collision meshes
+      VisibleMesh = 3      ///< Rendered mesh polygons
+   };
+
+   PhysicsWorld* mPhysicsWorld;
+   PhysicsBody* mPhysicsRep;
+
+protected:
+   MeshType mCollisionType;
+   MeshType mDecalType;
+   MeshType mLOSType;
+
+   Vector<S32> mCollisionDetails;
+   Vector<S32> mLOSDetails;
+
+   StringTableEntry colisionMeshPrefix;
+
+   RenderComponentInterface* mOwnerRenderInterface;
+
+   PhysicsComponentInterface* mOwnerPhysicsInterface;
+
+   //only really relevent for the collision mesh type
+   //if we note an animation component is added, we flag as being animated.
+   //This way, if we're using collision meshes, we can set it up to update their transforms
+   //as needed
+   bool mAnimated;
+
+   enum
+   {
+      ColliderMask = Parent::NextFreeMask,
+   };
+
+public:
+   CollisionComponent();
+   virtual ~CollisionComponent();
+   DECLARE_CONOBJECT(CollisionComponent);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   virtual void componentAddedToOwner(Component *comp);
+   virtual void componentRemovedFromOwner(Component *comp);
+   virtual void ownerTransformSet(MatrixF *mat);
+   void targetShapeChanged(RenderComponentInterface* instanceInterface);
+
+   virtual void onComponentRemove();
+   virtual void onComponentAdd();
+
+   virtual void checkDependencies();
+
+   static void initPersistFields();
+
+   void inspectPostApply();
+
+   virtual void processTick();
+
+   void prepCollision();
+
+   PhysicsCollision* buildColShapes();
+
+   void updatePhysics();
+
+   virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
+
+   virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere){ return false; }
+
+   virtual PhysicsCollision* getCollisionData();
+
+   //Utility functions, mostly for script
+   Point3F getContactNormal() { return mContactInfo.contactNormal; }
+   bool hasContact()
+   {
+      if (mContactInfo.contactObject)
+         return true;
+      else
+         return false;
+   }
+   S32 getCollisionCount()
+   {
+      return mCollisionList.getCount();
+   }
+
+   Point3F getCollisionNormal(S32 collisionIndex)
+   {
+      if (collisionIndex < 0 || mCollisionList.getCount() < collisionIndex)
+         return Point3F::Zero;
+
+      return mCollisionList[collisionIndex].normal;
+   }
+
+   F32 getCollisionAngle(S32 collisionIndex, Point3F upVector)
+   {
+      if (collisionIndex < 0 || mCollisionList.getCount() < collisionIndex)
+         return 0.0f;
+
+      return mRadToDeg(mAcos(mDot(mCollisionList[collisionIndex].normal, upVector)));
+   }
+
+   S32 getBestCollision(Point3F upVector)
+   {
+      S32 bestCollision = -1;
+
+      F32 bestAngle = 360.f;
+      S32 count = mCollisionList.getCount();
+      for (U32 i = 0; i < count; ++i)
+      {
+         F32 angle = mRadToDeg(mAcos(mDot(mCollisionList[i].normal, upVector)));
+
+         if (angle < bestAngle)
+         {
+            bestCollision = i;
+            bestAngle = angle;
+         }
+      }
+
+      return bestCollision;
+   }
+
+   F32 getBestCollisionAngle(VectorF upVector)
+   {
+      S32 bestCol = getBestCollision(upVector);
+
+      if (bestCol == -1)
+         return 0;
+
+      return getCollisionAngle(bestCol, upVector);
+   }
+};
+
+typedef CollisionComponent::MeshType CollisionMeshMeshType;
+DefineEnumType(CollisionMeshMeshType);
+
+#endif // COLLISION_COMPONENT_H

+ 258 - 0
Engine/source/T3D/components/Collision/collisionInterfaces.cpp

@@ -0,0 +1,258 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/collision/collisionInterfaces.h"
+#include "scene/sceneObject.h"
+#include "T3D/Entity.h"
+#include "console/engineAPI.h"
+#include "T3D/trigger.h"
+#include "materials/baseMatInstance.h"
+
+void CollisionInterface::handleCollisionList( CollisionList &collisionList, VectorF velocity )
+{
+   Collision bestCol;
+
+   mCollisionList = collisionList;
+
+   for (U32 i=0; i < collisionList.getCount(); ++i)
+   {
+      Collision& colCheck = collisionList[i];
+
+      if (colCheck.object)
+      {
+         if (colCheck.object->getTypeMask() & PlayerObjectType)
+         {
+            handleCollision( colCheck, velocity );
+         }
+         else if (colCheck.object->getTypeMask() & TriggerObjectType)
+         {
+            // We've hit it's bounding box, that's close enough for triggers
+            Trigger* pTrigger = static_cast<Trigger*>(colCheck.object);
+
+            Component *comp = dynamic_cast<Component*>(this);
+            pTrigger->potentialEnterObject(comp->getOwner());
+         }
+         else if (colCheck.object->getTypeMask() & DynamicShapeObjectType)
+         {
+            Con::printf("HIT A GENERICALLY DYNAMIC OBJECT");
+            handleCollision(colCheck, velocity);
+         }
+         else if(colCheck.object->getTypeMask() & EntityObjectType)
+         {
+            Entity* ent = dynamic_cast<Entity*>(colCheck.object);
+            if (ent)
+            {
+               CollisionInterface *colObjectInterface = ent->getComponent<CollisionInterface>();
+               if (colObjectInterface)
+               {
+                  //convert us to our component
+                  Component *thisComp = dynamic_cast<Component*>(this);
+                  if (thisComp)
+                  {
+                     colObjectInterface->onCollisionSignal.trigger(thisComp->getOwner());
+
+                     //TODO: properly do this
+                     Collision oppositeCol = colCheck;
+                     oppositeCol.object = thisComp->getOwner();
+
+                     colObjectInterface->handleCollision(oppositeCol, velocity);
+                  }
+               }
+            }
+         }
+         else
+         {
+            handleCollision(colCheck, velocity);
+         }
+      }
+   }
+}
+
+void CollisionInterface::handleCollision( Collision &col, VectorF velocity )
+{
+   if (col.object && (mContactInfo.contactObject == NULL ||
+      col.object->getId() != mContactInfo.contactObject->getId()))
+   {
+      queueCollision(col.object, velocity - col.object->getVelocity());
+
+      //do the callbacks to script for this collision
+      Component *comp = dynamic_cast<Component*>(this);
+      if (comp->isMethod("onCollision"))
+      {
+         S32 matId = col.material != NULL ? col.material->getMaterial()->getId() : 0;
+         Con::executef(comp, "onCollision", col.object, col.normal, col.point, matId, velocity);
+      }
+
+      if (comp->getOwner()->isMethod("onCollisionEvent"))
+      {
+         S32 matId = col.material != NULL ? col.material->getMaterial()->getId() : 0;
+         Con::executef(comp->getOwner(), "onCollisionEvent", col.object, col.normal, col.point, matId, velocity);
+      }
+   }
+}
+
+void CollisionInterface::handleCollisionNotifyList()
+{
+   //special handling for any collision components we should notify that a collision happened.
+   for (U32 i = 0; i < mCollisionNotifyList.size(); ++i)
+   {
+      //convert us to our component
+      Component *thisComp = dynamic_cast<Component*>(this);
+      if (thisComp)
+      {
+         mCollisionNotifyList[i]->onCollisionSignal.trigger(thisComp->getOwner());
+      }
+   }
+
+   mCollisionNotifyList.clear();
+}
+
+Chunker<CollisionInterface::CollisionTimeout> sTimeoutChunker;
+CollisionInterface::CollisionTimeout* CollisionInterface::sFreeTimeoutList = 0;
+
+void CollisionInterface::queueCollision( SceneObject *obj, const VectorF &vec)
+{
+   // Add object to list of collisions.
+   SimTime time = Sim::getCurrentTime();
+   S32 num = obj->getId();
+
+   CollisionTimeout** adr = &mTimeoutList;
+   CollisionTimeout* ptr = mTimeoutList;
+   while (ptr) 
+   {
+      if (ptr->objectNumber == num) 
+      {
+         if (ptr->expireTime < time) 
+         {
+            ptr->expireTime = time + CollisionTimeoutValue;
+            ptr->object = obj;
+            ptr->vector = vec;
+         }
+         return;
+      }
+      // Recover expired entries
+      if (ptr->expireTime < time) 
+      {
+         CollisionTimeout* cur = ptr;
+         *adr = ptr->next;
+         ptr = ptr->next;
+         cur->next = sFreeTimeoutList;
+         sFreeTimeoutList = cur;
+      }
+      else 
+      {
+         adr = &ptr->next;
+         ptr = ptr->next;
+      }
+   }
+
+   // New entry for the object
+   if (sFreeTimeoutList != NULL)
+   {
+      ptr = sFreeTimeoutList;
+      sFreeTimeoutList = ptr->next;
+      ptr->next = NULL;
+   }
+   else
+   {
+      ptr = sTimeoutChunker.alloc();
+   }
+
+   ptr->object = obj;
+   ptr->objectNumber = obj->getId();
+   ptr->vector = vec;
+   ptr->expireTime = time + CollisionTimeoutValue;
+   ptr->next = mTimeoutList;
+
+   mTimeoutList = ptr;
+}
+
+bool CollisionInterface::checkEarlyOut(Point3F start, VectorF velocity, F32 time, Box3F objectBox, Point3F objectScale, 
+														Box3F collisionBox, U32 collisionMask, CollisionWorkingList &colWorkingList)
+{
+   Point3F end = start + velocity * time;
+   Point3F distance = end - start;
+
+   Box3F scaledBox = objectBox;
+   scaledBox.minExtents.convolve(objectScale);
+   scaledBox.maxExtents.convolve(objectScale);
+
+   if (mFabs(distance.x) < objectBox.len_x() &&
+      mFabs(distance.y) < objectBox.len_y() &&
+      mFabs(distance.z) < objectBox.len_z())
+   {
+      // We can potentially early out of this.  If there are no polys in the clipped polylist at our
+      //  end position, then we can bail, and just set start = end;
+      Box3F wBox = scaledBox;
+      wBox.minExtents += end;
+      wBox.maxExtents += end;
+
+      static EarlyOutPolyList eaPolyList;
+      eaPolyList.clear();
+      eaPolyList.mNormal.set(0.0f, 0.0f, 0.0f);
+      eaPolyList.mPlaneList.clear();
+      eaPolyList.mPlaneList.setSize(6);
+      eaPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1.0f, 0.0f, 0.0f));
+      eaPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0.0f, 1.0f, 0.0f));
+      eaPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1.0f, 0.0f, 0.0f));
+      eaPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0.0f, -1.0f, 0.0f));
+      eaPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0.0f, 0.0f, -1.0f));
+      eaPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0.0f, 0.0f, 1.0f));
+
+      // Build list from convex states here...
+      CollisionWorkingList& rList = colWorkingList;
+      CollisionWorkingList* pList = rList.wLink.mNext;
+      while (pList != &rList) 
+      {
+         Convex* pConvex = pList->mConvex;
+
+         if (pConvex->getObject()->getTypeMask() & collisionMask) 
+         {
+            Box3F convexBox = pConvex->getBoundingBox();
+
+            if (wBox.isOverlapped(convexBox))
+            {
+               // No need to separate out the physical zones here, we want those
+               //  to cause a fallthrough as well...
+               pConvex->getPolyList(&eaPolyList);
+            }
+         }
+         pList = pList->wLink.mNext;
+      }
+
+      if (eaPolyList.isEmpty())
+      {
+         return true;
+      }
+   }
+
+   return false;
+}
+
+
+Collision* CollisionInterface::getCollision(S32 col) 
+{ 
+   if(col < mCollisionList.getCount() && col >= 0) 
+      return &mCollisionList[col];
+   else 
+      return NULL; 
+}

+ 167 - 0
Engine/source/T3D/components/Collision/collisionInterfaces.h

@@ -0,0 +1,167 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef COLLISION_INTERFACES_H
+#define COLLISION_INTERFACES_H
+
+#ifndef _CONVEX_H_
+#include "collision/convex.h"
+#endif
+#ifndef _COLLISION_H_
+#include "collision/collision.h"
+#endif
+#ifndef _EARLYOUTPOLYLIST_H_
+#include "collision/earlyOutPolyList.h"
+#endif
+#ifndef _SIM_H_
+#include "console/sim.h"
+#endif
+#ifndef _SCENECONTAINER_H_
+#include "scene/sceneContainer.h"
+#endif
+#ifndef _T3D_PHYSICSCOMMON_H_
+#include "T3D/physics/physicsCommon.h"
+#endif
+
+struct ContactInfo 
+{
+   bool contacted, move;
+   SceneObject *contactObject;
+   VectorF  idealContactNormal;
+   VectorF  contactNormal;
+   Point3F  contactPoint;
+   F32	   contactTime;
+   S32	   contactTimer;
+   BaseMatInstance *contactMaterial;
+
+   void clear()
+   {
+      contacted=move=false; 
+      contactObject = NULL; 
+      contactNormal.set(0,0,0);
+      contactTime = 0.f;
+      contactTimer = 0;
+      idealContactNormal.set(0, 0, 1);
+      contactMaterial = NULL;
+   }
+
+   ContactInfo() { clear(); }
+
+};
+
+class CollisionInterface// : public Interface<CollisionInterface>
+{
+public:
+	// CollisionTimeout
+	// This struct lets us track our collisions and estimate when they've have timed out and we'll need to act on it.
+	struct CollisionTimeout 
+   {
+      CollisionTimeout* next;
+      SceneObject* object;
+      U32 objectNumber;
+      SimTime expireTime;
+      VectorF vector;
+   };
+
+   Signal< void( SceneObject* ) > CollisionInterface::onCollisionSignal;
+   Signal< void( SceneObject* ) > CollisionInterface::onContactSignal;
+
+protected:
+   CollisionTimeout* mTimeoutList;
+   static CollisionTimeout* sFreeTimeoutList;
+
+   CollisionList mCollisionList;
+   Vector<CollisionInterface*> mCollisionNotifyList;
+
+   ContactInfo mContactInfo;
+
+   Box3F mWorkingQueryBox;
+
+   U32 CollisionMoveMask;
+
+   Convex *mConvexList;
+
+   bool mBlockColliding;
+
+   void handleCollisionNotifyList();
+
+   void queueCollision( SceneObject *obj, const VectorF &vec);
+
+	/// checkEarlyOut
+	/// This function lets you trying and early out of any expensive collision checks by using simple extruded poly boxes representing our objects
+	/// If it returns true, we know we won't hit with the given parameters and can successfully early out. If it returns false, our test case collided
+	/// and we should do the full collision sim.
+	bool checkEarlyOut(Point3F start, VectorF velocity, F32 time, Box3F objectBox, Point3F objectScale, 
+														Box3F collisionBox, U32 collisionMask, CollisionWorkingList &colWorkingList);
+
+public:
+   /// checkCollisions
+   // This is our main function for checking if a collision is happening based on the start point, velocity and time
+   // We do the bulk of the collision checking in here
+   //virtual bool checkCollisions( const F32 travelTime, Point3F *velocity, Point3F start )=0;
+
+   CollisionList *getCollisionList() { return &mCollisionList; }
+
+   void clearCollisionList() { mCollisionList.clear(); }
+
+   void clearCollisionNotifyList() { mCollisionNotifyList.clear(); }
+
+   Collision *getCollision(S32 col);
+
+   ContactInfo* getContactInfo() { return &mContactInfo; }
+
+	Convex *getConvexList() { return mConvexList; }
+
+   virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) = 0;
+
+	enum PublicConstants { 
+      CollisionTimeoutValue = 250
+   };
+
+   bool doesBlockColliding() { return mBlockColliding; }
+
+   /// handleCollisionList
+   /// This basically takes in a CollisionList and calls handleCollision for each.
+   void handleCollisionList(CollisionList &collisionList, VectorF velocity);
+
+   /// handleCollision
+   /// This will take a collision and queue the collision info for the object so that in knows about the collision.
+   void handleCollision(Collision &col, VectorF velocity);
+
+   virtual PhysicsCollision* getCollisionData() = 0;
+
+   Signal< void(PhysicsCollision* collision) > CollisionInterface::onCollisionChanged;
+};
+
+class BuildConvexInterface //: public Interface<CollisionInterface>
+{
+public:
+   virtual void buildConvex(const Box3F& box, Convex* convex)=0;
+};
+
+class BuildPolyListInterface// : public Interface<CollisionInterface>
+{
+public:
+   virtual bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) = 0;
+};
+
+#endif

+ 619 - 0
Engine/source/T3D/components/Collision/collisionTrigger.cpp

@@ -0,0 +1,619 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "platform/platform.h"
+#include "T3D/Components/Collision/CollisionTrigger.h"
+
+#include "scene/sceneRenderState.h"
+#include "console/consoleTypes.h"
+#include "console/engineAPI.h"
+#include "collision/boxConvex.h"
+
+#include "core/stream/bitStream.h"
+#include "math/mathIO.h"
+#include "gfx/gfxTransformSaver.h"
+#include "renderInstance/renderPassManager.h"
+#include "gfx/gfxDrawUtil.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/physics/physicsBody.h"
+#include "T3D/physics/physicsCollision.h"
+
+
+bool CollisionTrigger::smRenderCollisionTriggers = false;
+
+//-----------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------
+//--------------------------------------------------------------------------
+
+IMPLEMENT_CO_NETOBJECT_V1(CollisionTrigger);
+
+ConsoleDocClass(CollisionTrigger,
+   "@brief A CollisionTrigger is a volume of space that initiates script callbacks "
+   "when objects pass through the CollisionTrigger.\n\n"
+
+   "CollisionTriggerData provides the callbacks for the CollisionTrigger when an object enters, stays inside "
+   "or leaves the CollisionTrigger's volume.\n\n"
+
+   "@see CollisionTriggerData\n"
+   "@ingroup gameObjects\n"
+   );
+
+IMPLEMENT_CALLBACK(CollisionTrigger, onAdd, void, (U32 objectId), (objectId),
+   "@brief Called when the CollisionTrigger is being created.\n\n"
+   "@param objectId the object id of the CollisionTrigger being created\n");
+
+IMPLEMENT_CALLBACK(CollisionTrigger, onRemove, void, (U32 objectId), (objectId),
+   "@brief Called just before the CollisionTrigger is deleted.\n\n"
+   "@param objectId the object id of the CollisionTrigger being deleted\n");
+
+CollisionTrigger::CollisionTrigger()
+{
+   // Don't ghost by default.
+   mNetFlags.set(Ghostable | ScopeAlways);
+
+   mTypeMask |= TriggerObjectType;
+
+   mObjScale.set(1, 1, 1);
+   mObjToWorld.identity();
+   mWorldToObj.identity();
+
+   mLastThink = 0;
+   mCurrTick = 0;
+
+   mConvexList = new Convex;
+
+   mPhysicsRep = NULL;
+}
+
+CollisionTrigger::~CollisionTrigger()
+{
+   delete mConvexList;
+   mConvexList = NULL;
+   SAFE_DELETE(mPhysicsRep);
+}
+
+bool CollisionTrigger::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
+{
+   // Collide against bounding box
+   F32 st, et, fst = 0, fet = 1;
+   F32 *bmin = &mObjBox.minExtents.x;
+   F32 *bmax = &mObjBox.maxExtents.x;
+   F32 const *si = &start.x;
+   F32 const *ei = &end.x;
+
+   for (S32 i = 0; i < 3; i++)
+   {
+      if (*si < *ei)
+      {
+         if (*si > *bmax || *ei < *bmin)
+            return false;
+         F32 di = *ei - *si;
+         st = (*si < *bmin) ? (*bmin - *si) / di : 0;
+         et = (*ei > *bmax) ? (*bmax - *si) / di : 1;
+      }
+      else
+      {
+         if (*ei > *bmax || *si < *bmin)
+            return false;
+         F32 di = *ei - *si;
+         st = (*si > *bmax) ? (*bmax - *si) / di : 0;
+         et = (*ei < *bmin) ? (*bmin - *si) / di : 1;
+      }
+      if (st > fst) fst = st;
+      if (et < fet) fet = et;
+      if (fet < fst)
+         return false;
+      bmin++; bmax++;
+      si++; ei++;
+   }
+
+   info->normal = start - end;
+   info->normal.normalizeSafe();
+   getTransform().mulV(info->normal);
+
+   info->t = fst;
+   info->object = this;
+   info->point.interpolate(start, end, fst);
+   info->material = 0;
+   return true;
+}
+
+//-----------------------------------------------------------------------------
+void CollisionTrigger::consoleInit()
+{
+   Con::addVariable("$CollisionTrigger::renderCollisionTriggers", TypeBool, &smRenderCollisionTriggers,
+      "@brief Forces all CollisionTrigger's to render.\n\n"
+      "Used by the Tools and debug render modes.\n"
+      "@ingroup gameObjects");
+}
+
+void CollisionTrigger::initPersistFields()
+{
+   addField("polyhedron", TypeTriggerPolyhedron, Offset(mCollisionTriggerPolyhedron, CollisionTrigger),
+      "@brief Defines a non-rectangular area for the CollisionTrigger.\n\n"
+      "Rather than the standard rectangular bounds, this optional parameter defines a quadrilateral "
+      "CollisionTrigger area.  The quadrilateral is defined as a corner point followed by three vectors "
+      "representing the edges extending from the corner.\n");
+
+   addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, CollisionTrigger), &setEnterCmd, &defaultProtectedGetFn,
+      "The command to execute when an object enters this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
+   addProtectedField("leaveCommand", TypeCommand, Offset(mLeaveCommand, CollisionTrigger), &setLeaveCmd, &defaultProtectedGetFn,
+      "The command to execute when an object leaves this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
+   addProtectedField("tickCommand", TypeCommand, Offset(mTickCommand, CollisionTrigger), &setTickCmd, &defaultProtectedGetFn,
+      "The command to execute while an object is inside this CollisionTrigger. Maximum 1023 characters.");
+
+   Parent::initPersistFields();
+}
+
+bool CollisionTrigger::setEnterCmd(void *object, const char *index, const char *data)
+{
+   static_cast<CollisionTrigger*>(object)->setMaskBits(EnterCmdMask);
+   return true; // to update the actual field
+}
+
+bool CollisionTrigger::setLeaveCmd(void *object, const char *index, const char *data)
+{
+   static_cast<CollisionTrigger*>(object)->setMaskBits(LeaveCmdMask);
+   return true; // to update the actual field
+}
+
+bool CollisionTrigger::setTickCmd(void *object, const char *index, const char *data)
+{
+   static_cast<CollisionTrigger*>(object)->setMaskBits(TickCmdMask);
+   return true; // to update the actual field
+}
+
+//--------------------------------------------------------------------------
+
+bool CollisionTrigger::onAdd()
+{
+   if (!Parent::onAdd())
+      return false;
+
+   onAdd_callback(getId());
+
+   Polyhedron temp = mCollisionTriggerPolyhedron;
+   setTriggerPolyhedron(temp);
+
+   addToScene();
+
+   if (isServerObject())
+      scriptOnAdd();
+
+   return true;
+}
+
+void CollisionTrigger::onRemove()
+{
+   onRemove_callback(getId());
+
+   mConvexList->nukeList();
+
+   removeFromScene();
+   Parent::onRemove();
+}
+
+bool CollisionTrigger::onNewDataBlock(GameBaseData *dptr, bool reload)
+{
+   return true;
+}
+
+void CollisionTrigger::onDeleteNotify(SimObject *obj)
+{
+   GameBase* pScene = dynamic_cast<GameBase*>(obj);
+
+   if (pScene != NULL)
+   {
+      for (U32 i = 0; i < mObjects.size(); i++)
+      {
+         if (pScene == mObjects[i])
+         {
+            mObjects.erase(i);
+            //onLeaveCollisionTrigger_callback(this, pScene);
+            break;
+         }
+      }
+   }
+
+   Parent::onDeleteNotify(obj);
+}
+
+void CollisionTrigger::inspectPostApply()
+{
+   setTriggerPolyhedron(mCollisionTriggerPolyhedron);
+   setMaskBits(PolyMask);
+   Parent::inspectPostApply();
+}
+
+//--------------------------------------------------------------------------
+
+void CollisionTrigger::buildConvex(const Box3F& box, Convex* convex)
+{
+   // These should really come out of a pool
+   mConvexList->collectGarbage();
+
+   Box3F realBox = box;
+   mWorldToObj.mul(realBox);
+   realBox.minExtents.convolveInverse(mObjScale);
+   realBox.maxExtents.convolveInverse(mObjScale);
+
+   if (realBox.isOverlapped(getObjBox()) == false)
+      return;
+
+   // Just return a box convex for the entire shape...
+   Convex* cc = 0;
+   CollisionWorkingList& wl = convex->getWorkingList();
+   for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
+      if (itr->mConvex->getType() == BoxConvexType &&
+         itr->mConvex->getObject() == this) {
+         cc = itr->mConvex;
+         break;
+      }
+   }
+   if (cc)
+      return;
+
+   // Create a new convex.
+   BoxConvex* cp = new BoxConvex;
+   mConvexList->registerObject(cp);
+   convex->addToWorkingList(cp);
+   cp->init(this);
+
+   mObjBox.getCenter(&cp->mCenter);
+   cp->mSize.x = mObjBox.len_x() / 2.0f;
+   cp->mSize.y = mObjBox.len_y() / 2.0f;
+   cp->mSize.z = mObjBox.len_z() / 2.0f;
+}
+
+
+//------------------------------------------------------------------------------
+
+void CollisionTrigger::setTransform(const MatrixF & mat)
+{
+   Parent::setTransform(mat);
+
+   if (mPhysicsRep)
+      mPhysicsRep->setTransform(mat);
+
+   if (isServerObject()) {
+      MatrixF base(true);
+      base.scale(Point3F(1.0 / mObjScale.x,
+         1.0 / mObjScale.y,
+         1.0 / mObjScale.z));
+      base.mul(mWorldToObj);
+      mClippedList.setBaseTransform(base);
+
+      setMaskBits(TransformMask | ScaleMask);
+   }
+}
+
+void CollisionTrigger::prepRenderImage(SceneRenderState *state)
+{
+   // only render if selected or render flag is set
+   if (!smRenderCollisionTriggers && !isSelected())
+      return;
+
+   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+   ri->renderDelegate.bind(this, &CollisionTrigger::renderObject);
+   ri->type = RenderPassManager::RIT_Editor;
+   ri->translucentSort = true;
+   ri->defaultKey = 1;
+   state->getRenderPass()->addInst(ri);
+}
+
+void CollisionTrigger::renderObject(ObjectRenderInst *ri,
+   SceneRenderState *state,
+   BaseMatInstance *overrideMat)
+{
+   if (overrideMat)
+      return;
+
+   GFXStateBlockDesc desc;
+   desc.setZReadWrite(true, false);
+   desc.setBlend(true);
+
+   // CollisionTrigger polyhedrons are set up with outward facing normals and CCW ordering
+   // so can't enable backface culling.
+   desc.setCullMode(GFXCullNone);
+
+   GFXTransformSaver saver;
+
+   MatrixF mat = getRenderTransform();
+   mat.scale(getScale());
+
+   GFX->multWorld(mat);
+
+   GFXDrawUtil *drawer = GFX->getDrawUtil();
+
+   drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI(255, 192, 0, 45));
+
+   // Render wireframe.
+
+   desc.setFillModeWireframe();
+   drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI::BLACK);
+}
+
+void CollisionTrigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron)
+{
+   mCollisionTriggerPolyhedron = rPolyhedron;
+
+   if (mCollisionTriggerPolyhedron.pointList.size() != 0) {
+      mObjBox.minExtents.set(1e10, 1e10, 1e10);
+      mObjBox.maxExtents.set(-1e10, -1e10, -1e10);
+      for (U32 i = 0; i < mCollisionTriggerPolyhedron.pointList.size(); i++) {
+         mObjBox.minExtents.setMin(mCollisionTriggerPolyhedron.pointList[i]);
+         mObjBox.maxExtents.setMax(mCollisionTriggerPolyhedron.pointList[i]);
+      }
+   }
+   else {
+      mObjBox.minExtents.set(-0.5, -0.5, -0.5);
+      mObjBox.maxExtents.set(0.5, 0.5, 0.5);
+   }
+
+   MatrixF xform = getTransform();
+   setTransform(xform);
+
+   mClippedList.clear();
+   mClippedList.mPlaneList = mCollisionTriggerPolyhedron.planeList;
+   //   for (U32 i = 0; i < mClippedList.mPlaneList.size(); i++)
+   //      mClippedList.mPlaneList[i].neg();
+
+   MatrixF base(true);
+   base.scale(Point3F(1.0 / mObjScale.x,
+      1.0 / mObjScale.y,
+      1.0 / mObjScale.z));
+   base.mul(mWorldToObj);
+
+   mClippedList.setBaseTransform(base);
+
+   SAFE_DELETE(mPhysicsRep);
+
+   if (PHYSICSMGR)
+   {
+      PhysicsCollision *colShape = PHYSICSMGR->createCollision();
+
+      MatrixF colMat(true);
+      colMat.displace(Point3F(0, 0, mObjBox.getExtents().z * 0.5f * mObjScale.z));
+
+      colShape->addBox(mObjBox.getExtents() * 0.5f * mObjScale, colMat);
+      //MatrixF colMat( true );
+      //colMat.scale( mObjScale );
+      //colShape->addConvex( mCollisionTriggerPolyhedron.pointList.address(), mCollisionTriggerPolyhedron.pointList.size(), colMat );
+
+      PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
+      mPhysicsRep = PHYSICSMGR->createBody();
+      mPhysicsRep->init(colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world);
+      mPhysicsRep->setTransform(getTransform());
+   }
+}
+
+
+//--------------------------------------------------------------------------
+
+bool CollisionTrigger::testObject(GameBase* enter)
+{
+   if (mCollisionTriggerPolyhedron.pointList.size() == 0)
+      return false;
+
+   mClippedList.clear();
+
+   SphereF sphere;
+   sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5;
+   VectorF bv = mWorldBox.maxExtents - sphere.center;
+   sphere.radius = bv.len();
+
+   enter->buildPolyList(PLC_Collision, &mClippedList, mWorldBox, sphere);
+   return mClippedList.isEmpty() == false;
+}
+
+
+void CollisionTrigger::potentialEnterObject(GameBase* enter)
+{
+   for (U32 i = 0; i < mObjects.size(); i++) {
+      if (mObjects[i] == enter)
+         return;
+   }
+
+   if (testObject(enter) == true) {
+      mObjects.push_back(enter);
+      deleteNotify(enter);
+
+      if (!mEnterCommand.isEmpty())
+      {
+         String command = String("%obj = ") + enter->getIdString() + ";" + mEnterCommand;
+         Con::evaluate(command.c_str());
+      }
+
+      //onEnterCollisionTrigger_callback(this, enter);
+   }
+}
+
+
+void CollisionTrigger::processTick(const Move* move)
+{
+   Parent::processTick(move);
+
+   //
+   if (mObjects.size() == 0)
+      return;
+
+   if (mLastThink + 100 < mCurrTick)
+   {
+      mCurrTick = 0;
+      mLastThink = 0;
+
+      for (S32 i = S32(mObjects.size() - 1); i >= 0; i--)
+      {
+         if (testObject(mObjects[i]) == false)
+         {
+            GameBase* remove = mObjects[i];
+            mObjects.erase(i);
+            clearNotify(remove);
+
+            if (!mLeaveCommand.isEmpty())
+            {
+               String command = String("%obj = ") + remove->getIdString() + ";" + mLeaveCommand;
+               Con::evaluate(command.c_str());
+            }
+
+            //onLeaveCollisionTrigger_callback(this, remove);
+         }
+      }
+
+      if (!mTickCommand.isEmpty())
+         Con::evaluate(mTickCommand.c_str());
+
+      //if (mObjects.size() != 0)
+      //   onTickCollisionTrigger_callback(this);
+   }
+   else
+   {
+      mCurrTick += TickMs;
+   }
+}
+
+//--------------------------------------------------------------------------
+
+U32 CollisionTrigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
+{
+   U32 i;
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if (stream->writeFlag(mask & TransformMask))
+   {
+      stream->writeAffineTransform(mObjToWorld);
+   }
+
+   // Write the polyhedron
+   if (stream->writeFlag(mask & PolyMask))
+   {
+      stream->write(mCollisionTriggerPolyhedron.pointList.size());
+      for (i = 0; i < mCollisionTriggerPolyhedron.pointList.size(); i++)
+         mathWrite(*stream, mCollisionTriggerPolyhedron.pointList[i]);
+
+      stream->write(mCollisionTriggerPolyhedron.planeList.size());
+      for (i = 0; i < mCollisionTriggerPolyhedron.planeList.size(); i++)
+         mathWrite(*stream, mCollisionTriggerPolyhedron.planeList[i]);
+
+      stream->write(mCollisionTriggerPolyhedron.edgeList.size());
+      for (i = 0; i < mCollisionTriggerPolyhedron.edgeList.size(); i++) {
+         const Polyhedron::Edge& rEdge = mCollisionTriggerPolyhedron.edgeList[i];
+
+         stream->write(rEdge.face[0]);
+         stream->write(rEdge.face[1]);
+         stream->write(rEdge.vertex[0]);
+         stream->write(rEdge.vertex[1]);
+      }
+   }
+
+   if (stream->writeFlag(mask & EnterCmdMask))
+      stream->writeLongString(CMD_SIZE - 1, mEnterCommand.c_str());
+   if (stream->writeFlag(mask & LeaveCmdMask))
+      stream->writeLongString(CMD_SIZE - 1, mLeaveCommand.c_str());
+   if (stream->writeFlag(mask & TickCmdMask))
+      stream->writeLongString(CMD_SIZE - 1, mTickCommand.c_str());
+
+   return retMask;
+}
+
+void CollisionTrigger::unpackUpdate(NetConnection* con, BitStream* stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   U32 i, size;
+
+   // Transform
+   if (stream->readFlag())
+   {
+      MatrixF temp;
+      stream->readAffineTransform(&temp);
+      setTransform(temp);
+   }
+
+   // Read the polyhedron
+   if (stream->readFlag())
+   {
+      Polyhedron tempPH;
+      stream->read(&size);
+      tempPH.pointList.setSize(size);
+      for (i = 0; i < tempPH.pointList.size(); i++)
+         mathRead(*stream, &tempPH.pointList[i]);
+
+      stream->read(&size);
+      tempPH.planeList.setSize(size);
+      for (i = 0; i < tempPH.planeList.size(); i++)
+         mathRead(*stream, &tempPH.planeList[i]);
+
+      stream->read(&size);
+      tempPH.edgeList.setSize(size);
+      for (i = 0; i < tempPH.edgeList.size(); i++) {
+         Polyhedron::Edge& rEdge = tempPH.edgeList[i];
+
+         stream->read(&rEdge.face[0]);
+         stream->read(&rEdge.face[1]);
+         stream->read(&rEdge.vertex[0]);
+         stream->read(&rEdge.vertex[1]);
+      }
+      setTriggerPolyhedron(tempPH);
+   }
+
+   if (stream->readFlag())
+   {
+      char buf[CMD_SIZE];
+      stream->readLongString(CMD_SIZE - 1, buf);
+      mEnterCommand = buf;
+   }
+   if (stream->readFlag())
+   {
+      char buf[CMD_SIZE];
+      stream->readLongString(CMD_SIZE - 1, buf);
+      mLeaveCommand = buf;
+   }
+   if (stream->readFlag())
+   {
+      char buf[CMD_SIZE];
+      stream->readLongString(CMD_SIZE - 1, buf);
+      mTickCommand = buf;
+   }
+}
+
+//ConsoleMethod( CollisionTrigger, getNumObjects, S32, 2, 2, "")
+DefineEngineMethod(CollisionTrigger, getNumObjects, S32, (), ,
+   "@brief Get the number of objects that are within the CollisionTrigger's bounds.\n\n"
+   "@see getObject()\n")
+{
+   return object->getNumCollisionTriggeringObjects();
+}
+
+//ConsoleMethod( CollisionTrigger, getObject, S32, 3, 3, "(int idx)")
+DefineEngineMethod(CollisionTrigger, getObject, S32, (S32 index), ,
+   "@brief Retrieve the requested object that is within the CollisionTrigger's bounds.\n\n"
+   "@param index Index of the object to get (range is 0 to getNumObjects()-1)\n"
+   "@returns The SimObjectID of the object, or -1 if the requested index is invalid.\n"
+   "@see getNumObjects()\n")
+{
+   if (index >= object->getNumCollisionTriggeringObjects() || index < 0)
+      return -1;
+   else
+      return object->getObject(U32(index))->getId();
+}

+ 145 - 0
Engine/source/T3D/components/Collision/collisionTrigger.h

@@ -0,0 +1,145 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef _H_CollisionTrigger
+#define _H_CollisionTrigger
+
+#ifndef _GAMEBASE_H_
+#include "T3D/gameBase/gameBase.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef _EARLYOUTPOLYLIST_H_
+#include "collision/earlyOutPolyList.h"
+#endif
+#ifndef _MPOLYHEDRON_H_
+#include "math/mPolyhedron.h"
+#endif
+#ifndef _TRIGGER_H_
+#include "T3D/trigger.h"
+#endif
+
+class Convex;
+class PhysicsBody;
+class TriggerPolyhedronType;
+
+class CollisionTrigger : public GameBase
+{
+   typedef GameBase Parent;
+
+   /// CollisionTrigger polyhedron with *outward* facing normals and CCW ordered
+   /// vertices.
+   Polyhedron mCollisionTriggerPolyhedron;
+
+   EarlyOutPolyList  mClippedList;
+   Vector<GameBase*> mObjects;
+
+   PhysicsBody      *mPhysicsRep;
+
+   U32               mLastThink;
+   U32               mCurrTick;
+   Convex            *mConvexList;
+
+   String            mEnterCommand;
+   String            mLeaveCommand;
+   String            mTickCommand;
+
+   enum CollisionTriggerUpdateBits
+   {
+      TransformMask = Parent::NextFreeMask << 0,
+      PolyMask = Parent::NextFreeMask << 1,
+      EnterCmdMask = Parent::NextFreeMask << 2,
+      LeaveCmdMask = Parent::NextFreeMask << 3,
+      TickCmdMask = Parent::NextFreeMask << 4,
+      NextFreeMask = Parent::NextFreeMask << 5,
+   };
+
+   static const U32 CMD_SIZE = 1024;
+
+protected:
+
+   static bool smRenderCollisionTriggers;
+   bool testObject(GameBase* enter);
+   void processTick(const Move *move);
+
+   void buildConvex(const Box3F& box, Convex* convex);
+
+   static bool setEnterCmd(void *object, const char *index, const char *data);
+   static bool setLeaveCmd(void *object, const char *index, const char *data);
+   static bool setTickCmd(void *object, const char *index, const char *data);
+
+public:
+   CollisionTrigger();
+   ~CollisionTrigger();
+
+   // SimObject
+   DECLARE_CONOBJECT(CollisionTrigger);
+
+   DECLARE_CALLBACK(void, onAdd, (U32 objectId));
+   DECLARE_CALLBACK(void, onRemove, (U32 objectId));
+
+   static void consoleInit();
+   static void initPersistFields();
+   bool onAdd();
+   void onRemove();
+   void onDeleteNotify(SimObject*);
+   void inspectPostApply();
+
+   // NetObject
+   U32  packUpdate(NetConnection *conn, U32 mask, BitStream* stream);
+   void unpackUpdate(NetConnection *conn, BitStream* stream);
+
+   // SceneObject
+   void setTransform(const MatrixF &mat);
+   void prepRenderImage(SceneRenderState* state);
+
+   // GameBase
+   bool onNewDataBlock(GameBaseData *dptr, bool reload);
+
+   // CollisionTrigger
+   void setTriggerPolyhedron(const Polyhedron&);
+
+   void      potentialEnterObject(GameBase*);
+   U32       getNumCollisionTriggeringObjects() const;
+   GameBase* getObject(const U32);
+   const Vector<GameBase*>& getObjects() const { return mObjects; }
+
+   void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
+
+   bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
+};
+
+inline U32 CollisionTrigger::getNumCollisionTriggeringObjects() const
+{
+   return mObjects.size();
+}
+
+inline GameBase* CollisionTrigger::getObject(const U32 index)
+{
+   AssertFatal(index < getNumCollisionTriggeringObjects(), "Error, out of range object index");
+
+   return mObjects[index];
+}
+
+#endif // _H_CollisionTrigger
+

+ 638 - 0
Engine/source/T3D/components/Component.cpp

@@ -0,0 +1,638 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "platform/platform.h"
+#include "console/simBase.h"
+#include "console/consoleTypes.h"
+#include "T3D/Components/Component.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "core/stream/bitStream.h"
+#include "console/engineAPI.h"
+#include "sim/netConnection.h"
+#include "console/consoleInternal.h"
+
+#define DECLARE_NATIVE_COMPONENT( ComponentType )                   \
+	 Component* staticComponentTemplate = new ComponentType; \
+     Sim::gNativeComponentSet->addObject(staticComponentTemplate);
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+
+Component::Component()
+{
+   mFriendlyName = StringTable->lookup("");
+   mFromResource = StringTable->lookup("");
+   mComponentType = StringTable->lookup("");
+   mComponentGroup = StringTable->lookup("");
+   mNetworkType = StringTable->lookup("");
+   mTemplateName = StringTable->lookup("");
+   //mDependency = StringTable->lookup("");
+
+   mNetworked = false;
+
+
+   // [tom, 1/12/2007] We manage the memory for the description since it
+   // could be loaded from a file and thus massive. This is accomplished with
+   // protected fields, but since they still call Con::getData() the field
+   // needs to always be valid. This is pretty lame.
+   mDescription = new char[1];
+   ((char *)mDescription)[0] = 0;
+
+   mOwner = NULL;
+
+   mCanSaveFieldDictionary = false;
+
+   mNetFlags.set(Ghostable);
+}
+
+Component::~Component()
+{
+   for (S32 i = 0; i < mFields.size(); ++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(Component);
+
+//////////////////////////////////////////////////////////////////////////
+
+void Component::initPersistFields()
+{
+   addGroup("Component");
+   addField("componentType", TypeCaseString, Offset(mComponentType, Component), "The type of behavior.", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+   addField("networkType", TypeCaseString, Offset(mNetworkType, Component), "The type of behavior.", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+   addField("friendlyName", TypeCaseString, Offset(mFriendlyName, Component), "Human friendly name of this behavior", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+      addProtectedField("description", TypeCaseString, Offset(mDescription, Component), &setDescription, &getDescription,
+         "The description of this behavior which can be set to a \"string\" or a fileName\n", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+
+      addField("networked", TypeBool, Offset(mNetworked, Component), "Is this behavior ghosted to clients?", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+
+      addProtectedField("Owner", TypeSimObjectPtr, Offset(mOwner, Component), &setOwner, &defaultProtectedGetFn, "", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+
+      //addField("hidden", TypeBool, Offset(mHidden, Component), "Flags if this behavior is shown in the editor or not", AbstractClassRep::FieldFlags::FIELD_HideInInspectors);
+      addProtectedField("enabled", TypeBool, Offset(mEnabled, Component), &_setEnabled, &defaultProtectedGetFn, "");
+   endGroup("Component");
+
+   Parent::initPersistFields();
+
+   //clear out irrelevent fields
+   removeField("name");
+   //removeField("internalName");
+   removeField("parentGroup");
+   //removeField("class");
+   removeField("superClass");
+   removeField("hidden");
+   removeField("canSave");
+   removeField("canSaveDynamicFields");
+   removeField("persistentId");
+}
+
+bool Component::_setEnabled(void *object, const char *index, const char *data)
+{
+   Component *c = static_cast<Component*>(object);
+
+   c->mEnabled = dAtob(data);
+   c->setMaskBits(EnableMask);
+
+   return true;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+bool Component::setDescription(void *object, const char *index, const char *data)
+{
+   Component *bT = static_cast<Component *>(object);
+   SAFE_DELETE_ARRAY(bT->mDescription);
+   bT->mDescription = bT->getDescriptionText(data);
+
+   // We return false since we don't want the console to mess with the data
+   return false;
+}
+
+const char * Component::getDescription(void* obj, const char* data)
+{
+   Component *object = static_cast<Component *>(obj);
+
+   return object->mDescription ? object->mDescription : "";
+}
+
+//////////////////////////////////////////////////////////////////////////
+bool Component::onAdd()
+{
+   if (!Parent::onAdd())
+      return false;
+
+   setMaskBits(UpdateMask);
+
+   return true;
+}
+
+void Component::onRemove()
+{
+   onDataSet.removeAll();
+
+   if (mOwner)
+   {
+      //notify our removal to the owner, so we have no loose ends
+      mOwner->removeComponent(this, false);
+   }
+
+   Parent::onRemove();
+}
+
+void Component::onComponentAdd()
+{
+   if (isServerObject())
+   {
+      if (isMethod("onAdd"))
+         Con::executef(this, "onAdd");
+   }
+
+   mEnabled = true;
+}
+
+void Component::onComponentRemove()
+{
+   mEnabled = false;
+
+   if (isServerObject())
+   {
+      if (isMethod("onRemove"))
+         Con::executef(this, "onRemove");
+   }
+
+   if (mOwner)
+   {
+      mOwner->onComponentAdded.remove(this, &Component::componentAddedToOwner);
+      mOwner->onComponentRemoved.remove(this, &Component::componentRemovedFromOwner);
+      mOwner->onTransformSet.remove(this, &Component::ownerTransformSet);
+   }
+
+   mOwner = NULL;
+   setDataField("owner", NULL, "");
+}
+
+void Component::setOwner(Entity* owner)
+{
+   //first, catch if we have an existing owner, and we're changing from it
+   if (mOwner && mOwner != owner)
+   {
+      mOwner->onComponentAdded.remove(this, &Component::componentAddedToOwner);
+      mOwner->onComponentRemoved.remove(this, &Component::componentRemovedFromOwner);
+      mOwner->onTransformSet.remove(this, &Component::ownerTransformSet);
+
+      mOwner->removeComponent(this, false);
+   }
+
+   mOwner = owner;
+
+   if (mOwner != NULL)
+   {
+      mOwner->onComponentAdded.notify(this, &Component::componentAddedToOwner);
+      mOwner->onComponentRemoved.notify(this, &Component::componentRemovedFromOwner);
+      mOwner->onTransformSet.notify(this, &Component::ownerTransformSet);
+   }
+
+   if (isServerObject())
+      setMaskBits(OwnerMask);
+}
+
+void Component::componentAddedToOwner(Component *comp)
+{
+   return;
+}
+
+void Component::componentRemovedFromOwner(Component *comp)
+{
+   return;
+}
+
+void Component::ownerTransformSet(MatrixF *mat)
+{
+   return;
+}
+
+U32 Component::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if (mask & OwnerMask)
+   {
+      if (mOwner != NULL)
+      {
+         S32 ghostIndex = con->getGhostIndex(mOwner);
+
+         if (ghostIndex == -1)
+         {
+            stream->writeFlag(false);
+            retMask |= OwnerMask;
+         }
+         else
+         {
+            stream->writeFlag(true);
+            stream->writeFlag(true);
+            stream->writeInt(ghostIndex, NetConnection::GhostIdBitSize);
+         }
+      }
+      else
+      {
+         stream->writeFlag(true);
+         stream->writeFlag(false);
+      }
+   }
+   else
+      stream->writeFlag(false);
+
+   if (stream->writeFlag(mask & EnableMask))
+   {
+      stream->writeFlag(mEnabled);
+   }
+
+   return retMask;
+}
+
+void Component::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if (stream->readFlag())
+   {
+      if (stream->readFlag())
+      {
+         //we have an owner object, so fetch it
+         S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
+
+         Entity *e = dynamic_cast<Entity*>(con->resolveGhost(gIndex));
+         if (e)
+            e->addComponent(this);
+      }
+      else
+      {
+         //it's being nulled out
+         setOwner(NULL);
+      }
+   }
+
+   if (stream->readFlag())
+   {
+      mEnabled = stream->readFlag();
+   }
+}
+
+void Component::packToStream(Stream &stream, U32 tabStop, S32 behaviorID, U32 flags /* = 0  */)
+{
+   char buffer[1024];
+
+   writeFields(stream, tabStop);
+
+   // Write out the fields which the behavior template knows about
+   for (int i = 0; i < getComponentFieldCount(); i++)
+   {
+      ComponentField *field = getComponentField(i);
+      const char *objFieldValue = getDataField(field->mFieldName, NULL);
+
+      // If the field holds the same value as the template's default value than it
+      // will get initialized by the template, and so it won't be included just
+      // to try to keep the object files looking as non-horrible as possible.
+      if (dStrcmp(field->mDefaultValue, objFieldValue) != 0)
+      {
+         dSprintf(buffer, sizeof(buffer), "%s = \"%s\";\n", field->mFieldName, (dStrlen(objFieldValue) > 0 ? objFieldValue : "0"));
+
+         stream.writeTabs(tabStop);
+         stream.write(dStrlen(buffer), buffer);
+      }
+   }
+}
+
+void Component::processTick()
+{
+   if (isServerObject() && mEnabled)
+   {
+      if (mOwner != NULL && isMethod("Update"))
+         Con::executef(this, "Update");
+   }
+}
+
+void Component::setDataField(StringTableEntry slotName, const char *array, const char *value)
+{
+   Parent::setDataField(slotName, array, value);
+
+   onDataSet.trigger(this, slotName, value);
+}
+
+
+//catch any behavior field updates
+void Component::onStaticModified(const char* slotName, const char* newValue)
+{
+   Parent::onStaticModified(slotName, newValue);
+
+   //If we don't have an owner yet, then this is probably the initial setup, so we don't need the console callbacks yet.
+   if (!mOwner)
+      return;
+
+   onDataSet.trigger(this, slotName, newValue);
+
+   checkComponentFieldModified(slotName, newValue);
+}
+
+void Component::onDynamicModified(const char* slotName, const char* newValue)
+{
+   Parent::onDynamicModified(slotName, newValue);
+
+   //If we don't have an owner yet, then this is probably the initial setup, so we don't need the console callbacks yet.
+   if (!mOwner)
+      return;
+
+   checkComponentFieldModified(slotName, newValue);
+}
+
+void Component::checkComponentFieldModified(const char* slotName, const char* newValue)
+{
+   StringTableEntry slotNameEntry = StringTable->insert(slotName);
+
+   //find if it's a behavior field
+   for (int i = 0; i < mFields.size(); i++)
+   {
+      ComponentField *field = getComponentField(i);
+      if (field->mFieldName == slotNameEntry)
+      {
+         //we have a match, do the script callback that we updated a field
+         if (isMethod("onInspectorUpdate"))
+            Con::executef(this, "onInspectorUpdate", slotName);
+
+         return;
+      }
+   }
+}
+//////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////
+void Component::addComponentField(const char *fieldName, const char *desc, const char *type, const char *defaultValue /* = NULL */, const char *userData /* = NULL */, /*const char* dependency /* = NULL *//*,*/ bool hidden /* = false */)
+{
+   StringTableEntry stFieldName = StringTable->insert(fieldName);
+
+   for (S32 i = 0; i < mFields.size(); ++i)
+   {
+      if (mFields[i].mFieldName == stFieldName)
+         return;
+   }
+
+   ComponentField field;
+   field.mFieldName = stFieldName;
+
+   //find the field type
+   S32 fieldTypeMask = -1;
+   StringTableEntry fieldType = StringTable->insert(type);
+
+   if (fieldType == StringTable->insert("TypeS32"))
+      fieldTypeMask = TypeS32;
+   else if (fieldType == StringTable->insert("TypeF32"))
+      fieldTypeMask = TypeF32;
+   else if (fieldType == StringTable->insert("TypePoint3F"))
+      fieldTypeMask = TypePoint3F;
+   else if (fieldType == StringTable->insert("TypeMaterialName"))
+      fieldTypeMask = TypeMaterialName;
+   else if (fieldType == StringTable->insert("TypeImageFilename"))
+      fieldTypeMask = TypeImageFilename;
+   else if (fieldType == StringTable->insert("TypeShapeFilename"))
+      fieldTypeMask = TypeShapeFilename;
+   else if (fieldType == StringTable->insert("TypeBool"))
+      fieldTypeMask = TypeBool;
+   else
+      fieldTypeMask = TypeString;
+
+   field.mFieldType = fieldTypeMask;
+
+   field.mUserData = StringTable->insert(userData ? userData : "");
+   field.mDefaultValue = StringTable->insert(defaultValue ? defaultValue : "");
+   field.mFieldDescription = getDescriptionText(desc);
+
+   field.mGroup = mComponentGroup;
+
+   field.mHidden = hidden;
+
+   mFields.push_back(field);
+
+   //Before we set this, we need to do a test to see if this field was already set, like from the mission file or a taml file
+   const char* curFieldData = getDataField(field.mFieldName, NULL);
+
+   if (dStrIsEmpty(curFieldData))
+      setDataField(field.mFieldName, NULL, field.mDefaultValue);
+}
+
+ComponentField* Component::getComponentField(const char *fieldName)
+{
+   StringTableEntry stFieldName = StringTable->insert(fieldName);
+
+   for (S32 i = 0; i < mFields.size(); ++i)
+   {
+      if (mFields[i].mFieldName == stFieldName)
+         return &mFields[i];
+   }
+
+   return NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+const char * Component::getDescriptionText(const char *desc)
+{
+   if (desc == NULL)
+      return NULL;
+
+   char *newDesc;
+
+   // [tom, 1/12/2007] If it isn't a file, just do it the easy way
+   if (!Platform::isFile(desc))
+   {
+      newDesc = new char[dStrlen(desc) + 1];
+      dStrcpy(newDesc, desc);
+
+      return newDesc;
+   }
+
+   FileStream str;
+   str.open(desc, Torque::FS::File::Read);
+
+   Stream *stream = &str;
+   if (stream == NULL){
+      str.close();
+      return NULL;
+   }
+
+   U32 size = stream->getStreamSize();
+   if (size > 0)
+   {
+      newDesc = new char[size + 1];
+      if (stream->read(size, (void *)newDesc))
+         newDesc[size] = 0;
+      else
+      {
+         SAFE_DELETE_ARRAY(newDesc);
+      }
+   }
+
+   str.close();
+   delete stream;
+
+   return newDesc;
+}
+//////////////////////////////////////////////////////////////////////////
+void Component::beginFieldGroup(const char* groupName)
+{
+   if (dStrcmp(mComponentGroup, ""))
+   {
+      Con::errorf("Component: attempting to begin new field group with a group already begun!");
+      return;
+   }
+
+   mComponentGroup = StringTable->insert(groupName);
+}
+
+void Component::endFieldGroup()
+{
+   mComponentGroup = StringTable->insert("");
+}
+
+void Component::addDependency(StringTableEntry name)
+{
+   mDependencies.push_back_unique(name);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Console Methods
+//////////////////////////////////////////////////////////////////////////
+ConsoleMethod(Component, beginGroup, void, 3, 3, "(groupName)\n"
+   "Starts the grouping for following fields being added to be grouped into\n"
+   "@param groupName The name of this group\n"
+   "@param desc The Description of this field\n"
+   "@param type The DataType for this field (default, int, float, Point2F, bool, enum, Object, keybind, color)\n"
+   "@param defaultValue The Default value for this field\n"
+   "@param userData An extra data field that can be used for custom data on a per-field basis<br>Usage for default types<br>"
+   "-enum: a TAB separated list of possible values<br>"
+   "-object: the T2D object type that are valid choices for the field.  The object types observe inheritance, so if you have a t2dSceneObject field you will be able to choose t2dStaticSrpites, t2dAnimatedSprites, etc.\n"
+   "@return Nothing\n")
+{
+   object->beginFieldGroup(argv[2]);
+}
+
+ConsoleMethod(Component, endGroup, void, 2, 2, "()\n"
+   "Ends the grouping for prior fields being added to be grouped into\n"
+   "@param groupName The name of this group\n"
+   "@param desc The Description of this field\n"
+   "@param type The DataType for this field (default, int, float, Point2F, bool, enum, Object, keybind, color)\n"
+   "@param defaultValue The Default value for this field\n"
+   "@param userData An extra data field that can be used for custom data on a per-field basis<br>Usage for default types<br>"
+   "-enum: a TAB separated list of possible values<br>"
+   "-object: the T2D object type that are valid choices for the field.  The object types observe inheritance, so if you have a t2dSceneObject field you will be able to choose t2dStaticSrpites, t2dAnimatedSprites, etc.\n"
+   "@return Nothing\n")
+{
+   object->endFieldGroup();
+}
+
+DefineConsoleMethod(Component, addComponentField, void, (String fieldName, String fieldDesc, String fieldType, String defValue, String userData, bool hidden),
+   ("", "", "", "", "", false),
+   "Get the number of static fields on the object.\n"
+   "@return The number of static fields defined on the object.")
+{
+   object->addComponentField(fieldName, fieldDesc, fieldType, defValue, userData, hidden);
+}
+
+ConsoleMethod(Component, getComponentFieldCount, S32, 2, 2, "() - Get the number of ComponentField's on this object\n"
+   "@return Returns the number of BehaviorFields as a nonnegative integer\n")
+{
+   return object->getComponentFieldCount();
+}
+
+// [tom, 1/12/2007] Field accessors split into multiple methods to allow space
+// for long descriptions and type data.
+
+ConsoleMethod(Component, getComponentField, const char *, 3, 3, "(int index) - Gets a Tab-Delimited list of information about a ComponentField specified by Index\n"
+   "@param index The index of the behavior\n"
+   "@return FieldName, FieldType and FieldDefaultValue, each separated by a TAB character.\n")
+{
+   ComponentField *field = object->getComponentField(dAtoi(argv[2]));
+   if (field == NULL)
+      return "";
+
+   char *buf = Con::getReturnBuffer(1024);
+   dSprintf(buf, 1024, "%s\t%s\t%s\t%s", field->mFieldName, field->mFieldType, field->mDefaultValue, field->mGroup);
+
+   return buf;
+}
+
+ConsoleMethod(Component, setComponentield, const char *, 3, 3, "(int index) - Gets a Tab-Delimited list of information about a ComponentField specified by Index\n"
+   "@param index The index of the behavior\n"
+   "@return FieldName, FieldType and FieldDefaultValue, each separated by a TAB character.\n")
+{
+   ComponentField *field = object->getComponentField(dAtoi(argv[2]));
+   if (field == NULL)
+      return "";
+
+   char *buf = Con::getReturnBuffer(1024);
+   dSprintf(buf, 1024, "%s\t%s\t%s", field->mFieldName, field->mFieldType, field->mDefaultValue);
+
+   return buf;
+}
+
+ConsoleMethod(Component, getBehaviorFieldUserData, const char *, 3, 3, "(int index) - Gets the UserData associated with a field by index in the field list\n"
+   "@param index The index of the behavior\n"
+   "@return Returns a string representing the user data of this field\n")
+{
+   ComponentField *field = object->getComponentField(dAtoi(argv[2]));
+   if (field == NULL)
+      return "";
+
+   return field->mUserData;
+}
+
+ConsoleMethod(Component, getComponentFieldDescription, const char *, 3, 3, "(int index) - Gets a field description by index\n"
+   "@param index The index of the behavior\n"
+   "@return Returns a string representing the description of this field\n")
+{
+   ComponentField *field = object->getComponentField(dAtoi(argv[2]));
+   if (field == NULL)
+      return "";
+
+   return field->mFieldDescription ? field->mFieldDescription : "";
+}
+
+ConsoleMethod(Component, addDependency, void, 3, 3, "(string behaviorName) - Gets a field description by index\n"
+   "@param index The index of the behavior\n"
+   "@return Returns a string representing the description of this field\n")
+{
+   object->addDependency(argv[2]);
+}
+
+ConsoleMethod(Component, setDirty, void, 2, 2, "() - Gets a field description by index\n"
+   "@param index The index of the behavior\n"
+   "@return Returns a string representing the description of this field\n")
+{
+   object->setMaskBits(Component::OwnerMask);
+}

+ 197 - 0
Engine/source/T3D/components/Component.h

@@ -0,0 +1,197 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef COMPONENT_H
+#define COMPONENT_H
+
+#ifndef _NETOBJECT_H_
+#include "sim/netObject.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+
+class Entity;
+
+struct ComponentField
+{
+   StringTableEntry mFieldName;
+   StringTableEntry mFieldDescription;
+
+   S32 mFieldType;
+   StringTableEntry mUserData;
+
+   StringTableEntry mDefaultValue;
+
+   StringTableEntry mGroup;
+
+   StringTableEntry mDependency;
+
+   bool mHidden;
+};
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class Component : public NetObject, public UpdateInterface
+{
+   typedef NetObject Parent;
+
+protected:
+   StringTableEntry mFriendlyName;
+   StringTableEntry mDescription;
+
+   StringTableEntry mFromResource;
+   StringTableEntry mComponentGroup;
+   StringTableEntry mComponentType;
+   StringTableEntry mNetworkType;
+   StringTableEntry mTemplateName;
+
+   Vector<StringTableEntry> mDependencies;
+   Vector<ComponentField> mFields;
+
+   bool mNetworked;
+
+   U32 componentIdx;
+
+   Entity*              mOwner;
+   bool					   mHidden;
+   bool					   mEnabled;
+
+public:
+   Component();
+   virtual ~Component();
+   DECLARE_CONOBJECT(Component);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void packToStream(Stream &stream, U32 tabStop, S32 behaviorID, U32 flags = 0);
+
+   //This is called when we are added to an entity
+   virtual void onComponentAdd();            
+   //This is called when we are removed from an entity
+   virtual void onComponentRemove();         
+
+   //This is called when a different component is added to our owner entity
+   virtual void componentAddedToOwner(Component *comp);  
+   //This is called when a different component is removed from our owner entity
+   virtual void componentRemovedFromOwner(Component *comp);  
+
+   virtual void ownerTransformSet(MatrixF *mat);
+
+   void setOwner(Entity* pOwner);
+   inline Entity *getOwner() { return mOwner ? mOwner : NULL; }
+   static bool setOwner(void *object, const char *index, const char *data) { return true; }
+
+   bool	isEnabled() { return mEnabled; }
+   void  setEnabled(bool toggle) { mEnabled = toggle; setMaskBits(EnableMask); }
+
+   bool isActive() { return mEnabled && mOwner != NULL; }
+
+   static bool _setEnabled(void *object, const char *index, const char *data);
+
+   virtual void processTick();
+   virtual void interpolateTick(F32 dt){}
+   virtual void advanceTime(F32 dt){}
+
+   /// @name Adding Named Fields
+   /// @{
+
+   /// Adds a named field to a Component that can specify a description, data type, default value and userData
+   ///
+   /// @param   fieldName    The name of the Field
+   /// @param   desc         The Description of the Field
+   /// @param   type         The Type of field that this is, example 'Text' or 'Bool'
+   /// @param   defaultValue The Default value of this field
+   /// @param   userData     An extra optional field that can be used for user data
+   void addComponentField(const char *fieldName, const char *desc, const char *type, const char *defaultValue = NULL, const char *userData = NULL, bool hidden = false);
+
+   /// Returns the number of ComponentField's on this template
+   inline S32 getComponentFieldCount() { return mFields.size(); };
+
+   /// Gets a ComponentField by its index in the mFields vector 
+   /// @param idx  The index of the field in the mField vector
+   inline ComponentField *getComponentField(S32 idx)
+   {
+      if (idx < 0 || idx >= mFields.size())
+         return NULL;
+
+      return &mFields[idx];
+   }
+
+   ComponentField *getComponentField(const char* fieldName);
+
+   const char* getComponentType() { return mComponentType; }
+
+   const char *getDescriptionText(const char *desc);
+
+   const char *getName() { return mTemplateName; }
+
+   const char *getFriendlyName() { return mFriendlyName; }
+
+   bool isNetworked() { return mNetworked; }
+
+   void beginFieldGroup(const char* groupName);
+   void endFieldGroup();
+
+   void addDependency(StringTableEntry name);
+   /// @}
+
+   /// @name Description
+   /// @{
+   static bool setDescription(void *object, const char *index, const char *data);
+   static const char* getDescription(void* obj, const char* data);
+
+   /// @Primary usage functions
+   /// @These are used by the various engine-based behaviors to integrate with the component classes
+   enum NetMaskBits
+   {
+      InitialUpdateMask = BIT(0),
+      OwnerMask = BIT(1),
+      UpdateMask = BIT(2),
+      EnableMask = BIT(3),
+      NextFreeMask = BIT(4)
+   };
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+   /// @}
+
+   Signal< void(SimObject*, String, String) > onDataSet;
+   virtual void setDataField(StringTableEntry slotName, const char *array, const char *value);
+
+   virtual void onStaticModified(const char* slotName, const char* newValue); ///< Called when a static field is modified.
+   virtual void onDynamicModified(const char* slotName, const char*newValue = NULL); ///< Called when a dynamic field is modified.
+
+   /// This is what we actually use to check if the modified field is one of our behavior fields. If it is, we update and make the correct callbacks
+   void checkComponentFieldModified(const char* slotName, const char* newValue);
+
+   virtual void checkDependencies(){}
+};
+
+#endif // COMPONENT_H

+ 215 - 0
Engine/source/T3D/components/Game/StateMachineComponent.cpp

@@ -0,0 +1,215 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/game/StateMachineComponent.h"
+
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "ts/tsShapeInstance.h"
+#include "core/stream/bitStream.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+
+IMPLEMENT_CALLBACK( StateMachineComponent, onStateChange, void, (), (),
+                   "@brief Called when we collide with another object.\n\n"
+                   "@param obj The ShapeBase object\n"
+                   "@param collObj The object we collided with\n"
+                   "@param vec Collision impact vector\n"
+                   "@param len Length of the impact vector\n" );
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+
+StateMachineComponent::StateMachineComponent() : Component()
+{
+   mFriendlyName = "State Machine";
+   mComponentType = "Game";
+
+   mDescription = getDescriptionText("A generic state machine.");
+
+   mStateMachineFile = "";
+
+   //doesn't need to be networked
+   mNetworked = false;
+   mNetFlags.clear();
+}
+
+StateMachineComponent::~StateMachineComponent()
+{
+   for(S32 i = 0;i < mFields.size();++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(StateMachineComponent);
+
+bool StateMachineComponent::onAdd()
+{
+   if(! Parent::onAdd())
+      return false;
+
+   // Register for the resource change signal.
+   ResourceManager::get().getChangedSignal().notify(this, &StateMachineComponent::_onResourceChanged);
+
+   mStateMachine.onStateChanged.notify(this, &StateMachineComponent::onStateChanged);
+
+   return true;
+}
+
+void StateMachineComponent::onRemove()
+{
+   Parent::onRemove();
+}
+
+U32 StateMachineComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+   return retMask;
+}
+
+void StateMachineComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+}
+
+//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
+void StateMachineComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+}
+
+void StateMachineComponent::onComponentRemove()
+{
+   Parent::onComponentRemove();
+}
+
+void StateMachineComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addProtectedField("stateMachineFile", TypeFilename, Offset(mStateMachineFile, StateMachineComponent), 
+      &_setSMFile, &defaultProtectedGetFn, "The sim time of when we started this state");
+}
+
+bool StateMachineComponent::_setSMFile(void *object, const char *index, const char *data)
+{
+   StateMachineComponent* smComp = static_cast<StateMachineComponent*>(object);
+   if (smComp)
+   {
+      smComp->setStateMachineFile(data);
+      smComp->loadStateMachineFile();
+
+      return true;
+   }
+
+   return false;
+}
+
+void StateMachineComponent::_onResourceChanged(const Torque::Path &path)
+{
+   if (path != Torque::Path(mStateMachineFile))
+      return;
+
+   loadStateMachineFile();
+}
+
+void StateMachineComponent::loadStateMachineFile()
+{
+   if (!dStrIsEmpty(mStateMachineFile))
+   {
+      mStateMachine.mStateMachineFile = mStateMachineFile;
+      mStateMachine.loadStateMachineFile();
+
+      //now that it's loaded, we need to parse the SM's fields and set them as script vars on ourselves
+      S32 smFieldCount = mStateMachine.getFieldsCount();
+
+      for (U32 i = 0; i < smFieldCount; i++)
+      {
+         StateMachine::StateField field = mStateMachine.getField(i);
+
+         char buffer[128];
+
+         if (field.fieldType == StateMachine::StateField::BooleanType)
+         {
+            dSprintf(buffer, sizeof(buffer), "%b", field.triggerBoolVal);
+            setDataField(field.name, NULL, buffer);
+         }
+         else if (field.fieldType == StateMachine::StateField::NumberType)
+         {
+            dSprintf(buffer, sizeof(buffer), "%g", field.triggerNumVal);
+            setDataField(field.name, NULL, buffer);
+         }
+         else if (field.fieldType == StateMachine::StateField::StringType)
+         {
+            setDataField(field.name, NULL, field.triggerStringVal);
+         }
+      }
+   }
+}
+
+void StateMachineComponent::processTick()
+{
+   if (!isServerObject() || !isActive())
+      return;
+
+   mStateMachine.update();
+}
+
+void StateMachineComponent::onDynamicModified( const char* slotName, const char* newValue )
+{
+   Parent::onDynamicModified(slotName, newValue);
+
+   StringTableEntry fieldName = StringTable->insert(slotName);
+   mStateMachine.checkTransitions(fieldName, newValue);
+}
+
+void StateMachineComponent::onStaticModified( const char* slotName, const char* newValue )
+{
+   Parent::onStaticModified(slotName, newValue);
+
+   StringTableEntry fieldName = StringTable->insert(slotName);
+   mStateMachine.checkTransitions(fieldName, newValue);
+}
+
+void StateMachineComponent::onStateChanged(StateMachine* sm, S32 stateIdx)
+{
+   //do a script callback, if we have one
+   //check if we have a function for that, and then also check if our owner does
+   StringTableEntry callbackName = mStateMachine.getCurrentState().callbackName;
+
+   if (isMethod(callbackName))
+         Con::executef(this, callbackName);
+
+   if (mOwner->isMethod(callbackName))
+      Con::executef(mOwner, callbackName);
+}

+ 81 - 0
Engine/source/T3D/components/Game/StateMachineComponent.h

@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef STATE_MACHINE_COMPONENT_H
+#define STATE_MACHINE_COMPONENT_H
+
+#ifndef COMPONENT_H
+   #include "T3D/Components/Component.h"
+#endif
+#ifndef STATE_MACHINE_H
+#include "T3D/components/Game/stateMachine.h"
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class StateMachineComponent : public Component
+{
+   typedef Component Parent;
+
+public:
+   StateMachine mStateMachine;
+
+protected:
+   StringTableEntry		mStateMachineFile;
+
+public:
+   StateMachineComponent();
+   virtual ~StateMachineComponent();
+   DECLARE_CONOBJECT(StateMachineComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+   virtual void onComponentRemove();
+
+   void _onResourceChanged(const Torque::Path &path);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   virtual void processTick();
+
+   virtual void onDynamicModified(const char* slotName, const char* newValue);
+   virtual void onStaticModified(const char* slotName, const char* newValue);
+
+   virtual void loadStateMachineFile();
+
+   void setStateMachineFile(const char* fileName) { mStateMachineFile = StringTable->insert(fileName); }
+
+   static bool _setSMFile(void *object, const char *index, const char *data);
+
+   void onStateChanged(StateMachine* sm, S32 stateIdx);
+
+   //Callbacks
+   DECLARE_CALLBACK(void, onStateChange, ());
+};
+
+#endif

+ 434 - 0
Engine/source/T3D/components/Game/stateMachine.cpp

@@ -0,0 +1,434 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Game/stateMachine.h"
+
+StateMachine::StateMachine()
+{
+   mStateStartTime = -1;
+   mStateTime = 0;
+
+   mStartingState = "";
+
+   mCurCreateState = NULL;
+}
+
+StateMachine::~StateMachine()
+{
+}
+
+void StateMachine::loadStateMachineFile()
+{
+   if (!mXMLReader)
+   {
+      SimXMLDocument *xmlrdr = new SimXMLDocument();
+      xmlrdr->registerObject();
+
+      mXMLReader = xmlrdr;
+   }
+
+   bool hasStartState = false;
+
+   if (!dStrIsEmpty(mStateMachineFile))
+   {
+      //use our xml reader to parse the file!
+      SimXMLDocument *reader = mXMLReader.getObject();
+      if (!reader->loadFile(mStateMachineFile))
+         Con::errorf("Could not load state machine file: &s", mStateMachineFile);
+
+      if (!reader->pushFirstChildElement("StateMachine"))
+         return;
+
+      //find our starting state
+      if (reader->pushFirstChildElement("StartingState"))
+      {
+         mStartingState = reader->getData();
+         reader->popElement();
+         hasStartState = true;
+      }
+
+      readStates();
+   }
+
+   if (hasStartState)
+      mCurrentState = getStateByName(mStartingState);
+
+   mStateStartTime = -1;
+   mStateTime = 0;
+}
+
+void StateMachine::readStates()
+{
+   SimXMLDocument *reader = mXMLReader.getObject();
+
+   //iterate through our states now!
+   if (reader->pushFirstChildElement("State"))
+   {
+      //get our first state
+      State firstState;
+
+      readStateName(&firstState, reader);
+      readStateScriptFunction(&firstState, reader);
+
+      readTransitions(firstState);
+
+      mStates.push_back(firstState);
+
+      //now, iterate the siblings
+      while (reader->nextSiblingElement("State"))
+      {
+         State newState;
+         readStateName(&newState, reader);
+         readStateScriptFunction(&newState, reader);
+
+         readTransitions(newState);
+
+         mStates.push_back(newState);
+      }
+   }
+}
+
+void StateMachine::readTransitions(State &currentState)
+{
+   SimXMLDocument *reader = mXMLReader.getObject();
+
+   //iterate through our states now!
+   if (reader->pushFirstChildElement("Transition"))
+   {
+      //get our first state
+      StateTransition firstTransition;
+
+      readTransitonTarget(&firstTransition, reader);
+
+      readConditions(firstTransition);
+
+      currentState.mTransitions.push_back(firstTransition);
+
+      //now, iterate the siblings
+      while (reader->nextSiblingElement("Transition"))
+      {
+         StateTransition newTransition;
+         readTransitonTarget(&newTransition, reader);
+
+         readConditions(newTransition);
+
+         currentState.mTransitions.push_back(newTransition);
+      }
+
+      reader->popElement();
+   }
+}
+
+void StateMachine::readConditions(StateTransition &currentTransition)
+{
+   SimXMLDocument *reader = mXMLReader.getObject();
+
+   //iterate through our states now!
+   if (reader->pushFirstChildElement("Rule"))
+   {
+      //get our first state
+      StateTransition::Condition firstCondition;
+      StateField firstField;
+      bool fieldRead = false;
+      
+      readFieldName(&firstField, reader);
+      firstCondition.field = firstField;
+
+      readFieldComparitor(&firstCondition, reader);
+
+      readFieldValue(&firstCondition.field, reader);
+
+      currentTransition.mTransitionRules.push_back(firstCondition);
+
+      //now, iterate the siblings
+      while (reader->nextSiblingElement("Transition"))
+      {
+         StateTransition::Condition newCondition;
+         StateField newField;
+
+         readFieldName(&newField, reader);
+         newCondition.field = newField;
+
+         readFieldComparitor(&newCondition, reader);
+
+         readFieldValue(&newCondition.field, reader);
+
+         currentTransition.mTransitionRules.push_back(newCondition);
+      }
+
+      reader->popElement();
+   }
+}
+
+S32 StateMachine::parseComparitor(const char* comparitorName)
+{
+   S32 targetType = -1;
+
+   if (!dStrcmp("GreaterThan", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::GeaterThan;
+   else if (!dStrcmp("GreaterOrEqual", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::GreaterOrEqual;
+   else if (!dStrcmp("LessThan", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::LessThan;
+   else if (!dStrcmp("LessOrEqual", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::LessOrEqual;
+   else if (!dStrcmp("Equals", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::Equals;
+   else if (!dStrcmp("True", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::True;
+   else if (!dStrcmp("False", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::False;
+   else if (!dStrcmp("Negative", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::Negative;
+   else if (!dStrcmp("Positive", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::Positive;
+   else if (!dStrcmp("DoesNotEqual", comparitorName))
+      targetType = StateMachine::StateTransition::Condition::DoesNotEqual;
+
+   return targetType;
+}
+
+void StateMachine::update()
+{
+   //we always check if there's a timout transition, as that's the most generic transition possible.
+   F32 curTime = Sim::getCurrentTime();
+
+   if (mStateStartTime == -1)
+      mStateStartTime = curTime;
+
+   mStateTime = curTime - mStateStartTime;
+
+   char buffer[64];
+   dSprintf(buffer, sizeof(buffer), "%g", mStateTime);
+
+   checkTransitions("stateTime", buffer);
+}
+
+void StateMachine::checkTransitions(const char* slotName, const char* newValue)
+{
+   //because we use our current state's fields as dynamic fields on the instance
+   //we'll want to catch any fields being set so we can treat changes as transition triggers if
+   //any of the transitions on this state call for it
+
+   //One example would be in order to implement burst fire on a weapon state machine.
+   //The behavior instance has a dynamic variable set up like: GunStateMachine.burstShotCount = 0;
+
+   //We also have a transition in our fire state, as: GunStateMachine.addTransition("FireState", "burstShotCount", "DoneShooting", 3);
+   //What that does is for our fire state, we check the dynamicField burstShotCount if it's equal or greater than 3. If it is, we perform the transition.
+
+   //As state fields are handled as dynamicFields for the instance, regular dynamicFields are processed as well as state fields. So we can use the regular 
+   //dynamic fields for our transitions, to act as 'global' variables that are state-agnostic. Alternately, we can use state-specific fields, such as a transition
+   //like this:
+   //GunStateMachine.addTransition("IdleState", "Fidget", "Timeout", ">=", 5000);
+
+   //That uses the the timeout field, which is reset each time the state changes, and so state-specific, to see if it's been 5 seconds. If it has been, we transition
+   //to our fidget state
+
+   //so, lets check our current transitions
+   //now that we have the type, check our transitions!
+   for (U32 t = 0; t < mCurrentState.mTransitions.size(); t++)
+   {
+      //if (!dStrcmp(mCurrentState.mTransitions[t]., slotName))
+      {
+         //found a transition looking for this variable, so do work
+         //first, figure out what data type thie field is
+         //S32 type = getVariableType(newValue);
+
+         bool fail = false;
+         bool match = false;
+         S32 ruleCount = mCurrentState.mTransitions[t].mTransitionRules.size();
+
+         for (U32 r = 0; r < ruleCount; r++)
+         {
+            const char* fieldName = mCurrentState.mTransitions[t].mTransitionRules[r].field.name;
+            if (!dStrcmp(fieldName, slotName))
+            {
+               match = true;
+               //now, check the value with the comparitor and see if we do the transition.
+               if (!passComparitorCheck(newValue, mCurrentState.mTransitions[t].mTransitionRules[r]))
+               {
+                  fail = true;
+                  break;
+               }
+            }
+         }
+
+         //If we do have a transition rule for this field, and we didn't fail on the condition, go ahead and switch states
+         if (match && !fail)
+         {
+            setState(mCurrentState.mTransitions[t].mStateTarget);
+
+            return;
+         }
+      }
+   }
+}
+
+bool StateMachine::passComparitorCheck(const char* var, StateTransition::Condition transitionRule)
+{
+   F32 num = dAtof(var);
+   switch (transitionRule.field.fieldType)
+   {
+   case StateField::Type::VectorType:
+      switch (transitionRule.triggerComparitor)
+      {
+      case StateTransition::Condition::Equals:
+      case StateTransition::Condition::GeaterThan:
+      case StateTransition::Condition::GreaterOrEqual:
+      case StateTransition::Condition::LessThan:
+      case StateTransition::Condition::LessOrEqual:
+      case StateTransition::Condition::DoesNotEqual:
+         //do
+         break;
+      default:
+         return false;
+      };
+   case StateField::Type::StringType:
+      switch (transitionRule.triggerComparitor)
+      {
+      case StateTransition::Condition::Equals:
+         if (!dStrcmp(var, transitionRule.field.triggerStringVal))
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::DoesNotEqual:
+         if (dStrcmp(var, transitionRule.field.triggerStringVal))
+            return true;
+         else
+            return false;
+      default:
+         return false;
+      };
+   case StateField::Type::BooleanType:
+      switch (transitionRule.triggerComparitor)
+      {
+      case StateTransition::Condition::TriggerValueTarget::True:
+         if (dAtob(var))
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::False:
+         if (dAtob(var))
+            return false;
+         else
+            return true;
+      default:
+         return false;
+      };
+   case StateField::Type::NumberType:
+      switch (transitionRule.triggerComparitor)
+      {
+      case StateTransition::Condition::TriggerValueTarget::Equals:
+         if (num == transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::GeaterThan:
+         if (num > transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::GreaterOrEqual:
+         if (num >= transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::LessThan:
+         if (num < transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::LessOrEqual:
+         if (num <= transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::DoesNotEqual:
+         if (num != transitionRule.field.triggerNumVal)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::Positive:
+         if (num > 0)
+            return true;
+         else
+            return false;
+      case StateTransition::Condition::TriggerValueTarget::Negative:
+         if (num < 0)
+            return true;
+         else
+            return false;
+      default:
+         return false;
+      };
+   default:
+      return false;
+   };
+}
+
+void StateMachine::setState(const char* stateName, bool clearFields)
+{
+   State oldState = mCurrentState;
+   StringTableEntry sName = StringTable->insert(stateName);
+   for (U32 i = 0; i < mStates.size(); i++)
+   {
+      //if(!dStrcmp(mStates[i]->stateName, stateName))
+      if (!dStrcmp(mStates[i].stateName,sName))
+      {
+         mCurrentState = mStates[i];
+         mStateStartTime = Sim::getCurrentTime();
+
+         onStateChanged.trigger(this, i);
+         return;
+      }
+   }
+}
+
+const char* StateMachine::getStateByIndex(S32 index)
+{
+   if (index >= 0 && mStates.size() > index)
+      return mStates[index].stateName;
+   else
+      return "";
+}
+
+StateMachine::State& StateMachine::getStateByName(const char* name)
+{
+   StringTableEntry stateName = StringTable->insert(name);
+
+   for (U32 i = 0; i < mStates.size(); i++)
+   {
+      if (!dStrcmp(stateName, mStates[i].stateName))
+         return mStates[i];
+   }
+}
+
+S32 StateMachine::findFieldByName(const char* name)
+{
+   for (U32 i = 0; i < mFields.size(); i++)
+   {
+      if (!dStrcmp(mFields[i].name, name))
+         return i;
+   }
+
+   return -1;
+}

+ 259 - 0
Engine/source/T3D/components/Game/stateMachine.h

@@ -0,0 +1,259 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef STATE_MACHINE_H
+#define STATE_MACHINE_H
+
+#ifndef _SIMBASE_H_
+#include "console/simBase.h"
+#endif
+#ifndef _OBJECTTYPES_H_
+#include "T3D/objectTypes.h"
+#endif
+#ifndef _MMATH_H_
+#include "math/mMath.h"
+#endif
+#ifndef _XMLDOC_H_
+#include "console/SimXMLDocument.h"
+#endif
+
+class StateMachine
+{
+public:
+   struct StateField
+   {
+      StringTableEntry name;
+      
+      bool 			 triggerBoolVal;
+      float 		 triggerNumVal;
+      Point3F 		 triggerVectorVal;
+      String 		 triggerStringVal;
+
+      enum Type
+      {
+         BooleanType = 0,
+         NumberType,
+         VectorType,
+         StringType
+      }fieldType;
+   };
+
+   struct UniqueReference
+   {
+      SimObject* referenceObj;
+      const char* referenceVar;
+      const char* uniqueName;
+   };
+
+   struct StateTransition
+   {
+      struct Condition
+      {
+         enum TriggerValueTarget
+         {
+            Equals = 0,
+            GeaterThan,
+            LessThan,
+            GreaterOrEqual,
+            LessOrEqual,
+            True,
+            False,
+            Positive,
+            Negative,
+            DoesNotEqual
+         };
+
+         StateField field;
+
+         TriggerValueTarget   triggerComparitor;
+
+         UniqueReference      *valUniqueRef;
+      };
+
+      StringTableEntry	mName;
+      StringTableEntry	mStateTarget;
+      Vector<Condition>	mTransitionRules;
+   };
+
+   struct State 
+   {
+      Vector<StateTransition> mTransitions;
+
+      StringTableEntry stateName;
+
+      StringTableEntry callbackName;
+   };
+
+   StringTableEntry		mStateMachineFile;
+
+protected:
+   Vector<State> mStates;
+
+   Vector<StateField> mFields;
+
+   Vector<UniqueReference> mUniqueReferences;
+
+   State mCurrentState;
+
+   F32 mStateStartTime;
+   F32 mStateTime;
+
+   StringTableEntry mStartingState;
+
+   State *mCurCreateSuperState;
+   State *mCurCreateState;
+
+   SimObjectPtr<SimXMLDocument> mXMLReader;
+
+public:
+   StateMachine();
+   virtual ~StateMachine();
+
+   void update();
+
+   void loadStateMachineFile();
+   void readStates();
+   void readTransitions(State &currentState);
+   void readConditions(StateTransition &newTransition);
+
+   void setState(const char* stateName, bool clearFields = true);
+
+   const char* getCurrentStateName() { return mCurrentState.stateName; }
+   State& getCurrentState() {
+      return mCurrentState;
+   }
+
+   S32 getStateCount() { return mStates.size(); }
+   const char* getStateByIndex(S32 index);
+   State& getStateByName(const char* name);
+
+   void checkTransitions(const char* slotName, const char* newValue);
+
+   bool passComparitorCheck(const char* var, StateTransition::Condition transitionRule);
+
+   S32 findFieldByName(const char* name);
+
+   S32 getFieldsCount() { return mFields.size(); }
+
+   StateField getField(U32 index)
+   {
+      if (index <= mFields.size())
+         return mFields[index];
+   }
+
+   Signal< void(StateMachine*, S32 stateIdx) > StateMachine::onStateChanged;
+
+   //
+   inline bool readStateName(State* state, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("Name"))
+      {
+         state->stateName = reader->getData();
+         reader->popElement();
+
+         return true;
+      }
+
+      return false;
+   }
+   inline bool readStateScriptFunction(State* state, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("ScriptFunction"))
+      {
+         state->callbackName = reader->getData();
+         reader->popElement();
+
+         return true;
+      }
+
+      return false;
+   }
+   inline bool readTransitonTarget(StateTransition* transition, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("StateTarget"))
+      {
+         transition->mStateTarget = reader->getData();
+         reader->popElement();
+
+         return true;
+      }
+
+      return false;
+   }
+   //
+   inline bool readFieldName(StateField* newField, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("FieldName"))
+      {
+         newField->name = reader->getData();
+         reader->popElement();
+
+         return true;
+      }
+
+      return false;
+   }
+   inline bool readFieldComparitor(StateTransition::Condition* condition, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("Comparitor"))
+      {
+         S32 compIdx = parseComparitor(reader->getData());
+         condition->triggerComparitor = static_cast<StateTransition::Condition::TriggerValueTarget>(compIdx);
+         reader->popElement();
+
+         return true;
+      }
+
+      return false;
+   }
+   inline bool readFieldValue(StateField* field, SimXMLDocument* reader)
+   {
+      if (reader->pushFirstChildElement("NumValue"))
+      {
+         field->fieldType = StateField::NumberType;
+         field->triggerNumVal = dAtof(reader->getData());
+         reader->popElement();
+         return true;
+      }
+      else if (reader->pushFirstChildElement("StringValue"))
+      {
+         field->fieldType = StateField::StringType;
+         field->triggerStringVal = reader->getData();
+         reader->popElement();
+         return true;
+      }
+      else if (reader->pushFirstChildElement("BoolValue"))
+      {
+         field->fieldType = StateField::BooleanType;
+         field->triggerBoolVal = dAtob(reader->getData());
+         reader->popElement();
+         return true;
+      }
+
+      return false;
+   }
+
+private:
+   S32 parseComparitor(const char* comparitorName);
+};
+
+#endif

+ 358 - 0
Engine/source/T3D/components/Game/triggerComponent.cpp

@@ -0,0 +1,358 @@
+//-----------------------------------------------------------------------------
+// Torque Game Engine
+// Copyright (C) GarageGames.com, Inc.
+//-----------------------------------------------------------------------------
+#include "console/consoleTypes.h"
+#include "T3D/Components/game/TriggerComponent.h"
+#include "core/util/safeDelete.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "core/stream/bitStream.h"
+#include "console/engineAPI.h"
+#include "sim/netConnection.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/Components/coreInterfaces.h"
+#include "math/mathUtils.h"
+#include "collision/concretePolyList.h"
+#include "collision/clippedPolyList.h"
+
+#include "gfx/sim/debugDraw.h"
+
+IMPLEMENT_CALLBACK( TriggerComponent, onEnterViewCmd, void, 
+   ( Entity* cameraEnt, bool firstTimeSeeing ), ( cameraEnt, firstTimeSeeing ),
+   "@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
+
+   "@param trigger the Trigger instance whose volume the object entered\n"
+   "@param obj the object that entered the volume of the Trigger instance\n" );
+
+IMPLEMENT_CALLBACK( TriggerComponent, onExitViewCmd, void, 
+   ( Entity* cameraEnt ), ( cameraEnt ),
+   "@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
+
+   "@param trigger the Trigger instance whose volume the object entered\n"
+   "@param obj the object that entered the volume of the Trigger instance\n" );
+
+IMPLEMENT_CALLBACK( TriggerComponent, onUpdateInViewCmd, void, 
+   ( Entity* cameraEnt ), ( cameraEnt ),
+   "@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
+
+   "@param trigger the Trigger instance whose volume the object entered\n"
+   "@param obj the object that entered the volume of the Trigger instance\n" );
+
+IMPLEMENT_CALLBACK( TriggerComponent, onUpdateOutOfViewCmd, void, 
+   ( Entity* cameraEnt ), ( cameraEnt ),
+   "@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
+
+   "@param trigger the Trigger instance whose volume the object entered\n"
+   "@param obj the object that entered the volume of the Trigger instance\n" );
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+
+TriggerComponent::TriggerComponent() : Component()
+{
+   mObjectList.clear();
+
+   mVisible = false;
+
+   mFriendlyName = "Trigger";
+   mComponentType = "Trigger";
+
+   mDescription = getDescriptionText("Calls trigger events when a client starts and stops seeing it. Also ticks while visible to clients.");
+}
+
+TriggerComponent::~TriggerComponent()
+{
+   for(S32 i = 0;i < mFields.size();++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(TriggerComponent);
+
+
+bool TriggerComponent::onAdd()
+{
+   if(! Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+void TriggerComponent::onRemove()
+{
+   Parent::onRemove();
+}
+
+//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
+void TriggerComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
+
+   if(colInt)
+   {
+      colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject);
+   }
+}
+
+void TriggerComponent::onComponentRemove()
+{
+   CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
+
+   if(colInt)
+   {
+      colInt->onCollisionSignal.remove(this, &TriggerComponent::potentialEnterObject);
+   }
+
+   Parent::onComponentRemove();
+}
+
+void TriggerComponent::componentAddedToOwner(Component *comp)
+{
+   if (comp->getId() == getId())
+      return;
+
+   CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
+
+   if (colInt)
+   {
+      colInt->onCollisionSignal.notify(this, &TriggerComponent::potentialEnterObject);
+   }
+}
+
+void TriggerComponent::componentRemovedFromOwner(Component *comp)
+{
+   if (comp->getId() == getId()) //?????????
+      return;
+
+   CollisionInterface *colInt = mOwner->getComponent<CollisionInterface>();
+
+   if (colInt)
+   {
+      colInt->onCollisionSignal.remove(this, &TriggerComponent::potentialEnterObject);
+   }
+}
+
+void TriggerComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addField("visibile",   TypeBool,  Offset( mVisible, TriggerComponent ), "" );
+
+   addField("onEnterViewCmd", TypeCommand, Offset(mEnterCommand, TriggerComponent), "");
+   addField("onExitViewCmd", TypeCommand, Offset(mOnExitCommand, TriggerComponent), "");
+   addField("onUpdateInViewCmd", TypeCommand, Offset(mOnUpdateInViewCmd, TriggerComponent), "");
+}
+
+U32 TriggerComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+   return retMask;
+}
+
+void TriggerComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+}
+
+void TriggerComponent::potentialEnterObject(SceneObject *collider)
+{
+   if(testObject(collider))
+   {
+      bool found = false;
+      for(U32 i=0; i < mObjectList.size(); i++)
+      {
+         if(mObjectList[i]->getId() == collider->getId())
+         {
+            found = true;
+            break;
+         }
+      }
+
+      if (!found)
+      {
+         mObjectList.push_back(collider);
+
+         if (!mEnterCommand.isEmpty())
+         {
+            String command = String("%obj = ") + collider->getIdString() + ";" + 
+               String("%this = ") + getIdString() + ";" + mEnterCommand;
+            Con::evaluate(command.c_str());
+         }
+
+         //onEnterTrigger_callback(this, enter);
+      }
+   }
+}
+
+bool TriggerComponent::testObject(SceneObject* enter)
+{
+   //First, test to early out
+   Box3F enterBox = enter->getWorldBox();
+
+   //if(!mOwner->getWorldBox().intersect(enterBox) || !)
+   //   return false;
+
+   //We're still here, so we should do actual work
+   //We're going to be 
+   ConcretePolyList mClippedList;
+
+   SphereF sphere;
+   sphere.center = (mOwner->getWorldBox().minExtents + mOwner->getWorldBox().maxExtents) * 0.5;
+   VectorF bv = mOwner->getWorldBox().maxExtents - sphere.center;
+   sphere.radius = bv.len();
+
+   Entity* enterEntity = dynamic_cast<Entity*>(enter);
+   if(enterEntity)
+   {
+      //quick early out. If the bounds don't overlap, it cannot be colliding or inside
+      if (!mOwner->getWorldBox().isOverlapped(enterBox))
+         return false;
+
+      //check if the entity has a collision shape
+      CollisionInterface *cI = enterEntity->getComponent<CollisionInterface>();
+      if (cI)
+      {
+         cI->buildPolyList(PLC_Collision, &mClippedList, mOwner->getWorldBox(), sphere);
+
+         if (!mClippedList.isEmpty())
+         {
+            //well, it's clipped with, or inside, our bounds
+            //now to test the clipped list against our own collision mesh
+            CollisionInterface *myCI = mOwner->getComponent<CollisionInterface>();
+
+            //wait, how would we NOT have this?
+            if (myCI)
+            {
+               //anywho, build our list and then we'll check intersections
+               ClippedPolyList myList;
+
+               myList.setTransform(&(mOwner->getTransform()), mOwner->getScale());
+               myList.setObject(mOwner);
+
+               myCI->buildPolyList(PLC_Collision, &myList, enterBox, sphere);
+
+               bool test = true;
+            }
+         }
+      }
+   }
+
+   return mClippedList.isEmpty() == false;
+}
+
+void TriggerComponent::processTick()
+{
+   Parent::processTick();
+
+   if (!isActive())
+      return;
+
+	//get our list of active clients, and see if they have cameras, if they do, build a frustum and see if we exist inside that
+   mVisible = false;
+   if(isServerObject())
+   {
+      for(U32 i=0; i < mObjectList.size(); i++)
+      {
+         if(!testObject(mObjectList[i]))
+         {
+            if (!mOnExitCommand.isEmpty())
+            {
+               String command = String("%obj = ") + mObjectList[i]->getIdString() + ";" +
+                  String("%this = ") + getIdString() + ";" + mOnExitCommand;
+               Con::evaluate(command.c_str());
+            }
+
+            mObjectList.erase(i);
+            //mDataBlock->onLeaveTrigger_callback( this, remove );
+            //onLeaveTrigger_callback(this, remove);
+         }
+      }
+
+      /*if (!mTickCommand.isEmpty())
+         Con::evaluate(mTickCommand.c_str());
+
+      if (mObjects.size() != 0)
+         onTickTrigger_callback(this);*/
+   }
+}
+
+void TriggerComponent::visualizeFrustums(F32 renderTimeMS)
+{
+   
+}
+
+GameConnection* TriggerComponent::getConnection(S32 connectionID)
+{
+   for(NetConnection *conn = NetConnection::getConnectionList(); conn; conn = conn->getNext())  
+   {  
+      GameConnection* gameConn = dynamic_cast<GameConnection*>(conn);
+  
+      if (!gameConn || (gameConn && gameConn->isAIControlled()))
+         continue; 
+
+      if(connectionID == gameConn->getId())
+         return gameConn;
+   }
+
+   return NULL;
+}
+
+void TriggerComponent::addClient(S32 clientID)
+{
+   
+}
+
+void TriggerComponent::removeClient(S32 clientID)
+{
+
+}
+
+DefineEngineMethod( TriggerComponent, addClient, void,
+                   ( S32 clientID ), ( -1 ),
+                   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+                   "@param objB  Object to mount onto us\n"
+                   "@param slot  Mount slot ID\n"
+                   "@param txfm (optional) mount offset transform\n"
+                   "@return true if successful, false if failed (objB is not valid)" )
+{
+   if(clientID == -1)
+      return;
+
+   object->addClient( clientID );
+}
+
+DefineEngineMethod( TriggerComponent, removeClient, void,
+                   ( S32 clientID ), ( -1 ),
+                   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+                   "@param objB  Object to mount onto us\n"
+                   "@param slot  Mount slot ID\n"
+                   "@param txfm (optional) mount offset transform\n"
+                   "@return true if successful, false if failed (objB is not valid)" )
+{
+   if(clientID == -1)
+      return;
+
+   object->removeClient( clientID );
+}
+
+DefineEngineMethod( TriggerComponent, visualizeFrustums, void,
+                   (F32 renderTime), (1000),
+                   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+                   "@param objB  Object to mount onto us\n"
+                   "@param slot  Mount slot ID\n"
+                   "@param txfm (optional) mount offset transform\n"
+                   "@return true if successful, false if failed (objB is not valid)" )
+{
+   object->visualizeFrustums(renderTime);
+}

+ 74 - 0
Engine/source/T3D/components/Game/triggerComponent.h

@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------------
+// Torque Game Engine
+// Copyright (C) GarageGames.com, Inc.
+//-----------------------------------------------------------------------------
+#ifndef _TRIGGER_COMPONENT_H_
+#define _TRIGGER_COMPONENT_H_
+
+#ifndef _COMPONENT_H_
+#include "T3D/Components/Component.h"
+#endif
+
+#ifndef _ENTITY_H_
+#include "T3D/Entity.h"
+#endif
+
+#ifndef _COLLISION_INTERFACES_H_
+#include "T3D/Components/collision/collisionInterfaces.h"
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class TriggerComponent : public Component
+{
+   typedef Component Parent;
+
+protected:
+   Vector<SceneObject*> mObjectList;
+
+   bool mVisible;
+
+   String mEnterCommand;
+   String mOnExitCommand;
+   String mOnUpdateInViewCmd;
+
+public:
+   TriggerComponent();
+   virtual ~TriggerComponent();
+   DECLARE_CONOBJECT(TriggerComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+   virtual void onComponentRemove();
+
+   virtual void componentAddedToOwner(Component *comp);
+   virtual void componentRemovedFromOwner(Component *comp);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   void potentialEnterObject(SceneObject *collider);
+
+   bool testObject(SceneObject* enter);
+
+   virtual void processTick();
+
+   GameConnection* getConnection(S32 connectionID);
+
+   void addClient(S32 clientID);
+   void removeClient(S32 clientID);
+
+   void visualizeFrustums(F32 renderTimeMS);
+
+   DECLARE_CALLBACK(void, onEnterViewCmd, (Entity* cameraEnt, bool firstTimeSeeing));
+   DECLARE_CALLBACK(void, onExitViewCmd, (Entity* cameraEnt));
+   DECLARE_CALLBACK(void, onUpdateInViewCmd, (Entity* cameraEnt));
+   DECLARE_CALLBACK(void, onUpdateOutOfViewCmd, (Entity* cameraEnt));
+};
+
+#endif // _EXAMPLEBEHAVIOR_H_

+ 368 - 0
Engine/source/T3D/components/Physics/physicsBehavior.cpp

@@ -0,0 +1,368 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Physics/physicsBehavior.h"
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "ts/tsShapeInstance.h"
+#include "core/stream/bitStream.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/containerQuery.h"
+#include "math/mathIO.h"
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+PhysicsComponent::PhysicsComponent() : Component()
+{
+   addComponentField("isStatic", "If enabled, object will not simulate physics", "bool", "0", "");
+   addComponentField("gravity", "The direction of gravity affecting this object, as a vector", "vector", "0 0 -9", "");
+   addComponentField("drag", "The drag coefficient that constantly affects the object", "float", "0.7", "");
+   addComponentField("mass", "The mass of the object", "float", "1", "");
+
+   mStatic = false;
+   mAtRest = false;
+   mAtRestCounter = 0;
+
+   mGravity = VectorF(0, 0, 0);
+   mVelocity = VectorF(0, 0, 0);
+   mDrag = 0.7f;
+   mMass = 1.f;
+
+   mGravityMod = 1.f;
+
+   csmAtRestTimer = 64;
+   sAtRestVelocity = 0.15f;
+
+   mDelta.pos = Point3F(0, 0, 0);
+   mDelta.posVec = Point3F(0, 0, 0);
+   mDelta.warpTicks = mDelta.warpCount = 0;
+   mDelta.dt = 1;
+   mDelta.move = NullMove;
+   mPredictionCount = 0;
+}
+
+PhysicsComponent::~PhysicsComponent()
+{
+   for(S32 i = 0;i < mFields.size();++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(PhysicsComponent);
+
+void PhysicsComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   // Initialize interpolation vars.      
+   mDelta.rot[1] = mDelta.rot[0] = QuatF(mOwner->getTransform());
+   mDelta.pos = mOwner->getPosition();
+   mDelta.posVec = Point3F(0,0,0);
+}
+
+void PhysicsComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addField("gravity", TypePoint3F, Offset(mGravity, PhysicsComponent));
+   addField("velocity", TypePoint3F, Offset(mVelocity, PhysicsComponent));
+   addField("isStatic", TypeBool, Offset(mStatic, PhysicsComponent));
+}
+
+U32 PhysicsComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if(stream->writeFlag(mask & VelocityMask))
+      mathWrite( *stream, mVelocity );
+
+   if(stream->writeFlag(mask & UpdateMask))
+   {
+      stream->writeFlag(mStatic);
+      stream->writeFlag(mAtRest);
+      stream->writeInt(mAtRestCounter,8);
+
+      mathWrite( *stream, mGravity );
+
+      stream->writeFloat(mDrag, 12);
+      //stream->writeFloat(mMass, 12);
+
+      stream->writeFloat(mGravityMod, 12);
+   }
+   return retMask;
+}
+
+void PhysicsComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if(stream->readFlag())
+      mathRead( *stream, &mVelocity );
+
+   if(stream->readFlag())
+   {
+      mStatic = stream->readFlag();
+      mAtRest = stream->readFlag();
+      mAtRestCounter = stream->readInt(8);
+
+      mathRead( *stream, &mGravity );
+
+      mDrag = stream->readFloat(12);
+      //mMass = stream->readFloat(12);
+
+      mGravityMod = stream->readFloat(12);
+   }
+}
+
+//
+void PhysicsComponent::interpolateTick(F32 dt)
+{
+   Point3F pos = mDelta.pos + mDelta.posVec * dt;
+   //Point3F rot = mDelta.rot + mDelta.rotVec * dt;
+
+   setRenderPosition(pos,dt);
+}
+
+//
+void PhysicsComponent::updateContainer()
+{
+   PROFILE_SCOPE( PhysicsBehaviorInstance_updateContainer );
+
+   // Update container drag and buoyancy properties
+
+   // Set default values.
+   //mDrag = mDataBlock->drag;
+   //mBuoyancy = 0.0f;      
+   //mGravityMod = 1.0;
+   //mAppliedForce.set(0,0,0);
+
+   ContainerQueryInfo info;
+   info.box = mOwner->getWorldBox();
+   info.mass = mMass;
+
+   mOwner->getContainer()->findObjects(info.box, WaterObjectType|PhysicalZoneObjectType,findRouter,&info);
+
+   //mWaterCoverage = info.waterCoverage;
+   //mLiquidType    = info.liquidType;
+   //mLiquidHeight  = info.waterHeight;   
+   //setCurrentWaterObject( info.waterObject );
+
+   // This value might be useful as a datablock value,
+   // This is what allows the player to stand in shallow water (below this coverage)
+   // without jiggling from buoyancy
+   if (info.waterCoverage >= 0.25f) 
+   {      
+      // water viscosity is used as drag for in water.
+      // ShapeBaseData drag is used for drag outside of water.
+      // Combine these two components to calculate this ShapeBase object's 
+      // current drag.
+      mDrag = ( info.waterCoverage * info.waterViscosity ) + 
+         ( 1.0f - info.waterCoverage ) * mDrag;
+      //mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage;
+   }
+
+   //mAppliedForce = info.appliedForce;
+   mGravityMod = info.gravityScale;
+}
+//
+void PhysicsComponent::_updatePhysics()
+{
+   /*SAFE_DELETE( mOwner->mPhysicsRep );
+
+   if ( !PHYSICSMGR )
+   return;
+
+   if (mDataBlock->simpleServerCollision)
+   {
+   // We only need the trigger on the server.
+   if ( isServerObject() )
+   {
+   PhysicsCollision *colShape = PHYSICSMGR->createCollision();
+   colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
+
+   PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
+   mPhysicsRep = PHYSICSMGR->createBody();
+   mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
+   mPhysicsRep->setTransform( getTransform() );
+   }
+   }
+   else
+   {
+   if ( !mShapeInstance )
+   return;
+
+   PhysicsCollision* colShape = mShapeInstance->getShape()->buildColShape( false, getScale() );
+
+   if ( colShape )
+   {
+   PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
+   mPhysicsRep = PHYSICSMGR->createBody();
+   mPhysicsRep->init( colShape, 0, PhysicsBody::BF_KINEMATIC, this, world );
+   mPhysicsRep->setTransform( getTransform() );
+   }
+   }*/
+   return;
+}
+
+PhysicsBody *PhysicsComponent::getPhysicsRep()
+{
+   /*if(mOwner)
+   {
+      Entity* ac = dynamic_cast<Entity*>(mOwner);
+      if(ac)
+         return ac->mPhysicsRep;
+   }*/
+   return NULL;
+}
+//
+void PhysicsComponent::setTransform(const MatrixF& mat)
+{
+   mOwner->setTransform(mat);
+
+   if (!mStatic)
+   {
+      mAtRest = false;
+      mAtRestCounter = 0;
+   }
+
+   if ( getPhysicsRep() )
+      getPhysicsRep()->setTransform( mOwner->getTransform() );
+
+   setMaskBits(UpdateMask);
+}
+
+void PhysicsComponent::setPosition(const Point3F& pos)
+{
+   MatrixF mat = mOwner->getTransform();
+   if (mOwner->isMounted()) {
+      // Use transform from mounted object
+      //mOwner->getObjectMount()->getMountTransform( mOwner->getMountNode(), mMount.xfm, &mat );
+      return;
+   }
+   else {
+      mat.setColumn(3,pos);
+   }
+
+   mOwner->setTransform(mat);
+
+   if ( getPhysicsRep() )
+      getPhysicsRep()->setTransform( mat );
+}
+
+
+void PhysicsComponent::setRenderPosition(const Point3F& pos, F32 dt)
+{
+   MatrixF mat = mOwner->getRenderTransform();
+   if (mOwner->isMounted()) {
+      // Use transform from mounted object
+      //mOwner->getObjectMount()->getMountRenderTransform( dt, mOwner->getMountNode(), mMount.xfm, &mat );
+      return;
+   }
+   else {
+      mat.setColumn(3,pos);
+   }
+
+   mOwner->setRenderTransform(mat);
+}
+
+void PhysicsComponent::updateVelocity(const F32 dt)
+{
+}
+
+void PhysicsComponent::setVelocity(const VectorF& vel)
+{
+   mVelocity = vel;
+
+   mAtRest = false;
+   mAtRestCounter = 0;
+   setMaskBits(VelocityMask);
+}
+
+void PhysicsComponent::getVelocity(const Point3F& r, Point3F* v)
+{
+   *v = mVelocity;
+}
+
+void PhysicsComponent::getOriginVector(const Point3F &p,Point3F* r)
+{
+   *r = p - mOwner->getObjBox().getCenter();
+}
+
+F32 PhysicsComponent::getZeroImpulse(const Point3F& r,const Point3F& normal)
+{
+   Point3F a,b,c;
+
+   //set up our inverse matrix
+   MatrixF iv,qmat;
+   MatrixF inverse = MatrixF::Identity;
+   qmat = mOwner->getTransform();
+   iv.mul(qmat,inverse);
+   qmat.transpose();
+   inverse.mul(iv,qmat);
+
+   mCross(r, normal, &a);
+   inverse.mulV(a, &b);
+   mCross(b, r, &c);
+
+   return 1 / ((1/mMass) + mDot(c, normal));
+}
+
+void PhysicsComponent::accumulateForce(F32 dt, Point3F force)
+{
+   mVelocity += force * dt;
+}
+
+void PhysicsComponent::applyImpulse(const Point3F&,const VectorF& vec)
+{
+   // Items ignore angular velocity
+   VectorF vel;
+   vel.x = vec.x / mMass;
+   vel.y = vec.y / mMass;
+   vel.z = vec.z / mMass;
+   setVelocity(mVelocity + vel);
+}
+
+DefineEngineMethod( PhysicsComponent, applyImpulse, bool, ( Point3F pos, VectorF vel ),,
+                   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+                   "@param pos impulse world position\n"
+                   "@param vel impulse velocity (impulse force F = m * v)\n"
+                   "@return Always true\n"
+
+                   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   object->applyImpulse(pos,vel);
+   return true;
+}

+ 135 - 0
Engine/source/T3D/components/Physics/physicsBehavior.h

@@ -0,0 +1,135 @@
+//-----------------------------------------------------------------------------
+// Torque Game Engine
+// Copyright (C) GarageGames.com, Inc.
+//-----------------------------------------------------------------------------
+
+#ifndef _PHYSICSBEHAVIOR_H_
+#define _PHYSICSBEHAVIOR_H_
+#include "T3D/Components/Component.h"
+
+#ifndef __RESOURCE_H__
+#include "core/resource.h"
+#endif
+#ifndef _TSSHAPE_H_
+#include "ts/tsShape.h"
+#endif
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef _ENTITY_H_
+#include "T3D/Entity.h"
+#endif
+#ifndef _CONVEX_H_
+#include "collision/convex.h"
+#endif
+#ifndef _BOXCONVEX_H_
+#include "collision/boxConvex.h"
+#endif
+#ifndef _RIGID_H_
+#include "T3D/rigid.h"
+#endif
+#ifndef _T3D_PHYSICS_PHYSICSBODY_H_
+#include "T3D/physics/physicsBody.h"
+#endif
+
+#ifndef _RENDER_COMPONENT_INTERFACE_H_
+#include "T3D/Components/render/renderComponentInterface.h"
+#endif
+
+class TSShapeInstance;
+class SceneRenderState;
+class PhysicsBody;
+class PhysicsBehaviorInstance;
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class PhysicsComponent : public Component
+{
+   typedef Component Parent;
+
+protected:
+   bool mStatic;
+   bool mAtRest;
+   S32  mAtRestCounter;
+
+   VectorF mGravity;
+   VectorF mVelocity;
+   F32     mDrag;
+   F32		mMass;
+
+   F32		mGravityMod;
+
+   S32 csmAtRestTimer;
+   F32 sAtRestVelocity;      // Min speed after collisio
+
+public:
+   enum MaskBits {
+      PositionMask = Parent::NextFreeMask << 0,
+      FreezeMask = Parent::NextFreeMask << 1,
+      ForceMoveMask = Parent::NextFreeMask << 2,
+      VelocityMask = Parent::NextFreeMask << 3,
+      NextFreeMask = Parent::NextFreeMask << 4
+   };
+
+   struct StateDelta
+   {
+      Move move;                    ///< Last move from server
+      F32 dt;                       ///< Last interpolation time
+      // Interpolation data
+      Point3F pos;
+      Point3F posVec;
+      QuatF rot[2];
+      // Warp data
+      S32 warpTicks;                ///< Number of ticks to warp
+      S32 warpCount;                ///< Current pos in warp
+      Point3F warpOffset;
+      QuatF warpRot[2];
+   };
+
+   StateDelta mDelta;
+   S32 mPredictionCount;            ///< Number of ticks to predict
+
+public:
+   PhysicsComponent();
+   virtual ~PhysicsComponent();
+   DECLARE_CONOBJECT(PhysicsComponent);
+
+   static void initPersistFields();
+
+   virtual void interpolateTick(F32 dt);
+   virtual void updatePos(const U32 /*mask*/, const F32 dt){}
+   virtual void _updatePhysics();
+   virtual PhysicsBody *getPhysicsRep();
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   virtual void onComponentAdd();
+
+   void updateContainer();
+
+   virtual void updateVelocity(const F32 dt);
+   virtual Point3F getVelocity() { return mVelocity; }
+   virtual void getOriginVector(const Point3F &p, Point3F* r);
+   virtual void getVelocity(const Point3F& r, Point3F* v);
+   virtual void setVelocity(const VectorF& vel);
+   virtual void setTransform(const MatrixF& mat);
+   virtual void setPosition(const Point3F& pos);
+   void setRenderPosition(const Point3F& pos, F32 dt);
+
+   virtual void applyImpulse(const Point3F&, const VectorF& vec);
+   virtual F32 getZeroImpulse(const Point3F& r, const Point3F& normal);
+   virtual void accumulateForce(F32 dt, Point3F force);
+
+   //Rigid Body Collision Conveinence Hooks
+   virtual bool updateCollision(F32 dt, Rigid& ns, CollisionList &cList) { return false; }
+   virtual bool resolveContacts(Rigid& ns, CollisionList& cList, F32 dt) { return false; }
+   //virtual bool resolveCollision(Rigid&  ns, CollisionList& cList) { return false; }
+   virtual bool resolveCollision(const Point3F& p, const Point3F &normal) { return false; }
+};
+
+#endif // _COMPONENT_H_

+ 0 - 0
Engine/source/T3D/components/Physics/physicsComponentInterface.cpp


+ 49 - 0
Engine/source/T3D/components/Physics/physicsComponentInterface.h

@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef PHYSICS_COMPONENT_INTERFACE_H
+#define PHYSICS_COMPONENT_INTERFACE_H
+
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+
+class PhysicsComponentInterface : public Interface<PhysicsComponentInterface>
+{
+protected:
+   VectorF  mVelocity;
+   F32      mMass;
+
+   F32		mGravityMod;
+
+public:
+   void updateForces();
+
+   VectorF getVelocity() { return mVelocity; }
+   void setVelocity(VectorF vel) { mVelocity = vel; }
+
+   F32 getMass() { return mMass; }
+
+   Signal< void(VectorF normal, Vector<SceneObject*> overlappedObjects) > PhysicsComponentInterface::onPhysicsCollision;
+};
+
+#endif

+ 863 - 0
Engine/source/T3D/components/Physics/playerControllerComponent.cpp

@@ -0,0 +1,863 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/Physics/playerControllerComponent.h"
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "ts/tsShapeInstance.h"
+#include "core/stream/bitStream.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "collision/collision.h"
+#include "T3D/physics/physicsPlayer.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/Components/Collision/collisionInterfaces.h"
+#include "T3D/trigger.h"
+#include "T3D/components/collision/collisionTrigger.h"
+
+// Movement constants
+static F32 sVerticalStepDot = 0.173f;   // 80
+static F32 sMinFaceDistance = 0.01f;
+static F32 sTractionDistance = 0.04f;
+static F32 sNormalElasticity = 0.01f;
+static U32 sMoveRetryCount = 5;
+static F32 sMaxImpulseVelocity = 200.0f;
+
+//////////////////////////////////////////////////////////////////////////
+// Callbacks
+IMPLEMENT_CALLBACK(PlayerControllerComponent, updateMove, void, (PlayerControllerComponent* obj), (obj),
+   "Called when the player updates it's movement, only called if object is set to callback in script(doUpdateMove).\n"
+   "@param obj the Player object\n");
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+PlayerControllerComponent::PlayerControllerComponent() : Component()
+{
+   addComponentField("isStatic", "If enabled, object will not simulate physics", "bool", "0", "");
+   addComponentField("gravity", "The direction of gravity affecting this object, as a vector", "vector", "0 0 -9", "");
+   addComponentField("drag", "The drag coefficient that constantly affects the object", "float", "0.7", "");
+   addComponentField("mass", "The mass of the object", "float", "1", "");
+
+   mBuoyancy = 0.f;
+   mFriction = 0.3f;
+   mElasticity = 0.4f;
+   mMaxVelocity = 3000.f;
+   mSticky = false;
+   
+   mFalling = false;
+   mSwimming = false;
+   mInWater = false;
+
+   mDelta.pos = mDelta.posVec = Point3F::Zero;
+   mDelta.warpTicks = mDelta.warpCount = 0;
+   mDelta.rot[0].identity(); 
+   mDelta.rot[1].identity();
+   mDelta.dt = 1;
+
+   mUseDirectMoveInput = false;
+
+   mFriendlyName = "Player Controller";
+   mComponentType = "Physics";
+
+   mDescription = getDescriptionText("A general-purpose physics player controller.");
+
+   mNetFlags.set(Ghostable | ScopeAlways);
+
+   mMass = 9.0f;         // from ShapeBase
+   mDrag = 1.0f;         // from ShapeBase
+
+   maxStepHeight = 1.0f;
+   moveSurfaceAngle = 60.0f;
+   contactSurfaceAngle = 85.0f;
+
+   fallingSpeedThreshold = -10.0f;
+
+   horizMaxSpeed = 80.0f;
+   horizMaxAccel = 100.0f;
+   horizResistSpeed = 38.0f;
+   horizResistFactor = 1.0f;
+
+   upMaxSpeed = 80.0f;
+   upMaxAccel = 100.0f;
+   upResistSpeed = 38.0f;
+   upResistFactor = 1.0f;
+
+   // Air control
+   airControl = 0.0f;
+
+   //Grav mod
+   mGravityMod = 1;
+
+   mInputVelocity = Point3F(0, 0, 0);
+
+   mPhysicsRep = NULL;
+   mPhysicsWorld = NULL;
+}
+
+PlayerControllerComponent::~PlayerControllerComponent()
+{
+   for (S32 i = 0; i < mFields.size(); ++i)
+   {
+      ComponentField &field = mFields[i];
+      SAFE_DELETE_ARRAY(field.mFieldDescription);
+   }
+
+   SAFE_DELETE_ARRAY(mDescription);
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(PlayerControllerComponent);
+
+//////////////////////////////////////////////////////////////////////////
+
+bool PlayerControllerComponent::onAdd()
+{
+   if (!Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+void PlayerControllerComponent::onRemove()
+{
+   Parent::onRemove();
+
+   SAFE_DELETE(mPhysicsRep);
+}
+
+void PlayerControllerComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   updatePhysics();
+}
+
+void PlayerControllerComponent::componentAddedToOwner(Component *comp)
+{
+   if (comp->getId() == getId())
+      return;
+
+   //test if this is a shape component!
+   CollisionInterface *collisionInterface = dynamic_cast<CollisionInterface*>(comp);
+   if (collisionInterface)
+   {
+      collisionInterface->onCollisionChanged.notify(this, &PlayerControllerComponent::updatePhysics);
+      mOwnerCollisionInterface = collisionInterface;
+      updatePhysics();
+   }
+}
+
+void PlayerControllerComponent::componentRemovedFromOwner(Component *comp)
+{
+   if (comp->getId() == getId()) //?????????
+      return;
+
+   //test if this is a shape component!
+   CollisionInterface *collisionInterface = dynamic_cast<CollisionInterface*>(comp);
+   if (collisionInterface)
+   {
+      collisionInterface->onCollisionChanged.remove(this, &PlayerControllerComponent::updatePhysics);
+      mOwnerCollisionInterface = NULL;
+      updatePhysics();
+   }
+}
+
+void PlayerControllerComponent::updatePhysics(PhysicsCollision *collision)
+{
+   if (!PHYSICSMGR)
+      return;
+
+   mPhysicsWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
+
+   //first, clear the old physRep
+   SAFE_DELETE(mPhysicsRep);
+
+   mPhysicsRep = PHYSICSMGR->createPlayer();
+
+   F32 runSurfaceCos = mCos(mDegToRad(moveSurfaceAngle));
+
+   Point3F ownerBounds = mOwner->getObjBox().getExtents() * mOwner->getScale();
+
+   mPhysicsRep->init("", ownerBounds, runSurfaceCos, maxStepHeight, mOwner, mPhysicsWorld);
+
+   mPhysicsRep->setTransform(mOwner->getTransform());
+}
+
+void PlayerControllerComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   addField("inputVelocity", TypePoint3F, Offset(mInputVelocity, PlayerControllerComponent), "");
+   addField("useDirectMoveInput", TypePoint3F, Offset(mUseDirectMoveInput, PlayerControllerComponent), "");
+}
+
+U32 PlayerControllerComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   return retMask;
+}
+
+void PlayerControllerComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+}
+
+//
+void PlayerControllerComponent::processTick()
+{
+   Parent::processTick();
+
+   if (!isServerObject() || !isActive())
+      return;
+
+   // Warp to catch up to server
+   if (mDelta.warpCount < mDelta.warpTicks)
+   {
+      mDelta.warpCount++;
+
+      // Set new pos.
+      mDelta.pos = mOwner->getPosition();
+      mDelta.pos += mDelta.warpOffset;
+      mDelta.rot[0] = mDelta.rot[1];
+      mDelta.rot[1].interpolate(mDelta.warpRot[0], mDelta.warpRot[1], F32(mDelta.warpCount) / mDelta.warpTicks);
+      
+      MatrixF trans;
+      mDelta.rot[1].setMatrix(&trans);
+      trans.setPosition(mDelta.pos);
+
+      mOwner->setTransform(trans);
+
+      // Pos backstepping
+      mDelta.posVec.x = -mDelta.warpOffset.x;
+      mDelta.posVec.y = -mDelta.warpOffset.y;
+      mDelta.posVec.z = -mDelta.warpOffset.z;
+   }
+   else
+   {
+      // Save current rigid state interpolation
+      mDelta.posVec = mOwner->getPosition();
+      mDelta.rot[0] = mOwner->getTransform();
+
+      updateMove();
+      updatePos(TickSec);
+
+      // Wrap up interpolation info
+      mDelta.pos = mOwner->getPosition();
+      mDelta.posVec -= mOwner->getPosition();
+      mDelta.rot[1]  = mOwner->getTransform();
+
+      // Update container database
+      setTransform(mOwner->getTransform());
+      
+      setMaskBits(VelocityMask);
+      setMaskBits(PositionMask);
+   }
+}
+
+void PlayerControllerComponent::interpolateTick(F32 dt)
+{
+}
+
+void PlayerControllerComponent::ownerTransformSet(MatrixF *mat)
+{
+   if (mPhysicsRep)
+      mPhysicsRep->setTransform(mOwner->getTransform());
+}
+
+void PlayerControllerComponent::setTransform(const MatrixF& mat)
+{
+   mOwner->setTransform(mat);
+
+   setMaskBits(UpdateMask);
+}
+
+//
+void PlayerControllerComponent::updateMove()
+{
+   if (!PHYSICSMGR)
+      return;
+
+   Move *move = &mOwner->lastMove;
+
+   //If we're not set to use mUseDirectMoveInput, then we allow for an override in the form of mInputVelocity
+   if (!mUseDirectMoveInput)
+   {
+      move->x = mInputVelocity.x;
+      move->y = mInputVelocity.y;
+      move->z = mInputVelocity.z;
+   }
+
+   // Is waterCoverage high enough to be 'swimming'?
+   {
+      bool swimming = mOwner->getContainerInfo().waterCoverage > 0.65f/* && canSwim()*/;
+
+      if (swimming != mSwimming)
+      {
+         mSwimming = swimming;
+      }
+   }
+
+   // Update current orientation
+   bool doStandardMove = true;
+   GameConnection* con = mOwner->getControllingClient();
+
+#ifdef TORQUE_EXTENDED_MOVE
+   // Work with an absolute rotation from the ExtendedMove class?
+   if (con && con->getControlSchemeAbsoluteRotation())
+   {
+      doStandardMove = false;
+      const ExtendedMove* emove = dynamic_cast<const ExtendedMove*>(move);
+      U32 emoveIndex = smExtendedMoveHeadPosRotIndex;
+      if (emoveIndex >= ExtendedMove::MaxPositionsRotations)
+         emoveIndex = 0;
+
+      if (emove->EulerBasedRotation[emoveIndex])
+      {
+         // Head pitch
+         mHead.x += (emove->rotX[emoveIndex] - mLastAbsolutePitch);
+
+         // Do we also include the relative yaw value?
+         if (con->getControlSchemeAddPitchToAbsRot())
+         {
+            F32 x = move->pitch;
+            if (x > M_PI_F)
+               x -= M_2PI_F;
+
+            mHead.x += x;
+         }
+
+         // Constrain the range of mHead.x
+         while (mHead.x < -M_PI_F)
+            mHead.x += M_2PI_F;
+         while (mHead.x > M_PI_F)
+            mHead.x -= M_2PI_F;
+
+         // Rotate (heading) head or body?
+         if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))
+         {
+            // Rotate head
+            mHead.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw);
+
+            // Do we also include the relative yaw value?
+            if (con->getControlSchemeAddYawToAbsRot())
+            {
+               F32 z = move->yaw;
+               if (z > M_PI_F)
+                  z -= M_2PI_F;
+
+               mHead.z += z;
+            }
+
+            // Constrain the range of mHead.z
+            while (mHead.z < 0.0f)
+               mHead.z += M_2PI_F;
+            while (mHead.z > M_2PI_F)
+               mHead.z -= M_2PI_F;
+         }
+         else
+         {
+            // Rotate body
+            mRot.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw);
+
+            // Do we also include the relative yaw value?
+            if (con->getControlSchemeAddYawToAbsRot())
+            {
+               F32 z = move->yaw;
+               if (z > M_PI_F)
+                  z -= M_2PI_F;
+
+               mRot.z += z;
+            }
+
+            // Constrain the range of mRot.z
+            while (mRot.z < 0.0f)
+               mRot.z += M_2PI_F;
+            while (mRot.z > M_2PI_F)
+               mRot.z -= M_2PI_F;
+         }
+         mLastAbsoluteYaw = emove->rotZ[emoveIndex];
+         mLastAbsolutePitch = emove->rotX[emoveIndex];
+
+         // Head bank
+         mHead.y = emove->rotY[emoveIndex];
+
+         // Constrain the range of mHead.y
+         while (mHead.y > M_PI_F)
+            mHead.y -= M_2PI_F;
+      }
+   }
+#endif
+
+   MatrixF zRot;
+   zRot.set(EulerF(0.0f, 0.0f, mOwner->getRotation().asEulerF().z));
+
+   // Desired move direction & speed
+   VectorF moveVec;
+   F32 moveSpeed = mInputVelocity.len();
+
+   zRot.getColumn(0, &moveVec);
+   moveVec *= move->x;
+   VectorF tv;
+   zRot.getColumn(1, &tv);
+   moveVec += tv * move->y;
+
+   // Acceleration due to gravity
+   VectorF acc(mPhysicsWorld->getGravity() * mGravityMod * TickSec);
+
+   // Determine ground contact normal. Only look for contacts if
+   // we can move and aren't mounted.
+   mContactInfo.contactNormal = VectorF::Zero;
+   mContactInfo.jump = false;
+   mContactInfo.run = false;
+
+   bool jumpSurface = false, runSurface = false;
+   if (!mOwner->isMounted())
+      findContact(&mContactInfo.run, &mContactInfo.jump, &mContactInfo.contactNormal);
+   if (mContactInfo.jump)
+      mJumpSurfaceNormal = mContactInfo.contactNormal;
+
+   // If we don't have a runSurface but we do have a contactNormal,
+   // then we are standing on something that is too steep.
+   // Deflect the force of gravity by the normal so we slide.
+   // We could also try aligning it to the runSurface instead,
+   // but this seems to work well.
+   if (!mContactInfo.run && !mContactInfo.contactNormal.isZero())
+      acc = (acc - 2 * mContactInfo.contactNormal * mDot(acc, mContactInfo.contactNormal));
+
+   // Acceleration on run surface
+   if (mContactInfo.run && !mSwimming)
+   {
+      mContactTimer = 0;
+
+      VectorF pv = moveVec;
+
+      // Adjust the player's requested dir. to be parallel
+      // to the contact surface.
+      F32 pvl = pv.len();
+
+      // Convert to acceleration
+      if (pvl)
+         pv *= moveSpeed / pvl;
+      VectorF runAcc = pv - (mVelocity + acc);
+      F32 runSpeed = runAcc.len();
+
+      // Clamp acceleration, player also accelerates faster when
+      // in his hard landing recover state.
+      F32 maxAcc;
+
+      maxAcc = (horizMaxAccel / mMass) * TickSec;
+
+      if (runSpeed > maxAcc)
+         runAcc *= maxAcc / runSpeed;
+
+      acc += runAcc;
+   }
+   else if (!mSwimming && airControl > 0.0f)
+   {
+      VectorF pv;
+      pv = moveVec;
+      F32 pvl = pv.len();
+
+      if (pvl)
+         pv *= moveSpeed / pvl;
+
+      VectorF runAcc = pv - (mVelocity + acc);
+      runAcc.z = 0;
+      runAcc.x = runAcc.x * airControl;
+      runAcc.y = runAcc.y * airControl;
+      F32 runSpeed = runAcc.len();
+
+      // We don't test for sprinting when performing air control
+      F32 maxAcc = (horizMaxAccel / mMass) * TickSec * 0.3f;
+
+      if (runSpeed > maxAcc)
+         runAcc *= maxAcc / runSpeed;
+
+      acc += runAcc;
+
+      // There are no special air control animations 
+      // so... increment this unless you really want to 
+      // play the run anims in the air.
+      mContactTimer++;
+   }
+   else if (mSwimming)
+   {
+      // Remove acc into contact surface (should only be gravity)
+      // Clear out floating point acc errors, this will allow
+      // the player to "rest" on the ground.
+      F32 vd = -mDot(acc, mContactInfo.contactNormal);
+      if (vd > 0.0f) 
+      {
+         VectorF dv = mContactInfo.contactNormal * (vd + 0.002f);
+         acc += dv;
+         if (acc.len() < 0.0001f)
+            acc.set(0.0f, 0.0f, 0.0f);
+      }
+
+      // get the head pitch and add it to the moveVec
+      // This more accurate swim vector calc comes from Matt Fairfax
+      MatrixF xRot, zRot;
+      xRot.set(EulerF(mOwner->getRotation().asEulerF().x, 0, 0));
+      zRot.set(EulerF(0, 0, mOwner->getRotation().asEulerF().z));
+      MatrixF rot;
+      rot.mul(zRot, xRot);
+      rot.getColumn(0, &moveVec);
+
+      moveVec *= move->x;
+      VectorF tv;
+      rot.getColumn(1, &tv);
+      moveVec += tv * move->y;
+      rot.getColumn(2, &tv);
+      moveVec += tv * move->z;
+
+      // Force a 0 move if there is no energy, and only drain
+      // move energy if we're moving.
+      VectorF swimVec = moveVec;
+
+      // If we are swimming but close enough to the shore/ground
+      // we can still have a surface-normal. In this case align the
+      // velocity to the normal to make getting out of water easier.
+
+      moveVec.normalize();
+      F32 isSwimUp = mDot(moveVec, mContactInfo.contactNormal);
+
+      if (!mContactInfo.contactNormal.isZero() && isSwimUp < 0.1f)
+      {
+         F32 pvl = swimVec.len();
+
+         if (pvl)
+         {
+            VectorF nn;
+            mCross(swimVec, VectorF(0.0f, 0.0f, 1.0f), &nn);
+            nn *= 1.0f / pvl;
+            VectorF cv = mContactInfo.contactNormal;
+            cv -= nn * mDot(nn, cv);
+            swimVec -= cv * mDot(swimVec, cv);
+         }
+      }
+
+      F32 swimVecLen = swimVec.len();
+
+      // Convert to acceleration.
+      if (swimVecLen)
+         swimVec *= moveSpeed / swimVecLen;
+      VectorF swimAcc = swimVec - (mVelocity + acc);
+      F32 swimSpeed = swimAcc.len();
+
+      // Clamp acceleration.
+      F32 maxAcc = (horizMaxAccel / mMass) * TickSec;
+      if (swimSpeed > maxAcc)
+         swimAcc *= maxAcc / swimSpeed;
+
+      acc += swimAcc;
+
+      mContactTimer++;
+   }
+   else
+      mContactTimer++;
+
+   // Add in force from physical zones...
+   acc += (mOwner->getContainerInfo().appliedForce / mMass) * TickSec;
+
+   // Adjust velocity with all the move & gravity acceleration
+   // TG: I forgot why doesn't the TickSec multiply happen here...
+   mVelocity += acc;
+
+   // apply horizontal air resistance
+
+   F32 hvel = mSqrt(mVelocity.x * mVelocity.x + mVelocity.y * mVelocity.y);
+
+   if (hvel > horizResistSpeed)
+   {
+      F32 speedCap = hvel;
+      if (speedCap > horizMaxSpeed)
+         speedCap = horizMaxSpeed;
+      speedCap -= horizResistFactor * TickSec * (speedCap - horizResistSpeed);
+      F32 scale = speedCap / hvel;
+      mVelocity.x *= scale;
+      mVelocity.y *= scale;
+   }
+   if (mVelocity.z > upResistSpeed)
+   {
+      if (mVelocity.z > upMaxSpeed)
+         mVelocity.z = upMaxSpeed;
+      mVelocity.z -= upResistFactor * TickSec * (mVelocity.z - upResistSpeed);
+   }
+
+   // Apply drag
+   mVelocity -= mVelocity * mDrag * TickSec;
+
+   // Clamp very small velocity to zero
+   if (mVelocity.isZero())
+      mVelocity = Point3F::Zero;
+
+   // If we are not touching anything and have sufficient -z vel,
+   // we are falling.
+   if (mContactInfo.run)
+   {
+      mFalling = false;
+   }
+   else
+   {
+      VectorF vel;
+      mOwner->getWorldToObj().mulV(mVelocity, &vel);
+      mFalling = vel.z < fallingSpeedThreshold;
+   }
+
+   // Enter/Leave Liquid
+   if (!mInWater && mOwner->getContainerInfo().waterCoverage > 0.0f)
+   {
+      mInWater = true;
+   }
+   else if (mInWater && mOwner->getContainerInfo().waterCoverage <= 0.0f)
+   {
+      mInWater = false;
+   }
+}
+
+void PlayerControllerComponent::updatePos(const F32 travelTime)
+{
+   if (!PHYSICSMGR)
+      return;
+
+   PROFILE_SCOPE(PlayerControllerComponent_UpdatePos);
+
+   Point3F newPos;
+
+   Collision col;
+   dMemset(&col, 0, sizeof(col));
+
+   static CollisionList collisionList;
+   collisionList.clear();
+
+   newPos = mPhysicsRep->move(mVelocity * travelTime, collisionList);
+
+   bool haveCollisions = false;
+   bool wasFalling = mFalling;
+   if (collisionList.getCount() > 0)
+   {
+      mFalling = false;
+      haveCollisions = true;
+
+      //TODO: clean this up so the phys component doesn't have to tell the col interface to do this
+      CollisionInterface* colInterface = mOwner->getComponent<CollisionInterface>();
+      if (colInterface)
+      {
+         colInterface->handleCollisionList(collisionList, mVelocity);
+      }
+   }
+
+   if (haveCollisions)
+   {
+      // Pick the collision that most closely matches our direction
+      VectorF velNormal = mVelocity;
+      velNormal.normalizeSafe();
+      const Collision *collision = &collisionList[0];
+      F32 collisionDot = mDot(velNormal, collision->normal);
+      const Collision *cp = collision + 1;
+      const Collision *ep = collision + collisionList.getCount();
+      for (; cp != ep; cp++)
+      {
+         F32 dp = mDot(velNormal, cp->normal);
+         if (dp < collisionDot)
+         {
+            collisionDot = dp;
+            collision = cp;
+         }
+      }
+
+      // Modify our velocity based on collisions
+      for (U32 i = 0; i<collisionList.getCount(); ++i)
+      {
+         F32 bd = -mDot(mVelocity, collisionList[i].normal);
+         VectorF dv = collisionList[i].normal * (bd + sNormalElasticity);
+         mVelocity += dv;
+      }
+
+      // Store the last collision for use later on.  The handle collision
+      // code only expects a single collision object.
+      if (collisionList.getCount() > 0)
+         col = collisionList[collisionList.getCount() - 1];
+
+      // We'll handle any player-to-player collision, and the last collision
+      // with other obejct types.
+      for (U32 i = 0; i<collisionList.getCount(); ++i)
+      {
+         Collision& colCheck = collisionList[i];
+         if (colCheck.object)
+         {
+            col = colCheck;
+         }
+      }
+   }
+   
+   MatrixF newMat;
+   newMat.setPosition(newPos);
+   mPhysicsRep->setTransform(newMat);
+
+   mOwner->setPosition(newPos);
+}
+
+//
+void PlayerControllerComponent::setVelocity(const VectorF& vel)
+{
+   mVelocity = vel;
+
+   // Clamp against the maximum velocity.
+   if (mMaxVelocity > 0)
+   {
+      F32 len = mVelocity.magnitudeSafe();
+      if (len > mMaxVelocity)
+      {
+         Point3F excess = mVelocity * (1.0f - (mMaxVelocity / len));
+         mVelocity -= excess;
+      }
+   }
+
+   setMaskBits(VelocityMask);
+}
+
+void PlayerControllerComponent::findContact(bool *run, bool *jump, VectorF *contactNormal)
+{
+   SceneObject *contactObject = NULL;
+
+   Vector<SceneObject*> overlapObjects;
+
+   mPhysicsRep->findContact(&contactObject, contactNormal, &overlapObjects);
+
+   F32 vd = (*contactNormal).z;
+   *run = vd > mCos(mDegToRad(moveSurfaceAngle));
+   *jump = vd > mCos(mDegToRad(contactSurfaceAngle));
+
+   // Check for triggers
+   for (U32 i = 0; i < overlapObjects.size(); i++)
+   {
+      SceneObject *obj = overlapObjects[i];
+      U32 objectMask = obj->getTypeMask();
+
+      // Check: triggers, corpses and items...
+      //
+      if (objectMask & TriggerObjectType)
+      {
+         if (Trigger* pTrigger = dynamic_cast<Trigger*>(obj))
+         {
+            pTrigger->potentialEnterObject(mOwner);
+         }
+         else if (CollisionTrigger* pTriggerEx = dynamic_cast<CollisionTrigger*>(obj))
+         {
+            if (pTriggerEx)
+               pTriggerEx->potentialEnterObject(mOwner);
+         }
+         //Add any other custom classes and the sort here that should be filtered against
+         /*else if (TriggerExample* pTriggerEx = dynamic_cast<TriggerExample*>(obj))
+         {
+            if (pTriggerEx)
+               pTriggerEx->potentialEnterObject(mOwner);
+         }*/
+      }
+   }
+
+   mContactInfo.contacted = contactObject != NULL;
+   mContactInfo.contactObject = contactObject;
+
+   if (mContactInfo.contacted)
+      mContactInfo.contactNormal = *contactNormal;
+}
+
+void PlayerControllerComponent::applyImpulse(const Point3F &pos, const VectorF &vec)
+{
+
+   AssertFatal(!mIsNaN(vec), "Player::applyImpulse() - The vector is NaN!");
+
+   // Players ignore angular velocity
+   VectorF vel;
+   vel.x = vec.x / getMass();
+   vel.y = vec.y / getMass();
+   vel.z = vec.z / getMass();
+
+   // Make sure the impulse isn't too bigg
+   F32 len = vel.magnitudeSafe();
+   if (len > sMaxImpulseVelocity)
+   {
+      Point3F excess = vel * (1.0f - (sMaxImpulseVelocity / len));
+      vel -= excess;
+   }
+
+   setVelocity(mVelocity + vel);
+}
+
+DefineEngineMethod(PlayerControllerComponent, applyImpulse, bool, (Point3F pos, VectorF vel), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   object->applyImpulse(pos, vel);
+   return true;
+}
+
+DefineEngineMethod(PlayerControllerComponent, getContactNormal, Point3F, (), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getContactNormal();
+}
+
+DefineEngineMethod(PlayerControllerComponent, getContactObject, SceneObject*, (), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->getContactObject();
+}
+
+DefineEngineMethod(PlayerControllerComponent, isContacted, bool, (), ,
+   "@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
+
+   "@param pos impulse world position\n"
+   "@param vel impulse velocity (impulse force F = m * v)\n"
+   "@return Always true\n"
+
+   "@note Not all objects that derrive from GameBase have this defined.\n")
+{
+   return object->isContacted();
+}

+ 212 - 0
Engine/source/T3D/components/Physics/playerControllerComponent.h

@@ -0,0 +1,212 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef PLAYER_CONTORLLER_COMPONENT_H
+#define PLAYER_CONTORLLER_COMPONENT_H
+
+#ifndef PHYSICSBEHAVIOR_H
+#include "T3D/Components/Physics/physicsBehavior.h"
+#endif
+#ifndef __RESOURCE_H__
+#include "core/resource.h"
+#endif
+#ifndef _TSSHAPE_H_
+#include "ts/tsShape.h"
+#endif
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef _CONVEX_H_
+#include "collision/convex.h"
+#endif
+#ifndef _BOXCONVEX_H_
+#include "collision/boxConvex.h"
+#endif
+#ifndef _T3D_PHYSICSCOMMON_H_
+#include "T3D/physics/physicsCommon.h"
+#endif
+#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_
+#include "T3D/physics/physicsWorld.h"
+#endif
+#ifndef PHYSICS_COMPONENT_INTERFACE_H
+#include "T3D/Components/physics/physicsComponentInterface.h"
+#endif
+#ifndef COLLISION_INTERFACES_H
+#include "T3D/Components/collision/collisionInterfaces.h"
+#endif
+
+class SceneRenderState;
+class PhysicsWorld;
+class PhysicsPlayer;
+class SimplePhysicsBehaviorInstance;
+class CollisionInterface;
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class PlayerControllerComponent : public Component,
+   public PhysicsComponentInterface
+{
+   typedef Component Parent;
+
+   enum MaskBits {
+      VelocityMask = Parent::NextFreeMask << 0,
+      PositionMask = Parent::NextFreeMask << 1,
+      NextFreeMask = Parent::NextFreeMask << 2
+   };
+
+   struct StateDelta
+   {
+      Move move;                    ///< Last move from server
+      F32 dt;                       ///< Last interpolation time
+      // Interpolation data
+      Point3F pos;
+      Point3F posVec;
+      QuatF rot[2];
+      // Warp data
+      S32 warpTicks;                ///< Number of ticks to warp
+      S32 warpCount;                ///< Current pos in warp
+      Point3F warpOffset;
+      QuatF warpRot[2];
+   };
+
+   StateDelta mDelta;
+
+   PhysicsPlayer *mPhysicsRep;
+   PhysicsWorld  *mPhysicsWorld;
+
+   CollisionInterface* mOwnerCollisionInterface;
+
+   struct ContactInfo
+   {
+      bool contacted, jump, run;
+      SceneObject *contactObject;
+      VectorF  contactNormal;
+      F32 contactTime;
+
+      void clear()
+      {
+         contacted = jump = run = false;
+         contactObject = NULL;
+         contactNormal.set(1, 1, 1);
+      }
+
+      ContactInfo() { clear(); }
+
+   } mContactInfo;
+
+protected:
+   F32 mDrag;
+   F32 mBuoyancy;
+   F32 mFriction;
+   F32 mElasticity;
+   F32 mMaxVelocity;
+   bool mSticky;
+
+   bool mFalling;
+   bool mSwimming;
+   bool mInWater;
+
+   S32 mContactTimer;               ///< Ticks since last contact
+
+   U32 mIntegrationCount;
+
+   Point3F mJumpSurfaceNormal;      ///< Normal of the surface the player last jumped on
+
+   F32 maxStepHeight;         ///< Maximum height the player can step up
+   F32 moveSurfaceAngle;      ///< Maximum angle from vertical in degrees the player can run up
+   F32 contactSurfaceAngle;   ///< Maximum angle from vertical in degrees we consider having real 'contact'
+
+   F32 horizMaxSpeed;         ///< Max speed attainable in the horizontal
+   F32 horizMaxAccel;
+   F32 horizResistSpeed;      ///< Speed at which resistance will take place
+   F32 horizResistFactor;     ///< Factor of resistance once horizResistSpeed has been reached
+
+   F32 upMaxSpeed;            ///< Max vertical speed attainable
+   F32 upMaxAccel;
+   F32 upResistSpeed;         ///< Speed at which resistance will take place
+   F32 upResistFactor;        ///< Factor of resistance once upResistSpeed has been reached
+
+   F32 fallingSpeedThreshold; ///< Downward speed at which we consider the player falling
+
+   // Air control
+   F32 airControl;
+
+   Point3F mInputVelocity;
+
+   bool mUseDirectMoveInput;
+
+public:
+   PlayerControllerComponent();
+   virtual ~PlayerControllerComponent();
+   DECLARE_CONOBJECT(PlayerControllerComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+
+   virtual void componentAddedToOwner(Component *comp);
+   virtual void componentRemovedFromOwner(Component *comp);
+
+   virtual void ownerTransformSet(MatrixF *mat);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   void updatePhysics(PhysicsCollision *collision = NULL);
+
+   virtual void processTick();
+   virtual void interpolateTick(F32 dt);
+   virtual void updatePos(const F32 dt);
+   void updateMove();
+
+   virtual VectorF getVelocity() { return mVelocity; }
+   virtual void setVelocity(const VectorF& vel);
+   virtual void setTransform(const MatrixF& mat);
+
+   void findContact(bool *run, bool *jump, VectorF *contactNormal);
+   Point3F getContactNormal() { return mContactInfo.contactNormal; }
+   SceneObject* getContactObject() { return mContactInfo.contactObject; }
+   bool isContacted() { return mContactInfo.contacted; }
+
+   //
+   void applyImpulse(const Point3F &pos, const VectorF &vec);
+
+   //This is a weird artifact of the PhysicsReps. We want the collision component to be privvy to any events that happen
+   //so when the physics components do a findContact test during their update, they'll have a signal collision components
+   //can be listening to to update themselves with that info
+   Signal< void(SceneObject*) > PlayerControllerComponent::onContactSignal;
+
+   //
+   DECLARE_CALLBACK(void, updateMove, (PlayerControllerComponent* obj));
+};
+
+#endif // _COMPONENT_H_

+ 467 - 0
Engine/source/T3D/components/Physics/rigidBodyComponent.cpp

@@ -0,0 +1,467 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "T3D/Components/physics/RigidBodyComponent.h"
+#include "core/util/safeDelete.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "core/stream/bitStream.h"
+#include "console/engineAPI.h"
+#include "sim/netConnection.h"
+#include "T3D/physics/physicsBody.h"
+#include "T3D/physics/physicsPlugin.h"
+#include "T3D/physics/physicsWorld.h"
+#include "T3D/physics/physicsCollision.h"
+#include "T3D/Components/Collision/collisionComponent.h"
+
+bool RigidBodyComponent::smNoCorrections = false;
+bool RigidBodyComponent::smNoSmoothing = false;
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+RigidBodyComponent::RigidBodyComponent() : Component()   
+{
+   mMass = 20;
+   mDynamicFriction = 1;
+   mStaticFriction = 0.1f;
+   mRestitution = 10;
+   mLinearDamping = 0;
+   mAngularDamping = 0;
+   mLinearSleepThreshold = 1;
+   mAngularSleepThreshold = 1;
+   mWaterDampingScale = 0.1f;
+   mBuoyancyDensity = 1;
+
+   mSimType = SimType_ServerOnly;
+
+   mPhysicsRep = NULL;
+   mResetPos = MatrixF::Identity;
+
+   mOwnerColComponent = NULL;
+
+   mFriendlyName = "RigidBody(Component)";
+}
+
+RigidBodyComponent::~RigidBodyComponent()
+{
+}
+
+IMPLEMENT_CO_NETOBJECT_V1(RigidBodyComponent);
+
+bool RigidBodyComponent::onAdd()
+{
+   if(! Parent::onAdd())
+      return false;
+
+   return true;
+}
+
+void RigidBodyComponent::onRemove()
+{
+   Parent::onRemove();
+}
+void RigidBodyComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+}
+
+//This is mostly a catch for situations where the behavior is re-added to the object and the like and we may need to force an update to the behavior
+void RigidBodyComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   if (isServerObject())
+   {
+      storeRestorePos();
+      PhysicsPlugin::getPhysicsResetSignal().notify(this, &RigidBodyComponent::_onPhysicsReset);
+   }
+
+   CollisionComponent *colComp = mOwner->getComponent<CollisionComponent>();
+   if (colComp)
+   {
+      colComp->onCollisionChanged.notify(this, &RigidBodyComponent::updatePhysics);
+      updatePhysics(colComp->getCollisionData());
+   }
+   else
+      updatePhysics();
+}
+
+void RigidBodyComponent::onComponentRemove()
+{
+   Parent::onComponentRemove();
+
+   if (isServerObject())
+   {
+      PhysicsPlugin::getPhysicsResetSignal().remove(this, &RigidBodyComponent::_onPhysicsReset);
+   }
+
+   CollisionComponent *colComp = mOwner->getComponent<CollisionComponent>();
+   if (colComp)
+   {
+      colComp->onCollisionChanged.remove(this, &RigidBodyComponent::updatePhysics);
+   }
+
+   SAFE_DELETE(mPhysicsRep);
+}
+
+void RigidBodyComponent::componentAddedToOwner(Component *comp)
+{
+   CollisionComponent *colComp = dynamic_cast<CollisionComponent*>(comp);
+   if (colComp)
+   {
+      colComp->onCollisionChanged.notify(this, &RigidBodyComponent::updatePhysics);
+      updatePhysics(colComp->getCollisionData());
+   }
+}
+
+void RigidBodyComponent::componentRemovedFromOwner(Component *comp)
+{
+   //test if this is a shape component!
+   CollisionComponent *colComp = dynamic_cast<CollisionComponent*>(comp);
+   if (colComp)
+   {
+      colComp->onCollisionChanged.remove(this, &RigidBodyComponent::updatePhysics);
+      updatePhysics();
+   }
+}
+
+void RigidBodyComponent::ownerTransformSet(MatrixF *mat)
+{
+   if (mPhysicsRep)
+      mPhysicsRep->setTransform(mOwner->getTransform());
+}
+
+void RigidBodyComponent::updatePhysics(PhysicsCollision* collision)
+{
+   SAFE_DELETE(mPhysicsRep);
+
+   if (!PHYSICSMGR)
+      return;
+
+   mWorld = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
+
+   if (!collision)
+      return;
+
+   mPhysicsRep = PHYSICSMGR->createBody();
+
+   mPhysicsRep->init(collision, mMass, 0, mOwner, mWorld);
+
+   mPhysicsRep->setMaterial(mRestitution, mDynamicFriction, mStaticFriction);
+
+   mPhysicsRep->setDamping(mLinearDamping, mAngularDamping);
+   mPhysicsRep->setSleepThreshold(mLinearSleepThreshold, mAngularSleepThreshold);
+
+   mPhysicsRep->setTransform(mOwner->getTransform());
+
+   // The reset position is the transform on the server
+   // at creation time... its not used on the client.
+   if (isServerObject())
+   {
+      storeRestorePos();
+      PhysicsPlugin::getPhysicsResetSignal().notify(this, &RigidBodyComponent::_onPhysicsReset);
+   }
+}
+
+U32 RigidBodyComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if (stream->writeFlag(mask & StateMask))
+   {
+      // This will encode the position relative to the control
+      // object position.  
+      //
+      // This will compress the position to as little as 6.25
+      // bytes if the position is within about 30 meters of the
+      // control object.
+      //
+      // Worst case its a full 12 bytes + 2 bits if the position
+      // is more than 500 meters from the control object.
+      //
+      stream->writeCompressedPoint(mState.position);
+
+      // Use only 3.5 bytes to send the orientation.
+      stream->writeQuat(mState.orientation, 9);
+
+      // If the server object has been set to sleep then
+      // we don't need to send any velocity.
+      if (!stream->writeFlag(mState.sleeping))
+      {
+         // This gives me ~0.015f resolution in velocity magnitude
+         // while only costing me 1 bit of the velocity is zero length,
+         // <5 bytes in normal cases, and <8 bytes if the velocity is
+         // greater than 1000.
+         AssertWarn(mState.linVelocity.len() < 1000.0f,
+            "PhysicsShape::packUpdate - The linVelocity is out of range!");
+         stream->writeVector(mState.linVelocity, 1000.0f, 16, 9);
+
+         // For angular velocity we get < 0.01f resolution in magnitude
+         // with the most common case being under 4 bytes.
+         AssertWarn(mState.angVelocity.len() < 10.0f,
+            "PhysicsShape::packUpdate - The angVelocity is out of range!");
+         stream->writeVector(mState.angVelocity, 10.0f, 10, 9);
+      }
+   }
+
+   return retMask;
+}
+
+void RigidBodyComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if (stream->readFlag()) // StateMask
+   {
+      PhysicsState state;
+
+      // Read the encoded and compressed position... commonly only 6.25 bytes.
+      stream->readCompressedPoint(&state.position);
+
+      // Read the compressed quaternion... 3.5 bytes.
+      stream->readQuat(&state.orientation, 9);
+
+      state.sleeping = stream->readFlag();
+      if (!state.sleeping)
+      {
+         stream->readVector(&state.linVelocity, 1000.0f, 16, 9);
+         stream->readVector(&state.angVelocity, 10.0f, 10, 9);
+      }
+
+      if (!smNoCorrections && mPhysicsRep && mPhysicsRep->isDynamic())
+      {
+         // Set the new state on the physics object immediately.
+         mPhysicsRep->applyCorrection(state.getTransform());
+
+         mPhysicsRep->setSleeping(state.sleeping);
+         if (!state.sleeping)
+         {
+            mPhysicsRep->setLinVelocity(state.linVelocity);
+            mPhysicsRep->setAngVelocity(state.angVelocity);
+         }
+
+         mPhysicsRep->getState(&mState);
+      }
+
+      // If there is no physics object then just set the
+      // new state... the tick will take care of the 
+      // interpolation and extrapolation.
+      if (!mPhysicsRep || !mPhysicsRep->isDynamic())
+         mState = state;
+   }
+}
+
+void RigidBodyComponent::processTick()
+{
+   Parent::processTick();
+
+   if (!mPhysicsRep || !PHYSICSMGR)
+      return;
+
+   // Note that unlike TSStatic, the serverside PhysicsShape does not
+   // need to play the ambient animation because even if the animation were
+   // to move collision shapes it would not affect the physx representation.
+
+   PROFILE_START(RigidBodyComponent_ProcessTick);
+
+   if (!mPhysicsRep->isDynamic())
+      return;
+
+   // SINGLE PLAYER HACK!!!!
+   if (PHYSICSMGR->isSinglePlayer() && isClientObject() && getServerObject())
+   {
+      RigidBodyComponent *servObj = (RigidBodyComponent*)getServerObject();
+      mOwner->setTransform(servObj->mState.getTransform());
+      mRenderState[0] = servObj->mRenderState[0];
+      mRenderState[1] = servObj->mRenderState[1];
+
+      return;
+   }
+
+   // Store the last render state.
+   mRenderState[0] = mRenderState[1];
+
+   // If the last render state doesn't match the last simulation 
+   // state then we got a correction and need to 
+   Point3F errorDelta = mRenderState[1].position - mState.position;
+   const bool doSmoothing = !errorDelta.isZero() && !smNoSmoothing;
+
+   const bool wasSleeping = mState.sleeping;
+
+   // Get the new physics state.
+   mPhysicsRep->getState(&mState);
+   updateContainerForces();
+
+   // Smooth the correction back into the render state.
+   mRenderState[1] = mState;
+   if (doSmoothing)
+   {
+      F32 correction = mClampF(errorDelta.len() / 20.0f, 0.1f, 0.9f);
+      mRenderState[1].position.interpolate(mState.position, mRenderState[0].position, correction);
+      mRenderState[1].orientation.interpolate(mState.orientation, mRenderState[0].orientation, correction);
+   }
+
+   //Check if any collisions occured
+   findContact();
+
+   // If we haven't been sleeping then update our transform
+   // and set ourselves as dirty for the next client update.
+   if (!wasSleeping || !mState.sleeping)
+   {
+      // Set the transform on the parent so that
+      // the physics object isn't moved.
+      mOwner->setTransform(mState.getTransform());
+
+      // If we're doing server simulation then we need
+      // to send the client a state update.
+      if (isServerObject() && mPhysicsRep && !smNoCorrections &&
+         !PHYSICSMGR->isSinglePlayer() // SINGLE PLAYER HACK!!!!
+         )
+         setMaskBits(StateMask);
+   }
+
+   PROFILE_END();
+}
+
+void RigidBodyComponent::findContact()
+{
+   SceneObject *contactObject = NULL;
+
+   VectorF *contactNormal = new VectorF(0, 0, 0);
+
+   Vector<SceneObject*> overlapObjects;
+
+   mPhysicsRep->findContact(&contactObject, contactNormal, &overlapObjects);
+
+   if (!overlapObjects.empty())
+   {
+      //fire our signal that the physics sim said collisions happened
+      onPhysicsCollision.trigger(*contactNormal, overlapObjects);
+   }
+}
+
+void RigidBodyComponent::_onPhysicsReset(PhysicsResetEvent reset)
+{
+   if (reset == PhysicsResetEvent_Store)
+      mResetPos = mOwner->getTransform();
+
+   else if (reset == PhysicsResetEvent_Restore)
+   {
+      mOwner->setTransform(mResetPos);
+   }
+}
+
+void RigidBodyComponent::storeRestorePos()
+{
+   mResetPos = mOwner->getTransform();
+}
+
+void RigidBodyComponent::applyImpulse(const Point3F &pos, const VectorF &vec)
+{
+   if (mPhysicsRep && mPhysicsRep->isDynamic())
+      mPhysicsRep->applyImpulse(pos, vec);
+}
+
+void RigidBodyComponent::applyRadialImpulse(const Point3F &origin, F32 radius, F32 magnitude)
+{
+   if (!mPhysicsRep || !mPhysicsRep->isDynamic())
+      return;
+
+   // TODO: Find a better approximation of the
+   // force vector using the object box.
+
+   VectorF force = mOwner->getWorldBox().getCenter() - origin;
+   F32 dist = force.magnitudeSafe();
+   force.normalize();
+
+   if (dist == 0.0f)
+      force *= magnitude;
+   else
+      force *= mClampF(radius / dist, 0.0f, 1.0f) * magnitude;
+
+   mPhysicsRep->applyImpulse(origin, force);
+
+   // TODO: There is no simple way to really sync this sort of an 
+   // event with the client.
+   //
+   // The best is to send the current physics snapshot, calculate the
+   // time difference from when this event occured and the time when the
+   // client recieves it, and then extrapolate where it should be.
+   //
+   // Even then its impossible to be absolutely sure its synced.
+   //
+   // Bottom line... you shouldn't use physics over the network like this.
+   //
+}
+
+void RigidBodyComponent::updateContainerForces()
+{
+   PROFILE_SCOPE(RigidBodyComponent_updateContainerForces);
+
+   // If we're not simulating don't update forces.
+   PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
+   if (!world || !world->isEnabled())
+      return;
+
+   ContainerQueryInfo info;
+   info.box = mOwner->getWorldBox();
+   info.mass = mMass;
+
+   // Find and retreive physics info from intersecting WaterObject(s)
+   mOwner->getContainer()->findObjects(mOwner->getWorldBox(), WaterObjectType | PhysicalZoneObjectType, findRouter, &info);
+
+   // Calculate buoyancy and drag
+   F32 angDrag = mAngularDamping;
+   F32 linDrag = mLinearDamping;
+   F32 buoyancy = 0.0f;
+   Point3F cmass = mPhysicsRep->getCMassPosition();
+
+   F32 density = mBuoyancyDensity;
+   if (density > 0.0f)
+   {
+      if (info.waterCoverage > 0.0f)
+      {
+         F32 waterDragScale = info.waterViscosity * mWaterDampingScale;
+         F32 powCoverage = mPow(info.waterCoverage, 0.25f);
+
+         angDrag = mLerp(angDrag, angDrag * waterDragScale, powCoverage);
+         linDrag = mLerp(linDrag, linDrag * waterDragScale, powCoverage);
+      }
+
+      buoyancy = (info.waterDensity / density) * mPow(info.waterCoverage, 2.0f);
+
+      // A little hackery to prevent oscillation
+      // Based on this blog post:
+      // (http://reinot.blogspot.com/2005/11/oh-yes-they-float-georgie-they-all.html)
+      // JCF: disabled!
+      Point3F buoyancyForce = buoyancy * -world->getGravity() * TickSec * mMass;
+      mPhysicsRep->applyImpulse(cmass, buoyancyForce);
+   }
+
+   // Update the dampening as the container might have changed.
+   mPhysicsRep->setDamping(linDrag, angDrag);
+
+   // Apply physical zone forces.
+   if (!info.appliedForce.isZero())
+      mPhysicsRep->applyImpulse(cmass, info.appliedForce);
+}

+ 183 - 0
Engine/source/T3D/components/Physics/rigidBodyComponent.h

@@ -0,0 +1,183 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef RIGID_BODY_COMPONENT_H
+#define RIGID_BODY_COMPONENT_H
+
+#ifndef COMPONENT_H
+#include "T3D/Components/Component.h"
+#endif
+#ifndef _T3D_PHYSICSCOMMON_H_
+#include "T3D/physics/physicsCommon.h"
+#endif
+#ifndef COLLISION_COMPONENT_H
+#include "T3D/Components/collision/collisionComponent.h"
+#endif
+#ifndef PHYSICS_COMPONENT_INTERFACE_H
+#include "T3D/Components/physics/physicsComponentInterface.h"
+#endif
+
+class PhysicsBody;
+
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class RigidBodyComponent : public Component, public PhysicsComponentInterface
+{
+   typedef Component Parent;
+
+   enum SimType
+   {
+      /// This physics representation only exists on the client
+      /// world and the server only does ghosting.
+      SimType_ClientOnly,
+
+      /// The physics representation only exists on the server world
+      /// and the client gets delta updates for rendering.
+      SimType_ServerOnly,
+
+      /// The physics representation exists on the client and the server
+      /// worlds with corrections occuring when the client gets out of sync.
+      SimType_ClientServer,
+
+      /// The bits used to pack the SimType field.
+      SimType_Bits = 3,
+
+   } mSimType;
+
+   //
+   //
+   /// The current physics state.
+   PhysicsState mState;
+
+   /// The previous and current render states.
+   PhysicsState mRenderState[2];
+
+   /// The abstracted physics actor.
+   PhysicsBody *mPhysicsRep;
+
+   PhysicsWorld *mWorld;
+
+   /// The starting position to place the shape when
+   /// the level begins or is reset.
+   MatrixF mResetPos;
+   //
+   //
+
+   /// If true then no corrections are sent from the server 
+   /// and/or applied from the client.
+   ///
+   /// This is only ment for debugging.
+   ///
+   static bool smNoCorrections;
+
+   /// If true then no smoothing is done on the client when
+   /// applying server corrections.
+   ///
+   /// This is only ment for debugging.
+   ///
+   static bool smNoSmoothing;
+
+   ///
+   F32 mMass;
+
+   /// 
+   F32 mDynamicFriction;
+
+   /// 
+   F32 mStaticFriction;
+
+   ///
+   F32 mRestitution;
+
+   ///
+   F32 mLinearDamping;
+
+   ///
+   F32 mAngularDamping;
+
+   /// 
+   F32 mLinearSleepThreshold;
+
+   ///
+   F32 mAngularSleepThreshold;
+
+   // A scale applied to the normal linear and angular damping
+   // when the object enters a water volume.
+   F32 mWaterDampingScale;
+
+   // The density of this object used for water buoyancy effects.
+   F32 mBuoyancyDensity;
+
+   CollisionComponent* mOwnerColComponent;
+
+   enum MaskBits {
+      PositionMask = Parent::NextFreeMask << 0,
+      FreezeMask = Parent::NextFreeMask << 1,
+      StateMask = Parent::NextFreeMask << 2,
+      VelocityMask = Parent::NextFreeMask << 3,
+      NextFreeMask = Parent::NextFreeMask << 4
+   };
+
+public:
+   RigidBodyComponent();
+   virtual ~RigidBodyComponent();
+   DECLARE_CONOBJECT(RigidBodyComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void onComponentAdd();
+   virtual void onComponentRemove();
+
+   virtual void componentAddedToOwner(Component *comp);
+   virtual void componentRemovedFromOwner(Component *comp);
+
+   virtual void ownerTransformSet(MatrixF *mat);
+
+   inline F32 getMass() { return mMass; }
+   Point3F getVelocity() const { return mState.linVelocity; }
+   void applyImpulse(const Point3F &pos, const VectorF &vec);
+   void applyRadialImpulse(const Point3F &origin, F32 radius, F32 magnitude);
+
+   void updateContainerForces();
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   virtual void processTick();
+
+   void findContact();
+
+   /// Save the current transform as where we return to when a physics reset
+   /// event occurs. This is automatically set in onAdd but some manipulators
+   /// such as Prefab need to make use of this.
+   void storeRestorePos();
+
+   void updatePhysics(PhysicsCollision *collision = NULL);
+
+   void _onPhysicsReset(PhysicsResetEvent reset);
+};
+
+#endif // _RIGID_BODY_COMPONENT_H_

+ 524 - 0
Engine/source/T3D/components/Render/MeshComponent.cpp

@@ -0,0 +1,524 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+#include "platform/platform.h"
+#include "console/consoleTypes.h"
+#include "T3D/Components/Render/MeshComponent.h"
+#include "core/util/safeDelete.h"
+#include "core/resourceManager.h"
+#include "core/stream/fileStream.h"
+#include "console/consoleTypes.h"
+#include "console/consoleObject.h"
+#include "core/stream/bitStream.h"
+#include "sim/netConnection.h"
+#include "gfx/gfxTransformSaver.h"
+#include "console/engineAPI.h"
+#include "lighting/lightQuery.h"
+#include "scene/sceneManager.h"
+#include "gfx/bitmap/ddsFile.h"
+#include "gfx/bitmap/ddsUtils.h"
+#include "gfx/gfxTextureManager.h"
+#include "materials/materialFeatureTypes.h"
+#include "renderInstance/renderImposterMgr.h"
+#include "util/imposterCapture.h"
+#include "gfx/sim/debugDraw.h"  
+#include "gfx/gfxDrawUtil.h"
+#include "materials/materialManager.h"
+#include "materials/matInstance.h"
+#include "core/strings/findMatch.h"
+#include "T3D/components/Render/MeshComponent_ScriptBinding.h"
+
+//////////////////////////////////////////////////////////////////////////
+// Constructor/Destructor
+//////////////////////////////////////////////////////////////////////////
+MeshComponent::MeshComponent() : Component()
+{
+   mShapeName = StringTable->insert("");
+   mShapeAsset = StringTable->insert("");
+
+   mChangingMaterials.clear();
+
+   mMaterials.clear();
+
+   mFriendlyName = "Mesh Component";
+   mComponentType = "Render";
+
+   mDescription = getDescriptionText("Causes the object to render a non-animating 3d shape using the file provided.");
+
+   mNetworked = true;
+   mNetFlags.set(Ghostable | ScopeAlways);
+}
+
+MeshComponent::~MeshComponent(){}
+
+IMPLEMENT_CO_NETOBJECT_V1(MeshComponent);
+
+//==========================================================================================
+void MeshComponent::boneObject::addObject(SimObject* object)
+{
+   SceneObject* sc = dynamic_cast<SceneObject*>(object);
+
+   if(sc && mOwner)
+   {
+      if(TSShape* shape = mOwner->getShape())
+      {
+         S32 nodeID = shape->findNode(mBoneName);
+
+         //we may have a offset on the shape's center
+         //so make sure we accomodate for that when setting up the mount offsets
+         MatrixF mat = mOwner->getNodeTransform(nodeID);
+
+         mOwner->getOwner()->mountObject(sc, nodeID, mat);
+      }
+   }
+}
+
+bool MeshComponent::onAdd()
+{
+   if(! Parent::onAdd())
+      return false;
+
+   // Register for the resource change signal.
+   ResourceManager::get().getChangedSignal().notify( this, &MeshComponent::_onResourceChanged );
+
+   return true;
+}
+
+void MeshComponent::onComponentAdd()
+{
+   Parent::onComponentAdd();
+
+   //get the default shape, if any
+   updateShape();
+}
+
+void MeshComponent::onRemove()
+{
+   Parent::onRemove();
+
+   SAFE_DELETE(mShapeInstance);
+}
+
+void MeshComponent::onComponentRemove()
+{
+   if(mOwner)
+   {
+      Point3F pos = mOwner->getPosition(); //store our center pos
+      mOwner->setObjectBox(Box3F(Point3F(-1,-1,-1), Point3F(1,1,1)));
+      mOwner->setPosition(pos);
+   }  
+
+   Parent::onComponentRemove();  
+}
+
+void MeshComponent::initPersistFields()
+{
+   Parent::initPersistFields();
+
+   //create a hook to our internal variables
+   addGroup("Model");
+   addProtectedField("MeshAsset", TypeAssetId, Offset(mShapeAsset, MeshComponent), &_setMesh, &defaultProtectedGetFn, 
+      "The asset Id used for the mesh.", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
+   endGroup("Model");
+}
+
+bool MeshComponent::_setMesh(void *object, const char *index, const char *data)
+{
+   MeshComponent *rbI = static_cast<MeshComponent*>(object);
+   
+   // Sanity!
+   AssertFatal(data != NULL, "Cannot use a NULL asset Id.");
+
+   return rbI->setMeshAsset(data);
+}
+
+bool MeshComponent::_setShape( void *object, const char *index, const char *data )
+{
+   MeshComponent *rbI = static_cast<MeshComponent*>(object);
+   rbI->mShapeName = StringTable->insert(data);
+   rbI->updateShape(); //make sure we force the update to resize the owner bounds
+   rbI->setMaskBits(ShapeMask);
+
+   return true;
+}
+
+bool MeshComponent::setMeshAsset(const char* assetName)
+{
+   // Fetch the asset Id.
+   mMeshAssetId = StringTable->insert(assetName);
+   mMeshAsset.setAssetId(mMeshAssetId);
+
+   if (mMeshAsset.isNull())
+   {
+      Con::errorf("[MeshComponent] Failed to load mesh asset.");
+      return false;
+   }
+
+   mShapeName = mMeshAssetId;
+   mShapeAsset = mShapeName;
+   updateShape(); //make sure we force the update to resize the owner bounds
+   setMaskBits(ShapeMask);
+
+   return true;
+}
+
+void MeshComponent::_onResourceChanged( const Torque::Path &path )
+{
+   if ( path != Torque::Path( mShapeName ) )
+      return;
+
+   updateShape();
+   setMaskBits(ShapeMask);
+}
+
+void MeshComponent::inspectPostApply()
+{
+   Parent::inspectPostApply();
+}
+
+U32 MeshComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(con, mask, stream);
+
+   if (!mOwner || con->getGhostIndex(mOwner) == -1)
+   {
+      stream->writeFlag(false);
+      stream->writeFlag(false);
+
+      if (mask & ShapeMask)
+         retMask |= ShapeMask;
+      if (mask & MaterialMask)
+         retMask |= MaterialMask;
+      return retMask;
+   }
+
+   if (stream->writeFlag(mask & ShapeMask))
+   {
+      stream->writeString(mShapeName);
+   }
+
+   if (stream->writeFlag( mask & MaterialMask ))
+   {
+      stream->writeInt(mChangingMaterials.size(), 16);
+
+      for(U32 i=0; i < mChangingMaterials.size(); i++)
+      {
+         stream->writeInt(mChangingMaterials[i].slot, 16);
+         con->packNetStringHandleU(stream, NetStringHandle(mChangingMaterials[i].matName));
+      }
+
+      mChangingMaterials.clear();
+    }
+
+   return retMask;
+}
+
+void MeshComponent::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con, stream);
+
+   if(stream->readFlag())
+   {
+      mShapeName = stream->readSTString();
+      setMeshAsset(mShapeName);
+      updateShape();
+   }
+
+   if(stream->readFlag())
+   {
+      mChangingMaterials.clear();
+      U32 materialCount = stream->readInt(16);
+
+      for(U32 i=0; i < materialCount; i++)
+      {
+         matMap newMatMap;
+         newMatMap.slot = stream->readInt(16);
+         newMatMap.matName = String(con->unpackNetStringHandleU(stream).getString());
+
+         mChangingMaterials.push_back(newMatMap);
+      }
+
+      updateMaterials();
+   }
+}
+
+void MeshComponent::prepRenderImage( SceneRenderState *state )
+{
+   if (!mEnabled || !mOwner || !mShapeInstance)
+      return;
+
+   Point3F cameraOffset;
+   mOwner->getRenderTransform().getColumn(3, &cameraOffset);
+   cameraOffset -= state->getDiffuseCameraPosition();
+   F32 dist = cameraOffset.len();
+   if (dist < 0.01f)
+      dist = 0.01f;
+
+   Point3F objScale = getOwner()->getScale();
+   F32 invScale = (1.0f / getMax(getMax(objScale.x, objScale.y), objScale.z));
+
+   mShapeInstance->setDetailFromDistance(state, dist * invScale);
+
+   if (mShapeInstance->getCurrentDetail() < 0)
+      return;
+
+   GFXTransformSaver saver;
+
+   // Set up our TS render state.
+   TSRenderState rdata;
+   rdata.setSceneState(state);
+   rdata.setFadeOverride(1.0f);
+   rdata.setOriginSort(false);
+
+   // We might have some forward lit materials
+   // so pass down a query to gather lights.
+   LightQuery query;
+   query.init(mOwner->getWorldSphere());
+   rdata.setLightQuery(&query);
+
+   MatrixF mat = mOwner->getRenderTransform();
+   Point3F renderPos = mat.getPosition();
+   EulerF renderRot = mat.toEuler();
+   mat.scale(objScale);
+   GFX->setWorldMatrix(mat);
+
+   mShapeInstance->render(rdata);
+}
+
+void MeshComponent::updateShape()
+{
+   bool isServer = isServerObject();
+
+   if ((mShapeName && mShapeName[0] != '\0') || (mShapeAsset && mShapeAsset[0] != '\0'))
+   {
+      if (mMeshAsset == NULL)
+         return;
+
+      mShape = mMeshAsset->getShape();
+
+      if (!mShape)
+         return;
+
+      setupShape();
+
+      //Do this on both the server and client
+      S32 materialCount = mShape->materialList->getMaterialNameList().size();
+
+      if(isServerObject())
+      {
+         //we need to update the editor
+         for (U32 i = 0; i < mFields.size(); i++)
+         {
+            //find any with the materialslot title and clear them out
+            if (FindMatch::isMatch("MaterialSlot*", mFields[i].mFieldName, false))
+            {
+               setDataField(mFields[i].mFieldName, NULL, "");
+               mFields.erase(i);
+               continue;
+            }
+         }
+
+         //next, get a listing of our materials in the shape, and build our field list for them
+         char matFieldName[128];
+
+         if(materialCount > 0)
+            mComponentGroup = StringTable->insert("Materials");
+
+         for(U32 i=0; i < materialCount; i++)
+         {
+            String materialname = mShape->materialList->getMaterialName(i);
+            if(materialname == String("ShapeBounds"))
+               continue;
+
+            dSprintf(matFieldName, 128, "MaterialSlot%d", i);
+            
+            addComponentField(matFieldName, "A material used in the shape file", "TypeAssetId", materialname, "");
+         }
+
+         if(materialCount > 0)
+            mComponentGroup = "";
+      }
+
+      if(mOwner != NULL)
+      {
+         Point3F min, max, pos;
+         pos = mOwner->getPosition();
+
+         mOwner->getWorldToObj().mulP(pos);
+
+         min = mShape->bounds.minExtents;
+         max = mShape->bounds.maxExtents;
+
+         mShapeBounds.set(min, max);
+
+         mOwner->setObjectBox(Box3F(min, max));
+
+         if( mOwner->getSceneManager() != NULL )
+            mOwner->getSceneManager()->notifyObjectDirty( mOwner );
+      }
+
+      //finally, notify that our shape was changed
+      onShapeInstanceChanged.trigger(this);
+   }
+}
+
+void MeshComponent::setupShape()
+{
+   mShapeInstance = new TSShapeInstance(mShape, true);
+}
+
+void MeshComponent::updateMaterials()
+{
+   if (mChangingMaterials.empty() || !mShape)
+      return;
+
+   TSMaterialList* pMatList = mShapeInstance->getMaterialList();
+   pMatList->setTextureLookupPath(getShapeResource().getPath().getPath());
+
+   const Vector<String> &materialNames = pMatList->getMaterialNameList();
+   for ( S32 i = 0; i < materialNames.size(); i++ )
+   {
+      const String &pName = materialNames[i];
+
+      for(U32 m=0; m < mChangingMaterials.size(); m++)
+      {
+         if(mChangingMaterials[m].slot == i)
+         {
+            pMatList->renameMaterial( i, mChangingMaterials[m].matName );
+         }
+      }
+
+      mChangingMaterials.clear();
+   }
+
+   // Initialize the material instances
+   mShapeInstance->initMaterialList();
+}
+
+MatrixF MeshComponent::getNodeTransform(S32 nodeIdx)
+{
+   if (mShape)
+   {
+      S32 nodeCount = getShape()->nodes.size();
+
+      if(nodeIdx >= 0 && nodeIdx < nodeCount)
+      {
+         //animate();
+         MatrixF mountTransform = mShapeInstance->mNodeTransforms[nodeIdx];
+         mountTransform.mul(mOwner->getRenderTransform());
+
+         return mountTransform;
+      }
+   }
+
+   return MatrixF::Identity;
+}
+
+S32 MeshComponent::getNodeByName(String nodeName)
+{
+   if (mShape)
+   {
+      S32 nodeIdx = getShape()->findNode(nodeName);
+
+      return nodeIdx;
+   }
+
+   return -1;
+}
+
+bool MeshComponent::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info)
+{
+   return false;
+}
+
+void MeshComponent::mountObjectToNode(SceneObject* objB, String node, MatrixF txfm)
+{
+   const char* test;
+   test = node.c_str();
+   if(dIsdigit(test[0]))
+   {
+      getOwner()->mountObject(objB, dAtoi(node), txfm);
+   }
+   else
+   {
+      if(TSShape* shape = getShape())
+      {
+         S32 idx = shape->findNode(node);
+         getOwner()->mountObject(objB, idx, txfm);
+      }
+   }
+}
+
+void MeshComponent::onDynamicModified(const char* slotName, const char* newValue)
+{
+   if(FindMatch::isMatch( "materialslot*", slotName, false ))
+   {
+      if(!getShape())
+         return;
+
+      S32 slot = -1;
+      String outStr( String::GetTrailingNumber( slotName, slot ) );
+
+      if(slot == -1)
+         return;
+
+      bool found = false;
+      for(U32 i=0; i < mChangingMaterials.size(); i++)
+      {
+         if(mChangingMaterials[i].slot == slot)
+         {
+            mChangingMaterials[i].matName = String(newValue);
+            found = true;
+         }
+      }
+
+      if(!found)
+      {
+         matMap newMatMap;
+         newMatMap.slot = slot;
+         newMatMap.matName = String(newValue);
+
+         mChangingMaterials.push_back(newMatMap);
+      }
+
+      setMaskBits(MaterialMask);
+   }
+
+   Parent::onDynamicModified(slotName, newValue);
+}
+
+void MeshComponent::changeMaterial(U32 slot, const char* newMat)
+{
+   
+   char fieldName[512];
+
+   //update our respective field
+   dSprintf(fieldName, 512, "materialSlot%d", slot);
+   setDataField(fieldName, NULL, newMat);
+}
+
+void MeshComponent::onInspect()
+{
+}
+
+void MeshComponent::onEndInspect()
+{
+}

+ 183 - 0
Engine/source/T3D/components/Render/MeshComponent.h

@@ -0,0 +1,183 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef STATIC_MESH_COMPONENT_H
+#define STATIC_MESH_COMPONENT_H
+
+#ifndef COMPONENT_H
+#include "T3D/Components/Component.h"
+#endif
+#ifndef __RESOURCE_H__
+#include "core/resource.h"
+#endif
+#ifndef _TSSHAPE_H_
+#include "ts/tsShape.h"
+#endif
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+#ifndef _MBOX_H_
+#include "math/mBox.h"
+#endif
+#ifndef ENTITY_H
+#include "T3D/Entity.h"
+#endif
+#ifndef _NETSTRINGTABLE_H_
+   #include "sim/netStringTable.h"
+#endif
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+#ifndef RENDER_COMPONENT_INTERFACE_H
+#include "T3D/Components/Render/renderComponentInterface.h"
+#endif
+#ifndef _ASSET_PTR_H_
+#include "assets/assetPtr.h"
+#endif 
+#ifndef _SHAPE_ASSET_H_
+#include "T3D/assets/ShapeAsset.h"
+#endif 
+#ifndef _GFXVERTEXFORMAT_H_
+#include "gfx/gfxVertexFormat.h"
+#endif
+
+class TSShapeInstance;
+class SceneRenderState;
+//////////////////////////////////////////////////////////////////////////
+/// 
+/// 
+//////////////////////////////////////////////////////////////////////////
+class MeshComponent : public Component,
+   public RenderComponentInterface,
+   public CastRayRenderedInterface,
+   public EditorInspectInterface
+{
+   typedef Component Parent;
+
+protected:
+   enum
+   {
+      ShapeMask = Parent::NextFreeMask,
+      MaterialMask = Parent::NextFreeMask << 1,
+      NextFreeMask = Parent::NextFreeMask << 2,
+   };
+
+   StringTableEntry		mShapeName;
+   StringTableEntry		mShapeAsset;
+   TSShape*		         mShape;
+   Box3F						mShapeBounds;
+   Point3F					mCenterOffset;
+
+   struct matMap
+   {
+      String matName;
+      U32 slot;
+   };
+
+   Vector<matMap>  mChangingMaterials;
+   Vector<matMap>  mMaterials;
+
+   class boneObject : public SimGroup
+   {
+      MeshComponent *mOwner;
+   public:
+      boneObject(MeshComponent *owner){ mOwner = owner; }
+
+      StringTableEntry mBoneName;
+      S32 mItemID;
+
+      virtual void addObject(SimObject *obj);
+   };
+
+   Vector<boneObject*> mNodesList;
+
+public:
+   StringTableEntry       mMeshAssetId;
+   AssetPtr<ShapeAsset>   mMeshAsset;
+
+   TSShapeInstance*       mShapeInstance;
+
+public:
+   MeshComponent();
+   virtual ~MeshComponent();
+   DECLARE_CONOBJECT(MeshComponent);
+
+   virtual bool onAdd();
+   virtual void onRemove();
+   static void initPersistFields();
+
+   virtual void inspectPostApply();
+
+   virtual void prepRenderImage(SceneRenderState *state);
+
+   virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream);
+   virtual void unpackUpdate(NetConnection *con, BitStream *stream);
+
+   Box3F getShapeBounds() { return mShapeBounds; }
+
+   virtual MatrixF getNodeTransform(S32 nodeIdx);
+   S32 getNodeByName(String nodeName);
+
+   void setupShape();
+   void updateShape();
+   void updateMaterials();
+
+   virtual void onComponentRemove();
+   virtual void onComponentAdd();
+
+   static bool _setMesh(void *object, const char *index, const char *data);
+   static bool _setShape(void *object, const char *index, const char *data);
+   const char* _getShape(void *object, const char *data);
+
+   bool setMeshAsset(const char* assetName);
+
+   virtual TSShape* getShape() { if (mMeshAsset)  return mMeshAsset->getShape(); else return NULL; }
+   virtual TSShapeInstance* getShapeInstance() { return mShapeInstance; }
+
+   Resource<TSShape> getShapeResource() { if (mMeshAsset)  return mMeshAsset->getShapeResource(); else return NULL; }
+
+   void _onResourceChanged(const Torque::Path &path);
+
+   virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info);
+
+   void mountObjectToNode(SceneObject* objB, String node, MatrixF txfm);
+
+   virtual void onDynamicModified(const char* slotName, const char* newValue);
+
+   void changeMaterial(U32 slot, const char* newMat);
+
+   virtual void onInspect();
+   virtual void onEndInspect();
+
+   virtual Vector<MatrixF> getNodeTransforms()
+   {
+      Vector<MatrixF> bob;
+      return bob;
+   }
+
+   virtual void setNodeTransforms(Vector<MatrixF> transforms)
+   {
+      return;
+   }
+};
+
+#endif

+ 155 - 0
Engine/source/T3D/components/Render/MeshComponent_ScriptBinding.h

@@ -0,0 +1,155 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/engineAPI.h"
+#include "T3D/components/Render/MeshComponent.h"
+#include "scene/sceneObject.h"
+#include "math/mTransform.h"
+
+DefineEngineMethod(MeshComponent, getShapeBounds, Box3F, (), ,
+   "@brief Get the cobject we're in contact with.\n\n"
+
+   "The controlling client is the one that will send moves to us to act on.\n"
+
+   "@return the ID of the controlling GameConnection, or 0 if this object is not "
+   "controlled by any client.\n"
+
+   "@see GameConnection\n")
+{
+   return object->getShapeBounds();
+}
+
+DefineEngineMethod(MeshComponent, mountObject, bool,
+   (SceneObject* objB, String node, TransformF txfm), (MatrixF::Identity),
+   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+   "@param objB  Object to mount onto us\n"
+   "@param slot  Mount slot ID\n"
+   "@param txfm (optional) mount offset transform\n"
+   "@return true if successful, false if failed (objB is not valid)")
+{
+   if (objB)
+   {
+      //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
+      //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
+      object->mountObjectToNode(objB, node, /*MatrixF::Identity*/txfm.getMatrix());
+      return true;
+   }
+   return false;
+}
+
+DefineEngineMethod(MeshComponent, getNodeTransform, TransformF,
+   (S32 node), (-1),
+   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+   "@param objB  Object to mount onto us\n"
+   "@param slot  Mount slot ID\n"
+   "@param txfm (optional) mount offset transform\n"
+   "@return true if successful, false if failed (objB is not valid)")
+{
+   if (node != -1)
+   {
+      //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
+      //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
+      //object->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
+      MatrixF mat = object->getNodeTransform(node);
+      return mat;
+   }
+
+   return TransformF::Identity;
+}
+
+DefineEngineMethod(MeshComponent, getNodeEulerRot, EulerF,
+   (S32 node, bool radToDeg), (-1, true),
+   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+   "@param objB  Object to mount onto us\n"
+   "@param slot  Mount slot ID\n"
+   "@param txfm (optional) mount offset transform\n"
+   "@return true if successful, false if failed (objB is not valid)")
+{
+   if (node != -1)
+   {
+      //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
+      //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
+      //object->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
+      MatrixF mat = object->getNodeTransform(node);
+
+      EulerF eul = mat.toEuler();
+      if (radToDeg)
+         eul = EulerF(mRadToDeg(eul.x), mRadToDeg(eul.y), mRadToDeg(eul.z));
+
+      return eul;
+   }
+
+   return EulerF(0, 0, 0);
+}
+
+DefineEngineMethod(MeshComponent, getNodePosition, Point3F,
+   (S32 node), (-1),
+   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+   "@param objB  Object to mount onto us\n"
+   "@param slot  Mount slot ID\n"
+   "@param txfm (optional) mount offset transform\n"
+   "@return true if successful, false if failed (objB is not valid)")
+{
+   if (node != -1)
+   {
+      //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
+      //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
+      //object->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
+      MatrixF mat = object->getNodeTransform(node);
+
+      return mat.getPosition();
+   }
+
+   return Point3F(0, 0, 0);
+}
+
+DefineEngineMethod(MeshComponent, getNodeByName, S32,
+   (String nodeName), ,
+   "@brief Mount objB to this object at the desired slot with optional transform.\n\n"
+
+   "@param objB  Object to mount onto us\n"
+   "@param slot  Mount slot ID\n"
+   "@param txfm (optional) mount offset transform\n"
+   "@return true if successful, false if failed (objB is not valid)")
+{
+   if (!nodeName.isEmpty())
+   {
+      //BUG: Unsure how it broke, but atm the default transform passed in here is rotated 180 degrees. This doesn't happen
+      //for the SceneObject mountobject method. Hackish, but for now, just default to a clean MatrixF::Identity
+      //object->mountObjectToNode( objB, node, /*MatrixF::Identity*/txfm.getMatrix() );
+      S32 node = object->getNodeByName(nodeName);
+
+      return node;
+   }
+
+   return -1;
+}
+
+DefineEngineMethod(MeshComponent, changeMaterial, void, (U32 slot, const char* newMat), (0, ""),
+   "@brief Change one of the materials on the shape.\n\n")
+{
+   object->changeMaterial(slot, newMat);
+}

+ 62 - 0
Engine/source/T3D/components/Render/renderComponentInterface.h

@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef RENDER_COMPONENT_INTERFACE_H
+#define RENDER_COMPONENT_INTERFACE_H
+
+#ifndef _TSSHAPE_H_
+#include "ts/TSShape.h"
+#endif
+#ifndef _TSSHAPEINSTANCE_H_
+#include "ts/TSShapeInstance.h"
+#endif
+#ifndef CORE_INTERFACES_H
+#include "T3D/Components/coreInterfaces.h"
+#endif
+
+class RenderComponentInterface : public Interface < RenderComponentInterface >
+{
+public:
+   virtual void prepRenderImage(SceneRenderState *state) = 0;
+
+   virtual TSShape* getShape() = 0;
+
+   Signal< void(RenderComponentInterface*) > RenderComponentInterface::onShapeChanged;
+
+   virtual TSShapeInstance* getShapeInstance() = 0;
+
+   virtual MatrixF getNodeTransform(S32 nodeIdx) = 0;
+
+   virtual Vector<MatrixF> getNodeTransforms() = 0;
+
+   virtual void setNodeTransforms(Vector<MatrixF> transforms) = 0;
+
+   Signal< void(RenderComponentInterface*) > RenderComponentInterface::onShapeInstanceChanged;
+};
+
+class CastRayRenderedInterface// : public Interface<CastRayRenderedInterface>
+{
+public:
+   virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info)=0;
+};
+
+#endif

+ 101 - 0
Engine/source/T3D/components/coreInterfaces.h

@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+#ifndef CORE_INTERFACES_H
+#define CORE_INTERFACES_H
+
+#ifndef _SCENERENDERSTATE_H_
+#include "scene/sceneRenderState.h"
+#endif
+
+template<typename T>
+class Interface
+{
+public:
+   static Vector<T*> all;
+
+   Interface()
+   {
+      all.push_back((T*)this);
+   }
+   virtual ~Interface()
+   {
+      for (U32 i = 0; i < all.size(); i++)
+      {
+         if (all[i] == (T*)this)
+         {
+            all.erase(i);
+            return;
+         }
+      }
+   }
+};
+template<typename T> Vector<T*> Interface<T>::all(0);
+
+//Basically a file for generic interfaces that many behaviors may make use of
+class SetTransformInterface// : public Interface<SetTransformInterface>
+{
+public:
+   virtual void setTransform( MatrixF transform );
+   virtual void setTransform( Point3F pos, EulerF rot );
+};
+
+class UpdateInterface : public Interface<UpdateInterface>
+{
+public:
+   virtual void processTick(){}
+   virtual void interpolateTick(F32 dt){}
+   virtual void advanceTime(F32 dt){}
+};
+
+class BehaviorFieldInterface// : public Interface<BehaviorFieldInterface>
+{
+public:
+   virtual void onFieldChange(const char* fieldName, const char* newValue){};
+};
+
+class CameraInterface// : public Interface<CameraInterface>
+{
+public:
+   virtual bool getCameraTransform(F32* pos,MatrixF* mat)=0;
+   virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)=0;
+   virtual Frustum getFrustum()=0;
+   virtual F32 getCameraFov()=0;
+   virtual void setCameraFov(F32 fov)=0;
+
+   virtual bool isValidCameraFov(F32 fov)=0;
+};
+
+class CastRayInterface// : public Interface<CastRayInterface>
+{
+public:
+   virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info)=0;
+};
+
+class EditorInspectInterface// : public Interface<EditorInspectInterface>
+{
+public:
+   virtual void onInspect()=0;
+   virtual void onEndInspect()=0;
+};
+
+#endif

+ 2 - 0
Engine/source/console/consoleObject.h

@@ -473,6 +473,8 @@ public:
    enum FieldFlags
    {
       FIELD_HideInInspectors     = BIT( 0 ),    ///< Do not show the field in inspectors.
+      FIELD_ComponentInspectors = BIT(1),       ///< Custom fields used by components. They are likely to be non-standard size/configuration, so 
+                                                ///< They are handled specially
    };
 
    struct Field