소스 검색

Added basic Walkabout with #define renamed and no editor.

Daniel Buckmaster 10 년 전
부모
커밋
f4c940f4fe

+ 563 - 15
Engine/source/T3D/aiPlayer.cpp

@@ -100,6 +100,12 @@ AIPlayer::AIPlayer()
    mTargetInLOS = false;
    mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+   mJump = None;
+   mNavSize = Regular;
+   mLinkTypes = LinkData(AllFlags);
+#endif // TORQUE_NAVIGATION_ENABLED
+
    mIsAiControlled = true;
 }
 
@@ -136,6 +142,27 @@ void AIPlayer::initPersistFields()
 
    endGroup( "AI" );
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+   addGroup("Pathfinding");
+
+   addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer),
+      "Allow the character to walk on dry land.");
+   addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer),
+      "Allow the character to use jump links.");
+   addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, AIPlayer),
+      "Allow the character to use drop links.");
+   addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, AIPlayer),
+      "Allow the character tomove in water.");
+   addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, AIPlayer),
+      "Allow the character to jump ledges.");
+   addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, AIPlayer),
+      "Allow the character to use climb links.");
+   addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, AIPlayer),
+      "Allow the character to use teleporters.");
+
+   endGroup("Pathfinding");
+#endif // TORQUE_NAVIGATION_ENABLED
+
    Parent::initPersistFields();
 }
 
@@ -152,6 +179,16 @@ bool AIPlayer::onAdd()
    return true;
 }
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+void AIPlayer::onRemove()
+{
+   clearPath();
+   clearCover();
+   clearFollow();
+   Parent::onRemove();
+}
+#endif // TORQUE_NAVIGATION_ENABLED
+
 /**
  * Sets the speed at which this AI moves
  *
@@ -168,6 +205,11 @@ void AIPlayer::setMoveSpeed( F32 speed )
 void AIPlayer::stopMove()
 {
    mMoveState = ModeStop;
+#ifdef TORQUE_NAVIGATION_ENABLED
+   clearPath();
+   clearCover();
+   clearFollow();
+#endif // TORQUE_NAVIGATION_ENABLED
 }
 
 /**
@@ -258,6 +300,32 @@ bool AIPlayer::getAIMove(Move *movePtr)
    Point3F location = eye.getPosition();
    Point3F rotation = getRotation();
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if(mDamageState == Enabled)
+   {
+      if(mMoveState != ModeStop)
+         updateNavMesh();
+      if(!mFollowData.object.isNull())
+      {
+         if(mPathData.path.isNull())
+         {
+            if((getPosition() - mFollowData.object->getPosition()).len() > mFollowData.radius)
+               followObject(mFollowData.object, mFollowData.radius);
+         }
+         else
+         {
+            if((mPathData.path->mTo - mFollowData.object->getPosition()).len() > mFollowData.radius)
+               repath();
+            else if((getPosition() - mFollowData.object->getPosition()).len() < mFollowData.radius)
+            {
+               clearPath();
+               mMoveState = ModeStop;
+            }
+         }
+      }
+   }
+#endif // TORQUE_NAVIGATION_ENABLED
+
    // Orient towards the aim point, aim object, or towards
    // our destination.
    if (mAimObject || mAimLocationSet || mMoveState != ModeStop) 
@@ -340,7 +408,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
       if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) 
       {
          mMoveState = ModeStop;
+#ifdef TORQUE_NAVIGATION_ENABLED
+         onReachDestination();
+#else
          throwCallback("onReachDestination");
+#endif // TORQUE_NAVIGATION_ENABLED
       }
       else 
       {
@@ -409,7 +481,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
                if ( mMoveState != ModeSlowing || locationDelta == 0 )
                {
                   mMoveState = ModeStuck;
+#ifdef TORQUE_NAVIGATION_ENABLED
+                  onStuck();
+#else
                   throwCallback("onMoveStuck");
+#endif // TORQUE_NAVIGATION_ENABLED
                }
             }
          }
@@ -419,28 +495,53 @@ bool AIPlayer::getAIMove(Move *movePtr)
    // Test for target location in sight if it's an object. The LOS is
    // run from the eye position to the center of the object's bounding,
    // which is not very accurate.
-   if (mAimObject)
-   {
-      if (checkInLos(mAimObject.getPointer()))
-      {
-         if (!mTargetInLOS)
-         {
-            throwCallback("onTargetEnterLOS");
-            mTargetInLOS = true;
+   if (mAimObject) {
+      MatrixF eyeMat;
+      getEyeTransform(&eyeMat);
+      eyeMat.getColumn(3,&location);
+      Point3F targetLoc = mAimObject->getBoxCenter();
+
+      // This ray ignores non-static shapes. Cast Ray returns true
+      // if it hit something.
+      RayInfo dummy;
+      if (getContainer()->castRay( location, targetLoc,
+            StaticShapeObjectType | StaticObjectType |
+            TerrainObjectType, &dummy)) {
+         if (mTargetInLOS) {
+            throwCallback( "onTargetExitLOS" );
+            mTargetInLOS = false;
          }
       }
-      else if (mTargetInLOS)
-      {
-         throwCallback("onTargetExitLOS");
-         mTargetInLOS = false;
-      }
+      else
+         if (!mTargetInLOS) {
+            throwCallback( "onTargetEnterLOS" );
+            mTargetInLOS = true;
+         }
    }
 
    // Replicate the trigger state into the move so that
    // triggers can be controlled from scripts.
-   for( S32 i = 0; i < MaxTriggerKeys; i++ )
+   for( int i = 0; i < MaxTriggerKeys; i++ )
       movePtr->trigger[i] = getImageTriggerState(i);
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if(mJump == Now)
+   {
+      movePtr->trigger[2] = true;
+      mJump = None;
+   }
+   else if(mJump == Ledge)
+   {
+      // If we're not touching the ground, jump!
+      RayInfo info;
+      if(!getContainer()->castRay(getPosition(), getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info))
+      {
+         movePtr->trigger[2] = true;
+         mJump = None;
+      }
+   }
+#endif // TORQUE_NAVIGATION_ENABLED
+
    mLastLocation = location;
 
    return true;
@@ -457,6 +558,453 @@ void AIPlayer::throwCallback( const char *name )
    Con::executef(getDataBlock(), name, getIdString());
 }
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+/**
+ * Called when we get within mMoveTolerance of our destination set using
+ * setMoveDestination(). Only fires the script callback if we are at the end
+ * of a pathfinding path, or have no pathfinding path.
+ */
+void AIPlayer::onReachDestination()
+{
+   if(!mPathData.path.isNull())
+   {
+      if(mPathData.index == mPathData.path->size() - 1)
+      {
+         // Handle looping paths.
+         if(mPathData.path->mIsLooping)
+            moveToNode(0);
+         // Otherwise end path.
+         else
+         {
+            clearPath();
+            throwCallback("onReachDestination");
+         }
+      }
+      else
+      {
+         moveToNode(mPathData.index + 1);
+         // Throw callback every time if we're on a looping path.
+         //if(mPathData.path->mIsLooping)
+            //throwCallback("onReachDestination");
+      }
+   }
+   else
+      throwCallback("onReachDestination");
+}
+
+/**
+ * Called when we move less than mMoveStuckTolerance in a tick, signalling
+ * that some obstacle is preventing us from getting where we need to go.
+ */
+void AIPlayer::onStuck()
+{
+   if(!mPathData.path.isNull())
+      repath();
+   else
+      throwCallback("onMoveStuck");
+}
+
+// --------------------------------------------------------------------------------------------
+// Pathfinding
+// --------------------------------------------------------------------------------------------
+
+void AIPlayer::clearPath()
+{
+   // Only delete if we own the path.
+   if(!mPathData.path.isNull() && mPathData.owned)
+      mPathData.path->deleteObject();
+   // Reset path data.
+   mPathData = PathData();
+}
+
+void AIPlayer::clearCover()
+{
+   // Notify cover that we are no longer on our way.
+   if(!mCoverData.cover.isNull())
+      mCoverData.cover->setOccupied(false);
+   mCoverData = CoverData();
+}
+
+void AIPlayer::clearFollow()
+{
+   mFollowData = FollowData();
+}
+
+void AIPlayer::moveToNode(S32 node)
+{
+   if(mPathData.path.isNull())
+      return;
+
+   // -1 is shorthand for 'last path node'.
+   if(node == -1)
+      node = mPathData.path->size() - 1;
+
+   // Consider slowing down on the last path node.
+   setMoveDestination(mPathData.path->getNode(node), false);
+
+   // Check flags for this segment.
+   if(mPathData.index)
+   {
+      U16 flags = mPathData.path->getFlags(node - 1);
+      // Jump if we must.
+      if(flags & LedgeFlag)
+         mJump = Ledge;
+      else if(flags & JumpFlag)
+         mJump = Now;
+      else
+         // Catch pathing errors.
+         mJump = None;
+   }
+
+   // Store current index.
+   mPathData.index = node;
+}
+
+bool AIPlayer::setPathDestination(const Point3F &pos)
+{
+   // Pathfinding only happens on the server.
+   if(!isServerObject())
+      return false;
+
+   if(!getNavMesh())
+      updateNavMesh();
+   // If we can't find a mesh, just move regularly.
+   if(!getNavMesh())
+   {
+      //setMoveDestination(pos);
+      return false;
+   }
+
+   // Create a new path.
+   NavPath *path = new NavPath();
+   if(path)
+   {
+      path->mMesh = getNavMesh();
+      path->mFrom = getPosition();
+      path->mTo = pos;
+      path->mFromSet = path->mToSet = true;
+      path->mAlwaysRender = true;
+      path->mLinkTypes = mLinkTypes;
+      path->mXray = true;
+      // Paths plan automatically upon being registered.
+      if(!path->registerObject())
+      {
+         delete path;
+         return false;
+      }
+   }
+   else
+      return false;
+
+   if(path->success())
+   {
+      // Clear any current path we might have.
+      clearPath();
+      clearCover();
+      clearFollow();
+      // Store new path.
+      mPathData.path = path;
+      mPathData.owned = true;
+      // Skip node 0, which we are currently standing on.
+      moveToNode(1);
+      return true;
+   }
+   else
+   {
+      // Just move normally if we can't path.
+      //setMoveDestination(pos, true);
+      //return;
+      //throwCallback("onPathFailed");
+      path->deleteObject();
+      return false;
+   }
+}
+
+DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),,
+   "@brief Tells the AI to find a path to the location provided\n\n"
+
+   "@param goal Coordinates in world space representing location to move to.\n"
+   "@return True if a path was found.\n\n"
+
+   "@see getPathDestination()\n"
+   "@see setMoveDestination()\n")
+{
+   return object->setPathDestination(goal);
+}
+
+Point3F AIPlayer::getPathDestination() const
+{
+   if(!mPathData.path.isNull())
+      return mPathData.path->mTo;
+   return Point3F(0, 0, 0);
+}
+
+DefineEngineMethod(AIPlayer, getPathDestination, Point3F, (),,
+   "@brief Get the AIPlayer's current pathfinding destination.\n\n"
+
+   "@return Returns a point containing the \"x y z\" position "
+   "of the AIPlayer's current path destination. If no path destination "
+   "has yet been set, this returns \"0 0 0\"."
+
+   "@see setPathDestination()\n")
+{
+	return object->getPathDestination();
+}
+
+void AIPlayer::followNavPath(NavPath *path)
+{
+   if(!isServerObject())
+      return;
+
+   // Get rid of our current path.
+   clearPath();
+   clearCover();
+   clearFollow();
+
+   // Follow new path.
+   mPathData.path = path;
+   mPathData.owned = false;
+   // Start from 0 since we might not already be there.
+   moveToNode(0);
+}
+
+DefineEngineMethod(AIPlayer, followNavPath, void, (SimObjectId obj),,
+   "@brief Tell the AIPlayer to follow a path.\n\n"
+
+   "@param obj ID of a NavPath object for the character to follow.")
+{
+   NavPath *path;
+   if(Sim::findObject(obj, path))
+      object->followNavPath(path);
+}
+
+void AIPlayer::followObject(SceneObject *obj, F32 radius)
+{
+   if(!isServerObject())
+      return;
+
+   if(setPathDestination(obj->getPosition()))
+   {
+      clearCover();
+      mFollowData.object = obj;
+      mFollowData.radius = radius;
+   }
+}
+
+DefineEngineMethod(AIPlayer, followObject, void, (SimObjectId obj, F32 radius),,
+   "@brief Tell the AIPlayer to follow another object.\n\n"
+
+   "@param obj ID of the object to follow.\n"
+   "@param radius Maximum distance we let the target escape to.")
+{
+   SceneObject *follow;
+   if(Sim::findObject(obj, follow))
+      object->followObject(follow, radius);
+}
+
+void AIPlayer::repath()
+{
+   // Ineffectual if we don't have a path, or are using someone else's.
+   if(mPathData.path.isNull() || !mPathData.owned)
+      return;
+
+   // If we're following, get their position.
+   if(!mFollowData.object.isNull())
+      mPathData.path->mTo = mFollowData.object->getPosition();
+   // Update from position and replan.
+   mPathData.path->mFrom = getPosition();
+   mPathData.path->plan();
+   // Move to first node (skip start pos).
+   moveToNode(1);
+}
+
+DefineEngineMethod(AIPlayer, repath, void, (),,
+   "@brief Tells the AI to re-plan its path. Does nothing if the character "
+   "has no path, or if it is following a mission path.\n\n")
+{
+   object->repath();
+}
+
+struct CoverSearch
+{
+   Point3F loc;
+   Point3F from;
+   F32 dist;
+   F32 best;
+   CoverPoint *point;
+   CoverSearch() : loc(0, 0, 0), from(0, 0, 0)
+   {
+      best = -FLT_MAX;
+      point = NULL;
+      dist = FLT_MAX;
+   }
+};
+
+static void findCoverCallback(SceneObject *obj, void *key)
+{
+   CoverPoint *p = dynamic_cast<CoverPoint*>(obj);
+   if(!p || p->isOccupied())
+      return;
+   CoverSearch *s = static_cast<CoverSearch*>(key);
+   Point3F dir = s->from - p->getPosition();
+   dir.normalizeSafe();
+   // Score first based on angle of cover point to enemy.
+   F32 score = mDot(p->getNormal(), dir);
+   // Score also based on distance from seeker.
+   score -= (p->getPosition() - s->loc).len() / s->dist;
+   // Finally, consider cover size.
+   score += (p->getSize() + 1) / CoverPoint::NumSizes;
+   score *= p->getQuality();
+   if(score > s->best)
+   {
+      s->best = score;
+      s->point = p;
+   }
+}
+
+bool AIPlayer::findCover(const Point3F &from, F32 radius)
+{
+   if(radius <= 0)
+      return false;
+
+   // Create a search state.
+   CoverSearch s;
+   s.loc = getPosition();
+   s.dist = radius;
+   // Direction we seek cover FROM.
+   s.from = from;
+
+   // Find cover points.
+   Box3F box(radius * 2.0f);
+   box.setCenter(getPosition());
+   getContainer()->findObjects(box, MarkerObjectType, findCoverCallback, &s);
+
+   // Go to cover!
+   if(s.point)
+   {
+      // Calling setPathDestination clears cover...
+      bool foundPath = setPathDestination(s.point->getPosition());
+      // Now store the cover info.
+      mCoverData.cover = s.point;
+      s.point->setOccupied(true);
+      return foundPath;
+   }
+   return false;
+}
+
+DefineEngineMethod(AIPlayer, findCover, S32, (Point3F from, F32 radius),,
+   "@brief Tells the AI to find cover nearby.\n\n"
+
+   "@param from   Location to find cover from (i.e., enemy position).\n"
+   "@param radius Distance to search for cover.\n"
+   "@return Cover point ID if cover was found, -1 otherwise.\n\n")
+{
+   if(object->findCover(from, radius))
+   {
+      CoverPoint* cover = object->getCover();
+      return cover ? cover->getId() : -1;
+   }
+   else
+   {
+      return -1;
+   }
+}
+
+NavMesh *AIPlayer::findNavMesh() const
+{
+   // Search for NavMeshes that contain us entirely with the smallest possible
+   // volume.
+   NavMesh *mesh = NULL;
+   SimSet *set = NavMesh::getServerSet();
+   for(U32 i = 0; i < set->size(); i++)
+   {
+      NavMesh *m = static_cast<NavMesh*>(set->at(i));
+      if(m->getWorldBox().isContained(getWorldBox()))
+      {
+         // Check that mesh size is appropriate.
+         if(mMount.object) // Should use isMounted() but it's not const. Grr.
+         {
+            if(!m->mVehicles)
+               continue;
+         }
+         else
+         {
+            if(getNavSize() == Small && !m->mSmallCharacters ||
+               getNavSize() == Regular && !m->mRegularCharacters ||
+               getNavSize() == Large && !m->mLargeCharacters)
+               continue;
+         }
+         if(!mesh || m->getWorldBox().getVolume() < mesh->getWorldBox().getVolume())
+            mesh = m;
+      }
+   }
+   return mesh;
+}
+
+DefineEngineMethod(AIPlayer, findNavMesh, S32, (),,
+   "@brief Get the NavMesh object this AIPlayer is currently using.\n\n"
+
+   "@return The ID of the NavPath object this character is using for "
+   "pathfinding. This is determined by the character's location, "
+   "navigation type and other factors. Returns -1 if no NavMesh is "
+   "found.")
+{
+   NavMesh *mesh = object->getNavMesh();
+   return mesh ? mesh->getId() : -1;
+}
+
+void AIPlayer::updateNavMesh()
+{
+   NavMesh *old = mNavMesh;
+   if(mNavMesh.isNull())
+      mNavMesh = findNavMesh();
+   else
+   {
+      if(!mNavMesh->getWorldBox().isContained(getWorldBox()))
+         mNavMesh = findNavMesh();
+   }
+   // See if we need to update our path.
+   if(mNavMesh != old && !mPathData.path.isNull())
+   {
+      setPathDestination(mPathData.path->mTo);
+   }
+}
+
+DefineEngineMethod(AIPlayer, getNavMesh, S32, (),,
+   "@brief Return the NavMesh this AIPlayer is using to navigate.\n\n")
+{
+   NavMesh *m = object->getNavMesh();
+   return m ? m->getId() : 0;
+}
+
+DefineEngineMethod(AIPlayer, setNavSize, void, (const char *size),,
+   "@brief Set the size of NavMesh this character uses. One of \"Small\", \"Regular\" or \"Large\".")
+{
+   if(!dStrcmp(size, "Small"))
+      object->setNavSize(AIPlayer::Small);
+   else if(!dStrcmp(size, "Regular"))
+      object->setNavSize(AIPlayer::Regular);
+   else if(!dStrcmp(size, "Large"))
+      object->setNavSize(AIPlayer::Large);
+   else
+      Con::errorf("AIPlayer::setNavSize: no such size '%s'.", size);
+}
+
+DefineEngineMethod(AIPlayer, getNavSize, const char*, (),,
+   "@brief Return the size of NavMesh this character uses for pathfinding.")
+{
+   switch(object->getNavSize())
+   {
+   case AIPlayer::Small:
+      return "Small";
+   case AIPlayer::Regular:
+      return "Regular";
+   case AIPlayer::Large:
+      return "Large";
+   }
+   return "";
+}
+#endif // TORQUE_NAVIGATION_ENABLED
 
 // --------------------------------------------------------------------------------------------
 // Console Functions
@@ -713,4 +1261,4 @@ DefineEngineMethod(AIPlayer, checkInFoV, bool, (ShapeBase* obj, F32 fov, bool ch
    "@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
 {
    return object->checkInFoV(obj, fov, checkEnabled);
-}
+}

+ 124 - 0
Engine/source/T3D/aiPlayer.h

@@ -27,6 +27,11 @@
 #include "T3D/player.h"
 #endif
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+#include "walkabout/navPath.h"
+#include "walkabout/navMesh.h"
+#include "walkabout/coverPoint.h"
+#endif // TORQUE_NAVIGATION_ENABLED
 
 class AIPlayer : public Player {
 
@@ -61,6 +66,90 @@ private:
    // Utility Methods
    void throwCallback( const char *name );
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+public:
+   /// Get cover we are moving to.
+   CoverPoint *getCover() { return mCoverData.cover; }
+
+private:
+   /// Should we jump?
+   enum JumpStates {
+      None,  ///< No, don't jump.
+      Now,   ///< Jump immediately.
+      Ledge, ///< Jump when we walk off a ledge.
+   } mJump;
+
+   /// Stores information about a path.
+   struct PathData {
+      /// Pointer to path object.
+      SimObjectPtr<NavPath> path;
+      /// Do we own our path? If so, we will delete it when finished.
+      bool owned;
+      /// Path node we're at.
+      U32 index;
+      /// Default constructor.
+      PathData() : path(NULL)
+      {
+         owned = false;
+         index = 0;
+      }
+   };
+
+   /// Path we are currently following.
+   PathData mPathData;
+
+   /// Clear out the current path.
+   void clearPath();
+
+   /// Get the current path we're following.
+   NavPath *getPath() { return mPathData.path; }
+
+   /// Stores information about our cover.
+   struct CoverData {
+      /// Pointer to a cover point.
+      SimObjectPtr<CoverPoint> cover;
+      /// Default constructor.
+      CoverData() : cover(NULL)
+      {
+      }
+   };
+
+   /// Current cover we're trying to get to.
+   CoverData mCoverData;
+
+   /// Stop searching for cover.
+   void clearCover();
+
+   /// Information about a target we're following.
+   struct FollowData {
+      /// Object to follow.
+      SimObjectPtr<SceneObject> object;
+      /// Distance at whcih to follow.
+      F32 radius;
+      /// Default constructor.
+      FollowData() : object(NULL)
+      {
+         radius = 5.0f;
+      }
+   };
+
+   /// Current object we're following.
+   FollowData mFollowData;
+
+   /// Stop following me!
+   void clearFollow();
+
+   /// NavMesh we pathfind on.
+   SimObjectPtr<NavMesh> mNavMesh;
+
+   /// Move to the specified node in the current path.
+   void moveToNode(S32 node);
+
+protected:
+   virtual void onReachDestination();
+   virtual void onStuck();
+#endif // TORQUE_NAVIGATION_ENABLED
+
 public:
    DECLARE_CONOBJECT( AIPlayer );
 
@@ -70,6 +159,9 @@ public:
    static void initPersistFields();
 
    bool onAdd();
+#ifdef TORQUE_NAVIGATION_ENABLED
+   void onRemove();
+#endif // TORQUE_NAVIGATION_ENABLED
 
    virtual bool getAIMove( Move *move );
 
@@ -91,6 +183,38 @@ public:
    void setMoveDestination( const Point3F &location, bool slowdown );
    Point3F getMoveDestination() const { return mMoveDestination; }
    void stopMove();
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+   /// @name Pathfinding
+   /// @{
+
+   enum NavSize {
+      Small,
+      Regular,
+      Large
+   } mNavSize;
+   void setNavSize(NavSize size) { mNavSize = size; updateNavMesh(); }
+   NavSize getNavSize() const { return mNavSize; }
+
+   bool setPathDestination(const Point3F &pos);
+   Point3F getPathDestination() const;
+
+   void followNavPath(NavPath *path);
+   void followObject(SceneObject *obj, F32 radius);
+
+   void repath();
+
+   bool findCover(const Point3F &from, F32 radius);
+
+   NavMesh *findNavMesh() const;
+   void updateNavMesh();
+   NavMesh *getNavMesh() const { return mNavMesh; }
+
+   /// Types of link we can use.
+   LinkData mLinkTypes;
+
+   /// @}
+#endif // TORQUE_NAVIGATION_ENABLED
 };
 
 #endif

+ 50 - 0
Engine/source/environment/river.cpp

@@ -1293,6 +1293,56 @@ bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info)
 	return false;
 }
 
+bool River::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere )
+{
+   Vector<const RiverSegment*> hitSegments;
+   for ( U32 i = 0; i < mSegments.size(); i++ )
+   {
+      const RiverSegment &segment = mSegments[i];
+      if ( segment.worldbounds.isOverlapped( box ) )
+      {
+         hitSegments.push_back( &segment );
+      }
+   }
+
+   if ( !hitSegments.size() )
+      return false;
+   
+   polyList->setObject( this );
+   polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
+
+   for ( U32 i = 0; i < hitSegments.size(); i++ )
+   {
+      const RiverSegment* segment = hitSegments[i];
+      for ( U32 k = 0; k < 2; k++ )
+      {
+         // gIdxArray[0] gives us the top plane (see table definition).
+         U32 idx0 = gIdxArray[0][k][0];
+         U32 idx1 = gIdxArray[0][k][1];
+         U32 idx2 = gIdxArray[0][k][2];
+
+         const Point3F &v0 = (*segment)[idx0];
+         const Point3F &v1 = (*segment)[idx1];
+         const Point3F &v2 = (*segment)[idx2];
+      
+         // Add vertices to poly list.
+         U32 i0 = polyList->addPoint(v0);
+         polyList->addPoint(v1);
+         polyList->addPoint(v2);
+
+         // Add plane between them.
+         polyList->begin(0, 0);
+         polyList->vertex(i0);
+         polyList->vertex(i0+1);
+         polyList->vertex(i0+2);
+         polyList->plane(i0, i0+1, i0+2);
+         polyList->end();
+      }
+   }
+
+   return true;
+}
+
 F32 River::getWaterCoverage( const Box3F &worldBox ) const
 {
    PROFILE_SCOPE( River_GetWaterCoverage );

+ 1 - 0
Engine/source/environment/river.h

@@ -404,6 +404,7 @@ public:
 	virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
 	virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info);
    virtual bool containsPoint( const Point3F& point ) const { return containsPoint( point, NULL ); }
+   virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
 
    // WaterObject
    virtual F32 getWaterCoverage( const Box3F &worldBox ) const;   

+ 46 - 0
Engine/source/environment/waterBlock.cpp

@@ -652,6 +652,52 @@ bool WaterBlock::castRay( const Point3F &start, const Point3F &end, RayInfo *inf
    return mObjBox.isContained(info->point);
 }
 
+bool WaterBlock::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& )
+{
+   if(context == PLC_Navigation && box.isOverlapped(mWorldBox))
+   {
+      polyList->setObject( this );
+      MatrixF mat(true);
+      Point3F pos = getPosition();
+      pos.x = pos.y = 0;
+      mat.setPosition(pos);
+      polyList->setTransform( &mat, Point3F(1, 1, 1) );
+
+      Box3F ov = box.getOverlap(mWorldBox);
+      Point3F
+         p0(ov.minExtents.x, ov.maxExtents.y, 0),
+         p1(ov.maxExtents.x, ov.maxExtents.y, 0),
+         p2(ov.maxExtents.x, ov.minExtents.y, 0),
+         p3(ov.minExtents.x, ov.minExtents.y, 0);
+
+      // Add vertices to poly list.
+      U32 v0 = polyList->addPoint(p0);
+      polyList->addPoint(p1);
+      polyList->addPoint(p2);
+      polyList->addPoint(p3);
+
+      // Add plane between first three vertices.
+      polyList->begin(0, 0);
+      polyList->vertex(v0);
+      polyList->vertex(v0+1);
+      polyList->vertex(v0+2);
+      polyList->plane(v0, v0+1, v0+2);
+      polyList->end();
+
+      // Add plane between last three vertices.
+      polyList->begin(0, 1);
+      polyList->vertex(v0+2);
+      polyList->vertex(v0+3);
+      polyList->vertex(v0);
+      polyList->plane(v0+2, v0+3, v0);
+      polyList->end();
+
+      return true;
+   }
+
+   return false;
+}
+
 F32 WaterBlock::getWaterCoverage( const Box3F &testBox ) const
 {
    Box3F wbox = getWorldBox();

+ 1 - 0
Engine/source/environment/waterBlock.h

@@ -127,6 +127,7 @@ public:
    virtual void inspectPostApply();
    virtual void setTransform( const MatrixF & mat );
    virtual void setScale( const Point3F &scale );
+   virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
 
    // WaterObject
    virtual F32 getWaterCoverage( const Box3F &worldBox ) const;

+ 42 - 0
Engine/source/environment/waterPlane.cpp

@@ -816,6 +816,48 @@ F32 WaterPlane::distanceTo( const Point3F& point ) const
       return ( point.z - getPosition().z );
 }
 
+bool WaterPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& )
+{
+   if(context == PLC_Navigation)
+   {
+      polyList->setObject( this );
+      polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
+
+      F32 z = getPosition().z;
+      Point3F
+         p0(box.minExtents.x, box.maxExtents.y, z),
+         p1(box.maxExtents.x, box.maxExtents.y, z),
+         p2(box.maxExtents.x, box.minExtents.y, z),
+         p3(box.minExtents.x, box.minExtents.y, z);
+
+      // Add vertices to poly list.
+      U32 v0 = polyList->addPoint(p0);
+      polyList->addPoint(p1);
+      polyList->addPoint(p2);
+      polyList->addPoint(p3);
+
+      // Add plane between first three vertices.
+      polyList->begin(0, 0);
+      polyList->vertex(v0);
+      polyList->vertex(v0+1);
+      polyList->vertex(v0+2);
+      polyList->plane(v0, v0+1, v0+2);
+      polyList->end();
+
+      // Add plane between last three vertices.
+      polyList->begin(0, 1);
+      polyList->vertex(v0+2);
+      polyList->vertex(v0+3);
+      polyList->vertex(v0);
+      polyList->plane(v0+2, v0+3, v0);
+      polyList->end();
+
+      return true;
+   }
+
+   return false;
+}
+
 void WaterPlane::inspectPostApply()
 {
    Parent::inspectPostApply();

+ 1 - 0
Engine/source/environment/waterPlane.h

@@ -120,6 +120,7 @@ public:
    virtual void inspectPostApply();
    virtual void setTransform( const MatrixF & mat );
    virtual F32 distanceTo( const Point3F& point ) const;
+   virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
 
    // WaterObject
    virtual F32 getWaterCoverage( const Box3F &worldBox ) const;

+ 187 - 0
Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp

@@ -0,0 +1,187 @@
+#ifdef TORQUE_WALKABOUT_EXTRAS_ENABLED
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/engineAPI.h"
+#include "platform/platform.h"
+#include "gui/containers/guiFlexibleArrayCtrl.h"
+#include "platform/types.h"
+
+GuiFlexibleArrayControl::GuiFlexibleArrayControl()
+{
+   mRows = 0;
+   mRowSpacing = 0;
+   mColSpacing = 0;
+   mIsContainer = true;
+
+   mResizing = false;
+
+   mFrozen = false;
+
+   mPadding.set(0, 0, 0, 0);
+}
+
+GuiFlexibleArrayControl::~GuiFlexibleArrayControl()
+{
+}
+
+IMPLEMENT_CONOBJECT(GuiFlexibleArrayControl);
+
+ConsoleDocClass( GuiFlexibleArrayControl,
+   "@brief A container that arranges children into a grid.\n\n"
+
+   "This container maintains a 2D grid of GUI controls. If one is added, deleted, "
+   "or resized, then the grid is updated. The insertion order into the grid is "
+   "determined by the internal order of the children (ie. the order of addition).<br>"
+
+   "Children are added to the grid by row or column until they fill the assocated "
+   "GuiFlexibleArrayControl extent (width or height). For example, a "
+   "GuiFlexibleArrayControl with 10 children, and <i>fillRowFirst</i> set to "
+   "true may be arranged as follows:\n\n"
+
+   "<pre>\n"
+   "1  2  ...3...  4\n"
+   "..5..  6  7  .8.\n"
+   "9 ....10....\n"
+   "</pre>\n"
+
+   "@tsexample\n"
+   "new GuiFlexibleArrayControl()\n"
+   "{\n"
+   "   colSpacing = \"2\";\n"
+   "   rowSpacing = \"2\";\n"
+   "   frozen = \"0\";\n"
+   "   padding = \"0 0 0 0\";\n"
+   "   //Properties not specific to this control have been omitted from this example.\n"
+   "};\n"
+   "@endtsexample\n\n"
+
+   "@see GuiDynamicCtrlArrayControl\n"
+   "@ingroup GuiContainers"
+);
+
+// ConsoleObject...
+
+void GuiFlexibleArrayControl::initPersistFields()
+{
+   addField("rowCount", TypeS32, Offset( mRows, GuiFlexibleArrayControl),
+      "Number of rows the child controls have been arranged into. This value "
+      "is calculated automatically when children are added, removed or resized; "
+      "writing it directly has no effect.");
+
+   addField("rowSpacing", TypeS32, Offset(mRowSpacing, GuiFlexibleArrayControl),
+      "Spacing between rows");
+
+   addField("colSpacing", TypeS32, Offset(mColSpacing, GuiFlexibleArrayControl),
+      "Spacing between columns");
+
+   addField("frozen", TypeBool, Offset(mFrozen, GuiFlexibleArrayControl),
+      "When true, the array will not update when new children are added or in "
+      "response to child resize events. This is useful to prevent unnecessary "
+      "resizing when adding, removing or resizing a number of child controls.");
+
+   addField("padding", TypeRectSpacingI, Offset(mPadding, GuiFlexibleArrayControl),
+      "Padding around the top, bottom, left, and right of this control. This "
+      "reduces the area available for child controls.");
+
+   Parent::initPersistFields();
+}
+
+void GuiFlexibleArrayControl::inspectPostApply()
+{
+   resize(getPosition(), getExtent());
+   Parent::inspectPostApply();
+}
+
+void GuiFlexibleArrayControl::addObject(SimObject *obj)
+{
+   Parent::addObject(obj);
+
+   if(!mFrozen)
+      refresh();
+}
+
+bool GuiFlexibleArrayControl::resize(const Point2I &newPosition, const Point2I &newExtent)
+{
+   if(size() == 0)
+      return Parent::resize(newPosition, newExtent);
+
+   if(mResizing) 
+      return false;
+
+   mResizing = true;
+
+   // Place each child.
+   S32 childcount = 0;
+   Point2I pos(mPadding.left, mPadding.top);
+   mRows = 0;
+   S32 rowHeight = 0;
+   for(S32 i = 0; i < size(); i++)
+   {
+      GuiControl *gc = dynamic_cast<GuiControl*>(operator [](i));
+      if(gc && gc->isVisible()) 
+      {
+         if(pos.x + gc->getWidth() > getExtent().x - mPadding.right)
+         {
+            pos.y += rowHeight + mRowSpacing;
+            pos.x = mPadding.left;
+            rowHeight = 0;
+            mRows++;
+         }
+         gc->setPosition(pos);
+
+         rowHeight = getMax(rowHeight, gc->getHeight());
+
+         pos.x += mColSpacing + gc->getWidth();
+         childcount++;
+      }
+   }
+
+   Point2I realExtent(newExtent);
+   realExtent.y = pos.y + rowHeight;
+   realExtent.y += mPadding.bottom;
+
+   mResizing = false;
+
+   return Parent::resize(newPosition, realExtent);
+}
+
+void GuiFlexibleArrayControl::childResized(GuiControl *child)
+{
+   Parent::childResized(child);
+
+   if ( !mFrozen )
+      refresh();
+}
+
+void GuiFlexibleArrayControl::refresh()
+{
+   resize(getPosition(), getExtent());
+}
+
+DefineEngineMethod( GuiFlexibleArrayControl, refresh, void, (),,
+   "Recalculates the position and size of this control and all its children." )
+{
+   object->refresh();
+}
+
+#endif // TORQUE_WALKABOUT_EXTRAS_ENABLED

+ 70 - 0
Engine/source/gui/containers/guiFlexibleArrayCtrl.h

@@ -0,0 +1,70 @@
+#ifdef TORQUE_WALKABOUT_EXTRAS_ENABLED
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _GUIFLEXIBLEARRAYCTRL_H_
+#define _GUIFLEXIBLEARRAYCTRL_H_
+
+#include "gui/core/guiControl.h"
+
+class GuiFlexibleArrayControl : public GuiControl
+{
+   typedef GuiControl Parent;
+
+public:
+
+   GuiFlexibleArrayControl();
+   virtual ~GuiFlexibleArrayControl();
+
+   DECLARE_CONOBJECT(GuiFlexibleArrayControl);
+   DECLARE_CATEGORY( "Gui Containers" );
+
+   // ConsoleObject
+   static void initPersistFields();
+
+   // SimObject
+   void inspectPostApply();
+
+   // SimSet
+   void addObject(SimObject *obj);
+
+   // GuiControl
+   bool resize(const Point2I &newPosition, const Point2I &newExtent);
+   void childResized(GuiControl *child);
+
+   // GuiFlexibleArrayCtrl
+   void refresh();
+
+protected:
+
+   S32 mRows;
+   S32 mRowSpacing;
+   S32 mColSpacing;
+   bool mResizing;
+   bool mFrozen;
+
+   RectSpacingI mPadding;
+};
+
+#endif // _GUIFLEXIBLEARRAYCTRL_H_
+
+#endif // TORQUE_WALKABOUT_EXTRAS_ENABLED

+ 344 - 0
Engine/source/navigation/coverPoint.cpp

@@ -0,0 +1,344 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "coverPoint.h"
+
+#include "math/mathIO.h"
+#include "scene/sceneRenderState.h"
+#include "core/stream/bitStream.h"
+#include "materials/sceneData.h"
+#include "gfx/gfxDebugEvent.h"
+#include "gfx/gfxTransformSaver.h"
+#include "gfx/gfxDrawUtil.h"
+#include "renderInstance/renderPassManager.h"
+#include "console/engineAPI.h"
+
+extern bool gEditingMission;
+
+IMPLEMENT_CO_NETOBJECT_V1(CoverPoint);
+
+ConsoleDocClass(CoverPoint,
+   "@brief A type of marker that designates a location AI characters can take cover.\n\n"
+);
+
+ImplementEnumType(CoverPointSize,
+   "The size of a cover point.\n")
+   { CoverPoint::Prone,  "Prone",  "Only provides cover when prone.\n" },
+   { CoverPoint::Crouch, "Crouch", "Only provides cover when crouching.\n" },
+   { CoverPoint::Stand,  "Stand",  "Provides cover when standing.\n" },
+EndImplementEnumType;
+
+//-----------------------------------------------------------------------------
+// Object setup and teardown
+//-----------------------------------------------------------------------------
+CoverPoint::CoverPoint()
+{
+   mNetFlags.clear(Ghostable);
+   mTypeMask |= MarkerObjectType;
+   mSize = Stand;
+   mQuality = 1.0f;
+   mOccupied = false;
+   mPeekLeft = false;
+   mPeekRight = false;
+   mPeekOver = false;
+}
+
+CoverPoint::~CoverPoint()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Object Editing
+//-----------------------------------------------------------------------------
+void CoverPoint::initPersistFields()
+{
+   addGroup("CoverPoint");
+
+   addField("size", TYPEID<CoverPointSize>(), Offset(mSize, CoverPoint),
+      "The size of this cover point.");
+
+   addField("quality", TypeF32, Offset(mQuality, CoverPoint),
+      "Reliability of this point as solid cover. (0...1)");
+
+   addField("peekLeft", TypeBool, Offset(mPeekLeft, CoverPoint),
+      "Can characters look left around this cover point?");
+   addField("peekRight", TypeBool, Offset(mPeekRight, CoverPoint),
+      "Can characters look right around this cover point?");
+   addField("peekOver", TypeBool, Offset(mPeekOver, CoverPoint),
+      "Can characters look over the top of this cover point?");
+
+   endGroup("CoverPoint");
+
+   Parent::initPersistFields();
+}
+
+bool CoverPoint::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   // Set up a 1x1x1 bounding box
+   mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f),
+               Point3F( 0.5f,  0.5f,  0.5f));
+   resetWorldBox();
+
+   if(gEditingMission)
+      onEditorEnable();
+
+   addToScene();
+
+   return true;
+}
+
+void CoverPoint::onRemove()
+{
+   if(gEditingMission)
+      onEditorDisable();
+
+   removeFromScene();
+
+   Parent::onRemove();
+
+   for(U32 i = 0; i < NumSizes; i++)
+      smVertexBuffer[i] = NULL;
+}
+
+void CoverPoint::setTransform(const MatrixF & mat)
+{
+   Parent::setTransform(mat);
+   setMaskBits(TransformMask);
+}
+
+void CoverPoint::onEditorEnable()
+{
+   mNetFlags.set(Ghostable);
+}
+
+void CoverPoint::onEditorDisable()
+{
+   mNetFlags.clear(Ghostable);
+}
+
+void CoverPoint::inspectPostApply()
+{
+   setMaskBits(TransformMask);
+}
+
+U32 CoverPoint::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+   stream->writeInt(mSize, 4);
+
+   stream->writeFlag(mOccupied);
+
+   stream->writeFlag(peekLeft());
+   stream->writeFlag(peekRight());
+   stream->writeFlag(peekOver());
+
+   // Write our transform information
+   if(stream->writeFlag(mask & TransformMask))
+   {
+      mathWrite(*stream, getTransform());
+      mathWrite(*stream, getScale());
+   }
+
+   return retMask;
+}
+
+void CoverPoint::unpackUpdate(NetConnection *conn, BitStream *stream)
+{
+   Parent::unpackUpdate(conn, stream);
+
+   mSize = (Size)stream->readInt(4);
+
+   setOccupied(stream->readFlag());
+
+   mPeekLeft = stream->readFlag();
+   mPeekRight = stream->readFlag();
+   mPeekOver = stream->readFlag();
+
+   if(stream->readFlag()) // TransformMask
+   {
+      mathRead(*stream, &mObjToWorld);
+      mathRead(*stream, &mObjScale);
+
+      setTransform(mObjToWorld);
+   }
+}
+
+//-----------------------------------------------------------------------------
+// Functionality
+//-----------------------------------------------------------------------------
+
+Point3F CoverPoint::getNormal() const
+{
+   return getTransform().getForwardVector();
+}
+
+DefineEngineMethod(CoverPoint, isOccupied, bool, (),,
+   "@brief Returns true if someone is already using this cover point.")
+{
+   return object->isOccupied();
+}
+
+//-----------------------------------------------------------------------------
+// Object Rendering
+//-----------------------------------------------------------------------------
+
+GFXStateBlockRef CoverPoint::smNormalSB;
+GFXVertexBufferHandle<CoverPoint::VertexType> CoverPoint::smVertexBuffer[CoverPoint::NumSizes];
+
+void CoverPoint::initGFXResources()
+{
+   if(smVertexBuffer[0] != NULL)
+      return;
+
+   static const Point3F planePoints[4] = 
+   {
+      Point3F(-1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 2.0f),
+      Point3F( 1.0f, 0.0f, 0.0f), Point3F( 1.0f, 0.0f, 2.0f),
+   };
+
+   static const U32 planeFaces[6] =
+   {
+      0, 1, 2,
+      1, 2, 3
+   };
+
+   static const Point3F scales[NumSizes] =
+   {
+      Point3F(1.0f, 1.0f, 0.5f), // Prone
+      Point3F(1.0f, 1.0f, 1.0f), // Crouch
+      Point3F(1.0f, 1.0f, 2.0f)  // Stand
+   };
+
+   static const ColorI colours[NumSizes] =
+   {
+      ColorI(180,   0,  0, 128), // Prone
+      ColorI(250, 200, 90, 128), // Crouch
+      ColorI( 80, 190, 20, 128)  // Stand
+   };
+
+   for(U32 i = 0; i < NumSizes; i++)
+   {
+      // Fill the vertex buffer
+      VertexType *pVert = NULL;
+      smVertexBuffer[i].set(GFX, 6, GFXBufferTypeStatic);
+
+      pVert = smVertexBuffer[i].lock();
+      for(U32 j = 0; j < 6; j++)
+      {
+         pVert[j].point  = planePoints[planeFaces[j]] * scales[i] * 0.5f;
+         pVert[j].normal = Point3F(0.0f, -1.0f, 0.0f);
+         pVert[j].color  = colours[i];
+      }
+      smVertexBuffer[i].unlock();
+   }
+
+   // Set up our StateBlock
+   GFXStateBlockDesc desc;
+   desc.cullDefined = true;
+   desc.cullMode = GFXCullNone;
+   desc.setBlend(true);
+   smNormalSB = GFX->createStateBlock(desc);
+}
+
+void CoverPoint::prepRenderImage(SceneRenderState *state)
+{
+   // Allocate an ObjectRenderInst so that we can submit it to the RenderPassManager
+   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+
+   // Now bind our rendering function so that it will get called
+   ri->renderDelegate.bind(this, &CoverPoint::render);
+
+   // Set our RenderInst as a standard object render
+   ri->type = RenderPassManager::RIT_Editor;
+
+   // Set our sorting keys to a default value
+   ri->defaultKey = 0;
+   ri->defaultKey2 = 0;
+
+   // Submit our RenderInst to the RenderPassManager
+   state->getRenderPass()->addInst(ri);
+}
+
+void CoverPoint::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat)
+{
+   initGFXResources();
+
+   if(overrideMat)
+      return;
+
+   if(smVertexBuffer[mSize].isNull())
+      return;
+
+   PROFILE_SCOPE(CoverPoint_Render);
+
+   // Set up a GFX debug event (this helps with debugging rendering events in external tools)
+   GFXDEBUGEVENT_SCOPE(CoverPoint_Render, ColorI::RED);
+
+   // GFXTransformSaver is a handy helper class that restores
+   // the current GFX matrices to their original values when
+   // it goes out of scope at the end of the function
+   GFXTransformSaver saver;
+
+   // Calculate our object to world transform matrix
+   MatrixF objectToWorld = getRenderTransform();
+   objectToWorld.scale(getScale());
+
+   // Apply our object transform
+   GFX->multWorld(objectToWorld);
+
+   // Set the state block
+   GFX->setStateBlock(smNormalSB);
+
+   // Set up the "generic" shaders
+   // These handle rendering on GFX layers that don't support
+   // fixed function. Otherwise they disable shaders.
+   GFX->setupGenericShaders(GFXDevice::GSModColorTexture);
+
+   // Set the vertex buffer
+   GFX->setVertexBuffer(smVertexBuffer[mSize]);
+
+   // Draw our triangles
+   GFX->drawPrimitive(GFXTriangleList, 0, 2);
+
+   // Data for decorations.
+   GFXStateBlockDesc desc;
+   F32 height = (float)(mSize + 1) / NumSizes * 2.0f;
+
+   // Draw an X if we're occupied.
+   if(isOccupied())
+   {
+      GFX->getDrawUtil()->drawArrow(desc, Point3F(-0.5, 0, 0), Point3F(0.5, 0, height), ColorI::RED);
+      GFX->getDrawUtil()->drawArrow(desc, Point3F(0.5, 0, 0), Point3F(-0.5, 0, height), ColorI::RED);
+   }
+
+   // Draw arrows to represent peek directions.
+   if(peekLeft())
+      GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(-0.5, 0, height * 0.5), ColorI::GREEN);
+   if(peekRight())
+      GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(0.5, 0, height * 0.5), ColorI::GREEN);
+   if(peekOver())
+      GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(0, 0, height), ColorI::GREEN);
+}

+ 136 - 0
Engine/source/navigation/coverPoint.h

@@ -0,0 +1,136 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _COVERPOINT_H_
+#define _COVERPOINT_H_
+
+#ifndef _SCENEOBJECT_H_
+#include "scene/sceneObject.h"
+#endif
+#ifndef _GFXSTATEBLOCK_H_
+#include "gfx/gfxStateBlock.h"
+#endif
+#ifndef _GFXVERTEXBUFFER_H_
+#include "gfx/gfxVertexBuffer.h"
+#endif
+#ifndef _GFXPRIMITIVEBUFFER_H_
+#include "gfx/gfxPrimitiveBuffer.h"
+#endif
+
+class BaseMatInstance;
+
+class CoverPoint : public SceneObject
+{
+   typedef SceneObject Parent;
+
+   /// Network mask bits.
+   enum MaskBits 
+   {
+      TransformMask = Parent::NextFreeMask << 0,
+      NextFreeMask  = Parent::NextFreeMask << 1
+   };
+
+public:
+   CoverPoint();
+   virtual ~CoverPoint();
+
+   DECLARE_CONOBJECT(CoverPoint);
+
+   /// Amount of cover provided at this point.
+   enum Size {
+      Prone,
+      Crouch,
+      Stand,
+      NumSizes
+   };
+
+   void setSize(Size size) { mSize = size; setMaskBits(TransformMask); }
+   Size getSize() const { return mSize; }
+
+   /// Is this point occupied?
+   bool isOccupied() const { return mOccupied; }
+   void setOccupied(bool occ) { mOccupied = occ; setMaskBits(TransformMask); }
+
+   /// Get the normal of this point (i.e., the direction from which it provides cover).
+   Point3F getNormal() const;
+
+   F32 getQuality() const { return mQuality; }
+
+   bool peekLeft() const { return mPeekLeft; }
+   bool peekRight() const { return mPeekRight; }
+   bool peekOver() const { return mPeekOver; }
+
+   void setPeek(bool left, bool right, bool over) { mPeekLeft = left, mPeekRight = right, mPeekOver = over; }
+
+   /// Init static buffers for rendering.
+   static void initGFXResources();
+
+   /// @name SceneObject
+   /// @{
+   static void initPersistFields();
+
+   bool onAdd();
+   void onRemove();
+
+   void onEditorEnable();
+   void onEditorDisable();
+   void inspectPostApply();
+
+   void setTransform(const MatrixF &mat);
+
+   void prepRenderImage(SceneRenderState *state);
+   void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
+   /// @}
+
+   /// @name NetObject
+   /// @{
+   U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+   void unpackUpdate(NetConnection *conn, BitStream *stream);
+   /// @}
+
+protected:
+
+private:
+   typedef GFXVertexPCN VertexType;
+   static GFXStateBlockRef smNormalSB;
+   static GFXVertexBufferHandle<VertexType> smVertexBuffer[NumSizes];
+
+   /// Size of this point.
+   Size mSize;
+   /// Quality of this point as cover.
+   F32 mQuality;
+
+   /// Can we look left around this cover?
+   bool mPeekLeft;
+   /// Can we look right around this cover?
+   bool mPeekRight;
+   /// Can we look up over this cover?
+   bool mPeekOver;
+
+   /// Is this point currently occupied?
+   bool mOccupied;
+};
+
+typedef CoverPoint::Size CoverPointSize;
+DefineEnumType(CoverPointSize);
+
+#endif

+ 641 - 0
Engine/source/navigation/guiNavEditorCtrl.cpp

@@ -0,0 +1,641 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "platform/platform.h"
+#include "guiNavEditorCtrl.h"
+#include "duDebugDrawTorque.h"
+#include "console/engineAPI.h"
+
+#include "console/consoleTypes.h"
+#include "math/util/frustum.h"
+#include "math/mathUtils.h"
+#include "gfx/primBuilder.h"
+#include "gfx/gfxDrawUtil.h"
+#include "scene/sceneRenderState.h"
+#include "scene/sceneManager.h"
+#include "gui/core/guiCanvas.h"
+#include "gui/buttons/guiButtonCtrl.h"
+#include "gui/worldEditor/undoActions.h"
+#include "T3D/gameBase/gameConnection.h"
+
+IMPLEMENT_CONOBJECT(GuiNavEditorCtrl);
+
+ConsoleDocClass(GuiNavEditorCtrl,
+                "@brief GUI tool that makes up the Navigation Editor\n\n"
+                "Editor use only.\n\n"
+                "@internal"
+                );
+
+// Each of the mode names directly correlates with the Nav Editor's tool palette.
+const String GuiNavEditorCtrl::mSelectMode = "SelectMode";
+const String GuiNavEditorCtrl::mLinkMode = "LinkMode";
+const String GuiNavEditorCtrl::mCoverMode = "CoverMode";
+const String GuiNavEditorCtrl::mTileMode = "TileMode";
+const String GuiNavEditorCtrl::mTestMode = "TestMode";
+
+GuiNavEditorCtrl::GuiNavEditorCtrl()
+{
+   mMode = mSelectMode;
+   mIsDirty = false;
+   mStartDragMousePoint = InvalidMousePoint;
+   mMesh = NULL;
+   mCurTile = mTile = -1;
+   mPlayer = mCurPlayer = NULL;
+   mSpawnClass = mSpawnDatablock = "";
+   mLinkStart = Point3F::Max;
+   mLink = mCurLink = -1;
+}
+
+GuiNavEditorCtrl::~GuiNavEditorCtrl()
+{
+}
+
+void GuiNavEditorUndoAction::undo()
+{
+}
+
+bool GuiNavEditorCtrl::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   GFXStateBlockDesc desc;
+   desc.fillMode = GFXFillSolid;
+   desc.setBlend(false);
+   desc.setZReadWrite(false, false);
+   desc.setCullMode(GFXCullNone);
+
+   mZDisableSB = GFX->createStateBlock(desc);
+
+   desc.setZReadWrite(true, true);
+   mZEnableSB = GFX->createStateBlock(desc);
+
+   SceneManager::getPreRenderSignal().notify(this, &GuiNavEditorCtrl::_prepRenderImage);
+
+   return true;
+}
+
+void GuiNavEditorCtrl::initPersistFields()
+{
+   addField("isDirty", TypeBool, Offset(mIsDirty, GuiNavEditorCtrl));
+
+   addField("spawnClass", TypeRealString, Offset(mSpawnClass, GuiNavEditorCtrl),
+      "Class of object to spawn in test mode.");
+   addField("spawnDatablock", TypeRealString, Offset(mSpawnDatablock, GuiNavEditorCtrl),
+      "Datablock to give new objects in test mode.");
+
+   Parent::initPersistFields();
+}
+
+void GuiNavEditorCtrl::onSleep()
+{
+   Parent::onSleep();
+
+   //mMode = mSelectMode;
+}
+
+void GuiNavEditorCtrl::selectMesh(NavMesh *mesh)
+{
+   mesh->setSelected(true);
+   mMesh = mesh;
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, selectMesh, void, (S32 id),,
+   "@brief Select a NavMesh object.")
+{
+   NavMesh *obj;
+   if(Sim::findObject(id, obj))
+      object->selectMesh(obj);
+}
+
+S32 GuiNavEditorCtrl::getMeshId()
+{
+   return mMesh.isNull() ? 0 : mMesh->getId();
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),,
+   "@brief Select a NavMesh object.")
+{
+   return object->getMeshId();
+}
+
+S32 GuiNavEditorCtrl::getPlayerId()
+{
+   return mPlayer.isNull() ? 0 : mPlayer->getId();
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, getPlayer, S32, (),,
+   "@brief Select a NavMesh object.")
+{
+   return object->getPlayerId();
+}
+
+void GuiNavEditorCtrl::deselect()
+{
+   if(!mMesh.isNull())
+      mMesh->setSelected(false);
+   mMesh = NULL;
+   mPlayer = mCurPlayer = NULL;
+   mCurTile = mTile = -1;
+   mLinkStart = Point3F::Max;
+   mLink = mCurLink = -1;
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),,
+   "@brief Deselect whatever is currently selected in the editor.")
+{
+   object->deselect();
+}
+
+void GuiNavEditorCtrl::deleteLink()
+{
+   if(!mMesh.isNull() && mLink != -1)
+   {
+      mMesh->selectLink(mLink, false);
+      mMesh->deleteLink(mLink);
+      mLink = -1;
+      Con::executef(this, "onLinkDeselected");
+   }
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, deleteLink, void, (),,
+   "@brief Delete the currently selected link.")
+{
+   object->deleteLink();
+}
+
+void GuiNavEditorCtrl::setLinkFlags(const LinkData &d)
+{
+   if(mMode == mLinkMode && !mMesh.isNull() && mLink != -1)
+   {
+      mMesh->setLinkFlags(mLink, d);
+   }
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, setLinkFlags, void, (U32 flags),,
+   "@Brief Set jump and drop properties of the selected link.")
+{
+   object->setLinkFlags(LinkData(flags));
+}
+
+void GuiNavEditorCtrl::buildTile()
+{
+   if(!mMesh.isNull() && mTile != -1)
+      mMesh->buildTile(mTile);
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, buildTile, void, (),,
+   "@brief Build the currently selected tile.")
+{
+   object->buildTile();
+}
+
+void GuiNavEditorCtrl::spawnPlayer(const Point3F &pos)
+{
+   SceneObject *obj = (SceneObject*)Sim::spawnObject(mSpawnClass, mSpawnDatablock);
+   if(obj)
+   {
+      MatrixF mat(true);
+      mat.setPosition(pos);
+      obj->setTransform(mat);
+      SimObject* cleanup = Sim::findObject("MissionCleanup");
+      if(cleanup)
+      {
+         SimGroup* missionCleanup = dynamic_cast<SimGroup*>(cleanup);
+         missionCleanup->addObject(obj);
+      }
+      mPlayer = static_cast<AIPlayer*>(obj);
+      Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags()));
+   }
+}
+
+DefineEngineMethod(GuiNavEditorCtrl, spawnPlayer, void, (),,
+                   "@brief Spawn an AIPlayer at the centre of the screen.")
+{
+   Point3F c;
+   if(object->get3DCentre(c))
+      object->spawnPlayer(c);
+}
+
+void GuiNavEditorCtrl::get3DCursor(GuiCursor *&cursor,
+                                   bool &visible,
+                                   const Gui3DMouseEvent &event_)
+{
+   //cursor = mAddNodeCursor;
+   //visible = false;
+
+   cursor = NULL;
+   visible = false;
+
+   GuiCanvas *root = getRoot();
+   if(!root)
+      return;
+
+   S32 currCursor = PlatformCursorController::curArrow;
+
+   if(root->mCursorChanged == currCursor)
+      return;
+
+   PlatformWindow *window = root->getPlatformWindow();
+   PlatformCursorController *controller = window->getCursorController();
+
+   // We've already changed the cursor,
+   // so set it back before we change it again.
+   if(root->mCursorChanged != -1)
+      controller->popCursor();
+
+   // Now change the cursor shape
+   controller->pushCursor(currCursor);
+   root->mCursorChanged = currCursor;
+}
+
+bool GuiNavEditorCtrl::get3DCentre(Point3F &pos)
+{
+   Point3F screen, start, end;
+
+   screen.set(getExtent().x / 2, getExtent().y / 2, 0);
+   unproject(screen, &start);
+
+   screen.z = 1.0f;
+   unproject(screen, &end);
+
+   RayInfo ri;
+   if(gServerContainer.castRay(start, end, StaticObjectType, &ri))
+   {
+      pos = ri.point;
+      return true;
+   }
+   return false;
+}
+
+void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
+{
+   mGizmo->on3DMouseDown(event);
+
+   if(!isFirstResponder())
+      setFirstResponder();
+
+   mouseLock();
+
+   // Construct a LineSegment from the camera position to 1000 meters away in
+   // the direction clicked.
+   // If that segment hits the terrain, truncate the ray to only be that length.
+
+   Point3F startPnt = event.pos;
+   Point3F endPnt = event.pos + event.vec * 1000.0f;
+
+   RayInfo ri;
+
+   U8 keys = Input::getModifierKeys();
+   bool shift = keys & SI_LSHIFT;
+   bool ctrl = keys & SI_LCTRL;
+   bool alt = keys & SI_LALT;
+
+   if(mMode == mLinkMode && !mMesh.isNull())
+   {
+      if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+      {
+         U32 link = mMesh->getLink(ri.point);
+         if(link != -1)
+         {
+            if(mLink != -1)
+               mMesh->selectLink(mLink, false);
+            mMesh->selectLink(link, true, false);
+            mLink = link;
+            LinkData d = mMesh->getLinkFlags(mLink);
+            Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()));
+         }
+         else
+         {
+            if(mLink != -1)
+            {
+               mMesh->selectLink(mLink, false);
+               mLink = -1;
+               Con::executef(this, "onLinkDeselected");
+            }
+            else
+            {
+               if(mLinkStart != Point3F::Max)
+               {
+                  mMesh->addLink(mLinkStart, ri.point);
+                  if(!shift)
+                     mLinkStart = Point3F::Max;
+               }
+               else
+               {
+                  mLinkStart = ri.point;
+               }
+            }
+         }
+      }
+      else
+      {
+         mMesh->selectLink(mLink, false);
+         mLink = -1;
+         Con::executef(this, "onLinkDeselected");
+      }
+   }
+
+   if(mMode == mTileMode && !mMesh.isNull())
+   {
+      if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri))
+      {
+         mTile = mMesh->getTile(ri.point);
+         dd.clear();
+         mMesh->renderTileData(dd, mTile);
+      }
+   }
+
+   if(mMode == mTestMode)
+   {
+      // Spawn new character
+      if(ctrl)
+      {
+         if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+            spawnPlayer(ri.point);
+      }
+      // Deselect character
+      else if(shift)
+      {
+         mPlayer = NULL;
+         Con::executef(this, "onPlayerDeselected");
+      }
+      // Select/move character
+      else
+      {
+         if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri))
+         {
+            if(dynamic_cast<AIPlayer*>(ri.object))
+            {
+               mPlayer = dynamic_cast<AIPlayer*>(ri.object);
+               Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags()));
+            }
+         }
+         else if(!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+            mPlayer->setPathDestination(ri.point);
+      }
+   }
+}
+
+void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
+{
+   // Keep the Gizmo up to date.
+   mGizmo->on3DMouseUp(event);
+
+   mouseUnlock();
+}
+
+void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
+{
+   //if(mSelRiver != NULL && mSelNode != -1)
+      //mGizmo->on3DMouseMove(event);
+
+   Point3F startPnt = event.pos;
+   Point3F endPnt = event.pos + event.vec * 1000.0f;
+
+   RayInfo ri;
+
+   if(mMode == mLinkMode && !mMesh.isNull())
+   {
+      if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+      {
+         U32 link = mMesh->getLink(ri.point);
+         if(link != -1)
+         {
+            if(link != mLink)
+            {
+               if(mCurLink != -1)
+                  mMesh->selectLink(mCurLink, false);
+               mMesh->selectLink(link, true, true);
+            }
+            mCurLink = link;
+         }
+         else
+         {
+            if(mCurLink != mLink)
+               mMesh->selectLink(mCurLink, false);
+            mCurLink = -1;
+         }
+      }
+      else
+      {
+         mMesh->selectLink(mCurLink, false);
+         mCurLink = -1;
+      }
+   }
+
+   // Select a tile from our current NavMesh.
+   if(mMode == mTileMode && !mMesh.isNull())
+   {
+      if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+         mCurTile = mMesh->getTile(ri.point);
+      else
+         mCurTile = -1;
+   }
+
+   if(mMode == mTestMode)
+   {
+      if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri))
+         mCurPlayer = dynamic_cast<AIPlayer*>(ri.object);
+      else
+         mCurPlayer = NULL;
+   }
+}
+
+void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
+{
+   mGizmo->on3DMouseDragged(event);
+   if(mGizmo->isDirty())
+   {
+      Point3F pos = mGizmo->getPosition();
+      Point3F scale = mGizmo->getScale();
+      const MatrixF &mat = mGizmo->getTransform();
+      VectorF normal;
+      mat.getColumn(2, &normal);
+
+      //mSelRiver->setNode(pos, scale.x, scale.z, normal, mSelNode);
+      mIsDirty = true;
+   }
+}
+
+void GuiNavEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event)
+{
+}
+
+void GuiNavEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event)
+{
+}
+
+void GuiNavEditorCtrl::updateGuiInfo()
+{
+}
+
+void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect)
+{
+   PROFILE_SCOPE(GuiNavEditorCtrl_OnRender);
+
+   Parent::onRender(offset, updateRect);
+   return;
+}
+
+static void renderBoxOutline(const Box3F &box, const ColorI &col)
+{
+   if(box != Box3F::Invalid)
+   {
+      GFXStateBlockDesc desc;
+      desc.setCullMode(GFXCullNone);
+      desc.setFillModeSolid();
+      desc.setZReadWrite(true, false);
+      desc.setBlend(true);
+      GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20));
+      desc.setFillModeWireframe();
+      desc.setBlend(false);
+      GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255));
+   }
+}
+
+void GuiNavEditorCtrl::renderScene(const RectI & updateRect)
+{
+   GFX->setStateBlock(mZDisableSB);
+
+   // get the projected size...
+   GameConnection* connection = GameConnection::getConnectionToServer();
+   if(!connection)
+      return;
+
+   // Grab the camera's transform
+   MatrixF mat;
+   connection->getControlCameraTransform(0, &mat);
+
+   // Get the camera position
+   Point3F camPos;
+   mat.getColumn(3,&camPos);
+
+   if(mMode == mLinkMode)
+   {
+      if(mLinkStart != Point3F::Max)
+      {
+         GFXStateBlockDesc desc;
+         desc.setBlend(false);
+         desc.setZReadWrite(true ,true);
+         MatrixF mat(true);
+         mat.setPosition(mLinkStart);
+         Point3F scale(0.8f, 0.8f, 0.8f);
+         GFX->getDrawUtil()->drawTransform(desc, mat, &scale);
+      }
+   }
+
+   if(mMode == mTileMode && !mMesh.isNull())
+   {
+      renderBoxOutline(mMesh->getTileBox(mCurTile), ColorI::BLUE);
+      renderBoxOutline(mMesh->getTileBox(mTile), ColorI::GREEN);
+      if(Con::getBoolVariable("$Nav::Editor::renderVoxels", false)) dd.renderGroup(0);
+      if(Con::getBoolVariable("$Nav::Editor::renderInput", false))
+      {
+         dd.depthMask(false);
+         dd.renderGroup(1);
+         dd.depthMask(true);
+      }
+   }
+
+   if(mMode == mTestMode)
+   {
+      if(!mCurPlayer.isNull())
+         renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE);
+      if(!mPlayer.isNull())
+         renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN);
+   }
+
+   duDebugDrawTorque d;
+   if(!mMesh.isNull())
+      mMesh->renderLinks(d);
+   d.render();
+
+   // Now draw all the 2d stuff!
+   GFX->setClipRect(updateRect);
+}
+
+bool GuiNavEditorCtrl::getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos)
+{
+   // Find clicked point on the terrain
+
+   Point3F startPnt = event.pos;
+   Point3F endPnt = event.pos + event.vec * 1000.0f;
+
+   RayInfo ri;
+   bool hit;
+
+   hit = gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri);
+   tpos = ri.point;
+
+   return hit;
+}
+
+void GuiNavEditorCtrl::setMode(String mode, bool sourceShortcut = false)
+{
+   mMode = mode;
+   Con::executef(this, "onModeSet", mode);
+
+   if(sourceShortcut)
+      Con::executef(this, "paletteSync", mode);
+}
+
+void GuiNavEditorCtrl::submitUndo(const UTF8 *name)
+{
+   // Grab the mission editor undo manager.
+   UndoManager *undoMan = NULL;
+   if(!Sim::findObject("EUndoManager", undoMan))
+   {
+      Con::errorf("GuiNavEditorCtrl::submitUndo() - EUndoManager not found!");
+      return;
+   }
+
+   // Setup the action.
+   GuiNavEditorUndoAction *action = new GuiNavEditorUndoAction(name);
+
+   action->mNavEditor = this;
+
+   undoMan->addAction(action);
+}
+
+void GuiNavEditorCtrl::_prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* state)
+{
+   /*if(isAwake() && River::smEditorOpen && mSelRiver)
+   {
+      ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+      ri->type = RenderPassManager::RIT_Editor;
+      ri->renderDelegate.bind(this, &GuiNavEditorCtrl::_renderSelectedRiver);
+      ri->defaultKey = 100;
+      state->getRenderPass()->addInst(ri);
+   }*/
+}
+
+ConsoleMethod(GuiNavEditorCtrl, getMode, const char*, 2, 2, "")
+{
+   return object->getMode();
+}
+
+ConsoleMethod(GuiNavEditorCtrl, setMode, void, 3, 3, "setMode(String mode)")
+{
+   String newMode = (argv[2]);
+   object->setMode(newMode);
+}

+ 189 - 0
Engine/source/navigation/guiNavEditorCtrl.h

@@ -0,0 +1,189 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _GUINAVEDITORCTRL_H_
+#define _GUINAVEDITORCTRL_H_
+
+#ifndef _EDITTSCTRL_H_
+#include "gui/worldEditor/editTSCtrl.h"
+#endif
+#ifndef _UNDO_H_
+#include "util/undo.h"
+#endif
+#ifndef _GIZMO_H_
+#include "gui/worldEditor/gizmo.h"
+#endif
+
+#include "navMesh.h"
+#include "T3D/aiPlayer.h"
+
+struct ObjectRenderInst;
+class SceneManager;
+class SceneRenderState;
+class BaseMatInstance;
+
+class GuiNavEditorCtrl : public EditTSCtrl
+{
+   typedef EditTSCtrl Parent;
+   friend class GuiNavEditorUndoAction;
+
+public:
+   static const String mSelectMode;
+   static const String mLinkMode;
+   static const String mCoverMode;
+   static const String mTileMode;
+   static const String mTestMode;
+
+   GuiNavEditorCtrl();
+   ~GuiNavEditorCtrl();
+
+   DECLARE_CONOBJECT(GuiNavEditorCtrl);
+
+   /// @name SimObject
+   /// @{
+
+   bool onAdd();
+   static void initPersistFields();
+
+   /// @}
+
+   /// @name GuiControl
+   /// @{
+
+   virtual void onSleep();
+   virtual void onRender(Point2I offset, const RectI &updateRect);
+
+   /// @}
+
+   /// @name EditTSCtrl
+   /// @{
+
+   void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_);
+   bool get3DCentre(Point3F &pos);
+   void on3DMouseDown(const Gui3DMouseEvent & event);
+   void on3DMouseUp(const Gui3DMouseEvent & event);
+   void on3DMouseMove(const Gui3DMouseEvent & event);
+   void on3DMouseDragged(const Gui3DMouseEvent & event);
+   void on3DMouseEnter(const Gui3DMouseEvent & event);
+   void on3DMouseLeave(const Gui3DMouseEvent & event);
+   void updateGuiInfo();      
+   void renderScene(const RectI & updateRect);
+
+   /// @}
+
+   /// @name GuiNavEditorCtrl
+   /// @{
+
+   bool getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos);
+
+   void setMode(String mode, bool sourceShortcut);
+   String getMode() { return mMode; }
+
+   void selectMesh(NavMesh *mesh);
+   void deselect();
+
+   S32 getMeshId();
+   S32 getPlayerId();
+
+   String mSpawnClass;
+   String mSpawnDatablock;
+
+   void deleteLink();
+   void setLinkFlags(const LinkData &d);
+
+   void buildTile();
+
+   void spawnPlayer(const Point3F &pos);
+
+   /// @}
+
+protected:
+
+   void _prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* sceneState);
+
+   void submitUndo(const UTF8 *name = "Action");
+
+   GFXStateBlockRef mZDisableSB;
+   GFXStateBlockRef mZEnableSB;
+
+   bool mSavedDrag;
+   bool mIsDirty;
+
+   String mMode;
+
+   /// Currently-selected NavMesh.
+   SimObjectPtr<NavMesh> mMesh;
+
+   /// @name Link mode
+   /// @{
+
+   Point3F mLinkStart;
+   S32 mCurLink;
+   S32 mLink;
+
+   /// @}
+
+   /// @name Tile mode
+   /// @{
+
+   S32 mCurTile;
+   S32 mTile;
+
+   duDebugDrawTorque dd;
+
+   /// @}
+
+   /// @name Test mode
+   /// @{
+
+   SimObjectPtr<AIPlayer> mPlayer;
+   SimObjectPtr<AIPlayer> mCurPlayer;
+
+   /// @}
+
+   Gui3DMouseEvent mLastMouseEvent;
+
+#define InvalidMousePoint Point2I(-100,-100)
+   Point2I mStartDragMousePoint;
+};
+
+class GuiNavEditorUndoAction : public UndoAction
+{
+public:
+   GuiNavEditorUndoAction(const UTF8* actionName) : UndoAction(actionName)
+   {
+   }
+
+   GuiNavEditorCtrl *mNavEditor;         
+
+   SimObjectId mObjId;
+   F32 mMetersPerSegment;
+   U32 mSegmentsPerBatch;
+
+   virtual void undo();
+   virtual void redo() { undo(); }
+};
+
+#endif
+
+
+

+ 69 - 0
Engine/source/navigation/navContext.cpp

@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "navContext.h"
+#include "console/sim.h"
+
+void NavContext::doResetLog()
+{
+}
+
+void NavContext::log(const rcLogCategory category, const String &msg)
+{
+   doLog(category, msg.c_str(), msg.length());
+}
+
+void NavContext::doLog(const rcLogCategory category, const char* msg, const int len)
+{
+   if(category == RC_LOG_ERROR)
+      Con::errorf(msg);
+   else
+      Con::printf(msg);
+}
+
+void NavContext::doResetTimers()
+{
+   for(U32 i = 0; i < RC_MAX_TIMERS; i++)
+   {
+      mTimers[i][0] = -1;
+      mTimers[i][1] = -1;
+   }
+}
+
+void NavContext::doStartTimer(const rcTimerLabel label)
+{
+   // Store starting time.
+   mTimers[label][0] = Platform::getRealMilliseconds();
+}
+
+void NavContext::doStopTimer(const rcTimerLabel label)
+{
+   // Compute final time based on starting time.
+   mTimers[label][1] = Platform::getRealMilliseconds() - mTimers[label][0];
+}
+
+int NavContext::doGetAccumulatedTime(const rcTimerLabel label) const
+{
+   return mTimers[label][1] == -1
+      ? Platform::getRealMilliseconds() - mTimers[label][0]
+      : mTimers[label][1];
+}

+ 68 - 0
Engine/source/navigation/navContext.h

@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2014 Daniel Buckmaster
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _NAV_CONTEXT_H_
+#define _NAV_CONTEXT_H_
+
+#include "torqueRecast.h"
+#include <Recast.h>
+
+/// @brief Implements the rcContext interface in Torque.
+class NavContext: public rcContext {
+public:
+   /// Default constructor.
+   NavContext() : rcContext(true) { doResetTimers(); }
+
+   void log(const rcLogCategory category, const String &msg);
+
+protected:
+   /// Clears all log entries.
+   virtual void doResetLog();
+
+   /// Logs a message.
+   /// @param[in] category The category of the message.
+   /// @param[in] msg      The formatted message.
+   /// @param[in] len      The length of the formatted message.
+   virtual void doLog(const rcLogCategory category, const char* msg, const int len);
+
+   /// Clears all timers. (Resets all to unused.)
+   virtual void doResetTimers();
+
+   /// Starts the specified performance timer.
+   /// @param[in] label The category of timer.
+   virtual void doStartTimer(const rcTimerLabel label);
+
+   /// Stops the specified performance timer.
+   /// @param[in] label The category of the timer.
+   virtual void doStopTimer(const rcTimerLabel label);
+
+   /// Returns the total accumulated time of the specified performance timer.
+   /// @param[in] label The category of the timer.
+   /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started.
+   virtual int doGetAccumulatedTime(const rcTimerLabel label) const;
+
+private:
+   /// Store start time and final time for each timer.
+   S32 mTimers[RC_MAX_TIMERS][2];
+};
+
+#endif

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 722 - 27
Engine/source/navigation/navMesh.cpp


+ 154 - 2
Engine/source/navigation/navMesh.h

@@ -1,5 +1,5 @@
 //-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
+// Copyright (c) 2014 Daniel Buckmaster
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -25,16 +25,20 @@
 
 #include <queue>
 
-#include "torqueRecast.h"
 #include "scene/sceneObject.h"
+#include "collision/concretePolyList.h"
 #include "recastPolyList.h"
+#include "util/messaging/eventManager.h"
 
+#include "torqueRecast.h"
 #include "duDebugDrawTorque.h"
+#include "coverPoint.h"
 
 #include <Recast.h>
 #include <DetourNavMesh.h>
 #include <DetourNavMeshBuilder.h>
 #include <DebugDraw.h>
+#include <DetourNavMeshQuery.h>
 
 /// @class NavMesh
 /// Represents a set of bounds within which a Recast navigation mesh is generated.
@@ -53,6 +57,10 @@ public:
    bool build(bool background = true, bool saveIntermediates = false);
    /// Stop a build in progress.
    void cancelBuild();
+   /// Generate cover points from a nav mesh.
+   bool createCoverPoints();
+   /// Remove all cover points
+   void deleteCoverPoints();
 
    /// Save the navmesh to a file.
    bool save();
@@ -65,9 +73,15 @@ public:
    /// Instantly rebuild a specific tile.
    void buildTile(const U32 &tile);
 
+   /// Rebuild parts of the navmesh where links have changed.
+   void buildLinks();
+
    /// Data file to store this nav mesh in. (From engine executable dir.)
    StringTableEntry mFileName;
 
+   /// Name of the SimSet to store cover points in. (Usually a SimGroup.)
+   StringTableEntry mCoverSet;
+
    /// Cell width and height.
    F32 mCellSize, mCellHeight;
    /// @name Actor data
@@ -90,6 +104,17 @@ public:
    U32 mMaxPolysPerTile;
    /// @}
 
+   /// @name Water
+   /// @{
+   enum WaterMethod {
+      Ignore,
+      Solid,
+      Impassable
+   };
+
+   WaterMethod mWaterMethod;
+   /// @}
+
    /// @}
 
    /// Return the index of the tile included by this point.
@@ -98,6 +123,68 @@ public:
    /// Return the box of a given tile.
    Box3F getTileBox(U32 id);
 
+   /// @name Links
+   /// @{
+
+   /// Add an off-mesh link.
+   S32 addLink(const Point3F &from, const Point3F &to, U32 flags = 0);
+
+   /// Get the ID of the off-mesh link near the point.
+   S32 getLink(const Point3F &pos);
+
+   /// Get the number of links this mesh has.
+   S32 getLinkCount();
+
+   /// Get the starting point of a link.
+   Point3F getLinkStart(U32 idx);
+
+   /// Get the ending point of a link.
+   Point3F getLinkEnd(U32 idx);
+
+   /// Get the flags used by a link.
+   LinkData getLinkFlags(U32 idx);
+
+   /// Set flags used by a link.
+   void setLinkFlags(U32 idx, const LinkData &d);
+
+   /// Set the selected state of a link.
+   void selectLink(U32 idx, bool select, bool hover = true);
+
+   /// Delete the selected link.
+   void deleteLink(U32 idx);
+
+   /// @}
+
+   /// Should small characters use this mesh?
+   bool mSmallCharacters;
+   /// Should regular-sized characters use this mesh?
+   bool mRegularCharacters;
+   /// Should large characters use this mesh?
+   bool mLargeCharacters;
+   /// Should vehicles use this mesh?
+   bool mVehicles;
+
+   /// @name Annotations
+   /// @{
+
+   /// Should we automatically generate jump-down links?
+   bool mJumpDownLinks;
+   /// Height of a 'small' jump link.
+   F32 mJumpLinkSmall;
+   /// Height of a 'large' jump link.
+   F32 mJumpLinkLarge;
+
+   /// Distance to search for cover.
+   F32 mCoverDist;
+
+   /// Distance to search horizontally when peeking around cover.
+   F32 mPeekDist;
+
+   /// Add cover to walls that don't have corners?
+   bool mInnerCover;
+
+   /// @}
+
    /// @name SimObject
    /// @{
 
@@ -142,6 +229,8 @@ public:
 
    void prepRenderImage(SceneRenderState *state);
    void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
+   void renderLinks(duDebugDraw &dd);
+   void renderTileData(duDebugDrawTorque &dd, U32 tile);
 
    bool mAlwaysRender;
 
@@ -151,6 +240,12 @@ public:
    ~NavMesh();
    DECLARE_CONOBJECT(NavMesh);
 
+   /// Return the server-side NavMesh SimSet.
+   static SimSet *getServerSet();
+
+   /// Return the EventManager for all NavMeshes.
+   static EventManager *getEventManager();
+
    void inspectPostApply();
 
 protected:
@@ -165,6 +260,9 @@ private:
    /// Builds the next tile in the dirty list.
    void buildNextTile();
 
+   /// Save imtermediate navmesh creation data?
+   bool mSaveIntermediates;
+
    /// @name Tiles
    /// @{
 
@@ -223,6 +321,9 @@ private:
    /// List of tiles.
    Vector<Tile> mTiles;
 
+   /// List of tile intermediate data.
+   Vector<TileData> mTileData;
+
    /// List of indices to the tile array which are dirty.
    std::queue<U32> mDirtyTiles;
 
@@ -234,6 +335,33 @@ private:
 
    /// @}
 
+   /// @name Off-mesh links
+   /// @{
+
+   enum SelectState {
+      Unselected,
+      Hovered,
+      Selected
+   };
+
+   Vector<F32> mLinkVerts;            ///< Coordinates of each link vertex
+   Vector<bool> mLinksUnsynced;       ///< Are the editor links unsynced from the mesh?
+   Vector<F32> mLinkRads;             ///< Radius of each link
+   Vector<U8> mLinkDirs;              ///< Direction (one-way or bidirectional)
+   Vector<U8> mLinkAreas;             ///< Area ID
+   Vector<unsigned short> mLinkFlags; ///< Flags
+   Vector<U32> mLinkIDs;              ///< ID number of each link
+   Vector<U8> mLinkSelectStates;      ///< Selection state of links
+   Vector<bool> mDeleteLinks;         ///< Link will be deleted next build.
+
+   U32 mCurLinkID;
+
+   void eraseLink(U32 idx);
+   void eraseLinks();
+   void setLinkCount(U32 c);
+
+   /// @}
+
    /// @name Intermediate data
    /// @{
 
@@ -244,6 +372,21 @@ private:
    void updateConfig();
 
    dtNavMesh *nm;
+   rcContext *ctx;
+
+   /// @}
+
+   /// @name Cover
+   /// @{
+
+   struct CoverPointData {
+      MatrixF trans;
+      CoverPoint::Size size;
+      bool peek[3];
+   };
+
+   /// Attempt to place cover points along a given edge.
+   bool testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointData &data);
 
    /// @}
 
@@ -269,6 +412,15 @@ private:
    void renderToDrawer();
 
    /// @}
+
+   /// Server-side set for all NavMesh objects.
+   static SimObjectPtr<SimSet> smServerSet;
+
+   /// Use this object to manage update events.
+   static SimObjectPtr<EventManager> smEventManager;
 };
 
+typedef NavMesh::WaterMethod NavMeshWaterMethod;
+DefineEnumType(NavMeshWaterMethod);
+
 #endif

+ 213 - 112
Engine/source/navigation/navPath.cpp

@@ -1,5 +1,5 @@
 //-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
+// Copyright (c) 2014 Daniel Buckmaster
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,10 +22,12 @@
 
 #include "torqueRecast.h"
 #include "navPath.h"
+#include "duDebugDrawTorque.h"
 
 #include "console/consoleTypes.h"
 #include "console/engineAPI.h"
 #include "console/typeValidators.h"
+#include "math/mathTypes.h"
 
 #include "scene/sceneRenderState.h"
 #include "gfx/gfxDrawUtil.h"
@@ -44,8 +46,7 @@ NavPath::NavPath() :
    mFrom(0.0f, 0.0f, 0.0f),
    mTo(0.0f, 0.0f, 0.0f)
 {
-   mTypeMask |= StaticShapeObjectType | MarkerObjectType;
-   mNetFlags.clear(Ghostable);
+   mTypeMask |= MarkerObjectType;
 
    mMesh = NULL;
    mWaypoints = NULL;
@@ -56,43 +57,51 @@ NavPath::NavPath() :
    mToSet = false;
    mLength = 0.0f;
 
+   mCurIndex = -1;
    mIsLooping = false;
+   mAutoUpdate = false;
+   mIsSliced = false;
+
+   mMaxIterations = 1;
 
    mAlwaysRender = false;
    mXray = false;
+   mRenderSearch = false;
 
-   mQuery = dtAllocNavMeshQuery();
+   mQuery = NULL;
 }
 
 NavPath::~NavPath()
 {
-   // Required for Detour.
    dtFreeNavMeshQuery(mQuery);
    mQuery = NULL;
 }
 
-bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data)
+void NavPath::checkAutoUpdate()
 {
-   NavMesh *mesh = NULL;
-   NavPath *object = static_cast<NavPath*>(obj);
-
-   if(Sim::findObject(data, mesh))
-      object->mMesh = mesh;
-
-   return false;
+   EventManager *em = NavMesh::getEventManager();
+   em->removeAll(this);
+   if(mMesh)
+   {
+      if(mAutoUpdate)
+      {
+         em->subscribe(this, "NavMeshRemoved");
+         em->subscribe(this, "NavMeshUpdate");
+         em->subscribe(this, "NavMeshUpdateBox");
+         em->subscribe(this, "NavMeshObstacleAdded");
+         em->subscribe(this, "NavMeshObstacleRemoved");
+      }
+   }
 }
 
-const char *NavPath::getProtectedMesh(void *obj, const char *data)
+bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data)
 {
    NavPath *object = static_cast<NavPath*>(obj);
 
-   if(object->mMesh.isNull())
-      return "";
+   if(Sim::findObject(data, object->mMesh))
+      object->checkAutoUpdate();
 
-   if(object->mMesh->getName())
-      return object->mMesh->getName();
-   else
-      return object->mMesh->getIdString();
+   return true;
 }
 
 bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *data)
@@ -111,6 +120,16 @@ bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *da
    return false;
 }
 
+bool NavPath::setProtectedAutoUpdate(void *obj, const char *index, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   object->mAutoUpdate = dAtob(data);
+   object->checkAutoUpdate();
+
+   return false;
+}
+
 bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data)
 {
    NavPath *object = static_cast<NavPath*>(obj);
@@ -150,7 +169,7 @@ const char *NavPath::getProtectedFrom(void *obj, const char *data)
    if(object->mFromSet)
       return data;
    else
-      return "";
+      return StringTable->insert("");
 }
 
 const char *NavPath::getProtectedTo(void *obj, const char *data)
@@ -160,29 +179,10 @@ const char *NavPath::getProtectedTo(void *obj, const char *data)
    if(object->mToSet)
       return data;
    else
-      return "";
+      return StringTable->insert("");
 }
 
-bool NavPath::setProtectedAlwaysRender(void *obj, const char *index, const char *data)
-{
-   NavPath *path = static_cast<NavPath*>(obj);
-   bool always = dAtob(data);
-   if(always)
-   {
-      if(!gEditingMission)
-         path->mNetFlags.set(Ghostable);
-   }
-   else
-   {
-      if(!gEditingMission)
-         path->mNetFlags.clear(Ghostable);
-   }
-   path->mAlwaysRender = always;
-   path->setMaskBits(PathMask);
-   return true;
-}
-
-static IRangeValidator NaturalNumber(1, S32_MAX);
+IRangeValidator ValidIterations(1, S32_MAX);
 
 void NavPath::initPersistFields()
 {
@@ -195,25 +195,52 @@ void NavPath::initPersistFields()
       &setProtectedTo, &getProtectedTo,
       "World location this path should end at.");
 
-   addProtectedField("mesh", TYPEID<NavMesh>(), Offset(mMesh, NavPath),
-      &setProtectedMesh, &getProtectedMesh,
-      "NavMesh object this path travels within.");
+   addProtectedField("mesh", TypeRealString, Offset(mMeshName, NavPath),
+      &setProtectedMesh, &defaultProtectedGetFn,
+      "Name of the NavMesh object this path travels within.");
    addProtectedField("waypoints", TYPEID<SimPath::Path>(), Offset(mWaypoints, NavPath),
       &setProtectedWaypoints, &defaultProtectedGetFn,
       "Path containing waypoints for this NavPath to visit.");
 
    addField("isLooping", TypeBool, Offset(mIsLooping, NavPath),
       "Does this path loop?");
+   addField("isSliced", TypeBool, Offset(mIsSliced, NavPath),
+      "Plan this path over multiple updates instead of all at once.");
+   addFieldV("maxIterations", TypeS32, Offset(mMaxIterations, NavPath), &ValidIterations,
+      "Maximum iterations of path planning this path does per tick.");
+   addProtectedField("autoUpdate", TypeBool, Offset(mAutoUpdate, NavPath),
+      &setProtectedAutoUpdate, &defaultProtectedGetFn,
+      "If set, this path will automatically replan when its navigation mesh changes.");
 
    endGroup("NavPath");
 
+   addGroup("Flags");
+
+   addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, NavPath),
+      "Allow the path to use dry land.");
+   addField("allowJump", TypeBool, Offset(mLinkTypes.jump, NavPath),
+      "Allow the path to use jump links.");
+   addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, NavPath),
+      "Allow the path to use drop links.");
+   addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, NavPath),
+      "Allow the path tomove in water.");
+   addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, NavPath),
+      "Allow the path to jump ledges.");
+   addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, NavPath),
+      "Allow the path to use climb links.");
+   addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, NavPath),
+      "Allow the path to use teleporters.");
+
+   endGroup("Flags");
+
    addGroup("NavPath Render");
-   
-   addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh),
-      &setProtectedAlwaysRender, &defaultProtectedGetFn,
-      "Display this NavPath even outside the editor.");
+
+   addField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavPath),
+      "Render this NavPath even when not selected.");
    addField("xray", TypeBool, Offset(mXray, NavPath),
       "Render this NavPath through other objects.");
+   addField("renderSearch", TypeBool, Offset(mRenderSearch, NavPath),
+      "Render the closed list of this NavPath's search.");
 
    endGroup("NavPath Render");
 
@@ -225,59 +252,62 @@ bool NavPath::onAdd()
    if(!Parent::onAdd())
       return false;
 
-   addToScene();
-
-   // Ghost immediately if the editor's already open.
-   if(gEditingMission || mAlwaysRender)
+   if(gEditingMission)
       mNetFlags.set(Ghostable);
 
-   // Automatically find a path if we can.
-   if(isServerObject())
-      plan();
-
-   // Set initial world bounds and stuff.
    resize();
 
+   addToScene();
+
+   if(isServerObject())
+   {
+      mQuery = dtAllocNavMeshQuery();
+      if(!mQuery)
+         return false;
+      checkAutoUpdate();
+      if(!plan())
+         setProcessTick(true);
+   }
+
    return true;
 }
 
 void NavPath::onRemove()
 {
-   // Remove from simulation.
-   removeFromScene();
-
    Parent::onRemove();
+
+   removeFromScene();
 }
 
 bool NavPath::init()
 {
-   // Check that enough data is provided.
-   if(mMesh.isNull() || !mMesh->getNavMesh())
+   mStatus = DT_FAILURE;
+
+   // Check that all the right data is provided.
+   if(!mMesh || !mMesh->getNavMesh())
       return false;
-   if(!(mFromSet && mToSet) && !(!mWaypoints.isNull() && mWaypoints->size()))
+   if(!(mFromSet && mToSet) && !(mWaypoints && mWaypoints->size()))
       return false;
 
-   // Initialise query in Detour.
+   // Initialise our query.
    if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
       return false;
 
    mPoints.clear();
+   mFlags.clear();
    mVisitPoints.clear();
    mLength = 0.0f;
 
-   // Send path data to clients who are ghosting this object.
    if(isServerObject())
       setMaskBits(PathMask);
 
    // Add points we need to visit in reverse order.
    if(mWaypoints && mWaypoints->size())
    {
-      // Add destination. For looping paths, that includes 'from'.
       if(mIsLooping && mFromSet)
          mVisitPoints.push_back(mFrom);
       if(mToSet)
          mVisitPoints.push_front(mTo);
-      // Add waypoints.
       for(S32 i = mWaypoints->size() - 1; i >= 0; i--)
       {
          SceneObject *s = dynamic_cast<SceneObject*>(mWaypoints->at(i));
@@ -289,13 +319,11 @@ bool NavPath::init()
                mVisitPoints.push_front(s->getPosition());
          }
       }
-      // Add source (only ever specified by 'from').
       if(mFromSet)
          mVisitPoints.push_back(mFrom);
    }
    else
    {
-      // Add (from,) to and from
       if(mIsLooping)
          mVisitPoints.push_back(mFrom);
       mVisitPoints.push_back(mTo);
@@ -316,7 +344,6 @@ void NavPath::resize()
       return;
    }
 
-   // Grow a box to just fit over all our points.
    Point3F max(mPoints[0]), min(mPoints[0]), pos(0.0f);
    for(U32 i = 1; i < mPoints.size(); i++)
    {
@@ -341,20 +368,38 @@ void NavPath::resize()
 
 bool NavPath::plan()
 {
+   // Initialise filter.
+   mFilter.setIncludeFlags(mLinkTypes.getFlags());
+
+   // Initialise query and visit locations.
    if(!init())
       return false;
 
-   if(!visitNext())
-      return false;
+   if(mIsSliced)
+      return planSliced();
+   else
+      return planInstant();
+}
 
-   while(update());
+bool NavPath::planSliced()
+{
+   bool visited = visitNext();
 
-   if(!finalise())
-      return false;
+   if(visited)
+      setProcessTick(true);
 
-   resize();
+   return visited;
+}
 
-   return true;
+bool NavPath::planInstant()
+{
+   setProcessTick(false);
+   visitNext();
+   S32 store = mMaxIterations;
+   mMaxIterations = INT_MAX;
+   while(update());
+   mMaxIterations = store;
+   return finalise();
 }
 
 bool NavPath::visitNext()
@@ -364,23 +409,32 @@ bool NavPath::visitNext()
       return false;
 
    // Current leg of journey.
-   Point3F start = mVisitPoints[s-1];
-   Point3F end = mVisitPoints[s-2];
+   Point3F &start = mVisitPoints[s-1];
+   Point3F &end = mVisitPoints[s-2];
+
+   // Drop to height of statics.
+   RayInfo info;
+   if(getContainer()->castRay(start, start - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info))
+      start = info.point;
+   if(getContainer()->castRay(end + Point3F(0, 0, 0.1f), end - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info))
+      end = info.point;
 
    // Convert to Detour-friendly coordinates and data structures.
    F32 from[] = {start.x, start.z, -start.y};
    F32 to[] =   {end.x,   end.z,   -end.y};
-   F32 extents[] = {1.0f, 1.0f, 1.0f};
+   F32 extx = mMesh->mWalkableRadius * 4.0f;
+   F32 extz = mMesh->mWalkableHeight;
+   F32 extents[] = {extx, extz, extx};
    dtPolyRef startRef, endRef;
 
-   if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, from)) || !startRef)
+   if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, NULL)) || !startRef)
    {
       Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
          start.x, start.y, start.z, getIdString());
       return false;
    }
 
-   if(dtStatusFailed(mQuery->findNearestPoly(to, extents, &mFilter, &endRef, to)) || !startRef)
+   if(dtStatusFailed(mQuery->findNearestPoly(to, extents, &mFilter, &endRef, NULL)) || !endRef)
    {
       Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
          end.x, end.y, end.z, getIdString());
@@ -397,20 +451,16 @@ bool NavPath::visitNext()
 
 bool NavPath::update()
 {
-   // StatusInProgress means a query is underway.
    if(dtStatusInProgress(mStatus))
-      mStatus = mQuery->updateSlicedFindPath(INT_MAX, NULL);
-   // StatusSucceeded means the query found its destination.
+      mStatus = mQuery->updateSlicedFindPath(mMaxIterations, NULL);
    if(dtStatusSucceed(mStatus))
    {
-      // Finalize the path. Need to use the static path length cap again.
+      // Add points from this leg.
       dtPolyRef path[MaxPathLen];
       S32 pathLen;
       mStatus = mQuery->finalizeSlicedFindPath(path, &pathLen, MaxPathLen);
-      // Apparently stuff can go wrong during finalizing, so check the status again.
       if(dtStatusSucceed(mStatus) && pathLen)
       {
-         // These next few blocks are straight from Detour example code.
          F32 straightPath[MaxPathLen * 3];
          S32 straightPathLen;
          dtPolyRef straightPathPolys[MaxPathLen];
@@ -422,19 +472,19 @@ bool NavPath::update()
          F32 from[] = {start.x, start.z, -start.y};
          F32 to[] =   {end.x,   end.z,   -end.y};
 
-         // Straightens out the path.
          mQuery->findStraightPath(from, to, path, pathLen,
             straightPath, straightPathFlags,
             straightPathPolys, &straightPathLen, MaxPathLen);
 
-         // Convert Detour point path to list of Torque points.
          s = mPoints.size();
          mPoints.increment(straightPathLen);
+         mFlags.increment(straightPathLen);
          for(U32 i = 0; i < straightPathLen; i++)
          {
             F32 *f = straightPath + i * 3;
             mPoints[s + i] = RCtoDTS(f);
-            // Accumulate length if we're not the first vertex.
+            mMesh->getNavMesh()->getPolyFlags(straightPathPolys[i], &mFlags[s + i]);
+            // Add to length
             if(s > 0 || i > 0)
                mLength += (mPoints[s+i] - mPoints[s+i-1]).len();
          }
@@ -467,30 +517,37 @@ bool NavPath::update()
 
 bool NavPath::finalise()
 {
-   // Stop ticking.
    setProcessTick(false);
 
-   // Reset world bounds and stuff.
    resize();
 
-   return dtStatusSucceed(mStatus);
+   return success();
 }
 
 void NavPath::processTick(const Move *move)
 {
+   if(!mMesh)
+      if(Sim::findObject(mMeshName.c_str(), mMesh))
+         plan();
    if(dtStatusInProgress(mStatus))
       update();
 }
 
-Point3F NavPath::getNode(S32 idx)
+Point3F NavPath::getNode(S32 idx) const
 {
-   if(idx < getCount() && idx >= 0)
+   if(idx < size() && idx >= 0)
       return mPoints[idx];
-   Con::errorf("Trying to access out-of-bounds path index %d (path length: %d)!", idx, getCount());
    return Point3F(0,0,0);
 }
 
-S32 NavPath::getCount()
+U16 NavPath::getFlags(S32 idx) const
+{
+   if(idx < size() && idx >= 0)
+      return mFlags[idx];
+   return 0;
+}
+
+S32 NavPath::size() const
 {
    return mPoints.size();
 }
@@ -498,18 +555,11 @@ S32 NavPath::getCount()
 void NavPath::onEditorEnable()
 {
    mNetFlags.set(Ghostable);
-   if(isClientObject() && !mAlwaysRender)
-      addToScene();
 }
 
 void NavPath::onEditorDisable()
 {
-   if(!mAlwaysRender)
-   {
-      mNetFlags.clear(Ghostable);
-      if(isClientObject())
-         removeFromScene();
-   }
+   mNetFlags.clear(Ghostable);
 }
 
 void NavPath::inspectPostApply()
@@ -530,7 +580,7 @@ void NavPath::prepRenderImage(SceneRenderState *state)
 {
    ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
    ri->renderDelegate.bind(this, &NavPath::renderSimple);
-   ri->type = RenderPassManager::RIT_Object;
+   ri->type = RenderPassManager::RIT_Editor;      
    ri->translucentSort = true;
    ri->defaultKey = 1;
    state->getRenderPass()->addInst(ri);
@@ -577,6 +627,18 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa
    for (U32 i = 0; i < mPoints.size(); i++)
       PrimBuild::vertex3fv(mPoints[i]);
    PrimBuild::end();
+
+   if(mRenderSearch && getServerObject())
+   {
+      NavPath *np = static_cast<NavPath*>(getServerObject());
+      if(np->mQuery && !dtStatusSucceed(np->mStatus))
+      {
+         duDebugDrawTorque dd;
+         dd.overrideColor(duRGBA(250, 20, 20, 255));
+         duDebugDrawNavMeshNodes(&dd, *np->mQuery);
+         dd.render();
+      }
+   }
 }
 
 U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
@@ -586,6 +648,7 @@ U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
    stream->writeFlag(mIsLooping);
    stream->writeFlag(mAlwaysRender);
    stream->writeFlag(mXray);
+   stream->writeFlag(mRenderSearch);
 
    if(stream->writeFlag(mFromSet))
       mathWrite(*stream, mFrom);
@@ -596,7 +659,10 @@ U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
    {
       stream->writeInt(mPoints.size(), 32);
       for(U32 i = 0; i < mPoints.size(); i++)
+      {
          mathWrite(*stream, mPoints[i]);
+         stream->writeInt(mFlags[i], 16);
+      }
    }
 
    return retMask;
@@ -609,6 +675,7 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
    mIsLooping = stream->readFlag();
    mAlwaysRender = stream->readFlag();
    mXray = stream->readFlag();
+   mRenderSearch = stream->readFlag();
 
    if((mFromSet = stream->readFlag()) == true)
       mathRead(*stream, &mFrom);
@@ -618,27 +685,55 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
    if(stream->readFlag())
    {
       mPoints.clear();
+      mFlags.clear();
       mPoints.setSize(stream->readInt(32));
+      mFlags.setSize(mPoints.size());
       for(U32 i = 0; i < mPoints.size(); i++)
       {
          Point3F p;
          mathRead(*stream, &p);
          mPoints[i] = p;
+         mFlags[i] = stream->readInt(16);
       }
       resize();
    }
 }
 
-DefineEngineMethod(NavPath, replan, bool, (),,
+DefineEngineMethod(NavPath, plan, bool, (),,
    "@brief Find a path using the already-specified path properties.")
 {
    return object->plan();
 }
 
-DefineEngineMethod(NavPath, getCount, S32, (),,
+DefineEngineMethod(NavPath, onNavMeshUpdate, void, (const char *data),,
+   "@brief Callback when this path's NavMesh is loaded or rebuilt.")
+{
+   if(object->mMesh && !dStrcmp(data, object->mMesh->getIdString()))
+      object->plan();
+}
+
+DefineEngineMethod(NavPath, onNavMeshUpdateBox, void, (const char *data),,
+   "@brief Callback when a particular area in this path's NavMesh is rebuilt.")
+{
+   String s(data);
+   U32 space = s.find(' ');
+   if(space != String::NPos)
+   {
+      String id = s.substr(0, space);
+      if(!object->mMesh || id.compare(object->mMesh->getIdString()))
+         return;
+      String boxstr = s.substr(space + 1);
+      Box3F box;
+      castConsoleTypeFromString(box, boxstr.c_str());
+      if(object->getWorldBox().isOverlapped(box))
+         object->plan();
+   }
+}
+
+DefineEngineMethod(NavPath, size, S32, (),,
    "@brief Return the number of nodes in this path.")
 {
-   return object->getCount();
+   return object->size();
 }
 
 DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
@@ -647,8 +742,14 @@ DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
    return object->getNode(idx);
 }
 
+DefineEngineMethod(NavPath, getFlags, S32, (S32 idx),,
+   "@brief Get a specified node along the path.")
+{
+   return (S32)object->getFlags(idx);
+}
+
 DefineEngineMethod(NavPath, getLength, F32, (),,
-   "@brief Get the length of this path in Torque units (i.e. the total distance it covers).")
+   "@brief Get the length of this path.")
 {
    return object->getLength();
 }

+ 51 - 36
Engine/source/navigation/navPath.h

@@ -1,5 +1,5 @@
 //-----------------------------------------------------------------------------
-// Copyright (c) 2013 GarageGames, LLC
+// Copyright (c) 2014 Daniel Buckmaster
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -30,33 +30,33 @@
 
 class NavPath: public SceneObject {
    typedef SceneObject Parent;
-   /// Maximum size of Detour path.
-   static const U32 MaxPathLen = 1024;
-
+   static const U32 MaxPathLen = 2048;
 public:
    /// @name NavPath
    /// Functions for planning and accessing the path.
    /// @{
 
-   SimObjectPtr<NavMesh> mMesh;
-   SimObjectPtr<SimPath::Path> mWaypoints;
+   String mMeshName;
+   NavMesh *mMesh;
+   SimPath::Path *mWaypoints;
 
-   /// Location to start at.
    Point3F mFrom;
-   /// Has a starting location been set?
    bool mFromSet;
-   /// Location to end at.
    Point3F mTo;
-   /// Has an end been set?
    bool mToSet;
 
-   /// This path should include a segment from the end to the start.
    bool mIsLooping;
+   bool mAutoUpdate;
+   bool mIsSliced;
+
+   S32 mMaxIterations;
 
-   /// Render even when not selected in the editor.
    bool mAlwaysRender;
-   /// Render on top of other objects.
    bool mXray;
+   bool mRenderSearch;
+
+   /// What sort of link types are we allowed to move on?
+   LinkData mLinkTypes;
 
    /// Plan the path.
    bool plan();
@@ -69,20 +69,28 @@ public:
    /// @return True if the plan was successful overall.
    bool finalise();
 
+   /// Did the path plan successfully?
+   bool success() const { return dtStatusSucceed(mStatus); }
+
    /// @}
 
    /// @name Path interface
+   /// These functions are provided to make NavPath behave
+   /// similarly to the existing Path class, despite NavPath
+   /// not being a SimSet.
    /// @{
 
-   /// Return world-space position of a path node.
-   /// @param[in] idx Node index to retrieve.
-   Point3F getNode(S32 idx);
+   /// Return the length of this path.
+   F32 getLength() const { return mLength; };
 
-   /// Return the number of nodes in this path.
-   S32 getCount();
+   /// Get the number of nodes in a path.
+   S32 size() const;
 
-   /// Return the length of this path.
-   F32 getLength() { return mLength; };
+   /// Return world-space position of a path node.
+   Point3F getNode(S32 idx) const;
+
+   /// Get the flags for a given path node.
+   U16 getFlags(S32 idx) const;
 
    /// @}
 
@@ -128,39 +136,46 @@ private:
    /// Create appropriate data structures and stuff.
    bool init();
 
-   /// 'Visit' the most recent two points on our visit list.
+   /// Plan the path.
+   bool planInstant();
+
+   /// Start a sliced plan.
+   /// @return True if the plan initialised successfully.
+   bool planSliced();
+
+   /// Add points of the path between the two specified points.
+   //bool addPoints(Point3F from, Point3F to, Vector<Point3F> *points);
+
+   /// 'Visit' the last two points on our visit list.
    bool visitNext();
 
-   /// Detour path query.
    dtNavMeshQuery *mQuery;
-   /// Current status of our Detour query.
    dtStatus mStatus;
-   /// Filter that provides the movement costs for paths.
    dtQueryFilter mFilter;
-   
-   /// List of points the path should visit (waypoints, if you will).
-   Vector<Point3F> mVisitPoints;
-   /// List of points in the final path.
+   S32 mCurIndex;
    Vector<Point3F> mPoints;
-
-   /// Total length of path in world units.
+   Vector<unsigned short> mFlags;
+   Vector<Point3F> mVisitPoints;
    F32 mLength;
 
    /// Resets our world transform and bounds to fit our point list.
    void resize();
 
-   /// @name Protected console getters/setters
-   /// @{
+   /// Function used to set mMesh object from console.
    static bool setProtectedMesh(void *obj, const char *index, const char *data);
-   static const char *getProtectedMesh(void *obj, const char *data);
+
+   /// Function used to set mWaypoints from console.
    static bool setProtectedWaypoints(void *obj, const char *index, const char *data);
 
-   static bool setProtectedAlwaysRender(void *obj, const char *index, const char *data);
+   void checkAutoUpdate();
+   /// Function used to protect auto-update flag.
+   static bool setProtectedAutoUpdate(void *obj, const char *index, const char *data);
 
+   /// @name Protected from and to vectors
+   /// @{
    static bool setProtectedFrom(void *obj, const char *index, const char *data);
-   static const char *getProtectedFrom(void *obj, const char *data);
-
    static bool setProtectedTo(void *obj, const char *index, const char *data);
+   static const char *getProtectedFrom(void *obj, const char *data);
    static const char *getProtectedTo(void *obj, const char *data);
    /// @}
 };

+ 32 - 0
Engine/source/navigation/torqueRecast.h

@@ -69,4 +69,36 @@ enum PolyFlags {
    AllFlags = 0xffff
 };
 
+/// Stores information about a link.
+struct LinkData {
+   bool walk;
+   bool jump;
+   bool drop;
+   bool swim;
+   bool ledge;
+   bool climb;
+   bool teleport;
+   LinkData(unsigned short flags = 0)
+   {
+      walk = flags & WalkFlag;
+      jump = flags & JumpFlag;
+      drop = flags & DropFlag;
+      swim = flags & SwimFlag;
+      ledge = flags & LedgeFlag;
+      climb = flags & ClimbFlag;
+      teleport = flags & TeleportFlag;
+   }
+   unsigned short getFlags() const
+   {
+      return
+         (walk ? WalkFlag : 0) |
+         (jump ? JumpFlag : 0) |
+         (drop ? DropFlag : 0) |
+         (swim ? SwimFlag : 0) |
+         (ledge ? LedgeFlag : 0) |
+         (climb ? ClimbFlag : 0) |
+         (teleport ? TeleportFlag : 0);
+   }
+};
+
 #endif

+ 1 - 1
Engine/source/terrain/terrCollision.cpp

@@ -562,7 +562,7 @@ bool TerrainBlock::buildPolyList(PolyListContext context, AbstractPolyList* poly
 
          // Add the missing points
          U32 vi[5];
-         for (S32 i = 0; i < 4 ; i++) 
+         for (int i = 0; i < 4 ; i++) 
          {
             S32 dx = i >> 1;
             S32 dy = dx ^ (i & 1);

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.