@woosh/meep-engine 3.1.0 → 3.3.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 (1869) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -152
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/avif/encoder_worker.js +5 -18
  134. package/src/core/binary/BitImage2.js +246 -246
  135. package/src/core/binary/align_32.js +21 -21
  136. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  137. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  138. package/src/core/binary/hash/XXH128Hash.js +55 -55
  139. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  140. package/src/core/binary/hash/xxh3_128.js +341 -341
  141. package/src/core/binary/hash/xxh3_64.js +241 -241
  142. package/src/core/binary/hash/xxh3_common.js +476 -476
  143. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  144. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  145. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  146. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  147. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  148. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  149. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  150. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  151. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  152. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  153. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  154. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  155. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  156. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  157. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  158. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  159. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  160. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  161. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  162. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  163. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  164. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  165. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  166. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  167. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  168. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  171. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  176. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  177. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  178. package/src/core/bvh8/BVH8.js +436 -436
  179. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  180. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  181. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  182. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  183. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  184. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  185. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  186. package/src/core/bvh8/build/byte.js +9 -9
  187. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  188. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  189. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  190. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  191. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  192. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  193. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  194. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  195. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  196. package/src/core/bvh8/bvh8_quality.js +72 -72
  197. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  198. package/src/core/clipboard/obtainClipBoard.js +29 -29
  199. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  200. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  201. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  202. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  203. package/src/core/collection/union-find/union_find.js +79 -79
  204. package/src/core/color/PQ/PQ_constants.d.ts +43 -0
  205. package/src/core/color/PQ/PQ_constants.d.ts.map +1 -0
  206. package/src/core/color/PQ/PQ_constants.js +50 -0
  207. package/src/core/color/PQ/PQ_to_linear.d.ts +24 -0
  208. package/src/core/color/PQ/PQ_to_linear.d.ts.map +1 -0
  209. package/src/core/color/PQ/PQ_to_linear.js +35 -0
  210. package/src/core/color/PQ/linear_to_PQ.d.ts +27 -0
  211. package/src/core/color/PQ/linear_to_PQ.d.ts.map +1 -0
  212. package/src/core/color/PQ/linear_to_PQ.js +32 -0
  213. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  214. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  215. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  216. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  217. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  218. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  219. package/src/core/color/lab/xyz_to_lab.js +62 -62
  220. package/src/core/color/operations/color_darken.js +24 -24
  221. package/src/core/color/operations/color_desaturate.js +24 -24
  222. package/src/core/color/operations/color_lighten.js +24 -24
  223. package/src/core/color/operations/color_saturate.js +24 -24
  224. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  225. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  226. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  227. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  228. package/src/core/dom/isImageBitmap.js +12 -12
  229. package/src/core/function/frameThrottle.js +23 -23
  230. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  231. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  232. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  233. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  234. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  235. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  236. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  237. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  238. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  239. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  240. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  241. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  242. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  243. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  244. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  245. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  246. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  247. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  248. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  249. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  250. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  251. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  252. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  253. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  254. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  255. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  256. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  257. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  258. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  259. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  260. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  261. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  262. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  263. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  264. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  265. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  266. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  267. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  268. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  269. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  270. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  271. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  272. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  273. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  274. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  275. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  276. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  278. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  279. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  280. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  283. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  285. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  286. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  287. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  288. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  289. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  290. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  291. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  292. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  293. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  294. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  295. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  296. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  297. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  298. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  299. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  300. package/src/core/geom/3d/atlas/Chart.js +77 -77
  301. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  302. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  303. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  304. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  305. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  306. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  307. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  308. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  309. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  310. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  311. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  312. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  313. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  314. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  315. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  316. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  317. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  318. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  319. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  320. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  321. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  322. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  323. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  324. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  325. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  326. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  327. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  328. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  329. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  330. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  331. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  332. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  333. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  334. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  335. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  336. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  337. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  338. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  339. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  340. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  341. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  342. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  343. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  344. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  345. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  346. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  347. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  348. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  349. package/src/core/geom/3d/gjk/gjk.js +430 -430
  350. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  351. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  352. package/src/core/geom/3d/gjk/mpr.js +368 -368
  353. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  354. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  355. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  356. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  357. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  358. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  359. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  360. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  361. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  362. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  363. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  364. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  365. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  366. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  367. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  368. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  369. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  370. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  371. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  372. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  373. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  374. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  375. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  376. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  377. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  378. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  379. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  380. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  381. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  382. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  383. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  384. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  385. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  386. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  387. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  388. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  389. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  390. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  391. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  392. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  393. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  394. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  395. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  396. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  397. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  398. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  399. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  400. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  401. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  402. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  403. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  404. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  405. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  406. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  407. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  408. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  409. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  410. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  411. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  412. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  413. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  414. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  415. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  416. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  417. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  418. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  419. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  420. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  421. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  422. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  423. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  424. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  425. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  426. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  427. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  428. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  429. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  430. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  431. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  432. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  433. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  434. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  435. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  436. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  437. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  438. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  439. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  440. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  441. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  442. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  443. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  444. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  445. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  446. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  447. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  448. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  449. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  450. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  451. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  452. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  453. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  454. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  455. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  456. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  457. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  458. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  459. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  460. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  461. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  462. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  463. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  464. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  465. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  466. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  467. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  468. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  469. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  470. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  471. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  472. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  474. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  475. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  476. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  477. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  478. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  479. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  480. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  481. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  482. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  483. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  484. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  485. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  486. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  487. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  488. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  489. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  490. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  491. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  494. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  495. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  496. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  497. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  498. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  499. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  500. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  501. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  502. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  503. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  504. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  505. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  506. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  507. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  508. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  509. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  510. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  511. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  512. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  513. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  514. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  515. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  516. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  517. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  518. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  519. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  520. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  521. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  522. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  523. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  524. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  525. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  526. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  527. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.d.ts +32 -0
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.d.ts.map +1 -0
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_other_face.js +59 -0
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  544. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  545. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  546. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.d.ts +26 -0
  547. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.d.ts.map +1 -0
  548. package/src/core/geom/3d/topology/struct/binary/query/bt_face_third_vertex.js +55 -0
  549. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  550. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  551. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  552. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  553. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  554. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  555. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  556. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  557. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  558. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  559. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  560. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  561. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  562. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  563. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  564. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  565. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  566. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  567. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  568. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  569. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  570. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  571. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  572. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  573. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  574. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  575. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  576. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  577. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  578. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  579. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  580. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  581. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  582. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  583. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  584. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  585. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  586. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  587. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  588. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  589. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  590. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  591. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  592. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  593. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  594. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  595. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  596. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  597. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  598. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  599. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  600. package/src/core/geom/random/randomPointInBox.js +7 -7
  601. package/src/core/geom/random/randomPointInCircle.js +7 -7
  602. package/src/core/geom/random/randomPointInPoint.js +7 -7
  603. package/src/core/geom/random/randomPointInSphere.js +7 -7
  604. package/src/core/geom/random/randomPointOnBox.js +7 -7
  605. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  606. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  607. package/src/core/geom/vec/vector_axpy.js +15 -15
  608. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  609. package/src/core/geom/vec/vector_copy.js +7 -7
  610. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  611. package/src/core/geom/vec/vector_scale_array.js +7 -7
  612. package/src/core/geom/vec2/v2_rotate.js +21 -21
  613. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  614. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  615. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  616. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  617. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  618. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  619. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  620. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  621. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  622. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  623. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  624. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  625. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  626. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  627. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  628. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  629. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  630. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  631. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  632. package/src/core/geom/vec3/v3_multiply.js +23 -23
  633. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  634. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  635. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  636. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  637. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  638. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  639. package/src/core/geom/vec3/v3_reflect.js +24 -24
  640. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  641. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  642. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  643. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  644. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  645. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  646. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  647. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  648. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  649. package/src/core/graph/graph_k_means_cluster.js +250 -250
  650. package/src/core/graph/layout/CircleLayout.js +1 -1
  651. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  652. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  653. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  654. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  655. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  656. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  657. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  658. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  659. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  660. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  661. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  662. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  663. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  664. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  665. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  666. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  667. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  668. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  669. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  670. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  671. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  672. package/src/core/math/complex/complex_horner_eval.js +31 -31
  673. package/src/core/math/computeWholeDivisorLow.js +33 -33
  674. package/src/core/math/f32_next_down.js +24 -24
  675. package/src/core/math/f32_next_up.js +27 -27
  676. package/src/core/math/hash/hash_mix2.js +22 -22
  677. package/src/core/math/hash/hash_mix3.js +19 -19
  678. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  679. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  680. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  681. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  682. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  683. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  684. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  685. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  686. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  687. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  688. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  689. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  690. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  691. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  692. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  693. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  694. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  695. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  696. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  697. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  698. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  699. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  700. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  701. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  702. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  703. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  704. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  705. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  706. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  707. package/src/core/math/noise/curl_noise_3d.js +18 -18
  708. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  709. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  710. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  711. package/src/core/math/random/randomSeed.js +16 -16
  712. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  713. package/src/core/math/round_half_away_from_zero.js +13 -13
  714. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  715. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  716. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  717. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  718. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  719. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  720. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  721. package/src/core/path/convertPathToURL.js +107 -107
  722. package/src/core/process/delay.js +16 -16
  723. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  724. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  725. package/src/core/science/units/NamedUnit.js +48 -48
  726. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  727. package/src/core/science/units/UnitDimension.js +28 -28
  728. package/src/core/science/units/format_quantity.js +74 -74
  729. package/src/core/science/units/magnitude_prefix.js +127 -127
  730. package/src/core/science/units/si/AMPERE.js +13 -13
  731. package/src/core/science/units/si/CANDELA.js +13 -13
  732. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  733. package/src/core/science/units/si/KELVIN.js +13 -13
  734. package/src/core/science/units/si/KILOGRAM.js +13 -13
  735. package/src/core/science/units/si/METER.js +13 -13
  736. package/src/core/science/units/si/MOLE.js +13 -13
  737. package/src/core/science/units/si/SECOND.js +13 -13
  738. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  739. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  740. package/src/core/science/units/si/derived/FARAD.js +13 -13
  741. package/src/core/science/units/si/derived/GRAY.js +16 -16
  742. package/src/core/science/units/si/derived/HENRY.js +13 -13
  743. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  744. package/src/core/science/units/si/derived/JOULE.js +13 -13
  745. package/src/core/science/units/si/derived/KATAL.js +13 -13
  746. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  747. package/src/core/science/units/si/derived/LUX.js +13 -13
  748. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  749. package/src/core/science/units/si/derived/OHM.js +13 -13
  750. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  751. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  752. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  753. package/src/core/science/units/si/derived/TESLA.js +13 -13
  754. package/src/core/science/units/si/derived/VOLT.js +13 -13
  755. package/src/core/science/units/si/derived/WATT.js +13 -13
  756. package/src/core/science/units/si/derived/WEBER.js +13 -13
  757. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  758. package/src/engine/Engine.js +726 -726
  759. package/src/engine/EngineHarness.js +590 -590
  760. package/src/engine/REVIEW_2026_07_03.md +211 -211
  761. package/src/engine/animation/AnimatedActions.js +81 -81
  762. package/src/engine/animation/AnimationUtils.js +252 -252
  763. package/src/engine/animation/async/TimeSeries.js +300 -300
  764. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  765. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  766. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  767. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  768. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  769. package/src/engine/asset/GameAssetType.d.ts +0 -1
  770. package/src/engine/asset/GameAssetType.js +0 -1
  771. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  772. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  773. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  774. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  775. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  776. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  777. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  778. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  779. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  780. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  781. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  782. package/src/engine/control/first-person/DESIGN.md +640 -640
  783. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  784. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  785. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  786. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  787. package/src/engine/control/first-person/math/Spring.js +71 -71
  788. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  789. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  790. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  791. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  792. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  793. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  794. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  795. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  796. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  797. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  798. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  799. package/src/engine/ecs/EntityManager.d.ts +7 -3
  800. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  801. package/src/engine/ecs/EntityManager.js +10 -5
  802. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  803. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  804. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  805. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  806. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  807. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  808. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  809. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  810. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  811. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  812. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  813. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  814. package/src/engine/ecs/ik/IKProblem.js +42 -42
  815. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  816. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  817. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  818. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  819. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  820. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  821. package/src/engine/ecs/parent/EntityNode.js +2 -2
  822. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  823. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  824. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  825. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  826. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  827. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  828. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  829. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  830. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  831. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  832. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  833. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  834. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  835. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  836. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +5 -1
  837. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  838. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  839. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  840. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  841. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  842. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  843. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  844. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  845. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  846. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  847. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  848. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  849. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  850. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  851. package/src/engine/ecs/transform/Transform64.js +814 -814
  852. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  853. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  854. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  855. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  856. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  857. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  858. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  859. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  860. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  861. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  862. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  863. package/src/engine/graphics/FrameThrottle.js +2 -2
  864. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  865. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  866. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  867. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  868. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  869. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  870. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  871. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  872. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  873. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  874. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  875. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  876. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  877. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  878. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  879. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  880. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  881. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  882. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  883. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  884. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  885. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  886. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  887. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  888. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  889. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  890. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  891. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  892. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  893. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  894. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  895. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  896. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  897. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  898. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  899. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  900. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  901. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  902. package/src/engine/graphics/ecs/water/Water.js +104 -104
  903. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  904. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  905. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  906. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  907. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  908. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  909. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  910. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  911. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  912. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  913. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  914. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  915. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  916. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  917. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  918. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  919. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  920. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  921. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  922. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  923. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  924. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  925. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  926. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  927. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  928. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  929. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  930. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  931. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  932. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  933. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  934. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  935. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  936. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  937. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  938. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  940. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  941. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  942. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  943. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  944. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  945. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  946. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  947. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  948. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  949. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  950. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  951. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  952. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  953. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  954. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  955. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  956. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  957. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  958. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  959. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  960. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  961. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  962. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  963. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  964. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  965. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  966. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  967. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  968. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  969. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  970. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  971. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  972. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  973. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  974. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  975. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  976. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  977. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  978. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  979. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  980. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  981. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  982. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  983. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  984. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  985. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  986. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  987. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  988. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  989. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  990. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  991. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  992. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  993. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  994. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  995. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  996. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  997. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  998. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  999. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  1000. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  1001. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  1002. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  1003. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  1004. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  1005. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  1006. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  1007. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  1008. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  1009. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  1010. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  1011. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  1012. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  1013. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  1014. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  1015. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  1016. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1017. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1018. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1019. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1020. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1021. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1022. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1023. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1024. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1025. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1026. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1027. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1028. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1029. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1030. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1031. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1032. package/src/engine/graphics3/LightSystem3.js +211 -211
  1033. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1034. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1035. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1036. package/src/engine/graphics3/PickingQuery.js +48 -48
  1037. package/src/engine/graphics3/PickingResult.js +47 -47
  1038. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1039. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1040. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1041. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1042. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1043. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1044. package/src/engine/graphics3/VolumetricLightMap.d.ts +90 -0
  1045. package/src/engine/graphics3/VolumetricLightMap.d.ts.map +1 -0
  1046. package/src/engine/graphics3/VolumetricLightMap.js +160 -0
  1047. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.d.ts +32 -0
  1048. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.d.ts.map +1 -0
  1049. package/src/engine/graphics3/VolumetricLightMapSerializationAdapter.js +67 -0
  1050. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts +98 -0
  1051. package/src/engine/graphics3/VolumetricLightMapSystem3.d.ts.map +1 -0
  1052. package/src/engine/graphics3/VolumetricLightMapSystem3.js +261 -0
  1053. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1054. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1055. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1056. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1057. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1058. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1059. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1060. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1061. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1062. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1063. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1064. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1065. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1066. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1067. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1068. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1069. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1070. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1071. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1072. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1073. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1074. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1075. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1076. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1077. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1078. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1079. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1080. package/src/engine/graphics3/make_default_environment.js +107 -107
  1081. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1082. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1083. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1084. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1085. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1086. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1087. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1088. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1089. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1090. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1091. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1092. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1093. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1094. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1095. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1096. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1097. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1098. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1099. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1100. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1101. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1102. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1103. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1104. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1105. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1106. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1107. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1108. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1109. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1110. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1111. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1112. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1113. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1114. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1115. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1116. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1117. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1118. package/src/engine/input/ecs/components/Input.js +118 -118
  1119. package/src/engine/input/ecs/components/InputController.js +78 -78
  1120. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1121. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1122. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1123. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1124. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1125. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1126. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1127. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1128. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1129. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1130. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1131. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1132. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1133. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1134. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1135. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1136. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1137. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1138. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1139. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1140. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1141. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1142. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1143. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1144. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1145. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1146. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1147. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1148. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1149. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1150. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1151. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1152. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1153. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1154. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1155. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1156. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1157. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1158. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1159. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1160. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1161. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1162. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1163. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1164. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1165. package/src/engine/interpolation/Interpoland.js +49 -49
  1166. package/src/engine/interpolation/Interpolated.js +149 -149
  1167. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1168. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1169. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1170. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1171. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1172. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1173. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1174. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1175. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1176. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1177. package/src/engine/network/README.md +52 -52
  1178. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1179. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1180. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1181. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1182. package/src/engine/network/convertPathToURL.js +2 -2
  1183. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1184. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1185. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1186. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1187. package/src/engine/network/core/sequence/seq16.js +69 -69
  1188. package/src/engine/network/core/sequence/seq32.js +73 -73
  1189. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1190. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1191. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1192. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1193. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1194. package/src/engine/network/sim/ActionLog.js +152 -152
  1195. package/src/engine/network/sim/SimAction.js +273 -273
  1196. package/src/engine/network/sim/Snapshotter.js +222 -222
  1197. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1198. package/src/engine/network/state/InputRing.js +134 -134
  1199. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1200. package/src/engine/network/time/TimeDilation.js +62 -62
  1201. package/src/engine/network/transport/Channel.js +336 -336
  1202. package/src/engine/network/transport/Transport.js +203 -203
  1203. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1204. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1205. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1206. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1207. package/src/engine/network/xhr.js +23 -23
  1208. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1209. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1210. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1211. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1212. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1213. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1214. package/src/engine/physics/PLAN.md +1120 -1094
  1215. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1216. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1217. package/src/engine/physics/REVIEW_002.md +151 -151
  1218. package/src/engine/physics/REVIEW_003.md +166 -166
  1219. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1220. package/src/engine/physics/constraint/DofMode.js +35 -35
  1221. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1222. package/src/engine/physics/contact/combine_material.js +35 -35
  1223. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1224. package/src/engine/physics/ecs/Collider.js +177 -177
  1225. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1226. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1227. package/src/engine/physics/ecs/Joint.js +475 -475
  1228. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1229. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1230. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1231. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1232. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1233. package/src/engine/physics/ecs/SleepState.js +12 -12
  1234. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1235. package/src/engine/physics/fluid/FluidField.js +867 -867
  1236. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1237. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1238. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1239. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1240. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1241. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1242. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  1243. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1244. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1245. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1246. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1247. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1248. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1249. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1250. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1251. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1252. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1253. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1254. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1255. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1256. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1257. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1258. package/src/engine/physics/fluid/prototype.js +126 -126
  1259. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1260. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1261. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1262. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1263. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1264. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1265. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1266. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1267. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1268. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1269. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1270. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1271. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1272. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1273. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1274. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1275. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1276. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1277. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1278. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1279. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1280. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1281. package/src/engine/physics/integration/integrate_position.js +97 -97
  1282. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1283. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1284. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1285. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1286. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1287. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1288. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1289. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1290. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1291. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1292. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1293. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1294. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1295. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1296. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1297. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1298. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1299. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1300. package/src/engine/physics/queries/raycast.js +11 -5
  1301. package/src/engine/physics/queries/shape_cast.js +417 -417
  1302. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1303. package/src/engine/physics/solver/friction_cone.js +37 -37
  1304. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1305. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1306. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1307. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1308. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1309. package/src/engine/scene/transitionToScene.js +121 -121
  1310. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1311. package/src/engine/simulation/Ticker.js +33 -0
  1312. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1313. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1314. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1315. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1316. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1317. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1318. package/src/engine/sound/simulation/README.md +277 -277
  1319. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1320. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1321. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1322. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1323. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1324. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1325. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1326. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1327. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1328. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1329. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1330. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1331. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1332. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1333. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1334. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1335. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1336. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1337. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1338. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1339. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1340. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1341. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1342. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1343. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1344. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1345. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1346. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1347. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1348. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1349. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1350. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1351. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1352. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1353. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1354. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1355. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1356. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1357. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1358. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1359. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1360. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1361. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1362. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1363. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1364. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1365. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1366. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1367. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1368. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1369. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1370. package/src/engine/sound/sopra/README.md +643 -643
  1371. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1372. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1373. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1374. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1375. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1376. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1377. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1378. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1379. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1380. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1381. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1382. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1383. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1384. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1385. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1386. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1387. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1388. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1389. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1390. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1391. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1392. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1393. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1394. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1395. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1396. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1397. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1398. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1399. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1400. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1401. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1402. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1403. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1404. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1405. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1406. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1407. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1408. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1409. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1410. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1411. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1412. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1413. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1414. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1415. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1416. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1417. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1418. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1419. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1420. package/src/engine/ui/DraggableAspect.js +115 -115
  1421. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1422. package/src/generation/COORDINATES.md +54 -54
  1423. package/src/generation/GridGenerationResource.js +24 -24
  1424. package/src/generation/GridTaskGroup.js +214 -214
  1425. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1426. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1427. package/src/generation/automata/CellularAutomata.js +22 -22
  1428. package/src/generation/compute_writes_union.js +25 -25
  1429. package/src/generation/filtering/CellFilter.js +117 -117
  1430. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1431. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1432. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1433. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1434. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1435. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1436. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1437. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1438. package/src/generation/grid/GridData.js +233 -233
  1439. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1440. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1441. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1442. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1443. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1444. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1445. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1446. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1447. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1448. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1449. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1450. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1451. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1452. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1453. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1454. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1455. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1456. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1457. package/src/generation/placement/action/GridCellAction.js +45 -45
  1458. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1459. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1460. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1461. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1462. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1463. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1464. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1465. package/src/generation/theme/ThemeEngine.js +666 -666
  1466. package/src/shade/RENDERER_CONTRACT.md +568 -568
  1467. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1468. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1469. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1470. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1471. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1472. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1473. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1474. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1475. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1476. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1477. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1478. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1479. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1480. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1481. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1482. package/src/shade/device/mock/texture_copy.js +287 -287
  1483. package/src/shade/device/timing/GPUTimer.js +285 -285
  1484. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1485. package/src/shade/playground/add_random_lights.js +49 -49
  1486. package/src/shade/playground/add_random_meshes.js +83 -83
  1487. package/src/shade/playground/animation_prototype.js +508 -508
  1488. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1489. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1490. package/src/shade/playground/enable_picker.js +59 -59
  1491. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1492. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1493. package/src/shade/playground/gltf_viewer.js +282 -282
  1494. package/src/shade/playground/load_default_environment.js +19 -19
  1495. package/src/shade/playground/main.js +2727 -2727
  1496. package/src/shade/playground/make_animated_instances.js +117 -117
  1497. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1498. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1499. package/src/shade/playground/sample.js +234 -234
  1500. package/src/shade/playground/sample_load_gltf.js +102 -102
  1501. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1502. package/src/shade/renderer/Renderer.js +2436 -2436
  1503. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1504. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1505. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1506. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1507. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1508. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1509. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1510. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1511. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1512. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1513. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1514. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1515. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1516. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1517. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1518. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1519. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1520. package/src/shade/renderer/camera/Camera.js +281 -281
  1521. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1522. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1523. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1524. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1525. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1526. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1527. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1528. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1529. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1530. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1531. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1532. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1533. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1534. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1535. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1536. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1537. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1538. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1539. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1540. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1541. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1542. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1543. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1544. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1545. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1546. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1547. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1548. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1549. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1550. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1551. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1552. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1553. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1554. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1555. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1556. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1557. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1558. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1559. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1560. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1561. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1562. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1563. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1564. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1565. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1566. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1567. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1568. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1569. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1570. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1571. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1572. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1573. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1574. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1575. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1576. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1577. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1578. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1579. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1580. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1581. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1582. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1583. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1584. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1585. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1586. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1587. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1588. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1589. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1590. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1591. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1592. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1593. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1594. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1595. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1596. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1597. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1598. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1599. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1600. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1601. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1602. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1603. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1604. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1605. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1606. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1607. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1608. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1609. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1610. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1611. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1612. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1613. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1616. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1617. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1618. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1619. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1620. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1621. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1622. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1623. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1624. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1626. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1627. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1628. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1629. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1630. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1631. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1632. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1633. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1634. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1635. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1636. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1637. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1638. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1639. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1640. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1641. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1642. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1643. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1644. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1645. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1646. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1647. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1648. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1649. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1650. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1651. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1652. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1653. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1654. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1655. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1656. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1657. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1658. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1659. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1660. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.d.ts +23 -0
  1661. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.d.ts.map +1 -0
  1662. package/src/shade/renderer/global_illumination/brick4/cpu/brick4_empty_gpu_structure.js +34 -0
  1663. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1664. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1665. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1666. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1667. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1668. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1669. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1670. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1671. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1672. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1673. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1674. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1675. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1676. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1677. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1678. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1679. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1680. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1681. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1682. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1683. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1684. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1685. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1686. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1687. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1688. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1689. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1690. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1691. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1692. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1693. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1694. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1695. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1696. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1697. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1698. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1699. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1700. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1701. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1702. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1703. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1704. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1705. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1706. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1707. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1708. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1709. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1710. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1711. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1712. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1713. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1714. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1715. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1716. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1717. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1718. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1719. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1720. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1721. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1722. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1723. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1724. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1725. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1726. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1727. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1728. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1729. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1730. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1731. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1732. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1733. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1734. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1735. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1736. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1737. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1738. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1739. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1740. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1741. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1742. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1743. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1744. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1745. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1746. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1747. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1748. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1749. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1750. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1751. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1752. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1753. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1754. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1755. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1756. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1757. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1758. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1759. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1760. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1761. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1762. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1763. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1764. package/src/shade/renderer/scene/optimization/geometry/internal/anchor_grow_topological.d.ts.map +1 -1
  1765. package/src/shade/renderer/scene/optimization/geometry/internal/anchor_grow_topological.js +12 -3
  1766. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1767. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1768. package/src/shade/renderer/scene/optimization/geometry/internal/match_validate_topology.js +3 -3
  1769. package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_window.js +3 -3
  1770. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1771. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1772. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1773. package/src/shade/renderer/scene/serialization/deserialize_scene.d.ts.map +1 -1
  1774. package/src/shade/renderer/scene/serialization/deserialize_scene.js +0 -2
  1775. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1776. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1777. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1778. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1779. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1780. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1781. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1782. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1783. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1784. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1785. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1786. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1787. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1788. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1789. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1790. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1791. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1792. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1793. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1794. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1795. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1796. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1797. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1798. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1799. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1800. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1801. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1802. package/src/shade/renderer/water/shader_water.js +246 -246
  1803. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1804. package/src/shade/vitest.setup.mjs +89 -89
  1805. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1806. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1807. package/src/view/controller/controls/Vector2Control.js +83 -83
  1808. package/src/view/controller/controls/Vector3Control.js +90 -90
  1809. package/src/view/elements/Group.js +56 -56
  1810. package/src/view/elements/drawCheckers.js +33 -33
  1811. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1812. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1813. package/src/view/layout/DockPanel.js +51 -51
  1814. package/src/view/layout/SplitView.js +365 -365
  1815. package/src/view/layout/show_popup_menu.js +81 -81
  1816. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1817. package/src/view/minimap/MinimapImageCache.js +88 -88
  1818. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1819. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1820. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1821. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1822. package/src/view/minimap/minimap_focus_area.js +97 -97
  1823. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1824. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1825. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1826. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1827. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1828. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1829. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1830. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1831. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1832. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1833. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1834. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1835. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1836. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1837. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1838. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1839. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1840. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1841. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1842. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1843. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1844. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1845. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1846. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1847. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1848. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1849. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1850. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1851. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1852. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1853. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1854. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1855. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1856. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1857. package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
  1858. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.d.ts +0 -32
  1859. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.d.ts.map +0 -1
  1860. package/src/shade/renderer/scene/optimization/geometry/internal/bt_face_loop_helpers.js +0 -77
  1861. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.d.ts +0 -12
  1862. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.d.ts.map +0 -1
  1863. package/src/shade/renderer/scene/optimization/geometry/internal/bt_find_edge_between.js +0 -35
  1864. package/src/shade/renderer/scene/serialization/linear_to_pq.d.ts +0 -2
  1865. package/src/shade/renderer/scene/serialization/linear_to_pq.d.ts.map +0 -1
  1866. package/src/shade/renderer/scene/serialization/linear_to_pq.js +0 -14
  1867. package/src/shade/renderer/scene/serialization/pq_to_linear.d.ts +0 -9
  1868. package/src/shade/renderer/scene/serialization/pq_to_linear.d.ts.map +0 -1
  1869. package/src/shade/renderer/scene/serialization/pq_to_linear.js +0 -30
@@ -1,1738 +1,1738 @@
1
- import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
- import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
- import { body_id_index } from "../body/BodyStorage.js";
4
- import { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
- import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
- import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
7
- import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
8
- import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
9
- import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
10
- import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
11
- import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
12
- import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
13
- import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
14
- import {
15
- CAPSULE_BOX_CONTACT_STRIDE,
16
- CAPSULE_BOX_MAX_CONTACTS,
17
- capsule_box_multi_contacts,
18
- capsule_capsule_contact,
19
- CAPSULE_CAPSULE_CONTACT_STRIDE,
20
- capsule_capsule_multi_contacts,
21
- capsule_sphere_contact,
22
- } from "./capsule_contacts.js";
23
- import {
24
- capsule_triangle_contact,
25
- CAPSULE_TRIANGLE_CONTACT_STRIDE,
26
- CAPSULE_TRIANGLE_MAX_CONTACTS,
27
- } from "./capsule_triangle_contact.js";
28
- import { aabb3_transform_oriented_inverse } from "../../../core/geom/3d/aabb/aabb3_transform_oriented_inverse.js";
29
- import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
30
- import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
31
- import { PosedShape3D } from "../../../core/geom/3d/shape/PosedShape3D.js";
32
- import { sphere_box_contact } from "./sphere_box_contact.js";
33
- import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
34
- import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
35
-
36
- const posed_a = new PosedShape3D();
37
- const posed_b = new PosedShape3D();
38
-
39
- // Penetration axis + depth from the robust GJK + EPA query
40
- // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
41
- // internally, so the narrowphase no longer keeps a simplex buffer).
42
- const epa_result = new Float64Array(3);
43
- const sphere_result = new Float64Array(4);
44
- const closed_form_result = new Float64Array(10);
45
-
46
- /** Scratch for the capsule-capsule multi-contact manifold (2 contacts max). */
47
- const capsule_capsule_result = new Float64Array(2 * CAPSULE_CAPSULE_CONTACT_STRIDE);
48
- const sphere_triangle_result = new Float64Array(10);
49
- const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
- const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
- const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
52
- const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
53
- const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
54
-
55
- /**
56
- * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
57
- * feature_id, friction, restitution.
58
- *
59
- * The `feature_id` (offset 10) is a stable cross-frame identifier of the
60
- * geometric feature pair that produced this contact — used by the
61
- * match-and-merge pass in {@link narrowphase_step} to carry warm-start
62
- * impulses from the previous frame's manifold to the slot index that
63
- * corresponds to the same physical contact. A value of 0 means
64
- * "no feature info, fall back to position matching".
65
- *
66
- * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
67
- * coefficients for the specific (colliderA, colliderB) pair that produced this
68
- * contact, combined here (the only place that knows the exact source collider
69
- * on each side) and carried into the manifold so a compound body's per-collider
70
- * materials are honoured per-contact.
71
- *
72
- * @type {number}
73
- */
74
- const CANDIDATE_STRIDE = 13;
75
-
76
- /**
77
- * Combined friction / restitution for the collider pair currently being
78
- * dispatched. Set once at the top of {@link dispatch_pair} (which is called
79
- * per collider pair) and written into every contact that call appends, so
80
- * each contact carries the material of its actual source colliders. Module
81
- * scratch rather than threaded through every `append_contact` call site.
82
- * @type {number}
83
- */
84
- let g_pair_friction = 0;
85
- let g_pair_restitution = 0;
86
-
87
- /**
88
- * Maximum number of contacts emitted into the per-pair manifold after the
89
- * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
90
- * @type {number}
91
- */
92
- const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
93
-
94
- /**
95
- * Position-fallback tolerance for warm-start matching: when a candidate
96
- * contact has no feature id (or none of the previous-frame contacts shares
97
- * its id), match by closest world_a within this 3-D distance.
98
- *
99
- * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
100
- * — generous enough to follow small inter-frame contact migration on
101
- * curved surfaces, tight enough that distinct contacts on a single
102
- * manifold (typically >5 cm apart) don't get confused with each other.
103
- *
104
- * @type {number}
105
- */
106
- const MATCH_TOL_SQR = 0.02 * 0.02;
107
-
108
- /**
109
- * Per-(colliderA, colliderB) feature-id salt. Each emitter numbers its
110
- * features in a small private range (sphere-box voronoi 1..27, capsule-box
111
- * 1..k, triangles 1..N, …), so two SUB-PAIRS of a compound body can produce
112
- * identical fids for unrelated features and warm-start impulses could be
113
- * inherited across collider pairs. Salting by the sub-pair index keeps every
114
- * fid unique across the body pair while staying exactly representable in f64
115
- * (sub_pair · 2³² + fid stays far below 2⁵³ for any realistic compound; no
116
- * single-pair emitter exceeds 2³²). Sub-pair 0 — the universal
117
- * single-collider case — is left unsalted, so simple bodies see unchanged
118
- * fids.
119
- * @type {number}
120
- */
121
- const FID_PAIR_SALT = 0x100000000; // 2^32
122
-
123
- /**
124
- * Distance ceiling for a feature-id warm-start match. A fid match may track
125
- * a fast-sliding contact far beyond {@link MATCH_TOL_SQR} — that is its
126
- * advantage over position matching — but an impulse inherited across half a
127
- * metre in one frame is stale by any standard (a teleported body re-deriving
128
- * the same voronoi bucket, or a fid collision between unrelated features).
129
- * The position fallback keeps its own 2 cm gate.
130
- * @type {number}
131
- */
132
- const FID_MATCH_MAX_D2 = 0.5 * 0.5;
133
-
134
- /**
135
- * Scratch for the previous-frame contact snapshot taken at the top of the
136
- * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
137
- * contacts, 7 floats per contact:
138
- * 0 : feature_id
139
- * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
140
- * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
141
- *
142
- * Snapshotting upfront decouples the read (from the slot's previous state)
143
- * from the write (the new contact data + impulse copy), avoiding the
144
- * read-after-write hazard when the matching mapping shuffles indices.
145
- *
146
- * @type {Float64Array}
147
- */
148
- const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
149
-
150
- /**
151
- * Per-prev-contact "already claimed by a candidate" flag.
152
- * @type {Uint8Array}
153
- */
154
- const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
155
-
156
- /**
157
- * For each kept candidate, the matched prev-contact index in
158
- * `[0, prev_count)` or `-1` if no match.
159
- * @type {Int32Array}
160
- */
161
- const cand_to_prev = new Int32Array(MAX_KEPT);
162
-
163
- /**
164
- * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
165
- * 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
166
- * buffer capacity (64) so it covers any per-pair candidate count. Without it,
167
- * several existing contacts that share one triangle's `feature_id` (the
168
- * box/capsule-triangle paths emit multiple contacts per triangle) would all
169
- * match the same first candidate, collapsing the manifold to duplicate witness
170
- * points.
171
- * @type {Uint8Array}
172
- */
173
- const redetect_claimed = new Uint8Array(64);
174
-
175
- /**
176
- * Per body-pair scratch buffer for candidate contacts produced by the
177
- * cross-product of A's colliders × B's colliders. Sized generously for
178
- * typical compound bodies (each collider-pair contributes 1..4 contacts;
179
- * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
180
- * @type {Float64Array}
181
- */
182
- const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
183
-
184
- /**
185
- * Maximum triangles a concave-side enumerator can produce per pair.
186
- * The query AABB is bounded by the broadphase's fattened envelope of
187
- * the convex-side body, so a single concave-vs-convex pair typically
188
- * yields tens of triangles, not thousands. 1024 is the safety cap.
189
- *
190
- * For a heightmap the per-cell triangle count scales O(N²) with the
191
- * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
192
- * triangles, and there are N×N sub-cells per sampler cell). The bounded
193
- * query AABB keeps the cell count small, so a typical pair stays well
194
- * inside 1024 at moderate tessellation; the silent-drop backstop below
195
- * covers any overflow at extreme values.
196
- *
197
- * Enumerators return the TRUE overlap count and have no bounds check of
198
- * their own: writes past the scratch buffer are dropped (typed-array OOB
199
- * semantics), so on overflow the consume loop MUST clamp the returned
200
- * count to this cap or it reads `undefined` past the end and poisons the
201
- * manifold with NaN. The worst case after clamping is a missed contact
202
- * on a far edge of the filtered region, which the next-step
203
- * rebroadphase corrects.
204
- *
205
- * @type {number}
206
- */
207
- const MAX_TRIANGLES_PER_PAIR = 1024;
208
-
209
- /**
210
- * Per-pair scratch for the concave-side triangle decomposition.
211
- * @type {Float64Array}
212
- */
213
- const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
214
-
215
- /**
216
- * Flyweight triangle shape — rebound to each successive triangle slice
217
- * of `triangle_buffer` during the concave-side dispatch loop. Zero
218
- * allocation per triangle.
219
- * @type {Triangle3D}
220
- */
221
- const triangle_shape = new Triangle3D();
222
-
223
- /**
224
- * Scratch AABB buffers used only by the concave-side dispatch:
225
- * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
226
- * - `concave_world_aabb` : convex shape's world AABB
227
- * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
228
- * (what the triangle enumerator filters against)
229
- */
230
- const concave_local_aabb = new Float64Array(6);
231
- const concave_world_aabb = new Float64Array(6);
232
- const concave_query_aabb = new Float64Array(6);
233
-
234
- /**
235
- * Rotate + translate a triangle's three vertices from a concave body's
236
- * local frame into world space: writes 9 floats `(A_w, B_w, C_w)` into
237
- * `out`. Inlined `q · v · q*` + translate per vertex (see
238
- * PosedShape3D.support for the V8-inliner rationale); the operation order
239
- * matches the blocks this replaced bit-exactly. Per-vertex cost ~21 flops
240
- * × 3 — essentially free next to any narrowphase kernel it feeds.
241
- *
242
- * Shared by the sphere / box / capsule closed-form triangle fast-paths in
243
- * the concave dispatch, which each carried an identical inlined copy.
244
- */
245
- function triangle_vertices_to_world(out, ax, ay, az, bx, by, bz, cx, cy, cz, qx, qy, qz, qw, px, py, pz) {
246
- const axi = qw * ax + qy * az - qz * ay;
247
- const ayi = qw * ay + qz * ax - qx * az;
248
- const azi = qw * az + qx * ay - qy * ax;
249
- const awi = -qx * ax - qy * ay - qz * az;
250
-
251
- out[0] = axi * qw + awi * -qx + ayi * -qz - azi * -qy + px;
252
- out[1] = ayi * qw + awi * -qy + azi * -qx - axi * -qz + py;
253
- out[2] = azi * qw + awi * -qz + axi * -qy - ayi * -qx + pz;
254
-
255
- const bxi = qw * bx + qy * bz - qz * by;
256
- const byi = qw * by + qz * bx - qx * bz;
257
- const bzi = qw * bz + qx * by - qy * bx;
258
- const bwi = -qx * bx - qy * by - qz * bz;
259
-
260
- out[3] = bxi * qw + bwi * -qx + byi * -qz - bzi * -qy + px;
261
- out[4] = byi * qw + bwi * -qy + bzi * -qx - bxi * -qz + py;
262
- out[5] = bzi * qw + bwi * -qz + bxi * -qy - byi * -qx + pz;
263
-
264
- const cxi = qw * cx + qy * cz - qz * cy;
265
- const cyi = qw * cy + qz * cx - qx * cz;
266
- const czi = qw * cz + qx * cy - qy * cx;
267
- const cwi = -qx * cx - qy * cy - qz * cz;
268
-
269
- out[6] = cxi * qw + cwi * -qx + cyi * -qz - czi * -qy + px;
270
- out[7] = cyi * qw + cwi * -qy + czi * -qx - cxi * -qz + py;
271
- out[8] = czi * qw + cwi * -qz + cxi * -qy - cyi * -qx + pz;
272
- }
273
-
274
- /** Destination for {@link triangle_vertices_to_world}. */
275
- const scratch_tri_world = new Float64Array(9);
276
-
277
- /**
278
- * Append one contact to the candidate buffer. Returns the new count.
279
- *
280
- * @param {number} count
281
- * @param {number} wax
282
- * @param {number} way
283
- * @param {number} waz
284
- * @param {number} wbx
285
- * @param {number} wby
286
- * @param {number} wbz
287
- * @param {number} nx
288
- * @param {number} ny
289
- * @param {number} nz
290
- * @param {number} depth
291
- * @param {number} feature_id stable cross-frame ID for warm-start matching;
292
- * `0` means no info — match-and-merge will fall back to position.
293
- * @returns {number}
294
- */
295
- function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
296
- if (count * CANDIDATE_STRIDE >= candidates.length){
297
- return count;
298
- }
299
-
300
- const off = count * CANDIDATE_STRIDE;
301
-
302
- candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
303
- candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
304
- candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
305
- candidates[off + 9] = depth;
306
- candidates[off + 10] = feature_id;
307
- candidates[off + 11] = g_pair_friction;
308
- candidates[off + 12] = g_pair_restitution;
309
-
310
- return count + 1;
311
- }
312
-
313
- /**
314
- * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
315
- * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
316
- * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
317
- * faces + 1 interior). Stable across frames as long as the sphere stays
318
- * in the same region — the typical case in steady-state contact.
319
- *
320
- * Encodes:
321
- * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
322
- * bucket_y, bucket_z similarly
323
- * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
324
- *
325
- * The "+1" offset ensures the result is never 0, since 0 is reserved
326
- * for "no feature info, use position-fallback".
327
- *
328
- * @param {number} sx sphere centre x
329
- * @param {number} sy
330
- * @param {number} sz
331
- * @param {number} bx box centre x
332
- * @param {number} by
333
- * @param {number} bz
334
- * @param {number} bqx box quaternion x
335
- * @param {number} bqy
336
- * @param {number} bqz
337
- * @param {number} bqw
338
- * @param {number} hx box half-extent x
339
- * @param {number} hy
340
- * @param {number} hz
341
- * @returns {number}
342
- */
343
- function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
344
- // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
345
- // to get the sphere centre in box-local frame. Inlined for the same
346
- // V8-inliner reason described in PosedShape3D.support — see
347
- // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
348
- const dx = sx - bx;
349
- const dy = sy - by;
350
- const dz = sz - bz;
351
-
352
- const tx0 = bqw * dx - bqy * dz + bqz * dy;
353
- const ty0 = bqw * dy - bqz * dx + bqx * dz;
354
- const tz0 = bqw * dz - bqx * dy + bqy * dx;
355
- const tw0 = bqx * dx + bqy * dy + bqz * dz;
356
-
357
- const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
358
- const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
359
- const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
360
-
361
- const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
362
- const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
363
- const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
364
-
365
- return 1 + bx_b + 3 * by_b + 9 * bz_b;
366
- }
367
-
368
- /**
369
- * Multi-point contact manifold between two convex polytopes (authored
370
- * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
371
- * GJK + EPA finds the separating axis in O(support queries) — face-count
372
- * independent, where SAT's edge-pair term is prohibitive — then
373
- * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
374
- * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
375
- * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
376
- * state → reset-and-resimulate determinism.
377
- *
378
- * @param {number} count
379
- * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
380
- * @param {Transform} trA
381
- * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
382
- * @param {Transform} trB
383
- * @param {Function} append_contact
384
- * @returns {number}
385
- */
386
- function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
387
- posed_a.setup(hullA, trA.position, trA.rotation);
388
- posed_b.setup(hullB, trB.position, trB.rotation);
389
- const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
390
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
391
-
392
- // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
393
- // writes the final normal into convex_manifold_result[0..2].
394
- convex_hull_clip(convex_manifold_result,
395
- hullA, trA.position, trA.rotation,
396
- hullB, trB.position, trB.rotation,
397
- epa_result[0], epa_result[1], epa_result[2]);
398
- const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
399
- const cc = convex_manifold_result[3] | 0;
400
- // Clipped points migrate across features as the hulls rotate, so leave
401
- // fid = 0 and let the match-and-merge pass position-match (as box-box does).
402
- for (let k = 0; k < cc; k++) {
403
- const base = 4 + k * 7;
404
- count = append_contact(count,
405
- convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
406
- convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
407
- nx, ny, nz,
408
- convex_manifold_result[base + 6],
409
- 0);
410
- }
411
- return count;
412
- }
413
-
414
- /**
415
- * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
416
- * by shape type and appends 0..K contacts to the candidate buffer. Returns
417
- * the new candidate count.
418
- *
419
- * @param {number} count
420
- * @param {Collider} colA
421
- * @param {Transform} trA
422
- * @param {Collider} colB
423
- * @param {Transform} trB
424
- * @returns {number}
425
- */
426
- function dispatch_pair(count, colA, trA, colB, trB) {
427
- const shapeA = colA.shape;
428
- const shapeB = colB.shape;
429
-
430
- // Per-contact materials: combine the two source colliders' coefficients
431
- // once here (this is the only place that knows the exact collider on each
432
- // side) and stamp them onto every contact this dispatch appends. The
433
- // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
434
- // material fields — it never writes to a manifold, so 0 is fine there.
435
- const fa = colA.friction, fb = colB.friction;
436
-
437
- if (fa !== undefined && fb !== undefined) {
438
-
439
- g_pair_friction = combine_friction(fa, fb);
440
- g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
441
-
442
- } else {
443
- g_pair_friction = 0;
444
- g_pair_restitution = 0;
445
- }
446
-
447
- // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
448
- // SphereShape3D (arbitrary radius). Both expose `radius`.
449
- const isSphereA = shapeA.isSphereShape3D === true;
450
- const isSphereB = shapeB.isSphereShape3D === true;
451
- // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
452
- // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
453
- const isBoxA = shapeA.isBoxShape3D === true;
454
- const isBoxB = shapeB.isBoxShape3D === true;
455
- const isCapsuleA = shapeA.isCapsuleShape3D === true;
456
- const isCapsuleB = shapeB.isCapsuleShape3D === true;
457
-
458
- // sphere-sphere
459
- if (isSphereA && isSphereB) {
460
- const ra = shapeA.radius, rb = shapeB.radius;
461
-
462
- const ok = sphere_sphere_contact(
463
- sphere_result,
464
- trA.position.x, trA.position.y, trA.position.z,
465
- trB.position.x, trB.position.y, trB.position.z,
466
- ra, rb
467
- );
468
-
469
- if (!ok) return count;
470
-
471
- const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
472
-
473
- // Sphere-sphere produces exactly one contact per pair; fid = 1
474
- // identifies it as a real feature (distinguishes from "no info" = 0)
475
- // and is trivially stable across frames. Witnesses are the surface
476
- // points along the (unit) normal, scaled by each sphere's radius.
477
- return append_contact(count,
478
- trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
479
- trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
480
- nx, ny, nz, sphere_result[3], 1);
481
- }
482
-
483
- // sphere ↔ box
484
- if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
485
- const sphereTr = isSphereA ? trA : trB;
486
- const sphereShape = isSphereA ? shapeA : shapeB;
487
- const boxTr = isSphereA ? trB : trA;
488
- const boxShape = isSphereA ? shapeB : shapeA;
489
- const bh = boxShape.half_extents;
490
-
491
- const ok = sphere_box_contact(
492
- closed_form_result,
493
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
494
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
495
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
496
- bh.x, bh.y, bh.z
497
- );
498
-
499
- if (!ok) return count;
500
-
501
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
502
-
503
- const depth = closed_form_result[3];
504
- const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
505
- const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
506
- // Feature id from the box-local voronoi region (1..27) — stable
507
- // while the sphere stays on the same face / edge / vertex of the
508
- // box, which is the steady-state case for resting / sliding contact.
509
-
510
- const fid = sphere_box_voronoi_fid(
511
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
512
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
513
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
514
- bh.x, bh.y, bh.z
515
- );
516
-
517
- if (isSphereA) {
518
- return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
519
- } else {
520
- return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
521
- }
522
- }
523
-
524
- // box-box multi-point
525
- if (isBoxA && isBoxB) {
526
- const ah = shapeA.half_extents;
527
- const bh = shapeB.half_extents;
528
- const ok = box_box_manifold(
529
- box_manifold_result,
530
- trA.position.x, trA.position.y, trA.position.z,
531
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
532
- ah.x, ah.y, ah.z,
533
- trB.position.x, trB.position.y, trB.position.z,
534
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
535
- bh.x, bh.y, bh.z
536
- );
537
- if (!ok) return count;
538
- const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
539
- const cc = box_manifold_result[3] | 0;
540
- // Box-box manifolds: closed-form clipping doesn't expose stable
541
- // per-contact feature ids (a contact migrates from "incident-vertex k"
542
- // to "clip-intersection on edge j" as the boxes rotate). Leave
543
- // fid = 0 so the match-and-merge pass uses position-fallback —
544
- // the per-contact clipped points are spread by face geometry and
545
- // typically stay >>MATCH_TOL apart, so position matching is
546
- // unambiguous frame-to-frame.
547
- for (let k = 0; k < cc; k++) {
548
- const base = 4 + k * 7;
549
- count = append_contact(count,
550
- box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
551
- box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
552
- nx, ny, nz,
553
- box_manifold_result[base + 6],
554
- 0);
555
- }
556
- return count;
557
- }
558
-
559
- // capsule-capsule (multi-point: near-parallel overlap emits both ends of
560
- // the overlap interval; everything else the single closest-point contact)
561
- if (isCapsuleA && isCapsuleB) {
562
- const a_shape = colA.shape, b_shape = colB.shape;
563
- const n_cc = capsule_capsule_multi_contacts(
564
- capsule_capsule_result,
565
- trA.position.x, trA.position.y, trA.position.z,
566
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
567
- a_shape.radius, a_shape.height * 0.5,
568
- trB.position.x, trB.position.y, trB.position.z,
569
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
570
- b_shape.radius, b_shape.height * 0.5
571
- );
572
- for (let k = 0; k < n_cc; k++) {
573
- const off = k * CAPSULE_CAPSULE_CONTACT_STRIDE;
574
- // fid = k + 1: stable per interval end (1-based; 0 = no info).
575
- count = append_contact(count,
576
- capsule_capsule_result[off], capsule_capsule_result[off + 1], capsule_capsule_result[off + 2],
577
- capsule_capsule_result[off + 3], capsule_capsule_result[off + 4], capsule_capsule_result[off + 5],
578
- capsule_capsule_result[off + 6], capsule_capsule_result[off + 7], capsule_capsule_result[off + 8],
579
- capsule_capsule_result[off + 9], k + 1);
580
- }
581
- return count;
582
- }
583
-
584
- // capsule ↔ sphere
585
- if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
586
- const capsuleTr = isCapsuleA ? trA : trB;
587
- const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
588
- const sphereTr = isCapsuleA ? trB : trA;
589
- const sphereShape = isCapsuleA ? colB.shape : colA.shape;
590
- const ok = capsule_sphere_contact(
591
- closed_form_result,
592
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
593
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
594
- capsuleShape.radius, capsuleShape.height * 0.5,
595
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
596
- );
597
- if (!ok) return count;
598
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
599
- const depth = closed_form_result[3];
600
- const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
601
- const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
602
- // Single contact per capsule-sphere pair; fid = 1.
603
- if (isCapsuleA) {
604
- return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
605
- } else {
606
- return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
607
- }
608
- }
609
-
610
- // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
611
- if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
612
- const capsuleTr = isCapsuleA ? trA : trB;
613
- const capsuleShape = isCapsuleA ? shapeA : shapeB;
614
- const boxTr = isCapsuleA ? trB : trA;
615
- const boxShape = isCapsuleA ? shapeB : shapeA;
616
- const bh = boxShape.half_extents;
617
- const cc = capsule_box_multi_contacts(
618
- capsule_box_multi_result,
619
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
620
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
621
- capsuleShape.radius, capsuleShape.height * 0.5,
622
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
623
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
624
- bh.x, bh.y, bh.z
625
- );
626
- if (cc === 0) return count;
627
- // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
628
- // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
629
- // emission order from capsule_box_multi_contacts is stable across
630
- // frames for the same geometric configuration.
631
- for (let k = 0; k < cc; k++) {
632
- const o = k * CAPSULE_BOX_CONTACT_STRIDE;
633
- const cap_x = capsule_box_multi_result[o];
634
- const cap_y = capsule_box_multi_result[o + 1];
635
- const cap_z = capsule_box_multi_result[o + 2];
636
- const box_x = capsule_box_multi_result[o + 3];
637
- const box_y = capsule_box_multi_result[o + 4];
638
- const box_z = capsule_box_multi_result[o + 5];
639
- const nx = capsule_box_multi_result[o + 6];
640
- const ny = capsule_box_multi_result[o + 7];
641
- const nz = capsule_box_multi_result[o + 8];
642
- const depth = capsule_box_multi_result[o + 9];
643
- const sub_fid = k + 1;
644
- if (isCapsuleA) {
645
- count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
646
- } else {
647
- count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
648
- }
649
- }
650
- return count;
651
- }
652
-
653
- // convex hull ↔ convex hull (multi-point clipping via GJK + EPA + face-clip).
654
- if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
655
- return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
656
- }
657
-
658
- // mesh ↔ mesh. A globally-convex mesh collides as a single cached hull
659
- // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
660
- // mesh, so detecting convexity and routing through the hull path is the
661
- // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
662
- // the per-piece decomposed path (convex pieces vs convex pieces).
663
- if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
664
- const hullA = get_mesh_convex_hull(shapeA);
665
- const hullB = get_mesh_convex_hull(shapeB);
666
- if (hullA !== null && hullB !== null) {
667
- return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
668
- }
669
- return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
670
- }
671
-
672
- // ── Concave (non-convex) path ───────────────────────────────────────
673
- //
674
- // If either shape has `is_convex === false`, GJK on the whole shape
675
- // produces incorrect results (Minkowski difference is not convex).
676
- // We decompose the concave side into triangles overlapping the
677
- // convex side's AABB, then run convex per-triangle GJK + EPA.
678
- //
679
- // Concave-vs-concave is intentionally NOT supported in v1: the
680
- // M×N triangle pairs would dominate runtime, and the physics
681
- // engine's design contract requires at least one side to be
682
- // static / kinematic for concave shapes anyway (the broadphase +
683
- // filter should keep such pairs out of the narrowphase entirely).
684
- // If one slips through, we skip rather than burn cycles.
685
- const isConcaveA = shapeA.is_convex === false;
686
- const isConcaveB = shapeB.is_convex === false;
687
- if (isConcaveA && isConcaveB) return count;
688
- if (isConcaveA || isConcaveB) {
689
- const concave_col = isConcaveA ? colA : colB;
690
- const concave_tr = isConcaveA ? trA : trB;
691
- const convex_col = isConcaveA ? colB : colA;
692
- const convex_tr = isConcaveA ? trB : trA;
693
-
694
- // 1. Convex shape's world AABB.
695
- convex_col.shape.compute_bounding_box(concave_local_aabb);
696
- aabb3_transform_oriented(
697
- concave_world_aabb, 0,
698
- concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
699
- concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
700
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
701
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
702
- );
703
-
704
- // 2. Project into concave's body-local frame.
705
- aabb3_transform_oriented_inverse(
706
- concave_query_aabb, 0,
707
- concave_world_aabb,
708
- concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
709
- concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
710
- );
711
-
712
- // 3. Decompose concave shape into triangles overlapping the query.
713
- // The enumerator returns the TRUE overlap count — clamp to the
714
- // scratch capacity (overflow writes were dropped; reading past the
715
- // buffer would poison the manifold with NaN).
716
- let tri_count = decompose_to_triangles(
717
- triangle_buffer, 0, concave_col.shape,
718
- concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
719
- concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
720
- );
721
- if (tri_count > MAX_TRIANGLES_PER_PAIR) tri_count = MAX_TRIANGLES_PER_PAIR;
722
- if (tri_count === 0) return count;
723
-
724
- // 4. Set up the convex side once; the concave side gets a
725
- // Triangle3D rebound to each triangle in the loop. We keep
726
- // the concave side as our internal "A" so EPA's sign-check
727
- // convention matches the convex fallback below; the final
728
- // append_contact() swaps if the original A was the convex one.
729
- posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
730
- posed_a.shape = triangle_shape;
731
- posed_a.px = concave_tr.position.x;
732
- posed_a.py = concave_tr.position.y;
733
- posed_a.pz = concave_tr.position.z;
734
- posed_a.qx = concave_tr.rotation.x;
735
- posed_a.qy = concave_tr.rotation.y;
736
- posed_a.qz = concave_tr.rotation.z;
737
- posed_a.qw = concave_tr.rotation.w;
738
-
739
- // Pre-compute the centre-axis used by EPA's sign-check loop.
740
- // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
741
- const convex_wx = convex_tr.position.x;
742
- const convex_wy = convex_tr.position.y;
743
- const convex_wz = convex_tr.position.z;
744
-
745
- // Track the candidate-buffer index at the start of this
746
- // concave dispatch — the per-triangle dedup pass scans from
747
- // here to the current count, ignoring contacts from earlier
748
- // collider pairs in the same body pair.
749
- const pair_start_count = count;
750
-
751
- // Pre-compute concave's rotation components for the q · v · q⁻¹
752
- // rotations done per-triangle below (face normal + centroid).
753
- const cqx = concave_tr.rotation.x;
754
- const cqy = concave_tr.rotation.y;
755
- const cqz = concave_tr.rotation.z;
756
- const cqw = concave_tr.rotation.w;
757
- const c_pos_x = concave_tr.position.x;
758
- const c_pos_y = concave_tr.position.y;
759
- const c_pos_z = concave_tr.position.z;
760
-
761
- // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
762
- // entirely per triangle and use the closed-form
763
- // {@link sphere_triangle_contact}. This avoids the EPA precision
764
- // wall on Triangle3D (whose support function is degenerate along
765
- // the face normal — all 3 vertices project to the same value),
766
- // which was producing noisy depths at small penetrations and
767
- // letting dropped spheres tunnel through heightmaps / meshes.
768
- const isSphereConvex = convex_col.shape.isSphereShape3D === true;
769
- const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
770
-
771
- // Box fast-path: closed-form {@link box_triangle_contact} via SAT
772
- // over 13 axes + polygon clipping for face-vs-face contacts.
773
- // Same motivation as the sphere path — Triangle3D's degenerate
774
- // face-normal support kills EPA precision and produces noisy
775
- // depths. The box path uses world-space triangle vertices.
776
- const isBoxConvex = convex_col.shape.isBoxShape3D === true;
777
- const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
778
-
779
- // Capsule fast-path: closed-form {@link capsule_triangle_contact}
780
- // via segment-vs-triangle closest-point + cap-centre sphere
781
- // queries for a multi-point manifold. Same motivation as the
782
- // sphere and box paths.
783
- const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
784
- const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
785
-
786
- for (let i = 0; i < tri_count; i++) {
787
- const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
788
- triangle_shape.bind(triangle_buffer, tri_offset);
789
-
790
- const ax = triangle_buffer[tri_offset ];
791
- const ay = triangle_buffer[tri_offset + 1];
792
- const az = triangle_buffer[tri_offset + 2];
793
-
794
- const bx = triangle_buffer[tri_offset + 3];
795
- const by = triangle_buffer[tri_offset + 4];
796
- const bz = triangle_buffer[tri_offset + 5];
797
-
798
- const cx_v = triangle_buffer[tri_offset + 6];
799
- const cy_v = triangle_buffer[tri_offset + 7];
800
- const cz_v = triangle_buffer[tri_offset + 8];
801
-
802
- // Triangle decomposition emits a stable per-triangle feature_id
803
- // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
804
- // the same shape gets the same id across frames. This is the
805
- // gold-standard fid for the match-and-merge pass.
806
- const tri_fid = triangle_buffer[tri_offset + 9];
807
-
808
- // Triangle face normal in body-local frame: (B − A) × (C − A).
809
- // Winding convention (CCW from outside) gives an outward face
810
- // normal — the heightmap / mesh enumerators both promise this.
811
- const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
812
- const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
813
-
814
- const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
815
- const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
816
- const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
817
-
818
- // Rotate face normal to world via concave's quaternion
819
- // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape3D.support.
820
- const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
821
- const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
822
- const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
823
- const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
824
-
825
- const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
826
- const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
827
- const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
828
-
829
- // Sphere-vs-triangle closed-form fast-path.
830
- if (isSphereConvex) {
831
-
832
- // Rotate each triangle vertex from concave-local to world
833
- // via the shared helper.
834
- triangle_vertices_to_world(
835
- scratch_tri_world,
836
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
837
- cqx, cqy, cqz, cqw,
838
- c_pos_x, c_pos_y, c_pos_z
839
- );
840
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
841
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
842
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
843
-
844
- const ok = sphere_triangle_contact(
845
- sphere_triangle_result,
846
- convex_wx, convex_wy, convex_wz, sphere_radius,
847
- ax_w, ay_w, az_w,
848
- bx_w, by_w, bz_w,
849
- cx_w, cy_w, cz_w
850
- );
851
-
852
- if (!ok) continue;
853
-
854
- // sphere_triangle_contact's normal points from the
855
- // triangle surface toward the sphere centre — same
856
- // direction as the post-sign-check EPA MTV in the
857
- // fallback path below (concave A → convex B).
858
- const sd = sphere_triangle_result[3];
859
- const n_t2s_x = sphere_triangle_result[0];
860
- const n_t2s_y = sphere_triangle_result[1];
861
- const n_t2s_z = sphere_triangle_result[2];
862
-
863
- // One-sided rejection: the sphere must lie on the
864
- // outward side of the triangle. If the contact normal
865
- // opposes the face normal, the sphere is behind /
866
- // inside the solid — skip rather than push it deeper.
867
- if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
868
-
869
- // Stored normal convention is "B → A". `nx, ny, nz` here
870
- // points convex → concave (which the existing EPA branch
871
- // also produces just before append). Dedup uses the
872
- // post-swap stored_n so adjacent-triangle duplicates
873
- // collapse the same way regardless of code path.
874
- const nx_s = -n_t2s_x;
875
- const ny_s = -n_t2s_y;
876
- const nz_s = -n_t2s_z;
877
-
878
- const stored_nx_s = isConcaveA ? nx_s : -nx_s;
879
- const stored_ny_s = isConcaveA ? ny_s : -ny_s;
880
- const stored_nz_s = isConcaveA ? nz_s : -nz_s;
881
- let is_duplicate_s = false;
882
- for (let k = pair_start_count; k < count; k++) {
883
- const ko = k * CANDIDATE_STRIDE;
884
- const dnx = candidates[ko + 6] - stored_nx_s;
885
- const dny = candidates[ko + 7] - stored_ny_s;
886
- const dnz = candidates[ko + 8] - stored_nz_s;
887
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
888
- is_duplicate_s = true;
889
- break;
890
- }
891
- }
892
- if (is_duplicate_s) continue;
893
-
894
- // Surface witnesses from the closed-form solver:
895
- // result[4..6] = sphere surface point (convex side)
896
- // result[7..9] = triangle closest point (concave side)
897
- // Use the actual witnesses rather than body centres —
898
- // unlike the EPA fallback (which uses body centres
899
- // because flat-faced support witnesses can be arbitrarily
900
- // far from the contact patch), closest-point-on-triangle
901
- // is exact and lies on the contact patch.
902
- const sphere_wx = sphere_triangle_result[4];
903
- const sphere_wy = sphere_triangle_result[5];
904
- const sphere_wz = sphere_triangle_result[6];
905
- const tri_wx = sphere_triangle_result[7];
906
- const tri_wy = sphere_triangle_result[8];
907
- const tri_wz = sphere_triangle_result[9];
908
- if (isConcaveA) {
909
- count = append_contact(count,
910
- tri_wx, tri_wy, tri_wz,
911
- sphere_wx, sphere_wy, sphere_wz,
912
- nx_s, ny_s, nz_s, sd, tri_fid);
913
- } else {
914
- count = append_contact(count,
915
- sphere_wx, sphere_wy, sphere_wz,
916
- tri_wx, tri_wy, tri_wz,
917
- -nx_s, -ny_s, -nz_s, sd, tri_fid);
918
- }
919
- continue;
920
- }
921
-
922
- // Box-vs-triangle closed-form fast-path.
923
- if (isBoxConvex) {
924
- // Rotate each triangle vertex from concave-local to world
925
- // via the shared helper.
926
- triangle_vertices_to_world(
927
- scratch_tri_world,
928
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
929
- cqx, cqy, cqz, cqw,
930
- c_pos_x, c_pos_y, c_pos_z
931
- );
932
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
933
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
934
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
935
-
936
- const ok = box_triangle_contact(
937
- box_triangle_result,
938
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
939
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
940
- box_half_extents.x, box_half_extents.y, box_half_extents.z,
941
- ax_w, ay_w, az_w,
942
- bx_w, by_w, bz_w,
943
- cx_w, cy_w, cz_w
944
- );
945
- if (!ok) continue;
946
-
947
- // Same convention as sphere_triangle: result normal points
948
- // from triangle surface toward box centre. To match the
949
- // EPA branch's `nx, ny, nz` (which points convex →
950
- // concave = box → triangle), negate.
951
- const n_t2box_x = box_triangle_result[0];
952
- const n_t2box_y = box_triangle_result[1];
953
- const n_t2box_z = box_triangle_result[2];
954
-
955
- // One-sided rejection: the box must lie on the outward
956
- // side of the triangle. If the contact normal opposes
957
- // the face normal, the box is inside the solid → skip.
958
- if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
959
-
960
- const nx_b = -n_t2box_x;
961
- const ny_b = -n_t2box_y;
962
- const nz_b = -n_t2box_z;
963
- const stored_nx_b = isConcaveA ? nx_b : -nx_b;
964
- const stored_ny_b = isConcaveA ? ny_b : -ny_b;
965
- const stored_nz_b = isConcaveA ? nz_b : -nz_b;
966
-
967
- // Emit each clipped contact. The contact count from
968
- // box_triangle_contact is at most 4; combined with the
969
- // existing per-pair candidate cap (64 entries), tens of
970
- // overlapping triangles per pair are safely accommodated.
971
- const cc = box_triangle_result[3] | 0;
972
- for (let k = 0; k < cc; k++) {
973
- const base = 4 + k * 7;
974
- const t_wx = box_triangle_result[base];
975
- const t_wy = box_triangle_result[base + 1];
976
- const t_wz = box_triangle_result[base + 2];
977
- const b_wx = box_triangle_result[base + 3];
978
- const b_wy = box_triangle_result[base + 4];
979
- const b_wz = box_triangle_result[base + 5];
980
- const d_k = box_triangle_result[base + 6];
981
-
982
- // Dedup against earlier candidates in THIS dispatch
983
- // (adjacent triangles can produce coincident contacts
984
- // — same as the sphere and EPA paths).
985
- let is_duplicate_k = false;
986
- for (let q = pair_start_count; q < count; q++) {
987
- const qo = q * CANDIDATE_STRIDE;
988
- const dnx = candidates[qo + 6] - stored_nx_b;
989
- const dny = candidates[qo + 7] - stored_ny_b;
990
- const dnz = candidates[qo + 8] - stored_nz_b;
991
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
992
- // Same normal; check positions too (a
993
- // multi-point manifold has distinct points
994
- // with the same normal — those should NOT
995
- // be deduped). Compare the world-A side
996
- // (triangle surface or box surface depending
997
- // on dispatcher swap).
998
- const pa_x = isConcaveA ? t_wx : b_wx;
999
- const pa_y = isConcaveA ? t_wy : b_wy;
1000
- const pa_z = isConcaveA ? t_wz : b_wz;
1001
- const dpx = candidates[qo] - pa_x;
1002
- const dpy = candidates[qo + 1] - pa_y;
1003
- const dpz = candidates[qo + 2] - pa_z;
1004
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1005
- is_duplicate_k = true;
1006
- break;
1007
- }
1008
- }
1009
- }
1010
- if (is_duplicate_k) continue;
1011
-
1012
- if (isConcaveA) {
1013
- count = append_contact(count,
1014
- t_wx, t_wy, t_wz,
1015
- b_wx, b_wy, b_wz,
1016
- nx_b, ny_b, nz_b, d_k, tri_fid);
1017
- } else {
1018
- count = append_contact(count,
1019
- b_wx, b_wy, b_wz,
1020
- t_wx, t_wy, t_wz,
1021
- -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1022
- }
1023
- }
1024
- continue;
1025
- }
1026
-
1027
- // Capsule-vs-triangle closed-form fast-path.
1028
- if (isCapsuleConvex) {
1029
- // Rotate each triangle vertex from concave-local to world
1030
- // via the shared helper.
1031
- triangle_vertices_to_world(
1032
- scratch_tri_world,
1033
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
1034
- cqx, cqy, cqz, cqw,
1035
- c_pos_x, c_pos_y, c_pos_z
1036
- );
1037
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
1038
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
1039
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
1040
-
1041
- const cap_cc = capsule_triangle_contact(
1042
- capsule_triangle_result,
1043
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1044
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1045
- capsule_shape.radius, capsule_shape.height * 0.5,
1046
- ax_w, ay_w, az_w,
1047
- bx_w, by_w, bz_w,
1048
- cx_w, cy_w, cz_w
1049
- );
1050
- if (cap_cc === 0) continue;
1051
-
1052
- // Each sub-contact's normal can be distinct (a flat
1053
- // capsule produces primary + endpoint contacts whose
1054
- // normals all point ≈ same direction, but an edge or
1055
- // vertex contact at a cap can deviate). Apply
1056
- // face-normal rejection per-contact.
1057
- for (let k = 0; k < cap_cc; k++) {
1058
- const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1059
- const cap_x = capsule_triangle_result[base];
1060
- const cap_y = capsule_triangle_result[base + 1];
1061
- const cap_z = capsule_triangle_result[base + 2];
1062
- const ct_x = capsule_triangle_result[base + 3];
1063
- const ct_y = capsule_triangle_result[base + 4];
1064
- const ct_z = capsule_triangle_result[base + 5];
1065
- const n_t2cap_x = capsule_triangle_result[base + 6];
1066
- const n_t2cap_y = capsule_triangle_result[base + 7];
1067
- const n_t2cap_z = capsule_triangle_result[base + 8];
1068
- const d_c = capsule_triangle_result[base + 9];
1069
-
1070
- // One-sided face-normal rejection: capsule must be
1071
- // on the outward side of the triangle.
1072
- if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1073
-
1074
- // Stored normal "convex → concave" = "capsule →
1075
- // triangle" = negated t2cap.
1076
- const nx_c = -n_t2cap_x;
1077
- const ny_c = -n_t2cap_y;
1078
- const nz_c = -n_t2cap_z;
1079
- const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1080
- const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1081
- const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1082
-
1083
- // Dedup against earlier candidates in THIS dispatch.
1084
- let is_duplicate_c = false;
1085
- for (let q = pair_start_count; q < count; q++) {
1086
- const qo = q * CANDIDATE_STRIDE;
1087
- const dnx = candidates[qo + 6] - stored_nx_c;
1088
- const dny = candidates[qo + 7] - stored_ny_c;
1089
- const dnz = candidates[qo + 8] - stored_nz_c;
1090
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1091
- const pa_x = isConcaveA ? ct_x : cap_x;
1092
- const pa_y = isConcaveA ? ct_y : cap_y;
1093
- const pa_z = isConcaveA ? ct_z : cap_z;
1094
- const dpx = candidates[qo] - pa_x;
1095
- const dpy = candidates[qo + 1] - pa_y;
1096
- const dpz = candidates[qo + 2] - pa_z;
1097
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1098
- is_duplicate_c = true;
1099
- break;
1100
- }
1101
- }
1102
- }
1103
- if (is_duplicate_c) continue;
1104
-
1105
- if (isConcaveA) {
1106
- count = append_contact(count,
1107
- ct_x, ct_y, ct_z,
1108
- cap_x, cap_y, cap_z,
1109
- nx_c, ny_c, nz_c, d_c, tri_fid);
1110
- } else {
1111
- count = append_contact(count,
1112
- cap_x, cap_y, cap_z,
1113
- ct_x, ct_y, ct_z,
1114
- -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1115
- }
1116
- }
1117
- continue;
1118
- }
1119
-
1120
- // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1121
- // penetration depth and writes a UNIT axis into epa_result. We scale
1122
- // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1123
- // rejection / dedup below already consume. The old gjk_with_axis +
1124
- // expanding_polytope_algorithm pair was replaced because it returned
1125
- // a non-minimal axis on degenerate simplices (see memory:
1126
- // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1127
- // safety net for the rare case the robust query reports no overlap.
1128
- let ex, ey, ez, depth;
1129
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1130
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1131
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1132
- depth = pen_depth;
1133
- } else {
1134
- if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1135
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1136
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1137
- if (!(depth > 0) || !Number.isFinite(depth)) continue;
1138
- }
1139
-
1140
- // Sign-check: validate MTV direction against (convex −
1141
- // triangle_centroid). EPA's polytope-closest-face normal
1142
- // can come out on the wrong side of the origin; this flip
1143
- // canonicalises MTV to point from triangle (A) toward the
1144
- // convex shape (B).
1145
- const cent_lx = (ax + bx + cx_v) / 3;
1146
- const cent_ly = (ay + by + cy_v) / 3;
1147
- const cent_lz = (az + bz + cz_v) / 3;
1148
- // Inlined q · v · q* + translate.
1149
- const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1150
- const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1151
- const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1152
- const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1153
- const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1154
- const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1155
- const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1156
-
1157
- const tri_ab_x = convex_wx - cent_wx;
1158
- const tri_ab_y = convex_wy - cent_wy;
1159
- const tri_ab_z = convex_wz - cent_wz;
1160
- if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1161
- ex = -ex; ey = -ey; ez = -ez;
1162
- }
1163
-
1164
- // One-sided rejection (post-sign-check). MTV now points
1165
- // from triangle (A) toward convex shape (B). For a CCW
1166
- // outward-wound triangle, the convex shape is on the
1167
- // OUTWARD side iff MTV aligns with the face normal. If it
1168
- // opposes the face normal, the convex shape is on the
1169
- // back / inward side — invalid for heightmap/mesh (it's
1170
- // inside the solid). Skip the triangle rather than push
1171
- // the body deeper into the solid through the opposite
1172
- // face on the next step.
1173
- if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1174
-
1175
- // After the validated flip, MTV points from triangle
1176
- // (concave-side A) into convex (B). Stored normal is
1177
- // "B → A" so we negate. Final A/B order matches
1178
- // append_contact's contract: original A first, original B
1179
- // second, normal "from original B toward original A".
1180
- const inv = 1 / depth;
1181
- const nx = -ex * inv;
1182
- const ny = -ey * inv;
1183
- const nz = -ez * inv;
1184
-
1185
- // Dedup against earlier contacts emitted in THIS dispatch.
1186
- // Adjacent triangles (heightmap cells sharing a diagonal;
1187
- // mesh triangles sharing an edge or vertex) often report
1188
- // identical contacts (same normal, same body-centre
1189
- // application points), and feeding duplicates to the
1190
- // sequential-impulse solver makes it escalate the impulse
1191
- // across iterations without bound. We deduplicate by
1192
- // contact normal within a small angular threshold: same
1193
- // body pair + same normal + same body-centre positions =
1194
- // the same physical contact, only one copy belongs in the
1195
- // manifold.
1196
- const stored_nx = isConcaveA ? nx : -nx;
1197
- const stored_ny = isConcaveA ? ny : -ny;
1198
- const stored_nz = isConcaveA ? nz : -nz;
1199
- let is_duplicate = false;
1200
- for (let k = pair_start_count; k < count; k++) {
1201
- const ko = k * CANDIDATE_STRIDE;
1202
- const dnx = candidates[ko + 6] - stored_nx;
1203
- const dny = candidates[ko + 7] - stored_ny;
1204
- const dnz = candidates[ko + 8] - stored_nz;
1205
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1206
- is_duplicate = true;
1207
- break;
1208
- }
1209
- }
1210
- if (is_duplicate) continue;
1211
-
1212
- // Contact application points: body centres rather than
1213
- // the triangle's centroid. The existing GJK+EPA fallback
1214
- // (below) uses the same convention — for vertical contacts
1215
- // (sphere/box on a flat surface, the common case), the
1216
- // lever arm r × n vanishes and the impulse resolves
1217
- // cleanly.
1218
- if (isConcaveA) {
1219
- count = append_contact(count,
1220
- c_pos_x, c_pos_y, c_pos_z,
1221
- convex_wx, convex_wy, convex_wz,
1222
- nx, ny, nz, depth, tri_fid);
1223
- } else {
1224
- count = append_contact(count,
1225
- convex_wx, convex_wy, convex_wz,
1226
- c_pos_x, c_pos_y, c_pos_z,
1227
- -nx, -ny, -nz, depth, tri_fid);
1228
- }
1229
- }
1230
-
1231
- return count;
1232
- }
1233
-
1234
- // Robust GJK + EPA fallback for any convex pair without a closed form or a
1235
- // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1236
- // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1237
- // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1238
- // sign-guard below consumes. Replaces the old gjk_with_axis +
1239
- // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1240
- // degenerate simplices its GJK produced (see memory:
1241
- // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1242
- posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
- posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
-
1245
- let ex, ey, ez, depth;
1246
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1247
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1248
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1249
- depth = pen_depth;
1250
- } else {
1251
- if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1252
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1253
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1254
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
1255
- }
1256
-
1257
- // EPA's output vector should point along the minimum-translation
1258
- // axis from A into B (the direction you'd push B by to separate).
1259
- // On non-convergent EPA — common on smooth high-poly colliders like
1260
- // a torus knot, where the polytope can't tighten onto the true
1261
- // closest face within the iteration cap — the fallback returns
1262
- // *some* face's normal, and that face can be on either side of the
1263
- // origin. Resulting MTV occasionally points B *into* A instead of
1264
- // *away from* A; the solver then sees a "separating" relative
1265
- // velocity, clamps the normal impulse to zero, and the bodies pass
1266
- // straight through each other.
1267
- //
1268
- // Validate against the vector from body A's centre to body B's
1269
- // centre — EPA's direction must correlate positively with it. Flip
1270
- // if not. For convex bodies whose origin is inside the geometry,
1271
- // body-centre-to-body-centre is a strong proxy for the correct
1272
- // separation direction. For pathological cases (deeply
1273
- // interpenetrating bodies with coincident centres) the dot product
1274
- // is near zero and we leave EPA's guess intact.
1275
- const ab_x = trB.position.x - trA.position.x;
1276
- const ab_y = trB.position.y - trA.position.y;
1277
- const ab_z = trB.position.z - trA.position.z;
1278
- if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1279
- ex = -ex; ey = -ey; ez = -ez;
1280
- }
1281
-
1282
- const inv = 1 / depth;
1283
- const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1284
-
1285
- // Contact application points: body centres rather than support-function
1286
- // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1287
- // and `posed_b.support(-EPA_dir)` — interacts badly with flat-faced
1288
- // supports: a large floor's support in `+Y` returns a *corner* (the box
1289
- // support is multi-valued along an axis-aligned direction and picks the
1290
- // sign-tied corner), which can be tens of metres from the actual
1291
- // contact patch. That blows up the solver's lever arm `r × n` and
1292
- // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1293
- //
1294
- // Body centres give `r ‖ n` for vertical contacts (sphere/knot landing
1295
- // on a floor — the common case), so `r × n = 0` and `m_eff = invM`
1296
- // resolves the impact cleanly. For oblique contacts the application
1297
- // point lies on the line between centres, which is the right-ish lever
1298
- // arm for translational impulse and degrades gracefully on angular
1299
- // response. A future closed-form mesh-vs-box path will compute a
1300
- // proper face-projected witness; until then, body centres are the
1301
- // robust fallback for the GJK+EPA route.
1302
- // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1303
- // so match-and-merge uses position-fallback. Single contact per pair.
1304
- return append_contact(count,
1305
- trA.position.x, trA.position.y, trA.position.z,
1306
- trB.position.x, trB.position.y, trB.position.z,
1307
- nx, ny, nz,
1308
- depth, 0
1309
- );
1310
- }
1311
-
1312
- // Reusable single-pair adapters for the penetration query below — no per-call
1313
- // allocation. dispatch_pair only reads `.shape` off a collider and
1314
- // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1315
- // all it needs.
1316
- const _pp_colA = { shape: null };
1317
- const _pp_colB = { shape: null };
1318
- const _pp_trA = { position: null, rotation: null };
1319
- const _pp_trB = { position: null, rotation: null };
1320
-
1321
-
1322
- /**
1323
- * Single-pair penetration query: the depth and world normal of the DEEPEST
1324
- * contact the narrowphase would generate for one posed shape pair.
1325
- *
1326
- * Routes through the exact same {@link dispatch_pair} the contact solver
1327
- * consumes — closed-form for every sphere / box / capsule pair (box-box via
1328
- * SAT, so the true minimum-translation axis is found rather than the
1329
- * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1330
- * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1331
- * convex pair. The deepest contact's depth is the minimum-translation distance
1332
- * and its normal is the MTV axis, so the result is correct for every shape pair
1333
- * the engine can build and agrees bit-for-bit with what the solver acts on.
1334
- *
1335
- * The normal follows the narrowphase's stored convention: a unit vector
1336
- * pointing from B toward A — the direction to translate A to separate it.
1337
- *
1338
- * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1339
- * 0; callers needing to reject that case must check `is_convex` themselves.
1340
- *
1341
- * Not re-entrant: shares the module-level candidate / scratch buffers with
1342
- * {@link narrowphase_step}. Intended for main-thread queries run outside a
1343
- * step (depenetration, overlap depth, tooling) — never from inside one.
1344
- *
1345
- * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1346
- * normal on penetration (untouched when the return value is 0)
1347
- * @param {AbstractShape3D} shapeA
1348
- * @param {{x:number,y:number,z:number}} posA
1349
- * @param {{x:number,y:number,z:number,w:number}} rotA
1350
- * @param {AbstractShape3D} shapeB
1351
- * @param {{x:number,y:number,z:number}} posB
1352
- * @param {{x:number,y:number,z:number,w:number}} rotB
1353
- * @returns {number} deepest penetration depth (> 0) or 0 if separated
1354
- */
1355
- export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1356
- _pp_colA.shape = shapeA;
1357
- _pp_trA.position = posA;
1358
- _pp_trA.rotation = rotA;
1359
- _pp_colB.shape = shapeB;
1360
- _pp_trB.position = posB;
1361
- _pp_trB.rotation = rotB;
1362
-
1363
- const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1364
- if (n === 0) {
1365
- return 0;
1366
- }
1367
-
1368
- // Deepest contact = the minimum-translation depth; its stored normal is the
1369
- // separation axis. (For multi-point manifolds — box-box, capsule-box, a
1370
- // convex straddling several mesh triangles — every point shares the
1371
- // separating axis, so the max depth along it is the distance to separate.)
1372
- let best_depth = -1;
1373
- let best_off = 0;
1374
- for (let i = 0; i < n; i++) {
1375
- const off = i * CANDIDATE_STRIDE;
1376
- const d = candidates[off + 9];
1377
- if (d > best_depth) {
1378
- best_depth = d;
1379
- best_off = off;
1380
- }
1381
- }
1382
-
1383
- if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1384
- return 0;
1385
- }
1386
-
1387
- out_normal[0] = candidates[best_off + 6];
1388
- out_normal[1] = candidates[best_off + 7];
1389
- out_normal[2] = candidates[best_off + 8];
1390
-
1391
- return best_depth;
1392
- }
1393
-
1394
- /**
1395
- * For every pair in `pair_list`, do a cross-product over A's collider list ×
1396
- * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1397
- * to the manifold slot.
1398
- *
1399
- * @param {PairList} pair_list
1400
- * @param {ManifoldStore} manifolds
1401
- * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1402
- * per-body collider lists, indexed by body-storage slot index. Typically
1403
- * `system.__body_collider_lists` — passed in directly so this helper
1404
- * has no dependency on `PhysicsSystem`.
1405
- */
1406
- export function narrowphase_step(pair_list, manifolds, lists) {
1407
- const count = pair_list.count;
1408
-
1409
- for (let i = 0; i < count; i++) {
1410
- const idA = pair_list.get_a(i);
1411
- const idB = pair_list.get_b(i);
1412
-
1413
- const idxA = body_id_index(idA);
1414
- const idxB = body_id_index(idB);
1415
-
1416
- const list_a = lists[idxA];
1417
- const list_b = lists[idxB];
1418
-
1419
- const slot = manifolds.find(idA, idB);
1420
-
1421
- if (list_a === undefined || list_b === undefined
1422
- || list_a.length === 0 || list_b.length === 0
1423
- ) {
1424
-
1425
- manifolds.clear_contacts(slot);
1426
- continue;
1427
-
1428
- }
1429
-
1430
- let cand_count = 0;
1431
-
1432
- const la_len = list_a.length;
1433
- const lb_len = list_b.length;
1434
-
1435
- let sub_pair = 0;
1436
- for (let a = 0; a < la_len; a++) {
1437
- const ea = list_a[a];
1438
-
1439
- for (let b = 0; b < lb_len; b++) {
1440
- const eb = list_b[b];
1441
-
1442
- const before = cand_count;
1443
- cand_count = dispatch_pair(
1444
- cand_count,
1445
- ea.collider,
1446
- ea.transform,
1447
- eb.collider,
1448
- eb.transform
1449
- );
1450
-
1451
- // Salt this sub-pair's fids ({@link FID_PAIR_SALT}) so a
1452
- // compound body cannot fid-match — and inherit warm-start
1453
- // impulses — across different collider pairs. fid 0 stays 0.
1454
- if (sub_pair !== 0) {
1455
- const salt = sub_pair * FID_PAIR_SALT;
1456
- for (let c = before; c < cand_count; c++) {
1457
- const co = c * CANDIDATE_STRIDE;
1458
- if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1459
- }
1460
- }
1461
- sub_pair++;
1462
- }
1463
- }
1464
-
1465
- if (cand_count === 0) {
1466
- // No contacts this frame for an existing manifold. Keep the
1467
- // slot in the cache (the grace window in advance_frame() will
1468
- // evict it if this persists), but zero out impulses and
1469
- // contact count — there's nothing for the solver to act on
1470
- // and stale impulses would mislead next frame's warm-start
1471
- // if contact re-establishes at a different feature.
1472
- manifolds.clear_contacts(slot);
1473
- continue;
1474
- }
1475
-
1476
- const kept = reduce_manifold_contacts(candidates, cand_count, CANDIDATE_STRIDE, MAX_KEPT, 9);
1477
-
1478
- // ── Match-and-merge: feature-id (with position fallback) ──────
1479
- //
1480
- // Snapshot prev-frame state for matching + impulse carry-over.
1481
- const data = manifolds.data_buffer;
1482
- const slot_off = manifolds.slot_data_offset(slot);
1483
- const prev_count_raw = manifolds.contact_count(slot);
1484
- const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1485
- ? MAX_CONTACTS_PER_MANIFOLD
1486
- : prev_count_raw;
1487
- for (let j = 0; j < prev_count; j++) {
1488
- const off = slot_off + j * CONTACT_STRIDE;
1489
- const snap_off = j * 7;
1490
- prev_snapshot[snap_off] = data[off + 13]; // feature_id
1491
- prev_snapshot[snap_off + 1] = data[off]; // world_a x
1492
- prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1493
- prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1494
- prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1495
- prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1496
- prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1497
- prev_claimed[j] = 0;
1498
- }
1499
-
1500
- // For each new candidate, find a matching prev contact.
1501
- // Step 1: feature-id match (only if BOTH sides have a non-zero
1502
- // feature_id — fid = 0 means "no info"). Same-fid prev
1503
- // contacts are disambiguated by NEAREST previous witness:
1504
- // a fid identifies the FEATURE (e.g. a triangle), not the
1505
- // contact point — the box/capsule-triangle paths emit up
1506
- // to 4 contacts sharing one triangle's fid, and the
1507
- // reducer reorders them frame to frame, so first-match
1508
- // would shuffle warm-start impulses between a face's own
1509
- // contact points. (redetect_pair_geometry documents and
1510
- // fixes the same failure for the per-substep path.)
1511
- // Step 2: position-fallback within MATCH_TOL_SQR.
1512
- for (let k = 0; k < kept; k++) {
1513
- const cand_off = k * CANDIDATE_STRIDE;
1514
- const cand_fid = candidates[cand_off + 10];
1515
- const cand_ax = candidates[cand_off];
1516
- const cand_ay = candidates[cand_off + 1];
1517
- const cand_az = candidates[cand_off + 2];
1518
-
1519
- let best_prev = -1;
1520
- if (cand_fid !== 0) {
1521
- // Gated by FID_MATCH_MAX_D2: a fid recomputed half a metre
1522
- // away (teleport, fid collision) must not inherit impulses.
1523
- let best_d2 = FID_MATCH_MAX_D2;
1524
- for (let j = 0; j < prev_count; j++) {
1525
- if (prev_claimed[j]) continue;
1526
- if (prev_snapshot[j * 7] !== cand_fid) continue;
1527
- const snap_off = j * 7;
1528
- const dx = prev_snapshot[snap_off + 1] - cand_ax;
1529
- const dy = prev_snapshot[snap_off + 2] - cand_ay;
1530
- const dz = prev_snapshot[snap_off + 3] - cand_az;
1531
- const d2 = dx * dx + dy * dy + dz * dz;
1532
- if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1533
- }
1534
- }
1535
- if (best_prev === -1) {
1536
- let best_d2 = MATCH_TOL_SQR;
1537
- for (let j = 0; j < prev_count; j++) {
1538
- if (prev_claimed[j]) continue;
1539
- const snap_off = j * 7;
1540
- const dx = prev_snapshot[snap_off + 1] - cand_ax;
1541
- const dy = prev_snapshot[snap_off + 2] - cand_ay;
1542
- const dz = prev_snapshot[snap_off + 3] - cand_az;
1543
- const d2 = dx * dx + dy * dy + dz * dz;
1544
- if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1545
- }
1546
- }
1547
- cand_to_prev[k] = best_prev;
1548
- if (best_prev !== -1) prev_claimed[best_prev] = 1;
1549
- }
1550
-
1551
- // Reset count without zeroing the data slab — set_contact below
1552
- // will overwrite geometry, and matched candidates will inherit
1553
- // the impulses we snapshotted above (written back at the new
1554
- // slot index after set_contact).
1555
- manifolds.begin_refill(slot);
1556
-
1557
- for (let k = 0; k < kept; k++) {
1558
- const off = k * CANDIDATE_STRIDE;
1559
- // Target slot index = k (we write candidates 0..kept-1 into
1560
- // slot indices 0..kept-1). The impulse carry-over below
1561
- // copies the matched prev contact's impulses into THIS k,
1562
- // so the geometric and warm-start state stay correlated
1563
- // even if the matching mapping permuted the order.
1564
- manifolds.set_contact(
1565
- slot, k,
1566
- candidates[off], candidates[off + 1], candidates[off + 2],
1567
- candidates[off + 3], candidates[off + 4], candidates[off + 5],
1568
- candidates[off + 6], candidates[off + 7], candidates[off + 8],
1569
- candidates[off + 9],
1570
- candidates[off + 10],
1571
- candidates[off + 11], candidates[off + 12]
1572
- );
1573
- const prev_j = cand_to_prev[k];
1574
- if (prev_j !== -1) {
1575
- // Copy prev_j's impulse to this slot index.
1576
- const dst_off = slot_off + k * CONTACT_STRIDE;
1577
- const src_off = prev_j * 7;
1578
- data[dst_off + 10] = prev_snapshot[src_off + 4];
1579
- data[dst_off + 11] = prev_snapshot[src_off + 5];
1580
- data[dst_off + 12] = prev_snapshot[src_off + 6];
1581
- } else {
1582
- // No match — explicitly zero the impulses, since this
1583
- // slot index may have stale data from an earlier frame's
1584
- // contact at the same index.
1585
- manifolds.clear_impulses(slot, k);
1586
- }
1587
- }
1588
- }
1589
- }
1590
-
1591
- /**
1592
- * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1593
- * current poses, updating the witness points / normal / depth of the slot's
1594
- * existing contacts in place. Does NOT change the contact count, the
1595
- * feature ids, or the accumulated impulses — it only refreshes geometry.
1596
- *
1597
- * This is the per-substep concave path (TGS): for a contact pair involving a
1598
- * concave body, the contact *feature* (which triangle is deepest, and its
1599
- * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1600
- * refresh — which freezes the feature for the whole outer step — pumps energy
1601
- * in. Re-running the narrowphase geometry each substep gives a fresh,
1602
- * correct normal so the body settles instead of rocking. Convex pairs keep
1603
- * the analytic refresh and never call this (their feature is stable).
1604
- *
1605
- * Matching is by feature id (stable per-triangle for the decomposition path),
1606
- * so a contact's geometry tracks the same triangle across substeps. A contact
1607
- * whose triangle isn't found this substep keeps its previous geometry (a rare
1608
- * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1609
- * contact set next outer step). Count never changes here, so the solver's
1610
- * per-contact scratch (sized once at prepare) stays aligned.
1611
- *
1612
- * @param {ManifoldStore} manifolds
1613
- * @param {number} slot
1614
- * @param {Array<{collider: Collider, transform: Transform}>} list_a
1615
- * @param {Array<{collider: Collider, transform: Transform}>} list_b
1616
- */
1617
- export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1618
- if (list_a === undefined || list_b === undefined) return;
1619
- const la_len = list_a.length;
1620
- const lb_len = list_b.length;
1621
- if (la_len === 0 || lb_len === 0) return;
1622
-
1623
- const count = manifolds.contact_count(slot);
1624
- if (count === 0) return;
1625
-
1626
- let cc = 0;
1627
- let sub_pair = 0;
1628
- for (let a = 0; a < la_len; a++) {
1629
- const ea = list_a[a];
1630
- for (let b = 0; b < lb_len; b++) {
1631
- const eb = list_b[b];
1632
- const before = cc;
1633
- cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1634
- // Stored fids are sub-pair-salted (see narrowphase_step's dispatch
1635
- // loop) — salt the redetected candidates identically or no fid
1636
- // would ever match for a compound body's non-first sub-pair.
1637
- if (sub_pair !== 0) {
1638
- const salt = sub_pair * FID_PAIR_SALT;
1639
- for (let c = before; c < cc; c++) {
1640
- const co = c * CANDIDATE_STRIDE;
1641
- if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1642
- }
1643
- }
1644
- sub_pair++;
1645
- }
1646
- }
1647
- if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1648
-
1649
- const data = manifolds.data_buffer;
1650
- const slot_off = manifolds.slot_data_offset(slot);
1651
-
1652
- // Match each existing contact to a DISTINCT fresh candidate. feature_id
1653
- // identifies the TRIANGLE, not the contact point — the box/capsule-triangle
1654
- // paths emit several contacts for one triangle, all sharing that triangle's
1655
- // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1656
- // plain first-match-by-fid therefore maps every same-fid existing contact
1657
- // onto the SAME candidate, collapsing the manifold to duplicate witness
1658
- // points → a degenerate support polygon the solver can't damp (the
1659
- // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1660
- // candidates by NEAREST previous witness (world-A) position, and claim each
1661
- // candidate so no two existing contacts take the same one. For the common
1662
- // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1663
- // single candidate exactly once — identical to the old behaviour.
1664
- for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1665
-
1666
- for (let j = 0; j < count; j++) {
1667
- const off = slot_off + j * CONTACT_STRIDE;
1668
- const fid = data[off + 13];
1669
- if (fid === 0) continue; // no feature info to match on
1670
-
1671
- // Previous witness (world-A) of this existing contact — the anchor we
1672
- // disambiguate same-fid candidates against.
1673
- const pax = data[off];
1674
- const pay = data[off + 1];
1675
- const paz = data[off + 2];
1676
-
1677
- let best_k = -1;
1678
- let best_d2 = Infinity;
1679
- for (let k = 0; k < cc; k++) {
1680
- if (redetect_claimed[k] === 1) continue;
1681
- const co = k * CANDIDATE_STRIDE;
1682
- if (candidates[co + 10] !== fid) continue;
1683
- const dx = candidates[co] - pax;
1684
- const dy = candidates[co + 1] - pay;
1685
- const dz = candidates[co + 2] - paz;
1686
- const d2 = dx * dx + dy * dy + dz * dz;
1687
- if (d2 < best_d2) {
1688
- best_d2 = d2;
1689
- best_k = k;
1690
- }
1691
- }
1692
-
1693
- // Position fallback (mirrors narrowphase_step's match-and-merge). A
1694
- // single triangle's clipped contact count is NOT stable across sub-mm
1695
- // pose changes — a box straddling a cell seam can have one triangle
1696
- // yield 4 points one substep and 3 the next — so an existing contact can
1697
- // outnumber this substep's same-fid candidates. Freezing its stale
1698
- // witness then duplicates a sibling contact's point, leaving a
1699
- // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1700
- // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1701
- // witness. The fid label is left intact and re-resolved by the next
1702
- // once-per-step narrowphase. (At a fixed pose the same-fid match always
1703
- // succeeds, so this never fires for the depth-equality guards or the
1704
- // one-contact-per-triangle sphere/mesh paths.)
1705
- if (best_k === -1) {
1706
- for (let k = 0; k < cc; k++) {
1707
- if (redetect_claimed[k] === 1) continue;
1708
- const co = k * CANDIDATE_STRIDE;
1709
- const dx = candidates[co] - pax;
1710
- const dy = candidates[co + 1] - pay;
1711
- const dz = candidates[co + 2] - paz;
1712
- const d2 = dx * dx + dy * dy + dz * dz;
1713
- if (d2 < best_d2) {
1714
- best_d2 = d2;
1715
- best_k = k;
1716
- }
1717
- }
1718
- }
1719
-
1720
- if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1721
-
1722
- redetect_claimed[best_k] = 1;
1723
- const co = best_k * CANDIDATE_STRIDE;
1724
- // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1725
- // ids and accumulated impulses are intentionally left untouched — see
1726
- // the function contract.)
1727
- data[off] = candidates[co];
1728
- data[off + 1] = candidates[co + 1];
1729
- data[off + 2] = candidates[co + 2];
1730
- data[off + 3] = candidates[co + 3];
1731
- data[off + 4] = candidates[co + 4];
1732
- data[off + 5] = candidates[co + 5];
1733
- data[off + 6] = candidates[co + 6];
1734
- data[off + 7] = candidates[co + 7];
1735
- data[off + 8] = candidates[co + 8];
1736
- data[off + 9] = candidates[co + 9];
1737
- }
1738
- }
1
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
+ import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
+ import { body_id_index } from "../body/BodyStorage.js";
4
+ import { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
+ import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
+ import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
7
+ import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
8
+ import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
9
+ import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
10
+ import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
11
+ import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
12
+ import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
13
+ import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
14
+ import {
15
+ CAPSULE_BOX_CONTACT_STRIDE,
16
+ CAPSULE_BOX_MAX_CONTACTS,
17
+ capsule_box_multi_contacts,
18
+ capsule_capsule_contact,
19
+ CAPSULE_CAPSULE_CONTACT_STRIDE,
20
+ capsule_capsule_multi_contacts,
21
+ capsule_sphere_contact,
22
+ } from "./capsule_contacts.js";
23
+ import {
24
+ capsule_triangle_contact,
25
+ CAPSULE_TRIANGLE_CONTACT_STRIDE,
26
+ CAPSULE_TRIANGLE_MAX_CONTACTS,
27
+ } from "./capsule_triangle_contact.js";
28
+ import { aabb3_transform_oriented_inverse } from "../../../core/geom/3d/aabb/aabb3_transform_oriented_inverse.js";
29
+ import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
30
+ import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
31
+ import { PosedShape3D } from "../../../core/geom/3d/shape/PosedShape3D.js";
32
+ import { sphere_box_contact } from "./sphere_box_contact.js";
33
+ import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
34
+ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
35
+
36
+ const posed_a = new PosedShape3D();
37
+ const posed_b = new PosedShape3D();
38
+
39
+ // Penetration axis + depth from the robust GJK + EPA query
40
+ // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
41
+ // internally, so the narrowphase no longer keeps a simplex buffer).
42
+ const epa_result = new Float64Array(3);
43
+ const sphere_result = new Float64Array(4);
44
+ const closed_form_result = new Float64Array(10);
45
+
46
+ /** Scratch for the capsule-capsule multi-contact manifold (2 contacts max). */
47
+ const capsule_capsule_result = new Float64Array(2 * CAPSULE_CAPSULE_CONTACT_STRIDE);
48
+ const sphere_triangle_result = new Float64Array(10);
49
+ const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
+ const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
+ const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
52
+ const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
53
+ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
54
+
55
+ /**
56
+ * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
57
+ * feature_id, friction, restitution.
58
+ *
59
+ * The `feature_id` (offset 10) is a stable cross-frame identifier of the
60
+ * geometric feature pair that produced this contact — used by the
61
+ * match-and-merge pass in {@link narrowphase_step} to carry warm-start
62
+ * impulses from the previous frame's manifold to the slot index that
63
+ * corresponds to the same physical contact. A value of 0 means
64
+ * "no feature info, fall back to position matching".
65
+ *
66
+ * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
67
+ * coefficients for the specific (colliderA, colliderB) pair that produced this
68
+ * contact, combined here (the only place that knows the exact source collider
69
+ * on each side) and carried into the manifold so a compound body's per-collider
70
+ * materials are honoured per-contact.
71
+ *
72
+ * @type {number}
73
+ */
74
+ const CANDIDATE_STRIDE = 13;
75
+
76
+ /**
77
+ * Combined friction / restitution for the collider pair currently being
78
+ * dispatched. Set once at the top of {@link dispatch_pair} (which is called
79
+ * per collider pair) and written into every contact that call appends, so
80
+ * each contact carries the material of its actual source colliders. Module
81
+ * scratch rather than threaded through every `append_contact` call site.
82
+ * @type {number}
83
+ */
84
+ let g_pair_friction = 0;
85
+ let g_pair_restitution = 0;
86
+
87
+ /**
88
+ * Maximum number of contacts emitted into the per-pair manifold after the
89
+ * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
90
+ * @type {number}
91
+ */
92
+ const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
93
+
94
+ /**
95
+ * Position-fallback tolerance for warm-start matching: when a candidate
96
+ * contact has no feature id (or none of the previous-frame contacts shares
97
+ * its id), match by closest world_a within this 3-D distance.
98
+ *
99
+ * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
100
+ * — generous enough to follow small inter-frame contact migration on
101
+ * curved surfaces, tight enough that distinct contacts on a single
102
+ * manifold (typically >5 cm apart) don't get confused with each other.
103
+ *
104
+ * @type {number}
105
+ */
106
+ const MATCH_TOL_SQR = 0.02 * 0.02;
107
+
108
+ /**
109
+ * Per-(colliderA, colliderB) feature-id salt. Each emitter numbers its
110
+ * features in a small private range (sphere-box voronoi 1..27, capsule-box
111
+ * 1..k, triangles 1..N, …), so two SUB-PAIRS of a compound body can produce
112
+ * identical fids for unrelated features and warm-start impulses could be
113
+ * inherited across collider pairs. Salting by the sub-pair index keeps every
114
+ * fid unique across the body pair while staying exactly representable in f64
115
+ * (sub_pair · 2³² + fid stays far below 2⁵³ for any realistic compound; no
116
+ * single-pair emitter exceeds 2³²). Sub-pair 0 — the universal
117
+ * single-collider case — is left unsalted, so simple bodies see unchanged
118
+ * fids.
119
+ * @type {number}
120
+ */
121
+ const FID_PAIR_SALT = 0x100000000; // 2^32
122
+
123
+ /**
124
+ * Distance ceiling for a feature-id warm-start match. A fid match may track
125
+ * a fast-sliding contact far beyond {@link MATCH_TOL_SQR} — that is its
126
+ * advantage over position matching — but an impulse inherited across half a
127
+ * metre in one frame is stale by any standard (a teleported body re-deriving
128
+ * the same voronoi bucket, or a fid collision between unrelated features).
129
+ * The position fallback keeps its own 2 cm gate.
130
+ * @type {number}
131
+ */
132
+ const FID_MATCH_MAX_D2 = 0.5 * 0.5;
133
+
134
+ /**
135
+ * Scratch for the previous-frame contact snapshot taken at the top of the
136
+ * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
137
+ * contacts, 7 floats per contact:
138
+ * 0 : feature_id
139
+ * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
140
+ * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
141
+ *
142
+ * Snapshotting upfront decouples the read (from the slot's previous state)
143
+ * from the write (the new contact data + impulse copy), avoiding the
144
+ * read-after-write hazard when the matching mapping shuffles indices.
145
+ *
146
+ * @type {Float64Array}
147
+ */
148
+ const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
149
+
150
+ /**
151
+ * Per-prev-contact "already claimed by a candidate" flag.
152
+ * @type {Uint8Array}
153
+ */
154
+ const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
155
+
156
+ /**
157
+ * For each kept candidate, the matched prev-contact index in
158
+ * `[0, prev_count)` or `-1` if no match.
159
+ * @type {Int32Array}
160
+ */
161
+ const cand_to_prev = new Int32Array(MAX_KEPT);
162
+
163
+ /**
164
+ * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
165
+ * 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
166
+ * buffer capacity (64) so it covers any per-pair candidate count. Without it,
167
+ * several existing contacts that share one triangle's `feature_id` (the
168
+ * box/capsule-triangle paths emit multiple contacts per triangle) would all
169
+ * match the same first candidate, collapsing the manifold to duplicate witness
170
+ * points.
171
+ * @type {Uint8Array}
172
+ */
173
+ const redetect_claimed = new Uint8Array(64);
174
+
175
+ /**
176
+ * Per body-pair scratch buffer for candidate contacts produced by the
177
+ * cross-product of A's colliders × B's colliders. Sized generously for
178
+ * typical compound bodies (each collider-pair contributes 1..4 contacts;
179
+ * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
180
+ * @type {Float64Array}
181
+ */
182
+ const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
183
+
184
+ /**
185
+ * Maximum triangles a concave-side enumerator can produce per pair.
186
+ * The query AABB is bounded by the broadphase's fattened envelope of
187
+ * the convex-side body, so a single concave-vs-convex pair typically
188
+ * yields tens of triangles, not thousands. 1024 is the safety cap.
189
+ *
190
+ * For a heightmap the per-cell triangle count scales O(N²) with the
191
+ * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
192
+ * triangles, and there are N×N sub-cells per sampler cell). The bounded
193
+ * query AABB keeps the cell count small, so a typical pair stays well
194
+ * inside 1024 at moderate tessellation; the silent-drop backstop below
195
+ * covers any overflow at extreme values.
196
+ *
197
+ * Enumerators return the TRUE overlap count and have no bounds check of
198
+ * their own: writes past the scratch buffer are dropped (typed-array OOB
199
+ * semantics), so on overflow the consume loop MUST clamp the returned
200
+ * count to this cap or it reads `undefined` past the end and poisons the
201
+ * manifold with NaN. The worst case after clamping is a missed contact
202
+ * on a far edge of the filtered region, which the next-step
203
+ * rebroadphase corrects.
204
+ *
205
+ * @type {number}
206
+ */
207
+ const MAX_TRIANGLES_PER_PAIR = 1024;
208
+
209
+ /**
210
+ * Per-pair scratch for the concave-side triangle decomposition.
211
+ * @type {Float64Array}
212
+ */
213
+ const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
214
+
215
+ /**
216
+ * Flyweight triangle shape — rebound to each successive triangle slice
217
+ * of `triangle_buffer` during the concave-side dispatch loop. Zero
218
+ * allocation per triangle.
219
+ * @type {Triangle3D}
220
+ */
221
+ const triangle_shape = new Triangle3D();
222
+
223
+ /**
224
+ * Scratch AABB buffers used only by the concave-side dispatch:
225
+ * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
226
+ * - `concave_world_aabb` : convex shape's world AABB
227
+ * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
228
+ * (what the triangle enumerator filters against)
229
+ */
230
+ const concave_local_aabb = new Float64Array(6);
231
+ const concave_world_aabb = new Float64Array(6);
232
+ const concave_query_aabb = new Float64Array(6);
233
+
234
+ /**
235
+ * Rotate + translate a triangle's three vertices from a concave body's
236
+ * local frame into world space: writes 9 floats `(A_w, B_w, C_w)` into
237
+ * `out`. Inlined `q · v · q*` + translate per vertex (see
238
+ * PosedShape3D.support for the V8-inliner rationale); the operation order
239
+ * matches the blocks this replaced bit-exactly. Per-vertex cost ~21 flops
240
+ * × 3 — essentially free next to any narrowphase kernel it feeds.
241
+ *
242
+ * Shared by the sphere / box / capsule closed-form triangle fast-paths in
243
+ * the concave dispatch, which each carried an identical inlined copy.
244
+ */
245
+ function triangle_vertices_to_world(out, ax, ay, az, bx, by, bz, cx, cy, cz, qx, qy, qz, qw, px, py, pz) {
246
+ const axi = qw * ax + qy * az - qz * ay;
247
+ const ayi = qw * ay + qz * ax - qx * az;
248
+ const azi = qw * az + qx * ay - qy * ax;
249
+ const awi = -qx * ax - qy * ay - qz * az;
250
+
251
+ out[0] = axi * qw + awi * -qx + ayi * -qz - azi * -qy + px;
252
+ out[1] = ayi * qw + awi * -qy + azi * -qx - axi * -qz + py;
253
+ out[2] = azi * qw + awi * -qz + axi * -qy - ayi * -qx + pz;
254
+
255
+ const bxi = qw * bx + qy * bz - qz * by;
256
+ const byi = qw * by + qz * bx - qx * bz;
257
+ const bzi = qw * bz + qx * by - qy * bx;
258
+ const bwi = -qx * bx - qy * by - qz * bz;
259
+
260
+ out[3] = bxi * qw + bwi * -qx + byi * -qz - bzi * -qy + px;
261
+ out[4] = byi * qw + bwi * -qy + bzi * -qx - bxi * -qz + py;
262
+ out[5] = bzi * qw + bwi * -qz + bxi * -qy - byi * -qx + pz;
263
+
264
+ const cxi = qw * cx + qy * cz - qz * cy;
265
+ const cyi = qw * cy + qz * cx - qx * cz;
266
+ const czi = qw * cz + qx * cy - qy * cx;
267
+ const cwi = -qx * cx - qy * cy - qz * cz;
268
+
269
+ out[6] = cxi * qw + cwi * -qx + cyi * -qz - czi * -qy + px;
270
+ out[7] = cyi * qw + cwi * -qy + czi * -qx - cxi * -qz + py;
271
+ out[8] = czi * qw + cwi * -qz + cxi * -qy - cyi * -qx + pz;
272
+ }
273
+
274
+ /** Destination for {@link triangle_vertices_to_world}. */
275
+ const scratch_tri_world = new Float64Array(9);
276
+
277
+ /**
278
+ * Append one contact to the candidate buffer. Returns the new count.
279
+ *
280
+ * @param {number} count
281
+ * @param {number} wax
282
+ * @param {number} way
283
+ * @param {number} waz
284
+ * @param {number} wbx
285
+ * @param {number} wby
286
+ * @param {number} wbz
287
+ * @param {number} nx
288
+ * @param {number} ny
289
+ * @param {number} nz
290
+ * @param {number} depth
291
+ * @param {number} feature_id stable cross-frame ID for warm-start matching;
292
+ * `0` means no info — match-and-merge will fall back to position.
293
+ * @returns {number}
294
+ */
295
+ function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
296
+ if (count * CANDIDATE_STRIDE >= candidates.length){
297
+ return count;
298
+ }
299
+
300
+ const off = count * CANDIDATE_STRIDE;
301
+
302
+ candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
303
+ candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
304
+ candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
305
+ candidates[off + 9] = depth;
306
+ candidates[off + 10] = feature_id;
307
+ candidates[off + 11] = g_pair_friction;
308
+ candidates[off + 12] = g_pair_restitution;
309
+
310
+ return count + 1;
311
+ }
312
+
313
+ /**
314
+ * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
315
+ * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
316
+ * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
317
+ * faces + 1 interior). Stable across frames as long as the sphere stays
318
+ * in the same region — the typical case in steady-state contact.
319
+ *
320
+ * Encodes:
321
+ * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
322
+ * bucket_y, bucket_z similarly
323
+ * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
324
+ *
325
+ * The "+1" offset ensures the result is never 0, since 0 is reserved
326
+ * for "no feature info, use position-fallback".
327
+ *
328
+ * @param {number} sx sphere centre x
329
+ * @param {number} sy
330
+ * @param {number} sz
331
+ * @param {number} bx box centre x
332
+ * @param {number} by
333
+ * @param {number} bz
334
+ * @param {number} bqx box quaternion x
335
+ * @param {number} bqy
336
+ * @param {number} bqz
337
+ * @param {number} bqw
338
+ * @param {number} hx box half-extent x
339
+ * @param {number} hy
340
+ * @param {number} hz
341
+ * @returns {number}
342
+ */
343
+ function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
344
+ // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
345
+ // to get the sphere centre in box-local frame. Inlined for the same
346
+ // V8-inliner reason described in PosedShape3D.support — see
347
+ // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
348
+ const dx = sx - bx;
349
+ const dy = sy - by;
350
+ const dz = sz - bz;
351
+
352
+ const tx0 = bqw * dx - bqy * dz + bqz * dy;
353
+ const ty0 = bqw * dy - bqz * dx + bqx * dz;
354
+ const tz0 = bqw * dz - bqx * dy + bqy * dx;
355
+ const tw0 = bqx * dx + bqy * dy + bqz * dz;
356
+
357
+ const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
358
+ const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
359
+ const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
360
+
361
+ const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
362
+ const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
363
+ const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
364
+
365
+ return 1 + bx_b + 3 * by_b + 9 * bz_b;
366
+ }
367
+
368
+ /**
369
+ * Multi-point contact manifold between two convex polytopes (authored
370
+ * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
371
+ * GJK + EPA finds the separating axis in O(support queries) — face-count
372
+ * independent, where SAT's edge-pair term is prohibitive — then
373
+ * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
374
+ * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
375
+ * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
376
+ * state → reset-and-resimulate determinism.
377
+ *
378
+ * @param {number} count
379
+ * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
380
+ * @param {Transform} trA
381
+ * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
382
+ * @param {Transform} trB
383
+ * @param {Function} append_contact
384
+ * @returns {number}
385
+ */
386
+ function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
387
+ posed_a.setup(hullA, trA.position, trA.rotation);
388
+ posed_b.setup(hullB, trB.position, trB.rotation);
389
+ const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
390
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
391
+
392
+ // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
393
+ // writes the final normal into convex_manifold_result[0..2].
394
+ convex_hull_clip(convex_manifold_result,
395
+ hullA, trA.position, trA.rotation,
396
+ hullB, trB.position, trB.rotation,
397
+ epa_result[0], epa_result[1], epa_result[2]);
398
+ const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
399
+ const cc = convex_manifold_result[3] | 0;
400
+ // Clipped points migrate across features as the hulls rotate, so leave
401
+ // fid = 0 and let the match-and-merge pass position-match (as box-box does).
402
+ for (let k = 0; k < cc; k++) {
403
+ const base = 4 + k * 7;
404
+ count = append_contact(count,
405
+ convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
406
+ convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
407
+ nx, ny, nz,
408
+ convex_manifold_result[base + 6],
409
+ 0);
410
+ }
411
+ return count;
412
+ }
413
+
414
+ /**
415
+ * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
416
+ * by shape type and appends 0..K contacts to the candidate buffer. Returns
417
+ * the new candidate count.
418
+ *
419
+ * @param {number} count
420
+ * @param {Collider} colA
421
+ * @param {Transform} trA
422
+ * @param {Collider} colB
423
+ * @param {Transform} trB
424
+ * @returns {number}
425
+ */
426
+ function dispatch_pair(count, colA, trA, colB, trB) {
427
+ const shapeA = colA.shape;
428
+ const shapeB = colB.shape;
429
+
430
+ // Per-contact materials: combine the two source colliders' coefficients
431
+ // once here (this is the only place that knows the exact collider on each
432
+ // side) and stamp them onto every contact this dispatch appends. The
433
+ // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
434
+ // material fields — it never writes to a manifold, so 0 is fine there.
435
+ const fa = colA.friction, fb = colB.friction;
436
+
437
+ if (fa !== undefined && fb !== undefined) {
438
+
439
+ g_pair_friction = combine_friction(fa, fb);
440
+ g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
441
+
442
+ } else {
443
+ g_pair_friction = 0;
444
+ g_pair_restitution = 0;
445
+ }
446
+
447
+ // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
448
+ // SphereShape3D (arbitrary radius). Both expose `radius`.
449
+ const isSphereA = shapeA.isSphereShape3D === true;
450
+ const isSphereB = shapeB.isSphereShape3D === true;
451
+ // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
452
+ // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
453
+ const isBoxA = shapeA.isBoxShape3D === true;
454
+ const isBoxB = shapeB.isBoxShape3D === true;
455
+ const isCapsuleA = shapeA.isCapsuleShape3D === true;
456
+ const isCapsuleB = shapeB.isCapsuleShape3D === true;
457
+
458
+ // sphere-sphere
459
+ if (isSphereA && isSphereB) {
460
+ const ra = shapeA.radius, rb = shapeB.radius;
461
+
462
+ const ok = sphere_sphere_contact(
463
+ sphere_result,
464
+ trA.position.x, trA.position.y, trA.position.z,
465
+ trB.position.x, trB.position.y, trB.position.z,
466
+ ra, rb
467
+ );
468
+
469
+ if (!ok) return count;
470
+
471
+ const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
472
+
473
+ // Sphere-sphere produces exactly one contact per pair; fid = 1
474
+ // identifies it as a real feature (distinguishes from "no info" = 0)
475
+ // and is trivially stable across frames. Witnesses are the surface
476
+ // points along the (unit) normal, scaled by each sphere's radius.
477
+ return append_contact(count,
478
+ trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
479
+ trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
480
+ nx, ny, nz, sphere_result[3], 1);
481
+ }
482
+
483
+ // sphere ↔ box
484
+ if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
485
+ const sphereTr = isSphereA ? trA : trB;
486
+ const sphereShape = isSphereA ? shapeA : shapeB;
487
+ const boxTr = isSphereA ? trB : trA;
488
+ const boxShape = isSphereA ? shapeB : shapeA;
489
+ const bh = boxShape.half_extents;
490
+
491
+ const ok = sphere_box_contact(
492
+ closed_form_result,
493
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
494
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
495
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
496
+ bh.x, bh.y, bh.z
497
+ );
498
+
499
+ if (!ok) return count;
500
+
501
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
502
+
503
+ const depth = closed_form_result[3];
504
+ const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
505
+ const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
506
+ // Feature id from the box-local voronoi region (1..27) — stable
507
+ // while the sphere stays on the same face / edge / vertex of the
508
+ // box, which is the steady-state case for resting / sliding contact.
509
+
510
+ const fid = sphere_box_voronoi_fid(
511
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
512
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
513
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
514
+ bh.x, bh.y, bh.z
515
+ );
516
+
517
+ if (isSphereA) {
518
+ return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
519
+ } else {
520
+ return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
521
+ }
522
+ }
523
+
524
+ // box-box multi-point
525
+ if (isBoxA && isBoxB) {
526
+ const ah = shapeA.half_extents;
527
+ const bh = shapeB.half_extents;
528
+ const ok = box_box_manifold(
529
+ box_manifold_result,
530
+ trA.position.x, trA.position.y, trA.position.z,
531
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
532
+ ah.x, ah.y, ah.z,
533
+ trB.position.x, trB.position.y, trB.position.z,
534
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
535
+ bh.x, bh.y, bh.z
536
+ );
537
+ if (!ok) return count;
538
+ const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
539
+ const cc = box_manifold_result[3] | 0;
540
+ // Box-box manifolds: closed-form clipping doesn't expose stable
541
+ // per-contact feature ids (a contact migrates from "incident-vertex k"
542
+ // to "clip-intersection on edge j" as the boxes rotate). Leave
543
+ // fid = 0 so the match-and-merge pass uses position-fallback —
544
+ // the per-contact clipped points are spread by face geometry and
545
+ // typically stay >>MATCH_TOL apart, so position matching is
546
+ // unambiguous frame-to-frame.
547
+ for (let k = 0; k < cc; k++) {
548
+ const base = 4 + k * 7;
549
+ count = append_contact(count,
550
+ box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
551
+ box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
552
+ nx, ny, nz,
553
+ box_manifold_result[base + 6],
554
+ 0);
555
+ }
556
+ return count;
557
+ }
558
+
559
+ // capsule-capsule (multi-point: near-parallel overlap emits both ends of
560
+ // the overlap interval; everything else the single closest-point contact)
561
+ if (isCapsuleA && isCapsuleB) {
562
+ const a_shape = colA.shape, b_shape = colB.shape;
563
+ const n_cc = capsule_capsule_multi_contacts(
564
+ capsule_capsule_result,
565
+ trA.position.x, trA.position.y, trA.position.z,
566
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
567
+ a_shape.radius, a_shape.height * 0.5,
568
+ trB.position.x, trB.position.y, trB.position.z,
569
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
570
+ b_shape.radius, b_shape.height * 0.5
571
+ );
572
+ for (let k = 0; k < n_cc; k++) {
573
+ const off = k * CAPSULE_CAPSULE_CONTACT_STRIDE;
574
+ // fid = k + 1: stable per interval end (1-based; 0 = no info).
575
+ count = append_contact(count,
576
+ capsule_capsule_result[off], capsule_capsule_result[off + 1], capsule_capsule_result[off + 2],
577
+ capsule_capsule_result[off + 3], capsule_capsule_result[off + 4], capsule_capsule_result[off + 5],
578
+ capsule_capsule_result[off + 6], capsule_capsule_result[off + 7], capsule_capsule_result[off + 8],
579
+ capsule_capsule_result[off + 9], k + 1);
580
+ }
581
+ return count;
582
+ }
583
+
584
+ // capsule ↔ sphere
585
+ if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
586
+ const capsuleTr = isCapsuleA ? trA : trB;
587
+ const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
588
+ const sphereTr = isCapsuleA ? trB : trA;
589
+ const sphereShape = isCapsuleA ? colB.shape : colA.shape;
590
+ const ok = capsule_sphere_contact(
591
+ closed_form_result,
592
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
593
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
594
+ capsuleShape.radius, capsuleShape.height * 0.5,
595
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
596
+ );
597
+ if (!ok) return count;
598
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
599
+ const depth = closed_form_result[3];
600
+ const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
601
+ const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
602
+ // Single contact per capsule-sphere pair; fid = 1.
603
+ if (isCapsuleA) {
604
+ return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
605
+ } else {
606
+ return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
607
+ }
608
+ }
609
+
610
+ // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
611
+ if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
612
+ const capsuleTr = isCapsuleA ? trA : trB;
613
+ const capsuleShape = isCapsuleA ? shapeA : shapeB;
614
+ const boxTr = isCapsuleA ? trB : trA;
615
+ const boxShape = isCapsuleA ? shapeB : shapeA;
616
+ const bh = boxShape.half_extents;
617
+ const cc = capsule_box_multi_contacts(
618
+ capsule_box_multi_result,
619
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
620
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
621
+ capsuleShape.radius, capsuleShape.height * 0.5,
622
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
623
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
624
+ bh.x, bh.y, bh.z
625
+ );
626
+ if (cc === 0) return count;
627
+ // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
628
+ // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
629
+ // emission order from capsule_box_multi_contacts is stable across
630
+ // frames for the same geometric configuration.
631
+ for (let k = 0; k < cc; k++) {
632
+ const o = k * CAPSULE_BOX_CONTACT_STRIDE;
633
+ const cap_x = capsule_box_multi_result[o];
634
+ const cap_y = capsule_box_multi_result[o + 1];
635
+ const cap_z = capsule_box_multi_result[o + 2];
636
+ const box_x = capsule_box_multi_result[o + 3];
637
+ const box_y = capsule_box_multi_result[o + 4];
638
+ const box_z = capsule_box_multi_result[o + 5];
639
+ const nx = capsule_box_multi_result[o + 6];
640
+ const ny = capsule_box_multi_result[o + 7];
641
+ const nz = capsule_box_multi_result[o + 8];
642
+ const depth = capsule_box_multi_result[o + 9];
643
+ const sub_fid = k + 1;
644
+ if (isCapsuleA) {
645
+ count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
646
+ } else {
647
+ count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
648
+ }
649
+ }
650
+ return count;
651
+ }
652
+
653
+ // convex hull ↔ convex hull (multi-point clipping via GJK + EPA + face-clip).
654
+ if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
655
+ return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
656
+ }
657
+
658
+ // mesh ↔ mesh. A globally-convex mesh collides as a single cached hull
659
+ // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
660
+ // mesh, so detecting convexity and routing through the hull path is the
661
+ // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
662
+ // the per-piece decomposed path (convex pieces vs convex pieces).
663
+ if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
664
+ const hullA = get_mesh_convex_hull(shapeA);
665
+ const hullB = get_mesh_convex_hull(shapeB);
666
+ if (hullA !== null && hullB !== null) {
667
+ return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
668
+ }
669
+ return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
670
+ }
671
+
672
+ // ── Concave (non-convex) path ───────────────────────────────────────
673
+ //
674
+ // If either shape has `is_convex === false`, GJK on the whole shape
675
+ // produces incorrect results (Minkowski difference is not convex).
676
+ // We decompose the concave side into triangles overlapping the
677
+ // convex side's AABB, then run convex per-triangle GJK + EPA.
678
+ //
679
+ // Concave-vs-concave is intentionally NOT supported in v1: the
680
+ // M×N triangle pairs would dominate runtime, and the physics
681
+ // engine's design contract requires at least one side to be
682
+ // static / kinematic for concave shapes anyway (the broadphase +
683
+ // filter should keep such pairs out of the narrowphase entirely).
684
+ // If one slips through, we skip rather than burn cycles.
685
+ const isConcaveA = shapeA.is_convex === false;
686
+ const isConcaveB = shapeB.is_convex === false;
687
+ if (isConcaveA && isConcaveB) return count;
688
+ if (isConcaveA || isConcaveB) {
689
+ const concave_col = isConcaveA ? colA : colB;
690
+ const concave_tr = isConcaveA ? trA : trB;
691
+ const convex_col = isConcaveA ? colB : colA;
692
+ const convex_tr = isConcaveA ? trB : trA;
693
+
694
+ // 1. Convex shape's world AABB.
695
+ convex_col.shape.compute_bounding_box(concave_local_aabb);
696
+ aabb3_transform_oriented(
697
+ concave_world_aabb, 0,
698
+ concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
699
+ concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
700
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
701
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
702
+ );
703
+
704
+ // 2. Project into concave's body-local frame.
705
+ aabb3_transform_oriented_inverse(
706
+ concave_query_aabb, 0,
707
+ concave_world_aabb,
708
+ concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
709
+ concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
710
+ );
711
+
712
+ // 3. Decompose concave shape into triangles overlapping the query.
713
+ // The enumerator returns the TRUE overlap count — clamp to the
714
+ // scratch capacity (overflow writes were dropped; reading past the
715
+ // buffer would poison the manifold with NaN).
716
+ let tri_count = decompose_to_triangles(
717
+ triangle_buffer, 0, concave_col.shape,
718
+ concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
719
+ concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
720
+ );
721
+ if (tri_count > MAX_TRIANGLES_PER_PAIR) tri_count = MAX_TRIANGLES_PER_PAIR;
722
+ if (tri_count === 0) return count;
723
+
724
+ // 4. Set up the convex side once; the concave side gets a
725
+ // Triangle3D rebound to each triangle in the loop. We keep
726
+ // the concave side as our internal "A" so EPA's sign-check
727
+ // convention matches the convex fallback below; the final
728
+ // append_contact() swaps if the original A was the convex one.
729
+ posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
730
+ posed_a.shape = triangle_shape;
731
+ posed_a.px = concave_tr.position.x;
732
+ posed_a.py = concave_tr.position.y;
733
+ posed_a.pz = concave_tr.position.z;
734
+ posed_a.qx = concave_tr.rotation.x;
735
+ posed_a.qy = concave_tr.rotation.y;
736
+ posed_a.qz = concave_tr.rotation.z;
737
+ posed_a.qw = concave_tr.rotation.w;
738
+
739
+ // Pre-compute the centre-axis used by EPA's sign-check loop.
740
+ // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
741
+ const convex_wx = convex_tr.position.x;
742
+ const convex_wy = convex_tr.position.y;
743
+ const convex_wz = convex_tr.position.z;
744
+
745
+ // Track the candidate-buffer index at the start of this
746
+ // concave dispatch — the per-triangle dedup pass scans from
747
+ // here to the current count, ignoring contacts from earlier
748
+ // collider pairs in the same body pair.
749
+ const pair_start_count = count;
750
+
751
+ // Pre-compute concave's rotation components for the q · v · q⁻¹
752
+ // rotations done per-triangle below (face normal + centroid).
753
+ const cqx = concave_tr.rotation.x;
754
+ const cqy = concave_tr.rotation.y;
755
+ const cqz = concave_tr.rotation.z;
756
+ const cqw = concave_tr.rotation.w;
757
+ const c_pos_x = concave_tr.position.x;
758
+ const c_pos_y = concave_tr.position.y;
759
+ const c_pos_z = concave_tr.position.z;
760
+
761
+ // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
762
+ // entirely per triangle and use the closed-form
763
+ // {@link sphere_triangle_contact}. This avoids the EPA precision
764
+ // wall on Triangle3D (whose support function is degenerate along
765
+ // the face normal — all 3 vertices project to the same value),
766
+ // which was producing noisy depths at small penetrations and
767
+ // letting dropped spheres tunnel through heightmaps / meshes.
768
+ const isSphereConvex = convex_col.shape.isSphereShape3D === true;
769
+ const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
770
+
771
+ // Box fast-path: closed-form {@link box_triangle_contact} via SAT
772
+ // over 13 axes + polygon clipping for face-vs-face contacts.
773
+ // Same motivation as the sphere path — Triangle3D's degenerate
774
+ // face-normal support kills EPA precision and produces noisy
775
+ // depths. The box path uses world-space triangle vertices.
776
+ const isBoxConvex = convex_col.shape.isBoxShape3D === true;
777
+ const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
778
+
779
+ // Capsule fast-path: closed-form {@link capsule_triangle_contact}
780
+ // via segment-vs-triangle closest-point + cap-centre sphere
781
+ // queries for a multi-point manifold. Same motivation as the
782
+ // sphere and box paths.
783
+ const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
784
+ const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
785
+
786
+ for (let i = 0; i < tri_count; i++) {
787
+ const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
788
+ triangle_shape.bind(triangle_buffer, tri_offset);
789
+
790
+ const ax = triangle_buffer[tri_offset ];
791
+ const ay = triangle_buffer[tri_offset + 1];
792
+ const az = triangle_buffer[tri_offset + 2];
793
+
794
+ const bx = triangle_buffer[tri_offset + 3];
795
+ const by = triangle_buffer[tri_offset + 4];
796
+ const bz = triangle_buffer[tri_offset + 5];
797
+
798
+ const cx_v = triangle_buffer[tri_offset + 6];
799
+ const cy_v = triangle_buffer[tri_offset + 7];
800
+ const cz_v = triangle_buffer[tri_offset + 8];
801
+
802
+ // Triangle decomposition emits a stable per-triangle feature_id
803
+ // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
804
+ // the same shape gets the same id across frames. This is the
805
+ // gold-standard fid for the match-and-merge pass.
806
+ const tri_fid = triangle_buffer[tri_offset + 9];
807
+
808
+ // Triangle face normal in body-local frame: (B − A) × (C − A).
809
+ // Winding convention (CCW from outside) gives an outward face
810
+ // normal — the heightmap / mesh enumerators both promise this.
811
+ const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
812
+ const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
813
+
814
+ const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
815
+ const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
816
+ const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
817
+
818
+ // Rotate face normal to world via concave's quaternion
819
+ // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape3D.support.
820
+ const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
821
+ const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
822
+ const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
823
+ const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
824
+
825
+ const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
826
+ const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
827
+ const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
828
+
829
+ // Sphere-vs-triangle closed-form fast-path.
830
+ if (isSphereConvex) {
831
+
832
+ // Rotate each triangle vertex from concave-local to world
833
+ // via the shared helper.
834
+ triangle_vertices_to_world(
835
+ scratch_tri_world,
836
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
837
+ cqx, cqy, cqz, cqw,
838
+ c_pos_x, c_pos_y, c_pos_z
839
+ );
840
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
841
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
842
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
843
+
844
+ const ok = sphere_triangle_contact(
845
+ sphere_triangle_result,
846
+ convex_wx, convex_wy, convex_wz, sphere_radius,
847
+ ax_w, ay_w, az_w,
848
+ bx_w, by_w, bz_w,
849
+ cx_w, cy_w, cz_w
850
+ );
851
+
852
+ if (!ok) continue;
853
+
854
+ // sphere_triangle_contact's normal points from the
855
+ // triangle surface toward the sphere centre — same
856
+ // direction as the post-sign-check EPA MTV in the
857
+ // fallback path below (concave A → convex B).
858
+ const sd = sphere_triangle_result[3];
859
+ const n_t2s_x = sphere_triangle_result[0];
860
+ const n_t2s_y = sphere_triangle_result[1];
861
+ const n_t2s_z = sphere_triangle_result[2];
862
+
863
+ // One-sided rejection: the sphere must lie on the
864
+ // outward side of the triangle. If the contact normal
865
+ // opposes the face normal, the sphere is behind /
866
+ // inside the solid — skip rather than push it deeper.
867
+ if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
868
+
869
+ // Stored normal convention is "B → A". `nx, ny, nz` here
870
+ // points convex → concave (which the existing EPA branch
871
+ // also produces just before append). Dedup uses the
872
+ // post-swap stored_n so adjacent-triangle duplicates
873
+ // collapse the same way regardless of code path.
874
+ const nx_s = -n_t2s_x;
875
+ const ny_s = -n_t2s_y;
876
+ const nz_s = -n_t2s_z;
877
+
878
+ const stored_nx_s = isConcaveA ? nx_s : -nx_s;
879
+ const stored_ny_s = isConcaveA ? ny_s : -ny_s;
880
+ const stored_nz_s = isConcaveA ? nz_s : -nz_s;
881
+ let is_duplicate_s = false;
882
+ for (let k = pair_start_count; k < count; k++) {
883
+ const ko = k * CANDIDATE_STRIDE;
884
+ const dnx = candidates[ko + 6] - stored_nx_s;
885
+ const dny = candidates[ko + 7] - stored_ny_s;
886
+ const dnz = candidates[ko + 8] - stored_nz_s;
887
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
888
+ is_duplicate_s = true;
889
+ break;
890
+ }
891
+ }
892
+ if (is_duplicate_s) continue;
893
+
894
+ // Surface witnesses from the closed-form solver:
895
+ // result[4..6] = sphere surface point (convex side)
896
+ // result[7..9] = triangle closest point (concave side)
897
+ // Use the actual witnesses rather than body centres —
898
+ // unlike the EPA fallback (which uses body centres
899
+ // because flat-faced support witnesses can be arbitrarily
900
+ // far from the contact patch), closest-point-on-triangle
901
+ // is exact and lies on the contact patch.
902
+ const sphere_wx = sphere_triangle_result[4];
903
+ const sphere_wy = sphere_triangle_result[5];
904
+ const sphere_wz = sphere_triangle_result[6];
905
+ const tri_wx = sphere_triangle_result[7];
906
+ const tri_wy = sphere_triangle_result[8];
907
+ const tri_wz = sphere_triangle_result[9];
908
+ if (isConcaveA) {
909
+ count = append_contact(count,
910
+ tri_wx, tri_wy, tri_wz,
911
+ sphere_wx, sphere_wy, sphere_wz,
912
+ nx_s, ny_s, nz_s, sd, tri_fid);
913
+ } else {
914
+ count = append_contact(count,
915
+ sphere_wx, sphere_wy, sphere_wz,
916
+ tri_wx, tri_wy, tri_wz,
917
+ -nx_s, -ny_s, -nz_s, sd, tri_fid);
918
+ }
919
+ continue;
920
+ }
921
+
922
+ // Box-vs-triangle closed-form fast-path.
923
+ if (isBoxConvex) {
924
+ // Rotate each triangle vertex from concave-local to world
925
+ // via the shared helper.
926
+ triangle_vertices_to_world(
927
+ scratch_tri_world,
928
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
929
+ cqx, cqy, cqz, cqw,
930
+ c_pos_x, c_pos_y, c_pos_z
931
+ );
932
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
933
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
934
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
935
+
936
+ const ok = box_triangle_contact(
937
+ box_triangle_result,
938
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
939
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
940
+ box_half_extents.x, box_half_extents.y, box_half_extents.z,
941
+ ax_w, ay_w, az_w,
942
+ bx_w, by_w, bz_w,
943
+ cx_w, cy_w, cz_w
944
+ );
945
+ if (!ok) continue;
946
+
947
+ // Same convention as sphere_triangle: result normal points
948
+ // from triangle surface toward box centre. To match the
949
+ // EPA branch's `nx, ny, nz` (which points convex →
950
+ // concave = box → triangle), negate.
951
+ const n_t2box_x = box_triangle_result[0];
952
+ const n_t2box_y = box_triangle_result[1];
953
+ const n_t2box_z = box_triangle_result[2];
954
+
955
+ // One-sided rejection: the box must lie on the outward
956
+ // side of the triangle. If the contact normal opposes
957
+ // the face normal, the box is inside the solid → skip.
958
+ if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
959
+
960
+ const nx_b = -n_t2box_x;
961
+ const ny_b = -n_t2box_y;
962
+ const nz_b = -n_t2box_z;
963
+ const stored_nx_b = isConcaveA ? nx_b : -nx_b;
964
+ const stored_ny_b = isConcaveA ? ny_b : -ny_b;
965
+ const stored_nz_b = isConcaveA ? nz_b : -nz_b;
966
+
967
+ // Emit each clipped contact. The contact count from
968
+ // box_triangle_contact is at most 4; combined with the
969
+ // existing per-pair candidate cap (64 entries), tens of
970
+ // overlapping triangles per pair are safely accommodated.
971
+ const cc = box_triangle_result[3] | 0;
972
+ for (let k = 0; k < cc; k++) {
973
+ const base = 4 + k * 7;
974
+ const t_wx = box_triangle_result[base];
975
+ const t_wy = box_triangle_result[base + 1];
976
+ const t_wz = box_triangle_result[base + 2];
977
+ const b_wx = box_triangle_result[base + 3];
978
+ const b_wy = box_triangle_result[base + 4];
979
+ const b_wz = box_triangle_result[base + 5];
980
+ const d_k = box_triangle_result[base + 6];
981
+
982
+ // Dedup against earlier candidates in THIS dispatch
983
+ // (adjacent triangles can produce coincident contacts
984
+ // — same as the sphere and EPA paths).
985
+ let is_duplicate_k = false;
986
+ for (let q = pair_start_count; q < count; q++) {
987
+ const qo = q * CANDIDATE_STRIDE;
988
+ const dnx = candidates[qo + 6] - stored_nx_b;
989
+ const dny = candidates[qo + 7] - stored_ny_b;
990
+ const dnz = candidates[qo + 8] - stored_nz_b;
991
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
992
+ // Same normal; check positions too (a
993
+ // multi-point manifold has distinct points
994
+ // with the same normal — those should NOT
995
+ // be deduped). Compare the world-A side
996
+ // (triangle surface or box surface depending
997
+ // on dispatcher swap).
998
+ const pa_x = isConcaveA ? t_wx : b_wx;
999
+ const pa_y = isConcaveA ? t_wy : b_wy;
1000
+ const pa_z = isConcaveA ? t_wz : b_wz;
1001
+ const dpx = candidates[qo] - pa_x;
1002
+ const dpy = candidates[qo + 1] - pa_y;
1003
+ const dpz = candidates[qo + 2] - pa_z;
1004
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1005
+ is_duplicate_k = true;
1006
+ break;
1007
+ }
1008
+ }
1009
+ }
1010
+ if (is_duplicate_k) continue;
1011
+
1012
+ if (isConcaveA) {
1013
+ count = append_contact(count,
1014
+ t_wx, t_wy, t_wz,
1015
+ b_wx, b_wy, b_wz,
1016
+ nx_b, ny_b, nz_b, d_k, tri_fid);
1017
+ } else {
1018
+ count = append_contact(count,
1019
+ b_wx, b_wy, b_wz,
1020
+ t_wx, t_wy, t_wz,
1021
+ -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1022
+ }
1023
+ }
1024
+ continue;
1025
+ }
1026
+
1027
+ // Capsule-vs-triangle closed-form fast-path.
1028
+ if (isCapsuleConvex) {
1029
+ // Rotate each triangle vertex from concave-local to world
1030
+ // via the shared helper.
1031
+ triangle_vertices_to_world(
1032
+ scratch_tri_world,
1033
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
1034
+ cqx, cqy, cqz, cqw,
1035
+ c_pos_x, c_pos_y, c_pos_z
1036
+ );
1037
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
1038
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
1039
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
1040
+
1041
+ const cap_cc = capsule_triangle_contact(
1042
+ capsule_triangle_result,
1043
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1044
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1045
+ capsule_shape.radius, capsule_shape.height * 0.5,
1046
+ ax_w, ay_w, az_w,
1047
+ bx_w, by_w, bz_w,
1048
+ cx_w, cy_w, cz_w
1049
+ );
1050
+ if (cap_cc === 0) continue;
1051
+
1052
+ // Each sub-contact's normal can be distinct (a flat
1053
+ // capsule produces primary + endpoint contacts whose
1054
+ // normals all point ≈ same direction, but an edge or
1055
+ // vertex contact at a cap can deviate). Apply
1056
+ // face-normal rejection per-contact.
1057
+ for (let k = 0; k < cap_cc; k++) {
1058
+ const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1059
+ const cap_x = capsule_triangle_result[base];
1060
+ const cap_y = capsule_triangle_result[base + 1];
1061
+ const cap_z = capsule_triangle_result[base + 2];
1062
+ const ct_x = capsule_triangle_result[base + 3];
1063
+ const ct_y = capsule_triangle_result[base + 4];
1064
+ const ct_z = capsule_triangle_result[base + 5];
1065
+ const n_t2cap_x = capsule_triangle_result[base + 6];
1066
+ const n_t2cap_y = capsule_triangle_result[base + 7];
1067
+ const n_t2cap_z = capsule_triangle_result[base + 8];
1068
+ const d_c = capsule_triangle_result[base + 9];
1069
+
1070
+ // One-sided face-normal rejection: capsule must be
1071
+ // on the outward side of the triangle.
1072
+ if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1073
+
1074
+ // Stored normal "convex → concave" = "capsule →
1075
+ // triangle" = negated t2cap.
1076
+ const nx_c = -n_t2cap_x;
1077
+ const ny_c = -n_t2cap_y;
1078
+ const nz_c = -n_t2cap_z;
1079
+ const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1080
+ const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1081
+ const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1082
+
1083
+ // Dedup against earlier candidates in THIS dispatch.
1084
+ let is_duplicate_c = false;
1085
+ for (let q = pair_start_count; q < count; q++) {
1086
+ const qo = q * CANDIDATE_STRIDE;
1087
+ const dnx = candidates[qo + 6] - stored_nx_c;
1088
+ const dny = candidates[qo + 7] - stored_ny_c;
1089
+ const dnz = candidates[qo + 8] - stored_nz_c;
1090
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1091
+ const pa_x = isConcaveA ? ct_x : cap_x;
1092
+ const pa_y = isConcaveA ? ct_y : cap_y;
1093
+ const pa_z = isConcaveA ? ct_z : cap_z;
1094
+ const dpx = candidates[qo] - pa_x;
1095
+ const dpy = candidates[qo + 1] - pa_y;
1096
+ const dpz = candidates[qo + 2] - pa_z;
1097
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1098
+ is_duplicate_c = true;
1099
+ break;
1100
+ }
1101
+ }
1102
+ }
1103
+ if (is_duplicate_c) continue;
1104
+
1105
+ if (isConcaveA) {
1106
+ count = append_contact(count,
1107
+ ct_x, ct_y, ct_z,
1108
+ cap_x, cap_y, cap_z,
1109
+ nx_c, ny_c, nz_c, d_c, tri_fid);
1110
+ } else {
1111
+ count = append_contact(count,
1112
+ cap_x, cap_y, cap_z,
1113
+ ct_x, ct_y, ct_z,
1114
+ -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1115
+ }
1116
+ }
1117
+ continue;
1118
+ }
1119
+
1120
+ // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1121
+ // penetration depth and writes a UNIT axis into epa_result. We scale
1122
+ // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1123
+ // rejection / dedup below already consume. The old gjk_with_axis +
1124
+ // expanding_polytope_algorithm pair was replaced because it returned
1125
+ // a non-minimal axis on degenerate simplices (see memory:
1126
+ // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1127
+ // safety net for the rare case the robust query reports no overlap.
1128
+ let ex, ey, ez, depth;
1129
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1130
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1131
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1132
+ depth = pen_depth;
1133
+ } else {
1134
+ if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1135
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1136
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1137
+ if (!(depth > 0) || !Number.isFinite(depth)) continue;
1138
+ }
1139
+
1140
+ // Sign-check: validate MTV direction against (convex −
1141
+ // triangle_centroid). EPA's polytope-closest-face normal
1142
+ // can come out on the wrong side of the origin; this flip
1143
+ // canonicalises MTV to point from triangle (A) toward the
1144
+ // convex shape (B).
1145
+ const cent_lx = (ax + bx + cx_v) / 3;
1146
+ const cent_ly = (ay + by + cy_v) / 3;
1147
+ const cent_lz = (az + bz + cz_v) / 3;
1148
+ // Inlined q · v · q* + translate.
1149
+ const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1150
+ const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1151
+ const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1152
+ const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1153
+ const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1154
+ const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1155
+ const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1156
+
1157
+ const tri_ab_x = convex_wx - cent_wx;
1158
+ const tri_ab_y = convex_wy - cent_wy;
1159
+ const tri_ab_z = convex_wz - cent_wz;
1160
+ if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1161
+ ex = -ex; ey = -ey; ez = -ez;
1162
+ }
1163
+
1164
+ // One-sided rejection (post-sign-check). MTV now points
1165
+ // from triangle (A) toward convex shape (B). For a CCW
1166
+ // outward-wound triangle, the convex shape is on the
1167
+ // OUTWARD side iff MTV aligns with the face normal. If it
1168
+ // opposes the face normal, the convex shape is on the
1169
+ // back / inward side — invalid for heightmap/mesh (it's
1170
+ // inside the solid). Skip the triangle rather than push
1171
+ // the body deeper into the solid through the opposite
1172
+ // face on the next step.
1173
+ if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1174
+
1175
+ // After the validated flip, MTV points from triangle
1176
+ // (concave-side A) into convex (B). Stored normal is
1177
+ // "B → A" so we negate. Final A/B order matches
1178
+ // append_contact's contract: original A first, original B
1179
+ // second, normal "from original B toward original A".
1180
+ const inv = 1 / depth;
1181
+ const nx = -ex * inv;
1182
+ const ny = -ey * inv;
1183
+ const nz = -ez * inv;
1184
+
1185
+ // Dedup against earlier contacts emitted in THIS dispatch.
1186
+ // Adjacent triangles (heightmap cells sharing a diagonal;
1187
+ // mesh triangles sharing an edge or vertex) often report
1188
+ // identical contacts (same normal, same body-centre
1189
+ // application points), and feeding duplicates to the
1190
+ // sequential-impulse solver makes it escalate the impulse
1191
+ // across iterations without bound. We deduplicate by
1192
+ // contact normal within a small angular threshold: same
1193
+ // body pair + same normal + same body-centre positions =
1194
+ // the same physical contact, only one copy belongs in the
1195
+ // manifold.
1196
+ const stored_nx = isConcaveA ? nx : -nx;
1197
+ const stored_ny = isConcaveA ? ny : -ny;
1198
+ const stored_nz = isConcaveA ? nz : -nz;
1199
+ let is_duplicate = false;
1200
+ for (let k = pair_start_count; k < count; k++) {
1201
+ const ko = k * CANDIDATE_STRIDE;
1202
+ const dnx = candidates[ko + 6] - stored_nx;
1203
+ const dny = candidates[ko + 7] - stored_ny;
1204
+ const dnz = candidates[ko + 8] - stored_nz;
1205
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1206
+ is_duplicate = true;
1207
+ break;
1208
+ }
1209
+ }
1210
+ if (is_duplicate) continue;
1211
+
1212
+ // Contact application points: body centres rather than
1213
+ // the triangle's centroid. The existing GJK+EPA fallback
1214
+ // (below) uses the same convention — for vertical contacts
1215
+ // (sphere/box on a flat surface, the common case), the
1216
+ // lever arm r × n vanishes and the impulse resolves
1217
+ // cleanly.
1218
+ if (isConcaveA) {
1219
+ count = append_contact(count,
1220
+ c_pos_x, c_pos_y, c_pos_z,
1221
+ convex_wx, convex_wy, convex_wz,
1222
+ nx, ny, nz, depth, tri_fid);
1223
+ } else {
1224
+ count = append_contact(count,
1225
+ convex_wx, convex_wy, convex_wz,
1226
+ c_pos_x, c_pos_y, c_pos_z,
1227
+ -nx, -ny, -nz, depth, tri_fid);
1228
+ }
1229
+ }
1230
+
1231
+ return count;
1232
+ }
1233
+
1234
+ // Robust GJK + EPA fallback for any convex pair without a closed form or a
1235
+ // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1236
+ // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1237
+ // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1238
+ // sign-guard below consumes. Replaces the old gjk_with_axis +
1239
+ // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1240
+ // degenerate simplices its GJK produced (see memory:
1241
+ // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1242
+ posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
+ posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
+
1245
+ let ex, ey, ez, depth;
1246
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1247
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1248
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1249
+ depth = pen_depth;
1250
+ } else {
1251
+ if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1252
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1253
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1254
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
1255
+ }
1256
+
1257
+ // EPA's output vector should point along the minimum-translation
1258
+ // axis from A into B (the direction you'd push B by to separate).
1259
+ // On non-convergent EPA — common on smooth high-poly colliders like
1260
+ // a torus knot, where the polytope can't tighten onto the true
1261
+ // closest face within the iteration cap — the fallback returns
1262
+ // *some* face's normal, and that face can be on either side of the
1263
+ // origin. Resulting MTV occasionally points B *into* A instead of
1264
+ // *away from* A; the solver then sees a "separating" relative
1265
+ // velocity, clamps the normal impulse to zero, and the bodies pass
1266
+ // straight through each other.
1267
+ //
1268
+ // Validate against the vector from body A's centre to body B's
1269
+ // centre — EPA's direction must correlate positively with it. Flip
1270
+ // if not. For convex bodies whose origin is inside the geometry,
1271
+ // body-centre-to-body-centre is a strong proxy for the correct
1272
+ // separation direction. For pathological cases (deeply
1273
+ // interpenetrating bodies with coincident centres) the dot product
1274
+ // is near zero and we leave EPA's guess intact.
1275
+ const ab_x = trB.position.x - trA.position.x;
1276
+ const ab_y = trB.position.y - trA.position.y;
1277
+ const ab_z = trB.position.z - trA.position.z;
1278
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1279
+ ex = -ex; ey = -ey; ez = -ez;
1280
+ }
1281
+
1282
+ const inv = 1 / depth;
1283
+ const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1284
+
1285
+ // Contact application points: body centres rather than support-function
1286
+ // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1287
+ // and `posed_b.support(-EPA_dir)` — interacts badly with flat-faced
1288
+ // supports: a large floor's support in `+Y` returns a *corner* (the box
1289
+ // support is multi-valued along an axis-aligned direction and picks the
1290
+ // sign-tied corner), which can be tens of metres from the actual
1291
+ // contact patch. That blows up the solver's lever arm `r × n` and
1292
+ // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1293
+ //
1294
+ // Body centres give `r ‖ n` for vertical contacts (sphere/knot landing
1295
+ // on a floor — the common case), so `r × n = 0` and `m_eff = invM`
1296
+ // resolves the impact cleanly. For oblique contacts the application
1297
+ // point lies on the line between centres, which is the right-ish lever
1298
+ // arm for translational impulse and degrades gracefully on angular
1299
+ // response. A future closed-form mesh-vs-box path will compute a
1300
+ // proper face-projected witness; until then, body centres are the
1301
+ // robust fallback for the GJK+EPA route.
1302
+ // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1303
+ // so match-and-merge uses position-fallback. Single contact per pair.
1304
+ return append_contact(count,
1305
+ trA.position.x, trA.position.y, trA.position.z,
1306
+ trB.position.x, trB.position.y, trB.position.z,
1307
+ nx, ny, nz,
1308
+ depth, 0
1309
+ );
1310
+ }
1311
+
1312
+ // Reusable single-pair adapters for the penetration query below — no per-call
1313
+ // allocation. dispatch_pair only reads `.shape` off a collider and
1314
+ // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1315
+ // all it needs.
1316
+ const _pp_colA = { shape: null };
1317
+ const _pp_colB = { shape: null };
1318
+ const _pp_trA = { position: null, rotation: null };
1319
+ const _pp_trB = { position: null, rotation: null };
1320
+
1321
+
1322
+ /**
1323
+ * Single-pair penetration query: the depth and world normal of the DEEPEST
1324
+ * contact the narrowphase would generate for one posed shape pair.
1325
+ *
1326
+ * Routes through the exact same {@link dispatch_pair} the contact solver
1327
+ * consumes — closed-form for every sphere / box / capsule pair (box-box via
1328
+ * SAT, so the true minimum-translation axis is found rather than the
1329
+ * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1330
+ * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1331
+ * convex pair. The deepest contact's depth is the minimum-translation distance
1332
+ * and its normal is the MTV axis, so the result is correct for every shape pair
1333
+ * the engine can build and agrees bit-for-bit with what the solver acts on.
1334
+ *
1335
+ * The normal follows the narrowphase's stored convention: a unit vector
1336
+ * pointing from B toward A — the direction to translate A to separate it.
1337
+ *
1338
+ * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1339
+ * 0; callers needing to reject that case must check `is_convex` themselves.
1340
+ *
1341
+ * Not re-entrant: shares the module-level candidate / scratch buffers with
1342
+ * {@link narrowphase_step}. Intended for main-thread queries run outside a
1343
+ * step (depenetration, overlap depth, tooling) — never from inside one.
1344
+ *
1345
+ * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1346
+ * normal on penetration (untouched when the return value is 0)
1347
+ * @param {AbstractShape3D} shapeA
1348
+ * @param {{x:number,y:number,z:number}} posA
1349
+ * @param {{x:number,y:number,z:number,w:number}} rotA
1350
+ * @param {AbstractShape3D} shapeB
1351
+ * @param {{x:number,y:number,z:number}} posB
1352
+ * @param {{x:number,y:number,z:number,w:number}} rotB
1353
+ * @returns {number} deepest penetration depth (> 0) or 0 if separated
1354
+ */
1355
+ export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1356
+ _pp_colA.shape = shapeA;
1357
+ _pp_trA.position = posA;
1358
+ _pp_trA.rotation = rotA;
1359
+ _pp_colB.shape = shapeB;
1360
+ _pp_trB.position = posB;
1361
+ _pp_trB.rotation = rotB;
1362
+
1363
+ const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1364
+ if (n === 0) {
1365
+ return 0;
1366
+ }
1367
+
1368
+ // Deepest contact = the minimum-translation depth; its stored normal is the
1369
+ // separation axis. (For multi-point manifolds — box-box, capsule-box, a
1370
+ // convex straddling several mesh triangles — every point shares the
1371
+ // separating axis, so the max depth along it is the distance to separate.)
1372
+ let best_depth = -1;
1373
+ let best_off = 0;
1374
+ for (let i = 0; i < n; i++) {
1375
+ const off = i * CANDIDATE_STRIDE;
1376
+ const d = candidates[off + 9];
1377
+ if (d > best_depth) {
1378
+ best_depth = d;
1379
+ best_off = off;
1380
+ }
1381
+ }
1382
+
1383
+ if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1384
+ return 0;
1385
+ }
1386
+
1387
+ out_normal[0] = candidates[best_off + 6];
1388
+ out_normal[1] = candidates[best_off + 7];
1389
+ out_normal[2] = candidates[best_off + 8];
1390
+
1391
+ return best_depth;
1392
+ }
1393
+
1394
+ /**
1395
+ * For every pair in `pair_list`, do a cross-product over A's collider list ×
1396
+ * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1397
+ * to the manifold slot.
1398
+ *
1399
+ * @param {PairList} pair_list
1400
+ * @param {ManifoldStore} manifolds
1401
+ * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1402
+ * per-body collider lists, indexed by body-storage slot index. Typically
1403
+ * `system.__body_collider_lists` — passed in directly so this helper
1404
+ * has no dependency on `PhysicsSystem`.
1405
+ */
1406
+ export function narrowphase_step(pair_list, manifolds, lists) {
1407
+ const count = pair_list.count;
1408
+
1409
+ for (let i = 0; i < count; i++) {
1410
+ const idA = pair_list.get_a(i);
1411
+ const idB = pair_list.get_b(i);
1412
+
1413
+ const idxA = body_id_index(idA);
1414
+ const idxB = body_id_index(idB);
1415
+
1416
+ const list_a = lists[idxA];
1417
+ const list_b = lists[idxB];
1418
+
1419
+ const slot = manifolds.find(idA, idB);
1420
+
1421
+ if (list_a === undefined || list_b === undefined
1422
+ || list_a.length === 0 || list_b.length === 0
1423
+ ) {
1424
+
1425
+ manifolds.clear_contacts(slot);
1426
+ continue;
1427
+
1428
+ }
1429
+
1430
+ let cand_count = 0;
1431
+
1432
+ const la_len = list_a.length;
1433
+ const lb_len = list_b.length;
1434
+
1435
+ let sub_pair = 0;
1436
+ for (let a = 0; a < la_len; a++) {
1437
+ const ea = list_a[a];
1438
+
1439
+ for (let b = 0; b < lb_len; b++) {
1440
+ const eb = list_b[b];
1441
+
1442
+ const before = cand_count;
1443
+ cand_count = dispatch_pair(
1444
+ cand_count,
1445
+ ea.collider,
1446
+ ea.transform,
1447
+ eb.collider,
1448
+ eb.transform
1449
+ );
1450
+
1451
+ // Salt this sub-pair's fids ({@link FID_PAIR_SALT}) so a
1452
+ // compound body cannot fid-match — and inherit warm-start
1453
+ // impulses — across different collider pairs. fid 0 stays 0.
1454
+ if (sub_pair !== 0) {
1455
+ const salt = sub_pair * FID_PAIR_SALT;
1456
+ for (let c = before; c < cand_count; c++) {
1457
+ const co = c * CANDIDATE_STRIDE;
1458
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1459
+ }
1460
+ }
1461
+ sub_pair++;
1462
+ }
1463
+ }
1464
+
1465
+ if (cand_count === 0) {
1466
+ // No contacts this frame for an existing manifold. Keep the
1467
+ // slot in the cache (the grace window in advance_frame() will
1468
+ // evict it if this persists), but zero out impulses and
1469
+ // contact count — there's nothing for the solver to act on
1470
+ // and stale impulses would mislead next frame's warm-start
1471
+ // if contact re-establishes at a different feature.
1472
+ manifolds.clear_contacts(slot);
1473
+ continue;
1474
+ }
1475
+
1476
+ const kept = reduce_manifold_contacts(candidates, cand_count, CANDIDATE_STRIDE, MAX_KEPT, 9);
1477
+
1478
+ // ── Match-and-merge: feature-id (with position fallback) ──────
1479
+ //
1480
+ // Snapshot prev-frame state for matching + impulse carry-over.
1481
+ const data = manifolds.data_buffer;
1482
+ const slot_off = manifolds.slot_data_offset(slot);
1483
+ const prev_count_raw = manifolds.contact_count(slot);
1484
+ const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1485
+ ? MAX_CONTACTS_PER_MANIFOLD
1486
+ : prev_count_raw;
1487
+ for (let j = 0; j < prev_count; j++) {
1488
+ const off = slot_off + j * CONTACT_STRIDE;
1489
+ const snap_off = j * 7;
1490
+ prev_snapshot[snap_off] = data[off + 13]; // feature_id
1491
+ prev_snapshot[snap_off + 1] = data[off]; // world_a x
1492
+ prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1493
+ prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1494
+ prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1495
+ prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1496
+ prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1497
+ prev_claimed[j] = 0;
1498
+ }
1499
+
1500
+ // For each new candidate, find a matching prev contact.
1501
+ // Step 1: feature-id match (only if BOTH sides have a non-zero
1502
+ // feature_id — fid = 0 means "no info"). Same-fid prev
1503
+ // contacts are disambiguated by NEAREST previous witness:
1504
+ // a fid identifies the FEATURE (e.g. a triangle), not the
1505
+ // contact point — the box/capsule-triangle paths emit up
1506
+ // to 4 contacts sharing one triangle's fid, and the
1507
+ // reducer reorders them frame to frame, so first-match
1508
+ // would shuffle warm-start impulses between a face's own
1509
+ // contact points. (redetect_pair_geometry documents and
1510
+ // fixes the same failure for the per-substep path.)
1511
+ // Step 2: position-fallback within MATCH_TOL_SQR.
1512
+ for (let k = 0; k < kept; k++) {
1513
+ const cand_off = k * CANDIDATE_STRIDE;
1514
+ const cand_fid = candidates[cand_off + 10];
1515
+ const cand_ax = candidates[cand_off];
1516
+ const cand_ay = candidates[cand_off + 1];
1517
+ const cand_az = candidates[cand_off + 2];
1518
+
1519
+ let best_prev = -1;
1520
+ if (cand_fid !== 0) {
1521
+ // Gated by FID_MATCH_MAX_D2: a fid recomputed half a metre
1522
+ // away (teleport, fid collision) must not inherit impulses.
1523
+ let best_d2 = FID_MATCH_MAX_D2;
1524
+ for (let j = 0; j < prev_count; j++) {
1525
+ if (prev_claimed[j]) continue;
1526
+ if (prev_snapshot[j * 7] !== cand_fid) continue;
1527
+ const snap_off = j * 7;
1528
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1529
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1530
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1531
+ const d2 = dx * dx + dy * dy + dz * dz;
1532
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1533
+ }
1534
+ }
1535
+ if (best_prev === -1) {
1536
+ let best_d2 = MATCH_TOL_SQR;
1537
+ for (let j = 0; j < prev_count; j++) {
1538
+ if (prev_claimed[j]) continue;
1539
+ const snap_off = j * 7;
1540
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1541
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1542
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1543
+ const d2 = dx * dx + dy * dy + dz * dz;
1544
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1545
+ }
1546
+ }
1547
+ cand_to_prev[k] = best_prev;
1548
+ if (best_prev !== -1) prev_claimed[best_prev] = 1;
1549
+ }
1550
+
1551
+ // Reset count without zeroing the data slab — set_contact below
1552
+ // will overwrite geometry, and matched candidates will inherit
1553
+ // the impulses we snapshotted above (written back at the new
1554
+ // slot index after set_contact).
1555
+ manifolds.begin_refill(slot);
1556
+
1557
+ for (let k = 0; k < kept; k++) {
1558
+ const off = k * CANDIDATE_STRIDE;
1559
+ // Target slot index = k (we write candidates 0..kept-1 into
1560
+ // slot indices 0..kept-1). The impulse carry-over below
1561
+ // copies the matched prev contact's impulses into THIS k,
1562
+ // so the geometric and warm-start state stay correlated
1563
+ // even if the matching mapping permuted the order.
1564
+ manifolds.set_contact(
1565
+ slot, k,
1566
+ candidates[off], candidates[off + 1], candidates[off + 2],
1567
+ candidates[off + 3], candidates[off + 4], candidates[off + 5],
1568
+ candidates[off + 6], candidates[off + 7], candidates[off + 8],
1569
+ candidates[off + 9],
1570
+ candidates[off + 10],
1571
+ candidates[off + 11], candidates[off + 12]
1572
+ );
1573
+ const prev_j = cand_to_prev[k];
1574
+ if (prev_j !== -1) {
1575
+ // Copy prev_j's impulse to this slot index.
1576
+ const dst_off = slot_off + k * CONTACT_STRIDE;
1577
+ const src_off = prev_j * 7;
1578
+ data[dst_off + 10] = prev_snapshot[src_off + 4];
1579
+ data[dst_off + 11] = prev_snapshot[src_off + 5];
1580
+ data[dst_off + 12] = prev_snapshot[src_off + 6];
1581
+ } else {
1582
+ // No match — explicitly zero the impulses, since this
1583
+ // slot index may have stale data from an earlier frame's
1584
+ // contact at the same index.
1585
+ manifolds.clear_impulses(slot, k);
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ /**
1592
+ * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1593
+ * current poses, updating the witness points / normal / depth of the slot's
1594
+ * existing contacts in place. Does NOT change the contact count, the
1595
+ * feature ids, or the accumulated impulses — it only refreshes geometry.
1596
+ *
1597
+ * This is the per-substep concave path (TGS): for a contact pair involving a
1598
+ * concave body, the contact *feature* (which triangle is deepest, and its
1599
+ * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1600
+ * refresh — which freezes the feature for the whole outer step — pumps energy
1601
+ * in. Re-running the narrowphase geometry each substep gives a fresh,
1602
+ * correct normal so the body settles instead of rocking. Convex pairs keep
1603
+ * the analytic refresh and never call this (their feature is stable).
1604
+ *
1605
+ * Matching is by feature id (stable per-triangle for the decomposition path),
1606
+ * so a contact's geometry tracks the same triangle across substeps. A contact
1607
+ * whose triangle isn't found this substep keeps its previous geometry (a rare
1608
+ * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1609
+ * contact set next outer step). Count never changes here, so the solver's
1610
+ * per-contact scratch (sized once at prepare) stays aligned.
1611
+ *
1612
+ * @param {ManifoldStore} manifolds
1613
+ * @param {number} slot
1614
+ * @param {Array<{collider: Collider, transform: Transform}>} list_a
1615
+ * @param {Array<{collider: Collider, transform: Transform}>} list_b
1616
+ */
1617
+ export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1618
+ if (list_a === undefined || list_b === undefined) return;
1619
+ const la_len = list_a.length;
1620
+ const lb_len = list_b.length;
1621
+ if (la_len === 0 || lb_len === 0) return;
1622
+
1623
+ const count = manifolds.contact_count(slot);
1624
+ if (count === 0) return;
1625
+
1626
+ let cc = 0;
1627
+ let sub_pair = 0;
1628
+ for (let a = 0; a < la_len; a++) {
1629
+ const ea = list_a[a];
1630
+ for (let b = 0; b < lb_len; b++) {
1631
+ const eb = list_b[b];
1632
+ const before = cc;
1633
+ cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1634
+ // Stored fids are sub-pair-salted (see narrowphase_step's dispatch
1635
+ // loop) — salt the redetected candidates identically or no fid
1636
+ // would ever match for a compound body's non-first sub-pair.
1637
+ if (sub_pair !== 0) {
1638
+ const salt = sub_pair * FID_PAIR_SALT;
1639
+ for (let c = before; c < cc; c++) {
1640
+ const co = c * CANDIDATE_STRIDE;
1641
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1642
+ }
1643
+ }
1644
+ sub_pair++;
1645
+ }
1646
+ }
1647
+ if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1648
+
1649
+ const data = manifolds.data_buffer;
1650
+ const slot_off = manifolds.slot_data_offset(slot);
1651
+
1652
+ // Match each existing contact to a DISTINCT fresh candidate. feature_id
1653
+ // identifies the TRIANGLE, not the contact point — the box/capsule-triangle
1654
+ // paths emit several contacts for one triangle, all sharing that triangle's
1655
+ // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1656
+ // plain first-match-by-fid therefore maps every same-fid existing contact
1657
+ // onto the SAME candidate, collapsing the manifold to duplicate witness
1658
+ // points → a degenerate support polygon the solver can't damp (the
1659
+ // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1660
+ // candidates by NEAREST previous witness (world-A) position, and claim each
1661
+ // candidate so no two existing contacts take the same one. For the common
1662
+ // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1663
+ // single candidate exactly once — identical to the old behaviour.
1664
+ for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1665
+
1666
+ for (let j = 0; j < count; j++) {
1667
+ const off = slot_off + j * CONTACT_STRIDE;
1668
+ const fid = data[off + 13];
1669
+ if (fid === 0) continue; // no feature info to match on
1670
+
1671
+ // Previous witness (world-A) of this existing contact — the anchor we
1672
+ // disambiguate same-fid candidates against.
1673
+ const pax = data[off];
1674
+ const pay = data[off + 1];
1675
+ const paz = data[off + 2];
1676
+
1677
+ let best_k = -1;
1678
+ let best_d2 = Infinity;
1679
+ for (let k = 0; k < cc; k++) {
1680
+ if (redetect_claimed[k] === 1) continue;
1681
+ const co = k * CANDIDATE_STRIDE;
1682
+ if (candidates[co + 10] !== fid) continue;
1683
+ const dx = candidates[co] - pax;
1684
+ const dy = candidates[co + 1] - pay;
1685
+ const dz = candidates[co + 2] - paz;
1686
+ const d2 = dx * dx + dy * dy + dz * dz;
1687
+ if (d2 < best_d2) {
1688
+ best_d2 = d2;
1689
+ best_k = k;
1690
+ }
1691
+ }
1692
+
1693
+ // Position fallback (mirrors narrowphase_step's match-and-merge). A
1694
+ // single triangle's clipped contact count is NOT stable across sub-mm
1695
+ // pose changes — a box straddling a cell seam can have one triangle
1696
+ // yield 4 points one substep and 3 the next — so an existing contact can
1697
+ // outnumber this substep's same-fid candidates. Freezing its stale
1698
+ // witness then duplicates a sibling contact's point, leaving a
1699
+ // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1700
+ // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1701
+ // witness. The fid label is left intact and re-resolved by the next
1702
+ // once-per-step narrowphase. (At a fixed pose the same-fid match always
1703
+ // succeeds, so this never fires for the depth-equality guards or the
1704
+ // one-contact-per-triangle sphere/mesh paths.)
1705
+ if (best_k === -1) {
1706
+ for (let k = 0; k < cc; k++) {
1707
+ if (redetect_claimed[k] === 1) continue;
1708
+ const co = k * CANDIDATE_STRIDE;
1709
+ const dx = candidates[co] - pax;
1710
+ const dy = candidates[co + 1] - pay;
1711
+ const dz = candidates[co + 2] - paz;
1712
+ const d2 = dx * dx + dy * dy + dz * dz;
1713
+ if (d2 < best_d2) {
1714
+ best_d2 = d2;
1715
+ best_k = k;
1716
+ }
1717
+ }
1718
+ }
1719
+
1720
+ if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1721
+
1722
+ redetect_claimed[best_k] = 1;
1723
+ const co = best_k * CANDIDATE_STRIDE;
1724
+ // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1725
+ // ids and accumulated impulses are intentionally left untouched — see
1726
+ // the function contract.)
1727
+ data[off] = candidates[co];
1728
+ data[off + 1] = candidates[co + 1];
1729
+ data[off + 2] = candidates[co + 2];
1730
+ data[off + 3] = candidates[co + 3];
1731
+ data[off + 4] = candidates[co + 4];
1732
+ data[off + 5] = candidates[co + 5];
1733
+ data[off + 6] = candidates[co + 6];
1734
+ data[off + 7] = candidates[co + 7];
1735
+ data[off + 8] = candidates[co + 8];
1736
+ data[off + 9] = candidates[co + 9];
1737
+ }
1738
+ }