mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-30 08:07:46 +00:00
add headless navmesh exporting
This commit is contained in:
parent
3d88447968
commit
c608d9e629
4 changed files with 674 additions and 456 deletions
572
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
572
src/tools/pcb_reader/nav/TiledNavmeshGenerator.cpp
Normal file
|
@ -0,0 +1,572 @@
|
|||
#include "TiledNavmeshGenerator.h"
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <cstring>
|
||||
|
||||
#include <recastnavigation/Detour/Include/DetourNavMeshBuilder.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
|
||||
inline unsigned int nextPow2( uint32_t v )
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline unsigned int ilog2( uint32_t v )
|
||||
{
|
||||
uint32_t r;
|
||||
uint32_t shift;
|
||||
r = (v > 0xffff) << 4; v >>= r;
|
||||
shift = (v > 0xff) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xf) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool TiledNavmeshGenerator::init( const std::string& path )
|
||||
{
|
||||
if( !fs::exists( path ) )
|
||||
throw std::runtime_error( "what" );
|
||||
|
||||
// ignore logging/bullshit/etc
|
||||
m_ctx = new rcContext( false );
|
||||
|
||||
printf( "[Navmesh] loading obj: %s\n", path.substr( path.find( "pcb_export" ) - 1 ).c_str() );
|
||||
|
||||
m_mesh = new rcMeshLoaderObj;
|
||||
assert( m_mesh );
|
||||
|
||||
if( !m_mesh->load( path ) )
|
||||
{
|
||||
printf( "[Navmesh] Failed to allocate rcMeshLoaderObj\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
rcCalcBounds( m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax );
|
||||
|
||||
m_chunkyMesh = new rcChunkyTriMesh;
|
||||
assert( m_chunkyMesh );
|
||||
|
||||
if( !rcCreateChunkyTriMesh( m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Failed to build chunky mesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: load some bullshit settings from exd
|
||||
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||
|
||||
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||
const uint32_t tw = (gw + ts-1) / ts;
|
||||
const uint32_t th = (gh + ts-1) / ts;
|
||||
|
||||
printf( "[Navmesh] - Tiles %d x %d\n", tw, th );
|
||||
|
||||
int tileBits = rcMin( ( int ) ilog2( nextPow2( tw * th ) ), 14 );
|
||||
if ( tileBits > 14 ) tileBits = 14;
|
||||
int polyBits = 22 - tileBits;
|
||||
m_maxTiles = 1 << tileBits;
|
||||
m_maxPolysPerTile = 1 << polyBits;
|
||||
|
||||
printf( "[Navmesh] - %.1fK verts, %.1fK tris\n", m_mesh->getVertCount() / 1000.0f, m_mesh->getTriCount() / 1000.0f );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TiledNavmeshGenerator::~TiledNavmeshGenerator()
|
||||
{
|
||||
delete m_mesh;
|
||||
delete m_chunkyMesh;
|
||||
}
|
||||
|
||||
void TiledNavmeshGenerator::saveNavmesh( const std::string& name )
|
||||
{
|
||||
assert( m_navMesh );
|
||||
|
||||
// fuck this gay earth
|
||||
auto mesh = const_cast< const dtNavMesh* >( m_navMesh );
|
||||
|
||||
auto dir = fs::current_path().string() + "/pcb_export/" + name + "/";
|
||||
auto fileName = dir + name + ".nav";
|
||||
|
||||
fs::create_directories( dir );
|
||||
|
||||
FILE* fp = fopen( fileName.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 < mesh->getMaxTiles(); ++i )
|
||||
{
|
||||
auto 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 )
|
||||
{
|
||||
auto 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 );
|
||||
}
|
||||
|
||||
fclose( fp );
|
||||
|
||||
auto pos = fileName.find( "pcb_export" );
|
||||
fileName = fileName.substr( pos - 1 );
|
||||
|
||||
printf( "[Navmesh] Saved navmesh to '%s'\n", fileName.c_str() );
|
||||
}
|
||||
|
||||
bool TiledNavmeshGenerator::buildNavmesh()
|
||||
{
|
||||
assert( m_mesh );
|
||||
|
||||
m_navMesh = dtAllocNavMesh();
|
||||
if( !m_navMesh )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not allocate navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMeshParams params{};
|
||||
rcVcopy( params.orig, m_meshBMin );
|
||||
params.tileWidth = m_tileSize * m_cellSize;
|
||||
params.tileHeight = m_tileSize * m_cellSize;
|
||||
params.maxTiles = m_maxTiles;
|
||||
params.maxPolys = m_maxPolysPerTile;
|
||||
|
||||
dtStatus status;
|
||||
|
||||
status = m_navMesh->init( ¶ms );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not init navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_navQuery = dtAllocNavMeshQuery();
|
||||
assert( m_navQuery );
|
||||
|
||||
status = m_navQuery->init( m_navMesh, 2048 );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not init Detour navmesh query\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: duplicated from above, we can probably cache all this and only do it once
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||
const int tw = ( gw + ts - 1 ) / ts;
|
||||
const int th = ( gh + ts - 1 ) / ts;
|
||||
const float tcs = m_tileSize * m_cellSize;
|
||||
|
||||
for( int y = 0; y < th; y++ )
|
||||
{
|
||||
for( int x = 0; x < tw; x++ )
|
||||
{
|
||||
m_lastBuiltTileBmin[ 0 ] = m_meshBMin[ 0 ] + x * tcs;
|
||||
m_lastBuiltTileBmin[ 1 ] = m_meshBMin[ 1 ];
|
||||
m_lastBuiltTileBmin[ 2 ] = m_meshBMin[ 2 ] + y * tcs;
|
||||
|
||||
m_lastBuiltTileBmax[ 0 ] = m_meshBMin[ 0 ] + ( x + 1 ) * tcs;
|
||||
m_lastBuiltTileBmax[ 1 ] = m_meshBMax[ 1 ];
|
||||
m_lastBuiltTileBmax[ 2 ] = m_meshBMin[ 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 ), nullptr, nullptr );
|
||||
|
||||
// Let the navmesh own the data.
|
||||
status = m_navMesh->addTile( data, dataSize, DT_TILE_FREE_DATA, 0, nullptr );
|
||||
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
dtFree( data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
unsigned char* TiledNavmeshGenerator::buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax,
|
||||
int& dataSize )
|
||||
{
|
||||
const float* verts = m_mesh->getVerts();
|
||||
const int nverts = m_mesh->getVertCount();
|
||||
const int ntris = m_mesh->getTriCount();
|
||||
|
||||
// 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 = static_cast< int >( ceilf( m_agentHeight / m_cfg.ch ) );
|
||||
m_cfg.walkableClimb = static_cast< int >( floorf( m_agentMaxClimb / m_cfg.ch ) );
|
||||
m_cfg.walkableRadius = static_cast< int >( ceilf( m_agentRadius / m_cfg.cs ) );
|
||||
m_cfg.maxEdgeLen = static_cast< int >( m_edgeMaxLen / m_cellSize );
|
||||
m_cfg.maxSimplificationError = m_edgeMaxError;
|
||||
m_cfg.minRegionArea = static_cast< int >( rcSqr( m_regionMinSize ) ); // Note: area = size*size
|
||||
m_cfg.mergeRegionArea = static_cast< int >( rcSqr( m_regionMergeSize ) ); // Note: area = size*size
|
||||
m_cfg.maxVertsPerPoly = static_cast< int >( m_vertsPerPoly );
|
||||
m_cfg.tileSize = static_cast< 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;
|
||||
|
||||
m_solid = rcAllocHeightfield();
|
||||
if( !m_solid )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'solid'.\n" );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( !rcCreateHeightfield( m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not create solid heightfield.\n" );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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[ m_chunkyMesh->maxTrisPerChunk ];
|
||||
if( !m_triareas )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'm_triareas' (%d).\n", m_chunkyMesh->maxTrisPerChunk );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float tbmin[ 2 ];
|
||||
float 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( m_chunkyMesh, tbmin, tbmax, cid, 512 );
|
||||
|
||||
if( !ncid )
|
||||
return nullptr;
|
||||
|
||||
m_tileTriCount = 0;
|
||||
|
||||
for (int i = 0; i < ncid; ++i)
|
||||
{
|
||||
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[ cid[ i ] ];
|
||||
const int* ctris = &m_chunkyMesh->tris[ node.i * 3 ];
|
||||
const int nctris = node.n;
|
||||
|
||||
m_tileTriCount += nctris;
|
||||
|
||||
memset( m_triareas, 0, nctris * sizeof( unsigned char ) );
|
||||
rcMarkWalkableTriangles( m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, ctris, nctris, m_triareas );
|
||||
if( !rcRasterizeTriangles( m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb ) )
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
delete [] m_triareas;
|
||||
m_triareas = nullptr;
|
||||
|
||||
// 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.
|
||||
rcFilterLowHangingWalkableObstacles( m_ctx, m_cfg.walkableClimb, *m_solid );
|
||||
rcFilterLedgeSpans( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid );
|
||||
rcFilterWalkableLowHeightSpans( m_ctx, 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 )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'chf'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildCompactHeightfield( m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build compact data." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rcFreeHeightField(m_solid);
|
||||
m_solid = nullptr;
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if( !rcErodeWalkableArea( m_ctx, m_cfg.walkableRadius, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not erode." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// (Optional) Mark areas.
|
||||
// const ConvexVolume* vols = m_mesh->getConvexVolumes();
|
||||
// for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i)
|
||||
// rcMarkConvexPolyArea(m_ctx, 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, *m_chf ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build distance field." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if( !rcBuildRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build watershed regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
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, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build monotone regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else // SAMPLE_PARTITION_LAYERS
|
||||
{
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if( !rcBuildLayerRegions( m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not build layer regions." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Create contours.
|
||||
m_cset = rcAllocContourSet();
|
||||
if( !m_cset )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'cset'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildContours( m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not create contours." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( m_cset->nconts == 0 )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
m_pmesh = rcAllocPolyMesh();
|
||||
if( !m_pmesh )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'pmesh'." );
|
||||
return nullptr;
|
||||
}
|
||||
if( !rcBuildPolyMesh( m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could not triangulate contours." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Build detail mesh.
|
||||
m_dmesh = rcAllocPolyMeshDetail();
|
||||
if( !m_dmesh )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Out of memory 'dmesh'." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ( !rcBuildPolyMeshDetail( m_ctx, *m_pmesh, *m_chf,
|
||||
m_cfg.detailSampleDist, m_cfg.detailSampleMaxError,
|
||||
*m_dmesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildNavigation: Could build polymesh detail." );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rcFreeCompactHeightfield( m_chf );
|
||||
rcFreeContourSet( m_cset );
|
||||
m_chf = nullptr;
|
||||
m_cset = nullptr;
|
||||
|
||||
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.
|
||||
printf( "[Navmesh] Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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;
|
||||
memset( ¶ms, 0, sizeof( 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 = nullptr;
|
||||
params.offMeshConRad = nullptr;
|
||||
params.offMeshConDir = nullptr;
|
||||
params.offMeshConAreas = nullptr;
|
||||
params.offMeshConFlags = nullptr;
|
||||
params.offMeshConUserID = nullptr;
|
||||
params.offMeshConCount = 0;
|
||||
|
||||
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 ) )
|
||||
{
|
||||
printf( "[Navmesh] Could not build Detour navmesh." );
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
rcFreePolyMesh( m_pmesh );
|
||||
rcFreePolyMeshDetail( m_dmesh );
|
||||
m_pmesh = nullptr;
|
||||
m_dmesh = nullptr;
|
||||
|
||||
dataSize = navDataSize;
|
||||
return navData;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <experimental/filesystem>
|
||||
#include <cmath>
|
||||
|
||||
#include "ext/MeshLoaderObj.h"
|
||||
#include "ext/ChunkyTriMesh.h"
|
||||
|
@ -12,147 +12,112 @@
|
|||
#include "recastnavigation/Detour/Include/DetourNavMeshQuery.h"
|
||||
#include "recastnavigation/Recast/Include/Recast.h"
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class TiledNavmeshGenerator
|
||||
{
|
||||
public:
|
||||
enum SamplePartitionType
|
||||
{
|
||||
SAMPLE_PARTITION_WATERSHED,
|
||||
SAMPLE_PARTITION_MONOTONE,
|
||||
SAMPLE_PARTITION_LAYERS,
|
||||
};
|
||||
|
||||
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.
|
||||
};
|
||||
|
||||
static const int NAVMESHSET_MAGIC = 'M'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'MSET';
|
||||
static const int NAVMESHSET_VERSION = 1;
|
||||
|
||||
struct NavMeshSetHeader
|
||||
{
|
||||
int magic;
|
||||
int version;
|
||||
int numTiles;
|
||||
dtNavMeshParams params;
|
||||
};
|
||||
|
||||
struct NavMeshTileHeader
|
||||
{
|
||||
dtTileRef tileRef;
|
||||
int dataSize;
|
||||
};
|
||||
|
||||
|
||||
TiledNavmeshGenerator() = default;
|
||||
~TiledNavmeshGenerator();
|
||||
|
||||
bool init( const std::string& path );
|
||||
unsigned char* buildTileMesh( const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize );
|
||||
bool buildNavmesh();
|
||||
void saveNavmesh( const std::string& name );
|
||||
|
||||
private:
|
||||
rcConfig m_cfg;
|
||||
|
||||
rcMeshLoaderObj* m_mesh;
|
||||
rcChunkyTriMesh* m_chunkyMesh;
|
||||
|
||||
rcContext* m_ctx;
|
||||
dtNavMesh* m_navMesh;
|
||||
dtNavMeshQuery* m_navQuery;
|
||||
rcHeightfield* m_solid;
|
||||
rcContourSet* m_cset;
|
||||
rcPolyMesh* m_pmesh;
|
||||
rcPolyMeshDetail* m_dmesh;
|
||||
|
||||
rcCompactHeightfield* m_chf;
|
||||
|
||||
float m_meshBMin[ 3 ];
|
||||
float m_meshBMax[ 3 ];
|
||||
|
||||
float m_tileSize = 160.f;
|
||||
float m_cellSize = 0.2f;
|
||||
unsigned char* m_triareas;
|
||||
|
||||
int m_maxTiles = 0;
|
||||
int m_maxPolysPerTile = 0;
|
||||
|
||||
inline unsigned int nextPow2( uint32_t v )
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
int m_tileTriCount = 0;
|
||||
|
||||
inline unsigned int ilog2( uint32_t v )
|
||||
{
|
||||
uint32_t r;
|
||||
uint32_t shift;
|
||||
r = (v > 0xffff) << 4; v >>= r;
|
||||
shift = (v > 0xff) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xf) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
return r;
|
||||
}
|
||||
int m_partitionType = SamplePartitionType::SAMPLE_PARTITION_WATERSHED;
|
||||
|
||||
// options
|
||||
float m_meshBMin[ 3 ];
|
||||
float m_meshBMax[ 3 ];
|
||||
|
||||
public:
|
||||
explicit TiledNavmeshGenerator( const std::string& path )
|
||||
{
|
||||
if( !fs::exists( path ) )
|
||||
throw std::runtime_error( "what" );
|
||||
float m_lastBuiltTileBmin[3];
|
||||
float m_lastBuiltTileBmax[3];
|
||||
|
||||
printf( "[Navmesh] loading obj: %s\n", path.c_str() );
|
||||
float m_tileSize = 160.f;
|
||||
float m_cellSize = 0.2f;
|
||||
float m_cellHeight = 0.2f;
|
||||
|
||||
m_mesh = new rcMeshLoaderObj;
|
||||
assert( m_mesh );
|
||||
float m_agentMaxSlope = 56.f;
|
||||
float m_agentHeight = 2.f;
|
||||
float m_agentMaxClimb = 0.6f;
|
||||
float m_agentRadius = 0.5f;
|
||||
|
||||
if( !m_mesh->load( path ) )
|
||||
{
|
||||
printf( "[Navmesh] Failed to allocate rcMeshLoaderObj\n" );
|
||||
return;
|
||||
}
|
||||
float m_edgeMaxLen = 12.f;
|
||||
float m_edgeMaxError = 1.4f;
|
||||
|
||||
rcCalcBounds( m_mesh->getVerts(), m_mesh->getVertCount(), m_meshBMin, m_meshBMax );
|
||||
float m_regionMinSize = 8.f;
|
||||
float m_regionMergeSize = 20.f;
|
||||
|
||||
m_chunkyMesh = new rcChunkyTriMesh;
|
||||
assert( m_chunkyMesh );
|
||||
|
||||
if( !rcCreateChunkyTriMesh( m_mesh->getVerts(), m_mesh->getTris(), m_mesh->getTriCount(), 256, m_chunkyMesh ) )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Failed to build chunky mesh.\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
printf( "[Navmesh] loaded obj, verts: %i tris: %i\n", m_mesh->getVertCount(), m_mesh->getTriCount() );
|
||||
|
||||
// todo: load some bullshit settings from exd
|
||||
|
||||
int gw = 0, gh = 0;
|
||||
rcCalcGridSize( m_meshBMin, m_meshBMax, m_cellSize, &gw, &gh );
|
||||
|
||||
auto ts = static_cast< uint32_t >( m_tileSize );
|
||||
const uint32_t tw = (gw + ts-1) / ts;
|
||||
const uint32_t th = (gh + ts-1) / ts;
|
||||
|
||||
printf( "[Navmesh] Tiles %d x %d\n", tw, th );
|
||||
|
||||
int tileBits = rcMin((int)ilog2(nextPow2(tw*th)), 14);
|
||||
if (tileBits > 14) tileBits = 14;
|
||||
int polyBits = 22 - tileBits;
|
||||
m_maxTiles = 1 << tileBits;
|
||||
m_maxPolysPerTile = 1 << polyBits;
|
||||
|
||||
printf( "[Navmesh] Max Tiles: %d\tMax Polys: %d\n", m_maxTiles, m_maxPolysPerTile );
|
||||
}
|
||||
|
||||
bool buildNavmesh()
|
||||
{
|
||||
assert( m_mesh );
|
||||
|
||||
m_navMesh = dtAllocNavMesh();
|
||||
if( !m_navMesh )
|
||||
{
|
||||
printf( "[Navmesh] buildTiledNavigation: Could not allocate navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
dtNavMeshParams params{};
|
||||
rcVcopy( params.orig, m_meshBMin );
|
||||
params.tileWidth = m_tileSize * m_cellSize;
|
||||
params.tileHeight = m_tileSize * m_cellSize;
|
||||
params.maxTiles = m_maxTiles;
|
||||
params.maxPolys = m_maxPolysPerTile;
|
||||
|
||||
dtStatus status;
|
||||
|
||||
status = m_navMesh->init( ¶ms );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navigation] buildTiledNavigation: Could not init navmesh.\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_navQuery = dtAllocNavMeshQuery();
|
||||
assert( m_navQuery );
|
||||
|
||||
status = m_navQuery->init( m_navMesh, 2048 );
|
||||
if( dtStatusFailed( status ) )
|
||||
{
|
||||
printf( "[Navigation] buildTiledNavigation: Could not init Detour navmesh query\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
~TiledNavmeshGenerator()
|
||||
{
|
||||
delete m_mesh;
|
||||
delete m_chunkyMesh;
|
||||
}
|
||||
float m_vertsPerPoly = 6.f;
|
||||
|
||||
float m_detailSampleDist = 6.f;
|
||||
float m_detailSampleMaxError = 1.f;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
#include "exporter.h"
|
||||
#include "nav/TiledNavmeshGenerator.h"
|
||||
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
class NavmeshExporter
|
||||
{
|
||||
public:
|
||||
|
@ -18,10 +22,16 @@ public:
|
|||
{
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
auto dir = std::experimental::filesystem::current_path().string() + "/pcb_export/" + zone.name + "/";
|
||||
auto dir = fs::current_path().string() + "/pcb_export/" + zone.name + "/";
|
||||
auto fileName = dir + zone.name + ".obj";
|
||||
|
||||
TiledNavmeshGenerator gen( fileName );
|
||||
TiledNavmeshGenerator gen;
|
||||
|
||||
if( !gen.init( fileName ) )
|
||||
{
|
||||
printf( "[Navmesh] failed to init TiledNavmeshGenerator for file '%s'\n", fileName.c_str() );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !gen.buildNavmesh() )
|
||||
{
|
||||
|
@ -29,6 +39,8 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
gen.saveNavmesh( zone.name );
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Navmesh] Finished exporting %s in %lu ms\n",
|
||||
fileName.c_str(),
|
||||
|
@ -39,336 +51,5 @@ public:
|
|||
{
|
||||
|
||||
}
|
||||
private:
|
||||
/*/
|
||||
static unsigned char* buildTileMesh( const ExportedGroup& group, int tx, int ty )
|
||||
{
|
||||
unsigned char* navData;
|
||||
rcConfig cfg;
|
||||
cfg.ch = 0.2f;
|
||||
cfg.cs = 0.2f;
|
||||
cfg.walkableHeight = 2.f;
|
||||
cfg.walkableRadius = 0.5;
|
||||
cfg.walkableClimb = 0.6;
|
||||
cfg.walkableSlopeAngle = 58.f;
|
||||
cfg.minRegionArea = 8.0f;
|
||||
cfg.mergeRegionArea = 20.f;
|
||||
cfg.maxEdgeLen = 12.f;
|
||||
cfg.maxSimplificationError = 1.4f;
|
||||
cfg.maxVertsPerPoly = 6.f;
|
||||
cfg.detailSampleDist = 6.f;
|
||||
cfg.detailSampleMaxError = 1.f;
|
||||
cfg.tileSize = 160.f;
|
||||
|
||||
cfg.walkableHeight = (int)ceilf( cfg.walkableHeight / cfg.ch );
|
||||
cfg.walkableClimb = (int)floorf( cfg.walkableClimb / cfg.ch );
|
||||
cfg.walkableRadius = (int)ceilf( cfg.walkableRadius / cfg.cs );
|
||||
cfg.maxEdgeLen = (int)( cfg.maxEdgeLen / cfg.cs );
|
||||
cfg.minRegionArea = (int)rcSqr( cfg.minRegionArea ); // Note: area = size*size
|
||||
cfg.mergeRegionArea = (int)rcSqr( cfg.mergeRegionArea ); // Note: area = size*size
|
||||
cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding.
|
||||
cfg.width = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.height = cfg.tileSize + cfg.borderSize*2;
|
||||
cfg.detailSampleDist = cfg.detailSampleDist < 0.9f ? 0 : cfg.cs * cfg.detailSampleDist;
|
||||
cfg.detailSampleMaxError = cfg.ch * cfg.detailSampleMaxError;
|
||||
|
||||
rcContext ctx;
|
||||
auto hf = rcAllocHeightfield();
|
||||
auto chf = rcAllocCompactHeightfield();
|
||||
auto cs = rcAllocContourSet();
|
||||
auto pmesh = rcAllocPolyMesh();
|
||||
auto pdetailmesh = rcAllocPolyMeshDetail();
|
||||
|
||||
std::vector< float > verts;
|
||||
std::vector< int > indices;
|
||||
|
||||
int i = 0;
|
||||
int numIndices = 0;
|
||||
for( const auto& model : group.models )
|
||||
{
|
||||
for( const auto& mesh : model.second.meshes )
|
||||
{
|
||||
auto size = mesh.verts.size();
|
||||
rcCalcBounds( mesh.verts.data(), size / 3, &cfg.bmin[0], &cfg.bmax[0] );
|
||||
verts.reserve( verts.size() + size );
|
||||
memcpy( &verts[i], mesh.verts.data(), size );
|
||||
i += size;
|
||||
|
||||
size = mesh.indices.size();
|
||||
indices.reserve( indices.size() + size );
|
||||
for( auto j = 0; j < mesh.indices.size(); j += 3 )
|
||||
{
|
||||
indices[j] = mesh.indices[j] + numIndices;
|
||||
indices[j + 1] = mesh.indices[j + 1] + numIndices;
|
||||
indices[j + 2] = mesh.indices[j + 2] + numIndices;
|
||||
}
|
||||
numIndices += size;
|
||||
}
|
||||
}
|
||||
|
||||
auto chunkyMesh = new rcChunkyTriMesh;
|
||||
rcCreateChunkyTriMesh( &verts[0], &indices[0], verts.size() / 3, 256, chunkyMesh );
|
||||
if( !rcCreateHeightfield( &ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) )
|
||||
{
|
||||
|
||||
}
|
||||
float tbmin[2], tbmax[2];
|
||||
tbmin[0] = cfg.bmin[0];
|
||||
tbmin[1] = cfg.bmin[2];
|
||||
tbmax[0] = cfg.bmax[0];
|
||||
tbmax[1] = 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;
|
||||
|
||||
auto tileTriCount = 0;
|
||||
auto triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
|
||||
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;
|
||||
|
||||
tileTriCount += nctris;
|
||||
|
||||
memset(triareas, 0, nctris*sizeof(unsigned char));
|
||||
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle,
|
||||
&verts[0], verts.size() / 3, ctris, nctris, triareas);
|
||||
|
||||
if (!rcRasterizeTriangles(&ctx, &verts[0], verts.size() / 3, ctris, triareas, nctris, *hf, cfg.walkableClimb))
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
delete [] triareas;
|
||||
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.
|
||||
|
||||
rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
|
||||
|
||||
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
|
||||
rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
|
||||
|
||||
// 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.
|
||||
chf = rcAllocCompactHeightfield();
|
||||
if (!chf)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
rcFreeHeightField(hf);
|
||||
hf = 0;
|
||||
}
|
||||
|
||||
// Erode the walkable area by agent radius.
|
||||
if (!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 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(&ctx, *chf))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build distance field.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Partition the walkable surface into simple regions without holes.
|
||||
if (!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
{
|
||||
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(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
|
||||
// {
|
||||
// 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(&ctx, *chf, cfg.borderSize, cfg.minRegionArea))
|
||||
// {
|
||||
// ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
// Create contours.
|
||||
cs = rcAllocContourSet();
|
||||
if (!cs)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cs))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not create contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cs->nconts == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build polygon navmesh from the contours.
|
||||
pmesh = rcAllocPolyMesh();
|
||||
if (!pmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'.");
|
||||
return 0;
|
||||
}
|
||||
if (!rcBuildPolyMesh(&ctx, *cs, cfg.maxVertsPerPoly, *pmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Build detail mesh.
|
||||
pdetailmesh = rcAllocPolyMeshDetail();
|
||||
if (!pdetailmesh)
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!rcBuildPolyMeshDetail(&ctx, *pmesh, *chf,
|
||||
cfg.detailSampleDist, cfg.detailSampleMaxError,
|
||||
*pdetailmesh))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
rcFreeCompactHeightfield(chf);
|
||||
chf = 0;
|
||||
rcFreeContourSet(cs);
|
||||
cs = 0;
|
||||
}
|
||||
|
||||
unsigned char* navData = 0;
|
||||
int navDataSize = 0;
|
||||
if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
if (pmesh->nverts >= 0xffff)
|
||||
{
|
||||
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
|
||||
ctx.log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", pmesh->nverts, 0xffff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < pmesh->npolys; ++i)
|
||||
{
|
||||
//pmesh->flags[i] = sampleAreaToFlags(pmesh->areas[i]);
|
||||
}
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = pmesh->verts;
|
||||
params.vertCount = pmesh->nverts;
|
||||
params.polys = pmesh->polys;
|
||||
params.polyAreas = pmesh->areas;
|
||||
params.polyFlags = pmesh->flags;
|
||||
params.polyCount = pmesh->npolys;
|
||||
params.nvp = pmesh->nvp;
|
||||
params.detailMeshes = pdetailmesh->meshes;
|
||||
params.detailVerts = pdetailmesh->verts;
|
||||
params.detailVertsCount = pdetailmesh->nverts;
|
||||
params.detailTris = pdetailmesh->tris;
|
||||
params.detailTriCount = pdetailmesh->ntris;
|
||||
params.offMeshConVerts = 0;
|
||||
params.offMeshConRad = 0;
|
||||
params.offMeshConDir = 0;
|
||||
params.offMeshConAreas = 0;
|
||||
params.offMeshConFlags = 0;
|
||||
params.offMeshConUserID = 0;
|
||||
params.offMeshConCount = 0;
|
||||
params.walkableHeight = cfg.walkableHeight;
|
||||
params.walkableRadius = cfg.walkableRadius;
|
||||
params.walkableClimb = cfg.walkableClimb;
|
||||
params.tileX = 0;
|
||||
params.tileY = 0;
|
||||
params.tileLayer = 0;
|
||||
rcVcopy(params.bmin, pmesh->bmin);
|
||||
rcVcopy(params.bmax, pmesh->bmax);
|
||||
params.cs = cfg.cs;
|
||||
params.ch = cfg.ch;
|
||||
params.buildBvTree = true;
|
||||
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
ctx.log(RC_LOG_ERROR, "Could not build Detour navmesh.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
auto tileMemUsage = navDataSize/1024.0f;
|
||||
|
||||
ctx.stopTimer(RC_TIMER_TOTAL);
|
||||
|
||||
// Show performance stats.
|
||||
//duLogBuildTimes(*&ctx, ctx.getAccumulatedTime(RC_TIMER_TOTAL));
|
||||
ctx.log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", pmesh->nverts, pmesh->npolys);
|
||||
|
||||
auto tileBuildTime = ctx.getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f;
|
||||
|
||||
auto dataSize = navDataSize;
|
||||
return navData;
|
||||
|
||||
}
|
||||
//*/
|
||||
};
|
||||
#endif // !OBJ_EXPORTER_H
|
||||
|
|
|
@ -51,7 +51,7 @@ public:
|
|||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||
fileName.c_str(),
|
||||
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public:
|
|||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
printf( "[Obj] Finished exporting %s in %lu ms\n",
|
||||
fileName.c_str(),
|
||||
fileName.substr( fileName.find( "pcb_export" ) - 1 ).c_str(),
|
||||
std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() );
|
||||
}
|
||||
private:
|
||||
|
|
Loading…
Add table
Reference in a new issue