Bladeren bron

Added navmesh wrapper code and module.

Daniel Buckmaster 12 jaren geleden
bovenliggende
commit
df2abed2c4

+ 244 - 0
Engine/source/navigation/duDebugDrawTorque.cpp

@@ -0,0 +1,244 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 "torqueRecast.h"
+#include "duDebugDrawTorque.h"
+
+#include "gfx/gfxDevice.h"
+#include "gfx/primBuilder.h"
+#include "gfx/gfxStateBlock.h"
+
+/// @class duDebugDrawTorque
+/// This class uses the primitive builder (gfx/primBuild.h) to render navmeshes
+/// and other Recast data. To facilitate the primbuilder's requirement to know
+/// the number of vertices to render beforehand, this class stores all vertices
+/// in a buffer of its own, then passes on that known-size buffer.
+/// This means that you only need to call the duDebugDraw functions when your
+/// data changes. At other times, you can cache the duDebugDrawTorque object
+/// and call its render() method, which actually renders its buffered data.
+
+duDebugDrawTorque::duDebugDrawTorque()
+{
+   mOverrideColor = 0;
+   mOverride = false;
+   mGroup = 0;
+}
+
+duDebugDrawTorque::~duDebugDrawTorque()
+{
+   clear();
+}
+
+void duDebugDrawTorque::depthMask(bool state)
+{
+   mDesc.setZReadWrite(state, state);
+}
+
+void duDebugDrawTorque::texture(bool state)
+{
+}
+
+/// Begin drawing primitives.
+/// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives.
+/// @param size [in] size of a primitive, applies to point size and line width only.
+void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size)
+{
+   mCurrColor = -1;
+   mQuadsMode = false;
+   mVertCount = 0;
+   mPrimType = 0;
+   switch(prim)
+   {
+   case DU_DRAW_POINTS: mPrimType = GFXPointList;    break;
+   case DU_DRAW_LINES:  mPrimType = GFXLineList;     break;
+   case DU_DRAW_TRIS:   mPrimType = GFXTriangleList; break;
+   case DU_DRAW_QUADS:  mPrimType = GFXTriangleList;
+                        mQuadsMode = true;           break;
+   }
+   mBuffers.push_back(Buffer(mPrimType));
+   mBuffers.last().group = mGroup;
+   mDesc.setCullMode(GFXCullNone);
+   mDesc.setBlend(true);
+}
+
+void duDebugDrawTorque::beginGroup(U32 group)
+{
+   mGroup = group;
+}
+
+/// Submit a vertex
+/// @param pos [in] position of the verts.
+/// @param color [in] color of the verts.
+void duDebugDrawTorque::vertex(const float* pos, unsigned int color)
+{
+   vertex(pos[0], pos[1], pos[2], color);
+}
+
+/// Submit a vertex
+/// @param x,y,z [in] position of the verts.
+/// @param color [in] color of the verts.
+void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsigned int color)
+{
+   if(mQuadsMode)
+   {
+      if(mVertCount == 3)
+      {
+         _vertex(x, -z, y, color);
+         _vertex(mStore[0][0], mStore[0][1], mStore[0][2], color);
+         _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color);
+         _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color);
+         _vertex(mStore[2][0], mStore[2][1], mStore[2][2], color);
+         _vertex(x, -z, y, color);
+         mVertCount = 0;
+      }
+      else
+      {
+         mStore[mVertCount][0] = x;
+         mStore[mVertCount][1] = -z;
+         mStore[mVertCount][2] = y;
+         mVertCount++;
+      }
+   }
+   else 
+   {
+      _vertex(x, -z, y, color);
+   }
+}
+
+/// Submit a vertex
+/// @param pos [in] position of the verts.
+/// @param color [in] color of the verts.
+void duDebugDrawTorque::vertex(const float* pos, unsigned int color, const float* uv)
+{
+   vertex(pos[0], pos[1], pos[2], color);
+}
+
+/// Submit a vertex
+/// @param x,y,z [in] position of the verts.
+/// @param color [in] color of the verts.
+void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v)
+{
+   vertex(x, y, z, color);
+}
+
+/// Push a vertex onto the buffer.
+void duDebugDrawTorque::_vertex(const float x, const float y, const float z, unsigned int color)
+{
+   // Use override color if we must.
+   //if(mOverride)
+      //color = mOverrideColor;
+   if(mCurrColor != color || !mBuffers.last().buffer.size())
+   {
+      U8 r, g, b, a;
+      // Convert color integer to components.
+      rcCol(color, r, g, b, a);
+      mBuffers.last().buffer.push_back(Instruction(r, g, b, a));
+      mCurrColor = color;
+   }
+   // Construct vertex data.
+   mBuffers.last().buffer.push_back(Instruction(x, y, z));
+}
+
+/// End drawing primitives.
+void duDebugDrawTorque::end()
+{
+}
+
+void duDebugDrawTorque::overrideColor(unsigned int col)
+{
+   mOverride = true;
+   mOverrideColor = col;
+}
+
+void duDebugDrawTorque::cancelOverride()
+{
+   mOverride = false;
+}
+
+void duDebugDrawTorque::renderBuffer(Buffer &b)
+{
+   PrimBuild::begin(b.primType, b.buffer.size());
+   Vector<Instruction> &buf = b.buffer;
+   for(U32 i = 0; i < buf.size(); i++)
+   {
+      switch(buf[i].type)
+      {
+      case Instruction::POINT:
+         PrimBuild::vertex3f(buf[i].data.point.x,
+                             buf[i].data.point.y,
+                             buf[i].data.point.z);
+         break;
+
+      case Instruction::COLOR:
+         if(mOverride)
+            break;
+         PrimBuild::color4i(buf[i].data.color.r,
+                            buf[i].data.color.g,
+                            buf[i].data.color.b,
+                            buf[i].data.color.a);
+         break;
+      }
+   }
+   PrimBuild::end();
+}
+
+void duDebugDrawTorque::render()
+{
+   GFXStateBlockRef sb = GFX->createStateBlock(mDesc);
+   GFX->setStateBlock(sb);
+   // Use override color for all rendering.
+   if(mOverride)
+   {
+      U8 r, g, b, a;
+      rcCol(mOverrideColor, r, g, b, a);
+      PrimBuild::color4i(r, g, b, a);
+   }
+   for(U32 b = 0; b < mBuffers.size(); b++)
+   {
+      renderBuffer(mBuffers[b]);
+   }
+}
+
+void duDebugDrawTorque::renderGroup(U32 group)
+{
+   GFXStateBlockRef sb = GFX->createStateBlock(mDesc);
+   GFX->setStateBlock(sb);
+   // Use override color for all rendering.
+   if(mOverride)
+   {
+      U8 r, g, b, a;
+      rcCol(mOverrideColor, r, g, b, a);
+      PrimBuild::color4i(r, g, b, a);
+   }
+   for(U32 b = 0; b < mBuffers.size(); b++)
+   {
+      if(mBuffers[b].group == group)
+         renderBuffer(mBuffers[b]);
+   }
+}
+
+void duDebugDrawTorque::clear()
+{
+   for(U32 b = 0; b < mBuffers.size(); b++)
+      mBuffers[b].buffer.clear();
+   mBuffers.clear();
+}

+ 157 - 0
Engine/source/navigation/duDebugDrawTorque.h

@@ -0,0 +1,157 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 _DU_DEBUG_DRAW_TORQUE_H_
+#define _DU_DEBUG_DRAW_TORQUE_H_
+
+#include "core/util/tVector.h"
+#include <DebugDraw.h>
+#include "gfx/gfxStateBlock.h"
+
+/// @brief Implements the duDebugDraw interface in Torque.
+class duDebugDrawTorque: public duDebugDraw {
+public:
+   duDebugDrawTorque();
+   ~duDebugDrawTorque();
+
+   /// Enable/disable Z read.
+   void depthMask(bool state);
+
+   /// Enable/disable texturing. Not used.
+   void texture(bool state);
+
+   /// Special colour overwrite for when I get picky about the colours Mikko chose.
+   void overrideColor(unsigned int col);
+
+   /// Stop the colour override.
+   void cancelOverride();
+
+   /// Begin drawing primitives.
+   /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives.
+   /// @param size [in] size of a primitive, applies to point size and line width only.
+   void begin(duDebugDrawPrimitives prim, float size = 1.0f);
+
+   /// All new buffers go into this group.
+   void beginGroup(U32 group);
+
+   /// Submit a vertex
+   /// @param pos [in] position of the verts.
+   /// @param color [in] color of the verts.
+   void vertex(const float* pos, unsigned int color);
+
+   /// Submit a vertex
+   /// @param x,y,z [in] position of the verts.
+   /// @param color [in] color of the verts.
+   void vertex(const float x, const float y, const float z, unsigned int color);
+
+   /// Submit a vertex
+   /// @param pos [in] position of the verts.
+   /// @param color [in] color of the verts.
+   void vertex(const float* pos, unsigned int color, const float* uv);
+
+   /// Submit a vertex
+   /// @param x,y,z [in] position of the verts.
+   /// @param color [in] color of the verts.
+   void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v);
+
+   /// End drawing primitives.
+   void end();
+
+   /// Render buffered primitive.
+   void render();
+
+   /// Render buffered primitives in a group.
+   void renderGroup(U32 group);
+
+   /// Delete buffered primitive.
+   void clear();
+      
+private:
+   GFXStateBlockDesc mDesc;
+
+   U32 mPrimType;
+   bool mQuadsMode;
+
+   U32 mVertCount;
+   F32 mStore[3][3];
+
+   U32 mGroup;
+
+   struct Instruction {
+      // Contain either a point or a color command.
+      union {
+         struct {
+            U8 r, g, b, a;
+         } color;
+         struct {
+            float x, y, z;
+         } point;
+         U32 primType;
+      } data;
+      // Which type of data do we store?
+      enum {
+         COLOR,
+         POINT,
+         PRIMTYPE,
+      } type;
+      // Construct as color instruction.
+      Instruction(U8 r, U8 g, U8 b, U8 a) {
+         type = COLOR;
+         data.color.r = r;
+         data.color.g = g;
+         data.color.b = b;
+         data.color.a = a;
+      }
+      // Construct as point.
+      Instruction(float x, float y, float z) {
+         type = POINT;
+         data.point.x = x;
+         data.point.y = y;
+         data.point.z = z;
+      }
+      Instruction(U32 t = 0) {
+         type = PRIMTYPE;
+         data.primType = t;
+      }
+   };
+
+   struct Buffer {
+      U32 group;
+      Vector<Instruction> buffer;
+      GFXPrimitiveType primType;
+      Buffer(U32 type = 0) {
+         primType = (GFXPrimitiveType)type;
+         group = 0;
+      }
+   };
+   Vector<Buffer> mBuffers;
+
+   U32 mCurrColor;
+   U32 mOverrideColor;
+   bool mOverride;
+
+   void _vertex(const float x, const float y, const float z, unsigned int color);
+
+   void renderBuffer(Buffer &b);
+};
+
+#endif

+ 939 - 0
Engine/source/navigation/navMesh.cpp

@@ -0,0 +1,939 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 <stdio.h>
+
+#include "navMesh.h"
+#include <DetourDebugDraw.h>
+#include <RecastDebugDraw.h>
+
+#include "math/mathUtils.h"
+#include "math/mRandom.h"
+#include "console/consoleTypes.h"
+#include "console/engineAPI.h"
+#include "console/typeValidators.h"
+
+#include "scene/sceneRenderState.h"
+#include "gfx/gfxDrawUtil.h"
+#include "renderInstance/renderPassManager.h"
+#include "gfx/primBuilder.h"
+
+#include "core/stream/bitStream.h"
+#include "math/mathIO.h"
+
+extern bool gEditingMission;
+
+IMPLEMENT_CO_NETOBJECT_V1(NavMesh);
+
+const U32 NavMesh::mMaxVertsPerPoly = 3;
+
+NavMesh::NavMesh()
+{
+   mTypeMask |= StaticShapeObjectType | MarkerObjectType;
+   mFileName = StringTable->insert("");
+   mNetFlags.clear(Ghostable);
+
+   nm = NULL;
+
+   dMemset(&cfg, 0, sizeof(cfg));
+   mCellSize = mCellHeight = 0.2f;
+   mWalkableHeight = 2.0f;
+   mWalkableClimb = 0.3f;
+   mWalkableRadius = 0.5f;
+   mWalkableSlope = 40.0f;
+   mBorderSize = 1;
+   mDetailSampleDist = 6.0f;
+   mDetailSampleMaxError = 1.0f;
+   mMaxEdgeLen = 12;
+   mMaxSimplificationError = 1.3f;
+   mMinRegionArea = 8;
+   mMergeRegionArea = 20;
+   mTileSize = 10.0f;
+   mMaxPolysPerTile = 128;
+
+   mAlwaysRender = false;
+
+   mBuilding = false;
+}
+
+NavMesh::~NavMesh()
+{
+   dtFreeNavMesh(nm);
+   nm = NULL;
+}
+
+bool NavMesh::setProtectedDetailSampleDist(void *obj, const char *index, const char *data)
+{
+   F32 dist = dAtof(data);
+   if(dist == 0.0f || dist >= 0.9f)
+      return true;
+   Con::errorf("NavMesh::detailSampleDist must be 0 or greater than 0.9!");
+   return false;
+}
+
+bool NavMesh::setProtectedAlwaysRender(void *obj, const char *index, const char *data)
+{
+   NavMesh *mesh = static_cast<NavMesh*>(obj);
+   bool always = dAtob(data);
+   if(always)
+   {
+      if(!gEditingMission)
+         mesh->mNetFlags.set(Ghostable);
+   }
+   else
+   {
+      if(!gEditingMission)
+         mesh->mNetFlags.clear(Ghostable);
+   }
+   mesh->mAlwaysRender = always;
+   mesh->setMaskBits(LoadFlag);
+   return true;
+}
+
+FRangeValidator ValidCellSize(0.01f, 10.0f);
+FRangeValidator ValidSlopeAngle(0.0f, 89.9f);
+IRangeValidator PositiveInt(0, S32_MAX);
+IRangeValidator NaturalNumber(1, S32_MAX);
+FRangeValidator CornerAngle(0.0f, 90.0f);
+
+void NavMesh::initPersistFields()
+{
+   addGroup("NavMesh Options");
+
+   addField("fileName", TypeString, Offset(mFileName, NavMesh),
+      "Name of the data file to store this navmesh in (relative to engine executable).");
+
+   addFieldV("cellSize", TypeF32, Offset(mCellSize, NavMesh), &ValidCellSize,
+      "Length/width of a voxel.");
+   addFieldV("cellHeight", TypeF32, Offset(mCellHeight, NavMesh), &ValidCellSize,
+      "Height of a voxel.");
+   addFieldV("tileSize", TypeF32, Offset(mTileSize, NavMesh), &CommonValidators::PositiveNonZeroFloat,
+      "The horizontal size of tiles.");
+
+   addFieldV("actorHeight", TypeF32, Offset(mWalkableHeight, NavMesh), &CommonValidators::PositiveFloat,
+      "Height of an actor.");
+   addFieldV("actorClimb", TypeF32, Offset(mWalkableClimb, NavMesh), &CommonValidators::PositiveFloat,
+      "Maximum climbing height of an actor.");
+   addFieldV("actorRadius", TypeF32, Offset(mWalkableRadius, NavMesh), &CommonValidators::PositiveFloat,
+      "Radius of an actor.");
+   addFieldV("walkableSlope", TypeF32, Offset(mWalkableSlope, NavMesh), &ValidSlopeAngle,
+      "Maximum walkable slope in degrees.");
+
+   endGroup("NavMesh Options");
+
+   addGroup("NavMesh Rendering");
+
+   addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh),
+      &setProtectedAlwaysRender, &defaultProtectedGetFn,
+      "Display this NavMesh even outside the editor.");
+
+   endGroup("NavMesh Rendering");
+
+   addGroup("NavMesh Advanced Options");
+
+   addFieldV("borderSize", TypeS32, Offset(mBorderSize, NavMesh), &PositiveInt,
+      "Size of the non-walkable border around the navigation mesh (in voxels).");
+   addProtectedField("detailSampleDist", TypeF32, Offset(mDetailSampleDist, NavMesh),
+      &setProtectedDetailSampleDist, &defaultProtectedGetFn,
+      "Sets the sampling distance to use when generating the detail mesh.");
+   addFieldV("detailSampleError", TypeF32, Offset(mDetailSampleMaxError, NavMesh), &CommonValidators::PositiveFloat,
+      "The maximum distance the detail mesh surface should deviate from heightfield data.");
+   addFieldV("maxEdgeLen", TypeS32, Offset(mDetailSampleDist, NavMesh), &PositiveInt,
+      "The maximum allowed length for contour edges along the border of the mesh.");
+   addFieldV("simplificationError", TypeF32, Offset(mMaxSimplificationError, NavMesh), &CommonValidators::PositiveFloat,
+      "The maximum distance a simplfied contour's border edges should deviate from the original raw contour.");
+   addFieldV("minRegionArea", TypeS32, Offset(mMinRegionArea, NavMesh), &PositiveInt,
+      "The minimum number of cells allowed to form isolated island areas.");
+   addFieldV("mergeRegionArea", TypeS32, Offset(mMergeRegionArea, NavMesh), &PositiveInt,
+      "Any regions with a span count smaller than this value will, if possible, be merged with larger regions.");
+   addFieldV("maxPolysPerTile", TypeS32, Offset(mMaxPolysPerTile, NavMesh), &NaturalNumber,
+      "The maximum number of polygons allowed in a tile.");
+
+   endGroup("NavMesh Advanced Options");
+
+   Parent::initPersistFields();
+}
+
+bool NavMesh::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   mObjBox.set(Point3F(-10.0f, -10.0f, -1.0f),
+      Point3F( 10.0f,  10.0f,  1.0f));
+   resetWorldBox();
+
+   addToScene();
+
+   if(gEditingMission || mAlwaysRender)
+   {
+      mNetFlags.set(Ghostable);
+      if(isClientObject())
+         renderToDrawer();
+   }
+
+   if(isServerObject())
+   {
+      setProcessTick(true);
+   }
+
+   load();
+
+   return true;
+}
+
+void NavMesh::onRemove()
+{
+   removeFromScene();
+
+   Parent::onRemove();
+}
+
+void NavMesh::setTransform(const MatrixF &mat)
+{
+   Parent::setTransform(mat);
+}
+
+void NavMesh::setScale(const VectorF &scale)
+{
+   Parent::setScale(scale);
+}
+
+bool NavMesh::build(bool background, bool saveIntermediates)
+{
+   if(mBuilding)
+      cancelBuild();
+
+   mBuilding = true;
+
+   dtFreeNavMesh(nm);
+   // Allocate a new navmesh.
+   nm = dtAllocNavMesh();
+   if(!nm)
+   {
+      Con::errorf("Could not allocate dtNavMesh for NavMesh %s", getIdString());
+      return false;
+   }
+
+   updateConfig();
+
+   // Build navmesh parameters from console members.
+   dtNavMeshParams params;
+   rcVcopy(params.orig, cfg.bmin);
+   params.tileWidth = cfg.tileSize * mCellSize;
+   params.tileHeight = cfg.tileSize * mCellSize;
+   params.maxTiles = mCeil(getWorldBox().len_x() / params.tileWidth) * mCeil(getWorldBox().len_y() / params.tileHeight);
+   params.maxPolys = mMaxPolysPerTile;
+
+   // Initialise our navmesh.
+   if(dtStatusFailed(nm->init(&params)))
+   {
+      Con::errorf("Could not init dtNavMesh for NavMesh %s", getIdString());
+      return false;
+   }
+
+   updateTiles(true);
+
+   if(!background)
+   {
+      while(mDirtyTiles.size())
+         buildNextTile();
+   }
+
+   return true;
+}
+
+DefineEngineMethod(NavMesh, build, bool, (bool background, bool save), (true, false),
+   "@brief Create a Recast nav mesh.")
+{
+   return object->build(background, save);
+}
+
+void NavMesh::cancelBuild()
+{
+   while(mDirtyTiles.size()) mDirtyTiles.pop();
+   mBuilding = false;
+}
+
+DefineEngineMethod(NavMesh, cancelBuild, void, (),,
+   "@brief Cancel the current NavMesh build.")
+{
+   object->cancelBuild();
+}
+
+void NavMesh::inspectPostApply()
+{
+   if(mBuilding)
+      cancelBuild();
+}
+
+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(Point3F pos)
+{
+   if(mBuilding)
+      return -1;
+   for(U32 i = 0; i < mTiles.size(); i++)
+   {
+      if(mTiles[i].box.isContained(pos))
+         return i;
+   }
+   return -1;
+}
+
+Box3F NavMesh::getTileBox(U32 id)
+{
+   if(mBuilding || id >= mTiles.size())
+      return Box3F::Invalid;
+   return mTiles[id].box;
+}
+
+void NavMesh::updateTiles(bool dirty)
+{
+   if(!isProperlyAdded())
+      return;
+
+   mTiles.clear();
+   while(mDirtyTiles.size()) mDirtyTiles.pop();
+
+   const Box3F &box = DTStoRC(getWorldBox());
+   if(box.isEmpty())
+      return;
+
+   updateConfig();
+
+   // Calculate tile dimensions.
+   const U32 ts = cfg.tileSize;
+   const U32 tw = (cfg.width  + ts-1) / ts;
+   const U32 th = (cfg.height + ts-1) / ts;
+   const F32 tcs = cfg.tileSize * cfg.cs;
+
+   // Iterate over tiles.
+   F32 tileBmin[3], tileBmax[3];
+   for(U32 y = 0; y < th; ++y)
+   {
+      for(U32 x = 0; x < tw; ++x)
+      {
+         tileBmin[0] = cfg.bmin[0] + x*tcs;
+         tileBmin[1] = cfg.bmin[1];
+         tileBmin[2] = cfg.bmin[2] + y*tcs;
+
+         tileBmax[0] = cfg.bmin[0] + (x+1)*tcs;
+         tileBmax[1] = cfg.bmax[1];
+         tileBmax[2] = cfg.bmin[2] + (y+1)*tcs;
+
+         mTiles.push_back(
+            Tile(RCtoDTS(tileBmin, tileBmax),
+                  x, y,
+                  tileBmin, tileBmax));
+
+         if(dirty)
+            mDirtyTiles.push(mTiles.size() - 1);
+      }
+   }
+}
+
+void NavMesh::processTick(const Move *move)
+{
+   buildNextTile();
+}
+
+void NavMesh::buildNextTile()
+{
+   if(mDirtyTiles.size())
+   {
+      // Pop a single dirty tile and process it.
+      U32 i = mDirtyTiles.front();
+      mDirtyTiles.pop();
+      const Tile &tile = mTiles[i];
+      // Intermediate data for tile build.
+      TileData tempdata;
+      // Generate navmesh for this tile.
+      U32 dataSize = 0;
+      unsigned char* data = buildTileData(tile, tempdata, dataSize);
+      if(data)
+      {
+         // Remove any previous data.
+         nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0);
+         // Add new data (navmesh owns and deletes the data).
+         dtStatus status = nm->addTile(data, dataSize, DT_TILE_FREE_DATA, 0, 0);
+         int success = 1;
+         if(dtStatusFailed(status))
+         {
+            success = 0;
+            dtFree(data);
+         }
+      }
+      // Did we just build the last tile?
+      if(!mDirtyTiles.size())
+      {
+         mBuilding = false;
+      }
+      setMaskBits(BuildFlag);
+   }
+}
+
+static void buildCallback(SceneObject* object,void *key)
+{
+   SceneContainer::CallbackInfo* info = reinterpret_cast<SceneContainer::CallbackInfo*>(key);
+   object->buildPolyList(info->context,info->polyList,info->boundingBox,info->boundingSphere);
+}
+
+unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dataSize)
+{
+   // Push out tile boundaries a bit.
+   F32 tileBmin[3], tileBmax[3];
+   rcVcopy(tileBmin, tile.bmin);
+   rcVcopy(tileBmax, tile.bmax);
+   tileBmin[0] -= cfg.borderSize * cfg.cs;
+   tileBmin[2] -= cfg.borderSize * cfg.cs;
+   tileBmax[0] += cfg.borderSize * cfg.cs;
+   tileBmax[2] += cfg.borderSize * cfg.cs;
+
+   // Parse objects from level into RC-compatible format.
+   Box3F box = RCtoDTS(tileBmin, tileBmax);
+   SceneContainer::CallbackInfo info;
+   info.context = PLC_Navigation;
+   info.boundingBox = box;
+   info.polyList = &data.geom;
+   getContainer()->findObjects(box, StaticObjectType, buildCallback, &info);
+
+   // Check for no geometry.
+   if(!data.geom.getVertCount())
+      return false;
+
+   // Figure out voxel dimensions of this tile.
+   U32 width = 0, height = 0;
+   width = cfg.tileSize + cfg.borderSize * 2;
+   height = cfg.tileSize + cfg.borderSize * 2;
+
+   // Create a dummy context.
+   rcContext ctx(false);
+
+   // Create a heightfield to voxelise our input geometry.
+   data.hf = rcAllocHeightfield();
+   if(!data.hf)
+   {
+      Con::errorf("Out of memory (rcHeightField) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcCreateHeightfield(&ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch))
+   {
+      Con::errorf("Could not generate rcHeightField for NavMesh %s", getIdString());
+      return NULL;
+   }
+
+   unsigned char *areas = new unsigned char[data.geom.getTriCount()];
+   if(!areas)
+   {
+      Con::errorf("Out of memory (area flags) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   dMemset(areas, 0, data.geom.getTriCount() * sizeof(unsigned char));
+
+   // Filter triangles by angle and rasterize.
+   rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle,
+      data.geom.getVerts(), data.geom.getVertCount(),
+      data.geom.getTris(), data.geom.getTriCount(), areas);
+   rcRasterizeTriangles(&ctx, data.geom.getVerts(), data.geom.getVertCount(),
+      data.geom.getTris(), areas, data.geom.getTriCount(),
+      *data.hf, cfg.walkableClimb);
+
+   delete[] areas;
+
+   // Filter out areas with low ceilings and other stuff.
+   rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *data.hf);
+   rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf);
+   rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *data.hf);
+
+   data.chf = rcAllocCompactHeightfield();
+   if(!data.chf)
+   {
+      Con::errorf("Out of memory (rcCompactHeightField) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf))
+   {
+      Con::errorf("Could not generate rcCompactHeightField for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *data.chf))
+   {
+      Con::errorf("Could not erode walkable area for NavMesh %s", getIdString());
+      return NULL;
+   }
+
+   //--------------------------
+   // Todo: mark areas here.
+   //const ConvexVolume* vols = m_geom->getConvexVolumes();
+   //for (int i  = 0; i < m_geom->getConvexVolumeCount(); ++i)
+      //rcMarkConvexPolyArea(m_NULL, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
+   //--------------------------
+
+   if(false)
+   {
+      if(!rcBuildRegionsMonotone(&ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
+      {
+         Con::errorf("Could not build regions for NavMesh %s", getIdString());
+         return NULL;
+      }
+   }
+   else
+   {
+      if(!rcBuildDistanceField(&ctx, *data.chf))
+      {
+         Con::errorf("Could not build distance field for NavMesh %s", getIdString());
+         return NULL;
+      }
+      if(!rcBuildRegions(&ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
+      {
+         Con::errorf("Could not build regions for NavMesh %s", getIdString());
+         return NULL;
+      }
+   }
+
+   data.cs = rcAllocContourSet();
+   if(!data.cs)
+   {
+      Con::errorf("Out of memory (rcContourSet) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcBuildContours(&ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs))
+   {
+      Con::errorf("Could not construct rcContourSet for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(data.cs->nconts <= 0)
+   {
+      Con::errorf("No contours in rcContourSet for NavMesh %s", getIdString());
+      return NULL;
+   }
+
+   data.pm = rcAllocPolyMesh();
+   if(!data.pm)
+   {
+      Con::errorf("Out of memory (rcPolyMesh) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcBuildPolyMesh(&ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm))
+   {
+      Con::errorf("Could not construct rcPolyMesh for NavMesh %s", getIdString());
+      return NULL;
+   }
+
+   data.pmd = rcAllocPolyMeshDetail();
+   if(!data.pmd)
+   {
+      Con::errorf("Out of memory (rcPolyMeshDetail) for NavMesh %s", getIdString());
+      return NULL;
+   }
+   if(!rcBuildPolyMeshDetail(&ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd))
+   {
+      Con::errorf("Could not construct rcPolyMeshDetail for NavMesh %s", getIdString());
+      return NULL;
+   }
+
+   if(data.pm->nverts >= 0xffff)
+   {
+      Con::errorf("Too many vertices in rcPolyMesh for NavMesh %s", getIdString());
+      return NULL;
+   }
+   for(U32 i = 0; i < data.pm->npolys; i++)
+   {
+      if(data.pm->areas[i] == RC_WALKABLE_AREA)
+         data.pm->areas[i] = GroundArea;
+
+      if(data.pm->areas[i] == GroundArea)
+         data.pm->flags[i] |= WalkFlag;
+      if(data.pm->areas[i] == WaterArea)
+         data.pm->flags[i] |= SwimFlag;
+   }
+
+   unsigned char* navData = 0;
+   int navDataSize = 0;
+
+   dtNavMeshCreateParams params;
+   dMemset(&params, 0, sizeof(params));
+
+   params.verts = data.pm->verts;
+   params.vertCount = data.pm->nverts;
+   params.polys = data.pm->polys;
+   params.polyAreas = data.pm->areas;
+   params.polyFlags = data.pm->flags;
+   params.polyCount = data.pm->npolys;
+   params.nvp = data.pm->nvp;
+
+   params.detailMeshes = data.pmd->meshes;
+   params.detailVerts = data.pmd->verts;
+   params.detailVertsCount = data.pmd->nverts;
+   params.detailTris = data.pmd->tris;
+   params.detailTriCount = data.pmd->ntris;
+
+   params.walkableHeight = mWalkableHeight;
+   params.walkableRadius = mWalkableRadius;
+   params.walkableClimb = mWalkableClimb;
+   params.tileX = tile.x;
+   params.tileY = tile.y;
+   params.tileLayer = 0;
+   rcVcopy(params.bmin, data.pm->bmin);
+   rcVcopy(params.bmax, data.pm->bmax);
+   params.cs = cfg.cs;
+   params.ch = cfg.ch;
+   params.buildBvTree = true;
+
+   if(!dtCreateNavMeshData(&params, &navData, &navDataSize))
+   {
+      Con::errorf("Could not create dtNavMeshData for tile (%d, %d) of NavMesh %s",
+         tile.x, tile.y, getIdString());
+      return NULL;
+   }
+
+   dataSize = navDataSize;
+
+   return navData;
+}
+
+/// This method should never be called in a separate thread to the rendering
+/// or pathfinding logic. It directly replaces data in the dtNavMesh for
+/// this NavMesh object.
+void NavMesh::buildTiles(const Box3F &box)
+{
+   // Make sure we've already built or loaded.
+   if(!nm)
+      return;
+   // Iterate over tiles.
+   for(U32 i = 0; i < mTiles.size(); i++)
+   {
+      const Tile &tile = mTiles[i];
+      // Check tile box.
+      if(!tile.box.isOverlapped(box))
+         continue;
+      // Mark as dirty.
+      mDirtyTiles.push(i);
+   }
+}
+
+DefineEngineMethod(NavMesh, buildTiles, void, (Box3F box),,
+   "@brief Rebuild the tiles overlapped by the input box.")
+{
+   return object->buildTiles(box);
+}
+
+void NavMesh::buildTile(const U32 &tile)
+{
+   if(tile < mTiles.size())
+   {
+      mDirtyTiles.push(tile);
+   }
+}
+
+void NavMesh::renderToDrawer()
+{
+   dd.clear();
+   // Recast debug draw
+   NetObject *no = getServerObject();
+   if(no)
+   {
+      NavMesh *n = static_cast<NavMesh*>(no);
+
+      if(n->nm)
+      {
+         dd.beginGroup(0);
+         duDebugDrawNavMesh       (&dd, *n->nm, 0);
+         dd.beginGroup(1);
+         duDebugDrawNavMeshPortals(&dd, *n->nm);
+         dd.beginGroup(2);
+         duDebugDrawNavMeshBVTree (&dd, *n->nm);
+      }
+   }
+}
+
+void NavMesh::prepRenderImage(SceneRenderState *state)
+{
+   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+   ri->renderDelegate.bind(this, &NavMesh::render);
+   ri->type = RenderPassManager::RIT_Object;
+   ri->translucentSort = true;
+   ri->defaultKey = 1;
+   state->getRenderPass()->addInst(ri);
+}
+
+void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat)
+{
+   if(overrideMat)
+      return;
+
+   if(state->isReflectPass())
+      return;
+
+   PROFILE_SCOPE(NavMesh_Render);
+   
+   // Recast debug draw
+   NetObject *no = getServerObject();
+   if(no)
+   {
+      NavMesh *n = static_cast<NavMesh*>(no);
+
+      if(n->isSelected())
+      {
+         GFXDrawUtil *drawer = GFX->getDrawUtil();
+
+         GFXStateBlockDesc desc;
+         desc.setZReadWrite(true, false);
+         desc.setBlend(true);
+         desc.setCullMode(GFXCullNone);
+
+         drawer->drawCube(desc, getWorldBox(), n->mBuilding
+            ? ColorI(255, 0, 0, 80)
+            : ColorI(136, 228, 255, 45));
+         desc.setFillModeWireframe();
+         drawer->drawCube(desc, getWorldBox(), ColorI::BLACK);
+      }
+
+      if(n->mBuilding)
+      {
+         int alpha = 80;
+         if(!n->isSelected() || !Con::getBoolVariable("$Nav::EditorOpen"))
+            alpha = 20;
+         dd.overrideColor(duRGBA(255, 0, 0, alpha));
+      }
+      else
+      {
+         dd.cancelOverride();
+      }
+      
+      if((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) dd.renderGroup(0);
+      if(Con::getBoolVariable("$Nav::Editor::renderPortals")) dd.renderGroup(1);
+      if(Con::getBoolVariable("$Nav::Editor::renderBVTree"))  dd.renderGroup(2);
+   }
+}
+
+void NavMesh::onEditorEnable()
+{
+   mNetFlags.set(Ghostable);
+   if(isClientObject() && !mAlwaysRender)
+      addToScene();
+}
+
+void NavMesh::onEditorDisable()
+{
+   if(!mAlwaysRender)
+   {
+      mNetFlags.clear(Ghostable);
+      if(isClientObject())
+         removeFromScene();
+   }
+}
+
+U32 NavMesh::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+   mathWrite(*stream, getTransform());
+   mathWrite(*stream, getScale());
+   stream->writeFlag(mAlwaysRender);
+
+   return retMask;
+}
+
+void NavMesh::unpackUpdate(NetConnection *conn, BitStream *stream)
+{
+   Parent::unpackUpdate(conn, stream);
+
+   mathRead(*stream, &mObjToWorld);
+   mathRead(*stream, &mObjScale);
+   mAlwaysRender = stream->readFlag();
+
+   setTransform(mObjToWorld);
+
+   renderToDrawer();
+}
+
+static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
+static const int NAVMESHSET_VERSION = 1;
+
+struct NavMeshSetHeader
+{
+   int magic;
+   int version;
+   int numTiles;
+   dtNavMeshParams params;
+};
+
+struct NavMeshTileHeader
+{
+   dtTileRef tileRef;
+   int dataSize;
+};
+
+bool NavMesh::load()
+{
+   if(!dStrlen(mFileName))
+      return false;
+
+   FILE* fp = fopen(mFileName, "rb");
+   if(!fp)
+      return false;
+
+   // Read header.
+   NavMeshSetHeader header;
+   fread(&header, sizeof(NavMeshSetHeader), 1, fp);
+   if(header.magic != NAVMESHSET_MAGIC)
+   {
+      fclose(fp);
+      return 0;
+   }
+   if(header.version != NAVMESHSET_VERSION)
+   {
+      fclose(fp);
+      return 0;
+   }
+
+   if(nm)
+      dtFreeNavMesh(nm);
+   nm = dtAllocNavMesh();
+   if(!nm)
+   {
+      fclose(fp);
+      return false;
+   }
+
+   dtStatus status = nm->init(&header.params);
+   if(dtStatusFailed(status))
+   {
+      fclose(fp);
+      return false;
+   }
+
+   // Read tiles.
+   for(U32 i = 0; i < header.numTiles; ++i)
+   {
+      NavMeshTileHeader tileHeader;
+      fread(&tileHeader, sizeof(tileHeader), 1, fp);
+      if(!tileHeader.tileRef || !tileHeader.dataSize)
+         break;
+
+      unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
+      if(!data) break;
+      memset(data, 0, tileHeader.dataSize);
+      fread(data, tileHeader.dataSize, 1, fp);
+
+      nm->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
+   }
+
+   fclose(fp);
+
+   updateTiles();
+
+   if(isServerObject())
+   {
+      setMaskBits(LoadFlag);
+   }
+
+   return true;
+}
+
+DefineEngineMethod(NavMesh, load, bool, (),,
+   "@brief Load this NavMesh from its file.")
+{
+   return object->load();
+}
+
+bool NavMesh::save()
+{
+   if(!dStrlen(mFileName) || !nm)
+      return false;
+
+   // Save our navmesh into a file to load from next time
+   FILE* fp = fopen(mFileName, "wb");
+   if(!fp)
+      return false;
+
+   // Store header.
+   NavMeshSetHeader header;
+   header.magic = NAVMESHSET_MAGIC;
+   header.version = NAVMESHSET_VERSION;
+   header.numTiles = 0;
+   for(U32 i = 0; i < nm->getMaxTiles(); ++i)
+   {
+      const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i);
+      if (!tile || !tile->header || !tile->dataSize) continue;
+      header.numTiles++;
+   }
+   memcpy(&header.params, nm->getParams(), sizeof(dtNavMeshParams));
+   fwrite(&header, sizeof(NavMeshSetHeader), 1, fp);
+
+   // Store tiles.
+   for(U32 i = 0; i < nm->getMaxTiles(); ++i)
+   {
+      const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i);
+      if(!tile || !tile->header || !tile->dataSize) continue;
+
+      NavMeshTileHeader tileHeader;
+      tileHeader.tileRef = nm->getTileRef(tile);
+      tileHeader.dataSize = tile->dataSize;
+      fwrite(&tileHeader, sizeof(tileHeader), 1, fp);
+
+      fwrite(tile->data, tile->dataSize, 1, fp);
+   }
+
+   fclose(fp);
+
+   return true;
+}
+
+DefineEngineMethod(NavMesh, save, void, (),,
+   "@brief Save this NavMesh to its file.")
+{
+   object->save();
+}
+
+void NavMesh::write(Stream &stream, U32 tabStop, U32 flags)
+{
+   save();
+   Parent::write(stream, tabStop, flags);
+}

+ 274 - 0
Engine/source/navigation/navMesh.h

@@ -0,0 +1,274 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 _NAVMESH_H_
+#define _NAVMESH_H_
+
+#include <queue>
+
+#include "torqueRecast.h"
+#include "scene/sceneObject.h"
+#include "recastPolyList.h"
+
+#include "duDebugDrawTorque.h"
+
+#include <Recast.h>
+#include <DetourNavMesh.h>
+#include <DetourNavMeshBuilder.h>
+#include <DebugDraw.h>
+
+/// @class NavMesh
+/// Represents a set of bounds within which a Recast navigation mesh is generated.
+/// @see NavMeshPolyList
+/// @see Trigger
+class NavMesh : public SceneObject {
+   typedef SceneObject Parent;
+   friend class NavPath;
+
+public:
+   /// @name NavMesh build
+   /// @{
+
+   /// Initiates the navmesh build process, which includes notifying the
+   /// clients and posting an event.
+   bool build(bool background = true, bool saveIntermediates = false);
+   /// Stop a build in progress.
+   void cancelBuild();
+
+   /// Save the navmesh to a file.
+   bool save();
+   /// Load a saved navmesh from a file.
+   bool load();
+
+   /// Instantly rebuild the tiles in the navmesh that overlap the box.
+   void buildTiles(const Box3F &box);
+
+   /// Instantly rebuild a specific tile.
+   void buildTile(const U32 &tile);
+
+   /// Data file to store this nav mesh in. (From engine executable dir.)
+   StringTableEntry mFileName;
+
+   /// Cell width and height.
+   F32 mCellSize, mCellHeight;
+   /// @name Actor data
+   /// @{
+   F32 mWalkableHeight,
+      mWalkableClimb,
+      mWalkableRadius,
+      mWalkableSlope;
+   /// @}
+   /// @name Generation data
+   /// @{
+   U32 mBorderSize;
+   F32 mDetailSampleDist, mDetailSampleMaxError;
+   U32 mMaxEdgeLen;
+   F32 mMaxSimplificationError;
+   static const U32 mMaxVertsPerPoly;
+   U32 mMinRegionArea;
+   U32 mMergeRegionArea;
+   F32 mTileSize;
+   U32 mMaxPolysPerTile;
+   /// @}
+
+   /// @}
+
+   /// Return the index of the tile included by this point.
+   S32 getTile(Point3F pos);
+
+   /// Return the box of a given tile.
+   Box3F getTileBox(U32 id);
+
+   /// @name SimObject
+   /// @{
+
+   virtual void onEditorEnable();
+   virtual void onEditorDisable();
+
+   void write(Stream &stream, U32 tabStop, U32 flags);
+
+   /// @}
+
+   /// @name SceneObject
+   /// @{
+
+   static void initPersistFields();
+
+   bool onAdd();
+   void onRemove();
+
+   enum flags {
+      BuildFlag    = Parent::NextFreeMask << 0,
+      LoadFlag     = Parent::NextFreeMask << 1,
+      NextFreeMask = Parent::NextFreeMask << 2,
+   };
+
+   U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+   void unpackUpdate(NetConnection *conn, BitStream *stream);
+
+   void setTransform(const MatrixF &mat);
+   void setScale(const VectorF &scale);
+
+   /// @}
+
+   /// @name ProcessObject
+   /// @{
+
+   void processTick(const Move *move);
+
+   /// @}
+
+   /// @name Rendering
+   /// @{
+
+   void prepRenderImage(SceneRenderState *state);
+   void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
+
+   bool mAlwaysRender;
+
+   /// @}
+
+   NavMesh();
+   ~NavMesh();
+   DECLARE_CONOBJECT(NavMesh);
+
+   void inspectPostApply();
+
+protected:
+
+   dtNavMesh const* getNavMesh() { return nm; }
+
+private:
+   /// Generates a navigation mesh for the collection of objects in this
+   /// mesh. Returns true if successful. Stores the created mesh in tnm.
+   bool generateMesh();
+
+   /// Builds the next tile in the dirty list.
+   void buildNextTile();
+
+   /// @name Tiles
+   /// @{
+
+   struct Tile {
+      /// Torque-space world box of this tile.
+      Box3F box;
+      /// Local coordinates of this box.
+      U32 x, y;
+      /// Recast min and max points.
+      F32 bmin[3], bmax[3];
+      /// Default constructor.
+      Tile() : box(Box3F::Invalid), x(0), y(0)
+      {
+         bmin[0] = bmin[1] = bmin[2] = bmax[0] = bmax[1] = bmax[2] = 0.0f;
+      }
+      /// Value constructor.
+      Tile(const Box3F &b, U32 _x, U32 _y, const F32 *min, const F32 *max)
+         : box(b), x(_x), y(_y)
+      {
+         rcVcopy(bmin, min);
+         rcVcopy(bmax, max);
+      }
+   };
+
+   /// Intermediate data for tile creation.
+   struct TileData {
+      RecastPolyList          geom;
+      rcHeightfield        *hf;
+      rcCompactHeightfield *chf;
+      rcContourSet         *cs;
+      rcPolyMesh           *pm;
+      rcPolyMeshDetail     *pmd;
+      TileData()
+      {
+         hf = NULL;
+         chf = NULL;
+         cs = NULL;
+         pm = NULL;
+         pmd = NULL;
+      }
+      void freeAll()
+      {
+         geom.clear();
+         rcFreeHeightField(hf);
+         rcFreeCompactHeightfield(chf);
+         rcFreeContourSet(cs);
+         rcFreePolyMesh(pm);
+         rcFreePolyMeshDetail(pmd);
+      }
+      ~TileData()
+      {
+         freeAll();
+      }
+   };
+
+   /// List of tiles.
+   Vector<Tile> mTiles;
+
+   /// List of indices to the tile array which are dirty.
+   std::queue<U32> mDirtyTiles;
+
+   /// Update tile dimensions.
+   void updateTiles(bool dirty = false);
+
+   /// Generates navmesh data for a single tile.
+   unsigned char *buildTileData(const Tile &tile, TileData &data, U32 &dataSize);
+
+   /// @}
+
+   /// @name Intermediate data
+   /// @{
+
+   /// Config struct.
+   rcConfig cfg;
+
+   /// Updates our config from console members.
+   void updateConfig();
+
+   dtNavMesh *nm;
+
+   /// @}
+
+   /// Used to perform non-standard validation. detailSampleDist can be 0, or >= 0.9.
+   static bool setProtectedDetailSampleDist(void *obj, const char *index, const char *data);
+
+   /// Updates the client when we check the alwaysRender option.
+   static bool setProtectedAlwaysRender(void *obj, const char *index, const char *data);
+
+   /// @name Threaded updates
+   /// @{
+
+   /// A simple flag to say we are building.
+   bool mBuilding;
+
+   /// @}
+
+   /// @name Rendering
+   /// @{
+
+   duDebugDrawTorque dd;
+
+   void renderToDrawer();
+
+   /// @}
+};
+
+#endif

+ 625 - 0
Engine/source/navigation/navPath.cpp

@@ -0,0 +1,625 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 "torqueRecast.h"
+#include "navPath.h"
+
+#include "console/consoleTypes.h"
+#include "console/engineAPI.h"
+#include "console/typeValidators.h"
+
+#include "scene/sceneRenderState.h"
+#include "gfx/gfxDrawUtil.h"
+#include "renderInstance/renderPassManager.h"
+#include "gfx/primBuilder.h"
+#include "core/stream/bitStream.h"
+#include "math/mathIO.h"
+
+#include <DetourDebugDraw.h>
+
+extern bool gEditingMission;
+
+IMPLEMENT_CO_NETOBJECT_V1(NavPath);
+
+NavPath::NavPath() :
+   mFrom(0.0f, 0.0f, 0.0f),
+   mTo(0.0f, 0.0f, 0.0f)
+{
+   mTypeMask |= MarkerObjectType;
+
+   mMesh = NULL;
+   mWaypoints = NULL;
+
+   mFrom.set(0, 0, 0);
+   mFromSet = false;
+   mTo.set(0, 0, 0);
+   mToSet = false;
+   mLength = 0.0f;
+
+   mIsLooping = false;
+
+   mAlwaysRender = false;
+   mXray = false;
+
+   mQuery = dtAllocNavMeshQuery();
+}
+
+NavPath::~NavPath()
+{
+   // Required for Detour.
+   dtFreeNavMeshQuery(mQuery);
+   mQuery = NULL;
+}
+
+bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data)
+{
+   NavMesh *mesh = NULL;
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(Sim::findObject(data, mesh))
+      object->mMesh = mesh;
+
+   return false;
+}
+
+const char *NavPath::getProtectedMesh(void *obj, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(object->mMesh.isNull())
+      return "";
+
+   if(object->mMesh->getName())
+      return object->mMesh->getName();
+   else
+      return object->mMesh->getIdString();
+}
+
+bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *data)
+{
+   SimPath::Path *points = NULL;
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(Sim::findObject(data, points))
+   {
+      object->mWaypoints = points;
+      object->mIsLooping = points->isLooping();
+   }
+   else
+      object->mWaypoints = NULL;
+
+   return false;
+}
+
+bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(dStrcmp(data, ""))
+   {
+      object->mFromSet = true;
+      return true;
+   }
+   else
+   {
+      object->mFromSet = false;
+      return false;
+   }
+}
+
+bool NavPath::setProtectedTo(void *obj, const char *index, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(dStrcmp(data, ""))
+   {
+      object->mToSet = true;
+      return true;
+   }
+   else
+   {
+      object->mToSet = false;
+      return false;
+   }
+}
+
+const char *NavPath::getProtectedFrom(void *obj, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(object->mFromSet)
+      return data;
+   else
+      return "";
+}
+
+const char *NavPath::getProtectedTo(void *obj, const char *data)
+{
+   NavPath *object = static_cast<NavPath*>(obj);
+
+   if(object->mToSet)
+      return data;
+   else
+      return "";
+}
+
+static IRangeValidator NaturalNumber(1, S32_MAX);
+
+void NavPath::initPersistFields()
+{
+   addGroup("NavPath");
+
+   addProtectedField("from", TypePoint3F, Offset(mFrom, NavPath),
+      &setProtectedFrom, &getProtectedFrom,
+      "World location this path starts at.");
+   addProtectedField("to", TypePoint3F, Offset(mTo, NavPath),
+      &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("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?");
+
+   endGroup("NavPath");
+
+   addGroup("NavPath Render");
+
+   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.");
+
+   endGroup("NavPath Render");
+
+   Parent::initPersistFields();
+}
+
+bool NavPath::onAdd()
+{
+   if(!Parent::onAdd())
+      return false;
+
+   // Ghost immediately if the editor's already open.
+   if(gEditingMission)
+      mNetFlags.set(Ghostable);
+
+   // Automatically find a path if we can.
+   if(isServerObject())
+      plan();
+
+   // Set initial world bounds and stuff.
+   resize();
+
+   // Finally, add us to the simulation.
+   addToScene();
+
+   return true;
+}
+
+void NavPath::onRemove()
+{
+   Parent::onRemove();
+
+   // Remove from simulation.
+   removeFromScene();
+}
+
+bool NavPath::init()
+{
+   // Check that enough data is provided.
+   if(mMesh.isNull() || !mMesh->getNavMesh())
+      return false;
+   if(!(mFromSet && mToSet) && !(!mWaypoints.isNull() && mWaypoints->size()))
+      return false;
+
+   // Initialise query in Detour.
+   if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
+      return false;
+
+   mPoints.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));
+         if(s)
+         {
+            mVisitPoints.push_back(s->getPosition());
+            // This is potentially slow, but safe.
+            if(!i && mIsLooping && !mFromSet)
+               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);
+      mVisitPoints.push_back(mFrom);
+   }
+
+   return true;
+}
+
+void NavPath::resize()
+{
+   if(!mPoints.size())
+   {
+      mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f),
+                  Point3F( 0.5f,  0.5f,  0.5f));
+      resetWorldBox();
+      setTransform(MatrixF(true));
+      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++)
+   {
+      Point3F p = mPoints[i];
+      max.x = getMax(max.x, p.x);
+      max.y = getMax(max.y, p.y);
+      max.z = getMax(max.z, p.z);
+      min.x = getMin(min.x, p.x);
+      min.y = getMin(min.y, p.y);
+      min.z = getMin(min.z, p.z);
+      pos += p;
+   }
+   pos /= mPoints.size();
+   min -= Point3F(0.5f, 0.5f, 0.5f);
+   max += Point3F(0.5f, 0.5f, 0.5f);
+
+   mObjBox.set(min - pos, max - pos);
+   MatrixF mat = Parent::getTransform();
+   mat.setPosition(pos);
+   Parent::setTransform(mat);
+}
+
+bool NavPath::plan()
+{
+   if(!init())
+      return false;
+
+   visitNext();
+   while(update());
+
+   if(!finalise())
+      return false;
+
+   resize();
+
+   return true;
+}
+
+bool NavPath::visitNext()
+{
+   U32 s = mVisitPoints.size();
+   if(s < 2)
+      return false;
+
+   // Current leg of journey.
+   Point3F start = mVisitPoints[s-1];
+   Point3F end = mVisitPoints[s-2];
+
+   // 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};
+   dtPolyRef startRef, endRef;
+
+   if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, start)))
+   {
+      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, end)))
+   {
+      Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
+         end.x, end.y, end.z, getIdString());
+      return false;
+   }
+
+   // Init sliced pathfind.
+   mStatus = mQuery->initSlicedFindPath(startRef, endRef, from, to, &mFilter);
+   if(dtStatusFailed(mStatus))
+      return false;
+
+   return true;
+}
+
+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.
+   if(dtStatusSucceed(mStatus))
+   {
+      // Finalize the path. Need to use the static path length cap again.
+      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];
+         U8 straightPathFlags[MaxPathLen];
+
+         U32 s = mVisitPoints.size();
+         Point3F start = mVisitPoints[s-1];
+         Point3F end = mVisitPoints[s-2];
+         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);
+         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.
+            if(s > 0 || i > 0)
+               mLength += (mPoints[s+i] - mPoints[s+i-1]).len();
+         }
+
+         if(isServerObject())
+            setMaskBits(PathMask);
+      }
+      else
+         return false;
+      // Check to see where we still need to visit.
+      if(mVisitPoints.size() > 1)
+      {
+         //Next leg of the journey.
+         mVisitPoints.pop_back();
+         return visitNext();
+      }
+      else
+      {
+         // Finished!
+         return false;
+      }
+   }
+   else if(dtStatusFailed(mStatus))
+   {
+      // Something went wrong in planning.
+      return false;
+   }
+   return true;
+}
+
+bool NavPath::finalise()
+{
+   // Stop ticking.
+   setProcessTick(false);
+
+   // Reset world bounds and stuff.
+   resize();
+
+   return dtStatusSucceed(mStatus);
+}
+
+void NavPath::processTick(const Move *move)
+{
+   if(dtStatusInProgress(mStatus))
+      update();
+}
+
+Point3F NavPath::getNode(S32 idx)
+{
+   if(idx < getCount() && 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()
+{
+   return mPoints.size();
+}
+
+void NavPath::onEditorEnable()
+{
+   mNetFlags.set(Ghostable);
+}
+
+void NavPath::onEditorDisable()
+{
+   mNetFlags.clear(Ghostable);
+}
+
+void NavPath::inspectPostApply()
+{
+   plan();
+}
+
+void NavPath::onDeleteNotify(SimObject *obj)
+{
+   if(obj == (SimObject*)mMesh)
+   {
+      mMesh = NULL;
+      plan();
+   }
+}
+
+void NavPath::prepRenderImage(SceneRenderState *state)
+{
+   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
+   ri->renderDelegate.bind(this, &NavPath::renderSimple);
+   ri->type = RenderPassManager::RIT_Editor;      
+   ri->translucentSort = true;
+   ri->defaultKey = 1;
+   state->getRenderPass()->addInst(ri);
+}
+
+void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat)
+{
+   if(overrideMat)
+      return;
+
+   if(state->isReflectPass() || !(isSelected() || mAlwaysRender))
+      return;
+
+   GFXDrawUtil *drawer = GFX->getDrawUtil();
+   GFXStateBlockDesc desc;
+   desc.setZReadWrite(true, false);
+   desc.setBlend(true);
+   desc.setCullMode(GFXCullNone);
+
+   if(isSelected())
+   {
+      drawer->drawCube(desc, getWorldBox(), ColorI(136, 255, 228, 5));
+      desc.setFillModeWireframe();
+      drawer->drawCube(desc, getWorldBox(), ColorI::BLACK);
+   }
+
+   desc.setZReadWrite(!mXray, false);
+
+   ColorI pathColour(255, 0, 255);
+
+   if(!mIsLooping)
+   {
+      desc.setFillModeSolid();
+      if(mFromSet) drawer->drawCube(desc, Point3F(0.2f, 0.2f, 0.2f), mFrom, pathColour);
+      if(mToSet)   drawer->drawCube(desc, Point3F(0.2f, 0.2f, 0.2f), mTo, pathColour);
+   }
+
+   GFXStateBlockRef sb = GFX->createStateBlock(desc);
+   GFX->setStateBlock(sb);
+
+   PrimBuild::color3i(pathColour.red, pathColour.green, pathColour.blue);
+
+   PrimBuild::begin(GFXLineStrip, mPoints.size());
+   for (U32 i = 0; i < mPoints.size(); i++)
+      PrimBuild::vertex3fv(mPoints[i]);
+   PrimBuild::end();
+}
+
+U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
+{
+   U32 retMask = Parent::packUpdate(conn, mask, stream);
+
+   stream->writeFlag(mIsLooping);
+   stream->writeFlag(mAlwaysRender);
+   stream->writeFlag(mXray);
+
+   if(stream->writeFlag(mFromSet))
+      mathWrite(*stream, mFrom);
+   if(stream->writeFlag(mToSet))
+      mathWrite(*stream, mTo);
+
+   if(stream->writeFlag(mask & PathMask))
+   {
+      stream->writeInt(mPoints.size(), 32);
+      for(U32 i = 0; i < mPoints.size(); i++)
+         mathWrite(*stream, mPoints[i]);
+   }
+
+   return retMask;
+}
+
+void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
+{
+   Parent::unpackUpdate(conn, stream);
+
+   mIsLooping = stream->readFlag();
+   mAlwaysRender = stream->readFlag();
+   mXray = stream->readFlag();
+
+   if((mFromSet = stream->readFlag()) == true)
+      mathRead(*stream, &mFrom);
+   if((mToSet = stream->readFlag()) == true)
+      mathRead(*stream, &mTo);
+
+   if(stream->readFlag())
+   {
+      mPoints.clear();
+      mPoints.setSize(stream->readInt(32));
+      for(U32 i = 0; i < mPoints.size(); i++)
+      {
+         Point3F p;
+         mathRead(*stream, &p);
+         mPoints[i] = p;
+      }
+      resize();
+   }
+}
+
+DefineEngineMethod(NavPath, replan, bool, (),,
+   "@brief Find a path using the already-specified path properties.")
+{
+   return object->plan();
+}
+
+DefineEngineMethod(NavPath, getCount, S32, (),,
+   "@brief Return the number of nodes in this path.")
+{
+   return object->getCount();
+}
+
+DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
+   "@brief Get a specified node along the path.")
+{
+   return object->getNode(idx);
+}
+
+DefineEngineMethod(NavPath, getLength, F32, (),,
+   "@brief Get the length of this path in Torque units (i.e. the total distance it covers).")
+{
+   return object->getLength();
+}

+ 166 - 0
Engine/source/navigation/navPath.h

@@ -0,0 +1,166 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 _NAVPATH_H_
+#define _NAVPATH_H_
+
+#include "scene/sceneObject.h"
+#include "scene/simPath.h"
+#include "navMesh.h"
+#include <DetourNavMeshQuery.h>
+
+class NavPath: public SceneObject {
+   typedef SceneObject Parent;
+   /// Maximum size of Detour path.
+   static const U32 MaxPathLen = 1024;
+
+public:
+   /// @name NavPath
+   /// Functions for planning and accessing the path.
+   /// @{
+
+   SimObjectPtr<NavMesh> mMesh;
+   SimObjectPtr<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;
+
+   /// Render even when not selected in the editor.
+   bool mAlwaysRender;
+   /// Render on top of other objects.
+   bool mXray;
+
+   /// Plan the path.
+   bool plan();
+
+   /// Updated a sliced plan.
+   /// @return True if we need to keep updating, false if we can stop.
+   bool update();
+
+   /// Finalise a sliced plan.
+   /// @return True if the plan was successful overall.
+   bool finalise();
+
+   /// @}
+
+   /// @name Path interface
+   /// @{
+
+   /// Return world-space position of a path node.
+   /// @param[in] idx Node index to retrieve.
+   Point3F getNode(S32 idx);
+
+   /// Return the number of nodes in this path.
+   S32 getCount();
+
+   /// Return the length of this path.
+   F32 getLength() { return mLength; };
+
+   /// @}
+
+   /// @name SceneObject
+   /// @{
+
+   static void initPersistFields();
+
+   bool onAdd();
+   void onRemove();
+
+   void onEditorEnable();
+   void onEditorDisable();
+   void inspectPostApply();
+
+   void onDeleteNotify(SimObject *object);
+
+   U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
+   void unpackUpdate(NetConnection *conn, BitStream *stream);
+
+   void prepRenderImage(SceneRenderState *state);
+   void renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
+
+   DECLARE_CONOBJECT(NavPath);
+
+   /// @}
+
+   /// @name ProcessObject
+   /// @{
+   void processTick(const Move *move);
+   /// @}
+
+   NavPath();
+   ~NavPath();
+
+protected:
+   enum masks {
+      PathMask     = Parent::NextFreeMask << 0,
+      NextFreeMask = Parent::NextFreeMask << 1
+   };
+
+private:
+   /// Create appropriate data structures and stuff.
+   bool init();
+
+   /// 'Visit' the most recent 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.
+   Vector<Point3F> mPoints;
+
+   /// Total length of path in world units.
+   F32 mLength;
+
+   /// Resets our world transform and bounds to fit our point list.
+   void resize();
+
+   /// @name Protected console getters/setters
+   /// @{
+   static bool setProtectedMesh(void *obj, const char *index, const char *data);
+   static const char *getProtectedMesh(void *obj, const char *data);
+   static bool setProtectedWaypoints(void *obj, const char *index, const char *data);
+
+   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 *getProtectedTo(void *obj, const char *data);
+   /// @}
+};
+
+#endif

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

@@ -0,0 +1,182 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 "recastPolyList.h"
+#include "platform/platform.h"
+
+#include "gfx/gfxDevice.h"
+#include "gfx/primBuilder.h"
+#include "gfx/gfxStateBlock.h"
+
+RecastPolyList::RecastPolyList()
+{
+   nverts = 0;
+   verts = NULL;
+   vertcap = 0;
+
+   ntris = 0;
+   tris = NULL;
+   tricap = 0;
+}
+
+RecastPolyList::~RecastPolyList()
+{
+   clear();
+}
+
+void RecastPolyList::clear()
+{
+   nverts = 0;
+   delete[] verts;
+   verts = NULL;
+   vertcap = 0;
+
+   ntris = 0;
+   delete[] tris;
+   tris = NULL;
+   tricap = 0;
+}
+
+bool RecastPolyList::isEmpty() const
+{
+   return getTriCount() == 0;
+}
+
+U32 RecastPolyList::addPoint(const Point3F &p)
+{
+   // If we've reached the vertex cap, double the array size.
+   if(nverts == vertcap)
+   {
+      // vertcap starts at 64, otherwise it doubles.
+      if(vertcap == 0) vertcap = 16;
+      else vertcap *= 2;
+      // Allocate new vertex storage.
+      F32 *newverts = new F32[vertcap*3];
+      if(!newverts)
+         return 0;
+      dMemcpy(newverts, verts, nverts*3 * sizeof(F32));
+      dFree(verts);
+      verts = newverts;
+   }
+   Point3F v = p;
+   mMatrix.mulP(v);
+   // Insert the new vertex.
+   verts[nverts*3] = v.x;
+   verts[nverts*3+1] = v.z;
+   verts[nverts*3+2] = -v.y;
+   // Return nverts before incrementing it.
+   return nverts++;
+}
+
+U32 RecastPolyList::addPlane(const PlaneF &plane)
+{
+   planes.increment();
+   mPlaneTransformer.transform(plane, planes.last());
+   return planes.size() - 1;
+}
+
+void RecastPolyList::begin(BaseMatInstance *material, U32 surfaceKey)
+{
+   vidx = 0;
+   // If we've reached the tri cap, grow the array.
+   if(ntris == tricap)
+   {
+      if(tricap == 0) tricap = 16;
+      else tricap *= 2;
+      // Allocate new vertex storage.
+      S32 *newtris = new S32[tricap*3];
+      if(!newtris)
+         return;
+      dMemcpy(newtris, tris, ntris*3 * sizeof(S32));
+      dFree(tris);
+      tris = newtris;
+   }
+}
+
+void RecastPolyList::plane(U32 v1, U32 v2, U32 v3)
+{
+}
+
+void RecastPolyList::plane(const PlaneF& p)
+{
+}
+
+void RecastPolyList::plane(const U32 index)
+{
+}
+
+void RecastPolyList::vertex(U32 vi)
+{
+   if(vidx == 3)
+      return;
+   tris[ntris*3+2-vidx] = vi;
+   vidx++;
+}
+
+void RecastPolyList::end()
+{
+   ntris++;
+}
+
+U32 RecastPolyList::getVertCount() const
+{
+   return nverts;
+}
+
+const F32 *RecastPolyList::getVerts() const
+{
+   return verts;
+}
+
+U32 RecastPolyList::getTriCount() const
+{
+   return ntris;
+}
+
+const S32 *RecastPolyList::getTris() const
+{
+   return tris;
+}
+
+void RecastPolyList::renderWire() const
+{
+   GFXStateBlockDesc desc;
+   desc.setCullMode(GFXCullNone);
+   desc.setZReadWrite(false, false);
+   //desc.setBlend(true);
+   GFXStateBlockRef sb = GFX->createStateBlock(desc);
+   GFX->setStateBlock(sb);
+
+   PrimBuild::color3i(255, 0, 255);
+
+   for(U32 t = 0; t < getTriCount(); t++)
+   {
+      PrimBuild::begin(GFXLineStrip, 4);
+
+      PrimBuild::vertex3f(verts[tris[t*3]*3],   -verts[tris[t*3]*3+2],   verts[tris[t*3]*3+1]);
+      PrimBuild::vertex3f(verts[tris[t*3+1]*3], -verts[tris[t*3+1]*3+2], verts[tris[t*3+1]*3+1]);
+      PrimBuild::vertex3f(verts[tris[t*3+2]*3], -verts[tris[t*3+2]*3+2], verts[tris[t*3+2]*3+1]);
+      PrimBuild::vertex3f(verts[tris[t*3]*3],   -verts[tris[t*3]*3+2],   verts[tris[t*3]*3+1]);
+
+      PrimBuild::end();
+   }
+}

+ 99 - 0
Engine/source/navigation/recastPolyList.h

@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 _RECAST_POLYLIST_H_
+#define _RECAST_POLYLIST_H_
+
+#include "collision/abstractPolyList.h"
+#include "core/util/tVector.h"
+
+/// Represents polygons in the same manner as the .obj file format. Handy for
+/// padding data to Recast, since it expects this data format. At the moment,
+/// this class only accepts triangles.
+/// @see AbstractPolyList
+class RecastPolyList : public AbstractPolyList {
+public:
+   /// @name AbstractPolyList
+   /// @{
+
+   bool isEmpty() const;
+
+   U32 addPoint(const Point3F &p);
+   U32 addPlane(const PlaneF &plane);
+
+   void begin(BaseMatInstance *material, U32 surfaceKey);
+
+   void plane(U32 v1, U32 v2, U32 v3);
+   void plane(const PlaneF& p);
+   void plane(const U32 index);
+
+   void vertex(U32 vi);
+
+   void end();
+
+   /// @}
+
+   /// @name Data interface
+   /// @{
+   U32 getVertCount() const;
+   const F32 *getVerts() const;
+
+   U32 getTriCount() const;
+   const S32 *getTris() const;
+
+   void clear();
+   /// @}
+
+   void renderWire() const;
+
+   /// Default constructor.
+   RecastPolyList();
+   /// Default destructor.
+   ~RecastPolyList();
+
+protected:
+   /// Number of vertices defined.
+   U32 nverts;
+   /// Array of vertex coordinates. Size nverts*3
+   F32 *verts;
+   /// Size of vertex array.
+   U32 vertcap;
+
+   /// Number of triangles defined.
+   U32 ntris;
+   /// Array of triangle vertex indices. Size ntris*3
+   S32 *tris;
+   /// Size of triangle array.
+   U32 tricap;
+
+   /// Index of vertex we're adding to the current triangle.
+   U8 vidx;
+
+   /// Store a list of planes - not actually used.
+   Vector<PlaneF> planes;
+   /// Another inherited utility function.
+   const PlaneF& getIndexedPlane(const U32 index) { return planes[index]; }
+
+private:
+};
+
+#endif

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

@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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 _TORQUE_RECAST_H_
+#define _TORQUE_RECAST_H_
+
+#include "console/simSet.h"
+#include "math/mPoint3.h"
+#include "math/mBox.h"
+
+inline Point3F DTStoRC(F32 x, F32 y, F32 z) { return Point3F(x, z, -y); }
+inline Point3F DTStoRC(Point3F point)       { return Point3F(point.x, point.z, -point.y); }
+inline Point3F RCtoDTS(const F32* xyz)      { return Point3F(xyz[0], -xyz[2], xyz[1]); }
+inline Point3F RCtoDTS(F32 x, F32 y, F32 z) { return Point3F(x, -z, y); }
+inline Point3F RCtoDTS(Point3F point)       { return Point3F(point.x, -point.z, point.y); }
+inline Box3F DTStoRC(Box3F box)
+{
+   return Box3F(box.minExtents.x, box.minExtents.z, -box.maxExtents.y,
+                  box.maxExtents.x, box.maxExtents.z, -box.minExtents.y);
+}
+inline Box3F RCtoDTS(const F32 *min, const F32 *max)
+{
+   return Box3F(min[0], -max[2], min[1], max[0], -min[2], max[1]);
+}
+
+/// Convert a Rcast colour integer to RGBA components.
+inline void rcCol(unsigned int col, U8 &r, U8 &g, U8 &b, U8 &a)
+{
+   r = col % 256; col /= 256;
+   g = col % 256; col /= 256;
+   b = col % 256; col /= 256;
+   a = col % 256;
+}
+
+enum PolyAreas {
+   GroundArea,
+   WaterArea,
+   OffMeshArea,
+   NumAreas
+};
+
+enum PolyFlags {
+   WalkFlag = 1 << 0,
+   SwimFlag = 1 << 1,
+   JumpFlag = 1 << 2,
+   LedgeFlag = 1 << 3,
+   DropFlag = 1 << 4,
+   ClimbFlag = 1 << 5,
+   TeleportFlag = 1 << 6,
+   AllFlags = 0xffff
+};
+
+#endif

BIN
Templates/Empty/game/tools/classIcons/NavMesh.png


BIN
Templates/Empty/game/tools/classIcons/NavPath.png


+ 2 - 0
Templates/Empty/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -82,6 +82,8 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SpawnSphere",  "Observer Spawn Sphere", "ObserverDropPoint" );
       %this.registerMissionObject( "SFXSpace",      "Sound Space" );
       %this.registerMissionObject( "OcclusionVolume", "Occlusion Volume" );
+      %this.registerMissionObject("NavMesh", "Navigation mesh");
+      %this.registerMissionObject("NavPath", "Path");
       
    %this.endGroup();
    

BIN
Templates/Full/game/tools/classIcons/NavMesh.png


BIN
Templates/Full/game/tools/classIcons/NavPath.png


+ 2 - 0
Templates/Full/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -82,6 +82,8 @@ function EWCreatorWindow::init( %this )
       %this.registerMissionObject( "SpawnSphere",  "Observer Spawn Sphere", "ObserverDropPoint" );
       %this.registerMissionObject( "SFXSpace",      "Sound Space" );
       %this.registerMissionObject( "OcclusionVolume", "Occlusion Volume" );
+      %this.registerMissionObject("NavMesh", "Navigation mesh");
+      %this.registerMissionObject("NavPath", "Path");
       
    %this.endGroup();
    

+ 44 - 0
Tools/projectGenerator/modules/navigation.inc

@@ -0,0 +1,44 @@
+<?php
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// 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.
+//-----------------------------------------------------------------------------
+
+beginModule( 'navigation' );
+
+   addProjectDefine( 'TORQUE_NAVIGATION_ENABLED' );
+   addSrcDir(getEngineSrcDir() . 'navigation', true);
+
+   includeLib( 'librecast' );
+   addLibIncludePath( 'recast/DebugUtils/Include' );
+   addLibIncludePath( 'recast/Recast/Include' );
+   addLibIncludePath( 'recast/Detour/Include' );
+   addLibIncludePath( 'recast/DetourTileCache/Include' );
+   addLibIncludePath( 'recast/DetourCrowd/Include' );
+
+   if (inProjectConfig())
+   {
+      addProjectDependency( 'librecast' );
+      addSolutionProjectRef( 'librecast' );
+   }
+
+endModule();
+
+?>