浏览代码

Merge pull request #1092 from eightyeight/walkabout

Walkabout navigation editor
Daniel Buckmaster 10 年之前
父节点
当前提交
29c2b98c47
共有 63 个文件被更改,包括 9107 次插入205 次删除
  1. 561 17
      Engine/source/T3D/aiPlayer.cpp
  2. 118 0
      Engine/source/T3D/aiPlayer.h
  3. 50 0
      Engine/source/environment/river.cpp
  4. 1 0
      Engine/source/environment/river.h
  5. 46 0
      Engine/source/environment/waterBlock.cpp
  6. 1 0
      Engine/source/environment/waterBlock.h
  7. 42 0
      Engine/source/environment/waterPlane.cpp
  8. 1 0
      Engine/source/environment/waterPlane.h
  9. 344 0
      Engine/source/navigation/coverPoint.cpp
  10. 136 0
      Engine/source/navigation/coverPoint.h
  11. 641 0
      Engine/source/navigation/guiNavEditorCtrl.cpp
  12. 189 0
      Engine/source/navigation/guiNavEditorCtrl.h
  13. 69 0
      Engine/source/navigation/navContext.cpp
  14. 68 0
      Engine/source/navigation/navContext.h
  15. 702 29
      Engine/source/navigation/navMesh.cpp
  16. 154 2
      Engine/source/navigation/navMesh.h
  17. 213 112
      Engine/source/navigation/navPath.cpp
  18. 51 36
      Engine/source/navigation/navPath.h
  19. 1 0
      Engine/source/navigation/recastPolyList.cpp
  20. 32 0
      Engine/source/navigation/torqueRecast.h
  21. 1 1
      Engine/source/terrain/terrCollision.cpp
  22. 392 0
      Templates/Empty/game/tools/navEditor/CreateNewNavMeshDlg.gui
  23. 169 0
      Templates/Empty/game/tools/navEditor/NavEditorConsoleDlg.gui
  24. 854 0
      Templates/Empty/game/tools/navEditor/NavEditorGui.gui
  25. 506 0
      Templates/Empty/game/tools/navEditor/NavEditorSettingsTab.gui
  26. 144 0
      Templates/Empty/game/tools/navEditor/NavEditorToolbar.gui
  27. 二进制
      Templates/Empty/game/tools/navEditor/done.wav
  28. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-cover_d.png
  29. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-cover_h.png
  30. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-cover_n.png
  31. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-editor_d.png
  32. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-editor_h.png
  33. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-editor_n.png
  34. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-link_d.png
  35. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-link_h.png
  36. 二进制
      Templates/Empty/game/tools/navEditor/images/nav-link_n.png
  37. 285 0
      Templates/Empty/game/tools/navEditor/main.cs
  38. 360 0
      Templates/Empty/game/tools/navEditor/navEditor.cs
  39. 130 0
      Templates/Empty/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui
  40. 1 1
      Templates/Empty/game/tools/worldEditor/gui/ToolsToolbar.ed.gui
  41. 1 0
      Templates/Empty/game/tools/worldEditor/scripts/EditorGui.ed.cs
  42. 1 3
      Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs
  43. 392 0
      Templates/Full/game/tools/navEditor/CreateNewNavMeshDlg.gui
  44. 169 0
      Templates/Full/game/tools/navEditor/NavEditorConsoleDlg.gui
  45. 854 0
      Templates/Full/game/tools/navEditor/NavEditorGui.gui
  46. 506 0
      Templates/Full/game/tools/navEditor/NavEditorSettingsTab.gui
  47. 144 0
      Templates/Full/game/tools/navEditor/NavEditorToolbar.gui
  48. 二进制
      Templates/Full/game/tools/navEditor/done.wav
  49. 二进制
      Templates/Full/game/tools/navEditor/images/nav-cover_d.png
  50. 二进制
      Templates/Full/game/tools/navEditor/images/nav-cover_h.png
  51. 二进制
      Templates/Full/game/tools/navEditor/images/nav-cover_n.png
  52. 二进制
      Templates/Full/game/tools/navEditor/images/nav-editor_d.png
  53. 二进制
      Templates/Full/game/tools/navEditor/images/nav-editor_h.png
  54. 二进制
      Templates/Full/game/tools/navEditor/images/nav-editor_n.png
  55. 二进制
      Templates/Full/game/tools/navEditor/images/nav-link_d.png
  56. 二进制
      Templates/Full/game/tools/navEditor/images/nav-link_h.png
  57. 二进制
      Templates/Full/game/tools/navEditor/images/nav-link_n.png
  58. 285 0
      Templates/Full/game/tools/navEditor/main.cs
  59. 360 0
      Templates/Full/game/tools/navEditor/navEditor.cs
  60. 130 0
      Templates/Full/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui
  61. 1 1
      Templates/Full/game/tools/worldEditor/gui/ToolsToolbar.ed.gui
  62. 1 0
      Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs
  63. 1 3
      Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs

+ 561 - 17
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
+
    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;
 }
 
+void AIPlayer::onRemove()
+{
+#ifdef TORQUE_NAVIGATION_ENABLED
+   clearPath();
+   clearCover();
+   clearFollow();
+#endif
+   Parent::onRemove();
+}
+
 /**
  * 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
 }
 
 /**
@@ -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,7 @@ bool AIPlayer::getAIMove(Move *movePtr)
       if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) 
       {
          mMoveState = ModeStop;
-         throwCallback("onReachDestination");
+         onReachDestination();
       }
       else 
       {
@@ -409,7 +477,7 @@ bool AIPlayer::getAIMove(Move *movePtr)
                if ( mMoveState != ModeSlowing || locationDelta == 0 )
                {
                   mMoveState = ModeStuck;
-                  throwCallback("onMoveStuck");
+                  onStuck();
                }
             }
          }
@@ -419,28 +487,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( U32 i = 0; i < MaxMountedImages; 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 +550,457 @@ void AIPlayer::throwCallback( const char *name )
    Con::executef(getDataBlock(), name, getIdString());
 }
 
+/**
+ * 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()
+{
+#ifdef TORQUE_NAVIGATION_ENABLED
+   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
+#endif
+      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()
+{
+#ifdef TORQUE_NAVIGATION_ENABLED
+   if(!mPathData.path.isNull())
+      repath();
+   else
+#endif
+      throwCallback("onMoveStuck");
+}
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+// --------------------------------------------------------------------------------------------
+// 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
@@ -711,4 +1255,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);
-}
+}

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

@@ -27,6 +27,11 @@
 #include "T3D/player.h"
 #endif
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+#include "navigation/navPath.h"
+#include "navigation/navMesh.h"
+#include "navigation/coverPoint.h"
+#endif // TORQUE_NAVIGATION_ENABLED
 
 class AIPlayer : public Player {
 
@@ -61,6 +66,83 @@ private:
    // Utility Methods
    void throwCallback( const char *name );
 
+#ifdef TORQUE_NAVIGATION_ENABLED
+   /// 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);
+#endif // TORQUE_NAVIGATION_ENABLED
+
+protected:
+   virtual void onReachDestination();
+   virtual void onStuck();
+
 public:
    DECLARE_CONOBJECT( AIPlayer );
 
@@ -70,6 +152,7 @@ public:
    static void initPersistFields();
 
    bool onAdd();
+   void onRemove();
 
    virtual bool getAIMove( Move *move );
 
@@ -91,6 +174,41 @@ 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; }
+
+   /// Get cover we are moving to.
+   CoverPoint *getCover() { return mCoverData.cover; }
+
+   /// 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;

+ 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

文件差异内容过多而无法显示
+ 702 - 29
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<U16> 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<U16> 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);
    /// @}
 };

+ 1 - 0
Engine/source/navigation/recastPolyList.cpp

@@ -78,6 +78,7 @@ U32 RecastPolyList::addPoint(const Point3F &p)
       verts = newverts;
    }
    Point3F v = p;
+   v.convolve(mScale);
    mMatrix.mulP(v);
    // Insert the new vertex.
    verts[nverts*3] = v.x;

+ 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(U16 flags = 0)
+   {
+      walk = flags & WalkFlag;
+      jump = flags & JumpFlag;
+      drop = flags & DropFlag;
+      swim = flags & SwimFlag;
+      ledge = flags & LedgeFlag;
+      climb = flags & ClimbFlag;
+      teleport = flags & TeleportFlag;
+   }
+   U16 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);

+ 392 - 0
Templates/Empty/game/tools/navEditor/CreateNewNavMeshDlg.gui

@@ -0,0 +1,392 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiControl(CreateNewNavMeshDlg) {
+   position = "0 0";
+   extent = "1024 768";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiWindowCtrl() {
+      text = "New NavMesh";
+      resizeWidth = "0";
+      resizeHeight = "0";
+      canMove = "1";
+      canClose = "1";
+      canMinimize = "0";
+      canMaximize = "0";
+      canCollapse = "0";
+      closeCommand = "Canvas.popDialog(CreateNewNavMeshDlg);";
+      edgeSnap = "1";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "283 240";
+      extent = "200 176";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiWindowProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiTextCtrl() {
+         text = "Name:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 29";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "Nav";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 30";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshName";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextCtrl() {
+         text = "Position:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 51";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "0 0 0";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 52";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshPosition";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextCtrl() {
+         text = "Scale:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 73";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "50 50 20";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 74";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshScale";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiCheckBoxCtrl(MeshMissionBounds) {
+         text = " Fit NavMesh to mission area";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "1";
+         position = "22 99";
+         extent = "159 15";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiCheckBoxProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         tooltip = "Positions and scales the NavMesh so it includes all your mission objects.";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiCheckBoxCtrl(MeshTerrainBounds) {
+         text = " Include terrain";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         position = "22 121";
+         extent = "159 15";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiCheckBoxProfile";
+         visible = "1";
+         active = "0";
+         tooltipProfile = "GuiToolTipProfile";
+         tooltip = "Consider terrain when calculating NavMesh bounds.";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiButtonCtrl() {
+         text = "Create!";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         position = "12 146";
+         extent = "87 19";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiButtonProfile";
+         visible = "1";
+         active = "1";
+         command = "CreateNewNavMeshDlg.create();";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiButtonCtrl() {
+         text = "Cancel";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         position = "104 146";
+         extent = "84 19";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiButtonProfile";
+         visible = "1";
+         active = "1";
+         command = "Canvas.popDialog(CreateNewNavMeshDlg);";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---
+
+function CreateNewNavMeshDlg::onWake(%this)
+{
+   %this-->MeshName.setText("Nav");
+   %this-->MeshPosition.setText("0 0 0");
+   %this-->MeshScale.setText("50 50 20");
+   MeshMissionBounds.setStateOn(false);
+   MeshTerrainBounds.setStateOn(true);
+}
+
+function MissionBoundsExtents(%group)
+{
+   %box = "0 0 0 0 0 0";
+   foreach(%obj in %group)
+   {
+      %cls = %obj.getClassName();
+      if(%cls $= "SimGroup" || %cls $= "SimSet" || %cls $= "Path")
+      {
+         // Need to recursively check grouped objects.
+         %wbox = MissionBoundsExtents(%obj);
+      }
+      else
+      {
+         // Skip objects that are too big and shouldn't really be considered
+         // part of the scene, or are global bounds and we therefore can't get
+         // any sensible information out of them.
+         if(%cls $= "LevelInfo")
+            continue;
+         if(!MeshTerrainBounds.isStateOn() && %cls $= "TerrainBlock")
+            continue;
+
+         if(!(%obj.getType() & $TypeMasks::StaticObjectType) ||
+            %obj.getType() & $TypeMasks::EnvironmentObjectType)
+            continue;
+
+         if(%obj.isGlobalBounds())
+            continue;
+
+         %wbox = %obj.getWorldBox();
+      }
+
+      // Update min point.
+      for(%j = 0; %j < 3; %j++)
+      {
+         if(GetWord(%box, %j) > GetWord(%wbox, %j))
+            %box = SetWord(%box, %j, GetWord(%wbox, %j));
+      }
+      // Update max point.
+      for(%j = 3; %j < 6; %j++)
+      {
+         if(GetWord(%box, %j) < GetWord(%wbox, %j))
+            %box = SetWord(%box, %j, GetWord(%wbox, %j));
+      }
+   }
+   return %box;
+}
+
+function CreateNewNavMeshDlg::create(%this)
+{
+   %name = %this-->MeshName.getText();
+   if(%name $= "" || nameToID(%name) != -1)
+   {
+      MessageBoxOk("Error", "A NavMesh must have a unique name!");
+      return;
+   }
+
+   %mesh = 0;
+
+   if(MeshMissionBounds.isStateOn())
+   {
+      if(!isObject(MissionGroup))
+      {
+         MessageBoxOk("Error", "You must have a MissionGroup to use the mission bounds function.");
+         return;
+      }
+      // Get maximum extents of all objects.
+      %box = MissionBoundsExtents(MissionGroup);
+      %pos = GetBoxCenter(%box);
+      %scale = (GetWord(%box, 3) - GetWord(%box, 0)) / 2 + 5
+         SPC (GetWord(%box, 4) - GetWord(%box, 1)) / 2 + 5
+         SPC (GetWord(%box, 5) - GetWord(%box, 2)) / 2 + 5;
+
+      %mesh = new NavMesh(%name)
+      {
+         position = %pos;
+         scale = %scale;
+      };
+   }
+   else
+   {
+      %mesh = new NavMesh(%name)
+      {
+         position = %this-->MeshPosition.getText();
+         scale = %this-->MeshScale.getText();
+      };
+   }
+   MissionGroup.add(%mesh);
+   NavEditorGui.selectObject(%mesh);
+
+   Canvas.popDialog(CreateNewNavMeshDlg);
+}
+
+function MeshMissionBounds::onClick(%this)
+{
+   MeshTerrainBounds.setActive(%this.isStateOn());
+}

+ 169 - 0
Templates/Empty/game/tools/navEditor/NavEditorConsoleDlg.gui

@@ -0,0 +1,169 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiWindowCtrl(NavEditorConsoleDlg) {
+   text = "Nav Console";
+   resizeWidth = "1";
+   resizeHeight = "1";
+   canMove = "1";
+   canClose = "1";
+   canMinimize = "1";
+   canMaximize = "1";
+   canCollapse = "0";
+   closeCommand = "NavEditorConsoleDlg.setVisible(false);";
+   edgeSnap = "1";
+   margin = "0 0 0 0";
+   padding = "0 0 0 0";
+   anchorTop = "1";
+   anchorBottom = "0";
+   anchorLeft = "1";
+   anchorRight = "0";
+   position = "238 170";
+   extent = "320 240";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiWindowProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiTextCtrl() {
+      maxLength = "1024";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "3 222";
+      extent = "149 13";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "top";
+      profile = "GuiTextProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      internalName = "StatusLeft";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiScrollCtrl() {
+      willFirstRespond = "1";
+      hScrollBar = "dynamic";
+      vScrollBar = "dynamic";
+      lockHorizScroll = "1";
+      lockVertScroll = "0";
+      constantThumbHeight = "0";
+      childMargin = "0 0";
+      mouseWheelScrollSpeed = "-1";
+      margin = "-14 41 3 3";
+      padding = "0 0 0 0";
+      anchorTop = "0";
+      anchorBottom = "0";
+      anchorLeft = "0";
+      anchorRight = "0";
+      position = "3 23";
+      extent = "314 194";
+      minExtent = "8 2";
+      horizSizing = "relative";
+      vertSizing = "relative";
+      profile = "GuiEditorScrollProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      internalName = "OutputScroll";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiListBoxCtrl() {
+         allowMultipleSelections = "0";
+         fitParentWidth = "1";
+         colorBullet = "1";
+         position = "1 1";
+         extent = "312 16";
+         minExtent = "8 2";
+         horizSizing = "relative";
+         vertSizing = "relative";
+         profile = "GuiListBoxProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Output";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---
+
+new ScriptMsgListener(NavEditorConsoleListener);
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshCreated");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshRemoved");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshStartUpdate");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshUpdate");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshTileUpdate");
+
+function NavEditorConsoleListener::onNavMeshCreated(%this, %data)
+{
+}
+
+function NavEditorConsoleListener::onNavMeshRemoved(%this, %data)
+{
+}
+
+function NavEditorConsoleListener::onNavMeshStartUpdate(%this, %data)
+{
+   NavEditorConsoleDlg-->Output.clearItems();
+   NavEditorConsoleDlg-->Output.addItem("Build starting for NavMesh" SPC %data, "0 0.6 0");
+   NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+}
+
+function NavEditorConsoleListener::onNavMeshUpdate(%this, %data)
+{
+   %message = "";
+   if(getWordCount(%data) == 2)
+   {
+      %seconds = getWord(%data, 1);
+      %minutes = mFloor(%seconds / 60);
+      %seconds -= %minutes * 60;
+      %message = "Built NavMesh" SPC getWord(%data, 0) SPC "in" SPC %minutes @ "m" SPC mRound(%seconds) @ "s";
+      if(NavEditorGui.playSoundWhenDone)
+      {
+         sfxPlayOnce(Audio2D, "tools/navEditor/done.wav");
+      }
+   }
+   else
+   {
+      %message = "Loaded NavMesh" SPC %data;
+   }
+   NavEditorConsoleDlg-->Output.addItem(%message, "0 0.6 0");
+   NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+   NavEditorConsoleDlg->StatusLeft.setText("");
+}
+
+function NavEditorConsoleListener::onNavMeshTileUpdate(%this, %data)
+{
+   %mesh = getWord(%data, 0);
+   %index = getWord(%data, 1);
+   %total = getWord(%data, 2);
+   %tile = getWords(%data, 3, 4);
+   %success = getWord(%data, 5) == "1";
+   if(!%success)
+   {
+      %message = "NavMesh" SPC %mesh SPC "tile" SPC %tile SPC "build failed!";
+      NavEditorConsoleDlg-->Output.addItem(%message, "1 0 0");
+      NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+   }
+   %percent = %index / %total * 100;
+   NavEditorConsoleDlg->StatusLeft.setText("Build progress:" SPC mRound(%percent) @ "%");
+}

+ 854 - 0
Templates/Empty/game/tools/navEditor/NavEditorGui.gui

@@ -0,0 +1,854 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
+   canSaveDynamicFields = "0";
+   Enabled = "1";
+   isContainer = "1";
+   Profile = "NavEditorProfile";
+   HorizSizing = "width";
+   VertSizing = "height";
+   Position = "0 0";
+   Extent = "800 600";
+   MinExtent = "8 8";
+   canSave = "1";
+   Visible = "1";
+   tooltipprofile = "GuiToolTipProfile";
+   hovertime = "1000";
+   Docking = "None";
+   Margin = "0 0 0 0";
+   Padding = "0 0 0 0";
+   AnchorTop = "0";
+   AnchorBottom = "0";
+   AnchorLeft = "0";
+   AnchorRight = "0";
+   cameraZRot = "0";
+   forceFOV = "0";
+   renderMissionArea = "0";
+   missionAreaFillColor = "255 0 0 20";
+   missionAreaFrameColor = "255 0 0 128";
+   allowBorderMove = "0";
+   borderMovePixelSize = "20";
+   borderMoveSpeed = "0.1";
+   consoleFrameColor = "255 0 0 255";
+   consoleFillColor = "0 0 0 0";
+   consoleSphereLevel = "1";
+   consoleCircleSegments = "32";
+   consoleLineWidth = "1";
+   GizmoProfile = "GlobalGizmoProfile";
+
+   new GuiWindowCollapseCtrl(NavEditorTreeWindow) {
+      internalName = "";
+      canSaveDynamicFields = "0";
+      Enabled = "1";
+      isContainer = "1";
+      Profile = "GuiWindowProfile";
+      HorizSizing = "windowRelative";
+      VertSizing = "windowRelative";
+      Position = getWord($pref::Video::mode, 0) - 209
+         SPC getWord(EditorGuiToolbar.extent, 1) - 1;
+      Extent = "210 167";
+      MinExtent = "210 100";
+      canSave = "1";
+      isDecoy = "0";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      Margin = "0 0 0 0";
+      Padding = "0 0 0 0";
+      AnchorTop = "1";
+      AnchorBottom = "0";
+      AnchorLeft = "1";
+      AnchorRight = "0";
+      resizeWidth = "1";
+      resizeHeight = "1";
+      canMove = "1";
+      canClose = "0";
+      canMinimize = "0";
+      canMaximize = "0";
+      minSize = "50 50";
+      EdgeSnap = "1";
+      text = "Navigation";
+
+      new GuiButtonCtrl() {
+         Profile = "GuiButtonProfile";
+         buttonType = "PushButton";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "115 2";
+         extent = "90 18";
+         text = "New NavMesh";
+         command = "Canvas.pushDialog(CreateNewNavMeshDlg);";
+      };
+
+      new GuiContainer(){
+         profile = GuiDefaultProfile;
+         Position = "5 25";
+         Extent = "200 120";
+         Docking = "Client";
+         Margin = "3 1 3 3 ";
+         HorizSizing = "width";
+         VertSizing = "height";
+         isContainer = "1";
+         
+         new GuiScrollCtrl() {
+            canSaveDynamicFields = "0";
+            Enabled = "1";
+            isContainer = "1";
+            Profile = "GuiEditorScrollProfile";
+            HorizSizing = "width";
+            VertSizing = "height";
+            Position = "0 0";
+            Extent = "200 118";
+            MinExtent = "8 8";
+            canSave = "1";
+            isDecoy = "0";
+            Visible = "1";
+            tooltipprofile = "GuiToolTipProfile";
+            hovertime = "1000";
+            Docking = "Client";
+            Margin = "0 0 0 0";
+            Padding = "0 0 0 0";
+            AnchorTop = "1";
+            AnchorBottom = "0";
+            AnchorLeft = "1";
+            AnchorRight = "0";
+            willFirstRespond = "1";
+            hScrollBar = "alwaysOff";
+            vScrollBar = "dynamic";
+            lockHorizScroll = "true";
+            lockVertScroll = "false";
+            constantThumbHeight = "0";
+            childMargin = "0 0";
+            mouseWheelScrollSpeed = "-1";
+
+            new GuiTreeViewCtrl(NavTreeView) {
+               canSaveDynamicFields = "0";
+               Enabled = "1";
+               isContainer = "1";
+               Profile = "ToolsGuiTreeViewProfile";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Position = "1 1";
+               Extent = "193 21";
+               MinExtent = "8 8";
+               canSave = "1";
+               Visible = "1";
+               hovertime = "1000";
+               tabSize = "16";
+               textOffset = "2";
+               fullRowSelect = "0";
+               itemHeight = "21";
+               destroyTreeOnSleep = "1";
+               MouseDragging = "0";
+               MultipleSelections = "0";
+               DeleteObjectAllowed = "1";
+               DragToItemAllowed = "0";
+               showRoot = "0";
+               internalNamesOnly = "0";
+            };
+         };
+      };
+   };
+   new GuiWindowCollapseCtrl(NavEditorOptionsWindow) {
+      internalName = "Window";
+      canSaveDynamicFields = "0";
+      Enabled = "1";
+      isContainer = "1";
+      Profile = "GuiWindowProfile";
+      HorizSizing = "windowRelative";
+      VertSizing = "windowRelative";
+      Position = getWord($pref::Video::mode, 0) - 209 
+         SPC getWord(EditorGuiToolbar.extent, 1) + getWord(NavEditorTreeWindow.extent, 1) - 2;
+      Extent = "210 530";
+      MinExtent = "210 300";
+      canSave = "1";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      Margin = "0 0 0 0";
+      Padding = "0 0 0 0";
+      AnchorTop = "0";
+      AnchorBottom = "0";
+      AnchorLeft = "0";
+      AnchorRight = "0";
+      resizeWidth = "1";
+      resizeHeight = "1";
+      canMove = "1";
+      canClose = "0";
+      canMinimize = "0";
+      canMaximize = "0";
+      minSize = "50 50";
+      EdgeSnap = "1";
+      text = "Properties";
+      
+      new GuiContainer(){ //Actions
+         isContainer = "1";
+         Profile = "inspectorStyleRolloutDarkProfile";
+         HorizSizing = "width";
+         VertSizing = "bottom";
+         Position = "4 24";
+         Extent = "202 85";
+         Docking = "Top";
+         Margin = "3 3 3 3";
+         internalName = "ActionsBox";
+         
+         new GuiTextCtrl(){
+            Profile = "GuiDefaultProfile";
+            HorizSizing = "right";
+            VertSizing = "bottom";
+            Position = "5 0";
+            Extent = "86 18";
+            text = "Actions";
+         };
+         new GuiStackControl()
+         {
+            internalName = "SelectActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Build NavMesh";
+               command = "NavEditorGui.buildSelectedMeshes();";
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "182 20";
+               position = "0 20";
+
+               new GuiCheckboxCtrl() {
+                  internalName = "BackgroundBuildButton";
+                  text = "Background";
+                  groupNum = "-1";
+                  buttonType = "ToggleButton";
+                  useMouseEvents = "0";
+                  extent = "75 20";
+                  minExtent = "8 2";
+                  profile = "GuiCheckBoxProfile";
+                  visible = "1";
+                  active = "1";
+                  variable = "NavEditorGui.backgroundBuild";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "0";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+               };
+               new GuiCheckboxCtrl() {
+                  position = "75 0";
+                  internalName = "SaveIntermediatesButton";
+                  text = "Keep intermediates";
+                  groupNum = "-1";
+                  buttonType = "ToggleButton";
+                  useMouseEvents = "0";
+                  extent = "105 20";
+                  profile = "GuiCheckBoxProfile";
+                  visible = "1";
+                  active = "1";
+                  variable = "NavEditorGui.saveIntermediates";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "0";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+               };
+            };
+            new GuiCheckboxCtrl() {
+               internalName = "BuildSoundButton";
+               text = "Play sound when done";
+               groupNum = "-1";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "150 20";
+               minExtent = "8 2";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               variable = "NavEditorGui.playSoundWhenDone";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "LinkActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Rebuild links";
+               command = "NavEditorGui.buildLinks();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "CoverActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Create Cover";
+               command = "NavEditorGui.createCoverPoints();";
+            };
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Delete Cover";
+               command = "NavEditorGui.deleteCoverPoints();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "TileActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Rebuild tile";
+               command = "NavEditorGui.buildTile();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "TestActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "180 18";
+               text = "Spawn";
+               command = "NavEditorGui.spawnPlayer();";
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "190 18";
+
+               new GuiButtonCtrl() {
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Delete";
+                  command = "NavEditorGui.getPlayer().delete();";
+               };
+               new GuiButtonCtrl() {
+                  position = "100 0";
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Find cover";
+                  command = "NavEditorGui.findCover();";
+               };
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "190 18";
+
+               new GuiButtonCtrl() {
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Follow";
+                  command = "NavEditorGui.followObject();";
+               };
+               new GuiButtonCtrl() {
+                  position = "100 0";
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Stop";
+                  command = "NavEditorGui.getPlayer().stop();";
+               };
+            };
+         };
+      };
+      new GuiContainer(){
+         isContainer = "1";
+         Profile = "inspectorStyleRolloutDarkProfile";
+         HorizSizing = "width";
+         VertSizing = "bottom";
+         Position = "4 112";
+         Extent = "202 31";
+         Docking = "Top";
+         Margin = "0 0 3 3";
+         
+         new GuiTextCtrl(){
+            Profile = "GuiDefaultProfile";
+            HorizSizing = "right";
+            VertSizing = "bottom";
+            Position = "5 0";
+            Extent = "121 18";
+            text = "Properties";
+         };
+      };
+
+      new GuiScrollCtrl() {
+         canSaveDynamicFields = "0";
+         Enabled = "1";
+         isContainer = "1";
+         Profile = "GuiEditorScrollProfile";
+         HorizSizing = "width";
+         VertSizing = "height";
+         Position = "4 129";
+         Extent = "202 357";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         Docking = "Client";
+         Margin = "-14 41 3 3";
+         Padding = "0 0 0 0";
+         AnchorTop = "0";
+         AnchorBottom = "0";
+         AnchorLeft = "0";
+         AnchorRight = "0";
+         willFirstRespond = "1";
+         hScrollBar = "alwaysOff";
+         vScrollBar = "dynamic";
+         lockHorizScroll = "true";
+         lockVertScroll = "false";
+         constantThumbHeight = "0";
+         childMargin = "0 0";
+         internalName = "PropertiesBox";
+
+         new GuiInspector(NavInspector) {
+            StackingType = "Vertical";
+            HorizStacking = "Left to Right";
+            VertStacking = "Top to Bottom";
+            Padding = "1";
+            name = "NavInspector";
+            canSaveDynamicFields = "0";
+            Enabled = "1";
+            isContainer = "1";
+            Profile = "GuiTransparentProfile";
+            HorizSizing = "width";
+            VertSizing = "height";
+            Position = "1 1";
+            Extent = "178 16";
+            MinExtent = "16 16";
+            canSave = "1";
+            Visible = "1";
+            tooltipprofile = "GuiToolTipProfile";
+            hovertime = "1000";
+            dividerMargin = "5";
+         };
+         
+         new GuiStackControl() {
+            internalName = "LinkProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkWalkFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Walk";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "This link is just ordinary flat ground.";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkJumpFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Jump";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link require a jump?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkDropFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Drop";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link involve a significant drop?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkLedgeFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Ledge";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Should the character jump at the next ledge?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkClimbFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Climb";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link involve climbing?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkTeleportFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Teleport";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Is this link a teleporter?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+         new GuiStackControl() {
+            internalName = "TileProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+            new GuiCheckBoxCtrl() {
+               text = " Display input geometry";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+               variable = "$Nav::Editor::renderInput";
+            };
+            new GuiCheckBoxCtrl() {
+               text = " Display voxels";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+               variable = "$Nav::Editor::renderVoxels";
+            };
+         };
+         new GuiStackControl() {
+            internalName = "TestProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+			new GuiTextCtrl() {
+               text = "Cover";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "CoverRadius";
+               text = "10";
+               profile = "GuiTextEditProfile";
+               extent = "40 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Radius for cover-finding.";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "CoverPosition";
+               text = "LocalClientConnection.getControlObject().getPosition();";
+               profile = "GuiTextEditProfile";
+               extent = "140 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Position to find cover from.";
+            };
+            new GuiTextCtrl() {
+               text = "Follow";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "FollowRadius";
+               text = "1";
+               profile = "GuiTextEditProfile";
+               extent = "40 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Radius for following.";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "FollowObject";
+               text = "LocalClientConnection.player";
+               profile = "GuiTextEditProfile";
+               extent = "140 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Object to follow.";
+            };
+            new GuiTextCtrl() {
+               text = "Movement";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkWalkFlag";
+               class = "NavMeshTestFlagButton";
+               text = " Walk";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character walk on ground?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkJumpFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Jump";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character jump?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkDropFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Drop";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character drop over edges?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkLedgeFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Ledge";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character jump from ledges?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkClimbFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Climb";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character climb?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkTeleportFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Teleport";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character teleport?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+      };
+      new GuiMLTextCtrl(NavFieldInfoControl) {
+         canSaveDynamicFields = "0";
+         Enabled = "1";
+         isContainer = "0";
+         Profile = "GuiInspectorFieldInfoMLTextProfile";
+         HorizSizing = "width";
+         VertSizing = "top";
+         Position = "1 485";
+         Extent = "202 42";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         lineSpacing = "2";
+         allowColorChars = "0";
+         maxChars = "-1";
+         useURLMouseCursor = "0";
+      };
+   };
+   
+};
+
+//--- OBJECT WRITE END ---

+ 506 - 0
Templates/Empty/game/tools/navEditor/NavEditorSettingsTab.gui

@@ -0,0 +1,506 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiTabPageCtrl(ENavEditorSettingsPage) {
+   fitBook = "1";
+   text = "Navigation Editor";
+   maxLength = "1024";
+   margin = "0 0 0 0";
+   padding = "0 0 0 0";
+   anchorTop = "1";
+   anchorBottom = "0";
+   anchorLeft = "1";
+   anchorRight = "0";
+   position = "0 0";
+   extent = "208 292";
+   minExtent = "8 2";
+   horizSizing = "width";
+   vertSizing = "height";
+   profile = "GuiSolidDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiScrollCtrl() {
+      willFirstRespond = "1";
+      hScrollBar = "alwaysOff";
+      vScrollBar = "dynamic";
+      lockHorizScroll = "1";
+      lockVertScroll = "0";
+      constantThumbHeight = "0";
+      childMargin = "0 0";
+      mouseWheelScrollSpeed = "-1";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "0 0";
+      extent = "208 292";
+      minExtent = "8 2";
+      horizSizing = "width";
+      vertSizing = "height";
+      profile = "GuiScrollProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiStackControl() {
+         stackingType = "Vertical";
+         horizStacking = "Left to Right";
+         vertStacking = "Top to Bottom";
+         padding = "0";
+         dynamicSize = "1";
+         dynamicNonStackExtent = "0";
+         dynamicPos = "0";
+         changeChildSizeToFit = "1";
+         changeChildPosition = "1";
+         position = "1 1";
+         extent = "206 124";
+         minExtent = "8 2";
+         horizSizing = "width";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+
+         new GuiRolloutCtrl() {
+            caption = "Test spawn";
+            margin = "0 3 0 0";
+            defaultHeight = "40";
+            expanded = "1";
+            clickCollapse = "1";
+            hideHeader = "0";
+            autoCollapseSiblings = "0";
+            position = "0 0";
+            extent = "206 62";
+            minExtent = "8 2";
+            horizSizing = "right";
+            vertSizing = "bottom";
+            profile = "GuiRolloutProfile";
+            visible = "1";
+            active = "1";
+            tooltipProfile = "GuiToolTipProfile";
+            hovertime = "1000";
+            isContainer = "1";
+            canSave = "1";
+            canSaveDynamicFields = "0";
+
+            new GuiStackControl() {
+               stackingType = "Vertical";
+               horizStacking = "Left to Right";
+               vertStacking = "Top to Bottom";
+               padding = "3";
+               dynamicSize = "1";
+               dynamicNonStackExtent = "0";
+               dynamicPos = "0";
+               changeChildSizeToFit = "1";
+               changeChildPosition = "1";
+               position = "0 20";
+               extent = "206 39";
+               minExtent = "8 2";
+               horizSizing = "width";
+               vertSizing = "bottom";
+               profile = "GuiDefaultProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "1";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+
+               new GuiControl() {
+                  position = "0 0";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+
+                  new GuiTextCtrl() {
+                     text = "Spawn class:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "5 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "0";
+                  };
+                  new GuiPopUpMenuCtrlEx() {
+                     maxPopupHeight = "200";
+                     sbUsesNAColor = "0";
+                     reverseTextList = "0";
+                     bitmapBounds = "16 16";
+                     hotTrackCallback = "0";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "81 0";
+                     extent = "121 18";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiPopUpMenuProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "1";
+                     internalName = "SpawnClassOptions";
+                     class = "ESettingsWindowPopup";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                        editorSettingsRead = "NavEditorPlugin.readSettings();";
+                        editorSettingsValue = "NavEditor/SpawnClass";
+                        editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+                  };
+               };
+               new GuiControl() {
+                  position = "0 21";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+
+                  new GuiTextCtrl() {
+                     text = "Datablock:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "5 1";
+                     extent = "70 18";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "0";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     text = "DefaultPlayerData";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "81 0";
+                     extent = "121 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     class = "ESettingsWindowTextEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                        editorSettingsRead = "NavEditorPlugin.readSettings();";
+                        editorSettingsValue = "NavEditor/SpawnDatablock";
+                        editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+                  };
+               };
+            };
+         };
+         new GuiRolloutCtrl() {
+            caption = "Colors";
+            margin = "0 3 0 0";
+            defaultHeight = "40";
+            expanded = "1";
+            clickCollapse = "1";
+            hideHeader = "0";
+            autoCollapseSiblings = "0";
+            position = "0 62";
+            extent = "206 62";
+            minExtent = "8 2";
+            horizSizing = "right";
+            vertSizing = "bottom";
+            profile = "GuiRolloutProfile";
+            visible = "1";
+            active = "1";
+            tooltipProfile = "GuiToolTipProfile";
+            hovertime = "1000";
+            isContainer = "1";
+            canSave = "1";
+            canSaveDynamicFields = "0";
+
+            new GuiStackControl() {
+               stackingType = "Vertical";
+               horizStacking = "Left to Right";
+               vertStacking = "Top to Bottom";
+               padding = "3";
+               dynamicSize = "1";
+               dynamicNonStackExtent = "0";
+               dynamicPos = "0";
+               changeChildSizeToFit = "1";
+               changeChildPosition = "1";
+               position = "0 20";
+               extent = "206 39";
+               minExtent = "8 2";
+               horizSizing = "width";
+               vertSizing = "bottom";
+               profile = "GuiDefaultProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "1";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+
+               new GuiControl() {
+                  position = "0 0";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  class = "ESettingsWindowColor";
+                  canSave = "1";
+                  canSaveDynamicFields = "1";
+                     editorSettingsRead = "NavEditorPlugin.readSettings();";
+                     editorSettingsValue = "NavEditor/HoverSplineColor";
+                     editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+
+                  new GuiTextCtrl() {
+                     text = "Hover Spline:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "0 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "80 0";
+                     extent = "104 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorEdit";
+                     class = "ESettingsWindowColorEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiSwatchButtonCtrl() {
+                     color = "0 0 0 0";
+                     groupNum = "-1";
+                     buttonType = "PushButton";
+                     useMouseEvents = "0";
+                     position = "188 2";
+                     extent = "14 14";
+                     minExtent = "8 2";
+                     horizSizing = "left";
+                     vertSizing = "bottom";
+                     profile = "GuiDefaultProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorButton";
+                     class = "ESettingsWindowColorButton";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+               };
+               new GuiControl() {
+                  position = "0 21";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  class = "ESettingsWindowColor";
+                  canSave = "1";
+                  canSaveDynamicFields = "1";
+                     editorSettingsRead = "NavEditorPlugin.readSettings();";
+                     editorSettingsValue = "NavEditor/SelectedSplineColor";
+                     editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+
+                  new GuiTextCtrl() {
+                     text = "Sel. Spline:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "0 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "80 0";
+                     extent = "104 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorEdit";
+                     class = "ESettingsWindowColorEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiSwatchButtonCtrl() {
+                     color = "0 0 0 0";
+                     groupNum = "-1";
+                     buttonType = "PushButton";
+                     useMouseEvents = "0";
+                     position = "188 2";
+                     extent = "14 14";
+                     minExtent = "8 2";
+                     horizSizing = "left";
+                     vertSizing = "bottom";
+                     profile = "GuiDefaultProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorButton";
+                     class = "ESettingsWindowColorButton";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+               };
+            };
+         };
+      };
+   };
+};
+//--- OBJECT WRITE END ---

+ 144 - 0
Templates/Empty/game/tools/navEditor/NavEditorToolbar.gui

@@ -0,0 +1,144 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiControl(NavEditorToolbar,EditorGuiGroup) {
+   position = "306 0";
+   extent = "800 32";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   internalName = "NavEditorToolbar";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+      enabled = "1";
+
+   new GuiTextCtrl() {
+      text = "Navigation Editor";
+      maxLength = "255";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "6 6";
+      extent = "150 20";
+      minExtent = "8 8";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiTextProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiBitmapCtrl() {
+      bitmap = "core/art/gui/images/separator-h.png";
+      wrap = "0";
+      position = "90 3";
+      extent = "2 26";
+      minExtent = "1 1";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiDefaultProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiButtonCtrl(NavEditorAboutBtn) {
+      text = "Console";
+      groupNum = "7";
+      buttonType = "PushButton";
+      useMouseEvents = "0";
+      position = "100 6";
+      extent = "54 20";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiButtonProfile";
+      visible = "1";
+      active = "1";
+      command = "NavEditorConsoleDlg.setVisible(!NavEditorConsoleDlg.isVisible());";
+      tooltipProfile = "GuiToolTipProfile";
+      tooltip = "Show Console";
+      hovertime = "1000";
+      isContainer = "0";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "Mesh";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "167 1";
+      extent = "50 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderMesh";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "MeshButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "Portals";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "224 1";
+      extent = "54 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderPortals";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "PortalButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "BV tree";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "286 1";
+      extent = "140 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderBVTree";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "BVTreeButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+};
+//--- OBJECT WRITE END ---

二进制
Templates/Empty/game/tools/navEditor/done.wav


二进制
Templates/Empty/game/tools/navEditor/images/nav-cover_d.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-cover_h.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-cover_n.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-editor_d.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-editor_h.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-editor_n.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-link_d.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-link_h.png


二进制
Templates/Empty/game/tools/navEditor/images/nav-link_n.png


+ 285 - 0
Templates/Empty/game/tools/navEditor/main.cs

@@ -0,0 +1,285 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+// These values should align with enum PolyFlags in walkabout/nav.h
+$Nav::WalkFlag = 1 << 0;
+$Nav::SwimFlag = 1 << 1;
+$Nav::JumpFlag = 1 << 2;
+$Nav::LedgeFlag = 1 << 3;
+$Nav::DropFlag = 1 << 4;
+$Nav::ClimbFlag = 1 << 5;
+$Nav::TeleportFlag = 1 << 6;
+
+function initializeNavEditor()
+{
+   echo(" % - Initializing Navigation Editor");
+
+   // Execute all relevant scripts and GUIs.
+   exec("./NavEditor.cs");
+   exec("./NavEditorGui.gui");
+   exec("./NavEditorToolbar.gui");
+   exec("./NavEditorConsoleDlg.gui");
+   exec("./CreateNewNavMeshDlg.gui");
+
+   // Add ourselves to EditorGui, where all the other tools reside
+   NavEditorGui.setVisible(false);  
+   NavEditorToolbar.setVisible(false); 
+   NavEditorOptionsWindow.setVisible(false);
+   NavEditorTreeWindow.setVisible(false);
+   NavEditorConsoleDlg.setVisible(false);
+
+   EditorGui.add(NavEditorGui);
+   EditorGui.add(NavEditorToolbar);
+   EditorGui.add(NavEditorOptionsWindow);
+   EditorGui.add(NavEditorTreeWindow);
+   EditorGui.add(NavEditorConsoleDlg);
+
+   new ScriptObject(NavEditorPlugin)
+   {
+      superClass = "EditorPlugin";
+      editorGui = NavEditorGui;
+   };
+
+   // Bind shortcuts for the nav editor.
+   %map = new ActionMap();
+   %map.bindCmd(keyboard, "1", "ENavEditorSelectModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "2", "ENavEditorLinkModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "3", "ENavEditorCoverModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "4", "ENavEditorTileModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "5", "ENavEditorTestModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "c", "NavEditorConsoleBtn.performClick();", "");
+   NavEditorPlugin.map = %map;
+
+   NavEditorPlugin.initSettings();
+}
+
+function destroyNavEditor()
+{
+}
+
+function NavEditorPlugin::onWorldEditorStartup(%this)
+{    
+    // Add ourselves to the window menu.
+   %accel = EditorGui.addToEditorsMenu("Navigation Editor", "", NavEditorPlugin);   
+
+   // Add ourselves to the ToolsToolbar.
+   %tooltip = "Navigation Editor (" @ %accel @ ")";   
+   EditorGui.addToToolsToolbar("NavEditorPlugin", "NavEditorPalette", expandFilename("tools/navEditor/images/nav-editor"), %tooltip);
+
+   GuiWindowCtrl::attach(NavEditorOptionsWindow, NavEditorTreeWindow);
+
+   // Add ourselves to the Editor Settings window.
+   exec("./NavEditorSettingsTab.gui");
+   ESettingsWindow.addTabPage(ENavEditorSettingsPage);
+   ENavEditorSettingsPage.init();
+
+   // Add items to World Editor Creator
+   EWCreatorWindow.beginGroup("Navigation");
+
+      EWCreatorWindow.registerMissionObject("CoverPoint", "Cover point");
+
+   EWCreatorWindow.endGroup();
+}
+
+function ENavEditorSettingsPage::init(%this)
+{
+   // Initialises the settings controls in the settings dialog box.
+   %this-->SpawnClassOptions.clear();
+   %this-->SpawnClassOptions.add("AIPlayer");
+   %this-->SpawnClassOptions.setFirstSelected();
+}
+
+function NavEditorPlugin::onActivated(%this)
+{
+   %this.readSettings();
+
+   // Set a global variable so everyone knows we're editing!
+   $Nav::EditorOpen = true;
+
+   // Start off in Select mode.
+   ToolsPaletteArray->NavEditorSelectMode.performClick();
+   EditorGui.bringToFront(NavEditorGui);
+
+   NavEditorGui.setVisible(true);
+   NavEditorGui.makeFirstResponder(true);
+   NavEditorToolbar.setVisible(true);
+
+   NavEditorOptionsWindow.setVisible(true);
+   NavEditorTreeWindow.setVisible(true);
+
+   // Inspect the ServerNavMeshSet, which contains all the NavMesh objects
+   // in the mission.
+   if(!isObject(ServerNavMeshSet))
+      new SimSet(ServerNavMeshSet);
+   if(ServerNavMeshSet.getCount() == 0)
+	  MessageBoxYesNo("No NavMesh", "There is no NavMesh in this level. Would you like to create one?" SPC
+	                                "If not, please use the Nav Editor to create a new NavMesh.",
+	                                "Canvas.pushDialog(CreateNewNavMeshDlg);");
+   NavTreeView.open(ServerNavMeshSet, true);
+
+   // Push our keybindings to the top. (See initializeNavEditor for where this
+   // map was created.)
+   %this.map.push();
+
+   // Store this on a dynamic field
+   // in order to restore whatever setting
+   // the user had before.
+   %this.prevGizmoAlignment = GlobalGizmoProfile.alignment;
+
+   // Always use Object alignment.
+   GlobalGizmoProfile.alignment = "Object";
+
+   // Set the status until some other editing mode adds useful information.
+   EditorGuiStatusBar.setInfo("Navigation editor.");
+   EditorGuiStatusBar.setSelection("");
+
+   // Allow the Gui to setup.
+   NavEditorGui.onEditorActivated(); 
+
+   Parent::onActivated(%this);
+}
+
+function NavEditorPlugin::onDeactivated(%this)
+{
+   %this.writeSettings();
+
+   $Nav::EditorOpen = false;   
+
+   NavEditorGui.setVisible(false);
+   NavEditorToolbar.setVisible(false);
+   NavEditorOptionsWindow.setVisible(false);
+   NavEditorTreeWindow.setVisible(false);
+   %this.map.pop();
+
+   // Restore the previous Gizmo alignment settings.
+   GlobalGizmoProfile.alignment = %this.prevGizmoAlignment;  
+
+   // Allow the Gui to cleanup.
+   NavEditorGui.onEditorDeactivated(); 
+
+   Parent::onDeactivated(%this);
+}
+
+function NavEditorPlugin::onEditMenuSelect(%this, %editMenu)
+{
+   %hasSelection = false;
+}
+
+function NavEditorPlugin::handleDelete(%this)
+{
+   // Event happens when the user hits 'delete'.
+   NavEditorGui.deleteSelected();
+}
+
+function NavEditorPlugin::handleEscape(%this)
+{
+   return NavEditorGui.onEscapePressed();  
+}
+
+function NavEditorPlugin::isDirty(%this)
+{
+   return NavEditorGui.isDirty;
+}
+
+function NavEditorPlugin::onSaveMission(%this, %missionFile)
+{
+   if(NavEditorGui.isDirty)
+   {
+      MissionGroup.save(%missionFile);
+      NavEditorGui.isDirty = false;
+   }
+}
+
+//-----------------------------------------------------------------------------
+// Settings
+//-----------------------------------------------------------------------------
+
+function NavEditorPlugin::initSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   EditorSettings.setDefaultValue("SpawnClass",     "AIPlayer");
+   EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData");
+
+   EditorSettings.endGroup();
+}
+
+function NavEditorPlugin::readSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   // Currently these are globals because of the way they are accessed in navMesh.cpp.
+   $Nav::Editor::renderMesh       = EditorSettings.value("RenderMesh");
+   $Nav::Editor::renderPortals    = EditorSettings.value("RenderPortals");
+   $Nav::Editor::renderBVTree     = EditorSettings.value("RenderBVTree");
+   NavEditorGui.spawnClass        = EditorSettings.value("SpawnClass");
+   NavEditorGui.spawnDatablock    = EditorSettings.value("SpawnDatablock");
+   NavEditorGui.backgroundBuild   = EditorSettings.value("BackgroundBuild");
+   NavEditorGui.saveIntermediates = EditorSettings.value("SaveIntermediates");
+   NavEditorGui.playSoundWhenDone = EditorSettings.value("PlaySoundWhenDone");
+
+   EditorSettings.endGroup();  
+}
+
+function NavEditorPlugin::writeSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   EditorSettings.setValue("RenderMesh",        $Nav::Editor::renderMesh);
+   EditorSettings.setValue("RenderPortals",     $Nav::Editor::renderPortals);
+   EditorSettings.setValue("RenderBVTree",      $Nav::Editor::renderBVTree);
+   EditorSettings.setValue("SpawnClass",        NavEditorGui.spawnClass);
+   EditorSettings.setValue("SpawnDatablock",    NavEditorGui.spawnDatablock);
+   EditorSettings.setValue("BackgroundBuild",   NavEditorGui.backgroundBuild);
+   EditorSettings.setValue("SaveIntermediates", NavEditorGui.saveIntermediates);
+   EditorSettings.setValue("PlaySoundWhenDone", NavEditorGui.playSoundWhenDone);
+
+   EditorSettings.endGroup();
+}
+
+function ESettingsWindowPopup::onWake(%this)
+{
+   %this.setSelected(%this.findText(EditorSettings.value(%this.editorSettingsValue)));
+}
+
+function ESettingsWindowPopup::onSelect(%this)
+{
+   EditorSettings.setValue(%this.editorSettingsValue, %this.getText());
+   eval(%this.editorSettingsRead);
+}
+
+//-----------------------------------------------------------------------------
+// Demo
+//-----------------------------------------------------------------------------
+
+function OnWalkaboutDemoLimit()
+{
+   MessageBoxOK("Walkabout demo",
+      "This demo only allows two NavMeshes to be created. Sorry!");
+}
+
+function OnWalkaboutDemoSave()
+{
+   MessageBoxOK("Walkabout demo",
+      "This demo doesn't allow you to save NavMeshes. Sorry!" SPC
+      "The rest of your mission will still be saved.");
+}

+ 360 - 0
Templates/Empty/game/tools/navEditor/navEditor.cs

@@ -0,0 +1,360 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+$Nav::EditorOpen = false;
+
+function NavEditorGui::onEditorActivated(%this)
+{
+   if(%this.selectedObject)
+      %this.selectObject(%this.selectedObject);
+   %this.prepSelectionMode();
+}
+
+function NavEditorGui::onEditorDeactivated(%this)
+{
+   if(%this.getMesh())
+      %this.deselect();
+}
+
+function NavEditorGui::onModeSet(%this, %mode)
+{
+   // Callback when the nav editor changes mode. Set the appropriate dynamic
+   // GUI contents in the properties/actions boxes.
+   NavInspector.setVisible(false);
+
+   %actions = NavEditorOptionsWindow->ActionsBox;
+   %actions->SelectActions.setVisible(false);
+   %actions->LinkActions.setVisible(false);
+   %actions->CoverActions.setVisible(false);
+   %actions->TileActions.setVisible(false);
+   %actions->TestActions.setVisible(false);
+
+   %properties = NavEditorOptionsWindow->PropertiesBox;
+   %properties->LinkProperties.setVisible(false);
+   %properties->TileProperties.setVisible(false);
+   %properties->TestProperties.setVisible(false);
+
+   switch$(%mode)
+   {
+   case "SelectMode":
+      NavInspector.setVisible(true);
+      %actions->SelectActions.setVisible(true);
+   case "LinkMode":
+      %actions->LinkActions.setVisible(true);
+      %properties->LinkProperties.setVisible(true);
+   case "CoverMode":
+      // 
+      %actions->CoverActions.setVisible(true);
+   case "TileMode":
+      %actions->TileActions.setVisible(true);
+      %properties->TileProperties.setVisible(true);
+   case "TestMode":
+      %actions->TestActions.setVisible(true);
+      %properties->TestProperties.setVisible(true);
+   }
+}
+
+function NavEditorGui::paletteSync(%this, %mode)
+{
+   // Synchronise the palette (small buttons on the left) with the actual mode
+   // the nav editor is in.
+   %evalShortcut = "ToolsPaletteArray-->" @ %mode @ ".setStateOn(1);";
+   eval(%evalShortcut);
+} 
+
+function NavEditorGui::onEscapePressed(%this)
+{
+   return false;
+}
+
+function NavEditorGui::selectObject(%this, %obj)
+{
+   NavTreeView.clearSelection();
+   if(isObject(%obj))
+      NavTreeView.selectItem(%obj);
+   %this.onObjectSelected(%obj);
+}
+
+function NavEditorGui::onObjectSelected(%this, %obj)
+{
+   if(isObject(%this.selectedObject))
+      %this.deselect();
+   %this.selectedObject = %obj;
+   if(isObject(%obj))
+   {
+      %this.selectMesh(%obj);
+      NavInspector.inspect(%obj);
+   }
+}
+
+function NavEditorGui::deleteMesh(%this)
+{
+   if(isObject(%this.selectedObject))
+   {
+      %this.selectedObject.delete();
+      %this.selectObject(-1);
+   }
+}
+
+function NavEditorGui::deleteSelected(%this)
+{
+   switch$(%this.getMode())
+   {
+   case "SelectMode":
+      // Try to delete the selected NavMesh.
+      if(isObject(NavEditorGui.selectedObject))
+         MessageBoxYesNo("Warning",
+            "Are you sure you want to delete" SPC NavEditorGui.selectedObject.getName(),
+            "NavEditorGui.deleteMesh();");
+   case "TestMode":
+      %this.getPlayer().delete();
+      %this.onPlayerDeselected();
+   case "LinkMode":
+      %this.deleteLink();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::buildSelectedMeshes(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().build(NavEditorGui.backgroundBuild, NavEditorGui.saveIntermediates);
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::buildLinks(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().buildLinks();
+      %this.isDirty = true;
+   }
+}
+
+function updateLinkData(%control, %flags)
+{
+   %control->LinkWalkFlag.setActive(true);
+   %control->LinkJumpFlag.setActive(true);
+   %control->LinkDropFlag.setActive(true);
+   %control->LinkLedgeFlag.setActive(true);
+   %control->LinkClimbFlag.setActive(true);
+   %control->LinkTeleportFlag.setActive(true);
+
+   %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag);
+   %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag);
+   %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag);
+   %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag);
+   %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag);
+   %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag);
+}
+
+function getLinkFlags(%control)
+{
+   return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) |
+          (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) |
+          (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) |
+          (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) |
+          (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) |
+          (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0);
+}
+
+function disableLinkData(%control)
+{
+   %control->LinkWalkFlag.setActive(false);
+   %control->LinkJumpFlag.setActive(false);
+   %control->LinkDropFlag.setActive(false);
+   %control->LinkLedgeFlag.setActive(false);
+   %control->LinkClimbFlag.setActive(false);
+   %control->LinkTeleportFlag.setActive(false);
+}
+
+function NavEditorGui::onLinkSelected(%this, %flags)
+{
+   updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags);
+}
+
+function NavEditorGui::onPlayerSelected(%this, %flags)
+{
+   updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags);
+}
+
+function NavEditorGui::updateLinkFlags(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %properties = NavEditorOptionsWindow-->LinkProperties;
+      %this.setLinkFlags(getLinkFlags(%properties));
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::updateTestFlags(%this)
+{
+   if(isObject(%this.getPlayer()))
+   {
+      %properties = NavEditorOptionsWindow-->TestProperties;
+      %player = %this.getPlayer();
+
+      %player.allowWwalk = %properties->LinkWalkFlag.isStateOn();
+      %player.allowJump = %properties->LinkJumpFlag.isStateOn();
+      %player.allowDrop = %properties->LinkDropFlag.isStateOn();
+      %player.allowLedge = %properties->LinkLedgeFlag.isStateOn();
+      %player.allowClimb = %properties->LinkClimbFlag.isStateOn();
+      %player.allowTeleport = %properties->LinkTeleportFlag.isStateOn();
+
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::onLinkDeselected(%this)
+{
+   disableLinkData(NavEditorOptionsWindow-->LinkProperties);
+}
+
+function NavEditorGui::onPlayerDeselected(%this)
+{
+   disableLinkData(NavEditorOptionsWindow-->TestProperties);
+}
+
+function NavEditorGui::createCoverPoints(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().createCoverPoints();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::deleteCoverPoints(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().deleteCoverPoints();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::findCover(%this)
+{
+   if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer()))
+   {
+      %pos = LocalClientConnection.getControlObject().getPosition();
+      %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText();
+      if(%text !$= "")
+         %pos = eval(%text);
+      %this.getPlayer().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText());
+   }
+}
+
+function NavEditorGui::followObject(%this)
+{
+   if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer()))
+   {
+      %obj = LocalClientConnection.player;
+      %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText();
+      if(%text !$= "")
+      {
+         eval("%obj = " @ %text);
+         if(!isObject(%obj))
+            MessageBoxOk("Error", "Cannot find object" SPC %text);
+      }
+      if(isObject(%obj))
+         %this.getPlayer().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText());
+   }
+}
+
+function NavInspector::inspect(%this, %obj)
+{
+   %name = "";
+   if(isObject(%obj))
+      %name = %obj.getName();
+   else
+      NavFieldInfoControl.setText("");
+
+   Parent::inspect(%this, %obj);
+}
+
+function NavInspector::onInspectorFieldModified(%this, %object, %fieldName, %arrayIndex, %oldValue, %newValue)
+{
+   // Same work to do as for the regular WorldEditor Inspector.
+   Inspector::onInspectorFieldModified(%this, %object, %fieldName, %arrayIndex, %oldValue, %newValue);
+}
+
+function NavInspector::onFieldSelected(%this, %fieldName, %fieldTypeStr, %fieldDoc)
+{
+   NavFieldInfoControl.setText("<font:ArialBold:14>" @ %fieldName @ "<font:ArialItalic:14> (" @ %fieldTypeStr @ ") " NL "<font:Arial:14>" @ %fieldDoc);
+}
+
+function NavTreeView::onInspect(%this, %obj)
+{
+   NavInspector.inspect(%obj);
+}
+
+function NavTreeView::onSelect(%this, %obj)
+{
+   NavInspector.inspect(%obj);
+   NavEditorGui.onObjectSelected(%obj);
+}
+
+function NavEditorGui::prepSelectionMode(%this)
+{
+   %this.setMode("SelectMode");
+   ToolsPaletteArray-->NavEditorSelectMode.setStateOn(1);
+}
+
+//-----------------------------------------------------------------------------
+
+function ENavEditorPaletteButton::onClick(%this)
+{
+   // When clicking on a pelette button, add its description to the bottom of
+   // the editor window.
+   EditorGuiStatusBar.setInfo(%this.DetailedDesc);
+}
+
+//-----------------------------------------------------------------------------
+
+function NavMeshLinkFlagButton::onClick(%this)
+{
+   NavEditorGui.updateLinkFlags();
+}
+
+function NavMeshTestFlagButton::onClick(%this)
+{
+   NavEditorGui.updateTestFlags();
+}
+
+singleton GuiControlProfile(NavEditorProfile)
+{
+   canKeyFocus = true;
+   opaque = true;
+   fillColor = "192 192 192 192";
+   category = "Editor";
+};
+
+singleton GuiControlProfile(GuiSimpleBorderProfile)
+{
+   opaque = false;   
+   border = 1;   
+   category = "Editor";
+};

+ 130 - 0
Templates/Empty/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui

@@ -0,0 +1,130 @@
+%paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) {
+   canSaveDynamicFields = "0";
+   Enabled = "1";
+   isContainer = "1";
+   Profile = "GuiDefaultProfile";
+   HorizSizing = "right";
+   VertSizing = "bottom";
+   Position = "0 0";
+   Extent = "1024 768";
+   MinExtent = "8 2";
+   canSave = "1";
+   Visible = "1";
+   hovertime = "1000";
+   
+   new GuiBitmapButtonCtrl(ENavEditorSelectModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorSelectMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.prepSelectionMode();";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "View NavMesh (1).";
+      DetailedDesc = "";
+      hovertime = "1000";
+      bitmap = "tools/gui/images/menubar/visibility-toggle";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorLinkModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorLinkMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"LinkMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Create off-mesh links (2).";
+      DetailedDesc = "Click to select/add. Shift-click to add multiple end points.";
+      hovertime = "1000";
+      bitmap = "tools/navEditor/images/nav-link";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorCoverModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorCoverMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"CoverMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Edit cover (3).";
+      DetailedDesc = "";
+      hovertime = "1000";
+      bitmap = "tools/navEditor/images/nav-cover";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorTileModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorTileMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"TileMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "View tiles (4).";
+      DetailedDesc = "Click to select.";
+      hovertime = "1000";
+      bitmap = "tools/gui/images/menubar/select-bounds";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorTestModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorTestMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"TestMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Test pathfinding (5).";
+      DetailedDesc = "Click to select/move character, CTRL-click to spawn, SHIFT-click to deselect.";
+      hovertime = "1000";
+      bitmap = "tools/worldEditor/images/toolbar/3rd-person-camera";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+};

+ 1 - 1
Templates/Empty/game/tools/worldEditor/gui/ToolsToolbar.ed.gui

@@ -8,7 +8,7 @@
    HorizSizing = "right";
    VertSizing = "bottom";
    Position = "0 31";
-   Extent = (29 + 4) * 14 + 12 SPC "33";
+   Extent = "0 33";
    MinExtent = "8 2";
    canSave = "1";
    Visible = "1";

+ 1 - 0
Templates/Empty/game/tools/worldEditor/scripts/EditorGui.ed.cs

@@ -384,6 +384,7 @@ function EditorGui::addToToolsToolbar( %this, %pluginName, %internalName, %bitma
          useMouseEvents = "0";
       };
       ToolsToolbarArray.add(%button);
+      EWToolsToolbar.setExtent((25 + 8) * (%count + 1) + 12 SPC "33");
    }
 }
 

+ 1 - 3
Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -84,8 +84,6 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SFXSpace",      "Sound Space" );
       %this.registerMissionObject( "OcclusionVolume", "Occlusion Volume" );
       %this.registerMissionObject( "AccumulationVolume", "Accumulation Volume" );
-      %this.registerMissionObject("NavMesh", "Navigation mesh");
-      %this.registerMissionObject("NavPath", "Path");
       
    %this.endGroup();
    
@@ -788,4 +786,4 @@ function genericCreateObject( %class )
    
    // In case the caller wants it.
    return %obj;   
-}
+}

+ 392 - 0
Templates/Full/game/tools/navEditor/CreateNewNavMeshDlg.gui

@@ -0,0 +1,392 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiControl(CreateNewNavMeshDlg) {
+   position = "0 0";
+   extent = "1024 768";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiWindowCtrl() {
+      text = "New NavMesh";
+      resizeWidth = "0";
+      resizeHeight = "0";
+      canMove = "1";
+      canClose = "1";
+      canMinimize = "0";
+      canMaximize = "0";
+      canCollapse = "0";
+      closeCommand = "Canvas.popDialog(CreateNewNavMeshDlg);";
+      edgeSnap = "1";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "283 240";
+      extent = "200 176";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiWindowProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiTextCtrl() {
+         text = "Name:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 29";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "Nav";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 30";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshName";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextCtrl() {
+         text = "Position:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 51";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "0 0 0";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 52";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshPosition";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextCtrl() {
+         text = "Scale:";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "12 73";
+         extent = "39 21";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextRightProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         password = "0";
+         passwordMask = "*";
+         text = "50 50 20";
+         maxLength = "1024";
+         margin = "0 0 0 0";
+         padding = "0 0 0 0";
+         anchorTop = "1";
+         anchorBottom = "0";
+         anchorLeft = "1";
+         anchorRight = "0";
+         position = "59 74";
+         extent = "129 18";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiTextEditProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         internalName = "MeshScale";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiCheckBoxCtrl(MeshMissionBounds) {
+         text = " Fit NavMesh to mission area";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "1";
+         position = "22 99";
+         extent = "159 15";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiCheckBoxProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         tooltip = "Positions and scales the NavMesh so it includes all your mission objects.";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiCheckBoxCtrl(MeshTerrainBounds) {
+         text = " Include terrain";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         position = "22 121";
+         extent = "159 15";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiCheckBoxProfile";
+         visible = "1";
+         active = "0";
+         tooltipProfile = "GuiToolTipProfile";
+         tooltip = "Consider terrain when calculating NavMesh bounds.";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiButtonCtrl() {
+         text = "Create!";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         position = "12 146";
+         extent = "87 19";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiButtonProfile";
+         visible = "1";
+         active = "1";
+         command = "CreateNewNavMeshDlg.create();";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+      new GuiButtonCtrl() {
+         text = "Cancel";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         position = "104 146";
+         extent = "84 19";
+         minExtent = "8 2";
+         horizSizing = "right";
+         vertSizing = "bottom";
+         profile = "GuiButtonProfile";
+         visible = "1";
+         active = "1";
+         command = "Canvas.popDialog(CreateNewNavMeshDlg);";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---
+
+function CreateNewNavMeshDlg::onWake(%this)
+{
+   %this-->MeshName.setText("Nav");
+   %this-->MeshPosition.setText("0 0 0");
+   %this-->MeshScale.setText("50 50 20");
+   MeshMissionBounds.setStateOn(false);
+   MeshTerrainBounds.setStateOn(true);
+}
+
+function MissionBoundsExtents(%group)
+{
+   %box = "0 0 0 0 0 0";
+   foreach(%obj in %group)
+   {
+      %cls = %obj.getClassName();
+      if(%cls $= "SimGroup" || %cls $= "SimSet" || %cls $= "Path")
+      {
+         // Need to recursively check grouped objects.
+         %wbox = MissionBoundsExtents(%obj);
+      }
+      else
+      {
+         // Skip objects that are too big and shouldn't really be considered
+         // part of the scene, or are global bounds and we therefore can't get
+         // any sensible information out of them.
+         if(%cls $= "LevelInfo")
+            continue;
+         if(!MeshTerrainBounds.isStateOn() && %cls $= "TerrainBlock")
+            continue;
+
+         if(!(%obj.getType() & $TypeMasks::StaticObjectType) ||
+            %obj.getType() & $TypeMasks::EnvironmentObjectType)
+            continue;
+
+         if(%obj.isGlobalBounds())
+            continue;
+
+         %wbox = %obj.getWorldBox();
+      }
+
+      // Update min point.
+      for(%j = 0; %j < 3; %j++)
+      {
+         if(GetWord(%box, %j) > GetWord(%wbox, %j))
+            %box = SetWord(%box, %j, GetWord(%wbox, %j));
+      }
+      // Update max point.
+      for(%j = 3; %j < 6; %j++)
+      {
+         if(GetWord(%box, %j) < GetWord(%wbox, %j))
+            %box = SetWord(%box, %j, GetWord(%wbox, %j));
+      }
+   }
+   return %box;
+}
+
+function CreateNewNavMeshDlg::create(%this)
+{
+   %name = %this-->MeshName.getText();
+   if(%name $= "" || nameToID(%name) != -1)
+   {
+      MessageBoxOk("Error", "A NavMesh must have a unique name!");
+      return;
+   }
+
+   %mesh = 0;
+
+   if(MeshMissionBounds.isStateOn())
+   {
+      if(!isObject(MissionGroup))
+      {
+         MessageBoxOk("Error", "You must have a MissionGroup to use the mission bounds function.");
+         return;
+      }
+      // Get maximum extents of all objects.
+      %box = MissionBoundsExtents(MissionGroup);
+      %pos = GetBoxCenter(%box);
+      %scale = (GetWord(%box, 3) - GetWord(%box, 0)) / 2 + 5
+         SPC (GetWord(%box, 4) - GetWord(%box, 1)) / 2 + 5
+         SPC (GetWord(%box, 5) - GetWord(%box, 2)) / 2 + 5;
+
+      %mesh = new NavMesh(%name)
+      {
+         position = %pos;
+         scale = %scale;
+      };
+   }
+   else
+   {
+      %mesh = new NavMesh(%name)
+      {
+         position = %this-->MeshPosition.getText();
+         scale = %this-->MeshScale.getText();
+      };
+   }
+   MissionGroup.add(%mesh);
+   NavEditorGui.selectObject(%mesh);
+
+   Canvas.popDialog(CreateNewNavMeshDlg);
+}
+
+function MeshMissionBounds::onClick(%this)
+{
+   MeshTerrainBounds.setActive(%this.isStateOn());
+}

+ 169 - 0
Templates/Full/game/tools/navEditor/NavEditorConsoleDlg.gui

@@ -0,0 +1,169 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiWindowCtrl(NavEditorConsoleDlg) {
+   text = "Nav Console";
+   resizeWidth = "1";
+   resizeHeight = "1";
+   canMove = "1";
+   canClose = "1";
+   canMinimize = "1";
+   canMaximize = "1";
+   canCollapse = "0";
+   closeCommand = "NavEditorConsoleDlg.setVisible(false);";
+   edgeSnap = "1";
+   margin = "0 0 0 0";
+   padding = "0 0 0 0";
+   anchorTop = "1";
+   anchorBottom = "0";
+   anchorLeft = "1";
+   anchorRight = "0";
+   position = "238 170";
+   extent = "320 240";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiWindowProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiTextCtrl() {
+      maxLength = "1024";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "3 222";
+      extent = "149 13";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "top";
+      profile = "GuiTextProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      internalName = "StatusLeft";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiScrollCtrl() {
+      willFirstRespond = "1";
+      hScrollBar = "dynamic";
+      vScrollBar = "dynamic";
+      lockHorizScroll = "1";
+      lockVertScroll = "0";
+      constantThumbHeight = "0";
+      childMargin = "0 0";
+      mouseWheelScrollSpeed = "-1";
+      margin = "-14 41 3 3";
+      padding = "0 0 0 0";
+      anchorTop = "0";
+      anchorBottom = "0";
+      anchorLeft = "0";
+      anchorRight = "0";
+      position = "3 23";
+      extent = "314 194";
+      minExtent = "8 2";
+      horizSizing = "relative";
+      vertSizing = "relative";
+      profile = "GuiEditorScrollProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      internalName = "OutputScroll";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiListBoxCtrl() {
+         allowMultipleSelections = "0";
+         fitParentWidth = "1";
+         colorBullet = "1";
+         position = "1 1";
+         extent = "312 16";
+         minExtent = "8 2";
+         horizSizing = "relative";
+         vertSizing = "relative";
+         profile = "GuiListBoxProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "0";
+         internalName = "Output";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---
+
+new ScriptMsgListener(NavEditorConsoleListener);
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshCreated");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshRemoved");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshStartUpdate");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshUpdate");
+getNavMeshEventManager().subscribe(NavEditorConsoleListener, "NavMeshTileUpdate");
+
+function NavEditorConsoleListener::onNavMeshCreated(%this, %data)
+{
+}
+
+function NavEditorConsoleListener::onNavMeshRemoved(%this, %data)
+{
+}
+
+function NavEditorConsoleListener::onNavMeshStartUpdate(%this, %data)
+{
+   NavEditorConsoleDlg-->Output.clearItems();
+   NavEditorConsoleDlg-->Output.addItem("Build starting for NavMesh" SPC %data, "0 0.6 0");
+   NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+}
+
+function NavEditorConsoleListener::onNavMeshUpdate(%this, %data)
+{
+   %message = "";
+   if(getWordCount(%data) == 2)
+   {
+      %seconds = getWord(%data, 1);
+      %minutes = mFloor(%seconds / 60);
+      %seconds -= %minutes * 60;
+      %message = "Built NavMesh" SPC getWord(%data, 0) SPC "in" SPC %minutes @ "m" SPC mRound(%seconds) @ "s";
+      if(NavEditorGui.playSoundWhenDone)
+      {
+         sfxPlayOnce(Audio2D, "tools/navEditor/done.wav");
+      }
+   }
+   else
+   {
+      %message = "Loaded NavMesh" SPC %data;
+   }
+   NavEditorConsoleDlg-->Output.addItem(%message, "0 0.6 0");
+   NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+   NavEditorConsoleDlg->StatusLeft.setText("");
+}
+
+function NavEditorConsoleListener::onNavMeshTileUpdate(%this, %data)
+{
+   %mesh = getWord(%data, 0);
+   %index = getWord(%data, 1);
+   %total = getWord(%data, 2);
+   %tile = getWords(%data, 3, 4);
+   %success = getWord(%data, 5) == "1";
+   if(!%success)
+   {
+      %message = "NavMesh" SPC %mesh SPC "tile" SPC %tile SPC "build failed!";
+      NavEditorConsoleDlg-->Output.addItem(%message, "1 0 0");
+      NavEditorConsoleDlg-->OutputScroll.scrollToBottom();
+   }
+   %percent = %index / %total * 100;
+   NavEditorConsoleDlg->StatusLeft.setText("Build progress:" SPC mRound(%percent) @ "%");
+}

+ 854 - 0
Templates/Full/game/tools/navEditor/NavEditorGui.gui

@@ -0,0 +1,854 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
+   canSaveDynamicFields = "0";
+   Enabled = "1";
+   isContainer = "1";
+   Profile = "NavEditorProfile";
+   HorizSizing = "width";
+   VertSizing = "height";
+   Position = "0 0";
+   Extent = "800 600";
+   MinExtent = "8 8";
+   canSave = "1";
+   Visible = "1";
+   tooltipprofile = "GuiToolTipProfile";
+   hovertime = "1000";
+   Docking = "None";
+   Margin = "0 0 0 0";
+   Padding = "0 0 0 0";
+   AnchorTop = "0";
+   AnchorBottom = "0";
+   AnchorLeft = "0";
+   AnchorRight = "0";
+   cameraZRot = "0";
+   forceFOV = "0";
+   renderMissionArea = "0";
+   missionAreaFillColor = "255 0 0 20";
+   missionAreaFrameColor = "255 0 0 128";
+   allowBorderMove = "0";
+   borderMovePixelSize = "20";
+   borderMoveSpeed = "0.1";
+   consoleFrameColor = "255 0 0 255";
+   consoleFillColor = "0 0 0 0";
+   consoleSphereLevel = "1";
+   consoleCircleSegments = "32";
+   consoleLineWidth = "1";
+   GizmoProfile = "GlobalGizmoProfile";
+
+   new GuiWindowCollapseCtrl(NavEditorTreeWindow) {
+      internalName = "";
+      canSaveDynamicFields = "0";
+      Enabled = "1";
+      isContainer = "1";
+      Profile = "GuiWindowProfile";
+      HorizSizing = "windowRelative";
+      VertSizing = "windowRelative";
+      Position = getWord($pref::Video::mode, 0) - 209
+         SPC getWord(EditorGuiToolbar.extent, 1) - 1;
+      Extent = "210 167";
+      MinExtent = "210 100";
+      canSave = "1";
+      isDecoy = "0";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      Margin = "0 0 0 0";
+      Padding = "0 0 0 0";
+      AnchorTop = "1";
+      AnchorBottom = "0";
+      AnchorLeft = "1";
+      AnchorRight = "0";
+      resizeWidth = "1";
+      resizeHeight = "1";
+      canMove = "1";
+      canClose = "0";
+      canMinimize = "0";
+      canMaximize = "0";
+      minSize = "50 50";
+      EdgeSnap = "1";
+      text = "Navigation";
+
+      new GuiButtonCtrl() {
+         Profile = "GuiButtonProfile";
+         buttonType = "PushButton";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "115 2";
+         extent = "90 18";
+         text = "New NavMesh";
+         command = "Canvas.pushDialog(CreateNewNavMeshDlg);";
+      };
+
+      new GuiContainer(){
+         profile = GuiDefaultProfile;
+         Position = "5 25";
+         Extent = "200 120";
+         Docking = "Client";
+         Margin = "3 1 3 3 ";
+         HorizSizing = "width";
+         VertSizing = "height";
+         isContainer = "1";
+         
+         new GuiScrollCtrl() {
+            canSaveDynamicFields = "0";
+            Enabled = "1";
+            isContainer = "1";
+            Profile = "GuiEditorScrollProfile";
+            HorizSizing = "width";
+            VertSizing = "height";
+            Position = "0 0";
+            Extent = "200 118";
+            MinExtent = "8 8";
+            canSave = "1";
+            isDecoy = "0";
+            Visible = "1";
+            tooltipprofile = "GuiToolTipProfile";
+            hovertime = "1000";
+            Docking = "Client";
+            Margin = "0 0 0 0";
+            Padding = "0 0 0 0";
+            AnchorTop = "1";
+            AnchorBottom = "0";
+            AnchorLeft = "1";
+            AnchorRight = "0";
+            willFirstRespond = "1";
+            hScrollBar = "alwaysOff";
+            vScrollBar = "dynamic";
+            lockHorizScroll = "true";
+            lockVertScroll = "false";
+            constantThumbHeight = "0";
+            childMargin = "0 0";
+            mouseWheelScrollSpeed = "-1";
+
+            new GuiTreeViewCtrl(NavTreeView) {
+               canSaveDynamicFields = "0";
+               Enabled = "1";
+               isContainer = "1";
+               Profile = "ToolsGuiTreeViewProfile";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Position = "1 1";
+               Extent = "193 21";
+               MinExtent = "8 8";
+               canSave = "1";
+               Visible = "1";
+               hovertime = "1000";
+               tabSize = "16";
+               textOffset = "2";
+               fullRowSelect = "0";
+               itemHeight = "21";
+               destroyTreeOnSleep = "1";
+               MouseDragging = "0";
+               MultipleSelections = "0";
+               DeleteObjectAllowed = "1";
+               DragToItemAllowed = "0";
+               showRoot = "0";
+               internalNamesOnly = "0";
+            };
+         };
+      };
+   };
+   new GuiWindowCollapseCtrl(NavEditorOptionsWindow) {
+      internalName = "Window";
+      canSaveDynamicFields = "0";
+      Enabled = "1";
+      isContainer = "1";
+      Profile = "GuiWindowProfile";
+      HorizSizing = "windowRelative";
+      VertSizing = "windowRelative";
+      Position = getWord($pref::Video::mode, 0) - 209 
+         SPC getWord(EditorGuiToolbar.extent, 1) + getWord(NavEditorTreeWindow.extent, 1) - 2;
+      Extent = "210 530";
+      MinExtent = "210 300";
+      canSave = "1";
+      Visible = "1";
+      tooltipprofile = "GuiToolTipProfile";
+      hovertime = "1000";
+      Margin = "0 0 0 0";
+      Padding = "0 0 0 0";
+      AnchorTop = "0";
+      AnchorBottom = "0";
+      AnchorLeft = "0";
+      AnchorRight = "0";
+      resizeWidth = "1";
+      resizeHeight = "1";
+      canMove = "1";
+      canClose = "0";
+      canMinimize = "0";
+      canMaximize = "0";
+      minSize = "50 50";
+      EdgeSnap = "1";
+      text = "Properties";
+      
+      new GuiContainer(){ //Actions
+         isContainer = "1";
+         Profile = "inspectorStyleRolloutDarkProfile";
+         HorizSizing = "width";
+         VertSizing = "bottom";
+         Position = "4 24";
+         Extent = "202 85";
+         Docking = "Top";
+         Margin = "3 3 3 3";
+         internalName = "ActionsBox";
+         
+         new GuiTextCtrl(){
+            Profile = "GuiDefaultProfile";
+            HorizSizing = "right";
+            VertSizing = "bottom";
+            Position = "5 0";
+            Extent = "86 18";
+            text = "Actions";
+         };
+         new GuiStackControl()
+         {
+            internalName = "SelectActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Build NavMesh";
+               command = "NavEditorGui.buildSelectedMeshes();";
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "182 20";
+               position = "0 20";
+
+               new GuiCheckboxCtrl() {
+                  internalName = "BackgroundBuildButton";
+                  text = "Background";
+                  groupNum = "-1";
+                  buttonType = "ToggleButton";
+                  useMouseEvents = "0";
+                  extent = "75 20";
+                  minExtent = "8 2";
+                  profile = "GuiCheckBoxProfile";
+                  visible = "1";
+                  active = "1";
+                  variable = "NavEditorGui.backgroundBuild";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "0";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+               };
+               new GuiCheckboxCtrl() {
+                  position = "75 0";
+                  internalName = "SaveIntermediatesButton";
+                  text = "Keep intermediates";
+                  groupNum = "-1";
+                  buttonType = "ToggleButton";
+                  useMouseEvents = "0";
+                  extent = "105 20";
+                  profile = "GuiCheckBoxProfile";
+                  visible = "1";
+                  active = "1";
+                  variable = "NavEditorGui.saveIntermediates";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "0";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+               };
+            };
+            new GuiCheckboxCtrl() {
+               internalName = "BuildSoundButton";
+               text = "Play sound when done";
+               groupNum = "-1";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "150 20";
+               minExtent = "8 2";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               variable = "NavEditorGui.playSoundWhenDone";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "LinkActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Rebuild links";
+               command = "NavEditorGui.buildLinks();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "CoverActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Create Cover";
+               command = "NavEditorGui.createCoverPoints();";
+            };
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Delete Cover";
+               command = "NavEditorGui.deleteCoverPoints();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "TileActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "182 18";
+               text = "Rebuild tile";
+               command = "NavEditorGui.buildTile();";
+            };
+         };
+         new GuiStackControl()
+         {
+            internalName = "TestActions";
+            position = "7 21";
+            extent = "190 64";
+            
+            new GuiButtonCtrl() {
+               Profile = "GuiButtonProfile";
+               buttonType = "PushButton";
+               HorizSizing = "right";
+               VertSizing = "bottom";
+               Extent = "180 18";
+               text = "Spawn";
+               command = "NavEditorGui.spawnPlayer();";
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "190 18";
+
+               new GuiButtonCtrl() {
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Delete";
+                  command = "NavEditorGui.getPlayer().delete();";
+               };
+               new GuiButtonCtrl() {
+                  position = "100 0";
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Find cover";
+                  command = "NavEditorGui.findCover();";
+               };
+            };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "190 18";
+
+               new GuiButtonCtrl() {
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Follow";
+                  command = "NavEditorGui.followObject();";
+               };
+               new GuiButtonCtrl() {
+                  position = "100 0";
+                  Profile = "GuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Stop";
+                  command = "NavEditorGui.getPlayer().stop();";
+               };
+            };
+         };
+      };
+      new GuiContainer(){
+         isContainer = "1";
+         Profile = "inspectorStyleRolloutDarkProfile";
+         HorizSizing = "width";
+         VertSizing = "bottom";
+         Position = "4 112";
+         Extent = "202 31";
+         Docking = "Top";
+         Margin = "0 0 3 3";
+         
+         new GuiTextCtrl(){
+            Profile = "GuiDefaultProfile";
+            HorizSizing = "right";
+            VertSizing = "bottom";
+            Position = "5 0";
+            Extent = "121 18";
+            text = "Properties";
+         };
+      };
+
+      new GuiScrollCtrl() {
+         canSaveDynamicFields = "0";
+         Enabled = "1";
+         isContainer = "1";
+         Profile = "GuiEditorScrollProfile";
+         HorizSizing = "width";
+         VertSizing = "height";
+         Position = "4 129";
+         Extent = "202 357";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         Docking = "Client";
+         Margin = "-14 41 3 3";
+         Padding = "0 0 0 0";
+         AnchorTop = "0";
+         AnchorBottom = "0";
+         AnchorLeft = "0";
+         AnchorRight = "0";
+         willFirstRespond = "1";
+         hScrollBar = "alwaysOff";
+         vScrollBar = "dynamic";
+         lockHorizScroll = "true";
+         lockVertScroll = "false";
+         constantThumbHeight = "0";
+         childMargin = "0 0";
+         internalName = "PropertiesBox";
+
+         new GuiInspector(NavInspector) {
+            StackingType = "Vertical";
+            HorizStacking = "Left to Right";
+            VertStacking = "Top to Bottom";
+            Padding = "1";
+            name = "NavInspector";
+            canSaveDynamicFields = "0";
+            Enabled = "1";
+            isContainer = "1";
+            Profile = "GuiTransparentProfile";
+            HorizSizing = "width";
+            VertSizing = "height";
+            Position = "1 1";
+            Extent = "178 16";
+            MinExtent = "16 16";
+            canSave = "1";
+            Visible = "1";
+            tooltipprofile = "GuiToolTipProfile";
+            hovertime = "1000";
+            dividerMargin = "5";
+         };
+         
+         new GuiStackControl() {
+            internalName = "LinkProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkWalkFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Walk";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "This link is just ordinary flat ground.";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkJumpFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Jump";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link require a jump?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkDropFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Drop";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link involve a significant drop?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkLedgeFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Ledge";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Should the character jump at the next ledge?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkClimbFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Climb";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Does this link involve climbing?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkTeleportFlag";
+               class = "NavMeshLinkFlagButton";
+               text = " Teleport";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Is this link a teleporter?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+         new GuiStackControl() {
+            internalName = "TileProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+            new GuiCheckBoxCtrl() {
+               text = " Display input geometry";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+               variable = "$Nav::Editor::renderInput";
+            };
+            new GuiCheckBoxCtrl() {
+               text = " Display voxels";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+               variable = "$Nav::Editor::renderVoxels";
+            };
+         };
+         new GuiStackControl() {
+            internalName = "TestProperties";
+            position = "7 21";
+            extent = "186 64";
+            padding = "2 2 2 2";
+            
+			new GuiTextCtrl() {
+               text = "Cover";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "CoverRadius";
+               text = "10";
+               profile = "GuiTextEditProfile";
+               extent = "40 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Radius for cover-finding.";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "CoverPosition";
+               text = "LocalClientConnection.getControlObject().getPosition();";
+               profile = "GuiTextEditProfile";
+               extent = "140 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Position to find cover from.";
+            };
+            new GuiTextCtrl() {
+               text = "Follow";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "FollowRadius";
+               text = "1";
+               profile = "GuiTextEditProfile";
+               extent = "40 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Radius for following.";
+            };
+            new GuiTextEditCtrl() {
+               internalName = "FollowObject";
+               text = "LocalClientConnection.player";
+               profile = "GuiTextEditProfile";
+               extent = "140 20";
+               minExtent = "8 2";
+               visible = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               toolTip = "Object to follow.";
+            };
+            new GuiTextCtrl() {
+               text = "Movement";
+               profile = "GuiTextProfile";
+               extent = "180 20";
+               minExtent = "8 2";
+               visible = "1";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkWalkFlag";
+               class = "NavMeshTestFlagButton";
+               text = " Walk";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character walk on ground?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkJumpFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Jump";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character jump?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkDropFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Drop";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character drop over edges?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkLedgeFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Ledge";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character jump from ledges?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkClimbFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Climb";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character climb?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+			   internalName = "LinkTeleportFlag";
+			   class = "NavMeshTestFlagButton";
+               text = " Teleport";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "GuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			   toolTip = "Can this character teleport?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+         };
+      };
+      new GuiMLTextCtrl(NavFieldInfoControl) {
+         canSaveDynamicFields = "0";
+         Enabled = "1";
+         isContainer = "0";
+         Profile = "GuiInspectorFieldInfoMLTextProfile";
+         HorizSizing = "width";
+         VertSizing = "top";
+         Position = "1 485";
+         Extent = "202 42";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "GuiToolTipProfile";
+         hovertime = "1000";
+         lineSpacing = "2";
+         allowColorChars = "0";
+         maxChars = "-1";
+         useURLMouseCursor = "0";
+      };
+   };
+   
+};
+
+//--- OBJECT WRITE END ---

+ 506 - 0
Templates/Full/game/tools/navEditor/NavEditorSettingsTab.gui

@@ -0,0 +1,506 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiTabPageCtrl(ENavEditorSettingsPage) {
+   fitBook = "1";
+   text = "Navigation Editor";
+   maxLength = "1024";
+   margin = "0 0 0 0";
+   padding = "0 0 0 0";
+   anchorTop = "1";
+   anchorBottom = "0";
+   anchorLeft = "1";
+   anchorRight = "0";
+   position = "0 0";
+   extent = "208 292";
+   minExtent = "8 2";
+   horizSizing = "width";
+   vertSizing = "height";
+   profile = "GuiSolidDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+
+   new GuiScrollCtrl() {
+      willFirstRespond = "1";
+      hScrollBar = "alwaysOff";
+      vScrollBar = "dynamic";
+      lockHorizScroll = "1";
+      lockVertScroll = "0";
+      constantThumbHeight = "0";
+      childMargin = "0 0";
+      mouseWheelScrollSpeed = "-1";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "0 0";
+      extent = "208 292";
+      minExtent = "8 2";
+      horizSizing = "width";
+      vertSizing = "height";
+      profile = "GuiScrollProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+
+      new GuiStackControl() {
+         stackingType = "Vertical";
+         horizStacking = "Left to Right";
+         vertStacking = "Top to Bottom";
+         padding = "0";
+         dynamicSize = "1";
+         dynamicNonStackExtent = "0";
+         dynamicPos = "0";
+         changeChildSizeToFit = "1";
+         changeChildPosition = "1";
+         position = "1 1";
+         extent = "206 124";
+         minExtent = "8 2";
+         horizSizing = "width";
+         vertSizing = "bottom";
+         profile = "GuiDefaultProfile";
+         visible = "1";
+         active = "1";
+         tooltipProfile = "GuiToolTipProfile";
+         hovertime = "1000";
+         isContainer = "1";
+         canSave = "1";
+         canSaveDynamicFields = "0";
+
+         new GuiRolloutCtrl() {
+            caption = "Test spawn";
+            margin = "0 3 0 0";
+            defaultHeight = "40";
+            expanded = "1";
+            clickCollapse = "1";
+            hideHeader = "0";
+            autoCollapseSiblings = "0";
+            position = "0 0";
+            extent = "206 62";
+            minExtent = "8 2";
+            horizSizing = "right";
+            vertSizing = "bottom";
+            profile = "GuiRolloutProfile";
+            visible = "1";
+            active = "1";
+            tooltipProfile = "GuiToolTipProfile";
+            hovertime = "1000";
+            isContainer = "1";
+            canSave = "1";
+            canSaveDynamicFields = "0";
+
+            new GuiStackControl() {
+               stackingType = "Vertical";
+               horizStacking = "Left to Right";
+               vertStacking = "Top to Bottom";
+               padding = "3";
+               dynamicSize = "1";
+               dynamicNonStackExtent = "0";
+               dynamicPos = "0";
+               changeChildSizeToFit = "1";
+               changeChildPosition = "1";
+               position = "0 20";
+               extent = "206 39";
+               minExtent = "8 2";
+               horizSizing = "width";
+               vertSizing = "bottom";
+               profile = "GuiDefaultProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "1";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+
+               new GuiControl() {
+                  position = "0 0";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+
+                  new GuiTextCtrl() {
+                     text = "Spawn class:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "5 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "0";
+                  };
+                  new GuiPopUpMenuCtrlEx() {
+                     maxPopupHeight = "200";
+                     sbUsesNAColor = "0";
+                     reverseTextList = "0";
+                     bitmapBounds = "16 16";
+                     hotTrackCallback = "0";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "81 0";
+                     extent = "121 18";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiPopUpMenuProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "1";
+                     internalName = "SpawnClassOptions";
+                     class = "ESettingsWindowPopup";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                        editorSettingsRead = "NavEditorPlugin.readSettings();";
+                        editorSettingsValue = "NavEditor/SpawnClass";
+                        editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+                  };
+               };
+               new GuiControl() {
+                  position = "0 21";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  canSave = "1";
+                  canSaveDynamicFields = "0";
+
+                  new GuiTextCtrl() {
+                     text = "Datablock:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "5 1";
+                     extent = "70 18";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "0";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     text = "DefaultPlayerData";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "81 0";
+                     extent = "121 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     class = "ESettingsWindowTextEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                        editorSettingsRead = "NavEditorPlugin.readSettings();";
+                        editorSettingsValue = "NavEditor/SpawnDatablock";
+                        editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+                  };
+               };
+            };
+         };
+         new GuiRolloutCtrl() {
+            caption = "Colors";
+            margin = "0 3 0 0";
+            defaultHeight = "40";
+            expanded = "1";
+            clickCollapse = "1";
+            hideHeader = "0";
+            autoCollapseSiblings = "0";
+            position = "0 62";
+            extent = "206 62";
+            minExtent = "8 2";
+            horizSizing = "right";
+            vertSizing = "bottom";
+            profile = "GuiRolloutProfile";
+            visible = "1";
+            active = "1";
+            tooltipProfile = "GuiToolTipProfile";
+            hovertime = "1000";
+            isContainer = "1";
+            canSave = "1";
+            canSaveDynamicFields = "0";
+
+            new GuiStackControl() {
+               stackingType = "Vertical";
+               horizStacking = "Left to Right";
+               vertStacking = "Top to Bottom";
+               padding = "3";
+               dynamicSize = "1";
+               dynamicNonStackExtent = "0";
+               dynamicPos = "0";
+               changeChildSizeToFit = "1";
+               changeChildPosition = "1";
+               position = "0 20";
+               extent = "206 39";
+               minExtent = "8 2";
+               horizSizing = "width";
+               vertSizing = "bottom";
+               profile = "GuiDefaultProfile";
+               visible = "1";
+               active = "1";
+               tooltipProfile = "GuiToolTipProfile";
+               hovertime = "1000";
+               isContainer = "1";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+
+               new GuiControl() {
+                  position = "0 0";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  class = "ESettingsWindowColor";
+                  canSave = "1";
+                  canSaveDynamicFields = "1";
+                     editorSettingsRead = "NavEditorPlugin.readSettings();";
+                     editorSettingsValue = "NavEditor/HoverSplineColor";
+                     editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+
+                  new GuiTextCtrl() {
+                     text = "Hover Spline:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "0 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "80 0";
+                     extent = "104 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorEdit";
+                     class = "ESettingsWindowColorEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiSwatchButtonCtrl() {
+                     color = "0 0 0 0";
+                     groupNum = "-1";
+                     buttonType = "PushButton";
+                     useMouseEvents = "0";
+                     position = "188 2";
+                     extent = "14 14";
+                     minExtent = "8 2";
+                     horizSizing = "left";
+                     vertSizing = "bottom";
+                     profile = "GuiDefaultProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorButton";
+                     class = "ESettingsWindowColorButton";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+               };
+               new GuiControl() {
+                  position = "0 21";
+                  extent = "206 18";
+                  minExtent = "8 2";
+                  horizSizing = "right";
+                  vertSizing = "bottom";
+                  profile = "GuiDefaultProfile";
+                  visible = "1";
+                  active = "1";
+                  tooltipProfile = "GuiToolTipProfile";
+                  hovertime = "1000";
+                  isContainer = "1";
+                  class = "ESettingsWindowColor";
+                  canSave = "1";
+                  canSaveDynamicFields = "1";
+                     editorSettingsRead = "NavEditorPlugin.readSettings();";
+                     editorSettingsValue = "NavEditor/SelectedSplineColor";
+                     editorSettingsWrite = "NavEditorPlugin.writeSettings();";
+
+                  new GuiTextCtrl() {
+                     text = "Sel. Spline:";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "0 1";
+                     extent = "70 16";
+                     minExtent = "8 2";
+                     horizSizing = "right";
+                     vertSizing = "bottom";
+                     profile = "GuiTextRightProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiTextEditCtrl() {
+                     historySize = "0";
+                     tabComplete = "0";
+                     sinkAllKeyEvents = "0";
+                     password = "0";
+                     passwordMask = "*";
+                     maxLength = "1024";
+                     margin = "0 0 0 0";
+                     padding = "0 0 0 0";
+                     anchorTop = "1";
+                     anchorBottom = "0";
+                     anchorLeft = "1";
+                     anchorRight = "0";
+                     position = "80 0";
+                     extent = "104 18";
+                     minExtent = "8 2";
+                     horizSizing = "width";
+                     vertSizing = "bottom";
+                     profile = "GuiTextEditProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorEdit";
+                     class = "ESettingsWindowColorEdit";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+                  new GuiSwatchButtonCtrl() {
+                     color = "0 0 0 0";
+                     groupNum = "-1";
+                     buttonType = "PushButton";
+                     useMouseEvents = "0";
+                     position = "188 2";
+                     extent = "14 14";
+                     minExtent = "8 2";
+                     horizSizing = "left";
+                     vertSizing = "bottom";
+                     profile = "GuiDefaultProfile";
+                     visible = "1";
+                     active = "1";
+                     tooltipProfile = "GuiToolTipProfile";
+                     hovertime = "1000";
+                     isContainer = "0";
+                     internalName = "ColorButton";
+                     class = "ESettingsWindowColorButton";
+                     canSave = "1";
+                     canSaveDynamicFields = "1";
+                  };
+               };
+            };
+         };
+      };
+   };
+};
+//--- OBJECT WRITE END ---

+ 144 - 0
Templates/Full/game/tools/navEditor/NavEditorToolbar.gui

@@ -0,0 +1,144 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiControl(NavEditorToolbar,EditorGuiGroup) {
+   position = "306 0";
+   extent = "800 32";
+   minExtent = "8 2";
+   horizSizing = "right";
+   vertSizing = "bottom";
+   profile = "GuiDefaultProfile";
+   visible = "1";
+   active = "1";
+   tooltipProfile = "GuiToolTipProfile";
+   hovertime = "1000";
+   isContainer = "1";
+   internalName = "NavEditorToolbar";
+   canSave = "1";
+   canSaveDynamicFields = "1";
+      enabled = "1";
+
+   new GuiTextCtrl() {
+      text = "Navigation Editor";
+      maxLength = "255";
+      margin = "0 0 0 0";
+      padding = "0 0 0 0";
+      anchorTop = "1";
+      anchorBottom = "0";
+      anchorLeft = "1";
+      anchorRight = "0";
+      position = "6 6";
+      extent = "150 20";
+      minExtent = "8 8";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiTextProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "1";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiBitmapCtrl() {
+      bitmap = "core/art/gui/images/separator-h.png";
+      wrap = "0";
+      position = "90 3";
+      extent = "2 26";
+      minExtent = "1 1";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiDefaultProfile";
+      visible = "1";
+      active = "1";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiButtonCtrl(NavEditorAboutBtn) {
+      text = "Console";
+      groupNum = "7";
+      buttonType = "PushButton";
+      useMouseEvents = "0";
+      position = "100 6";
+      extent = "54 20";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiButtonProfile";
+      visible = "1";
+      active = "1";
+      command = "NavEditorConsoleDlg.setVisible(!NavEditorConsoleDlg.isVisible());";
+      tooltipProfile = "GuiToolTipProfile";
+      tooltip = "Show Console";
+      hovertime = "1000";
+      isContainer = "0";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "Mesh";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "167 1";
+      extent = "50 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderMesh";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "MeshButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "Portals";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "224 1";
+      extent = "54 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderPortals";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "PortalButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+   new GuiCheckBoxCtrl() {
+      text = "BV tree";
+      groupNum = "-1";
+      buttonType = "ToggleButton";
+      useMouseEvents = "0";
+      position = "286 1";
+      extent = "140 30";
+      minExtent = "8 2";
+      horizSizing = "right";
+      vertSizing = "bottom";
+      profile = "GuiCheckBoxProfile";
+      visible = "1";
+      active = "1";
+      variable = "$Nav::Editor::renderBVTree";
+      tooltipProfile = "GuiToolTipProfile";
+      hovertime = "1000";
+      isContainer = "0";
+      internalName = "BVTreeButton";
+      canSave = "1";
+      canSaveDynamicFields = "0";
+   };
+};
+//--- OBJECT WRITE END ---

二进制
Templates/Full/game/tools/navEditor/done.wav


二进制
Templates/Full/game/tools/navEditor/images/nav-cover_d.png


二进制
Templates/Full/game/tools/navEditor/images/nav-cover_h.png


二进制
Templates/Full/game/tools/navEditor/images/nav-cover_n.png


二进制
Templates/Full/game/tools/navEditor/images/nav-editor_d.png


二进制
Templates/Full/game/tools/navEditor/images/nav-editor_h.png


二进制
Templates/Full/game/tools/navEditor/images/nav-editor_n.png


二进制
Templates/Full/game/tools/navEditor/images/nav-link_d.png


二进制
Templates/Full/game/tools/navEditor/images/nav-link_h.png


二进制
Templates/Full/game/tools/navEditor/images/nav-link_n.png


+ 285 - 0
Templates/Full/game/tools/navEditor/main.cs

@@ -0,0 +1,285 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+// These values should align with enum PolyFlags in walkabout/nav.h
+$Nav::WalkFlag = 1 << 0;
+$Nav::SwimFlag = 1 << 1;
+$Nav::JumpFlag = 1 << 2;
+$Nav::LedgeFlag = 1 << 3;
+$Nav::DropFlag = 1 << 4;
+$Nav::ClimbFlag = 1 << 5;
+$Nav::TeleportFlag = 1 << 6;
+
+function initializeNavEditor()
+{
+   echo(" % - Initializing Navigation Editor");
+
+   // Execute all relevant scripts and GUIs.
+   exec("./NavEditor.cs");
+   exec("./NavEditorGui.gui");
+   exec("./NavEditorToolbar.gui");
+   exec("./NavEditorConsoleDlg.gui");
+   exec("./CreateNewNavMeshDlg.gui");
+
+   // Add ourselves to EditorGui, where all the other tools reside
+   NavEditorGui.setVisible(false);  
+   NavEditorToolbar.setVisible(false); 
+   NavEditorOptionsWindow.setVisible(false);
+   NavEditorTreeWindow.setVisible(false);
+   NavEditorConsoleDlg.setVisible(false);
+
+   EditorGui.add(NavEditorGui);
+   EditorGui.add(NavEditorToolbar);
+   EditorGui.add(NavEditorOptionsWindow);
+   EditorGui.add(NavEditorTreeWindow);
+   EditorGui.add(NavEditorConsoleDlg);
+
+   new ScriptObject(NavEditorPlugin)
+   {
+      superClass = "EditorPlugin";
+      editorGui = NavEditorGui;
+   };
+
+   // Bind shortcuts for the nav editor.
+   %map = new ActionMap();
+   %map.bindCmd(keyboard, "1", "ENavEditorSelectModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "2", "ENavEditorLinkModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "3", "ENavEditorCoverModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "4", "ENavEditorTileModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "5", "ENavEditorTestModeBtn.performClick();", "");
+   %map.bindCmd(keyboard, "c", "NavEditorConsoleBtn.performClick();", "");
+   NavEditorPlugin.map = %map;
+
+   NavEditorPlugin.initSettings();
+}
+
+function destroyNavEditor()
+{
+}
+
+function NavEditorPlugin::onWorldEditorStartup(%this)
+{    
+    // Add ourselves to the window menu.
+   %accel = EditorGui.addToEditorsMenu("Navigation Editor", "", NavEditorPlugin);   
+
+   // Add ourselves to the ToolsToolbar.
+   %tooltip = "Navigation Editor (" @ %accel @ ")";   
+   EditorGui.addToToolsToolbar("NavEditorPlugin", "NavEditorPalette", expandFilename("tools/navEditor/images/nav-editor"), %tooltip);
+
+   GuiWindowCtrl::attach(NavEditorOptionsWindow, NavEditorTreeWindow);
+
+   // Add ourselves to the Editor Settings window.
+   exec("./NavEditorSettingsTab.gui");
+   ESettingsWindow.addTabPage(ENavEditorSettingsPage);
+   ENavEditorSettingsPage.init();
+
+   // Add items to World Editor Creator
+   EWCreatorWindow.beginGroup("Navigation");
+
+      EWCreatorWindow.registerMissionObject("CoverPoint", "Cover point");
+
+   EWCreatorWindow.endGroup();
+}
+
+function ENavEditorSettingsPage::init(%this)
+{
+   // Initialises the settings controls in the settings dialog box.
+   %this-->SpawnClassOptions.clear();
+   %this-->SpawnClassOptions.add("AIPlayer");
+   %this-->SpawnClassOptions.setFirstSelected();
+}
+
+function NavEditorPlugin::onActivated(%this)
+{
+   %this.readSettings();
+
+   // Set a global variable so everyone knows we're editing!
+   $Nav::EditorOpen = true;
+
+   // Start off in Select mode.
+   ToolsPaletteArray->NavEditorSelectMode.performClick();
+   EditorGui.bringToFront(NavEditorGui);
+
+   NavEditorGui.setVisible(true);
+   NavEditorGui.makeFirstResponder(true);
+   NavEditorToolbar.setVisible(true);
+
+   NavEditorOptionsWindow.setVisible(true);
+   NavEditorTreeWindow.setVisible(true);
+
+   // Inspect the ServerNavMeshSet, which contains all the NavMesh objects
+   // in the mission.
+   if(!isObject(ServerNavMeshSet))
+      new SimSet(ServerNavMeshSet);
+   if(ServerNavMeshSet.getCount() == 0)
+	  MessageBoxYesNo("No NavMesh", "There is no NavMesh in this level. Would you like to create one?" SPC
+	                                "If not, please use the Nav Editor to create a new NavMesh.",
+	                                "Canvas.pushDialog(CreateNewNavMeshDlg);");
+   NavTreeView.open(ServerNavMeshSet, true);
+
+   // Push our keybindings to the top. (See initializeNavEditor for where this
+   // map was created.)
+   %this.map.push();
+
+   // Store this on a dynamic field
+   // in order to restore whatever setting
+   // the user had before.
+   %this.prevGizmoAlignment = GlobalGizmoProfile.alignment;
+
+   // Always use Object alignment.
+   GlobalGizmoProfile.alignment = "Object";
+
+   // Set the status until some other editing mode adds useful information.
+   EditorGuiStatusBar.setInfo("Navigation editor.");
+   EditorGuiStatusBar.setSelection("");
+
+   // Allow the Gui to setup.
+   NavEditorGui.onEditorActivated(); 
+
+   Parent::onActivated(%this);
+}
+
+function NavEditorPlugin::onDeactivated(%this)
+{
+   %this.writeSettings();
+
+   $Nav::EditorOpen = false;   
+
+   NavEditorGui.setVisible(false);
+   NavEditorToolbar.setVisible(false);
+   NavEditorOptionsWindow.setVisible(false);
+   NavEditorTreeWindow.setVisible(false);
+   %this.map.pop();
+
+   // Restore the previous Gizmo alignment settings.
+   GlobalGizmoProfile.alignment = %this.prevGizmoAlignment;  
+
+   // Allow the Gui to cleanup.
+   NavEditorGui.onEditorDeactivated(); 
+
+   Parent::onDeactivated(%this);
+}
+
+function NavEditorPlugin::onEditMenuSelect(%this, %editMenu)
+{
+   %hasSelection = false;
+}
+
+function NavEditorPlugin::handleDelete(%this)
+{
+   // Event happens when the user hits 'delete'.
+   NavEditorGui.deleteSelected();
+}
+
+function NavEditorPlugin::handleEscape(%this)
+{
+   return NavEditorGui.onEscapePressed();  
+}
+
+function NavEditorPlugin::isDirty(%this)
+{
+   return NavEditorGui.isDirty;
+}
+
+function NavEditorPlugin::onSaveMission(%this, %missionFile)
+{
+   if(NavEditorGui.isDirty)
+   {
+      MissionGroup.save(%missionFile);
+      NavEditorGui.isDirty = false;
+   }
+}
+
+//-----------------------------------------------------------------------------
+// Settings
+//-----------------------------------------------------------------------------
+
+function NavEditorPlugin::initSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   EditorSettings.setDefaultValue("SpawnClass",     "AIPlayer");
+   EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData");
+
+   EditorSettings.endGroup();
+}
+
+function NavEditorPlugin::readSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   // Currently these are globals because of the way they are accessed in navMesh.cpp.
+   $Nav::Editor::renderMesh       = EditorSettings.value("RenderMesh");
+   $Nav::Editor::renderPortals    = EditorSettings.value("RenderPortals");
+   $Nav::Editor::renderBVTree     = EditorSettings.value("RenderBVTree");
+   NavEditorGui.spawnClass        = EditorSettings.value("SpawnClass");
+   NavEditorGui.spawnDatablock    = EditorSettings.value("SpawnDatablock");
+   NavEditorGui.backgroundBuild   = EditorSettings.value("BackgroundBuild");
+   NavEditorGui.saveIntermediates = EditorSettings.value("SaveIntermediates");
+   NavEditorGui.playSoundWhenDone = EditorSettings.value("PlaySoundWhenDone");
+
+   EditorSettings.endGroup();  
+}
+
+function NavEditorPlugin::writeSettings(%this)
+{
+   EditorSettings.beginGroup("NavEditor", true);
+
+   EditorSettings.setValue("RenderMesh",        $Nav::Editor::renderMesh);
+   EditorSettings.setValue("RenderPortals",     $Nav::Editor::renderPortals);
+   EditorSettings.setValue("RenderBVTree",      $Nav::Editor::renderBVTree);
+   EditorSettings.setValue("SpawnClass",        NavEditorGui.spawnClass);
+   EditorSettings.setValue("SpawnDatablock",    NavEditorGui.spawnDatablock);
+   EditorSettings.setValue("BackgroundBuild",   NavEditorGui.backgroundBuild);
+   EditorSettings.setValue("SaveIntermediates", NavEditorGui.saveIntermediates);
+   EditorSettings.setValue("PlaySoundWhenDone", NavEditorGui.playSoundWhenDone);
+
+   EditorSettings.endGroup();
+}
+
+function ESettingsWindowPopup::onWake(%this)
+{
+   %this.setSelected(%this.findText(EditorSettings.value(%this.editorSettingsValue)));
+}
+
+function ESettingsWindowPopup::onSelect(%this)
+{
+   EditorSettings.setValue(%this.editorSettingsValue, %this.getText());
+   eval(%this.editorSettingsRead);
+}
+
+//-----------------------------------------------------------------------------
+// Demo
+//-----------------------------------------------------------------------------
+
+function OnWalkaboutDemoLimit()
+{
+   MessageBoxOK("Walkabout demo",
+      "This demo only allows two NavMeshes to be created. Sorry!");
+}
+
+function OnWalkaboutDemoSave()
+{
+   MessageBoxOK("Walkabout demo",
+      "This demo doesn't allow you to save NavMeshes. Sorry!" SPC
+      "The rest of your mission will still be saved.");
+}

+ 360 - 0
Templates/Full/game/tools/navEditor/navEditor.cs

@@ -0,0 +1,360 @@
+//-----------------------------------------------------------------------------
+// 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.
+//-----------------------------------------------------------------------------
+
+$Nav::EditorOpen = false;
+
+function NavEditorGui::onEditorActivated(%this)
+{
+   if(%this.selectedObject)
+      %this.selectObject(%this.selectedObject);
+   %this.prepSelectionMode();
+}
+
+function NavEditorGui::onEditorDeactivated(%this)
+{
+   if(%this.getMesh())
+      %this.deselect();
+}
+
+function NavEditorGui::onModeSet(%this, %mode)
+{
+   // Callback when the nav editor changes mode. Set the appropriate dynamic
+   // GUI contents in the properties/actions boxes.
+   NavInspector.setVisible(false);
+
+   %actions = NavEditorOptionsWindow->ActionsBox;
+   %actions->SelectActions.setVisible(false);
+   %actions->LinkActions.setVisible(false);
+   %actions->CoverActions.setVisible(false);
+   %actions->TileActions.setVisible(false);
+   %actions->TestActions.setVisible(false);
+
+   %properties = NavEditorOptionsWindow->PropertiesBox;
+   %properties->LinkProperties.setVisible(false);
+   %properties->TileProperties.setVisible(false);
+   %properties->TestProperties.setVisible(false);
+
+   switch$(%mode)
+   {
+   case "SelectMode":
+      NavInspector.setVisible(true);
+      %actions->SelectActions.setVisible(true);
+   case "LinkMode":
+      %actions->LinkActions.setVisible(true);
+      %properties->LinkProperties.setVisible(true);
+   case "CoverMode":
+      // 
+      %actions->CoverActions.setVisible(true);
+   case "TileMode":
+      %actions->TileActions.setVisible(true);
+      %properties->TileProperties.setVisible(true);
+   case "TestMode":
+      %actions->TestActions.setVisible(true);
+      %properties->TestProperties.setVisible(true);
+   }
+}
+
+function NavEditorGui::paletteSync(%this, %mode)
+{
+   // Synchronise the palette (small buttons on the left) with the actual mode
+   // the nav editor is in.
+   %evalShortcut = "ToolsPaletteArray-->" @ %mode @ ".setStateOn(1);";
+   eval(%evalShortcut);
+} 
+
+function NavEditorGui::onEscapePressed(%this)
+{
+   return false;
+}
+
+function NavEditorGui::selectObject(%this, %obj)
+{
+   NavTreeView.clearSelection();
+   if(isObject(%obj))
+      NavTreeView.selectItem(%obj);
+   %this.onObjectSelected(%obj);
+}
+
+function NavEditorGui::onObjectSelected(%this, %obj)
+{
+   if(isObject(%this.selectedObject))
+      %this.deselect();
+   %this.selectedObject = %obj;
+   if(isObject(%obj))
+   {
+      %this.selectMesh(%obj);
+      NavInspector.inspect(%obj);
+   }
+}
+
+function NavEditorGui::deleteMesh(%this)
+{
+   if(isObject(%this.selectedObject))
+   {
+      %this.selectedObject.delete();
+      %this.selectObject(-1);
+   }
+}
+
+function NavEditorGui::deleteSelected(%this)
+{
+   switch$(%this.getMode())
+   {
+   case "SelectMode":
+      // Try to delete the selected NavMesh.
+      if(isObject(NavEditorGui.selectedObject))
+         MessageBoxYesNo("Warning",
+            "Are you sure you want to delete" SPC NavEditorGui.selectedObject.getName(),
+            "NavEditorGui.deleteMesh();");
+   case "TestMode":
+      %this.getPlayer().delete();
+      %this.onPlayerDeselected();
+   case "LinkMode":
+      %this.deleteLink();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::buildSelectedMeshes(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().build(NavEditorGui.backgroundBuild, NavEditorGui.saveIntermediates);
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::buildLinks(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().buildLinks();
+      %this.isDirty = true;
+   }
+}
+
+function updateLinkData(%control, %flags)
+{
+   %control->LinkWalkFlag.setActive(true);
+   %control->LinkJumpFlag.setActive(true);
+   %control->LinkDropFlag.setActive(true);
+   %control->LinkLedgeFlag.setActive(true);
+   %control->LinkClimbFlag.setActive(true);
+   %control->LinkTeleportFlag.setActive(true);
+
+   %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag);
+   %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag);
+   %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag);
+   %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag);
+   %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag);
+   %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag);
+}
+
+function getLinkFlags(%control)
+{
+   return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) |
+          (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) |
+          (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) |
+          (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) |
+          (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) |
+          (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0);
+}
+
+function disableLinkData(%control)
+{
+   %control->LinkWalkFlag.setActive(false);
+   %control->LinkJumpFlag.setActive(false);
+   %control->LinkDropFlag.setActive(false);
+   %control->LinkLedgeFlag.setActive(false);
+   %control->LinkClimbFlag.setActive(false);
+   %control->LinkTeleportFlag.setActive(false);
+}
+
+function NavEditorGui::onLinkSelected(%this, %flags)
+{
+   updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags);
+}
+
+function NavEditorGui::onPlayerSelected(%this, %flags)
+{
+   updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags);
+}
+
+function NavEditorGui::updateLinkFlags(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %properties = NavEditorOptionsWindow-->LinkProperties;
+      %this.setLinkFlags(getLinkFlags(%properties));
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::updateTestFlags(%this)
+{
+   if(isObject(%this.getPlayer()))
+   {
+      %properties = NavEditorOptionsWindow-->TestProperties;
+      %player = %this.getPlayer();
+
+      %player.allowWwalk = %properties->LinkWalkFlag.isStateOn();
+      %player.allowJump = %properties->LinkJumpFlag.isStateOn();
+      %player.allowDrop = %properties->LinkDropFlag.isStateOn();
+      %player.allowLedge = %properties->LinkLedgeFlag.isStateOn();
+      %player.allowClimb = %properties->LinkClimbFlag.isStateOn();
+      %player.allowTeleport = %properties->LinkTeleportFlag.isStateOn();
+
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::onLinkDeselected(%this)
+{
+   disableLinkData(NavEditorOptionsWindow-->LinkProperties);
+}
+
+function NavEditorGui::onPlayerDeselected(%this)
+{
+   disableLinkData(NavEditorOptionsWindow-->TestProperties);
+}
+
+function NavEditorGui::createCoverPoints(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().createCoverPoints();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::deleteCoverPoints(%this)
+{
+   if(isObject(%this.getMesh()))
+   {
+      %this.getMesh().deleteCoverPoints();
+      %this.isDirty = true;
+   }
+}
+
+function NavEditorGui::findCover(%this)
+{
+   if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer()))
+   {
+      %pos = LocalClientConnection.getControlObject().getPosition();
+      %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText();
+      if(%text !$= "")
+         %pos = eval(%text);
+      %this.getPlayer().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText());
+   }
+}
+
+function NavEditorGui::followObject(%this)
+{
+   if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer()))
+   {
+      %obj = LocalClientConnection.player;
+      %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText();
+      if(%text !$= "")
+      {
+         eval("%obj = " @ %text);
+         if(!isObject(%obj))
+            MessageBoxOk("Error", "Cannot find object" SPC %text);
+      }
+      if(isObject(%obj))
+         %this.getPlayer().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText());
+   }
+}
+
+function NavInspector::inspect(%this, %obj)
+{
+   %name = "";
+   if(isObject(%obj))
+      %name = %obj.getName();
+   else
+      NavFieldInfoControl.setText("");
+
+   Parent::inspect(%this, %obj);
+}
+
+function NavInspector::onInspectorFieldModified(%this, %object, %fieldName, %arrayIndex, %oldValue, %newValue)
+{
+   // Same work to do as for the regular WorldEditor Inspector.
+   Inspector::onInspectorFieldModified(%this, %object, %fieldName, %arrayIndex, %oldValue, %newValue);
+}
+
+function NavInspector::onFieldSelected(%this, %fieldName, %fieldTypeStr, %fieldDoc)
+{
+   NavFieldInfoControl.setText("<font:ArialBold:14>" @ %fieldName @ "<font:ArialItalic:14> (" @ %fieldTypeStr @ ") " NL "<font:Arial:14>" @ %fieldDoc);
+}
+
+function NavTreeView::onInspect(%this, %obj)
+{
+   NavInspector.inspect(%obj);
+}
+
+function NavTreeView::onSelect(%this, %obj)
+{
+   NavInspector.inspect(%obj);
+   NavEditorGui.onObjectSelected(%obj);
+}
+
+function NavEditorGui::prepSelectionMode(%this)
+{
+   %this.setMode("SelectMode");
+   ToolsPaletteArray-->NavEditorSelectMode.setStateOn(1);
+}
+
+//-----------------------------------------------------------------------------
+
+function ENavEditorPaletteButton::onClick(%this)
+{
+   // When clicking on a pelette button, add its description to the bottom of
+   // the editor window.
+   EditorGuiStatusBar.setInfo(%this.DetailedDesc);
+}
+
+//-----------------------------------------------------------------------------
+
+function NavMeshLinkFlagButton::onClick(%this)
+{
+   NavEditorGui.updateLinkFlags();
+}
+
+function NavMeshTestFlagButton::onClick(%this)
+{
+   NavEditorGui.updateTestFlags();
+}
+
+singleton GuiControlProfile(NavEditorProfile)
+{
+   canKeyFocus = true;
+   opaque = true;
+   fillColor = "192 192 192 192";
+   category = "Editor";
+};
+
+singleton GuiControlProfile(GuiSimpleBorderProfile)
+{
+   opaque = false;   
+   border = 1;   
+   category = "Editor";
+};

+ 130 - 0
Templates/Full/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui

@@ -0,0 +1,130 @@
+%paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) {
+   canSaveDynamicFields = "0";
+   Enabled = "1";
+   isContainer = "1";
+   Profile = "GuiDefaultProfile";
+   HorizSizing = "right";
+   VertSizing = "bottom";
+   Position = "0 0";
+   Extent = "1024 768";
+   MinExtent = "8 2";
+   canSave = "1";
+   Visible = "1";
+   hovertime = "1000";
+   
+   new GuiBitmapButtonCtrl(ENavEditorSelectModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorSelectMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.prepSelectionMode();";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "View NavMesh (1).";
+      DetailedDesc = "";
+      hovertime = "1000";
+      bitmap = "tools/gui/images/menubar/visibility-toggle";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorLinkModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorLinkMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"LinkMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Create off-mesh links (2).";
+      DetailedDesc = "Click to select/add. Shift-click to add multiple end points.";
+      hovertime = "1000";
+      bitmap = "tools/navEditor/images/nav-link";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorCoverModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorCoverMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"CoverMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Edit cover (3).";
+      DetailedDesc = "";
+      hovertime = "1000";
+      bitmap = "tools/navEditor/images/nav-cover";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorTileModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorTileMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"TileMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "View tiles (4).";
+      DetailedDesc = "Click to select.";
+      hovertime = "1000";
+      bitmap = "tools/gui/images/menubar/select-bounds";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+   new GuiBitmapButtonCtrl(ENavEditorTestModeBtn) {
+      canSaveDynamicFields = "1";
+      class = ENavEditorPaletteButton;
+      internalName = "NavEditorTestMode";
+      Enabled = "1";
+      isContainer = "0";
+      Profile = "GuiButtonProfile";
+      HorizSizing = "right";
+      VertSizing = "bottom";
+      Position = "0 0";
+      Extent = "25 19";
+      MinExtent = "8 2";
+      canSave = "1";
+      Visible = "1";
+      Command = "NavEditorGui.setMode(\"TestMode\");";
+      tooltipprofile = "GuiToolTipProfile";
+      ToolTip = "Test pathfinding (5).";
+      DetailedDesc = "Click to select/move character, CTRL-click to spawn, SHIFT-click to deselect.";
+      hovertime = "1000";
+      bitmap = "tools/worldEditor/images/toolbar/3rd-person-camera";
+      buttonType = "RadioButton";
+      useMouseEvents = "0";
+   };
+};

+ 1 - 1
Templates/Full/game/tools/worldEditor/gui/ToolsToolbar.ed.gui

@@ -8,7 +8,7 @@
    HorizSizing = "right";
    VertSizing = "bottom";
    Position = "0 31";
-   Extent = (29 + 4) * 14 + 12 SPC "33";
+   Extent = "0 33";
    MinExtent = "8 2";
    canSave = "1";
    Visible = "1";

+ 1 - 0
Templates/Full/game/tools/worldEditor/scripts/EditorGui.ed.cs

@@ -384,6 +384,7 @@ function EditorGui::addToToolsToolbar( %this, %pluginName, %internalName, %bitma
          useMouseEvents = "0";
       };
       ToolsToolbarArray.add(%button);
+      EWToolsToolbar.setExtent((25 + 8) * (%count + 1) + 12 SPC "33");
    }
 }
 

+ 1 - 3
Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -84,8 +84,6 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SFXSpace",      "Sound Space" );
       %this.registerMissionObject( "OcclusionVolume", "Occlusion Volume" );
       %this.registerMissionObject( "AccumulationVolume", "Accumulation Volume" );
-      %this.registerMissionObject("NavMesh", "Navigation mesh");
-      %this.registerMissionObject("NavPath", "Path");
       
    %this.endGroup();
    
@@ -788,4 +786,4 @@ function genericCreateObject( %class )
    
    // In case the caller wants it.
    return %obj;   
-}
+}

部分文件因为文件数量过多而无法显示