@jgengine/core 0.18.0 → 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 (318) hide show
  1. package/CHANGELOG.md +265 -2
  2. package/dist/ai/decisionGraph.d.ts +96 -0
  3. package/dist/ai/decisionGraph.js +212 -0
  4. package/dist/ai/groupAssist.d.ts +10 -0
  5. package/dist/ai/groupAssist.js +11 -0
  6. package/dist/ai/mobBrainGraph.d.ts +3 -0
  7. package/dist/ai/mobBrainGraph.js +25 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/tacticalQueries.d.ts +30 -0
  11. package/dist/ai/tacticalQueries.js +45 -0
  12. package/dist/ai/threat.d.ts +8 -0
  13. package/dist/ai/threat.js +10 -0
  14. package/dist/anim/animGraph.d.ts +155 -0
  15. package/dist/anim/animGraph.js +414 -0
  16. package/dist/anim/boneTexture.d.ts +55 -0
  17. package/dist/anim/boneTexture.js +43 -0
  18. package/dist/anim/footPlacement.d.ts +57 -0
  19. package/dist/anim/footPlacement.js +67 -0
  20. package/dist/anim/ikSolver.d.ts +51 -0
  21. package/dist/anim/ikSolver.js +124 -0
  22. package/dist/anim/locomotionGraph.d.ts +35 -0
  23. package/dist/anim/locomotionGraph.js +62 -0
  24. package/dist/assets/spriteAtlas.d.ts +21 -0
  25. package/dist/assets/spriteAtlas.js +103 -0
  26. package/dist/audio/audioFalloff.d.ts +12 -0
  27. package/dist/audio/doppler.d.ts +21 -0
  28. package/dist/audio/doppler.js +32 -0
  29. package/dist/audio/engineLayers.d.ts +83 -0
  30. package/dist/audio/engineLayers.js +115 -0
  31. package/dist/combat/abilityKit.d.ts +1 -0
  32. package/dist/combat/abilityKit.js +1 -0
  33. package/dist/combat/death.d.ts +7 -0
  34. package/dist/combat/shotOrigin.d.ts +6 -0
  35. package/dist/combat/shotOrigin.js +6 -0
  36. package/dist/combat/weaponFeedback.d.ts +36 -0
  37. package/dist/combat/weaponFeedback.js +42 -0
  38. package/dist/combat/weaponHandling.d.ts +161 -0
  39. package/dist/combat/weaponHandling.js +202 -0
  40. package/dist/combat/weaponPresentation.d.ts +115 -0
  41. package/dist/combat/weaponPresentation.js +96 -0
  42. package/dist/devtools/warnOnce.d.ts +8 -0
  43. package/dist/devtools/warnOnce.js +18 -0
  44. package/dist/economy/currency.d.ts +16 -2
  45. package/dist/economy/currency.js +55 -14
  46. package/dist/economy/wallet.d.ts +5 -4
  47. package/dist/economy/wallet.js +8 -4
  48. package/dist/editor/commandApply.js +1 -0
  49. package/dist/editor/commands.d.ts +4 -1
  50. package/dist/editor/document.d.ts +2 -1
  51. package/dist/editor/document.js +92 -1
  52. package/dist/editor/environment.d.ts +3 -0
  53. package/dist/editor/environment.js +24 -0
  54. package/dist/editor/index.d.ts +4 -2
  55. package/dist/editor/index.js +1 -1
  56. package/dist/editor/types.d.ts +20 -0
  57. package/dist/faction/reputation.d.ts +6 -0
  58. package/dist/faction/reputation.js +11 -0
  59. package/dist/game/cameraConfig.d.ts +59 -1
  60. package/dist/game/chat.d.ts +22 -1
  61. package/dist/game/chat.js +48 -19
  62. package/dist/game/controlGate.d.ts +6 -0
  63. package/dist/game/controlGate.js +30 -1
  64. package/dist/game/defineGame.d.ts +10 -1
  65. package/dist/game/events.d.ts +14 -1
  66. package/dist/game/lootTable.d.ts +4 -0
  67. package/dist/game/lootTable.js +16 -0
  68. package/dist/game/pixelPerfect.d.ts +25 -0
  69. package/dist/game/pixelPerfect.js +29 -0
  70. package/dist/game/playableGame.d.ts +167 -6
  71. package/dist/game/systemRuntime.js +6 -1
  72. package/dist/game/toasts.d.ts +8 -0
  73. package/dist/game/toasts.js +7 -0
  74. package/dist/game/vfxInstance.d.ts +9 -0
  75. package/dist/game/vfxInstance.js +12 -1
  76. package/dist/game/viewports.d.ts +43 -0
  77. package/dist/game/viewports.js +46 -0
  78. package/dist/game/worldItem.d.ts +5 -1
  79. package/dist/game/worldItem.js +0 -1
  80. package/dist/input/actionBindings.d.ts +17 -2
  81. package/dist/input/actionBindings.js +24 -11
  82. package/dist/input/actionContexts.d.ts +45 -0
  83. package/dist/input/actionContexts.js +113 -0
  84. package/dist/input/axisInput.d.ts +4 -0
  85. package/dist/input/axisInput.js +4 -0
  86. package/dist/input/axisShaper.d.ts +73 -0
  87. package/dist/input/axisShaper.js +105 -0
  88. package/dist/input/gamepadModel.d.ts +79 -0
  89. package/dist/input/gamepadModel.js +108 -0
  90. package/dist/input/haptics.d.ts +50 -0
  91. package/dist/input/haptics.js +74 -0
  92. package/dist/input/inputBuffer.d.ts +40 -0
  93. package/dist/input/inputBuffer.js +59 -0
  94. package/dist/input/lookChannel.d.ts +10 -0
  95. package/dist/input/lookChannel.js +6 -0
  96. package/dist/input/pointer.d.ts +2 -0
  97. package/dist/input/pointer.js +4 -0
  98. package/dist/item/affix.d.ts +6 -0
  99. package/dist/item/affix.js +6 -0
  100. package/dist/meta/changelog.d.ts +1 -1
  101. package/dist/meta/changelog.js +196 -1
  102. package/dist/movement/characterController.d.ts +76 -0
  103. package/dist/movement/characterController.js +216 -0
  104. package/dist/movement/freeFlight.d.ts +119 -0
  105. package/dist/movement/freeFlight.js +294 -0
  106. package/dist/movement/glideModel.d.ts +3 -0
  107. package/dist/movement/glideModel.js +6 -0
  108. package/dist/movement/grappleSwing.d.ts +2 -0
  109. package/dist/movement/grappleSwing.js +5 -1
  110. package/dist/movement/movementModel.d.ts +39 -0
  111. package/dist/movement/movementModel.js +62 -19
  112. package/dist/movement/movementProbe.d.ts +51 -0
  113. package/dist/movement/movementProbe.js +119 -0
  114. package/dist/movement/playerMovement.d.ts +30 -3
  115. package/dist/movement/playerMovement.js +304 -7
  116. package/dist/movement/serverStep.d.ts +5 -0
  117. package/dist/movement/serverStep.js +6 -0
  118. package/dist/movement/solidObstacles.d.ts +39 -8
  119. package/dist/movement/solidObstacles.js +71 -6
  120. package/dist/movement/voxelController.js +7 -7
  121. package/dist/multiplayer/poseSyncGate.d.ts +3 -1
  122. package/dist/multiplayer/poseSyncGate.js +9 -1
  123. package/dist/multiplayer/presenceContract.d.ts +6 -0
  124. package/dist/multiplayer/presenceModel.d.ts +2 -0
  125. package/dist/multiplayer/presenceModel.js +11 -0
  126. package/dist/nav/navFromEnvironment.d.ts +9 -3
  127. package/dist/nav/navFromEnvironment.js +18 -12
  128. package/dist/nav/navMesh.d.ts +74 -0
  129. package/dist/nav/navMesh.js +570 -0
  130. package/dist/physics/aircraftDynamics.d.ts +390 -0
  131. package/dist/physics/aircraftDynamics.js +704 -0
  132. package/dist/physics/boatDynamics.d.ts +116 -0
  133. package/dist/physics/boatDynamics.js +160 -0
  134. package/dist/physics/drivableVehicle.d.ts +22 -8
  135. package/dist/physics/drivableVehicle.js +4 -2
  136. package/dist/physics/flightDynamics.d.ts +29 -4
  137. package/dist/physics/flightDynamics.js +28 -5
  138. package/dist/physics/handlingProbe.d.ts +249 -0
  139. package/dist/physics/handlingProbe.js +606 -0
  140. package/dist/physics/kinematicVehicle.d.ts +37 -2
  141. package/dist/physics/kinematicVehicle.js +15 -0
  142. package/dist/physics/physicsBackend.d.ts +203 -0
  143. package/dist/physics/physicsBackend.js +49 -0
  144. package/dist/physics/physicsBackendConformance.d.ts +27 -0
  145. package/dist/physics/physicsBackendConformance.js +151 -0
  146. package/dist/physics/physicsWorldBackend.d.ts +45 -0
  147. package/dist/physics/physicsWorldBackend.js +518 -0
  148. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  149. package/dist/physics/vehicleBackendLink.js +102 -0
  150. package/dist/physics/vehicleDynamics.d.ts +374 -0
  151. package/dist/physics/vehicleDynamics.js +737 -0
  152. package/dist/physics/worldColliders.d.ts +13 -0
  153. package/dist/physics/worldColliders.js +128 -0
  154. package/dist/puzzle/cellGrid.d.ts +15 -9
  155. package/dist/puzzle/cellGrid.js +13 -9
  156. package/dist/random/rng.d.ts +12 -1
  157. package/dist/random/rng.js +20 -3
  158. package/dist/render/environment.d.ts +23 -0
  159. package/dist/render/environment.js +1 -0
  160. package/dist/render/lookPreset.d.ts +19 -6
  161. package/dist/render/lookPreset.js +56 -4
  162. package/dist/render/postProcessing.d.ts +40 -1
  163. package/dist/render/postProcessing.js +1 -1
  164. package/dist/render/sprite2d.d.ts +38 -0
  165. package/dist/render/sprite2d.js +40 -0
  166. package/dist/runtime/adapter.d.ts +20 -1
  167. package/dist/runtime/adapter.js +8 -1
  168. package/dist/runtime/cameraDirector.d.ts +17 -3
  169. package/dist/runtime/cameraDirector.js +25 -0
  170. package/dist/runtime/commandInput.d.ts +52 -0
  171. package/dist/runtime/commandInput.js +71 -0
  172. package/dist/runtime/commandRunner.d.ts +39 -7
  173. package/dist/runtime/commandRunner.js +35 -5
  174. package/dist/runtime/context/combat.d.ts +1 -0
  175. package/dist/runtime/context/combat.js +2 -1
  176. package/dist/runtime/context/deathLoot.d.ts +2 -0
  177. package/dist/runtime/context/deathLoot.js +1 -0
  178. package/dist/runtime/gameContext.js +18 -1
  179. package/dist/runtime/gameContextTypes.d.ts +33 -7
  180. package/dist/runtime/gameRuntime.d.ts +23 -5
  181. package/dist/runtime/gameRuntime.js +21 -4
  182. package/dist/runtime/headlessRunner.d.ts +10 -0
  183. package/dist/runtime/headlessRunner.js +26 -8
  184. package/dist/runtime/hostPolicy.d.ts +3 -2
  185. package/dist/runtime/hostPolicy.js +8 -10
  186. package/dist/runtime/hostedGameRunner.js +34 -6
  187. package/dist/runtime/hostedWorldSession.d.ts +12 -16
  188. package/dist/runtime/hostedWorldSession.js +29 -6
  189. package/dist/runtime/hostedWorldStore.d.ts +20 -0
  190. package/dist/runtime/hostedWorldStore.js +1 -0
  191. package/dist/runtime/inputRecorder.d.ts +30 -0
  192. package/dist/runtime/inputRecorder.js +59 -0
  193. package/dist/runtime/inputSnapshot.d.ts +14 -0
  194. package/dist/runtime/inputSnapshot.js +11 -0
  195. package/dist/runtime/localPlayers.d.ts +73 -0
  196. package/dist/runtime/localPlayers.js +119 -0
  197. package/dist/runtime/poseInterpolation.d.ts +36 -0
  198. package/dist/runtime/poseInterpolation.js +91 -0
  199. package/dist/runtime/prediction.d.ts +45 -0
  200. package/dist/runtime/prediction.js +62 -0
  201. package/dist/runtime/simContext.d.ts +45 -0
  202. package/dist/runtime/simContext.js +65 -0
  203. package/dist/runtime/simLoop.d.ts +52 -0
  204. package/dist/runtime/simLoop.js +72 -0
  205. package/dist/runtime/simSnapshot.d.ts +32 -0
  206. package/dist/runtime/simSnapshot.js +60 -0
  207. package/dist/runtime/snapshot.d.ts +8 -0
  208. package/dist/runtime/snapshotBuffer.d.ts +33 -0
  209. package/dist/runtime/snapshotBuffer.js +58 -0
  210. package/dist/runtime/transport.d.ts +41 -2
  211. package/dist/runtime/transport.js +7 -1
  212. package/dist/runtime/worldChunks.d.ts +2 -0
  213. package/dist/runtime/worldChunks.js +14 -0
  214. package/dist/runtime/worldProjection.d.ts +9 -0
  215. package/dist/runtime/worldProjection.js +17 -1
  216. package/dist/runtime/worldSnapshot.d.ts +6 -0
  217. package/dist/runtime/worldSnapshot.js +3 -0
  218. package/dist/scene/behaviorRuntime.d.ts +29 -1
  219. package/dist/scene/behaviorRuntime.js +71 -4
  220. package/dist/scene/behaviors.d.ts +12 -1
  221. package/dist/scene/builtinSceneKinds.js +10 -0
  222. package/dist/scene/sceneKinds.d.ts +9 -0
  223. package/dist/scene/vehicleSeat.d.ts +5 -1
  224. package/dist/scene/vehicleSeat.js +5 -1
  225. package/dist/sensor/concealment.d.ts +6 -0
  226. package/dist/sensor/concealment.js +8 -0
  227. package/dist/sensor/freezeMonitor.d.ts +6 -0
  228. package/dist/sensor/freezeMonitor.js +8 -0
  229. package/dist/sensor/perception.d.ts +70 -0
  230. package/dist/sensor/perception.js +189 -0
  231. package/dist/sensor/recordingBuffer.d.ts +3 -0
  232. package/dist/sensor/recordingBuffer.js +8 -0
  233. package/dist/session/roundState.d.ts +4 -0
  234. package/dist/session/roundState.js +11 -0
  235. package/dist/settings/frameRateLimit.d.ts +16 -0
  236. package/dist/settings/frameRateLimit.js +37 -0
  237. package/dist/settings/graphicsProfile.d.ts +21 -0
  238. package/dist/settings/graphicsProfile.js +48 -0
  239. package/dist/settings/graphicsSettings.d.ts +40 -0
  240. package/dist/settings/graphicsSettings.js +81 -0
  241. package/dist/settings/settingsModel.d.ts +9 -1
  242. package/dist/settings/settingsModel.js +10 -0
  243. package/dist/stats/eventMeter.d.ts +1 -1
  244. package/dist/stats/eventMeter.js +1 -1
  245. package/dist/time/accrueSince.d.ts +10 -0
  246. package/dist/time/accrueSince.js +9 -0
  247. package/dist/time/rateWindow.d.ts +16 -0
  248. package/dist/time/rateWindow.js +14 -0
  249. package/dist/time/serverTick.d.ts +21 -3
  250. package/dist/time/serverTick.js +14 -13
  251. package/dist/vfx/feedbackMixer.d.ts +61 -0
  252. package/dist/vfx/feedbackMixer.js +106 -0
  253. package/dist/vfx/particleDirector.d.ts +68 -0
  254. package/dist/vfx/particleDirector.js +81 -0
  255. package/dist/visibility/simulationCulling.d.ts +8 -1
  256. package/dist/visibility/simulationCulling.js +7 -0
  257. package/dist/visibility/spatialIndex.d.ts +21 -0
  258. package/dist/visibility/spatialIndex.js +16 -0
  259. package/dist/world/authoredSolids.d.ts +46 -0
  260. package/dist/world/authoredSolids.js +75 -0
  261. package/dist/world/buildingGenerator.js +2 -2
  262. package/dist/world/buildingKit.d.ts +3 -1
  263. package/dist/world/buildings.d.ts +46 -2
  264. package/dist/world/buildings.js +53 -3
  265. package/dist/world/cityContent.js +103 -25
  266. package/dist/world/cityKind.d.ts +139 -0
  267. package/dist/world/cityKind.js +367 -16
  268. package/dist/world/features.d.ts +56 -3
  269. package/dist/world/features.js +13 -2
  270. package/dist/world/footprintGrid.d.ts +7 -0
  271. package/dist/world/footprintGrid.js +16 -0
  272. package/dist/world/interiors.d.ts +1 -0
  273. package/dist/world/interiors.js +5 -3
  274. package/dist/world/lod.d.ts +6 -0
  275. package/dist/world/lod.js +8 -0
  276. package/dist/world/placement.d.ts +7 -0
  277. package/dist/world/placement.js +2 -0
  278. package/dist/world/placementController.d.ts +1 -1
  279. package/dist/world/placementController.js +7 -0
  280. package/dist/world/raceCircuit.d.ts +168 -0
  281. package/dist/world/raceCircuit.js +797 -0
  282. package/dist/world/terraform.d.ts +8 -0
  283. package/dist/world/terraform.js +7 -0
  284. package/dist/world/terrain.d.ts +5 -1
  285. package/dist/world/terrain.js +5 -0
  286. package/dist/world/territory.d.ts +97 -0
  287. package/dist/world/territory.js +182 -0
  288. package/dist/world/walls.d.ts +7 -0
  289. package/dist/world/walls.js +7 -0
  290. package/dist/world/worldSolids.d.ts +84 -0
  291. package/dist/world/worldSolids.js +240 -0
  292. package/dist/world.d.ts +5 -3
  293. package/dist/world.js +4 -2
  294. package/package.json +1 -1
  295. package/skills/ce-handoff/SKILL.md +2 -2
  296. package/skills/game-design/SKILL.md +11 -1
  297. package/skills/jgengine/SKILL.md +9 -5
  298. package/skills/jgengine/api.md +205 -101
  299. package/skills/jgengine/capabilities.md +56 -0
  300. package/skills/jgengine/recipes/minimal-game.md +3 -0
  301. package/skills/jgengine-combat/SKILL.md +5 -0
  302. package/skills/jgengine-combat/api.md +138 -103
  303. package/skills/jgengine-combat/capabilities.md +29 -1
  304. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  305. package/skills/jgengine-gameplay/SKILL.md +10 -0
  306. package/skills/jgengine-gameplay/api.md +501 -453
  307. package/skills/jgengine-gameplay/capabilities.md +36 -0
  308. package/skills/jgengine-gameplay/reference-systems.md +4 -0
  309. package/skills/jgengine-world/SKILL.md +21 -3
  310. package/skills/jgengine-world/api.md +1172 -625
  311. package/skills/jgengine-world/capabilities.md +229 -3
  312. package/skills/jgengine-world/recipes/character-animation.md +39 -0
  313. package/skills/jgengine-world/recipes/controls.md +44 -0
  314. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  315. package/skills/jgengine-world/recipes/npc-senses.md +43 -0
  316. package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
  317. package/skills/jgengine-world/reference.md +31 -2
  318. package/skills/jgengine-world/visibility.md +112 -0
@@ -0,0 +1,570 @@
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;
9
+ }
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
+ }
27
+ }
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;
35
+ for (let i = 0; i < poly.length; i += 1) {
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);
49
+ }
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;
74
+ }
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)
81
+ continue;
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);
87
+ }
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]);
118
+ }
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];
124
+ let inside = false;
125
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
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);
129
+ if (Math.abs(cross) < 1e-8 && dot <= 1e-8)
130
+ return true;
131
+ if ((az > z) !== (bz > z) && x < ((bx - ax) * (z - az)) / (bz - az) + ax)
132
+ inside = !inside;
133
+ }
134
+ return inside;
135
+ }
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;
161
+ }
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
+ }
193
+ }
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;
200
+ }
201
+ return best;
202
+ }
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;
211
+ }
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)
326
+ return null;
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;
357
+ break;
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);
381
+ }
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
+ }
394
+ }
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);
508
+ }
509
+ }
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);
514
+ }
515
+ }
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);
566
+ }
567
+ /** True when the segment remains over walkable polygons. */
568
+ export function raycastNav(mesh, from, to) {
569
+ return raycastPrepared(prepare(mesh), from, to, {});
570
+ }