Add support for multiple navmeshes on a scene

This commit is contained in:
Wojtek Figat
2021-01-13 14:28:46 +01:00
parent dba43c4e9f
commit 27ed23c1b9
15 changed files with 1012 additions and 626 deletions

View File

@@ -8,7 +8,10 @@
#include "Engine/Physics/Colliders/MeshCollider.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/ActorsCache.h"
#include "Engine/Navigation/NavigationScene.h"
#include "Engine/Navigation/NavigationSettings.h"
#include "Engine/Navigation/NavMeshBoundsVolume.h"
#include "Engine/Navigation/NavMesh.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/Serialization.h"
REGISTER_JSON_ASSET(SceneAsset, "FlaxEngine.SceneAsset");
@@ -27,7 +30,6 @@ Scene::Scene(const SpawnParams& params)
, Ticking(this)
, LightmapsData(this)
, CSGData(this)
, Navigation(::New<NavigationScene>(this))
{
// Default name
_name = TEXT("Scene");
@@ -42,7 +44,6 @@ Scene::Scene(const SpawnParams& params)
Scene::~Scene()
{
Delete(Navigation);
}
LightmapSettings Scene::GetLightmapSettings() const
@@ -55,6 +56,31 @@ void Scene::SetLightmapSettings(const LightmapSettings& value)
Info.LightmapSettings = value;
}
BoundingBox Scene::GetNavigationBounds()
{
if (NavigationVolumes.IsEmpty())
return BoundingBox::Empty;
PROFILE_CPU_NAMED("GetNavigationBounds");
auto box = NavigationVolumes[0]->GetBox();
for (int32 i = 1; i < NavigationVolumes.Count(); i++)
BoundingBox::Merge(box, NavigationVolumes[i]->GetBox(), box);
return box;
}
NavMeshBoundsVolume* Scene::FindNavigationBoundsOverlap(const BoundingBox& bounds)
{
NavMeshBoundsVolume* result = nullptr;
for (int32 i = 0; i < NavigationVolumes.Count(); i++)
{
if (NavigationVolumes[i]->GetBox().Intersects(bounds))
{
result = NavigationVolumes[i];
break;
}
}
return result;
}
void Scene::ClearLightmaps()
{
LightmapsData.ClearLightmaps();
@@ -216,12 +242,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj)
// Update scene info object
SaveTime = DateTime::NowUTC();
#if USE_EDITOR
// Save navmesh tiles to asset (if modified)
if (Navigation->IsDataDirty)
Navigation->SaveNavMesh();
#endif
LightmapsData.SaveLightmaps(Info.Lightmaps);
Info.Serialize(stream, other ? &other->Info : nullptr);
@@ -230,8 +250,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj)
stream.JKEY("CSG");
stream.Object(&CSGData, other ? &other->CSGData : nullptr);
}
SERIALIZE_MEMBER(NavMesh, Navigation->DataAsset);
}
void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -243,7 +261,37 @@ void Scene::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
LightmapsData.LoadLightmaps(Info.Lightmaps);
CSGData.DeserializeIfExists(stream, "CSG", modifier);
DESERIALIZE_MEMBER(NavMesh, Navigation->DataAsset);
// [Deprecated on 13.01.2021, expires on 13.01.2023]
if (modifier->EngineBuild <= 6215 && NavigationMeshes.IsEmpty())
{
const auto e = SERIALIZE_FIND_MEMBER(stream, "NavMesh");
if (e != stream.MemberEnd())
{
// Upgrade from old single hidden navmesh data into NavMesh actors on a scene
AssetReference<RawDataAsset> dataAsset;
Serialization::Deserialize(e->value, dataAsset, modifier);
const auto settings = NavigationSettings::Get();
if (dataAsset && settings->NavMeshes.HasItems())
{
auto navMesh = New<NavMesh>();
navMesh->SetStaticFlags(StaticFlags::FullyStatic);
navMesh->SetName(TEXT("NavMesh.") + settings->NavMeshes[0].Name);
navMesh->DataAsset = dataAsset;
navMesh->Properties = settings->NavMeshes[0];
if (IsDuringPlay())
{
navMesh->SetParent(this, false);
}
else
{
navMesh->_parent = this;
navMesh->_scene = this;
Children.Add(navMesh);
navMesh->CreateManaged();
}
}
}
}
}
void Scene::OnDeleteObject()
@@ -309,22 +357,6 @@ void Scene::EndPlay()
Actor::EndPlay();
}
void Scene::OnEnable()
{
// Base
Actor::OnEnable();
Navigation->OnEnable();
}
void Scene::OnDisable()
{
Navigation->OnDisable();
// Base
Actor::OnDisable();
}
void Scene::OnTransformChanged()
{
// Base

View File

@@ -12,8 +12,9 @@
class MeshCollider;
class Level;
class NavigationScene;
class ReloadScriptsAction;
class NavMeshBoundsVolume;
class NavMesh;
/// <summary>
/// The scene root object that contains a hierarchy of actors.
@@ -69,11 +70,6 @@ public:
/// </summary>
CSG::SceneCSGData CSGData;
/// <summary>
/// The navigation scene (always valid).
/// </summary>
NavigationScene* Navigation;
/// <summary>
/// Gets the lightmap settings (per scene).
/// </summary>
@@ -85,6 +81,31 @@ public:
/// </summary>
API_PROPERTY() void SetLightmapSettings(const LightmapSettings& value);
public:
/// <summary>
/// The list of registered navigation bounds volumes (in the scene).
/// </summary>
Array<NavMeshBoundsVolume*> NavigationVolumes;
/// <summary>
/// The list of registered navigation meshes (in the scene).
/// </summary>
Array<NavMesh*> NavigationMeshes;
/// <summary>
/// Gets the total navigation volumes bounds.
/// </summary>
/// <returns>The navmesh bounds.</returns>
BoundingBox GetNavigationBounds();
/// <summary>
/// Finds the navigation volume bounds that have intersection with the given world-space bounding box.
/// </summary>
/// <param name="bounds">The bounds.</param>
/// <returns>The intersecting volume or null if none found.</returns>
NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds);
public:
/// <summary>
@@ -148,12 +169,10 @@ public:
protected:
// [Scene]
// [Actor]
void PostLoad() override;
void PostSpawn() override;
void BeginPlay(SceneBeginData* data) override;
void OnEnable() override;
void OnDisable() override;
void OnTransformChanged() override;
};

View File

@@ -1,61 +1,33 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "NavigationScene.h"
#include "Navigation.h"
#include "NavMesh.h"
#include "NavMeshRuntime.h"
#include "NavMeshBoundsVolume.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Content/Assets/RawDataAsset.h"
#include "Engine/Core/Log.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
#include "Engine/Serialization/Serialization.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
#endif
NavigationScene::NavigationScene(::Scene* scene)
: Scene(scene)
NavMesh::NavMesh(const SpawnParams& params)
: Actor(params)
, IsDataDirty(false)
{
DataAsset.Loaded.Bind<NavigationScene, &NavigationScene::OnDataAssetLoaded>(this);
DataAsset.Loaded.Bind<NavMesh, &NavMesh::OnDataAssetLoaded>(this);
}
BoundingBox NavigationScene::GetNavigationBounds()
{
if (Volumes.IsEmpty())
return BoundingBox::Empty;
PROFILE_CPU_NAMED("GetNavigationBounds");
auto box = Volumes[0]->GetBox();
for (int32 i = 1; i < Volumes.Count(); i++)
BoundingBox::Merge(box, Volumes[i]->GetBox(), box);
return box;
}
NavMeshBoundsVolume* NavigationScene::FindNavigationBoundsOverlap(const BoundingBox& bounds)
{
NavMeshBoundsVolume* result = nullptr;
for (int32 i = 0; i < Volumes.Count(); i++)
{
if (Volumes[i]->GetBox().Intersects(bounds))
{
result = Volumes[i];
break;
}
}
return result;
}
void NavigationScene::SaveNavMesh()
void NavMesh::SaveNavMesh()
{
#if COMPILE_WITH_ASSETS_IMPORTER
// Skip if scene is missing
const auto scene = GetScene();
if (!scene)
return;
#if USE_EDITOR
// Skip if game is running in editor (eg. game scripts update dynamic navmesh)
if (Editor::IsPlayMode)
@@ -77,7 +49,7 @@ void NavigationScene::SaveNavMesh()
Guid assetId = DataAsset.GetID();
if (!assetId.IsValid())
assetId = Guid::New();
const String assetPath = Scene->GetDataFolderPath() / TEXT("NavMesh") + ASSET_FILES_EXTENSION_WITH_DOT;
const String assetPath = scene->GetDataFolderPath() / TEXT("NavMesh") + Properties.Name + ASSET_FILES_EXTENSION_WITH_DOT;
// Generate navmesh tiles data
const int32 streamInitialCapacity = Math::RoundUpToPowerOf2((Data.Tiles.Count() + 1) * 1024);
@@ -99,32 +71,46 @@ void NavigationScene::SaveNavMesh()
#endif
}
void NavigationScene::OnEnable()
void NavMesh::ClearData()
{
auto navMesh = NavMeshRuntime::Get();
CHECK(navMesh);
if (Data.Tiles.HasItems())
{
IsDataDirty = true;
Data.TileSize = 0.0f;
Data.Tiles.Resize(0);
}
}
NavMeshRuntime* NavMesh::GetRuntime(bool createIfMissing) const
{
return NavMeshRuntime::Get(Properties, createIfMissing);
}
void NavMesh::AddTiles()
{
auto navMesh = NavMeshRuntime::Get(Properties, true);
navMesh->AddTiles(this);
}
void NavigationScene::OnDisable()
void NavMesh::RemoveTiles()
{
auto navMesh = NavMeshRuntime::Get();
auto navMesh = NavMeshRuntime::Get(Properties, false);
if (navMesh)
navMesh->RemoveTiles(this);
}
void NavigationScene::OnDataAssetLoaded()
void NavMesh::OnDataAssetLoaded()
{
// Skip if already has data (prevent reloading navmesh on saving)
if (Data.Tiles.HasItems())
return;
const bool isEnabled = Scene->IsDuringPlay() && Scene->IsActiveInHierarchy();
const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
// Remove added tiles
if (isEnabled)
{
OnDisable();
RemoveTiles();
}
// Load navmesh tiles
@@ -136,6 +122,49 @@ void NavigationScene::OnDataAssetLoaded()
// Add loaded tiles
if (isEnabled)
{
OnEnable();
AddTiles();
}
}
void NavMesh::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
Actor::Serialize(stream, otherObj);
#if USE_EDITOR
// Save navmesh tiles to asset (if modified)
if (IsDataDirty)
SaveNavMesh();
#endif
SERIALIZE_GET_OTHER_OBJ(NavMesh);
SERIALIZE(DataAsset);
SERIALIZE(Properties);
}
void NavMesh::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
Actor::Deserialize(stream, modifier);
DESERIALIZE(DataAsset);
DESERIALIZE(Properties);
}
void NavMesh::OnEnable()
{
// Base
Actor::OnEnable();
GetScene()->NavigationMeshes.Add(this);
AddTiles();
}
void NavMesh::OnDisable()
{
RemoveTiles();
GetScene()->NavigationMeshes.Remove(this);
// Base
Actor::OnDisable();
}

View File

@@ -0,0 +1,76 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "NavMeshData.h"
#include "NavigationTypes.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/Assets/RawDataAsset.h"
#include "Engine/Level/Actor.h"
class NavMeshBoundsVolume;
class NavMeshRuntime;
/// <summary>
/// The navigation mesh actor that holds a navigation data for a scene.
/// </summary>
API_CLASS() class FLAXENGINE_API NavMesh : public Actor
{
DECLARE_SCENE_OBJECT(NavMesh);
public:
/// <summary>
/// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization.
/// </summary>
bool IsDataDirty;
/// <summary>
/// The navmesh tiles data.
/// </summary>
NavMeshData Data;
/// <summary>
/// The cached navmesh data asset.
/// </summary>
AssetReference<RawDataAsset> DataAsset;
/// <summary>
/// The navigation mesh properties.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"Nav Mesh\")") NavMeshProperties Properties;
public:
/// <summary>
/// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time).
/// </summary>
void SaveNavMesh();
/// <summary>
/// Clears the data.
/// </summary>
void ClearData();
/// <summary>
/// Gets the navmesh runtime object that matches with properties.
/// </summary>
NavMeshRuntime* GetRuntime(bool createIfMissing = true) const;
private:
void AddTiles();
void RemoveTiles();
void OnDataAssetLoaded();
public:
// [Actor]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
protected:
// [Actor]
void OnEnable() override;
void OnDisable() override;
};

View File

@@ -2,7 +2,6 @@
#include "NavMeshBoundsVolume.h"
#include "Engine/Level/Scene/Scene.h"
#include "NavigationScene.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
@@ -16,15 +15,15 @@ NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params)
void NavMeshBoundsVolume::OnEnable()
{
GetScene()->Navigation->Volumes.Add(this);
// Base
Actor::OnEnable();
GetScene()->NavigationVolumes.Add(this);
}
void NavMeshBoundsVolume::OnDisable()
{
GetScene()->Navigation->Volumes.Remove(this);
GetScene()->NavigationVolumes.Remove(this);
// Base
Actor::OnDisable();

View File

@@ -3,6 +3,11 @@
#if COMPILE_WITH_NAV_MESH_BUILDER
#include "NavMeshBuilder.h"
#include "NavMesh.h"
#include "NavigationSettings.h"
#include "NavMeshBoundsVolume.h"
#include "NavLink.h"
#include "NavMeshRuntime.h"
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Math/VectorInt.h"
#include "Engine/Physics/Colliders/BoxCollider.h"
@@ -15,12 +20,6 @@
#include "Engine/Level/Level.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Core/Log.h"
#include "NavigationScene.h"
#include "NavigationSettings.h"
#include "NavMeshBoundsVolume.h"
#include "NavLink.h"
#include "Navigation.h"
#include "NavMeshRuntime.h"
#include <ThirdParty/recastnavigation/Recast.h>
#include <ThirdParty/recastnavigation/DetourNavMeshBuilder.h>
#include <ThirdParty/recastnavigation/DetourNavMesh.h>
@@ -113,11 +112,9 @@ struct NavigationSceneRasterization
{
PROFILE_CPU_NAMED("BoxCollider");
OrientedBoundingBox box = boxCollider->GetOrientedBox();
const OrientedBoundingBox box = boxCollider->GetOrientedBox();
vb.Resize(8);
box.GetCorners(vb.Get());
ib.Add(BoxTrianglesIndicesCache, 36);
e.RasterizeTriangles();
@@ -127,7 +124,7 @@ struct NavigationSceneRasterization
PROFILE_CPU_NAMED("MeshCollider");
auto collisionData = meshCollider->CollisionData.Get();
if (!collisionData || collisionData->WaitForLoaded(1000.0f))
if (!collisionData || collisionData->WaitForLoaded())
return true;
collisionData->ExtractGeometry(vb, ib);
@@ -181,7 +178,7 @@ void RasterizeGeometry(const BoundingBox& tileBounds, rcContext* context, rcConf
// Builds navmesh tile bounds and check if there are any valid navmesh volumes at that tile location
// Returns true if tile is intersecting with any navmesh bounds volume actor - which means tile is in use
bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds)
bool GetNavMeshTileBounds(Scene* scene, int32 x, int32 y, float tileSize, BoundingBox& tileBounds)
{
// Build initial tile bounds (with infinite extent)
tileBounds.Minimum.X = (float)x * tileSize;
@@ -194,9 +191,9 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi
// Check if any navmesh volume intersects with the tile
bool foundAnyVolume = false;
Vector2 rangeY;
for (int32 i = 0; i < scene->Volumes.Count(); i++)
for (int32 i = 0; i < scene->NavigationVolumes.Count(); i++)
{
const auto volume = scene->Volumes[i];
const auto volume = scene->NavigationVolumes[i];
const auto& volumeBounds = volume->GetBox();
if (volumeBounds.Intersects(tileBounds))
{
@@ -224,27 +221,27 @@ bool GetNavMeshTileBounds(NavigationScene* scene, int32 x, int32 y, float tileSi
return foundAnyVolume;
}
void RemoveTile(NavMeshRuntime* navMesh, NavigationScene* scene, int32 x, int32 y, int32 layer)
void RemoveTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, int32 layer)
{
ScopeLock lock(navMesh->Locker);
ScopeLock lock(runtime->Locker);
// Find tile data and remove it
for (int32 i = 0; i < scene->Data.Tiles.Count(); i++)
for (int32 i = 0; i < navMesh->Data.Tiles.Count(); i++)
{
auto& tile = scene->Data.Tiles[i];
auto& tile = navMesh->Data.Tiles[i];
if (tile.PosX == x && tile.PosY == y && tile.Layer == layer)
{
scene->Data.Tiles.RemoveAt(i);
scene->IsDataDirty = true;
navMesh->Data.Tiles.RemoveAt(i);
navMesh->IsDataDirty = true;
break;
}
}
// Remove tile from navmesh
navMesh->RemoveTile(x, y, layer);
runtime->RemoveTile(x, y, layer);
}
bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config)
bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, BoundingBox& tileBounds, float tileSize, rcConfig& config)
{
rcContext context;
int32 layer = 0;
@@ -355,7 +352,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou
if (polyMesh->nverts == 0)
{
// Empty tile
RemoveTile(NavMeshRuntime::Get(), scene, x, y, layer);
RemoveTile(navMesh, runtime, x, y, layer);
return false;
}
@@ -438,11 +435,11 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou
{
PROFILE_CPU_NAMED("Navigation.CreateTile");
ScopeLock lock(NavMeshRuntime::Get()->Locker);
ScopeLock lock(runtime->Locker);
// Add tile data
scene->IsDataDirty = true;
auto& tile = scene->Data.Tiles.AddOne();
navMesh->IsDataDirty = true;
auto& tile = navMesh->Data.Tiles.AddOne();
tile.PosX = x;
tile.PosY = y;
tile.Layer = layer;
@@ -451,7 +448,7 @@ bool GenerateTile(NavigationScene* scene, int32 x, int32 y, BoundingBox& tileBou
tile.Data.Copy(navData, navDataSize);
// Add tile to navmesh
NavMeshRuntime::Get()->AddTile(scene, tile);
runtime->AddTile(navMesh, tile);
}
dtFree(navData);
@@ -465,16 +462,17 @@ float GetTileSize()
return settings.CellSize * settings.TileSize;
}
void InitConfig(rcConfig& config)
void InitConfig(rcConfig& config, NavMesh* navMesh)
{
auto& settings = *NavigationSettings::Get();
auto& navMeshProperties = navMesh->Properties;
config.cs = settings.CellSize;
config.ch = settings.CellHeight;
config.walkableSlopeAngle = settings.WalkableMaxSlopeAngle;
config.walkableHeight = (int)(settings.WalkableHeight / config.ch + 0.99f);
config.walkableClimb = (int)(settings.WalkableMaxClimb / config.ch);
config.walkableRadius = (int)(settings.WalkableRadius / config.cs + 0.99f);
config.walkableSlopeAngle = navMeshProperties.Agent.MaxSlopeAngle;
config.walkableHeight = (int)(navMeshProperties.Agent.Height / config.ch + 0.99f);
config.walkableClimb = (int)(navMeshProperties.Agent.StepHeight / config.ch);
config.walkableRadius = (int)(navMeshProperties.Agent.Radius / config.cs + 0.99f);
config.maxEdgeLen = (int)(settings.MaxEdgeLen / config.cs);
config.maxSimplificationError = settings.MaxEdgeError;
config.minRegionArea = rcSqr(settings.MinRegionArea);
@@ -506,7 +504,9 @@ class NavMeshTileBuildTask : public ThreadPoolTask
{
public:
NavigationScene* Scene;
Scene* Scene;
NavMesh* NavMesh;
NavMeshRuntime* Runtime;
BoundingBox TileBounds;
int32 X;
int32 Y;
@@ -520,7 +520,7 @@ public:
{
PROFILE_CPU_NAMED("BuildNavMeshTile");
if (GenerateTile(Scene, X, Y, TileBounds, TileSize, Config))
if (GenerateTile(NavMesh, Runtime, X, Y, TileBounds, TileSize, Config))
{
LOG(Warning, "Failed to generate navmesh tile at {0}x{1}.", X, Y);
}
@@ -557,7 +557,7 @@ void OnSceneUnloading(Scene* scene, const Guid& sceneId)
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
auto task = NavBuildTasks[i];
if (task->Scene == scene->Navigation)
if (task->Scene == scene)
{
NavBuildTasksLocker.Unlock();
@@ -600,15 +600,16 @@ float NavMeshBuilder::GetNavMeshBuildingProgress()
return result;
}
void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize)
void BuildTileAsync(NavMesh* navMesh, int32 x, int32 y, rcConfig& config, const BoundingBox& tileBounds, float tileSize)
{
NavMeshRuntime* runtime = navMesh->GetRuntime();
NavBuildTasksLocker.Lock();
// Skip if this tile is already during cooking
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
const auto task = NavBuildTasks[i];
if (task->X == x && task->Y == y)
if (task->X == x && task->Y == y && task->Runtime == runtime)
{
NavBuildTasksLocker.Unlock();
return;
@@ -617,7 +618,9 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config,
// Create task
auto task = New<NavMeshTileBuildTask>();
task->Scene = scene;
task->Scene = navMesh->GetScene();
task->NavMesh = navMesh;
task->Runtime = runtime;
task->X = x;
task->Y = y;
task->TileBounds = tileBounds;
@@ -632,70 +635,10 @@ void BuildTileAsync(NavigationScene* scene, int32 x, int32 y, rcConfig& config,
task->Start();
}
void BuildWholeScene(NavigationScene* scene)
void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBounds)
{
const float tileSize = GetTileSize();
const auto navMesh = NavMeshRuntime::Get();
// Compute total navigation area bounds
const BoundingBox worldBounds = scene->GetNavigationBounds();
// Align total bounds to tile size
BoundingBox worldBoundsAligned;
worldBoundsAligned.Minimum = Vector3::Floor(worldBounds.Minimum / tileSize) * tileSize;
worldBoundsAligned.Maximum = Vector3::Ceil(worldBounds.Maximum / tileSize) * tileSize;
// Calculate tiles range for the given navigation world bounds (aligned to tiles size)
const Int3 tilesMin = Int3(worldBoundsAligned.Minimum / tileSize);
const Int3 tilesMax = Int3(worldBoundsAligned.Maximum / tileSize);
const int32 tilesX = tilesMax.X - tilesMin.X;
const int32 tilesY = tilesMax.Z - tilesMin.Z;
{
PROFILE_CPU_NAMED("Prepare");
// Prepare navmesh
navMesh->RemoveTiles(scene);
navMesh->SetTileSize(tileSize);
navMesh->EnsureCapacity(tilesX * tilesY);
// Prepare scene data
scene->Data.TileSize = tileSize;
scene->Data.Tiles.Clear();
scene->Data.Tiles.EnsureCapacity(tilesX * tilesX);
scene->IsDataDirty = true;
}
// Initialize nav mesh configuration
rcConfig config;
InitConfig(config);
// Generate all tiles that intersect with the navigation volume bounds
{
PROFILE_CPU_NAMED("StartBuildingTiles");
for (int32 y = tilesMin.Z; y < tilesMax.Z; y++)
{
for (int32 x = tilesMin.X; x < tilesMax.X; x++)
{
BoundingBox tileBounds;
if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds))
{
BuildTileAsync(scene, x, y, config, tileBounds, tileSize);
}
else
{
RemoveTile(navMesh, scene, x, y, 0);
}
}
}
}
}
void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds)
{
const float tileSize = GetTileSize();
const auto navMesh = NavMeshRuntime::Get();
NavMeshRuntime* runtime = navMesh->GetRuntime();
// Align dirty bounds to tile size
BoundingBox dirtyBoundsAligned;
@@ -703,8 +646,8 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds)
dirtyBoundsAligned.Maximum = Vector3::Ceil(dirtyBounds.Maximum / tileSize) * tileSize;
// Calculate tiles range for the given navigation dirty bounds (aligned to tiles size)
const Int3 tilesMin = Int3(dirtyBoundsAligned.Minimum / tileSize);
const Int3 tilesMax = Int3(dirtyBoundsAligned.Maximum / tileSize);
const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize);
const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize);
const int32 tilesX = tilesMax.X - tilesMin.X;
const int32 tilesY = tilesMax.Z - tilesMin.Z;
@@ -712,28 +655,28 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds)
PROFILE_CPU_NAMED("Prepare");
// Prepare scene data and navmesh
if (Math::NotNearEqual(scene->Data.TileSize, tileSize))
if (Math::NotNearEqual(navMesh->Data.TileSize, tileSize))
{
navMesh->RemoveTiles(scene);
navMesh->SetTileSize(tileSize);
navMesh->EnsureCapacity(tilesX * tilesY);
runtime->RemoveTiles(navMesh);
runtime->SetTileSize(tileSize);
runtime->EnsureCapacity(tilesX * tilesY);
scene->Data.TileSize = tileSize;
scene->Data.Tiles.Clear();
scene->Data.Tiles.EnsureCapacity(tilesX * tilesX);
scene->IsDataDirty = true;
navMesh->Data.TileSize = tileSize;
navMesh->Data.Tiles.Clear();
navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX);
navMesh->IsDataDirty = true;
}
else
{
// Prepare navmesh
navMesh->SetTileSize(tileSize);
navMesh->EnsureCapacity(tilesX * tilesY);
runtime->SetTileSize(tileSize);
runtime->EnsureCapacity(tilesX * tilesY);
}
}
// Initialize nav mesh configuration
rcConfig config;
InitConfig(config);
InitConfig(config, navMesh);
// Generate all tiles that intersect with the navigation volume bounds
{
@@ -746,17 +689,103 @@ void BuildDirtyBounds(NavigationScene* scene, const BoundingBox& dirtyBounds)
BoundingBox tileBounds;
if (GetNavMeshTileBounds(scene, x, y, tileSize, tileBounds))
{
BuildTileAsync(scene, x, y, config, tileBounds, tileSize);
BuildTileAsync(navMesh, x, y, config, tileBounds, tileSize);
}
else
{
RemoveTile(navMesh, scene, x, y, 0);
RemoveTile(navMesh, runtime, x, y, 0);
}
}
}
}
}
void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds)
{
auto settings = NavigationSettings::Get();
// Sync navmeshes
for (auto& navMeshProperties : settings->NavMeshes)
{
NavMesh* navMesh = nullptr;
for (auto e : scene->NavigationMeshes)
{
if (e->Properties.Name == navMeshProperties.Name)
{
navMesh = e;
break;
}
}
if (navMesh)
{
// Sync settings
auto runtime = navMesh->GetRuntime(false);
navMesh->Properties = navMeshProperties;
if (runtime)
runtime->Properties = navMeshProperties;
}
else if (settings->AutoAddMissingNavMeshes)
{
// Spawn missing navmesh
navMesh = New<NavMesh>();
navMesh->SetStaticFlags(StaticFlags::FullyStatic);
navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name);
navMesh->Properties = navMeshProperties;
navMesh->SetParent(scene, false);
}
}
// Build all navmeshes on the scene
for (NavMesh* navMesh : scene->NavigationMeshes)
{
BuildDirtyBounds(scene, navMesh, dirtyBounds);
}
// Remove unused navmeshes
if (settings->AutoRemoveMissingNavMeshes)
{
for (NavMesh* navMesh : scene->NavigationMeshes)
{
// Skip used navmeshes
if (navMesh->Data.Tiles.HasItems())
continue;
// Skip navmeshes during async building
int32 usageCount = 0;
NavBuildTasksLocker.Lock();
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
{
if (NavBuildTasks[i]->NavMesh == navMesh)
usageCount++;
}
NavBuildTasksLocker.Unlock();
if (usageCount != 0)
continue;
navMesh->DeleteObject();
}
}
}
void BuildWholeScene(Scene* scene)
{
// Compute total navigation area bounds
const BoundingBox worldBounds = scene->GetNavigationBounds();
BuildDirtyBounds(scene, worldBounds);
}
void ClearNavigation(Scene* scene)
{
const bool autoRemoveMissingNavMeshes = NavigationSettings::Get()->AutoRemoveMissingNavMeshes;
for (NavMesh* navMesh : scene->NavigationMeshes)
{
navMesh->ClearData();
if (autoRemoveMissingNavMeshes)
navMesh->DeleteObject();
}
}
void NavMeshBuilder::Update()
{
ScopeLock lock(NavBuildQueueLocker);
@@ -769,15 +798,12 @@ void NavMeshBuilder::Update()
if (now - req.Time >= 0)
{
NavBuildQueue.RemoveAt(i--);
auto scene = req.Scene->Navigation;
const auto scene = req.Scene.Get();
// Early out if scene has no bounds volumes to define nav mesh area
if (scene->Volumes.IsEmpty())
if (scene->NavigationVolumes.IsEmpty())
{
// Cleanup if no navigation to use
scene->Data.TileSize = 0;
scene->Data.Tiles.Resize(0);
scene->IsDataDirty = true;
ClearNavigation(scene);
continue;
}
@@ -797,9 +823,9 @@ void NavMeshBuilder::Update()
void NavMeshBuilder::Build(Scene* scene, float timeoutMs)
{
// Early out if scene is not using navigation
if (scene->Navigation->Volumes.IsEmpty())
if (scene->NavigationVolumes.IsEmpty())
{
scene->Navigation->ClearData();
ClearNavigation(scene);
return;
}
@@ -828,9 +854,9 @@ void NavMeshBuilder::Build(Scene* scene, float timeoutMs)
void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
{
// Early out if scene is not using navigation
if (scene->Navigation->Volumes.IsEmpty())
if (scene->NavigationVolumes.IsEmpty())
{
scene->Navigation->ClearData();
ClearNavigation(scene);
return;
}

View File

@@ -4,9 +4,14 @@
#if COMPILE_WITH_NAV_MESH_BUILDER
#include "Engine/Core/Compiler.h"
class Scene;
class NavMeshBuilder
/// <summary>
/// The navigation mesh building utility.
/// </summary>
class FLAXENGINE_API NavMeshBuilder
{
public:

View File

@@ -1,18 +1,23 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "NavMeshRuntime.h"
#include "NavigationScene.h"
#include "NavMesh.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include <ThirdParty/recastnavigation/DetourNavMesh.h>
#include <ThirdParty/recastnavigation/DetourNavMeshQuery.h>
#include <ThirdParty/recastnavigation/RecastAlloc.h>
#define MAX_NODES 2048
#define USE_DATA_LINK 0
#define USE_NAV_MESH_ALLOC 1
NavMeshRuntime::NavMeshRuntime()
#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f
#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f
NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties)
: Properties(properties)
{
_navMesh = nullptr;
_navMeshQuery = dtAllocNavMeshQuery();
@@ -30,6 +35,150 @@ int32 NavMeshRuntime::GetTilesCapacity() const
return _navMesh ? _navMesh->getMaxTiles() : 0;
}
bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance) const
{
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query)
{
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
return dtStatusSucceed(query->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X));
}
bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array<Vector3, HeapAllocation>& resultPath) const
{
resultPath.Clear();
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query)
{
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
dtPolyRef endPoly = 0;
query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr);
if (!endPoly)
{
return false;
}
dtPolyRef path[NAV_MESH_PATH_MAX_SIZE];
int32 pathSize;
const auto findPathStatus = query->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE);
if (dtStatusFailed(findPathStatus))
{
return false;
}
// Check for special case, where path has not been found, and starting polygon was the one closest to the target
if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT))
{
// In this case we find a point on starting polygon, that's closest to destination and store it as path end
resultPath.Resize(2);
resultPath[0] = startPosition;
resultPath[1] = startPosition;
query->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X);
}
else
{
int straightPathCount = 0;
resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE);
const auto findStraightPathStatus = query->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS);
if (dtStatusFailed(findStraightPathStatus))
{
return false;
}
resultPath.Resize(straightPathCount);
}
return true;
}
bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const
{
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query)
{
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X);
if (!startPoly)
{
return false;
}
return true;
}
bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const
{
ScopeLock lock(Locker);
const auto query = GetNavMeshQuery();
if (!query)
{
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
dtRaycastHit hit;
hit.path = nullptr;
hit.maxPath = 0;
const bool result = dtStatusSucceed(query->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit));
if (hit.t >= MAX_float)
{
hitInfo.Position = endPosition;
hitInfo.Distance = 0;
}
else
{
hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t;
hitInfo.Distance = hit.t;
}
hitInfo.Normal = *(Vector3*)&hit.hitNormal;
return result;
}
void NavMeshRuntime::SetTileSize(float tileSize)
{
ScopeLock lock(Locker);
@@ -132,13 +281,13 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
}
}
void NavMeshRuntime::AddTiles(NavigationScene* scene)
void NavMeshRuntime::AddTiles(NavMesh* navMesh)
{
// Skip if no data
ASSERT(scene);
if (scene->Data.Tiles.IsEmpty())
ASSERT(navMesh);
if (navMesh->Data.Tiles.IsEmpty())
return;
auto& data = scene->Data;
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles");
@@ -164,14 +313,14 @@ void NavMeshRuntime::AddTiles(NavigationScene* scene)
// Add new tiles
for (auto& tileData : data.Tiles)
{
AddTileInternal(scene, tileData);
AddTileInternal(navMesh, tileData);
}
}
void NavMeshRuntime::AddTile(NavigationScene* scene, NavMeshTileData& tileData)
void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData)
{
ASSERT(scene);
auto& data = scene->Data;
ASSERT(navMesh);
auto& data = navMesh->Data;
PROFILE_CPU_NAMED("NavMeshRuntime.AddTile");
@@ -195,17 +344,17 @@ void NavMeshRuntime::AddTile(NavigationScene* scene, NavMeshTileData& tileData)
EnsureCapacity(1);
// Add new tile
AddTileInternal(scene, tileData);
AddTileInternal(navMesh, tileData);
}
bool IsTileFromScene(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData)
{
return tile.Scene == (NavigationScene*)customData;
return tile.NavMesh == (NavMesh*)customData;
}
void NavMeshRuntime::RemoveTiles(NavigationScene* scene)
void NavMeshRuntime::RemoveTiles(NavMesh* navMesh)
{
RemoveTiles(IsTileFromScene, scene);
RemoveTiles(IsTileFromScene, navMesh);
}
void NavMeshRuntime::RemoveTile(int32 x, int32 y, int32 layer)
@@ -274,6 +423,101 @@ void NavMeshRuntime::RemoveTiles(bool (* prediction)(const NavMeshRuntime* navMe
}
}
#if COMPILE_WITH_DEBUG_DRAW
#include "Engine/Debug/DebugDraw.h"
void DrawPoly(NavMeshRuntime* navMesh, const dtMeshTile& tile, const dtPoly& poly)
{
const unsigned int ip = (unsigned int)(&poly - tile.polys);
const dtPolyDetail& pd = tile.detailMeshes[ip];
const Color color = navMesh->Properties.Color;
const float drawOffsetY = 10.0f + ((float)GetHash(color) / (float)MAX_uint32) * 10.0f; // Apply some offset to prevent Z-fighting for different navmeshes
const Color fillColor = color * 0.5f;
const Color edgesColor = Color::FromHSV(color.ToHSV() + Vector3(20.0f, 0, -0.1f), color.A);
for (int i = 0; i < pd.triCount; i++)
{
Vector3 v[3];
const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4];
for (int k = 0; k < 3; k++)
{
if (t[k] < poly.vertCount)
{
v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3];
}
else
{
v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3];
}
}
v[0].Y += drawOffsetY;
v[1].Y += drawOffsetY;
v[2].Y += drawOffsetY;
DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], fillColor, 0, true);
}
for (int k = 0; k < pd.triCount; k++)
{
const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4];
Vector3 v[3];
for (int m = 0; m < 3; m++)
{
if (t[m] < poly.vertCount)
v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3];
else
v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3];
}
v[0].Y += drawOffsetY;
v[1].Y += drawOffsetY;
v[2].Y += drawOffsetY;
for (int m = 0, n = 2; m < 3; n = m++)
{
// Skip inner detail edges
if (((t[3] >> (n * 2)) & 0x3) == 0)
continue;
DEBUG_DRAW_LINE(v[n], v[m], edgesColor, 0, true);
}
}
}
void NavMeshRuntime::DebugDraw()
{
ScopeLock lock(Locker);
const dtNavMesh* dtNavMesh = GetNavMesh();
const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0;
if (tilesCount == 0)
return;
for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++)
{
const dtMeshTile* tile = dtNavMesh->getTile(tileIndex);
if (!tile->header)
continue;
//DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue);
for (int i = 0; i < tile->header->polyCount; i++)
{
const dtPoly* poly = &tile->polys[i];
if (poly->getType() != DT_POLYTYPE_GROUND)
continue;
DrawPoly(this, *tile, *poly);
}
}
}
#endif
void NavMeshRuntime::Dispose()
{
if (_navMesh)
@@ -284,7 +528,7 @@ void NavMeshRuntime::Dispose()
_tiles.Resize(0);
}
void NavMeshRuntime::AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData)
void NavMeshRuntime::AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData)
{
// Check if that tile has been added to navmesh
NavMeshTile* tile = nullptr;
@@ -313,7 +557,7 @@ void NavMeshRuntime::AddTileInternal(NavigationScene* scene, NavMeshTileData& ti
ASSERT(tile);
// Copy tile properties
tile->Scene = scene;
tile->NavMesh = navMesh;
tile->X = tileData.PosX;
tile->Y = tileData.PosY;
tile->Layer = tileData.Layer;

View File

@@ -5,27 +5,41 @@
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Platform/CriticalSection.h"
#include "NavMeshData.h"
#include "NavigationTypes.h"
class NavigationScene;
class dtNavMesh;
class dtNavMeshQuery;
class NavMesh;
class NavMeshTile
/// <summary>
/// The navigation mesh tile data.
/// </summary>
class FLAXENGINE_API NavMeshTile
{
public:
int32 X;
int32 Y;
int32 Layer;
NavigationScene* Scene;
NavMesh* NavMesh;
BytesContainer Data;
};
class NavMeshRuntime
/// <summary>
/// The navigation mesh runtime object that builds the navmesh from all loaded scenes.
/// </summary>
class FLAXENGINE_API NavMeshRuntime
{
public:
static NavMeshRuntime* Get();
// Gets the navigation mesh runtime for a given navmesh name. Return null if missing.
static NavMeshRuntime* Get(const StringView& navMeshName);
// Gets the navigation mesh runtime for a given agent properties trying to pick the best matching navmesh.
static NavMeshRuntime* Get(const NavAgentProperties& agentProperties);
// Gets the navigation mesh runtime for a given navmesh properties.
static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false);
private:
@@ -36,7 +50,7 @@ private:
public:
NavMeshRuntime();
NavMeshRuntime(const NavMeshProperties& properties);
~NavMeshRuntime();
public:
@@ -46,6 +60,11 @@ public:
/// </summary>
CriticalSection Locker;
/// <summary>
/// The navigation mesh properties.
/// </summary>
NavMeshProperties Properties;
/// <summary>
/// Gets the size of the tile (in world-units). Returns zero if not initialized yet.
/// </summary>
@@ -66,6 +85,43 @@ public:
int32 GetTilesCapacity() const;
public:
/// <summary>
/// Finds the distance from the specified start position to the nearest polygon wall.
/// </summary>
/// <param name="startPosition">The start position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const;
/// <summary>
/// Finds the path between the two positions presented as a list of waypoints stored in the corners array.
/// </summary>
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="resultPath">The result path.</param>
/// <returns>True if found valid path between given two points (it may be partial), otherwise false if failed.</returns>
bool FindPath(const Vector3& startPosition, const Vector3& endPosition, Array<Vector3, HeapAllocation>& resultPath) const;
/// <summary>
/// Projects the point to nav mesh surface (finds the nearest polygon).
/// </summary>
/// <param name="point">The source point.</param>
/// <param name="result">The result position on the navmesh (valid only if method returns true).</param>
/// <returns>True if found valid location on the navmesh, otherwise false.</returns>
bool ProjectPoint(const Vector3& point, Vector3& result) const;
/// <summary>
/// Casts a 'walkability' ray along the surface of the navigation mesh from the start position toward the end position.
/// </summary>
/// <param name="startPosition">The start position.</param>
/// <param name="endPosition">The end position.</param>
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
bool RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo) const;
public:
/// <summary>
@@ -83,21 +139,21 @@ public:
/// <summary>
/// Adds the tiles from the given scene to the runtime navmesh.
/// </summary>
/// <param name="scene">The navigation scene.</param>
void AddTiles(NavigationScene* scene);
/// <param name="navMesh">The navigation mesh.</param>
void AddTiles(NavMesh* navMesh);
/// <summary>
/// Adds the tile from the given scene to the runtime navmesh.
/// </summary>
/// <param name="scene">The navigation scene.</param>
/// <param name="navMesh">The navigation mesh.</param>
/// <param name="tileData">The tile data.</param>
void AddTile(NavigationScene* scene, NavMeshTileData& tileData);
void AddTile(NavMesh* navMesh, NavMeshTileData& tileData);
/// <summary>
/// Removes all the tiles from the navmesh that has been added from the given navigation scene.
/// </summary>
/// <param name="scene">The scene.</param>
void RemoveTiles(NavigationScene* scene);
/// <param name="navMesh">The navigation mesh.</param>
void RemoveTiles(NavMesh* navMesh);
/// <summary>
/// Removes the tile from the navmesh.
@@ -114,6 +170,10 @@ public:
/// <param name="userData">The user data passed to the callback method.</param>
void RemoveTiles(bool (*prediction)(const NavMeshRuntime* navMesh, const NavMeshTile& tile, void* customData), void* userData);
#if COMPILE_WITH_DEBUG_DRAW
void DebugDraw();
#endif
/// <summary>
/// Releases the navmesh.
/// </summary>
@@ -121,5 +181,5 @@ public:
private:
void AddTileInternal(NavigationScene* scene, NavMeshTileData& tileData);
void AddTileInternal(NavMesh* navMesh, NavMeshTileData& tileData);
};

View File

@@ -11,21 +11,90 @@
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include <ThirdParty/recastnavigation/RecastAlloc.h>
#include "Engine/Serialization/Serialization.h"
#include <ThirdParty/recastnavigation/DetourNavMesh.h>
#include <ThirdParty/recastnavigation/DetourNavMeshQuery.h>
#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f
#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f
#include <ThirdParty/recastnavigation/RecastAlloc.h>
namespace
{
NavMeshRuntime* _navMesh;
Array<NavMeshRuntime*, InlinedAllocation<16>> NavMeshes;
}
NavMeshRuntime* NavMeshRuntime::Get()
NavMeshRuntime* NavMeshRuntime::Get(const StringView& navMeshName)
{
return ::_navMesh;
NavMeshRuntime* result = nullptr;
for (auto navMesh : NavMeshes)
{
if (navMesh->Properties.Name == navMeshName)
{
result = navMesh;
break;
}
}
return result;
}
NavMeshRuntime* NavMeshRuntime::Get(const NavAgentProperties& agentProperties)
{
NavMeshRuntime* result = nullptr;
// TODO: maybe build lookup table for agentProperties -> navMesh to improve perf on frequent calls?
float bestAgentRadiusDiff = -MAX_float;
float bestAgentHeightDiff = -MAX_float;
bool bestIsValid = false;
for (auto navMesh : NavMeshes)
{
const auto& navMeshProperties = navMesh->Properties;
const float agentRadiusDiff = navMeshProperties.Agent.Radius - agentProperties.Radius;
const float agentHeightDiff = navMeshProperties.Agent.Height - agentProperties.Height;
const bool isValid = agentRadiusDiff >= 0.0f && agentHeightDiff >= 0.0f;
// NavMesh must be valid for an agent and be first valid or have better properties than the best matching result so far
if (isValid
&&
(
!bestIsValid
||
(agentRadiusDiff + agentHeightDiff < bestAgentRadiusDiff + bestAgentHeightDiff)
)
)
{
result = navMesh;
bestIsValid = true;
bestAgentRadiusDiff = agentRadiusDiff;
bestAgentHeightDiff = agentHeightDiff;
}
}
return result;
}
NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties, bool createIfMissing)
{
NavMeshRuntime* result = nullptr;
for (auto navMesh : NavMeshes)
{
if (navMesh->Properties == navMeshProperties)
{
result = navMesh;
break;
}
}
if (!result && createIfMissing)
{
// Create a new navmesh
result = New<NavMeshRuntime>(navMeshProperties);
NavMeshes.Add(result);
}
return result;
}
bool NavAgentProperties::operator==(const NavAgentProperties& other) const
{
return Math::NearEqual(Radius, other.Radius) && Math::NearEqual(Height, other.Height) && Math::NearEqual(StepHeight, other.StepHeight) && Math::NearEqual(MaxSlopeAngle, other.MaxSlopeAngle);
}
bool NavMeshProperties::operator==(const NavMeshProperties& other) const
{
return Name == other.Name && Quaternion::NearEqual(Rotation, other.Rotation, 0.001f) && Agent == other.Agent;
}
class NavigationService : public EngineService
@@ -59,17 +128,59 @@ void* rcAllocDefault(size_t size, rcAllocHint)
return Allocator::Allocate(size);
}
NavigationSettings::NavigationSettings()
{
NavMeshes.Resize(1);
auto& navMesh = NavMeshes[0];
navMesh.Name = TEXT("Default");
}
IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation);
void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(AutoAddMissingNavMeshes);
DESERIALIZE(AutoRemoveMissingNavMeshes);
DESERIALIZE(CellHeight);
DESERIALIZE(CellSize);
DESERIALIZE(TileSize);
DESERIALIZE(MinRegionArea);
DESERIALIZE(MergeRegionArea);
DESERIALIZE(MaxEdgeLen);
DESERIALIZE(MaxEdgeError);
DESERIALIZE(DetailSamplingDist);
DESERIALIZE(MaxDetailSamplingError);
if (modifier->EngineBuild >= 6215)
{
DESERIALIZE(NavMeshes);
}
else
{
// [Deprecated on 12.01.2021, expires on 12.01.2022]
float WalkableRadius = 34.0f;
float WalkableHeight = 144.0f;
float WalkableMaxClimb = 35.0f;
float WalkableMaxSlopeAngle = 60.0f;
DESERIALIZE(WalkableRadius);
DESERIALIZE(WalkableHeight);
DESERIALIZE(WalkableMaxClimb);
DESERIALIZE(WalkableMaxSlopeAngle);
NavMeshes.Resize(1);
auto& navMesh = NavMeshes[0];
navMesh.Name = TEXT("Default");
navMesh.Agent.Radius = WalkableRadius;
navMesh.Agent.Height = WalkableHeight;
navMesh.Agent.StepHeight = WalkableMaxClimb;
navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle;
}
}
bool NavigationService::Init()
{
// Link memory allocation calls to use engine default allocator
dtAllocSetCustom(dtAllocDefault, Allocator::Free);
rcAllocSetCustom(rcAllocDefault, Allocator::Free);
// Create global nav mesh
_navMesh = New<NavMeshRuntime>();
return false;
}
@@ -84,156 +195,42 @@ void NavigationService::Update()
void NavigationService::Dispose()
{
if (_navMesh)
// Release nav meshes
for (auto navMesh : NavMeshes)
{
_navMesh->Dispose();
Delete(_navMesh);
_navMesh = nullptr;
navMesh->Dispose();
Delete(navMesh);
}
NavMeshes.Clear();
NavMeshes.ClearDelete();
}
bool Navigation::FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance)
{
ScopeLock lock(_navMesh->Locker);
const auto query = _navMesh->GetNavMeshQuery();
if (!query)
{
if (NavMeshes.IsEmpty())
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
return dtStatusSucceed(_navMesh->GetNavMeshQuery()->findDistanceToWall(startPoly, &startPosition.X, maxDistance, &filter, &hitInfo.Distance, &hitInfo.Position.X, &hitInfo.Normal.X));
return NavMeshes.First()->FindDistanceToWall(startPosition, hitInfo, maxDistance);
}
bool Navigation::FindPath(const Vector3& startPosition, const Vector3& endPosition, Array<Vector3, HeapAllocation>& resultPath)
{
resultPath.Clear();
ScopeLock lock(_navMesh->Locker);
const auto query = _navMesh->GetNavMeshQuery();
if (!query)
{
if (NavMeshes.IsEmpty())
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
dtPolyRef endPoly = 0;
query->findNearestPoly(&endPosition.X, &extent.X, &filter, &endPoly, nullptr);
if (!endPoly)
{
return false;
}
dtPolyRef path[NAV_MESH_PATH_MAX_SIZE];
int32 pathSize;
const auto findPathStatus = _navMesh->GetNavMeshQuery()->findPath(startPoly, endPoly, &startPosition.X, &endPosition.X, &filter, path, &pathSize, NAV_MESH_PATH_MAX_SIZE);
if (dtStatusFailed(findPathStatus))
{
return false;
}
// Check for special case, where path has not been found, and starting polygon was the one closest to the target
if (pathSize == 1 && dtStatusDetail(findPathStatus, DT_PARTIAL_RESULT))
{
// In this case we find a point on starting polygon, that's closest to destination and store it as path end
resultPath.Resize(2);
resultPath[0] = startPosition;
resultPath[1] = startPosition;
_navMesh->GetNavMeshQuery()->closestPointOnPolyBoundary(startPoly, &endPosition.X, &resultPath[1].X);
}
else
{
int straightPathCount = 0;
resultPath.EnsureCapacity(NAV_MESH_PATH_MAX_SIZE);
const auto findStraightPathStatus = _navMesh->GetNavMeshQuery()->findStraightPath(&startPosition.X, &endPosition.X, path, pathSize, (float*)resultPath.Get(), nullptr, nullptr, &straightPathCount, resultPath.Capacity(), DT_STRAIGHTPATH_AREA_CROSSINGS);
if (dtStatusFailed(findStraightPathStatus))
{
return false;
}
resultPath.Resize(straightPathCount);
}
return true;
return NavMeshes.First()->FindPath(startPosition, endPosition, resultPath);
}
bool Navigation::ProjectPoint(const Vector3& point, Vector3& result)
{
ScopeLock lock(_navMesh->Locker);
const auto query = _navMesh->GetNavMeshQuery();
if (!query)
{
if (NavMeshes.IsEmpty())
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&point.X, &extent.X, &filter, &startPoly, &result.X);
if (!startPoly)
{
return false;
}
return true;
return NavMeshes.First()->ProjectPoint(point, result);
}
bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPosition, NavMeshHit& hitInfo)
{
ScopeLock lock(_navMesh->Locker);
const auto query = _navMesh->GetNavMeshQuery();
if (!query)
{
if (NavMeshes.IsEmpty())
return false;
}
dtQueryFilter filter;
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
dtPolyRef startPoly = 0;
query->findNearestPoly(&startPosition.X, &extent.X, &filter, &startPoly, nullptr);
if (!startPoly)
{
return false;
}
dtRaycastHit hit;
hit.path = nullptr;
hit.maxPath = 0;
const bool result = dtStatusSucceed(_navMesh->GetNavMeshQuery()->raycast(startPoly, &startPosition.X, &endPosition.X, &filter, 0, &hit));
if (hit.t >= MAX_float)
{
hitInfo.Position = endPosition;
hitInfo.Distance = 0;
}
else
{
hitInfo.Position = startPosition + (endPosition - startPosition) * hit.t;
hitInfo.Distance = hit.t;
}
hitInfo.Normal = *(Vector3*)&hit.hitNormal;
return result;
return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo);
}
#if COMPILE_WITH_NAV_MESH_BUILDER
@@ -260,96 +257,13 @@ void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, floa
#endif
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
void DrawPoly(const dtMeshTile& tile, const dtPoly& poly)
{
const unsigned int ip = (unsigned int)(&poly - tile.polys);
const dtPolyDetail& pd = tile.detailMeshes[ip];
const float DrawOffsetY = 20.0f;
for (int i = 0; i < pd.triCount; i++)
{
Vector3 v[3];
const unsigned char* t = &tile.detailTris[(pd.triBase + i) * 4];
for (int k = 0; k < 3; k++)
{
if (t[k] < poly.vertCount)
{
v[k] = *(Vector3*)&tile.verts[poly.verts[t[k]] * 3];
}
else
{
v[k] = *(Vector3*)&tile.detailVerts[(pd.vertBase + t[k] - poly.vertCount) * 3];
}
}
v[0].Y += DrawOffsetY;
v[1].Y += DrawOffsetY;
v[2].Y += DrawOffsetY;
DEBUG_DRAW_TRIANGLE(v[0], v[1], v[2], Color::Green * 0.5f, 0, true);
}
for (int k = 0; k < pd.triCount; k++)
{
const unsigned char* t = &tile.detailTris[(pd.triBase + k) * 4];
Vector3 v[3];
for (int m = 0; m < 3; m++)
{
if (t[m] < poly.vertCount)
v[m] = *(Vector3*)&tile.verts[poly.verts[t[m]] * 3];
else
v[m] = *(Vector3*)&tile.detailVerts[(pd.vertBase + (t[m] - poly.vertCount)) * 3];
}
v[0].Y += DrawOffsetY;
v[1].Y += DrawOffsetY;
v[2].Y += DrawOffsetY;
for (int m = 0, n = 2; m < 3; n = m++)
{
// Skip inner detail edges
if (((t[3] >> (n * 2)) & 0x3) == 0)
continue;
DEBUG_DRAW_LINE(v[n], v[m], Color::YellowGreen, 0, true);
}
}
}
#if COMPILE_WITH_DEBUG_DRAW
void Navigation::DrawNavMesh()
{
if (!_navMesh)
return;
ScopeLock lock(_navMesh->Locker);
const dtNavMesh* dtNavMesh = _navMesh->GetNavMesh();
const int tilesCount = dtNavMesh ? dtNavMesh->getMaxTiles() : 0;
if (tilesCount == 0)
return;
for (int tileIndex = 0; tileIndex < tilesCount; tileIndex++)
for (auto navMesh : NavMeshes)
{
const dtMeshTile* tile = dtNavMesh->getTile(tileIndex);
if (!tile->header)
continue;
//DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue);
for (int i = 0; i < tile->header->polyCount; i++)
{
const dtPoly* poly = &tile->polys[i];
if (poly->getType() != DT_POLYTYPE_GROUND)
continue;
DrawPoly(*tile, *poly);
}
navMesh->DebugDraw();
}
}

View File

@@ -6,8 +6,6 @@
class Scene;
#define NAV_MESH_PATH_MAX_SIZE 200
/// <summary>
/// The navigation service used for path finding and agents navigation system.
/// </summary>
@@ -88,7 +86,7 @@ public:
#endif
#if USE_EDITOR
#if COMPILE_WITH_DEBUG_DRAW
/// <summary>
/// Draws the navigation for all the scenes (uses DebugDraw interface).

View File

@@ -1,94 +0,0 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "NavMeshData.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/Assets/RawDataAsset.h"
class Scene;
class NavMeshBoundsVolume;
/// <summary>
/// Scene object navigation data.
/// </summary>
class NavigationScene
{
friend Scene;
public:
/// <summary>
/// Initializes a new instance of the <see cref="NavigationScene"/> class.
/// </summary>
/// <param name="scene">The scene.</param>
NavigationScene(Scene* scene);
public:
/// <summary>
/// The parent scene.
/// </summary>
Scene* Scene;
/// <summary>
/// The flag used to mark that navigation data has been modified since load. Used to save runtime data to the file on scene serialization.
/// </summary>
bool IsDataDirty;
/// <summary>
/// The navmesh tiles data.
/// </summary>
NavMeshData Data;
/// <summary>
/// The cached navmesh data asset.
/// </summary>
AssetReference<RawDataAsset> DataAsset;
public:
/// <summary>
/// The list of registered navigation bounds volumes (in the scene).
/// </summary>
Array<NavMeshBoundsVolume*> Volumes;
/// <summary>
/// Gets the total navigation volumes bounds.
/// </summary>
/// <returns>The navmesh bounds.</returns>
BoundingBox GetNavigationBounds();
/// <summary>
/// Finds the navigation volume bounds that have intersection with the given world-space bounding box.
/// </summary>
/// <param name="bounds">The bounds.</param>
/// <returns>The intersecting volume or null if none found.</returns>
NavMeshBoundsVolume* FindNavigationBoundsOverlap(const BoundingBox& bounds);
public:
/// <summary>
/// Saves the nav mesh tiles data to the asset. Supported only in builds with assets saving enabled (eg. editor) and not during gameplay (eg. design time).
/// </summary>
void SaveNavMesh();
/// <summary>
/// Clears the data.
/// </summary>
void ClearData()
{
if (Data.Tiles.HasItems())
{
IsDataDirty = true;
Data.TileSize = 0.0f;
Data.Tiles.Resize(0);
}
}
protected:
void OnEnable();
void OnDisable();
void OnDataAssetLoaded();
};

View File

@@ -1,8 +1,74 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
namespace FlaxEditor.Content.Settings
{
partial class NavigationSettings
{
// [Deprecated on 12.01.2021, expires on 12.01.2022]
private void UpgradeToNavMeshes()
{
if (NavMeshes != null && NavMeshes.Length == 1)
return;
NavMeshes = new NavMeshProperties[1];
ref var navMesh = ref NavMeshes[0];
navMesh.Name = "Default";
navMesh.Color = Color.Green;
navMesh.Rotation = Quaternion.Identity;
navMesh.Agent.Radius = 34.0f;
navMesh.Agent.Height = 144.0f;
navMesh.Agent.StepHeight = 35.0f;
navMesh.Agent.MaxSlopeAngle = 60.0f;
}
// [Deprecated on 12.01.2021, expires on 12.01.2022]
[Serialize, Obsolete]
private float WalkableRadius
{
get => throw new Exception();
set
{
UpgradeToNavMeshes();
NavMeshes[0].Agent.Radius = value;
}
}
// [Deprecated on 12.01.2021, expires on 12.01.2022]
[Serialize, Obsolete]
private float WalkableHeight
{
get => throw new Exception();
set
{
UpgradeToNavMeshes();
NavMeshes[0].Agent.Height = value;
}
}
// [Deprecated on 12.01.2021, expires on 12.01.2022]
[Serialize, Obsolete]
private float WalkableMaxClimb
{
get => throw new Exception();
set
{
UpgradeToNavMeshes();
NavMeshes[0].Agent.StepHeight = value;
}
}
// [Deprecated on 12.01.2021, expires on 12.01.2022]
[Serialize, Obsolete]
private float WalkableMaxSlopeAngle
{
get => throw new Exception();
set
{
UpgradeToNavMeshes();
NavMeshes[0].Agent.MaxSlopeAngle = value;
}
}
}
}

View File

@@ -4,7 +4,7 @@
#include "NavigationTypes.h"
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
/// The navigation system settings container.
@@ -12,108 +12,93 @@
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings);
public:
/// <summary>
/// If checked, enables automatic navmesh actors spawning on a scenes that are using it during navigation building.
/// </summary>
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Navigation\")")
bool AutoAddMissingNavMeshes = true;
/// <summary>
/// If checked, enables automatic navmesh actors removing from a scenes that are not using it during navigation building.
/// </summary>
API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Navigation\")")
bool AutoRemoveMissingNavMeshes = true;
public:
/// <summary>
/// The height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the vertical axis but longer build times.
/// </summary>
API_FIELD(Attributes="DefaultValue(10.0f), Limit(1, 400), EditorOrder(10), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(1, 400), EditorOrder(210), EditorDisplay(\"Nav Mesh Options\")")
float CellHeight = 10.0f;
/// <summary>
/// The width/height of a grid cell in the navigation mesh building steps using heightfields. A lower number means higher precision on the horizontal axes but longer build times.
/// </summary>
API_FIELD(Attributes="DefaultValue(30.0f), Limit(1, 400), EditorOrder(20), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(1, 400), EditorOrder(220), EditorDisplay(\"Nav Mesh Options\")")
float CellSize = 30.0f;
/// <summary>
/// Tile size used for Navigation mesh tiles, the final size of a tile is CellSize*TileSize.
/// </summary>
API_FIELD(Attributes="DefaultValue(64), Limit(8, 4096), EditorOrder(30), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(8, 4096), EditorOrder(230), EditorDisplay(\"Nav Mesh Options\")")
int32 TileSize = 64;
/// <summary>
/// The minimum number of cells allowed to form isolated island areas.
/// </summary>
API_FIELD(Attributes="DefaultValue(0), Limit(0, 100), EditorOrder(40), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(0, 100), EditorOrder(240), EditorDisplay(\"Nav Mesh Options\")")
int32 MinRegionArea = 0;
/// <summary>
/// Any regions with a span count smaller than this value will, if possible, be merged with larger regions.
/// </summary>
API_FIELD(Attributes="DefaultValue(20), Limit(0, 100), EditorOrder(50), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(0, 100), EditorOrder(250), EditorDisplay(\"Nav Mesh Options\")")
int32 MergeRegionArea = 20;
/// <summary>
/// The maximum allowed length for contour edges along the border of the mesh.
/// </summary>
API_FIELD(Attributes="DefaultValue(1200.0f), Limit(100), EditorOrder(60), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")")
API_FIELD(Attributes="Limit(100), EditorOrder(260), EditorDisplay(\"Nav Mesh Options\", \"Max Edge Length\")")
float MaxEdgeLen = 1200.0f;
/// <summary>
/// The maximum distance a simplified contour's border edges should deviate from the original raw contour.
/// </summary>
API_FIELD(Attributes="DefaultValue(1.3f), Limit(0.1f, 4), EditorOrder(70), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(0.1f, 4), EditorOrder(270), EditorDisplay(\"Nav Mesh Options\")")
float MaxEdgeError = 1.3f;
/// <summary>
/// The sampling distance to use when generating the detail mesh.
/// </summary>
API_FIELD(Attributes="DefaultValue(600.0f), Limit(1), EditorOrder(80), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")")
API_FIELD(Attributes="Limit(1), EditorOrder(280), EditorDisplay(\"Nav Mesh Options\", \"Detail Sampling Distance\")")
float DetailSamplingDist = 600.0f;
/// <summary>
/// The maximum distance the detail mesh surface should deviate from heightfield data.
/// </summary>
API_FIELD(Attributes="DefaultValue(1.0f), Limit(0, 3), EditorOrder(90), EditorDisplay(\"Nav Mesh Options\")")
API_FIELD(Attributes="Limit(0, 3), EditorOrder(290), EditorDisplay(\"Nav Mesh Options\")")
float MaxDetailSamplingError = 1.0f;
/// <summary>
/// The radius of the smallest objects to traverse this nav mesh. Objects can't pass through gaps of less than twice the radius.
/// </summary>
API_FIELD(Attributes="DefaultValue(34.0f), Limit(0), EditorOrder(1000), EditorDisplay(\"Agent Options\")")
float WalkableRadius = 34.0f;
public:
/// <summary>
/// The height of the smallest objects to traverse this nav mesh. Objects can't enter areas with ceilings lower than this value.
/// The configuration for navmeshes.
/// </summary>
API_FIELD(Attributes="DefaultValue(144.0f), Limit(0), EditorOrder(1010), EditorDisplay(\"Agent Options\")")
float WalkableHeight = 144.0f;
/// <summary>
/// The maximum ledge height that is considered to still be traversable.
/// </summary>
API_FIELD(Attributes="DefaultValue(35.0f), Limit(0), EditorOrder(1020), EditorDisplay(\"Agent Options\")")
float WalkableMaxClimb = 35.0f;
/// <summary>
/// The maximum slope that is considered walkable (in degrees). Objects can't go up or down slopes higher than this value.
/// </summary>
API_FIELD(Attributes="DefaultValue(60.0f), Limit(0, 89.0f), EditorOrder(1030), EditorDisplay(\"Agent Options\")")
float WalkableMaxSlopeAngle = 60.0f;
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\")")
Array<NavMeshProperties> NavMeshes;
public:
NavigationSettings();
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static NavigationSettings* Get();
// [SettingsBase]
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
DESERIALIZE(CellHeight);
DESERIALIZE(CellSize);
DESERIALIZE(TileSize);
DESERIALIZE(MinRegionArea);
DESERIALIZE(MergeRegionArea);
DESERIALIZE(MaxEdgeLen);
DESERIALIZE(MaxEdgeError);
DESERIALIZE(DetailSamplingDist);
DESERIALIZE(MaxDetailSamplingError);
DESERIALIZE(WalkableRadius);
DESERIALIZE(WalkableHeight);
DESERIALIZE(WalkableMaxClimb);
DESERIALIZE(WalkableMaxSlopeAngle);
}
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override;
};

View File

@@ -7,61 +7,88 @@
#include "Engine/Core/Math/Color.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Quaternion.h"
#include "Engine/Core/ISerializable.h"
#define NAV_MESH_PATH_MAX_SIZE 200
/// <summary>
/// The navigation system agent properties container for navmesh building and querying.
/// </summary>
API_STRUCT() struct FLAXENGINE_API NavAgentProperties
API_STRUCT() struct FLAXENGINE_API NavAgentProperties : ISerializable
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_MINIMAL(NavAgentProperties);
/// <summary>
/// The radius of the agent used for navigation. Agents can't pass through gaps of less than twice the radius.
/// </summary>
API_FIELD() float Radius = 34.0f;
API_FIELD(Attributes="EditorOrder(0)")
float Radius = 34.0f;
/// <summary>
/// The height of the agent used for navigation. Agents can't enter areas with ceilings lower than this value.
/// </summary>
API_FIELD() float Height = 144.0f;
API_FIELD(Attributes="EditorOrder(10)")
float Height = 144.0f;
/// <summary>
/// The step height used for navigation. Defines the maximum ledge height that is considered to still be traversable by the agent.
/// </summary>
API_FIELD() float StepHeight = 35.0f;
API_FIELD(Attributes="EditorOrder(20)")
float StepHeight = 35.0f;
/// <summary>
/// The maximum slope (in degrees) that is considered walkable for navigation. Agents can't go up or down slopes higher than this value.
/// </summary>
API_FIELD() float MaxSlopeAngle = 60.0f;
API_FIELD(Attributes="EditorOrder(30)")
float MaxSlopeAngle = 60.0f;
bool operator==(const NavAgentProperties& other) const;
bool operator!=(const NavAgentProperties& other) const
{
return !operator==(other);
}
};
/// <summary>
/// The navigation mesh properties container for navmesh building.
/// </summary>
API_STRUCT() struct FLAXENGINE_API NavMeshProperties
API_STRUCT() struct FLAXENGINE_API NavMeshProperties : ISerializable
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_MINIMAL(NavMeshProperties);
/// <summary>
/// The navmesh type name (for debugging).
/// The navmesh type name. Identifies different types of the navmeshes, used to sync navmesh properties with settings asset.
/// </summary>
API_FIELD() String Name;
API_FIELD(Attributes="EditorOrder(0)")
String Name;
/// <summary>
/// The navmesh type color (for debugging).
/// </summary>
API_FIELD() Color Color;
API_FIELD(Attributes="EditorOrder(10)")
Color Color = Color::Green;
/// <summary>
/// The navmesh rotation applied to navigation surface. Used during building to the rotate scene geometry and to revert back result during path finding queries. Can be used to generate navmesh on walls.
/// </summary>
API_FIELD() Quaternion Rotation;
API_FIELD(Attributes="EditorOrder(20)")
Quaternion Rotation = Quaternion::Identity;
/// <summary>
/// The properties of the agent used to generate walkable navigation surface.
/// </summary>
API_FIELD() NavAgentProperties Agent;
API_FIELD(Attributes="EditorOrder(30)")
NavAgentProperties Agent;
bool operator==(const NavMeshProperties& other) const;
bool operator!=(const NavMeshProperties& other) const
{
return !operator==(other);
}
};
/// <summary>