navMesh.cpp

Engine/source/navigation/navMesh.cpp

More...

Classes:

Public Functions

DefineEngineFunction(getNavMeshEventManager , S32 , () , "@brief Get the <a href="/coding/class/classeventmanager/">EventManager</a> object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all <a href="/coding/class/classnavmesh/">NavMesh</a> updates." )
DefineEngineFunction(NavMeshIgnore , void , (S32 objid, bool _ignore) , (0, true) , "@brief Flag this object as not generating <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> navmesh result." )
DefineEngineFunction(NavMeshUpdateAll , void , (S32 objid, bool remove) , (0, false) , "@brief Update all <a href="/coding/class/classnavmesh/">NavMesh</a> tiles that intersect the given object's world box." )
DefineEngineFunction(NavMeshUpdateAroundObject , void , (S32 objid, bool remove) , (0, false) , "@brief Update all <a href="/coding/class/classnavmesh/">NavMesh</a> tiles that intersect the given object's world box." )
DefineEngineFunction(NavMeshUpdateOne , void , (S32 meshid, S32 objid, bool remove) , (0, 0, false) , "@brief Update all tiles in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given <a href="/coding/class/classnavmesh/">NavMesh</a> that intersect the given object's world box." )
DefineEngineMethod(NavMesh , addLink , S32 , (Point3F from, Point3F to, U32 flags) , (0) , "Add <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> link <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this <a href="/coding/class/classnavmesh/">NavMesh</a> between two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">points.\n\n</a>" "" )
DefineEngineMethod(NavMesh , build , bool , (bool background, bool save) , (true, false) , "@brief Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Recast nav mesh." )
DefineEngineMethod(NavMesh , buildLinks , void , () , "@brief Build tiles of this mesh where there are unsynchronised links." )
DefineEngineMethod(NavMesh , buildTiles , void , (Box3F box) , "@brief Rebuild the tiles overlapped by the input box." )
DefineEngineMethod(NavMesh , cancelBuild , void , () , "@brief Cancel the current <a href="/coding/class/classnavmesh/">NavMesh</a> build." )
DefineEngineMethod(NavMesh , createCoverPoints , bool , () , "@brief Create cover points <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this NavMesh." )
DefineEngineMethod(NavMesh , deleteCoverPoints , void , () , "@brief Remove all cover points <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this NavMesh." )
DefineEngineMethod(NavMesh , deleteLink , void , (U32 id) , "Delete <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given off-mesh link." )
DefineEngineMethod(NavMesh , deleteLinks , void , () , "Deletes all off-mesh links on this NavMesh." )
DefineEngineMethod(NavMesh , getLink , S32 , (Point3F pos) , "Get the off-mesh link closest <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given world point." )
DefineEngineMethod(NavMesh , getLinkCount , S32 , () , "Return the number of links this mesh has." )
DefineEngineMethod(NavMesh , getLinkEnd , Point3F , (U32 id) , "Get the ending point of an off-mesh link." )
DefineEngineMethod(NavMesh , getLinkFlags , S32 , (U32 id) , "Get the flags set <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular off-mesh link." )
DefineEngineMethod(NavMesh , getLinkStart , Point3F , (U32 id) , "Get the starting point of an off-mesh link." )
DefineEngineMethod(NavMesh , load , bool , () , "@brief Load this <a href="/coding/class/classnavmesh/">NavMesh</a> from its file." )
DefineEngineMethod(NavMesh , save , void , () , "@brief Save this <a href="/coding/class/classnavmesh/">NavMesh</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> its file." )
DefineEngineMethod(NavMesh , setLinkFlags , void , (U32 id, U32 flags) , "Set the flags of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular off-mesh link." )
ImplementEnumType(NavMeshWaterMethod , "The method used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> include water surfaces in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">NavMesh.\n</a>" )

Detailed Description

Public Variables

FRangeValidator CornerAngle (0.0f, 90.0f)
 EndImplementEnumType 
bool gEditingMission 

For frame signal.

IRangeValidator NaturalNumber (1, S32_MAX)
const int NAVMESHSET_MAGIC 
const int NAVMESHSET_VERSION 
IRangeValidator PositiveInt (0, S32_MAX)
FRangeValidator ValidCellSize (0.01f, 10.0f)
FRangeValidator ValidSlopeAngle (0.0f, 89.9f)

Public Functions

buildCallback(SceneObject * object, void * key)

DefineEngineFunction(getNavMeshEventManager , S32 , () , "@brief Get the <a href="/coding/class/classeventmanager/">EventManager</a> object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> all <a href="/coding/class/classnavmesh/">NavMesh</a> updates." )

DefineEngineFunction(NavMeshIgnore , void , (S32 objid, bool _ignore) , (0, true) , "@brief Flag this object as not generating <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> navmesh result." )

DefineEngineFunction(NavMeshUpdateAll , void , (S32 objid, bool remove) , (0, false) , "@brief Update all <a href="/coding/class/classnavmesh/">NavMesh</a> tiles that intersect the given object's world box." )

DefineEngineFunction(NavMeshUpdateAroundObject , void , (S32 objid, bool remove) , (0, false) , "@brief Update all <a href="/coding/class/classnavmesh/">NavMesh</a> tiles that intersect the given object's world box." )

DefineEngineFunction(NavMeshUpdateOne , void , (S32 meshid, S32 objid, bool remove) , (0, 0, false) , "@brief Update all tiles in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given <a href="/coding/class/classnavmesh/">NavMesh</a> that intersect the given object's world box." )

DefineEngineMethod(NavMesh , addLink , S32 , (Point3F from, Point3F to, U32 flags) , (0) , "Add <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> link <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> this <a href="/coding/class/classnavmesh/">NavMesh</a> between two <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">points.\n\n</a>" "" )

DefineEngineMethod(NavMesh , build , bool , (bool background, bool save) , (true, false) , "@brief Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> Recast nav mesh." )

DefineEngineMethod(NavMesh , buildLinks , void , () , "@brief Build tiles of this mesh where there are unsynchronised links." )

DefineEngineMethod(NavMesh , buildTiles , void , (Box3F box) , "@brief Rebuild the tiles overlapped by the input box." )

DefineEngineMethod(NavMesh , cancelBuild , void , () , "@brief Cancel the current <a href="/coding/class/classnavmesh/">NavMesh</a> build." )

DefineEngineMethod(NavMesh , createCoverPoints , bool , () , "@brief Create cover points <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this NavMesh." )

DefineEngineMethod(NavMesh , deleteCoverPoints , void , () , "@brief Remove all cover points <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this NavMesh." )

DefineEngineMethod(NavMesh , deleteLink , void , (U32 id) , "Delete <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given off-mesh link." )

DefineEngineMethod(NavMesh , deleteLinks , void , () , "Deletes all off-mesh links on this NavMesh." )

DefineEngineMethod(NavMesh , getLink , S32 , (Point3F pos) , "Get the off-mesh link closest <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> given world point." )

DefineEngineMethod(NavMesh , getLinkCount , S32 , () , "Return the number of links this mesh has." )

DefineEngineMethod(NavMesh , getLinkEnd , Point3F , (U32 id) , "Get the ending point of an off-mesh link." )

DefineEngineMethod(NavMesh , getLinkFlags , S32 , (U32 id) , "Get the flags set <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular off-mesh link." )

DefineEngineMethod(NavMesh , getLinkStart , Point3F , (U32 id) , "Get the starting point of an off-mesh link." )

DefineEngineMethod(NavMesh , load , bool , () , "@brief Load this <a href="/coding/class/classnavmesh/">NavMesh</a> from its file." )

DefineEngineMethod(NavMesh , save , void , () , "@brief Save this <a href="/coding/class/classnavmesh/">NavMesh</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> its file." )

DefineEngineMethod(NavMesh , setLinkFlags , void , (U32 id, U32 flags) , "Set the flags of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular off-mesh link." )

IMPLEMENT_CO_NETOBJECT_V1(NavMesh )

ImplementEnumType(NavMeshWaterMethod , "The method used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> include water surfaces in the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">NavMesh.\n</a>" )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2014 Daniel Buckmaster
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "navMesh.h"
  25#include "navContext.h"
  26#include <DetourDebugDraw.h>
  27#include <RecastDebugDraw.h>
  28
  29#include "math/mathUtils.h"
  30#include "math/mRandom.h"
  31#include "console/consoleTypes.h"
  32#include "console/engineAPI.h"
  33#include "console/typeValidators.h"
  34
  35#include "scene/sceneRenderState.h"
  36#include "gfx/gfxDrawUtil.h"
  37#include "renderInstance/renderPassManager.h"
  38#include "gfx/primBuilder.h"
  39
  40#include "core/stream/bitStream.h"
  41#include "math/mathIO.h"
  42
  43#include "core/fileio.h"
  44
  45extern bool gEditingMission;
  46
  47IMPLEMENT_CO_NETOBJECT_V1(NavMesh);
  48
  49const U32 NavMesh::mMaxVertsPerPoly = 3;
  50
  51SimObjectPtr<SimSet> NavMesh::smServerSet = NULL;
  52
  53ImplementEnumType(NavMeshWaterMethod,
  54   "The method used to include water surfaces in the NavMesh.\n")
  55   { NavMesh::Ignore,     "Ignore",     "Ignore all water surfaces.\n" },
  56   { NavMesh::Solid,      "Solid",      "Treat water surfaces as solid and walkable.\n" },
  57   { NavMesh::Impassable, "Impassable", "Treat water as an impassable obstacle.\n" },
  58EndImplementEnumType;
  59
  60SimSet *NavMesh::getServerSet()
  61{
  62   if(!smServerSet)
  63   {
  64      SimSet *set = NULL;
  65      if(Sim::findObject("ServerNavMeshSet", set))
  66         smServerSet = set;
  67      else
  68      {
  69         smServerSet = new SimSet();
  70         smServerSet->registerObject("ServerNavMeshSet");
  71         Sim::getRootGroup()->addObject(smServerSet);
  72      }
  73   }
  74   return smServerSet;
  75}
  76
  77SimObjectPtr<EventManager> NavMesh::smEventManager = NULL;
  78
  79EventManager *NavMesh::getEventManager()
  80{
  81   if(!smEventManager)
  82   {
  83      smEventManager = new EventManager();
  84      smEventManager->registerObject("NavEventManager");
  85      Sim::getRootGroup()->addObject(smEventManager);
  86      smEventManager->setMessageQueue("NavEventManagerQueue");
  87      smEventManager->registerEvent("NavMeshCreated");
  88      smEventManager->registerEvent("NavMeshRemoved");
  89      smEventManager->registerEvent("NavMeshStartUpdate");
  90      smEventManager->registerEvent("NavMeshUpdate");
  91      smEventManager->registerEvent("NavMeshTileUpdate");
  92      smEventManager->registerEvent("NavMeshUpdateBox");
  93      smEventManager->registerEvent("NavMeshObstacleAdded");
  94      smEventManager->registerEvent("NavMeshObstacleRemoved");
  95   }
  96   return smEventManager;
  97}
  98
  99DefineEngineFunction(getNavMeshEventManager, S32, (),,
 100   "@brief Get the EventManager object for all NavMesh updates.")
 101{
 102   return NavMesh::getEventManager()->getId();
 103}
 104
 105DefineEngineFunction(NavMeshUpdateAll, void, (S32 objid, bool remove), (0, false),
 106   "@brief Update all NavMesh tiles that intersect the given object's world box.")
 107{
 108   SceneObject *obj;
 109   if(!Sim::findObject(objid, obj))
 110      return;
 111   obj->mPathfindingIgnore = remove;
 112   SimSet *set = NavMesh::getServerSet();
 113   for(U32 i = 0; i < set->size(); i++)
 114   {
 115      NavMesh *m = dynamic_cast<NavMesh*>(set->at(i));
 116      if (m)
 117      {
 118         m->cancelBuild();
 119         m->buildTiles(obj->getWorldBox());
 120      }
 121   }
 122}
 123
 124DefineEngineFunction(NavMeshUpdateAroundObject, void, (S32 objid, bool remove), (0, false),
 125   "@brief Update all NavMesh tiles that intersect the given object's world box.")
 126{
 127   SceneObject *obj;
 128   if (!Sim::findObject(objid, obj))
 129      return;
 130   obj->mPathfindingIgnore = remove;
 131   SimSet *set = NavMesh::getServerSet();
 132   for (U32 i = 0; i < set->size(); i++)
 133   {
 134      NavMesh *m = dynamic_cast<NavMesh*>(set->at(i));
 135      if (m)
 136      {
 137         m->cancelBuild();
 138         m->buildTiles(obj->getWorldBox());
 139      }
 140   }
 141}
 142
 143
 144DefineEngineFunction(NavMeshIgnore, void, (S32 objid, bool _ignore), (0, true),
 145   "@brief Flag this object as not generating a navmesh result.")
 146{
 147   SceneObject *obj;
 148   if(!Sim::findObject(objid, obj))
 149      return;
 150
 151      obj->mPathfindingIgnore = _ignore;
 152}
 153
 154DefineEngineFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove), (0, 0, false),
 155   "@brief Update all tiles in a given NavMesh that intersect the given object's world box.")
 156{
 157   NavMesh *mesh;
 158   SceneObject *obj;
 159   if(!Sim::findObject(meshid, mesh))
 160   {
 161      Con::errorf("NavMeshUpdateOne: cannot find NavMesh %d", meshid);
 162      return;
 163   }
 164   if(!Sim::findObject(objid, obj))
 165   {
 166      Con::errorf("NavMeshUpdateOne: cannot find SceneObject %d", objid);
 167      return;
 168   }
 169   if(remove)
 170      obj->disableCollision();
 171   mesh->buildTiles(obj->getWorldBox());
 172   if(remove)
 173      obj->enableCollision();
 174}
 175
 176NavMesh::NavMesh()
 177{
 178   mTypeMask |= StaticShapeObjectType | MarkerObjectType;
 179   mFileName = StringTable->EmptyString();
 180   mNetFlags.clear(Ghostable);
 181
 182   mSaveIntermediates = false;
 183   nm = NULL;
 184   ctx = NULL;
 185
 186   mWaterMethod = Ignore;
 187
 188   dMemset(&cfg, 0, sizeof(cfg));
 189   mCellSize = mCellHeight = 0.2f;
 190   mWalkableHeight = 2.0f;
 191   mWalkableClimb = 0.3f;
 192   mWalkableRadius = 0.5f;
 193   mWalkableSlope = 40.0f;
 194   mBorderSize = 1;
 195   mDetailSampleDist = 6.0f;
 196   mDetailSampleMaxError = 1.0f;
 197   mMaxEdgeLen = 12;
 198   mMaxSimplificationError = 1.3f;
 199   mMinRegionArea = 8;
 200   mMergeRegionArea = 20;
 201   mTileSize = 10.0f;
 202   mMaxPolysPerTile = 128;
 203
 204   mSmallCharacters = false;
 205   mRegularCharacters = true;
 206   mLargeCharacters = false;
 207   mVehicles = false;
 208
 209   mCoverSet = StringTable->EmptyString();
 210   mInnerCover = false;
 211   mCoverDist = 1.0f;
 212   mPeekDist = 0.7f;
 213
 214   mAlwaysRender = false;
 215
 216   mBuilding = false;
 217   mCurLinkID = 0;
 218}
 219
 220NavMesh::~NavMesh()
 221{
 222   dtFreeNavMesh(nm);
 223   nm = NULL;
 224   delete ctx;
 225   ctx = NULL;
 226}
 227
 228bool NavMesh::setProtectedDetailSampleDist(void *obj, const char *index, const char *data)
 229{
 230   F32 dist = dAtof(data);
 231   if(dist == 0.0f || dist >= 0.9f)
 232      return true;
 233   Con::errorf("NavMesh::detailSampleDist must be 0 or greater than 0.9!");
 234   return false;
 235}
 236
 237bool NavMesh::setProtectedAlwaysRender(void *obj, const char *index, const char *data)
 238{
 239   NavMesh *mesh = static_cast<NavMesh*>(obj);
 240   bool always = dAtob(data);
 241   if(always)
 242   {
 243      if(!gEditingMission)
 244         mesh->mNetFlags.set(Ghostable);
 245   }
 246   else
 247   {
 248      if(!gEditingMission)
 249         mesh->mNetFlags.clear(Ghostable);
 250   }
 251   mesh->mAlwaysRender = always;
 252   mesh->setMaskBits(LoadFlag);
 253   return true;
 254}
 255
 256FRangeValidator ValidCellSize(0.01f, 10.0f);
 257FRangeValidator ValidSlopeAngle(0.0f, 89.9f);
 258IRangeValidator PositiveInt(0, S32_MAX);
 259IRangeValidator NaturalNumber(1, S32_MAX);
 260FRangeValidator CornerAngle(0.0f, 90.0f);
 261
 262void NavMesh::initPersistFields()
 263{
 264   addGroup("NavMesh Options");
 265
 266   addField("fileName", TypeString, Offset(mFileName, NavMesh),
 267      "Name of the data file to store this navmesh in (relative to engine executable).");
 268
 269   addField("waterMethod", TYPEID<NavMeshWaterMethod>(), Offset(mWaterMethod, NavMesh),
 270      "The method to use to handle water surfaces.");
 271
 272   addFieldV("cellSize", TypeF32, Offset(mCellSize, NavMesh), &ValidCellSize,
 273      "Length/width of a voxel.");
 274   addFieldV("cellHeight", TypeF32, Offset(mCellHeight, NavMesh), &ValidCellSize,
 275      "Height of a voxel.");
 276   addFieldV("tileSize", TypeF32, Offset(mTileSize, NavMesh), &CommonValidators::PositiveNonZeroFloat,
 277      "The horizontal size of tiles.");
 278
 279   addFieldV("actorHeight", TypeF32, Offset(mWalkableHeight, NavMesh), &CommonValidators::PositiveFloat,
 280      "Height of an actor.");
 281   addFieldV("actorClimb", TypeF32, Offset(mWalkableClimb, NavMesh), &CommonValidators::PositiveFloat,
 282      "Maximum climbing height of an actor.");
 283   addFieldV("actorRadius", TypeF32, Offset(mWalkableRadius, NavMesh), &CommonValidators::PositiveFloat,
 284      "Radius of an actor.");
 285   addFieldV("walkableSlope", TypeF32, Offset(mWalkableSlope, NavMesh), &ValidSlopeAngle,
 286      "Maximum walkable slope in degrees.");
 287
 288   addField("smallCharacters", TypeBool, Offset(mSmallCharacters, NavMesh),
 289      "Is this NavMesh for smaller-than-usual characters?");
 290   addField("regularCharacters", TypeBool, Offset(mRegularCharacters, NavMesh),
 291      "Is this NavMesh for regular-sized characters?");
 292   addField("largeCharacters", TypeBool, Offset(mLargeCharacters, NavMesh),
 293      "Is this NavMesh for larger-than-usual characters?");
 294   addField("vehicles", TypeBool, Offset(mVehicles, NavMesh),
 295      "Is this NavMesh for characters driving vehicles?");
 296
 297   endGroup("NavMesh Options");
 298
 299   addGroup("NavMesh Annotations");
 300
 301   addField("coverGroup", TypeString, Offset(mCoverSet, NavMesh),
 302      "Name of the SimGroup to store cover points in.");
 303
 304   addField("innerCover", TypeBool, Offset(mInnerCover, NavMesh),
 305      "Add cover points everywhere, not just on corners?");
 306
 307   addField("coverDist", TypeF32, Offset(mCoverDist, NavMesh),
 308      "Distance from the edge of the NavMesh to search for cover.");
 309   addField("peekDist", TypeF32, Offset(mPeekDist, NavMesh),
 310      "Distance to the side of each cover point that peeking happens.");
 311
 312   endGroup("NavMesh Annotations");
 313
 314   addGroup("NavMesh Rendering");
 315
 316   addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh),
 317      &setProtectedAlwaysRender, &defaultProtectedGetFn,
 318      "Display this NavMesh even outside the editor.");
 319
 320   endGroup("NavMesh Rendering");
 321
 322   addGroup("NavMesh Advanced Options");
 323
 324   addFieldV("borderSize", TypeS32, Offset(mBorderSize, NavMesh), &PositiveInt,
 325      "Size of the non-walkable border around the navigation mesh (in voxels).");
 326   addProtectedField("detailSampleDist", TypeF32, Offset(mDetailSampleDist, NavMesh),
 327      &setProtectedDetailSampleDist, &defaultProtectedGetFn,
 328      "Sets the sampling distance to use when generating the detail mesh.");
 329   addFieldV("detailSampleError", TypeF32, Offset(mDetailSampleMaxError, NavMesh), &CommonValidators::PositiveFloat,
 330      "The maximum distance the detail mesh surface should deviate from heightfield data.");
 331   addFieldV("maxEdgeLen", TypeS32, Offset(mDetailSampleDist, NavMesh), &PositiveInt,
 332      "The maximum allowed length for contour edges along the border of the mesh.");
 333   addFieldV("simplificationError", TypeF32, Offset(mMaxSimplificationError, NavMesh), &CommonValidators::PositiveFloat,
 334      "The maximum distance a simplfied contour's border edges should deviate from the original raw contour.");
 335   addFieldV("minRegionArea", TypeS32, Offset(mMinRegionArea, NavMesh), &PositiveInt,
 336      "The minimum number of cells allowed to form isolated island areas.");
 337   addFieldV("mergeRegionArea", TypeS32, Offset(mMergeRegionArea, NavMesh), &PositiveInt,
 338      "Any regions with a span count smaller than this value will, if possible, be merged with larger regions.");
 339   addFieldV("maxPolysPerTile", TypeS32, Offset(mMaxPolysPerTile, NavMesh), &NaturalNumber,
 340      "The maximum number of polygons allowed in a tile.");
 341
 342   endGroup("NavMesh Advanced Options");
 343
 344   Parent::initPersistFields();
 345}
 346
 347bool NavMesh::onAdd()
 348{
 349   if(!Parent::onAdd())
 350      return false;
 351
 352   mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f),
 353               Point3F( 0.5f,  0.5f,  0.5f));
 354   resetWorldBox();
 355
 356   addToScene();
 357
 358   if(gEditingMission || mAlwaysRender)
 359   {
 360      mNetFlags.set(Ghostable);
 361      if(isClientObject())
 362         renderToDrawer();
 363   }
 364
 365   if(isServerObject())
 366   {
 367      getServerSet()->addObject(this);
 368      ctx = new NavContext();
 369      setProcessTick(true);
 370      if(getEventManager())
 371         getEventManager()->postEvent("NavMeshCreated", getIdString());
 372   }
 373
 374   load();
 375
 376   return true;
 377}
 378
 379void NavMesh::onRemove()
 380{
 381   if(getEventManager())
 382      getEventManager()->postEvent("NavMeshRemoved", getIdString());
 383
 384   removeFromScene();
 385
 386   Parent::onRemove();
 387}
 388
 389void NavMesh::setTransform(const MatrixF &mat)
 390{
 391   Parent::setTransform(mat);
 392}
 393
 394void NavMesh::setScale(const VectorF &scale)
 395{
 396   Parent::setScale(scale);
 397}
 398
 399S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags)
 400{
 401   Point3F rcFrom = DTStoRC(from), rcTo = DTStoRC(to);
 402   mLinkVerts.push_back(rcFrom.x);
 403   mLinkVerts.push_back(rcFrom.y);
 404   mLinkVerts.push_back(rcFrom.z);
 405   mLinkVerts.push_back(rcTo.x);
 406   mLinkVerts.push_back(rcTo.y);
 407   mLinkVerts.push_back(rcTo.z);
 408   mLinksUnsynced.push_back(true);
 409   mLinkRads.push_back(mWalkableRadius);
 410   mLinkDirs.push_back(0);
 411   mLinkAreas.push_back(OffMeshArea);
 412   if (flags == 0) {
 413      Point3F dir = to - from;
 414      F32 drop = -dir.z;
 415      dir.z = 0;
 416      // If we drop more than we travel horizontally, we're a drop link.
 417      if(drop > dir.len())
 418         mLinkFlags.push_back(DropFlag);
 419      else
 420         mLinkFlags.push_back(JumpFlag);
 421   }
 422   mLinkIDs.push_back(1000 + mCurLinkID);
 423   mLinkSelectStates.push_back(Unselected);
 424   mDeleteLinks.push_back(false);
 425   mCurLinkID++;
 426   return mLinkIDs.size() - 1;
 427}
 428
 429DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, U32 flags), (0),
 430   "Add a link to this NavMesh between two points.\n\n"
 431   "")
 432{
 433   return object->addLink(from, to, flags);
 434}
 435
 436S32 NavMesh::getLink(const Point3F &pos)
 437{
 438   for(U32 i = 0; i < mLinkIDs.size(); i++)
 439   {
 440      if(mDeleteLinks[i])
 441         continue;
 442      SphereF start(getLinkStart(i), mLinkRads[i]);
 443      SphereF end(getLinkEnd(i), mLinkRads[i]);
 444      if(start.isContained(pos) || end.isContained(pos))
 445         return i;
 446   }
 447   return -1;
 448}
 449
 450DefineEngineMethod(NavMesh, getLink, S32, (Point3F pos),,
 451   "Get the off-mesh link closest to a given world point.")
 452{
 453   return object->getLink(pos);
 454}
 455
 456S32 NavMesh::getLinkCount()
 457{
 458   return mLinkIDs.size();
 459}
 460
 461DefineEngineMethod(NavMesh, getLinkCount, S32, (),,
 462   "Return the number of links this mesh has.")
 463{
 464   return object->getLinkCount();
 465}
 466
 467LinkData NavMesh::getLinkFlags(U32 idx)
 468{
 469   if(idx < mLinkIDs.size())
 470   {
 471      return LinkData(mLinkFlags[idx]);
 472   }
 473   return LinkData();
 474}
 475
 476DefineEngineMethod(NavMesh, getLinkFlags, S32, (U32 id),,
 477   "Get the flags set for a particular off-mesh link.")
 478{
 479   return object->getLinkFlags(id).getFlags();
 480}
 481
 482void NavMesh::setLinkFlags(U32 idx, const LinkData &d)
 483{
 484   if(idx < mLinkIDs.size())
 485   {
 486      mLinkFlags[idx] = d.getFlags();
 487      mLinksUnsynced[idx] = true;
 488   }
 489}
 490
 491DefineEngineMethod(NavMesh, setLinkFlags, void, (U32 id, U32 flags),,
 492   "Set the flags of a particular off-mesh link.")
 493{
 494   LinkData d(flags);
 495   object->setLinkFlags(id, d);
 496}
 497
 498Point3F NavMesh::getLinkStart(U32 idx)
 499{
 500   return RCtoDTS(Point3F(
 501      mLinkVerts[idx*6],
 502      mLinkVerts[idx*6 + 1],
 503      mLinkVerts[idx*6 + 2]));
 504}
 505
 506DefineEngineMethod(NavMesh, getLinkStart, Point3F, (U32 id),,
 507   "Get the starting point of an off-mesh link.")
 508{
 509   return object->getLinkStart(id);
 510}
 511
 512Point3F NavMesh::getLinkEnd(U32 idx)
 513{
 514   return RCtoDTS(Point3F(
 515      mLinkVerts[idx*6 + 3],
 516      mLinkVerts[idx*6 + 4],
 517      mLinkVerts[idx*6 + 5]));
 518}
 519
 520DefineEngineMethod(NavMesh, getLinkEnd, Point3F, (U32 id),,
 521   "Get the ending point of an off-mesh link.")
 522{
 523   return object->getLinkEnd(id);
 524}
 525
 526void NavMesh::selectLink(U32 idx, bool select, bool hover)
 527{
 528   if(idx < mLinkIDs.size())
 529   {
 530      if(!select)
 531         mLinkSelectStates[idx] = Unselected;
 532      else
 533         mLinkSelectStates[idx] = hover ? Hovered : Selected;
 534   }
 535}
 536
 537void NavMesh::eraseLink(U32 i)
 538{
 539   mLinkVerts.erase(i*6, 6);
 540   mLinksUnsynced.erase(i);
 541   mLinkRads.erase(i);
 542   mLinkDirs.erase(i);
 543   mLinkAreas.erase(i);
 544   mLinkFlags.erase(i);
 545   mLinkIDs.erase(i);
 546   mLinkSelectStates.erase(i);
 547   mDeleteLinks.erase(i);
 548}
 549
 550void NavMesh::eraseLinks()
 551{
 552   mLinkVerts.clear();
 553   mLinksUnsynced.clear();
 554   mLinkRads.clear();
 555   mLinkDirs.clear();
 556   mLinkAreas.clear();
 557   mLinkFlags.clear();
 558   mLinkIDs.clear();
 559   mLinkSelectStates.clear();
 560   mDeleteLinks.clear();
 561}
 562
 563void NavMesh::setLinkCount(U32 c)
 564{
 565   eraseLinks();
 566   mLinkVerts.setSize(c * 6);
 567   mLinksUnsynced.setSize(c);
 568   mLinkRads.setSize(c);
 569   mLinkDirs.setSize(c);
 570   mLinkAreas.setSize(c);
 571   mLinkFlags.setSize(c);
 572   mLinkIDs.setSize(c);
 573   mLinkSelectStates.setSize(c);
 574   mDeleteLinks.setSize(c);
 575}
 576
 577void NavMesh::deleteLink(U32 idx)
 578{
 579   if(idx < mLinkIDs.size())
 580   {
 581      mDeleteLinks[idx] = true;
 582      if(mLinksUnsynced[idx])
 583         eraseLink(idx);
 584      else
 585         mLinksUnsynced[idx] = true;
 586   }
 587}
 588
 589DefineEngineMethod(NavMesh, deleteLink, void, (U32 id),,
 590   "Delete a given off-mesh link.")
 591{
 592   object->deleteLink(id);
 593}
 594
 595DefineEngineMethod(NavMesh, deleteLinks, void, (),,
 596   "Deletes all off-mesh links on this NavMesh.")
 597{
 598   //object->eraseLinks();
 599}
 600
 601bool NavMesh::build(bool background, bool saveIntermediates)
 602{
 603   if(mBuilding)
 604      cancelBuild();
 605   else
 606   {
 607      if(getEventManager())
 608         getEventManager()->postEvent("NavMeshStartUpdate", getIdString());
 609   }
 610
 611   mBuilding = true;
 612
 613   ctx->startTimer(RC_TIMER_TOTAL);
 614
 615   dtFreeNavMesh(nm);
 616   // Allocate a new navmesh.
 617   nm = dtAllocNavMesh();
 618   if(!nm)
 619   {
 620      Con::errorf("Could not allocate dtNavMesh for NavMesh %s", getIdString());
 621      return false;
 622   }
 623
 624   updateConfig();
 625
 626   // Build navmesh parameters from console members.
 627   dtNavMeshParams params;
 628   rcVcopy(params.orig, cfg.bmin);
 629   params.tileWidth = cfg.tileSize * mCellSize;
 630   params.tileHeight = cfg.tileSize * mCellSize;
 631   params.maxTiles = mCeil(getWorldBox().len_x() / params.tileWidth) * mCeil(getWorldBox().len_y() / params.tileHeight);
 632   params.maxPolys = mMaxPolysPerTile;
 633
 634   // Initialise our navmesh.
 635   if(dtStatusFailed(nm->init(&params)))
 636   {
 637      Con::errorf("Could not init dtNavMesh for NavMesh %s", getIdString());
 638      return false;
 639   }
 640
 641   // Update links to be deleted.
 642   for(U32 i = 0; i < mLinkIDs.size();)
 643   {
 644      if(mDeleteLinks[i])
 645         eraseLink(i);
 646      else
 647         i++;
 648   }
 649   mLinksUnsynced.fill(false);
 650   mCurLinkID = 0;
 651
 652   mSaveIntermediates = saveIntermediates;
 653
 654   updateTiles(true);
 655
 656   if(!background)
 657   {
 658      while(!mDirtyTiles.empty())
 659         buildNextTile();
 660   }
 661
 662   return true;
 663}
 664
 665DefineEngineMethod(NavMesh, build, bool, (bool background, bool save), (true, false),
 666   "@brief Create a Recast nav mesh.")
 667{
 668   return object->build(background, save);
 669}
 670
 671void NavMesh::cancelBuild()
 672{
 673   mDirtyTiles.clear();
 674   ctx->stopTimer(RC_TIMER_TOTAL);
 675   mBuilding = false;
 676}
 677
 678DefineEngineMethod(NavMesh, cancelBuild, void, (),,
 679   "@brief Cancel the current NavMesh build.")
 680{
 681   object->cancelBuild();
 682}
 683
 684void NavMesh::inspectPostApply()
 685{
 686   if(mBuilding)
 687      cancelBuild();
 688}
 689
 690void NavMesh::updateConfig()
 691{
 692   // Build rcConfig object from our console members.
 693   dMemset(&cfg, 0, sizeof(cfg));
 694   cfg.cs = mCellSize;
 695   cfg.ch = mCellHeight;
 696   Box3F box = DTStoRC(getWorldBox());
 697   rcVcopy(cfg.bmin, box.minExtents);
 698   rcVcopy(cfg.bmax, box.maxExtents);
 699   rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
 700
 701   cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight);
 702   cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight);
 703   cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize);
 704   cfg.walkableSlopeAngle = mWalkableSlope;
 705   cfg.borderSize = cfg.walkableRadius + 3;
 706
 707   cfg.detailSampleDist = mDetailSampleDist;
 708   cfg.detailSampleMaxError = mDetailSampleMaxError;
 709   cfg.maxEdgeLen = mMaxEdgeLen;
 710   cfg.maxSimplificationError = mMaxSimplificationError;
 711   cfg.maxVertsPerPoly = mMaxVertsPerPoly;
 712   cfg.minRegionArea = mMinRegionArea;
 713   cfg.mergeRegionArea = mMergeRegionArea;
 714   cfg.tileSize = mTileSize / cfg.cs;
 715}
 716
 717S32 NavMesh::getTile(const Point3F& pos)
 718{
 719   if(mBuilding)
 720      return -1;
 721   for(U32 i = 0; i < mTiles.size(); i++)
 722   {
 723      if(mTiles[i].box.isContained(pos))
 724         return i;
 725   }
 726   return -1;
 727}
 728
 729Box3F NavMesh::getTileBox(U32 id)
 730{
 731   if(mBuilding || id >= mTiles.size())
 732      return Box3F::Invalid;
 733   return mTiles[id].box;
 734}
 735
 736void NavMesh::updateTiles(bool dirty)
 737{
 738   PROFILE_SCOPE(NavMesh_updateTiles);
 739   if(!isProperlyAdded())
 740      return;
 741
 742   mTiles.clear();
 743   mTileData.clear();
 744   mDirtyTiles.clear();
 745
 746   const Box3F &box = DTStoRC(getWorldBox());
 747   if(box.isEmpty())
 748      return;
 749
 750   updateConfig();
 751
 752   // Calculate tile dimensions.
 753   const U32 ts = cfg.tileSize;
 754   const U32 tw = (cfg.width  + ts-1) / ts;
 755   const U32 th = (cfg.height + ts-1) / ts;
 756   const F32 tcs = cfg.tileSize * cfg.cs;
 757
 758   // Iterate over tiles.
 759   F32 tileBmin[3], tileBmax[3];
 760   for(U32 y = 0; y < th; ++y)
 761   {
 762      for(U32 x = 0; x < tw; ++x)
 763      {
 764         tileBmin[0] = cfg.bmin[0] + x*tcs;
 765         tileBmin[1] = cfg.bmin[1];
 766         tileBmin[2] = cfg.bmin[2] + y*tcs;
 767
 768         tileBmax[0] = cfg.bmin[0] + (x+1)*tcs;
 769         tileBmax[1] = cfg.bmax[1];
 770         tileBmax[2] = cfg.bmin[2] + (y+1)*tcs;
 771
 772         mTiles.push_back(
 773            Tile(RCtoDTS(tileBmin, tileBmax),
 774                  x, y,
 775                  tileBmin, tileBmax));
 776
 777         if(dirty)
 778            mDirtyTiles.push_back_unique(mTiles.size() - 1);
 779
 780         if(mSaveIntermediates)
 781            mTileData.increment();
 782      }
 783   }
 784}
 785
 786void NavMesh::processTick(const Move *move)
 787{
 788   buildNextTile();
 789}
 790
 791void NavMesh::buildNextTile()
 792{
 793   PROFILE_SCOPE(NavMesh_buildNextTile);
 794   if(!mDirtyTiles.empty())
 795   {
 796      // Pop a single dirty tile and process it.
 797      U32 i = mDirtyTiles.front();
 798      mDirtyTiles.pop_front();
 799      const Tile &tile = mTiles[i];
 800      // Intermediate data for tile build.
 801      TileData tempdata;
 802      TileData &tdata = mSaveIntermediates ? mTileData[i] : tempdata;
 803      
 804      // Remove any previous data.
 805      nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0);
 806
 807      // Generate navmesh for this tile.
 808      U32 dataSize = 0;
 809      unsigned char* data = buildTileData(tile, tdata, dataSize);
 810      if(data)
 811      {
 812         // Add new data (navmesh owns and deletes the data).
 813         dtStatus status = nm->addTile(data, dataSize, DT_TILE_FREE_DATA, 0, 0);
 814         int success = 1;
 815         if(dtStatusFailed(status))
 816         {
 817            success = 0;
 818            dtFree(data);
 819         }
 820         if(getEventManager())
 821         {
 822            String str = String::ToString("%d %d %d (%d, %d) %d %.3f %s",
 823               getId(),
 824               i, mTiles.size(),
 825               tile.x, tile.y,
 826               success,
 827               ctx->getAccumulatedTime(RC_TIMER_TOTAL) / 1000.0f,
 828               castConsoleTypeToString(tile.box));
 829            getEventManager()->postEvent("NavMeshTileUpdate", str.c_str());
 830            setMaskBits(LoadFlag);
 831         }
 832      }
 833      // Did we just build the last tile?
 834      if(mDirtyTiles.empty())
 835      {
 836         ctx->stopTimer(RC_TIMER_TOTAL);
 837         if(getEventManager())
 838         {
 839            String str = String::ToString("%d", getId());
 840            getEventManager()->postEvent("NavMeshUpdate", str.c_str());
 841            setMaskBits(LoadFlag);
 842         }
 843         mBuilding = false;
 844      }
 845   }
 846}
 847
 848static void buildCallback(SceneObject* object,void *key)
 849{
 850   SceneContainer::CallbackInfo* info = reinterpret_cast<SceneContainer::CallbackInfo*>(key);
 851   if (!object->mPathfindingIgnore)
 852   object->buildPolyList(info->context,info->polyList,info->boundingBox,info->boundingSphere);
 853}
 854
 855unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dataSize)
 856{
 857   // Push out tile boundaries a bit.
 858   F32 tileBmin[3], tileBmax[3];
 859   rcVcopy(tileBmin, tile.bmin);
 860   rcVcopy(tileBmax, tile.bmax);
 861   tileBmin[0] -= cfg.borderSize * cfg.cs;
 862   tileBmin[2] -= cfg.borderSize * cfg.cs;
 863   tileBmax[0] += cfg.borderSize * cfg.cs;
 864   tileBmax[2] += cfg.borderSize * cfg.cs;
 865
 866   // Parse objects from level into RC-compatible format.
 867   Box3F box = RCtoDTS(tileBmin, tileBmax);
 868   SceneContainer::CallbackInfo info;
 869   info.context = PLC_Navigation;
 870   info.boundingBox = box;
 871   data.geom.clear();
 872   info.polyList = &data.geom;
 873   info.key = this;
 874   getContainer()->findObjects(box, StaticObjectType | DynamicShapeObjectType, buildCallback, &info);
 875
 876   // Parse water objects into the same list, but remember how much geometry was /not/ water.
 877   U32 nonWaterVertCount = data.geom.getVertCount();
 878   U32 nonWaterTriCount = data.geom.getTriCount();
 879   if(mWaterMethod != Ignore)
 880   {
 881      getContainer()->findObjects(box, WaterObjectType, buildCallback, &info);
 882   }
 883
 884   // Check for no geometry.
 885   if (!data.geom.getVertCount())
 886   {
 887      data.geom.clear();
 888      return NULL;
 889   }
 890
 891   // Figure out voxel dimensions of this tile.
 892   U32 width = 0, height = 0;
 893   width = cfg.tileSize + cfg.borderSize * 2;
 894   height = cfg.tileSize + cfg.borderSize * 2;
 895
 896   // Create a heightfield to voxelise our input geometry.
 897   data.hf = rcAllocHeightfield();
 898   if(!data.hf)
 899   {
 900      Con::errorf("Out of memory (rcHeightField) for NavMesh %s", getIdString());
 901      return NULL;
 902   }
 903   if(!rcCreateHeightfield(ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch))
 904   {
 905      Con::errorf("Could not generate rcHeightField for NavMesh %s", getIdString());
 906      return NULL;
 907   }
 908
 909   unsigned char *areas = new unsigned char[data.geom.getTriCount()];
 910
 911   dMemset(areas, 0, data.geom.getTriCount() * sizeof(unsigned char));
 912
 913   // Mark walkable triangles with the appropriate area flags, and rasterize.
 914   if(mWaterMethod == Solid)
 915   {
 916      // Treat water as solid: i.e. mark areas as walkable based on angle.
 917      rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle,
 918         data.geom.getVerts(), data.geom.getVertCount(),
 919         data.geom.getTris(), data.geom.getTriCount(), areas);
 920   }
 921   else
 922   {
 923      // Treat water as impassable: leave all area flags 0.
 924      rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle,
 925         data.geom.getVerts(), nonWaterVertCount,
 926         data.geom.getTris(), nonWaterTriCount, areas);
 927   }
 928   rcRasterizeTriangles(ctx,
 929      data.geom.getVerts(), data.geom.getVertCount(),
 930      data.geom.getTris(), areas, data.geom.getTriCount(),
 931      *data.hf, cfg.walkableClimb);
 932
 933   delete[] areas;
 934
 935   // Filter out areas with low ceilings and other stuff.
 936   rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *data.hf);
 937   rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf);
 938   rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *data.hf);
 939
 940   data.chf = rcAllocCompactHeightfield();
 941   if(!data.chf)
 942   {
 943      Con::errorf("Out of memory (rcCompactHeightField) for NavMesh %s", getIdString());
 944      return NULL;
 945   }
 946   if(!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf))
 947   {
 948      Con::errorf("Could not generate rcCompactHeightField for NavMesh %s", getIdString());
 949      return NULL;
 950   }
 951   if(!rcErodeWalkableArea(ctx, cfg.walkableRadius, *data.chf))
 952   {
 953      Con::errorf("Could not erode walkable area for NavMesh %s", getIdString());
 954      return NULL;
 955   }
 956
 957   //--------------------------
 958   // Todo: mark areas here.
 959   //const ConvexVolume* vols = m_geom->getConvexVolumes();
 960   //for (int i  = 0; i < m_geom->getConvexVolumeCount(); ++i)
 961      //rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
 962   //--------------------------
 963
 964   if(false)
 965   {
 966      if(!rcBuildRegionsMonotone(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
 967      {
 968         Con::errorf("Could not build regions for NavMesh %s", getIdString());
 969         return NULL;
 970      }
 971   }
 972   else
 973   {
 974      if(!rcBuildDistanceField(ctx, *data.chf))
 975      {
 976         Con::errorf("Could not build distance field for NavMesh %s", getIdString());
 977         return NULL;
 978      }
 979      if(!rcBuildRegions(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
 980      {
 981         Con::errorf("Could not build regions for NavMesh %s", getIdString());
 982         return NULL;
 983      }
 984   }
 985
 986   data.cs = rcAllocContourSet();
 987   if(!data.cs)
 988   {
 989      Con::errorf("Out of memory (rcContourSet) for NavMesh %s", getIdString());
 990      return NULL;
 991   }
 992   if(!rcBuildContours(ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs))
 993   {
 994      Con::errorf("Could not construct rcContourSet for NavMesh %s", getIdString());
 995      return NULL;
 996   }
 997   if(data.cs->nconts <= 0)
 998   {
 999      Con::errorf("No contours in rcContourSet for NavMesh %s", getIdString());
1000      return NULL;
1001   }
1002
1003   data.pm = rcAllocPolyMesh();
1004   if(!data.pm)
1005   {
1006      Con::errorf("Out of memory (rcPolyMesh) for NavMesh %s", getIdString());
1007      return NULL;
1008   }
1009   if(!rcBuildPolyMesh(ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm))
1010   {
1011      Con::errorf("Could not construct rcPolyMesh for NavMesh %s", getIdString());
1012      return NULL;
1013   }
1014
1015   data.pmd = rcAllocPolyMeshDetail();
1016   if(!data.pmd)
1017   {
1018      Con::errorf("Out of memory (rcPolyMeshDetail) for NavMesh %s", getIdString());
1019      return NULL;
1020   }
1021   if(!rcBuildPolyMeshDetail(ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd))
1022   {
1023      Con::errorf("Could not construct rcPolyMeshDetail for NavMesh %s", getIdString());
1024      return NULL;
1025   }
1026
1027   if(data.pm->nverts >= 0xffff)
1028   {
1029      Con::errorf("Too many vertices in rcPolyMesh for NavMesh %s", getIdString());
1030      return NULL;
1031   }
1032   for(U32 i = 0; i < data.pm->npolys; i++)
1033   {
1034      if(data.pm->areas[i] == RC_WALKABLE_AREA)
1035         data.pm->areas[i] = GroundArea;
1036
1037      if(data.pm->areas[i] == GroundArea)
1038         data.pm->flags[i] |= WalkFlag;
1039      if(data.pm->areas[i] == WaterArea)
1040         data.pm->flags[i] |= SwimFlag;
1041   }
1042
1043   unsigned char* navData = 0;
1044   int navDataSize = 0;
1045
1046   dtNavMeshCreateParams params;
1047   dMemset(&params, 0, sizeof(params));
1048
1049   params.verts = data.pm->verts;
1050   params.vertCount = data.pm->nverts;
1051   params.polys = data.pm->polys;
1052   params.polyAreas = data.pm->areas;
1053   params.polyFlags = data.pm->flags;
1054   params.polyCount = data.pm->npolys;
1055   params.nvp = data.pm->nvp;
1056
1057   params.detailMeshes = data.pmd->meshes;
1058   params.detailVerts = data.pmd->verts;
1059   params.detailVertsCount = data.pmd->nverts;
1060   params.detailTris = data.pmd->tris;
1061   params.detailTriCount = data.pmd->ntris;
1062
1063   params.offMeshConVerts = mLinkVerts.address();
1064   params.offMeshConRad = mLinkRads.address();
1065   params.offMeshConDir = mLinkDirs.address();
1066   params.offMeshConAreas = mLinkAreas.address();
1067   params.offMeshConFlags = mLinkFlags.address();
1068   params.offMeshConUserID = mLinkIDs.address();
1069   params.offMeshConCount = mLinkIDs.size();
1070
1071   params.walkableHeight = mWalkableHeight;
1072   params.walkableRadius = mWalkableRadius;
1073   params.walkableClimb = mWalkableClimb;
1074   params.tileX = tile.x;
1075   params.tileY = tile.y;
1076   params.tileLayer = 0;
1077   rcVcopy(params.bmin, data.pm->bmin);
1078   rcVcopy(params.bmax, data.pm->bmax);
1079   params.cs = cfg.cs;
1080   params.ch = cfg.ch;
1081   params.buildBvTree = true;
1082
1083   if(!dtCreateNavMeshData(&params, &navData, &navDataSize))
1084   {
1085      Con::errorf("Could not create dtNavMeshData for tile (%d, %d) of NavMesh %s",
1086         tile.x, tile.y, getIdString());
1087      return NULL;
1088   }
1089
1090   dataSize = navDataSize;
1091
1092   return navData;
1093}
1094
1095/// This method should never be called in a separate thread to the rendering
1096/// or pathfinding logic. It directly replaces data in the dtNavMesh for
1097/// this NavMesh object.
1098void NavMesh::buildTiles(const Box3F &box)
1099{
1100   PROFILE_SCOPE(NavMesh_buildTiles);
1101   // Make sure we've already built or loaded.
1102   if(!nm)
1103      return;
1104   // Iterate over tiles.
1105   for(U32 i = 0; i < mTiles.size(); i++)
1106   {
1107      const Tile &tile = mTiles[i];
1108      // Check tile box.
1109      if(!tile.box.isOverlapped(box))
1110         continue;
1111      // Mark as dirty.
1112      mDirtyTiles.push_back_unique(i);
1113   }
1114   if(mDirtyTiles.size())
1115      ctx->startTimer(RC_TIMER_TOTAL);
1116}
1117
1118DefineEngineMethod(NavMesh, buildTiles, void, (Box3F box),,
1119   "@brief Rebuild the tiles overlapped by the input box.")
1120{
1121   return object->buildTiles(box);
1122}
1123
1124void NavMesh::buildTile(const U32 &tile)
1125{
1126   PROFILE_SCOPE(NavMesh_buildTile);
1127   if(tile < mTiles.size())
1128   {
1129      mDirtyTiles.push_back_unique(tile);
1130      ctx->startTimer(RC_TIMER_TOTAL);
1131   }
1132}
1133
1134void NavMesh::buildLinks()
1135{
1136   // Make sure we've already built or loaded.
1137   if(!nm)
1138      return;
1139   // Iterate over tiles.
1140   for(U32 i = 0; i < mTiles.size(); i++)
1141   {
1142      const Tile &tile = mTiles[i];
1143      // Iterate over links
1144      for(U32 j = 0; j < mLinkIDs.size(); j++)
1145      {
1146         if (mLinksUnsynced[j])
1147         {
1148         if(tile.box.isContained(getLinkStart(j)) ||
1149               tile.box.isContained(getLinkEnd(j)))
1150         {
1151            // Mark tile for build.
1152            mDirtyTiles.push_back_unique(i);
1153            // Delete link if necessary
1154            if(mDeleteLinks[j])
1155            {
1156               eraseLink(j);
1157               j--;
1158            }
1159            else
1160               mLinksUnsynced[j] = false;
1161         }
1162      }
1163   }
1164   }
1165   if(mDirtyTiles.size())
1166      ctx->startTimer(RC_TIMER_TOTAL);
1167}
1168
1169DefineEngineMethod(NavMesh, buildLinks, void, (),,
1170   "@brief Build tiles of this mesh where there are unsynchronised links.")
1171{
1172   object->buildLinks();
1173}
1174
1175void NavMesh::deleteCoverPoints()
1176{
1177   SimSet *set = NULL;
1178   if(Sim::findObject(mCoverSet, set))
1179      set->deleteAllObjects();
1180}
1181
1182DefineEngineMethod(NavMesh, deleteCoverPoints, void, (),,
1183   "@brief Remove all cover points for this NavMesh.")
1184{
1185   object->deleteCoverPoints();
1186}
1187
1188bool NavMesh::createCoverPoints()
1189{
1190   if(!nm || !isServerObject())
1191      return false;
1192
1193   SimSet *set = NULL;
1194   if(Sim::findObject(mCoverSet, set))
1195   {
1196      set->deleteAllObjects();
1197   }
1198   else
1199   {
1200      set = new SimGroup();
1201      if(set->registerObject(mCoverSet))
1202      {
1203         getGroup()->addObject(set);
1204      }
1205      else
1206      {
1207         delete set;
1208         set = getGroup();
1209      }
1210   }
1211
1212   dtNavMeshQuery *query = dtAllocNavMeshQuery();
1213   if(!query || dtStatusFailed(query->init(nm, 1)))
1214      return false;
1215
1216   dtQueryFilter f;
1217
1218   // Iterate over all polys in our navmesh.
1219   const int MAX_SEGS = 6;
1220   for(U32 i = 0; i < nm->getMaxTiles(); ++i)
1221   {
1222      const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i);
1223      if(!tile->header) continue;
1224      const dtPolyRef base = nm->getPolyRefBase(tile);
1225      for(U32 j = 0; j < tile->header->polyCount; ++j)
1226      {
1227         const dtPolyRef ref = base | j;
1228         float segs[MAX_SEGS*6];
1229         int nsegs = 0;
1230         query->getPolyWallSegments(ref, &f, segs, NULL, &nsegs, MAX_SEGS);
1231         for(int segIDx = 0; segIDx < nsegs; ++segIDx)
1232         {
1233            const float* sa = &segs[segIDx *6];
1234            const float* sb = &segs[segIDx *6+3];
1235            Point3F a = RCtoDTS(sa), b = RCtoDTS(sb);
1236            F32 len = (b - a).len();
1237            if(len < mWalkableRadius * 2)
1238               continue;
1239            Point3F edge = b - a;
1240            edge.normalize();
1241            // Number of points to try placing - for now, one at each end.
1242            U32 pointCount = (len > mWalkableRadius * 4) ? 2 : 1;
1243            for(U32 pointIDx = 0; pointIDx < pointCount; pointIDx++)
1244            {
1245               MatrixF mat;
1246               Point3F pos;
1247               // If we're only placing one point, put it in the middle.
1248               if(pointCount == 1)
1249                  pos = a + edge * len / 2;
1250               // Otherwise, stand off from edge ends.
1251               else
1252               {
1253                  if(pointIDx % 2)
1254                     pos = a + edge * (pointIDx /2+1) * mWalkableRadius;
1255                  else
1256                     pos = b - edge * (pointIDx /2+1) * mWalkableRadius;
1257               }
1258               CoverPointData data;
1259               if(testEdgeCover(pos, edge, data))
1260               {
1261                  CoverPoint *m = new CoverPoint();
1262                  if(!m->registerObject())
1263                     delete m;
1264                  else
1265                  {
1266                     m->setTransform(data.trans);
1267                     m->setSize(data.size);
1268                     m->setPeek(data.peek[0], data.peek[1], data.peek[2]);
1269                     if(set)
1270                        set->addObject(m);
1271                  }
1272               }
1273            }
1274         }
1275      }
1276   }
1277   return true;
1278}
1279
1280DefineEngineMethod(NavMesh, createCoverPoints, bool, (),,
1281   "@brief Create cover points for this NavMesh.")
1282{
1283   return object->createCoverPoints();
1284}
1285
1286bool NavMesh::testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointData &data)
1287{
1288   data.peek[0] = data.peek[1] = data.peek[2] = false;
1289   // Get the edge normal.
1290   Point3F norm;
1291   mCross(dir, Point3F(0, 0, 1), &norm);
1292   RayInfo ray;
1293   U32 hits = 0;
1294   for(U32 j = 0; j < CoverPoint::NumSizes; j++)
1295   {
1296      Point3F test = pos + Point3F(0.0f, 0.0f, mWalkableHeight * j / CoverPoint::NumSizes);
1297      if(getContainer()->castRay(test, test + norm * mCoverDist, StaticObjectType, &ray))
1298      {
1299         // Test peeking.
1300         Point3F left = test + dir * mPeekDist;
1301         data.peek[0] = !getContainer()->castRay(test, left, StaticObjectType, &ray)
1302            && !getContainer()->castRay(left, left + norm * mCoverDist, StaticObjectType, &ray);
1303
1304         Point3F right = test - dir * mPeekDist;
1305         data.peek[1] = !getContainer()->castRay(test, right, StaticObjectType, &ray)
1306            && !getContainer()->castRay(right, right + norm * mCoverDist, StaticObjectType, &ray);
1307
1308         Point3F over = test + Point3F(0, 0, 1) * 0.2f;
1309         data.peek[2] = !getContainer()->castRay(test, over, StaticObjectType, &ray)
1310            && !getContainer()->castRay(over, over + norm * mCoverDist, StaticObjectType, &ray);
1311
1312         if(mInnerCover || data.peek[0] || data.peek[1] || data.peek[2])
1313            hits++;
1314         // If we couldn't peek here, we may be able to peek further up.
1315      }
1316      else
1317         // No cover at this height - break off.
1318         break;
1319   }
1320   if(hits > 0)
1321   {
1322      data.size = (CoverPoint::Size)(hits - 1);
1323      data.trans = MathUtils::createOrientFromDir(norm);
1324      data.trans.setPosition(pos);
1325   }
1326   return hits > 0;
1327}
1328
1329void NavMesh::renderToDrawer()
1330{
1331   mDbgDraw.clear();
1332   // Recast debug draw
1333   NetObject *no = getServerObject();
1334   if(no)
1335   {
1336      NavMesh *n = static_cast<NavMesh*>(no);
1337
1338      if(n->nm)
1339      {
1340         mDbgDraw.beginGroup(0);
1341         duDebugDrawNavMesh       (&mDbgDraw, *n->nm, 0);
1342       mDbgDraw.beginGroup(1);
1343         duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm);
1344       mDbgDraw.beginGroup(2);
1345         duDebugDrawNavMeshBVTree (&mDbgDraw, *n->nm);
1346      }
1347   }
1348}
1349
1350void NavMesh::prepRenderImage(SceneRenderState *state)
1351{
1352   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
1353   ri->renderDelegate.bind(this, &NavMesh::render);
1354   ri->type = RenderPassManager::RIT_Object;
1355   ri->translucentSort = true;
1356   ri->defaultKey = 1;
1357   state->getRenderPass()->addInst(ri);
1358}
1359
1360void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat)
1361{
1362   if(overrideMat)
1363      return;
1364
1365   if(state->isReflectPass())
1366      return;
1367
1368   PROFILE_SCOPE(NavMesh_Render);
1369   
1370   // Recast debug draw
1371   NetObject *no = getServerObject();
1372   if(no)
1373   {
1374      NavMesh *n = static_cast<NavMesh*>(no);
1375
1376      if(n->isSelected())
1377      {
1378         GFXDrawUtil *drawer = GFX->getDrawUtil();
1379
1380         GFXStateBlockDesc desc;
1381         desc.setZReadWrite(true, false);
1382         desc.setBlend(true);
1383         desc.setCullMode(GFXCullNone);
1384
1385         drawer->drawCube(desc, getWorldBox(), n->mBuilding
1386            ? ColorI(255, 0, 0, 80)
1387            : ColorI(136, 228, 255, 45));
1388         desc.setFillModeWireframe();
1389         drawer->drawCube(desc, getWorldBox(), ColorI::BLACK);
1390      }
1391
1392      if(n->mBuilding)
1393      {
1394         int alpha = 80;
1395         if(!n->isSelected() || !Con::getBoolVariable("$Nav::EditorOpen"))
1396            alpha = 20;
1397       mDbgDraw.overrideColor(duRGBA(255, 0, 0, alpha));
1398      }
1399      else
1400      {
1401        mDbgDraw.cancelOverride();
1402      }
1403      
1404      if((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) mDbgDraw.renderGroup(0);
1405      if(Con::getBoolVariable("$Nav::Editor::renderPortals")) mDbgDraw.renderGroup(1);
1406      if(Con::getBoolVariable("$Nav::Editor::renderBVTree"))  mDbgDraw.renderGroup(2);
1407   }
1408}
1409
1410void NavMesh::renderLinks(duDebugDraw &dd)
1411{
1412   if(mBuilding)
1413      return;
1414   dd.depthMask(true);
1415   dd.begin(DU_DRAW_LINES);
1416   for(U32 i = 0; i < mLinkIDs.size(); i++)
1417   {
1418      U32 col = 0;
1419      switch(mLinkSelectStates[i])
1420      {
1421         case Unselected: col = mLinksUnsynced[i] ? duRGBA(255, 0, 0, 200) : duRGBA(0, 0, 255, 255); break;
1422         case Hovered:    col = duRGBA(255, 255, 255, 255); break;
1423         case Selected:   col = duRGBA(0, 255, 0, 255); break;
1424      }
1425      F32 *s = &mLinkVerts[i*6];
1426      F32 *e = &mLinkVerts[i*6 + 3];
1427      if(!mDeleteLinks[i])
1428         duAppendCircle(&dd, s[0], s[1], s[2], mLinkRads[i], col);
1429      duAppendArc(&dd,
1430         s[0], s[1], s[2],
1431         e[0], e[1], e[2],
1432         0.3f,
1433         0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.4f,
1434         col);
1435      if(!mDeleteLinks[i])
1436         duAppendCircle(&dd, e[0], e[1], e[2], mLinkRads[i], col);
1437   }
1438   dd.end();
1439}
1440
1441void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile)
1442{
1443   if(tile >= mTileData.size())
1444      return;
1445   if(nm)
1446   {
1447      dd.beginGroup(0);
1448      if(mTileData[tile].chf) duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf);
1449
1450      dd.beginGroup(1);
1451      int col = duRGBA(255, 0, 255, 255);
1452      RecastPolyList &in = mTileData[tile].geom;
1453      dd.begin(DU_DRAW_LINES);
1454      const F32 *verts = in.getVerts();
1455      const S32 *tris = in.getTris();
1456      for(U32 t = 0; t < in.getTriCount(); t++)
1457      {
1458         dd.vertex(&verts[tris[t*3]*3], col);
1459         dd.vertex(&verts[tris[t*3+1]*3], col);
1460         dd.vertex(&verts[tris[t*3+1]*3], col);
1461         dd.vertex(&verts[tris[t*3+2]*3], col);
1462         dd.vertex(&verts[tris[t*3+2]*3], col);
1463         dd.vertex(&verts[tris[t*3]*3], col);
1464      }
1465      dd.end();
1466   }
1467}
1468
1469void NavMesh::onEditorEnable()
1470{
1471   mNetFlags.set(Ghostable);
1472   if(isClientObject() && !mAlwaysRender)
1473      addToScene();
1474}
1475
1476void NavMesh::onEditorDisable()
1477{
1478   if(!mAlwaysRender)
1479   {
1480      mNetFlags.clear(Ghostable);
1481      if(isClientObject())
1482         removeFromScene();
1483   }
1484}
1485
1486U32 NavMesh::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
1487{
1488   U32 retMask = Parent::packUpdate(conn, mask, stream);
1489
1490   mathWrite(*stream, getTransform());
1491   mathWrite(*stream, getScale());
1492   stream->writeFlag(mAlwaysRender);
1493
1494   return retMask;
1495}
1496
1497void NavMesh::unpackUpdate(NetConnection *conn, BitStream *stream)
1498{
1499   Parent::unpackUpdate(conn, stream);
1500
1501   mathRead(*stream, &mObjToWorld);
1502   mathRead(*stream, &mObjScale);
1503   mAlwaysRender = stream->readFlag();
1504
1505   setTransform(mObjToWorld);
1506
1507   renderToDrawer();
1508}
1509
1510static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
1511static const int NAVMESHSET_VERSION = 1;
1512
1513struct NavMeshSetHeader
1514{
1515   int magic;
1516   int version;
1517   int numTiles;
1518   dtNavMeshParams params;
1519};
1520
1521struct NavMeshTileHeader
1522{
1523   dtTileRef tileRef;
1524   int dataSize;
1525};
1526
1527bool NavMesh::load()
1528{
1529   if(!dStrlen(mFileName))
1530      return false;
1531
1532   File file;
1533   if(file.open(mFileName, File::Read) != File::Ok)
1534   {
1535      file.close();
1536      Con::errorf("Could not open file %s when loading navmesh %s.",
1537         mFileName, getName() ? getName() : getIdString());
1538      return false;
1539   }
1540
1541   // Read header.
1542   NavMeshSetHeader header;
1543   file.read(sizeof(NavMeshSetHeader), (char*)&header);
1544   if(header.magic != NAVMESHSET_MAGIC)
1545   {
1546      file.close();
1547      Con::errorf("Navmesh magic incorrect when loading navmesh %s; possible corrupt navmesh file %s.",
1548         getName() ? getName() : getIdString(), mFileName);
1549      return false;
1550   }
1551   if(header.version != NAVMESHSET_VERSION)
1552   {
1553      file.close();
1554      Con::errorf("Navmesh version incorrect when loading navmesh %s; possible corrupt navmesh file %s.",
1555         getName() ? getName() : getIdString(), mFileName);
1556      return false;
1557   }
1558
1559   if(nm)
1560      dtFreeNavMesh(nm);
1561   nm = dtAllocNavMesh();
1562   if(!nm)
1563   {
1564      file.close();
1565      Con::errorf("Out of memory when loading navmesh %s.",
1566         getName() ? getName() : getIdString());
1567      return false;
1568   }
1569
1570   dtStatus status = nm->init(&header.params);
1571   if(dtStatusFailed(status))
1572   {
1573      file.close();
1574      Con::errorf("Failed to initialise navmesh params when loading navmesh %s.",
1575         getName() ? getName() : getIdString());
1576      return false;
1577   }
1578
1579   // Read tiles.
1580   for(U32 i = 0; i < header.numTiles; ++i)
1581   {
1582      NavMeshTileHeader tileHeader;
1583      file.read(sizeof(NavMeshTileHeader), (char*)&tileHeader);
1584      if(!tileHeader.tileRef || !tileHeader.dataSize)
1585         break;
1586
1587      unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
1588      if(!data) break;
1589      memset(data, 0, tileHeader.dataSize);
1590      file.read(tileHeader.dataSize, (char*)data);
1591
1592      nm->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
1593   }
1594
1595   S32 s;
1596   file.read(sizeof(S32), (char*)&s);
1597   setLinkCount(s);
1598   if (s > 0)
1599   {
1600      file.read(sizeof(F32) * s * 6, (char*)const_cast<F32*>(mLinkVerts.address()));
1601      file.read(sizeof(F32) * s, (char*)const_cast<F32*>(mLinkRads.address()));
1602      file.read(sizeof(U8) * s, (char*)const_cast<U8*>(mLinkDirs.address()));
1603      file.read(sizeof(U8) * s, (char*)const_cast<U8*>(mLinkAreas.address()));
1604      file.read(sizeof(U16) * s, (char*)const_cast<U16*>(mLinkFlags.address()));
1605      file.read(sizeof(F32) * s, (char*)const_cast<U32*>(mLinkIDs.address()));
1606   }
1607   mLinksUnsynced.fill(false);
1608   mLinkSelectStates.fill(Unselected);
1609   mDeleteLinks.fill(false);
1610
1611   file.close();
1612
1613   updateTiles();
1614
1615   if(isServerObject())
1616   {
1617      setMaskBits(LoadFlag);
1618      if(getEventManager())
1619         getEventManager()->postEvent("NavMeshUpdate", getIdString());
1620   }
1621
1622   return true;
1623}
1624
1625DefineEngineMethod(NavMesh, load, bool, (),,
1626   "@brief Load this NavMesh from its file.")
1627{
1628   return object->load();
1629}
1630
1631bool NavMesh::save()
1632{
1633   if(!dStrlen(mFileName) || !nm)
1634      return false;
1635   
1636   File file;
1637   if(file.open(mFileName, File::Write) != File::Ok)
1638   {
1639      file.close();
1640      Con::errorf("Could not open file %s when saving navmesh %s.",
1641         mFileName, getName() ? getName() : getIdString());
1642      return false;
1643   }
1644
1645   // Store header.
1646   NavMeshSetHeader header;
1647   header.magic = NAVMESHSET_MAGIC;
1648   header.version = NAVMESHSET_VERSION;
1649   header.numTiles = 0;
1650   for(U32 i = 0; i < nm->getMaxTiles(); ++i)
1651   {
1652      const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i);
1653      if (!tile || !tile->header || !tile->dataSize) continue;
1654      header.numTiles++;
1655   }
1656   memcpy(&header.params, nm->getParams(), sizeof(dtNavMeshParams));
1657   file.write(sizeof(NavMeshSetHeader), (const char*)&header);
1658
1659   // Store tiles.
1660   for(U32 i = 0; i < nm->getMaxTiles(); ++i)
1661   {
1662      const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i);
1663      if(!tile || !tile->header || !tile->dataSize) continue;
1664
1665      NavMeshTileHeader tileHeader;
1666      tileHeader.tileRef = nm->getTileRef(tile);
1667      tileHeader.dataSize = tile->dataSize;
1668
1669      file.write(sizeof(tileHeader), (const char*)&tileHeader);
1670      file.write(tile->dataSize, (const char*)tile->data);
1671   }
1672
1673   S32 s = mLinkIDs.size();
1674   file.write(sizeof(S32), (const char*)&s);
1675   if (s > 0)
1676   {
1677      file.write(sizeof(F32) * s * 6, (const char*)mLinkVerts.address());
1678      file.write(sizeof(F32) * s,     (const char*)mLinkRads.address());
1679      file.write(sizeof(U8) * s,      (const char*)mLinkDirs.address());
1680      file.write(sizeof(U8) * s,      (const char*)mLinkAreas.address());
1681      file.write(sizeof(U16) * s,     (const char*)mLinkFlags.address());
1682      file.write(sizeof(U32) * s,     (const char*)mLinkIDs.address());
1683   }
1684
1685   file.close();
1686
1687   return true;
1688}
1689
1690DefineEngineMethod(NavMesh, save, void, (),,
1691   "@brief Save this NavMesh to its file.")
1692{
1693   object->save();
1694}
1695
1696void NavMesh::write(Stream &stream, U32 tabStop, U32 flags)
1697{
1698   save();
1699   Parent::write(stream, tabStop, flags);
1700}
1701