@vgai/engine 0.5.36 → 0.5.37

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.
@@ -12,7 +12,8 @@
12
12
  import { NavMeshHelper } from '@recast-navigation/three';
13
13
  import type { CrowdAgent } from 'recast-navigation';
14
14
  import { Crowd } from 'recast-navigation';
15
- import type { Mesh, Scene } from 'three';
15
+ import type { Scene } from 'three';
16
+ import { Mesh } from 'three';
16
17
  export type { CrowdAgent };
17
18
  export interface Vector3Like {
18
19
  x: number;
@@ -39,6 +40,12 @@ export interface CrowdAgentSnapshot {
39
40
  corners: Vector3Like[];
40
41
  }
41
42
  export interface NavMeshBuildParams {
43
+ /** Force bounded tiled generation for imported large-world scenes whose instanced render
44
+ * census cannot be inferred from the source BufferGeometry counts alone. */
45
+ tiled?: boolean | undefined;
46
+ /** Recast tile width/height in voxels. Unity's NavMeshSurface default is 256; keeping tiles at
47
+ * that scale also avoids thousands of empty-tile allocations across sparse imported worlds. */
48
+ tileSize?: number | undefined;
42
49
  cellSize?: number | undefined;
43
50
  cellHeight?: number | undefined;
44
51
  walkableSlopeAngle?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/ai/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAsB,MAAM,0BAA0B,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAoD,MAAM,mBAAmB,CAAC;AAC5F,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGzC,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;iEACiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;8EAE0E;IAC1E,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC;+EAC2E;IAC3E,MAAM,EAAE,WAAW,CAAC;IACpB;;mEAE+D;IAC/D,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAKD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAID,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAIpD;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAA8B;IAE5C,2EAA2E;IAC3E,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO;IA4BrE;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAoBrB,iFAAiF;IACjF,UAAU,IAAI,OAAO;IAIrB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE;IAkB7D,kDAAkD;IAClD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK;IAMrC,sDAAsD;IACtD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI7B;;;;OAIG;IACH,cAAc,IAAI,kBAAkB,EAAE;IAwBtC,iGAAiG;IACjG,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,GAAG,IAAI;IAShD,sDAAsD;IACtD,UAAU,IAAI,UAAU;IAKxB,6DAA6D;IAC7D,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAQpC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAM5B"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/ai/navigation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAA2C,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAoD,MAAM,mBAAmB,CAAC;AAC5F,OAAO,KAAK,EAAkB,KAAK,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAA0B,IAAI,EAAE,MAAM,OAAO,CAAC;AAGrD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;iEACiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,WAAW,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;8EAE0E;IAC1E,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC;+EAC2E;IAC3E,MAAM,EAAE,WAAW,CAAC;IACpB;;mEAE+D;IAC/D,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAKD,MAAM,WAAW,kBAAkB;IACjC;gFAC4E;IAC5E,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B;mGAC+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAID,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAIpD;AA2ED,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,MAAM,CAA8B;IAE5C,2EAA2E;IAC3E,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO;IA+CrE;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAoBrB,iFAAiF;IACjF,UAAU,IAAI,OAAO;IAIrB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,GAAG,WAAW,EAAE;IAkB7D,kDAAkD;IAClD,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK;IAMrC,sDAAsD;IACtD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI7B;;;;OAIG;IACH,cAAc,IAAI,kBAAkB,EAAE;IAwBtC,iGAAiG;IACjG,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,GAAG,IAAI;IAShD,sDAAsD;IACtD,UAAU,IAAI,UAAU;IAKxB,6DAA6D;IAC7D,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAQpC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;CAM5B"}
@@ -9,8 +9,9 @@
9
9
  * const crowd = nav.createCrowd(10);
10
10
  * // In game loop: nav.updateCrowd(dt);
11
11
  */
12
- import { NavMeshHelper, threeToSoloNavMesh } from '@recast-navigation/three';
12
+ import { NavMeshHelper, threeToSoloNavMesh, threeToTiledNavMesh } from '@recast-navigation/three';
13
13
  import { Crowd, exportNavMesh, importNavMesh, init, NavMeshQuery } from 'recast-navigation';
14
+ import { InstancedMesh, Matrix4, Mesh } from 'three';
14
15
  import { DEFAULTS } from '../defaults';
15
16
  /** Detour `CrowdAgentState` values, named. */
16
17
  const AGENT_STATES = ['invalid', 'walking', 'offmesh'];
@@ -21,6 +22,74 @@ export async function initNavigation() {
21
22
  await init();
22
23
  wasmInitialized = true;
23
24
  }
25
+ /**
26
+ * `@recast-navigation/three` applies each mesh's world matrix to its positions but copies the
27
+ * source indices unchanged. A mirrored world matrix therefore reverses every triangle and can
28
+ * make a visibly upward floor entirely unwalkable. Seat a winding-corrected geometry on an
29
+ * equivalent root mesh for the bake; the game keeps its own native mesh untouched.
30
+ */
31
+ function reverseTriangleWinding(geometry) {
32
+ const index = geometry.getIndex();
33
+ if (index === null) {
34
+ const position = geometry.getAttribute('position').clone();
35
+ for (let vertex = 0; vertex + 2 < position.count; vertex += 3) {
36
+ for (let component = 0; component < position.itemSize; component += 1) {
37
+ const middle = position.getComponent(vertex + 1, component);
38
+ position.setComponent(vertex + 1, component, position.getComponent(vertex + 2, component));
39
+ position.setComponent(vertex + 2, component, middle);
40
+ }
41
+ }
42
+ geometry.setAttribute('position', position);
43
+ return;
44
+ }
45
+ for (let triangle = 0; triangle + 2 < index.count; triangle += 3) {
46
+ const middle = index.getX(triangle + 1);
47
+ index.setX(triangle + 1, index.getX(triangle + 2));
48
+ index.setX(triangle + 2, middle);
49
+ }
50
+ index.needsUpdate = true;
51
+ }
52
+ function recastInputMeshes(meshes) {
53
+ const temporaryGeometries = [];
54
+ const mirroredGeometries = new WeakMap();
55
+ const geometryForMatrix = (source, matrix) => {
56
+ if (matrix.determinant() >= 0)
57
+ return source;
58
+ const cached = mirroredGeometries.get(source);
59
+ if (cached !== undefined)
60
+ return cached;
61
+ const geometry = source.clone();
62
+ reverseTriangleWinding(geometry);
63
+ mirroredGeometries.set(source, geometry);
64
+ temporaryGeometries.push(geometry);
65
+ return geometry;
66
+ };
67
+ const proxyAt = (geometry, matrix) => {
68
+ const proxy = new Mesh(geometryForMatrix(geometry, matrix));
69
+ // The proxy has no parent, so its local matrix is the source mesh's complete world matrix.
70
+ // The recast bridge calls updateMatrixWorld() itself; disabling auto-update preserves it.
71
+ proxy.matrixAutoUpdate = false;
72
+ proxy.matrix.copy(matrix);
73
+ proxy.matrixWorldNeedsUpdate = true;
74
+ return proxy;
75
+ };
76
+ const corrected = [];
77
+ const instanceMatrix = new Matrix4();
78
+ const worldMatrix = new Matrix4();
79
+ for (const mesh of meshes) {
80
+ mesh.updateWorldMatrix(true, false);
81
+ if (mesh instanceof InstancedMesh) {
82
+ for (let instance = 0; instance < mesh.count; instance += 1) {
83
+ mesh.getMatrixAt(instance, instanceMatrix);
84
+ worldMatrix.multiplyMatrices(mesh.matrixWorld, instanceMatrix);
85
+ corrected.push(proxyAt(mesh.geometry, worldMatrix));
86
+ }
87
+ continue;
88
+ }
89
+ corrected.push(mesh.matrixWorld.determinant() >= 0 ? mesh : proxyAt(mesh.geometry, mesh.matrixWorld));
90
+ }
91
+ return { meshes: corrected, temporaryGeometries };
92
+ }
24
93
  export class NavMeshManager {
25
94
  navMesh = null;
26
95
  query = null;
@@ -29,7 +98,13 @@ export class NavMeshManager {
29
98
  /** Build navmesh from Three.js Mesh objects (grounds, obstacles, etc.). */
30
99
  buildFromMeshes(meshes, params) {
31
100
  const d = DEFAULTS.navigation;
32
- const result = threeToSoloNavMesh(meshes, {
101
+ const input = recastInputMeshes(meshes);
102
+ const triangleCount = input.meshes.reduce((total, mesh) => {
103
+ const index = mesh.geometry.getIndex();
104
+ const vertices = mesh.geometry.getAttribute('position').count;
105
+ return total + Math.floor((index?.count ?? vertices) / 3);
106
+ }, 0);
107
+ const config = {
33
108
  cs: params?.cellSize ?? d.cellSize,
34
109
  ch: params?.cellHeight ?? d.cellHeight,
35
110
  walkableSlopeAngle: params?.walkableSlopeAngle ?? d.walkableSlopeAngle,
@@ -44,7 +119,22 @@ export class NavMeshManager {
44
119
  maxVertsPerPoly: 6,
45
120
  detailSampleDist: 6,
46
121
  detailSampleMaxError: 1,
47
- });
122
+ };
123
+ // A solo heightfield allocates for the whole scene at once. Large imported Unity scenes can
124
+ // exceed Recast's WASM address space during rasterization even though every individual mesh
125
+ // is ordinary-sized. Tiled generation is the library's native large-world path and keeps the
126
+ // same NavMesh result/API while bounding each rasterization allocation.
127
+ let result;
128
+ try {
129
+ result =
130
+ params?.tiled === true || input.meshes.length > 256 || triangleCount > 10_000
131
+ ? threeToTiledNavMesh(input.meshes, { ...config, tileSize: params?.tileSize ?? 256 })
132
+ : threeToSoloNavMesh(input.meshes, config);
133
+ }
134
+ finally {
135
+ for (const geometry of input.temporaryGeometries)
136
+ geometry.dispose();
137
+ }
48
138
  if (!result.success || !result.navMesh)
49
139
  return false;
50
140
  // Free the previous recast objects before replacing them (WASM heap leak).
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.5.36",
5
+ "version": "0.5.37",
6
6
  "description": "Readable TypeScript game engine and universal host for Three.js, PixiJS, and React games.",
7
7
  "keywords": [
8
8
  "game-engine",
@@ -10,10 +10,11 @@
10
10
  * // In game loop: nav.updateCrowd(dt);
11
11
  */
12
12
 
13
- import { NavMeshHelper, threeToSoloNavMesh } from '@recast-navigation/three';
13
+ import { NavMeshHelper, threeToSoloNavMesh, threeToTiledNavMesh } from '@recast-navigation/three';
14
14
  import type { CrowdAgent, NavMesh } from 'recast-navigation';
15
15
  import { Crowd, exportNavMesh, importNavMesh, init, NavMeshQuery } from 'recast-navigation';
16
- import type { Mesh, Scene } from 'three';
16
+ import type { BufferGeometry, Scene } from 'three';
17
+ import { InstancedMesh, Matrix4, Mesh } from 'three';
17
18
  import { DEFAULTS } from '../defaults';
18
19
 
19
20
  export type { CrowdAgent };
@@ -48,6 +49,12 @@ export interface CrowdAgentSnapshot {
48
49
  const AGENT_STATES = ['invalid', 'walking', 'offmesh'] as const;
49
50
 
50
51
  export interface NavMeshBuildParams {
52
+ /** Force bounded tiled generation for imported large-world scenes whose instanced render
53
+ * census cannot be inferred from the source BufferGeometry counts alone. */
54
+ tiled?: boolean | undefined;
55
+ /** Recast tile width/height in voxels. Unity's NavMeshSurface default is 256; keeping tiles at
56
+ * that scale also avoids thousands of empty-tile allocations across sparse imported worlds. */
57
+ tileSize?: number | undefined;
51
58
  cellSize?: number | undefined;
52
59
  cellHeight?: number | undefined;
53
60
  walkableSlopeAngle?: number | undefined;
@@ -68,6 +75,79 @@ export async function initNavigation(): Promise<void> {
68
75
  wasmInitialized = true;
69
76
  }
70
77
 
78
+ /**
79
+ * `@recast-navigation/three` applies each mesh's world matrix to its positions but copies the
80
+ * source indices unchanged. A mirrored world matrix therefore reverses every triangle and can
81
+ * make a visibly upward floor entirely unwalkable. Seat a winding-corrected geometry on an
82
+ * equivalent root mesh for the bake; the game keeps its own native mesh untouched.
83
+ */
84
+ function reverseTriangleWinding(geometry: BufferGeometry): void {
85
+ const index = geometry.getIndex();
86
+ if (index === null) {
87
+ const position = geometry.getAttribute('position').clone();
88
+ for (let vertex = 0; vertex + 2 < position.count; vertex += 3) {
89
+ for (let component = 0; component < position.itemSize; component += 1) {
90
+ const middle = position.getComponent(vertex + 1, component);
91
+ position.setComponent(vertex + 1, component, position.getComponent(vertex + 2, component));
92
+ position.setComponent(vertex + 2, component, middle);
93
+ }
94
+ }
95
+ geometry.setAttribute('position', position);
96
+ return;
97
+ }
98
+ for (let triangle = 0; triangle + 2 < index.count; triangle += 3) {
99
+ const middle = index.getX(triangle + 1);
100
+ index.setX(triangle + 1, index.getX(triangle + 2));
101
+ index.setX(triangle + 2, middle);
102
+ }
103
+ index.needsUpdate = true;
104
+ }
105
+
106
+ function recastInputMeshes(meshes: Mesh[]): {
107
+ readonly meshes: Mesh[];
108
+ readonly temporaryGeometries: BufferGeometry[];
109
+ } {
110
+ const temporaryGeometries: BufferGeometry[] = [];
111
+ const mirroredGeometries = new WeakMap<BufferGeometry, BufferGeometry>();
112
+ const geometryForMatrix = (source: BufferGeometry, matrix: Matrix4): BufferGeometry => {
113
+ if (matrix.determinant() >= 0) return source;
114
+ const cached = mirroredGeometries.get(source);
115
+ if (cached !== undefined) return cached;
116
+ const geometry = source.clone();
117
+ reverseTriangleWinding(geometry);
118
+ mirroredGeometries.set(source, geometry);
119
+ temporaryGeometries.push(geometry);
120
+ return geometry;
121
+ };
122
+ const proxyAt = (geometry: BufferGeometry, matrix: Matrix4): Mesh => {
123
+ const proxy = new Mesh(geometryForMatrix(geometry, matrix));
124
+ // The proxy has no parent, so its local matrix is the source mesh's complete world matrix.
125
+ // The recast bridge calls updateMatrixWorld() itself; disabling auto-update preserves it.
126
+ proxy.matrixAutoUpdate = false;
127
+ proxy.matrix.copy(matrix);
128
+ proxy.matrixWorldNeedsUpdate = true;
129
+ return proxy;
130
+ };
131
+ const corrected: Mesh[] = [];
132
+ const instanceMatrix = new Matrix4();
133
+ const worldMatrix = new Matrix4();
134
+ for (const mesh of meshes) {
135
+ mesh.updateWorldMatrix(true, false);
136
+ if (mesh instanceof InstancedMesh) {
137
+ for (let instance = 0; instance < mesh.count; instance += 1) {
138
+ mesh.getMatrixAt(instance, instanceMatrix);
139
+ worldMatrix.multiplyMatrices(mesh.matrixWorld, instanceMatrix);
140
+ corrected.push(proxyAt(mesh.geometry, worldMatrix));
141
+ }
142
+ continue;
143
+ }
144
+ corrected.push(
145
+ mesh.matrixWorld.determinant() >= 0 ? mesh : proxyAt(mesh.geometry, mesh.matrixWorld),
146
+ );
147
+ }
148
+ return { meshes: corrected, temporaryGeometries };
149
+ }
150
+
71
151
  export class NavMeshManager {
72
152
  private navMesh: NavMesh | null = null;
73
153
  private query: NavMeshQuery | null = null;
@@ -77,7 +157,13 @@ export class NavMeshManager {
77
157
  /** Build navmesh from Three.js Mesh objects (grounds, obstacles, etc.). */
78
158
  buildFromMeshes(meshes: Mesh[], params?: NavMeshBuildParams): boolean {
79
159
  const d = DEFAULTS.navigation;
80
- const result = threeToSoloNavMesh(meshes, {
160
+ const input = recastInputMeshes(meshes);
161
+ const triangleCount = input.meshes.reduce((total, mesh) => {
162
+ const index = mesh.geometry.getIndex();
163
+ const vertices = mesh.geometry.getAttribute('position').count;
164
+ return total + Math.floor((index?.count ?? vertices) / 3);
165
+ }, 0);
166
+ const config = {
81
167
  cs: params?.cellSize ?? d.cellSize,
82
168
  ch: params?.cellHeight ?? d.cellHeight,
83
169
  walkableSlopeAngle: params?.walkableSlopeAngle ?? d.walkableSlopeAngle,
@@ -92,7 +178,20 @@ export class NavMeshManager {
92
178
  maxVertsPerPoly: 6,
93
179
  detailSampleDist: 6,
94
180
  detailSampleMaxError: 1,
95
- });
181
+ };
182
+ // A solo heightfield allocates for the whole scene at once. Large imported Unity scenes can
183
+ // exceed Recast's WASM address space during rasterization even though every individual mesh
184
+ // is ordinary-sized. Tiled generation is the library's native large-world path and keeps the
185
+ // same NavMesh result/API while bounding each rasterization allocation.
186
+ let result: ReturnType<typeof threeToSoloNavMesh> | ReturnType<typeof threeToTiledNavMesh>;
187
+ try {
188
+ result =
189
+ params?.tiled === true || input.meshes.length > 256 || triangleCount > 10_000
190
+ ? threeToTiledNavMesh(input.meshes, { ...config, tileSize: params?.tileSize ?? 256 })
191
+ : threeToSoloNavMesh(input.meshes, config);
192
+ } finally {
193
+ for (const geometry of input.temporaryGeometries) geometry.dispose();
194
+ }
96
195
 
97
196
  if (!result.success || !result.navMesh) return false;
98
197