navcat-zup 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2574 -0
  3. package/dist/blocks/agents/crowd.d.ts +244 -0
  4. package/dist/blocks/agents/index.d.ts +4 -0
  5. package/dist/blocks/agents/local-boundary.d.ts +42 -0
  6. package/dist/blocks/agents/obstacle-avoidance.d.ts +93 -0
  7. package/dist/blocks/agents/path-corridor.d.ts +64 -0
  8. package/dist/blocks/generators/generate-solo-nav-mesh.d.ts +39 -0
  9. package/dist/blocks/generators/generate-tiled-nav-mesh.d.ts +45 -0
  10. package/dist/blocks/generators/index.d.ts +2 -0
  11. package/dist/blocks/geometry/chunky-tri-mesh.d.ts +62 -0
  12. package/dist/blocks/geometry/index.d.ts +2 -0
  13. package/dist/blocks/geometry/merge-positions-and-indices.d.ts +4 -0
  14. package/dist/blocks/index.d.ts +7 -0
  15. package/dist/blocks/search/flood-fill-nav-mesh.d.ts +5 -0
  16. package/dist/blocks/search/index.d.ts +1 -0
  17. package/dist/blocks.js +2695 -0
  18. package/dist/blocks.js.map +1 -0
  19. package/dist/index.js +12644 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/src/debug.d.ts +65 -0
  22. package/dist/src/generate/build-context.d.ts +26 -0
  23. package/dist/src/generate/common.d.ts +31 -0
  24. package/dist/src/generate/compact-heightfield-regions.d.ts +17 -0
  25. package/dist/src/generate/compact-heightfield.d.ts +109 -0
  26. package/dist/src/generate/contour-set.d.ts +42 -0
  27. package/dist/src/generate/heightfield.d.ts +43 -0
  28. package/dist/src/generate/index.d.ts +10 -0
  29. package/dist/src/generate/input-triangle-mesh.d.ts +20 -0
  30. package/dist/src/generate/nav-mesh-tile.d.ts +39 -0
  31. package/dist/src/generate/poly-mesh-detail.d.ts +23 -0
  32. package/dist/src/generate/poly-mesh.d.ts +39 -0
  33. package/dist/src/generate/poly-neighbours.d.ts +8 -0
  34. package/dist/src/geometry.d.ts +97 -0
  35. package/dist/src/index-pool.d.ts +7 -0
  36. package/dist/src/index.d.ts +8 -0
  37. package/dist/src/query/bv-tree.d.ts +7 -0
  38. package/dist/src/query/find-path.d.ts +64 -0
  39. package/dist/src/query/find-smooth-path.d.ts +78 -0
  40. package/dist/src/query/find-straight-path.d.ts +47 -0
  41. package/dist/src/query/index.d.ts +8 -0
  42. package/dist/src/query/local-neighbourhood.d.ts +54 -0
  43. package/dist/src/query/nav-mesh-api.d.ts +222 -0
  44. package/dist/src/query/nav-mesh-search.d.ts +269 -0
  45. package/dist/src/query/nav-mesh.d.ts +198 -0
  46. package/dist/src/query/node.d.ts +26 -0
  47. package/dist/three/debug.d.ts +28 -0
  48. package/dist/three/get-positions-and-indices.d.ts +2 -0
  49. package/dist/three/index.d.ts +5 -0
  50. package/dist/three.js +296 -0
  51. package/dist/three.js.map +1 -0
  52. package/package.json +67 -0
package/README.md ADDED
@@ -0,0 +1,2574 @@
1
+ ![./docs/cover.png](./docs/cover.png)
2
+
3
+ ```bash
4
+ > npm install navcat-zup
5
+ ```
6
+
7
+ # navcat-zup
8
+
9
+ navcat-zup is a **Z-up** fork of [navcat](https://github.com/isaac-mason/navcat), a javascript navigation mesh construction and querying library for 3D floor-based navigation.
10
+
11
+ Everything in this fork uses a right-handed, Z-up coordinate system: the ground plane is XY and +Z is up. This matches the conventions of Blender, CAD tools, and many simulation engines. If you want the standard Y-up conventions (matching three.js defaults), use the original [navcat](https://github.com/isaac-mason/navcat) instead.
12
+
13
+ navcat is ideal for use in games, simulations, and creative websites that require navigation in complex 3D environments.
14
+
15
+ **Features**
16
+
17
+ - Navigation mesh generation from 3D geometry
18
+ - Navigation mesh querying
19
+ - Single and multi-tile navigation mesh support
20
+ - Fully JSON serializable data structures
21
+ - Pure javascript, written to be highly tree-shakeable
22
+ - Works with any javascript engine/library - Babylon.js, PlayCanvas, Three.js, or your own engine
23
+
24
+ **Documentation**
25
+
26
+ This README provides curated explanations, guides, and examples to help you get started with navcat.
27
+
28
+ API documentation can be found at [navcat.dev/docs](https://navcat.dev/docs).
29
+
30
+ **Installation**
31
+
32
+ navcat-zup is available on npm:
33
+
34
+ ```bash
35
+ npm install navcat-zup
36
+ ```
37
+
38
+ An example of using navcat without any build tools using unpkg can be found here: https://github.com/isaac-mason/navcat-vanilla-html-js-example
39
+
40
+ **Changelog**
41
+
42
+ See the [CHANGELOG.md](./CHANGELOG.md) for a detailed list of changes in each version.
43
+
44
+ **Examples**
45
+
46
+ <table>
47
+ <tr>
48
+ <td align="center">
49
+ <a href="https://navcat.dev/examples#example-crowd-simulation">
50
+ <img src="./examples/public/screenshots/example-crowd-simulation.png" width="180" height="120" style="object-fit:cover;"/><br/>
51
+ Crowd Simulation
52
+ </a>
53
+ </td>
54
+ <td align="center">
55
+ <a href="https://navcat.dev/examples#example-navmesh-constrained-character-controller">
56
+ <img src="./examples/public/screenshots/example-navmesh-constrained-character-controller.png" width="180" height="120" style="object-fit:cover;"/><br/>
57
+ NavMesh Constrained Character Controller
58
+ </a>
59
+ </td>
60
+ <td align="center">
61
+ <a href="https://navcat.dev/examples#example-area-filters">
62
+ <img src="./examples/public/screenshots/example-area-filters.png" width="180" height="120" style="object-fit:cover;"/><br/>
63
+ Area Filters
64
+ </a>
65
+ </td>
66
+ </tr>
67
+ <tr>
68
+ <td align="center">
69
+ <a href="https://navcat.dev/examples#example-area-costs">
70
+ <img src="./examples/public/screenshots/example-area-costs.png" width="180" height="120" style="object-fit:cover;"/><br/>
71
+ Area Costs
72
+ </a>
73
+ </td>
74
+ <td align="center">
75
+ <a href="https://navcat.dev/examples#example-doors-and-keys">
76
+ <img src="./examples/public/screenshots/example-doors-and-keys.png" width="180" height="120" style="object-fit:cover;"/><br/>
77
+ Doors and Keys
78
+ </a>
79
+ </td>
80
+ <td align="center">
81
+ <a href="https://navcat.dev/examples#example-dynamic-obstacles">
82
+ <img src="./examples/public/screenshots/example-dynamic-obstacles.png" width="180" height="120" style="object-fit:cover;"/><br/>
83
+ Dynamic Obstacles
84
+ </a>
85
+ </td>
86
+ </tr>
87
+ <tr>
88
+ <td align="center">
89
+ <a href="https://navcat.dev/examples#example-dynamic-navmesh">
90
+ <img src="./examples/public/screenshots/example-dynamic-navmesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
91
+ Dynamic NavMesh
92
+ </a>
93
+ </td>
94
+ <td align="center">
95
+ <a href="https://navcat.dev/examples#example-fps-dynamic-navmesh">
96
+ <img src="./examples/public/screenshots/example-fps-dynamic-navmesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
97
+ FPS Dynamic NavMesh
98
+ </a>
99
+ </td>
100
+ <td align="center">
101
+ <a href="https://navcat.dev/examples#example-multiple-agent-sizes">
102
+ <img src="./examples/public/screenshots/example-multiple-agent-sizes.png" width="180" height="120" style="object-fit:cover;"/><br/>
103
+ Multiple Agent Sizes
104
+ </a>
105
+ </td>
106
+ </tr>
107
+ <tr>
108
+ <td align="center">
109
+ <a href="https://navcat.dev/examples#example-crowd-simulation-stress-test">
110
+ <img src="./examples/public/screenshots/example-crowd-simulation-stress-test.png" width="180" height="120" style="object-fit:cover;"/><br/>
111
+ Crowd Simulation Stress Test
112
+ </a>
113
+ </td>
114
+ <td align="center">
115
+ <a href="https://navcat.dev/examples#example-flow-field-pathfinding">
116
+ <img src="./examples/public/screenshots/example-flow-field-pathfinding.png" width="180" height="120" style="object-fit:cover;"/><br/>
117
+ Flow Field Pathfinding
118
+ </a>
119
+ </td>
120
+ <td align="center">
121
+ <a href="https://navcat.dev/examples#example-find-path">
122
+ <img src="./examples/public/screenshots/example-find-path.png" width="180" height="120" style="object-fit:cover;"/><br/>
123
+ Find Path
124
+ </a>
125
+ </td>
126
+ </tr>
127
+ <tr>
128
+ <td align="center">
129
+ <a href="https://navcat.dev/examples#example-find-node-path">
130
+ <img src="./examples/public/screenshots/example-find-node-path.png" width="180" height="120" style="object-fit:cover;"/><br/>
131
+ Find Node Path
132
+ </a>
133
+ </td>
134
+ <td align="center">
135
+ <a href="https://navcat.dev/examples#example-find-straight-path">
136
+ <img src="./examples/public/screenshots/example-find-straight-path.png" width="180" height="120" style="object-fit:cover;"/><br/>
137
+ Find Straight Path
138
+ </a>
139
+ </td>
140
+ <td align="center">
141
+ <a href="https://navcat.dev/examples#example-find-smooth-path">
142
+ <img src="./examples/public/screenshots/example-find-smooth-path.png" width="180" height="120" style="object-fit:cover;"/><br/>
143
+ Find Smooth Path
144
+ </a>
145
+ </td>
146
+ </tr>
147
+ <tr>
148
+ <td align="center">
149
+ <a href="https://navcat.dev/examples#example-find-shortest-paths">
150
+ <img src="./examples/public/screenshots/example-find-shortest-paths.png" width="180" height="120" style="object-fit:cover;"/><br/>
151
+ Find Shortest Paths
152
+ </a>
153
+ </td>
154
+ <td align="center">
155
+ <a href="https://navcat.dev/examples#example-find-diverse-paths">
156
+ <img src="./examples/public/screenshots/example-find-diverse-paths.png" width="180" height="120" style="object-fit:cover;"/><br/>
157
+ Find Diverse Paths
158
+ </a>
159
+ </td>
160
+ <td align="center">
161
+ <a href="https://navcat.dev/examples#example-raycast">
162
+ <img src="./examples/public/screenshots/example-raycast.png" width="180" height="120" style="object-fit:cover;"/><br/>
163
+ Raycast
164
+ </a>
165
+ </td>
166
+ </tr>
167
+ <tr>
168
+ <td align="center">
169
+ <a href="https://navcat.dev/examples#example-off-mesh-connections">
170
+ <img src="./examples/public/screenshots/example-off-mesh-connections.png" width="180" height="120" style="object-fit:cover;"/><br/>
171
+ Off-Mesh Connections
172
+ </a>
173
+ </td>
174
+ <td align="center">
175
+ <a href="https://navcat.dev/examples#example-upload-model">
176
+ <img src="./examples/public/screenshots/example-upload-model.png" width="180" height="120" style="object-fit:cover;"/><br/>
177
+ Upload Model
178
+ </a>
179
+ </td>
180
+ <td align="center">
181
+ <a href="https://navcat.dev/examples#example-custom-gltf-navmesh">
182
+ <img src="./examples/public/screenshots/example-custom-gltf-navmesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
183
+ Custom GLTF NavMesh
184
+ </a>
185
+ </td>
186
+ </tr>
187
+ <tr>
188
+ <td align="center">
189
+ <a href="https://navcat.dev/examples#example-solo-navmesh">
190
+ <img src="./examples/public/screenshots/example-solo-navmesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
191
+ Solo NavMesh
192
+ </a>
193
+ </td>
194
+ <td align="center">
195
+ <a href="https://navcat.dev/examples#example-tiled-navmesh">
196
+ <img src="./examples/public/screenshots/example-tiled-navmesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
197
+ Tiled NavMesh
198
+ </a>
199
+ </td>
200
+ <td align="center">
201
+ <a href="https://navcat.dev/examples#example-flood-fill-pruning">
202
+ <img src="./examples/public/screenshots/example-flood-fill-pruning.png" width="180" height="120" style="object-fit:cover;"/><br/>
203
+ Flood Fill Pruning
204
+ </a>
205
+ </td>
206
+ </tr>
207
+ <tr>
208
+ <td align="center">
209
+ <a href="https://navcat.dev/examples#example-find-nearest-poly">
210
+ <img src="./examples/public/screenshots/example-find-nearest-poly.png" width="180" height="120" style="object-fit:cover;"/><br/>
211
+ Find Nearest Poly
212
+ </a>
213
+ </td>
214
+ <td align="center">
215
+ <a href="https://navcat.dev/examples#example-find-random-point">
216
+ <img src="./examples/public/screenshots/example-find-random-point.png" width="180" height="120" style="object-fit:cover;"/><br/>
217
+ Find Random Point
218
+ </a>
219
+ </td>
220
+ <td align="center">
221
+ <a href="https://navcat.dev/examples#example-find-random-point-around-circle">
222
+ <img src="./examples/public/screenshots/example-find-random-point-around-circle.png" width="180" height="120" style="object-fit:cover;"/><br/>
223
+ Find Random Point Around Circle
224
+ </a>
225
+ </td>
226
+ </tr>
227
+ <tr>
228
+ <td align="center">
229
+ <a href="https://navcat.dev/examples#example-mark-compact-heightfield-areas">
230
+ <img src="./examples/public/screenshots/example-mark-compact-heightfield-areas.png" width="180" height="120" style="object-fit:cover;"/><br/>
231
+ Mark Compact Heightfield Areas
232
+ </a>
233
+ </td>
234
+ <td align="center">
235
+ <a href="https://navcat.dev/examples#example-custom-navmesh-generation">
236
+ <img src="./examples/public/screenshots/example-custom-navmesh-generation.png" width="180" height="120" style="object-fit:cover;"/><br/>
237
+ Custom NavMesh Generation
238
+ </a>
239
+ </td>
240
+ <td align="center">
241
+ <a href="https://navcat.dev/examples#example-move-along-surface">
242
+ <img src="./examples/public/screenshots/example-move-along-surface.png" width="180" height="120" style="object-fit:cover;"/><br/>
243
+ Move Along Surface
244
+ </a>
245
+ </td>
246
+ </tr>
247
+ <tr>
248
+ <td align="center">
249
+ <a href="https://navcat.dev/examples#example-chunky-tri-mesh">
250
+ <img src="./examples/public/screenshots/example-chunky-tri-mesh.png" width="180" height="120" style="object-fit:cover;"/><br/>
251
+ Chunky Tri Mesh
252
+ </a>
253
+ </td>
254
+ <td align="center">
255
+ <a href="https://navcat.dev/examples#example-rasterize-filled-volume">
256
+ <img src="./examples/public/screenshots/example-rasterize-filled-volume.png" width="180" height="120" style="object-fit:cover;"/><br/>
257
+ Rasterize Filled Volume
258
+ </a>
259
+ </td>
260
+ </tr>
261
+ </table>
262
+
263
+
264
+ ## Table of Contents
265
+
266
+ - [What is a Navigation Mesh?](#what-is-a-navigation-mesh)
267
+ - [Can navcat be integrated with my engine/library?](#can-navcat-be-integrated-with-my-enginelibrary)
268
+ - [Quick Start / Minimal Example](#quick-start-minimal-example)
269
+ - [Navigation Mesh Querying](#navigation-mesh-querying)
270
+ - [`findPath`](#findpath)
271
+ - [`findSmoothPath`](#findsmoothpath)
272
+ - [`findNodePath`](#findnodepath)
273
+ - [`findStraightPath`](#findstraightpath)
274
+ - [`moveAlongSurface`](#movealongsurface)
275
+ - [`raycast` & `raycastWithCosts`](#raycast-raycastwithcosts)
276
+ - [`findNearestPoly`](#findnearestpoly)
277
+ - [`findRandomPoint`](#findrandompoint)
278
+ - [`findRandomPointAroundCircle`](#findrandompointaroundcircle)
279
+ - [Crowd Simulation](#crowd-simulation)
280
+ - [Custom Pathfinding Algorithms](#custom-pathfinding-algorithms)
281
+ - [Navigation Mesh Generation](#navigation-mesh-generation)
282
+ - [Overview](#overview)
283
+ - [The Structure of a Navigation Mesh](#the-structure-of-a-navigation-mesh)
284
+ - [Single-Tile vs Tiled Navigation Meshes](#single-tile-vs-tiled-navigation-meshes)
285
+ - [Generation Presets](#generation-presets)
286
+ - [Generation Process: Deep Dive](#generation-process-deep-dive)
287
+ - [0. Input and setup](#0-input-and-setup)
288
+ - [1. Mark walkable triangles](#1-mark-walkable-triangles)
289
+ - [2. Rasterize triangles into a heightfield, do filtering with the heightfield](#2-rasterize-triangles-into-a-heightfield-do-filtering-with-the-heightfield)
290
+ - [3. Build compact heightfield, erode walkable area, mark areas](#3-build-compact-heightfield-erode-walkable-area-mark-areas)
291
+ - [4. Build compact heightfield regions](#4-build-compact-heightfield-regions)
292
+ - [5. Build contours from compact heightfield regions](#5-build-contours-from-compact-heightfield-regions)
293
+ - [6. Build polygon mesh from contours, build detail mesh](#6-build-polygon-mesh-from-contours-build-detail-mesh)
294
+ - [7. Convert build-time poly mesh and poly mesh detail to runtime navmesh tile format](#7-convert-build-time-poly-mesh-and-poly-mesh-detail-to-runtime-navmesh-tile-format)
295
+ - [8. Assemble the navigation mesh](#8-assemble-the-navigation-mesh)
296
+ - [Post-Processing](#post-processing)
297
+ - [Custom Query Filters and Custom Area Types](#custom-query-filters-and-custom-area-types)
298
+ - [Off-Mesh Connections](#off-mesh-connections)
299
+ - [Advanced Navigation Mesh APIs](#advanced-navigation-mesh-apis)
300
+ - [`isValidNodeRef`](#isvalidnoderef)
301
+ - [`getNodeByRef`](#getnodebyref)
302
+ - [`getNodeByTileAndPoly`](#getnodebytileandpoly)
303
+ - [`getPolyHeight`](#getpolyheight)
304
+ - [`getClosestPointOnPoly`](#getclosestpointonpoly)
305
+ - [`getClosestPointOnDetailEdges`](#getclosestpointondetailedges)
306
+ - [`getPortalPoints`](#getportalpoints)
307
+ - [`queryPolygons`](#querypolygons)
308
+ - [`queryPolygonsInTile`](#querypolygonsintile)
309
+ - [Using Externally Created Navigation Meshes](#using-externally-created-navigation-meshes)
310
+ - [Saving and Loading NavMeshes](#saving-and-loading-navmeshes)
311
+ - [Debug Utilities](#debug-utilities)
312
+ - [`navcat-zup/three`](#navcat-zupthree)
313
+ - [How does navcat compare to other libraries?](#how-does-navcat-compare-to-other-libraries)
314
+ - [Community](#community)
315
+ - [Acknowledgements](#acknowledgements)
316
+
317
+ ## What is a Navigation Mesh?
318
+
319
+ A navigation mesh (or navmesh) is a simplified representation of a 3D environment that is used for pathfinding and AI navigation in video games and simulations. It consists of interconnected polygons that define walkable areas within the environment. These polygons are connected by edges and off-mesh connections, allowing agents (characters) to move from one polygon to another.
320
+
321
+ ![./docs/1-whats-a-navmesh](./docs/1-whats-a-navmesh.png)
322
+
323
+ ## Can navcat be integrated with my engine/library?
324
+
325
+ navcat is agnostic of rendering or game engine library, so it will work well with any javascript engine - Babylon.js, PlayCanvas, Three.js, or your own engine.
326
+
327
+ If you are using threejs, you may make use of the utilities in the `navcat-zup/three` entrypoint, see the [navcat-zup/three docs](#navcat-zupthree). Integrations for other engines may be added in future.
328
+
329
+ navcat-zup uses the following conventions:
330
+
331
+ - Right-handed coordinate system with **+Z up** (the ground plane is XY)
332
+ - Indices should be in counter-clockwise winding order (a floor triangle viewed from above / from +Z should wind counter-clockwise, giving a +Z normal)
333
+
334
+ If you are importing a navmesh created externally, note that navmesh poly vertices must be indexed / must share vertices between adjacent polygons.
335
+
336
+ If your environment uses a different coordinate system, you will need to transform coordinates going into and out of navcat.
337
+
338
+ The examples use threejs for rendering, but the core navcat APIs are completely agnostic of any rendering or game engine libraries.
339
+
340
+ ## Quick Start / Minimal Example
341
+
342
+ Below is a minimal example of using the presets in `navcat-zup/blocks` to generate a navigation mesh, and then using APIs in `navcat` to find a path on the generated navmesh.
343
+
344
+ For information on how to tune these options, and how the generation process works under the hood with images, see the [Navigation mesh generation](#navigation-mesh-generation) section below.
345
+
346
+ If you are using threejs, you can find [a threejs-specific version of this snippet in the navcat-zup/three section](#navcat-zupthree).
347
+
348
+ ```ts
349
+ import { DEFAULT_QUERY_FILTER, findPath, type Vec3 } from 'navcat-zup';
350
+ import { generateSoloNavMesh, type SoloNavMeshInput, type SoloNavMeshOptions } from 'navcat-zup/blocks';
351
+
352
+ /* generation input */
353
+ // populate positions and indices with your level geometry
354
+ // don't include geometry that shouldn't contribute to walkable-surface
355
+ // generation, like foliage or small decorative props
356
+ const positions = new Float32Array([
357
+ /* ... */
358
+ ]);
359
+ const indices = new Uint32Array([
360
+ /* ... */
361
+ ]);
362
+
363
+ const input: SoloNavMeshInput = {
364
+ positions,
365
+ indices,
366
+ };
367
+
368
+ /* generation options */
369
+ // the following are defaults you might start from for a human-sized agent in a 1 m scale world.
370
+ // it's generally recommended that you use the library debug helpers to visualize the navmesh
371
+ // generation and fine tune these parameters.
372
+
373
+ // heightfield parameters
374
+ const cellSize = 0.15;
375
+ const cellHeight = 0.25;
376
+
377
+ // agent parameters
378
+ const walkableRadiusWorld = 0.3;
379
+ const walkableHeightWorld = 2.0;
380
+ const walkableClimbWorld = 0.5;
381
+ const walkableSlopeAngleDegrees = 45;
382
+
383
+ const walkableRadiusVoxels = Math.ceil(walkableRadiusWorld / cellSize);
384
+ const walkableClimbVoxels = Math.ceil(walkableClimbWorld / cellHeight);
385
+ const walkableHeightVoxels = Math.ceil(walkableHeightWorld / cellHeight);
386
+
387
+ // compact heightfield region parameters
388
+ const minRegionArea = 8;
389
+ const mergeRegionArea = 20;
390
+
391
+ // polygon generation parameters
392
+ const maxSimplificationError = 1.3;
393
+ const maxEdgeLength = 12;
394
+ const maxVerticesPerPoly = 5;
395
+
396
+ // detail mesh generation parameters
397
+ const detailSampleDistanceVoxels = 6;
398
+ const detailSampleMaxErrorVoxels = 1;
399
+
400
+ const detailSampleDistance = detailSampleDistanceVoxels < 0.9 ? 0 : cellSize * detailSampleDistanceVoxels;
401
+ const detailSampleMaxError = cellHeight * detailSampleMaxErrorVoxels;
402
+
403
+ // border size around each tile, in voxels. 0 for solo navmesh.
404
+ const borderSize = 0;
405
+
406
+ const options: SoloNavMeshOptions = {
407
+ cellSize,
408
+ cellHeight,
409
+ walkableRadiusWorld,
410
+ walkableRadiusVoxels,
411
+ walkableClimbWorld,
412
+ walkableClimbVoxels,
413
+ walkableHeightWorld,
414
+ walkableHeightVoxels,
415
+ walkableSlopeAngleDegrees,
416
+ borderSize,
417
+ minRegionArea,
418
+ mergeRegionArea,
419
+ maxSimplificationError,
420
+ maxEdgeLength,
421
+ maxVerticesPerPoly,
422
+ detailSampleDistance,
423
+ detailSampleMaxError,
424
+ };
425
+
426
+ /* generate the navmesh */
427
+ const result = generateSoloNavMesh(input, options);
428
+
429
+ const navMesh = result.navMesh; // the nav mesh
430
+ const intermediates = result.intermediates; // intermediate data for debugging
431
+
432
+ console.log('generated navmesh:', navMesh, intermediates);
433
+
434
+ /* find a path */
435
+ const start: Vec3 = [-4, -4, 0];
436
+ const end: Vec3 = [4, 4, 0];
437
+ const halfExtents: Vec3 = [0.5, 0.5, 0.5];
438
+
439
+ const path = findPath(navMesh, start, end, halfExtents, DEFAULT_QUERY_FILTER);
440
+
441
+ console.log(
442
+ 'path:',
443
+ path.path.map((p) => p.position),
444
+ );
445
+ ```
446
+
447
+ Below is a quick summary of the navmesh generation parameters used above, and how to start tuning them:
448
+
449
+ | Parameter | Description | Range / Heuristic for 1 = 1m humanoid agents |
450
+ | --------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
451
+ | `cellSize` | Horizontal voxel size (XY). Smaller = finer detail, slower generation. | ≈ `walkableRadiusWorld / 3` |
452
+ | `cellHeight` | Vertical voxel size (Z). Controls height resolution. | ≈ `walkableClimbWorld / 2` |
453
+ | `walkableRadiusWorld` | Agent radius (half-width). Determines clearance around walls. | 0.2–0.5 m |
454
+ | `walkableHeightWorld` | Agent height. Areas with ceilings lower than this are excluded. | 1.6–2.0 m |
455
+ | `walkableSlopeAngleDegrees` | Max slope angle the agent can walk. This filters out input triangles at the very beginning of navmesh generation. | 35–50° |
456
+ | `walkableClimbWorld` | Max step height. Allows stepping up/down small edges. This filters at the heightfield navmesh generation stage. | 0.3–0.5 m |
457
+ | `minRegionArea` | Smallest isolated region kept. | 4–16 voxels |
458
+ | `mergeRegionArea` | Regions smaller than this merge into neighbors. | 8–32 voxels |
459
+ | `maxSimplificationError` | Edge simplification tolerance (higher = simpler mesh). | 1–2 |
460
+ | `maxEdgeLength` | Max polygon edge length before splitting. | 8–24 |
461
+ | `maxVerticesPerPoly` | Max vertices per polygon. | 3–6 |
462
+ | `detailSampleDistance` | Distance between height samples (affects vertical detail). | `cellSize * 4–8`, e.g. `0.9` |
463
+ | `detailSampleMaxError` | Allowed height deviation when simplifying detail mesh. | `cellHeight * 1–2`, e.g. `0.25` |
464
+
465
+ ## Navigation Mesh Querying
466
+
467
+ This section covers the main features you'll use for navigation mesh querying, including pathfinding, agent simulation, and spatial queries on your navigation mesh. For lower-level querying APIs and navmesh internals, see the [Advanced Navigation Mesh APIs](#advanced-navigation-mesh-apis) section.
468
+
469
+ ### `findPath`
470
+
471
+ The `findPath` function is a convenience wrapper around `findNearestPoly`, `findNodePath`, and `findStraightPath` to get a path between two points on the navigation mesh.
472
+
473
+ **When to use:** This is the simplest way to find a complete path. Use this for one-off pathfinding queries, or when you aren't steering agents along a path and re-querying frequently.
474
+
475
+ ```ts
476
+ const start: Nav.Vec3 = [1, 1, 0];
477
+ const end: Nav.Vec3 = [8, 8, 0];
478
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
479
+
480
+ // find a path from start to end
481
+ const findPathResult = Nav.findPath(navMesh, start, end, halfExtents, Nav.DEFAULT_QUERY_FILTER);
482
+
483
+ if (findPathResult.success) {
484
+ const points = findPathResult.path.map((p) => p.position);
485
+ console.log('path points:', points); // [ [x1, y1, z1], [x2, y2, z2], ... ]
486
+ }
487
+ ```
488
+
489
+ ```ts
490
+ /**
491
+ * Find a path between two positions on a NavMesh.
492
+ *
493
+ * If the end node cannot be reached through the navigation graph,
494
+ * the last node in the path will be the nearest the end node.
495
+ *
496
+ * Internally:
497
+ * - finds the closest poly for the start and end positions with @see findNearestPoly
498
+ * - finds a nav mesh node path with @see findNodePath
499
+ * - finds a straight path with @see findStraightPath
500
+ *
501
+ * If you want more fine tuned behaviour you can call these methods directly.
502
+ * For example, for agent movement you might want to find a node path once but regularly re-call @see findStraightPath
503
+ *
504
+ * @param navMesh The navigation mesh.
505
+ * @param start The starting position in world space.
506
+ * @param end The ending position in world space.
507
+ * @param queryFilter The query filter.
508
+ * @param options Optional configuration for the pathfinding operation.
509
+ * @returns The result of the pathfinding operation.
510
+ */
511
+ export function findPath(navMesh: NavMesh, start: Vec3, end: Vec3, halfExtents: Vec3, queryFilter: QueryFilter, options?: FindPathOptions): FindPathResult;
512
+ ```
513
+
514
+ **[`findPath` API Documentation →](https://navcat.dev/docs/functions/navcat.findPath.html)**
515
+
516
+ <table>
517
+ <tr>
518
+ <td align="center">
519
+ <a href="https://navcat.dev/examples#example-find-path">
520
+ <img src="./examples/public/screenshots/example-find-path.png" width="200" height="133" style="object-fit:cover;"/><br/>
521
+ <strong>Find Path</strong>
522
+ </a>
523
+ </td>
524
+ </tr>
525
+ </table>
526
+
527
+ ### `findSmoothPath`
528
+
529
+ Combines `findNodePath`, `findStraightPath`, and `moveAlongSurface` to produce a smooth path that respects the navmesh surface.
530
+
531
+ **When to use:** Use this when you want a smooth path that follows the navmesh surface without sharp corners, and you need it infrequently (e.g. for visual previews, not for many agents per frame).
532
+
533
+ ```ts
534
+ /**
535
+ * Find a smooth path between two positions on a NavMesh.
536
+ *
537
+ * This method computes a smooth path by iteratively moving along the navigation
538
+ * mesh surface using the polygon path found between start and end positions.
539
+ * The resulting path follows the surface more naturally than a straight path.
540
+ *
541
+ * If the end node cannot be reached through the navigation graph,
542
+ * the path will go as far as possible toward the target.
543
+ *
544
+ * Internally:
545
+ * - finds the closest poly for the start and end positions with @see findNearestPoly
546
+ * - finds a nav mesh node path with @see findNodePath
547
+ * - computes a smooth path by iteratively moving along the surface with @see moveAlongSurface
548
+ *
549
+ * @param navMesh The navigation mesh.
550
+ * @param start The starting position in world space.
551
+ * @param end The ending position in world space.
552
+ * @param halfExtents The half extents for nearest polygon queries.
553
+ * @param queryFilter The query filter.
554
+ * @param options Configuration for the smooth pathfinding operation.
555
+ * @returns The result of the smooth pathfinding operation, with path points containing position, type, and nodeRef information.
556
+ */
557
+ export function findSmoothPath(navMesh: NavMesh, start: Vec3, end: Vec3, halfExtents: Vec3, queryFilter: QueryFilter, options: FindSmoothPathOptions): FindSmoothPathResult;
558
+ ```
559
+
560
+ **[`findSmoothPath` API Documentation →](https://navcat.dev/docs/functions/navcat.findSmoothPath.html)**
561
+
562
+ <table>
563
+ <tr>
564
+ <td align="center">
565
+ <a href="https://navcat.dev/examples#example-find-smooth-path">
566
+ <img src="./examples/public/screenshots/example-find-smooth-path.png" width="200" height="133" style="object-fit:cover;"/><br/>
567
+ <strong>Find Smooth Path</strong>
568
+ </a>
569
+ </td>
570
+ </tr>
571
+ </table>
572
+
573
+ ### `findNodePath`
574
+
575
+ Finds a path through the navigation mesh as a sequence of polygon and offmesh connection node references.
576
+
577
+ **When to use:** Use this when you want to cache a node path and recalculate the straight path multiple times (e.g., for dynamic agent movement where the start position changes but the destination stays the same). This is more efficient than calling `findPath` repeatedly.
578
+
579
+ ```ts
580
+ const start: Nav.Vec3 = [1, 1, 0];
581
+ const end: Nav.Vec3 = [8, 8, 0];
582
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
583
+
584
+ // find the nearest nav mesh poly node to the start position
585
+ const startNode = Nav.findNearestPoly(
586
+ Nav.createFindNearestPolyResult(),
587
+ navMesh,
588
+ start,
589
+ halfExtents,
590
+ Nav.DEFAULT_QUERY_FILTER,
591
+ );
592
+
593
+ // find the nearest nav mesh poly node to the end position
594
+ const endNode = Nav.findNearestPoly(Nav.createFindNearestPolyResult(), navMesh, end, halfExtents, Nav.DEFAULT_QUERY_FILTER);
595
+
596
+ // find a "node" path from start to end
597
+ if (startNode.success && endNode.success) {
598
+ const nodePath = Nav.findNodePath(
599
+ navMesh,
600
+ startNode.nodeRef,
601
+ endNode.nodeRef,
602
+ startNode.position,
603
+ endNode.position,
604
+ Nav.DEFAULT_QUERY_FILTER,
605
+ );
606
+
607
+ console.log(nodePath.success); // true if a partial or full path was found
608
+ console.log(nodePath.path); // [0, 1, 2, ... ]
609
+ }
610
+ ```
611
+
612
+ ```ts
613
+ /**
614
+ * Find a path between two nodes.
615
+ *
616
+ * If the end node cannot be reached through the navigation graph,
617
+ * the last node in the path will be the nearest the end node.
618
+ *
619
+ * The start and end positions are used to calculate traversal costs.
620
+ * (The z-values impact the result.)
621
+ *
622
+ * @param startNodeRef The reference ID of the starting node.
623
+ * @param endNodeRef The reference ID of the ending node.
624
+ * @param startPosition The starting position in world space.
625
+ * @param endPosition The ending position in world space.
626
+ * @param filter The query filter.
627
+ * @param options Optional configuration for the pathfinding operation.
628
+ * @returns The result of the pathfinding operation.
629
+ */
630
+ export function findNodePath(navMesh: NavMesh, startNodeRef: NodeRef, endNodeRef: NodeRef, startPosition: Vec3, endPosition: Vec3, filter: QueryFilter, options?: FindNodePathOptions): FindNodePathResult;
631
+ ```
632
+
633
+ **[`findNodePath` API Documentation →](https://navcat.dev/docs/functions/navcat.findNodePath.html)**
634
+
635
+ ### `findStraightPath`
636
+
637
+ Performs "string pulling" to convert a sequence of nodes into a series of waypoints that form the actual path an agent should follow.
638
+
639
+ **When to use:** Call this after `findNodePath` to get the actual waypoint positions. You might recalculate this frequently while keeping the same node path, or when implementing custom path following behavior.
640
+
641
+ ```ts
642
+ const start: Nav.Vec3 = [1, 1, 0];
643
+ const end: Nav.Vec3 = [8, 8, 0];
644
+
645
+ // array of nav mesh node refs, often retrieved from a call to findNodePath
646
+ const findStraightPathNodes: Nav.NodeRef[] = [
647
+ /* ... */
648
+ ];
649
+
650
+ // find the nearest nav mesh poly node to the start position
651
+ const straightPathResult = Nav.findStraightPath(navMesh, start, end, findStraightPathNodes);
652
+
653
+ console.log(straightPathResult.success); // true if a partial or full path was found
654
+ console.log(straightPathResult.path); // [ { position: [x, y, z], nodeType: NodeType, nodeRef: NodeRef }, ... ]
655
+ ```
656
+
657
+ ```ts
658
+ /**
659
+ * This method peforms what is often called 'string pulling'.
660
+ *
661
+ * The start position is clamped to the first polygon node in the path, and the
662
+ * end position is clamped to the last. So the start and end positions should
663
+ * normally be within or very near the first and last polygons respectively.
664
+ *
665
+ * @param navMesh The navigation mesh to use for the search.
666
+ * @param start The start position in world space.
667
+ * @param end The end position in world space.
668
+ * @param pathNodeRefs The list of polygon node references that form the path, generally obtained from `findNodePath`
669
+ * @param maxPoints The maximum number of points to return in the straight path. If null, no limit is applied.
670
+ * @param straightPathOptions @see FindStraightPathOptions
671
+ * @returns The straight path
672
+ */
673
+ export function findStraightPath(navMesh: NavMesh, start: Vec3, end: Vec3, pathNodeRefs: NodeRef[], maxPoints: number | null = null, straightPathOptions = 0): FindStraightPathResult;
674
+ ```
675
+
676
+ **[`findStraightPath` API Documentation →](https://navcat.dev/docs/functions/navcat.findStraightPath.html)**
677
+
678
+ ### `moveAlongSurface`
679
+
680
+ Moves along a navmesh from a start position toward an end position along the navmesh surface, constrained to walkable areas.
681
+
682
+ This should be called with small movement deltas (e.g., per frame) to move an agent while respecting the navmesh boundaries.
683
+
684
+ **When to use:** Perfect for simple character controllers where you want to constrain movement to the navmesh without full pathfinding. Ideal for local movement, sliding along walls, or implementing custom movement logic that respects the navmesh.
685
+
686
+ ```ts
687
+ const start: Nav.Vec3 = [1, 1, 0];
688
+ const end: Nav.Vec3 = [8, 8, 0];
689
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
690
+
691
+ const startNode = Nav.findNearestPoly(
692
+ Nav.createFindNearestPolyResult(),
693
+ navMesh,
694
+ start,
695
+ halfExtents,
696
+ Nav.DEFAULT_QUERY_FILTER,
697
+ );
698
+
699
+ const moveAlongSurfaceResult = Nav.moveAlongSurface(navMesh, startNode.nodeRef, start, end, Nav.DEFAULT_QUERY_FILTER);
700
+
701
+ console.log(moveAlongSurfaceResult.success); // true if the move was successful
702
+ console.log(moveAlongSurfaceResult.position); // the resulting position after the move [x, y, z]
703
+ console.log(moveAlongSurfaceResult.nodeRef); // the resulting poly node ref after the move, or 0 if none
704
+ console.log(moveAlongSurfaceResult.visited); // array of node refs that were visited during the move
705
+ ```
706
+
707
+ ```ts
708
+ /**
709
+ * Moves from start position towards end position along the navigation mesh surface.
710
+ *
711
+ * This method is optimized for small delta movement and a small number of
712
+ * polygons. If used for too great a distance, the result set will form an
713
+ * incomplete path.
714
+ *
715
+ * The resultPosition will equal the endPosition if the end is reached.
716
+ * Otherwise the closest reachable position will be returned.
717
+ *
718
+ * The resulting position is projected onto the surface of the navigation mesh with @see getPolyHeight.
719
+ *
720
+ * @param navMesh The navigation mesh
721
+ * @param startNodeRef The reference ID of the starting polygon
722
+ * @param startPosition The starting position [(y, z, x)]
723
+ * @param endPosition The ending position [(y, z, x)]
724
+ * @param filter The query filter.
725
+ * @returns Result containing status, final position, and visited polygons
726
+ */
727
+ export function moveAlongSurface(navMesh: NavMesh, startNodeRef: NodeRef, startPosition: Vec3, endPosition: Vec3, filter: QueryFilter): MoveAlongSurfaceResult;
728
+ ```
729
+
730
+ **[`moveAlongSurface` API Documentation →](https://navcat.dev/docs/functions/navcat.moveAlongSurface.html)**
731
+
732
+ <table>
733
+ <tr>
734
+ <td align="center">
735
+ <a href="https://navcat.dev/examples#example-navmesh-constrained-character-controller">
736
+ <img src="./examples/public/screenshots/example-navmesh-constrained-character-controller.png" width="200" height="133" style="object-fit:cover;"/><br/>
737
+ <strong>NavMesh Constrained Character Controller</strong>
738
+ </a>
739
+ </td>
740
+ <td align="center">
741
+ <a href="https://navcat.dev/examples#example-move-along-surface">
742
+ <img src="./examples/public/screenshots/example-move-along-surface.png" width="200" height="133" style="object-fit:cover;"/><br/>
743
+ <strong>Move Along Surface</strong>
744
+ </a>
745
+ </td>
746
+ </tr>
747
+ </table>
748
+
749
+ ### `raycast` & `raycastWithCosts`
750
+
751
+ Casts a ray along the navmesh surface to check for walkability and detect obstacles.
752
+
753
+ **When to use:** Check line-of-sight between positions, validate if a straight path exists, or detect walls/obstacles. Avoid using this for long rays; it's best suited for short-range checks given its two dimensional nature.
754
+
755
+ ```ts
756
+ const start: Nav.Vec3 = [1, 1, 0];
757
+ const end: Nav.Vec3 = [8, 8, 0];
758
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
759
+
760
+ const startNode = Nav.findNearestPoly(
761
+ Nav.createFindNearestPolyResult(),
762
+ navMesh,
763
+ start,
764
+ halfExtents,
765
+ Nav.DEFAULT_QUERY_FILTER,
766
+ );
767
+
768
+ const raycastResult = Nav.raycast(navMesh, startNode.nodeRef, start, end, Nav.DEFAULT_QUERY_FILTER);
769
+
770
+ console.log(raycastResult.t); // the normalized distance along the ray where an obstruction was found, or 1.0 if none
771
+ console.log(raycastResult.hitNormal); // the normal of the obstruction hit, or [0, 0, 0] if none
772
+ console.log(raycastResult.hitEdgeIndex); // the index of the edge of the poly that was hit, or -1 if none
773
+ console.log(raycastResult.path); // array of node refs that were visited during the raycast
774
+ ```
775
+
776
+ ```ts
777
+ /**
778
+ * Casts a 'walkability' ray along the surface of the navigation mesh from
779
+ * the start position toward the end position.
780
+ *
781
+ * This method is meant to be used for quick, short distance checks.
782
+ * The raycast ignores the z-value of the end position (2D check).
783
+ *
784
+ * @param navMesh The navigation mesh to use for the raycast.
785
+ * @param startNodeRef The NodeRef for the start polygon
786
+ * @param startPosition The starting position in world space.
787
+ * @param endPosition The ending position in world space.
788
+ * @param filter The query filter to apply.
789
+ * @returns The raycast result with hit information and visited polygons (without cost calculation).
790
+ */
791
+ export function raycast(navMesh: NavMesh, startNodeRef: NodeRef, startPosition: Vec3, endPosition: Vec3, filter: QueryFilter): RaycastResult;
792
+ ```
793
+
794
+ ```ts
795
+ const start: Nav.Vec3 = [1, 1, 0];
796
+ const end: Nav.Vec3 = [8, 8, 0];
797
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
798
+
799
+ const startNode = Nav.findNearestPoly(
800
+ Nav.createFindNearestPolyResult(),
801
+ navMesh,
802
+ start,
803
+ halfExtents,
804
+ Nav.DEFAULT_QUERY_FILTER,
805
+ );
806
+
807
+ // raycastWithCosts calculates path costs and requires the previous polygon reference
808
+ const prevRef = 0; // example
809
+ const raycastResult = Nav.raycastWithCosts(navMesh, startNode.nodeRef, start, end, Nav.DEFAULT_QUERY_FILTER, prevRef);
810
+
811
+ console.log(raycastResult.t); // the normalized distance along the ray where an obstruction was found, or 1.0 if none
812
+ console.log(raycastResult.hitNormal); // the normal of the obstruction hit, or [0, 0, 0] if none
813
+ console.log(raycastResult.hitEdgeIndex); // the index of the edge of the poly that was hit, or -1 if none
814
+ console.log(raycastResult.path); // array of node refs that were visited during the raycast
815
+ console.log(raycastResult.pathCost); // accumulated cost along the raycast path
816
+ ```
817
+
818
+ ```ts
819
+ /**
820
+ * Casts a 'walkability' ray along the surface of the navigation mesh from
821
+ * the start position toward the end position, calculating accumulated path costs.
822
+ *
823
+ * This method is meant to be used for quick, short distance checks.
824
+ * The raycast ignores the z-value of the end position (2D check).
825
+ *
826
+ * @param navMesh The navigation mesh to use for the raycast.
827
+ * @param startNodeRef The NodeRef for the start polygon
828
+ * @param startPosition The starting position in world space.
829
+ * @param endPosition The ending position in world space.
830
+ * @param filter The query filter to apply.
831
+ * @param prevRef The reference to the polygon we came from (for accurate cost calculations).
832
+ * @returns The raycast result with hit information, visited polygons, and accumulated path costs.
833
+ */
834
+ export function raycastWithCosts(navMesh: NavMesh, startNodeRef: NodeRef, startPosition: Vec3, endPosition: Vec3, filter: QueryFilter, prevRef: NodeRef): RaycastResult;
835
+ ```
836
+
837
+ **[`raycast` API Documentation →](https://navcat.dev/docs/functions/navcat.raycast.html)**
838
+
839
+ **[`raycastWithCosts` API Documentation →](https://navcat.dev/docs/functions/navcat.raycastWithCosts.html)**
840
+
841
+ <table>
842
+ <tr>
843
+ <td align="center">
844
+ <a href="https://navcat.dev/examples#example-raycast">
845
+ <img src="./examples/public/screenshots/example-raycast.png" width="200" height="133" style="object-fit:cover;"/><br/>
846
+ <strong>Raycast</strong>
847
+ </a>
848
+ </td>
849
+ </tr>
850
+ </table>
851
+
852
+ ### `findNearestPoly`
853
+
854
+ Finds the nearest polygon on the navmesh to a given world position.
855
+
856
+ **When to use:** This is often your first step - use it to "snap" world positions onto the navmesh before pathfinding or querying. Essential when placing agents, checking if a position is on the navmesh, or converting world coordinates to navmesh coordinates.
857
+
858
+ ```ts
859
+ const position: Nav.Vec3 = [1, 1, 0];
860
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
861
+
862
+ // find the nearest nav mesh poly node to the position
863
+ const findNearestPolyResult = Nav.createFindNearestPolyResult();
864
+ Nav.findNearestPoly(findNearestPolyResult, navMesh, position, halfExtents, Nav.DEFAULT_QUERY_FILTER);
865
+
866
+ console.log(findNearestPolyResult.success); // true if a nearest poly was found
867
+ console.log(findNearestPolyResult.nodeRef); // the nearest poly's node ref, or 0 if none found
868
+ console.log(findNearestPolyResult.position); // the nearest point on the poly in world space [x, y, z]
869
+ ```
870
+
871
+ ```ts
872
+ export function findNearestPoly(result: FindNearestPolyResult, navMesh: NavMesh, center: Vec3, halfExtents: Vec3, queryFilter: QueryFilter): FindNearestPolyResult;
873
+ ```
874
+
875
+ **[`findNearestPoly` API Documentation →](https://navcat.dev/docs/functions/navcat.findNearestPoly.html)**
876
+
877
+ <table>
878
+ <tr>
879
+ <td align="center">
880
+ <a href="https://navcat.dev/examples#example-find-nearest-poly">
881
+ <img src="./examples/public/screenshots/example-find-nearest-poly.png" width="200" height="133" style="object-fit:cover;"/><br/>
882
+ <strong>Find Nearest Poly</strong>
883
+ </a>
884
+ </td>
885
+ </tr>
886
+ </table>
887
+
888
+ ### `findRandomPoint`
889
+
890
+ Finds a random walkable point anywhere on the navmesh.
891
+
892
+ **When to use:** Spawn points, random patrol destinations, procedural NPC placement, or testing. Great for open-world scenarios where agents need random destinations across the entire navigable area.
893
+
894
+ ```ts
895
+ const randomPoint = Nav.findRandomPoint(navMesh, Nav.DEFAULT_QUERY_FILTER, Math.random);
896
+
897
+ console.log(randomPoint.success); // true if a random point was found
898
+ console.log(randomPoint.position); // [x, y, z]
899
+ console.log(randomPoint.nodeRef); // the poly node ref that the random point is on
900
+ ```
901
+
902
+ ```ts
903
+ /**
904
+ * Finds a random point on the navigation mesh.
905
+ * This function is using reservoir sampling and can fail in rare occurences even if there is an actual solution.
906
+ *
907
+ * @param navMesh The navigation mesh
908
+ * @param filter Query filter to apply to polygons
909
+ * @param rand Function that returns random values between [0,1]
910
+ * @returns The result object with success flag, random point, and polygon reference
911
+ */
912
+ export function findRandomPoint(navMesh: NavMesh, filter: QueryFilter, rand: () => number): FindRandomPointResult;
913
+ ```
914
+
915
+ **[`findRandomPoint` API Documentation →](https://navcat.dev/docs/functions/navcat.findRandomPoint.html)**
916
+
917
+ <table>
918
+ <tr>
919
+ <td align="center">
920
+ <a href="https://navcat.dev/examples#example-find-random-point">
921
+ <img src="./examples/public/screenshots/example-find-random-point.png" width="200" height="133" style="object-fit:cover;"/><br/>
922
+ <strong>Find Random Point</strong>
923
+ </a>
924
+ </td>
925
+ </tr>
926
+ </table>
927
+
928
+ ### `findRandomPointAroundCircle`
929
+
930
+ Finds a random walkable point within a circular radius around a center position.
931
+
932
+ **When to use:** Local randomization like scatter formations, patrol areas around a point, or finding nearby positions. Perfect for "move near target" AI behaviors or creating natural-looking patrol patterns.
933
+
934
+ ```ts
935
+ const center: Nav.Vec3 = [5, 5, 0];
936
+ const radius = 3.0; // world units
937
+
938
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
939
+
940
+ const centerNode = Nav.findNearestPoly(
941
+ Nav.createFindNearestPolyResult(),
942
+ navMesh,
943
+ center,
944
+ halfExtents,
945
+ Nav.DEFAULT_QUERY_FILTER,
946
+ );
947
+
948
+ if (centerNode.success) {
949
+ const randomPointAroundCircle = Nav.findRandomPointAroundCircle(
950
+ navMesh,
951
+ centerNode.nodeRef,
952
+ center,
953
+ radius,
954
+ Nav.DEFAULT_QUERY_FILTER,
955
+ Math.random,
956
+ );
957
+
958
+ console.log(randomPointAroundCircle.success); // true if a random point was found
959
+ console.log(randomPointAroundCircle.position); // [x, y, z]
960
+ console.log(randomPointAroundCircle.nodeRef); // the poly node ref that the random point is on
961
+ }
962
+ ```
963
+
964
+ ```ts
965
+ /**
966
+ * Finds a random point around a center position on the navigation mesh.
967
+ * The location is not exactly constrained by the circle, but it limits the visited polygons.
968
+ *
969
+ * Uses Dijkstra-like search to explore reachable polygons within the circle,
970
+ * then selects a random polygon weighted by area, and finally generates
971
+ * a random point within that polygon.
972
+ * This function is using reservoir sampling and can fail in rare occurences even if there is an actual solution.
973
+ *
974
+ * @param navMesh The navigation mesh
975
+ * @param startNodeRef Reference to the polygon to start the search from
976
+ * @param position Center position of the search circle
977
+ * @param maxRadius Maximum radius of the search circle
978
+ * @param filter Query filter to apply to polygons
979
+ * @param rand Function that returns random values [0,1]
980
+ * @returns The result object with success flag, random point, and polygon reference
981
+ */
982
+ export function findRandomPointAroundCircle(navMesh: NavMesh, startNodeRef: NodeRef, position: Vec3, maxRadius: number, filter: QueryFilter, rand: () => number): FindRandomPointAroundCircleResult;
983
+ ```
984
+
985
+ **[`findRandomPointAroundCircle` API Documentation →](https://navcat.dev/docs/functions/navcat.findRandomPointAroundCircle.html)**
986
+
987
+ <table>
988
+ <tr>
989
+ <td align="center">
990
+ <a href="https://navcat.dev/examples#example-find-random-point-around-circle">
991
+ <img src="./examples/public/screenshots/example-find-random-point-around-circle.png" width="200" height="133" style="object-fit:cover;"/><br/>
992
+ <strong>Find Random Point Around Circle</strong>
993
+ </a>
994
+ </td>
995
+ </tr>
996
+ </table>
997
+
998
+ ## Crowd Simulation
999
+
1000
+ The `crowd` API in `navcat-zup/blocks` provides a high-level agent simulation system built on top of navcat's pathfinding and local steering capabilities.
1001
+
1002
+ For simple use cases you can use it directly, and for more advanced use cases you might copy it into your project and modify it as needed.
1003
+
1004
+ - Agent management: add/remove agents, set target positions or velocities
1005
+ - Frame-distributed pathfinding to maintain performance with many agents
1006
+ - Agent-to-agent and wall avoidance
1007
+ - Off-mesh connection support with animation hooks
1008
+
1009
+ It internally makes use of other `navcat-zup/blocks` APIs like `pathCorridor`, `localBoundary`, and `obstacleAvoidance` to manage agent node corridors and handle obstacle avoidance.
1010
+
1011
+ See the docs for API specifics:
1012
+
1013
+ - `crowd`: https://navcat.dev/docs/modules/navcat_blocks.crowd.html
1014
+ - `pathCorridor`: https://navcat.dev/docs/modules/navcat_blocks.pathCorridor.html
1015
+ - `localBoundary`: https://navcat.dev/docs/modules/navcat_blocks.localBoundary.html
1016
+ - `obstacleAvoidance`: https://navcat.dev/docs/modules/navcat_blocks.obstacleAvoidance.html
1017
+
1018
+ And see the below for interactive examples:
1019
+
1020
+ <table>
1021
+ <tr>
1022
+ <td align="center">
1023
+ <a href="https://navcat.dev/examples#example-crowd-simulation">
1024
+ <img src="./examples/public/screenshots/example-crowd-simulation.png" width="200" height="133" style="object-fit:cover;"/><br/>
1025
+ <strong>Crowd Simulation</strong>
1026
+ </a>
1027
+ </td>
1028
+ <td align="center">
1029
+ <a href="https://navcat.dev/examples#example-crowd-simulation-stress-test">
1030
+ <img src="./examples/public/screenshots/example-crowd-simulation-stress-test.png" width="200" height="133" style="object-fit:cover;"/><br/>
1031
+ <strong>Crowd Simulation Stress Test</strong>
1032
+ </a>
1033
+ </td>
1034
+ </tr>
1035
+ </table>
1036
+
1037
+ ## Custom Pathfinding Algorithms
1038
+
1039
+ If your use case is not covered by built-in high level APIs, navcat provides lower-level access to the navigation mesh structure to enable building custom pathfinding algorithms.
1040
+
1041
+ The [Advanced Navigation Mesh APIs](#advanced-navigation-mesh-apis) section below covers APIs for working with the navigation mesh structure.
1042
+
1043
+ See the examples below for demonstrations of userland pathfinding algorithms:
1044
+
1045
+ <table>
1046
+ <tr>
1047
+ <td align="center">
1048
+ <a href="https://navcat.dev/examples#example-flow-field-pathfinding">
1049
+ <img src="./examples/public/screenshots/example-flow-field-pathfinding.png" width="200" height="133" style="object-fit:cover;"/><br/>
1050
+ <strong>Flow Field Pathfinding</strong>
1051
+ </a>
1052
+ </td>
1053
+ <td align="center">
1054
+ <a href="https://navcat.dev/examples#example-find-shortest-paths">
1055
+ <img src="./examples/public/screenshots/example-find-shortest-paths.png" width="200" height="133" style="object-fit:cover;"/><br/>
1056
+ <strong>Find Shortest Paths</strong>
1057
+ </a>
1058
+ </td>
1059
+ <td align="center">
1060
+ <a href="https://navcat.dev/examples#example-find-diverse-paths">
1061
+ <img src="./examples/public/screenshots/example-find-diverse-paths.png" width="200" height="133" style="object-fit:cover;"/><br/>
1062
+ <strong>Find Diverse Paths</strong>
1063
+ </a>
1064
+ </td>
1065
+ </tr>
1066
+ </table>
1067
+
1068
+ ## Navigation Mesh Generation
1069
+
1070
+ ### Overview
1071
+
1072
+ Navigation mesh generation is the process of transforming 3D geometry into a graph of walkable polygons. This graph is then used for pathfinding and navigation queries.
1073
+
1074
+ #### The Structure of a Navigation Mesh
1075
+
1076
+ A navigation mesh is organized into one or more **tiles**. Each tile contains walkable polygons and height detail information. For most projects, a single tile covering your entire level is perfect. For larger or dynamic worlds, you can split the navmesh into a grid of tiles.
1077
+
1078
+ Behind the scenes, navcat maintains a graph of **nodes** (representing polygons) and **links** (representing connections between polygons). This graph is what powers pathfinding - when you query for a path, navcat searches this graph to find the route.
1079
+
1080
+ If you want to dig deeper into the internal structure (useful for advanced cases like building custom pathfinding algorithms), the navigation mesh data is fully accessible. Check out the "Flow Field Pathfinding" example to see custom graph traversal in action.
1081
+
1082
+ #### Single-Tile vs Tiled Navigation Meshes
1083
+
1084
+ Most projects should start with a **single-tile navmesh** - it's simpler and covers the majority of use cases.
1085
+
1086
+ Consider using **tiled navmeshes** when you need:
1087
+
1088
+ - Dynamic updates (rebuild only affected tiles when geometry changes)
1089
+ - Memory management (stream tiles in/out based on player location)
1090
+ - Parallel generation (generate tiles independently)
1091
+ - Large worlds (tiled navmesh generation can give better results over large areas)
1092
+
1093
+ For smaller, static scenes, a single-tile navmesh is simpler and sufficient.
1094
+
1095
+ How you want to manage tiles is up to you. You can create and add all navmesh tiles for a level at once, or you can create and add/remove tiles dynamically at runtime.
1096
+
1097
+ If you remove and re-add tiles at given coordinates, note that the node references for polygons will become invalidated. Any custom pathfinding logic you write that references polygons will need to call `isValidNodeRef` to check if a node reference is still valid before using it.
1098
+
1099
+ ### Generation Presets
1100
+
1101
+ The `navcat-zup/blocks` entrypoint provides `generateSoloNavMesh` and `generateTiledNavMesh` presets that bundle together the common steps of the navigation mesh generation process into easy-to-use functions.
1102
+
1103
+ If your use case is simple, you can use these presets to get started quickly. As your use case becomes more complex, you can eject from these presets by copying the functions (that are separate from navcat core) into your project and modifying them as needed.
1104
+
1105
+ You can find API docs for these blocks in the API docs:
1106
+
1107
+ - https://navcat.dev/docs/functions/navcat_blocks.generateSoloNavMesh.html
1108
+ - https://navcat.dev/docs/functions/navcat_blocks.generateTiledNavMesh.html
1109
+
1110
+ See the Solo NavMesh and Tiled NavMesh examples for interactive examples of using these presets:
1111
+
1112
+ <table>
1113
+ <tr>
1114
+ <td align="center">
1115
+ <a href="https://navcat.dev/examples#example-solo-navmesh">
1116
+ <img src="./examples/public/screenshots/example-solo-navmesh.png" width="200" height="133" style="object-fit:cover;"/><br/>
1117
+ <strong>Solo NavMesh</strong>
1118
+ </a>
1119
+ </td>
1120
+ <td align="center">
1121
+ <a href="https://navcat.dev/examples#example-tiled-navmesh">
1122
+ <img src="./examples/public/screenshots/example-tiled-navmesh.png" width="200" height="133" style="object-fit:cover;"/><br/>
1123
+ <strong>Tiled NavMesh</strong>
1124
+ </a>
1125
+ </td>
1126
+ </tr>
1127
+ </table>
1128
+
1129
+ ### Generation Process: Deep Dive
1130
+
1131
+ This section provides a deep-dive into how navigation mesh generation works. Understanding this process is useful for tuning parameters to get the best results for your specific environment and agent requirements.
1132
+
1133
+ The core of the navigation mesh generation approach is based on the [recastnavigation library](https://github.com/recastnavigation/recastnavigation)'s voxelization-based approach.
1134
+
1135
+ At a high-level:
1136
+
1137
+ - Input triangles are rasterized into voxels / into a heightfield
1138
+ - Voxels in areas where agents (defined by your parameters) would not be able to move are filtered and removed
1139
+ - Walkable areas described by the voxel grid are divided into sets of polygonal regions
1140
+ - Navigation mesh polygons are created by triangulating the generated polygonal regions
1141
+
1142
+ Like recast, navcat supports both single and tiled navigation meshes. Single-tile meshes are suitable for many simple, static cases and are easy to work with. Tiled navmeshes are more complex to work with but better support larger, more dynamic environments, and enable advanced use cases like re-baking, navmesh data-streaming.
1143
+
1144
+ If you want an interactive example / starter, see the examples:
1145
+
1146
+ - https://navcat.dev/examples#example-generate-navmesh
1147
+ - [./examples/src/example-solo-navmesh.ts](./examples/src/example-solo-navmesh.ts)
1148
+ - [./blocks/generate-solo-nav-mesh.ts](./blocks/generate-solo-nav-mesh.ts)
1149
+
1150
+ #### 0. Input and setup
1151
+
1152
+ The input to the navigation mesh generation process is a set of 3D triangles that define the environment. These triangles should represent the collision surfaces in the environment, and shouldn't include any non-collidable decorative geometry that shouldn't affect navigation.
1153
+
1154
+ The input positions should adhere to the OpenGL conventions (right-handed coordinate system, counter-clockwise winding order).
1155
+
1156
+ The navigation mesh generation process emits diagnostic messages, warnings, and errors. These are captured with a build context object.
1157
+
1158
+ ```ts
1159
+ import * as Nav from 'navcat-zup';
1160
+
1161
+ // flat array of vertex positions [x1, y1, z1, x2, y2, z2, ...]
1162
+ const positions: number[] = [];
1163
+
1164
+ // flat array of triangle vertex indices
1165
+ const indices: number[] = [];
1166
+
1167
+ // build context to capture diagnostic messages, warnings, and errors
1168
+ const ctx = Nav.BuildContext.create();
1169
+ ```
1170
+
1171
+ ![2-1-navmesh-gen-input](./docs/2-1-navmesh-gen-input.png)
1172
+
1173
+ ```ts
1174
+ export type BuildContextState = {
1175
+ logs: BuildContextLog[];
1176
+ times: BuildContextTime[];
1177
+ _startTimes: Record<string, number>;
1178
+ };
1179
+ ```
1180
+
1181
+ #### 1. Mark walkable triangles
1182
+
1183
+ The first step is to filter the input triangles to find the walkable triangles. This is done by checking the slope of each triangle against a maximum walkable slope angle. Triangles that are walkable are marked with the `WALKABLE_AREA` (`1`) area type.
1184
+
1185
+ ```ts
1186
+ // CONFIG: agent walkable slope angle
1187
+ const walkableSlopeAngleDegrees = 45;
1188
+
1189
+ // allocate an array to hold the area ids for each triangle
1190
+ const triAreaIds = new Uint8Array(indices.length / 3).fill(0);
1191
+
1192
+ // mark triangles as walkable or not depending on their slope angle
1193
+ Nav.markWalkableTriangles(positions, indices, triAreaIds, walkableSlopeAngleDegrees);
1194
+ ```
1195
+
1196
+ ![2-2-walkable-triangles](./docs/2-2-navmesh-gen-walkable-triangles.png)
1197
+
1198
+ ```ts
1199
+ /**
1200
+ * Marks triangles as walkable based on their slope angle
1201
+ * @param inVertices Array of vertex coordinates [x0, y0, z0, x1, y1, z1, ...]
1202
+ * @param inIndices Array of triangle indices [i0, i1, i2, i3, i4, i5, ...]
1203
+ * @param outTriAreaIds Output array of triangle area IDs, with a length equal to inIndices.length / 3
1204
+ * @param walkableSlopeAngle Maximum walkable slope angle in degrees (default: 45)
1205
+ */
1206
+ export function markWalkableTriangles(inVertices: ArrayLike<number>, inIndices: ArrayLike<number>, outTriAreaIds: ArrayLike<number>, walkableSlopeAngle = 45.0);
1207
+ ```
1208
+
1209
+ ```ts
1210
+ export function createTriangleAreaIdsHelper(input: {
1211
+ positions: ArrayLike<number>;
1212
+ indices: ArrayLike<number>;
1213
+ }, triAreaIds: ArrayLike<number>): DebugPrimitive[];
1214
+ ```
1215
+
1216
+ #### 2. Rasterize triangles into a heightfield, do filtering with the heightfield
1217
+
1218
+ The walkable triangles are then voxelized into a heightfield, taking the triangle's "walkability" into each span.
1219
+
1220
+ Some filtering is done to the heightfield to remove spans where a character cannot stand, and unwanted overhangs are removed.
1221
+
1222
+ The heightfield resolution is configurable, and greatly affects the fidelity of the resulting navigation mesh, and the performance of the navigation mesh generation process.
1223
+
1224
+ ```ts
1225
+ // CONFIG: heightfield cell size and height, in world units
1226
+ const cellSize = 0.2;
1227
+ const cellHeight = 0.2;
1228
+
1229
+ // CONFIG: agent walkable climb
1230
+ const walkableClimbWorld = 0.5; // in world units
1231
+ const walkableClimbVoxels = Math.ceil(walkableClimbWorld / cellHeight);
1232
+
1233
+ // CONFIG: agent walkable height
1234
+ const walkableHeightWorld = 1.0; // in world units
1235
+ const walkableHeightVoxels = Math.ceil(walkableHeightWorld / cellHeight);
1236
+
1237
+ // calculate the bounds of the input geometry
1238
+ const bounds: Nav.Box3 = [0, 0, 0, 0, 0, 0];
1239
+ Nav.calculateMeshBounds(bounds, positions, indices);
1240
+
1241
+ // calculate the grid size of the heightfield
1242
+ const [heightfieldWidth, heightfieldHeight] = Nav.calculateGridSize([0, 0], bounds, cellSize);
1243
+
1244
+ // create the heightfield
1245
+ const heightfield = Nav.createHeightfield(heightfieldWidth, heightfieldHeight, bounds, cellSize, cellHeight);
1246
+
1247
+ // rasterize the walkable triangles into the heightfield
1248
+ Nav.rasterizeTriangles(ctx, heightfield, positions, indices, triAreaIds, walkableClimbVoxels);
1249
+
1250
+ // filter walkable surfaces
1251
+ Nav.filterLowHangingWalkableObstacles(heightfield, walkableClimbVoxels);
1252
+ Nav.filterLedgeSpans(heightfield, walkableHeightVoxels, walkableClimbVoxels);
1253
+ Nav.filterWalkableLowHeightSpans(heightfield, walkableHeightVoxels);
1254
+ ```
1255
+
1256
+ ![2-3-heightfield](./docs/2-3-navmesh-gen-heightfield.png)
1257
+
1258
+ ```ts
1259
+ export type Heightfield = {
1260
+ /** the width of the heightfield (along y axis in cell units) */
1261
+ width: number;
1262
+ /** the height of the heightfield (along x axis in cell units) */
1263
+ height: number;
1264
+ /** the bounds in world space */
1265
+ bounds: Box3;
1266
+ /** the vertical size of each cell (minimum increment along z) */
1267
+ cellHeight: number;
1268
+ /** the horizontal size of each cell (minimum increment along x and y) */
1269
+ cellSize: number;
1270
+ /** the heightfield of spans, (width*height) */
1271
+ spans: (HeightfieldSpan | null)[];
1272
+ };
1273
+ ```
1274
+
1275
+ ```ts
1276
+ export type HeightfieldSpan = {
1277
+ /** the lower limit of the span */
1278
+ min: number;
1279
+ /** the upper limit of the span */
1280
+ max: number;
1281
+ /** the area id assigned to the span */
1282
+ area: number;
1283
+ /** the next heightfield span */
1284
+ next: HeightfieldSpan | null;
1285
+ };
1286
+ ```
1287
+
1288
+ ```ts
1289
+ export function calculateMeshBounds(outBounds: Box3, inVertices: ArrayLike<number>, inIndices: ArrayLike<number>): Box3;
1290
+ ```
1291
+
1292
+ ```ts
1293
+ export function calculateGridSize(outGridSize: Vec2, bounds: Box3, cellSize: number): [
1294
+ width: number,
1295
+ height: number
1296
+ ];
1297
+ ```
1298
+
1299
+ ```ts
1300
+ export function createHeightfield(width: number, height: number, bounds: Box3, cellSize: number, cellHeight: number): Heightfield;
1301
+ ```
1302
+
1303
+ ```ts
1304
+ export function rasterizeTriangles(ctx: BuildContextState, heightfield: Heightfield, vertices: ArrayLike<number>, indices: ArrayLike<number>, triAreaIds: ArrayLike<number>, flagMergeThreshold = 1);
1305
+ ```
1306
+
1307
+ ```ts
1308
+ export function filterLowHangingWalkableObstacles(heightfield: Heightfield, walkableClimb: number);
1309
+ ```
1310
+
1311
+ ```ts
1312
+ export function filterLedgeSpans(heightfield: Heightfield, walkableHeight: number, walkableClimb: number);
1313
+ ```
1314
+
1315
+ ```ts
1316
+ export function filterWalkableLowHeightSpans(heightfield: Heightfield, walkableHeight: number);
1317
+ ```
1318
+
1319
+ ```ts
1320
+ export function createHeightfieldHelper(heightfield: Heightfield): DebugPrimitive[];
1321
+ ```
1322
+
1323
+ #### 3. Build compact heightfield, erode walkable area, mark areas
1324
+
1325
+ The heightfield is then compacted to only represent the top walkable surfaces.
1326
+
1327
+ The compact heightfield is generally eroded by the agent radius to ensure that the resulting navigation mesh is navigable by agents of the specified radius.
1328
+
1329
+ ```ts
1330
+ // build the compact heightfield
1331
+ const compactHeightfield = Nav.buildCompactHeightfield(ctx, walkableHeightVoxels, walkableClimbVoxels, heightfield);
1332
+
1333
+ // CONFIG: agent radius
1334
+ const walkableRadiusWorld = 0.6; // in world units
1335
+ const walkableRadiusVoxels = Math.ceil(walkableRadiusWorld / cellSize);
1336
+
1337
+ // erode the walkable area by the agent radius / walkable radius
1338
+ Nav.erodeWalkableArea(walkableRadiusVoxels, compactHeightfield);
1339
+
1340
+ // OPTIONAL: you can use utilities like markBoxArea here on the compact heightfield to mark custom areas
1341
+ // see the "Custom Query Filters and Custom Area Types" section of the docs for more info
1342
+ ```
1343
+
1344
+ ![2-4-compact-heightfield](./docs/2-4-navmesh-gen-compact-heightfield.png)
1345
+
1346
+ ```ts
1347
+ export type CompactHeightfield = {
1348
+ /** the width of the heightfield (along y axis in cell units) */
1349
+ width: number;
1350
+ /** the height of the heightfield (along x axis in cell units) */
1351
+ height: number;
1352
+ /** the number of spans in the heightfield */
1353
+ spanCount: number;
1354
+ /** the walkable height used during the build of the heightfield */
1355
+ walkableHeightVoxels: number;
1356
+ /** the walkable climb used during the build of the heightfield */
1357
+ walkableClimbVoxels: number;
1358
+ /** the AABB border size used during the build of the heightfield */
1359
+ borderSize: number;
1360
+ /** the maxiumum distance value of any span within the heightfield */
1361
+ maxDistance: number;
1362
+ /** the maximum region id of any span within the heightfield */
1363
+ maxRegions: number;
1364
+ /** the heightfield bounds in world space */
1365
+ bounds: Box3;
1366
+ /** the size of each cell */
1367
+ cellSize: number;
1368
+ /** the height of each cell */
1369
+ cellHeight: number;
1370
+ /** array of cells, size = width*height */
1371
+ cells: CompactHeightfieldCell[];
1372
+ /** array of spans, size = spanCount */
1373
+ spans: CompactHeightfieldSpan[];
1374
+ /** array containing area id data, size = spanCount */
1375
+ areas: number[];
1376
+ /** array containing distance field data, size = spanCount */
1377
+ distances: number[];
1378
+ };
1379
+ ```
1380
+
1381
+ ```ts
1382
+ export type CompactHeightfieldCell = {
1383
+ /** index to the first span in the column */
1384
+ index: number;
1385
+ /** number of spans in the column */
1386
+ count: number;
1387
+ };
1388
+ ```
1389
+
1390
+ ```ts
1391
+ export type CompactHeightfieldSpan = {
1392
+ /** the lower extent of the span. measured from the heightfields base. */
1393
+ z: number;
1394
+ /** the id of the region the span belongs to, or zero if not in a region */
1395
+ region: number;
1396
+ /** packed neighbour connection data */
1397
+ con: number;
1398
+ /** the height of the span, measured from z */
1399
+ h: number;
1400
+ };
1401
+ ```
1402
+
1403
+ ```ts
1404
+ export function buildCompactHeightfield(ctx: BuildContextState, walkableHeightVoxels: number, walkableClimbVoxels: number, heightfield: Heightfield): CompactHeightfield;
1405
+ ```
1406
+
1407
+ ```ts
1408
+ export function erodeWalkableArea(walkableRadiusVoxels: number, compactHeightfield: CompactHeightfield);
1409
+ ```
1410
+
1411
+ ```ts
1412
+ /**
1413
+ * Erodes the walkable area for a base agent radius and marks restricted areas for larger agents based on given walkable radius thresholds.
1414
+ *
1415
+ * Note that this function requires careful tuning of the build parameters to get a good result:
1416
+ * - The cellSize needs to be small enough to accurately represent narrow passages. Generally you need to use smaller cellSizes than you otherwise would for single agent navmesh builds.
1417
+ * - The thresholds should not be so small that the resulting regions are too small to successfully build good navmesh polygons for. Values like 1-2 voxels will likely lead to poor results.
1418
+ * - You may get a better result using "buildRegionsMonotone" over "buildRegions" as this will better handle the many small clusters of areas that may be created from smaller thresholds.
1419
+ *
1420
+ * A typical workflow for using this utility to implement multi-agent support:
1421
+ * 1. Call erodeAndMarkWalkableAreas with your smallest agent radius and list of restricted areas
1422
+ * 2. Continue with buildDistanceField, buildRegionsMonotone, etc.
1423
+ * 3. Configure query filters so large agents exclude the narrow/restricted area IDs
1424
+ *
1425
+ * @param baseWalkableRadiusVoxels the smallest agent radius in voxels (used for erosion)
1426
+ * @param thresholds array of area ids and their corresponding walkable radius in voxels.
1427
+ * @param compactHeightfield the compact heightfield to process
1428
+ */
1429
+ export function erodeAndMarkWalkableAreas(baseWalkableRadiusVoxels: number, thresholds: Array<{
1430
+ areaId: number;
1431
+ walkableRadiusVoxels: number;
1432
+ }>, compactHeightfield: CompactHeightfield);
1433
+ ```
1434
+
1435
+ ```ts
1436
+ export function createCompactHeightfieldSolidHelper(compactHeightfield: CompactHeightfield): DebugPrimitive[];
1437
+ ```
1438
+
1439
+ #### 4. Build compact heightfield regions
1440
+
1441
+ The compact heightfield is then analyzed to identify distinct walkable regions. These regions are used to create the final navigation mesh.
1442
+
1443
+ Some of the region generation algorithms compute a distance field to identify regions.
1444
+
1445
+ ```ts
1446
+ // prepare for region partitioning by calculating a distance field along the walkable surface
1447
+ Nav.buildDistanceField(compactHeightfield);
1448
+
1449
+ // CONFIG: borderSize, relevant if you are building a tiled navmesh
1450
+ const borderSize = 0;
1451
+
1452
+ // CONFIG: minRegionArea
1453
+ const minRegionArea = 8; // voxel units
1454
+
1455
+ // CONFIG: mergeRegionArea
1456
+ const mergeRegionArea = 20; // voxel units
1457
+
1458
+ // partition the walkable surface into simple regions without holes
1459
+ Nav.buildRegions(ctx, compactHeightfield, borderSize, minRegionArea, mergeRegionArea);
1460
+ ```
1461
+
1462
+ ![2-5-distance-field](./docs/2-5-navmesh-gen-compact-heightfield-distances.png)
1463
+
1464
+ ![2-6-regions](./docs/2-6-navmesh-gen-compact-heightfield-regions.png)
1465
+
1466
+ ```ts
1467
+ export function buildDistanceField(compactHeightfield: CompactHeightfield): void;
1468
+ ```
1469
+
1470
+ ```ts
1471
+ export function buildRegions(ctx: BuildContextState, compactHeightfield: CompactHeightfield, borderSize: number, minRegionArea: number, mergeRegionArea: number): boolean;
1472
+ ```
1473
+
1474
+ ```ts
1475
+ /**
1476
+ * Build regions using monotone partitioning algorithm.
1477
+ * This is an alternative to the watershed-based buildRegions function.
1478
+ * Monotone partitioning creates regions by sweeping the heightfield and
1479
+ * does not generate overlapping regions.
1480
+ */
1481
+ export function buildRegionsMonotone(ctx: BuildContextState, compactHeightfield: CompactHeightfield, borderSize: number, minRegionArea: number, mergeRegionArea: number): boolean;
1482
+ ```
1483
+
1484
+ ```ts
1485
+ /**
1486
+ * Build layer regions using sweep-line algorithm.
1487
+ * This creates regions that can be used for building navigation mesh layers.
1488
+ * Layer regions handle overlapping walkable areas by creating separate layers.
1489
+ */
1490
+ export function buildLayerRegions(_ctx: BuildContextState, compactHeightfield: CompactHeightfield, borderSize: number, minRegionArea: number): boolean;
1491
+ ```
1492
+
1493
+ ```ts
1494
+ export function createCompactHeightfieldDistancesHelper(compactHeightfield: CompactHeightfield): DebugPrimitive[];
1495
+ ```
1496
+
1497
+ ```ts
1498
+ export function createCompactHeightfieldRegionsHelper(compactHeightfield: CompactHeightfield): DebugPrimitive[];
1499
+ ```
1500
+
1501
+ #### 5. Build contours from compact heightfield regions
1502
+
1503
+ Contours are generated around the edges of the regions. These contours are simplified to reduce the number of vertices while maintaining the overall shape.
1504
+
1505
+ ```ts
1506
+ // CONFIG: maxSimplificationError
1507
+ const maxSimplificationError = 1.3; // voxel units
1508
+
1509
+ // CONFIG: maxEdgeLength
1510
+ const maxEdgeLength = 6.0; // voxel units
1511
+
1512
+ // trace and simplify region contours
1513
+ const contourSet = Nav.buildContours(
1514
+ ctx,
1515
+ compactHeightfield,
1516
+ maxSimplificationError,
1517
+ maxEdgeLength,
1518
+ Nav.ContourBuildFlags.CONTOUR_TESS_WALL_EDGES,
1519
+ );
1520
+ ```
1521
+
1522
+ ![2-7-raw-contours](./docs/2-7-navmesh-gen-raw-contours.png)
1523
+
1524
+ ![2-8-simplified-contours](./docs/2-8-navmesh-gen-simplified-contours.png)
1525
+
1526
+ ```ts
1527
+ export type ContourSet = {
1528
+ /** an array of the contours in the set */
1529
+ contours: Contour[];
1530
+ /** the bounds in world space */
1531
+ bounds: Box3;
1532
+ /** the size of each cell */
1533
+ cellSize: number;
1534
+ /** the height of each cell */
1535
+ cellHeight: number;
1536
+ /** the width of the set */
1537
+ width: number;
1538
+ /** the height of the set */
1539
+ height: number;
1540
+ /**the aabb border size used to generate the source data that the contour set was derived from */
1541
+ borderSize: number;
1542
+ /** the max edge error that this contour set was simplified with */
1543
+ maxError: number;
1544
+ };
1545
+ ```
1546
+
1547
+ ```ts
1548
+ export type Contour = {
1549
+ /** simplified contour vertex and connection data. size: 4 * nVerts */
1550
+ vertices: number[];
1551
+ /** the number of vertices in the simplified contour */
1552
+ nVertices: number;
1553
+ /** raw contour vertex and connection data */
1554
+ rawVertices: number[];
1555
+ /** the number of vertices in the raw contour */
1556
+ nRawVertices: number;
1557
+ /** the region id of the contour */
1558
+ reg: number;
1559
+ /** the area id of the contour */
1560
+ area: number;
1561
+ };
1562
+ ```
1563
+
1564
+ ```ts
1565
+ export function buildContours(ctx: BuildContextState, compactHeightfield: CompactHeightfield, maxSimplificationError: number, maxEdgeLength: number, buildFlags: ContourBuildFlags): ContourSet;
1566
+ ```
1567
+
1568
+ ```ts
1569
+ export function createRawContoursHelper(contourSet: ContourSet): DebugPrimitive[];
1570
+ ```
1571
+
1572
+ ```ts
1573
+ export function createSimplifiedContoursHelper(contourSet: ContourSet): DebugPrimitive[];
1574
+ ```
1575
+
1576
+ #### 6. Build polygon mesh from contours, build detail mesh
1577
+
1578
+ From the simplified contours, a polygon mesh is created. This mesh consists of convex polygons that represent the walkable areas.
1579
+
1580
+ A "detail triangle mesh" is also generated to capture more accurate height information for each polygon.
1581
+
1582
+ ```ts
1583
+ // CONFIG: max vertices per polygon
1584
+ const maxVerticesPerPoly = 5; // 3-6, higher = less polys, but more complex polys
1585
+
1586
+ const polyMesh = Nav.buildPolyMesh(ctx, contourSet, maxVerticesPerPoly);
1587
+
1588
+ for (let polyIndex = 0; polyIndex < polyMesh.nPolys; polyIndex++) {
1589
+ // make all "areas" use a base area id of 0
1590
+ if (polyMesh.areas[polyIndex] === Nav.WALKABLE_AREA) {
1591
+ polyMesh.areas[polyIndex] = 0;
1592
+ }
1593
+
1594
+ // give all base "walkable" polys all flags
1595
+ if (polyMesh.areas[polyIndex] === 0) {
1596
+ polyMesh.flags[polyIndex] = 1;
1597
+ }
1598
+ }
1599
+
1600
+ // CONFIG: detail mesh sample distance
1601
+ const sampleDist = 1.0; // world units
1602
+
1603
+ // CONFIG: detail mesh max sample error
1604
+ const sampleMaxError = 1.0; // world units
1605
+
1606
+ const polyMeshDetail = Nav.buildPolyMeshDetail(ctx, polyMesh, compactHeightfield, sampleDist, sampleMaxError);
1607
+ ```
1608
+
1609
+ ![2-9-poly-mesh](./docs/2-9-navmesh-gen-poly-mesh.png)
1610
+
1611
+ ![2-10-detail-mesh](./docs/2-10-navmesh-gen-detail-mesh.png)
1612
+
1613
+ ```ts
1614
+ /**
1615
+ * Represents a polygon mesh suitable for use in building a navigation mesh.
1616
+ */
1617
+ export type PolyMesh = {
1618
+ /** The mesh vertices. Form: (x, y, z) * nverts */
1619
+ vertices: number[];
1620
+ /** Polygon vertex indices. Length: npolys * nvp */
1621
+ polys: number[];
1622
+ /** The region id assigned to each polygon. Length: npolys */
1623
+ regions: number[];
1624
+ /** The user defined flags for each polygon. Length: npolys */
1625
+ flags: number[];
1626
+ /** The area id assigned to each polygon. Length: npolys */
1627
+ areas: number[];
1628
+ /** The number of vertices */
1629
+ nVertices: number;
1630
+ /** The number of polygons */
1631
+ nPolys: number;
1632
+ /** The maximum number of vertices per polygon */
1633
+ maxVerticesPerPoly: number;
1634
+ /** the bounds in world space */
1635
+ bounds: Box3;
1636
+ /** the width in local space */
1637
+ localWidth: number;
1638
+ /** the height in local space */
1639
+ localHeight: number;
1640
+ /** The size of each cell. (On the xy-plane.) */
1641
+ cellSize: number;
1642
+ /** The height of each cell. (The minimum increment along the z-axis.) */
1643
+ cellHeight: number;
1644
+ /** The AABB border size used to generate the source data from which the mesh was derived */
1645
+ borderSize: number;
1646
+ /** The max error of the polygon edges in the mesh */
1647
+ maxEdgeError: number;
1648
+ };
1649
+ ```
1650
+
1651
+ ```ts
1652
+ /**
1653
+ * Contains triangle meshes that represent detailed height data associated
1654
+ * with the polygons in its associated polygon mesh object.
1655
+ */
1656
+ export type PolyMeshDetail = {
1657
+ /** The sub-mesh data. Size: 4*nMeshes. Layout: [verticesBase1, trianglesBase1, verticesCount1, trianglesCount1, ...] */
1658
+ meshes: number[];
1659
+ /** The mesh vertices. Size: 3*nVertices */
1660
+ vertices: number[];
1661
+ /** The mesh triangles. Size: 4*nTriangles */
1662
+ triangles: number[];
1663
+ /** The number of sub-meshes defined by meshes */
1664
+ nMeshes: number;
1665
+ /** The number of vertices in verts */
1666
+ nVertices: number;
1667
+ /** The number of triangles in tris */
1668
+ nTriangles: number;
1669
+ };
1670
+ ```
1671
+
1672
+ ```ts
1673
+ export function buildPolyMesh(ctx: BuildContextState, contourSet: ContourSet, maxVerticesPerPoly: number): PolyMesh;
1674
+ ```
1675
+
1676
+ ```ts
1677
+ export function buildPolyMeshDetail(ctx: BuildContextState, polyMesh: PolyMesh, compactHeightfield: CompactHeightfield, sampleDist: number, sampleMaxError: number): PolyMeshDetail;
1678
+ ```
1679
+
1680
+ ```ts
1681
+ export function createPolyMeshHelper(polyMesh: PolyMesh): DebugPrimitive[];
1682
+ ```
1683
+
1684
+ #### 7. Convert build-time poly mesh and poly mesh detail to runtime navmesh tile format
1685
+
1686
+ Next, we do a post-processing step on the poly mesh and the poly mesh detail to prepare them for use in the navigation mesh.
1687
+
1688
+ This step involes computing adjacency information for the polygons, and mapping the generation-time format to the runtime navigation mesh tile format.
1689
+
1690
+ ```ts
1691
+ // convert the poly mesh to a navmesh tile polys
1692
+ const tilePolys = Nav.polyMeshToTilePolys(polyMesh);
1693
+
1694
+ // convert the poly mesh detail to a navmesh tile detail mesh
1695
+ const tileDetailMesh = Nav.polyMeshDetailToTileDetailMesh(tilePolys.polys, polyMeshDetail);
1696
+ ```
1697
+
1698
+ ```ts
1699
+ export function polyMeshToTilePolys(polyMesh: PolyMesh): NavMeshTilePolys;
1700
+ ```
1701
+
1702
+ ```ts
1703
+ export type NavMeshPoly = {
1704
+ /** The indices of the polygon's vertices. vertices are stored in NavMeshTile.vertices */
1705
+ vertices: number[];
1706
+
1707
+ /**
1708
+ * Packed data representing neighbor polygons references and flags for each edge.
1709
+ * This is usually computed by the navcat's `buildPolyNeighbours` function .
1710
+ */
1711
+ neis: number[];
1712
+
1713
+ /** The user defined flags for this polygon */
1714
+ flags: number;
1715
+
1716
+ /** The user defined area id for this polygon */
1717
+ area: number;
1718
+ };
1719
+ ```
1720
+
1721
+ ```ts
1722
+ /**
1723
+ * Converts a given PolyMeshDetail to the tile detail mesh format.
1724
+ * @param polys
1725
+ * @param polyMeshDetail
1726
+ * @returns
1727
+ */
1728
+ export function polyMeshDetailToTileDetailMesh(polys: NavMeshPoly[], polyMeshDetail: PolyMeshDetail);
1729
+ ```
1730
+
1731
+ ```ts
1732
+ export type NavMeshPolyDetail = {
1733
+ /**
1734
+ * The offset of the vertices in the NavMeshTile detailVertices array.
1735
+ * If the base index is between 0 and `NavMeshTile.vertices.length`, this is used to index into the NavMeshTile vertices array.
1736
+ * If the base index is greater than `NavMeshTile.vertices.length`, it is used to index into the NavMeshTile detailVertices array.
1737
+ * This allows for detail meshes to either re-use the polygon vertices or to define their own vertices without duplicating data.
1738
+ */
1739
+ verticesBase: number;
1740
+
1741
+ /** The offset of the triangles in the NavMeshTile detailTriangles array */
1742
+ trianglesBase: number;
1743
+
1744
+ /** The number of vertices in thde sub-mesh */
1745
+ verticesCount: number;
1746
+
1747
+ /** The number of triangles in the sub-mesh */
1748
+ trianglesCount: number;
1749
+ };
1750
+ ```
1751
+
1752
+ ```ts
1753
+ export function createPolyMeshDetailHelper(polyMeshDetail: PolyMeshDetail): DebugPrimitive[];
1754
+ ```
1755
+
1756
+ #### 8. Assemble the navigation mesh
1757
+
1758
+ Finally, the polygon mesh and detail mesh are combined to create a navigation mesh tile. This tile can be used for pathfinding and navigation queries.
1759
+
1760
+ ```ts
1761
+ // create the navigation mesh
1762
+ const navMesh = Nav.createNavMesh();
1763
+
1764
+ // set the navmesh parameters using the poly mesh bounds
1765
+ // this example is for a single tile navmesh, so the tile width/height is the same as the poly mesh bounds size
1766
+ navMesh.tileWidth = polyMesh.bounds[4] - polyMesh.bounds[1];
1767
+ navMesh.tileHeight = polyMesh.bounds[3] - polyMesh.bounds[0];
1768
+ navMesh.origin[0] = polyMesh.bounds[0];
1769
+ navMesh.origin[1] = polyMesh.bounds[1];
1770
+ navMesh.origin[2] = polyMesh.bounds[2];
1771
+
1772
+ // assemble the navmesh tile params
1773
+ const tileParams: Nav.NavMeshTileParams = {
1774
+ bounds: polyMesh.bounds,
1775
+ vertices: tilePolys.vertices,
1776
+ polys: tilePolys.polys,
1777
+ detailMeshes: tileDetailMesh.detailMeshes,
1778
+ detailVertices: tileDetailMesh.detailVertices,
1779
+ detailTriangles: tileDetailMesh.detailTriangles,
1780
+ tileX: 0,
1781
+ tileY: 0,
1782
+ tileLayer: 0,
1783
+ cellSize,
1784
+ cellHeight,
1785
+ walkableHeight: walkableHeightWorld,
1786
+ walkableRadius: walkableRadiusWorld,
1787
+ walkableClimb: walkableClimbWorld,
1788
+ };
1789
+
1790
+ // build the nav mesh tile - this creates a BV tree, and initialises runtime tile properties
1791
+ const tile = Nav.buildTile(tileParams);
1792
+
1793
+ // add the tile to the navmesh
1794
+ Nav.addTile(navMesh, tile);
1795
+ ```
1796
+
1797
+ ![./docs/1-whats-a-navmesh](./docs/1-whats-a-navmesh.png)
1798
+
1799
+ ```ts
1800
+ /**
1801
+ * Creates a new empty navigation mesh.
1802
+ * @returns The created navigation mesh
1803
+ */
1804
+ export function createNavMesh(): NavMesh;
1805
+ ```
1806
+
1807
+ ```ts
1808
+ /**
1809
+ * Builds a navmesh tile from the given parameters
1810
+ * This builds a BV-tree for the tile, and initializes runtime tile properties
1811
+ * @param params the parameters to build the tile from
1812
+ * @returns the built navmesh tile
1813
+ */
1814
+ export function buildTile(params: NavMeshTileParams): NavMeshTile;
1815
+ ```
1816
+
1817
+ ```ts
1818
+ /**
1819
+ * Adds a tile to the navmesh.
1820
+ * If a tile already exists at the same position, it will be removed first.
1821
+ * @param navMesh the navmesh to add the tile to
1822
+ * @param tile the tile to add
1823
+ */
1824
+ export function addTile(navMesh: NavMesh, tile: NavMeshTile): void;
1825
+ ```
1826
+
1827
+ ```ts
1828
+ /**
1829
+ * Removes the tile at the given location
1830
+ * @param navMesh the navmesh to remove the tile from
1831
+ * @param x the x coordinate of the tile
1832
+ * @param y the y coordinate of the tile
1833
+ * @param layer the layer of the tile
1834
+ * @returns true if the tile was removed, otherwise false
1835
+ */
1836
+ export function removeTile(navMesh: NavMesh, x: number, y: number, layer: number): boolean;
1837
+ ```
1838
+
1839
+ ```ts
1840
+ export function createNavMeshHelper(navMesh: NavMesh): DebugPrimitive[];
1841
+ ```
1842
+
1843
+ ### Post-Processing
1844
+
1845
+ A common post-processing step after generating a navigation mesh is to flood-fill the navmesh from given "seed points" that represent valid starting locations, to exclude any isolated or unreachable areas. This is useful when generating navmeshes for complex environments where some inside of walls or on top of ceilings may be marked as walkable by the generation process, but are not actually reachable by agents for your use case.
1846
+
1847
+ The `navcat-zup/blocks` entrypoint provides a `floodFillNavMesh` utility that helps with this process.
1848
+
1849
+ You can see the "Flood Fill Pruning" example to see how to use this utility:
1850
+
1851
+ ```ts
1852
+ export function floodFillNavMesh(navMesh: NavMesh, startNodeRefs: NodeRef[]): {
1853
+ reachable: NodeRef[];
1854
+ unreachable: NodeRef[];
1855
+ };
1856
+ ```
1857
+
1858
+ <table>
1859
+ <tr>
1860
+ <td align="center">
1861
+ <a href="https://navcat.dev/examples#example-flood-fill-pruning">
1862
+ <img src="./examples/public/screenshots/example-flood-fill-pruning.png" width="200" height="133" style="object-fit:cover;"/><br/>
1863
+ <strong>Flood Fill Pruning</strong>
1864
+ </a>
1865
+ </td>
1866
+ </tr>
1867
+ </table>
1868
+
1869
+ ### Custom Query Filters and Custom Area Types
1870
+
1871
+ Most navigation mesh querying APIs accept a `queryFilter` parameter that allows you to customize how the query is performed.
1872
+
1873
+ You can provide a cost calculation function to modify the cost of traversing polygons, and you can provide a filter function to include/exclude polygons based on their area and flags.
1874
+
1875
+ ```ts
1876
+ /**
1877
+ * A query filter used in navigation queries.
1878
+ *
1879
+ * This allows for customization of what nodes are considered traversable, and
1880
+ * the cost of traversing between nodes.
1881
+ *
1882
+ * If you are getting started, you can use the built-in @see DEFAULT_QUERY_FILTER or @see ANY_QUERY_FILTER
1883
+ */
1884
+ export type QueryFilter = {
1885
+ /**
1886
+ * Checks if a NavMesh node passes the filter.
1887
+ * @param nodeRef The node reference.
1888
+ * @param navMesh The navmesh
1889
+ * @returns Whether the node reference passes the filter.
1890
+ */
1891
+ passFilter(nodeRef: NodeRef, navMesh: NavMesh): boolean;
1892
+
1893
+ /**
1894
+ * Calculates the cost of moving from one point to another.
1895
+ * @param pa The start position on the edge of the previous and current node. [(x, y, z)]
1896
+ * @param pb The end position on the edge of the current and next node. [(x, y, z)]
1897
+ * @param navMesh The navigation mesh
1898
+ * @param prevRef The reference id of the previous node. [opt]
1899
+ * @param curRef The reference id of the current node.
1900
+ * @param nextRef The reference id of the next node. [opt]
1901
+ * @returns The cost of moving from the start to the end position.
1902
+ */
1903
+ getCost(
1904
+ pa: Vec3,
1905
+ pb: Vec3,
1906
+ navMesh: NavMesh,
1907
+ prevRef: NodeRef | undefined,
1908
+ curRef: NodeRef,
1909
+ nextRef: NodeRef | undefined,
1910
+ ): number;
1911
+ };
1912
+ ```
1913
+
1914
+ ```ts
1915
+ export const DEFAULT_QUERY_FILTER = createDefaultQueryFilter();
1916
+ ```
1917
+
1918
+ Many simple use cases can get far with using the default query `Nav.DEFAULT_QUERY_FILTER`. If you want to customise cost calculations, or include/exclude areas based on areas and flags, you can provide your own query filter that implements the `QueryFilter` type interface.
1919
+
1920
+ You can reference the areas examples to see how to mark areas with different types and use custom query filters to control passability and costs:
1921
+
1922
+ <table>
1923
+ <tr>
1924
+ <td align="center">
1925
+ <a href="https://navcat.dev/examples#example-area-filters">
1926
+ <img src="./examples/public/screenshots/example-area-filters.png" width="200" height="133" style="object-fit:cover;"/><br/>
1927
+ <strong>Area Filters</strong>
1928
+ </a>
1929
+ </td>
1930
+ <td align="center">
1931
+ <a href="https://navcat.dev/examples#example-area-costs">
1932
+ <img src="./examples/public/screenshots/example-area-costs.png" width="200" height="133" style="object-fit:cover;"/><br/>
1933
+ <strong>Area Costs</strong>
1934
+ </a>
1935
+ </td>
1936
+ <td align="center">
1937
+ <a href="https://navcat.dev/examples#example-multiple-agent-sizes">
1938
+ <img src="./examples/public/screenshots/example-multiple-agent-sizes.png" width="200" height="133" style="object-fit:cover;"/><br/>
1939
+ <strong>Multiple Agent Sizes</strong>
1940
+ </a>
1941
+ </td>
1942
+ </tr>
1943
+ </table>
1944
+
1945
+ ### Off-Mesh Connections
1946
+
1947
+ Off-mesh connections enable navigation between non-adjacent areas by representing special traversal actions like jumping gaps, climbing ladders, teleporting, or opening doors.
1948
+
1949
+ **When to use:** Add off-mesh connections when your environment has gaps, vertical transitions, or special traversal mechanics that can't be represented by the standard navmesh polygons. The pathfinding system will automatically consider these connections when finding paths.
1950
+
1951
+ ```ts
1952
+ // define a bidirectional off-mesh connection between two points
1953
+ const bidirectionalOffMeshConnection: Nav.OffMeshConnectionParams = {
1954
+ // start position in world space
1955
+ start: [0, 0, 0],
1956
+ // end position in world space
1957
+ end: [1, 1, 0],
1958
+ // radius of the connection endpoints, if it's too small a poly may not be found to link the connection to
1959
+ radius: 0.5,
1960
+ // the direction of the off-mesh connection (START_TO_END or BIDIRECTIONAL)
1961
+ direction: Nav.OffMeshConnectionDirection.BIDIRECTIONAL,
1962
+ // flags for the off-mesh connection, you can use this for custom behaviour with query filters
1963
+ flags: 1,
1964
+ // area id for the off-mesh connection, you can use this for custom behaviour with query filters
1965
+ area: 0,
1966
+ };
1967
+
1968
+ // add the off-mesh connection to the nav mesh, returns the off-mesh connection id
1969
+ const bidirectionalOffMeshConnectionId = Nav.addOffMeshConnection(navMesh, bidirectionalOffMeshConnection);
1970
+
1971
+ // true if the off-mesh connection is linked to polys, false if a suitable poly couldn't be found
1972
+ Nav.isOffMeshConnectionConnected(navMesh, bidirectionalOffMeshConnectionId);
1973
+
1974
+ // retrieve the off-mesh connection attachment info, which contains the start and end poly node refs that the connection is linked to
1975
+ const offMeshConnectionAttachment = navMesh.offMeshConnectionAttachments[bidirectionalOffMeshConnectionId];
1976
+
1977
+ if (offMeshConnectionAttachment) {
1978
+ console.log(offMeshConnectionAttachment.startPolyNode); // the start poly node ref that the off-mesh connection is linked to
1979
+ console.log(offMeshConnectionAttachment.endPolyNode); // the end poly node ref that the off-mesh connection is linked to
1980
+ }
1981
+
1982
+ // remove the off-mesh connection from the nav mesh
1983
+ Nav.removeOffMeshConnection(navMesh, bidirectionalOffMeshConnectionId);
1984
+
1985
+ // define a one-way off-mesh connection (e.g. a teleporter that only goes one way)
1986
+ const oneWayTeleporterOffMeshConnection: Nav.OffMeshConnectionParams = {
1987
+ start: [2, 2, 0],
1988
+ end: [3, 3, 1],
1989
+ radius: 0.5,
1990
+ direction: Nav.OffMeshConnectionDirection.START_TO_END,
1991
+ flags: 1,
1992
+ area: 0,
1993
+ };
1994
+
1995
+ // add the off-mesh connection to the nav mesh, returns the off-mesh connection id
1996
+ const oneWayTeleporterOffMeshConnectionId = Nav.addOffMeshConnection(navMesh, oneWayTeleporterOffMeshConnection);
1997
+
1998
+ // remove the off-mesh connection from the nav mesh
1999
+ Nav.removeOffMeshConnection(navMesh, oneWayTeleporterOffMeshConnectionId);
2000
+ ```
2001
+
2002
+ To see a live example, see the "Off-Mesh Connections Example":
2003
+
2004
+ <table>
2005
+ <tr>
2006
+ <td align="center">
2007
+ <a href="https://navcat.dev/examples#example-off-mesh-connections">
2008
+ <img src="./examples/public/screenshots/example-off-mesh-connections.png" width="200" height="133" style="object-fit:cover;"/><br/>
2009
+ <strong>Off-Mesh Connections</strong>
2010
+ </a>
2011
+ </td>
2012
+ </tr>
2013
+ </table>
2014
+
2015
+ ```ts
2016
+ /**
2017
+ * Adds a new off mesh connection to the NavMesh, and returns it's ID
2018
+ * @param navMesh the navmesh to add the off mesh connection to
2019
+ * @param offMeshConnectionParams the parameters of the off mesh connection to add
2020
+ * @returns the ID of the added off mesh connection
2021
+ */
2022
+ export function addOffMeshConnection(navMesh: NavMesh, offMeshConnectionParams: OffMeshConnectionParams): number;
2023
+ ```
2024
+
2025
+ ```ts
2026
+ /**
2027
+ * Removes an off mesh connection from the NavMesh
2028
+ * @param navMesh the navmesh to remove the off mesh connection from
2029
+ * @param offMeshConnectionId the ID of the off mesh connection to remove
2030
+ */
2031
+ export function removeOffMeshConnection(navMesh: NavMesh, offMeshConnectionId: number): void;
2032
+ ```
2033
+
2034
+ ```ts
2035
+ /**
2036
+ * Returns whether the off mesh connection with the given ID is currently connected to the navmesh.
2037
+ * An off mesh connection may be disconnected if the start or end positions have no valid polygons nearby to connect to.
2038
+ * @param navMesh the navmesh
2039
+ * @param offMeshConnectionId the ID of the off mesh connection
2040
+ * @returns whether the off mesh connection is connected
2041
+ */
2042
+ export function isOffMeshConnectionConnected(navMesh: NavMesh, offMeshConnectionId: number): boolean;
2043
+ ```
2044
+
2045
+ ## Advanced Navigation Mesh APIs
2046
+
2047
+ This section covers lower-level APIs for working with the navigation mesh structure. Most users won't need these for everyday pathfinding, but they're useful for advanced use cases like understanding the navmesh internals, building custom pathfinding algorithms, or debugging.
2048
+
2049
+ ### `isValidNodeRef`
2050
+
2051
+ ```ts
2052
+ const nodeRef: Nav.NodeRef = 0;
2053
+
2054
+ // true if the node ref is valid, useful to call after updating tiles to validate the reference is still valid
2055
+ const isValid = Nav.isValidNodeRef(navMesh, nodeRef);
2056
+ console.log(isValid);
2057
+ ```
2058
+
2059
+ ```ts
2060
+ /**
2061
+ * Checks if a navigation mesh node reference is valid.
2062
+ * @param navMesh the navigation mesh
2063
+ * @param nodeRef the node reference
2064
+ * @returns true if the node reference is valid, false otherwise
2065
+ */
2066
+ export function isValidNodeRef(navMesh: NavMesh, nodeRef: NodeRef): boolean;
2067
+ ```
2068
+
2069
+ ### `getNodeByRef`
2070
+
2071
+ ```ts
2072
+ const node = Nav.getNodeByRef(navMesh, nodeRef);
2073
+ console.log(node);
2074
+ ```
2075
+
2076
+ ```ts
2077
+ /**
2078
+ * Gets a navigation mesh node by its reference.
2079
+ * Note that navmesh nodes are pooled and may be reused on removing then adding tiles, so do not store node objects.
2080
+ * @param navMesh the navigation mesh
2081
+ * @param nodeRef the node reference
2082
+ * @returns the navigation mesh node
2083
+ */
2084
+ export function getNodeByRef(navMesh: NavMesh, nodeRef: NodeRef);
2085
+ ```
2086
+
2087
+ ### `getNodeByTileAndPoly`
2088
+
2089
+ ```ts
2090
+ const node = Nav.getNodeByTileAndPoly(navMesh, tile, polyIndex);
2091
+ console.log(node);
2092
+ ```
2093
+
2094
+ ```ts
2095
+ /**
2096
+ * Gets a navigation mesh node by its tile and polygon index.
2097
+ * @param navMesh the navigation mesh
2098
+ * @param tile the navigation mesh tile
2099
+ * @param polyIndex the polygon index
2100
+ * @returns the navigation mesh node
2101
+ */
2102
+ export function getNodeByTileAndPoly(navMesh: NavMesh, tile: NavMeshTile, polyIndex: number);
2103
+ ```
2104
+
2105
+ ### `getPolyHeight`
2106
+
2107
+ ```ts
2108
+ const position: Nav.Vec3 = [1, 1, 0];
2109
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
2110
+
2111
+ const nearestPoly = Nav.findNearestPoly(
2112
+ Nav.createFindNearestPolyResult(),
2113
+ navMesh,
2114
+ position,
2115
+ halfExtents,
2116
+ Nav.DEFAULT_QUERY_FILTER,
2117
+ );
2118
+
2119
+ const tileAndPoly = Nav.getTileAndPolyByRef(nearestPoly.nodeRef, navMesh);
2120
+
2121
+ if (nearestPoly.success) {
2122
+ const getPolyHeightResult = Nav.createGetPolyHeightResult();
2123
+ Nav.getPolyHeight(getPolyHeightResult, tileAndPoly.tile!, tileAndPoly.poly!, tileAndPoly.polyIndex, position);
2124
+
2125
+ console.log(getPolyHeightResult.success); // true if a height was found
2126
+ console.log(getPolyHeightResult.height); // the height of the poly at the position
2127
+ }
2128
+ ```
2129
+
2130
+ ```ts
2131
+ /**
2132
+ * Gets the height of a polygon at a given point using detail mesh if available.
2133
+ * @param result The result object to populate
2134
+ * @param tile The tile containing the polygon
2135
+ * @param poly The polygon
2136
+ * @param polyIndex The index of the polygon in the tile
2137
+ * @param pos The position to get height for
2138
+ * @returns The result object with success flag and height
2139
+ */
2140
+ export function getPolyHeight(result: GetPolyHeightResult, tile: NavMeshTile, poly: NavMeshPoly, polyIndex: number, pos: Vec3): GetPolyHeightResult;
2141
+ ```
2142
+
2143
+ ### `getClosestPointOnPoly`
2144
+
2145
+ ```ts
2146
+ const polyRef = findNearestPolyResult.nodeRef;
2147
+ const getClosestPointOnPolyResult = Nav.createGetClosestPointOnPolyResult();
2148
+
2149
+ Nav.getClosestPointOnPoly(getClosestPointOnPolyResult, navMesh, polyRef, position);
2150
+
2151
+ console.log(getClosestPointOnPolyResult.success); // true if a closest point was found
2152
+ console.log(getClosestPointOnPolyResult.isOverPoly); // true if the position was inside the poly
2153
+ console.log(getClosestPointOnPolyResult.position); // the closest point on the poly in world space [x, y, z]
2154
+ ```
2155
+
2156
+ ```ts
2157
+ /**
2158
+ * Gets the closest point on a polygon to a given point
2159
+ * @param result the result object to populate
2160
+ * @param navMesh the navigation mesh
2161
+ * @param nodeRef the polygon node reference
2162
+ * @param position the point to find the closest point to
2163
+ * @returns the result object
2164
+ */
2165
+ export function getClosestPointOnPoly(result: GetClosestPointOnPolyResult, navMesh: NavMesh, nodeRef: NodeRef, position: Vec3): GetClosestPointOnPolyResult;
2166
+ ```
2167
+
2168
+ ### `getClosestPointOnDetailEdges`
2169
+
2170
+ ```ts
2171
+ const position: Nav.Vec3 = [1, 1, 0];
2172
+ const halfExtents: Nav.Vec3 = [0.5, 0.5, 0.5];
2173
+
2174
+ // find the nearest nav mesh poly node to the position
2175
+ const nearestPoly = Nav.findNearestPoly(
2176
+ Nav.createFindNearestPolyResult(),
2177
+ navMesh,
2178
+ position,
2179
+ halfExtents,
2180
+ Nav.DEFAULT_QUERY_FILTER,
2181
+ );
2182
+
2183
+ const tileAndPoly = Nav.getTileAndPolyByRef(nearestPoly.nodeRef, navMesh);
2184
+
2185
+ const closestPoint: Nav.Vec3 = [0, 0, 0];
2186
+ const onlyBoundaryEdges = false;
2187
+
2188
+ const squaredDistance = Nav.getClosestPointOnDetailEdges(
2189
+ closestPoint,
2190
+ tileAndPoly.tile!,
2191
+ tileAndPoly.poly!,
2192
+ tileAndPoly.polyIndex,
2193
+ position,
2194
+ onlyBoundaryEdges,
2195
+ );
2196
+
2197
+ console.log(squaredDistance); // squared distance from position to closest point
2198
+ console.log(closestPoint); // the closest point on the detail edges in world space [x, y, z]
2199
+ ```
2200
+
2201
+ ```ts
2202
+ /**
2203
+ * Gets the closest point on detail mesh edges to a given point
2204
+ * @param tile The tile containing the detail mesh
2205
+ * @param poly The polygon
2206
+ * @param pos The position to find closest point for
2207
+ * @param outClosestPoint Output parameter for the closest point
2208
+ * @param onlyBoundary If true, only consider boundary edges
2209
+ * @returns The squared distance to the closest point
2210
+ * closest point
2211
+ */
2212
+ export function getClosestPointOnDetailEdges(outClosestPoint: Vec3, tile: NavMeshTile, poly: NavMeshPoly, polyIndex: number, pos: Vec3, onlyBoundary: boolean): number;
2213
+ ```
2214
+
2215
+ ### `getPortalPoints`
2216
+
2217
+ ```ts
2218
+ const startNodeRef: Nav.NodeRef = 0; // example poly node ref, usually retrieved from a pathfinding call
2219
+ const endNodeRef: Nav.NodeRef = 0; // example poly node ref, usually retrieved from a pathfinding call
2220
+
2221
+ const left: Nav.Vec3 = [0, 0, 0];
2222
+ const right: Nav.Vec3 = [0, 0, 0];
2223
+
2224
+ const getPortalPointsSuccess = Nav.getPortalPoints(navMesh, startNodeRef, endNodeRef, left, right);
2225
+
2226
+ console.log(getPortalPointsSuccess); // true if the portal points were found
2227
+ console.log('left:', left);
2228
+ console.log('right:', right);
2229
+ ```
2230
+
2231
+ ```ts
2232
+ /**
2233
+ * Retrieves the left and right points of the portal edge between two adjacent polygons.
2234
+ * Or if one of the polygons is an off-mesh connection, returns the connection endpoint for both left and right.
2235
+ */
2236
+ export function getPortalPoints(navMesh: NavMesh, fromNodeRef: NodeRef, toNodeRef: NodeRef, outLeft: Vec3, outRight: Vec3): boolean;
2237
+ ```
2238
+
2239
+ ### `queryPolygons`
2240
+
2241
+ ```ts
2242
+ // find all polys within a box area
2243
+ const bounds: Nav.Box3 = [0, 0, 0, 1, 1, 1];
2244
+
2245
+ const queryPolygonsResult = Nav.queryPolygons(navMesh, bounds, Nav.DEFAULT_QUERY_FILTER);
2246
+
2247
+ console.log(queryPolygonsResult); // array of node refs that overlap the box area
2248
+ ```
2249
+
2250
+ ```ts
2251
+ export function queryPolygons(navMesh: NavMesh, bounds: Box3, filter: QueryFilter): NodeRef[];
2252
+ ```
2253
+
2254
+ ### `queryPolygonsInTile`
2255
+
2256
+ ```ts
2257
+ const tile = Object.values(navMesh.tiles)[0]; // example tile
2258
+ const bounds: Nav.Box3 = tile.bounds;
2259
+
2260
+ const outNodeRefs: Nav.NodeRef[] = [];
2261
+
2262
+ Nav.queryPolygonsInTile(outNodeRefs, navMesh, tile, bounds, Nav.DEFAULT_QUERY_FILTER);
2263
+ ```
2264
+
2265
+ ```ts
2266
+ export function queryPolygonsInTile(out: NodeRef[], navMesh: NavMesh, tile: NavMeshTile, bounds: Box3, filter: QueryFilter): void;
2267
+ ```
2268
+
2269
+ ## Using Externally Created Navigation Meshes
2270
+
2271
+ Although this library provides a robust method of generating navigation meshes from 3D geometry, you can also bring your own navigation meshes if you author them manually, or generate them with another tool.
2272
+
2273
+ You can pass any external polygon data to the `polygonsToNavMeshTilePolys` utility to convert it into the navcat runtime navigation mesh tile format.
2274
+
2275
+ You can also use `polysToTileDetailMesh` to generate a detail mesh for your polygons, or you can provide your own detail triangle mesh if you have height data for your polygons.
2276
+
2277
+ See the "Custom GLTF NavMesh" Example to see how to use an "externally generated" navigation mesh with navcat:
2278
+
2279
+ <table>
2280
+ <tr>
2281
+ <td align="center">
2282
+ <a href="https://navcat.dev/examples#example-custom-gltf-navmesh">
2283
+ <img src="./examples/public/screenshots/example-custom-gltf-navmesh.png" width="200" height="133" style="object-fit:cover;"/><br/>
2284
+ <strong>Custom GLTF NavMesh</strong>
2285
+ </a>
2286
+ </td>
2287
+ </tr>
2288
+ </table>
2289
+
2290
+ ## Saving and Loading NavMeshes
2291
+
2292
+ Because the navigation mesh is a normal JSON-serializable object, you can easily save and load navigation meshes to/from disk, or send them over a network. It is as simple as `JSON.stringify(navMesh)` and `JSON.parse(navMeshJsonString)`, really.
2293
+
2294
+ ## Debug Utilities
2295
+
2296
+ navcat provides graphics-library agnostic debug drawing functions to help visualize the navmesh and related data structures.
2297
+
2298
+ If you are using threejs, you can use the `navcat-zup/three` entrypoint's debug helpers to create threejs objects for visualization, see the [navcat-zup/three section](#navcat-zupthree) below.
2299
+
2300
+ If you are using a different library, you write your own functions to visualize the debug primitives below.
2301
+
2302
+ ```ts
2303
+ const triangleAreaIdsHelper = Nav.createTriangleAreaIdsHelper({ positions, indices }, triAreaIds);
2304
+
2305
+ const heightfieldHelper = Nav.createHeightfieldHelper(heightfield);
2306
+
2307
+ const compactHeightfieldSolidHelper = Nav.createCompactHeightfieldSolidHelper(compactHeightfield);
2308
+
2309
+ const compactHeightfieldDistancesHelper = Nav.createCompactHeightfieldDistancesHelper(compactHeightfield);
2310
+
2311
+ const compactHeightfieldRegionsHelper = Nav.createCompactHeightfieldRegionsHelper(compactHeightfield);
2312
+
2313
+ const rawContoursHelper = Nav.createRawContoursHelper(contourSet);
2314
+
2315
+ const simplifiedContoursHelper = Nav.createSimplifiedContoursHelper(contourSet);
2316
+
2317
+ const polyMeshHelper = Nav.createPolyMeshHelper(polyMesh);
2318
+
2319
+ const polyMeshDetailHelper = Nav.createPolyMeshDetailHelper(polyMeshDetail);
2320
+
2321
+ const navMeshHelper = Nav.createNavMeshHelper(navMesh);
2322
+
2323
+ const navMeshTileHelper = Nav.createNavMeshTileHelper(Object.values(navMesh.tiles)[0]);
2324
+
2325
+ const navMeshPolyHelper = Nav.createNavMeshPolyHelper(navMesh, 0);
2326
+
2327
+ const navMeshTileBvTreeHelper = Nav.createNavMeshTileBvTreeHelper(tile);
2328
+
2329
+ const navMeshBvTreeHelper = Nav.createNavMeshBvTreeHelper(navMesh);
2330
+
2331
+ const navMeshLinksHelper = Nav.createNavMeshLinksHelper(navMesh);
2332
+
2333
+ const navMeshTilePortalsHelper = Nav.createNavMeshTilePortalsHelper(tile);
2334
+
2335
+ const navMeshPortalsHelper = Nav.createNavMeshPortalsHelper(navMesh);
2336
+
2337
+ const findNodePathResult = Nav.findNodePath(navMesh, 0, 0, [1, 1, 0], [8, 8, 0], Nav.DEFAULT_QUERY_FILTER);
2338
+ const searchNodesHelper = Nav.createSearchNodesHelper(findNodePathResult.nodes);
2339
+
2340
+ const navMeshOffMeshConnectionsHelper = Nav.createNavMeshOffMeshConnectionsHelper(navMesh);
2341
+ ```
2342
+
2343
+ ```ts
2344
+ export type DebugPrimitive = DebugTriangles | DebugLines | DebugPoints | DebugBoxes;
2345
+ ```
2346
+
2347
+ ```ts
2348
+ export type DebugTriangles = {
2349
+ type: DebugPrimitiveType.Triangles;
2350
+ positions: number[]; // x,y,z for each vertex
2351
+ colors: number[]; // r,g,b for each vertex
2352
+ indices: number[]; // triangle indices
2353
+ transparent?: boolean;
2354
+ opacity?: number;
2355
+ doubleSided?: boolean;
2356
+ };
2357
+ ```
2358
+
2359
+ ```ts
2360
+ export type DebugLines = {
2361
+ type: DebugPrimitiveType.Lines;
2362
+ positions: number[]; // x,y,z for each line endpoint
2363
+ colors: number[]; // r,g,b for each line endpoint
2364
+ lineWidth?: number;
2365
+ transparent?: boolean;
2366
+ opacity?: number;
2367
+ };
2368
+ ```
2369
+
2370
+ ```ts
2371
+ export type DebugPoints = {
2372
+ type: DebugPrimitiveType.Points;
2373
+ positions: number[]; // x,y,z for each point
2374
+ colors: number[]; // r,g,b for each point
2375
+ size: number;
2376
+ transparent?: boolean;
2377
+ opacity?: number;
2378
+ };
2379
+ ```
2380
+
2381
+ ```ts
2382
+ export type DebugBoxes = {
2383
+ type: DebugPrimitiveType.Boxes;
2384
+ positions: number[]; // x,y,z center for each box
2385
+ colors: number[]; // r,g,b for each box
2386
+ scales: number[]; // sx,sy,sz for each box
2387
+ rotations?: number[]; // qx,qy,qz,qw for each box (optional)
2388
+ transparent?: boolean;
2389
+ opacity?: number;
2390
+ };
2391
+ ```
2392
+
2393
+ ## `navcat-zup/three`
2394
+
2395
+ The `navcat-zup/three` entrypoint provides some utilities to help integrate navcat with threejs.
2396
+
2397
+ Below is a snippet demonstrating how to use `getPositionsAndIndices` to extract geometry from a threejs mesh for navmesh generation, and how to use `createNavMeshHelper` to visualize the generated navmesh in threejs.
2398
+
2399
+ You can find a full list of threejs-specific utilities in the API docs: https://navcat.dev/docs/modules/navcat_three.html
2400
+
2401
+ ```ts
2402
+ import { DEFAULT_QUERY_FILTER, findPath, type Vec3 } from 'navcat-zup';
2403
+ import { generateSoloNavMesh, type SoloNavMeshInput, type SoloNavMeshOptions } from 'navcat-zup/blocks';
2404
+ import { createNavMeshHelper, createSearchNodesHelper, getPositionsAndIndices } from 'navcat-zup/three';
2405
+ import * as THREE from 'three';
2406
+
2407
+ // use z-up in threejs to match navcat-zup's coordinate system
2408
+ THREE.Object3D.DEFAULT_UP.set(0, 0, 1);
2409
+
2410
+ // create a simple threejs scene
2411
+ // PlaneGeometry lies in the XY plane with a +Z normal by default, which is exactly the z-up ground plane
2412
+ const floor = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshStandardMaterial({ color: 0x808080 }));
2413
+
2414
+ const box = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshStandardMaterial({ color: 0x8080ff }));
2415
+ box.position.set(0, 0, 0.5);
2416
+
2417
+ const scene = new THREE.Scene();
2418
+ scene.add(floor);
2419
+ scene.add(box);
2420
+
2421
+ // generation input
2422
+ const [positions, indices] = getPositionsAndIndices([floor, box]);
2423
+
2424
+ const input: SoloNavMeshInput = {
2425
+ positions,
2426
+ indices,
2427
+ };
2428
+
2429
+ // generation options
2430
+ const cellSize = 0.15;
2431
+ const cellHeight = 0.15;
2432
+
2433
+ const walkableRadiusWorld = 0.1;
2434
+ const walkableRadiusVoxels = Math.ceil(walkableRadiusWorld / cellSize);
2435
+ const walkableClimbWorld = 0.5;
2436
+ const walkableClimbVoxels = Math.ceil(walkableClimbWorld / cellHeight);
2437
+ const walkableHeightWorld = 0.25;
2438
+ const walkableHeightVoxels = Math.ceil(walkableHeightWorld / cellHeight);
2439
+ const walkableSlopeAngleDegrees = 45;
2440
+
2441
+ const borderSize = 0;
2442
+ const minRegionArea = 8;
2443
+ const mergeRegionArea = 20;
2444
+
2445
+ const maxSimplificationError = 1.3;
2446
+ const maxEdgeLength = 12;
2447
+
2448
+ const maxVerticesPerPoly = 5;
2449
+
2450
+ const detailSampleDistanceVoxels = 6;
2451
+ const detailSampleDistance = detailSampleDistanceVoxels < 0.9 ? 0 : cellSize * detailSampleDistanceVoxels;
2452
+
2453
+ const detailSampleMaxErrorVoxels = 1;
2454
+ const detailSampleMaxError = cellHeight * detailSampleMaxErrorVoxels;
2455
+
2456
+ const options: SoloNavMeshOptions = {
2457
+ cellSize,
2458
+ cellHeight,
2459
+ walkableRadiusWorld,
2460
+ walkableRadiusVoxels,
2461
+ walkableClimbWorld,
2462
+ walkableClimbVoxels,
2463
+ walkableHeightWorld,
2464
+ walkableHeightVoxels,
2465
+ walkableSlopeAngleDegrees,
2466
+ borderSize,
2467
+ minRegionArea,
2468
+ mergeRegionArea,
2469
+ maxSimplificationError,
2470
+ maxEdgeLength,
2471
+ maxVerticesPerPoly,
2472
+ detailSampleDistance,
2473
+ detailSampleMaxError,
2474
+ };
2475
+
2476
+ // generate a navmesh
2477
+ const result = generateSoloNavMesh(input, options);
2478
+
2479
+ const navMesh = result.navMesh; // the nav mesh
2480
+ const intermediates = result.intermediates; // intermediate data for debugging
2481
+
2482
+ console.log('generated navmesh:', navMesh, intermediates);
2483
+
2484
+ // visualize the navmesh in threejs
2485
+ const navMeshHelper = createNavMeshHelper(navMesh);
2486
+ scene.add(navMeshHelper.object);
2487
+
2488
+ // find a path
2489
+ const start: Vec3 = [-4, -4, 0];
2490
+ const end: Vec3 = [4, 4, 0];
2491
+ const halfExtents: Vec3 = [0.5, 0.5, 0.5];
2492
+
2493
+ const path = findPath(navMesh, start, end, halfExtents, DEFAULT_QUERY_FILTER);
2494
+
2495
+ console.log(
2496
+ 'path:',
2497
+ path.path.map((p) => p.position),
2498
+ );
2499
+
2500
+ // visualise the path points
2501
+ for (const point of path.path) {
2502
+ const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.1), new THREE.MeshStandardMaterial({ color: 0xff0000 }));
2503
+ sphere.position.set(point.position[0], point.position[1], point.position[2]);
2504
+ scene.add(sphere);
2505
+ }
2506
+
2507
+ // visualise the A* search nodes
2508
+ if (path.nodePath) {
2509
+ const searchNodesHelper = createSearchNodesHelper(path.nodePath.nodes);
2510
+ scene.add(searchNodesHelper.object);
2511
+ }
2512
+ ```
2513
+
2514
+ ## How does navcat compare to other libraries?
2515
+
2516
+ **recast-navigation-js**
2517
+
2518
+ recast-navigation-js is a WebAssembly port of the Recast and Detour C++ libraries. It has some advantages in performance, but also some downsides:
2519
+
2520
+ - **Performance**
2521
+ - navcat vs recast-navigation-js benchmarks can be found here, this repository will be maintained as each library is updated: https://github.com/isaac-mason/navcat-vs-recast-navigation-js-bench
2522
+ - recast-navigation-js offers notably better performance for runtime navmesh generation and typically offers better performance for querying
2523
+ - the webassembly initialization for recast-navigation-js is asynchronous, and can take tens of milliseconds (~40ms in benchmarks on M1 macbook pro)
2524
+ - navcat is still very performant for pathfinding, and navmesh generation performance is fast enough for many dynamic generation use cases (all of the examples dynamically generate navmeshes)
2525
+ - wherever possible, with both libraries, pre-generating navmeshes offline is the superior option for performance. if you can pre-generate your navmeshes, the generation performance difference is less relevant
2526
+ - **Bundle Size**
2527
+ - recast-navigation-js has a larger bundle size due to large wasm binary and emscripten glue code, and no support for tree-shaking unused functionality.
2528
+ - navcat is a pure javascript library that is highly tree-shakeable
2529
+ - **Off Mesh Connections**
2530
+ - navcat addresses some limitations of Recast and Detour's off mesh connections features that have been addressed in the closed source Unity version of recastnavigation, but are not present in the open source recastnavigation
2531
+ - off mesh connections can be added and removed dynamically without regenerating tiles, as they do not belong to tiles but are added globally to the navmesh
2532
+ - off mesh connections can be created between any tiles, not just between neighbouring tiles as in Recast/Detour
2533
+ - the navcat crowd API lets you control how agents traverse off mesh connections with animation hooks, the recast-navigation-js crowd API does not support this
2534
+ - **Query Filters**
2535
+ - query filters in navcat are regular javascript functions, no need to force your logic into an area cost table or includeFlags/excludeFlags
2536
+ - very useful functionality for c++ users such as virtual functions for custom query filters are impractical to expose to javascript due to the performance cost of wasm to javascript calls
2537
+ - **External NavMeshes**
2538
+ - recast-navigation-js currently has no support for using externally created navigation meshes
2539
+ - navcat supports pathfinding with externally provided navigation mesh polys, as it has decoupled the navmesh generation format and the runtime navmesh format.
2540
+ - **Extensibility**
2541
+ - it is impractical with recast-navigation-js to interact with the navmesh directly for anything that needs to be performant, some users have had to fork recast-navigation-js and modify Recast and Detour to suit their needs, as users of the c++ library would do
2542
+ - navcat's data structures are transparent, it is easy to write a custom graph traversal algorithm in userland if your use case requires it
2543
+ - **Manual Memory Management**
2544
+ - recast-navigation-js requires some manual memory management, users must remember to call destroy() on some objects to avoid memory leaks
2545
+ - navcat's data structures are regular javascript objects, no manual memory management is required
2546
+ - **Introspection and Debuggability**
2547
+ - recast-navigation-js's data structures are c++ wrapped objects, getting useful wasm stack traces requires a debug build
2548
+ - navcat doesn't use wasm, step into regular javascript to debug if you have to
2549
+ - **Future outlook**
2550
+ - navcat has greater flexibility to evolve faster, and to cater more strongly to javascript
2551
+ - recast-navigation-js will remain maintained, and will continue to track the upstream recastnavigation Recast/Detour library as it recieves new development
2552
+
2553
+ **three-pathfinding, yuka**
2554
+
2555
+ Both three-pathfinding (for three.js) and yuka provide navigation mesh querying capabilities, but neither provide navigation mesh generation. This library also provides support for dynamic off mesh connections for implementing traversal actions like jumping gaps, climbing ladders, teleporting, etc.
2556
+
2557
+ ## Community
2558
+
2559
+ **Used in**
2560
+
2561
+ - [manablade.com](https://manablade.com)
2562
+ - ... add your project!
2563
+
2564
+ **WebGameDev Discord**
2565
+
2566
+ Join the WebGameDev Discord to discuss navcat with other users and contributors, ask questions, and share your projects!
2567
+
2568
+ https://www.webgamedev.com/discord
2569
+
2570
+ ## Acknowledgements
2571
+
2572
+ - This library is heavily inspired by the recastnavigation library: https://github.com/recastnavigation/recastnavigation
2573
+ - Although navcat is not a direct port of recastnavigation, the core navigation mesh generation approach is based on the recastnavigation library's voxelization-based approach.
2574
+ - Shoutout to @verekia for the cute name idea :)