r5sdk/src/naveditor/Editor_Common.cpp
Amos 9dcb1dd86c Recast: move navmesh loading logic into single function
Make sure tools are always initialized properly whenever a navmesh is loaded.
2024-07-26 01:22:56 +02:00

595 lines
18 KiB
C++

//
// 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 "Shared/Include/SharedAssert.h"
#include "Include/Editor.h"
#include "Include/Editor_Common.h"
#include "DebugUtils/Include/RecastDebugDraw.h"
#include "DebugUtils/Include/DetourDebugDraw.h"
#include "Include/InputGeom.h"
#include <DetourTileCache/Include/DetourTileCache.h>
static void EditorCommon_DrawInputGeometry(duDebugDraw* const dd, const InputGeom* const geom,
const float maxSlope, const float textureScale)
{
duDebugDrawTriMeshSlope(dd, geom->getMesh()->getVerts(), geom->getMesh()->getVertCount(),
geom->getMesh()->getTris(), geom->getMesh()->getNormals(), geom->getMesh()->getTriCount(),
maxSlope, textureScale, nullptr);
}
static void EditorCommon_DrawBoundingBox(duDebugDraw* const dd, const InputGeom* const geom)
{
const float* const origBmin = geom->getMeshBoundsMin();
const float* const origBmax = geom->getMeshBoundsMax();
// Draw Mesh bounds
duDebugDrawBoxWire(dd, origBmin[0], origBmin[1], origBmin[2], origBmax[0], origBmax[1], origBmax[2], duRGBA(255, 255, 255, 170), 1.0f, nullptr);
const float* const origNavBmin = geom->getOriginalNavMeshBoundsMin();
const float* const origNavBmax = geom->getOriginalNavMeshBoundsMax();
const float* const navBmin = geom->getNavMeshBoundsMin();
const float* const navBmax = geom->getNavMeshBoundsMax();
// Draw Original NavMesh bounds (e.g. when loading from a .gset with a predetermined NavMesh bound that differs from the mesh bound)
if ((!rdVequal(origBmin, origNavBmin) || !rdVequal(origBmax, origNavBmax)) && (!rdVequal(navBmin, origNavBmin) || !rdVequal(navBmax, origNavBmax)))
duDebugDrawBoxWire(dd, origNavBmin[0], origNavBmin[1], origNavBmin[2], origNavBmax[0], origNavBmax[1], origNavBmax[2], duRGBA(0, 80, 255, 215), 1.0f, nullptr);
// Draw NavMesh bounds
if (!rdVequal(origBmin, navBmin) || !rdVequal(origBmax, navBmax))
duDebugDrawBoxWire(dd, navBmin[0], navBmin[1], navBmin[2], navBmax[0], navBmax[1], navBmax[2], duRGBA(0, 255, 0, 215), 1.0f, nullptr);
}
static void EditorCommon_DrawTilingGrid(duDebugDraw* const dd, const InputGeom* const geom, const int tileSize, const float cellSize)
{
const float* const bmin = geom->getNavMeshBoundsMin();
const float* const bmax = geom->getNavMeshBoundsMax();
int gw = 0, gh = 0;
rcCalcGridSize(bmin, bmax, cellSize, &gw, &gh);
const int tw = (gw + tileSize - 1) / tileSize;
const int th = (gh + tileSize - 1) / tileSize;
const float s = tileSize * cellSize;
duDebugDrawGridXY(dd, bmax[0], bmin[1], bmin[2], tw, th, s, duRGBA(0, 0, 0, 64), 1.0f, nullptr);
}
int EditorCommon_SetAndRenderTileProperties(const InputGeom* const geom, const int tileSize,
const float cellSize, int& maxTiles, int& maxPolysPerTile)
{
int gridSize = 1;
if (geom)
{
int gw = 0, gh = 0;
const float* bmin = geom->getNavMeshBoundsMin();
const float* bmax = geom->getNavMeshBoundsMax();
rcCalcGridSize(bmin, bmax, cellSize, &gw, &gh);
const int ts = tileSize;
const int tw = (gw + ts-1) / ts;
const int th = (gh + ts-1) / ts;
ImGui::Text("Tiles: %d x %d", tw, th);
ImGui::Text("Tile Sizes: %g x %g (%g)", tw* cellSize, th*cellSize, tileSize*cellSize);
// Max tiles and max polys affect how the tile IDs are calculated.
// There are 28 bits available for identifying a tile and a polygon.
int tileBits = rdMin((int)rdIlog2(rdNextPow2(tw*th)), 16);
int polyBits = 28 - tileBits;
maxTiles = 1 << tileBits;
maxPolysPerTile = 1 << polyBits;
gridSize = tw*th;
ImGui::Text("Max Tiles: %d", maxTiles);
ImGui::Text("Max Polys: %d", maxPolysPerTile);
}
else
{
maxTiles = 0;
maxPolysPerTile = 0;
gridSize = 1;
}
return gridSize;
}
Editor_StaticTileMeshCommon::Editor_StaticTileMeshCommon()
: m_triareas(nullptr)
, m_solid(nullptr)
, m_chf(nullptr)
, m_cset(nullptr)
, m_pmesh(nullptr)
, m_dmesh(nullptr)
, m_tileMeshDrawFlags(TM_DRAWFLAGS_INPUT_MESH|TM_DRAWFLAGS_NAVMESH)
, m_tileCol(duRGBA(0, 0, 0, 64))
, m_totalBuildTimeMs(0.0f)
, m_drawActiveTile(false)
, m_keepInterResults(false)
{
m_lastBuiltTileBmin[0] = 0.0f;
m_lastBuiltTileBmin[1] = 0.0f;
m_lastBuiltTileBmin[2] = 0.0f;
m_lastBuiltTileBmax[0] = 0.0f;
m_lastBuiltTileBmax[1] = 0.0f;
m_lastBuiltTileBmax[2] = 0.0f;
memset(&m_cfg, 0, sizeof(rcConfig));
}
void Editor_StaticTileMeshCommon::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;
}
void Editor_StaticTileMeshCommon::renderRecastDebugMenu()
{
ImGui::Text("Recast Render Options");
bool isEnabled = m_tileMeshDrawFlags & TM_DRAWFLAGS_INPUT_MESH;
// This should always be available, since if we load a large mesh we want to
// be able to toggle this off to save on performance. The renderer has to be
// moved to its own thread to solve this issue.
if (ImGui::Checkbox("Input Mesh", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_INPUT_MESH);
// Check which modes are valid.
//const bool hasNavMesh =m_navMesh != 0;
const bool hasChf = m_chf != 0;
const bool hasCset = m_cset != 0;
const bool hasSolid = m_solid != 0;
const bool hasDMesh = m_dmesh != 0;
const bool intermediateDataUnavailable = !hasChf || !hasCset || !hasSolid || !hasDMesh;
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_NAVMESH;
//ImGui::BeginDisabled(!hasNavMesh);
if (ImGui::Checkbox("NavMesh", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_NAVMESH);
//ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_VOXELS;
ImGui::BeginDisabled(!hasSolid);
if (ImGui::Checkbox("Voxels", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_VOXELS);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_VOXELS_WALKABLE;
ImGui::BeginDisabled(!hasSolid);
if (ImGui::Checkbox("Walkable Voxels", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_VOXELS_WALKABLE);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_COMPACT;
ImGui::BeginDisabled(!hasChf);
if (ImGui::Checkbox("Compact", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_COMPACT);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_COMPACT_DISTANCE;
ImGui::BeginDisabled(!hasChf);
if (ImGui::Checkbox("Compact Distance", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_COMPACT_DISTANCE);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_COMPACT_REGIONS;
ImGui::BeginDisabled(!hasChf);
if (ImGui::Checkbox("Compact Regions", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_COMPACT_REGIONS);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_REGION_CONNECTIONS;
ImGui::BeginDisabled(!hasCset);
if (ImGui::Checkbox("Region Connections", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_REGION_CONNECTIONS);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_RAW_CONTOURS;
ImGui::BeginDisabled(!hasCset);
if (ImGui::Checkbox("Raw Contours", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_RAW_CONTOURS);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_CONTOURS;
ImGui::BeginDisabled(!hasCset);
if (ImGui::Checkbox("Contours", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_CONTOURS);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_POLYMESH;
ImGui::BeginDisabled(!hasDMesh);
if (ImGui::Checkbox("Poly Mesh", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_POLYMESH);
ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_POLYMESH_DETAIL;
ImGui::BeginDisabled(!hasDMesh);
if (ImGui::Checkbox("Poly Mesh Detail", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_POLYMESH_DETAIL);
ImGui::EndDisabled();
//if (intermediateDataUnavailable) // todo(amos): tool tip
//{
// ImGui::Separator();
// ImGui::Text("Tick 'Keep Intermediate Results'");
// ImGui::Text("rebuild some tiles to see");
// ImGui::Text("more debug mode options.");
//}
}
void Editor_StaticTileMeshCommon::renderTileMeshData()
{
if (!m_geom || !m_geom->getMesh())
return;
const float texScale = 1.0f / (m_cellSize * 10.0f);
// Draw input mesh
if (getTileMeshDrawFlags() & TM_DRAWFLAGS_INPUT_MESH)
EditorCommon_DrawInputGeometry(&m_dd, m_geom, m_agentMaxSlope, texScale);
glDepthMask(GL_FALSE);
// Draw bounds
EditorCommon_DrawBoundingBox(&m_dd, m_geom);
// Tiling grid.
EditorCommon_DrawTilingGrid(&m_dd, m_geom, m_tileSize, m_cellSize);
const float* recastDrawOffset = getRecastDrawOffset();
const float* detourDrawOffset = getDetourDrawOffset();
const unsigned int recastDrawFlags = getTileMeshDrawFlags();
const unsigned int detourDrawFlags = getNavMeshDrawFlags();
if (m_drawActiveTile)
{
// Draw active tile
// NOTE: only perform offset in x-y
duDebugDrawBoxWire(&m_dd, m_lastBuiltTileBmin[0]+detourDrawOffset[0], m_lastBuiltTileBmin[1]+detourDrawOffset[1], m_lastBuiltTileBmin[2],
m_lastBuiltTileBmax[0]+detourDrawOffset[0], m_lastBuiltTileBmax[1]+detourDrawOffset[1], m_lastBuiltTileBmax[2], m_tileCol, 1.0f, nullptr);
}
if (m_navMesh && m_navQuery)
{
if (m_tileMeshDrawFlags & TM_DRAWFLAGS_NAVMESH)
{
duDebugDrawNavMeshWithClosedList(&m_dd, *m_navMesh, *m_navQuery, detourDrawOffset, m_navMeshDrawFlags, m_traverseLinkDrawTypes);
duDebugDrawNavMeshPolysWithFlags(&m_dd, *m_navMesh, EDITOR_POLYFLAGS_DISABLED, detourDrawOffset, detourDrawFlags, duRGBA(0, 0, 0, 128));
}
}
glDepthMask(GL_TRUE);
if (m_chf)
{
if (recastDrawFlags & TM_DRAWFLAGS_COMPACT)
duDebugDrawCompactHeightfieldSolid(&m_dd, *m_chf, recastDrawOffset);
if (recastDrawFlags & TM_DRAWFLAGS_COMPACT_DISTANCE)
duDebugDrawCompactHeightfieldDistance(&m_dd, *m_chf, recastDrawOffset);
if (recastDrawFlags & TM_DRAWFLAGS_COMPACT_REGIONS)
duDebugDrawCompactHeightfieldRegions(&m_dd, *m_chf, recastDrawOffset);
}
if (m_solid)
{
if (recastDrawFlags & TM_DRAWFLAGS_VOXELS)
{
glEnable(GL_FOG);
duDebugDrawHeightfieldSolid(&m_dd, *m_solid, recastDrawOffset);
glDisable(GL_FOG);
}
if (recastDrawFlags & TM_DRAWFLAGS_VOXELS_WALKABLE)
{
glEnable(GL_FOG);
duDebugDrawHeightfieldWalkable(&m_dd, *m_solid, recastDrawOffset);
glDisable(GL_FOG);
}
}
if (m_cset)
{
if (recastDrawFlags & TM_DRAWFLAGS_RAW_CONTOURS)
{
glDepthMask(GL_FALSE);
duDebugDrawRawContours(&m_dd, *m_cset, recastDrawOffset);
glDepthMask(GL_TRUE);
}
if (recastDrawFlags & TM_DRAWFLAGS_CONTOURS)
{
glDepthMask(GL_FALSE);
duDebugDrawContours(&m_dd, *m_cset, recastDrawOffset);
glDepthMask(GL_TRUE);
}
}
if ((m_chf &&m_cset) &&
(recastDrawFlags & TM_DRAWFLAGS_REGION_CONNECTIONS))
{
duDebugDrawCompactHeightfieldRegions(&m_dd, *m_chf, recastDrawOffset);
glDepthMask(GL_FALSE);
duDebugDrawRegionConnections(&m_dd, *m_cset, recastDrawOffset);
glDepthMask(GL_TRUE);
}
if (m_pmesh && (recastDrawFlags & TM_DRAWFLAGS_POLYMESH))
{
glDepthMask(GL_FALSE);
duDebugDrawPolyMesh(&m_dd, *m_pmesh, recastDrawOffset);
glDepthMask(GL_TRUE);
}
if (m_dmesh && (recastDrawFlags & TM_DRAWFLAGS_POLYMESH_DETAIL))
{
glDepthMask(GL_FALSE);
duDebugDrawPolyMeshDetail(&m_dd, *m_dmesh, recastDrawOffset);
glDepthMask(GL_TRUE);
}
// TODO: also add flags for this
m_geom->drawConvexVolumes(&m_dd, recastDrawOffset);
// NOTE: commented out because this already gets rendered when the off-mesh
// connection tool is activated. And if we generated an off-mesh link, this
// would overlap with that as well.
//m_geom->drawOffMeshConnections(&m_dd, recastDrawOffset);
if (m_tool)
m_tool->handleRender();
renderToolStates();
glDepthMask(GL_TRUE);
}
void Editor_StaticTileMeshCommon::renderIntermediateTileMeshOptions()
{
ImGui::Indent();
ImGui::Indent();
if (ImGui::Button("Load", ImVec2(123, 0)))
{
Editor::loadNavMesh(m_modelName.c_str());
}
if (ImGui::Button("Save", ImVec2(123, 0)))
{
Editor::saveAll(m_modelName.c_str(), m_navMesh);
}
ImGui::Unindent();
ImGui::Unindent();
ImGui::Text("Build Time: %.1fms", m_totalBuildTimeMs);
if (m_navMesh)
{
const dtNavMeshParams& params = *m_navMesh->getParams();
//const float* origin = m_navMesh->m_orig;
//ImGui::Text("Mesh Origin: \n\tX: %g \n\tY: %g \n\tZ: %g", origin[0], origin[1], origin[2]);
ImGui::Text("Tile Dimensions: %g x %g", params.tileWidth, params.tileHeight);
ImGui::Text("Poly Group Count: %d", params.polyGroupCount);
ImGui::Text("Traversal Table Size: %d", params.traversalTableSize);
ImGui::Text("Traversal Table Count: %d", params.traversalTableCount);
ImGui::Text("Max Tiles: %d", params.maxTiles);
ImGui::Text("Max Polys: %d", params.maxPolys);
ImGui::Separator();
}
else
ImGui::Separator();
}
void drawTiles(duDebugDraw* dd, dtTileCache* tc, const float* offset)
{
unsigned int fcol[6];
float bmin[3], bmax[3];
for (int i = 0; i < tc->getTileCount(); ++i)
{
const dtCompressedTile* tile = tc->getTile(i);
if (!tile->header) continue;
tc->calcTightTileBounds(tile->header, bmin, bmax);
const unsigned int col = duIntToCol(i, 64);
duCalcBoxColors(fcol, col, col);
duDebugDrawBox(dd, bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2], fcol, offset);
}
for (int i = 0; i < tc->getTileCount(); ++i)
{
const dtCompressedTile* tile = tc->getTile(i);
if (!tile->header) continue;
tc->calcTightTileBounds(tile->header, bmin, bmax);
const unsigned int col = duIntToCol(i, 255);
const float pad = tc->getParams()->cs * 0.1f;
duDebugDrawBoxWire(dd, bmin[0] - pad, bmin[1] - pad, bmin[2] - pad,
bmax[0] + pad, bmax[1] + pad, bmax[2] + pad, col, 2.0f, offset);
}
}
void drawObstacles(duDebugDraw* dd, const dtTileCache* tc, const float* offset)
{
// Draw obstacles
for (int i = 0; i < tc->getObstacleCount(); ++i)
{
const dtTileCacheObstacle* ob = tc->getObstacle(i);
if (ob->state == DT_OBSTACLE_EMPTY) continue;
float bmin[3], bmax[3];
tc->getObstacleBounds(ob, bmin, bmax);
unsigned int col = 0;
if (ob->state == DT_OBSTACLE_PROCESSING)
col = duRGBA(255, 255, 0, 128);
else if (ob->state == DT_OBSTACLE_PROCESSED)
col = duRGBA(255, 192, 0, 192);
else if (ob->state == DT_OBSTACLE_REMOVING)
col = duRGBA(220, 0, 0, 128);
duDebugDrawCylinder(dd, bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2], col, offset);
duDebugDrawCylinderWire(dd, bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2], duDarkenCol(col), 2.0f, offset);
}
}
Editor_DynamicTileMeshCommon::Editor_DynamicTileMeshCommon()
: m_tileCache(nullptr)
, m_talloc(nullptr)
, m_tcomp(nullptr)
, m_tmproc(nullptr)
, m_cacheBuildTimeMs(0.0f)
, m_cacheCompressedSize(0)
, m_cacheRawSize(0)
, m_cacheLayerCount(0)
, m_cacheBuildMemUsage(0)
, m_tileMeshDrawFlags(TM_DRAWFLAGS_INPUT_MESH|TM_DRAWFLAGS_NAVMESH)
, m_keepInterResults(false)
{
}
void Editor_DynamicTileMeshCommon::renderRecastRenderOptions()
{
ImGui::Text("Recast Render Options");
bool isEnabled = m_tileMeshDrawFlags & TM_DRAWFLAGS_INPUT_MESH;
// This should always be available, since if we load a large mesh we want to
// be able to toggle this off to save on performance. The renderer has to be
// moved to its own thread to solve this issue.
if (ImGui::Checkbox("Input Mesh", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_INPUT_MESH);
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_NAVMESH;
//ImGui::BeginDisabled(!hasNavMesh);
if (ImGui::Checkbox("NavMesh", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_NAVMESH);
//ImGui::EndDisabled();
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_TILE_CACHE_BOUNDS;
ImGui::BeginDisabled(!m_tileCache);
if (ImGui::Checkbox("Cache Bounds", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_TILE_CACHE_BOUNDS);
isEnabled = getTileMeshDrawFlags() & TM_DRAWFLAGS_TILE_CACHE_OBSTACLES;
if (ImGui::Checkbox("Temp Obstacles", &isEnabled))
toggleTileMeshDrawFlag(TM_DRAWFLAGS_TILE_CACHE_OBSTACLES);
ImGui::EndDisabled();
}
void Editor_DynamicTileMeshCommon::renderTileMeshData()
{
if (!m_geom || !m_geom->getMesh())
return;
const float texScale = 1.0f / (m_cellSize * 10.0f);
const unsigned int recastDrawFlags = getTileMeshDrawFlags();
const unsigned int detourDrawFlags = getNavMeshDrawFlags();
const float* recastDrawOffset = getRecastDrawOffset();
const float* detourDrawOffset = getDetourDrawOffset();
// Draw input mesh
if (recastDrawFlags & TM_DRAWFLAGS_INPUT_MESH)
EditorCommon_DrawInputGeometry(&m_dd, m_geom, m_agentMaxSlope, texScale);
// Draw bounds
EditorCommon_DrawBoundingBox(&m_dd, m_geom);
// Tiling grid.
EditorCommon_DrawTilingGrid(&m_dd, m_geom, m_tileSize, m_cellSize);
if (m_tileCache && recastDrawFlags & TM_DRAWFLAGS_TILE_CACHE_BOUNDS)
drawTiles(&m_dd, m_tileCache, detourDrawOffset);
if (m_tileCache && recastDrawFlags & TM_DRAWFLAGS_TILE_CACHE_OBSTACLES)
drawObstacles(&m_dd, m_tileCache, detourDrawOffset);
if (m_navMesh && m_navQuery)
{
if (recastDrawFlags & TM_DRAWFLAGS_NAVMESH)
{
duDebugDrawNavMeshWithClosedList(&m_dd, *m_navMesh, *m_navQuery, detourDrawOffset, detourDrawFlags);
duDebugDrawNavMeshPolysWithFlags(&m_dd, *m_navMesh, EDITOR_POLYFLAGS_DISABLED, detourDrawOffset, detourDrawFlags, duRGBA(0, 0, 0, 128));
}
}
// TODO: also add flags for this
m_geom->drawConvexVolumes(&m_dd, recastDrawOffset);
// NOTE: commented out because this already gets rendered when the off-mesh
// connection tool is activated. And if we generated an off-mesh link, this
// would overlap with that as well.
//m_geom->drawOffMeshConnections(&m_dd, recastDrawOffset);
if (m_tool)
m_tool->handleRender();
renderToolStates();
glDepthMask(GL_TRUE);
}