From 7874502ca22df5a56b8352f17c0baa4ef2fdae0c Mon Sep 17 00:00:00 2001 From: Nick Blakely Date: Mon, 4 Jan 2021 00:39:07 -0800 Subject: [PATCH] Initial commit for Recast based nav mesh generator. --- GiantsTools.sln | 24 + NavMeshGenerator/Framework/ChunkyTriMesh.cpp | 315 +++++++++ NavMeshGenerator/Framework/ChunkyTriMesh.h | 59 ++ NavMeshGenerator/Framework/InputGeom.cpp | 614 ++++++++++++++++++ NavMeshGenerator/Framework/InputGeom.h | 150 +++++ NavMeshGenerator/Framework/MeshLoaderObj.cpp | 245 +++++++ NavMeshGenerator/Framework/MeshLoaderObj.h | 56 ++ NavMeshGenerator/Framework/PerfTimer.cpp | 59 ++ NavMeshGenerator/Framework/PerfTimer.h | 32 + NavMeshGenerator/Framework/Sample.h | 190 ++++++ NavMeshGenerator/Framework/SampleInterfaces.h | 99 +++ NavMeshGenerator/Main.cpp | 49 ++ NavMeshGenerator/NavMeshGenerator.cpp | 527 +++++++++++++++ NavMeshGenerator/NavMeshGenerator.h | 77 +++ NavMeshGenerator/NavMeshGenerator.sln | 31 + NavMeshGenerator/NavMeshGenerator.vcxproj | 221 +++++++ .../NavMeshGenerator.vcxproj.filters | 243 +++++++ NavMeshGenerator/NavMeshUtil.h | 41 ++ NavMeshGenerator/RecastContext.cpp | 44 ++ NavMeshGenerator/RecastContext.h | 23 + NavMeshGenerator/pch.cpp | 0 NavMeshGenerator/pch.h | 3 + Plugins/imp_gbs/imp_gbs.vcxproj | 7 +- 23 files changed, 3106 insertions(+), 3 deletions(-) create mode 100644 NavMeshGenerator/Framework/ChunkyTriMesh.cpp create mode 100644 NavMeshGenerator/Framework/ChunkyTriMesh.h create mode 100644 NavMeshGenerator/Framework/InputGeom.cpp create mode 100644 NavMeshGenerator/Framework/InputGeom.h create mode 100644 NavMeshGenerator/Framework/MeshLoaderObj.cpp create mode 100644 NavMeshGenerator/Framework/MeshLoaderObj.h create mode 100644 NavMeshGenerator/Framework/PerfTimer.cpp create mode 100644 NavMeshGenerator/Framework/PerfTimer.h create mode 100644 NavMeshGenerator/Framework/Sample.h create mode 100644 NavMeshGenerator/Framework/SampleInterfaces.h create mode 100644 NavMeshGenerator/Main.cpp create mode 100644 NavMeshGenerator/NavMeshGenerator.cpp create mode 100644 NavMeshGenerator/NavMeshGenerator.h create mode 100644 NavMeshGenerator/NavMeshGenerator.sln create mode 100644 NavMeshGenerator/NavMeshGenerator.vcxproj create mode 100644 NavMeshGenerator/NavMeshGenerator.vcxproj.filters create mode 100644 NavMeshGenerator/NavMeshUtil.h create mode 100644 NavMeshGenerator/RecastContext.cpp create mode 100644 NavMeshGenerator/RecastContext.h create mode 100644 NavMeshGenerator/pch.cpp create mode 100644 NavMeshGenerator/pch.h diff --git a/GiantsTools.sln b/GiantsTools.sln index 91ea984..e444eed 100644 --- a/GiantsTools.sln +++ b/GiantsTools.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Giants.Services.Tests", "Gi EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServerConsole", "ServerConsoleExample\ServerConsole.vcxproj", "{8AEE9CFF-0E24-498F-B60A-627A7F4A727D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NavMeshGenerator", "NavMeshGenerator\NavMeshGenerator.vcxproj", "{CA9C0938-3ADA-4C73-A89A-E9447ABCE101}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -327,6 +329,28 @@ Global {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x64.Build.0 = Release|x64 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x86.ActiveCfg = Release|Win32 {8AEE9CFF-0E24-498F-B60A-627A7F4A727D}.ReleaseBeta|x86.Build.0 = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.ActiveCfg = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.Build.0 = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.Build.0 = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|Any CPU.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|Any CPU.Build.0 = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x64.ActiveCfg = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x64.Build.0 = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x86.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.DebugBeta|x86.Build.0 = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|Any CPU.ActiveCfg = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.ActiveCfg = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.Build.0 = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.ActiveCfg = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.Build.0 = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|Any CPU.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|Any CPU.Build.0 = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x64.ActiveCfg = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x64.Build.0 = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x86.ActiveCfg = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.ReleaseBeta|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NavMeshGenerator/Framework/ChunkyTriMesh.cpp b/NavMeshGenerator/Framework/ChunkyTriMesh.cpp new file mode 100644 index 0000000..242bf28 --- /dev/null +++ b/NavMeshGenerator/Framework/ChunkyTriMesh.cpp @@ -0,0 +1,315 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "ChunkyTriMesh.h" +#include +#include +#include + +struct BoundsItem +{ + float bmin[2]; + float bmax[2]; + int i; +}; + +static int compareItemX(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[0] < b->bmin[0]) + return -1; + if (a->bmin[0] > b->bmin[0]) + return 1; + return 0; +} + +static int compareItemY(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[1] < b->bmin[1]) + return -1; + if (a->bmin[1] > b->bmin[1]) + return 1; + return 0; +} + +static void calcExtends(const BoundsItem* items, const int /*nitems*/, + const int imin, const int imax, + float* bmin, float* bmax) +{ + bmin[0] = items[imin].bmin[0]; + bmin[1] = items[imin].bmin[1]; + + bmax[0] = items[imin].bmax[0]; + bmax[1] = items[imin].bmax[1]; + + for (int i = imin+1; i < imax; ++i) + { + const BoundsItem& it = items[i]; + if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; + if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; + + if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; + if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; + } +} + +inline int longestAxis(float x, float y) +{ + return y > x ? 1 : 0; +} + +static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk, + int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes, + int& curTri, int* outTris, const int* inTris) +{ + int inum = imax - imin; + int icur = curNode; + + if (curNode >= maxNodes) + return; + + rcChunkyTriMeshNode& node = nodes[curNode++]; + + if (inum <= trisPerChunk) + { + // Leaf + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + // Copy triangles. + node.i = curTri; + node.n = inum; + + for (int i = imin; i < imax; ++i) + { + const int* src = &inTris[items[i].i*3]; + int* dst = &outTris[curTri*3]; + curTri++; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } + else + { + // Split + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + int axis = longestAxis(node.bmax[0] - node.bmin[0], + node.bmax[1] - node.bmin[1]); + + if (axis == 0) + { + // Sort along x-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemX); + } + else if (axis == 1) + { + // Sort along y-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemY); + } + + int isplit = imin+inum/2; + + // Left + subdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + // Right + subdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + + int iescape = curNode - icur; + // Negative index means escape. + node.i = -iescape; + } +} + +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm) +{ + int nchunks = (ntris + trisPerChunk-1) / trisPerChunk; + + cm->nodes = new rcChunkyTriMeshNode[nchunks*4]; + if (!cm->nodes) + return false; + + cm->tris = new int[ntris*3]; + if (!cm->tris) + return false; + + cm->ntris = ntris; + + // Build tree + BoundsItem* items = new BoundsItem[ntris]; + if (!items) + return false; + + for (int i = 0; i < ntris; i++) + { + const int* t = &tris[i*3]; + BoundsItem& it = items[i]; + it.i = i; + // Calc triangle XZ bounds. + it.bmin[0] = it.bmax[0] = verts[t[0]*3+0]; + it.bmin[1] = it.bmax[1] = verts[t[0]*3+2]; + for (int j = 1; j < 3; ++j) + { + const float* v = &verts[t[j]*3]; + if (v[0] < it.bmin[0]) it.bmin[0] = v[0]; + if (v[2] < it.bmin[1]) it.bmin[1] = v[2]; + + if (v[0] > it.bmax[0]) it.bmax[0] = v[0]; + if (v[2] > it.bmax[1]) it.bmax[1] = v[2]; + } + } + + int curTri = 0; + int curNode = 0; + subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris); + + delete [] items; + + cm->nnodes = curNode; + + // Calc max tris per node. + cm->maxTrisPerChunk = 0; + for (int i = 0; i < cm->nnodes; ++i) + { + rcChunkyTriMeshNode& node = cm->nodes[i]; + const bool isLeaf = node.i >= 0; + if (!isLeaf) continue; + if (node.n > cm->maxTrisPerChunk) + cm->maxTrisPerChunk = node.n; + } + + return true; +} + + +inline bool checkOverlapRect(const float amin[2], const float amax[2], + const float bmin[2], const float bmax[2]) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + return overlap; +} + +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, + float bmin[2], float bmax[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} + + + +static bool checkOverlapSegment(const float p[2], const float q[2], + const float bmin[2], const float bmax[2]) +{ + static const float EPSILON = 1e-6f; + + float tmin = 0; + float tmax = 1; + float d[2]; + d[0] = q[0] - p[0]; + d[1] = q[1] - p[1]; + + for (int i = 0; i < 2; i++) + { + if (fabsf(d[i]) < EPSILON) + { + // Ray is parallel to slab. No hit if origin not within slab + if (p[i] < bmin[i] || p[i] > bmax[i]) + return false; + } + else + { + // Compute intersection t value of ray with near and far plane of slab + float ood = 1.0f / d[i]; + float t1 = (bmin[i] - p[i]) * ood; + float t2 = (bmax[i] - p[i]) * ood; + if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tmin) tmin = t1; + if (t2 < tmax) tmax = t2; + if (tmin > tmax) return false; + } + } + return true; +} + +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, + float p[2], float q[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} diff --git a/NavMeshGenerator/Framework/ChunkyTriMesh.h b/NavMeshGenerator/Framework/ChunkyTriMesh.h new file mode 100644 index 0000000..6584979 --- /dev/null +++ b/NavMeshGenerator/Framework/ChunkyTriMesh.h @@ -0,0 +1,59 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef CHUNKYTRIMESH_H +#define CHUNKYTRIMESH_H + +struct rcChunkyTriMeshNode +{ + float bmin[2]; + float bmax[2]; + int i; + int n; +}; + +struct rcChunkyTriMesh +{ + inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {}; + inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; } + + rcChunkyTriMeshNode* nodes; + int nnodes; + int* tris; + int ntris; + int maxTrisPerChunk; + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcChunkyTriMesh(const rcChunkyTriMesh&); + rcChunkyTriMesh& operator=(const rcChunkyTriMesh&); +}; + +/// Creates partitioned triangle mesh (AABB tree), +/// where each node contains at max trisPerChunk triangles. +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm); + +/// Returns the chunk indices which overlap the input rectable. +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds); + +/// Returns the chunk indices which overlap the input segment. +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds); + + +#endif // CHUNKYTRIMESH_H diff --git a/NavMeshGenerator/Framework/InputGeom.cpp b/NavMeshGenerator/Framework/InputGeom.cpp new file mode 100644 index 0000000..fc6af82 --- /dev/null +++ b/NavMeshGenerator/Framework/InputGeom.cpp @@ -0,0 +1,614 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include "Recast.h" +#include "InputGeom.h" +#include "ChunkyTriMesh.h" +#include "MeshLoaderObj.h" +#include "DebugDraw.h" +#include "RecastDebugDraw.h" +#include "DetourNavMesh.h" +#include "Sample.h" + +static bool intersectSegmentTriangle(const float* sp, const float* sq, + const float* a, const float* b, const float* c, + float &t) +{ + float v, w; + float ab[3], ac[3], qp[3], ap[3], norm[3], e[3]; + rcVsub(ab, b, a); + rcVsub(ac, c, a); + rcVsub(qp, sp, sq); + + // Compute triangle normal. Can be precalculated or cached if + // intersecting multiple segments against the same triangle + rcVcross(norm, ab, ac); + + // Compute denominator d. If d <= 0, segment is parallel to or points + // away from triangle, so exit early + float d = rcVdot(qp, norm); + if (d <= 0.0f) return false; + + // Compute intersection t value of pq with plane of triangle. A ray + // intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay + // dividing by d until intersection has been found to pierce triangle + rcVsub(ap, sp, a); + t = rcVdot(ap, norm); + if (t < 0.0f) return false; + if (t > d) return false; // For segment; exclude this code line for a ray test + + // Compute barycentric coordinate components and test if within bounds + rcVcross(e, qp, ap); + v = rcVdot(ac, e); + if (v < 0.0f || v > d) return false; + w = -rcVdot(ab, e); + if (w < 0.0f || v + w > d) return false; + + // Segment/ray intersects triangle. Perform delayed division + t /= d; + + return true; +} + +static char* parseRow(char* buf, char* bufEnd, char* row, int len) +{ + bool start = true; + bool done = false; + int n = 0; + while (!done && buf < bufEnd) + { + char c = *buf; + buf++; + // multirow + switch (c) + { + case '\n': + if (start) break; + done = true; + break; + case '\r': + break; + case '\t': + case ' ': + if (start) break; + // else falls through + default: + start = false; + row[n++] = c; + if (n >= len-1) + done = true; + break; + } + } + row[n] = '\0'; + return buf; +} + + + +InputGeom::InputGeom() : + m_chunkyMesh(0), + m_mesh(0), + m_hasBuildSettings(false), + m_offMeshConCount(0), + m_volumeCount(0) +{ +} + +InputGeom::~InputGeom() +{ + delete m_chunkyMesh; + delete m_mesh; +} + +bool InputGeom::loadMesh(rcContext* ctx, const std::string& filepath) +{ + if (m_mesh) + { + delete m_chunkyMesh; + m_chunkyMesh = 0; + delete m_mesh; + m_mesh = 0; + } + m_offMeshConCount = 0; + m_volumeCount = 0; + + m_mesh = new rcMeshLoaderObj; + if (!m_mesh) + { + ctx->log(RC_LOG_ERROR, "loadMesh: Out of memory 'm_mesh'."); + return false; + } + if (!m_mesh->load(filepath)) + { + ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath.c_str()); + return false; + } + + rcCalcBounds(m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax); + + m_chunkyMesh = new rcChunkyTriMesh; + if (!m_chunkyMesh) + { + ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'."); + return false; + } + if (!rcCreateChunkyTriMesh(m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh)) + { + ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Failed to build chunky mesh."); + return false; + } + + return true; +} + +bool InputGeom::loadGeomSet(rcContext* ctx, const std::string& filepath) +{ + char* buf = 0; + FILE* fp = fopen(filepath.c_str(), "rb"); + if (!fp) + { + return false; + } + if (fseek(fp, 0, SEEK_END) != 0) + { + fclose(fp); + return false; + } + + long bufSize = ftell(fp); + if (bufSize < 0) + { + fclose(fp); + return false; + } + if (fseek(fp, 0, SEEK_SET) != 0) + { + fclose(fp); + return false; + } + buf = new char[bufSize]; + if (!buf) + { + fclose(fp); + return false; + } + size_t readLen = fread(buf, bufSize, 1, fp); + fclose(fp); + if (readLen != 1) + { + delete[] buf; + return false; + } + + m_offMeshConCount = 0; + m_volumeCount = 0; + delete m_mesh; + m_mesh = 0; + + char* src = buf; + char* srcEnd = buf + bufSize; + char row[512]; + while (src < srcEnd) + { + // Parse one row + row[0] = '\0'; + src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char)); + if (row[0] == 'f') + { + // File name. + const char* name = row+1; + // Skip white spaces + while (*name && isspace(*name)) + name++; + if (*name) + { + if (!loadMesh(ctx, name)) + { + delete [] buf; + return false; + } + } + } + else if (row[0] == 'c') + { + // Off-mesh connection + if (m_offMeshConCount < MAX_OFFMESH_CONNECTIONS) + { + float* v = &m_offMeshConVerts[m_offMeshConCount*3*2]; + int bidir, area = 0, flags = 0; + float rad; + sscanf(row+1, "%f %f %f %f %f %f %f %d %d %d", + &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rad, &bidir, &area, &flags); + m_offMeshConRads[m_offMeshConCount] = rad; + m_offMeshConDirs[m_offMeshConCount] = (unsigned char)bidir; + m_offMeshConAreas[m_offMeshConCount] = (unsigned char)area; + m_offMeshConFlags[m_offMeshConCount] = (unsigned short)flags; + m_offMeshConCount++; + } + } + else if (row[0] == 'v') + { + // Convex volumes + if (m_volumeCount < MAX_VOLUMES) + { + ConvexVolume* vol = &m_volumes[m_volumeCount++]; + sscanf(row+1, "%d %d %f %f", &vol->nverts, &vol->area, &vol->hmin, &vol->hmax); + for (int i = 0; i < vol->nverts; ++i) + { + row[0] = '\0'; + src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char)); + sscanf(row, "%f %f %f", &vol->verts[i*3+0], &vol->verts[i*3+1], &vol->verts[i*3+2]); + } + } + } + else if (row[0] == 's') + { + // Settings + m_hasBuildSettings = true; + sscanf(row + 1, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f", + &m_buildSettings.cellSize, + &m_buildSettings.cellHeight, + &m_buildSettings.agentHeight, + &m_buildSettings.agentRadius, + &m_buildSettings.agentMaxClimb, + &m_buildSettings.agentMaxSlope, + &m_buildSettings.regionMinSize, + &m_buildSettings.regionMergeSize, + &m_buildSettings.edgeMaxLen, + &m_buildSettings.edgeMaxError, + &m_buildSettings.vertsPerPoly, + &m_buildSettings.detailSampleDist, + &m_buildSettings.detailSampleMaxError, + &m_buildSettings.partitionType, + &m_buildSettings.navMeshBMin[0], + &m_buildSettings.navMeshBMin[1], + &m_buildSettings.navMeshBMin[2], + &m_buildSettings.navMeshBMax[0], + &m_buildSettings.navMeshBMax[1], + &m_buildSettings.navMeshBMax[2], + &m_buildSettings.tileSize); + } + } + + delete [] buf; + + return true; +} + +bool InputGeom::load(rcContext* ctx, const std::string& filepath) +{ + size_t extensionPos = filepath.find_last_of('.'); + if (extensionPos == std::string::npos) + return false; + + std::string extension = filepath.substr(extensionPos); + std::transform(extension.begin(), extension.end(), extension.begin(), tolower); + + if (extension == ".gset") + return loadGeomSet(ctx, filepath); + if (extension == ".obj") + return loadMesh(ctx, filepath); + + return false; +} + +bool InputGeom::saveGeomSet(const BuildSettings* settings) +{ + if (!m_mesh) return false; + + // Change extension + std::string filepath = m_mesh->getFileName(); + size_t extPos = filepath.find_last_of('.'); + if (extPos != std::string::npos) + filepath = filepath.substr(0, extPos); + + filepath += ".gset"; + + FILE* fp = fopen(filepath.c_str(), "w"); + if (!fp) return false; + + // Store mesh filename. + fprintf(fp, "f %s\n", m_mesh->getFileName().c_str()); + + // Store settings if any + if (settings) + { + fprintf(fp, + "s %f %f %f %f %f %f %f %f %f %f %f %f %f %d %f %f %f %f %f %f %f\n", + settings->cellSize, + settings->cellHeight, + settings->agentHeight, + settings->agentRadius, + settings->agentMaxClimb, + settings->agentMaxSlope, + settings->regionMinSize, + settings->regionMergeSize, + settings->edgeMaxLen, + settings->edgeMaxError, + settings->vertsPerPoly, + settings->detailSampleDist, + settings->detailSampleMaxError, + settings->partitionType, + settings->navMeshBMin[0], + settings->navMeshBMin[1], + settings->navMeshBMin[2], + settings->navMeshBMax[0], + settings->navMeshBMax[1], + settings->navMeshBMax[2], + settings->tileSize); + } + + // Store off-mesh links. + for (int i = 0; i < m_offMeshConCount; ++i) + { + const float* v = &m_offMeshConVerts[i*3*2]; + const float rad = m_offMeshConRads[i]; + const int bidir = m_offMeshConDirs[i]; + const int area = m_offMeshConAreas[i]; + const int flags = m_offMeshConFlags[i]; + fprintf(fp, "c %f %f %f %f %f %f %f %d %d %d\n", + v[0], v[1], v[2], v[3], v[4], v[5], rad, bidir, area, flags); + } + + // Convex volumes + for (int i = 0; i < m_volumeCount; ++i) + { + ConvexVolume* vol = &m_volumes[i]; + fprintf(fp, "v %d %d %f %f\n", vol->nverts, vol->area, vol->hmin, vol->hmax); + for (int j = 0; j < vol->nverts; ++j) + fprintf(fp, "%f %f %f\n", vol->verts[j*3+0], vol->verts[j*3+1], vol->verts[j*3+2]); + } + + fclose(fp); + + return true; +} + +static bool isectSegAABB(const float* sp, const float* sq, + const float* amin, const float* amax, + float& tmin, float& tmax) +{ + static const float EPS = 1e-6f; + + float d[3]; + d[0] = sq[0] - sp[0]; + d[1] = sq[1] - sp[1]; + d[2] = sq[2] - sp[2]; + tmin = 0.0; + tmax = 1.0f; + + for (int i = 0; i < 3; i++) + { + if (fabsf(d[i]) < EPS) + { + if (sp[i] < amin[i] || sp[i] > amax[i]) + return false; + } + else + { + const float ood = 1.0f / d[i]; + float t1 = (amin[i] - sp[i]) * ood; + float t2 = (amax[i] - sp[i]) * ood; + if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tmin) tmin = t1; + if (t2 < tmax) tmax = t2; + if (tmin > tmax) return false; + } + } + + return true; +} + + +bool InputGeom::raycastMesh(float* src, float* dst, float& tmin) +{ + // Prune hit ray. + float btmin, btmax; + if (!isectSegAABB(src, dst, m_meshBMin, m_meshBMax, btmin, btmax)) + return false; + float p[2], q[2]; + p[0] = src[0] + (dst[0]-src[0])*btmin; + p[1] = src[2] + (dst[2]-src[2])*btmin; + q[0] = src[0] + (dst[0]-src[0])*btmax; + q[1] = src[2] + (dst[2]-src[2])*btmax; + + int cid[512]; + const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, cid, 512); + if (!ncid) + return false; + + tmin = 1.0f; + bool hit = false; + const float* verts = m_mesh->getVerts(); + + for (int i = 0; i < ncid; ++i) + { + const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]]; + const int* tris = &m_chunkyMesh->tris[node.i*3]; + const int ntris = node.n; + + for (int j = 0; j < ntris*3; j += 3) + { + float t = 1; + if (intersectSegmentTriangle(src, dst, + &verts[tris[j]*3], + &verts[tris[j+1]*3], + &verts[tris[j+2]*3], t)) + { + if (t < tmin) + tmin = t; + hit = true; + } + } + } + + return hit; +} + +void InputGeom::addOffMeshConnection(const float* spos, const float* epos, const float rad, + unsigned char bidir, unsigned char area, unsigned short flags) +{ + if (m_offMeshConCount >= MAX_OFFMESH_CONNECTIONS) return; + float* v = &m_offMeshConVerts[m_offMeshConCount*3*2]; + m_offMeshConRads[m_offMeshConCount] = rad; + m_offMeshConDirs[m_offMeshConCount] = bidir; + m_offMeshConAreas[m_offMeshConCount] = area; + m_offMeshConFlags[m_offMeshConCount] = flags; + m_offMeshConId[m_offMeshConCount] = 1000 + m_offMeshConCount; + rcVcopy(&v[0], spos); + rcVcopy(&v[3], epos); + m_offMeshConCount++; +} + +void InputGeom::deleteOffMeshConnection(int i) +{ + m_offMeshConCount--; + float* src = &m_offMeshConVerts[m_offMeshConCount*3*2]; + float* dst = &m_offMeshConVerts[i*3*2]; + rcVcopy(&dst[0], &src[0]); + rcVcopy(&dst[3], &src[3]); + m_offMeshConRads[i] = m_offMeshConRads[m_offMeshConCount]; + m_offMeshConDirs[i] = m_offMeshConDirs[m_offMeshConCount]; + m_offMeshConAreas[i] = m_offMeshConAreas[m_offMeshConCount]; + m_offMeshConFlags[i] = m_offMeshConFlags[m_offMeshConCount]; +} + +void InputGeom::drawOffMeshConnections(duDebugDraw* dd, bool hilight) +{ + unsigned int conColor = duRGBA(192,0,128,192); + unsigned int baseColor = duRGBA(0,0,0,64); + dd->depthMask(false); + + dd->begin(DU_DRAW_LINES, 2.0f); + for (int i = 0; i < m_offMeshConCount; ++i) + { + float* v = &m_offMeshConVerts[i*3*2]; + + dd->vertex(v[0],v[1],v[2], baseColor); + dd->vertex(v[0],v[1]+0.2f,v[2], baseColor); + + dd->vertex(v[3],v[4],v[5], baseColor); + dd->vertex(v[3],v[4]+0.2f,v[5], baseColor); + + duAppendCircle(dd, v[0],v[1]+0.1f,v[2], m_offMeshConRads[i], baseColor); + duAppendCircle(dd, v[3],v[4]+0.1f,v[5], m_offMeshConRads[i], baseColor); + + if (hilight) + { + duAppendArc(dd, v[0],v[1],v[2], v[3],v[4],v[5], 0.25f, + (m_offMeshConDirs[i]&1) ? 0.6f : 0.0f, 0.6f, conColor); + } + } + dd->end(); + + dd->depthMask(true); +} + +void InputGeom::addConvexVolume(const float* verts, const int nverts, + const float minh, const float maxh, unsigned char area) +{ + if (m_volumeCount >= MAX_VOLUMES) return; + ConvexVolume* vol = &m_volumes[m_volumeCount++]; + memset(vol, 0, sizeof(ConvexVolume)); + memcpy(vol->verts, verts, sizeof(float)*3*nverts); + vol->hmin = minh; + vol->hmax = maxh; + vol->nverts = nverts; + vol->area = area; +} + +void InputGeom::deleteConvexVolume(int i) +{ + m_volumeCount--; + m_volumes[i] = m_volumes[m_volumeCount]; +} + +void InputGeom::drawConvexVolumes(struct duDebugDraw* dd, bool /*hilight*/) +{ + dd->depthMask(false); + + dd->begin(DU_DRAW_TRIS); + + for (int i = 0; i < m_volumeCount; ++i) + { + const ConvexVolume* vol = &m_volumes[i]; + unsigned int col = duTransCol(dd->areaToCol(vol->area), 32); + for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++) + { + const float* va = &vol->verts[k*3]; + const float* vb = &vol->verts[j*3]; + + dd->vertex(vol->verts[0],vol->hmax,vol->verts[2], col); + dd->vertex(vb[0],vol->hmax,vb[2], col); + dd->vertex(va[0],vol->hmax,va[2], col); + + dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col)); + dd->vertex(va[0],vol->hmax,va[2], col); + dd->vertex(vb[0],vol->hmax,vb[2], col); + + dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col)); + dd->vertex(vb[0],vol->hmax,vb[2], col); + dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col)); + } + } + + dd->end(); + + dd->begin(DU_DRAW_LINES, 2.0f); + for (int i = 0; i < m_volumeCount; ++i) + { + const ConvexVolume* vol = &m_volumes[i]; + unsigned int col = duTransCol(dd->areaToCol(vol->area), 220); + for (int j = 0, k = vol->nverts-1; j < vol->nverts; k = j++) + { + const float* va = &vol->verts[k*3]; + const float* vb = &vol->verts[j*3]; + dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col)); + dd->vertex(vb[0],vol->hmin,vb[2], duDarkenCol(col)); + dd->vertex(va[0],vol->hmax,va[2], col); + dd->vertex(vb[0],vol->hmax,vb[2], col); + dd->vertex(va[0],vol->hmin,va[2], duDarkenCol(col)); + dd->vertex(va[0],vol->hmax,va[2], col); + } + } + dd->end(); + + dd->begin(DU_DRAW_POINTS, 3.0f); + for (int i = 0; i < m_volumeCount; ++i) + { + const ConvexVolume* vol = &m_volumes[i]; + unsigned int col = duDarkenCol(duTransCol(dd->areaToCol(vol->area), 220)); + for (int j = 0; j < vol->nverts; ++j) + { + dd->vertex(vol->verts[j*3+0],vol->verts[j*3+1]+0.1f,vol->verts[j*3+2], col); + dd->vertex(vol->verts[j*3+0],vol->hmin,vol->verts[j*3+2], col); + dd->vertex(vol->verts[j*3+0],vol->hmax,vol->verts[j*3+2], col); + } + } + dd->end(); + + + dd->depthMask(true); +} diff --git a/NavMeshGenerator/Framework/InputGeom.h b/NavMeshGenerator/Framework/InputGeom.h new file mode 100644 index 0000000..edc1295 --- /dev/null +++ b/NavMeshGenerator/Framework/InputGeom.h @@ -0,0 +1,150 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef INPUTGEOM_H +#define INPUTGEOM_H + +#include "ChunkyTriMesh.h" +#include "MeshLoaderObj.h" + +static const int MAX_CONVEXVOL_PTS = 12; +struct ConvexVolume +{ + float verts[MAX_CONVEXVOL_PTS*3]; + float hmin, hmax; + int nverts; + int area; +}; + +struct BuildSettings +{ + // Cell size in world units + float cellSize; + // Cell height in world units + float cellHeight; + // Agent height in world units + float agentHeight; + // Agent radius in world units + float agentRadius; + // Agent max climb in world units + float agentMaxClimb; + // Agent max slope in degrees + float agentMaxSlope; + // Region minimum size in voxels. + // regionMinSize = sqrt(regionMinArea) + float regionMinSize; + // Region merge size in voxels. + // regionMergeSize = sqrt(regionMergeArea) + float regionMergeSize; + // Edge max length in world units + float edgeMaxLen; + // Edge max error in voxels + float edgeMaxError; + float vertsPerPoly; + // Detail sample distance in voxels + float detailSampleDist; + // Detail sample max error in voxel heights. + float detailSampleMaxError; + // Partition type, see SamplePartitionType + int partitionType; + // Bounds of the area to mesh + float navMeshBMin[3]; + float navMeshBMax[3]; + // Size of the tiles in voxels + float tileSize; +}; + +class InputGeom +{ + rcChunkyTriMesh* m_chunkyMesh; + rcMeshLoaderObj* m_mesh; + float m_meshBMin[3], m_meshBMax[3]; + BuildSettings m_buildSettings; + bool m_hasBuildSettings; + + /// @name Off-Mesh connections. + ///@{ + static const int MAX_OFFMESH_CONNECTIONS = 256; + float m_offMeshConVerts[MAX_OFFMESH_CONNECTIONS*3*2]; + float m_offMeshConRads[MAX_OFFMESH_CONNECTIONS]; + unsigned char m_offMeshConDirs[MAX_OFFMESH_CONNECTIONS]; + unsigned char m_offMeshConAreas[MAX_OFFMESH_CONNECTIONS]; + unsigned short m_offMeshConFlags[MAX_OFFMESH_CONNECTIONS]; + unsigned int m_offMeshConId[MAX_OFFMESH_CONNECTIONS]; + int m_offMeshConCount; + ///@} + + /// @name Convex Volumes. + ///@{ + static const int MAX_VOLUMES = 256; + ConvexVolume m_volumes[MAX_VOLUMES]; + int m_volumeCount; + ///@} + + bool loadMesh(class rcContext* ctx, const std::string& filepath); + bool loadGeomSet(class rcContext* ctx, const std::string& filepath); +public: + InputGeom(); + ~InputGeom(); + + + bool load(class rcContext* ctx, const std::string& filepath); + bool saveGeomSet(const BuildSettings* settings); + + /// Method to return static mesh data. + const rcMeshLoaderObj* getMesh() const { return m_mesh; } + const float* getMeshBoundsMin() const { return m_meshBMin; } + const float* getMeshBoundsMax() const { return m_meshBMax; } + const float* getNavMeshBoundsMin() const { return m_hasBuildSettings ? m_buildSettings.navMeshBMin : m_meshBMin; } + const float* getNavMeshBoundsMax() const { return m_hasBuildSettings ? m_buildSettings.navMeshBMax : m_meshBMax; } + const rcChunkyTriMesh* getChunkyMesh() const { return m_chunkyMesh; } + const BuildSettings* getBuildSettings() const { return m_hasBuildSettings ? &m_buildSettings : 0; } + bool raycastMesh(float* src, float* dst, float& tmin); + + /// @name Off-Mesh connections. + ///@{ + int getOffMeshConnectionCount() const { return m_offMeshConCount; } + const float* getOffMeshConnectionVerts() const { return m_offMeshConVerts; } + const float* getOffMeshConnectionRads() const { return m_offMeshConRads; } + const unsigned char* getOffMeshConnectionDirs() const { return m_offMeshConDirs; } + const unsigned char* getOffMeshConnectionAreas() const { return m_offMeshConAreas; } + const unsigned short* getOffMeshConnectionFlags() const { return m_offMeshConFlags; } + const unsigned int* getOffMeshConnectionId() const { return m_offMeshConId; } + void addOffMeshConnection(const float* spos, const float* epos, const float rad, + unsigned char bidir, unsigned char area, unsigned short flags); + void deleteOffMeshConnection(int i); + void drawOffMeshConnections(struct duDebugDraw* dd, bool hilight = false); + ///@} + + /// @name Box Volumes. + ///@{ + int getConvexVolumeCount() const { return m_volumeCount; } + const ConvexVolume* getConvexVolumes() const { return m_volumes; } + void addConvexVolume(const float* verts, const int nverts, + const float minh, const float maxh, unsigned char area); + void deleteConvexVolume(int i); + void drawConvexVolumes(struct duDebugDraw* dd, bool hilight = false); + ///@} + +private: + // Explicitly disabled copy constructor and copy assignment operator. + InputGeom(const InputGeom&); + InputGeom& operator=(const InputGeom&); +}; + +#endif // INPUTGEOM_H diff --git a/NavMeshGenerator/Framework/MeshLoaderObj.cpp b/NavMeshGenerator/Framework/MeshLoaderObj.cpp new file mode 100644 index 0000000..38e6c4c --- /dev/null +++ b/NavMeshGenerator/Framework/MeshLoaderObj.cpp @@ -0,0 +1,245 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "MeshLoaderObj.h" +#include +#include +#include +#define _USE_MATH_DEFINES +#include + +rcMeshLoaderObj::rcMeshLoaderObj() : + m_scale(1.0f), + m_verts(0), + m_tris(0), + m_normals(0), + m_vertCount(0), + m_triCount(0) +{ +} + +rcMeshLoaderObj::~rcMeshLoaderObj() +{ + delete [] m_verts; + delete [] m_normals; + delete [] m_tris; +} + +void rcMeshLoaderObj::addVertex(float x, float y, float z, int& cap) +{ + if (m_vertCount+1 > cap) + { + cap = !cap ? 8 : cap*2; + float* nv = new float[cap*3]; + if (m_vertCount) + memcpy(nv, m_verts, m_vertCount*3*sizeof(float)); + delete [] m_verts; + m_verts = nv; + } + float* dst = &m_verts[m_vertCount*3]; + *dst++ = x*m_scale; + *dst++ = y*m_scale; + *dst++ = z*m_scale; + m_vertCount++; +} + +void rcMeshLoaderObj::addTriangle(int a, int b, int c, int& cap) +{ + if (m_triCount+1 > cap) + { + cap = !cap ? 8 : cap*2; + int* nv = new int[cap*3]; + if (m_triCount) + memcpy(nv, m_tris, m_triCount*3*sizeof(int)); + delete [] m_tris; + m_tris = nv; + } + int* dst = &m_tris[m_triCount*3]; + *dst++ = a; + *dst++ = b; + *dst++ = c; + m_triCount++; +} + +static char* parseRow(char* buf, char* bufEnd, char* row, int len) +{ + bool start = true; + bool done = false; + int n = 0; + while (!done && buf < bufEnd) + { + char c = *buf; + buf++; + // multirow + switch (c) + { + case '\\': + break; + case '\n': + if (start) break; + done = true; + break; + case '\r': + break; + case '\t': + case ' ': + if (start) break; + // else falls through + default: + start = false; + row[n++] = c; + if (n >= len-1) + done = true; + break; + } + } + row[n] = '\0'; + return buf; +} + +static int parseFace(char* row, int* data, int n, int vcnt) +{ + int j = 0; + while (*row != '\0') + { + // Skip initial white space + while (*row != '\0' && (*row == ' ' || *row == '\t')) + row++; + char* s = row; + // Find vertex delimiter and terminated the string there for conversion. + while (*row != '\0' && *row != ' ' && *row != '\t') + { + if (*row == '/') *row = '\0'; + row++; + } + if (*s == '\0') + continue; + int vi = atoi(s); + data[j++] = vi < 0 ? vi+vcnt : vi-1; + if (j >= n) return j; + } + return j; +} + +bool rcMeshLoaderObj::load(const std::string& filename) +{ + char* buf = 0; + FILE* fp = fopen(filename.c_str(), "rb"); + if (!fp) + return false; + if (fseek(fp, 0, SEEK_END) != 0) + { + fclose(fp); + return false; + } + long bufSize = ftell(fp); + if (bufSize < 0) + { + fclose(fp); + return false; + } + if (fseek(fp, 0, SEEK_SET) != 0) + { + fclose(fp); + return false; + } + buf = new char[bufSize]; + if (!buf) + { + fclose(fp); + return false; + } + size_t readLen = fread(buf, bufSize, 1, fp); + fclose(fp); + + if (readLen != 1) + { + delete[] buf; + return false; + } + + char* src = buf; + char* srcEnd = buf + bufSize; + char row[512]; + int face[32]; + float x,y,z; + int nv; + int vcap = 0; + int tcap = 0; + + while (src < srcEnd) + { + // Parse one row + row[0] = '\0'; + src = parseRow(src, srcEnd, row, sizeof(row)/sizeof(char)); + // Skip comments + if (row[0] == '#') continue; + if (row[0] == 'v' && row[1] != 'n' && row[1] != 't') + { + // Vertex pos + sscanf(row+1, "%f %f %f", &x, &y, &z); + addVertex(x, y, z, vcap); + } + if (row[0] == 'f') + { + // Faces + nv = parseFace(row+1, face, 32, m_vertCount); + for (int i = 2; i < nv; ++i) + { + const int a = face[0]; + const int b = face[i-1]; + const int c = face[i]; + if (a < 0 || a >= m_vertCount || b < 0 || b >= m_vertCount || c < 0 || c >= m_vertCount) + continue; + addTriangle(a, b, c, tcap); + } + } + } + + delete [] buf; + + // Calculate normals. + m_normals = new float[m_triCount*3]; + for (int i = 0; i < m_triCount*3; i += 3) + { + const float* v0 = &m_verts[m_tris[i]*3]; + const float* v1 = &m_verts[m_tris[i+1]*3]; + const float* v2 = &m_verts[m_tris[i+2]*3]; + float e0[3], e1[3]; + for (int j = 0; j < 3; ++j) + { + e0[j] = v1[j] - v0[j]; + e1[j] = v2[j] - v0[j]; + } + float* n = &m_normals[i]; + n[0] = e0[1]*e1[2] - e0[2]*e1[1]; + n[1] = e0[2]*e1[0] - e0[0]*e1[2]; + n[2] = e0[0]*e1[1] - e0[1]*e1[0]; + float d = sqrtf(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); + if (d > 0) + { + d = 1.0f/d; + n[0] *= d; + n[1] *= d; + n[2] *= d; + } + } + + m_filename = filename; + return true; +} diff --git a/NavMeshGenerator/Framework/MeshLoaderObj.h b/NavMeshGenerator/Framework/MeshLoaderObj.h new file mode 100644 index 0000000..8567432 --- /dev/null +++ b/NavMeshGenerator/Framework/MeshLoaderObj.h @@ -0,0 +1,56 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef MESHLOADER_OBJ +#define MESHLOADER_OBJ + +#include + +class rcMeshLoaderObj +{ +public: + rcMeshLoaderObj(); + ~rcMeshLoaderObj(); + + bool load(const std::string& fileName); + + const float* getVerts() const { return m_verts; } + const float* getNormals() const { return m_normals; } + const int* getTris() const { return m_tris; } + int getVertCount() const { return m_vertCount; } + int getTriCount() const { return m_triCount; } + const std::string& getFileName() const { return m_filename; } + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcMeshLoaderObj(const rcMeshLoaderObj&); + rcMeshLoaderObj& operator=(const rcMeshLoaderObj&); + + void addVertex(float x, float y, float z, int& cap); + void addTriangle(int a, int b, int c, int& cap); + + std::string m_filename; + float m_scale; + float* m_verts; + int* m_tris; + float* m_normals; + int m_vertCount; + int m_triCount; +}; + +#endif // MESHLOADER_OBJ diff --git a/NavMeshGenerator/Framework/PerfTimer.cpp b/NavMeshGenerator/Framework/PerfTimer.cpp new file mode 100644 index 0000000..a8c86bd --- /dev/null +++ b/NavMeshGenerator/Framework/PerfTimer.cpp @@ -0,0 +1,59 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "PerfTimer.h" + +#if defined(WIN32) + +// Win32 +#include + +TimeVal getPerfTime() +{ + __int64 count; + QueryPerformanceCounter((LARGE_INTEGER*)&count); + return count; +} + +int getPerfTimeUsec(const TimeVal duration) +{ + static __int64 freq = 0; + if (freq == 0) + QueryPerformanceFrequency((LARGE_INTEGER*)&freq); + return (int)(duration*1000000 / freq); +} + +#else + +// Linux, BSD, OSX + +#include + +TimeVal getPerfTime() +{ + timeval now; + gettimeofday(&now, 0); + return (TimeVal)now.tv_sec*1000000L + (TimeVal)now.tv_usec; +} + +int getPerfTimeUsec(const TimeVal duration) +{ + return (int)duration; +} + +#endif diff --git a/NavMeshGenerator/Framework/PerfTimer.h b/NavMeshGenerator/Framework/PerfTimer.h new file mode 100644 index 0000000..ed62c18 --- /dev/null +++ b/NavMeshGenerator/Framework/PerfTimer.h @@ -0,0 +1,32 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef PERFTIMER_H +#define PERFTIMER_H + +#ifdef __GNUC__ +#include +typedef int64_t TimeVal; +#else +typedef __int64 TimeVal; +#endif + +TimeVal getPerfTime(); +int getPerfTimeUsec(const TimeVal duration); + +#endif // PERFTIMER_H \ No newline at end of file diff --git a/NavMeshGenerator/Framework/Sample.h b/NavMeshGenerator/Framework/Sample.h new file mode 100644 index 0000000..aa46f86 --- /dev/null +++ b/NavMeshGenerator/Framework/Sample.h @@ -0,0 +1,190 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef RECASTSAMPLE_H +#define RECASTSAMPLE_H + +#include "Recast.h" +#include "SampleInterfaces.h" + + +/// Tool types. +enum SampleToolType +{ + TOOL_NONE = 0, + TOOL_TILE_EDIT, + TOOL_TILE_HIGHLIGHT, + TOOL_TEMP_OBSTACLE, + TOOL_NAVMESH_TESTER, + TOOL_NAVMESH_PRUNE, + TOOL_OFFMESH_CONNECTION, + TOOL_CONVEX_VOLUME, + TOOL_CROWD, + MAX_TOOLS +}; + +/// These are just sample areas to use consistent values across the samples. +/// The use should specify these base on his needs. +enum SamplePolyAreas +{ + SAMPLE_POLYAREA_GROUND, + SAMPLE_POLYAREA_WATER, + SAMPLE_POLYAREA_ROAD, + SAMPLE_POLYAREA_DOOR, + SAMPLE_POLYAREA_GRASS, + SAMPLE_POLYAREA_JUMP, +}; +enum SamplePolyFlags +{ + SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road) + SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water). + SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors. + SAMPLE_POLYFLAGS_JUMP = 0x08, // Ability to jump. + SAMPLE_POLYFLAGS_DISABLED = 0x10, // Disabled polygon + SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities. +}; + +class SampleDebugDraw : public DebugDrawGL +{ +public: + virtual unsigned int areaToCol(unsigned int area); +}; + +enum SamplePartitionType +{ + SAMPLE_PARTITION_WATERSHED, + SAMPLE_PARTITION_MONOTONE, + SAMPLE_PARTITION_LAYERS, +}; + +struct SampleTool +{ + virtual ~SampleTool() {} + virtual int type() = 0; + virtual void init(class Sample* sample) = 0; + virtual void reset() = 0; + virtual void handleMenu() = 0; + virtual void handleClick(const float* s, const float* p, bool shift) = 0; + virtual void handleRender() = 0; + virtual void handleRenderOverlay(double* proj, double* model, int* view) = 0; + virtual void handleToggle() = 0; + virtual void handleStep() = 0; + virtual void handleUpdate(const float dt) = 0; +}; + +struct SampleToolState { + virtual ~SampleToolState() {} + virtual void init(class Sample* sample) = 0; + virtual void reset() = 0; + virtual void handleRender() = 0; + virtual void handleRenderOverlay(double* proj, double* model, int* view) = 0; + virtual void handleUpdate(const float dt) = 0; +}; + +class Sample +{ +protected: + class InputGeom* m_geom; + class dtNavMesh* m_navMesh; + class dtNavMeshQuery* m_navQuery; + class dtCrowd* m_crowd; + + unsigned char m_navMeshDrawFlags; + + float m_cellSize; + float m_cellHeight; + float m_agentHeight; + float m_agentRadius; + float m_agentMaxClimb; + float m_agentMaxSlope; + float m_regionMinSize; + float m_regionMergeSize; + float m_edgeMaxLen; + float m_edgeMaxError; + float m_vertsPerPoly; + float m_detailSampleDist; + float m_detailSampleMaxError; + int m_partitionType; + + bool m_filterLowHangingObstacles; + bool m_filterLedgeSpans; + bool m_filterWalkableLowHeightSpans; + + SampleTool* m_tool; + SampleToolState* m_toolStates[MAX_TOOLS]; + + BuildContext* m_ctx; + + SampleDebugDraw m_dd; + + dtNavMesh* loadAll(const char* path); + void saveAll(const char* path, const dtNavMesh* mesh); + +public: + Sample(); + virtual ~Sample(); + + void setContext(BuildContext* ctx) { m_ctx = ctx; } + + void setTool(SampleTool* tool); + SampleToolState* getToolState(int type) { return m_toolStates[type]; } + void setToolState(int type, SampleToolState* s) { m_toolStates[type] = s; } + + SampleDebugDraw& getDebugDraw() { return m_dd; } + + virtual void handleSettings(); + virtual void handleTools(); + virtual void handleDebugMode(); + virtual void handleClick(const float* s, const float* p, bool shift); + virtual void handleToggle(); + virtual void handleStep(); + virtual void handleRender(); + virtual void handleRenderOverlay(double* proj, double* model, int* view); + virtual void handleMeshChanged(class InputGeom* geom); + virtual bool handleBuild(); + virtual void handleUpdate(const float dt); + virtual void collectSettings(struct BuildSettings& settings); + + virtual class InputGeom* getInputGeom() { return m_geom; } + virtual class dtNavMesh* getNavMesh() { return m_navMesh; } + virtual class dtNavMeshQuery* getNavMeshQuery() { return m_navQuery; } + virtual class dtCrowd* getCrowd() { return m_crowd; } + virtual float getAgentRadius() { return m_agentRadius; } + virtual float getAgentHeight() { return m_agentHeight; } + virtual float getAgentClimb() { return m_agentMaxClimb; } + + unsigned char getNavMeshDrawFlags() const { return m_navMeshDrawFlags; } + void setNavMeshDrawFlags(unsigned char flags) { m_navMeshDrawFlags = flags; } + + void updateToolStates(const float dt); + void initToolStates(Sample* sample); + void resetToolStates(); + void renderToolStates(); + void renderOverlayToolStates(double* proj, double* model, int* view); + + void resetCommonSettings(); + void handleCommonSettings(); + +private: + // Explicitly disabled copy constructor and copy assignment operator. + Sample(const Sample&); + Sample& operator=(const Sample&); +}; + + +#endif // RECASTSAMPLE_H diff --git a/NavMeshGenerator/Framework/SampleInterfaces.h b/NavMeshGenerator/Framework/SampleInterfaces.h new file mode 100644 index 0000000..139760f --- /dev/null +++ b/NavMeshGenerator/Framework/SampleInterfaces.h @@ -0,0 +1,99 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef SAMPLEINTERFACES_H +#define SAMPLEINTERFACES_H + +#include "DebugDraw.h" +#include "Recast.h" +#include "RecastDump.h" +#include "PerfTimer.h" + +// These are example implementations of various interfaces used in Recast and Detour. + +/// Recast build context. +class BuildContext : public rcContext +{ + TimeVal m_startTime[RC_MAX_TIMERS]; + TimeVal m_accTime[RC_MAX_TIMERS]; + + static const int MAX_MESSAGES = 1000; + const char* m_messages[MAX_MESSAGES]; + int m_messageCount; + static const int TEXT_POOL_SIZE = 8000; + char m_textPool[TEXT_POOL_SIZE]; + int m_textPoolSize; + +public: + BuildContext(); + + /// Dumps the log to stdout. + void dumpLog(const char* format, ...); + /// Returns number of log messages. + int getLogCount() const; + /// Returns log message text. + const char* getLogText(const int i) const; + +protected: + /// Virtual functions for custom implementations. + ///@{ + virtual void doResetLog(); + virtual void doLog(const rcLogCategory category, const char* msg, const int len); + virtual void doResetTimers(); + virtual void doStartTimer(const rcTimerLabel label); + virtual void doStopTimer(const rcTimerLabel label); + virtual int doGetAccumulatedTime(const rcTimerLabel label) const; + ///@} +}; + +/// OpenGL debug draw implementation. +class DebugDrawGL : public duDebugDraw +{ +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(); +}; + +/// stdio file implementation. +class FileIO : public duFileIO +{ + FILE* m_fp; + int m_mode; +public: + FileIO(); + virtual ~FileIO(); + bool openForWrite(const char* path); + bool openForRead(const char* path); + virtual bool isWriting() const; + virtual bool isReading() const; + virtual bool write(const void* ptr, const size_t size); + virtual bool read(void* ptr, const size_t size); +private: + // Explicitly disabled copy constructor and copy assignment operator. + FileIO(const FileIO&); + FileIO& operator=(const FileIO&); +}; + +#endif // SAMPLEINTERFACES_H + diff --git a/NavMeshGenerator/Main.cpp b/NavMeshGenerator/Main.cpp new file mode 100644 index 0000000..b9ecc57 --- /dev/null +++ b/NavMeshGenerator/Main.cpp @@ -0,0 +1,49 @@ +#include "Framework/InputGeom.h" + +#include "NavMeshGenerator.h" +#include "RecastContext.h" + +int main(int argc, char** argv) +{ + std::filesystem::path inputPath; + std::filesystem::path outputPath; + bool enableLogging = false; + + for (int i = 1; i < argc; ++i) + { + if (!_stricmp(argv[i], "--input")) + { + inputPath = argv[++i]; + } + else if (!_stricmp(argv[i], "--output")) + { + outputPath = argv[++i]; + } + else if (!_stricmp(argv[i], "--enableLogging")) + { + enableLogging = true; + } + } + + InputGeom* geom = new InputGeom(); + geom->load(nullptr, inputPath.string()); + + if (outputPath.empty()) + { + printf("Warning: no output path set, no file will be generated.\n"); + } + + const auto context = std::make_shared(enableLogging); + NavMeshGenerator generator(geom, context); + bool success = generator.BuildNavMesh(); + + float totalTime = context->getAccumulatedTime(RC_TIMER_TOTAL) / 1000.0f; + + printf("Success: %d\n", success); + printf("Total time in milliseconds: %.2f\n", totalTime); + + if (!outputPath.empty()) + { + generator.Serialize(outputPath); + } +} \ No newline at end of file diff --git a/NavMeshGenerator/NavMeshGenerator.cpp b/NavMeshGenerator/NavMeshGenerator.cpp new file mode 100644 index 0000000..6176f39 --- /dev/null +++ b/NavMeshGenerator/NavMeshGenerator.cpp @@ -0,0 +1,527 @@ +#include "NavMeshGenerator.h" + +#include "DetourNavMesh.h" +#include "DetourNavMeshBuilder.h" +#include "Recast.h" +#include "RecastContext.h" + +NavMeshGenerator::NavMeshGenerator(InputGeom* geom, std::shared_ptr context) + : m_geom(geom), + m_navMeshQuery(dtAllocNavMeshQuery()), + m_navMesh(dtAllocNavMesh()), + m_ctx(context) +{ +} + +NavMeshGenerator::~NavMeshGenerator() +{ + Cleanup(); +} + +void NavMeshGenerator::Cleanup() +{ + delete[] m_triareas; + m_triareas = 0; + rcFreeHeightField(m_solid); + m_solid = 0; + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; + rcFreePolyMesh(m_pmesh); + m_pmesh = 0; + rcFreePolyMeshDetail(m_dmesh); + m_dmesh = 0; +} + +bool NavMeshGenerator::BuildNavMesh() +{ + dtNavMeshParams params{}; + rcVcopy(params.orig, m_geom->getNavMeshBoundsMin()); + params.tileWidth = m_tileSize * m_cellSize; + params.tileHeight = m_tileSize * m_cellSize; + params.maxTiles = m_maxTiles; + params.maxPolys = m_maxPolysPerTile; + + dtStatus status = m_navMesh->init(¶ms); + if (dtStatusFailed(status)) + { + return false; + } + + status = m_navMeshQuery->init(m_navMesh.get(), 2048); + if (dtStatusFailed(status)) + { + //m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not init Detour navmesh query"); + return false; + } + + BuildAllTiles(); + return true; +} + +void NavMeshGenerator::BuildAllTiles() +{ + const float* bmin = m_geom->getNavMeshBoundsMin(); + const float* bmax = m_geom->getNavMeshBoundsMax(); + + int gw = 0, gh = 0; + rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh); + const int ts = (int)m_tileSize; + const int tw = (gw + ts - 1) / ts; + const int th = (gh + ts - 1) / ts; + const float tcs = m_tileSize * m_cellSize; + + m_ctx->startTimer(RC_TIMER_TEMP); + + for (int y = 0; y < th; ++y) + { + for (int x = 0; x < tw; ++x) + { + m_lastBuiltTileBmin[0] = bmin[0] + x * tcs; + m_lastBuiltTileBmin[1] = bmin[1]; + m_lastBuiltTileBmin[2] = bmin[2] + y * tcs; + + m_lastBuiltTileBmax[0] = bmin[0] + (x + 1) * tcs; + m_lastBuiltTileBmax[1] = bmax[1]; + m_lastBuiltTileBmax[2] = bmin[2] + (y + 1) * tcs; + + int dataSize = 0; + unsigned char* data = BuildTileMesh(x, y, m_lastBuiltTileBmin, m_lastBuiltTileBmax, dataSize); + if (data) + { + // Remove any previous data (navmesh owns and deletes the data). + m_navMesh->removeTile(m_navMesh->getTileRefAt(x, y, 0), 0, 0); + // Let the navmesh own the data. + dtStatus status = m_navMesh->addTile(data, dataSize, DT_TILE_FREE_DATA, 0, 0); + if (dtStatusFailed(status)) + dtFree(data); + } + } + } + + // Start the build process. + m_ctx->stopTimer(RC_TIMER_TEMP); + + //m_totalBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TEMP) / 1000.0f; +} + +unsigned char* NavMeshGenerator::BuildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize) +{ + if (!m_geom || !m_geom->getMesh() || !m_geom->getChunkyMesh()) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); + return 0; + } + + m_tileMemUsage = 0; + m_tileBuildTime = 0; + + Cleanup(); + + const float* verts = m_geom->getMesh()->getVerts(); + const int nverts = m_geom->getMesh()->getVertCount(); + const int ntris = m_geom->getMesh()->getTriCount(); + const rcChunkyTriMesh* chunkyMesh = m_geom->getChunkyMesh(); + + // Init build configuration from GUI + memset(&m_cfg, 0, sizeof(m_cfg)); + m_cfg.cs = m_cellSize; + m_cfg.ch = m_cellHeight; + m_cfg.walkableSlopeAngle = m_agentMaxSlope; + m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch); + m_cfg.walkableClimb = (int)floorf(m_agentMaxClimb / m_cfg.ch); + m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs); + m_cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); + m_cfg.maxSimplificationError = m_edgeMaxError; + m_cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size + m_cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size + m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly; + m_cfg.tileSize = (int)m_tileSize; + m_cfg.borderSize = m_cfg.walkableRadius + 3; // Reserve enough padding. + m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; + m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; + + // Expand the heighfield bounding box by border size to find the extents of geometry we need to build this tile. + // + // This is done in order to make sure that the navmesh tiles connect correctly at the borders, + // and the obstacles close to the border work correctly with the dilation process. + // No polygons (or contours) will be created on the border area. + // + // IMPORTANT! + // + // :''''''''': + // : +-----+ : + // : | | : + // : | |<--- tile to build + // : | | : + // : +-----+ :<-- geometry needed + // :.........: + // + // You should use this bounding box to query your input geometry. + // + // For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size + // you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours, + // or use the bounding box below to only pass in a sliver of each of the 8 neighbours. + rcVcopy(m_cfg.bmin, bmin); + rcVcopy(m_cfg.bmax, bmax); + m_cfg.bmin[0] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmin[2] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[0] += m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[2] += m_cfg.borderSize * m_cfg.cs; + + // Reset build times gathering. + m_ctx->resetTimers(); + + // Start the build process. + m_ctx->startTimer(RC_TIMER_TOTAL); + + m_ctx->log(RC_LOG_PROGRESS, "Building navigation:"); + m_ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height); + m_ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts / 1000.0f, ntris / 1000.0f); + + // Allocate voxel heightfield where we rasterize our input data to. + m_solid = rcAllocHeightfield(); + if (!m_solid) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); + return 0; + } + if (!rcCreateHeightfield(m_ctx.get(), *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield."); + return 0; + } + + // Allocate array that can hold triangle flags. + // If you have multiple meshes you need to process, allocate + // and array which can hold the max number of triangles you need to process. + m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; + if (!m_triareas) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); + return 0; + } + + float tbmin[2], tbmax[2]; + tbmin[0] = m_cfg.bmin[0]; + tbmin[1] = m_cfg.bmin[2]; + tbmax[0] = m_cfg.bmax[0]; + tbmax[1] = m_cfg.bmax[2]; + int cid[512];// TODO: Make grow when returning too many items. + const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); + if (!ncid) + return 0; + + m_tileTriCount = 0; + + for (int i = 0; i < ncid; ++i) + { + const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; + const int* ctris = &chunkyMesh->tris[node.i * 3]; + const int nctris = node.n; + + m_tileTriCount += nctris; + + memset(m_triareas, 0, nctris * sizeof(unsigned char)); + rcMarkWalkableTriangles(m_ctx.get(), m_cfg.walkableSlopeAngle, + verts, nverts, ctris, nctris, m_triareas); + + if (!rcRasterizeTriangles(m_ctx.get(), verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb)) + return 0; + } + + if (!m_keepInterResults) + { + delete[] m_triareas; + m_triareas = 0; + } + + // Once all geometry is rasterized, we do initial pass of filtering to + // remove unwanted overhangs caused by the conservative rasterization + // as well as filter spans where the character cannot possibly stand. + if (m_filterLowHangingObstacles) + rcFilterLowHangingWalkableObstacles(m_ctx.get(), m_cfg.walkableClimb, *m_solid); + if (m_filterLedgeSpans) + rcFilterLedgeSpans(m_ctx.get(), m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); + if (m_filterWalkableLowHeightSpans) + rcFilterWalkableLowHeightSpans(m_ctx.get(), m_cfg.walkableHeight, *m_solid); + + // Compact the heightfield so that it is faster to handle from now on. + // This will result more cache coherent data as well as the neighbours + // between walkable cells will be calculated. + m_chf = rcAllocCompactHeightfield(); + if (!m_chf) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); + return 0; + } + if (!rcBuildCompactHeightfield(m_ctx.get(), m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); + return 0; + } + + if (!m_keepInterResults) + { + rcFreeHeightField(m_solid); + m_solid = 0; + } + + // Erode the walkable area by agent radius. + if (!rcErodeWalkableArea(m_ctx.get(), m_cfg.walkableRadius, *m_chf)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); + return 0; + } + + // (Optional) Mark areas. + const ConvexVolume* vols = m_geom->getConvexVolumes(); + for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) + rcMarkConvexPolyArea(m_ctx.get(), vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); + + + // Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas. + // There are 3 martitioning methods, each with some pros and cons: + // 1) Watershed partitioning + // - the classic Recast partitioning + // - creates the nicest tessellation + // - usually slowest + // - partitions the heightfield into nice regions without holes or overlaps + // - the are some corner cases where this method creates produces holes and overlaps + // - holes may appear when a small obstacles is close to large open area (triangulation can handle this) + // - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail + // * generally the best choice if you precompute the nacmesh, use this if you have large open areas + // 2) Monotone partioning + // - fastest + // - partitions the heightfield into regions without holes and overlaps (guaranteed) + // - creates long thin polygons, which sometimes causes paths with detours + // * use this if you want fast navmesh generation + // 3) Layer partitoining + // - quite fast + // - partitions the heighfield into non-overlapping regions + // - relies on the triangulation code to cope with holes (thus slower than monotone partitioning) + // - produces better triangles than monotone partitioning + // - does not have the corner cases of watershed partitioning + // - can be slow and create a bit ugly tessellation (still better than monotone) + // if you have large open areas with small obstacles (not a problem if you use tiles) + // * good choice to use for tiled navmesh with medium and small sized tiles + + if (m_partitionType == SAMPLE_PARTITION_WATERSHED) + { + // Prepare for region partitioning, by calculating distance field along the walkable surface. + if (!rcBuildDistanceField(m_ctx.get(), *m_chf)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); + return 0; + } + + // Partition the walkable surface into simple regions without holes. + if (!rcBuildRegions(m_ctx.get(), *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions."); + return 0; + } + } + else if (m_partitionType == SAMPLE_PARTITION_MONOTONE) + { + // Partition the walkable surface into simple regions without holes. + // Monotone partitioning does not need distancefield. + if (!rcBuildRegionsMonotone(m_ctx.get(), *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions."); + return 0; + } + } + else // SAMPLE_PARTITION_LAYERS + { + // Partition the walkable surface into simple regions without holes. + if (!rcBuildLayerRegions(m_ctx.get(), *m_chf, m_cfg.borderSize, m_cfg.minRegionArea)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions."); + return 0; + } + } + + // Create contours. + m_cset = rcAllocContourSet(); + if (!m_cset) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); + return 0; + } + if (!rcBuildContours(m_ctx.get(), *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); + return 0; + } + + if (m_cset->nconts == 0) + { + return 0; + } + + // Build polygon navmesh from the contours. + m_pmesh = rcAllocPolyMesh(); + if (!m_pmesh) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); + return 0; + } + if (!rcBuildPolyMesh(m_ctx.get(), *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); + return 0; + } + + // Build detail mesh. + m_dmesh = rcAllocPolyMeshDetail(); + if (!m_dmesh) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); + return 0; + } + + if (!rcBuildPolyMeshDetail(m_ctx.get(), *m_pmesh, *m_chf, + m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, + *m_dmesh)) + { + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); + return 0; + } + + if (!m_keepInterResults) + { + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; + } + + unsigned char* navData = 0; + int navDataSize = 0; + if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) + { + if (m_pmesh->nverts >= 0xffff) + { + // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. + m_ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); + return 0; + } + + // Update poly flags from areas. + for (int i = 0; i < m_pmesh->npolys; ++i) + { + if (m_pmesh->areas[i] == RC_WALKABLE_AREA) + m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND; + + if (m_pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || + m_pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || + m_pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) + { + m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; + } + else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_WATER) + { + m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; + } + else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) + { + m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; + } + } + + dtNavMeshCreateParams params{}; + params.verts = m_pmesh->verts; + params.vertCount = m_pmesh->nverts; + params.polys = m_pmesh->polys; + params.polyAreas = m_pmesh->areas; + params.polyFlags = m_pmesh->flags; + params.polyCount = m_pmesh->npolys; + params.nvp = m_pmesh->nvp; + params.detailMeshes = m_dmesh->meshes; + params.detailVerts = m_dmesh->verts; + params.detailVertsCount = m_dmesh->nverts; + params.detailTris = m_dmesh->tris; + params.detailTriCount = m_dmesh->ntris; + 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(); + params.walkableHeight = m_agentHeight; + params.walkableRadius = m_agentRadius; + params.walkableClimb = m_agentMaxClimb; + params.tileX = tx; + params.tileY = ty; + params.tileLayer = 0; + rcVcopy(params.bmin, m_pmesh->bmin); + rcVcopy(params.bmax, m_pmesh->bmax); + params.cs = m_cfg.cs; + params.ch = m_cfg.ch; + params.buildBvTree = true; + + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); + return 0; + } + } + m_tileMemUsage = navDataSize / 1024.0f; + + m_ctx->stopTimer(RC_TIMER_TOTAL); + + // Show performance stats. + duLogBuildTimes(*m_ctx, m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)); + m_ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys); + + m_tileBuildTime = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL) / 1000.0f; + + dataSize = navDataSize; + return navData; +} + +void NavMeshGenerator::Serialize(const std::filesystem::path& path) const +{ + if (!m_navMesh) return; + + FILE* fp = fopen(path.string().c_str(), "wb"); + if (!fp) + return; + + // Store header. + NavMeshSetHeader header; + header.magic = NAVMESHSET_MAGIC; + header.version = NAVMESHSET_VERSION; + header.numTiles = 0; + for (int i = 0; i < m_navMesh->getMaxTiles(); ++i) + { + const dtNavMesh* navMesh = m_navMesh.get(); + const dtMeshTile* tile = navMesh->getTile(i); + if (!tile || !tile->header || !tile->dataSize) continue; + header.numTiles++; + } + memcpy(&header.params, m_navMesh->getParams(), sizeof(dtNavMeshParams)); + fwrite(&header, sizeof(NavMeshSetHeader), 1, fp); + + // Store tiles. + for (int i = 0; i < m_navMesh->getMaxTiles(); ++i) + { + const dtNavMesh* navMesh = m_navMesh.get(); + const dtMeshTile* tile = navMesh->getTile(i); + if (!tile || !tile->header || !tile->dataSize) continue; + + NavMeshTileHeader tileHeader; + tileHeader.tileRef = m_navMesh->getTileRef(tile); + tileHeader.dataSize = tile->dataSize; + fwrite(&tileHeader, sizeof(tileHeader), 1, fp); + + fwrite(tile->data, tile->dataSize, 1, fp); + } + + fclose(fp); +} \ No newline at end of file diff --git a/NavMeshGenerator/NavMeshGenerator.h b/NavMeshGenerator/NavMeshGenerator.h new file mode 100644 index 0000000..ead8eb5 --- /dev/null +++ b/NavMeshGenerator/NavMeshGenerator.h @@ -0,0 +1,77 @@ +#pragma once + +// Recast +#include "Recast.h" +#include "DetourNavMesh.h" +#include "DetourNavMeshQuery.h" + +// Framework +#include "Framework/InputGeom.h" +#include "Framework/Sample.h" + +#include "NavMeshUtil.h" +#include "RecastContext.h" + +class NavMeshGenerator +{ +public: + NavMeshGenerator(InputGeom* geom, std::shared_ptr context); + virtual ~NavMeshGenerator(); + + bool BuildNavMesh(); + void Serialize(const std::filesystem::path& path) const; +private: + void BuildAllTiles(); + void Cleanup(); + + unsigned char* BuildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize); + + std::unique_ptr m_geom; + std::unique_ptr m_navMesh; + std::unique_ptr m_navMeshQuery; + std::shared_ptr m_ctx; + + + unsigned char* m_triareas{}; + rcHeightfield* m_solid{}; + rcCompactHeightfield* m_chf{}; + rcContourSet* m_cset{}; + rcPolyMesh* m_pmesh{}; + rcPolyMeshDetail* m_dmesh{}; + rcConfig m_cfg{}; + + // Core configuration + float m_cellSize = 0.3f; + float m_cellHeight = 0.2f; + float m_agentHeight = 2.0f; + float m_agentRadius = 0.6f; + float m_agentMaxClimb = 0.9f; + float m_agentMaxSlope = 50.0f; //45.0f; + float m_regionMinSize = 8; + float m_regionMergeSize = 20; + float m_edgeMaxLen = 12.0f; + float m_edgeMaxError = 1.3f; + float m_vertsPerPoly = 6.0f; + float m_detailSampleDist = 6.0f; + float m_detailSampleMaxError = 1.0f; + int m_partitionType = SAMPLE_PARTITION_WATERSHED; + bool m_keepInterResults = false; + + // Core filtering configuration + bool m_filterLowHangingObstacles = true; + bool m_filterLedgeSpans = true; + bool m_filterWalkableLowHeightSpans = true; + + // Tile configuration + int m_maxTiles = 0; + int m_maxPolysPerTile = 0; + float m_tileSize = 32; + + unsigned int m_tileCol{}; + float m_lastBuiltTileBmin[3]{}; + float m_lastBuiltTileBmax[3]{}; + float m_tileMemUsage{}; + float m_tileBuildTime{}; + int m_tileTriCount{}; + +}; \ No newline at end of file diff --git a/NavMeshGenerator/NavMeshGenerator.sln b/NavMeshGenerator/NavMeshGenerator.sln new file mode 100644 index 0000000..e2d6632 --- /dev/null +++ b/NavMeshGenerator/NavMeshGenerator.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NavMeshGenerator", "NavMeshGenerator.vcxproj", "{CA9C0938-3ADA-4C73-A89A-E9447ABCE101}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.ActiveCfg = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x64.Build.0 = Debug|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.ActiveCfg = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Debug|x86.Build.0 = Debug|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.ActiveCfg = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x64.Build.0 = Release|x64 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.ActiveCfg = Release|Win32 + {CA9C0938-3ADA-4C73-A89A-E9447ABCE101}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4274A358-82D6-4297-99D3-9647B7FC1368} + EndGlobalSection +EndGlobal diff --git a/NavMeshGenerator/NavMeshGenerator.vcxproj b/NavMeshGenerator/NavMeshGenerator.vcxproj new file mode 100644 index 0000000..2f91ec7 --- /dev/null +++ b/NavMeshGenerator/NavMeshGenerator.vcxproj @@ -0,0 +1,221 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {ca9c0938-3ada-4c73-a89a-e9447abce101} + NavMeshGenerator + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\External\recastnavigation\DebugUtils\Include;..\External\recastnavigation\Detour\Include;..\External\recastnavigation\DetourTileCache\Include;;..\External\recastnavigation\Recast\Include + stdcpplatest + pch.h + Use + pch.h + + + Console + true + + + + + Level3 + true + true + true + WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\External\recastnavigation\DebugUtils\Include;..\External\recastnavigation\Detour\Include;..\External\recastnavigation\DetourTileCache\Include;;..\External\recastnavigation\Recast\Include + stdcpplatest + pch.h + Use + pch.h + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NavMeshGenerator/NavMeshGenerator.vcxproj.filters b/NavMeshGenerator/NavMeshGenerator.vcxproj.filters new file mode 100644 index 0000000..e601769 --- /dev/null +++ b/NavMeshGenerator/NavMeshGenerator.vcxproj.filters @@ -0,0 +1,243 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {82f0fd75-bb9d-4e6c-849a-71f66d5a8f0a} + + + {31af1122-2287-4254-860d-7d9819551310} + + + {7589cc4f-b87f-4191-81fc-17622331c7ca} + + + {203ec006-7bdb-47fa-858a-6f12327c5d44} + + + {b05a0256-22d5-478e-85d7-c56cef704e1e} + + + {d30da57a-2663-4461-a272-be169bd1a3a5} + + + {1cb89f5c-da8d-49cf-9bfa-0cb8b38fabac} + + + {b4850d74-861d-4dc8-aa4e-59b6f18a7754} + + + {f0447598-7859-485b-97e8-61035bd87634} + + + {1bc45b59-e845-4a30-b042-2c6ff3fdc3fe} + + + {b17dc902-8d50-4da4-9013-e1bd39206ca5} + + + {091ace17-5d99-46d0-9d81-57fd2b4eb225} + + + {54fffb89-f106-45f9-9a78-119f59e17fed} + + + {c5ca75e6-1ec9-4c06-a733-4323f130f5c4} + + + + + Recast\DebugUtils\Source + + + Recast\DebugUtils\Source + + + Recast\DebugUtils\Source + + + Recast\DebugUtils\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\Detour\Source + + + Recast\DetourTileCache\Source + + + Recast\DetourTileCache\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Recast\Recast\Source + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + + + Recast\DebugUtils\Include + + + Recast\DebugUtils\Include + + + Recast\DebugUtils\Include + + + Recast\DebugUtils\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\Detour\Include + + + Recast\DetourTileCache\Include + + + Recast\DetourTileCache\Include + + + Recast\Recast\Include + + + Recast\Recast\Include + + + Recast\Recast\Include + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + Source Files\Framework + + + \ No newline at end of file diff --git a/NavMeshGenerator/NavMeshUtil.h b/NavMeshGenerator/NavMeshUtil.h new file mode 100644 index 0000000..52ee28c --- /dev/null +++ b/NavMeshGenerator/NavMeshUtil.h @@ -0,0 +1,41 @@ +#pragma once + +////////////////////////////////////////////////////////// +// Smart pointer deleters + +struct NavMeshDeleter +{ + void operator()(dtNavMesh* navMesh) + { + if (navMesh) + dtFreeNavMesh(navMesh); + } +}; + +struct NavMeshQueryDeleter +{ + void operator()(dtNavMeshQuery* navMeshQuery) + { + if (navMeshQuery) + dtFreeNavMeshQuery(navMeshQuery); + } +}; + +////////////////////////////////////////////////////////// +// Serialization logic +struct NavMeshSetHeader +{ + int magic; + int version; + int numTiles; + dtNavMeshParams params; +}; + +struct NavMeshTileHeader +{ + dtTileRef tileRef; + int dataSize; +}; + +static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; +static const int NAVMESHSET_VERSION = 1; diff --git a/NavMeshGenerator/RecastContext.cpp b/NavMeshGenerator/RecastContext.cpp new file mode 100644 index 0000000..783b7a5 --- /dev/null +++ b/NavMeshGenerator/RecastContext.cpp @@ -0,0 +1,44 @@ +#include "RecastContext.h" + +#include + +RecastContext::RecastContext(bool enableLogging) + : m_enableLogging(enableLogging) +{ +} + +void RecastContext::doResetLog() +{ +} + +void RecastContext::doLog(const rcLogCategory category, const char* msg, const int len) +{ + if (m_enableLogging) + printf("RECAST: %s\n", msg); +} + +void RecastContext::doResetTimers() +{ + for (int i = 0; i < RC_MAX_TIMERS; ++i) + m_accTime[i] = -1; +} + +void RecastContext::doStartTimer(const rcTimerLabel label) +{ + m_startTime[label] = getPerfTime(); +} + +void RecastContext::doStopTimer(const rcTimerLabel label) +{ + const TimeVal endTime = getPerfTime(); + const TimeVal deltaTime = endTime - m_startTime[label]; + if (m_accTime[label] == -1) + m_accTime[label] = deltaTime; + else + m_accTime[label] += deltaTime; +} + +int RecastContext::doGetAccumulatedTime(const rcTimerLabel label) const +{ + return getPerfTimeUsec(m_accTime[label]); +} diff --git a/NavMeshGenerator/RecastContext.h b/NavMeshGenerator/RecastContext.h new file mode 100644 index 0000000..85ce524 --- /dev/null +++ b/NavMeshGenerator/RecastContext.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Recast.h" +#include "Framework/PerfTimer.h" + +class RecastContext : public rcContext +{ +public: + RecastContext(bool enableLogging); + +protected: + virtual void doResetLog(); + virtual void doLog(const rcLogCategory category, const char* msg, const int len); + virtual void doResetTimers(); + virtual void doStartTimer(const rcTimerLabel label); + virtual void doStopTimer(const rcTimerLabel label); + virtual int doGetAccumulatedTime(const rcTimerLabel label) const; + +private: + bool m_enableLogging{}; + TimeVal m_startTime[RC_MAX_TIMERS]{}; + TimeVal m_accTime[RC_MAX_TIMERS]{}; +}; \ No newline at end of file diff --git a/NavMeshGenerator/pch.cpp b/NavMeshGenerator/pch.cpp new file mode 100644 index 0000000..e69de29 diff --git a/NavMeshGenerator/pch.h b/NavMeshGenerator/pch.h new file mode 100644 index 0000000..fa2a521 --- /dev/null +++ b/NavMeshGenerator/pch.h @@ -0,0 +1,3 @@ +#pragma once + +#include \ No newline at end of file diff --git a/Plugins/imp_gbs/imp_gbs.vcxproj b/Plugins/imp_gbs/imp_gbs.vcxproj index 5f694bd..371fef5 100644 --- a/Plugins/imp_gbs/imp_gbs.vcxproj +++ b/Plugins/imp_gbs/imp_gbs.vcxproj @@ -19,6 +19,7 @@ GiantsExp Win32Proj imp_gbs + 10.0 @@ -74,7 +75,7 @@ Level3 ProgramDatabase Default - stdcpplatest + stdcpp17 stdafx.h @@ -110,7 +111,7 @@ Level3 ProgramDatabase Default - stdcpplatest + stdcpp17 stdafx.h @@ -142,7 +143,7 @@ Use Level3 ProgramDatabase - stdcpplatest + stdcpp17 stdafx.h