Ver Fonte

added navmesh tester tool

Added ground work for tester tool
tester tool works but needs to fill out list of acceptable datablocks and spawnclasses
navpaths now share 1 navmeshquery
AIControllerData now has a vector of area costs for different polyareas
General cleanup
marauder2k7 há 1 mês atrás
pai
commit
6d36e17d91

+ 25 - 0
Engine/source/T3D/AI/AIController.cpp

@@ -535,6 +535,10 @@ AIControllerData::AIControllerData()
 
 #ifdef TORQUE_NAVIGATION_ENABLED
    mLinkTypes = LinkData(AllFlags);
+   mFilter.setIncludeFlags(mLinkTypes.getFlags());
+   mFilter.setExcludeFlags(0);
+   mAreaCosts.setSize(PolyAreas::NumAreas);
+   mAreaCosts.fill(1.0f);
    mNavSize = AINavigation::Regular;
    mFlocking.mChance = 90;
    mFlocking.mMin = 1.0f;
@@ -560,6 +564,8 @@ AIControllerData::AIControllerData(const AIControllerData& other, bool temp_clon
 
 #ifdef TORQUE_NAVIGATION_ENABLED
    mLinkTypes = other.mLinkTypes;
+   mFilter = other.mFilter;
+   mAreaCosts = other.mAreaCosts;
    mNavSize = other.mNavSize;
    mFlocking.mChance = other.mFlocking.mChance;
    mFlocking.mMin = other.mFlocking.mMin;
@@ -629,6 +635,8 @@ void AIControllerData::initPersistFields()
    addFieldV("FlockSideStep", TypeRangedF32, Offset(mFlocking.mSideStep, AIControllerData), &CommonValidators::PositiveFloat,
       "@brief Distance from destination before we stop moving out of the way.");
 
+   addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIControllerData),
+      "Vector of costs for each PolyArea.");
    addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData),
       "Allow the character to walk on dry land.");
    addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData),
@@ -662,6 +670,10 @@ void AIControllerData::packData(BitStream* stream)
 
 #ifdef TORQUE_NAVIGATION_ENABLED
    //enums
+   stream->write(mAreaCosts.size());
+   for (U32 i = 0; i < mAreaCosts.size(); i++) {
+      stream->write(mAreaCosts[i]);
+   }
    stream->write(mLinkTypes.getFlags());
    stream->write((U32)mNavSize);
    // end enums
@@ -684,10 +696,23 @@ void AIControllerData::unpackData(BitStream* stream)
    stream->read(&mFollowTolerance);
 
 #ifdef TORQUE_NAVIGATION_ENABLED
+   U32 num;
+   stream->read(&num);
+   mAreaCosts.setSize(num);
+   for (U32 i = 0; i < num; i++)
+   {
+      stream->read(&mAreaCosts[i]);
+   }
    //enums
    U16 linkFlags;
    stream->read(&linkFlags);
    mLinkTypes = LinkData(linkFlags);
+   mFilter.setIncludeFlags(mLinkTypes.getFlags());
+   mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags());
+   for (U32 i = 0; i < PolyAreas::NumAreas; i++)
+   {
+      mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]);
+   }
    U32 navSize;
    stream->read(&navSize);
    mNavSize = (AINavigation::NavSize)(navSize);   

+ 2 - 0
Engine/source/T3D/AI/AIController.h

@@ -168,6 +168,8 @@ public:
 
    /// Types of link we can use.
    LinkData mLinkTypes;
+   dtQueryFilter mFilter;
+   Vector<F32> mAreaCosts;
    AINavigation::NavSize mNavSize;
 #endif
    Delegate<void(AIController* obj, Point3F location, Move* movePtr)> resolveYawPtr;

+ 1 - 0
Engine/source/T3D/AI/AINavigation.cpp

@@ -77,6 +77,7 @@ bool AINavigation::setPathDestination(const Point3F& pos, bool replace)
    path->mAlwaysRender = true;
    path->mLinkTypes = getCtrl()->mControllerData->mLinkTypes;
    path->mXray = true;
+   path->mFilter = getCtrl()->mControllerData->mFilter;
    // Paths plan automatically upon being registered.
    if (!path->registerObject())
    {

+ 18 - 1
Engine/source/T3D/aiPlayer.cpp

@@ -114,6 +114,10 @@ AIPlayer::AIPlayer()
    mJump = None;
    mNavSize = Regular;
    mLinkTypes = LinkData(AllFlags);
+   mFilter.setIncludeFlags(mLinkTypes.getFlags());
+   mFilter.setExcludeFlags(0);
+   mAreaCosts.setSize(PolyAreas::NumAreas);
+   mAreaCosts.fill(1.0f);
 #endif
 
    mIsAiControlled = true;
@@ -163,7 +167,8 @@ void AIPlayer::initPersistFields()
 
 #ifdef TORQUE_NAVIGATION_ENABLED
    addGroup("Pathfinding");
-
+      addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIPlayer),
+         "Vector of costs for each PolyArea.");
       addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer),
          "Allow the character to walk on dry land.");
       addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer),
@@ -785,6 +790,7 @@ void AIPlayer::moveToNode(S32 node)
 
 bool AIPlayer::setPathDestination(const Point3F &pos)
 {
+#ifdef TORQUE_NAVIGATION_ENABLED
    // Pathfinding only happens on the server.
    if(!isServerObject())
       return false;
@@ -799,6 +805,13 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
       return false;
    }
 
+   mFilter.setIncludeFlags(mLinkTypes.getFlags());
+   mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags());
+   for (U32 i = 0; i < PolyAreas::NumAreas; i++)
+   {
+      mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]);
+   }
+
    // Create a new path.
    NavPath *path = new NavPath();
 
@@ -808,6 +821,7 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
    path->mFromSet = path->mToSet = true;
    path->mAlwaysRender = true;
    path->mLinkTypes = mLinkTypes;
+   path->mFilter = mFilter;
    path->mXray = true;
    // Paths plan automatically upon being registered.
    if(!path->registerObject())
@@ -839,6 +853,9 @@ bool AIPlayer::setPathDestination(const Point3F &pos)
       path->deleteObject();
       return false;
    }
+#else
+   setMoveDestination(pos, false);
+#endif
 }
 
 DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),,

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

@@ -59,8 +59,6 @@ GuiNavEditorCtrl::GuiNavEditorCtrl()
    mIsDirty = false;
    mStartDragMousePoint = InvalidMousePoint;
    mMesh = NULL;
-   mPlayer = mCurPlayer = NULL;
-   mSpawnClass = mSpawnDatablock = "";
 }
 
 GuiNavEditorCtrl::~GuiNavEditorCtrl()
@@ -97,11 +95,6 @@ void GuiNavEditorCtrl::initPersistFields()
    docsURL;
    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();
 }
 
@@ -140,79 +133,6 @@ DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),,
    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;
-}
-
-DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),,
-   "@brief Deselect whatever is currently selected in the editor.")
-{
-   object->deselect();
-}
-
-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 = obj;
-#ifdef TORQUE_NAVIGATION_ENABLED
-      AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(obj);
-      if (asAIPlayer) //try direct
-      {
-         Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
-      }
-      else
-      {
-         ShapeBase* sbo = dynamic_cast<ShapeBase*>(obj);
-         if (sbo->getAIController())
-         {
-            if (sbo->getAIController()->mControllerData)
-               Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
-         }
-         else
-         {
-#endif
-            Con::executef(this, "onPlayerSelected");
-#ifdef TORQUE_NAVIGATION_ENABLED
-         }
-      }
-#endif
-   }
-}
-
-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_)
@@ -277,89 +197,6 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
    mouseLock();
 
    return;
-
-   // 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;
-
-   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 | VehicleObjectType, &ri))
-         {
-            if(ri.object)
-            {
-               mPlayer = ri.object;
-#ifdef TORQUE_NAVIGATION_ENABLED
-               AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
-               if (asAIPlayer) //try direct
-               {
-                  Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
-               }
-               else
-               {
-                  ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
-                  if (sbo->getAIController())
-                  {
-                     if (sbo->getAIController()->mControllerData)
-                        Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
-                  }
-                  else
-                  {
-#endif
-                     Con::executef(this, "onPlayerSelected");
-                  }
-#ifdef TORQUE_NAVIGATION_ENABLED
-               }
-            }
-#endif
-         }
-         else if (!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
-         {
-            AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
-            if (asAIPlayer) //try direct
-            {
-#ifdef TORQUE_NAVIGATION_ENABLED
-               asAIPlayer->setPathDestination(ri.point);
-#else
-                asAIPlayer->setMoveDestination(ri.point,false);
-#endif
-            }
-            else
-            {
-               ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
-               if (sbo->getAIController())
-               {
-                  if (sbo->getAIController()->mControllerData)
-                     sbo->getAIController()->getNav()->setPathDestination(ri.point, true);
-               }
-            }
-         }
-      }
-   }
 }
 
 void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
@@ -385,22 +222,6 @@ void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
       mTool->on3DMouseMove(event);
 
    return;
-
-   //if(mSelRiver != NULL && mSelNode != -1)
-      //mGizmo->on3DMouseMove(event);
-
-   Point3F startPnt = event.pos;
-   Point3F endPnt = event.pos + event.vec * 1000.0f;
-
-   RayInfo ri;
-
-   if(mMode == mTestMode)
-   {
-      if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
-         mCurPlayer = ri.object;
-      else
-         mCurPlayer = NULL;
-   }
 }
 
 void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
@@ -443,22 +264,6 @@ void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &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);
@@ -479,14 +284,6 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect)
    if (mTool)
       mTool->onRender3D();
 
-   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);

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

@@ -103,17 +103,8 @@ public:
    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 spawnPlayer(const Point3F &pos);
 
    /// @}
    void setActiveTool(NavMeshTool* tool);
@@ -148,14 +139,6 @@ protected:
 
    /// @}
 
-   /// @name Test mode
-   /// @{
-
-   SimObjectPtr<SceneObject> mPlayer;
-   SimObjectPtr<SceneObject> mCurPlayer;
-
-   /// @}
-
    Gui3DMouseEvent mLastMouseEvent;
 
 #define InvalidMousePoint Point2I(-100,-100)

+ 23 - 54
Engine/source/navigation/navMesh.cpp

@@ -225,10 +225,13 @@ NavMesh::NavMesh()
 
    mWaterVertStart = 0;
    mWaterTriStart = 0;
+
+   mQuery = NULL;
 }
 
 NavMesh::~NavMesh()
 {
+   dtFreeNavMeshQuery(mQuery);
    dtFreeNavMesh(nm);
    nm = NULL;
    delete ctx;
@@ -677,32 +680,6 @@ bool NavMesh::build(bool background, bool saveIntermediates)
       return false;
    }
 
-   Box3F worldBox = getWorldBox();
-   SceneContainer::CallbackInfo info;
-   info.context = PLC_Navigation;
-   info.boundingBox = worldBox;
-   m_geo = new RecastPolyList;
-   info.polyList = m_geo;
-   info.key = this;
-   getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info);
-
-   // Parse water objects into the same list, but remember how much geometry was /not/ water.
-   mWaterVertStart = m_geo->getVertCount();
-   mWaterTriStart = m_geo->getTriCount();
-   if (mWaterMethod != Ignore)
-   {
-      getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info);
-   }
-
-   // Check for no geometry.
-   if (!m_geo->getVertCount())
-   {
-      m_geo->clear();
-      return false;
-   }
-
-   m_geo->getChunkyMesh();
-
    // Needed for the recast config and generation params.
    Box3F rc_box = DTStoRC(getWorldBox());
    S32 gw = 0, gh = 0;
@@ -803,33 +780,6 @@ void NavMesh::createNewFile()
    mFileName = StringTable->insert(fileName.c_str());
 }
 
-void NavMesh::updateConfig()
-{
-   //// Build rcConfig object from our console members.
-   //dMemset(&cfg, 0, sizeof(cfg));
-   //cfg.cs = mCellSize;
-   //cfg.ch = mCellHeight;
-   //Box3F box = DTStoRC(getWorldBox());
-   //rcVcopy(cfg.bmin, box.minExtents);
-   //rcVcopy(cfg.bmax, box.maxExtents);
-   //rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
-
-   //cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight);
-   //cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight);
-   //cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize);
-   //cfg.walkableSlopeAngle = mWalkableSlope;
-   //cfg.borderSize = cfg.walkableRadius + 3;
-
-   //cfg.detailSampleDist = mDetailSampleDist;
-   //cfg.detailSampleMaxError = mDetailSampleMaxError;
-   //cfg.maxEdgeLen = mMaxEdgeLen;
-   //cfg.maxSimplificationError = mMaxSimplificationError;
-   //cfg.maxVertsPerPoly = mMaxVertsPerPoly;
-   //cfg.minRegionArea = mMinRegionArea;
-   //cfg.mergeRegionArea = mMergeRegionArea;
-   //cfg.tileSize = mTileSize / cfg.cs;
-}
-
 S32 NavMesh::getTile(const Point3F& pos)
 {
    if(mBuilding)
@@ -938,6 +888,8 @@ void NavMesh::buildNextTile()
 
    if(!mDirtyTiles.empty())
    {
+      dtFreeNavMeshQuery(mQuery);
+      mQuery = NULL;
       // Pop a single dirty tile and process it.
       U32 i = mDirtyTiles.front();
       mDirtyTiles.pop_front();
@@ -1002,6 +954,12 @@ void NavMesh::buildNextTile()
       // Did we just build the last tile?
       if(mDirtyTiles.empty())
       {
+         mQuery = dtAllocNavMeshQuery();
+         if (dtStatusFailed(mQuery->init(nm, 2048)))
+         {
+            Con::errorf("NavMesh - Failed to create navmesh query");
+         }
+
          ctx->stopTimer(RC_TIMER_TOTAL);
          if(getEventManager())
          {
@@ -1012,6 +970,15 @@ void NavMesh::buildNextTile()
          mBuilding = false;
       }
    }
+
+   if (mQuery == NULL)
+   {
+      mQuery = dtAllocNavMeshQuery();
+      if (dtStatusFailed(mQuery->init(nm, 2048)))
+      {
+         Con::errorf("NavMesh - Failed to create navmesh query");
+      }
+   }
 }
 
 unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize)
@@ -1614,11 +1581,13 @@ void NavMesh::renderToDrawer()
             m_drawMode == DRAWMODE_NAVMESH_INVIS))
       {
          if (m_drawMode != DRAWMODE_NAVMESH_INVIS)
-            duDebugDrawNavMesh(&mDbgDraw, *n->nm, 0);
+            duDebugDrawNavMeshWithClosedList(&mDbgDraw, *n->nm, *n->mQuery, 0);
          if(m_drawMode == DRAWMODE_NAVMESH_BVTREE)
             duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm);
          if(m_drawMode == DRAWMODE_NAVMESH_PORTALS)
             duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm);
+         if (m_drawMode == DRAWMODE_NAVMESH_NODES)
+            duDebugDrawNavMeshNodes(&mDbgDraw, *n->mQuery);
       }
 
       mDbgDraw.depthMask(true, false);

+ 3 - 8
Engine/source/navigation/navMesh.h

@@ -187,6 +187,8 @@ public:
    /// Delete the selected link.
    void deleteLink(U32 idx);
 
+   dtNavMeshQuery* getNavMeshQuery() { return mQuery; }
+
    /// @}
 
    /// Should small characters use this mesh?
@@ -381,16 +383,9 @@ private:
 
    /// @}
 
-   /// @name Intermediate data
-   /// @{
-
-   /// Updates our config from console members.
-   void updateConfig();
-
    dtNavMesh *nm;
    rcContext *ctx;
-
-   /// @}
+   dtNavMeshQuery* mQuery;
 
    /// @name Cover
    /// @{

+ 302 - 0
Engine/source/navigation/navMeshTools/navMeshTestTool.cpp

@@ -0,0 +1,302 @@
+#include "navMeshTestTool.h"
+#include "navigation/guiNavEditorCtrl.h"
+#include "console/consoleTypes.h"
+#include "gfx/gfxDrawUtil.h"
+#include "scene/sceneManager.h"
+#include "math/mathUtils.h"
+#include "T3D/gameBase/gameConnection.h"
+#include "T3D/AI/AIController.h"
+
+IMPLEMENT_CONOBJECT(NavMeshTestTool);
+
+////  take control
+//   GameConnection* conn = GameConnection::getConnectionToServer();
+//   if (conn)
+//      conn->setControlObject(static_cast<GameBase*>(obj));
+//}
+
+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 NavMeshTestTool::spawnPlayer(const Point3F& position)
+{
+   if (mSpawnClass.isEmpty() || mSpawnDatablock.isEmpty())
+   {
+      Con::warnf("NavMeshTestTool::spawnPlayer - spawn class/datablock not set!");
+      return;
+   }
+
+   SimObject* spawned = Sim::spawnObject(mSpawnClass, mSpawnDatablock);
+   SceneObject* obj = dynamic_cast<SceneObject*>(spawned);
+
+   if (!obj)
+   {
+      Con::warnf("NavMeshTestTool::spawnPlayer - spawn failed or not a SceneObject");
+      if (spawned)
+         spawned->deleteObject();
+      return;
+   }
+
+   obj->setPosition(position);
+   obj->registerObject();
+   SimObject* cleanup = Sim::findObject("MissionCleanup");
+   if (cleanup)
+   {
+      SimGroup* missionCleanup = dynamic_cast<SimGroup*>(cleanup);
+      missionCleanup->addObject(obj);
+   }
+   mPlayer = obj;
+
+   Con::executef(this, "onPlayerSpawned", obj->getIdString());
+}
+
+void NavMeshTestTool::drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col)
+{
+   dd.depthMask(false);
+
+   // Agent dimensions.	
+   duDebugDrawCylinderWire(&dd, pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f);
+
+   duDebugDrawCircle(&dd, pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f);
+
+   U32 colb = duRGBA(0, 0, 0, 196);
+   dd.begin(DU_DRAW_LINES);
+   dd.vertex(pos[0], pos[1] - c, pos[2], colb);
+   dd.vertex(pos[0], pos[1] + c, pos[2], colb);
+   dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb);
+   dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb);
+   dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb);
+   dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb);
+   dd.end();
+
+   dd.depthMask(true);
+}
+
+NavMeshTestTool::NavMeshTestTool()
+{
+   mSpawnClass = String::EmptyString;
+   mSpawnDatablock = String::EmptyString;
+   mPlayer = NULL;
+   mCurPlayer = NULL;
+
+   mPathStart = Point3F::Max;
+   mPathEnd = Point3F::Max;
+}
+
+void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt)
+{
+   Con::executef(this, "onActivated");
+}
+
+void NavMeshTestTool::onDeactivated()
+{
+   Con::executef(this, "onDeactivated");
+}
+
+void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt)
+{
+   if (mNavMesh.isNull())
+      return;
+
+   Point3F startPnt = evt.pos;
+   Point3F endPnt = evt.pos + evt.vec * 1000.0f;
+
+   RayInfo ri;
+   bool shift = evt.modifier & SI_LSHIFT;
+   bool ctrl = evt.modifier & SI_LCTRL;
+
+   if (ctrl)
+   {
+      if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+      {
+         spawnPlayer(ri.point);
+      }
+      return;
+   }
+
+   if (shift)
+   {
+      mPlayer = NULL;
+      Con::executef(this, "onPlayerDeselected");
+      return;
+   }
+
+   if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
+   {
+      if (!ri.object)
+         return;
+
+      mPlayer = ri.object;
+
+#ifdef TORQUE_NAVIGATION_ENABLED
+      AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
+      if (asAIPlayer)
+      {
+         Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags()));
+      }
+      else
+      {
+         ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
+         if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData)
+         {
+            Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags()));
+         }
+         else
+         {
+            Con::executef(this, "onPlayerSelected");
+         }
+      }
+#else
+      Con::executef(this, "onPlayerSelected");
+#endif
+   }
+   else if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri))
+   {
+      if (mPlayer.isNull())
+      {
+         if (mPathStart != Point3F::Max && mPathEnd != Point3F::Max) // start a new path.
+         {
+            mPathStart = Point3F::Max;
+            mPathEnd = Point3F::Max;
+         }
+
+         if (mPathStart != Point3F::Max)
+         {
+            mPathEnd = ri.point;
+         }
+         else
+         {
+            mPathStart = ri.point;
+         }
+      }
+      else
+      {
+         AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
+         if (asAIPlayer) //try direct
+         {
+            asAIPlayer->setPathDestination(ri.point);
+            mPathStart = mPlayer->getPosition();
+            mPathEnd = ri.point;
+         }
+         else
+         {
+            ShapeBase* sbo = dynamic_cast<ShapeBase*>(mPlayer.getPointer());
+            if (sbo->getAIController())
+            {
+               if (sbo->getAIController()->mControllerData)
+               {
+                  sbo->getAIController()->getNav()->setPathDestination(ri.point, true);
+                  mPathStart = mPlayer->getPosition();
+                  mPathEnd = ri.point;
+               }
+            }
+         }
+      }
+   }
+
+}
+
+void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt)
+{
+   if (mNavMesh.isNull())
+      return;
+
+   Point3F startPnt = evt.pos;
+   Point3F endPnt = evt.pos + evt.vec * 1000.0f;
+
+   RayInfo ri;
+
+   if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
+      mCurPlayer = ri.object;
+   else
+      mCurPlayer = NULL;
+
+}
+
+void NavMeshTestTool::onRender3D()
+{
+   if (mNavMesh.isNull())
+      return;
+
+   static const U32 startCol = duRGBA(128, 25, 0, 192);
+   static const U32 endCol = duRGBA(51, 102, 0, 129);
+
+   const F32 agentRadius = mNavMesh->mWalkableRadius;
+   const F32 agentHeight = mNavMesh->mWalkableHeight;
+   const F32 agentClimb = mNavMesh->mWalkableClimb;
+
+   duDebugDrawTorque dd;
+   dd.depthMask(false);
+   if (mPathStart != Point3F::Max)
+   {
+      drawAgent(dd, DTStoRC(mPathStart), agentRadius, agentHeight, agentClimb, startCol);
+   }
+
+   if (mPathEnd != Point3F::Max)
+   {
+      drawAgent(dd, DTStoRC(mPathEnd), agentRadius, agentHeight, agentClimb, endCol);
+   }
+   dd.depthMask(true);
+
+   dd.immediateRender();
+
+   if (!mCurPlayer.isNull())
+      renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE);
+   if (!mPlayer.isNull())
+      renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN);
+}
+
+bool NavMeshTestTool::updateGuiInfo()
+{
+   SimObject* statusbar;
+   Sim::findObject("EditorGuiStatusBar", statusbar);
+
+   GuiTextCtrl* selectionBar;
+   Sim::findObject("EWorldEditorStatusBarSelection", selectionBar);
+
+   String text;
+
+   if (statusbar)
+      Con::executef(statusbar, "setInfo", text.c_str());
+
+   text = "";
+
+   if (selectionBar)
+      selectionBar->setText(text);
+
+   return true;
+}
+
+S32 NavMeshTestTool::getPlayerId()
+{
+   return mPlayer.isNull() ? 0 : mPlayer->getId();
+}
+
+DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), ,
+   "@brief Return the current player id.")
+{
+   return object->getPlayerId();
+}
+
+DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "")
+{
+   object->setSpawnClass(className);
+}
+
+DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , "")
+{
+   object->setSpawnDatablock(dbName);
+}

+ 47 - 0
Engine/source/navigation/navMeshTools/navMeshTestTool.h

@@ -0,0 +1,47 @@
+#ifndef _NAVMESHTESTTOOL_H_
+#define _NAVMESHTESTTOOL_H_
+
+
+#ifndef _NAVMESH_TOOL_H_
+#include "navigation/navMeshTool.h"
+#endif
+
+class NavMeshTestTool : public NavMeshTool
+{
+   typedef NavMeshTool Parent;
+protected:
+   String mSpawnClass;
+   String mSpawnDatablock;
+   SimObjectPtr<SceneObject> mPlayer;
+   SimObjectPtr<SceneObject> mCurPlayer;
+   Point3F mPathStart;
+   Point3F mPathEnd;
+public:
+   DECLARE_CONOBJECT(NavMeshTestTool);
+
+   void spawnPlayer(const Point3F& position);
+
+   void drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col);
+
+   NavMeshTestTool();
+
+   virtual ~NavMeshTestTool() {}
+
+   void onActivated(const Gui3DMouseEvent& evt) override;
+   void onDeactivated() override;
+
+   void on3DMouseDown(const Gui3DMouseEvent& evt) override;
+   void on3DMouseMove(const Gui3DMouseEvent& evt) override;
+   void onRender3D() override;
+
+   bool updateGuiInfo() override;
+
+   S32 getPlayerId();
+
+   void setSpawnClass(String className) { mSpawnClass = className; }
+   void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; }
+
+};
+
+
+#endif // !_NAVMESHTESTTOOL_H_

+ 3 - 10
Engine/source/navigation/navPath.cpp

@@ -69,14 +69,11 @@ NavPath::NavPath() :
    mXray = false;
    mRenderSearch = false;
 
-   mQuery = NULL;
    mStatus = DT_FAILURE;
 }
 
 NavPath::~NavPath()
 {
-   dtFreeNavMeshQuery(mQuery);
-   mQuery = NULL;
 }
 
 void NavPath::checkAutoUpdate()
@@ -264,9 +261,6 @@ bool NavPath::onAdd()
 
    if(isServerObject())
    {
-      mQuery = dtAllocNavMeshQuery();
-      if(!mQuery)
-         return false;
       checkAutoUpdate();
       if(!plan())
          setProcessTick(true);
@@ -293,7 +287,8 @@ bool NavPath::init()
       return false;
 
    // Initialise our query.
-   if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
+   mQuery = mMesh->getNavMeshQuery();
+   if(!mQuery)
       return false;
 
    mPoints.clear();
@@ -372,9 +367,6 @@ void NavPath::resize()
 bool NavPath::plan()
 {
    PROFILE_SCOPE(NavPath_plan);
-   // Initialise filter.
-   mFilter.setIncludeFlags(mLinkTypes.getFlags());
-
    // Initialise query and visit locations.
    if(!init())
       return false;
@@ -641,6 +633,7 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa
       {
          duDebugDrawTorque dd;
          duDebugDrawNavMeshNodes(&dd, *np->mQuery);
+         dd.immediateRender();
       }
    }
 }

+ 1 - 2
Engine/source/navigation/navPath.h

@@ -57,7 +57,7 @@ public:
 
    /// What sort of link types are we allowed to move on?
    LinkData mLinkTypes;
-
+   dtQueryFilter mFilter;
    /// Plan the path.
    bool plan();
 
@@ -152,7 +152,6 @@ private:
 
    dtNavMeshQuery *mQuery;
    dtStatus mStatus;
-   dtQueryFilter mFilter;
    S32 mCurIndex;
    Vector<Point3F> mPoints;
    Vector<U16> mFlags;

+ 6 - 1
Engine/source/navigation/torqueRecast.h

@@ -52,7 +52,8 @@ inline void rcCol(unsigned int col, U8 &r, U8 &g, U8 &b, U8 &a)
 }
 
 enum PolyAreas {
-   GroundArea = 1,
+   NullArea = 0,
+   GroundArea,
    WaterArea,
    OffMeshArea,
    NumAreas
@@ -99,6 +100,10 @@ struct LinkData {
          (climb ? ClimbFlag : 0) |
          (teleport ? TeleportFlag : 0);
    }
+   U16 getExcludeFlags() const
+   {
+      return AllFlags & ~getFlags();
+   }
 };
 
 #endif

+ 21 - 1
Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui

@@ -857,7 +857,27 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
                visible = "1";
                active = "0";
                tooltipProfile = "GuiToolTipProfile";
-			   toolTip = "Can this character walk on ground?";
+			      toolTip = "Can this character walk on ground?";
+               hovertime = "1000";
+               isContainer = "0";
+               canSave = "1";
+               canSaveDynamicFields = "0";
+            };
+            new GuiCheckBoxCtrl() {
+               internalName = "LinkSwimFlag";
+               class = "NavMeshTestFlagButton";
+               text = " Swim";
+               buttonType = "ToggleButton";
+               useMouseEvents = "0";
+               extent = "159 15";
+               minExtent = "8 2";
+               horizSizing = "right";
+               vertSizing = "bottom";
+               profile = "ToolsGuiCheckBoxProfile";
+               visible = "1";
+               active = "0";
+               tooltipProfile = "GuiToolTipProfile";
+			      toolTip = "Can this character swim?";
                hovertime = "1000";
                isContainer = "0";
                canSave = "1";

+ 0 - 63
Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui

@@ -77,68 +77,5 @@ $guiContent = new GuiControl(NavEditorToolbar,EditorGuiGroup) {
       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 = "ToolsGuiCheckBoxProfile";
-      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 = "ToolsGuiCheckBoxProfile";
-      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 = "ToolsGuiCheckBoxProfile";
-      visible = "1";
-      active = "1";
-      variable = "$Nav::Editor::renderBVTree";
-      tooltipProfile = "GuiToolTipProfile";
-      hovertime = "1000";
-      isContainer = "0";
-      internalName = "BVTreeButton";
-      canSave = "1";
-      canSaveDynamicFields = "0";
-   };
 };
 //--- OBJECT WRITE END ---

+ 9 - 8
Templates/BaseGame/game/tools/navEditor/main.tscript

@@ -66,6 +66,13 @@ function initializeNavEditor()
          buttonImage = "ToolsModule:nav_link_n_image";  
       };
 
+      new NavMeshTestTool()
+      {
+         internalName = "TestTool";
+         toolTip = "PathFinding Test tool";
+         buttonImage = "ToolsModule:3rd_person_camera_n_image";
+      };
+
       new TileTool()
       {
          internalName = "TileTool";
@@ -140,7 +147,7 @@ function EditorGui::SetNavPalletBar()
    EWToolsPaletteWindow.addButton("LinkMode",    "ToolsModule:nav_link_n_image",          "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links",  "2");  
    // EWToolsPaletteWindow.addButton("CoverMode",   "ToolsModule:nav_cover_n_image",         "NavEditorGui.setMode(\"CoverMode\");", "","Edit cover",             "3");  
    // EWToolsPaletteWindow.addButton("TileMode",    "ToolsModule:select_bounds_n_image",     "NavEditorGui.setMode(\"TileMode\");", "", "View tiles",             "4"); 
-   // EWToolsPaletteWindow.addButton("TestMode",    "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding",       "5"); 
+   EWToolsPaletteWindow.addButton("TestMode",    "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding",       "5"); 
    EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4");
    EWToolsPaletteWindow.refresh();
 }
@@ -263,7 +270,7 @@ function NavEditorPlugin::initSettings(%this)
    EditorSettings.beginGroup("NavEditor", true);
 
    EditorSettings.setDefaultValue("SpawnClass",     "AIPlayer");
-   EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData");
+   EditorSettings.setDefaultValue("SpawnDatablock", "ProtoPlayer");
 
    EditorSettings.endGroup();
 }
@@ -273,9 +280,6 @@ 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");
@@ -295,9 +299,6 @@ 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);

+ 143 - 53
Templates/BaseGame/game/tools/navEditor/navEditor.tscript

@@ -411,6 +411,142 @@ function NavMeshLinkBiDirection::onClick(%this)
 
 //------------------------------------------------------
 
+function NavMeshTestTool::onActivated(%this)
+{
+   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);
+
+   %actions->TestActions.setVisible(true);
+   %properties->TestProperties.setVisible(true);
+}
+
+function NavMeshTestTool::onDeactivated(%this)
+{
+   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);
+}
+
+function updateTestData(%control, %flags)
+{
+   %control->LinkWalkFlag.setActive(true);
+   %control->LinkSwimFlag.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->LinkSwimFlag.setStateOn(%flags & $Nav::SwimFlag);
+   %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 getTestFlags(%control)
+{
+   return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) |
+          (%control->LinkSwimFlag.isStateOn() ? $Nav::SwimFlag : 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 disableTestData(%control)
+{
+   %control->LinkWalkFlag.setActive(false);
+   %control->LinkSwimFlag.setActive(false);
+   %control->LinkJumpFlag.setActive(false);
+   %control->LinkDropFlag.setActive(false);
+   %control->LinkLedgeFlag.setActive(false);
+   %control->LinkClimbFlag.setActive(false);
+   %control->LinkTeleportFlag.setActive(false);
+}
+
+function getControllerDataFlags(%controllerData)
+{
+   return (%controllerData.allowWalk  ? $Nav::WalkFlag : 0 ) |
+          (%controllerData.allowSwim  ? $Nav::SwimFlag : 0 ) |
+          (%controllerData.allowJump  ? $Nav::JumpFlag : 0 ) |
+          (%controllerData.allowDrop  ? $Nav::DropFlag : 0 ) |
+          (%controllerData.allowLedge ? $Nav::LedgeFlag : 0) |
+          (%controllerData.allowClimb ? $Nav::ClimbFlag : 0) |
+          (%controllerData.allowTeleport ? $Nav::TeleportFlag : 0 );
+}
+
+function NavMeshTestTool::updateTestFlags(%this)
+{
+   if(isObject(%this.getPlayer()))
+   {
+      %properties = NavEditorOptionsWindow-->TestProperties;
+      %player = %this.getPlayer();
+      %controllerData = %player.getDatablock().aiControllerData;
+
+      %controllerData.allowWalk = %properties->LinkWalkFlag.isStateOn();
+      %controllerData.allowSwim = %properties->LinkSwimFlag.isStateOn();
+      %controllerData.allowJump = %properties->LinkJumpFlag.isStateOn();
+      %controllerData.allowDrop = %properties->LinkDropFlag.isStateOn();
+      %controllerData.allowLedge = %properties->LinkLedgeFlag.isStateOn();
+      %controllerData.allowClimb = %properties->LinkClimbFlag.isStateOn();
+      %controllerData.allowTeleport = %properties->LinkTeleportFlag.isStateOn();
+
+      %player.aiController = new AIController(){ ControllerData = %controllerData; };
+      %player.setAIController(%player.aiController);
+   }
+}
+
+function NavMeshTestTool::onPlayerSelected(%this, %flags)
+{
+   if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer"))))
+   {
+      %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; };
+      %this.getPlayer().setAIController(%this.getPlayer().aiController);
+      %flags = getControllerDataFlags(%this.getPlayer().getDatablock().aiControllerData);
+   }
+   NavMeshIgnore(%this.getPlayer(), true);
+   %this.getPlayer().setDamageState("Enabled");
+   
+   updateTestData(NavEditorOptionsWindow-->TestProperties, %flags);
+}
+
+function NavMeshTestTool::onPlayerDeselected(%this)
+{
+   disableTestData(NavEditorOptionsWindow-->TestProperties);
+}
+
+function NavMeshTestFlagButton::onClick(%this)
+{
+   NavMeshTools->TestTool.updateTestFlags();
+}
+
+//------------------------------------------------------
+
 function TileTool::onActivated(%this)
 {
    NavInspector.setVisible(false);
@@ -540,13 +676,13 @@ function NavEditorGui::deleteSelected(%this)
          toolsMessageBoxYesNo("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;
-   }
+   // case "TestMode":
+   //    %this.getPlayer().delete();
+   //    %this.onPlayerDeselected();
+   // case "LinkMode":
+   //    %this.deleteLink();
+   //    %this.isDirty = true;
+   // }
 }
 
 function NavEditorGui::buildSelectedMeshes(%this)
@@ -567,47 +703,6 @@ function NavEditorGui::buildLinks(%this)
    }
 }
 
-function NavEditorGui::onLinkSelected(%this, %flags)
-{
-   updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags);
-}
-
-function NavEditorGui::onPlayerSelected(%this, %flags)
-{
-   if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer"))))
-   {
-      %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; };
-      %this.getPlayer().setAIController(%this.getPlayer().aiController);
-   }
-   NavMeshIgnore(%this.getPlayer(), true);
-   %this.getPlayer().setDamageState("Enabled");
-   
-   updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags);
-}
-
-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::onPlayerDeselected(%this)
-{
-   disableLinkData(NavEditorOptionsWindow-->TestProperties);
-}
-
 function NavEditorGui::createCoverPoints(%this)
 {
    if(isObject(%this.getMesh()))
@@ -718,11 +813,6 @@ function ENavEditorPaletteButton::onClick(%this)
 
 //-----------------------------------------------------------------------------
 
-function NavMeshTestFlagButton::onClick(%this)
-{
-   NavEditorGui.updateTestFlags();
-}
-
 singleton GuiControlProfile(NavEditorProfile)
 {
    canKeyFocus = true;