Recursive AI

Inspect navigation-mesh build stages in C#

OneScope is a free, open-source viewer for simulation recordings, with a desktop window or command-line control, from Recursive AI. MIT or Apache-2.0.

OnePath’s navigation-mesh build can be viewed at each of its seven stages, so you can look at the partial result.

OnePath is Recursive AI’s pathfinding and navigation-mesh library; OnePath.Scope plugs it into OneScope.

Terminal
dotnet add package RecursiveAI.OneScope.Sdk
dotnet add package RecursiveAI.OnePath.Scope

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

A recording has a time axis for moving between steps; this build also has a separate stage axis for stepping through the work inside a step.

Stage numbers are 1: voxels, 2: filtered spans, 3: compact spans, 4: regions, 5: contours, 6: polygons and 7: detail. Seek("stage", 2) selects filtered spans; position 0 returns to the time view.

Run the snippet from the unzipped example folder: it needs the included floor.opbi. This input is a flat 10 by 10 square made of two triangles; the full program shows all its vertices and indices.

C#
using OneScope.Sdk;
using RecursiveAI.OnePath.Scope;
 
var source = new OnePathModule().Open(
    File.ReadAllBytes("floor.opbi"), "floor.opbi");
var history = new History(source);
history.Seek("stage", 2);
var entities = new List<EntityInfo>();
history.View.CollectEntities(entities);
var stage = entities.Single(e => e.Name == "2. Filtered spans");
var properties = new PropertyList();
if (!history.View.Inspect(stage.Id, properties))
    throw new InvalidOperationException("Stage not found.");
Console.WriteLine(stage.Name);
Console.WriteLine(properties.Rows.Single(
    p => p.Name == "summary").Value.GetText());
Text
2. Filtered spans
1156 spans, 1024 walkable, after the low-obstacle, ledge and low-height filters; grid 34 x 34 x 12

Unzip the example, then dotnet run.

The full program

The example creates the input file, discovers its axes, reads each selected stage’s summary from History.View, and checks that the time state stays unchanged.

C#
using System.Buffers;
using OneScope.Sdk;
using RecursiveAI.OnePath.Scope;
 
// A flat 10 by 10 square, two upward-facing triangles.
var bytes = new ArrayBufferWriter<byte>();
BuildInput.Write([0,0,0, 10,0,0, 10,0,10, 0,0,10],
    [0,2,1, 0,3,2], null, bytes);
File.WriteAllBytes("floor.opbi", bytes.WrittenSpan);
var source = new OnePathModule().Open(bytes.WrittenMemory, "floor.opbi");
var history = new History(source);
long time = history.CurrentStep;
ulong hash = history.StateHash;
foreach (AxisInfo axis in source.Info.Axes)
{
    Console.WriteLine($"Axis: {axis.Id}, positions: {axis.PositionCount}");
    if (axis.Id == AxisIds.Time) continue;
    foreach (int position in new[] { 1, axis.PositionCount, 2, 0 })
    {
        history.Seek(axis.Id, position);
        bool unchanged = history.CurrentStep == time && history.StateHash == hash;
        if (!unchanged) throw new InvalidOperationException("Time state changed.");
        Console.WriteLine($"View: {history.ViewAxis}/{history.ViewPosition}, time unchanged: {unchanged}");
        if (position == 0) continue;
        var entities = new List<EntityInfo>();
        history.View.CollectEntities(entities);
        var stage = entities.Single(e => e.Kind == "stage" &&
            e.Name.StartsWith($"{position}. ", StringComparison.Ordinal));
        var properties = new PropertyList();
        if (!history.View.Inspect(stage.Id, properties))
            throw new InvalidOperationException("Stage not found.");
        Console.WriteLine(stage.Name);
        Console.WriteLine(properties.Rows.Single(
            p => p.Name == "summary").Value.GetText());
    }
}
if (source.Info.Axes.All(axis => axis.Id == AxisIds.Time))
    Console.WriteLine("This source has only time; no intermediate views.");

dotnet run prints:

Text
Axis: time, positions: 0
Axis: stage, positions: 7
View: stage/1, time unchanged: True
1. Voxels
1156 spans, 1156 walkable, after rasterizing; grid 34 x 34 x 12
View: stage/7, time unchanged: True
7. Detail
2 detail triangles, 0 extra vertices
View: stage/2, time unchanged: True
2. Filtered spans
1156 spans, 1024 walkable, after the low-obstacle, ledge and low-height filters; grid 34 x 34 x 12
View: time/0, time unchanged: True

An unknown axis throws ArgumentException; an out-of-range position throws ArgumentOutOfRangeException, and a positive axis seek at the recording’s end throws InvalidOperationException.

The short answer

Use a source that declares a non-time axis, then call History.Seek(axisId, position). Read the intermediate state from History.View, not History.World. The time cursor and StateHash still describe the main replay state before the next step. The released OnePath module declares a stage axis for navigation-mesh build inputs. The full program discovers it through source.Info.Axes; sources that expose only time print a message instead.

How do you inspect navigation-mesh build stages?

A OnePath build input stores triangle coordinates, indices and build settings. The full program creates those bytes directly. Passing null settings selects NavSettings.Humanoid. No crowd replay or external geometry file is needed for this square.

The module declares seven stage positions: voxels, filtered spans, compact spans, regions, contours, polygons and detail. These are the module’s stage outputs, in that order, as documented in its published package. This build recording starts at time step 0 and ends at step 1. Leave the time cursor at 0 while selecting stages: after seeking time step 1, a positive stage seek throws InvalidOperationException because no next step remains to inspect.

Position 0 clears the intermediate view and returns to the time view. Positive positions run partway through the next step on a scratch world restored from the cursor’s snapshot. Read or draw history.View to inspect that scratch world. Reading StateHash alone will not tell you which build stage is visible: it deliberately remains the main replay world’s hash.

The full program visits stages 1, 7 and 2, then clears the view. It checks that both the main cursor and main hash remain unchanged after each visit. Returning to an earlier stage does not undo the recording or create a branch. The history contract says the same step and axis position give the same hash by any path.

For opening navigation recordings in the desktop window, see Visualize a navmesh and crowds.

All onescope recipes