Recursive AI

Visualize a navmesh and crowds in C#

OneScope is a free, open-source viewer for deterministic libraries from Recursive AI. MIT or Apache-2.0.

OnePath is Recursive AI’s navigation library. This recipe opens only its navigation formats, not arbitrary navmesh files.

Terminal
dotnet tool install --global RecursiveAI.OneScope.Tool

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

Unzip the example, then dotnet run.

This writes crowd.oprp; then open it:

Terminal
onescope open crowd.oprp

Opens the example’s crowd replay in a window. crowd.oprp contains an 8 × 8 walkable floor and one character walking from (1, 0, 1) toward (6, 0, 6).

Record the crowd in C#

The full program creates the floor and records 30 steps. The project pins RecursiveAI.OnePath 1.2.0 and RecursiveAI.OnePath.Scope 1.0.0. OneScope.Sdk comes from the OnePath.Scope dependency; OneMath comes from OnePath.

Terminal
dotnet add package RecursiveAI.OnePath
dotnet add package RecursiveAI.OnePath.Scope
C#
using RecursiveAI.OneMath;
using RecursiveAI.OnePath;
using RecursiveAI.OnePath.Replay;
using RecursiveAI.OnePath.Scope;
using OneScope.Sdk;
 
// An 8 by 8 floor; true means walkable. No external input files.
var cells = Enumerable.Repeat(true, 64).ToArray();
var mesh = NavMesh.Build2D(cells, 8, 8, 1,
    new Vector2D(0, 0), NavSettings.Humanoid with { AgentRadius = 0 });
var recorder = new CrowdRecorder(mesh.CreateCrowd(), 1.0 / 30, keepSteps: true);
var agent = recorder.Add(new Vector3D(1, 0, 1), Agent.Small);
recorder.SetDestination(agent, new Vector3D(6, 0, 6));
for (int i = 0; i < 30; i++) recorder.Step();
File.WriteAllBytes("crowd.oprp", recorder.ToReplay().ToBytes());
Console.WriteLine("Wrote crowd.oprp (30 steps)");
 
var source = new OnePathModule().Open(File.ReadAllBytes("crowd.oprp"), "crowd.oprp");
var history = new History(source);
history.Seek(30);
Console.WriteLine($"Replayed step: {history.CurrentStep}");
Console.WriteLine($"Hashes match: {history.StateHash == recorder.Crowd.StateHash()}");
Text
Wrote crowd.oprp (30 steps)
Replayed step: 30
Hashes match: True

Run onescope open crowd.oprp from that folder after the program finishes. A file the module cannot read fails to open; the shell returns an error rather than treating arbitrary bytes as a navigation mesh.

Variations

Inspect the middle of the recording without opening a window:

Terminal
onescope open crowd.oprp --headless   --run 'seek {"step":15}'
JSON
{"step":15,"start":0,"steps":30,"sources":[{"source":0,"step":15,"hash":"251cab3c4833f883"}]}

Turn on path drawing explicitly when opening the replay:

Terminal
onescope open crowd.oprp --headless   --run 'overlay {"overlay":"Paths","on":true}'

List the crowd’s characters at the final step:

Terminal
onescope open crowd.oprp --headless --run 'seek {"step":30}' --run 'entities {"kind":"agent","limit":1}'
JSON
{"source":0,"step":30,"entities":[{"id":4611686018427387904,"parent":13835058055282163713,"kind":"agent","name":"agent 0"}],"count":1,"truncated":false}

The short answer

Record a OnePath crowd with CrowdRecorder, write the result of ToReplay().ToBytes() to an .oprp file, then open that file with onescope open. The OnePath module is bundled with OneScope, so this format does not need a custom --module argument. The viewer shows the navigation mesh and the crowd state together rather than showing a mesh with no connection to a recorded run.

The C# example builds a flat, fully walkable floor so the input is easy to change. The 64 Boolean cells are the geometry input, not a screenshot or an imported asset. The program adds a character through the recorder and sets its destination through the recorder too. That records the commands needed to reconstruct the movement, rather than merely collecting positions at presentation frames.

How do I visualize a navmesh in C#?

Choose the file that matches what you want to inspect. An .oprp crowd replay is useful when a character takes an unexpected route, stops, or approaches a target differently than expected. It holds the mesh together with the crowd recording. Opening this example reports a two-dimensional source because its mesh is flat; a non-flat crowd mesh opens as three-dimensional.

A navigation build input (.opbi) answers a different question: what happened while the navigation mesh was built? Its stage axis exposes voxels, filtered spans, compact spans, regions, contours, polygons, and detail. These are positions inside the build, not elapsed simulation time. The bundled-module documentation lists the file types and the stage sequence.

A saved mesh is not interchangeable with a crowd replay or build input just because all three concern navigation. Use the library’s recorder or module writer for the input the viewer expects. Renaming another file’s extension does not add the missing recording or build geometry.

For the crowd example, start by leaving the navigation mesh and paths visible. The default overlay declarations also include targets. Velocities and character IDs are separate toggles, useful when several moving characters overlap. Each overlay controls a group of drawing items; it does not change the underlying simulation or the pathfinding result.

How do I inspect a navmesh character’s path?

Seek to a specific step, list entities of kind agent, and inspect the returned entity ID. Use the IDs returned by the viewer rather than assuming that your application’s array index is the viewer’s entity ID. The shell accepts IDs as decimal strings as well as JSON integers, which matters for clients whose numeric type cannot represent every unsigned 64-bit integer.

Paths and targets are distinct observations. A destination is an order given to the character; a path is the route that the module draws for that state. Looking at both at the same recorded step avoids confusing an older order with a newer route. The sample replays to step 30 and compares the viewer’s world hash with the crowd’s hash at that step.

To test a different order, create a fork and use a declared crowd poke such as set_target, stop_agent, or teleport_agent. The shell’s operation reference describes the arguments. A fork preserves the original recording, so you can compare the altered trajectory with the run you first opened. For grid searches rather than crowds, .opgr inputs expose an iteration axis for the search slices; that is a different view from an individual crowd character’s path.

All onescope recipes