Add support for using NavAreas for navigation

This commit is contained in:
Wojtek Figat
2021-01-18 13:18:26 +01:00
parent ba050b9eaa
commit fdc8e371c4
10 changed files with 155 additions and 9 deletions

View File

@@ -59,6 +59,7 @@ struct OffMeshLink
struct Modifier
{
BoundingBox Bounds;
NavAreaProperties* NavArea;
};
struct NavigationSceneRasterization
@@ -300,6 +301,7 @@ struct NavigationSceneRasterization
OrientedBoundingBox bounds = navModifierVolume->GetOrientedBox();
bounds.Transform(e.WorldToNavMesh);
bounds.GetBoundingBox(modifier.Bounds);
modifier.NavArea = navModifierVolume->GetNavArea();
e.Modifiers->Add(modifier);
}
@@ -443,7 +445,8 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
// Mark areas
for (auto& modifier : modifiers)
{
rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, RC_NULL_AREA, *compactHeightfield);
const unsigned char areaId = modifier.NavArea ? modifier.NavArea->Id : RC_NULL_AREA;
rcMarkBoxArea(&context, &modifier.Bounds.Minimum.X, &modifier.Bounds.Maximum.X, areaId, *compactHeightfield);
}
if (!rcBuildDistanceField(&context, *compactHeightfield))
@@ -564,7 +567,7 @@ bool GenerateTile(NavMesh* navMesh, NavMeshRuntime* runtime, int32 x, int32 y, B
offMeshArea[i] = RC_WALKABLE_AREA;
offMeshFlags[i] = 1;
// TODO: support navigation areas, navigation area type for off mesh links
// TODO: support navigation area type for off mesh links
}
params.offMeshConCount = linksCount;
@@ -884,6 +887,27 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
{
auto settings = NavigationSettings::Get();
// Validate nav areas ids to be unique and in valid range
for (int32 i = 0; i < settings->NavAreas.Count(); i++)
{
auto& a = settings->NavAreas[i];
if (a.Id > RC_WALKABLE_AREA)
{
LOG(Error, "Nav Area {0} uses invalid Id. Valid values are in range 0-63 only.", a.Name);
return;
}
for (int32 j = i + 1; j < settings->NavAreas.Count(); j++)
{
auto& b = settings->NavAreas[j];
if (a.Id == b.Id)
{
LOG(Error, "Nav Area {0} uses the same Id={1} as Nav Area {2}. Each area hast to have unique Id.", a.Name, a.Id, b.Name);
return;
}
}
}
// Sync navmeshes
for (auto& navMeshProperties : settings->NavMeshes)
{

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "NavMeshRuntime.h"
#include "NavigationSettings.h"
#include "NavMesh.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Matrix.h"
@@ -18,6 +19,14 @@
#define DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL 50.0f
#define DEFAULT_NAV_QUERY_EXTENT_VERTICAL 250.0f
namespace
{
FORCE_INLINE void InitFilter(dtQueryFilter& filter)
{
Platform::MemoryCopy(filter.m_areaCost, NavMeshRuntime::NavAreasCosts, sizeof(NavMeshRuntime::NavAreasCosts));
}
}
NavMeshRuntime::NavMeshRuntime(const NavMeshProperties& properties)
: Properties(properties)
{
@@ -48,6 +57,7 @@ bool NavMeshRuntime::FindDistanceToWall(const Vector3& startPosition, NavMeshHit
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 startPositionNavMesh;
@@ -85,6 +95,7 @@ bool NavMeshRuntime::FindPath(const Vector3& startPosition, const Vector3& endPo
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 startPositionNavMesh, endPositionNavMesh;
@@ -153,6 +164,7 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 startPositionNavMesh, endPositionNavMesh;
@@ -199,6 +211,7 @@ bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 pointNavMesh;
@@ -229,6 +242,7 @@ bool NavMeshRuntime::RayCast(const Vector3& startPosition, const Vector3& endPos
}
dtQueryFilter filter;
InitFilter(filter);
Vector3 extent(DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL, DEFAULT_NAV_QUERY_EXTENT_VERTICAL, DEFAULT_NAV_QUERY_EXTENT_HORIZONTAL);
Vector3 startPositionNavMesh, endPositionNavMesh;
@@ -513,7 +527,8 @@ void DrawPoly(NavMeshRuntime* navMesh, const Matrix& navMeshToWorld, const dtMes
{
const unsigned int ip = (unsigned int)(&poly - tile.polys);
const dtPolyDetail& pd = tile.detailMeshes[ip];
const Color color = navMesh->Properties.Color;
const Color& areaColor = NavMeshRuntime::NavAreasColors[poly.getArea()];
const Color color = Color::Lerp(navMesh->Properties.Color, areaColor, areaColor.A);
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);

View File

@@ -41,6 +41,12 @@ public:
// Gets the navigation mesh runtime for a given navmesh properties.
static NavMeshRuntime* Get(const NavMeshProperties& navMeshProperties, bool createIfMissing = false);
// The lookup table that maps areaId of the navmesh to the current properties (applied by the NavigationSettings). Cached to improve runtime performance.
static float NavAreasCosts[64];
#if COMPILE_WITH_DEBUG_DRAW
static Color NavAreasColors[64];
#endif
private:
dtNavMesh* _navMesh;

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "NavModifierVolume.h"
#include "NavigationSettings.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Serialization/Serialization.h"
#if USE_EDITOR
@@ -15,6 +16,17 @@ NavModifierVolume::NavModifierVolume(const SpawnParams& params)
_size = 100.0f;
}
NavAreaProperties* NavModifierVolume::GetNavArea() const
{
auto settings = NavigationSettings::Get();
for (auto& navArea : settings->NavAreas)
{
if (navArea.Name == AreaName)
return &navArea;
}
return nullptr;
}
void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
@@ -23,6 +35,7 @@ void NavModifierVolume::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_GET_OTHER_OBJ(NavModifierVolume);
SERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask);
SERIALIZE(AreaName);
}
void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -31,6 +44,7 @@ void NavModifierVolume::Deserialize(DeserializeStream& stream, ISerializeModifie
BoxVolume::Deserialize(stream, modifier);
DESERIALIZE_MEMBER(AgentsMask, AgentsMask.Mask);
DESERIALIZE(AreaName);
}
#if USE_EDITOR

View File

@@ -16,9 +16,22 @@ public:
/// <summary>
/// The agent types used by this navmesh modifier volume (from navigation settings). Can be used to adjust navmesh for a certain set of agents.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Box Volume\"), EditorOrder(10)")
API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(10)")
NavAgentMask AgentsMask;
/// <summary>
/// The name of the nav area to apply within the modifiers volume. Nav area properties are picked from the Navigation Settings asset.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Navigation\"), EditorOrder(20)")
String AreaName;
public:
/// <summary>
/// Gets the properties of the nav area used by this volume. Null if missing or invalid area name.
/// </summary>
NavAreaProperties* GetNavArea() const;
public:
// [BoxVolume]

View File

@@ -92,6 +92,12 @@ NavMeshRuntime* NavMeshRuntime::Get(const NavMeshProperties& navMeshProperties,
return result;
}
static_assert(ARRAY_COUNT(NavMeshRuntime::NavAreasCosts) == DT_MAX_AREAS, "Invalid nav areas amount limit.");
float NavMeshRuntime::NavAreasCosts[64];
#if COMPILE_WITH_DEBUG_DRAW
Color NavMeshRuntime::NavAreasColors[64];
#endif
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);
@@ -176,13 +182,42 @@ void* rcAllocDefault(size_t size, rcAllocHint)
NavigationSettings::NavigationSettings()
{
// Init navmeshes
NavMeshes.Resize(1);
auto& navMesh = NavMeshes[0];
navMesh.Name = TEXT("Default");
// Init nav areas
NavAreas.Resize(2);
auto& areaNull = NavAreas[0];
areaNull.Name = TEXT("Null");
areaNull.Color = Color::Transparent;
areaNull.Id = 0;
areaNull.Cost = MAX_float;
auto& areaWalkable = NavAreas[1];
areaWalkable.Name = TEXT("Walkable");
areaWalkable.Color = Color::Transparent;
areaWalkable.Id = 63;
areaWalkable.Cost = 1;
}
IMPLEMENT_SETTINGS_GETTER(NavigationSettings, Navigation);
void NavigationSettings::Apply()
{
// Cache areas properties
for (auto& area : NavAreas)
{
if (area.Id < DT_MAX_AREAS)
{
NavMeshRuntime::NavAreasCosts[area.Id] = area.Cost;
#if USE_EDITOR
NavMeshRuntime::NavAreasColors[area.Id] = area.Color;
#endif
}
}
}
void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(AutoAddMissingNavMeshes);
@@ -219,6 +254,7 @@ void NavigationSettings::Deserialize(DeserializeStream& stream, ISerializeModifi
navMesh.Agent.StepHeight = WalkableMaxClimb;
navMesh.Agent.MaxSlopeAngle = WalkableMaxSlopeAngle;
}
DESERIALIZE(NavAreas);
}
bool NavigationService::Init()

View File

@@ -7,6 +7,36 @@ namespace FlaxEditor.Content.Settings
{
partial class NavigationSettings
{
/// <summary>
/// Initializes a new instance of the <see cref="NavigationSettings"/> class.
/// </summary>
public NavigationSettings()
{
// Init navmeshes
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;
// Init nav areas
NavAreas = new NavAreaProperties[2];
ref var areaNull = ref NavAreas[0];
areaNull.Name = "Null";
areaNull.Color = Color.Transparent;
areaNull.Id = 0;
areaNull.Cost = float.MaxValue;
ref var areaWalkable = ref NavAreas[1];
areaWalkable.Name = "Walkable";
areaWalkable.Color = Color.Transparent;
areaWalkable.Id = 63;
areaWalkable.Cost = 1;
}
// [Deprecated on 12.01.2021, expires on 12.01.2022]
private void UpgradeToNavMeshes()
{

View File

@@ -9,7 +9,7 @@
/// <summary>
/// The navigation system settings container.
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NavigationSettings : public SettingsBase
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API NavigationSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings);
public:
@@ -87,9 +87,15 @@ public:
/// <summary>
/// The configuration for navmeshes.
/// </summary>
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\")")
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Agents\", EditorDisplayAttribute.InlineStyle)")
Array<NavMeshProperties> NavMeshes;
/// <summary>
/// The configuration for nav areas.
/// </summary>
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Areas\", EditorDisplayAttribute.InlineStyle)")
Array<NavAreaProperties> NavAreas;
public:
NavigationSettings();
@@ -100,5 +106,6 @@ public:
static NavigationSettings* Get();
// [SettingsBase]
void Apply() override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override;
};

View File

@@ -156,18 +156,18 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(NavAreaProperties);
/// The area type color (for debugging). Alpha channel is used to blend with navmesh color (alpha 0 to use navmesh color only).
/// </summary>
API_FIELD(Attributes="EditorOrder(10)")
Color Color = Color::Red;
Color Color = Color::Transparent;
/// <summary>
/// The area id. It must be unique for the project. Valid range 0-63. Value 0 is reserved for Null areas (empty, non-navigable areas).
/// </summary>
API_FIELD(Attributes="EditorOrder(20), Limit(0, 63)")
byte Id = 1;
byte Id;
/// <summary>
/// The cost scale for the area traversal for agents. The higher the cost, the less likely agent wil choose the path that goes over it. For instance, areas that are harder to move like sand should have higher cost for proper path finding.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), Limit(0, 100000, 0.1f)")
API_FIELD(Attributes="EditorOrder(30), Limit(0, float.MaxValue, 0.1f)")
float Cost = 1;
bool operator==(const NavAreaProperties& other) const;

View File

@@ -34,6 +34,7 @@
/// @ingroup detour
class dtQueryFilter
{
public:
float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.)
unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.)
unsigned short m_excludeFlags; ///< Flags for polygons that should not be visted. (Used by default implementation.)