@woosh/meep-engine 2.147.0 → 2.149.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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts +23 -0
  3. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts.map +1 -0
  4. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js +295 -0
  5. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
  6. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
  7. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
  8. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
  9. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
  10. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
  11. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
  12. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  13. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
  14. package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -2
  15. package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
  16. package/src/engine/navigation/mesh/NavigationMesh.js +234 -212
  17. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +7 -3
  18. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
  19. package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +67 -73
  20. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +16 -5
  21. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +1 -1
  22. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +262 -147
  23. package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
  24. package/src/engine/navigation/mesh/build/navmesh_build_topology.js +33 -3
  25. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +4 -1
  26. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -1
  27. package/src/engine/navigation/mesh/bvh_query_nearest_face.js +164 -131
  28. package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
  29. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
  30. package/src/engine/physics/body/SolverBodyState.js +251 -0
  31. package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
  32. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  33. package/src/engine/physics/broadphase/generate_pairs.js +110 -108
  34. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  35. package/src/engine/physics/constraint/solve_constraints.js +691 -673
  36. package/src/engine/physics/ecs/PhysicsSystem.d.ts +21 -18
  37. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  38. package/src/engine/physics/ecs/PhysicsSystem.js +223 -91
  39. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
  40. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  41. package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
  42. package/src/engine/physics/integration/integrate_position.d.ts +11 -1
  43. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  44. package/src/engine/physics/integration/integrate_position.js +97 -79
  45. package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
  46. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  47. package/src/engine/physics/integration/integrate_velocity.js +201 -160
  48. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  49. package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
  50. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  51. package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -34
  52. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
  53. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
  54. package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
  55. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  56. package/src/engine/physics/narrowphase/narrowphase_step.js +24 -3
  57. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  58. package/src/engine/physics/queries/raycast.js +201 -198
  59. package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
  60. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  61. package/src/engine/physics/solver/solve_contacts.js +1341 -1173
@@ -1 +1 @@
1
- {"version":3,"file":"box_triangle_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/box_triangle_contact.js"],"names":[],"mappings":"AAuNA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,0CAtBW,MAAM,EAAE,GAAC,YAAY,OACrB,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,OAAO,CAuJnB;AAxUD;;;GAGG;AACH,sCAFU,MAAM,CAEyD"}
1
+ {"version":3,"file":"box_triangle_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/box_triangle_contact.js"],"names":[],"mappings":"AAyLA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,0CAtBW,MAAM,EAAE,GAAC,YAAY,OACrB,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,OAAO,CAuJnB;AAvSD;;;GAGG;AACH,sCAFU,MAAM,CAEyD"}
@@ -1,5 +1,8 @@
1
- import { line3_closest_points_segment_segment } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
1
+ import {
2
+ line3_closest_points_segment_segment
3
+ } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
2
4
  import { quat3_to_matrix3 } from "../../../core/geom/3d/quaternion/quat3_to_matrix3.js";
5
+ import { clip_against_axis_uv } from "./clip_against_axis_uv.js";
3
6
 
4
7
  /**
5
8
  * Multi-point manifold construction for an oriented box vs. a triangle.
@@ -97,39 +100,6 @@ function projected_box_half_extent(axes, hx, hy, hz, ux, uy, uz) {
97
100
  + (pz < 0 ? -pz : pz) * hz;
98
101
  }
99
102
 
100
- /**
101
- * Sutherland-Hodgman clip of `points_in` against the axis-aligned
102
- * half-plane `coord_idx ≤ bound` (or `coord_idx ≥ bound` when
103
- * `keep_below` is false). 2D stride 2; result to `points_out`.
104
- * Returns surviving vertex count.
105
- */
106
- function clip_against_axis_uv(points_in, point_count, points_out, coord_idx, bound, keep_below) {
107
- let out_count = 0;
108
- for (let i = 0; i < point_count; i++) {
109
- const j = (i + 1) % point_count;
110
- const ax = points_in[i * 2], ay = points_in[i * 2 + 1];
111
- const bx = points_in[j * 2], by = points_in[j * 2 + 1];
112
- const av = coord_idx === 0 ? ax : ay;
113
- const bv = coord_idx === 0 ? bx : by;
114
- const a_inside = keep_below ? (av <= bound) : (av >= bound);
115
- const b_inside = keep_below ? (bv <= bound) : (bv >= bound);
116
-
117
- if (a_inside) {
118
- points_out[out_count * 2] = ax;
119
- points_out[out_count * 2 + 1] = ay;
120
- out_count++;
121
- }
122
- if (a_inside !== b_inside) {
123
- const denom = bv - av;
124
- const t = denom !== 0 ? (bound - av) / denom : 0;
125
- points_out[out_count * 2] = ax + (bx - ax) * t;
126
- points_out[out_count * 2 + 1] = ay + (by - ay) * t;
127
- out_count++;
128
- }
129
- }
130
- return out_count;
131
- }
132
-
133
103
  /**
134
104
  * Sutherland-Hodgman clip of `points_in` against the general half-plane
135
105
  * `(p - (line_ox, line_oy)) · (line_nx, line_ny) ≤ 0`. 2D stride 2;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Sutherland-Hodgman clip of `points_in` against the axis-aligned
3
+ * half-plane `coord_idx ≤ bound` (or `coord_idx ≥ bound` when
4
+ * `keep_below` is false). 2D stride 2; result to `points_out`.
5
+ * Returns surviving vertex count.
6
+ *
7
+ * @param {Float64Array} points_in
8
+ * @param {number} point_count
9
+ * @param {Float64Array} points_out
10
+ * @param {number} coord_idx 0 = u, 1 = v
11
+ * @param {number} bound
12
+ * @param {boolean} keep_below true to keep points with coord ≤ bound
13
+ * @returns {number} surviving vertex count
14
+ */
15
+ export function clip_against_axis_uv(points_in: Float64Array, point_count: number, points_out: Float64Array, coord_idx: number, bound: number, keep_below: boolean): number;
16
+ //# sourceMappingURL=clip_against_axis_uv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip_against_axis_uv.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/clip_against_axis_uv.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,gDARW,YAAY,eACZ,MAAM,cACN,YAAY,aACZ,MAAM,SACN,MAAM,cACN,OAAO,GACL,MAAM,CAoClB"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Sutherland-Hodgman clip of `points_in` against the axis-aligned
3
+ * half-plane `coord_idx ≤ bound` (or `coord_idx ≥ bound` when
4
+ * `keep_below` is false). 2D stride 2; result to `points_out`.
5
+ * Returns surviving vertex count.
6
+ *
7
+ * @param {Float64Array} points_in
8
+ * @param {number} point_count
9
+ * @param {Float64Array} points_out
10
+ * @param {number} coord_idx 0 = u, 1 = v
11
+ * @param {number} bound
12
+ * @param {boolean} keep_below true to keep points with coord ≤ bound
13
+ * @returns {number} surviving vertex count
14
+ */
15
+ export function clip_against_axis_uv(points_in, point_count, points_out, coord_idx, bound, keep_below) {
16
+ let out_count = 0;
17
+
18
+ for (let i = 0; i < point_count; i++) {
19
+ const j = (i + 1) % point_count;
20
+
21
+ const ax = points_in[i * 2], ay = points_in[i * 2 + 1];
22
+ const bx = points_in[j * 2], by = points_in[j * 2 + 1];
23
+
24
+ const av = coord_idx === 0 ? ax : ay;
25
+ const bv = coord_idx === 0 ? bx : by;
26
+
27
+ const a_inside = keep_below ? (av <= bound) : (av >= bound);
28
+ const b_inside = keep_below ? (bv <= bound) : (bv >= bound);
29
+
30
+ if (a_inside) {
31
+ points_out[out_count * 2] = ax;
32
+ points_out[out_count * 2 + 1] = ay;
33
+ out_count++;
34
+ }
35
+
36
+ if (a_inside !== b_inside) {
37
+
38
+ const denom = bv - av;
39
+ const t = denom !== 0 ? (bound - av) / denom : 0;
40
+
41
+ points_out[out_count * 2] = ax + (bx - ax) * t;
42
+ points_out[out_count * 2 + 1] = ay + (by - ay) * t;
43
+
44
+ out_count++;
45
+ }
46
+ }
47
+
48
+ return out_count;
49
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AA2zCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qDAVW,YAAY,GAAC,MAAM,EAAE,iCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CAyClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA0JlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAkH3D"}
1
+ {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AA8zCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qDAVW,YAAY,GAAC,MAAM,EAAE,iCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CAyClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA4KlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAkH3D"}
@@ -403,9 +403,12 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
403
403
  // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
404
404
  // material fields — it never writes to a manifold, so 0 is fine there.
405
405
  const fa = colA.friction, fb = colB.friction;
406
+
406
407
  if (fa !== undefined && fb !== undefined) {
408
+
407
409
  g_pair_friction = combine_friction(fa, fb);
408
410
  g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
411
+
409
412
  } else {
410
413
  g_pair_friction = 0;
411
414
  g_pair_restitution = 0;
@@ -1429,22 +1432,29 @@ export function narrowphase_step(pair_list, manifolds, lists) {
1429
1432
  for (let i = 0; i < count; i++) {
1430
1433
  const idA = pair_list.get_a(i);
1431
1434
  const idB = pair_list.get_b(i);
1435
+
1432
1436
  const idxA = body_id_index(idA);
1433
1437
  const idxB = body_id_index(idB);
1434
1438
 
1435
1439
  const list_a = lists[idxA];
1436
1440
  const list_b = lists[idxB];
1441
+
1437
1442
  const slot = manifolds.find(idA, idB);
1438
1443
 
1439
1444
  if (list_a === undefined || list_b === undefined
1440
- || list_a.length === 0 || list_b.length === 0) {
1445
+ || list_a.length === 0 || list_b.length === 0
1446
+ ) {
1447
+
1441
1448
  manifolds.clear_contacts(slot);
1442
1449
  continue;
1450
+
1443
1451
  }
1444
1452
 
1445
1453
  let cand_count = 0;
1454
+
1446
1455
  const la_len = list_a.length;
1447
1456
  const lb_len = list_b.length;
1457
+
1448
1458
  // Per-manifold cached GJK separating-axis seed. Threaded into
1449
1459
  // dispatch_pair for the GJK fallback paths. For single-collider
1450
1460
  // bodies (the common case) there's no contention across the
@@ -1453,12 +1463,23 @@ export function narrowphase_step(pair_list, manifolds, lists) {
1453
1463
  // seed (better than a cold (1, 0, 0) restart every frame).
1454
1464
  const gjk_axis_buf = manifolds.slot_axis_buffer;
1455
1465
  const gjk_axis_off = manifolds.slot_axis_offset(slot);
1466
+
1456
1467
  for (let a = 0; a < la_len; a++) {
1457
1468
  const ea = list_a[a];
1469
+
1458
1470
  for (let b = 0; b < lb_len; b++) {
1459
1471
  const eb = list_b[b];
1460
- cand_count = dispatch_pair(cand_count, ea.collider, ea.transform, eb.collider, eb.transform,
1461
- gjk_axis_buf, gjk_axis_off);
1472
+
1473
+ cand_count = dispatch_pair(
1474
+ cand_count,
1475
+ ea.collider,
1476
+ ea.transform,
1477
+ eb.collider,
1478
+ eb.transform,
1479
+ gjk_axis_buf,
1480
+ gjk_axis_off
1481
+ );
1482
+
1462
1483
  }
1463
1484
  }
1464
1485
 
@@ -1 +1 @@
1
- {"version":3,"file":"raycast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/raycast.js"],"names":[],"mappings":"AA2IA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,yGAJmB,MAAM,yBAAsB,OAAO,GAEzC,OAAO,CAwCnB"}
1
+ {"version":3,"file":"raycast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/raycast.js"],"names":[],"mappings":"AA8IA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,yGAJmB,MAAM,yBAAsB,OAAO,GAEzC,OAAO,CAwCnB"}
@@ -1,198 +1,201 @@
1
- import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
2
- import {
3
- COLUMN_CHILD_1,
4
- COLUMN_CHILD_2,
5
- COLUMN_USER_DATA,
6
- ELEMENT_WORD_COUNT,
7
- NULL_NODE,
8
- } from "../../../core/bvh2/bvh3/BVH.js";
9
- import { returnTrue } from "../../../core/function/returnTrue.js";
10
- import { aabb3_near_distance_to_intersection_ray_segment } from "../../../core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js";
11
- import { refine_ray_hit, RAY_REFINE_UNSUPPORTED } from "../narrowphase/refine_ray_hit.js";
12
-
13
- const stack = SCRATCH_UINT32_TRAVERSAL_STACK;
14
-
15
- /**
16
- * Reusable nearest-hit accumulator + scratch. Module-scoped so {@link raycast}
17
- * doesn't allocate per call; the physics step is single-threaded so contention
18
- * isn't a concern.
19
- * @type {{best_t:number, best_body:number}}
20
- */
21
- const acc = { best_t: Infinity, best_body: 0 };
22
- const best_normal = new Float64Array(3); // winning hit's world normal
23
- const cand_normal = new Float64Array(3); // per-leaf candidate normal
24
-
25
- /**
26
- * Outward AABB-face normal at a hit point — the fallback normal for a leaf
27
- * whose shape has no exact ray test (composite convex). The hit point is
28
- * projected into the AABB's normalised local space (centre = 0, faces = ±1);
29
- * the dominant component's axis is the entry face and its sign the outward
30
- * normal.
31
- */
32
- function aabb_face_normal(out, min_x, min_y, min_z, max_x, max_y, max_z, hx, hy, hz) {
33
- const cx = (min_x + max_x) * 0.5;
34
- const cy = (min_y + max_y) * 0.5;
35
- const cz = (min_z + max_z) * 0.5;
36
- const px = (hx - cx) * 2 / (max_x - min_x);
37
- const py = (hy - cy) * 2 / (max_y - min_y);
38
- const pz = (hz - cz) * 2 / (max_z - min_z);
39
- const apx = px < 0 ? -px : px;
40
- const apy = py < 0 ? -py : py;
41
- const apz = pz < 0 ? -pz : pz;
42
- if (apx >= apy && apx >= apz) { out[0] = px >= 0 ? 1 : -1; out[1] = 0; out[2] = 0; }
43
- else if (apy >= apz) { out[0] = 0; out[1] = py >= 0 ? 1 : -1; out[2] = 0; }
44
- else { out[0] = 0; out[1] = 0; out[2] = pz >= 0 ? 1 : -1; }
45
- }
46
-
47
- /**
48
- * Walk a single BVH along a ray, refining each crossing leaf against its true
49
- * shape geometry and keeping the nearest confirmed hit.
50
- *
51
- * Pruning stays on the *inflated leaf AABB* entry distance `t_near`: a shape
52
- * hit is always at or beyond its tight AABB entry, which is at or beyond the
53
- * inflated-AABB entry, so a subtree whose AABB entry is past the current best
54
- * cannot contain a closer hit. A leaf whose ray crosses the fat AABB but misses
55
- * the true shape contributes nothing the correctness gain over broadphase.
56
- *
57
- * @param {BVH} bvh @param {number} root
58
- * @param {PhysicsSystem} system
59
- * @param {number} ox @param {number} oy @param {number} oz ray origin
60
- * @param {number} dx @param {number} dy @param {number} dz ray dir (unit)
61
- * @param {number} inv_dx @param {number} inv_dy @param {number} inv_dz
62
- * @param {number} max_distance
63
- * @param {(entity:number, collider:Collider)=>boolean} filter
64
- */
65
- function bvh_raycast_nearest(
66
- bvh, root, system,
67
- ox, oy, oz, dx, dy, dz,
68
- inv_dx, inv_dy, inv_dz,
69
- max_distance, filter
70
- ) {
71
- if (root === NULL_NODE) return;
72
-
73
- const float32 = bvh.__data_float32;
74
- const uint32 = bvh.__data_uint32;
75
-
76
- let pointer = stack.pointer;
77
- const stack_top = pointer;
78
- stack[pointer++] = root;
79
-
80
- while (pointer > stack_top) {
81
- pointer--;
82
- const node = stack[pointer];
83
- const address = node * ELEMENT_WORD_COUNT;
84
-
85
- const t_near = aabb3_near_distance_to_intersection_ray_segment(
86
- float32[address], float32[address + 1], float32[address + 2],
87
- float32[address + 3], float32[address + 4], float32[address + 5],
88
- ox, oy, oz,
89
- inv_dx, inv_dy, inv_dz,
90
- 0, max_distance
91
- );
92
-
93
- // No intersection or this subtree can't beat the best known hit — prune.
94
- if (t_near >= acc.best_t) continue;
95
-
96
- const child_1 = uint32[address + COLUMN_CHILD_1];
97
- if (child_1 !== NULL_NODE) {
98
- stack[pointer++] = uint32[address + COLUMN_CHILD_2];
99
- stack[pointer++] = child_1;
100
- continue;
101
- }
102
-
103
- // Leaf — refine against the true shape.
104
- const body_id = uint32[address + COLUMN_USER_DATA];
105
- const entity = system.entityOf(body_id);
106
- if (entity < 0) continue; // unlinked concurrently
107
- const idx = system.__index_of(body_id);
108
- const collider = system.__primary_collider(idx);
109
- if (collider === null) continue;
110
- if (!filter(entity, collider)) continue;
111
-
112
- const tr = system.__transforms[idx];
113
- const refined = refine_ray_hit(
114
- collider.shape, tr.position, tr.rotation,
115
- ox, oy, oz, dx, dy, dz, acc.best_t, cand_normal
116
- );
117
-
118
- if (refined === RAY_REFINE_UNSUPPORTED) {
119
- // No exact ray test for this shape — keep the broadphase AABB hit.
120
- if (t_near < acc.best_t) {
121
- acc.best_t = t_near;
122
- acc.best_body = body_id;
123
- aabb_face_normal(best_normal,
124
- float32[address], float32[address + 1], float32[address + 2],
125
- float32[address + 3], float32[address + 4], float32[address + 5],
126
- ox + dx * t_near, oy + dy * t_near, oz + dz * t_near);
127
- }
128
- } else if (refined < acc.best_t) { // a refined miss is Infinity → never wins
129
- acc.best_t = refined;
130
- acc.best_body = body_id;
131
- best_normal[0] = cand_normal[0];
132
- best_normal[1] = cand_normal[1];
133
- best_normal[2] = cand_normal[2];
134
- }
135
- }
136
-
137
- stack.pointer = stack_top;
138
- }
139
-
140
- /**
141
- * Raycast against both broadphase trees (static + dynamic) of a
142
- * {@link PhysicsSystem}, refined against each candidate's true shape geometry.
143
- * Fills `result` with the nearest hit and returns `true` on hit, `false` on
144
- * miss. `result.t` is the exact surface distance and `result.normal` the true
145
- * surface normal for sphere / box / capsule / mesh / heightmap colliders;
146
- * composite convex shapes (no exact ray test yet) fall back to the broadphase
147
- * AABB hit + AABB-face normal.
148
- *
149
- * Multi-collider bodies resolve their primary (first-attached) collider the
150
- * BVH leaf encodes only `body_id`; per-collider rays need the leaf user-data to
151
- * carry the collider index (future work).
152
- *
153
- * @param {PhysicsSystem} system
154
- * @param {Ray3} ray origin + unit direction + `tMax`
155
- * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
156
- * @param {(entity:number, collider:Collider)=>boolean} [filter] called once per
157
- * crossing leaf; defaults to {@link returnTrue}.
158
- * @returns {boolean} true on hit, false on miss
159
- */
160
- export function raycast(system, ray, result, filter = returnTrue) {
161
- const ox = ray.origin_x, oy = ray.origin_y, oz = ray.origin_z;
162
- const dx = ray.direction_x, dy = ray.direction_y, dz = ray.direction_z;
163
- const max_distance = ray.tMax;
164
-
165
- acc.best_t = max_distance;
166
- acc.best_body = 0;
167
- best_normal[0] = 0; best_normal[1] = 0; best_normal[2] = 0;
168
-
169
- const inv_dx = 1 / dx, inv_dy = 1 / dy, inv_dz = 1 / dz;
170
-
171
- bvh_raycast_nearest(
172
- system.staticBvh, system.staticBvh.root, system,
173
- ox, oy, oz, dx, dy, dz, inv_dx, inv_dy, inv_dz, max_distance, filter
174
- );
175
- bvh_raycast_nearest(
176
- system.dynamicBvh, system.dynamicBvh.root, system,
177
- ox, oy, oz, dx, dy, dz, inv_dx, inv_dy, inv_dz, max_distance, filter
178
- );
179
-
180
- // Any hit updates best_t to strictly below max_distance (both the refined
181
- // and AABB-fallback paths require t < current best); a miss leaves it at
182
- // max_distance. This is body_id-agnostic — the first body packs to id 0.
183
- if (acc.best_t >= max_distance) return false;
184
-
185
- const entity = system.entityOf(acc.best_body);
186
- if (entity < 0) return false; // body unlinked concurrently → treat as miss
187
-
188
- const t = acc.best_t;
189
- const rp = result.position;
190
- const rn = result.normal;
191
- rp[0] = ox + dx * t; rp[1] = oy + dy * t; rp[2] = oz + dz * t;
192
- rn[0] = best_normal[0]; rn[1] = best_normal[1]; rn[2] = best_normal[2];
193
- result.t = t;
194
- result.entity = entity;
195
- result.body_id = acc.best_body;
196
-
197
- return true;
198
- }
1
+ import {
2
+ COLUMN_CHILD_1,
3
+ COLUMN_CHILD_2,
4
+ COLUMN_USER_DATA,
5
+ ELEMENT_WORD_COUNT,
6
+ NULL_NODE,
7
+ } from "../../../core/bvh2/bvh3/BVH.js";
8
+ import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
9
+ import { returnTrue } from "../../../core/function/returnTrue.js";
10
+ import {
11
+ aabb3_near_distance_to_intersection_ray_segment
12
+ } from "../../../core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js";
13
+ import { body_id_index } from "../body/BodyStorage.js";
14
+ import { RAY_REFINE_UNSUPPORTED, refine_ray_hit } from "../narrowphase/refine_ray_hit.js";
15
+
16
+ const stack = SCRATCH_UINT32_TRAVERSAL_STACK;
17
+
18
+ /**
19
+ * Reusable nearest-hit accumulator + scratch. Module-scoped so {@link raycast}
20
+ * doesn't allocate per call; the physics step is single-threaded so contention
21
+ * isn't a concern.
22
+ * @type {{best_t:number, best_body:number}}
23
+ */
24
+ const acc = { best_t: Infinity, best_body: 0 };
25
+ const best_normal = new Float64Array(3); // winning hit's world normal
26
+ const cand_normal = new Float64Array(3); // per-leaf candidate normal
27
+
28
+ /**
29
+ * Outward AABB-face normal at a hit point the fallback normal for a leaf
30
+ * whose shape has no exact ray test (composite convex). The hit point is
31
+ * projected into the AABB's normalised local space (centre = 0, faces = ±1);
32
+ * the dominant component's axis is the entry face and its sign the outward
33
+ * normal.
34
+ */
35
+ function aabb_face_normal(out, min_x, min_y, min_z, max_x, max_y, max_z, hx, hy, hz) {
36
+ const cx = (min_x + max_x) * 0.5;
37
+ const cy = (min_y + max_y) * 0.5;
38
+ const cz = (min_z + max_z) * 0.5;
39
+ const px = (hx - cx) * 2 / (max_x - min_x);
40
+ const py = (hy - cy) * 2 / (max_y - min_y);
41
+ const pz = (hz - cz) * 2 / (max_z - min_z);
42
+ const apx = px < 0 ? -px : px;
43
+ const apy = py < 0 ? -py : py;
44
+ const apz = pz < 0 ? -pz : pz;
45
+ if (apx >= apy && apx >= apz) { out[0] = px >= 0 ? 1 : -1; out[1] = 0; out[2] = 0; }
46
+ else if (apy >= apz) { out[0] = 0; out[1] = py >= 0 ? 1 : -1; out[2] = 0; }
47
+ else { out[0] = 0; out[1] = 0; out[2] = pz >= 0 ? 1 : -1; }
48
+ }
49
+
50
+ /**
51
+ * Walk a single BVH along a ray, refining each crossing leaf against its true
52
+ * shape geometry and keeping the nearest confirmed hit.
53
+ *
54
+ * Pruning stays on the *inflated leaf AABB* entry distance `t_near`: a shape
55
+ * hit is always at or beyond its tight AABB entry, which is at or beyond the
56
+ * inflated-AABB entry, so a subtree whose AABB entry is past the current best
57
+ * cannot contain a closer hit. A leaf whose ray crosses the fat AABB but misses
58
+ * the true shape contributes nothing — the correctness gain over broadphase.
59
+ *
60
+ * @param {BVH} bvh @param {number} root
61
+ * @param {PhysicsSystem} system
62
+ * @param {number} ox @param {number} oy @param {number} oz ray origin
63
+ * @param {number} dx @param {number} dy @param {number} dz ray dir (unit)
64
+ * @param {number} inv_dx @param {number} inv_dy @param {number} inv_dz
65
+ * @param {number} max_distance
66
+ * @param {(entity:number, collider:Collider)=>boolean} filter
67
+ */
68
+ function bvh_raycast_nearest(
69
+ bvh, root, system,
70
+ ox, oy, oz, dx, dy, dz,
71
+ inv_dx, inv_dy, inv_dz,
72
+ max_distance, filter
73
+ ) {
74
+ if (root === NULL_NODE) return;
75
+
76
+ const float32 = bvh.__data_float32;
77
+ const uint32 = bvh.__data_uint32;
78
+
79
+ let pointer = stack.pointer;
80
+ const stack_top = pointer;
81
+ stack[pointer++] = root;
82
+
83
+ while (pointer > stack_top) {
84
+ pointer--;
85
+ const node = stack[pointer];
86
+ const address = node * ELEMENT_WORD_COUNT;
87
+
88
+ const t_near = aabb3_near_distance_to_intersection_ray_segment(
89
+ float32[address], float32[address + 1], float32[address + 2],
90
+ float32[address + 3], float32[address + 4], float32[address + 5],
91
+ ox, oy, oz,
92
+ inv_dx, inv_dy, inv_dz,
93
+ 0, max_distance
94
+ );
95
+
96
+ // No intersection or this subtree can't beat the best known hit — prune.
97
+ if (t_near >= acc.best_t) continue;
98
+
99
+ const child_1 = uint32[address + COLUMN_CHILD_1];
100
+ if (child_1 !== NULL_NODE) {
101
+ stack[pointer++] = uint32[address + COLUMN_CHILD_2];
102
+ stack[pointer++] = child_1;
103
+ continue;
104
+ }
105
+
106
+ // Leaf refine against the true shape.
107
+ const body_id = uint32[address + COLUMN_USER_DATA];
108
+ const entity = system.entityOf(body_id);
109
+ if (entity < 0) continue; // unlinked concurrently
110
+ const idx = body_id_index(body_id);
111
+ const collider = system.__primary_collider(idx);
112
+ if (collider === null) continue;
113
+ if (!filter(entity, collider)) continue;
114
+
115
+ const tr = system.__transforms[idx];
116
+ const refined = refine_ray_hit(
117
+ collider.shape, tr.position, tr.rotation,
118
+ ox, oy, oz, dx, dy, dz, acc.best_t, cand_normal
119
+ );
120
+
121
+ if (refined === RAY_REFINE_UNSUPPORTED) {
122
+ // No exact ray test for this shape — keep the broadphase AABB hit.
123
+ if (t_near < acc.best_t) {
124
+ acc.best_t = t_near;
125
+ acc.best_body = body_id;
126
+ aabb_face_normal(best_normal,
127
+ float32[address], float32[address + 1], float32[address + 2],
128
+ float32[address + 3], float32[address + 4], float32[address + 5],
129
+ ox + dx * t_near, oy + dy * t_near, oz + dz * t_near);
130
+ }
131
+ } else if (refined < acc.best_t) { // a refined miss is Infinity → never wins
132
+ acc.best_t = refined;
133
+ acc.best_body = body_id;
134
+ best_normal[0] = cand_normal[0];
135
+ best_normal[1] = cand_normal[1];
136
+ best_normal[2] = cand_normal[2];
137
+ }
138
+ }
139
+
140
+ stack.pointer = stack_top;
141
+ }
142
+
143
+ /**
144
+ * Raycast against both broadphase trees (static + dynamic) of a
145
+ * {@link PhysicsSystem}, refined against each candidate's true shape geometry.
146
+ * Fills `result` with the nearest hit and returns `true` on hit, `false` on
147
+ * miss. `result.t` is the exact surface distance and `result.normal` the true
148
+ * surface normal for sphere / box / capsule / mesh / heightmap colliders;
149
+ * composite convex shapes (no exact ray test yet) fall back to the broadphase
150
+ * AABB hit + AABB-face normal.
151
+ *
152
+ * Multi-collider bodies resolve their primary (first-attached) collider — the
153
+ * BVH leaf encodes only `body_id`; per-collider rays need the leaf user-data to
154
+ * carry the collider index (future work).
155
+ *
156
+ * @param {PhysicsSystem} system
157
+ * @param {Ray3} ray origin + unit direction + `tMax`
158
+ * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
159
+ * @param {(entity:number, collider:Collider)=>boolean} [filter] called once per
160
+ * crossing leaf; defaults to {@link returnTrue}.
161
+ * @returns {boolean} true on hit, false on miss
162
+ */
163
+ export function raycast(system, ray, result, filter = returnTrue) {
164
+ const ox = ray.origin_x, oy = ray.origin_y, oz = ray.origin_z;
165
+ const dx = ray.direction_x, dy = ray.direction_y, dz = ray.direction_z;
166
+ const max_distance = ray.tMax;
167
+
168
+ acc.best_t = max_distance;
169
+ acc.best_body = 0;
170
+ best_normal[0] = 0; best_normal[1] = 0; best_normal[2] = 0;
171
+
172
+ const inv_dx = 1 / dx, inv_dy = 1 / dy, inv_dz = 1 / dz;
173
+
174
+ bvh_raycast_nearest(
175
+ system.staticBvh, system.staticBvh.root, system,
176
+ ox, oy, oz, dx, dy, dz, inv_dx, inv_dy, inv_dz, max_distance, filter
177
+ );
178
+ bvh_raycast_nearest(
179
+ system.dynamicBvh, system.dynamicBvh.root, system,
180
+ ox, oy, oz, dx, dy, dz, inv_dx, inv_dy, inv_dz, max_distance, filter
181
+ );
182
+
183
+ // Any hit updates best_t to strictly below max_distance (both the refined
184
+ // and AABB-fallback paths require t < current best); a miss leaves it at
185
+ // max_distance. This is body_id-agnostic — the first body packs to id 0.
186
+ if (acc.best_t >= max_distance) return false;
187
+
188
+ const entity = system.entityOf(acc.best_body);
189
+ if (entity < 0) return false; // body unlinked concurrently → treat as miss
190
+
191
+ const t = acc.best_t;
192
+ const rp = result.position;
193
+ const rn = result.normal;
194
+ rp[0] = ox + dx * t; rp[1] = oy + dy * t; rp[2] = oz + dz * t;
195
+ rn[0] = best_normal[0]; rn[1] = best_normal[1]; rn[2] = best_normal[2];
196
+ result.t = t;
197
+ result.entity = entity;
198
+ result.body_id = acc.best_body;
199
+
200
+ return true;
201
+ }
@@ -48,9 +48,9 @@ export function warm_start_contacts(manifolds: ManifoldStore, system: PhysicsSys
48
48
  * for the small per-step rotation), so they are not recomputed here.
49
49
  *
50
50
  * @param {ManifoldStore} manifolds
51
- * @param {PhysicsSystem} system
51
+ * @param {Transform[]} transforms
52
52
  */
53
- export function refresh_contacts(manifolds: ManifoldStore, system: PhysicsSystem): void;
53
+ export function refresh_contacts(manifolds: ManifoldStore, transforms: Transform[]): void;
54
54
  /**
55
55
  * Stage 2b (per substep) — the concave counterpart of {@link refresh_contacts}.
56
56
  *