Divide Framework 0.1
A free and open-source 3D Framework under heavy development
Loading...
Searching...
No Matches
NavMesh.cpp
Go to the documentation of this file.
1
2
3#include "Headers/NavMesh.h"
6
11
13
15
16#include <recastnavigation/RecastDump.h>
17#include <recastnavigation/DetourDebugDraw.h>
18#include <recastnavigation/RecastDebugDraw.h>
19
20#include <SimpleIni.h>
21
23{
24
25 NavigationMesh::NavigationMesh( PlatformContext& context, DivideRecast& recastInterface, Scene& parentScene )
26 : GUIDWrapper()
27 , PlatformContextComponent( context )
28 , _parentScene(parentScene)
29 , _debugDrawInterface( std::make_unique<NavMeshDebugDraw>( context.gfx() ) )
30 , _recastInterface( recastInterface )
31 {
32 _filePath = Scene::GetSceneFullPath( _parentScene ) / Paths::g_navMeshesLocation;
33 _configFile = (_filePath / "navMeshConfig.ini").string();
34
35 _buildThreaded = true;
36 _debugDraw = false;
37 _renderConnections = false;
39 _heightField = nullptr;
40 _compactHeightField = nullptr;
41 _countourSet = nullptr;
42 _polyMesh = nullptr;
43 _polyMeshDetail = nullptr;
44 _navMesh = nullptr;
45 _tempNavMesh = nullptr;
46 _navQuery = nullptr;
47 _building = false;
48 _sgn = nullptr;
49 }
50
52 {
53 unload();
54 }
55
57 {
59
60 if ( _navQuery )
61 {
62 dtFreeNavMeshQuery( _navQuery );
63 _navQuery = nullptr;
64 }
65
66 freeIntermediates( true );
67 dtFreeNavMesh( _navMesh );
68 dtFreeNavMesh( _tempNavMesh );
69 _navMesh = nullptr;
70 _tempNavMesh = nullptr;
71
72 return true;
73 }
74
76 {
77 if ( _buildJobGUID != -1 )
78 {
79 _buildJobGUID = -1;
80 assert( _buildTask );
82 }
83 }
84
85 void NavigationMesh::freeIntermediates( const bool freeAll )
86 {
88
89 rcFreeHeightField( _heightField );
90 rcFreeCompactHeightfield( _compactHeightField );
91 _heightField = nullptr;
92 _compactHeightField = nullptr;
93
94 if ( !_saveIntermediates || freeAll )
95 {
96 rcFreeContourSet( _countourSet );
97 rcFreePolyMesh( _polyMesh );
98 rcFreePolyMeshDetail( _polyMeshDetail );
99 _countourSet = nullptr;
100 _polyMesh = nullptr;
101 _polyMeshDetail = nullptr;
102 }
103 }
104
105 namespace
106 {
107 I32 charToInt( const char* val )
108 {
109 return Util::ConvertData<I32, const char*>( val );
110 }
111
112 F32 charToFloat( const char* val )
113 {
114 return Util::ConvertData<F32, const char*>( val );
115 }
116
117 bool charToBool( const char* val ) noexcept
118 {
119 return _stricmp( val, "true" ) == 0;
120 }
121 }
122
124 {
125 // Use SimpleIni library for cross-platform INI parsing
126 CSimpleIniA ini;
127 ini.SetUnicode();
128 ini.LoadFile( _configFile.c_str() );
129
130 if ( !ini.GetSection( "Rasterization" ) || !ini.GetSection( "Agent" ) ||
131 !ini.GetSection( "Region" ) || !ini.GetSection( "Polygonization" ) ||
132 !ini.GetSection( "DetailMesh" ) )
133 return false;
134
135 // Load all key-value pairs for the "Rasterization" section
137 charToFloat( ini.GetValue( "Rasterization", "fCellSize", "0.3" ) ) );
139 charToFloat( ini.GetValue( "Rasterization", "fCellHeight", "0.2" ) ) );
141 charToInt( ini.GetValue( "Rasterization", "iTileSize", "48" ) ) );
142 // Load all key-value pairs for the "Agent" section
144 charToFloat( ini.GetValue( "Agent", "fAgentHeight", "2.5" ) ) );
146 charToFloat( ini.GetValue( "Agent", "fAgentRadius", "0.5" ) ) );
148 charToFloat( ini.GetValue( "Agent", "fAgentMaxClimb", "1" ) ) );
150 charToFloat( ini.GetValue( "Agent", "fAgentMaxSlope", "20" ) ) );
151 // Load all key-value pairs for the "Region" section
153 charToInt( ini.GetValue( "Region", "fMergeSize", "20" ) ) );
155 charToInt( ini.GetValue( "Region", "fMinSize", "50" ) ) );
156 // Load all key-value pairs for the "Polygonization" section
158 charToInt( ini.GetValue( "Polygonization", "fEdgeMaxLength", "12" ) ) );
160 charToFloat( ini.GetValue( "Polygonization", "fEdgeMaxError", "1.3" ) ) );
162 charToInt( ini.GetValue( "Polygonization", "iVertsPerPoly", "6" ) ) );
163 // Load all key-value pairs for the "DetailMesh" section
165 charToFloat( ini.GetValue( "DetailMesh", "fDetailSampleDist", "6" ) ) );
167 charToFloat( ini.GetValue( "DetailMesh", "fDetailSampleMaxError", "1" ) ) );
169 charToBool( ini.GetValue( "DetailMesh", "bKeepInterResults", "false" ) ) );
170
171 return true;
172 }
173
175 CreationCallback creationCompleteCallback,
176 const bool threaded )
177 {
179
180 if ( !loadConfigFromFile() )
181 {
182 Console::errorfn( LOCALE_STR( "NAV_MESH_CONFIG_NOT_FOUND" ) );
183 return false;
184 }
185
186 _sgn = sgn;
187 _loadCompleteClbk = MOV( creationCompleteCallback );
188
189 if ( _buildThreaded && threaded )
190 {
191 return buildThreaded();
192 }
193
194 return buildProcess();
195 }
196
198 {
200
201 _buildTask = CreateTask( [this]( const Task& /*parentTask*/ )
202 {
204 } );
206 _buildJobGUID = 1;
207
208 return true;
209 }
210
212 {
214
215 _building = true;
216 // Create mesh
217 Time::ProfileTimer importTimer;
218 importTimer.start();
219 const bool state = generateMesh();
220 importTimer.stop();
221 if ( state )
222 {
223 Console::printfn( LOCALE_STR( "NAV_MESH_GENERATION_COMPLETE" ),
224 Time::MicrosecondsToSeconds<F32>( importTimer.get() ) );
225
226 {
227 std::lock_guard<Mutex> lock( _navigationMeshLock );
228 // Copy new NavigationMesh into old.
229 dtNavMesh* old = _navMesh;
230 // I am trusting that this is atomic.
232 dtFreeNavMesh( old );
233 _debugDrawInterface->setDirty( true );
234 _tempNavMesh = nullptr;
235
236 const bool navQueryComplete = createNavigationQuery();
238 navQueryComplete,
239 "NavigationMesh Error: Navigation query creation failed!" );
240 }
241
242 // Free structs used during build
243 freeIntermediates( false );
244
245 if ( _loadCompleteClbk )
246 {
247 _loadCompleteClbk( this );
248 }
249
250 _building = false;
251 }
252 else
253 {
254 Console::errorfn( LOCALE_STR( "NAV_MESH_GENERATION_INCOMPLETE" ),
255 Time::MicrosecondsToSeconds<F32>( importTimer.get() ) );
256 }
257 }
258
260 {
261 _building = true;
262 // Create mesh
263 Time::ProfileTimer importTimer;
264 importTimer.start();
265 const bool success = generateMesh();
266 importTimer.stop();
267 if ( !success )
268 {
269 Console::errorfn( LOCALE_STR( "NAV_MESH_GENERATION_INCOMPLETE" ),
270 Time::MicrosecondsToSeconds<F32>( importTimer.get() ) );
271 return false;
272 }
273
274 Console::printfn( LOCALE_STR( "NAV_MESH_GENERATION_COMPLETE" ),
275 Time::MicrosecondsToSeconds<F32>( importTimer.get() ) );
276
277 {
279 // Copy new NavigationMesh into old.
280 dtNavMesh* old = _navMesh;
281 // I am trusting that this is atomic.
283 dtFreeNavMesh( old );
284 _debugDrawInterface->setDirty( true );
285 _tempNavMesh = nullptr;
286
287 const bool navQueryComplete = createNavigationQuery();
289 navQueryComplete,
290 "NavigationMesh Error: Navigation query creation failed!" );
291 }
292
293 // Free structs used during build
294 freeIntermediates( false );
295
296 _building = false;
297
298 if ( _loadCompleteClbk )
299 {
300 _loadCompleteClbk( this );
301 }
302
303 return success;
304 }
305
307 {
308 assert( _sgn != nullptr );
309
310 const Str<256> nodeName( GenerateMeshName( _sgn ) );
311
312 // Parse objects from level into RC-compatible format
313 _fileName.append( nodeName.c_str() );
314 _fileName.append( ".nm" );
315 Console::printfn( LOCALE_STR( "NAV_MESH_GENERATION_START" ), nodeName.c_str() );
316
317 NavModelData data;
318 Str<256> geometrySaveFile( _fileName );
319
320 Util::ReplaceStringInPlace( geometrySaveFile, ".nm", ".ig" );
321
322 data.clear();
323 data.name( nodeName );
324
325 if ( !NavigationMeshLoader::LoadMeshFile( data, _filePath, geometrySaveFile.c_str() ) )
326 {
328 {
329 Console::errorfn( LOCALE_STR( "ERROR_NAV_PARSE_FAILED" ),
330 nodeName.c_str() );
331 }
332 }
333
334 // Check for no geometry
335 if ( !data.getVertCount() )
336 {
337 data.valid( false );
338 return false;
339 }
340
341 // Free intermediate and final results
342 freeIntermediates( true );
343 // Recast initialisation data
344 rcContextDivide ctx( true );
345
346 rcConfig cfg;
347 memset( &cfg, 0, sizeof cfg );
348
349 cfg.cs = _configParams.getCellSize();
350 cfg.ch = _configParams.getCellHeight();
351 cfg.walkableHeight = _configParams.base_getWalkableHeight();
352 cfg.walkableClimb = _configParams.base_getWalkableClimb();
353 cfg.walkableRadius = _configParams.base_getWalkableRadius();
354 cfg.walkableSlopeAngle = _configParams.getAgentMaxSlope();
355 cfg.borderSize =
357 cfg.detailSampleDist = _configParams.getDetailSampleDist();
358 cfg.detailSampleMaxError = _configParams.getDetailSampleMaxError();
359 cfg.maxEdgeLen = _configParams.getEdgeMaxLen();
360 cfg.maxSimplificationError = _configParams.getEdgeMaxError();
361 cfg.maxVertsPerPoly = _configParams.getVertsPerPoly();
362 cfg.minRegionArea = _configParams.getRegionMinSize();
363 cfg.mergeRegionArea = _configParams.getRegionMergeSize();
364 cfg.tileSize = _configParams.getTileSize();
365
367 rcCalcBounds( data.getVerts(), data.getVertCount(), cfg.bmin, cfg.bmax );
368 rcCalcGridSize( cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height );
369 Console::printfn( LOCALE_STR( "NAV_MESH_BOUNDS" ), cfg.bmax[0], cfg.bmax[1],
370 cfg.bmax[2], cfg.bmin[0], cfg.bmin[1], cfg.bmin[2] );
371
372 _extents = vec3<F32>( cfg.bmax[0] - cfg.bmin[0], cfg.bmax[1] - cfg.bmin[1],
373 cfg.bmax[2] - cfg.bmin[2] );
374
375 if ( !createPolyMesh( cfg, data, &ctx ) )
376 {
377 data.valid( false );
378 return false;
379 }
380
381 // Detour initialisation data
382 dtNavMeshCreateParams params;
383 memset( &params, 0, sizeof params );
384 rcVcopy( params.bmax, cfg.bmax );
385 rcVcopy( params.bmin, cfg.bmin );
386
387 params.ch = cfg.ch;
388 params.cs = cfg.cs;
389 params.walkableHeight = to_F32( cfg.walkableHeight );
390 params.walkableRadius = to_F32( cfg.walkableRadius );
391 params.walkableClimb = to_F32( cfg.walkableClimb );
392
393 params.tileX = 0;
394 params.tileY = 0;
395 params.tileLayer = 0;
396 params.buildBvTree = true;
397
398 params.verts = _polyMesh->verts;
399 params.vertCount = _polyMesh->nverts;
400 params.polys = _polyMesh->polys;
401 params.polyAreas = _polyMesh->areas;
402 params.polyFlags = _polyMesh->flags;
403 params.polyCount = _polyMesh->npolys;
404 params.nvp = _polyMesh->nvp;
405
406 params.detailMeshes = _polyMeshDetail->meshes;
407 params.detailVerts = _polyMeshDetail->verts;
408 params.detailVertsCount = _polyMeshDetail->nverts;
409 params.detailTris = _polyMeshDetail->tris;
410 params.detailTriCount = _polyMeshDetail->ntris;
411
412
413 if ( _navMesh )
414 {
415 dtFreeNavMesh( _navMesh );
416 }
417
418 load( _sgn );
419 if ( _navMesh == nullptr )
420 {
421 createNavigationMesh( params );
422 }
423
424 if ( _navMesh == nullptr )
425 {
426 data.valid( false );
427 return false;
428 }
429
430 data.valid( true );
431 save( _sgn );
432
433 return NavigationMeshLoader::SaveMeshFile( data, _filePath, geometrySaveFile.c_str() ); // input geometry;
434 }
435
437 {
438 _navQuery = dtAllocNavMeshQuery();
439 return _navQuery->init( _navMesh, maxNodes ) == DT_SUCCESS;
440 }
441
442 bool NavigationMesh::createPolyMesh( const rcConfig& cfg, const NavModelData& data, rcContextDivide* ctx )
443 {
444 if ( _fileName.empty() )
445 {
446 _fileName = data.name();
447 }
448
449 // Create a heightfield to voxelise our input geometry
450 _heightField = rcAllocHeightfield();
451
452 if ( !_heightField )
453 {
454 Console::errorfn( LOCALE_STR( "ERROR_NAV_OUT_OF_MEMORY" ), "rcAllocHeightfield", _fileName.c_str() );
455 return false;
456 }
457
458 // Reset build times gathering.
459 ctx->resetTimers();
460 // Start the build process.
461 ctx->startTimer( RC_TIMER_TOTAL );
462 ctx->log( RC_LOG_PROGRESS, "Building navigation:" );
463 ctx->log( RC_LOG_PROGRESS, " - %d x %d cells", cfg.width, cfg.height );
464 ctx->log( RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris",
465 data.getVertCount() / 1000.0f, data.getTriCount() / 1000.0f );
466
467 if ( !rcCreateHeightfield( ctx, *_heightField, cfg.width, cfg.height,
468 cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) )
469 {
470 Console::errorfn( LOCALE_STR( "ERROR_NAV_HEIGHTFIELD" ), _fileName.c_str() );
471 return false;
472 }
473
474 U8* areas = new U8[data.getTriCount()];
475
476 if ( !areas )
477 {
478 Console::errorfn( LOCALE_STR( "ERROR_NAV_OUT_OF_MEMORY" ), "areaFlag allocation", _fileName.c_str() );
479 return false;
480 }
481
482 memset( areas, 0, data.getTriCount() * sizeof( U8 ) );
483
484 // Filter triangles by angle and rasterize
485 rcMarkWalkableTriangles( ctx, cfg.walkableSlopeAngle, data.getVerts(),
486 data.getVertCount(), data.getTris(),
487 data.getTriCount(), areas );
488
489 rcRasterizeTriangles( ctx, data.getVerts(), data.getVertCount(),
490 data.getTris(), areas, data.getTriCount(),
491 *_heightField, cfg.walkableClimb );
492
493 if ( !_saveIntermediates )
494 {
495 delete[] areas;
496 }
497
498 // Filter out areas with low ceilings and other stuff
499 rcFilterLowHangingWalkableObstacles( ctx, cfg.walkableClimb, *_heightField );
500
501 rcFilterLedgeSpans( ctx, cfg.walkableHeight, cfg.walkableClimb,
502 *_heightField );
503
504 rcFilterWalkableLowHeightSpans( ctx, cfg.walkableHeight, *_heightField );
505
506 _compactHeightField = rcAllocCompactHeightfield();
507
508 if ( !_compactHeightField ||
509 !rcBuildCompactHeightfield( ctx, cfg.walkableHeight, cfg.walkableClimb,
511 {
512 Console::errorfn( LOCALE_STR( "ERROR_NAV_COMPACT_HEIGHTFIELD" ), _fileName.c_str() );
513 return false;
514 }
515
516 if ( !rcErodeWalkableArea( ctx, cfg.walkableRadius, *_compactHeightField ) )
517 {
518 Console::errorfn( LOCALE_STR( "ERROR_NAV_WALKABLE" ), _fileName.c_str() );
519 return false;
520 }
521
522 if constexpr( false )
523 {
524 if ( !rcBuildRegionsMonotone( ctx, *_compactHeightField, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea ) )
525 {
526 Console::errorfn( LOCALE_STR( "ERROR_NAV_REGIONS" ), _fileName.c_str() );
527 return false;
528 }
529 }
530 else
531 {
532 if ( !rcBuildDistanceField( ctx, *_compactHeightField ) )
533 {
534 return false;
535 }
536
537 if ( !rcBuildRegions( ctx, *_compactHeightField, cfg.borderSize,
538 cfg.minRegionArea, cfg.mergeRegionArea ) )
539 {
540 return false;
541 }
542 }
543
544 _countourSet = rcAllocContourSet();
545 if ( !_countourSet ||
546 !rcBuildContours( ctx, *_compactHeightField, cfg.maxSimplificationError,
547 cfg.maxEdgeLen, *_countourSet ) )
548 {
549 Console::errorfn( LOCALE_STR( "ERROR_NAV_COUNTOUR" ), _fileName.c_str() );
550 return false;
551 }
552
553 _polyMesh = rcAllocPolyMesh();
554 if ( !_polyMesh ||
555 !rcBuildPolyMesh( ctx, *_countourSet, cfg.maxVertsPerPoly, *_polyMesh ) )
556 {
557 Console::errorfn( LOCALE_STR( "ERROR_NAV_POLY_MESH" ), _fileName.c_str() );
558 return false;
559 }
560
561 _polyMeshDetail = rcAllocPolyMeshDetail();
562 if ( !_polyMeshDetail ||
563 !rcBuildPolyMeshDetail( ctx, *_polyMesh, *_compactHeightField,
564 cfg.detailSampleDist, cfg.detailSampleMaxError,
565 *_polyMeshDetail ) )
566 {
567 Console::errorfn( LOCALE_STR( "ERROR_NAV_POLY_MESH_DETAIL" ), _fileName.c_str() );
568 return false;
569 }
570
571 // Show performance stats.
572 ctx->stopTimer( RC_TIMER_TOTAL );
573 duLogBuildTimes( *ctx, ctx->getAccumulatedTime( RC_TIMER_TOTAL ) );
574 ctx->log( RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons",
575 _polyMesh->nverts, _polyMesh->npolys );
576
578 "[RC_LOG_PROGRESS] Polymesh: %d vertices %d polygons %5.2f ms\n",
579 _polyMesh->nverts, _polyMesh->npolys,
580 to_F32( ctx->getAccumulatedTime( RC_TIMER_TOTAL ) / 1000.0f ) );
581
582 return true;
583 }
584
585 bool NavigationMesh::createNavigationMesh( dtNavMeshCreateParams& params )
586 {
587
588 U8* tileData = nullptr;
589 I32 tileDataSize = 0;
590 if ( !dtCreateNavMeshData( &params, &tileData, &tileDataSize ) )
591 {
592 Console::errorfn( LOCALE_STR( "ERROR_NAV_MESH_DATA" ), _fileName.c_str() );
593 return false;
594 }
595
596 _tempNavMesh = dtAllocNavMesh();
597 if ( !_tempNavMesh )
598 {
599 Console::errorfn( LOCALE_STR( "ERROR_NAV_DT_OUT_OF_MEMORY" ), _fileName.c_str() );
600 return false;
601 }
602
603 const dtStatus s = _tempNavMesh->init( tileData, tileDataSize, DT_TILE_FREE_DATA );
604 if ( dtStatusFailed( s ) )
605 {
606 Console::errorfn( LOCALE_STR( "ERROR_NAV_DT_INIT" ), _fileName.c_str() );
607 return false;
608 }
609
610 // Initialise all flags to something helpful.
611 for ( U32 i = 0; i < to_U32( _tempNavMesh->getMaxTiles() ); ++i )
612 {
613 const dtMeshTile* tile = ((const dtNavMesh*)_tempNavMesh)->getTile( i );
614
615 if ( !tile->header )
616 {
617 continue;
618 }
619
620 const dtPolyRef base = _tempNavMesh->getPolyRefBase( tile );
621
622 for ( U32 j = 0; j < to_U32( tile->header->polyCount ); ++j )
623 {
624 const dtPolyRef ref = base | j;
625 U16 f = 0;
626 _tempNavMesh->getPolyFlags( ref, &f );
627 _tempNavMesh->setPolyFlags( ref, f | 1 );
628 }
629 }
630
631 return true;
632 }
633
634 void NavigationMesh::draw( const bool force, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
635 {
636 _debugDrawInterface->paused( !_debugDraw && !force );
637
638 RenderMode mode = _renderMode;
639
640 if ( _building )
641 {
643 _debugDrawInterface->overrideColour( duRGBA( 255, 0, 0, 80 ) );
644 }
645
646 _debugDrawInterface->beginBatch();
647
648 {
650
651 switch ( mode )
652 {
654 if ( _navMesh )
655 {
656 duDebugDrawNavMesh( _debugDrawInterface.get(), *_navMesh, 0 );
657 }
658 break;
660 if ( _countourSet )
661 {
662 duDebugDrawContours( _debugDrawInterface.get(), *_countourSet );
663 }
664 break;
666 if ( _polyMesh )
667 {
668 duDebugDrawPolyMesh( _debugDrawInterface.get(), *_polyMesh );
669 }
670 break;
672 if ( _polyMeshDetail )
673 {
674 duDebugDrawPolyMeshDetail( _debugDrawInterface.get(), *_polyMeshDetail );
675 }
676 break;
678 if ( _navMesh )
679 {
680 duDebugDrawNavMeshPortals( _debugDrawInterface.get(), *_navMesh );
681 }
682 break;
683 }
684
685 if ( !_building )
686 {
688 {
689 duDebugDrawRegionConnections( _debugDrawInterface.get(), *_countourSet );
690 }
691 }
692
693 }
694
695 _debugDrawInterface->endBatch();
696
697 _debugDrawInterface->toCommandBuffer( bufferInOut, memCmdInOut );
698 }
699
700
702 {
703 if ( !_fileName.length() )
704 {
705 return false;
706 }
707
708
709 const Str<256> nodeName = GenerateMeshName( sgn );
710
711 // Parse objects from level into RC-compatible format
712 const ResourcePath file{ Util::StringFormat("{}{}.nm", _filePath, nodeName ) };
713
714 // Parse objects from level into RC-compatible format
715 const string sourceFile = (_filePath / file).string();
716
717 FILE* fp = fopen( sourceFile.c_str(), "rb" );
718 if ( !fp )
719 {
720 return false;
721 }
722 // Read header.
723 NavMeshSetHeader header;
724 fread( &header, sizeof( NavMeshSetHeader ), 1, fp );
725
726 if ( header.magic != NAVMESHSET_MAGIC )
727 {
728 fclose( fp );
729 return false;
730 }
731
732 if ( header.version != NAVMESHSET_VERSION )
733 {
734 fclose( fp );
735 return false;
736 }
737
738 std::lock_guard<Mutex> lock( _navigationMeshLock );
739 dtNavMesh* temp = dtAllocNavMesh();
740
741 if ( !temp )
742 {
743 fclose( fp );
744 return false;
745 }
746
747 const dtStatus status = temp->init( &header.params );
748
749 if ( dtStatusFailed( status ) )
750 {
751 fclose( fp );
752 return false;
753 }
754
755 // Read tiles.
756 for ( U32 i = 0; i < to_U32( header.numTiles ); ++i )
757 {
758 NavMeshTileHeader tileHeader;
759 fread( &tileHeader, sizeof tileHeader, 1, fp );
760 if ( !tileHeader.tileRef || !tileHeader.dataSize )
761 {
762 return false; // break;
763 }
764 U8* data = (U8*)dtAlloc( tileHeader.dataSize, DT_ALLOC_PERM );
765
766 if ( !data )
767 {
768 return false; // break;
769 }
770
771 memset( data, 0, tileHeader.dataSize );
772 fread( data, tileHeader.dataSize, 1, fp );
773
774 temp->addTile( data, tileHeader.dataSize, DT_TILE_FREE_DATA,
775 tileHeader.tileRef, nullptr );
776 }
777 fclose( fp );
778
779 _extents.set( header.extents[0], header.extents[1], header.extents[2] );
780 _navMesh = temp;
781 return createNavigationQuery();
782 }
783
785 {
786 if ( !_fileName.length() || !_navMesh )
787 {
788 return false;
789 }
790
791 const Str<256> nodeName = GenerateMeshName( sgn );
792
793 // Parse objects from level into RC-compatible format
794 const ResourcePath file{ Util::StringFormat( "{}{}.nm", _filePath, nodeName.c_str() ) };
795
796 // Save our NavigationMesh into a file to load from next time
797 const string sourceFile = (_filePath / file).string();
798 FILE* fp = fopen( sourceFile.c_str(), "wb" );
799 if ( !fp )
800 {
801 return false;
802 }
803
804 std::lock_guard<Mutex> lock( _navigationMeshLock );
805
806 // Store header.
807 NavMeshSetHeader header;
808 memcpy( header.extents, &_extents[0], sizeof( F32 ) * 3 );
809
810 header.magic = NAVMESHSET_MAGIC;
812 header.numTiles = 0;
813
814 for ( U32 i = 0; i < to_U32( _navMesh->getMaxTiles() ); ++i )
815 {
816 const dtMeshTile* tile = ((const dtNavMesh*)_navMesh)->getTile( i );
817
818 if ( !tile || !tile->header || !tile->dataSize )
819 {
820 continue;
821 }
822 header.numTiles++;
823 }
824
825 memcpy( &header.params, _navMesh->getParams(), sizeof( dtNavMeshParams ) );
826 fwrite( &header, sizeof( NavMeshSetHeader ), 1, fp );
827
828 // Store tiles.
829 for ( U32 i = 0; i < to_U32( _navMesh->getMaxTiles() ); ++i )
830 {
831 const dtMeshTile* tile = ((const dtNavMesh*)_navMesh)->getTile( i );
832
833 if ( !tile || !tile->header || !tile->dataSize )
834 {
835 continue;
836 }
837
838 NavMeshTileHeader tileHeader;
839 tileHeader.tileRef = _navMesh->getTileRef( tile );
840 tileHeader.dataSize = tile->dataSize;
841
842 fwrite( &tileHeader, sizeof tileHeader, 1, fp );
843 fwrite( tile->data, tile->dataSize, 1, fp );
844 }
845
846 fclose( fp );
847
848 return true;
849 }
850
852 {
853 return sgn->parent() != nullptr
854 ? Str<256>(Util::StringFormat("_node_[_{}_]", sgn->name() ) )
855 : Str<256>( "_root_node" );
856 }
857
859 const vec3<F32>& extents,
860 const F32 delta,
861 vec3<F32>& result ) const
862 {
863 dtPolyRef resultPoly;
864 return _recastInterface.findNearestPointOnNavmesh( *this, destination, extents, delta, result, resultPoly );
865 }
866
868 {
869 return _recastInterface.getRandomNavMeshPoint( *this, result );
870 }
871
873 const F32 radius,
874 const vec3<F32>& extents,
875 vec3<F32>& result,
876 const U8 maxIters ) const
877 {
878 return _recastInterface.getRandomPointAroundCircle( *this, center, radius, extents, result, maxIters );
879 }
880
881} // namespace Divide::AI::Navigation
#define LOCALE_STR(X)
Definition: Localization.h:91
#define MOV(...)
#define DIVIDE_ASSERT(...)
int _stricmp(const char *str1, const char *str2)
#define PROFILE_SCOPE_AUTO(CATEGORY)
Definition: Profiler.h:87
bool getRandomNavMeshPoint(const NavigationMesh &navMesh, vec3< F32 > &resultPt) const
bool findNearestPointOnNavmesh(const NavigationMesh &navMesh, const vec3< F32 > &position, const vec3< F32 > &extents, F32 delta, vec3< F32 > &resultPt, dtPolyRef &resultPoly) const
bool getRandomPointAroundCircle(const NavigationMesh &navMesh, const vec3< F32 > &centerPosition, F32 radius, const vec3< F32 > &extents, vec3< F32 > &resultPt, U8 maxIters) const
const F32 * getVerts() const noexcept
Definition: NavMeshLoader.h:86
const I32 * getTris() const noexcept
Definition: NavMeshLoader.h:88
NavigationMeshConfig _configParams
Definition: NavMesh.h:192
bool save(const SceneGraphNode *sgn)
Save the NavigationMesh to a file.
Definition: NavMesh.cpp:784
void draw(bool force, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
Render the debug mesh if debug drawing is enabled.
Definition: NavMesh.cpp:634
Mutex _navigationMeshLock
A mutex for accessing our actual NavigationMesh.
Definition: NavMesh.h:214
bool unload()
Unload the navmesh reverting the instance to an empty container.
Definition: NavMesh.cpp:56
DELEGATE< void, NavigationMesh * > CreationCallback
Definition: NavMesh.h:126
NavigationMesh(PlatformContext &context, DivideRecast &recastInterface, Scene &parentScene)
Definition: NavMesh.cpp:25
bool loadConfigFromFile()
Load nav mesh configuration from file.
Definition: NavMesh.cpp:123
static Str< 256 > GenerateMeshName(const SceneGraphNode *sgn)
Create a unique mesh name using the given root node.
Definition: NavMesh.cpp:851
bool build(SceneGraphNode *sgn, CreationCallback creationCompleteCallback, bool threaded=true)
Definition: NavMesh.cpp:174
I64 _buildJobGUID
A thread for us to update in.
Definition: NavMesh.h:212
std::atomic_bool _building
A simple flag to say we are building.
Definition: NavMesh.h:216
CreationCallback _loadCompleteClbk
A callback function to call after building is complete.
Definition: NavMesh.h:218
void buildInternal()
Used for multithreaded loading.
Definition: NavMesh.cpp:211
bool getClosestPosition(const vec3< F32 > &destination, const vec3< F32 > &extents, F32 delta, vec3< F32 > &result) const
Definition: NavMesh.cpp:858
bool getRandomPosition(vec3< F32 > &result) const
Definition: NavMesh.cpp:867
bool createPolyMesh(const rcConfig &cfg, const NavModelData &data, rcContextDivide *ctx)
Performs the Recast part of the build process.
Definition: NavMesh.cpp:442
SceneGraphNode * _sgn
SceneGraphNode from which to build.
Definition: NavMesh.h:229
std::unique_ptr< NavMeshDebugDraw > _debugDrawInterface
DebugDraw interface.
Definition: NavMesh.h:234
rcCompactHeightfield * _compactHeightField
Definition: NavMesh.h:200
dtNavMeshQuery * _navQuery
Query object used for this mesh.
Definition: NavMesh.h:227
vec3< F32 > _extents
NavMesh extents.
Definition: NavMesh.h:225
string _configFile
Configuration file.
Definition: NavMesh.h:223
bool createNavigationMesh(dtNavMeshCreateParams &params)
Performs the Detour part of the build process.
Definition: NavMesh.cpp:585
void stopThreadedBuild()
Stop the threaded build process;.
Definition: NavMesh.cpp:75
void freeIntermediates(bool freeAll)
Definition: NavMesh.cpp:85
bool createNavigationQuery(U32 maxNodes=2048)
Create a navigation mesh query to help in pathfinding.
Definition: NavMesh.cpp:436
Str< 256 > _fileName
Data file to store this nav mesh in.
Definition: NavMesh.h:220
bool load(const SceneGraphNode *sgn)
Load a saved NavigationMesh from a file.
Definition: NavMesh.cpp:701
bool buildThreaded()
Initiates the build process in a separate thread.
Definition: NavMesh.cpp:197
bool getRandomPositionInCircle(const vec3< F32 > &center, F32 radius, const vec3< F32 > &extents, vec3< F32 > &result, U8 maxIters=15) const
Definition: NavMesh.cpp:872
rcPolyMeshDetail * _polyMeshDetail
Definition: NavMesh.h:203
void setDetailSampleMaxError(const F32 detailSampleMaxError)
void setAgentRadius(const F32 agentRadius)
Definition: NavMeshConfig.h:76
I32 base_getWalkableHeight() const noexcept
I32 base_getWalkableClimb() const noexcept
I32 base_getWalkableRadius() const noexcept
F32 getEdgeMaxError() const noexcept
F32 getCellSize() const noexcept
void setAgentMaxSlope(const F32 agentMaxSlope) noexcept
Definition: NavMeshConfig.h:86
void setTileSize(const I32 tileSize) noexcept
Definition: NavMeshConfig.h:64
I32 getRegionMinSize() const noexcept
void setEdgeMaxError(const F32 edgeMaxError) noexcept
void setCellSize(const F32 cellSize)
Definition: NavMeshConfig.h:54
void setAgentMaxClimb(const F32 agentMaxClimb)
Definition: NavMeshConfig.h:81
F32 getDetailSampleMaxError() const noexcept
I32 getTileSize() const noexcept
F32 getCellHeight() const noexcept
void setDetailSampleDist(const F32 detailSampleDist)
void setKeepInterResults(const bool keepInterResults) noexcept
void setEdgeMaxLen(const I32 edgeMaxLength)
void setVertsPerPoly(const I32 vertsPerPoly) noexcept
F32 getDetailSampleDist() const noexcept
bool getKeepInterResults() const noexcept
F32 getAgentMaxSlope() const noexcept
I32 getRegionMergeSize() const noexcept
void setAgentHeight(const F32 agentHeight)
Definition: NavMeshConfig.h:71
I32 getVertsPerPoly() const noexcept
I32 getEdgeMaxLen() const noexcept
void setRegionMergeSize(const I32 regionMergeSize)
Definition: NavMeshConfig.h:98
void setRegionMinSize(const I32 regionMinSize)
Definition: NavMeshConfig.h:93
void setCellHeight(const F32 cellHeight)
Definition: NavMeshConfig.h:59
const BoundingBox & getBoundingBox() const noexcept
Utility class that adds basic GUID management to objects.
Definition: GUIDWrapper.h:44
TaskPool & taskPool(const TaskPoolType type) noexcept
FORCE_INLINE T * get() const
Returns a pointer to a specific component. Returns null if the SGN does not have the component reques...
static ResourcePath GetSceneFullPath(const Scene &scene)
Return the full path to the scene's location on disk. It's equivalent to GetSceneRootFolder(scene....
Definition: Scene.cpp:123
void set(const T *v) noexcept
set the 3 components of the vector manually using a source pointer to a (large enough) array
Definition: MathVectors.h:707
bool SaveMeshFile(const NavModelData &inData, const ResourcePath &filePath, const char *filename)
Save the navigation input geometry in Wavefront OBJ format.
bool LoadMeshFile(NavModelData &outData, const ResourcePath &filePath, const char *fileName)
Load the input geometry from file (Wavefront OBJ format) and save it in 'outData'.
bool Parse(const BoundingBox &box, NavModelData &outData, SceneGraphNode *sgn)
Parsing method that calls itself recursively until all geometry has been parsed.
constexpr I32 NAVMESHSET_MAGIC
Definition: NavMesh.h:84
constexpr I32 NAVMESHSET_VERSION
Definition: NavMesh.h:86
constexpr F32 BORDER_PADDING
constexpr Optick::Category::Type Streaming
Definition: Profiler.h:65
Str StringFormat(const char *fmt, Args &&...args)
bool ReplaceStringInPlace(T_str &subject, std::span< const std::string_view > search, std::string_view replace, bool recursive=false)
bool load() override
std::lock_guard< mutex > LockGuard
Definition: SharedMutex.h:55
constexpr U32 to_U32(const T value)
void Wait(const Task &task, TaskPool &pool)
Definition: Task.cpp:20
int32_t I32
uint8_t U8
Task * CreateTask(Predicate &&threadedFunction, bool allowedInIdle=true)
Definition: TaskPool.inl:45
constexpr F32 to_F32(const T value)
uint16_t U16
void Start(Task &task, TaskPool &pool, TaskPriority priority=TaskPriority::DONT_CARE, const DELEGATE< void > &onCompletionFunction={})
Definition: Task.cpp:9
uint32_t U32
static NO_INLINE void errorfn(const char *format, T &&... args)
static NO_INLINE void printfn(const char *format, T &&... args)