Explorar el Código

Merge pull request #221 from DavidWyand-GG/ExtendedMove

ExtendedMove class and support
David Wyand hace 12 años
padre
commit
2112878f30

+ 379 - 0
Engine/source/T3D/gameBase/extended/extendedGameProcess.cpp

@@ -0,0 +1,379 @@
+#include "platform/platform.h"
+#include "T3D/gameBase/extended/extendedGameProcess.h"
+#include "T3D/gameBase/extended/extendedMoveList.h"
+
+#include "platform/profiler.h"
+#include "console/consoleTypes.h"
+#include "core/dnet.h"
+#include "core/stream/bitStream.h"
+#include "core/frameAllocator.h"
+#include "core/util/refBase.h"
+#include "math/mPoint3.h"
+#include "math/mMatrix.h"
+#include "math/mathUtils.h"
+#include "T3D/gameBase/gameBase.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/fx/cameraFXMgr.h"
+
+MODULE_BEGIN( ProcessList )
+
+   MODULE_INIT
+   {
+      ExtendedServerProcessList::init();
+      ExtendedClientProcessList::init();
+   }
+
+   MODULE_SHUTDOWN
+   {
+      ExtendedServerProcessList::shutdown();
+      ExtendedClientProcessList::shutdown();
+   }
+
+MODULE_END;
+
+void ExtendedServerProcessList::init()
+{
+   smServerProcessList = new ExtendedServerProcessList();
+}
+
+void ExtendedServerProcessList::shutdown()
+{
+   delete smServerProcessList;
+}
+
+void ExtendedClientProcessList::init()
+{
+   smClientProcessList = new ExtendedClientProcessList();
+}
+
+void ExtendedClientProcessList::shutdown()
+{
+   delete smClientProcessList;
+}
+
+//----------------------------------------------------------------------------
+
+
+namespace
+{
+   // local work class
+   struct GameBaseListNode
+   {
+      GameBaseListNode()
+      {
+         mPrev=this;
+         mNext=this;
+         mObject=NULL;
+      }
+
+      GameBaseListNode * mPrev;
+      GameBaseListNode * mNext;
+      GameBase * mObject;
+
+      void linkBefore(GameBaseListNode * obj)
+      {
+         // Link this before obj
+         mNext = obj;
+         mPrev = obj->mPrev;
+         obj->mPrev = this;
+         mPrev->mNext = this;
+      }
+   };
+} // namespace
+
+//--------------------------------------------------------------------------
+// ExtendedClientProcessList
+//--------------------------------------------------------------------------
+
+ExtendedClientProcessList::ExtendedClientProcessList()
+{
+}
+
+bool ExtendedClientProcessList::advanceTime( SimTime timeDelta )
+{
+   PROFILE_SCOPE( ExtendedClientProcessList_AdvanceTime );
+
+   if ( doBacklogged( timeDelta ) )
+      return false;
+ 
+   bool ret = Parent::advanceTime( timeDelta );
+   ProcessObject *obj = NULL;
+
+   AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta is not zero to one.");
+   
+   obj = mHead.mProcessLink.next;
+   while ( obj != &mHead )
+   {      
+      if ( obj->isTicking() )
+         obj->interpolateTick( mLastDelta );
+
+      obj = obj->mProcessLink.next;
+   }
+
+   // Inform objects of total elapsed delta so they can advance
+   // client side animations.
+   F32 dt = F32(timeDelta) / 1000;
+
+   // Update camera FX.
+   gCamFXMgr.update( dt );
+
+   obj = mHead.mProcessLink.next;
+   while ( obj != &mHead )
+   {      
+      obj->advanceTime( dt );
+      obj = obj->mProcessLink.next;
+   }
+   
+   return ret;
+}
+
+//----------------------------------------------------------------------------
+void ExtendedClientProcessList::onAdvanceObjects()
+{
+   PROFILE_SCOPE( ExtendedClientProcessList_OnAdvanceObjects );
+
+   GameConnection* connection = GameConnection::getConnectionToServer();
+   if ( connection )
+   {
+      // process any demo blocks that are NOT moves, and exactly one move
+      // we advance time in the demo stream by a move inserted on
+      // each tick.  So before doing the tick processing we advance
+      // the demo stream until a move is ready
+      if ( connection->isPlayingBack() )
+      {
+         U32 blockType;
+         do
+         {
+            blockType = connection->getNextBlockType();
+            bool res = connection->processNextBlock();
+            // if there are no more blocks, exit out of this function,
+            // as no more client time needs to process right now - we'll
+            // get it all on the next advanceClientTime()
+            if(!res)
+               return;
+         }
+         while ( blockType != GameConnection::BlockTypeMove );
+      }
+
+      connection->mMoveList->collectMove();
+      advanceObjects();
+   }
+   else
+      advanceObjects();
+}
+
+void ExtendedClientProcessList::onTickObject( ProcessObject *obj )
+{
+   PROFILE_SCOPE( ExtendedClientProcessList_OnTickObject );
+
+   // In case the object deletes itself during its processTick.
+   SimObjectPtr<SceneObject> safePtr = static_cast<SceneObject*>( obj );   
+
+   // Each object is either advanced a single tick, or if it's
+   // being controlled by a client, ticked once for each pending move.
+   ExtendedMove* extMovePtr;
+   U32 numMoves;
+   GameConnection* con = obj->getControllingClient();
+   if  ( con && con->getControlObject() == obj )
+   {
+      ExtendedMoveList* extMoveList = static_cast<ExtendedMoveList*>(con->mMoveList);
+      extMoveList->getExtMoves( &extMovePtr, &numMoves );
+      if ( numMoves )
+      {
+         // Note: should only have a single move at this point
+         AssertFatal(numMoves==1,"ClientProccessList::onTickObject: more than one move in queue");
+
+         #ifdef TORQUE_DEBUG_NET_MOVES
+         U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
+         #endif
+
+         if ( obj->isTicking() )
+            obj->processTick( extMovePtr );
+
+         if ( bool(safePtr) && obj->getControllingClient() )
+         {
+            U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
+
+            // set checksum if not set or check against stored value if set
+            extMovePtr->checksum = newsum;
+
+            #ifdef TORQUE_DEBUG_NET_MOVES
+            Con::printf("move checksum: %i, (start %i), (move %f %f %f)",
+               movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z);
+            #endif
+         }
+         con->mMoveList->clearMoves( 1 );
+      }
+   }
+   else if ( obj->isTicking() )
+      obj->processTick( 0 );
+}
+
+void ExtendedClientProcessList::advanceObjects()
+{
+   PROFILE_SCOPE( ExtendedClientProcessList_AdvanceObjects );
+
+   #ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("Advance client time...");
+   #endif
+
+   Parent::advanceObjects();
+
+   #ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("---------");
+   #endif
+}
+
+void ExtendedClientProcessList::clientCatchup( GameConnection *  connection )
+{
+   SimObjectPtr<GameBase> control = connection->getControlObject();
+   if ( control )
+   {
+      ExtendedMove * extMovePtr;
+      U32 numMoves;
+      U32 m = 0;
+      ExtendedMoveList* extMoveList = static_cast<ExtendedMoveList*>(connection->mMoveList);
+      extMoveList->getExtMoves( &extMovePtr, &numMoves );
+
+      #ifdef TORQUE_DEBUG_NET_MOVES
+      Con::printf("client catching up... (%i)", numMoves);
+      #endif
+
+      preTickSignal().trigger();
+
+      if ( control->isTicking() )
+         for ( m = 0; m < numMoves; m++ )
+            control->processTick( extMovePtr++ );
+
+      extMoveList->clearMoves( m );
+   }
+
+   #ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("---------");
+   #endif
+}
+
+//--------------------------------------------------------------------------
+// ServerProcessList
+//--------------------------------------------------------------------------
+   
+ExtendedServerProcessList::ExtendedServerProcessList()
+{
+}
+
+void ExtendedServerProcessList::onPreTickObject( ProcessObject *pobj )
+{
+   if ( pobj->mIsGameBase )
+   {
+      SimObjectPtr<GameBase> obj = getGameBase( pobj );
+
+      // Each object is either advanced a single tick, or if it's
+      // being controlled by a client, ticked once for each pending move.
+      GameConnection *con = obj->getControllingClient();
+
+      if ( con && con->getControlObject() == obj )
+      {
+         ExtendedMove* extMovePtr;
+         U32 numMoves;
+         ExtendedMoveList* extMoveList = static_cast<ExtendedMoveList*>(con->mMoveList);
+         extMoveList->getExtMoves( &extMovePtr, &numMoves );
+
+         if ( numMoves == 0 )
+         {
+   #ifdef TORQUE_DEBUG_NET_MOVES
+            Con::printf("no moves on object %i, skip tick",obj->getId());
+   #endif         
+            return;
+         }
+      }
+   }
+
+   Parent::onPreTickObject (pobj );
+}
+
+void ExtendedServerProcessList::onTickObject( ProcessObject *pobj )
+{
+   PROFILE_SCOPE( ExtendedServerProcessList_OnTickObject );
+   
+   // Each object is either advanced a single tick, or if it's
+   // being controlled by a client, ticked once for each pending move.
+
+   GameConnection *con = pobj->getControllingClient();
+
+   if ( pobj->mIsGameBase && con && con->getControlObject() == pobj )
+   {
+      // In case the object is deleted during its own tick.
+      SimObjectPtr<GameBase> obj = getGameBase( pobj );
+
+      ExtendedMove* extMovePtr;
+      U32 m, numMoves;
+      ExtendedMoveList* extMoveList = static_cast<ExtendedMoveList*>(con->mMoveList);
+      extMoveList->getExtMoves( &extMovePtr, &numMoves );
+
+      // For debugging it can be useful to know when this happens.
+      //if ( numMoves > 1 )
+      //   Con::printf( "numMoves: %i", numMoves );
+
+      // Do we really need to test the control object each iteration? Does it change?
+      for ( m = 0; m < numMoves && con && con->getControlObject() == obj; m++, extMovePtr++ )
+      {         
+         #ifdef TORQUE_DEBUG_NET_MOVES
+         U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
+         #endif
+      
+         if ( obj->isTicking() )
+            obj->processTick( extMovePtr );
+
+         if ( con && con->getControlObject() == obj )
+         {
+            U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
+
+            // check move checksum
+            if ( extMovePtr->checksum != newsum )
+            {
+               #ifdef TORQUE_DEBUG_NET_MOVES
+               if( !obj->isAIControlled() )
+                  Con::printf("move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)",
+                     extMovePtr->id, extMovePtr->checksum,newsum,sum,extMovePtr->yaw,extMovePtr->y,extMovePtr->z);
+               #endif
+
+               extMovePtr->checksum = Move::ChecksumMismatch;
+            }
+            else
+            {
+               #ifdef TORQUE_DEBUG_NET_MOVES
+               Con::printf("move %i checksum agree: %i == %i, (start %i), (move %f %f %f)",
+                  extMovePtr->id, extMovePtr->checksum,newsum,sum,extMovePtr->yaw,extMovePtr->y,extMovePtr->z);
+               #endif
+            }
+         }
+      }
+
+      extMoveList->clearMoves( m );
+   }
+   else if ( pobj->isTicking() )
+      pobj->processTick( 0 );
+}
+
+void ExtendedServerProcessList::advanceObjects()
+{
+   #ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("Advance server time...");
+   #endif
+
+   Parent::advanceObjects();
+
+   // Credit all connections with the elapsed tick
+   SimGroup *clientGroup = Sim::getClientGroup();
+   for(SimGroup::iterator i = clientGroup->begin(); i != clientGroup->end(); i++)
+   {
+      if (GameConnection *con = dynamic_cast<GameConnection *>(*i))
+      {
+         con->mMoveList->advanceMove();
+      }
+   }
+
+   #ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("---------");
+   #endif
+}

+ 60 - 0
Engine/source/T3D/gameBase/extended/extendedGameProcess.h

@@ -0,0 +1,60 @@
+#ifndef _GAMEPROCESS_EXTENDED_H_
+#define _GAMEPROCESS_EXTENDED_H_
+
+//#include "T3D/gameBase/processList.h"
+#ifndef _GAMEPROCESS_H_
+#include "T3D/gameBase/gameProcess.h"
+#endif
+
+class GameBase;
+class GameConnection;
+struct Move;
+
+//----------------------------------------------------------------------------
+
+/// List to keep track of GameBases to process.
+class ExtendedClientProcessList : public ClientProcessList
+{
+   typedef ClientProcessList Parent;
+
+protected:
+   
+   // ProcessList
+   void onTickObject(ProcessObject *);
+   void advanceObjects();
+   void onAdvanceObjects();
+   
+public:
+
+   ExtendedClientProcessList();  
+
+   // ProcessList
+   bool advanceTime( SimTime timeDelta );
+   
+   // ClientProcessList
+   void clientCatchup( GameConnection *conn );
+   
+   static void init();
+   static void shutdown();
+};
+
+class ExtendedServerProcessList : public ServerProcessList
+{
+   typedef ServerProcessList Parent;
+
+protected:
+
+   // ProcessList
+   void onPreTickObject( ProcessObject *pobj );
+   void onTickObject( ProcessObject *pobj );
+   void advanceObjects();
+
+public:
+
+   ExtendedServerProcessList();  
+
+   static void init();
+   static void shutdown();
+};
+
+#endif   // _GAMEPROCESS_EXTENDED_H_

+ 258 - 0
Engine/source/T3D/gameBase/extended/extendedMove.cpp

@@ -0,0 +1,258 @@
+#include "T3D/gameBase/extended/extendedMove.h"
+#include "core/stream/bitStream.h"
+#include "math/mathIO.h"
+#include "core/module.h"
+#include "console/consoleTypes.h"
+#include "core/strings/stringFunctions.h"
+
+MODULE_BEGIN( ExtendedMoveManager )
+
+   MODULE_INIT_AFTER( MoveManager )
+   MODULE_INIT
+   {
+      ExtendedMoveManager::init();
+   }
+
+MODULE_END;
+
+S32 ExtendedMoveManager::mPosX[ExtendedMove::MaxPositionsRotations] = { 0, };
+S32 ExtendedMoveManager::mPosY[ExtendedMove::MaxPositionsRotations] = { 0, };
+S32 ExtendedMoveManager::mPosZ[ExtendedMove::MaxPositionsRotations] = { 0, };
+F32 ExtendedMoveManager::mRotAX[ExtendedMove::MaxPositionsRotations] = { 0, };
+F32 ExtendedMoveManager::mRotAY[ExtendedMove::MaxPositionsRotations] = { 0, };
+F32 ExtendedMoveManager::mRotAZ[ExtendedMove::MaxPositionsRotations] = { 0, };
+F32 ExtendedMoveManager::mRotAA[ExtendedMove::MaxPositionsRotations] = { 1, };
+
+void ExtendedMoveManager::init()
+{
+   for(U32 i = 0; i < ExtendedMove::MaxPositionsRotations; ++i)
+   {
+      char varName[256];
+
+      dSprintf(varName, sizeof(varName), "mvPosX%d", i);
+      Con::addVariable(varName, TypeS32, &mPosX[i], 
+         "X position of controller in millimeters.  Only 13 bits are networked.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvPosY%d", i);
+      Con::addVariable(varName, TypeS32, &mPosY[i], 
+         "Y position of controller in millimeters.  Only 13 bits are networked.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvPosZ%d", i);
+      Con::addVariable(varName, TypeS32, &mPosZ[i], 
+         "Z position of controller in millimeters.  Only 13 bits are networked.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvRotX%d", i);
+      Con::addVariable(varName, TypeF32, &mRotAX[i], 
+         "X rotation vector component of controller.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvRotY%d", i);
+      Con::addVariable(varName, TypeF32, &mRotAY[i], 
+         "Y rotation vector component of controller.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvRotZ%d", i);
+      Con::addVariable(varName, TypeF32, &mRotAZ[i], 
+         "Z rotation vector component of controller.\n"
+	      "@ingroup Game");
+
+      dSprintf(varName, sizeof(varName), "mvRotA%d", i);
+      Con::addVariable(varName, TypeF32, &mRotAA[i], 
+         "Angle rotation (in degrees) component of controller.\n"
+	      "@ingroup Game");
+   }
+}
+
+const ExtendedMove NullExtendedMove;
+
+#define CLAMPPOS(x) (x<0 ? -((-x) & (1<<(MaxPositionBits-1))-1) : (x & (1<<(MaxPositionBits-1))-1))
+#define CLAMPROT(f) ((S32)(((f + 1) * .5) * ((1 << MaxRotationBits) - 1)) & ((1<<MaxRotationBits)-1))
+#define UNCLAMPROT(x) ((F32)(x * 2 / F32((1 << MaxRotationBits) - 1) - 1.0f))
+
+ExtendedMove::ExtendedMove() : Move()
+{
+   for(U32 i=0; i<MaxPositionsRotations; ++i)
+   {
+      posX[i] = 0;
+      posY[i] = 0;
+      posZ[i] = 0;
+      rotX[i] = 0;
+      rotY[i] = 0;
+      rotZ[i] = 0;
+      rotW[i] = 1;
+   }
+}
+
+void ExtendedMove::pack(BitStream *stream, const Move * basemove)
+{
+   bool alwaysWriteAll = basemove!=NULL;
+   if (!basemove)
+      basemove = &NullExtendedMove;
+
+   // Write the standard Move stuff
+   packMove(stream, basemove, alwaysWriteAll);
+
+   // Extended Move
+   const ExtendedMove* extBaseMove = static_cast<const ExtendedMove*>(basemove);
+
+   bool extendedDifferent = false;
+   for(U32 i=0; i<MaxPositionsRotations; ++i)
+   {
+      bool check =   (posX[i] != extBaseMove->posX[i])   ||
+                     (posY[i] != extBaseMove->posY[i])   ||
+                     (posZ[i] != extBaseMove->posZ[i])   ||
+                     (rotX[i] != extBaseMove->rotX[i])   ||
+                     (rotY[i] != extBaseMove->rotY[i])   ||
+                     (rotZ[i] != extBaseMove->rotZ[i])   ||
+                     (rotW[i] != extBaseMove->rotW[i]);
+
+      extendedDifferent = extendedDifferent || check;
+   }
+
+   if (alwaysWriteAll || stream->writeFlag(extendedDifferent))
+   {
+      for(U32 i=0; i<MaxPositionsRotations; ++i)
+      {
+         // Position
+         if(stream->writeFlag(posX[i] != extBaseMove->posX[i]))
+            stream->writeSignedInt(posX[i], MaxPositionBits);
+         if(stream->writeFlag(posY[i] != extBaseMove->posY[i]))
+            stream->writeSignedInt(posY[i], MaxPositionBits);
+         if(stream->writeFlag(posZ[i] != extBaseMove->posZ[i]))
+            stream->writeSignedInt(posZ[i], MaxPositionBits);
+
+         // Rotation
+         if(stream->writeFlag(rotX[i] != extBaseMove->rotX[i]))
+            stream->writeInt(crotX[i], MaxRotationBits);
+         if(stream->writeFlag(rotY[i] != extBaseMove->rotY[i]))
+            stream->writeInt(crotY[i], MaxRotationBits);
+         if(stream->writeFlag(rotZ[i] != extBaseMove->rotZ[i]))
+            stream->writeInt(crotZ[i], MaxRotationBits);
+         if(stream->writeFlag(rotW[i] != extBaseMove->rotW[i]))
+            stream->writeInt(crotW[i], MaxRotationBits);
+      }
+   }
+}
+
+void ExtendedMove::unpack(BitStream *stream, const Move * basemove)
+{
+   bool alwaysReadAll = basemove!=NULL;
+   if (!basemove)
+      basemove=&NullExtendedMove;
+
+   // Standard Move stuff
+   bool isBaseMove = !unpackMove(stream, basemove, alwaysReadAll);
+
+   // ExtendedMove
+   const ExtendedMove* extBaseMove = static_cast<const ExtendedMove*>(basemove);
+
+   if (alwaysReadAll || stream->readFlag())
+   {
+      isBaseMove = false;
+
+      for(U32 i=0; i<MaxPositionsRotations; ++i)
+      {
+         // Position
+         if(stream->readFlag())
+            posX[i] = stream->readSignedInt(MaxPositionBits);
+         else
+            posX[i] = extBaseMove->posX[i];
+
+         if(stream->readFlag())
+            posY[i] = stream->readSignedInt(MaxPositionBits);
+         else
+            posY[i] = extBaseMove->posY[i];
+
+         if(stream->readFlag())
+            posZ[i] = stream->readSignedInt(MaxPositionBits);
+         else
+            posZ[i] = extBaseMove->posZ[i];
+
+         // Rotation
+         if(stream->readFlag())
+         {
+            crotX[i] = stream->readInt(MaxRotationBits);
+            rotX[i] = UNCLAMPROT(crotX[i]);
+         }
+         else
+         {
+            rotX[i] = extBaseMove->rotX[i];
+         }
+
+         if(stream->readFlag())
+         {
+            crotY[i] = stream->readInt(MaxRotationBits);
+            rotY[i] = UNCLAMPROT(crotY[i]);
+         }
+         else
+         {
+            rotY[i] = extBaseMove->rotY[i];
+         }
+
+         if(stream->readFlag())
+         {
+            crotZ[i] = stream->readInt(MaxRotationBits);
+            rotZ[i] = UNCLAMPROT(crotZ[i]);
+         }
+         else
+         {
+            rotZ[i] = extBaseMove->rotZ[i];
+         }
+
+         if(stream->readFlag())
+         {
+            crotW[i] = stream->readInt(MaxRotationBits);
+            rotW[i] = UNCLAMPROT(crotW[i]);
+         }
+         else
+         {
+            rotW[i] = extBaseMove->rotW[i];
+         }
+      }
+   }
+
+   if(isBaseMove)
+   {
+      *this = *extBaseMove;
+   }
+}
+
+void ExtendedMove::clamp()
+{
+   // Clamp the values the same as for net traffic so the client matches the server
+   for(U32 i=0; i<MaxPositionsRotations; ++i)
+   {
+      // Positions
+      posX[i] = CLAMPPOS(posX[i]);
+      posY[i] = CLAMPPOS(posY[i]);
+      posZ[i] = CLAMPPOS(posZ[i]);
+
+      // Rotations
+      crotX[i] = CLAMPROT(rotX[i]);
+      crotY[i] = CLAMPROT(rotY[i]);
+      crotZ[i] = CLAMPROT(rotZ[i]);
+      crotW[i] = CLAMPROT(rotW[i]);
+   }
+
+   // Perform the standard Move clamp
+   Parent::clamp();
+}
+
+void ExtendedMove::unclamp()
+{
+   // Unclamp the values the same as for net traffic so the client matches the server
+   for(U32 i=0; i<MaxPositionsRotations; ++i)
+   {
+      // Rotations
+      rotX[i] = UNCLAMPROT(crotX[i]);
+      rotY[i] = UNCLAMPROT(crotY[i]);
+      rotZ[i] = UNCLAMPROT(crotZ[i]);
+      rotW[i] = UNCLAMPROT(crotW[i]);
+   }
+
+   // Perform the standard Move unclamp
+   Parent::unclamp();
+}

+ 51 - 0
Engine/source/T3D/gameBase/extended/extendedMove.h

@@ -0,0 +1,51 @@
+#ifndef _EXTENDEDMOVE_H_
+#define _EXTENDEDMOVE_H_
+
+#include "T3D/gameBase/moveManager.h"
+#include "math/mQuat.h"
+
+struct ExtendedMove : public Move
+{
+   typedef Move Parent;
+
+   enum Constants {
+      MaxPositionsRotations = 2,
+
+      MaxPositionBits = 13,
+      MaxRotationBits = 11,
+   };
+
+   // Position is in millimeters
+   S32 posX[MaxPositionsRotations], posY[MaxPositionsRotations], posZ[MaxPositionsRotations];
+
+   F32 rotX[MaxPositionsRotations], rotY[MaxPositionsRotations], rotZ[MaxPositionsRotations], rotW[MaxPositionsRotations];
+
+   // Network clamped rotation
+   S32 crotX[MaxPositionsRotations], crotY[MaxPositionsRotations], crotZ[MaxPositionsRotations], crotW[MaxPositionsRotations];
+
+   ExtendedMove();
+
+   virtual void pack(BitStream *stream, const Move * move = NULL);
+   virtual void unpack(BitStream *stream, const Move * move = NULL);
+
+   virtual void clamp();
+   virtual void unclamp();
+};
+
+extern const ExtendedMove NullExtendedMove;
+
+class ExtendedMoveManager
+{
+public:
+   static S32 mPosX[ExtendedMove::MaxPositionsRotations];
+   static S32 mPosY[ExtendedMove::MaxPositionsRotations];
+   static S32 mPosZ[ExtendedMove::MaxPositionsRotations];
+   static F32 mRotAX[ExtendedMove::MaxPositionsRotations];
+   static F32 mRotAY[ExtendedMove::MaxPositionsRotations];
+   static F32 mRotAZ[ExtendedMove::MaxPositionsRotations];
+   static F32 mRotAA[ExtendedMove::MaxPositionsRotations];
+
+   static void init();
+};
+
+#endif   // _EXTENDEDMOVE_H_

+ 369 - 0
Engine/source/T3D/gameBase/extended/extendedMoveList.cpp

@@ -0,0 +1,369 @@
+#include "platform/platform.h"
+#include "T3D/gameBase/extended/extendedMoveList.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "core/stream/bitStream.h"
+
+#define MAX_MOVE_PACKET_SENDS 4
+
+ExtendedMoveList::ExtendedMoveList()
+{
+   mMoveCredit = MaxMoveCount;
+   mControlMismatch = false;
+   reset();
+}
+
+void ExtendedMoveList::reset()
+{
+   mLastMoveAck = 0;
+   mLastClientMove = 0;
+   mFirstMoveIndex = 0;
+
+   mExtMoveVec.clear();
+}
+
+bool ExtendedMoveList::getNextExtMove( ExtendedMove &curMove )
+{
+   if ( mExtMoveVec.size() > MaxMoveQueueSize )
+      return false;
+
+   // From MoveList
+   F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed;
+   F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed;
+   F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed;
+
+   curMove.pitch = MoveManager::mPitch + pitchAdd;
+   curMove.yaw = MoveManager::mYaw + yawAdd;
+   curMove.roll = MoveManager::mRoll + rollAdd;
+
+   MoveManager::mPitch = 0;
+   MoveManager::mYaw = 0;
+   MoveManager::mRoll = 0;
+
+   curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction + MoveManager::mXAxis_L;
+   curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction + MoveManager::mYAxis_L;
+   curMove.z = MoveManager::mUpAction - MoveManager::mDownAction;
+
+   curMove.freeLook = MoveManager::mFreeLook;
+   curMove.deviceIsKeyboardMouse = MoveManager::mDeviceIsKeyboardMouse;
+
+   for(U32 i = 0; i < MaxTriggerKeys; i++)
+   {
+      curMove.trigger[i] = false;
+      if(MoveManager::mTriggerCount[i] & 1)
+         curMove.trigger[i] = true;
+      else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i])
+         curMove.trigger[i] = true;
+      MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i];
+   }
+
+   for(U32 i=0; i<ExtendedMove::MaxPositionsRotations; ++i)
+   {
+      // Process position
+      curMove.posX[i] = ExtendedMoveManager::mPosX[i];
+      curMove.posY[i] = ExtendedMoveManager::mPosY[i];
+      curMove.posZ[i] = ExtendedMoveManager::mPosZ[i];
+
+      // Process rotation.  Rotation is passed in as an Angle Axis in degrees.
+      // We need to convert this into a Quat.
+      QuatF q(Point3F(ExtendedMoveManager::mRotAX[i], ExtendedMoveManager::mRotAY[i], ExtendedMoveManager::mRotAZ[i]), mDegToRad(ExtendedMoveManager::mRotAA[i]));
+      curMove.rotX[i] = q.x;
+      curMove.rotY[i] = q.y;
+      curMove.rotZ[i] = q.z;
+      curMove.rotW[i] = q.w;
+   }
+
+   if (mConnection->getControlObject())
+      mConnection->getControlObject()->preprocessMove(&curMove);
+
+   curMove.clamp();  // clamp for net traffic
+   return true;
+}
+
+U32 ExtendedMoveList::getMoves(Move** movePtr,U32* numMoves)
+{
+   // We don't want to be here
+   AssertFatal(0, "getMoves() called");
+
+   numMoves = 0;
+
+   return 0;
+}
+
+U32 ExtendedMoveList::getExtMoves( ExtendedMove** movePtr, U32 *numMoves )
+{
+   if (!mConnection->isConnectionToServer())
+   {
+      if (mExtMoveVec.size() > mMoveCredit)
+         mExtMoveVec.setSize(mMoveCredit);
+   }
+
+   // From MoveList but converted to use mExtMoveVec
+   if (mConnection->isConnectionToServer())
+   {
+      // give back moves starting at the last client move...
+
+      AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
+      AssertFatal(mLastClientMove - mFirstMoveIndex <= mExtMoveVec.size(), "Desynched first and last move.");
+      *numMoves = mExtMoveVec.size() - mLastClientMove + mFirstMoveIndex;
+      *movePtr = mExtMoveVec.address() + mLastClientMove - mFirstMoveIndex;
+   }
+   else
+   {
+      // return the full list
+      *numMoves = mExtMoveVec.size();
+      *movePtr = mExtMoveVec.begin();
+   }
+
+   return *numMoves;
+}
+
+void ExtendedMoveList::collectMove()
+{
+   ExtendedMove mv;   
+   if (mConnection)
+   {
+      if(!mConnection->isPlayingBack() && getNextExtMove(mv))
+      {
+         mv.checksum=Move::ChecksumMismatch;
+         pushMove(mv);
+         mConnection->recordBlock(GameConnection::BlockTypeMove, sizeof(ExtendedMove), &mv);
+      }
+   }
+   else
+   {
+      if(getNextExtMove(mv))
+      {
+         mv.checksum=Move::ChecksumMismatch;
+         pushMove(mv);
+      }
+   }
+}
+
+void ExtendedMoveList::pushMove(const Move &mv)
+{
+   const ExtendedMove* extMove = dynamic_cast<const ExtendedMove*>(&mv);
+   AssertFatal(extMove, "Regular Move struct passed to pushMove()");
+
+   pushExtMove(*extMove);
+}
+
+void ExtendedMoveList::pushExtMove( const ExtendedMove &mv )
+{
+   U32 id = mFirstMoveIndex + mExtMoveVec.size();
+   U32 sz = mExtMoveVec.size();
+   mExtMoveVec.push_back(mv);
+   mExtMoveVec[sz].id = id;
+   mExtMoveVec[sz].sendCount = 0;
+}
+
+void ExtendedMoveList::clearMoves(U32 count)
+{
+   if (!mConnection->isConnectionToServer())
+   {
+      count = mClamp(count,0,mMoveCredit);
+      mMoveCredit -= count;
+   }
+
+   // From MoveList but converted to use mExtMoveVec
+   if (mConnection->isConnectionToServer())
+   {
+      mLastClientMove += count;
+      AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
+      AssertFatal(mLastClientMove - mFirstMoveIndex <= mExtMoveVec.size(), "Desynched first and last move.");
+      if (!mConnection)
+         // drop right away if no connection
+         ackMoves(count);
+   }
+   else 
+   {
+      AssertFatal(count <= mExtMoveVec.size(),"GameConnection: Clearing too many moves");
+      for (S32 i=0; i<count; i++)
+         if (mExtMoveVec[i].checksum == Move::ChecksumMismatch)
+            mControlMismatch = true;
+         else 
+            mControlMismatch = false;
+      if (count == mExtMoveVec.size())
+         mExtMoveVec.clear();
+      else
+         while (count--)
+            mExtMoveVec.pop_front();
+   }
+}
+
+void ExtendedMoveList::advanceMove()
+{
+   AssertFatal(!mConnection->isConnectionToServer(), "Cannot inc move credit on the client.");
+
+   // Game tick increment
+   mMoveCredit++;
+   if (mMoveCredit > MaxMoveCount)
+      mMoveCredit = MaxMoveCount;
+
+   // Clear pending moves for the elapsed time if there
+   // is no control object.
+   if ( mConnection->getControlObject() == NULL )
+      mExtMoveVec.clear();
+}
+
+void ExtendedMoveList::clientWriteMovePacket(BitStream *bstream)
+{
+   AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
+   U32 count = mExtMoveVec.size();
+
+   ExtendedMove* extMove = mExtMoveVec.address();
+   U32 start = mLastMoveAck;
+   U32 offset;
+   for(offset = 0; offset < count; offset++)
+      if(extMove[offset].sendCount < MAX_MOVE_PACKET_SENDS)
+         break;
+   if(offset == count && count != 0)
+      offset--;
+
+   start += offset;
+   count -= offset;
+
+   if (count > MaxMoveCount)
+      count = MaxMoveCount;
+   bstream->writeInt(start,32);
+   bstream->writeInt(count,MoveCountBits);
+   ExtendedMove* prevExtMove = NULL;
+   for (int i = 0; i < count; i++)
+   {
+      extMove[offset + i].sendCount++;
+      extMove[offset + i].pack(bstream,prevExtMove);
+      bstream->writeInt(extMove[offset + i].checksum & (~(0xFFFFFFFF << Move::ChecksumBits)),Move::ChecksumBits);
+      prevExtMove = &extMove[offset+i];
+   }
+}
+
+void ExtendedMoveList::serverReadMovePacket(BitStream *bstream)
+{
+   // Server side packet read.
+   U32 start = bstream->readInt(32);
+   U32 count = bstream->readInt(MoveCountBits);
+
+   ExtendedMove* prevExtMove = NULL;
+   ExtendedMove prevExtMoveHolder;
+
+   // Skip forward (must be starting up), or over the moves
+   // we already have.
+   int skip = mLastMoveAck - start;
+   if (skip < 0) 
+   {
+      mLastMoveAck = start;
+   }
+   else 
+   {
+      if (skip > count)
+         skip = count;
+      for (int i = 0; i < skip; i++)
+      {
+         prevExtMoveHolder.unpack(bstream,prevExtMove);
+         prevExtMoveHolder.checksum = bstream->readInt(Move::ChecksumBits);
+         prevExtMove = &prevExtMoveHolder;
+         S32 idx = mExtMoveVec.size()-skip+i;
+         if (idx>=0)
+         {
+#ifdef TORQUE_DEBUG_NET_MOVES
+            if (mExtMoveVec[idx].checksum != prevExtMoveHolder.checksum)
+               Con::printf("updated checksum on move %i from %i to %i",mExtMoveVec[idx].id,mExtMoveVec[idx].checksum,prevExtMoveHolder.checksum);
+#endif
+            mExtMoveVec[idx].checksum = prevExtMoveHolder.checksum;
+         }
+      }
+      start += skip;
+      count = count - skip;
+   }
+
+   // Put the rest on the move list.
+   int index = mExtMoveVec.size();
+   mExtMoveVec.increment(count);
+   while (index < mExtMoveVec.size())
+   {
+      mExtMoveVec[index].unpack(bstream,prevExtMove);
+      mExtMoveVec[index].checksum = bstream->readInt(Move::ChecksumBits);
+      prevExtMove = &mExtMoveVec[index];
+      mExtMoveVec[index].id = start++;
+      index ++;
+   }
+
+   mLastMoveAck += count;
+}
+
+void ExtendedMoveList::serverWriteMovePacket(BitStream * bstream)
+{
+#ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("ack %i minus %i",mLastMoveAck,mExtMoveVec.size());
+#endif
+
+   // acknowledge only those moves that have been ticked
+   bstream->writeInt(mLastMoveAck - mExtMoveVec.size(),32);
+}
+
+void ExtendedMoveList::clientReadMovePacket(BitStream * bstream)
+{
+#ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("pre move ack: %i", mLastMoveAck);
+#endif
+
+   mLastMoveAck = bstream->readInt(32);
+
+#ifdef TORQUE_DEBUG_NET_MOVES
+   Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove);
+#endif
+
+   if (mLastMoveAck < mFirstMoveIndex)
+      mLastMoveAck = mFirstMoveIndex;
+
+   if(mLastMoveAck > mLastClientMove)
+      mLastClientMove = mLastMoveAck;
+   while(mFirstMoveIndex < mLastMoveAck)
+   {
+      if (mExtMoveVec.size())
+      {
+         mExtMoveVec.pop_front();
+         mFirstMoveIndex++;
+      }
+      else
+      {
+         AssertWarn(1, "Popping off too many moves!");
+         mFirstMoveIndex = mLastMoveAck;
+      }
+   }
+}
+
+bool ExtendedMoveList::isBacklogged()
+{
+   if ( !mConnection->isConnectionToServer() )
+      return false;
+
+   return mLastClientMove - mFirstMoveIndex == mExtMoveVec.size() && 
+          mExtMoveVec.size() >= MaxMoveCount;
+}
+
+bool ExtendedMoveList::areMovesPending()
+{
+   return mConnection->isConnectionToServer() ?
+          mExtMoveVec.size() - mLastClientMove + mFirstMoveIndex :
+          mExtMoveVec.size();
+}
+
+void ExtendedMoveList::ackMoves(U32 count)
+{
+   mLastMoveAck += count;
+   if(mLastMoveAck > mLastClientMove)
+      mLastClientMove = mLastMoveAck;
+   while(mFirstMoveIndex < mLastMoveAck)
+   {
+      if (mExtMoveVec.size())
+      {
+         mExtMoveVec.pop_front();
+         mFirstMoveIndex++;
+      }
+      else
+      {
+         AssertWarn(1, "Popping off too many moves!");
+         mFirstMoveIndex = mLastMoveAck;
+      }
+   }
+}

+ 55 - 0
Engine/source/T3D/gameBase/extended/extendedMoveList.h

@@ -0,0 +1,55 @@
+#ifndef _EXTENDEDMOVELIST_H_
+#define _EXTENDEDMOVELIST_H_
+
+#ifndef _TVECTOR_H_
+#include "core/util/tVector.h"
+#endif
+#ifndef _MOVELIST_H_
+#include "T3D/gameBase/moveList.h"
+#endif
+
+#include "T3D/gameBase/extended/extendedMove.h"
+
+class ExtendedMoveList : public MoveList
+{
+   typedef MoveList Parent;
+
+public:
+
+   ExtendedMoveList();
+
+   void clientWriteMovePacket(BitStream *);
+   void clientReadMovePacket(BitStream *);
+
+   void serverWriteMovePacket(BitStream *);
+   void serverReadMovePacket(BitStream *);
+
+   void advanceMove();
+   void onAdvanceObjects() {}
+   U32 getMoves(Move** movePtr,U32* numMoves);
+   U32 getExtMoves( ExtendedMove** movePtr, U32 *numMoves );
+
+   void collectMove();
+   void pushMove( const Move &mv );
+   void pushExtMove( const ExtendedMove &mv );
+   void clearMoves( U32 count );
+
+   virtual void reset();
+
+   bool isBacklogged();
+
+   bool areMovesPending();
+
+   void ackMoves( U32 count );
+
+protected:
+
+   U32 mMoveCredit;
+
+   Vector<ExtendedMove> mExtMoveVec;
+
+protected:
+   bool getNextExtMove( ExtendedMove &curMove );
+};
+
+#endif   // _EXTENDEDMOVELIST_H_

+ 4 - 0
Engine/source/T3D/gameBase/gameConnection.cpp

@@ -41,6 +41,8 @@
 
 #ifdef TORQUE_HIFI_NET
    #include "T3D/gameBase/hifi/hifiMoveList.h"
+#elif defined TORQUE_EXTENDED_MOVE
+   #include "T3D/gameBase/extended/extendedMoveList.h"
 #else
    #include "T3D/gameBase/std/stdMoveList.h"
 #endif
@@ -175,6 +177,8 @@ GameConnection::GameConnection()
 
 #ifdef TORQUE_HIFI_NET
    mMoveList = new HifiMoveList();
+#elif defined TORQUE_EXTENDED_MOVE
+   mMoveList = new ExtendedMoveList();
 #else
    mMoveList = new StdMoveList();
 #endif

+ 6 - 6
Engine/source/T3D/gameBase/moveList.h

@@ -76,8 +76,8 @@ public:
    /// Reset move list back to last acknowledged move.
    void resetCatchup() { mLastClientMove = mLastMoveAck; }
 
-   void collectMove();
-   void pushMove( const Move &mv );
+   virtual void collectMove();
+   virtual void pushMove( const Move &mv );
    virtual void clearMoves( U32 count );
 
    virtual void markControlDirty() { mLastClientMove = mLastMoveAck; }
@@ -85,15 +85,15 @@ public:
    void clearMismatch() { mControlMismatch = false; }
    
    /// Clear out all moves in the list and reset to initial state.
-   void reset();
+   virtual void reset();
 
    /// If there are no pending moves and the input queue is full,
    /// then the connection to the server must be clogged.
-   bool isBacklogged();
+   virtual bool isBacklogged();
 
-   bool areMovesPending();
+   virtual bool areMovesPending();
 
-   void ackMoves( U32 count );
+   virtual void ackMoves( U32 count );
 
 protected:
 

+ 44 - 17
Engine/source/T3D/gameBase/moveManager.cpp

@@ -66,20 +66,7 @@ F32 MoveManager::mYAxis_R = 0;
 U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, };
 U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, };
 
-const Move NullMove =
-{
-   /*px=*/16, /*py=*/16, /*pz=*/16,
-   /*pyaw=*/0, /*ppitch=*/0, /*proll=*/0,
-   /*x=*/0, /*y=*/0,/*z=*/0,
-   /*yaw=*/0, /*pitch=*/0, /*roll=*/0,
-   /*id=*/0, 
-   /*sendCount=*/0,
-
-   /*checksum=*/false,
-   /*deviceIsKeyboardMouse=*/false, 
-   /*freeLook=*/false,
-   /*triggers=*/{false,false,false,false,false,false}
-};
+const Move NullMove;
 
 void MoveManager::init()
 {
@@ -161,6 +148,26 @@ void MoveManager::init()
    }
 }
 
+Move::Move()
+{
+   px=16; py=16; pz=16;
+   pyaw=0; ppitch=0; proll=0;
+   x=0; y=0; z=0;
+   yaw=0; pitch=0; roll=0;
+   id=0;
+   sendCount=0;
+
+   checksum = false;
+   deviceIsKeyboardMouse = false;
+   freeLook = false;
+   trigger[0] = false;
+   trigger[1] = false;
+   trigger[2] = false;
+   trigger[3] = false;
+   trigger[4] = false;
+   trigger[5] = false;
+}
+
 static inline F32 clampFloatWrap(F32 val)
 {
    return val - F32(S32(val));
@@ -235,6 +242,11 @@ void Move::pack(BitStream *stream, const Move * basemove)
    if (!basemove)
       basemove = &NullMove;
 
+   packMove(stream, basemove, alwaysWriteAll);
+}
+
+bool Move::packMove(BitStream *stream, const Move* basemove, bool alwaysWriteAll)
+{
    S32 i;
    bool triggerDifferent = false;
    for (i=0; i < MaxTriggerKeys; i++)
@@ -272,6 +284,8 @@ void Move::pack(BitStream *stream, const Move * basemove)
          for(i = 0; i < MaxTriggerKeys; i++)
       stream->writeFlag(trigger[i]);
    }
+
+   return (triggerDifferent || somethingDifferent);
 }
 
 void Move::unpack(BitStream *stream, const Move * basemove)
@@ -280,7 +294,20 @@ void Move::unpack(BitStream *stream, const Move * basemove)
    if (!basemove)
       basemove=&NullMove;
 
-   if (alwaysReadAll || stream->readFlag())
+   bool readMove = unpackMove(stream, basemove, alwaysReadAll);
+   if(!readMove)
+      *this = *basemove;
+}
+
+bool Move::unpackMove(BitStream *stream, const Move* basemove, bool alwaysReadAll)
+{
+   bool readMove = alwaysReadAll;
+   if(!readMove)
+   {
+      readMove = stream->readFlag();
+   }
+
+   if (readMove)
    {
       pyaw = stream->readFlag() ? stream->readInt(16) : basemove->pyaw;
       ppitch = stream->readFlag() ? stream->readInt(16) : basemove->ppitch;
@@ -297,6 +324,6 @@ void Move::unpack(BitStream *stream, const Move * basemove)
          trigger[i] = triggersDiffer ? stream->readFlag() : basemove->trigger[i];
       unclamp();
    }
-   else
-      *this = *basemove;
+
+   return readMove;
 }

+ 10 - 4
Engine/source/T3D/gameBase/moveManager.h

@@ -51,10 +51,16 @@ struct Move
    bool freeLook;
    bool trigger[MaxTriggerKeys];
 
-   void pack(BitStream *stream, const Move * move = NULL);
-   void unpack(BitStream *stream, const Move * move = NULL);
-   void clamp();
-   void unclamp();
+   Move();
+
+   virtual void pack(BitStream *stream, const Move * move = NULL);
+   virtual void unpack(BitStream *stream, const Move * move = NULL);
+   virtual void clamp();
+   virtual void unclamp();
+
+protected:
+   bool packMove(BitStream *stream, const Move* basemove, bool alwaysWriteAll);
+   bool unpackMove(BitStream *stream, const Move* basemove, bool alwaysReadAll);
 };
 
 extern const Move NullMove;

+ 5 - 0
Templates/Empty PhysX/buildFiles/config/project.conf

@@ -6,6 +6,11 @@
 // a racing game.
 $TORQUE_HIFI_NET = false;
 
+// Set this to true to enable the ExtendedMove class.  This
+// allows the passing of absolute position and rotation input
+// device information from the client to the server.
+$TORQUE_EXTENDED_MOVE = false;
+
 // Configure Torque 3D
 Torque3D::beginConfig( "win32", "Empty PhysX" );
 

+ 5 - 0
Templates/Empty/buildFiles/config/project.conf

@@ -6,6 +6,11 @@
 // a racing game.
 $TORQUE_HIFI_NET = false;
 
+// Set this to true to enable the ExtendedMove class.  This
+// allows the passing of absolute position and rotation input
+// device information from the client to the server.
+$TORQUE_EXTENDED_MOVE = false;
+
 // Configure Torque 3D
 Torque3D::beginConfig( "win32", "Empty" );
 

+ 5 - 0
Templates/Full PhysX/buildFiles/config/project.conf

@@ -6,6 +6,11 @@
 // a racing game.
 $TORQUE_HIFI_NET = false;
 
+// Set this to true to enable the ExtendedMove class.  This
+// allows the passing of absolute position and rotation input
+// device information from the client to the server.
+$TORQUE_EXTENDED_MOVE = false;
+
 // Configure Torque 3D
 Torque3D::beginConfig( "win32", "Full PhysX" );
 

+ 5 - 0
Templates/Full/buildFiles/config/project.conf

@@ -6,6 +6,11 @@
 // a racing game.
 $TORQUE_HIFI_NET = false;
 
+// Set this to true to enable the ExtendedMove class.  This
+// allows the passing of absolute position and rotation input
+// device information from the client to the server.
+$TORQUE_EXTENDED_MOVE = false;
+
 // Configure Torque 3D
 Torque3D::beginConfig( "win32", "Full" );
 

+ 6 - 0
Tools/projectGenerator/modules/T3D.inc

@@ -60,11 +60,17 @@ addEngineSrcDir('T3D/gameBase');
 addEngineSrcDir('T3D/turret');
 
 global $TORQUE_HIFI_NET;
+global $TORQUE_EXTENDED_MOVE;
 if ( $TORQUE_HIFI_NET == true )
 {
    addProjectDefines( 'TORQUE_HIFI_NET' );
    addEngineSrcDir('T3D/gameBase/hifi');
 }
+elseif ( $TORQUE_EXTENDED_MOVE == true )
+{
+   addProjectDefines( 'TORQUE_EXTENDED_MOVE' );
+   addEngineSrcDir('T3D/gameBase/extended');
+}
 else
 	addEngineSrcDir('T3D/gameBase/std');