// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "NavMesh.h" #include "Engine/Core/Log.h" #include "NavigationScene.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Threading/Threading.h" #include #include #define MAX_NODES 2048 #define USE_DATA_LINK 0 #define USE_NAV_MESH_ALLOC 1 NavMesh::NavMesh() { _navMesh = nullptr; _navMeshQuery = dtAllocNavMeshQuery(); _tileSize = 0; } NavMesh::~NavMesh() { dtFreeNavMesh(_navMesh); dtFreeNavMeshQuery(_navMeshQuery); } int32 NavMesh::GetTilesCapacity() const { return _navMesh ? _navMesh->getMaxTiles() : 0; } void NavMesh::SetTileSize(float tileSize) { ScopeLock lock(Locker); // Skip if the same or invalid if (Math::NearEqual(_tileSize, tileSize) || tileSize < 1) return; // Dispose the existing mesh (its invalid) if (_navMesh) { dtFreeNavMesh(_navMesh); _navMesh = nullptr; _tiles.Clear(); } _tileSize = tileSize; } void NavMesh::EnsureCapacity(int32 tilesToAddCount) { ScopeLock lock(Locker); const int32 newTilesCount = _tiles.Count() + tilesToAddCount; const int32 capacity = GetTilesCapacity(); if (newTilesCount <= capacity) return; PROFILE_CPU_NAMED("NavMesh.EnsureCapacity"); // Navmesh tiles capacity growing rule int32 newCapacity = 0; if (capacity) { while (newCapacity < newTilesCount) { newCapacity = Math::RoundUpToPowerOf2(newCapacity); } } else { newCapacity = 32; } LOG(Info, "Resizing navmesh from {0} to {1} tiles capacity", capacity, newCapacity); // Ensure to have size assigned ASSERT(_tileSize != 0); // Free previous data (if any) if (_navMesh) { dtFreeNavMesh(_navMesh); } // Allocate new navmesh _navMesh = dtAllocNavMesh(); if (dtStatusFailed(_navMeshQuery->init(_navMesh, MAX_NODES))) { LOG(Fatal, "Failed to initialize nav mesh."); } // Prepare parameters dtNavMeshParams params; params.orig[0] = 0.0f; params.orig[1] = 0.0f; params.orig[2] = 0.0f; params.tileWidth = _tileSize; params.tileHeight = _tileSize; params.maxTiles = newCapacity; const int32 tilesBits = (int32)Math::Log2((float)Math::RoundUpToPowerOf2(params.maxTiles)); params.maxPolys = 1 << (22 - tilesBits); // Initialize nav mesh if (dtStatusFailed(_navMesh->init(¶ms))) { LOG(Fatal, "Navmesh init failed."); return; } // Prepare tiles container _tiles.EnsureCapacity(newCapacity); // Restore previous tiles for (auto& tile : _tiles) { const int32 dataSize = tile.Data.Length(); #if USE_NAV_MESH_ALLOC const auto flags = DT_TILE_FREE_DATA; const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); Platform::MemoryCopy(data, tile.Data.Get(), dataSize); #else const auto flags = 0; const auto data = tile.Data.Get(); #endif if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) { LOG(Warning, "Could not add tile to navmesh."); } } } void NavMesh::AddTiles(NavigationScene* scene) { // Skip if no data ASSERT(scene); if (scene->Data.Tiles.IsEmpty()) return; auto& data = scene->Data; PROFILE_CPU_NAMED("NavMesh.AddTiles"); ScopeLock lock(Locker); // Validate data (must match navmesh) or init navmesh to match the tiles options if (_navMesh) { if (Math::NotNearEqual(data.TileSize, _tileSize)) { LOG(Warning, "Cannot add navigation scene tiles to the navmesh. Navmesh tile size: {0}, input tiles size: {1}", _tileSize, data.TileSize); return; } } else { _tileSize = data.TileSize; } // Ensure to have space for new tiles EnsureCapacity(data.Tiles.Count()); // Add new tiles for (auto& tileData : data.Tiles) { AddTileInternal(scene, tileData); } } void NavMesh::AddTile(NavigationScene* scene, NavMeshTileData& tileData) { ASSERT(scene); auto& data = scene->Data; PROFILE_CPU_NAMED("NavMesh.AddTile"); ScopeLock lock(Locker); // Validate data (must match navmesh) or init navmesh to match the tiles options if (_navMesh) { if (Math::NotNearEqual(data.TileSize, _tileSize)) { LOG(Warning, "Cannot add navigation scene tile to the navmesh. Navmesh tile size: {0}, input tile size: {1}", _tileSize, data.TileSize); return; } } else { _tileSize = data.TileSize; } // Ensure to have space for new tile EnsureCapacity(1); // Add new tile AddTileInternal(scene, tileData); } bool IsTileFromScene(const NavMesh* navMesh, const NavMeshTile& tile, void* customData) { return tile.Scene == (NavigationScene*)customData; } void NavMesh::RemoveTiles(NavigationScene* scene) { RemoveTiles(IsTileFromScene, scene); } void NavMesh::RemoveTile(int32 x, int32 y, int32 layer) { ScopeLock lock(Locker); // Skip if no data if (!_navMesh) return; PROFILE_CPU_NAMED("NavMesh.RemoveTile"); const auto tileRef = _navMesh->getTileRefAt(x, y, layer); if (tileRef == 0) { return; } if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { LOG(Warning, "Failed to remove tile from navmesh."); } for (int32 i = 0; i < _tiles.Count(); i++) { auto& tile = _tiles[i]; if (tile.X == x && tile.Y == y && tile.Layer == layer) { _tiles.RemoveAt(i); break; } } } void NavMesh::RemoveTiles(bool (* prediction)(const NavMesh* navMesh, const NavMeshTile& tile, void* customData), void* userData) { ScopeLock lock(Locker); // Skip if no data ASSERT(prediction); if (!_navMesh) return; PROFILE_CPU_NAMED("NavMesh.RemoveTiles"); for (int32 i = 0; i < _tiles.Count(); i++) { auto& tile = _tiles[i]; if (prediction(this, tile, userData)) { const auto tileRef = _navMesh->getTileRefAt(tile.X, tile.Y, tile.Layer); if (tileRef == 0) { LOG(Warning, "Missing navmesh tile at {0}x{1}, layer: {2}", tile.X, tile.Y, tile.Layer); } else { if (dtStatusFailed(_navMesh->removeTile(tileRef, nullptr, nullptr))) { LOG(Warning, "Failed to remove tile from navmesh."); } } _tiles.RemoveAt(i--); } } } void NavMesh::Dispose() { if (_navMesh) { dtFreeNavMesh(_navMesh); _navMesh = nullptr; } _tiles.Resize(0); } void NavMesh::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData) { // Check if that tile has been added to navmesh NavMeshTile* tile = nullptr; const auto tileRef = _navMesh->getTileRefAt(tileData.PosX, tileData.PosY, tileData.Layer); if (tileRef) { // Remove any existing tile at that location _navMesh->removeTile(tileRef, nullptr, nullptr); // Reuse tile data container for (int32 i = 0; i < _tiles.Count(); i++) { auto& e = _tiles[i]; if (e.X == tileData.PosX && e.Y == tileData.PosY && e.Layer == tileData.Layer) { tile = &e; break; } } } else { // Add tile tile = &_tiles.AddOne(); } ASSERT(tile); // Copy tile properties tile->Scene = scene; tile->X = tileData.PosX; tile->Y = tileData.PosY; tile->Layer = tileData.Layer; #if USE_DATA_LINK tile->Data.Link(tileData.Data); #else tile->Data.Copy(tileData.Data); #endif // Add tile to navmesh const int32 dataSize = tile->Data.Length(); #if USE_NAV_MESH_ALLOC const auto flags = DT_TILE_FREE_DATA; const auto data = (byte*)dtAlloc(dataSize, DT_ALLOC_PERM); Platform::MemoryCopy(data, tile->Data.Get(), dataSize); #else const auto flags = 0; const auto data = tile->Data.Get(); #endif if (dtStatusFailed(_navMesh->addTile(data, dataSize, flags, 0, nullptr))) { LOG(Warning, "Could not add tile to navmesh."); } }