{"record":{"id":"8b0679e7a347bc3b","repo":"BabylonJS/Babylon.js","slug":"at-least-one-mesh-is-needed-to-create-the-nav-mesh","errorCode":null,"errorMessage":"At least one mesh is needed to create the nav mesh.","messagePattern":"At least one mesh is needed to create the nav mesh\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/dev/addons/src/navigation/generator/generator.single-thread.ts","lineNumber":27,"sourceCode":"import { GetRecast } from \"../factory/common\";\n\n/**\n * Builds a NavMesh and NavMeshQuery from meshes using provided parameters.\n * @param meshes The array of meshes used to create the NavMesh.\n * @param parameters The parameters used to configure the NavMesh generation.\n * @returns An object containing the NavMesh and NavMeshQuery.\n * @remarks This function generates a NavMesh based on the provided meshes and parameters.\n * It supports different configurations such as solo, tiled, and tile cache nav meshes.\n * If you need obstacles, ensure that `maxObstacles` is set to a value greater than 0.\n * Recommended values for `tileSize` are between 32 and 64 when using obstacles/tile cache.\n * If you need a tiled nav mesh, ensure that `tileSize` is set to a value greater than 0.\n * @throws Error if the NavMesh data is invalid or cannot be deserialized.\n */\nexport function GenerateNavMesh(meshes: Array<Mesh>, parameters: INavMeshParametersV2) {\n    const recast = GetRecast();\n\n    if (meshes.length === 0) {\n        throw new Error(\"At least one mesh is needed to create the nav mesh.\");\n    }\n\n    const [positions, indices] = GetPositionsAndIndices(meshes, { doNotReverseIndices: parameters.doNotReverseIndices });\n    if (!positions || !indices) {\n        throw new Error(\"Unable to get nav mesh. No vertices or indices.\");\n    }\n\n    // Decide on the type of nav mesh to generate based on parameters\n    // If tileSize is set, we will generate a tiled nav mesh\n    // If maxObstacles is set, we will generate a tile cache nav mesh\n    // Otherwise, we will generate a solo nav mesh\n    // Note: tileSize is only used for tiled nav meshes, not tile cache nav meshes\n    // If both tileSize and maxObstacles are set, we will generate a tile cache\n    const tileSize = parameters.tileSize ?? 0;\n    const needsTileCache = (parameters.maxObstacles ?? 0) > 0;\n    const needsTiledNavMesh = tileSize > 0;\n    if (needsTileCache) {\n        if (tileSize < 32 || tileSize > 64) {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/addons/src/navigation/generator/generator.single-thread.ts#L9-L45","documentation":"GenerateNavMesh() is the synchronous nav mesh builder in the Babylon.js navigation addon (generator.single-thread.ts:27). Before doing any work it validates that the caller supplied at least one Babylon.js Mesh, because the nav mesh is generated from the geometry of the meshes passed in. An empty meshes array means there is no geometry to rasterize into a Recast nav mesh, so the function throws immediately rather than returning an empty or invalid nav mesh.","triggerScenarios":"Calling GenerateNavMesh([], parameters) directly, or via a navigation plugin path that forwards an empty mesh array — e.g. collecting meshes with a predicate/filter (scene.getMeshesByTags, pickable/enabled filters) that matches nothing, awaiting mesh loading that failed so the array stays empty, or constructing the array conditionally and pushing nothing.","commonSituations":"Tag-based or predicate-based mesh selection returning zero results; scene meshes filtered out because they are disabled/not pickable; async mesh load races where generation runs before meshes are added; refactored code passing a filtered copy of the array instead of the original; calling the plugin before any level geometry exists.","solutions":["Pass at least one mesh with geometry: const plugin = await CreateNavigationPluginAsync(); plugin.createNavMesh([ground, walls], parameters)","Check the array before calling: if (meshes.length === 0) throw/return; e.g. verify your scene query actually matched (console.log(meshes.length))","Verify mesh collection logic — tag names, predicates, enabled/pickable filters, and that async loads resolved before generation","If geometry is meant to come from the scene, gather it explicitly: const meshes = scene.meshes.filter(m => m.isEnabled() && m.geometry)"],"exampleFix":"// before\nconst meshes = scene.getMeshesByTags(\"nav\"); // matches nothing\nplugin.createNavMesh(meshes, parameters); // throws\n\n// after\nconst meshes = scene.getMeshesByTags(\"nav\");\nif (meshes.length === 0) {\n    meshes.push(ground); // or fix the tag assignment on your meshes\n}\nplugin.createNavMesh(meshes, parameters);","handlingStrategy":"validation","validationCode":"const meshes = scene.meshes.filter(m => m.isEnabled() && m.geometry);\nif (meshes.length === 0) {\n    throw new Error(\"createNavMesh called with no meshes\");\n}\nplugin.createNavMesh(meshes, parameters);","typeGuard":"function hasMeshes(meshes: unknown): meshes is Array<Mesh> {\n    return Array.isArray(meshes) && meshes.length > 0;\n}","tryCatchPattern":"try {\n    const result = GenerateNavMesh(meshes, parameters);\n} catch (e) {\n    if (e instanceof Error && e.message.includes(\"At least one mesh is needed\")) {\n        // fix mesh collection; skip or retry with a fallback mesh\n    }\n}","preventionTips":["Always assert meshes.length > 0 before calling createNavMesh","Log the mesh-collection query result during development to catch filters that match nothing","Generate the nav mesh only after scene.whenReadyAsync() resolves","Never pass a copy/filter of the mesh array without checking it first"],"tags":["navigation","recast","navmesh","validation","babylonjs"],"backgroundTag":"empty-mesh-list-navmesh","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}