navPath.cpp

Engine/source/navigation/navPath.cpp

More...

Public Variables

bool

For frame signal.

Public Functions

DefineEngineMethod(NavPath , getFlags , S32 , (S32 idx) , "@brief Get <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specified node along the path." )
DefineEngineMethod(NavPath , getLength , F32 , () , "@brief Get the length of this path." )
DefineEngineMethod(NavPath , getNode , Point3F , (S32 idx) , "@brief Get <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specified node along the path." )
DefineEngineMethod(NavPath , onNavMeshUpdate , void , (const char *data) , "@brief <a href="/coding/file/sqliteobject_8cpp/#sqliteobject_8cpp_1a858cd1af41f1e4420d94dfe65b6a56a4">Callback</a> when this path's <a href="/coding/class/classnavmesh/">NavMesh</a> is loaded or rebuilt." )
DefineEngineMethod(NavPath , onNavMeshUpdateBox , void , (const char *data) , "@brief <a href="/coding/file/sqliteobject_8cpp/#sqliteobject_8cpp_1a858cd1af41f1e4420d94dfe65b6a56a4">Callback</a> when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular area in this path's <a href="/coding/class/classnavmesh/">NavMesh</a> is rebuilt." )
DefineEngineMethod(NavPath , plan , bool , () , "@brief Find <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> path using the already-specified path properties." )
DefineEngineMethod(NavPath , size , S32 , () , "@brief Return the number of nodes in this path." )

Detailed Description

Public Variables

bool gEditingMission 

For frame signal.

IRangeValidator ValidIterations (1, S32_MAX)

Public Functions

DefineEngineMethod(NavPath , getFlags , S32 , (S32 idx) , "@brief Get <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specified node along the path." )

DefineEngineMethod(NavPath , getLength , F32 , () , "@brief Get the length of this path." )

DefineEngineMethod(NavPath , getNode , Point3F , (S32 idx) , "@brief Get <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specified node along the path." )

DefineEngineMethod(NavPath , onNavMeshUpdate , void , (const char *data) , "@brief <a href="/coding/file/sqliteobject_8cpp/#sqliteobject_8cpp_1a858cd1af41f1e4420d94dfe65b6a56a4">Callback</a> when this path's <a href="/coding/class/classnavmesh/">NavMesh</a> is loaded or rebuilt." )

DefineEngineMethod(NavPath , onNavMeshUpdateBox , void , (const char *data) , "@brief <a href="/coding/file/sqliteobject_8cpp/#sqliteobject_8cpp_1a858cd1af41f1e4420d94dfe65b6a56a4">Callback</a> when <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> particular area in this path's <a href="/coding/class/classnavmesh/">NavMesh</a> is rebuilt." )

DefineEngineMethod(NavPath , plan , bool , () , "@brief Find <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> path using the already-specified path properties." )

DefineEngineMethod(NavPath , size , S32 , () , "@brief Return the number of nodes in this path." )

IMPLEMENT_CO_NETOBJECT_V1(NavPath )

  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 "torqueRecast.h"
 25#include "navPath.h"
 26#include "duDebugDrawTorque.h"
 27
 28#include "console/consoleTypes.h"
 29#include "console/engineAPI.h"
 30#include "console/typeValidators.h"
 31#include "math/mathTypes.h"
 32
 33#include "scene/sceneRenderState.h"
 34#include "gfx/gfxDrawUtil.h"
 35#include "renderInstance/renderPassManager.h"
 36#include "gfx/primBuilder.h"
 37#include "core/stream/bitStream.h"
 38#include "math/mathIO.h"
 39
 40#include <DetourDebugDraw.h>
 41#include <climits>
 42
 43extern bool gEditingMission;
 44
 45IMPLEMENT_CO_NETOBJECT_V1(NavPath);
 46
 47NavPath::NavPath() :
 48   mFrom(0.0f, 0.0f, 0.0f),
 49   mTo(0.0f, 0.0f, 0.0f)
 50{
 51   mTypeMask |= MarkerObjectType;
 52
 53   mMesh = NULL;
 54   mWaypoints = NULL;
 55
 56   mFrom.set(0, 0, 0);
 57   mFromSet = false;
 58   mTo.set(0, 0, 0);
 59   mToSet = false;
 60   mLength = 0.0f;
 61
 62   mCurIndex = -1;
 63   mIsLooping = false;
 64   mAutoUpdate = false;
 65   mIsSliced = false;
 66
 67   mMaxIterations = 1;
 68
 69   mAlwaysRender = false;
 70   mXray = false;
 71   mRenderSearch = false;
 72
 73   mQuery = NULL;
 74   mStatus = DT_FAILURE;
 75}
 76
 77NavPath::~NavPath()
 78{
 79   dtFreeNavMeshQuery(mQuery);
 80   mQuery = NULL;
 81}
 82
 83void NavPath::checkAutoUpdate()
 84{
 85   EventManager *em = NavMesh::getEventManager();
 86   em->removeAll(this);
 87   if(mMesh)
 88   {
 89      if(mAutoUpdate)
 90      {
 91         em->subscribe(this, "NavMeshRemoved");
 92         em->subscribe(this, "NavMeshUpdate");
 93         em->subscribe(this, "NavMeshUpdateBox");
 94         em->subscribe(this, "NavMeshObstacleAdded");
 95         em->subscribe(this, "NavMeshObstacleRemoved");
 96      }
 97   }
 98}
 99
100bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data)
101{
102   NavPath *object = static_cast<NavPath*>(obj);
103
104   if(Sim::findObject(data, object->mMesh))
105      object->checkAutoUpdate();
106
107   return true;
108}
109
110bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *data)
111{
112   SimPath::Path *points = NULL;
113   NavPath *object = static_cast<NavPath*>(obj);
114
115   if(Sim::findObject(data, points))
116   {
117      object->mWaypoints = points;
118      object->mIsLooping = points->isLooping();
119   }
120   else
121      object->mWaypoints = NULL;
122
123   return false;
124}
125
126bool NavPath::setProtectedAutoUpdate(void *obj, const char *index, const char *data)
127{
128   NavPath *object = static_cast<NavPath*>(obj);
129
130   object->mAutoUpdate = dAtob(data);
131   object->checkAutoUpdate();
132
133   return false;
134}
135
136bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data)
137{
138   NavPath *object = static_cast<NavPath*>(obj);
139
140   if(String::compare(data, ""))
141   {
142      object->mFromSet = true;
143      return true;
144   }
145   else
146   {
147      object->mFromSet = false;
148      return false;
149   }
150}
151
152bool NavPath::setProtectedTo(void *obj, const char *index, const char *data)
153{
154   NavPath *object = static_cast<NavPath*>(obj);
155
156   if(String::compare(data, ""))
157   {
158      object->mToSet = true;
159      return true;
160   }
161   else
162   {
163      object->mToSet = false;
164      return false;
165   }
166}
167
168const char *NavPath::getProtectedFrom(void *obj, const char *data)
169{
170   NavPath *object = static_cast<NavPath*>(obj);
171
172   if(object->mFromSet)
173      return data;
174   else
175      return StringTable->EmptyString();
176}
177
178const char *NavPath::getProtectedTo(void *obj, const char *data)
179{
180   NavPath *object = static_cast<NavPath*>(obj);
181
182   if(object->mToSet)
183      return data;
184   else
185      return StringTable->EmptyString();
186}
187
188IRangeValidator ValidIterations(1, S32_MAX);
189
190void NavPath::initPersistFields()
191{
192   addGroup("NavPath");
193
194   addProtectedField("from", TypePoint3F, Offset(mFrom, NavPath),
195      &setProtectedFrom, &getProtectedFrom,
196      "World location this path starts at.");
197   addProtectedField("to", TypePoint3F, Offset(mTo, NavPath),
198      &setProtectedTo, &getProtectedTo,
199      "World location this path should end at.");
200
201   addProtectedField("mesh", TypeRealString, Offset(mMeshName, NavPath),
202      &setProtectedMesh, &defaultProtectedGetFn,
203      "Name of the NavMesh object this path travels within.");
204   addProtectedField("waypoints", TYPEID<SimPath::Path>(), Offset(mWaypoints, NavPath),
205      &setProtectedWaypoints, &defaultProtectedGetFn,
206      "Path containing waypoints for this NavPath to visit.");
207
208   addField("isLooping", TypeBool, Offset(mIsLooping, NavPath),
209      "Does this path loop?");
210   addField("isSliced", TypeBool, Offset(mIsSliced, NavPath),
211      "Plan this path over multiple updates instead of all at once.");
212   addFieldV("maxIterations", TypeS32, Offset(mMaxIterations, NavPath), &ValidIterations,
213      "Maximum iterations of path planning this path does per tick.");
214   addProtectedField("autoUpdate", TypeBool, Offset(mAutoUpdate, NavPath),
215      &setProtectedAutoUpdate, &defaultProtectedGetFn,
216      "If set, this path will automatically replan when its navigation mesh changes.");
217
218   endGroup("NavPath");
219
220   addGroup("Flags");
221
222   addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, NavPath),
223      "Allow the path to use dry land.");
224   addField("allowJump", TypeBool, Offset(mLinkTypes.jump, NavPath),
225      "Allow the path to use jump links.");
226   addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, NavPath),
227      "Allow the path to use drop links.");
228   addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, NavPath),
229      "Allow the path to move in water.");
230   addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, NavPath),
231      "Allow the path to jump ledges.");
232   addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, NavPath),
233      "Allow the path to use climb links.");
234   addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, NavPath),
235      "Allow the path to use teleporters.");
236
237   endGroup("Flags");
238
239   addGroup("NavPath Render");
240
241   addField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavPath),
242      "Render this NavPath even when not selected.");
243   addField("xray", TypeBool, Offset(mXray, NavPath),
244      "Render this NavPath through other objects.");
245   addField("renderSearch", TypeBool, Offset(mRenderSearch, NavPath),
246      "Render the closed list of this NavPath's search.");
247
248   endGroup("NavPath Render");
249
250   Parent::initPersistFields();
251}
252
253bool NavPath::onAdd()
254{
255   if(!Parent::onAdd())
256      return false;
257
258   if(gEditingMission)
259      mNetFlags.set(Ghostable);
260
261   resize();
262
263   addToScene();
264
265   if(isServerObject())
266   {
267      mQuery = dtAllocNavMeshQuery();
268      if(!mQuery)
269         return false;
270      checkAutoUpdate();
271      if(!plan())
272         setProcessTick(true);
273   }
274
275   return true;
276}
277
278void NavPath::onRemove()
279{
280   Parent::onRemove();
281
282   removeFromScene();
283}
284
285bool NavPath::init()
286{
287   mStatus = DT_FAILURE;
288
289   // Check that all the right data is provided.
290   if(!mMesh || !mMesh->getNavMesh())
291      return false;
292   if(!(mFromSet && mToSet) && !(mWaypoints && mWaypoints->size()))
293      return false;
294
295   // Initialise our query.
296   if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
297      return false;
298
299   mPoints.clear();
300   mFlags.clear();
301   mVisitPoints.clear();
302   mLength = 0.0f;
303
304   if(isServerObject())
305      setMaskBits(PathMask);
306
307   // Add points we need to visit in reverse order.
308   if(mWaypoints && mWaypoints->size())
309   {
310      if(mIsLooping && mFromSet)
311         mVisitPoints.push_back(mFrom);
312      if(mToSet)
313         mVisitPoints.push_front(mTo);
314      for(S32 i = mWaypoints->size() - 1; i >= 0; i--)
315      {
316         SceneObject *s = dynamic_cast<SceneObject*>(mWaypoints->at(i));
317         if(s)
318         {
319            mVisitPoints.push_back(s->getPosition());
320            // This is potentially slow, but safe.
321            if(!i && mIsLooping && !mFromSet)
322               mVisitPoints.push_front(s->getPosition());
323         }
324      }
325      if(mFromSet)
326         mVisitPoints.push_back(mFrom);
327   }
328   else
329   {
330      if(mIsLooping)
331         mVisitPoints.push_back(mFrom);
332      mVisitPoints.push_back(mTo);
333      mVisitPoints.push_back(mFrom);
334   }
335
336   return true;
337}
338
339void NavPath::resize()
340{
341   if(!mPoints.size())
342   {
343      mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f),
344                  Point3F( 0.5f,  0.5f,  0.5f));
345      resetWorldBox();
346      setTransform(MatrixF(true));
347      return;
348   }
349
350   Point3F max(mPoints[0]), min(mPoints[0]), pos(0.0f);
351   for(U32 i = 1; i < mPoints.size(); i++)
352   {
353      Point3F p = mPoints[i];
354      max.x = getMax(max.x, p.x);
355      max.y = getMax(max.y, p.y);
356      max.z = getMax(max.z, p.z);
357      min.x = getMin(min.x, p.x);
358      min.y = getMin(min.y, p.y);
359      min.z = getMin(min.z, p.z);
360      pos += p;
361   }
362   pos /= mPoints.size();
363   min -= Point3F(0.5f, 0.5f, 0.5f);
364   max += Point3F(0.5f, 0.5f, 0.5f);
365
366   mObjBox.set(min - pos, max - pos);
367   MatrixF mat = Parent::getTransform();
368   mat.setPosition(pos);
369   Parent::setTransform(mat);
370}
371
372bool NavPath::plan()
373{
374   PROFILE_SCOPE(NavPath_plan);
375   // Initialise filter.
376   mFilter.setIncludeFlags(mLinkTypes.getFlags());
377
378   // Initialise query and visit locations.
379   if(!init())
380      return false;
381
382   if(mIsSliced)
383      return planSliced();
384   else
385      return planInstant();
386}
387
388bool NavPath::planSliced()
389{
390   bool visited = visitNext();
391
392   if(visited)
393      setProcessTick(true);
394
395   return visited;
396}
397
398bool NavPath::planInstant()
399{
400   setProcessTick(false);
401   visitNext();
402   S32 store = mMaxIterations;
403   mMaxIterations = INT_MAX;
404   while(update());
405   mMaxIterations = store;
406   return finalise();
407}
408
409bool NavPath::visitNext()
410{
411   U32 s = mVisitPoints.size();
412   if(s < 2)
413      return false;
414
415   // Current leg of journey.
416   Point3F &start = mVisitPoints[s-1];
417   Point3F &end = mVisitPoints[s-2];
418
419   // Drop to height of statics.
420   RayInfo info;
421   if(getContainer()->castRay(start, start - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info))
422      start = info.point;
423   if(getContainer()->castRay(end + Point3F(0, 0, 0.1f), end - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info))
424      end = info.point;
425
426   // Convert to Detour-friendly coordinates and data structures.
427   F32 from[] = {start.x, start.z, -start.y};
428   F32 to[] =   {end.x,   end.z,   -end.y};
429   F32 extx = mMesh->mWalkableRadius * 4.0f;
430   F32 extz = mMesh->mWalkableHeight;
431   F32 extents[] = {extx, extz, extx};
432   dtPolyRef startRef, endRef;
433
434   if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, NULL)) || !startRef)
435   {
436      //Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
437         //start.x, start.y, start.z, getIdString());
438      return false;
439   }
440
441   if(dtStatusFailed(mQuery->findNearestPoly(to, extents, &mFilter, &endRef, NULL)) || !endRef)
442   {
443      //Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
444         //end.x, end.y, end.z, getIdString());
445      return false;
446   }
447
448   // Init sliced pathfind.
449   mStatus = mQuery->initSlicedFindPath(startRef, endRef, from, to, &mFilter);
450   if(dtStatusFailed(mStatus))
451      return false;
452
453   return true;
454}
455
456bool NavPath::update()
457{
458   PROFILE_SCOPE(NavPath_update);
459   if(dtStatusInProgress(mStatus))
460      mStatus = mQuery->updateSlicedFindPath(mMaxIterations, NULL);
461   if(dtStatusSucceed(mStatus))
462   {
463      // Add points from this leg.
464      dtPolyRef path[MaxPathLen];
465      S32 pathLen;
466      mStatus = mQuery->finalizeSlicedFindPath(path, &pathLen, MaxPathLen);
467      if(dtStatusSucceed(mStatus) && pathLen)
468      {
469         F32 straightPath[MaxPathLen * 3];
470         S32 straightPathLen;
471         dtPolyRef straightPathPolys[MaxPathLen];
472         U8 straightPathFlags[MaxPathLen];
473
474         U32 s = mVisitPoints.size();
475         Point3F start = mVisitPoints[s-1];
476         Point3F end = mVisitPoints[s-2];
477         F32 from[] = {start.x, start.z, -start.y};
478         F32 to[] =   {end.x,   end.z,   -end.y};
479
480         mQuery->findStraightPath(from, to, path, pathLen,
481            straightPath, straightPathFlags,
482            straightPathPolys, &straightPathLen, MaxPathLen);
483
484         s = mPoints.size();
485         mPoints.increment(straightPathLen);
486         mFlags.increment(straightPathLen);
487         for(U32 i = 0; i < straightPathLen; i++)
488         {
489            F32 *f = straightPath + i * 3;
490            mPoints[s + i] = RCtoDTS(f);
491            mMesh->getNavMesh()->getPolyFlags(straightPathPolys[i], &mFlags[s + i]);
492            // Add to length
493            if(s > 0 || i > 0)
494               mLength += (mPoints[s+i] - mPoints[s+i-1]).len();
495         }
496
497         if(isServerObject())
498            setMaskBits(PathMask);
499      }
500      else
501         return false;
502      // Check to see where we still need to visit.
503      if(mVisitPoints.size() > 1)
504      {
505         //Next leg of the journey.
506         mVisitPoints.pop_back();
507         return visitNext();
508      }
509      else
510      {
511         // Finished!
512         return false;
513      }
514   }
515   else if(dtStatusFailed(mStatus))
516   {
517      // Something went wrong in planning.
518      return false;
519   }
520   return true;
521}
522
523bool NavPath::finalise()
524{
525   setProcessTick(false);
526
527   resize();
528
529   return success();
530}
531
532void NavPath::processTick(const Move *move)
533{
534   PROFILE_SCOPE(NavPath_processTick);
535   if(!mMesh)
536      if(Sim::findObject(mMeshName.c_str(), mMesh))
537         plan();
538   if(dtStatusInProgress(mStatus))
539      update();
540}
541
542Point3F NavPath::getNode(S32 idx) const
543{
544   if(idx < size() && idx >= 0)
545      return mPoints[idx];
546   return Point3F(0,0,0);
547}
548
549U16 NavPath::getFlags(S32 idx) const
550{
551   if(idx < size() && idx >= 0)
552      return mFlags[idx];
553   return 0;
554}
555
556S32 NavPath::size() const
557{
558   return mPoints.size();
559}
560
561void NavPath::onEditorEnable()
562{
563   mNetFlags.set(Ghostable);
564}
565
566void NavPath::onEditorDisable()
567{
568   mNetFlags.clear(Ghostable);
569}
570
571void NavPath::inspectPostApply()
572{
573   plan();
574}
575
576void NavPath::onDeleteNotify(SimObject *obj)
577{
578   if(obj == (SimObject*)mMesh)
579   {
580      mMesh = NULL;
581      plan();
582   }
583}
584
585void NavPath::prepRenderImage(SceneRenderState *state)
586{
587   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
588   ri->renderDelegate.bind(this, &NavPath::renderSimple);
589   ri->type = RenderPassManager::RIT_Editor;      
590   ri->translucentSort = true;
591   ri->defaultKey = 1;
592   state->getRenderPass()->addInst(ri);
593}
594
595void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat)
596{
597   if(overrideMat)
598      return;
599
600   if(state->isReflectPass() || !(isSelected() || mAlwaysRender))
601      return;
602
603   GFXDrawUtil *drawer = GFX->getDrawUtil();
604   GFXStateBlockDesc desc;
605   desc.setZReadWrite(true, false);
606   desc.setBlend(true);
607   desc.setCullMode(GFXCullNone);
608
609   if(isSelected())
610   {
611      drawer->drawCube(desc, getWorldBox(), ColorI(136, 255, 228, 5));
612      desc.setFillModeWireframe();
613      drawer->drawCube(desc, getWorldBox(), ColorI::BLACK);
614   }
615
616   desc.setZReadWrite(!mXray, false);
617
618   ColorI pathColour(255, 0, 255);
619
620   if(!mIsLooping)
621   {
622      desc.setFillModeSolid();
623      if(mFromSet) drawer->drawCube(desc, Point3F(0.2f, 0.2f, 0.2f), mFrom, pathColour);
624      if(mToSet)   drawer->drawCube(desc, Point3F(0.2f, 0.2f, 0.2f), mTo, pathColour);
625   }
626
627   GFXStateBlockRef sb = GFX->createStateBlock(desc);
628   GFX->setStateBlock(sb);
629
630   PrimBuild::color3i(pathColour.red, pathColour.green, pathColour.blue);
631
632   PrimBuild::begin(GFXLineStrip, mPoints.size());
633   for (U32 i = 0; i < mPoints.size(); i++)
634      PrimBuild::vertex3fv(mPoints[i]);
635   PrimBuild::end();
636
637   if(mRenderSearch && getServerObject())
638   {
639      NavPath *np = static_cast<NavPath*>(getServerObject());
640      if(np->mQuery && !dtStatusSucceed(np->mStatus))
641      {
642         duDebugDrawTorque dd;
643         dd.overrideColor(duRGBA(250, 20, 20, 255));
644         duDebugDrawNavMeshNodes(&dd, *np->mQuery);
645         dd.render();
646      }
647   }
648}
649
650U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
651{
652   U32 retMask = Parent::packUpdate(conn, mask, stream);
653
654   stream->writeFlag(mIsLooping);
655   stream->writeFlag(mAlwaysRender);
656   stream->writeFlag(mXray);
657   stream->writeFlag(mRenderSearch);
658
659   if(stream->writeFlag(mFromSet))
660      mathWrite(*stream, mFrom);
661   if(stream->writeFlag(mToSet))
662      mathWrite(*stream, mTo);
663
664   if(stream->writeFlag(mask & PathMask))
665   {
666      stream->writeInt(mPoints.size(), 32);
667      for(U32 i = 0; i < mPoints.size(); i++)
668      {
669         mathWrite(*stream, mPoints[i]);
670         stream->writeInt(mFlags[i], 16);
671      }
672   }
673
674   return retMask;
675}
676
677void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
678{
679   Parent::unpackUpdate(conn, stream);
680
681   mIsLooping = stream->readFlag();
682   mAlwaysRender = stream->readFlag();
683   mXray = stream->readFlag();
684   mRenderSearch = stream->readFlag();
685
686   if((mFromSet = stream->readFlag()) == true)
687      mathRead(*stream, &mFrom);
688   if((mToSet = stream->readFlag()) == true)
689      mathRead(*stream, &mTo);
690
691   if(stream->readFlag())
692   {
693      mPoints.clear();
694      mFlags.clear();
695      mPoints.setSize(stream->readInt(32));
696      mFlags.setSize(mPoints.size());
697      for(U32 i = 0; i < mPoints.size(); i++)
698      {
699         Point3F p;
700         mathRead(*stream, &p);
701         mPoints[i] = p;
702         mFlags[i] = stream->readInt(16);
703      }
704      resize();
705   }
706}
707
708DefineEngineMethod(NavPath, plan, bool, (),,
709   "@brief Find a path using the already-specified path properties.")
710{
711   return object->plan();
712}
713
714DefineEngineMethod(NavPath, onNavMeshUpdate, void, (const char *data),,
715   "@brief Callback when this path's NavMesh is loaded or rebuilt.")
716{
717   if(object->mMesh && !String::compare(data, object->mMesh->getIdString()))
718      object->plan();
719}
720
721DefineEngineMethod(NavPath, onNavMeshUpdateBox, void, (const char *data),,
722   "@brief Callback when a particular area in this path's NavMesh is rebuilt.")
723{
724   String s(data);
725   U32 space = s.find(' ');
726   if(space != String::NPos)
727   {
728      String id = s.substr(0, space);
729      if(!object->mMesh || id.compare(object->mMesh->getIdString()))
730         return;
731      String boxstr = s.substr(space + 1);
732      Box3F box;
733      castConsoleTypeFromString(box, boxstr.c_str());
734      if(object->getWorldBox().isOverlapped(box))
735         object->plan();
736   }
737}
738
739DefineEngineMethod(NavPath, size, S32, (),,
740   "@brief Return the number of nodes in this path.")
741{
742   return object->size();
743}
744
745DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
746   "@brief Get a specified node along the path.")
747{
748   return object->getNode(idx);
749}
750
751DefineEngineMethod(NavPath, getFlags, S32, (S32 idx),,
752   "@brief Get a specified node along the path.")
753{
754   return (S32)object->getFlags(idx);
755}
756
757DefineEngineMethod(NavPath, getLength, F32, (),,
758   "@brief Get the length of this path.")
759{
760   return object->getLength();
761}
762