From 30aed0538e99a4d097f69968b1ad0557670aec2a Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Sun, 21 Oct 2012 15:44:57 +0000 Subject: [PATCH] Added small area selection to prune tool. --- RecastDemo/Include/NavmeshPruneTool.h | 51 +++ RecastDemo/Source/NavmeshPruneTool.cpp | 466 +++++++++++++++++++++++++ 2 files changed, 517 insertions(+) create mode 100644 RecastDemo/Include/NavmeshPruneTool.h create mode 100644 RecastDemo/Source/NavmeshPruneTool.cpp diff --git a/RecastDemo/Include/NavmeshPruneTool.h b/RecastDemo/Include/NavmeshPruneTool.h new file mode 100644 index 0000000..7d24620 --- /dev/null +++ b/RecastDemo/Include/NavmeshPruneTool.h @@ -0,0 +1,51 @@ +// +// 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 NAVMESHPRUNETOOL_H +#define NAVMESHPRUNETOOL_H + +#include "Sample.h" + +// Prune navmesh to accessible locations from a point. + +class NavMeshPruneTool : public SampleTool +{ + Sample* m_sample; + + class NavmeshFlags* m_flags; + + float m_hitPos[3]; + bool m_hitPosSet; + +public: + NavMeshPruneTool(); + ~NavMeshPruneTool(); + + virtual int type() { return TOOL_NAVMESH_PRUNE; } + virtual void init(Sample* sample); + virtual void reset(); + virtual void handleMenu(); + virtual void handleClick(const float* s, const float* p, bool shift); + virtual void handleToggle(); + virtual void handleStep(); + virtual void handleUpdate(const float dt); + virtual void handleRender(); + virtual void handleRenderOverlay(double* proj, double* model, int* view); +}; + +#endif // NAVMESHPRUNETOOL_H diff --git a/RecastDemo/Source/NavmeshPruneTool.cpp b/RecastDemo/Source/NavmeshPruneTool.cpp new file mode 100644 index 0000000..47d6616 --- /dev/null +++ b/RecastDemo/Source/NavmeshPruneTool.cpp @@ -0,0 +1,466 @@ +// +// 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 "SDL.h" +#include "SDL_opengl.h" +#include "imgui.h" +#include "NavMeshPruneTool.h" +#include "InputGeom.h" +#include "Sample.h" +#include "DetourNavMesh.h" +#include "DetourCommon.h" +#include "DetourAssert.h" +#include "DetourDebugDraw.h" + +#ifdef WIN32 +# define snprintf _snprintf +#endif + + + +// Copy/paste from Recast int array +class PolyRefArray +{ + dtPolyRef* m_data; + int m_size, m_cap; + inline PolyRefArray(const PolyRefArray&); + inline PolyRefArray& operator=(const PolyRefArray&); +public: + + inline PolyRefArray() : m_data(0), m_size(0), m_cap(0) {} + inline PolyRefArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } + inline ~PolyRefArray() { dtFree(m_data); } + void resize(int n) + { + if (n > m_cap) + { + if (!m_cap) m_cap = n; + while (m_cap < n) m_cap *= 2; + dtPolyRef* newData = (dtPolyRef*)dtAlloc(m_cap*sizeof(dtPolyRef), DT_ALLOC_TEMP); + if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(dtPolyRef)); + dtFree(m_data); + m_data = newData; + } + m_size = n; + } + inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } + inline dtPolyRef pop() { if (m_size > 0) m_size--; return m_data[m_size]; } + inline const dtPolyRef& operator[](int i) const { return m_data[i]; } + inline dtPolyRef& operator[](int i) { return m_data[i]; } + inline int size() const { return m_size; } +}; + + + + +class NavmeshFlags +{ + struct TileFlags + { + inline void purge() { dtFree(flags); } + unsigned char* flags; + int nflags; + dtPolyRef base; + }; + + const dtNavMesh* m_nav; + TileFlags* m_tiles; + int m_ntiles; + +public: + NavmeshFlags() : + m_nav(0), m_tiles(0), m_ntiles(0) + { + } + + ~NavmeshFlags() + { + for (int i = 0; i < m_ntiles; ++i) + m_tiles[i].purge(); + dtFree(m_tiles); + } + + bool init(const dtNavMesh* nav) + { + m_ntiles = nav->getMaxTiles(); + if (!m_ntiles) + return true; + m_tiles = (TileFlags*)dtAlloc(sizeof(TileFlags)*m_ntiles, DT_ALLOC_TEMP); + if (!m_tiles) + { + return false; + } + memset(m_tiles, 0, sizeof(TileFlags)*m_ntiles); + + // Alloc flags for each tile. + for (int i = 0; i < nav->getMaxTiles(); ++i) + { + const dtMeshTile* tile = nav->getTile(i); + if (!tile->header) continue; + TileFlags* tf = &m_tiles[i]; + tf->nflags = tile->header->polyCount; + tf->base = nav->getPolyRefBase(tile); + if (tf->nflags) + { + tf->flags = (unsigned char*)dtAlloc(tf->nflags, DT_ALLOC_TEMP); + if (!tf->flags) + return false; + memset(tf->flags, 0, tf->nflags); + } + } + + m_nav = nav; + + return true; + } + + inline void clearAllFlags() + { + for (int i = 0; i < m_ntiles; ++i) + { + TileFlags* tf = &m_tiles[i]; + if (tf->nflags) + memset(tf->flags, 0, tf->nflags); + } + } + + inline unsigned char getFlags(dtPolyRef ref) + { + dtAssert(m_nav); + dtAssert(m_ntiles); + // Assume the ref is valid, no bounds checks. + unsigned int salt, it, ip; + m_nav->decodePolyId(ref, salt, it, ip); + return m_tiles[it].flags[ip]; + } + + inline void setFlags(dtPolyRef ref, unsigned char flags) + { + dtAssert(m_nav); + dtAssert(m_ntiles); + // Assume the ref is valid, no bounds checks. + unsigned int salt, it, ip; + m_nav->decodePolyId(ref, salt, it, ip); + m_tiles[it].flags[ip] = flags; + } + +}; + +static void floodNavmesh(const dtNavMesh* nav, NavmeshFlags* flags, dtPolyRef start, unsigned char flag, PolyRefArray* visited) +{ + // If already visited, skip. + if (flags->getFlags(start)) + return; + + PolyRefArray openList; + openList.push(start); + + while (openList.size()) + { + const dtPolyRef ref = openList.pop(); + if (visited) + visited->push(ref); + // Get current poly and tile. + // The API input has been cheked already, skip checking internal data. + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + nav->getTileAndPolyByRefUnsafe(ref, &tile, &poly); + + // Visit linked polygons. + for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next) + { + const dtPolyRef neiRef = tile->links[i].ref; + // Skip invalid and already visited. + if (!neiRef || flags->getFlags(neiRef)) + continue; + // Mark as visited + flags->setFlags(neiRef, flag); + // Visit neighbours + openList.push(neiRef); + } + } +} + +static int getPolyVerts(const dtNavMesh* nav, dtPolyRef ref, float* verts) +{ + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + dtStatus status = nav->getTileAndPolyByRef(ref, &tile, &poly); + if (dtStatusFailed(status)) + return 0; + for (int i = 0; i < (int)poly->vertCount; ++i) + dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); + return (int)poly->vertCount; +} + +static void calcBounds(const dtNavMesh* nav, const PolyRefArray& polys, float* bounds) +{ + dtVset(&bounds[0], FLT_MAX,FLT_MAX,FLT_MAX); + dtVset(&bounds[3], -FLT_MAX,-FLT_MAX,-FLT_MAX); + float verts[DT_VERTS_PER_POLYGON*3]; + for (int i = 0; i < polys.size(); i++) + { + const dtPolyRef ref = polys[i]; + int nv = getPolyVerts(nav, ref, verts); + for (int j = 0; j < nv; j++) + { + dtVmin(&bounds[0], &verts[j*3]); + dtVmax(&bounds[3], &verts[j*3]); + } + } +} + +static bool selectSmallAreas(const dtNavMesh* nav, NavmeshFlags* flags, + const float minSize, unsigned char flag) +{ + PolyRefArray visited; + NavmeshFlags oflags; + if (!oflags.init(nav)) + { + return false; + } + + for (int i = 0; i < nav->getMaxTiles(); ++i) + { + const dtMeshTile* tile = nav->getTile(i); + if (!tile->header) continue; + dtPolyRef base = nav->getPolyRefBase(tile); + for (int j = 0; j < tile->header->polyCount; j++) + { + dtPolyRef start = base | (dtPolyRef)j; + if (oflags.getFlags(start)) + continue; + + // Flood connected polygons. + visited.resize(0); + floodNavmesh(nav, &oflags, start, 1, &visited); + if (visited.size() > 0) + { + // Calculate the bbox of the visited polygons + float bounds[6]; + calcBounds(nav, visited, bounds); + const float w = bounds[3]-bounds[0]; + const float h = bounds[5]-bounds[2]; + // The visited area is too small, mark it. + if (w < minSize && h < minSize) + { + for (int k = 0; k < visited.size(); k++) + flags->setFlags(visited[k], flag); + } + } + } + + } + + return true; +} + + +static void disableSelectedPolys(dtNavMesh* nav, NavmeshFlags* flags) +{ + for (int i = 0; i < nav->getMaxTiles(); ++i) + { + const dtMeshTile* tile = ((const dtNavMesh*)nav)->getTile(i); + if (!tile->header) continue; + const dtPolyRef base = nav->getPolyRefBase(tile); + for (int j = 0; j < tile->header->polyCount; ++j) + { + const dtPolyRef ref = base | (unsigned int)j; + if (flags->getFlags(ref)) + { + unsigned short f = 0; + nav->getPolyFlags(ref, &f); + nav->setPolyFlags(ref, f | SAMPLE_POLYFLAGS_DISABLED); + } + } + } +} + +static void disableUnselectedPolys(dtNavMesh* nav, NavmeshFlags* flags) +{ + for (int i = 0; i < nav->getMaxTiles(); ++i) + { + const dtMeshTile* tile = ((const dtNavMesh*)nav)->getTile(i); + if (!tile->header) continue; + const dtPolyRef base = nav->getPolyRefBase(tile); + for (int j = 0; j < tile->header->polyCount; ++j) + { + const dtPolyRef ref = base | (unsigned int)j; + if (!flags->getFlags(ref)) + { + unsigned short f = 0; + nav->getPolyFlags(ref, &f); + nav->setPolyFlags(ref, f | SAMPLE_POLYFLAGS_DISABLED); + } + } + } +} + +NavMeshPruneTool::NavMeshPruneTool() : + m_flags(0), + m_hitPosSet(false) +{ +} + +NavMeshPruneTool::~NavMeshPruneTool() +{ + delete m_flags; +} + +void NavMeshPruneTool::init(Sample* sample) +{ + m_sample = sample; +} + +void NavMeshPruneTool::reset() +{ + m_hitPosSet = false; + delete m_flags; + m_flags = 0; +} + +void NavMeshPruneTool::handleMenu() +{ + dtNavMesh* nav = m_sample->getNavMesh(); + if (!nav) return; + + if (imguiButton("Select Small Areas")) + { + if (!m_flags) + { + m_flags = new NavmeshFlags; + m_flags->init(nav); + } + const float minSize = m_sample->getAgentRadius()*2 * 5.0f; + selectSmallAreas(nav, m_flags, minSize, 1); + } + + if (!m_flags) return; + + if (imguiButton("Clear Selection")) + { + m_flags->clearAllFlags(); + } + + if (imguiButton("Prune Selected")) + { + disableSelectedPolys(nav, m_flags); + delete m_flags; + m_flags = 0; + } + + if (imguiButton("Prune Unselected")) + { + disableUnselectedPolys(nav, m_flags); + delete m_flags; + m_flags = 0; + } + +} + +void NavMeshPruneTool::handleClick(const float* /*s*/, const float* p, bool shift) +{ + if (!m_sample) return; + InputGeom* geom = m_sample->getInputGeom(); + if (!geom) return; + dtNavMesh* nav = m_sample->getNavMesh(); + if (!nav) return; + dtNavMeshQuery* query = m_sample->getNavMeshQuery(); + if (!query) return; + + dtVcopy(m_hitPos, p); + m_hitPosSet = true; + + if (!m_flags) + { + m_flags = new NavmeshFlags; + m_flags->init(nav); + } + + const float ext[3] = {2,4,2}; + dtQueryFilter filter; + dtPolyRef ref = 0; + query->findNearestPoly(p, ext, &filter, &ref, 0); + + floodNavmesh(nav, m_flags, ref, 1, 0); +} + +void NavMeshPruneTool::handleToggle() +{ +} + +void NavMeshPruneTool::handleStep() +{ +} + +void NavMeshPruneTool::handleUpdate(const float /*dt*/) +{ +} + +void NavMeshPruneTool::handleRender() +{ + DebugDrawGL dd; + + if (m_hitPosSet) + { + const float s = m_sample->getAgentRadius(); + const unsigned int col = duRGBA(255,255,255,255); + dd.begin(DU_DRAW_LINES); + dd.vertex(m_hitPos[0]-s,m_hitPos[1],m_hitPos[2], col); + dd.vertex(m_hitPos[0]+s,m_hitPos[1],m_hitPos[2], col); + dd.vertex(m_hitPos[0],m_hitPos[1]-s,m_hitPos[2], col); + dd.vertex(m_hitPos[0],m_hitPos[1]+s,m_hitPos[2], col); + dd.vertex(m_hitPos[0],m_hitPos[1],m_hitPos[2]-s, col); + dd.vertex(m_hitPos[0],m_hitPos[1],m_hitPos[2]+s, col); + dd.end(); + } + + const dtNavMesh* nav = m_sample->getNavMesh(); + if (m_flags && nav) + { + for (int i = 0; i < nav->getMaxTiles(); ++i) + { + const dtMeshTile* tile = nav->getTile(i); + if (!tile->header) continue; + const dtPolyRef base = nav->getPolyRefBase(tile); + for (int j = 0; j < tile->header->polyCount; ++j) + { + const dtPolyRef ref = base | (unsigned int)j; + if (m_flags->getFlags(ref)) + { + duDebugDrawNavMeshPoly(&dd, *nav, ref, duRGBA(255,255,255,128)); + } + } + } + } + +} + +void NavMeshPruneTool::handleRenderOverlay(double* proj, double* model, int* view) +{ + // Tool help + const int h = view[3]; + + imguiDrawText(280, h-40, IMGUI_ALIGN_LEFT, "LMB: Click fill area.", imguiRGBA(255,255,255,192)); + +}