Sfoglia il codice sorgente

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 1 mese fa
parent
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;