Add support for using NavAreas for navigation
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.)
|
||||
|
||||
Reference in New Issue
Block a user