浏览代码

truly a community project, this has been kicking around since 2003 in various forms. adds a path following shape that can be ridden.

AzaezelX 5 年之前
父节点
当前提交
f7f8faf47e

+ 50 - 2
Engine/source/T3D/gameBase/gameBase.cpp

@@ -404,6 +404,12 @@ void GameBase::processTick(const Move * move)
 #endif
 }
 
+void GameBase::interpolateTick(F32 dt)
+{
+   // PATHSHAPE
+   updateRenderChangesByParent();
+   // PATHSHAPE END
+}
 //----------------------------------------------------------------------------
 
 F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
@@ -471,14 +477,15 @@ F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 u
    // Weight by updateSkips
    F32 wSkips = updateSkips * 0.5;
 
-   // Calculate final priority, should total to about 1.0f
+   // Calculate final priority, should total to about 1.0f (plus children)
    //
    return
       wFov       * sUpFov +
       wDistance  * sUpDistance +
       wVelocity  * sUpVelocity +
       wSkips     * sUpSkips +
-      wInterest  * sUpInterest;
+      wInterest  * sUpInterest +
+	  getNumChildren();
 }
 
 //----------------------------------------------------------------------------
@@ -757,3 +764,44 @@ DefineEngineMethod( GameBase, applyRadialImpulse, void, ( Point3F origin, F32 ra
 {
    object->applyRadialImpulse( origin, radius, magnitude );
 }
+
+// PATHSHAPE
+// Console Methods for attach children. can't put them in sceneobject because  //
+// we want the processafter functions////////////////////////////////////////////
+
+DefineEngineMethod(GameBase, attachChild, bool, (GameBase* _subObject), (nullAsType<GameBase*>()), "(SceneObject subObject)"
+              "attach an object to this one, preserving its present transform.")
+{
+    if (_subObject != nullptr)
+    {              
+		if (_subObject->getParent() != object){
+			Con::errorf("Object is (%d)", _subObject->getId());
+         _subObject->clearProcessAfter();
+         _subObject->processAfter(object);
+			return object->attachChild(_subObject);
+      }
+      else
+         return false;
+   }
+   else 
+   {
+      Con::errorf("Couldn't addObject()!");
+      return false;
+   }
+}
+
+
+DefineEngineMethod(GameBase, detachChild, bool, (GameBase* _subObject), (nullAsType<GameBase*>()), "(SceneObject subObject)"
+              "attach an object to this one, preserving its present transform.")
+{
+   if (_subObject != nullptr)
+	{
+      _subObject->clearProcessAfter();
+		return _subObject->attachToParent(NULL);
+	}
+	else
+	{
+		return false;
+	}
+}//end
+// PATHSHAPE END

+ 3 - 0
Engine/source/T3D/gameFunctions.cpp

@@ -665,6 +665,9 @@ static void RegisterGameFunctions()
    Con::setIntVariable("$TypeMasks::DebrisObjectType",         DebrisObjectType);
    Con::setIntVariable("$TypeMasks::PhysicalZoneObjectType",   PhysicalZoneObjectType);
    Con::setIntVariable("$TypeMasks::LightObjectType",          LightObjectType);
+// PATHSHAPE
+   Con::setIntVariable("$TypeMasks::PathShapeObjectType",     PathShapeObjectType);
+// PATHSHAPE END
 
    Con::addVariable("Ease::InOut", TypeS32, &gEaseInOut, 
       "InOut ease for curve movement.\n"

+ 3 - 0
Engine/source/T3D/item.cpp

@@ -618,6 +618,9 @@ void Item::interpolateTick(F32 dt)
    mat.setColumn(3,pos);
    setRenderTransform(mat);
    mDelta.dt = dt;
+// PATHSHAPE
+   updateRenderChangesByParent();
+// PATHSHAPE END
 }
 
 

+ 6 - 0
Engine/source/T3D/objectTypes.h

@@ -162,6 +162,12 @@ enum SceneObjectTypes
 #if defined(AFX_CAP_AFXMODEL_TYPE) 
    afxModelObjectType = BIT(26)
 #endif 
+
+   // PATHSHAPE 
+   PathShapeObjectType = BIT( 28 ),
+   // PATHSHAPE END
+
+   /// @}
 };
 
 enum SceneObjectTypeMasks : U32

+ 588 - 0
Engine/source/T3D/pathShape.cpp

@@ -0,0 +1,588 @@
+//-----------------------------------------------------------------------------
+// Torque Game Engine 
+// Copyright (C) GarageGames.com, Inc.
+// @author Stefan "Beffy" Moises
+// this is a modified version of PathCamera that allows to move shapes along paths
+//-----------------------------------------------------------------------------
+
+#include "platform/platform.h"
+#include "math/mMath.h"
+#include "math/mathIO.h"
+#include "console/simBase.h"
+#include "console/console.h"
+#include "console/consoleTypes.h"
+#include "core/stream/bitStream.h"
+#include "core/dnet.h"
+#include "scene/pathManager.h"
+#include "T3D/gameFunctions.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "gui/worldEditor/editor.h"
+#include "console/engineAPI.h"
+#include "math/mTransform.h"
+
+#include "T3D/pathShape.h"
+
+
+//----------------------------------------------------------------------------
+
+IMPLEMENT_CO_DATABLOCK_V1(PathShapeData);
+
+void PathShapeData::consoleInit()
+{
+}
+
+
+bool PathShapeData::preload(bool server, String &errorStr)
+{
+
+   if(!Parent::preload(server, errorStr))
+      return false;
+
+	return true;
+}
+
+
+void PathShapeData::initPersistFields()
+{
+   Parent::initPersistFields();
+}
+
+void PathShapeData::packData(BitStream* stream)
+{
+   Parent::packData(stream);
+}
+
+void PathShapeData::unpackData(BitStream* stream)
+{
+   Parent::unpackData(stream);
+}
+
+
+//----------------------------------------------------------------------------
+
+IMPLEMENT_CO_NETOBJECT_V1(PathShape);
+
+PathShape::PathShape()
+{
+   mNetFlags.set(Ghostable|ScopeAlways);
+   mTypeMask |= PathShapeObjectType | StaticShapeObjectType;
+   delta.time = 0;
+   delta.timeVec = 0;
+   mDataBlock = NULL;
+   mState = Forward;
+   mNodeBase = 0;
+   mNodeCount = 0;
+   mPosition = 0;
+   mTarget = 0;
+   mTargetSet = false;
+
+   MatrixF mat(1);
+   mat.setPosition(Point3F(0,0,700));
+   Parent::setTransform(mat);
+   
+   mLastXform = mat; 
+   for (U32 i = 0; i < 4; i++)
+   {
+      mControl[i] = StringTable->insert("");
+   }
+}
+
+PathShape::~PathShape()
+{
+}
+
+
+//----------------------------------------------------------------------------
+
+bool PathShape::onAdd()
+{
+   if(!Parent::onAdd() && !mDataBlock)
+      return false;
+
+   mTypeMask |= PathShapeObjectType | StaticShapeObjectType;
+   // Initialize from the current transform.
+   if (!mNodeCount) {
+      QuatF rot(getTransform());
+      Point3F pos = getPosition();
+      mSpline.removeAll();
+      mSpline.push_back(new CameraSpline::Knot(pos,rot,1,
+         CameraSpline::Knot::NORMAL, CameraSpline::Knot::SPLINE));
+      mNodeCount = 1;
+   }
+
+   if (isServerObject()) scriptOnAdd();
+   return true;
+
+}
+
+void PathShape::onRemove()
+{
+	scriptOnRemove();
+	removeFromScene();
+
+	 unmount();
+    Parent::onRemove();
+
+	   if (isGhost())
+      for (S32 i = 0; i < MaxSoundThreads; i++)
+         stopAudio(i);
+}
+
+bool PathShape::onNewDataBlock(GameBaseData* dptr, bool reload)
+{
+   mDataBlock = dynamic_cast<PathShapeData*>(dptr);
+   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
+      return false;
+
+   scriptOnNewDataBlock();
+   return true;
+}
+
+PathShapeData::PathShapeData()
+{
+
+}
+
+//----------------------------------------------------------------------------
+
+void PathShape::initPersistFields()
+{
+
+   addField( "Path", TYPEID< SimObjectRef<SimPath::Path> >(), Offset( mSimPath, PathShape ),
+         "@brief Name of a Path to follow." );
+
+   addField("Controler", TypeString, Offset(mControl, PathShape), 4, "controlers");
+
+   Parent::initPersistFields();
+
+}
+
+void PathShape::consoleInit()
+{
+}
+
+
+//----------------------------------------------------------------------------
+
+void PathShape::processTick(const Move* move)
+{
+   // client and server
+   Parent::processTick(move);
+   
+   // Move to new time
+   advancePosition(TickMs);
+
+	MatrixF mat;
+    interpolateMat(mPosition,&mat);
+	Parent::setTransform(mat);
+
+	updateContainer();
+}
+
+void PathShape::interpolateTick(F32 dt)          
+{
+   Parent::interpolateTick(dt);
+   MatrixF mat;
+   interpolateMat(delta.time + (delta.timeVec * dt),&mat);
+   Parent::setRenderTransform(mat);
+}
+
+void PathShape::interpolateMat(F32 pos,MatrixF* mat)
+{
+   CameraSpline::Knot knot;
+   mSpline.value(pos - mNodeBase,&knot);
+   knot.mRotation.setMatrix(mat);
+   mat->setPosition(knot.mPosition);
+}
+
+void PathShape::advancePosition(S32 ms)
+{
+   delta.timeVec = mPosition;
+
+   // Advance according to current speed
+   if (mState == Forward) {
+      mPosition = mSpline.advanceTime(mPosition - mNodeBase,ms);
+      if (mPosition > F32(mNodeCount - 1))
+         mPosition = F32(mNodeCount - 1);
+      mPosition += (F32)mNodeBase;
+      if (mTargetSet && mPosition >= mTarget) {
+         mTargetSet = false;
+         mPosition = mTarget;
+         mState = Stop;
+      }
+   }
+   else
+      if (mState == Backward) {
+         mPosition = mSpline.advanceTime(mPosition - mNodeBase,-ms);
+         if (mPosition < 0)
+            mPosition = 0;
+         mPosition += mNodeBase;
+         if (mTargetSet && mPosition <= mTarget) {
+            mTargetSet = false;
+            mPosition = mTarget;
+            mState = Stop;
+         }
+      }
+
+   // Script callbacks
+   if (int(mPosition) != int(delta.timeVec))
+      onNode(int(mPosition));
+
+   // Set frame interpolation
+   delta.time = mPosition;
+   delta.timeVec -= mPosition;
+}
+
+
+//----------------------------------------------------------------------------
+
+
+void PathShape::setPosition(F32 pos)
+{
+   mPosition = mClampF(pos,mNodeBase,mNodeBase + mNodeCount - 1);
+   MatrixF mat;
+   interpolateMat(mPosition,&mat);
+   Parent::setTransform(mat);
+   setMaskBits(PositionMask);
+}
+
+void PathShape::setTarget(F32 pos)
+{
+   mTarget = pos;
+   mTargetSet = true;
+   if (mTarget > mPosition)
+      mState = Forward;
+   else
+      if (mTarget < mPosition)
+         mState = Backward;
+      else {
+         mTargetSet = false;
+         mState = Stop;
+      }
+   setMaskBits(TargetMask | StateMask);
+}
+
+void PathShape::setState(State s)
+{
+   mState = s;
+   setMaskBits(StateMask);
+}
+
+S32 PathShape::getState()
+{
+   return mState;
+}
+
+
+//-----------------------------------------------------------------------------
+
+void PathShape::reset(F32 speed)
+{
+   CameraSpline::Knot *knot = new CameraSpline::Knot;
+   mSpline.value(mPosition - mNodeBase,knot);
+   if (speed)
+      knot->mSpeed = speed;
+   mSpline.removeAll();
+   mSpline.push_back(knot);
+
+   mNodeBase = 0;
+   mNodeCount = 1;
+   mPosition = 0;
+   mTargetSet = false;
+   mState = Forward;
+   setMaskBits(StateMask | PositionMask | WindowMask | TargetMask);
+}
+
+void PathShape::pushBack(CameraSpline::Knot *knot)
+{
+   // Make room at the end
+   if (mNodeCount == NodeWindow) {
+      delete mSpline.remove(mSpline.getKnot(0));
+      mNodeBase++;
+   }
+   else
+      mNodeCount++;
+
+   // Fill in the new node
+   mSpline.push_back(knot);
+   setMaskBits(WindowMask);
+
+   // Make sure the position doesn't fall off
+   if (mPosition < mNodeBase) {
+      mPosition = mNodeBase;
+      setMaskBits(PositionMask);
+   }
+}
+
+void PathShape::pushFront(CameraSpline::Knot *knot)
+{
+   // Make room at the front
+   if (mNodeCount == NodeWindow)
+      delete mSpline.remove(mSpline.getKnot(mNodeCount));
+   else
+      mNodeCount++;
+   mNodeBase--;
+
+   // Fill in the new node
+   mSpline.push_front(knot);
+   setMaskBits(WindowMask);
+
+   // Make sure the position doesn't fall off
+   if (mPosition > mNodeBase + (NodeWindow - 1)) {
+      mPosition = mNodeBase + (NodeWindow - 1);
+      setMaskBits(PositionMask);
+   }
+}
+
+void PathShape::popFront()
+{
+   if (mNodeCount < 2)
+      return;
+
+   // Remove the first node. Node base and position are unaffected.
+   mNodeCount--;
+   delete mSpline.remove(mSpline.getKnot(0));
+}
+
+
+//----------------------------------------------------------------------------
+
+void PathShape::onNode(S32 node)
+{
+   if (!isGhost())
+      Con::executef(mDataBlock,"onNode",getIdString(), Con::getIntArg(node));
+}
+
+
+//----------------------------------------------------------------------------
+
+
+
+//----------------------------------------------------------------------------
+
+U32 PathShape::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
+{
+
+   Parent::packUpdate(con,mask,stream);
+
+   if (stream->writeFlag(mask & StateMask))
+      stream->writeInt(mState,StateBits);
+
+   if (stream->writeFlag(mask & PositionMask))
+      stream->write(mPosition);
+
+   if (stream->writeFlag(mask & TargetMask))
+      if (stream->writeFlag(mTargetSet))
+         stream->write(mTarget);
+
+   if (stream->writeFlag(mask & WindowMask)) {
+      stream->write(mNodeBase);
+      stream->write(mNodeCount);
+      for (S32 i = 0; i < mNodeCount; i++) {
+         CameraSpline::Knot *knot = mSpline.getKnot(i);
+         mathWrite(*stream, knot->mPosition);
+         mathWrite(*stream, knot->mRotation);
+         stream->write(knot->mSpeed);
+         stream->writeInt(knot->mType, CameraSpline::Knot::NUM_TYPE_BITS);
+         stream->writeInt(knot->mPath, CameraSpline::Knot::NUM_PATH_BITS);
+      }
+   }
+
+   // The rest of the data is part of the control object packet update.
+   // If we're controlled by this client, we don't need to send it.
+   if(stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
+      return 0;
+
+   return 0;   
+}
+
+void PathShape::unpackUpdate(NetConnection *con, BitStream *stream)
+{
+   Parent::unpackUpdate(con,stream);   
+
+   // StateMask
+   if (stream->readFlag())
+      mState = stream->readInt(StateBits);
+
+   // PositionMask 
+   if (stream->readFlag()) {
+      stream->read(&mPosition);
+      delta.time = mPosition;
+      delta.timeVec = 0;
+   }
+   
+   // TargetMask
+   if (stream->readFlag()) { 
+		mTargetSet = stream->readFlag();
+		if (mTargetSet) {
+		   stream->read(&mTarget);
+		}
+	}
+
+   // WindowMask
+   if (stream->readFlag()) {
+      mSpline.removeAll();
+      stream->read(&mNodeBase);
+      stream->read(&mNodeCount);
+      for (S32 i = 0; i < mNodeCount; i++) {
+         CameraSpline::Knot *knot = new CameraSpline::Knot();
+         mathRead(*stream, &knot->mPosition);
+         mathRead(*stream, &knot->mRotation);
+         stream->read(&knot->mSpeed);
+         knot->mType = (CameraSpline::Knot::Type)stream->readInt(CameraSpline::Knot::NUM_TYPE_BITS);
+         knot->mPath = (CameraSpline::Knot::Path)stream->readInt(CameraSpline::Knot::NUM_PATH_BITS);
+         mSpline.push_back(knot);
+      }
+   }
+
+   // Controlled by the client?
+   if (stream->readFlag()) return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Console access methods
+//-----------------------------------------------------------------------------
+DefineEngineMethod(PathShape, setPosition, void, (F32 position), (0.0f), "Set the current position of the camera along the path.\n"
+	"@param position Position along the path, from 0.0 (path start) - 1.0 (path end), to place the camera.\n"
+	"@tsexample\n"
+	"// Set the camera on a position along its path from 0.0 - 1.0.\n"
+	"%position = \"0.35\";\n\n"
+	"// Force the pathCamera to its new position along the path.\n"
+	"%pathCamera.setPosition(%position);\n"
+	"@endtsexample\n")
+{
+	object->setPosition(position);
+}
+
+DefineEngineMethod(PathShape, setTarget, void, (F32 position), (1.0f), "@brief Set the movement target for this camera along its path.\n\n"
+	"The camera will attempt to move along the path to the given target in the direction provided "
+	"by setState() (the default is forwards).  Once the camera moves past this target it will come "
+	"to a stop, and the target state will be cleared.\n"
+	"@param position Target position, between 0.0 (path start) and 1.0 (path end), for the camera to move to along its path.\n"
+	"@tsexample\n"
+	"// Set the position target, between 0.0 (path start) and 1.0 (path end), for this camera to move to.\n"
+	"%position = \"0.50\";\n\n"
+	"// Inform the pathCamera of the new target position it will move to.\n"
+	"%pathCamera.setTarget(%position);\n"
+	"@endtsexample\n")
+{
+	object->setTarget(position);
+}
+
+DefineEngineMethod(PathShape, setState, void, (const char* newState), ("forward"), "Set the movement state for this path camera.\n"
+	"@param newState New movement state type for this camera. Forward, Backward or Stop.\n"
+	"@tsexample\n"
+	"// Set the state type (forward, backward, stop).\n"
+	"// In this example, the camera will travel from the first node\n"
+	"// to the last node (or target if given with setTarget())\n"
+	"%state = \"forward\";\n\n"
+	"// Inform the pathCamera to change its movement state to the defined value.\n"
+	"%pathCamera.setState(%state);\n"
+	"@endtsexample\n")
+{
+	if (!dStricmp(newState, "forward"))
+		object->setState(PathShape::Forward);
+	else
+		if (!dStricmp(newState, "backward"))
+			object->setState(PathShape::Backward);
+		else
+			object->setState(PathShape::Stop);
+}
+
+DefineEngineMethod(PathShape, reset, void, (F32 speed), (1.0f), "@brief Clear the camera's path and set the camera's current transform as the start of the new path.\n\n"
+	"What specifically occurs is a new knot is created from the camera's current transform.  Then the current path "
+	"is cleared and the new knot is pushed onto the path.  Any previous target is cleared and the camera's movement "
+	"state is set to Forward.  The camera is now ready for a new path to be defined.\n"
+	"@param speed Speed for the camera to move along its path after being reset.\n"
+	"@tsexample\n"
+	"//Determine the new movement speed of this camera. If not set, the speed will default to 1.0.\n"
+	"%speed = \"0.50\";\n\n"
+	"// Inform the path camera to start a new path at"
+	"// the camera's current position, and set the new "
+	"// path's speed value.\n"
+	"%pathCamera.reset(%speed);\n"
+	"@endtsexample\n")
+{
+	object->reset(speed);
+}
+
+static CameraSpline::Knot::Type resolveKnotType(const char *arg)
+{
+   if (dStricmp(arg, "Position Only") == 0) 
+      return CameraSpline::Knot::POSITION_ONLY;
+   if (dStricmp(arg, "Kink") == 0) 
+      return CameraSpline::Knot::KINK;
+   return CameraSpline::Knot::NORMAL;
+}
+
+static CameraSpline::Knot::Path resolveKnotPath(const char *arg)
+{
+   if (!dStricmp(arg, "Linear"))
+      return CameraSpline::Knot::LINEAR;
+   return CameraSpline::Knot::SPLINE;
+}
+
+DefineEngineMethod(PathShape, pushBack, void, (TransformF transform, F32 speed, const char* type, const char* path),
+   (TransformF::Identity, 1.0f, "Normal", "Linear"),
+   "@brief Adds a new knot to the back of a path camera's path.\n"
+   "@param transform Transform for the new knot.  In the form of \"x y z ax ay az aa\" such as returned by SceneObject::getTransform()\n"
+   "@param speed Speed setting for this knot.\n"
+   "@param type Knot type (Normal, Position Only, Kink).\n"
+   "@param path %Path type (Linear, Spline).\n"
+   "@tsexample\n"
+   "// Transform vector for new knot. (Pos_X Pos_Y Pos_Z Rot_X Rot_Y Rot_Z Angle)\n"
+   "%transform = \"15.0 5.0 5.0 1.4 1.0 0.2 1.0\"\n\n"
+   "// Speed setting for knot.\n"
+   "%speed = \"1.0\"\n\n"
+   "// Knot type. (Normal, Position Only, Kink)\n"
+   "%type = \"Normal\";\n\n"
+   "// Path Type. (Linear, Spline)\n"
+   "%path = \"Linear\";\n\n"
+   "// Inform the path camera to add a new knot to the back of its path\n"
+   "%pathCamera.pushBack(%transform,%speed,%type,%path);\n"
+   "@endtsexample\n")
+{
+   QuatF rot(transform.getOrientation());
+
+   object->pushBack(new CameraSpline::Knot(transform.getPosition(), rot, speed, resolveKnotType(type), resolveKnotPath(path)));
+}
+
+DefineEngineMethod(PathShape, pushFront, void, (TransformF transform, F32 speed, const char* type, const char* path),
+   (1.0f, "Normal", "Linear"),
+   "@brief Adds a new knot to the front of a path camera's path.\n"
+   "@param transform Transform for the new knot. In the form of \"x y z ax ay az aa\" such as returned by SceneObject::getTransform()\n"
+   "@param speed Speed setting for this knot.\n"
+   "@param type Knot type (Normal, Position Only, Kink).\n"
+   "@param path %Path type (Linear, Spline).\n"
+   "@tsexample\n"
+   "// Transform vector for new knot. (Pos_X,Pos_Y,Pos_Z,Rot_X,Rot_Y,Rot_Z,Angle)\n"
+   "%transform = \"15.0 5.0 5.0 1.4 1.0 0.2 1.0\"\n\n"
+   "// Speed setting for knot.\n"
+   "%speed = \"1.0\";\n\n"
+   "// Knot type. (Normal, Position Only, Kink)\n"
+   "%type = \"Normal\";\n\n"
+   "// Path Type. (Linear, Spline)\n"
+   "%path = \"Linear\";\n\n"
+   "// Inform the path camera to add a new knot to the front of its path\n"
+   "%pathCamera.pushFront(%transform, %speed, %type, %path);\n"
+   "@endtsexample\n")
+{
+   QuatF rot(transform.getOrientation());
+
+   object->pushFront(new CameraSpline::Knot(transform.getPosition(), rot, speed, resolveKnotType(type), resolveKnotPath(path)));
+}
+
+DefineEngineMethod(PathShape, popFront, void, (), , "Removes the knot at the front of the camera's path.\n"
+   "@tsexample\n"
+   "// Remove the first knot in the camera's path.\n"
+   "%pathCamera.popFront();\n"
+   "@endtsexample\n")
+{
+   object->popFront();
+}
+
+DefineEngineMethod(PathShape, getState, S32, (), , "PathShape.getState()")
+{
+	return object->getState(); 
+}

+ 114 - 0
Engine/source/T3D/pathShape.h

@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------------
+// Torque Game Engine
+// Copyright (C) GarageGames.com, Inc.
+//-----------------------------------------------------------------------------
+
+#ifndef _PATHSHAPE_H_
+#define _PATHSHAPE_H_
+
+#ifndef _STATICSHAPE_H_
+#include "T3D/staticShape.h"
+#endif
+
+#ifndef _CAMERASPLINE_H_
+#include "T3D/cameraSpline.h"
+#endif
+
+#ifndef _SIMPATH_H_
+#include "scene/simPath.h"
+#endif
+//----------------------------------------------------------------------------
+struct PathShapeData: public StaticShapeData {
+   typedef StaticShapeData Parent;
+
+
+   PathShapeData();
+   static void consoleInit();
+
+   DECLARE_CONOBJECT(PathShapeData);
+   bool preload(bool server, String &errorStr);
+   static void initPersistFields();
+   virtual void packData(BitStream* stream);
+   virtual void unpackData(BitStream* stream);
+};
+
+
+//----------------------------------------------------------------------------
+class PathShape: public StaticShape
+{
+public:
+   enum State {
+      Forward,
+      Backward,
+      Stop,
+      StateBits = 3
+   };
+
+private:
+   typedef StaticShape Parent;
+
+     enum MaskBits {
+      WindowMask     = Parent::NextFreeMask,
+      PositionMask   = WindowMask << 1,
+      TargetMask     = PositionMask << 1,
+      StateMask      = TargetMask << 1,
+      NextFreeMask   = StateMask << 1
+
+	  };
+
+   struct StateDelta {
+      F32 time;
+      F32 timeVec;
+   };
+   StateDelta delta;
+
+   enum Constants {
+      NodeWindow = 20    // Maximum number of active nodes
+   };
+
+   PathShapeData* mDataBlock;
+   CameraSpline mSpline;
+   S32 mNodeBase;
+   S32 mNodeCount;
+   F32 mPosition;
+   S32 mState;
+   F32 mTarget;
+   bool mTargetSet;
+   void interpolateMat(F32 pos,MatrixF* mat);
+   void advancePosition(S32 ms);
+
+public:
+   DECLARE_CONOBJECT(PathShape);
+
+   PathShape();
+   ~PathShape();
+
+   StringTableEntry mControl[4];
+
+   static void initPersistFields();
+   static void consoleInit();
+   bool onAdd();
+   void onRemove();
+   bool onNewDataBlock(GameBaseData* dptr, bool reload);
+   void onNode(S32 node);
+
+   void processTick(const Move*);
+   void interpolateTick(F32 dt);
+
+   U32  packUpdate(NetConnection *, U32 mask, BitStream *stream);
+   void unpackUpdate(NetConnection *, BitStream *stream);
+
+   void reset(F32 speed = 1);
+   void pushFront(CameraSpline::Knot *knot);
+   void pushBack(CameraSpline::Knot *knot);
+   void popFront();
+
+   void setPosition(F32 pos);
+   void setTarget(F32 pos);
+   void setState(State s);
+   S32 getState();
+   SimObjectRef< SimPath::Path >  mSimPath;
+};
+
+
+#endif

+ 53 - 2
Engine/source/T3D/player.cpp

@@ -120,7 +120,10 @@ static U32 sCollisionMoveMask =  TerrainObjectType       |
                                  PlayerObjectType        |
                                  StaticShapeObjectType   | 
                                  VehicleObjectType       |
-                                 PhysicalZoneObjectType;
+								 PhysicalZoneObjectType  |
+// PATHSHAPE
+								 PathShapeObjectType;
+// PATHSHAPE END
 
 static U32 sServerCollisionContactMask = sCollisionMoveMask |
                                          ItemObjectType     |
@@ -2206,6 +2209,9 @@ void Player::processTick(const Move* move)
          }
       }
    }
+// PATHSHAPE
+   if (!isGhost()) updateAttachment(); 
+// PATHSHAPE END
 }
 
 void Player::interpolateTick(F32 dt)
@@ -2239,6 +2245,9 @@ void Player::interpolateTick(F32 dt)
 
    updateLookAnimation(dt);
    mDelta.dt = dt;
+// PATHSHAPE
+   updateRenderChangesByParent();
+// PATHSHAPE END
 }
 
 void Player::advanceTime(F32 dt)
@@ -3658,7 +3667,10 @@ void Player::updateDeathOffsets()
 
 //----------------------------------------------------------------------------
 
-static const U32 sPlayerConformMask =  StaticShapeObjectType | StaticObjectType | TerrainObjectType;
+// PATHSHAPE
+static const U32 sPlayerConformMask =  StaticShapeObjectType | StaticObjectType |
+                                       TerrainObjectType | PathShapeObjectType;
+// PATHSHAPE END
 
 static void accel(F32& from, F32 to, F32 rate)
 {
@@ -4780,6 +4792,45 @@ bool Player::step(Point3F *pos,F32 *maxStep,F32 time)
    return false;
 }
 
+// PATHSHAPE
+// This Function does a ray cast down to see if a pathshape object is below
+// If so, it will attempt to attach to it.
+void Player::updateAttachment(){
+   Point3F rot, pos;
+    RayInfo rInfo;
+    MatrixF mat = getTransform();
+    mat.getColumn(3, &pos);
+    if (gServerContainer.castRay(Point3F(pos.x, pos.y, pos.z + 0.1f),
+        Point3F(pos.x, pos.y, pos.z - 1.0f ),
+        PathShapeObjectType, &rInfo))
+    {
+       if( rInfo.object->getTypeMask() & PathShapeObjectType) //Ramen
+       {
+          if (getParent() == NULL)
+          { // ONLY do this if we are not parented
+             //Con::printf("I'm on a pathshape object. Going to attempt attachment.");
+             ShapeBase* col = static_cast<ShapeBase*>(rInfo.object);
+             if (!isGhost())
+             {
+                this->attachToParent(col);
+             } 
+          }
+       }
+       else
+       {
+          //Con::printf("object %i",rInfo.object->getId());
+       }
+    }
+    else
+    {	 
+       if (getParent() !=NULL)
+       {
+          clearProcessAfter();
+          attachToParent(NULL);
+       }
+    }
+}
+// PATHSHAPE END
 
 //----------------------------------------------------------------------------
 inline Point3F createInterpPos(const Point3F& s, const Point3F& e, const F32 t, const F32 d)

+ 3 - 0
Engine/source/T3D/player.h

@@ -614,6 +614,9 @@ protected:
    void _handleCollision( const Collision &collision );
    virtual bool updatePos(const F32 travelTime = TickSec);
 
+   // PATHSHAPE
+   void updateAttachment();
+   // PATHSHAPE END
    ///Update head animation
    void updateLookAnimation(F32 dT = 0.f);
 

+ 8 - 0
Engine/source/T3D/staticShape.cpp

@@ -246,6 +246,14 @@ void StaticShape::processTick(const Move* move)
    }
 }
 
+void StaticShape::interpolateTick(F32 delta)
+{
+   Parent::interpolateTick(delta);
+   // PATHSHAPE
+   updateRenderChangesByParent();
+   // PATHSHAPE END
+}
+
 void StaticShape::setTransform(const MatrixF& mat)
 {
    Parent::setTransform(mat);

+ 1 - 0
Engine/source/T3D/staticShape.h

@@ -84,6 +84,7 @@ public:
    bool onNewDataBlock(GameBaseData *dptr, bool reload);
 
    void processTick(const Move *move);
+   void interpolateTick(F32 delta);
    void setTransform(const MatrixF &mat);
 
    U32  packUpdate  (NetConnection *conn, U32 mask, BitStream *stream);

+ 437 - 0
Engine/source/scene/sceneObject.cpp

@@ -149,6 +149,13 @@ SceneObject::SceneObject()
    mSceneObjectLinks = NULL;
 
    mObjectFlags.set( RenderEnabledFlag | SelectionEnabledFlag );
+// PATHSHAPE
+   // init the scenegraph relationships to indicate no parent, no children, and no siblings
+   mGraph.parent = NULL; 
+   mGraph.nextSibling = NULL;
+   mGraph.firstChild = NULL;
+   mGraph.objToParent.identity();
+// PATHSHAPE END
    mIsScopeAlways = false;
 
    mAccuTex = NULL;
@@ -330,6 +337,9 @@ void SceneObject::onRemove()
    plUnlink();
 
    Parent::onRemove();
+// PATHSHAPE
+   if ( getParent() != NULL)   attachToParent( NULL);
+// PATHSHAPE END
 }
 
 //-----------------------------------------------------------------------------
@@ -402,6 +412,9 @@ void SceneObject::setTransform( const MatrixF& mat )
 #endif
 
    PROFILE_SCOPE( SceneObject_setTransform );
+// PATHSHAPE
+   PerformUpdatesForChildren(mat);
+// PATHSHAPE END
 
    // Update the transforms.
 
@@ -876,6 +889,36 @@ U32 SceneObject::packUpdate( NetConnection* conn, U32 mask, BitStream* stream )
    if ( stream->writeFlag( mask & FlagMask ) )
       stream->writeRangedU32( (U32)mObjectFlags, 0, getObjectFlagMax() );
 
+   // PATHSHAPE
+   //Begin attachment
+   retMask = 0; //retry mask
+
+   if (stream->writeFlag(getParent() != NULL))   {
+      stream->writeAffineTransform(mGraph.objToParent);
+   }
+   if (stream->writeFlag(mask & MountedMask))
+   {
+      // Check to see if we need to write an object ID      
+      if (stream->writeFlag(mGraph.parent))      {
+         S32 t = conn->getGhostIndex(mGraph.parent);
+         // Check to see if we can actually ghost this...         
+         if (t == -1)         {
+            // Cant, try again later            
+            retMask |= MountedMask;
+            stream->writeFlag(false);
+         }
+         else {
+            // Can, write it.            
+            stream->writeFlag(true);
+            stream->writeRangedU32(U32(t), 0, NetConnection::MaxGhostCount);
+            stream->writeAffineTransform(mGraph.objToParent);
+            //Con::errorf("%d: sent mounted on %d", getId(), mGraph.parent->getId());         
+         }
+      }
+   }
+   // End of Attachment   
+   // PATHSHAPE END
+
    if ( mask & MountedMask ) 
    {                  
       if ( mMount.object ) 
@@ -915,6 +958,44 @@ void SceneObject::unpackUpdate( NetConnection* conn, BitStream* stream )
    if ( stream->readFlag() )      
       mObjectFlags = stream->readRangedU32( 0, getObjectFlagMax() );
 
+   // PATHSHAPE  
+   // begin of attachment
+   if (stream->readFlag())
+   {
+      MatrixF m;
+      stream->readAffineTransform(&m);
+      mGraph.objToParent = m;
+   }
+   if (stream->readFlag())
+   {
+      // Check to see if we need to read an object ID      
+      if (stream->readFlag())
+      {
+         // Check to see if we can actually ghost this...         
+         if (stream->readFlag())
+         {
+            GameBase *newParent = static_cast<GameBase*>(conn->resolveGhost(stream->readRangedU32(0, NetConnection::MaxGhostCount)));
+            MatrixF m;
+            stream->readAffineTransform(&m);
+
+            if (getParent() != newParent)
+            {
+               clearProcessAfter();
+               processAfter(newParent);
+            }
+
+            attachToParent(newParent, &m);
+            //Con::errorf("%d: got mounted on %d", getId(), mParentObject->getId());         
+         }
+      }
+      else
+      {
+         attachToParent(NULL);
+      }
+   }
+   // End of attachment
+   // PATHSHAPE END
+
    // MountedMask
    if ( stream->readFlag() ) 
    {
@@ -1479,6 +1560,9 @@ DefineEngineMethod( SceneObject, setTransform, void, ( TransformF txfm ),,
    "Set the object's transform (orientation and position)."
    "@param txfm object transform to set" )
 {
+// PATHSHAPE
+   object->PerformUpdatesForChildren(txfm.getMatrix());
+// PATHSHAPE END
    if ( !txfm.hasRotation() )
       object->setPosition( txfm.getPosition() );
    else
@@ -1552,3 +1636,356 @@ DefineEngineMethod(SceneObject, setForwardVector, void, (VectorF newForward, Vec
 {
    object->setForwardVector(newForward, upVector);
 }
+
+// PATHSHAPE
+// Move RenderTransform by set amount
+// no longer used
+
+void SceneObject::moveRender(const Point3F &delta) 
+{
+   Point3F pos;
+
+   const MatrixF& tmat = getRenderTransform();
+   tmat.getColumn(3,&pos);
+   AngAxisF aa(tmat);
+   pos += delta;
+
+   MatrixF mat;
+   aa.setMatrix(&mat);
+   mat.setColumn(3,pos);
+   setRenderTransform(mat);
+}
+
+void SceneObject::PerformUpdatesForChildren(MatrixF mat){
+	    UpdateXformChange(mat);
+		for (U32 i=0; i < getNumChildren(); i++) {
+			SceneObject *o = getChild(i);
+			o->updateChildTransform(); //update the position of the child object
+		}
+}
+
+
+
+
+
+// This function will move the players based on how much it's
+// parent have moved
+void SceneObject::updateChildTransform(){
+	if (getParent() != NULL){
+		MatrixF one;
+		MatrixF two;
+		MatrixF three;
+		MatrixF four;
+		MatrixF mat;
+		one= getTransform();
+		two = getParent()->getTransform();
+		one.affineInverse();
+		four.mul(two,one);
+		mat.mul(getParent()->mLastXform,getTransform());
+		setTransform(mat);
+	}
+}
+
+// This function will move the rendered image based on how much it's
+// parent have moved since the processtick.
+// For some reason the player object must be updated via it's GetRenderTransform seen below,
+// Other objects seem to require getTransform() only
+void SceneObject::updateRenderChangesByParent(){
+   if (getParent() != NULL){
+		MatrixF renderXform = getParent()->getRenderTransform();
+		MatrixF xform = getParent()->getTransform();
+		xform.affineInverse();
+
+		MatrixF offset;
+		offset.mul(renderXform, xform);
+
+   	    MatrixF mat;
+		
+		//add the "offset" caused by the parents change, and add it to it's own
+		// This is needed by objects that update their own render transform thru interpolate tick
+		// Mostly for stationary objects.
+
+		if (getClassName() == "Player")
+			mat.mul(offset,getRenderTransform());  
+		else										
+			mat.mul(offset,getTransform());	 
+			setRenderTransform(mat);
+	}
+}
+
+
+
+
+
+//Ramen - Move Transform by set amount
+//written by  Anthony Lovell
+void SceneObject::move(F32 x, F32 y, F32 z) 
+{
+    Point3F delta;
+    delta.x = x;
+    delta.y = y;
+    delta.z = z;
+    move(delta);
+}
+// move by a specified delta in root coordinate space
+void SceneObject::move(const Point3F &delta) 
+{
+   Point3F pos;
+
+   const MatrixF& tmat = getTransform();
+   tmat.getColumn(3,&pos);
+   AngAxisF aa(tmat);
+ 
+   pos += delta;
+
+   MatrixF mat;
+   aa.setMatrix(&mat);
+   mat.setColumn(3,pos);
+   setTransform(mat);
+}
+
+
+
+//written by  Anthony Lovell ----------------------------------------------------------
+U32
+SceneObject::getNumChildren() const
+{   
+    U32 num = 0;
+    for (SceneObject *cur = mGraph.firstChild; cur; cur = cur->mGraph.nextSibling)
+        num++;
+    return num;
+}
+//written by  Anthony Lovell ----------------------------------------------------------
+SceneObject *
+SceneObject::getChild(U32 index) const
+{       
+    SceneObject *cur = mGraph.firstChild;
+    for (U32 i = 0; 
+        cur && i < index; 
+        i++)
+        cur = cur->mGraph.nextSibling;
+    return cur;
+}
+
+
+
+
+void SceneObject::UpdateXformChange(const MatrixF &mat){
+// This function gets the difference between the Transform and current Render transform
+// Used for Interpolation matching with the child objects who rely on this data.
+
+	MatrixF oldxform = getTransform();
+
+	oldxform.affineInverse();
+	mLastXform.mul(mat,oldxform);
+
+}
+
+
+//----------------------------------------------------------
+bool
+SceneObject::attachChildAt(SceneObject *subObject, MatrixF atThisOffset, S32 node)
+{   
+    AssertFatal(subObject, "attaching a null subObject");
+    AssertFatal(!isChildOf(subObject), "cyclic attachChild()");
+    bool b = subObject->attachToParent(this, &atThisOffset, node);    
+    if (!b) 
+        return false;
+    
+    return true;
+}
+
+//----------------------------------------------------------
+bool
+SceneObject::attachChildAt(SceneObject *subObject, Point3F atThisPosition)
+{   
+    AssertFatal(subObject, "attaching a null subObject");
+    AssertFatal(!isChildOf(subObject), "cyclic attachChild()");
+    bool b = subObject->attachToParent(this);
+    if (!b) 
+        return false;
+        
+    subObject->mGraph.objToParent.setColumn(3, atThisPosition);
+//    calcTransformFromLocalTransform();
+
+    return true;
+}
+
+//----------------------------------------------------------
+bool
+SceneObject::attachChild(SceneObject *child)
+{   
+	AssertFatal(child, "attaching a null subObject");
+    AssertFatal(!isChildOf(child), "cyclic attachChild()");
+	
+    return  child->attachToParent(this);        
+}
+
+
+//----------------------------------------------------------
+/// returns a count of children plus their children, recursively
+U32
+SceneObject::getNumProgeny() const
+{   
+    U32 num = 0;
+    for (SceneObject *cur = mGraph.firstChild; cur; cur = cur->mGraph.nextSibling) {
+        num += 1 + cur->getNumProgeny();
+    }
+    return num;
+}
+
+DefineEngineMethod(SceneObject, getNumChildren, S32, (),, "returns number of direct child objects")
+{
+    return object->getNumChildren();
+}
+
+DefineEngineMethod(SceneObject, getNumProgeny, S32, (),, "returns number of recursively-nested child objects")
+{
+    return object->getNumProgeny();
+}
+
+DefineEngineMethod(SceneObject, getChild, S32, (S32 _index), (0), "getChild(S32 index) -- returns child SceneObject at given index")
+{
+    SceneObject *s = object->getChild(_index);
+    return s ? s->getId() : 0;
+}
+
+DefineEngineMethod(SceneObject, attachChildAt, bool, (SceneObject* _subObject, MatrixF _offset, S32 _node), (nullAsType<SceneObject*>(), MatrixF::Identity, 0), "(SceneObject subObject, MatrixF offset, S32 offset)"
+              "Mount object to this one with the specified offset expressed in our coordinate space.")
+{   
+   if (_subObject != nullptr)
+   {
+      return object->attachChildAt(_subObject, _offset, _node);
+   }
+   else
+   {
+      Con::errorf("Couldn't addObject()!");
+      return false;
+   }
+}
+
+DefineEngineMethod(SceneObject, attachToParent, bool, (const char*_sceneObject), ,"attachToParent(SceneObject)"
+              "specify a null or non-null parent")
+{   
+    SceneObject * t;   
+    
+    if(Sim::findObject(_sceneObject, t))
+    {
+        return object->attachToParent(t);
+    }
+    else
+    {      
+        if ((!dStrcmp("0", _sceneObject))|| (!dStrcmp("", _sceneObject)))
+            return object->attachToParent(NULL);
+        else
+        {
+            Con::errorf("Couldn't setParent()!");   
+            return false;
+        }
+    }
+}
+
+DefineEngineMethod(SceneObject, getParent, S32, (),, "returns ID of parent SceneObject")
+{
+    SceneObject *p = object->getParent();
+    return p ? p->getId() : -1;
+}
+
+DefineEngineMethod(SceneObject, attachChild, bool, (const char*_subObject),, "(SceneObject subObject)"
+              "attach an object to this one, preserving its present transform.")
+{   
+    SceneObject * t;   
+    MatrixF m;   
+    if(Sim::findObject(_subObject, t)) 
+        return object->attachChild(t);
+
+    Con::errorf("Couldn't addObject()!");
+    return false;
+}
+
+bool SceneObject::isChildOf(SceneObject *so)
+{    
+    SceneObject *p = mGraph.parent;
+    if (p) {
+        if (p == so) 
+            return true;
+        else
+            return p->isChildOf(so);
+    } else
+        return false;
+}
+
+
+
+bool SceneObject::attachToParent(SceneObject *newParent, MatrixF *atThisOffset/* = NULL */, S32 node )
+{   
+	SceneObject *oldParent = mGraph.parent;
+
+    if (oldParent == newParent)
+        return true;
+
+    // cycles in the scene hierarchy are forbidden!
+    // that is:  a SceneObject cannot be a child of its progeny
+    if (newParent && newParent->isChildOf(this))
+        return false;
+      
+    mGraph.parent = newParent;
+
+    if (oldParent) {
+
+        clearNotify(oldParent); 
+
+        // remove this SceneObject from the list of children of oldParent
+        SceneObject *cur = oldParent->mGraph.firstChild;
+        if (cur == this) { // if we are the first child, this is easy
+            oldParent->mGraph.firstChild = mGraph.nextSibling;
+        } else {
+            while (cur->mGraph.nextSibling != this) {
+                cur = cur->mGraph.nextSibling;
+                // ASSERT cur != NULL;
+            }
+            cur->mGraph.nextSibling = mGraph.nextSibling;
+        }
+        oldParent->onLostChild(this);
+    }
+     
+    if (newParent) {
+
+        deleteNotify(newParent); // if we are deleted, inform our parent
+
+        // add this SceneObject to the list of children of oldParent
+        mGraph.nextSibling = newParent->mGraph.firstChild;
+        newParent->mGraph.firstChild = this;
+        mGraph.parent = newParent;
+        
+        newParent->onNewChild(this);
+
+        if (atThisOffset)
+            mGraph.objToParent = *atThisOffset;
+    } else {
+        mGraph.parent = NULL;
+        mGraph.nextSibling = NULL;
+        mGraph.objToParent = mObjToWorld;
+    }
+
+    onLostParent(oldParent);
+    onNewParent(newParent);
+
+    setMaskBits(MountedMask);
+    return true;
+}
+
+DefineEngineMethod(SceneObject, detachChild, bool, (const char*_subObject),, "SceneObject subObject")
+{   
+    SceneObject * t;       
+    if(Sim::findObject(_subObject, t))   {
+        return t->attachToParent(NULL);
+    } else 
+        return false;  
+}
+
+// subclasses can do something with these if they care to
+void SceneObject::onNewParent(SceneObject *newParent) {}  
+void SceneObject::onLostParent(SceneObject *oldParent){}    
+void SceneObject::onNewChild(SceneObject *newKid){}   
+void SceneObject::onLostChild(SceneObject *lostKid){}

+ 102 - 0
Engine/source/scene/sceneObject.h

@@ -293,6 +293,10 @@ class SceneObject : public NetObject, private SceneContainer::Link, public Proce
       /// @name Transform and Collision Members
       /// @{
 
+      // PATHSHAPE
+      MatrixF mLastXform;
+      // PATHSHAPE END
+
       /// Transform from object space to world space.
       MatrixF mObjToWorld;
 
@@ -819,6 +823,27 @@ class SceneObject : public NetObject, private SceneContainer::Link, public Proce
       static bool _setAccuEnabled( void *object, const char *index, const char *data );
 
       /// @}
+// PATHSHAPE
+   /// @}
+   //Anthony's Original Code, still used so i keep it here
+   /// TGE uses the term "mount" in a quirky, staticky way relating to its limited use to have
+   /// riders and guns mounted on a vehicle (and similar)
+   /// I did not alter that code at all (yet) and did not want to keep its terminology for other reasons
+   /// I decided to support a hierarchy of scene objects and dubbed the operations 
+   /// attaching and removing child SceneObjects
+  protected:
+
+      // this member struct tracks the relationship to parent and children
+      // sceneObjects in a hierarchical scene graph whose root is the entire Scene
+   struct AttachInfo {
+      SceneObject* firstChild;  ///< Objects mounted on this object
+      SimObjectPtr<SceneObject>  parent; ///< Object this object is mounted on.
+      SceneObject* nextSibling;        ///< Link to next child object of this object's parent 
+      MatrixF      objToParent;   ///< this obects transformation in the parent object's space
+      MatrixF      RenderobjToParent;   ///< this obects Render Offset transformation to the parent object
+   } mGraph;
+// PATHSHAPE END
+
 
    // Accumulation Texture
    // Note: This was placed in SceneObject to both ShapeBase and TSStatic could support it.
@@ -841,6 +866,83 @@ class SceneObject : public NetObject, private SceneContainer::Link, public Proce
       //   as opposed to something like a Player that has a built-in camera that requires
       //   special calculations to determine the view transform.
       virtual bool isCamera() const { return false; }
+      // AFX CODE BLOCK (is-camera) >>
+// PATHSHAPE
+// Added for dynamic attaching
+	void UpdateXformChange(const MatrixF &mat);
+      /// this is useful for setting NULL parent (making SceneObject a root object)
+   virtual bool attachToParent(SceneObject *parent, MatrixF *atThisOffset = NULL, S32 node=0);
+   SceneObject *getParent() { return mGraph.parent; };
+
+   
+   /// attach a subobject, but do not alter the subObject's present absolute position or orientation
+   bool attachChild(SceneObject* subObject);   
+   /// attach a subobject, at the specified offset expressed in our local coordinate space
+   bool attachChildAt(SceneObject* subObject, MatrixF atThisTransform, S32 node);   
+
+   /// attach a subobject, at the specified position expressed in our local coordinate space
+   bool attachChildAt(SceneObject* subObject, Point3F atThisPosition);   
+   
+   /// how many child SceneObjects are (directly) attached to this one?
+   U32 getNumChildren() const;
+
+   /// how many child objects does this SceneObject have when we count them recursively?
+   U32 getNumProgeny() const;
+
+   /// returns the (direct) child SceneObject at the given index (0 <= index <= getNumChildren() - 1)
+   SceneObject *getChild(U32 index) const; 
+   
+   /// is this SceneObject a child (directly or indirectly) of the given object?
+   bool isChildOf(SceneObject *);
+
+   /// set position in parent SceneObject's coordinate space (or in world space if no parent)
+   //void setLocalPosition(const Point3F &pos);
+
+   /// move the object in parent SceneObject's coordinate space (or in world space if no parent)
+   //void localMove(const Point3F &delta);
+   /// as localMove(const Point3F &delta), with different signature
+   //void localMove(F32 x, F32 y, F32 z);
+   
+   /// move the object in world space, without altering place in scene hierarchy
+   void move(const Point3F &delta);
+   
+   // Does checks for children objects and updates their positions
+   void PerformUpdatesForChildren(MatrixF mat);
+   
+   // Move the RenderTransform
+   void moveRender(const Point3F &delta);
+   //Calculate how much to adjust the render transform - Called by the child objects
+   void updateRenderChangesByParent();
+   //Calculate how much to adjust the transform - Called by the parent object
+   void updateChildTransform(); 
+   /// as move(const Point3F &delta), with different signature   
+   void move(F32 x, F32 y, F32 z);
+
+   /// returns the transform relative to parent SceneObject transform (or world transform if no parent)
+   //const MatrixF& getLocalTransform() const;
+   /// returns the position within parent SceneObject space (or world space if no parent)
+   //Point3F getLocalPosition() const;
+   
+   
+//   virtual void onParentScaleChanged();   
+//   virtual void onParentTransformChanged();
+      
+   /// Sets the Object -> Parent transform.  If no parent SceneObject, this is equivalent to 
+   ///  setTransform()
+   ///
+   /// @param   mat   New transform matrix
+   //virtual void setLocalTransform(const MatrixF & mat);
+
+
+   /// Called to let instance specific code happen 
+   virtual void onLostParent(SceneObject *oldParent);   
+   /// Called to let instance specific code happen 
+   virtual void onNewParent(SceneObject *newParent);   
+   /// notification that a direct child object has been attached
+   virtual void onNewChild(SceneObject *subObject);   
+   /// notification that a direct child object has been detached
+   virtual void onLostChild(SceneObject *subObject);   
+// PATHSHAPE END
 };
 
 #endif  // _SCENEOBJECT_H_

+ 18 - 0
Engine/source/scene/simPath.cpp

@@ -34,6 +34,7 @@
 #include "core/stream/bitStream.h"
 #include "renderInstance/renderPassManager.h"
 #include "console/engineAPI.h"
+#include "T3D/pathShape.h"
 
 #include "T3D/Scene.h"
 
@@ -155,6 +156,11 @@ Path::Path()
 {
    mPathIndex = NoPathIndex;
    mIsLooping = true;
+   mPathSpeed = 1.0f;
+   mDataBlock = NULL;
+   mSpawnCount = 1;
+   mMinDelay = 0;
+   mMaxDelay = 0;
 }
 
 Path::~Path()
@@ -166,6 +172,13 @@ Path::~Path()
 void Path::initPersistFields()
 {
    addField("isLooping",   TypeBool, Offset(mIsLooping, Path), "If this is true, the loop is closed, otherwise it is open.\n");
+   addField("Speed",   TypeF32, Offset(mPathSpeed, Path), "Speed.\n");
+   addProtectedField("mPathShape", TYPEID< PathShapeData >(), Offset(mDataBlock, Path),
+	   &setDataBlockProperty, &defaultProtectedGetFn,
+	   "@brief Spawned PathShape.\n\n");
+   addField("spawnCount", TypeS32, Offset(mSpawnCount, Path), "Spawn Count.\n");
+   addField("minDelay", TypeS32, Offset(mMinDelay, Path), "Spawn Delay (min).\n");
+   addField("maxDelay", TypeS32, Offset(mMaxDelay, Path), "Spawn Delay (max).\n");
 
    Parent::initPersistFields();
    //
@@ -179,9 +192,14 @@ bool Path::onAdd()
    if(!Parent::onAdd())
       return false;
 
+   onAdd_callback(getId());
    return true;
 }
 
+IMPLEMENT_CALLBACK(Path, onAdd, void, (SimObjectId ID), (ID),
+	"Called when this ScriptGroup is added to the system.\n"
+	"@param ID Unique object ID assigned when created (%this in script).\n"
+);
 
 void Path::onRemove()
 {

+ 13 - 4
Engine/source/scene/simPath.h

@@ -36,17 +36,21 @@
 #include "gfx/gfxPrimitiveBuffer.h"
 #endif
 
-class BaseMatInstance;
+#ifndef _STATICSHAPE_H_
+#include "T3D/staticShape.h"
+#endif
 
+class BaseMatInstance;
+struct PathShapeData;
 
 namespace SimPath
 {
 
 //--------------------------------------------------------------------------
 /// A path!
-class Path : public SimGroup
+class Path : public GameBase
 {
-   typedef SimGroup Parent;
+   typedef GameBase Parent;
 
   public:
    enum : U32
@@ -57,8 +61,12 @@ class Path : public SimGroup
 
   private:
    U32 mPathIndex;
+   F32 mPathSpeed;
    bool mIsLooping;
-
+   PathShapeData* mDataBlock;
+   S32 mSpawnCount;
+   S32 mMinDelay;
+   S32 mMaxDelay;
   protected:
    bool onAdd();
    void onRemove();
@@ -77,6 +85,7 @@ class Path : public SimGroup
 
    DECLARE_CONOBJECT(Path);
    static void initPersistFields();
+   DECLARE_CALLBACK(void, onAdd, (SimObjectId ID));
 };
 
 //--------------------------------------------------------------------------