Navmesh cell size

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.

1 Like