Added Recast&Detour implementation

- Implemented debug renderer of navigation meshes
- Can now generate navigation meshes from .map files
This commit is contained in:
Antoine Pilote
2024-07-11 04:54:57 -04:00
parent cc87da1001
commit 7b48319ff5
14 changed files with 592 additions and 6 deletions

View File

@@ -3,6 +3,7 @@
#include <src/Scene/Components/QuakeMap.h>
#include "src/Scene/Systems/QuakeMapBuilder.h"
#include <src/Core/FileSystem.h>
#include <src/AI/NavManager.h>
class QuakeMapPanel : ComponentPanel {
@@ -74,6 +75,32 @@ public:
//ComponentTableReset(component.Class, "");
}
ImGui::TableNextColumn();
{
using namespace Nuake;
ImGui::Text("Build Navigation Mesh");
ImGui::TableNextColumn();
if (ImGui::Button("Build Navigation"))
{
for (auto& mesh : component.m_Brushes)
{
if (mesh.HasComponent<ModelComponent>())\
{
TransformComponent& transformComponent = mesh.GetComponent<TransformComponent>();
for (auto& mesh : mesh.GetComponent<ModelComponent>().ModelResource->GetMeshes())
{
Nuake::NavManager::Get().PushMesh(mesh, transformComponent.GetGlobalTransform());
}
}
}
Nuake::NavManager::Get().BuildNavMesh();
}
ImGui::TableNextColumn();
}
}
EndComponentTable();
}

View File

@@ -6,7 +6,9 @@
#include "src/Core/Input.h"
#include <glad/glad.h>
#include <glm/gtc/type_ptr.hpp>
#include "glad/glad.h"
void EditorLayer::OnAttach()
{
@@ -50,6 +52,28 @@ void EditorLayer::OnUpdate()
glDepthFunc(GL_LESS);
}
if (m_EditorInterface->ShouldDrawNavMesh())
{
auto cam = Engine::GetCurrentScene()->m_EditorCamera;
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(glm::value_ptr(cam->GetPerspective()));
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(glm::value_ptr(cam->GetTransform()));
glDepthFunc(GL_LESS);
RenderCommand::Disable(RendererEnum::DEPTH_TEST);
Nuake::NavManager::Get().DrawNavMesh();
RenderCommand::Enable(RendererEnum::DEPTH_TEST);
Nuake::NavManager::Get().DrawNavMesh();
glDepthFunc(GL_GREATER);
Nuake::NavManager::Get().DrawNavMesh();
glDepthFunc(GL_LESS);
}
if (m_EditorInterface->ShouldDrawCollision())
{
m_GizmoDrawer->DrawGizmos(currentScene, false);
@@ -58,6 +82,8 @@ void EditorLayer::OnUpdate()
m_GizmoDrawer->DrawGizmos(currentScene, true);
glDepthFunc(GL_LESS);
}
}
}
sceneFramebuffer->Unbind();

View File

@@ -1,6 +1,7 @@
#pragma once
#include <src/Application/Layer.h>
#include "Commands/CommandBuffer.h"
#include "src/AI/NavMeshDebugDrawer.h"
namespace Nuake {
@@ -25,4 +26,6 @@ private:
CommandBuffer mCommandBuffer;
Nuake::EditorInterface* m_EditorInterface;
GizmoDrawer* m_GizmoDrawer;
Nuake::NavMeshDebugDrawer m_NavMeshDrawer;
};

View File

@@ -2242,6 +2242,11 @@ namespace Nuake {
PhysicsManager::Get().SetDrawDebug(m_DebugCollisions);
}
if (ImGui::MenuItem("Draw navigation meshes", NULL, m_DrawNavMesh))
{
}
if (ImGui::MenuItem("Settings", NULL)) {}
ImGui::EndMenu();
}

View File

@@ -31,6 +31,7 @@ namespace Nuake
bool m_DrawAxis = true;
bool m_ShowImGuiDemo = false;
bool m_DebugCollisions = true;
bool m_DrawNavMesh = true;
bool m_ShowOverlay = true;
bool m_IsHoveringViewport = false;
bool m_IsViewportFocused = false;
@@ -76,6 +77,7 @@ namespace Nuake
bool ShouldDrawAxis() const { return m_DrawAxis; }
bool ShouldDrawCollision() const { return m_DebugCollisions; }
bool ShouldDrawNavMesh() const { return m_DrawNavMesh; }
bool LoadProject(const std::string& projectPath);
private:

View File

@@ -6,6 +6,7 @@
#include "src/Core/FileSystem.h"
#include "src/Scripting/ScriptingEngine.h"
#include "src/Audio/AudioManager.h"
#include "src/AI/NavManager.h"
#include "src/Threading/JobSystem.h"
#include "src/Rendering/Renderer.h"
#include "src/Rendering/Renderer2D.h"
@@ -33,6 +34,7 @@ namespace Nuake
{
AudioManager::Get().Initialize();
PhysicsManager::Get().Init();
NavManager::Get().Initialize();
// Creates the window
s_CurrentWindow = Window::Get();

274
Nuake/src/AI/NavManager.cpp Normal file
View File

@@ -0,0 +1,274 @@
#include "NavManager.h"
#include "src/Core/Logger.h"
#include "src/Rendering/Vertex.h"
#include "Recast.h"
#include <DetourNavMeshBuilder.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshQuery.h>
#include <DetourDebugDraw.h>
#include <DebugDraw.h>
namespace Nuake {
void NavManager::Initialize()
{
m_RecastContext = CreateRef<rcContext>();
}
void NavManager::Cleanup()
{
dtFree(m_DetourNavMesh);
}
void NavManager::PushMesh(const Ref<Mesh>& mesh, const Matrix4& transform)
{
m_Meshes.push_back({ mesh, transform });
}
void NavManager::BuildNavMesh()
{
// Merge all meshes togheter
std::vector<Vector3> vertices;
std::vector<int> indices;
// Since we are merging all the meshes togheter, we need to offset
// the indices so they point to the correct vertices in the array.
// Example: Model1 + Model2 + Model3 will get merged together in one array
// and we need to make sure the indices of Model2 point to the correct location in the merged array
uint32_t currentVertexOffset = 0;
for (auto& mesh : m_Meshes)
{
for (auto& vert : mesh.mesh->GetVertices())
{
Vector4 transformVertex = mesh.transform * Vector4(vert.position.x, vert.position.y, vert.position.z, 1.0f);
vertices.push_back({ transformVertex.x, transformVertex.y, transformVertex.z });
}
for (auto& index : mesh.mesh->GetIndices())
{
indices.push_back(currentVertexOffset + index);
}
currentVertexOffset = std::size(vertices);
}
float bmin[3] = { -100.0f, -100.0f, -100.0f };
float bmax[3] = { 100.0f, 100.0f, 100.0f };
rcConfig recastConfig;
recastConfig.cs = 0.2f;
recastConfig.ch = 0.2f;
recastConfig.tileSize = 10.0f;
recastConfig.walkableSlopeAngle = 45.0f;
recastConfig.maxEdgeLen = 12.0f;
recastConfig.detailSampleDist = 1.2f;
recastConfig.detailSampleMaxError = 0.1f;
recastConfig.maxVertsPerPoly = 6.0f;
recastConfig.walkableHeight = 1.0f;
recastConfig.walkableClimb = 1.0f;
recastConfig.walkableRadius = 1.0f;
recastConfig.maxSimplificationError = 1.3f;
recastConfig.minRegionArea = 8.0f;
recastConfig.mergeRegionArea = 20.0f;
recastConfig.detailSampleDist = 6.0f;
recastConfig.detailSampleMaxError = 1.0f;
rcVcopy(recastConfig.bmin, bmin);
rcVcopy(recastConfig.bmax, bmax);
// Calculate grid size given world size
rcCalcGridSize(recastConfig.bmin, recastConfig.bmax, recastConfig.cs, &recastConfig.width, &recastConfig.height);
Logger::Log("Building navigation:", "NavManager", VERBOSE);
Logger::Log(" - " + std::to_string(recastConfig.width) + " x " + std::to_string(recastConfig.height), "NavManager", VERBOSE);
auto voxelHeightField = rcAllocHeightfield();
if (!voxelHeightField)
{
Logger::Log("buildNavigation: Out of memory 'solid'.", "NavManager", CRITICAL);
}
if (!rcCreateHeightfield(m_RecastContext.get(), *voxelHeightField, recastConfig.width, recastConfig.height, recastConfig.bmin, recastConfig.bmax, recastConfig.cs, recastConfig.ch))
{
Logger::Log("buildNavigation: Could not create solid heightfield.", "NavManager", CRITICAL);
}
unsigned char* m_triareas = new unsigned char[indices.size()];
memset(m_triareas, 0, indices.size() * sizeof(unsigned char));
float* verts = reinterpret_cast<float*>(vertices.data());
int* tris = reinterpret_cast<int*>(indices.data());
rcMarkWalkableTriangles(m_RecastContext.get(), 45.0f, verts, std::size(vertices), tris, std::size(indices) / 3, m_triareas);
if (!rcRasterizeTriangles(m_RecastContext.get(), verts, std::size(vertices), tris, m_triareas, std::size(indices) / 3, *voxelHeightField, recastConfig.walkableClimb))
{
Logger::Log("buildNavigation: Could not rasterize triangles.", "NavManager", CRITICAL);
}
// Filter walkable surfaces.
const bool filterLowHangingObstacles = true;
const bool filterLedgeSpans = true;
const bool filterWalkableLowHeightSpans = true;
if (filterLowHangingObstacles)
{
rcFilterLowHangingWalkableObstacles(m_RecastContext.get(), recastConfig.walkableClimb, *voxelHeightField);
}
if (filterLedgeSpans)
{
rcFilterLedgeSpans(m_RecastContext.get(), recastConfig.walkableHeight, recastConfig.walkableClimb, *voxelHeightField);
}
if (filterWalkableLowHeightSpans)
{
rcFilterWalkableLowHeightSpans(m_RecastContext.get(), recastConfig.walkableHeight, *voxelHeightField);
}
auto compactHeightField = rcAllocCompactHeightfield();
bool result = rcBuildCompactHeightfield(m_RecastContext.get(), recastConfig.walkableHeight, recastConfig.walkableClimb, *voxelHeightField, *compactHeightField);
if (!result)
{
Logger::Log("buildNavigation: Could not build compact data.", "NavManager", CRITICAL);
}
// We dont need to keep in memory the height field
rcFreeHeightField(voxelHeightField);
result = rcErodeWalkableArea(m_RecastContext.get(), recastConfig.walkableRadius, *compactHeightField);
if (!result)
{
Logger::Log("buildNavigation: Could not erode.", "NavManager", CRITICAL);
}
// Water shed for now...
result = rcBuildDistanceField(m_RecastContext.get(), *compactHeightField);
if (!result)
{
Logger::Log("buildNavigation: Could not build distance field.", "NavManager", CRITICAL);
}
result = rcBuildRegions(m_RecastContext.get(), *compactHeightField, 0, recastConfig.minRegionArea, recastConfig.mergeRegionArea);
if (!result)
{
Logger::Log("buildNavigation: Could not build watershed regions.", "NavManager", CRITICAL);
}
// Trace and simplify region countours.
auto contourSet = rcAllocContourSet();
result = rcBuildContours(m_RecastContext.get(), *compactHeightField, recastConfig.maxSimplificationError, recastConfig.maxEdgeLen, *contourSet);
if (!result)
{
Logger::Log("buildNavigation: Could not create contours.", "NavManager", CRITICAL);
}
// Build polygons mesh from contours
auto polygonMesh = rcAllocPolyMesh();
result = rcBuildPolyMesh(m_RecastContext.get(), *contourSet, recastConfig.maxVertsPerPoly, *polygonMesh);
if (!result)
{
Logger::Log("buildNavigation: Could not triangulate contours.", "NavManager", CRITICAL);
}
// Create detail polygon mesh
auto detailMesh = rcAllocPolyMeshDetail();
result = rcBuildPolyMeshDetail(m_RecastContext.get(), *polygonMesh, *compactHeightField, recastConfig.detailSampleDist, recastConfig.detailSampleMaxError, *detailMesh);
if (!result)
{
Logger::Log("buildNavigation: Could not build detail mesh.", "NavManager", CRITICAL);
}
// Free memory
rcFreeCompactHeightfield(compactHeightField);
rcFreeContourSet(contourSet);
// Apply flags and areas type checking here, example, swim, grass, door, etc.
// Should be user defined almost...
// DETOUR
unsigned char* navData = 0;
int navDataSize = 0;
// Create detour data from poly mesh...
dtNavMeshCreateParams params;
memset(&params, 0, sizeof(params));
params.verts = polygonMesh->verts;
params.vertCount = polygonMesh->nverts;
params.polys = polygonMesh->polys;
params.polyAreas = polygonMesh->areas;
params.polyFlags = polygonMesh->flags;
params.polyCount = polygonMesh->npolys;
params.nvp = polygonMesh->nvp;
params.detailVerts = detailMesh->verts;
params.detailVertsCount = detailMesh->nverts;
params.detailTris = detailMesh->tris;
params.detailTriCount = detailMesh->ntris;
// Off connection, might be useful for user defined jump points?
// Maybe useful for ziplines, scripted jumppad, idk
// See sample OffMeshConnectionTool.cpp in RecastDemo project.
//params.offMeshConVerts = m_geom->getOffMeshConnectionVerts();
//params.offMeshConRad = m_geom->getOffMeshConnectionRads();
//params.offMeshConDir = m_geom->getOffMeshConnectionDirs();
//params.offMeshConAreas = m_geom->getOffMeshConnectionAreas();
//params.offMeshConFlags = m_geom->getOffMeshConnectionFlags();
//params.offMeshConUserID = m_geom->getOffMeshConnectionId();
//params.offMeshConCount = m_geom->getOffMeshConnectionCount();
const float agentHeight = 0.1f;
const float agentRadius = 0.1f;
const float agentMaxClimb = 0.1f;
params.walkableHeight = agentHeight;
params.walkableRadius = agentRadius;
params.walkableClimb = agentMaxClimb;
rcVcopy(params.bmin, polygonMesh->bmin);
rcVcopy(params.bmax, polygonMesh->bmax);
// Cell size and cell height
params.cs = recastConfig.cs;
params.ch = recastConfig.ch;
params.buildBvTree = true; // Not sure the difference it makes in performance. based on sample.
result = dtCreateNavMeshData(&params, &navData, &navDataSize);
if (!result)
{
Logger::Log("Could not build Detour navmesh.", "NavManager", CRITICAL);
}
m_DetourNavMesh = dtAllocNavMesh();
if (!m_DetourNavMesh)
{
Logger::Log("Could not create Detour navmesh.", "NavManager", CRITICAL);
}
dtStatus status;
status = m_DetourNavMesh->init(navData, navDataSize, DT_TILE_FREE_DATA);
if (dtStatusFailed(status))
{
dtFree(navData);
Logger::Log("Could not init Detour navmesh", "NavManager", CRITICAL);
}
m_DetourNavQuery = dtAllocNavMeshQuery();
if (!m_DetourNavQuery)
{
Logger::Log("Could not create Detour navquery.", "NavManager", CRITICAL);
}
status = m_DetourNavQuery->init(m_DetourNavMesh, 2048);
if (!status)
{
Logger::Log("Could not init Detour navmesh query", "NavManager", CRITICAL);
}
}
void NavManager::DrawNavMesh()
{
if (m_DetourNavMesh)
{
duDebugDrawNavMeshBVTree(&m_DebugDrawer, *m_DetourNavMesh);
duDebugDrawNavMeshNodes(&m_DebugDrawer, *m_DetourNavQuery);
duDebugDrawNavMesh(&m_DebugDrawer, *m_DetourNavMesh, DU_DRAWNAVMESH_OFFMESHCONS);
}
}
}

49
Nuake/src/AI/NavManager.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include "src/Core/Core.h"
#include "src/Rendering/Mesh/Mesh.h"
#include "NavMeshDebugDrawer.h"
class rcContext;
class dtNavMesh;
class dtNavMeshQuery;
namespace Nuake {
struct MeshTransformKeyPair
{
Ref<Mesh> mesh;
Matrix4 transform;
};
class NavManager
{
public:
static NavManager& Get()
{
static NavManager instance;
return instance;
}
void Initialize();
void PushMesh(const Ref<Mesh>& mesh, const Matrix4& transform);
void BuildNavMesh();
void DrawNavMesh();
void Cleanup();
private:
NavManager() = default;
~NavManager() = default;
std::vector<MeshTransformKeyPair> m_Meshes;
NavMeshDebugDrawer m_DebugDrawer;
Ref<rcContext> m_RecastContext;
dtNavMesh* m_DetourNavMesh;
dtNavMeshQuery* m_DetourNavQuery;
};
}

View File

@@ -0,0 +1,126 @@
#include "NavMeshDebugDrawer.h"
#include <glad/glad.h>
namespace Nuake {
GLCheckerTexture::GLCheckerTexture()
{
if (m_texId != 0)
glDeleteTextures(1, &m_texId);
}
void GLCheckerTexture::bind()
{
if (m_texId == 0)
{
// Create checker pattern.
const unsigned int col0 = duRGBA(215, 215, 215, 255);
const unsigned int col1 = duRGBA(255, 255, 255, 255);
static const int TSIZE = 64;
unsigned int data[TSIZE * TSIZE];
glGenTextures(1, &m_texId);
glBindTexture(GL_TEXTURE_2D, m_texId);
int level = 0;
int size = TSIZE;
while (size > 0)
{
for (int y = 0; y < size; ++y)
for (int x = 0; x < size; ++x)
data[x + y * size] = (x == 0 || y == 0) ? col0 : col1;
glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
size /= 2;
level++;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
else
{
glBindTexture(GL_TEXTURE_2D, m_texId);
}
}
GLCheckerTexture::~GLCheckerTexture()
{
if (m_texId != 0)
{
glDeleteTextures(1, &m_texId);
}
}
void NavMeshDebugDrawer::depthMask(bool state)
{
glDepthMask(state ? GL_TRUE : GL_FALSE);
}
void NavMeshDebugDrawer::texture(bool state)
{
if (state)
{
glEnable(GL_TEXTURE_2D);
m_Texture.bind();
}
else
{
glDisable(GL_TEXTURE_2D);
}
}
void NavMeshDebugDrawer::begin(duDebugDrawPrimitives prim, float size)
{
switch (prim)
{
case DU_DRAW_POINTS:
glPointSize(size);
glBegin(GL_POINTS);
break;
case DU_DRAW_LINES:
glLineWidth(size);
glBegin(GL_LINES);
break;
case DU_DRAW_TRIS:
glBegin(GL_TRIANGLES);
break;
case DU_DRAW_QUADS:
glBegin(GL_QUADS);
break;
};
}
void NavMeshDebugDrawer::vertex(const float* pos, unsigned int color)
{
glColor4ubv((GLubyte*)&color);
glVertex3fv(pos);
}
void NavMeshDebugDrawer::vertex(const float x, const float y, const float z, unsigned int color)
{
glColor4ubv((GLubyte*)&color);
glVertex3f(x, y, z);
}
void NavMeshDebugDrawer::vertex(const float* pos, unsigned int color, const float* uv)
{
glColor4ubv((GLubyte*)&color);
glTexCoord2fv(uv);
glVertex3fv(pos);
}
void NavMeshDebugDrawer::vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v)
{
glColor4ubv((GLubyte*)&color);
glTexCoord2f(u, v);
glVertex3f(x, y, z);
}
void NavMeshDebugDrawer::end()
{
glEnd();
glLineWidth(1.0f);
glPointSize(1.0f);
}
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <DebugDraw.h>
namespace Nuake {
class GLCheckerTexture
{
private:
unsigned int m_texId;
public:
GLCheckerTexture();
~GLCheckerTexture();
void bind();
};
class NavMeshDebugDrawer : public duDebugDraw
{
private:
GLCheckerTexture m_Texture;
public:
virtual void depthMask(bool state);
virtual void texture(bool state);
virtual void begin(duDebugDrawPrimitives prim, float size = 1.0f);
virtual void vertex(const float* pos, unsigned int color);
virtual void vertex(const float x, const float y, const float z, unsigned int color);
virtual void vertex(const float* pos, unsigned int color, const float* uv);
virtual void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v);
virtual void end();
};
}

View File

@@ -8,7 +8,6 @@
#include <string>
namespace Nuake
{
typedef unsigned int GLenum;

View File

@@ -5,12 +5,16 @@
#include "src/Rendering/Mesh/Mesh.h"
#include "src/Resource/Serializable.h"
#include "src/Scene/Systems/QuakeMapBuilder.h"
#include "src/Scene/Entities/Entity.h"
#include "Engine.h"
namespace Nuake {
class QuakeMapComponent
{
public:
std::vector<Ref<Mesh>> m_Meshes;
std::vector<Entity> m_Brushes;
std::string Path;
float ScaleFactor = 1.0f;
bool HasCollisions = false;
@@ -22,6 +26,11 @@ namespace Nuake {
SERIALIZE_VAL(HasCollisions);
SERIALIZE_VAL(Path);
SERIALIZE_VAL(AutoRebuild);
for (uint32_t i = 0; i < std::size(m_Brushes); i++)
{
j["Brushes"][i] = m_Brushes[i].GetID();
}
for (unsigned int i = 0; i < m_Meshes.size(); i++)
{
j["Meshes"][i] = m_Meshes[i]->Serialize();
@@ -37,6 +46,14 @@ namespace Nuake {
this->AutoRebuild = j["AutoRebuild"];
}
if (j.contains("Brushes"))
{
for (auto& b : j["Brushes"])
{
//m_Brushes.push_back(Engine::GetCurrentScene()->GetEntityByID(b));
}
}
this->Path = j["Path"];
this->HasCollisions = j["HasCollisions"];
return true;

View File

@@ -513,6 +513,8 @@ namespace Nuake {
auto& transformComponent = brushEntity.GetComponent<TransformComponent>();
auto& bsp = brushEntity.AddComponent<BSPBrushComponent>();
quakeMapC.m_Brushes.push_back(brushEntity);
bsp.IsSolid = true;
bsp.IsTransparent = false;
bsp.IsFunc = false;

View File

@@ -35,7 +35,7 @@ include "Nuake/dependencies/jolt_p5.lua"
include "Nuake/dependencies/soloud_p5.lua"
include "Nuake/dependencies/optick_p5.lua"
include "Nuake/dependencies/coral_p5.lua"
include "Nuake/dependencies/recastnavigation_p5.lua"
include "NuakeNet/premake5.lua"
project "Nuake"
@@ -90,7 +90,12 @@ project "Nuake"
"%{prj.name}/src/Vendors/incbin",
"%{prj.name}/dependencies/build",
"%{prj.name}/dependencies/soloud/include",
"%{prj.name}/dependencies/Coral/Coral.Native/Include"
"%{prj.name}/dependencies/Coral/Coral.Native/Include",
"%{prj.name}/dependencies/recastnavigation/DebugUtils/Include",
"%{prj.name}/dependencies/recastnavigation/Detour/Include",
"%{prj.name}/dependencies/recastnavigation/DetourCrowd/Include",
"%{prj.name}/dependencies/recastnavigation/DetourTileCache/Include",
"%{prj.name}/dependencies/recastnavigation/Recast/Include"
}
links
@@ -286,7 +291,7 @@ project "Editor"
"%{prj.name}/src/**.h"
}
includedirs
includedirs
{
"%{prj.name}/../Nuake",
"%{prj.name}/../Nuake/src/Vendors",
@@ -295,10 +300,15 @@ project "Editor"
"%{prj.name}/../Nuake/dependencies/assimp/include",
"%{prj.name}/../Nuake/dependencies/build",
"%{prj.name}/../Nuake/src/Vendors/msdfgen",
"%{prj.name}/../Nuake/dependencies/JoltPhysics",
"%{prj.name}/../Nuake/dependencies/JoltPhysics",
"%{prj.name}/../Nuake/dependencies/build",
"%{prj.name}/../Nuake/dependencies/soloud/include",
"/usr/include/gtk-3.0/",
"%{prj.name}/../Nuake/dependencies/recastnavigation/DebugUtils/Include",
"%{prj.name}/../Nuake/dependencies/recastnavigation/Detour/Include",
"%{prj.name}/../Nuake/dependencies/recastnavigation/DetourCrowd/Include",
"%{prj.name}/../Nuake/dependencies/recastnavigation/DetourTileCache/Include",
"%{prj.name}/../Nuake/dependencies/recastnavigation/Recast/Include"
}
libdirs
@@ -325,7 +335,12 @@ project "Editor"
"Freetype",
"JoltPhysics",
"soloud",
"Coral.Native"
"Coral.Native",
"DebugUtils",
"Detour",
"DetourCrowd",
"DetourTileCache",
"Recast"
}
filter "system:Windows"