A bake turns geometry into navmesh tiles. The world is cut into square tiles of tileCells cells of cellSize meters, counted from the def's origin (by default 128 cells of 0.25 m, so 32 m tiles), and each tile bakes on its own.
mnavBakeDef def = mnavDefaultBakeDef(); // agent, cells, limits
mnavBaker* baker = NULL;
mnavBakeDefResult made = mnavCreateBaker(&def, &baker);
// made.result; on a refused def, made.setting names the bad setting.
mnavTriangleMesh mesh = {vertices, vertexCount, indices, triangleCount, NULL};
mnavBakeInput input = {&mesh, 1, NULL, 0, NULL, 0, NULL}; // meshes, terrains, volumes, index
mnavBakeReport report;
size_t size = 0;
mnavResult baked = mnavBaker_BakeTile(baker, &input, tileX, tileZ, &report);
if (baked == mnav_success)
{
baked = mnavBaker_CopyTile(baker, bytes, capacity, &size); // the tile's bytes
}
The bytes are the tile: store them as cooked content, and load them into a navmesh at run time. The same def and input give the same bytes on every platform.
agent (radius, height, step height, steepest slope) decides what is walkable. A navmesh serves one agent profile; agents of very different sizes need a navmesh each.cellSize and cellHeight set the resolution. Smaller cells follow walls more closely and cost more memory and time. mnavValidateBakeDef checks a def without making anything and reports what its meters become in cells (mnavBakeCells), so a tool can show the effect of a setting.origin, a world position in doubles. A world much more than 1,000 km across at 0.25 m cells needs more than one origin.limits bounds the work of one bake: triangles, spans, polygons, vertices and memory. A limit reached is mnav_errorCapacity, with the report's stage saying where. A navmesh has a def and limits of its own; see the navmesh.mnavTriangleMesh): vertices, three indices per triangle, and an optional area type per triangle. Area 0 (mnav_areaNone) is not walkable and blocks what lies under it; 1 to 63 are walkable kinds that query filters cost and include. Without areas, every triangle is mnav_areaWalkable.mnavTerrain): heights on a regular grid with an optional area per cell; a cell of mnav_areaNone is a hole. A terrain is the compact form of the largest input and bakes to the same tile as the same triangles given as a mesh.mnavBakeVolume): a prism on a ring of points with a height range. An exclude volume drops the ground inside, and agents then keep their radius from it as from a wall. When a bake has include volumes, only the ground inside them is kept. An area volume gives the walkable ground inside its area type without shrinking it.mnavOutline): closed rings of points with an area, for top-down games. mnavBaker_BakeTile2D fills the cells a walkable outline holds as a flat floor at height 0; an outline of mnav_areaNone cuts an obstruction. The result is an ordinary tile that every query serves.mnavBaker_BakeTile takes meshes, terrains and volumes together in an mnavBakeInput, and mnavBaker_BakeTile2D takes outlines in an mnavBake2DInput.
Input is hostile: a NaN coordinate, an index out of range or an area past 63 is mnav_errorInvalid, a point past the extent, an infinity included, is mnav_errorLimit, and the report's input names the first input refused, by its kind (mnav_inputMesh, mnav_inputTerrain, mnav_inputVolume, mnav_inputOutline or mnav_inputObstacle) and its index in that kind's array, with the vertex, triangle, point, sample or cell refused. mnavValidateBakeInput and mnavValidateBake2DInput run the same checks on a whole input without baking, for an import step that wants to catch bad geometry early; input they accept, every bake with the def accepts at its input stage. A bake may still refuse it later, for a tile past the extent, a triangle cut into a piece of more than MNAV_MAX_PIECE_CORNERS corners, or a named limit or the memory limit reached; the return values of mnavBaker_BakeTile and mnavBaker_BakeTile2D list these. Degenerate and duplicate triangles are accepted and contribute nothing.
mnavCreateTileIndex) and pass it as mnavBakeInput.index: each bake then reads only the triangles near its tile and makes the same bytes. An index is never changed after it is made, so workers share one. After editing a mesh, make a new index: a bake refuses an index whose counts no longer match, but a triangle moved into a tile it was not listed for is missed. Outlines have their own index, mnavCreateTileIndex2D, passed in the mnavBake2DInput of mnavBaker_BakeTile2D.fingerprint, also written into the tile, hashes the bake's generation (MNAV_BAKE_GENERATOR, which rises only when a bake of the same input would differ), the def's settings, the tile's place and every input triangle that reaches it. A host can skip rebaking a tile whose fingerprint has not changed.mnavBakeReport gives the stage a bake ended in, the tile's counts and size, the memory peak, and the work it gave up on rather than fail: holes no bridge reached, triangulations cut short (partialRingCount, usually from a maxEdgeError of a cell or more, whose walls can cross), detail samples with no height, polygons whose detail reached MNAV_MAX_DETAIL_VERTICES (cappedDetailCount), edges sampled more widely than detailSampleDistance (coarseEdgeCount: an edge takes at most MNAV_MAX_DETAIL_EDGE_SAMPLES samples, each on a whole sixteenth of a cell, and edges between polygons or along a tile's side are not shortened by maxEdgeLength), and walkable islands smaller than minRegionArea dropped (regionsDropped), which usually point at geometry the host did not mean to be walkable. With a clock in the def (mnavClock), it also gives each stage's ticks. The clock never shapes the tile, and may call nothing of the baker: such a call is refused with mnav_errorBusy.
A crate or a barricade that stands still changes the tiles it stands on. Bake those tiles through a tile cache, which keeps each tile's open space as it stands before bake volumes apply, and then rebuild a tile with obstacles, given as bake volumes, without its triangles:
mnavTileCacheDef cacheDef = mnavDefaultTileCacheDef(); // 4096 tiles in 64 MiB
mnavTileCache* cache = NULL;
mnavResult cached = mnavCreateTileCache(&cacheDef, &cache);
mnavBakeInput input = {&mesh, 1, NULL, 0, NULL, 0, NULL};
if (cached == mnav_success)
{
cached = mnavBaker_BakeTileCached(baker, cache, &input, tileX, tileZ, &report);
}
// A crate on the tile: a ring on the ground and a height range.
const mnavVec2 ring[4] = {{36, 4}, {38, 4}, {38, 6}, {36, 6}};
const mnavBakeVolume crate = {ring, 4, -1.0f, 2.0f, mnav_volumeExclude, 0};
mnavResult rebuilt = mnavBaker_RebuildTile(baker, cache, tileX, tileZ, &crate, 1, &report);
mnavBaker_BakeTile bakes from the same input with the obstacles added to its volumes, in about a third of the time. Copy it out with mnavBaker_CopyTile and replace the tile in a navmesh of mnav_tierDynamic.mnavTileCache_DropTile as they stream out; mnavTileCache_GetBytes gives what the cache holds. The cache lives in memory only.