@woosh/meep-engine 2.139.0 → 2.140.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 (172) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
  6. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  7. package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
  8. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  9. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  11. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  12. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  13. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  14. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  17. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  18. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  22. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  23. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  24. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  25. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  26. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  27. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  29. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  30. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  31. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  32. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  41. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  42. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  43. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  44. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  45. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  46. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  47. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  48. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  49. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  50. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  53. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  54. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  55. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  56. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  57. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  59. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  60. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  62. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  63. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  64. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  65. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  66. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  71. package/src/engine/control/first-person/TODO.md +173 -127
  72. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  73. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  74. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  75. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  76. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  77. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  78. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  79. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  80. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  81. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  82. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  83. package/src/engine/physics/PLAN.md +461 -236
  84. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  85. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  87. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  88. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  89. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  90. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  91. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  92. package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
  93. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  94. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
  95. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  96. package/src/engine/physics/fluid/FluidSimulator.js +2 -1
  97. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
  98. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  99. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
  100. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  101. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  102. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  103. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  104. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  105. package/src/engine/physics/gjk/gjk.js +421 -378
  106. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  107. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  108. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  109. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  110. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  111. package/src/engine/physics/gjk/mpr.js +344 -0
  112. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  113. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  114. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  115. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  116. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  117. package/src/engine/physics/integration/integrate_position.js +43 -12
  118. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  119. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  120. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  121. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  122. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  123. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  124. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  125. package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
  126. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  127. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  128. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  129. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  130. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  131. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  132. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  133. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  134. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  135. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  136. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  137. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  138. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  139. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  140. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  141. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  142. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  143. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  144. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  145. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  146. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  147. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  148. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  149. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  150. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  151. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  152. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
  153. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  154. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
  155. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  156. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  157. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  158. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  159. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  160. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  161. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  162. package/src/engine/physics/queries/overlap_shape.js +183 -0
  163. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  164. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  165. package/src/engine/physics/queries/shape_cast.js +387 -0
  166. package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
  167. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  168. package/src/engine/physics/solver/solve_contacts.js +641 -223
  169. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  170. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  171. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  172. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -0,0 +1,396 @@
1
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
+ import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
3
+ import { mpr } from "../gjk/mpr.js";
4
+ import { aabb_world_to_local } from "./decomposition/aabb_world_to_local.js";
5
+ import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
6
+ import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
7
+ import { PosedShape } from "./PosedShape.js";
8
+
9
+ /**
10
+ * Module-scoped scratch — same single-thread re-entrancy assumption as
11
+ * the other narrowphase utilities. Safe because PhysicsSystem queries
12
+ * (and gameplay code calling this) run on the main thread.
13
+ */
14
+ const posed_a = new PosedShape();
15
+ const posed_b = new PosedShape();
16
+
17
+ // MPR result buffer — direction is "A's interior into B", magnitude is
18
+ // the depth. Same convention as EPA but MPR converges reliably on
19
+ // smooth-vs-smooth supports where EPA hits its iteration cap and
20
+ // returns a noisy closest-face approximation.
21
+ const mpr_result = new Float64Array(3);
22
+
23
+ /**
24
+ * Scratch buffers for the convex-vs-concave path (see
25
+ * {@link compute_penetration_concave}).
26
+ */
27
+ const local_aabb = new Float64Array(6);
28
+ const world_aabb = new Float64Array(6);
29
+ const concave_query_aabb = new Float64Array(6);
30
+ const scratch_v3 = new Float64Array(3);
31
+ // Dedicated scratch for the per-triangle q · v · q* rotations below
32
+ // (face normal + centroid). Distinct from `scratch_v3` (used by the
33
+ // support-function call later in the same iteration) to keep the
34
+ // data flow obvious.
35
+ const scratch_rot = new Float64Array(3);
36
+
37
+ /**
38
+ * Per-pair triangle decomposition cap. Same rationale as
39
+ * `narrowphase_step.MAX_TRIANGLES_PER_PAIR` and
40
+ * `overlap_shape.MAX_TRIANGLES_PER_PAIR`: the query AABB is already
41
+ * bounded by the convex shape's envelope, so a single pair typically
42
+ * yields tens of triangles. Excess triangles are dropped by the
43
+ * enumerator's bounds check.
44
+ * @type {number}
45
+ */
46
+ const MAX_TRIANGLES_PER_PAIR = 1024;
47
+ const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
48
+
49
+ /**
50
+ * Penetration depths below this are treated as no contact. GJK can
51
+ * report tangent configurations as "overlap" — at exact tangent the
52
+ * Minkowski difference touches the origin and the algorithm can go
53
+ * either way; if it says overlap, EPA then returns a sub-micron depth
54
+ * that reflects numerical noise rather than a real penetration. The
55
+ * "0 means no overlap" contract is more useful when small-noise hits
56
+ * are filtered out at the source.
57
+ *
58
+ * 1e-4 m (100 µm) chosen to be well below any practical world-scale
59
+ * tolerance while still being larger than typical EPA convergence
60
+ * residuals at exact tangent. Smaller values would let near-tangent
61
+ * floating-point noise leak through as "tiny positive depth".
62
+ *
63
+ * @type {number}
64
+ */
65
+ const CONTACT_EPSILON = 1e-4;
66
+
67
+ /**
68
+ * Compute the penetration depth between two shapes at world poses,
69
+ * returning the depth and writing the separation direction.
70
+ *
71
+ * If the shapes overlap, the return value is the positive distance
72
+ * along `out_direction` you would have to translate `shape_a` to
73
+ * separate it from `shape_b`. `out_direction` is the unit vector
74
+ * pointing from `shape_b` toward `shape_a` — applying
75
+ * `out_direction * return_value` to `position_a` (or equivalently
76
+ * `-out_direction * return_value` to `position_b`) is the minimum
77
+ * translation that produces separation.
78
+ *
79
+ * If the shapes do not overlap (or EPA degenerates on a tangent
80
+ * contact), the return value is `0` and `out_direction` is left
81
+ * untouched. Callers should treat 0 as "no penetration".
82
+ *
83
+ * Sign convention matches the narrowphase's stored contact normal:
84
+ * - `out_direction` ≡ "B → A" direction
85
+ * - Negative of cast direction in shape_cast at the kiss point
86
+ * - The outward normal of B's surface at the contact, pointing
87
+ * toward A
88
+ *
89
+ * Built on the existing GJK + EPA + PosedShape primitives — same
90
+ * precision characteristics (essentially exact for sign-based supports
91
+ * like cubes; asymptotic on curved supports like spheres near tangent,
92
+ * where the polytope iteration cap leaves a small angular residual on
93
+ * the direction).
94
+ *
95
+ * ## Non-convex support
96
+ *
97
+ * Exactly **one** of the two shapes may be non-convex (heightmap,
98
+ * mesh). The non-convex shape is decomposed into triangles overlapping
99
+ * the convex shape's AABB (via the same machinery the narrowphase
100
+ * uses), and per-triangle GJK + EPA is run; the deepest contact's
101
+ * direction and depth are reported. Concave-vs-concave throws — the
102
+ * M×N triangle-pair cost is out of scope for this primitive (and is
103
+ * also refused by the narrowphase for dynamic pairs).
104
+ *
105
+ * ## Non-convex precision limits
106
+ *
107
+ * The concave path is a per-triangle half-space test (convex's deepest
108
+ * point along −face_normal, compared to the triangle's plane). This
109
+ * is exact for **heightmaps** — adjacent triangles cover the
110
+ * boundary cases, and the face normal IS the contact direction.
111
+ *
112
+ * For **closed meshes** the half-space test extrapolates each triangle
113
+ * as an infinite plane, which can over-report depth on side faces
114
+ * when the convex shape extends past a face's 2D extent. The
115
+ * deepest-wins aggregation then picks a "false-deepest" face whose
116
+ * direction may not be the geometrically optimal one. A closed-form
117
+ * triangle-vs-X solver per primitive shape would fix this; until
118
+ * then, the function reports *some* outward direction with positive
119
+ * depth, which still resolves penetration over multiple iterations.
120
+ *
121
+ * Bodies fully inside the concave solid (or below a heightmap
122
+ * surface) are correctly recovered: every face's deepest-inward
123
+ * support point lands on the inward side, so the half-space test
124
+ * fires, and the deepest face wins. The reported direction pushes
125
+ * the body outward through that face.
126
+ *
127
+ * @param {Float64Array|number[]} out_direction length ≥ 3; receives
128
+ * the unit separation direction (B → A) on penetration
129
+ * @param {AbstractShape3D} shape_a in shape_a's local frame; may be concave
130
+ * @param {{x:number,y:number,z:number}} position_a world position of A
131
+ * @param {{x:number,y:number,z:number,w:number}} rotation_a world rotation of A
132
+ * @param {AbstractShape3D} shape_b in shape_b's local frame; may be concave
133
+ * @param {{x:number,y:number,z:number}} position_b world position of B
134
+ * @param {{x:number,y:number,z:number,w:number}} rotation_b world rotation of B
135
+ * @returns {number} penetration depth (positive) on overlap, 0 otherwise
136
+ * @throws {Error} if both shapes have `is_convex === false`
137
+ */
138
+ export function compute_penetration(
139
+ out_direction,
140
+ shape_a, position_a, rotation_a,
141
+ shape_b, position_b, rotation_b
142
+ ) {
143
+ const isConcaveA = shape_a.is_convex === false;
144
+ const isConcaveB = shape_b.is_convex === false;
145
+
146
+ if (isConcaveA && isConcaveB) {
147
+ throw new Error("compute_penetration: at most one shape may be non-convex (concave-vs-concave triangle-pair cost is out of scope)");
148
+ }
149
+
150
+ if (isConcaveA || isConcaveB) {
151
+ return compute_penetration_concave(
152
+ out_direction, isConcaveA,
153
+ shape_a, position_a, rotation_a,
154
+ shape_b, position_b, rotation_b
155
+ );
156
+ }
157
+
158
+ return compute_penetration_convex(
159
+ out_direction,
160
+ shape_a, position_a, rotation_a,
161
+ shape_b, position_b, rotation_b
162
+ );
163
+ }
164
+
165
+ /**
166
+ * Convex-vs-convex implementation — single MPR call.
167
+ *
168
+ * MPR (Minkowski Portal Refinement) gives an overlap-or-not answer
169
+ * plus the MTV in one pass, with reliable convergence on smooth
170
+ * supports where the previous GJK + EPA path struggled (sphere-vs-
171
+ * sphere shallow overlap, sphere-vs-box near-tangent — the closest-
172
+ * face direction would noise out by 20-30° before EPA hit its
173
+ * iteration cap). MPR converges in 5-15 iterations on those same
174
+ * configurations.
175
+ * @private
176
+ */
177
+ function compute_penetration_convex(
178
+ out_direction,
179
+ shape_a, position_a, rotation_a,
180
+ shape_b, position_b, rotation_b
181
+ ) {
182
+ posed_a.setup(shape_a, position_a, rotation_a);
183
+ posed_b.setup(shape_b, position_b, rotation_b);
184
+
185
+ if (!mpr(mpr_result, 0, posed_a, posed_b)) return 0;
186
+
187
+ let ex = mpr_result[0], ey = mpr_result[1], ez = mpr_result[2];
188
+ const depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
189
+ if (!(depth > CONTACT_EPSILON) || !Number.isFinite(depth)) return 0;
190
+
191
+ // MTV direction sanity check: should point from A's centre toward
192
+ // B's centre. Same trick as in `narrowphase_step` — even MPR can
193
+ // settle on either side of the origin for symmetric configurations
194
+ // (axis-aligned cubes), so dot against the body-centre axis and
195
+ // flip if needed.
196
+ const ab_x = position_b.x - position_a.x;
197
+ const ab_y = position_b.y - position_a.y;
198
+ const ab_z = position_b.z - position_a.z;
199
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
200
+ ex = -ex; ey = -ey; ez = -ez;
201
+ }
202
+
203
+ // MTV now points A → B. out_direction is B → A.
204
+ const inv = 1 / depth;
205
+ out_direction[0] = -ex * inv;
206
+ out_direction[1] = -ey * inv;
207
+ out_direction[2] = -ez * inv;
208
+
209
+ return depth;
210
+ }
211
+
212
+ /**
213
+ * Convex-vs-concave implementation — decompose, per-triangle half-space
214
+ * test, deepest-wins aggregation.
215
+ *
216
+ * Why the half-space test instead of per-triangle GJK + EPA:
217
+ *
218
+ * GJK + EPA on `Triangle3D` (a flat 2D shape) has a known degeneracy.
219
+ * `Triangle3D.support` along the triangle's face-normal axis returns
220
+ * the SAME vertex regardless of sign (all three vertices have equal
221
+ * projection along that axis). GJK can't converge from this degenerate
222
+ * support and returns false positives — sphere clearly above a flat
223
+ * surface gets reported as overlapping, with EPA producing a non-zero
224
+ * depth in some arbitrary direction.
225
+ *
226
+ * The half-space test is closed-form and exact for this case:
227
+ *
228
+ * 1. Compute the triangle's outward face normal in world frame
229
+ * (winding gives outward-CCW per the enumerator's contract).
230
+ * 2. Query `convex.support(-face_normal)` — the deepest point of the
231
+ * convex shape along the face-normal axis in the inward direction.
232
+ * 3. Project that point onto the face normal relative to the
233
+ * triangle's centroid plane.
234
+ * 4. If the projection is positive, the convex shape is entirely on
235
+ * the outward side — no penetration. Skip.
236
+ * 5. Otherwise the magnitude is the penetration depth, and the
237
+ * face normal IS the contact direction.
238
+ *
239
+ * For continuous concave surfaces (heightmaps with adjacent triangles
240
+ * sharing edges) the half-space test gives exact results: any penetrating
241
+ * convex either crosses a triangle's plane within the triangle's 2D
242
+ * extent (correct depth reported) or hits an adjacent triangle.
243
+ *
244
+ * For closed meshes (each face is a separate triangle, edges are shared
245
+ * between facets at angles), the half-space test can over-report when a
246
+ * convex is outside the triangle's 2D extent but still on the inward
247
+ * side of its infinite plane. Deepest-wins aggregation makes this
248
+ * harmless in practice — the dominant face's depth wins, which is the
249
+ * geometrically meaningful answer for the kinematic-resolution use case.
250
+ *
251
+ * One-sided rejection is built into the test itself: if the convex's
252
+ * deepest inward point is still on the outward side, depth ≤ 0 → skip.
253
+ * Bodies in fully-invalid states (inside a closed mesh, below a
254
+ * heightmap surface) get rejected naturally and return 0.
255
+ *
256
+ * @private
257
+ */
258
+ function compute_penetration_concave(
259
+ out_direction, isConcaveA,
260
+ shape_a, position_a, rotation_a,
261
+ shape_b, position_b, rotation_b
262
+ ) {
263
+ // Internally normalise: "concave" side is what we decompose;
264
+ // "convex" side is wrapped in PosedShape and queried via support.
265
+ const concave_shape = isConcaveA ? shape_a : shape_b;
266
+ const concave_pos = isConcaveA ? position_a : position_b;
267
+ const concave_rot = isConcaveA ? rotation_a : rotation_b;
268
+ const convex_shape = isConcaveA ? shape_b : shape_a;
269
+ const convex_pos = isConcaveA ? position_b : position_a;
270
+ const convex_rot = isConcaveA ? rotation_b : rotation_a;
271
+
272
+ // ── 1. Convex shape's world AABB ───────────────────────────────
273
+ convex_shape.compute_bounding_box(local_aabb);
274
+ aabb3_transform_oriented(
275
+ world_aabb, 0,
276
+ local_aabb[0], local_aabb[1], local_aabb[2],
277
+ local_aabb[3], local_aabb[4], local_aabb[5],
278
+ convex_pos.x, convex_pos.y, convex_pos.z,
279
+ convex_rot.x, convex_rot.y, convex_rot.z, convex_rot.w
280
+ );
281
+
282
+ // ── 2. Project into concave's body-local frame ─────────────────
283
+ aabb_world_to_local(
284
+ concave_query_aabb, 0,
285
+ world_aabb,
286
+ concave_pos.x, concave_pos.y, concave_pos.z,
287
+ concave_rot.x, concave_rot.y, concave_rot.z, concave_rot.w
288
+ );
289
+
290
+ // ── 3. Decompose to triangles ──────────────────────────────────
291
+ const tri_count = decompose_to_triangles(
292
+ triangle_buffer, 0, concave_shape,
293
+ concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
294
+ concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
295
+ );
296
+ if (tri_count === 0) return 0;
297
+
298
+ // ── 4. Setup convex PosedShape (we'll call support on it per tri) ─
299
+ posed_b.setup(convex_shape, convex_pos, convex_rot);
300
+
301
+ const cqx = concave_rot.x;
302
+ const cqy = concave_rot.y;
303
+ const cqz = concave_rot.z;
304
+ const cqw = concave_rot.w;
305
+ const c_pos_x = concave_pos.x;
306
+ const c_pos_y = concave_pos.y;
307
+ const c_pos_z = concave_pos.z;
308
+
309
+ // ── 5. Per-triangle half-space test, deepest-wins ──────────────
310
+ let best_depth = 0;
311
+ let best_fnx_w = 0, best_fny_w = 0, best_fnz_w = 0;
312
+
313
+ for (let i = 0; i < tri_count; i++) {
314
+ const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
315
+
316
+ const ax = triangle_buffer[tri_offset ];
317
+ const ay = triangle_buffer[tri_offset + 1];
318
+ const az = triangle_buffer[tri_offset + 2];
319
+ const bx = triangle_buffer[tri_offset + 3];
320
+ const by = triangle_buffer[tri_offset + 4];
321
+ const bz = triangle_buffer[tri_offset + 5];
322
+ const cx_v = triangle_buffer[tri_offset + 6];
323
+ const cy_v = triangle_buffer[tri_offset + 7];
324
+ const cz_v = triangle_buffer[tri_offset + 8];
325
+
326
+ // Face normal in body-local: (B − A) × (C − A), then normalise.
327
+ const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
328
+ const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
329
+ let fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
330
+ let fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
331
+ let fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
332
+ const fn_mag = Math.sqrt(fnx_l * fnx_l + fny_l * fny_l + fnz_l * fnz_l);
333
+ if (fn_mag < 1e-12) continue; // degenerate triangle
334
+ const fn_inv = 1 / fn_mag;
335
+ fnx_l *= fn_inv; fny_l *= fn_inv; fnz_l *= fn_inv;
336
+
337
+ // Rotate face normal to world via the concave body's quaternion.
338
+ v3_quat3_apply(scratch_rot, 0, fnx_l, fny_l, fnz_l, cqx, cqy, cqz, cqw);
339
+ const fnx_w = scratch_rot[0];
340
+ const fny_w = scratch_rot[1];
341
+ const fnz_w = scratch_rot[2];
342
+
343
+ // Centroid in body-local → world (one point on the triangle's plane).
344
+ const cent_lx = (ax + bx + cx_v) / 3;
345
+ const cent_ly = (ay + by + cy_v) / 3;
346
+ const cent_lz = (az + bz + cz_v) / 3;
347
+ v3_quat3_apply(scratch_rot, 0, cent_lx, cent_ly, cent_lz, cqx, cqy, cqz, cqw);
348
+ const cent_wx = scratch_rot[0] + c_pos_x;
349
+ const cent_wy = scratch_rot[1] + c_pos_y;
350
+ const cent_wz = scratch_rot[2] + c_pos_z;
351
+
352
+ // Deepest inward point of convex along -face_normal.
353
+ posed_b.support(scratch_v3, 0, -fnx_w, -fny_w, -fnz_w);
354
+
355
+ // Signed distance from that point to the triangle plane along
356
+ // the face normal: > 0 ⇒ convex is fully on outward side,
357
+ // ≤ 0 ⇒ convex extends |dist| into the solid through this face.
358
+ const signed_dist =
359
+ (scratch_v3[0] - cent_wx) * fnx_w
360
+ + (scratch_v3[1] - cent_wy) * fny_w
361
+ + (scratch_v3[2] - cent_wz) * fnz_w;
362
+
363
+ if (signed_dist >= 0) continue;
364
+
365
+ const depth = -signed_dist;
366
+ if (depth < CONTACT_EPSILON) continue;
367
+
368
+ if (depth > best_depth) {
369
+ best_depth = depth;
370
+ best_fnx_w = fnx_w;
371
+ best_fny_w = fny_w;
372
+ best_fnz_w = fnz_w;
373
+ }
374
+ }
375
+
376
+ if (best_depth === 0) return 0;
377
+
378
+ // ── 6. Write out_direction in the user's "B → A" convention ────
379
+ //
380
+ // The face normal points OUTWARD from the concave's solid.
381
+ // - isConcaveA: original A = concave. "B → A" = convex → concave
382
+ // = INTO the solid = −face_normal.
383
+ // - isConcaveB: original A = convex. "B → A" = concave → convex
384
+ // = AWAY from the solid = +face_normal.
385
+ if (isConcaveA) {
386
+ out_direction[0] = -best_fnx_w;
387
+ out_direction[1] = -best_fny_w;
388
+ out_direction[2] = -best_fnz_w;
389
+ } else {
390
+ out_direction[0] = best_fnx_w;
391
+ out_direction[1] = best_fny_w;
392
+ out_direction[2] = best_fnz_w;
393
+ }
394
+
395
+ return best_depth;
396
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Transform a world-space AABB into a body's local frame.
3
+ *
4
+ * Given the body's world transform (pos, rot), computes
5
+ * p_local = rot⁻¹ · (p_world − pos)
6
+ * for each of the world AABB's 8 corners, then writes the tight bbox
7
+ * over those 8 transformed corners.
8
+ *
9
+ * Used by the concave-shape narrowphase path: a concave collider's
10
+ * triangle enumerator wants its query AABB in the concave shape's
11
+ * body-local frame, so we project the other collider's *world* AABB
12
+ * through the concave body's inverse pose to get that.
13
+ *
14
+ * 8-corner brute force rather than Arvo's element-wise-abs trick:
15
+ * - The world AABB we start from is *already* a loose envelope (it
16
+ * came from oriented-rotate of the other shape's local AABB), so
17
+ * applying Arvo a second time would compound the looseness. The
18
+ * corner-by-corner pass produces the tightest projected AABB we
19
+ * can derive from the world AABB without going back to the
20
+ * untransformed local AABB.
21
+ * - One-time-per-pair cost, not in the per-triangle hot loop.
22
+ *
23
+ * @param {Float64Array} out 6-float min/max in body-local frame
24
+ * @param {number} out_offset
25
+ * @param {Float64Array|number[]} world_aabb 6-float min/max in world frame
26
+ * @param {number} pos_x body world position
27
+ * @param {number} pos_y
28
+ * @param {number} pos_z
29
+ * @param {number} rot_x body world rotation (unit quaternion)
30
+ * @param {number} rot_y
31
+ * @param {number} rot_z
32
+ * @param {number} rot_w
33
+ */
34
+ export function aabb_world_to_local(out: Float64Array, out_offset: number, world_aabb: Float64Array | number[], pos_x: number, pos_y: number, pos_z: number, rot_x: number, rot_y: number, rot_z: number, rot_w: number): void;
35
+ //# sourceMappingURL=aabb_world_to_local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aabb_world_to_local.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,yCAXW,YAAY,cACZ,MAAM,cACN,YAAY,GAAC,MAAM,EAAE,SACrB,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QAwChB"}
@@ -0,0 +1,80 @@
1
+ import { v3_quat3_apply_inverse } from "../../../../core/geom/vec3/v3_quat3_apply_inverse.js";
2
+
3
+ /**
4
+ * Scratch for the per-corner inverse-rotation output. 3 floats; allocation-free.
5
+ * @type {Float64Array}
6
+ */
7
+ const scratch_local = new Float64Array(3);
8
+
9
+ /**
10
+ * Transform a world-space AABB into a body's local frame.
11
+ *
12
+ * Given the body's world transform (pos, rot), computes
13
+ * p_local = rot⁻¹ · (p_world − pos)
14
+ * for each of the world AABB's 8 corners, then writes the tight bbox
15
+ * over those 8 transformed corners.
16
+ *
17
+ * Used by the concave-shape narrowphase path: a concave collider's
18
+ * triangle enumerator wants its query AABB in the concave shape's
19
+ * body-local frame, so we project the other collider's *world* AABB
20
+ * through the concave body's inverse pose to get that.
21
+ *
22
+ * 8-corner brute force rather than Arvo's element-wise-abs trick:
23
+ * - The world AABB we start from is *already* a loose envelope (it
24
+ * came from oriented-rotate of the other shape's local AABB), so
25
+ * applying Arvo a second time would compound the looseness. The
26
+ * corner-by-corner pass produces the tightest projected AABB we
27
+ * can derive from the world AABB without going back to the
28
+ * untransformed local AABB.
29
+ * - One-time-per-pair cost, not in the per-triangle hot loop.
30
+ *
31
+ * @param {Float64Array} out 6-float min/max in body-local frame
32
+ * @param {number} out_offset
33
+ * @param {Float64Array|number[]} world_aabb 6-float min/max in world frame
34
+ * @param {number} pos_x body world position
35
+ * @param {number} pos_y
36
+ * @param {number} pos_z
37
+ * @param {number} rot_x body world rotation (unit quaternion)
38
+ * @param {number} rot_y
39
+ * @param {number} rot_z
40
+ * @param {number} rot_w
41
+ */
42
+ export function aabb_world_to_local(
43
+ out, out_offset,
44
+ world_aabb,
45
+ pos_x, pos_y, pos_z,
46
+ rot_x, rot_y, rot_z, rot_w
47
+ ) {
48
+ let min_x = Infinity, min_y = Infinity, min_z = Infinity;
49
+ let max_x = -Infinity, max_y = -Infinity, max_z = -Infinity;
50
+
51
+ for (let corner = 0; corner < 8; corner++) {
52
+ const wx = (corner & 1) !== 0 ? world_aabb[3] : world_aabb[0];
53
+ const wy = (corner & 2) !== 0 ? world_aabb[4] : world_aabb[1];
54
+ const wz = (corner & 4) !== 0 ? world_aabb[5] : world_aabb[2];
55
+
56
+ // Translate to body-centered, then inverse-rotate into local frame.
57
+ v3_quat3_apply_inverse(
58
+ scratch_local, 0,
59
+ wx - pos_x, wy - pos_y, wz - pos_z,
60
+ rot_x, rot_y, rot_z, rot_w
61
+ );
62
+ const lx = scratch_local[0];
63
+ const ly = scratch_local[1];
64
+ const lz = scratch_local[2];
65
+
66
+ if (lx < min_x) min_x = lx;
67
+ if (ly < min_y) min_y = ly;
68
+ if (lz < min_z) min_z = lz;
69
+ if (lx > max_x) max_x = lx;
70
+ if (ly > max_y) max_y = ly;
71
+ if (lz > max_z) max_z = lz;
72
+ }
73
+
74
+ out[out_offset ] = min_x;
75
+ out[out_offset + 1] = min_y;
76
+ out[out_offset + 2] = min_z;
77
+ out[out_offset + 3] = max_x;
78
+ out[out_offset + 4] = max_y;
79
+ out[out_offset + 5] = max_z;
80
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Dispatch a non-convex shape to its triangle enumerator. Writes
3
+ * triangles into `output` at float-index `offset`, with the layout
4
+ * specified by {@link TRIANGLE_FLOAT_STRIDE} in `triangle_buffer_layout`.
5
+ *
6
+ * Explicit per-shape dispatch (no virtual `enumerate_triangles` method
7
+ * on AbstractShape3D) — adding support for a new concave shape kind is
8
+ * one line here plus a new enumerator file. The narrowphase calls this
9
+ * once per concave collider per contact pair; if performance ever
10
+ * becomes a worry, the entire function can be inlined into the
11
+ * call site without touching the enumerators.
12
+ *
13
+ * Throws if `shape` has no registered enumerator. The narrowphase
14
+ * gates this behind `shape.is_convex === false`, and every shape with
15
+ * `is_convex === false` is required to have an enumerator — there's
16
+ * no silent fallback to "empty triangle set" because that would
17
+ * masquerade the bug as a missed collision.
18
+ *
19
+ * @param {Float64Array} output
20
+ * @param {number} offset float-index into output
21
+ * @param {AbstractShape3D} shape (must have `is_convex === false`)
22
+ * @param {number} aabb_min_x query AABB in shape's body-local frame
23
+ * @param {number} aabb_min_y
24
+ * @param {number} aabb_min_z
25
+ * @param {number} aabb_max_x
26
+ * @param {number} aabb_max_y
27
+ * @param {number} aabb_max_z
28
+ * @returns {number} number of triangles written
29
+ */
30
+ export function decompose_to_triangles(output: Float64Array, offset: number, shape: AbstractShape3D, aabb_min_x: number, aabb_min_y: number, aabb_min_z: number, aabb_max_x: number, aabb_max_y: number, aabb_max_z: number): number;
31
+ //# sourceMappingURL=decompose_to_triangles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decompose_to_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,+CAXW,YAAY,UACZ,MAAM,sCAEN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,GACJ,MAAM,CAwBlB"}
@@ -0,0 +1,55 @@
1
+ import { heightmap_enumerate_triangles } from "./heightmap_enumerate_triangles.js";
2
+ import { mesh_enumerate_triangles } from "./mesh_enumerate_triangles.js";
3
+
4
+ /**
5
+ * Dispatch a non-convex shape to its triangle enumerator. Writes
6
+ * triangles into `output` at float-index `offset`, with the layout
7
+ * specified by {@link TRIANGLE_FLOAT_STRIDE} in `triangle_buffer_layout`.
8
+ *
9
+ * Explicit per-shape dispatch (no virtual `enumerate_triangles` method
10
+ * on AbstractShape3D) — adding support for a new concave shape kind is
11
+ * one line here plus a new enumerator file. The narrowphase calls this
12
+ * once per concave collider per contact pair; if performance ever
13
+ * becomes a worry, the entire function can be inlined into the
14
+ * call site without touching the enumerators.
15
+ *
16
+ * Throws if `shape` has no registered enumerator. The narrowphase
17
+ * gates this behind `shape.is_convex === false`, and every shape with
18
+ * `is_convex === false` is required to have an enumerator — there's
19
+ * no silent fallback to "empty triangle set" because that would
20
+ * masquerade the bug as a missed collision.
21
+ *
22
+ * @param {Float64Array} output
23
+ * @param {number} offset float-index into output
24
+ * @param {AbstractShape3D} shape (must have `is_convex === false`)
25
+ * @param {number} aabb_min_x query AABB in shape's body-local frame
26
+ * @param {number} aabb_min_y
27
+ * @param {number} aabb_min_z
28
+ * @param {number} aabb_max_x
29
+ * @param {number} aabb_max_y
30
+ * @param {number} aabb_max_z
31
+ * @returns {number} number of triangles written
32
+ */
33
+ export function decompose_to_triangles(
34
+ output, offset, shape,
35
+ aabb_min_x, aabb_min_y, aabb_min_z,
36
+ aabb_max_x, aabb_max_y, aabb_max_z
37
+ ) {
38
+ if (shape.isHeightMapShape3D === true) {
39
+ return heightmap_enumerate_triangles(
40
+ output, offset, shape,
41
+ aabb_min_x, aabb_min_y, aabb_min_z,
42
+ aabb_max_x, aabb_max_y, aabb_max_z
43
+ );
44
+ }
45
+
46
+ if (shape.isMeshShape3D === true) {
47
+ return mesh_enumerate_triangles(
48
+ output, offset, shape,
49
+ aabb_min_x, aabb_min_y, aabb_min_z,
50
+ aabb_max_x, aabb_max_y, aabb_max_z
51
+ );
52
+ }
53
+
54
+ throw new Error(`decompose_to_triangles: no enumerator registered for shape kind \`${shape.constructor.name}\``);
55
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Write the heightmap's triangles that may overlap the given body-local
3
+ * query AABB into `output`, starting at float-index `offset`.
4
+ *
5
+ * Each triangle occupies {@link TRIANGLE_FLOAT_STRIDE} consecutive floats:
6
+ * [+0..+2] vA.xyz (body-local frame)
7
+ * [+3..+5] vB.xyz
8
+ * [+6..+8] vC.xyz
9
+ * [+9] feature_id (stable across frames; warm-start key)
10
+ *
11
+ * The feature_id encodes `(cell_y * (W - 1) + cell_x) * 2 + tri_idx`
12
+ * where (cell_x, cell_y) is the grid cell and tri_idx ∈ {0, 1}. Two
13
+ * triangles per cell, lower-left-diagonal split (matches the existing
14
+ * {@link build_height_field_geometry} winding).
15
+ *
16
+ * The query AABB is filtered against the footprint *and* against the
17
+ * cell grid; triangles outside the query AABB along the height axis are
18
+ * NOT filtered out — that test is left to the per-triangle narrowphase
19
+ * (where the precise GJK distance is computed anyway). This keeps the
20
+ * enumerator branch-free over the height field and aligned with what
21
+ * Bullet's btHeightfieldTerrainShape does.
22
+ *
23
+ * The caller is responsible for ensuring
24
+ * `output.length - offset >= cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
25
+ * — sizing the scratch buffer for the *worst-case* query AABB is the
26
+ * right pattern (the broadphase already bounded the AABB to one body's
27
+ * world envelope, so the cell count is bounded by the footprint
28
+ * resolution).
29
+ *
30
+ * @param {Float64Array} output
31
+ * @param {number} offset float-index into output
32
+ * @param {HeightMapShape3D} shape
33
+ * @param {number} aabb_min_x query AABB in shape's body-local frame
34
+ * @param {number} aabb_min_y
35
+ * @param {number} aabb_min_z
36
+ * @param {number} aabb_max_x
37
+ * @param {number} aabb_max_y
38
+ * @param {number} aabb_max_z
39
+ * @returns {number} number of triangles written
40
+ */
41
+ export function heightmap_enumerate_triangles(output: Float64Array, offset: number, shape: HeightMapShape3D, aabb_min_x: number, aabb_min_y: number, aabb_min_z: number, aabb_max_x: number, aabb_max_y: number, aabb_max_z: number): number;
42
+ //# sourceMappingURL=heightmap_enumerate_triangles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heightmap_enumerate_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,sDAXW,YAAY,UACZ,MAAM,uCAEN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,GACJ,MAAM,CAmKlB"}