r5sdk/src/naveditor/Editor.cpp
Kawe Mazidjatari 1a3f7b6bda Recast: rename reachability table to traversal table
The static pathing code is split into 2 elements, the disjoint sets (poly groups), and the traversal table (which links poly groups, visually known as poly islands) together through jump or offmesh links. Reachability covers more than just traversal in the context of this game engine, therefore it has been renamed to avoid future confusion.
2024-07-05 17:48:48 +02:00

491 lines
12 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 "Pch.h"
#include "Recast/Include/Recast.h"
#include "Shared/Include/SharedAssert.h"
#include "Detour/Include/DetourNavMesh.h"
#include "Detour/Include/DetourNavMeshQuery.h"
#include "DetourCrowd/Include/DetourCrowd.h"
#include "DebugUtils/Include/RecastDebugDraw.h"
#include "DebugUtils/Include/DetourDebugDraw.h"
#include "NavEditor/Include/FileTypes.h"
#include "NavEditor/Include/GameUtils.h"
#include "NavEditor/Include/InputGeom.h"
#include "NavEditor/Include/Editor.h"
unsigned int EditorDebugDraw::areaToCol(unsigned int area)
{
switch(area)
{
// Ground (0) : light blue
case EDITOR_POLYAREA_GROUND: return duRGBA(0, 135, 255, 255);
// Water : blue
case EDITOR_POLYAREA_WATER: return duRGBA(0, 0, 255, 255);
// Road : brown
case EDITOR_POLYAREA_ROAD: return duRGBA(50, 20, 12, 255);
// Door : cyan
case EDITOR_POLYAREA_DOOR: return duRGBA(0, 255, 255, 255);
// Grass : green
case EDITOR_POLYAREA_GRASS: return duRGBA(0, 255, 0, 255);
// Jump : yellow
case EDITOR_POLYAREA_JUMP: return duRGBA(255, 255, 0, 255);
// Unexpected : red
default: return duRGBA(255, 0, 0, 255);
}
}
Editor::Editor() :
m_geom(0),
m_navMesh(0),
m_navQuery(0),
m_crowd(0),
m_navMeshDrawFlags(DU_DRAWNAVMESH_OFFMESHCONS|DU_DRAWNAVMESH_CLOSEDLIST),
m_filterLowHangingObstacles(true),
m_filterLedgeSpans(true),
m_filterWalkableLowHeightSpans(true),
m_tool(0),
m_ctx(0)
{
resetCommonSettings();
m_navQuery = rdAllocNavMeshQuery();
m_crowd = rdAllocCrowd();
for (int i = 0; i < MAX_TOOLS; i++)
m_toolStates[i] = 0;
}
Editor::~Editor()
{
rdFreeNavMeshQuery(m_navQuery);
rdFreeNavMesh(m_navMesh);
rdFreeCrowd(m_crowd);
delete m_tool;
for (int i = 0; i < MAX_TOOLS; i++)
delete m_toolStates[i];
}
void Editor::setTool(EditorTool* tool)
{
delete m_tool;
m_tool = tool;
if (tool)
m_tool->init(this);
}
void Editor::handleSettings()
{
}
void Editor::handleTools()
{
}
void Editor::handleDebugMode()
{
}
void Editor::handleRender()
{
if (!m_geom)
return;
// Draw mesh
duDebugDrawTriMesh(&m_dd, m_geom->getMesh()->getVerts(), m_geom->getMesh()->getVertCount(),
m_geom->getMesh()->getTris(), m_geom->getMesh()->getNormals(), m_geom->getMesh()->getTriCount(), 0, 1.0f);
// Draw bounds
const float* bmin = m_geom->getMeshBoundsMin();
const float* bmax = m_geom->getMeshBoundsMax();
duDebugDrawBoxWire(&m_dd, bmin[0],bmin[1],bmin[2], bmax[0],bmax[1],bmax[2], duRGBA(255,255,255,128), 1.0f);
}
void Editor::handleRenderOverlay(double* /*proj*/, double* /*model*/, int* /*view*/)
{
}
void Editor::handleMeshChanged(InputGeom* geom)
{
m_geom = geom;
const BuildSettings* buildSettings = geom->getBuildSettings();
if (buildSettings)
{
m_cellSize = buildSettings->cellSize;
m_cellHeight = buildSettings->cellHeight;
m_agentHeight = buildSettings->agentHeight;
m_agentRadius = buildSettings->agentRadius;
m_agentMaxClimb = buildSettings->agentMaxClimb;
m_agentMaxSlope = buildSettings->agentMaxSlope;
m_regionMinSize = buildSettings->regionMinSize;
m_regionMergeSize = buildSettings->regionMergeSize;
m_edgeMaxLen = buildSettings->edgeMaxLen;
m_edgeMaxError = buildSettings->edgeMaxError;
m_vertsPerPoly = buildSettings->vertsPerPoly;
m_detailSampleDist = buildSettings->detailSampleDist;
m_detailSampleMaxError = buildSettings->detailSampleMaxError;
m_partitionType = buildSettings->partitionType;
}
}
void Editor::collectSettings(BuildSettings& settings)
{
settings.cellSize = m_cellSize;
settings.cellHeight = m_cellHeight;
settings.agentHeight = m_agentHeight;
settings.agentRadius = m_agentRadius;
settings.agentMaxClimb = m_agentMaxClimb;
settings.agentMaxSlope = m_agentMaxSlope;
settings.regionMinSize = m_regionMinSize;
settings.regionMergeSize = m_regionMergeSize;
settings.edgeMaxLen = m_edgeMaxLen;
settings.edgeMaxError = m_edgeMaxError;
settings.vertsPerPoly = m_vertsPerPoly;
settings.detailSampleDist = m_detailSampleDist;
settings.detailSampleMaxError = m_detailSampleMaxError;
settings.partitionType = m_partitionType;
}
void Editor::resetCommonSettings()
{
m_cellSize = 16.0f;
m_cellHeight = 5.85f;
m_agentHeight = 2.0f;
m_agentRadius = 0.6f;
m_agentMaxClimb = 0.9f;
m_agentMaxSlope = 45.0f;
m_regionMinSize = 8;
m_regionMergeSize = 20;
m_edgeMaxLen = 12.0f;
m_edgeMaxError = 1.3f;
m_vertsPerPoly = 6.0f;
m_detailSampleDist = 6.0f;
m_detailSampleMaxError = 1.0f;
m_partitionType = EDITOR_PARTITION_WATERSHED;
}
void Editor::handleCommonSettings()
{
imguiLabel("Rasterization");
imguiSlider("Cell Size", &m_cellSize, 0.1f, 100.0f, 0.01f);
imguiSlider("Cell Height", &m_cellHeight, 0.1f, 100.0f, 0.01f);
if (m_geom)
{
const float* bmin = m_geom->getNavMeshBoundsMin();
const float* bmax = m_geom->getNavMeshBoundsMax();
int gw = 0, gh = 0;
rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh);
char text[64];
snprintf(text, 64, "Voxels %d x %d", gw, gh);
imguiValue(text);
}
imguiSeparator();
imguiLabel("Agent");
imguiSlider("Height", &m_agentHeight, 0.1f, 500.0f, 0.1f);
imguiSlider("Radius", &m_agentRadius, 0.0f, 500.0f, 0.1f);
imguiSlider("Max Climb", &m_agentMaxClimb, 0.1f, 120.0f, 0.1f);
imguiSlider("Max Slope", &m_agentMaxSlope, 0.0f, 90.0f, 1.0f);
imguiSeparator();
imguiLabel("Region");
imguiSlider("Min Region Size", &m_regionMinSize, 0.0f, 750.0f, 1.0f);
imguiSlider("Merged Region Size", &m_regionMergeSize, 0.0f, 750.0f, 1.0f);
imguiSeparator();
imguiLabel("Partitioning");
if (imguiCheck("Watershed", m_partitionType == EDITOR_PARTITION_WATERSHED))
m_partitionType = EDITOR_PARTITION_WATERSHED;
if (imguiCheck("Monotone", m_partitionType == EDITOR_PARTITION_MONOTONE))
m_partitionType = EDITOR_PARTITION_MONOTONE;
if (imguiCheck("Layers", m_partitionType == EDITOR_PARTITION_LAYERS))
m_partitionType = EDITOR_PARTITION_LAYERS;
imguiSeparator();
imguiLabel("Filtering");
if (imguiCheck("Low Hanging Obstacles", m_filterLowHangingObstacles))
m_filterLowHangingObstacles = !m_filterLowHangingObstacles;
if (imguiCheck("Ledge Spans", m_filterLedgeSpans))
m_filterLedgeSpans= !m_filterLedgeSpans;
if (imguiCheck("Walkable Low Height Spans", m_filterWalkableLowHeightSpans))
m_filterWalkableLowHeightSpans = !m_filterWalkableLowHeightSpans;
imguiSeparator();
imguiLabel("Polygonization");
imguiSlider("Max Edge Length", &m_edgeMaxLen, 0.0f, 50.0f, 1.0f);
imguiSlider("Max Edge Error", &m_edgeMaxError, 0.1f, 3.0f, 0.1f);
imguiSlider("Verts Per Poly", &m_vertsPerPoly, 3.0f, 6.0f, 1.0f);
imguiSeparator();
imguiLabel("Detail Mesh");
imguiSlider("Sample Distance", &m_detailSampleDist, 0.0f, 16.0f, 1.0f);
imguiSlider("Max Sample Error", &m_detailSampleMaxError, 0.0f, 16.0f, 1.0f);
imguiSeparator();
}
void Editor::handleClick(const float* s, const float* p, bool shift)
{
if (m_tool)
m_tool->handleClick(s, p, shift);
}
void Editor::handleToggle()
{
if (m_tool)
m_tool->handleToggle();
}
void Editor::handleStep()
{
if (m_tool)
m_tool->handleStep();
}
bool Editor::handleBuild()
{
return true;
}
void Editor::handleUpdate(const float dt)
{
if (m_tool)
m_tool->handleUpdate(dt);
updateToolStates(dt);
}
void Editor::updateToolStates(const float dt)
{
for (int i = 0; i < MAX_TOOLS; i++)
{
if (m_toolStates[i])
m_toolStates[i]->handleUpdate(dt);
}
}
void Editor::initToolStates(Editor* editor)
{
for (int i = 0; i < MAX_TOOLS; i++)
{
if (m_toolStates[i])
m_toolStates[i]->init(editor);
}
}
void Editor::resetToolStates()
{
for (int i = 0; i < MAX_TOOLS; i++)
{
if (m_toolStates[i])
m_toolStates[i]->reset();
}
}
void Editor::renderToolStates()
{
for (int i = 0; i < MAX_TOOLS; i++)
{
if (m_toolStates[i])
m_toolStates[i]->handleRender();
}
}
void Editor::renderOverlayToolStates(double* proj, double* model, int* view)
{
for (int i = 0; i < MAX_TOOLS; i++)
{
if (m_toolStates[i])
m_toolStates[i]->handleRenderOverlay(proj, model, view);
}
}
dtNavMesh* Editor::loadAll(std::string path)
{
fs::path p = "..\\maps\\navmesh\\";
if (fs::is_directory(p))
{
path.insert(0, p.string());
}
char buffer[256];
sprintf(buffer, "%s_%s.nm", path.c_str(), m_navmeshName);
FILE* fp = fopen(buffer, "rb");
if (!fp)
return 0;
// Read header.
NavMeshSetHeader header;
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);
if (readLen != 1)
{
fclose(fp);
return 0;
}
if (header.magic != NAVMESHSET_MAGIC)
{
fclose(fp);
return 0;
}
if (header.version != NAVMESHSET_VERSION)
{
fclose(fp);
return 0;
}
dtNavMesh* mesh = rdAllocNavMesh();
if (!mesh)
{
fclose(fp);
return 0;
}
dtStatus status = mesh->init(&header.params);
if (dtStatusFailed(status))
{
fclose(fp);
return 0;
}
// Read tiles.
for (int i = 0; i < header.numTiles; ++i)
{
NavMeshTileHeader tileHeader;
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
if (readLen != 1)
{
fclose(fp);
return 0;
}
if (!tileHeader.tileRef || !tileHeader.dataSize)
break;
unsigned char* data = (unsigned char*)rdAlloc(tileHeader.dataSize, RD_ALLOC_PERM);
if (!data)
break;
memset(data, 0, tileHeader.dataSize);
readLen = fread(data, tileHeader.dataSize, 1, fp);
if (readLen != 1)
{
rdFree(data);
fclose(fp);
return 0;
}
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, NULL);
}
// Read read static pathing data.
if (header.params.polyGroupCount >= DT_MIN_POLY_GROUP_COUNT)
{
for (int i = 0; i < header.params.traversalTableCount; i++)
{
int* traversalTable = (int*)rdAlloc(header.params.traversalTableSize, RD_ALLOC_PERM);
if (!traversalTable)
break;
memset(traversalTable, 0, header.params.traversalTableSize);
readLen = fread(traversalTable, header.params.traversalTableSize, 1, fp);
if (readLen != 1)
{
rdFree(traversalTable);
fclose(fp);
return 0;
}
mesh->m_traversalTables[i] = traversalTable;
}
}
fclose(fp);
return mesh;
}
void Editor::saveAll(std::string path, const dtNavMesh* mesh)
{
if (!mesh)
return;
fs::path p = "..\\maps\\navmesh\\";
if (fs::is_directory(p))
{
path.insert(0, p.string());
}
char buffer[256];
sprintf(buffer, "%s_%s.nm", path.c_str(), m_navmeshName);
FILE* fp = fopen(buffer, "wb");
if (!fp)
return;
// Store header.
NavMeshSetHeader header;
header.magic = NAVMESHSET_MAGIC;
header.version = NAVMESHSET_VERSION;
header.numTiles = 0;
for (int i = 0; i < mesh->getMaxTiles(); ++i)
{
const dtMeshTile* tile = mesh->getTile(i);
if (!tile || !tile->header || !tile->dataSize)
continue;
header.numTiles++;
}
memcpy(&header.params, mesh->getParams(), sizeof(dtNavMeshParams));
fwrite(&header, sizeof(NavMeshSetHeader), 1, fp);
// Store tiles.
for (int i = 0; i < mesh->getMaxTiles(); ++i)
{
const dtMeshTile* tile = mesh->getTile(i);
if (!tile || !tile->header || !tile->dataSize)
continue;
NavMeshTileHeader tileHeader;
tileHeader.tileRef = mesh->getTileRef(tile);
tileHeader.dataSize = tile->dataSize;
fwrite(&tileHeader, sizeof(tileHeader), 1, fp);
fwrite(tile->data, tile->dataSize, 1, fp);
}
// Only store if we have 3 or more poly groups.
if (mesh->m_params.polyGroupCount >= DT_MIN_POLY_GROUP_COUNT)
{
rdAssert(mesh->m_traversalTables);
for (int i = 0; i < header.params.traversalTableCount; i++)
{
const int* const tableData = mesh->m_traversalTables[i];
rdAssert(tableData);
fwrite(tableData, sizeof(int), (header.params.traversalTableSize/4), fp);
}
}
fclose(fp);
}