Its now called inputGeometryProvider.
InputGeomProvider geom = new SimpleInputGeomProvider(vertexPositions, indices);
Here is solo,
private void soloNavMeshBuilder() {
Mesh mesh = new Mesh();
GeometryBatchFactory.mergeGeometries(findGeometries(app.getRootNode(),
new LinkedList<>()), mesh);
List<Float> vertexPositions = getVertices(mesh);
List<Integer> indices = getIndices(mesh);
InputGeomProvider geomProvider = new SimpleInputGeomProvider(vertexPositions, indices);
long time1 = System.nanoTime();
float[] bmin = geomProvider.getMeshBoundsMin();
float[] bmax = geomProvider.getMeshBoundsMax();
// float[] verts = geomProvider.getVerts();
// int[] tris = geomProvider.getTris();
// int ntris = tris.length / 3;
// Step 1. Initialize build config.
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize,
m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
m_tileSize, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax);
Context m_ctx = new Context();
// Step 2. Rasterize input polygon soup.
// Allocate voxel heightfield where we rasterize our input data to.
Heightfield m_solid = new Heightfield(bcfg.width, bcfg.height, bcfg.bmin,
bcfg.bmax, cfg.cs, cfg.ch);
for (TriMesh geom : geomProvider.meshes()) {
float[] verts = geom.getVerts();
int[] tris = geom.getTris();
int ntris = tris.length / 3;
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
int[] m_triareas = Recast.markWalkableTriangles(m_ctx, cfg.walkableSlopeAngle, verts, tris, ntris, cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(m_ctx, verts, tris, m_triareas, ntris, m_solid, cfg.walkableClimb);
}
// Step 3. Filter walkables surfaces.
// 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.
RecastFilter.filterLowHangingWalkableObstacles(m_ctx, cfg.walkableClimb, m_solid);
RecastFilter.filterLedgeSpans(m_ctx, cfg.walkableHeight, cfg.walkableClimb, m_solid);
RecastFilter.filterWalkableLowHeightSpans(m_ctx, cfg.walkableHeight, m_solid);
// Step 4. Partition walkable surface to simple regions.
// 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.
CompactHeightfield m_chf = Recast.buildCompactHeightfield(m_ctx,cfg.walkableHeight, cfg.walkableClimb, m_solid);
// Erode the walkable area by agent radius.
RecastArea.erodeWalkableArea(m_ctx, cfg.walkableRadius, m_chf);
// (Optional) Mark areas.
/*
* ConvexVolume vols = m_geom->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 == PartitionType.WATERSHED) {
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
RecastRegion.buildDistanceField(m_ctx, m_chf);
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildRegions(m_ctx, m_chf, m_borderSize, cfg.minRegionArea, cfg.mergeRegionArea);
} else if (m_partitionType == PartitionType.MONOTONE) {
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RecastRegion.buildRegionsMonotone(m_ctx, m_chf, m_borderSize, cfg.minRegionArea, cfg.mergeRegionArea);
} else {
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildLayerRegions(m_ctx, m_chf, m_borderSize, cfg.minRegionArea);
}
// Step 5. Trace and simplify region contours.
// Create contours.
// ContourSet m_cset = RecastContour.buildContours(m_ctx, m_chf, cfg.maxSimplificationError, cfg.maxEdgeLen, RecastConstants.RC_CONTOUR_TESS_WALL_EDGES);
ContourSet m_cset = RecastContour.buildContours(m_ctx, m_chf, cfg.maxSimplificationError, cfg.maxEdgeLen, 0x01);
// Step 6. Build polygons mesh from contours.
// Build polygon navmesh from the contours.
PolyMesh m_pmesh = RecastMesh.buildPolyMesh(m_ctx, m_cset, cfg.maxVertsPerPoly);
// Step 7. Create detail mesh which allows to access approximate height
// on each polygon.
PolyMeshDetail m_dmesh = RecastMeshDetail.buildPolyMeshDetail(m_ctx,
m_pmesh, m_chf, cfg.detailSampleDist, cfg.detailSampleMaxError);
long time2 = System.nanoTime() - time1;
System.out.println(exportFilename + " : " + m_partitionType + " "
+ TimeUnit.SECONDS.convert(time2, TimeUnit.NANOSECONDS) + " s");
exportObj(exportFilename.substring(0, exportFilename.lastIndexOf('.'))
+ "_debug.obj", m_dmesh);
//must set flags for navigation controls to work
for (int i = 0; i < m_pmesh.npolys; ++i) {
m_pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
}
// NavMeshDataCreateParams params = new NavMeshDataCreateParams();
NavMeshDataCreateParams params = new NavMeshDataParameters();
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.walkableHeight = m_agentHeight;
params.walkableRadius = m_agentRadius;
params.walkableClimb = m_agentMaxClimb;
params.bmin = m_pmesh.bmin;
params.bmax = m_pmesh.bmax;
params.cs = m_cellSize;
params.ch = m_cellHeight;
params.buildBvTree = true;
MeshData meshData = NavMeshBuilder.createNavMeshData(params);
navMesh = new NavMesh(meshData, params.nvp, 0);
//create object to save solo NavMesh parameters
MeshParameters meshParams = new MeshParameters();
meshParams.addMeshDataParameters((NavMeshDataParameters) params);
//save NavMesh parameters as .j3o
saveNavMesh(meshParams, exportFilename.substring(0, exportFilename.lastIndexOf('.'))
+ "_solo_C" + m_tileSize);
//display NavMesh.obj
// showDebugMesh("Scenes/Recast/recastmesh_debug.obj", ColorRGBA.Green);
//save as obj no normals
// saveObj(exportFilename.substring(0, exportFilename.lastIndexOf('.')) + "_solo_" + m_partitionType + "_C" + m_tileSize + "_detail.obj", m_dmesh);
saveObj(exportFilename.substring(0, exportFilename.lastIndexOf('.')) + "_solo_" + m_partitionType + "_C" + m_tileSize + ".obj", m_pmesh);
//save as obj with normals
exportObj(exportFilename.substring(0, exportFilename.lastIndexOf('.')) + "_solo_" + m_partitionType + "_C" + m_tileSize + "_detail.obj", m_dmesh);
}
Here is tiled.
private void tiledNavMeshBuilder() {
Mesh mesh = new Mesh();
GeometryBatchFactory.mergeGeometries(findGeometries(app.getRootNode(),
new LinkedList<>()), mesh);
List<Float> vertexPositions = getVertices(mesh);
List<Integer> indices = getIndices(mesh);
InputGeomProvider geom = new SimpleInputGeomProvider(vertexPositions, indices);
long time1 = System.nanoTime();
// Initialize build config.
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize,
m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
m_tileSize, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
// Build all tiles
// RecastBuilder builder = new RecastBuilder();
time3 = System.nanoTime();
//uses listener param (this)
RecastBuilder builder = new RecastBuilder(this);
//use listener when calling buildTiles
RecastBuilderResult[][] rcResult = builder.buildTiles(geom, cfg, 1);
long time2 = System.nanoTime() - time1;
System.out.println(exportFilename + " : " + m_partitionType + " "
+ TimeUnit.SECONDS.convert(time2, TimeUnit.NANOSECONDS) + " s");
// Add tiles to nav mesh
int tw = rcResult.length;
int th = rcResult[0].length;
// Create empty nav mesh
// NavMeshParams navMeshParams = new NavMeshParams();
NavMeshParams navMeshParams = new NavMeshParameters();
copy(navMeshParams.orig, geom.getMeshBoundsMin());
navMeshParams.tileWidth = m_tileSize * m_cellSize;
navMeshParams.tileHeight = m_tileSize * m_cellSize;
navMeshParams.maxTiles = tw * th;
navMeshParams.maxPolys = 32768;
//Create object to save NavMeshParameters for tiled NavMesh
MeshParameters meshParams = new MeshParameters();
meshParams.addMeshParameters((NavMeshParameters) navMeshParams);
navMesh = new NavMesh(navMeshParams, 6);
List<PolyMeshDetail> dmeshList = new ArrayList<>();
for (int y = 0; y < th; y++) {
for (int x = 0; x < tw; x++) {
PolyMesh pmesh = rcResult[x][y].getMesh();
if (pmesh.npolys == 0) {
continue;
}
for (int i = 0; i < pmesh.npolys; ++i) {
pmesh.flags[i] = 1;
}
// NavMeshDataCreateParams params = new NavMeshDataCreateParams();
NavMeshDataCreateParams params = new NavMeshDataParameters();
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;
PolyMeshDetail dmesh = rcResult[x][y].getMeshDetail();
dmeshList.add(dmesh);
params.detailMeshes = dmesh.meshes;
params.detailVerts = dmesh.verts;
params.detailVertsCount = dmesh.nverts;
params.detailTris = dmesh.tris;
params.detailTriCount = dmesh.ntris;
params.walkableHeight = m_agentHeight;
params.walkableRadius = m_agentRadius;
params.walkableClimb = m_agentMaxClimb;
params.bmin = pmesh.bmin;
params.bmax = pmesh.bmax;
params.cs = m_cellSize;
params.ch = m_cellHeight;
params.tileX = x;
params.tileY = y;
params.buildBvTree = true;
navMesh.addTile(NavMeshBuilder.createNavMeshData(params), 0, 0);
//Add parameters to the
meshParams.addMeshDataParameters((NavMeshDataParameters) params);
}
}
//save NavMesh parameters as .j3o
saveNavMesh(meshParams, exportFilename.substring(0, exportFilename.lastIndexOf('.'))
+ "_tiled_C" + m_tileSize);
//save NavMesh as .obj
exportObj(exportFilename.substring(0, exportFilename.lastIndexOf('.'))
+ "_tiled_C" + m_tileSize + ".obj", dmeshList);
//display NavMesh.obj
// showDebugMesh("Scenes/Recast/recastmesh_tiled64_debug.obj", ColorRGBA.Green);
}
With tiled there is now an option to use a interface to setup a listener so you can know how many min, hours or days it would take to build the Navmesh. I say days because I pushed four 512x512 areas through with millions of vertices for testing. You don’t have to use a listener though.
Here is my listener that goes with the tiledNavMeshBuilder.
long time3;
long elapsedTime;
long elapsedTimeHr;
long elapsedTimeMin;
long elapsedTimeSec;
long buildTime;
long avBuildTime;
long buildTimeNano;
long estTotalTime;
long totalTimeHr;
long totalTimeMin;
long totalTimeSec;
long estTimeRemain;
long timeRemainHr;
long timeRemainMin;
long timeRemainSec;
long convert;
String minSec;
/**
* Listenr for result times to complete one tile in RecastBuilder.
* @param completed
* @param total
*/
@Override
public void onProgress(int completed, int total) {
buildTime = System.nanoTime() - time3;
elapsedTime += buildTime;
avBuildTime = elapsedTime/(long)completed;
estTotalTime = avBuildTime * (long)total;
estTimeRemain = estTotalTime - elapsedTime;
buildTimeNano = TimeUnit.MILLISECONDS.convert(avBuildTime, TimeUnit.NANOSECONDS);
System.out.printf("Completed %d[%d] Average [%dms] ", completed, total, buildTimeNano);
elapsedTimeHr = TimeUnit.HOURS.convert(elapsedTime, TimeUnit.NANOSECONDS) % 24;
elapsedTimeMin = TimeUnit.MINUTES.convert(elapsedTime, TimeUnit.NANOSECONDS) % 60;
elapsedTimeSec = TimeUnit.SECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS) % 60;
System.out.printf("Elapsed Time [%02d:%02d:%02d] ", elapsedTimeHr, elapsedTimeMin, elapsedTimeSec);
totalTimeHr = TimeUnit.HOURS.convert(estTotalTime, TimeUnit.NANOSECONDS) % 24;
totalTimeMin = TimeUnit.MINUTES.convert(estTotalTime, TimeUnit.NANOSECONDS) % 60;
totalTimeSec = TimeUnit.SECONDS.convert(estTotalTime, TimeUnit.NANOSECONDS) % 60;
System.out.printf("Estimated Total [%02d:%02d:%02d] ", totalTimeHr, totalTimeMin, totalTimeSec);
timeRemainHr = TimeUnit.HOURS.convert(estTimeRemain, TimeUnit.NANOSECONDS) % 24;
timeRemainMin = TimeUnit.MINUTES.convert(estTimeRemain, TimeUnit.NANOSECONDS) % 60;
timeRemainSec = TimeUnit.SECONDS.convert(estTimeRemain, TimeUnit.NANOSECONDS) % 60;
System.out.printf("Remaining Time [%02d:%02d:%02d]%n", timeRemainHr, timeRemainMin, timeRemainSec);
//reset time
time3 = System.nanoTime();
}
I also completely integrated recast4j to jme so you will need to change these two lines in the tiled builder.
// NavMeshParams navMeshParams = new NavMeshParams();
NavMeshParams navMeshParams = new NavMeshParameters();
// NavMeshDataCreateParams params = new NavMeshDataCreateParams();
NavMeshDataCreateParams params = new NavMeshDataParameters();
or this in solo,
// NavMeshDataCreateParams params = new NavMeshDataCreateParams();
NavMeshDataCreateParams params = new NavMeshDataParameters();
If I missed something let me know.