@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
@@ -2,19 +2,25 @@ import { assert } from "../../../core/assert.js";
2
2
  import { BinaryBuffer } from "../../../core/binary/BinaryBuffer.js";
3
3
  import { BVH } from "../../../core/bvh2/bvh3/BVH.js";
4
4
  import Signal from "../../../core/events/signal/Signal.js";
5
+ import { returnTrue } from "../../../core/function/returnTrue.js";
6
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
5
7
  import Vector3 from "../../../core/geom/Vector3.js";
6
8
  import { ResourceAccessKind } from "../../../core/model/ResourceAccessKind.js";
7
9
  import { ResourceAccessSpecification } from "../../../core/model/ResourceAccessSpecification.js";
8
10
  import { System } from "../../ecs/System.js";
9
11
  import { Transform } from "../../ecs/transform/Transform.js";
12
+ import { Interpolated } from "../../interpolation/Interpolated.js";
10
13
  import { body_id_index, BodyStorage } from "../body/BodyStorage.js";
11
- import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
14
+ import { SBS_STRIDE, SolverBodyState } from "../body/SolverBodyState.js";
12
15
  import { compute_fat_world_aabb } from "../broadphase/compute_fat_world_aabb.js";
13
16
  import { generate_pairs } from "../broadphase/generate_pairs.js";
14
17
  import { PairList } from "../broadphase/PairList.js";
15
- import { CONTACT_STRIDE, ManifoldStore } from "../contact/ManifoldStore.js";
18
+ import { ccd_resolve } from "../ccd/linear_sweep.js";
19
+ import { solve_joints } from "../constraint/solve_constraints.js";
20
+ import { ManifoldStore } from "../contact/ManifoldStore.js";
16
21
  import { ContactEventBuffer, ContactEventKind } from "../events/ContactEventBuffer.js";
17
22
  import { diff_manifolds } from "../events/diff_manifolds.js";
23
+ import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
18
24
  import { integrate_position } from "../integration/integrate_position.js";
19
25
  import { integrate_velocity_forces, integrate_velocity_gravity } from "../integration/integrate_velocity.js";
20
26
  import { IslandBuilder } from "../island/IslandBuilder.js";
@@ -22,33 +28,28 @@ import { narrowphase_step } from "../narrowphase/narrowphase_step.js";
22
28
  import { overlap_shape as overlap_shape_query } from "../queries/overlap_shape.js";
23
29
  import { raycast as raycast_query } from "../queries/raycast.js";
24
30
  import { shape_cast as shape_cast_query } from "../queries/shape_cast.js";
25
- import { ccd_resolve } from "../ccd/linear_sweep.js";
26
- import { returnTrue } from "../../../core/function/returnTrue.js";
27
31
  import {
32
+ apply_restitution,
28
33
  prepare_contacts,
29
- refresh_contacts,
30
34
  redetect_concave_contacts,
31
- warm_start_contacts,
32
- solve_velocity,
33
- apply_restitution,
35
+ refresh_contacts,
34
36
  solve_position,
37
+ solve_velocity,
38
+ warm_start_contacts,
35
39
  } from "../solver/solve_contacts.js";
36
- import { solve_joints } from "../constraint/solve_constraints.js";
37
- import { JOINT_WORLD, JOINT_UNALLOCATED } from "./Joint.js";
38
- import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
40
+ import { BodyKind } from "./BodyKind.js";
41
+ import { Collider, COLLIDER_UNBOUND } from "./Collider.js";
42
+ import { JOINT_UNALLOCATED, JOINT_WORLD } from "./Joint.js";
39
43
  import { PhysicsEvents } from "./PhysicsEvents.js";
44
+ import { RIGID_BODY_UNALLOCATED, RigidBody } from "./RigidBody.js";
45
+ import { RigidBodyFlags } from "./RigidBodyFlags.js";
46
+ import { SleepState } from "./SleepState.js";
40
47
 
41
48
  /**
42
49
  * Scratch for {@link applyImpulseAt}'s angular delta calculation.
43
50
  * @type {Float64Array}
44
51
  */
45
52
  const scratch_angular_delta = new Float64Array(3);
46
- import { BodyKind } from "./BodyKind.js";
47
- import { Collider, COLLIDER_UNBOUND } from "./Collider.js";
48
- import { RIGID_BODY_UNALLOCATED, RigidBody } from "./RigidBody.js";
49
- import { RigidBodyFlags } from "./RigidBodyFlags.js";
50
- import { SleepState } from "./SleepState.js";
51
- import { Interpolated } from "../../interpolation/Interpolated.js";
52
53
 
53
54
  /**
54
55
  * Reusable scratch buffer for world-AABB construction so the link path is
@@ -96,6 +97,7 @@ export class PhysicsSystem extends System {
96
97
 
97
98
  /**
98
99
  * @type {BodyStorage}
100
+ * @readonly
99
101
  */
100
102
  this.storage = new BodyStorage();
101
103
 
@@ -112,6 +114,7 @@ export class PhysicsSystem extends System {
112
114
  /**
113
115
  * Persistent contact-manifold cache. One slot per active pair.
114
116
  * @type {ManifoldStore}
117
+ * @readonly
115
118
  */
116
119
  this.manifolds = new ManifoldStore();
117
120
 
@@ -139,6 +142,18 @@ export class PhysicsSystem extends System {
139
142
  */
140
143
  this.islands = new IslandBuilder();
141
144
 
145
+ /**
146
+ * Data-oriented mirror of the per-body solver hot state (velocity,
147
+ * inverse mass / inertia, orientation), packed into one ArrayBuffer
148
+ * and indexed by body slot. Gathered from the `RigidBody` /
149
+ * `Transform` components once per step (after islands are built),
150
+ * mutated in place by the TGS substep loop, and the persistent
151
+ * velocity scattered back at the end. Keeps the solver's hottest inner
152
+ * loop free of component-object dereferences.
153
+ * @type {SolverBodyState}
154
+ */
155
+ this.__solver_state = new SolverBodyState();
156
+
142
157
  /**
143
158
  * Velocity-squared threshold below which a body is eligible to start
144
159
  * accumulating sleep time. Combined linear + angular kinetic-ish
@@ -240,7 +255,11 @@ export class PhysicsSystem extends System {
240
255
  * @type {RigidBody[]}
241
256
  */
242
257
  this.__bodies = [];
243
- /** @type {Transform[]} */
258
+
259
+ /**
260
+ * @type {Transform[]}
261
+ * @readonly
262
+ */
244
263
  this.__transforms = [];
245
264
 
246
265
  /**
@@ -314,13 +333,6 @@ export class PhysicsSystem extends System {
314
333
  */
315
334
  this.__ccd_start_pos = new Float64Array(0);
316
335
 
317
- /**
318
- * Bound reference to {@link __pair_filter} so we hand the same
319
- * callable to {@link generate_pairs} each step without per-step
320
- * allocation.
321
- * @private
322
- */
323
- this.__pair_filter_bound = (idA, idB) => this.__pair_filter(idA, idB);
324
336
 
325
337
  /**
326
338
  * Optional shared interpolation log to produce per-step pose snapshots
@@ -360,13 +372,21 @@ export class PhysicsSystem extends System {
360
372
  __pair_filter(idA, idB) {
361
373
  const idxA = body_id_index(idA);
362
374
  const idxB = body_id_index(idB);
375
+
363
376
  const rbA = this.__bodies[idxA];
364
377
  const rbB = this.__bodies[idxB];
365
- if (rbA === undefined || rbB === undefined) return false;
378
+
379
+ if (rbA === undefined || rbB === undefined) {
380
+ return false;
381
+ }
366
382
 
367
383
  // Layer/mask gate (symmetric).
368
- if (((rbA.layer & rbB.mask) | 0) === 0) return false;
369
- if (((rbB.layer & rbA.mask) | 0) === 0) return false;
384
+ if (((rbA.layer & rbB.mask) | 0) === 0) {
385
+ return false;
386
+ }
387
+ if (((rbB.layer & rbA.mask) | 0) === 0) {
388
+ return false;
389
+ }
370
390
 
371
391
  // User callback gate, if installed.
372
392
  const fn = this.__contact_filter;
@@ -834,7 +854,7 @@ export class PhysicsSystem extends System {
834
854
  if (rigidBody.kind !== BodyKind.Dynamic) {
835
855
  return;
836
856
  }
837
- rigidBody.accumulatedForce.add( force);
857
+ rigidBody.accumulatedForce.add(force);
838
858
  this.__wake_body(rigidBody);
839
859
  }
840
860
 
@@ -997,8 +1017,12 @@ export class PhysicsSystem extends System {
997
1017
  rb.sleep_group_next = -1;
998
1018
  rb.sleep_group_prev = -1;
999
1019
  rb.sleepState = SleepState.Sleeping;
1000
- rb.linearVelocity[0] = 0; rb.linearVelocity[1] = 0; rb.linearVelocity[2] = 0;
1001
- rb.angularVelocity[0] = 0; rb.angularVelocity[1] = 0; rb.angularVelocity[2] = 0;
1020
+ rb.linearVelocity[0] = 0;
1021
+ rb.linearVelocity[1] = 0;
1022
+ rb.linearVelocity[2] = 0;
1023
+ rb.angularVelocity[0] = 0;
1024
+ rb.angularVelocity[1] = 0;
1025
+ rb.angularVelocity[2] = 0;
1002
1026
  storage.mark_sleeping(idx);
1003
1027
  return;
1004
1028
  }
@@ -1012,22 +1036,16 @@ export class PhysicsSystem extends System {
1012
1036
  rb.sleep_group_next = next_idx;
1013
1037
  rb.sleep_group_prev = prev_idx;
1014
1038
  rb.sleepState = SleepState.Sleeping;
1015
- rb.linearVelocity[0] = 0; rb.linearVelocity[1] = 0; rb.linearVelocity[2] = 0;
1016
- rb.angularVelocity[0] = 0; rb.angularVelocity[1] = 0; rb.angularVelocity[2] = 0;
1039
+ rb.linearVelocity[0] = 0;
1040
+ rb.linearVelocity[1] = 0;
1041
+ rb.linearVelocity[2] = 0;
1042
+ rb.angularVelocity[0] = 0;
1043
+ rb.angularVelocity[1] = 0;
1044
+ rb.angularVelocity[2] = 0;
1017
1045
  storage.mark_sleeping(idx);
1018
1046
  }
1019
1047
  }
1020
1048
 
1021
- /**
1022
- * Get the body index for a packed body id without revalidation. Used by
1023
- * query traversals that already trust the id came from a live BVH leaf.
1024
- * @param {number} packed_body_id
1025
- * @returns {number}
1026
- */
1027
- __index_of(packed_body_id) {
1028
- return body_id_index(packed_body_id);
1029
- }
1030
-
1031
1049
  /**
1032
1050
  * Broadphase raycast against both BVHs. Fills `result` with the nearest
1033
1051
  * hit and returns `true` on hit, `false` on miss.
@@ -1234,7 +1252,7 @@ export class PhysicsSystem extends System {
1234
1252
  const lv = rb.linearVelocity;
1235
1253
  const av = rb.angularVelocity;
1236
1254
  const v_sqr = lv[0] * lv[0] + lv[1] * lv[1] + lv[2] * lv[2]
1237
- + av[0] * av[0] + av[1] * av[1] + av[2] * av[2];
1255
+ + av[0] * av[0] + av[1] * av[1] + av[2] * av[2];
1238
1256
  if (v_sqr > max_v_sqr) max_v_sqr = v_sqr;
1239
1257
  }
1240
1258
 
@@ -1311,14 +1329,22 @@ export class PhysicsSystem extends System {
1311
1329
  const pt = payload.point;
1312
1330
  const nm = payload.normal;
1313
1331
  if (has_contact) {
1314
- const wax = data[slot_off], way = data[slot_off + 1], waz = data[slot_off + 2];
1332
+ const wax = data[slot_off], way = data[slot_off + 1], waz = data[slot_off + 2];
1315
1333
  const wbx = data[slot_off + 3], wby = data[slot_off + 4], wbz = data[slot_off + 5];
1316
- pt[0] = (wax + wbx) * 0.5; pt[1] = (way + wby) * 0.5; pt[2] = (waz + wbz) * 0.5;
1317
- nm[0] = data[slot_off + 6]; nm[1] = data[slot_off + 7]; nm[2] = data[slot_off + 8];
1334
+ pt[0] = (wax + wbx) * 0.5;
1335
+ pt[1] = (way + wby) * 0.5;
1336
+ pt[2] = (waz + wbz) * 0.5;
1337
+ nm[0] = data[slot_off + 6];
1338
+ nm[1] = data[slot_off + 7];
1339
+ nm[2] = data[slot_off + 8];
1318
1340
  payload.depth = data[slot_off + 9];
1319
1341
  } else {
1320
- pt[0] = 0; pt[1] = 0; pt[2] = 0;
1321
- nm[0] = 0; nm[1] = 0; nm[2] = 0;
1342
+ pt[0] = 0;
1343
+ pt[1] = 0;
1344
+ pt[2] = 0;
1345
+ nm[0] = 0;
1346
+ nm[1] = 0;
1347
+ nm[2] = 0;
1322
1348
  payload.depth = 0;
1323
1349
  }
1324
1350
  payload.entityA = entA;
@@ -1326,9 +1352,16 @@ export class PhysicsSystem extends System {
1326
1352
 
1327
1353
  let event_name;
1328
1354
  let signal;
1329
- if (kind === ContactEventKind.Begin) { event_name = PhysicsEvents.ContactBegin; signal = this.onContactBegin; }
1330
- else if (kind === ContactEventKind.Stay) { event_name = PhysicsEvents.ContactStay; signal = this.onContactStay; }
1331
- else { event_name = PhysicsEvents.ContactEnd; signal = this.onContactEnd; }
1355
+ if (kind === ContactEventKind.Begin) {
1356
+ event_name = PhysicsEvents.ContactBegin;
1357
+ signal = this.onContactBegin;
1358
+ } else if (kind === ContactEventKind.Stay) {
1359
+ event_name = PhysicsEvents.ContactStay;
1360
+ signal = this.onContactStay;
1361
+ } else {
1362
+ event_name = PhysicsEvents.ContactEnd;
1363
+ signal = this.onContactEnd;
1364
+ }
1332
1365
 
1333
1366
  signal.send1(payload);
1334
1367
 
@@ -1440,7 +1473,14 @@ export class PhysicsSystem extends System {
1440
1473
  const gy = this.gravity.y;
1441
1474
  const gz = this.gravity.z;
1442
1475
 
1443
- const count = this.storage.awake_count;
1476
+ const storage = this.storage;
1477
+ const bodies = this.__bodies;
1478
+ const transforms = this.__transforms;
1479
+ const joints = this.__joints;
1480
+ const manifolds = this.manifolds;
1481
+
1482
+
1483
+ const count = storage.awake_count;
1444
1484
 
1445
1485
  // Stage 1: consume the per-frame force / torque accumulators into
1446
1486
  // velocity at the full `dt`, exactly once (a user force is a per-frame
@@ -1450,9 +1490,11 @@ export class PhysicsSystem extends System {
1450
1490
  // that substep's contact warm-start, keeping resting stacks at zero
1451
1491
  // velocity.
1452
1492
  for (let i = 0; i < count; i++) {
1453
- const idx = this.storage.awake_at(i);
1454
- const rb = this.__bodies[idx];
1455
- const tr = this.__transforms[idx];
1493
+ const idx = storage.awake_at(i);
1494
+
1495
+ const rb = bodies[idx];
1496
+ const tr = transforms[idx];
1497
+
1456
1498
  integrate_velocity_forces(rb, tr, dt);
1457
1499
  }
1458
1500
 
@@ -1462,19 +1504,30 @@ export class PhysicsSystem extends System {
1462
1504
  // is a sub-millimetre slack difference, safely inside the margin.
1463
1505
  const lists = this.__body_collider_lists;
1464
1506
  for (let i = 0; i < count; i++) {
1465
- const idx = this.storage.awake_at(i);
1466
- const rb = this.__bodies[idx];
1507
+
1508
+ const idx = storage.awake_at(i);
1509
+ const rb = bodies[idx];
1467
1510
  const list = lists[idx];
1468
- if (list === undefined) continue;
1511
+
1512
+ if (list === undefined) {
1513
+ continue;
1514
+ }
1515
+
1469
1516
  const lv = rb.linearVelocity;
1470
- for (let k = 0; k < list.length; k++) {
1517
+
1518
+ const list_length = list.length;
1519
+
1520
+ for (let k = 0; k < list_length; k++) {
1521
+
1471
1522
  const entry = list[k];
1523
+
1472
1524
  compute_fat_world_aabb(
1473
1525
  scratch_world_aabb, 0,
1474
1526
  entry.collider.shape, entry.transform,
1475
1527
  lv[0], lv[1], lv[2],
1476
1528
  dt
1477
1529
  );
1530
+
1478
1531
  this.dynamicBvh.node_move_aabb(entry.bvhNode, scratch_world_aabb);
1479
1532
  }
1480
1533
  }
@@ -1483,13 +1536,14 @@ export class PhysicsSystem extends System {
1483
1536
  // outer-step motion, so the pair set stays valid across all substeps
1484
1537
  // — broadphase runs once.
1485
1538
  generate_pairs(
1486
- this.storage,
1539
+ storage,
1487
1540
  this.dynamicBvh,
1488
1541
  this.staticBvh,
1489
- this.manifolds,
1542
+ manifolds,
1490
1543
  lists,
1491
1544
  this.pairs,
1492
- this.__pair_filter_bound,
1545
+ this.__pair_filter,
1546
+ this,
1493
1547
  );
1494
1548
 
1495
1549
  // Stage 4: wake propagation — through broadphase pairs, then through
@@ -1500,11 +1554,11 @@ export class PhysicsSystem extends System {
1500
1554
  // Stage 5: narrowphase — once per outer step. The substep loop below
1501
1555
  // re-derives each contact's penetration analytically from the moved
1502
1556
  // poses rather than re-running geometry.
1503
- narrowphase_step(this.pairs, this.manifolds, this.__body_collider_lists);
1557
+ narrowphase_step(this.pairs, manifolds, this.__body_collider_lists);
1504
1558
 
1505
1559
  // Stage 6: partition awake bodies + touched contacts into islands.
1506
1560
  // Consumed by the solver (flattened contact list) and the sleep test.
1507
- this.islands.build(this.storage, this.manifolds, this.__bodies, this.__body_collider_lists, this.__joints);
1561
+ this.islands.build(storage, manifolds, bodies, this.__body_collider_lists, joints);
1508
1562
 
1509
1563
  // Stage 7: TGS substep loop.
1510
1564
  //
@@ -1521,7 +1575,7 @@ export class PhysicsSystem extends System {
1521
1575
  // the loop.
1522
1576
  const N = this.substeps;
1523
1577
  const h = dt / N;
1524
- const count_after_wake = this.storage.awake_count;
1578
+ const count_after_wake = storage.awake_count;
1525
1579
 
1526
1580
  // CCD: capture start-of-step positions for flagged bodies over the
1527
1581
  // post-wake awake set (poses are unchanged until the substep loop below
@@ -1530,21 +1584,37 @@ export class PhysicsSystem extends System {
1530
1584
  // start matches the end the resolve pass reads. Zero-cost when no body
1531
1585
  // is flagged.
1532
1586
  const ccd_on = this.ccdEnabled;
1587
+
1533
1588
  if (ccd_on) {
1534
- const ccd_need = this.storage.high_water_mark * 3;
1589
+
1590
+ const ccd_need = storage.high_water_mark * 3;
1591
+
1535
1592
  if (this.__ccd_start_pos.length < ccd_need) {
1536
1593
  this.__ccd_start_pos = new Float64Array(ccd_need);
1537
1594
  }
1595
+
1538
1596
  const ccd_start = this.__ccd_start_pos;
1597
+
1539
1598
  for (let i = 0; i < count_after_wake; i++) {
1540
- const idx = this.storage.awake_at(i);
1541
- const rb = this.__bodies[idx];
1542
- if (rb.kind !== BodyKind.Dynamic) continue;
1543
- if ((rb.flags & RigidBodyFlags.CCD) === 0) continue;
1599
+ const idx = storage.awake_at(i);
1600
+ const rb = bodies[idx];
1601
+
1602
+ if (rb.kind !== BodyKind.Dynamic) {
1603
+ continue;
1604
+ }
1605
+ if ((rb.flags & RigidBodyFlags.CCD) === 0) {
1606
+ continue;
1607
+ }
1608
+
1544
1609
  const list = this.__body_collider_lists[idx];
1545
- if (list === undefined || list.length === 0) continue;
1610
+
1611
+ if (list === undefined || list.length === 0) {
1612
+ continue;
1613
+ }
1614
+
1546
1615
  const cp = list[0].transform.position;
1547
1616
  const cb = idx * 3;
1617
+
1548
1618
  ccd_start[cb] = cp[0];
1549
1619
  ccd_start[cb + 1] = cp[1];
1550
1620
  ccd_start[cb + 2] = cp[2];
@@ -1557,55 +1627,117 @@ export class PhysicsSystem extends System {
1557
1627
  // reallocate again this step.
1558
1628
  this.__reset_pseudo_velocity();
1559
1629
  const pseudoVel = this.__pseudo_velocity;
1560
- const pseudo_len = this.storage.high_water_mark * 6;
1630
+ const pseudo_len = storage.high_water_mark * 6;
1631
+
1632
+ // Gather the data-oriented solver state for every body the substep loop
1633
+ // will touch: the post-wake awake set (all dynamics, at their post-force
1634
+ // velocity) plus the static / kinematic anchors and jointed partners
1635
+ // referenced by this step's contacts and joints. From here the substep
1636
+ // loop reads / writes velocity + orientation through `ss_data` with no
1637
+ // component-object dereference; persistent velocity is scattered back
1638
+ // onto the RigidBodies after the solve.
1639
+ const solver_state = this.__solver_state;
1640
+ solver_state.begin(storage.high_water_mark);
1641
+
1642
+ for (let i = 0; i < count_after_wake; i++) {
1643
+ const idx = storage.awake_at(i);
1644
+ solver_state.gather(idx, bodies[idx], transforms[idx]);
1645
+ }
1561
1646
 
1562
- prepare_contacts(this.manifolds, this, h);
1647
+ const island_contacts = this.islands.contact_data;
1648
+ const island_contact_total = this.islands.contact_offsets[this.islands.island_count];
1649
+
1650
+ for (let i = 0; i < island_contact_total; i++) {
1651
+ const slot = island_contacts[i];
1652
+
1653
+ const ia = body_id_index(manifolds.bodyA(slot));
1654
+ const ib = body_id_index(manifolds.bodyB(slot));
1655
+
1656
+ solver_state.gather(ia, bodies[ia], transforms[ia]);
1657
+ solver_state.gather(ib, bodies[ib], transforms[ib]);
1658
+ }
1659
+
1660
+ const joint_count = joints.length;
1661
+ for (let i = 0; i < joint_count; i++) {
1662
+ const joint = joints[i];
1663
+
1664
+ if (joint === undefined || joint === null) {
1665
+ continue;
1666
+ }
1667
+
1668
+ if (!storage.is_valid(joint._bodyIdA)) {
1669
+ continue;
1670
+ }
1671
+
1672
+ const ia = body_id_index(joint._bodyIdA);
1673
+
1674
+ solver_state.gather(ia, bodies[ia], transforms[ia]);
1675
+
1676
+ if (joint._bodyIdB !== JOINT_WORLD && storage.is_valid(joint._bodyIdB)) {
1677
+
1678
+ const ib = body_id_index(joint._bodyIdB);
1679
+
1680
+ solver_state.gather(ib, bodies[ib], transforms[ib]);
1681
+
1682
+ }
1683
+ }
1684
+
1685
+ const ss_data = solver_state.data;
1686
+
1687
+ prepare_contacts(manifolds, this, h);
1563
1688
 
1564
1689
  for (let s = 0; s < N; s++) {
1690
+
1565
1691
  // Gravity (+ damping) for this substep.
1566
1692
  for (let i = 0; i < count_after_wake; i++) {
1567
- const idx = this.storage.awake_at(i);
1568
- integrate_velocity_gravity(this.__bodies[idx], this.__transforms[idx], gx, gy, gz, h);
1693
+ const idx = storage.awake_at(i);
1694
+ integrate_velocity_gravity(ss_data, idx * SBS_STRIDE, bodies[idx], gx, gy, gz, h);
1569
1695
  }
1570
1696
 
1571
1697
  // Re-derive contact geometry at the current poses: concave pairs
1572
1698
  // re-run narrowphase (fresh feature/normal as the body rocks),
1573
1699
  // convex pairs rotate frozen anchors analytically. Then replay
1574
1700
  // the per-substep warm-start and solve velocity.
1575
- redetect_concave_contacts(this.manifolds, this);
1576
- refresh_contacts(this.manifolds, this);
1577
- warm_start_contacts(this.manifolds, this);
1578
- solve_velocity(this.manifolds, this, this.velocityIterations);
1701
+ redetect_concave_contacts(manifolds, this);
1702
+ refresh_contacts(manifolds, this.__transforms);
1703
+ warm_start_contacts(manifolds, this);
1704
+ solve_velocity(manifolds, this, this.velocityIterations);
1579
1705
 
1580
1706
  // Joints share the substep: warm-start + velocity-solve the 6-DOF
1581
1707
  // constraints on real velocity, coupled with the contacts above
1582
1708
  // (a body touched by both sees one substep of Gauss-Seidel across
1583
1709
  // contacts then joints). Position correction for locked DOFs is a
1584
1710
  // SPOOK bias inside this solve, so no separate joint position pass.
1585
- if (this.__joints.length > 0) {
1586
- solve_joints(this.__joints, this, h, this.jointIterations);
1711
+ if (joints.length > 0) {
1712
+ solve_joints(joints, this, h, this.jointIterations);
1587
1713
  }
1588
1714
 
1589
1715
  // Position correction writes pseudo-velocity (zeroed first so it
1590
1716
  // is a fresh per-substep correction), folded into the pose by the
1591
1717
  // position integrate and then discarded.
1592
1718
  pseudoVel.fill(0, 0, pseudo_len);
1593
- solve_position(this.manifolds, this, this.positionIterations);
1719
+ solve_position(manifolds, this, this.positionIterations);
1594
1720
 
1595
1721
  for (let i = 0; i < count_after_wake; i++) {
1596
- const idx = this.storage.awake_at(i);
1597
- const rb = this.__bodies[idx];
1598
- const tr = this.__transforms[idx];
1722
+ const idx = storage.awake_at(i);
1723
+ const rb = bodies[idx];
1724
+ const tr = transforms[idx];
1599
1725
  const base = idx * 6;
1600
- integrate_position(rb, tr, h,
1601
- pseudoVel[base], pseudoVel[base + 1], pseudoVel[base + 2],
1726
+ integrate_position(ss_data, idx * SBS_STRIDE, rb, tr, h,
1727
+ pseudoVel[base], pseudoVel[base + 1], pseudoVel[base + 2],
1602
1728
  pseudoVel[base + 3], pseudoVel[base + 4], pseudoVel[base + 5]);
1603
1729
  }
1604
1730
  }
1605
1731
 
1606
1732
  // Stage 8: one-shot restitution, after the substep loop, keyed off
1607
1733
  // the approach velocity captured at prepare time.
1608
- apply_restitution(this.manifolds, this);
1734
+ apply_restitution(manifolds, this);
1735
+
1736
+ // Scatter the solved persistent linear / angular velocity back onto the
1737
+ // RigidBody components (pose was written through to the Transforms each
1738
+ // substep). After this the bodies are authoritative again for CCD,
1739
+ // interpolation recording, and the sleep test below.
1740
+ solver_state.scatter(bodies);
1609
1741
 
1610
1742
  // Stage 8.5: continuous collision — sweep CCD-flagged fast movers along
1611
1743
  // their net step translation and stop them at the first blocker, so they
@@ -1628,12 +1760,12 @@ export class PhysicsSystem extends System {
1628
1760
  // Stage 10: diff manifolds against the previous frame and dispatch
1629
1761
  // Begin / Stay / End events. MUST run before advance_frame, which
1630
1762
  // rolls the touched flags.
1631
- diff_manifolds(this.manifolds, this.storage, this.contactEvents);
1763
+ diff_manifolds(manifolds, storage, this.contactEvents);
1632
1764
  this.__dispatch_contact_events();
1633
1765
 
1634
1766
  // Stage 11 (end-of-step): roll touched → prev_touched and evict slots
1635
1767
  // whose pair has not been touched within the grace window.
1636
- this.manifolds.advance_frame();
1768
+ manifolds.advance_frame();
1637
1769
  }
1638
1770
  }
1639
1771
 
@@ -41,4 +41,27 @@ export function world_inverse_inertia_apply(result: number[] | Float64Array, res
41
41
  z: number;
42
42
  w: number;
43
43
  }, vx: number, vy: number, vz: number): void;
44
+ /**
45
+ * Raw-float counterpart of {@link world_inverse_inertia_apply}: the same
46
+ * `I_w⁻¹ = R · diag · R^T` application, with the inverse-inertia diagonal and
47
+ * orientation quaternion passed as scalars rather than read off
48
+ * `{x,y,z}` / `{x,y,z,w}` objects. The data-oriented contact / joint solver
49
+ * reads these straight out of its `SolverBodyState` typed array, avoiding the
50
+ * per-impulse object dereference on the hot path. The arithmetic is identical
51
+ * to the object version (same operation order), so results are bit-identical.
52
+ *
53
+ * @param {number[]|Float64Array} result length >= 3
54
+ * @param {number} result_offset
55
+ * @param {number} ix
56
+ * @param {number} iy
57
+ * @param {number} iz Body-frame inverse inertia diagonal.
58
+ * @param {number} qx
59
+ * @param {number} qy
60
+ * @param {number} qz
61
+ * @param {number} qw Body orientation as a unit quaternion.
62
+ * @param {number} vx
63
+ * @param {number} vy
64
+ * @param {number} vz
65
+ */
66
+ export function world_inverse_inertia_apply_raw(result: number[] | Float64Array, result_offset: number, ix: number, iy: number, iz: number, qx: number, qy: number, qz: number, qw: number, vx: number, vy: number, vz: number): void;
44
67
  //# sourceMappingURL=world_inverse_inertia.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"world_inverse_inertia.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/inertia/world_inverse_inertia.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,oDAVW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,qBACN;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,YAEjC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,MAE5C,MAAM,MACN,MAAM,MACN,MAAM,QA6ChB"}
1
+ {"version":3,"file":"world_inverse_inertia.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/inertia/world_inverse_inertia.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,oDAVW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,qBACN;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,YAEjC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,MAE5C,MAAM,MACN,MAAM,MACN,MAAM,QAkBhB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wDAbW,MAAM,EAAE,GAAC,YAAY,iBACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA4ChB"}