@jgengine/core 0.18.1 → 0.19.0

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 (220) hide show
  1. package/CHANGELOG.md +158 -7
  2. package/dist/ai/decisionGraph.d.ts +48 -4
  3. package/dist/ai/decisionGraph.js +153 -23
  4. package/dist/ai/groupAssist.d.ts +10 -0
  5. package/dist/ai/groupAssist.js +11 -0
  6. package/dist/ai/pursuit.d.ts +1 -1
  7. package/dist/ai/pursuit.js +1 -1
  8. package/dist/ai/threat.d.ts +8 -0
  9. package/dist/ai/threat.js +10 -0
  10. package/dist/anim/animGraph.d.ts +11 -0
  11. package/dist/anim/animGraph.js +128 -0
  12. package/dist/anim/boneTexture.d.ts +55 -0
  13. package/dist/anim/boneTexture.js +43 -0
  14. package/dist/anim/footPlacement.d.ts +57 -0
  15. package/dist/anim/footPlacement.js +67 -0
  16. package/dist/anim/locomotionGraph.d.ts +9 -0
  17. package/dist/anim/locomotionGraph.js +29 -0
  18. package/dist/audio/audioFalloff.d.ts +2 -0
  19. package/dist/audio/doppler.d.ts +21 -0
  20. package/dist/audio/doppler.js +32 -0
  21. package/dist/audio/engineLayers.d.ts +83 -0
  22. package/dist/audio/engineLayers.js +115 -0
  23. package/dist/combat/abilityKit.d.ts +1 -0
  24. package/dist/combat/abilityKit.js +1 -0
  25. package/dist/combat/death.d.ts +7 -0
  26. package/dist/combat/shotOrigin.d.ts +6 -0
  27. package/dist/combat/shotOrigin.js +6 -0
  28. package/dist/combat/weaponFeedback.d.ts +36 -0
  29. package/dist/combat/weaponFeedback.js +42 -0
  30. package/dist/combat/weaponHandling.d.ts +161 -0
  31. package/dist/combat/weaponHandling.js +202 -0
  32. package/dist/combat/weaponPresentation.d.ts +115 -0
  33. package/dist/combat/weaponPresentation.js +96 -0
  34. package/dist/devtools/warnOnce.d.ts +8 -0
  35. package/dist/devtools/warnOnce.js +18 -0
  36. package/dist/editor/document.js +3 -1
  37. package/dist/editor/environment.js +8 -0
  38. package/dist/editor/types.d.ts +4 -0
  39. package/dist/faction/reputation.d.ts +6 -0
  40. package/dist/faction/reputation.js +11 -0
  41. package/dist/game/cameraConfig.d.ts +51 -1
  42. package/dist/game/events.d.ts +6 -0
  43. package/dist/game/lootTable.d.ts +4 -0
  44. package/dist/game/lootTable.js +16 -0
  45. package/dist/game/playableGame.d.ts +84 -17
  46. package/dist/game/toasts.d.ts +8 -0
  47. package/dist/game/toasts.js +7 -0
  48. package/dist/game/vfxInstance.d.ts +9 -0
  49. package/dist/game/vfxInstance.js +12 -1
  50. package/dist/game/viewports.d.ts +43 -0
  51. package/dist/game/viewports.js +46 -0
  52. package/dist/game/worldItem.d.ts +5 -1
  53. package/dist/game/worldItem.js +0 -1
  54. package/dist/input/actionBindings.d.ts +15 -1
  55. package/dist/input/actionBindings.js +14 -3
  56. package/dist/input/actionContexts.d.ts +25 -1
  57. package/dist/input/actionContexts.js +82 -25
  58. package/dist/input/axisInput.d.ts +4 -0
  59. package/dist/input/axisInput.js +4 -0
  60. package/dist/input/axisShaper.d.ts +73 -0
  61. package/dist/input/axisShaper.js +105 -0
  62. package/dist/input/gamepadModel.d.ts +36 -2
  63. package/dist/input/gamepadModel.js +69 -30
  64. package/dist/input/haptics.d.ts +50 -0
  65. package/dist/input/haptics.js +74 -0
  66. package/dist/input/inputBuffer.d.ts +40 -0
  67. package/dist/input/inputBuffer.js +59 -0
  68. package/dist/input/lookChannel.d.ts +10 -0
  69. package/dist/input/lookChannel.js +6 -0
  70. package/dist/input/pointer.d.ts +2 -0
  71. package/dist/input/pointer.js +4 -0
  72. package/dist/item/affix.d.ts +6 -0
  73. package/dist/item/affix.js +6 -0
  74. package/dist/meta/changelog.d.ts +1 -1
  75. package/dist/meta/changelog.js +112 -1
  76. package/dist/movement/glideModel.d.ts +3 -0
  77. package/dist/movement/glideModel.js +6 -0
  78. package/dist/movement/grappleSwing.d.ts +2 -0
  79. package/dist/movement/grappleSwing.js +5 -1
  80. package/dist/movement/movementModel.d.ts +39 -0
  81. package/dist/movement/movementModel.js +62 -19
  82. package/dist/movement/movementProbe.d.ts +51 -0
  83. package/dist/movement/movementProbe.js +119 -0
  84. package/dist/movement/playerMovement.d.ts +21 -2
  85. package/dist/movement/playerMovement.js +89 -6
  86. package/dist/movement/solidObstacles.d.ts +9 -1
  87. package/dist/movement/solidObstacles.js +44 -2
  88. package/dist/movement/voxelController.js +7 -7
  89. package/dist/nav/navFromEnvironment.d.ts +9 -3
  90. package/dist/nav/navFromEnvironment.js +18 -12
  91. package/dist/nav/navMesh.d.ts +43 -2
  92. package/dist/nav/navMesh.js +534 -128
  93. package/dist/physics/aircraftDynamics.d.ts +390 -0
  94. package/dist/physics/aircraftDynamics.js +704 -0
  95. package/dist/physics/boatDynamics.d.ts +116 -0
  96. package/dist/physics/boatDynamics.js +160 -0
  97. package/dist/physics/drivableVehicle.d.ts +22 -8
  98. package/dist/physics/drivableVehicle.js +4 -2
  99. package/dist/physics/flightDynamics.d.ts +21 -4
  100. package/dist/physics/flightDynamics.js +28 -5
  101. package/dist/physics/handlingProbe.d.ts +249 -0
  102. package/dist/physics/handlingProbe.js +606 -0
  103. package/dist/physics/kinematicVehicle.d.ts +32 -2
  104. package/dist/physics/kinematicVehicle.js +15 -0
  105. package/dist/physics/physicsBackend.d.ts +12 -0
  106. package/dist/physics/physicsBackendConformance.js +34 -0
  107. package/dist/physics/physicsWorldBackend.d.ts +1 -0
  108. package/dist/physics/physicsWorldBackend.js +33 -0
  109. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  110. package/dist/physics/vehicleBackendLink.js +102 -0
  111. package/dist/physics/vehicleDynamics.d.ts +374 -0
  112. package/dist/physics/vehicleDynamics.js +737 -0
  113. package/dist/physics/worldColliders.d.ts +1 -1
  114. package/dist/physics/worldColliders.js +35 -3
  115. package/dist/puzzle/cellGrid.d.ts +15 -9
  116. package/dist/puzzle/cellGrid.js +13 -9
  117. package/dist/render/lookPreset.d.ts +9 -2
  118. package/dist/render/lookPreset.js +29 -2
  119. package/dist/render/postProcessing.d.ts +30 -0
  120. package/dist/render/postProcessing.js +1 -1
  121. package/dist/runtime/cameraDirector.d.ts +17 -3
  122. package/dist/runtime/cameraDirector.js +25 -0
  123. package/dist/runtime/commandInput.d.ts +37 -0
  124. package/dist/runtime/commandInput.js +58 -0
  125. package/dist/runtime/commandRunner.d.ts +27 -0
  126. package/dist/runtime/commandRunner.js +31 -3
  127. package/dist/runtime/context/combat.d.ts +1 -0
  128. package/dist/runtime/context/combat.js +2 -1
  129. package/dist/runtime/context/deathLoot.d.ts +2 -0
  130. package/dist/runtime/context/deathLoot.js +1 -0
  131. package/dist/runtime/gameContext.js +7 -0
  132. package/dist/runtime/gameContextTypes.d.ts +19 -3
  133. package/dist/runtime/gameRuntime.d.ts +3 -1
  134. package/dist/runtime/gameRuntime.js +4 -0
  135. package/dist/runtime/inputSnapshot.d.ts +6 -0
  136. package/dist/runtime/inputSnapshot.js +10 -0
  137. package/dist/runtime/localPlayers.d.ts +73 -0
  138. package/dist/runtime/localPlayers.js +119 -0
  139. package/dist/runtime/transport.d.ts +9 -0
  140. package/dist/runtime/transport.js +7 -1
  141. package/dist/scene/behaviorRuntime.d.ts +12 -2
  142. package/dist/scene/behaviorRuntime.js +30 -14
  143. package/dist/scene/behaviors.d.ts +5 -1
  144. package/dist/scene/sceneKinds.d.ts +9 -0
  145. package/dist/scene/vehicleSeat.d.ts +5 -1
  146. package/dist/scene/vehicleSeat.js +5 -1
  147. package/dist/sensor/concealment.d.ts +6 -0
  148. package/dist/sensor/concealment.js +8 -0
  149. package/dist/sensor/freezeMonitor.d.ts +6 -0
  150. package/dist/sensor/freezeMonitor.js +8 -0
  151. package/dist/sensor/perception.d.ts +11 -1
  152. package/dist/sensor/perception.js +57 -10
  153. package/dist/sensor/recordingBuffer.d.ts +3 -0
  154. package/dist/sensor/recordingBuffer.js +8 -0
  155. package/dist/session/roundState.d.ts +4 -0
  156. package/dist/session/roundState.js +11 -0
  157. package/dist/settings/frameRateLimit.d.ts +16 -0
  158. package/dist/settings/frameRateLimit.js +37 -0
  159. package/dist/settings/graphicsProfile.d.ts +2 -0
  160. package/dist/settings/graphicsProfile.js +12 -0
  161. package/dist/settings/graphicsSettings.d.ts +40 -0
  162. package/dist/settings/graphicsSettings.js +81 -0
  163. package/dist/settings/settingsModel.d.ts +9 -1
  164. package/dist/settings/settingsModel.js +10 -0
  165. package/dist/stats/eventMeter.d.ts +1 -1
  166. package/dist/stats/eventMeter.js +1 -1
  167. package/dist/vfx/feedbackMixer.d.ts +61 -0
  168. package/dist/vfx/feedbackMixer.js +106 -0
  169. package/dist/visibility/simulationCulling.d.ts +8 -1
  170. package/dist/visibility/simulationCulling.js +7 -0
  171. package/dist/visibility/spatialIndex.d.ts +21 -0
  172. package/dist/visibility/spatialIndex.js +16 -0
  173. package/dist/world/authoredSolids.d.ts +46 -0
  174. package/dist/world/authoredSolids.js +75 -0
  175. package/dist/world/buildingGenerator.js +2 -2
  176. package/dist/world/buildingKit.d.ts +3 -1
  177. package/dist/world/buildings.d.ts +34 -2
  178. package/dist/world/buildings.js +11 -1
  179. package/dist/world/cityKind.d.ts +7 -0
  180. package/dist/world/cityKind.js +52 -1
  181. package/dist/world/features.d.ts +29 -2
  182. package/dist/world/features.js +6 -0
  183. package/dist/world/footprintGrid.d.ts +7 -0
  184. package/dist/world/footprintGrid.js +16 -0
  185. package/dist/world/interiors.d.ts +1 -0
  186. package/dist/world/interiors.js +5 -3
  187. package/dist/world/lod.d.ts +6 -0
  188. package/dist/world/lod.js +8 -0
  189. package/dist/world/terraform.d.ts +8 -0
  190. package/dist/world/terraform.js +7 -0
  191. package/dist/world/walls.d.ts +7 -0
  192. package/dist/world/walls.js +7 -0
  193. package/dist/world/worldSolids.d.ts +84 -0
  194. package/dist/world/worldSolids.js +240 -0
  195. package/dist/world.d.ts +2 -2
  196. package/dist/world.js +1 -1
  197. package/package.json +1 -1
  198. package/skills/ce-handoff/SKILL.md +1 -1
  199. package/skills/game-design/SKILL.md +2 -2
  200. package/skills/jgengine/SKILL.md +7 -5
  201. package/skills/jgengine/api.md +118 -89
  202. package/skills/jgengine/capabilities.md +28 -0
  203. package/skills/jgengine/recipes/minimal-game.md +3 -0
  204. package/skills/jgengine-combat/SKILL.md +5 -0
  205. package/skills/jgengine-combat/api.md +138 -103
  206. package/skills/jgengine-combat/capabilities.md +29 -1
  207. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  208. package/skills/jgengine-gameplay/api.md +462 -436
  209. package/skills/jgengine-gameplay/capabilities.md +24 -0
  210. package/skills/jgengine-gameplay/reference-systems.md +4 -0
  211. package/skills/jgengine-world/SKILL.md +15 -3
  212. package/skills/jgengine-world/api.md +873 -626
  213. package/skills/jgengine-world/capabilities.md +130 -6
  214. package/skills/jgengine-world/recipes/character-animation.md +39 -0
  215. package/skills/jgengine-world/recipes/controls.md +44 -0
  216. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  217. package/skills/jgengine-world/recipes/npc-senses.md +43 -0
  218. package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
  219. package/skills/jgengine-world/reference.md +31 -2
  220. package/skills/jgengine-world/visibility.md +112 -0
@@ -1,164 +1,570 @@
1
- function vertex(mesh, index) {
2
- return [mesh.verts[index * 3] ?? 0, mesh.verts[index * 3 + 1] ?? 0, mesh.verts[index * 3 + 2] ?? 0];
1
+ const DEFAULT_MAX_NODES = 2048;
2
+ const EPS = 1e-9;
3
+ const preparedCache = new WeakMap();
4
+ function vx(mesh, i) { return mesh.verts[i * 3] ?? 0; }
5
+ function vy(mesh, i) { return mesh.verts[i * 3 + 1] ?? 0; }
6
+ function vz(mesh, i) { return mesh.verts[i * 3 + 2] ?? 0; }
7
+ function edgeKey(a, b, vertexCount) {
8
+ return a < b ? a * vertexCount + b : b * vertexCount + a;
3
9
  }
4
- function center(mesh, poly) {
5
- const result = [0, 0, 0];
6
- for (const index of poly) {
7
- const point = vertex(mesh, index);
8
- result[0] += point[0];
9
- result[1] += point[1];
10
- result[2] += point[2];
10
+ function prepare(mesh) {
11
+ const cached = preparedCache.get(mesh);
12
+ if (cached !== undefined)
13
+ return cached;
14
+ const polyCount = mesh.polys.length;
15
+ const vertexCount = Math.max(1, Math.floor(mesh.verts.length / 3));
16
+ const owners = new Map();
17
+ for (let p = 0; p < polyCount; p += 1) {
18
+ const poly = mesh.polys[p];
19
+ for (let i = 0; i < poly.length; i += 1) {
20
+ const key = edgeKey(poly[i], poly[(i + 1) % poly.length], vertexCount);
21
+ const list = owners.get(key);
22
+ if (list === undefined)
23
+ owners.set(key, [p]);
24
+ else if (!list.includes(p))
25
+ list.push(p);
26
+ }
11
27
  }
12
- const scale = poly.length > 0 ? 1 / poly.length : 0;
13
- return [result[0] * scale, result[1] * scale, result[2] * scale];
14
- }
15
- function distance(a, b) {
16
- return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
17
- }
18
- /** Build polygon adjacency from shared edges and explicit off-mesh links. */
19
- export function buildNavAdjacency(mesh) {
20
- const edges = new Map();
21
- mesh.polys.forEach((poly, polyIndex) => {
28
+ const bounds = new Float64Array(polyCount * 4);
29
+ const centers = new Float64Array(polyCount * 3);
30
+ const planes = new Float64Array(polyCount * 3);
31
+ let minX = Infinity, minZ = Infinity, maxX = -Infinity, maxZ = -Infinity;
32
+ for (let p = 0; p < polyCount; p += 1) {
33
+ const poly = mesh.polys[p];
34
+ let bx0 = Infinity, bz0 = Infinity, bx1 = -Infinity, bz1 = -Infinity, cx = 0, cy = 0, cz = 0, nx = 0, ny = 0, nz = 0;
22
35
  for (let i = 0; i < poly.length; i += 1) {
23
- const a = poly[i];
24
- const b = poly[(i + 1) % poly.length];
25
- const key = a < b ? `${a}:${b}` : `${b}:${a}`;
26
- const owners = edges.get(key) ?? [];
27
- owners.push(polyIndex);
28
- edges.set(key, owners);
36
+ const a = poly[i], b = poly[(i + 1) % poly.length];
37
+ const ax = vx(mesh, a), ay = vy(mesh, a), az = vz(mesh, a);
38
+ const bx = vx(mesh, b), by = vy(mesh, b), bz = vz(mesh, b);
39
+ bx0 = Math.min(bx0, ax);
40
+ bx1 = Math.max(bx1, ax);
41
+ bz0 = Math.min(bz0, az);
42
+ bz1 = Math.max(bz1, az);
43
+ cx += ax;
44
+ cy += ay;
45
+ cz += az;
46
+ nx += (ay - by) * (az + bz);
47
+ ny += (az - bz) * (ax + bx);
48
+ nz += (ax - bx) * (ay + by);
29
49
  }
30
- });
31
- const result = mesh.polys.map(() => ({ neighbors: [], cost: 1 }));
32
- for (const owners of edges.values()) {
33
- for (const a of owners)
34
- for (const b of owners) {
35
- if (a !== b && !result[a].neighbors.includes(b))
36
- result[a].neighbors.push(b);
37
- }
50
+ const scale = poly.length > 0 ? 1 / poly.length : 0;
51
+ centers[p * 3] = cx * scale;
52
+ centers[p * 3 + 1] = cy * scale;
53
+ centers[p * 3 + 2] = cz * scale;
54
+ // Height over XZ from the Newell normal: y = cy + sx*(x-cx) + sz*(z-cz); vertical polygons fall back to their mean height.
55
+ planes[p * 3] = cy * scale;
56
+ planes[p * 3 + 1] = Math.abs(ny) > EPS ? -nx / ny : 0;
57
+ planes[p * 3 + 2] = Math.abs(ny) > EPS ? -nz / ny : 0;
58
+ bounds[p * 4] = bx0;
59
+ bounds[p * 4 + 1] = bz0;
60
+ bounds[p * 4 + 2] = bx1;
61
+ bounds[p * 4 + 3] = bz1;
62
+ if (poly.length > 0) {
63
+ minX = Math.min(minX, bx0);
64
+ minZ = Math.min(minZ, bz0);
65
+ maxX = Math.max(maxX, bx1);
66
+ maxZ = Math.max(maxZ, bz1);
67
+ }
68
+ }
69
+ if (!Number.isFinite(minX)) {
70
+ minX = 0;
71
+ minZ = 0;
72
+ maxX = 0;
73
+ maxZ = 0;
38
74
  }
39
- for (const link of mesh.links ?? []) {
40
- if (!result[link.from] || !result[link.to])
75
+ const width = Math.max(maxX - minX, EPS), depth = Math.max(maxZ - minZ, EPS);
76
+ const cellSize = Math.max(Math.sqrt((width * depth) / Math.max(1, polyCount)), Math.max(width, depth) / 1024, 1e-6);
77
+ const cols = Math.max(1, Math.ceil(width / cellSize)), rows = Math.max(1, Math.ceil(depth / cellSize));
78
+ const cells = Array.from({ length: cols * rows }, () => []);
79
+ for (let p = 0; p < polyCount; p += 1) {
80
+ if (mesh.polys[p].length === 0)
41
81
  continue;
42
- if (!result[link.from].neighbors.includes(link.to))
43
- result[link.from].neighbors.push(link.to);
82
+ const c0 = clampInt(Math.floor((bounds[p * 4] - minX) / cellSize), cols), c1 = clampInt(Math.floor((bounds[p * 4 + 2] - minX) / cellSize), cols);
83
+ const r0 = clampInt(Math.floor((bounds[p * 4 + 1] - minZ) / cellSize), rows), r1 = clampInt(Math.floor((bounds[p * 4 + 3] - minZ) / cellSize), rows);
84
+ for (let r = r0; r <= r1; r += 1)
85
+ for (let c = c0; c <= c1; c += 1)
86
+ cells[r * cols + c].push(p);
44
87
  }
45
- return result;
88
+ const linksFrom = Array.from({ length: polyCount }, () => []);
89
+ (mesh.links ?? []).forEach((link, index) => {
90
+ if (link.from >= 0 && link.from < polyCount && link.to >= 0 && link.to < polyCount)
91
+ linksFrom[link.from].push(index);
92
+ });
93
+ const prepared = {
94
+ mesh, polyCount, owners, vertexCount, bounds, centers, planes, linksFrom,
95
+ gridMinX: minX, gridMinZ: minZ, cellSize, cols, rows, cells,
96
+ stamp: new Uint32Array(polyCount), stampId: 0,
97
+ g: new Float64Array(polyCount), parent: new Int32Array(polyCount), viaA: new Int32Array(polyCount), viaB: new Int32Array(polyCount), viaLink: new Int32Array(polyCount),
98
+ px: new Float64Array(polyCount), py: new Float64Array(polyCount), pz: new Float64Array(polyCount), closed: new Uint32Array(polyCount),
99
+ };
100
+ preparedCache.set(mesh, prepared);
101
+ return prepared;
102
+ }
103
+ function clampInt(value, size) {
104
+ return Math.min(size - 1, Math.max(0, value));
105
+ }
106
+ function nextStamp(prep) {
107
+ prep.stampId += 1;
108
+ if (prep.stampId === 0xffffffff) {
109
+ prep.stamp.fill(0);
110
+ prep.closed.fill(0);
111
+ prep.stampId = 1;
112
+ }
113
+ return prep.stampId;
114
+ }
115
+ function heightAt(prep, p, x, z) {
116
+ const c = prep.centers;
117
+ return prep.planes[p * 3] + prep.planes[p * 3 + 1] * (x - c[p * 3]) + prep.planes[p * 3 + 2] * (z - c[p * 3 + 2]);
46
118
  }
47
- function pointInPoly(mesh, poly, point) {
119
+ function insideXZ(prep, p, x, z) {
120
+ const b = prep.bounds;
121
+ if (x < b[p * 4] - EPS || z < b[p * 4 + 1] - EPS || x > b[p * 4 + 2] + EPS || z > b[p * 4 + 3] + EPS)
122
+ return false;
123
+ const mesh = prep.mesh, poly = mesh.polys[p];
48
124
  let inside = false;
49
125
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
50
- const a = vertex(mesh, poly[i]);
51
- const b = vertex(mesh, poly[j]);
52
- const cross = (point[0] - a[0]) * (b[2] - a[2]) - (point[2] - a[2]) * (b[0] - a[0]);
53
- const dot = (point[0] - a[0]) * (point[0] - b[0]) + (point[2] - a[2]) * (point[2] - b[2]);
126
+ const ax = vx(mesh, poly[i]), az = vz(mesh, poly[i]), bx = vx(mesh, poly[j]), bz = vz(mesh, poly[j]);
127
+ const cross = (x - ax) * (bz - az) - (z - az) * (bx - ax);
128
+ const dot = (x - ax) * (x - bx) + (z - az) * (z - bz);
54
129
  if (Math.abs(cross) < 1e-8 && dot <= 1e-8)
55
130
  return true;
56
- if ((a[2] > point[2]) !== (b[2] > point[2]) && point[0] < (b[0] - a[0]) * (point[2] - a[2]) / (b[2] - a[2]) + a[0])
131
+ if ((az > z) !== (bz > z) && x < ((bx - ax) * (z - az)) / (bz - az) + ax)
57
132
  inside = !inside;
58
133
  }
59
134
  return inside;
60
135
  }
61
- function closestOnSegment(point, a, b) {
62
- const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
63
- const len = dx * dx + dy * dy + dz * dz;
64
- const t = len > 0 ? Math.max(0, Math.min(1, ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy + (point[2] - a[2]) * dz) / len)) : 0;
65
- return [a[0] + dx * t, a[1] + dy * t, a[2] + dz * t];
136
+ function closestOnPoly(prep, p, point, out) {
137
+ if (insideXZ(prep, p, point[0], point[2])) {
138
+ out[0] = point[0];
139
+ out[1] = heightAt(prep, p, point[0], point[2]);
140
+ out[2] = point[2];
141
+ return Math.abs(point[1] - out[1]);
142
+ }
143
+ const mesh = prep.mesh, poly = mesh.polys[p];
144
+ let best = Infinity;
145
+ for (let i = 0; i < poly.length; i += 1) {
146
+ const a = poly[i], b = poly[(i + 1) % poly.length];
147
+ const ax = vx(mesh, a), ay = vy(mesh, a), az = vz(mesh, a);
148
+ const dx = vx(mesh, b) - ax, dy = vy(mesh, b) - ay, dz = vz(mesh, b) - az;
149
+ const len = dx * dx + dy * dy + dz * dz;
150
+ const t = len > 0 ? Math.max(0, Math.min(1, ((point[0] - ax) * dx + (point[1] - ay) * dy + (point[2] - az) * dz) / len)) : 0;
151
+ const qx = ax + dx * t, qy = ay + dy * t, qz = az + dz * t;
152
+ const d = Math.hypot(point[0] - qx, point[1] - qy, point[2] - qz);
153
+ if (d < best) {
154
+ best = d;
155
+ out[0] = qx;
156
+ out[1] = qy;
157
+ out[2] = qz;
158
+ }
159
+ }
160
+ return best;
66
161
  }
67
- /** Return the closest point on the mesh surface, or null for an empty mesh. */
68
- export function closestPoint(mesh, point) {
69
- let best = null;
70
- let bestDistance = Number.POSITIVE_INFINITY;
71
- for (const poly of mesh.polys) {
72
- if (pointInPoly(mesh, poly, point))
73
- return [point[0], center(mesh, poly)[1], point[2]];
74
- for (let i = 0; i < poly.length; i += 1) {
75
- const candidate = closestOnSegment(point, vertex(mesh, poly[i]), vertex(mesh, poly[(i + 1) % poly.length]));
76
- const d = distance(point, candidate);
77
- if (d < bestDistance) {
78
- bestDistance = d;
79
- best = candidate;
162
+ function nearestPolygon(prep, point, out) {
163
+ if (prep.polyCount === 0)
164
+ return -1;
165
+ const stamp = nextStamp(prep);
166
+ const cs = prep.cellSize;
167
+ const cx = clampInt(Math.floor((point[0] - prep.gridMinX) / cs), prep.cols);
168
+ const cz = clampInt(Math.floor((point[2] - prep.gridMinZ) / cs), prep.rows);
169
+ const maxRing = Math.max(prep.cols, prep.rows);
170
+ const scratch = [0, 0, 0];
171
+ let best = -1, bestDistance = Infinity;
172
+ for (let ring = 0; ring <= maxRing; ring += 1) {
173
+ for (let r = cz - ring; r <= cz + ring; r += 1) {
174
+ if (r < 0 || r >= prep.rows)
175
+ continue;
176
+ const edgeRow = r === cz - ring || r === cz + ring;
177
+ for (let c = cx - ring; c <= cx + ring; c += edgeRow ? 1 : ring * 2 || 1) {
178
+ if (c < 0 || c >= prep.cols)
179
+ continue;
180
+ for (const p of prep.cells[r * prep.cols + c]) {
181
+ if (prep.stamp[p] === stamp)
182
+ continue;
183
+ prep.stamp[p] = stamp;
184
+ const d = closestOnPoly(prep, p, point, scratch);
185
+ if (d < bestDistance - EPS || (Math.abs(d - bestDistance) <= EPS && p < best)) {
186
+ bestDistance = d;
187
+ best = p;
188
+ out[0] = scratch[0];
189
+ out[1] = scratch[1];
190
+ out[2] = scratch[2];
191
+ }
192
+ }
80
193
  }
81
194
  }
195
+ const left = prep.gridMinX + (cx - ring) * cs, right = prep.gridMinX + (cx + ring + 1) * cs;
196
+ const top = prep.gridMinZ + (cz - ring) * cs, bottom = prep.gridMinZ + (cz + ring + 1) * cs;
197
+ const covered = Math.min(point[0] - left, right - point[0], point[2] - top, bottom - point[2]);
198
+ if (best >= 0 && bestDistance <= covered)
199
+ break;
82
200
  }
83
201
  return best;
84
202
  }
85
- function polyAt(mesh, point) {
86
- for (let i = 0; i < mesh.polys.length; i += 1)
87
- if (pointInPoly(mesh, mesh.polys[i], point))
88
- return i;
89
- const nearest = closestPoint(mesh, point);
90
- if (!nearest)
91
- return null;
92
- let best = 0;
93
- let score = Number.POSITIVE_INFINITY;
94
- mesh.polys.forEach((poly, i) => { const d = distance(nearest, center(mesh, poly)); if (d < score) {
95
- score = d;
96
- best = i;
97
- } });
98
- return best;
203
+ function areaCost(mesh, costs, p) {
204
+ const area = mesh.areas?.[p];
205
+ if (area === undefined)
206
+ return 1;
207
+ const cost = costs[area];
208
+ if (cost === undefined)
209
+ return 1;
210
+ return Number.isFinite(cost) && cost >= 0 ? cost : -1;
99
211
  }
100
- /** A* over polygon centers, followed by deterministic visibility string-pulling. */
101
- export function findPath(mesh, from, to) {
102
- if (mesh.polys.length === 0)
103
- return null;
104
- const start = polyAt(mesh, from), goal = polyAt(mesh, to);
105
- if (start === null || goal === null)
212
+ /** Min-heap on `f`, breaking ties toward larger `g` so equal-cost plateaus expand depth-first. */
213
+ class MinHeap {
214
+ f = [];
215
+ g = [];
216
+ values = [];
217
+ less(fa, ga, va, fb, gb, vb) {
218
+ return fa < fb || (fa === fb && (ga > gb || (ga === gb && va < vb)));
219
+ }
220
+ push(f, g, value) {
221
+ let i = this.values.length;
222
+ this.f.push(f);
223
+ this.g.push(g);
224
+ this.values.push(value);
225
+ while (i > 0) {
226
+ const parent = (i - 1) >> 1;
227
+ if (!this.less(f, g, value, this.f[parent], this.g[parent], this.values[parent]))
228
+ break;
229
+ this.f[i] = this.f[parent];
230
+ this.g[i] = this.g[parent];
231
+ this.values[i] = this.values[parent];
232
+ i = parent;
233
+ }
234
+ this.f[i] = f;
235
+ this.g[i] = g;
236
+ this.values[i] = value;
237
+ }
238
+ pop() {
239
+ const top = this.values[0];
240
+ const lf = this.f.pop(), lg = this.g.pop(), lv = this.values.pop();
241
+ const n = this.values.length;
242
+ if (n > 0) {
243
+ let i = 0;
244
+ for (;;) {
245
+ let child = i * 2 + 1;
246
+ if (child >= n)
247
+ break;
248
+ if (child + 1 < n && this.less(this.f[child + 1], this.g[child + 1], this.values[child + 1], this.f[child], this.g[child], this.values[child]))
249
+ child += 1;
250
+ if (!this.less(this.f[child], this.g[child], this.values[child], lf, lg, lv))
251
+ break;
252
+ this.f[i] = this.f[child];
253
+ this.g[i] = this.g[child];
254
+ this.values[i] = this.values[child];
255
+ i = child;
256
+ }
257
+ this.f[i] = lf;
258
+ this.g[i] = lg;
259
+ this.values[i] = lv;
260
+ }
261
+ return top;
262
+ }
263
+ get size() { return this.values.length; }
264
+ }
265
+ function linkPoint(prep, poly, point) {
266
+ if (point !== undefined)
267
+ return point;
268
+ const c = prep.centers;
269
+ return [c[poly * 3], c[poly * 3 + 1], c[poly * 3 + 2]];
270
+ }
271
+ function cross2(ax, az, bx, bz, cx, cz) {
272
+ return (bx - ax) * (cz - az) - (bz - az) * (cx - ax);
273
+ }
274
+ function samePoint(a, b) {
275
+ return Math.abs(a[0] - b[0]) < 1e-9 && Math.abs(a[1] - b[1]) < 1e-9 && Math.abs(a[2] - b[2]) < 1e-9;
276
+ }
277
+ function funnel(start, end, lefts, rights, out) {
278
+ lefts.push(end);
279
+ rights.push(end);
280
+ let apex = start, left = start, right = start, apexIndex = 0, leftIndex = 0, rightIndex = 0;
281
+ for (let i = 0; i < lefts.length; i += 1) {
282
+ const l = lefts[i], r = rights[i];
283
+ if (cross2(apex[0], apex[2], right[0], right[2], r[0], r[2]) >= 0) {
284
+ if (samePoint(apex, right) || cross2(apex[0], apex[2], left[0], left[2], r[0], r[2]) < 0) {
285
+ right = r;
286
+ rightIndex = i + 1;
287
+ }
288
+ else {
289
+ if (!samePoint(out[out.length - 1], left))
290
+ out.push(left);
291
+ apex = left;
292
+ apexIndex = leftIndex;
293
+ right = apex;
294
+ rightIndex = apexIndex;
295
+ i = apexIndex - 1;
296
+ continue;
297
+ }
298
+ }
299
+ if (cross2(apex[0], apex[2], left[0], left[2], l[0], l[2]) <= 0) {
300
+ if (samePoint(apex, left) || cross2(apex[0], apex[2], right[0], right[2], l[0], l[2]) > 0) {
301
+ left = l;
302
+ leftIndex = i + 1;
303
+ }
304
+ else {
305
+ if (!samePoint(out[out.length - 1], right))
306
+ out.push(right);
307
+ apex = right;
308
+ apexIndex = rightIndex;
309
+ left = apex;
310
+ leftIndex = apexIndex;
311
+ i = apexIndex - 1;
312
+ continue;
313
+ }
314
+ }
315
+ }
316
+ if (!samePoint(out[out.length - 1], end))
317
+ out.push(end);
318
+ }
319
+ function search(prep, from, to, costs, maxNodes) {
320
+ const mesh = prep.mesh;
321
+ const startPoint = [0, 0, 0];
322
+ const goalPoint = [0, 0, 0];
323
+ const start = nearestPolygon(prep, from, startPoint);
324
+ const goal = nearestPolygon(prep, to, goalPoint);
325
+ if (start < 0 || goal < 0 || areaCost(mesh, costs, goal) < 0)
106
326
  return null;
107
- const adjacency = buildNavAdjacency(mesh);
108
- const g = new Map([[start, 0]]);
109
- const came = new Map();
110
- const open = [start];
111
- while (open.length) {
112
- open.sort((a, b) => (g.get(a) + distance(center(mesh, mesh.polys[a]), center(mesh, mesh.polys[goal]))) - (g.get(b) + distance(center(mesh, mesh.polys[b]), center(mesh, mesh.polys[goal]))));
113
- const current = open.shift();
114
- if (current === goal)
327
+ let minCost = 1;
328
+ for (const key in costs) {
329
+ const c = costs[key];
330
+ if (Number.isFinite(c) && c >= 0)
331
+ minCost = Math.min(minCost, c);
332
+ }
333
+ const stamp = nextStamp(prep);
334
+ const { g, parent, viaA, viaB, viaLink, px, py, pz, closed } = prep;
335
+ const heap = new MinHeap();
336
+ const open = (p, cost, from, a, b, link, x, y, z) => {
337
+ prep.stamp[p] = stamp;
338
+ g[p] = cost;
339
+ parent[p] = from;
340
+ viaA[p] = a;
341
+ viaB[p] = b;
342
+ viaLink[p] = link;
343
+ px[p] = x;
344
+ py[p] = y;
345
+ pz[p] = z;
346
+ heap.push(cost + Math.hypot(x - goalPoint[0], y - goalPoint[1], z - goalPoint[2]) * minCost, cost, p);
347
+ };
348
+ open(start, 0, -1, -1, -1, -1, startPoint[0], startPoint[1], startPoint[2]);
349
+ let reached = -1, nearest = start, nearestH = Infinity, expanded = 0;
350
+ while (heap.size > 0) {
351
+ const current = heap.pop();
352
+ if (closed[current] === stamp)
353
+ continue;
354
+ closed[current] = stamp;
355
+ if (current === goal) {
356
+ reached = goal;
115
357
  break;
116
- for (const next of adjacency[current].neighbors) {
117
- const link = mesh.links.find((candidate) => candidate.from === current && candidate.to === next);
118
- const cost = link?.cost ?? distance(center(mesh, mesh.polys[current]), center(mesh, mesh.polys[next]));
119
- const nextG = g.get(current) + Math.max(0, cost);
120
- if (nextG < (g.get(next) ?? Number.POSITIVE_INFINITY)) {
121
- g.set(next, nextG);
122
- came.set(next, current);
123
- if (!open.includes(next))
124
- open.push(next);
358
+ }
359
+ const h = Math.hypot(px[current] - goalPoint[0], py[current] - goalPoint[1], pz[current] - goalPoint[2]);
360
+ if (h < nearestH) {
361
+ nearestH = h;
362
+ nearest = current;
363
+ }
364
+ expanded += 1;
365
+ if (expanded > maxNodes)
366
+ break;
367
+ const here = Math.max(0, areaCost(mesh, costs, current));
368
+ const poly = mesh.polys[current];
369
+ for (let i = 0; i < poly.length; i += 1) {
370
+ const a = poly[i], b = poly[(i + 1) % poly.length];
371
+ const list = prep.owners.get(edgeKey(a, b, prep.vertexCount));
372
+ if (list.length < 2)
373
+ continue;
374
+ const mx = (vx(mesh, a) + vx(mesh, b)) / 2, my = (vy(mesh, a) + vy(mesh, b)) / 2, mz = (vz(mesh, a) + vz(mesh, b)) / 2;
375
+ for (const next of list) {
376
+ if (next === current || closed[next] === stamp || areaCost(mesh, costs, next) < 0)
377
+ continue;
378
+ const cost = g[current] + Math.hypot(mx - px[current], my - py[current], mz - pz[current]) * here;
379
+ if (prep.stamp[next] !== stamp || cost < g[next])
380
+ open(next, cost, current, a, b, -1, mx, my, mz);
125
381
  }
126
382
  }
383
+ for (const linkIndex of prep.linksFrom[current]) {
384
+ const link = mesh.links[linkIndex];
385
+ const next = link.to;
386
+ if (next === current || closed[next] === stamp || areaCost(mesh, costs, next) < 0)
387
+ continue;
388
+ const s = linkPoint(prep, current, link.start), e = linkPoint(prep, next, link.end);
389
+ const cost = g[current] + Math.hypot(s[0] - px[current], s[1] - py[current], s[2] - pz[current]) * here
390
+ + Math.max(0, link.cost ?? Math.hypot(e[0] - s[0], e[1] - s[1], e[2] - s[2]));
391
+ if (prep.stamp[next] !== stamp || cost < g[next])
392
+ open(next, cost, current, -1, -1, linkIndex, e[0], e[1], e[2]);
393
+ }
127
394
  }
128
- if (start !== goal && !came.has(goal))
129
- return null;
130
- const polys = [goal];
131
- while (polys[0] !== start)
132
- polys.unshift(came.get(polys[0]));
133
- const anchors = [from, ...polys.slice(1, -1).map((i) => center(mesh, mesh.polys[i])), to];
134
- const points = [anchors[0]];
135
- let anchor = 0;
136
- for (let i = 2; i < anchors.length; i += 1) {
137
- const steps = 24;
138
- let visible = true;
139
- for (let step = 1; step < steps; step += 1) {
140
- const t = step / steps;
141
- const p = [anchors[anchor][0] + (anchors[i][0] - anchors[anchor][0]) * t, anchors[anchor][1] + (anchors[i][1] - anchors[anchor][1]) * t, anchors[anchor][2] + (anchors[i][2] - anchors[anchor][2]) * t];
142
- if (!mesh.polys.some((poly) => pointInPoly(mesh, poly, p))) {
143
- visible = false;
144
- break;
395
+ const partial = reached < 0;
396
+ const last = partial ? nearest : reached;
397
+ const polys = [];
398
+ for (let p = last; p >= 0; p = parent[p])
399
+ polys.push(p);
400
+ polys.reverse();
401
+ let end = [goalPoint[0], goalPoint[1], goalPoint[2]];
402
+ if (partial) {
403
+ const snapped = [0, 0, 0];
404
+ closestOnPoly(prep, last, to, snapped);
405
+ end = snapped;
406
+ }
407
+ const points = [[startPoint[0], startPoint[1], startPoint[2]]];
408
+ let segmentStart = points[0];
409
+ let lefts = [], rights = [];
410
+ for (let k = 1; k < polys.length; k += 1) {
411
+ const p = polys[k], prev = polys[k - 1];
412
+ if (viaLink[p] >= 0) {
413
+ const link = mesh.links[viaLink[p]];
414
+ funnel(segmentStart, linkPoint(prep, prev, link.start), lefts, rights, points);
415
+ segmentStart = linkPoint(prep, p, link.end);
416
+ if (!samePoint(points[points.length - 1], segmentStart))
417
+ points.push(segmentStart);
418
+ lefts = [];
419
+ rights = [];
420
+ continue;
421
+ }
422
+ const a = viaA[p], b = viaB[p];
423
+ const ax = vx(mesh, a), az = vz(mesh, a), bx = vx(mesh, b), bz = vz(mesh, b);
424
+ const c = prep.centers;
425
+ const dx = c[p * 3] - c[prev * 3], dz = c[p * 3 + 2] - c[prev * 3 + 2];
426
+ const pa = [ax, vy(mesh, a), az], pb = [bx, vy(mesh, b), bz];
427
+ const aIsLeft = dx * (az - bz) - dz * (ax - bx) > 0;
428
+ lefts.push(aIsLeft ? pa : pb);
429
+ rights.push(aIsLeft ? pb : pa);
430
+ }
431
+ funnel(segmentStart, end, lefts, rights, points);
432
+ return partial ? { points, polys, partial: true } : { points, polys };
433
+ }
434
+ function sharesVertex(mesh, a, b) {
435
+ for (const v of mesh.polys[a])
436
+ if (mesh.polys[b].includes(v))
437
+ return true;
438
+ return false;
439
+ }
440
+ /** The polygon holding (x, z) among `current` and the polygons touching it, nearest in height; -1 when none. */
441
+ function stepInto(prep, current, x, z, costs) {
442
+ if (insideXZ(prep, current, x, z))
443
+ return current;
444
+ const c = clampInt(Math.floor((x - prep.gridMinX) / prep.cellSize), prep.cols);
445
+ const r = clampInt(Math.floor((z - prep.gridMinZ) / prep.cellSize), prep.rows);
446
+ const y = heightAt(prep, current, x, z);
447
+ let best = -1, bestDy = Infinity;
448
+ for (const p of prep.cells[r * prep.cols + c]) {
449
+ if (p === current || !insideXZ(prep, p, x, z) || !sharesVertex(prep.mesh, current, p) || areaCost(prep.mesh, costs, p) < 0)
450
+ continue;
451
+ const dy = Math.abs(heightAt(prep, p, x, z) - y);
452
+ if (dy < bestDy) {
453
+ bestDy = dy;
454
+ best = p;
455
+ }
456
+ }
457
+ return best;
458
+ }
459
+ function raycastPrepared(prep, from, to, costs) {
460
+ const mesh = prep.mesh;
461
+ let current = nearestPolygon(prep, from, [0, 0, 0]);
462
+ if (current < 0 || !insideXZ(prep, current, from[0], from[2]) || areaCost(mesh, costs, current) < 0)
463
+ return false;
464
+ const dx = to[0] - from[0], dz = to[2] - from[2];
465
+ const length = Math.hypot(dx, dz);
466
+ if (length < 1e-9)
467
+ return true;
468
+ // Probes step just past each crossing so segments through shared vertices pick the polygon they enter.
469
+ const nudge = Math.min(1, 1e-5 / length);
470
+ current = stepInto(prep, current, from[0] + dx * nudge, from[2] + dz * nudge, costs);
471
+ for (let steps = 0; current >= 0 && steps <= prep.polyCount; steps += 1) {
472
+ if (insideXZ(prep, current, to[0], to[2]))
473
+ return true;
474
+ const poly = mesh.polys[current];
475
+ let exitT = -Infinity;
476
+ for (let i = 0; i < poly.length; i += 1) {
477
+ const a = poly[i], b = poly[(i + 1) % poly.length];
478
+ const ax = vx(mesh, a), az = vz(mesh, a), ex = vx(mesh, b) - ax, ez = vz(mesh, b) - az;
479
+ const denom = dx * ez - dz * ex;
480
+ if (Math.abs(denom) < EPS)
481
+ continue;
482
+ const t = ((ax - from[0]) * ez - (az - from[2]) * ex) / denom;
483
+ const s = ((ax - from[0]) * dz - (az - from[2]) * dx) / denom;
484
+ if (s < -1e-7 || s > 1 + 1e-7 || t < -1e-7 || t > 1 + 1e-7)
485
+ continue;
486
+ exitT = Math.max(exitT, t);
487
+ }
488
+ if (exitT === -Infinity)
489
+ return false;
490
+ const t = Math.min(1, exitT + nudge);
491
+ const next = stepInto(prep, current, from[0] + dx * t, from[2] + dz * t, costs);
492
+ if (next === current)
493
+ return t >= 1;
494
+ current = next;
495
+ }
496
+ return false;
497
+ }
498
+ /** Build polygon adjacency from shared edges and explicit off-mesh links. */
499
+ export function buildNavAdjacency(mesh) {
500
+ const prep = prepare(mesh);
501
+ const result = mesh.polys.map(() => ({ neighbors: [], cost: 1 }));
502
+ for (let p = 0; p < prep.polyCount; p += 1) {
503
+ const poly = mesh.polys[p];
504
+ for (let i = 0; i < poly.length; i += 1) {
505
+ for (const other of prep.owners.get(edgeKey(poly[i], poly[(i + 1) % poly.length], prep.vertexCount))) {
506
+ if (other !== p && !result[p].neighbors.includes(other))
507
+ result[p].neighbors.push(other);
145
508
  }
146
509
  }
147
- if (!visible) {
148
- points.push(anchors[i - 1]);
149
- anchor = i - 1;
510
+ for (const linkIndex of prep.linksFrom[p]) {
511
+ const to = mesh.links[linkIndex].to;
512
+ if (!result[p].neighbors.includes(to))
513
+ result[p].neighbors.push(to);
150
514
  }
151
515
  }
152
- points.push(anchors[anchors.length - 1]);
153
- return { points, polys };
516
+ return result;
517
+ }
518
+ /**
519
+ * Create a cached query over a nav mesh: height-aware polygon lookup through a uniform grid, binary-heap A*
520
+ * priced by area costs and bounded by `maxNodes`, portal-funnel path straightening, and edge-walking raycasts.
521
+ * @capability navmesh-query route, snap, and raycast on a polygon navmesh with retunable area costs and a bounded search
522
+ */
523
+ export function createNavMeshQuery(mesh, options = {}) {
524
+ const prep = prepare(mesh);
525
+ let areaCosts = { ...(options.areaCosts ?? {}) };
526
+ let maxNodes = Math.max(1, Math.floor(options.maxNodes ?? DEFAULT_MAX_NODES));
527
+ return {
528
+ mesh,
529
+ findPolygon(point) {
530
+ const p = nearestPolygon(prep, point, [0, 0, 0]);
531
+ return p < 0 ? null : p;
532
+ },
533
+ closestPoint(point) {
534
+ const out = [0, 0, 0];
535
+ return nearestPolygon(prep, point, out) < 0 ? null : out;
536
+ },
537
+ findPath(from, to) {
538
+ return search(prep, from, to, areaCosts, maxNodes);
539
+ },
540
+ raycast(from, to) {
541
+ return raycastPrepared(prep, from, to, areaCosts);
542
+ },
543
+ retune(patch) {
544
+ if (patch.areaCosts !== undefined)
545
+ areaCosts = { ...patch.areaCosts };
546
+ if (patch.maxNodes !== undefined)
547
+ maxNodes = Math.max(1, Math.floor(patch.maxNodes));
548
+ },
549
+ snapshot() {
550
+ return { areaCosts: { ...areaCosts }, maxNodes };
551
+ },
552
+ restore(next) {
553
+ areaCosts = { ...next.areaCosts };
554
+ maxNodes = Math.max(1, Math.floor(next.maxNodes));
555
+ },
556
+ };
557
+ }
558
+ /** Return the closest point on the mesh surface, or null for an empty mesh. */
559
+ export function closestPoint(mesh, point) {
560
+ const out = [0, 0, 0];
561
+ return nearestPolygon(prepare(mesh), point, out) < 0 ? null : out;
562
+ }
563
+ /** A* over polygon portals with default area costs, straightened by a portal funnel. Use {@link createNavMeshQuery} to price areas. */
564
+ export function findPath(mesh, from, to) {
565
+ return search(prepare(mesh), from, to, {}, DEFAULT_MAX_NODES);
154
566
  }
155
567
  /** True when the segment remains over walkable polygons. */
156
568
  export function raycastNav(mesh, from, to) {
157
- for (let i = 0; i <= 32; i += 1) {
158
- const t = i / 32;
159
- const point = [from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t, from[2] + (to[2] - from[2]) * t];
160
- if (!mesh.polys.some((poly) => pointInPoly(mesh, poly, point)))
161
- return false;
162
- }
163
- return true;
569
+ return raycastPrepared(prepare(mesh), from, to, {});
164
570
  }