@woosh/meep-engine 2.139.0 → 2.141.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.
- package/package.json +1 -1
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
- package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
- package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
- package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +21 -0
- package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/quat3_multiply.js +25 -0
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
- package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
- package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
- package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/Triangle3D.js +318 -0
- package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
- package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
- package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
- package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
- package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
- package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_negate_array.js +2 -2
- package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
- package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
- package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
- package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
- package/src/engine/control/first-person/TODO.md +173 -127
- package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Slide.js +9 -1
- package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
- package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
- package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
- package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
- package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
- package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
- package/src/engine/physics/BULLET_REVIEW.md +945 -0
- package/src/engine/physics/CANNON_REVIEW.md +1300 -0
- package/src/engine/physics/JOLT_REVIEW.md +913 -0
- package/src/engine/physics/PLAN.md +578 -236
- package/src/engine/physics/RAPIER_REVIEW.md +934 -0
- package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
- package/src/engine/physics/REVIEW_002.md +151 -0
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
- package/src/engine/physics/constraint/DofMode.d.ts +28 -0
- package/src/engine/physics/constraint/DofMode.d.ts.map +1 -0
- package/src/engine/physics/constraint/DofMode.js +35 -0
- package/src/engine/physics/constraint/solve_constraints.d.ts +16 -0
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -0
- package/src/engine/physics/constraint/solve_constraints.js +436 -0
- package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +608 -499
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.d.ts +179 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -0
- package/src/engine/physics/ecs/Joint.js +234 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +180 -20
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +1423 -1159
- package/src/engine/physics/fluid/FluidField.d.ts +14 -10
- package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidField.js +14 -10
- package/src/engine/physics/fluid/FluidSimulator.js +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +17 -10
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +18 -11
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +13 -10
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +18 -13
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +4 -3
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +15 -11
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +30 -6
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +44 -18
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
- package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
- package/src/engine/physics/gjk/gjk.d.ts +28 -2
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk.js +421 -378
- package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
- package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
- package/src/engine/physics/gjk/minkowski_support.js +75 -0
- package/src/engine/physics/gjk/mpr.d.ts +56 -0
- package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
- package/src/engine/physics/gjk/mpr.js +344 -0
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
- package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
- package/src/engine/physics/integration/integrate_position.d.ts +25 -7
- package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_position.js +43 -12
- package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
- package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_velocity.js +82 -1
- package/src/engine/physics/island/IslandBuilder.d.ts +4 -1
- package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
- package/src/engine/physics/island/IslandBuilder.js +33 -16
- package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
- package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/PosedShape.js +28 -30
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +140 -18
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
- package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
- package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +41 -2
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +1497 -382
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
- package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
- package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
- package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
- package/src/engine/physics/queries/overlap_shape.js +183 -0
- package/src/engine/physics/queries/shape_cast.d.ts +56 -0
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
- package/src/engine/physics/queries/shape_cast.js +387 -0
- package/src/engine/physics/solver/solve_contacts.d.ts +146 -32
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +809 -223
- package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { BodyKind } from "../ecs/BodyKind.js";
|
|
2
|
+
import { SleepState } from "../ecs/SleepState.js";
|
|
3
|
+
import { JOINT_WORLD } from "../ecs/Joint.js";
|
|
4
|
+
import { DofMode } from "./DofMode.js";
|
|
5
|
+
import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
|
|
6
|
+
import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
|
|
7
|
+
import { quat3_to_matrix3 } from "../../../core/geom/3d/quaternion/quat3_to_matrix3.js";
|
|
8
|
+
import { quat3_multiply } from "../../../core/geom/3d/quaternion/quat3_multiply.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* # 6-DOF constraint (joint) solver
|
|
12
|
+
*
|
|
13
|
+
* Runs alongside the contact solver inside the same TGS substep loop: each
|
|
14
|
+
* substep the joints are warm-started and their velocity rows solved, sharing
|
|
15
|
+
* the contact solver's per-substep / warm-start / SPOOK-bias model so the two
|
|
16
|
+
* constraint families converge together (a body touched by both a contact and
|
|
17
|
+
* a joint sees a single coupled Gauss-Seidel sweep across the substep loop).
|
|
18
|
+
*
|
|
19
|
+
* A joint is a configurable 6-DOF constraint ({@link Joint}); each relative
|
|
20
|
+
* degree of freedom is independently locked / free / limited / springy /
|
|
21
|
+
* motorised. This module implements **LOCKED linear** DOFs first — i.e. the
|
|
22
|
+
* ball-socket (point-to-point) joint, which alone covers chains, ropes, and
|
|
23
|
+
* pendulums. Angular locks, limits, springs and motors slot into the same
|
|
24
|
+
* per-DOF loop as they land.
|
|
25
|
+
*
|
|
26
|
+
* Row math is identical in shape to a contact's normal row: for a unit axis
|
|
27
|
+
* `d` and lever arms `rA = anchorA − comA`, `rB = anchorB − comB`,
|
|
28
|
+
* `K = invMA + invMB + (rA×d)·Iw_A·(rA×d) + (rB×d)·Iw_B·(rB×d)`,
|
|
29
|
+
* `vrel = (vA + ωA×rA − vB − ωB×rB) · d`,
|
|
30
|
+
* `λ = −(1/K)·(vrel + bias)`,
|
|
31
|
+
* applied as `±λ·d` at the anchors. Locked DOFs are bilateral (no clamp); the
|
|
32
|
+
* position error is corrected by a SPOOK velocity bias. Geometry (anchors /
|
|
33
|
+
* lever arms / position error) is recomputed from the current pose every
|
|
34
|
+
* substep, so the joint tracks the bodies as they move.
|
|
35
|
+
*
|
|
36
|
+
* @author Alex Goldring
|
|
37
|
+
* @copyright Company Named Limited (c) 2026
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maximum SPOOK position-correction bias for a joint row, m/s — belt-and-braces
|
|
42
|
+
* against a wildly-violated constraint (e.g. a freshly-teleported body)
|
|
43
|
+
* yanking the solve. Equality joints sit near zero error at steady state, so
|
|
44
|
+
* this only clamps transients.
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
const MAX_JOINT_BIAS = 4;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Baumgarte / SPOOK position-correction gain `β`. The per-substep gain is
|
|
51
|
+
* `β / dt_sub`; with `β = 0.2` this matches the contact solver's position
|
|
52
|
+
* stiffness. Joints are bilateral equality constraints, so this only nudges
|
|
53
|
+
* residual position error toward zero (no clamp interplay to destabilise).
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
const JOINT_BAUMGARTE = 0.2;
|
|
57
|
+
|
|
58
|
+
const scratch_inertia = new Float64Array(3);
|
|
59
|
+
const scratch_anchor = new Float64Array(3);
|
|
60
|
+
/** Frame-A world basis (rows = A's frame axes in world), from quat3_to_matrix3. */
|
|
61
|
+
const scratch_frame_a = new Float64Array(9);
|
|
62
|
+
/** Scratch quaternion for frame composition / relative rotation. */
|
|
63
|
+
const scratch_q = new Float64Array(4);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Angular effective-mass contribution of one body about a unit world axis:
|
|
67
|
+
* `a · Iw⁻¹ · a`. Zero for non-dynamic / rotation-locked bodies.
|
|
68
|
+
* @returns {number}
|
|
69
|
+
*/
|
|
70
|
+
function angular_axis_effective_mass(rb, transform, ax, ay, az) {
|
|
71
|
+
if (rb.kind !== BodyKind.Dynamic) return 0;
|
|
72
|
+
const ii = rb.inverseInertiaLocal;
|
|
73
|
+
if (ii.x === 0 && ii.y === 0 && ii.z === 0) return 0;
|
|
74
|
+
world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, ax, ay, az);
|
|
75
|
+
return ax * scratch_inertia[0] + ay * scratch_inertia[1] + az * scratch_inertia[2];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply a pure angular impulse `sign·λ·axis`: Δω = Iw⁻¹·(sign·λ·axis).
|
|
80
|
+
* @param {RigidBody} rb @param {Transform} transform
|
|
81
|
+
* @param {number} ax @param {number} ay @param {number} az
|
|
82
|
+
* @param {number} lambda @param {number} sign
|
|
83
|
+
*/
|
|
84
|
+
function apply_angular_impulse(rb, transform, ax, ay, az, lambda, sign) {
|
|
85
|
+
if (rb.kind !== BodyKind.Dynamic) return;
|
|
86
|
+
const ii = rb.inverseInertiaLocal;
|
|
87
|
+
if (ii.x === 0 && ii.y === 0 && ii.z === 0) return;
|
|
88
|
+
const L = sign * lambda;
|
|
89
|
+
world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, ax * L, ay * L, az * L);
|
|
90
|
+
const av = rb.angularVelocity;
|
|
91
|
+
av[0] += scratch_inertia[0];
|
|
92
|
+
av[1] += scratch_inertia[1];
|
|
93
|
+
av[2] += scratch_inertia[2];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Effective-mass denominator contribution of one body along a unit axis:
|
|
98
|
+
* `invM + (r×d)·Iw⁻¹·(r×d)`. Static / kinematic bodies (invM === 0) contribute
|
|
99
|
+
* nothing.
|
|
100
|
+
*
|
|
101
|
+
* @param {RigidBody} rb
|
|
102
|
+
* @param {Transform} transform
|
|
103
|
+
* @param {number} invM
|
|
104
|
+
* @param {number} rx @param {number} ry @param {number} rz
|
|
105
|
+
* @param {number} dx @param {number} dy @param {number} dz
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
function axis_effective_mass(rb, transform, invM, rx, ry, rz, dx, dy, dz) {
|
|
109
|
+
if (invM === 0) return 0;
|
|
110
|
+
let k = invM;
|
|
111
|
+
const ii = rb.inverseInertiaLocal;
|
|
112
|
+
if (ii.x !== 0 || ii.y !== 0 || ii.z !== 0) {
|
|
113
|
+
const cx = ry * dz - rz * dy;
|
|
114
|
+
const cy = rz * dx - rx * dz;
|
|
115
|
+
const cz = rx * dy - ry * dx;
|
|
116
|
+
world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, cx, cy, cz);
|
|
117
|
+
k += cx * scratch_inertia[0] + cy * scratch_inertia[1] + cz * scratch_inertia[2];
|
|
118
|
+
}
|
|
119
|
+
return k;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Apply impulse `P` at body-relative offset `r`: Δv = P·invM, Δω = Iw⁻¹·(r×P).
|
|
124
|
+
* Direct typed-array writes (bypass Vector3#set observers — dead weight here).
|
|
125
|
+
*
|
|
126
|
+
* @param {RigidBody} rb @param {Transform} transform @param {number} invM
|
|
127
|
+
* @param {number} rx @param {number} ry @param {number} rz
|
|
128
|
+
* @param {number} Px @param {number} Py @param {number} Pz
|
|
129
|
+
* @param {number} sign +1 / −1
|
|
130
|
+
*/
|
|
131
|
+
function apply_impulse(rb, transform, invM, rx, ry, rz, Px, Py, Pz, sign) {
|
|
132
|
+
if (invM === 0) return;
|
|
133
|
+
const sPx = sign * Px, sPy = sign * Py, sPz = sign * Pz;
|
|
134
|
+
const lv = rb.linearVelocity;
|
|
135
|
+
lv[0] += sPx * invM; lv[1] += sPy * invM; lv[2] += sPz * invM;
|
|
136
|
+
|
|
137
|
+
const tx = ry * sPz - rz * sPy;
|
|
138
|
+
const ty = rz * sPx - rx * sPz;
|
|
139
|
+
const tz = rx * sPy - ry * sPx;
|
|
140
|
+
world_inverse_inertia_apply(scratch_inertia, 0, rb.inverseInertiaLocal, transform.rotation, tx, ty, tz);
|
|
141
|
+
const av = rb.angularVelocity;
|
|
142
|
+
av[0] += scratch_inertia[0]; av[1] += scratch_inertia[1]; av[2] += scratch_inertia[2];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {RigidBody} rb
|
|
147
|
+
* @returns {number}
|
|
148
|
+
*/
|
|
149
|
+
function inv_mass_of(rb) {
|
|
150
|
+
if (rb.kind !== BodyKind.Dynamic) return 0;
|
|
151
|
+
return rb.mass > 0 ? 1 / rb.mass : 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Solve every joint for one substep: recompute geometry at the current poses,
|
|
156
|
+
* replay the per-substep warm-start, and run `iters` velocity iterations of
|
|
157
|
+
* the locked linear DOFs.
|
|
158
|
+
*
|
|
159
|
+
* Called once per substep from `PhysicsSystem.fixedUpdate`, after the contact
|
|
160
|
+
* solve so the two share the substep / warm-start cadence.
|
|
161
|
+
*
|
|
162
|
+
* @param {Joint[]} joints live joints (sparse array; holes skipped)
|
|
163
|
+
* @param {PhysicsSystem} system reads `__bodies` / `__transforms` / index map
|
|
164
|
+
* @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
|
|
165
|
+
* from it, matching the contact solver's per-substep position stiffness)
|
|
166
|
+
* @param {number} iters velocity iterations
|
|
167
|
+
*/
|
|
168
|
+
export function solve_joints(joints, system, dt_sub, iters) {
|
|
169
|
+
const n = joints.length;
|
|
170
|
+
if (n === 0 || dt_sub <= 0) return;
|
|
171
|
+
|
|
172
|
+
const spook_a = JOINT_BAUMGARTE / dt_sub;
|
|
173
|
+
|
|
174
|
+
const storage = system.storage;
|
|
175
|
+
|
|
176
|
+
for (let ji = 0; ji < n; ji++) {
|
|
177
|
+
const joint = joints[ji];
|
|
178
|
+
if (joint === undefined || joint === null) continue;
|
|
179
|
+
|
|
180
|
+
// Generation-checked validity: if A's body was unlinked (and its slot
|
|
181
|
+
// possibly reused by a different body), the stored packed id no longer
|
|
182
|
+
// validates — skip the stale joint rather than attach to the wrong body.
|
|
183
|
+
if (!storage.is_valid(joint._bodyIdA)) continue;
|
|
184
|
+
const idxA = system.__index_of(joint._bodyIdA);
|
|
185
|
+
const rbA = system.__bodies[idxA];
|
|
186
|
+
if (rbA === undefined) continue;
|
|
187
|
+
const trA = system.__transforms[idxA];
|
|
188
|
+
|
|
189
|
+
const to_world = joint._bodyIdB === JOINT_WORLD;
|
|
190
|
+
let rbB = null, trB = null, idxB = -1;
|
|
191
|
+
if (!to_world) {
|
|
192
|
+
if (!storage.is_valid(joint._bodyIdB)) continue;
|
|
193
|
+
idxB = system.__index_of(joint._bodyIdB);
|
|
194
|
+
rbB = system.__bodies[idxB];
|
|
195
|
+
if (rbB === undefined) continue;
|
|
196
|
+
trB = system.__transforms[idxB];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Skip while a participating dynamic body sleeps — its velocity must
|
|
200
|
+
// stay zero until woken. (Island-aware joint sleep is a follow-up; for
|
|
201
|
+
// now jointed bodies that should stay coupled use DisableSleep.)
|
|
202
|
+
if (rbA.sleepState === SleepState.Sleeping) continue;
|
|
203
|
+
if (!to_world && rbB.sleepState === SleepState.Sleeping) continue;
|
|
204
|
+
|
|
205
|
+
// Both bodies static/kinematic → nothing to solve.
|
|
206
|
+
const invMA = inv_mass_of(rbA);
|
|
207
|
+
const invMB = to_world ? 0 : inv_mass_of(rbB);
|
|
208
|
+
if (invMA === 0 && invMB === 0) continue;
|
|
209
|
+
|
|
210
|
+
// World anchor A and its lever arm rA = R_A · localAnchorA.
|
|
211
|
+
const la = joint.localAnchorA;
|
|
212
|
+
v3_quat3_apply(scratch_anchor, 0, la.x, la.y, la.z,
|
|
213
|
+
trA.rotation[0], trA.rotation[1], trA.rotation[2], trA.rotation[3]);
|
|
214
|
+
const rAx = scratch_anchor[0], rAy = scratch_anchor[1], rAz = scratch_anchor[2];
|
|
215
|
+
const anchorAx = trA.position.x + rAx;
|
|
216
|
+
const anchorAy = trA.position.y + rAy;
|
|
217
|
+
const anchorAz = trA.position.z + rAz;
|
|
218
|
+
|
|
219
|
+
// World anchor B + lever arm rB. For a world anchor, localAnchorB is a
|
|
220
|
+
// fixed world point and there is no body B lever.
|
|
221
|
+
let rBx = 0, rBy = 0, rBz = 0;
|
|
222
|
+
let anchorBx, anchorBy, anchorBz;
|
|
223
|
+
const lb = joint.localAnchorB;
|
|
224
|
+
if (to_world) {
|
|
225
|
+
anchorBx = lb.x; anchorBy = lb.y; anchorBz = lb.z;
|
|
226
|
+
} else {
|
|
227
|
+
v3_quat3_apply(scratch_anchor, 0, lb.x, lb.y, lb.z,
|
|
228
|
+
trB.rotation[0], trB.rotation[1], trB.rotation[2], trB.rotation[3]);
|
|
229
|
+
rBx = scratch_anchor[0]; rBy = scratch_anchor[1]; rBz = scratch_anchor[2];
|
|
230
|
+
anchorBx = trB.position.x + rBx;
|
|
231
|
+
anchorBy = trB.position.y + rBy;
|
|
232
|
+
anchorBz = trB.position.z + rBz;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Position error C = anchorA − anchorB (drive to zero on locked axes).
|
|
236
|
+
const Cx = anchorAx - anchorBx;
|
|
237
|
+
const Cy = anchorAy - anchorBy;
|
|
238
|
+
const Cz = anchorAz - anchorBz;
|
|
239
|
+
|
|
240
|
+
const mode = joint.dofMode;
|
|
241
|
+
const imp = joint.dofImpulse;
|
|
242
|
+
const lvA = rbA.linearVelocity, avA = rbA.angularVelocity;
|
|
243
|
+
const lvB = to_world ? null : rbB.linearVelocity;
|
|
244
|
+
const avB = to_world ? null : rbB.angularVelocity;
|
|
245
|
+
|
|
246
|
+
// === Frame A world axes — shared by the linear AND angular rows ===
|
|
247
|
+
// Every DOF is expressed in the joint's frame on body A. A locked
|
|
248
|
+
// linear DOF pins the anchor offset along a frame axis (all three →
|
|
249
|
+
// ball-socket); a FREE linear DOF is a prismatic slide. A locked
|
|
250
|
+
// angular DOF pins relative rotation about a frame axis; a FREE one is
|
|
251
|
+
// a hinge axis. Frame A = body-A rotation ⊗ localBasisA; the rows of
|
|
252
|
+
// its matrix are the frame axes in world (see quat3_to_matrix3).
|
|
253
|
+
const linLockX = mode[0] === DofMode.LOCKED;
|
|
254
|
+
const linLockY = mode[1] === DofMode.LOCKED;
|
|
255
|
+
const linLockZ = mode[2] === DofMode.LOCKED;
|
|
256
|
+
const angLockX = mode[3] === DofMode.LOCKED;
|
|
257
|
+
const angLockY = mode[4] === DofMode.LOCKED;
|
|
258
|
+
const angLockZ = mode[5] === DofMode.LOCKED;
|
|
259
|
+
if (!(linLockX || linLockY || linLockZ || angLockX || angLockY || angLockZ)) continue;
|
|
260
|
+
|
|
261
|
+
const lba = joint.localBasisA;
|
|
262
|
+
quat3_multiply(scratch_q, 0, trA.rotation[0], trA.rotation[1], trA.rotation[2], trA.rotation[3], lba[0], lba[1], lba[2], lba[3]);
|
|
263
|
+
const qAx = scratch_q[0], qAy = scratch_q[1], qAz = scratch_q[2], qAw = scratch_q[3];
|
|
264
|
+
quat3_to_matrix3(scratch_frame_a, 0, qAx, qAy, qAz, qAw);
|
|
265
|
+
const aXx = scratch_frame_a[0], aXy = scratch_frame_a[1], aXz = scratch_frame_a[2];
|
|
266
|
+
const aYx = scratch_frame_a[3], aYy = scratch_frame_a[4], aYz = scratch_frame_a[5];
|
|
267
|
+
const aZx = scratch_frame_a[6], aZy = scratch_frame_a[7], aZz = scratch_frame_a[8];
|
|
268
|
+
|
|
269
|
+
// --- Linear rows: effective mass + SPOOK bias along each locked frame
|
|
270
|
+
// axis. Error along an axis is `C · axis` (signed anchor offset).
|
|
271
|
+
// Convention is A−B (relative anchor velocity vA−vB, impulse +to A
|
|
272
|
+
// / −to B), matching contacts. ---
|
|
273
|
+
let m_eff_lx = 0, m_eff_ly = 0, m_eff_lz = 0;
|
|
274
|
+
let biasLx = 0, biasLy = 0, biasLz = 0;
|
|
275
|
+
if (linLockX) {
|
|
276
|
+
const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aXx, aXy, aXz)
|
|
277
|
+
+ (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aXx, aXy, aXz));
|
|
278
|
+
m_eff_lx = k > 0 ? 1 / k : 0;
|
|
279
|
+
let b = spook_a * (Cx * aXx + Cy * aXy + Cz * aXz);
|
|
280
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
281
|
+
biasLx = b;
|
|
282
|
+
}
|
|
283
|
+
if (linLockY) {
|
|
284
|
+
const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aYx, aYy, aYz)
|
|
285
|
+
+ (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aYx, aYy, aYz));
|
|
286
|
+
m_eff_ly = k > 0 ? 1 / k : 0;
|
|
287
|
+
let b = spook_a * (Cx * aYx + Cy * aYy + Cz * aYz);
|
|
288
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
289
|
+
biasLy = b;
|
|
290
|
+
}
|
|
291
|
+
if (linLockZ) {
|
|
292
|
+
const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aZx, aZy, aZz)
|
|
293
|
+
+ (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aZx, aZy, aZz));
|
|
294
|
+
m_eff_lz = k > 0 ? 1 / k : 0;
|
|
295
|
+
let b = spook_a * (Cx * aZx + Cy * aZy + Cz * aZz);
|
|
296
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
297
|
+
biasLz = b;
|
|
298
|
+
}
|
|
299
|
+
const doLinX = linLockX && m_eff_lx !== 0;
|
|
300
|
+
const doLinY = linLockY && m_eff_ly !== 0;
|
|
301
|
+
const doLinZ = linLockZ && m_eff_lz !== 0;
|
|
302
|
+
|
|
303
|
+
// --- Angular rows: relative-rotation error + effective mass about each
|
|
304
|
+
// locked frame axis. Convention is B−A (relative angular velocity
|
|
305
|
+
// ωB−ωA, impulse +to B / −to A). ---
|
|
306
|
+
let m_eff_ax = 0, m_eff_ay = 0, m_eff_az = 0;
|
|
307
|
+
let biasAx = 0, biasAy = 0, biasAz = 0;
|
|
308
|
+
let lockAX = false, lockAY = false, lockAZ = false;
|
|
309
|
+
if (angLockX || angLockY || angLockZ) {
|
|
310
|
+
// Frame B world rotation (identity for a world anchor → locks A's
|
|
311
|
+
// frame to world axes).
|
|
312
|
+
let qBx = 0, qBy = 0, qBz = 0, qBw = 1;
|
|
313
|
+
if (!to_world) {
|
|
314
|
+
const lbb = joint.localBasisB;
|
|
315
|
+
quat3_multiply(scratch_q, 0, trB.rotation[0], trB.rotation[1], trB.rotation[2], trB.rotation[3], lbb[0], lbb[1], lbb[2], lbb[3]);
|
|
316
|
+
qBx = scratch_q[0]; qBy = scratch_q[1]; qBz = scratch_q[2]; qBw = scratch_q[3];
|
|
317
|
+
}
|
|
318
|
+
// qD = conj(qA) ⊗ qB; small-angle rotation vector (shortest path via
|
|
319
|
+
// sign(w)) is the per-axis error in frame-A-local coords — pairs
|
|
320
|
+
// with the world frame axes above (axis i ↔ error component i).
|
|
321
|
+
quat3_multiply(scratch_q, 0, -qAx, -qAy, -qAz, qAw, qBx, qBy, qBz, qBw);
|
|
322
|
+
const es = scratch_q[3] < 0 ? -2 : 2;
|
|
323
|
+
const eX = es * scratch_q[0];
|
|
324
|
+
const eY = es * scratch_q[1];
|
|
325
|
+
const eZ = es * scratch_q[2];
|
|
326
|
+
|
|
327
|
+
if (angLockX) {
|
|
328
|
+
const k = angular_axis_effective_mass(rbA, trA, aXx, aXy, aXz)
|
|
329
|
+
+ (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aXx, aXy, aXz));
|
|
330
|
+
m_eff_ax = k > 0 ? 1 / k : 0;
|
|
331
|
+
let b = spook_a * eX;
|
|
332
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
333
|
+
biasAx = b;
|
|
334
|
+
lockAX = m_eff_ax !== 0;
|
|
335
|
+
}
|
|
336
|
+
if (angLockY) {
|
|
337
|
+
const k = angular_axis_effective_mass(rbA, trA, aYx, aYy, aYz)
|
|
338
|
+
+ (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aYx, aYy, aYz));
|
|
339
|
+
m_eff_ay = k > 0 ? 1 / k : 0;
|
|
340
|
+
let b = spook_a * eY;
|
|
341
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
342
|
+
biasAy = b;
|
|
343
|
+
lockAY = m_eff_ay !== 0;
|
|
344
|
+
}
|
|
345
|
+
if (angLockZ) {
|
|
346
|
+
const k = angular_axis_effective_mass(rbA, trA, aZx, aZy, aZz)
|
|
347
|
+
+ (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aZx, aZy, aZz));
|
|
348
|
+
m_eff_az = k > 0 ? 1 / k : 0;
|
|
349
|
+
let b = spook_a * eZ;
|
|
350
|
+
if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
|
|
351
|
+
biasAz = b;
|
|
352
|
+
lockAZ = m_eff_az !== 0;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// --- Per-substep warm-start (frame axes): linear impulse `Σ imp[i]·aᵢ`
|
|
357
|
+
// applied at the anchors, angular impulse `Σ imp[3+i]·aᵢ` about the
|
|
358
|
+
// frame axes (+to B / −to A). ---
|
|
359
|
+
{
|
|
360
|
+
const Px = (doLinX ? imp[0] : 0) * aXx + (doLinY ? imp[1] : 0) * aYx + (doLinZ ? imp[2] : 0) * aZx;
|
|
361
|
+
const Py = (doLinX ? imp[0] : 0) * aXy + (doLinY ? imp[1] : 0) * aYy + (doLinZ ? imp[2] : 0) * aZy;
|
|
362
|
+
const Pz = (doLinX ? imp[0] : 0) * aXz + (doLinY ? imp[1] : 0) * aYz + (doLinZ ? imp[2] : 0) * aZz;
|
|
363
|
+
if (Px !== 0 || Py !== 0 || Pz !== 0) {
|
|
364
|
+
apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, Px, Py, Pz, +1);
|
|
365
|
+
if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, Px, Py, Pz, -1);
|
|
366
|
+
}
|
|
367
|
+
const Lx = (lockAX ? imp[3] : 0) * aXx + (lockAY ? imp[4] : 0) * aYx + (lockAZ ? imp[5] : 0) * aZx;
|
|
368
|
+
const Ly = (lockAX ? imp[3] : 0) * aXy + (lockAY ? imp[4] : 0) * aYy + (lockAZ ? imp[5] : 0) * aZy;
|
|
369
|
+
const Lz = (lockAX ? imp[3] : 0) * aXz + (lockAY ? imp[4] : 0) * aYz + (lockAZ ? imp[5] : 0) * aZz;
|
|
370
|
+
if (Lx !== 0 || Ly !== 0 || Lz !== 0) {
|
|
371
|
+
if (!to_world) apply_angular_impulse(rbB, trB, Lx, Ly, Lz, 1, +1);
|
|
372
|
+
apply_angular_impulse(rbA, trA, Lx, Ly, Lz, 1, -1);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (let it = 0; it < iters; it++) {
|
|
377
|
+
// Linear rows (frame axes). The relative anchor velocity is
|
|
378
|
+
// recomputed before each row so successive rows see the prior
|
|
379
|
+
// impulses (Gauss-Seidel coupling).
|
|
380
|
+
if (doLinX) {
|
|
381
|
+
const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
|
|
382
|
+
const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
|
|
383
|
+
const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
|
|
384
|
+
const lambda = -m_eff_lx * ((rvx * aXx + rvy * aXy + rvz * aXz) + biasLx);
|
|
385
|
+
imp[0] += lambda;
|
|
386
|
+
apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aXx, lambda * aXy, lambda * aXz, +1);
|
|
387
|
+
if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aXx, lambda * aXy, lambda * aXz, -1);
|
|
388
|
+
}
|
|
389
|
+
if (doLinY) {
|
|
390
|
+
const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
|
|
391
|
+
const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
|
|
392
|
+
const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
|
|
393
|
+
const lambda = -m_eff_ly * ((rvx * aYx + rvy * aYy + rvz * aYz) + biasLy);
|
|
394
|
+
imp[1] += lambda;
|
|
395
|
+
apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aYx, lambda * aYy, lambda * aYz, +1);
|
|
396
|
+
if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aYx, lambda * aYy, lambda * aYz, -1);
|
|
397
|
+
}
|
|
398
|
+
if (doLinZ) {
|
|
399
|
+
const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
|
|
400
|
+
const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
|
|
401
|
+
const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
|
|
402
|
+
const lambda = -m_eff_lz * ((rvx * aZx + rvy * aZy + rvz * aZz) + biasLz);
|
|
403
|
+
imp[2] += lambda;
|
|
404
|
+
apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aZx, lambda * aZy, lambda * aZz, +1);
|
|
405
|
+
if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aZx, lambda * aZy, lambda * aZz, -1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Angular rows (frame axes): drive ωB−ωA about each locked axis to
|
|
409
|
+
// zero with the SPOOK bias.
|
|
410
|
+
if (lockAX) {
|
|
411
|
+
let wrel = -(avA[0] * aXx + avA[1] * aXy + avA[2] * aXz);
|
|
412
|
+
if (!to_world) wrel += avB[0] * aXx + avB[1] * aXy + avB[2] * aXz;
|
|
413
|
+
const lambda = -m_eff_ax * (wrel + biasAx);
|
|
414
|
+
imp[3] += lambda;
|
|
415
|
+
if (!to_world) apply_angular_impulse(rbB, trB, aXx, aXy, aXz, lambda, +1);
|
|
416
|
+
apply_angular_impulse(rbA, trA, aXx, aXy, aXz, lambda, -1);
|
|
417
|
+
}
|
|
418
|
+
if (lockAY) {
|
|
419
|
+
let wrel = -(avA[0] * aYx + avA[1] * aYy + avA[2] * aYz);
|
|
420
|
+
if (!to_world) wrel += avB[0] * aYx + avB[1] * aYy + avB[2] * aYz;
|
|
421
|
+
const lambda = -m_eff_ay * (wrel + biasAy);
|
|
422
|
+
imp[4] += lambda;
|
|
423
|
+
if (!to_world) apply_angular_impulse(rbB, trB, aYx, aYy, aYz, lambda, +1);
|
|
424
|
+
apply_angular_impulse(rbA, trA, aYx, aYy, aYz, lambda, -1);
|
|
425
|
+
}
|
|
426
|
+
if (lockAZ) {
|
|
427
|
+
let wrel = -(avA[0] * aZx + avA[1] * aZy + avA[2] * aZz);
|
|
428
|
+
if (!to_world) wrel += avB[0] * aZx + avB[1] * aZy + avB[2] * aZz;
|
|
429
|
+
const lambda = -m_eff_az * (wrel + biasAz);
|
|
430
|
+
imp[5] += lambda;
|
|
431
|
+
if (!to_world) apply_angular_impulse(rbB, trB, aZx, aZy, aZz, lambda, +1);
|
|
432
|
+
apply_angular_impulse(rbA, trA, aZx, aZy, aZz, lambda, -1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -9,21 +9,30 @@ export const MAX_CONTACTS_PER_MANIFOLD: number;
|
|
|
9
9
|
* Per-contact field stride in the data Float64Array.
|
|
10
10
|
*
|
|
11
11
|
* Layout per contact (all world-space):
|
|
12
|
-
* 0..2 : world_a
|
|
13
|
-
* 3..5 : world_b
|
|
14
|
-
* 6..8 : normal_w
|
|
15
|
-
* 9 : depth
|
|
16
|
-
* 10 : j_n
|
|
17
|
-
* 11 : j_t1
|
|
18
|
-
* 12 : j_t2
|
|
12
|
+
* 0..2 : world_a (contact point on body A's surface)
|
|
13
|
+
* 3..5 : world_b (contact point on body B's surface)
|
|
14
|
+
* 6..8 : normal_w (from B toward A)
|
|
15
|
+
* 9 : depth (positive = penetration, negative = speculative gap)
|
|
16
|
+
* 10 : j_n (accumulated normal impulse, warm-start)
|
|
17
|
+
* 11 : j_t1 (accumulated tangent impulse, axis 1)
|
|
18
|
+
* 12 : j_t2 (accumulated tangent impulse, axis 2)
|
|
19
|
+
* 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
|
|
20
|
+
* geometric feature pair that produced this contact;
|
|
21
|
+
* 0 means "no info, fall back to position matching")
|
|
19
22
|
*
|
|
20
23
|
* Solver uses `(world_a + world_b) * 0.5` as the application point; storing
|
|
21
|
-
* both surface points
|
|
22
|
-
*
|
|
24
|
+
* both surface points enables the per-frame warm-start matcher in the
|
|
25
|
+
* narrowphase to compare contact-point positions when feature_id is 0.
|
|
23
26
|
*
|
|
24
27
|
* @type {number}
|
|
25
28
|
*/
|
|
26
29
|
export const CONTACT_STRIDE: number;
|
|
30
|
+
/**
|
|
31
|
+
* Per-contact feature_id offset within {@link CONTACT_STRIDE}.
|
|
32
|
+
* Exposed for the narrowphase matcher; solver code does not read this.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
export const CONTACT_FEATURE_ID_OFFSET: number;
|
|
27
36
|
/**
|
|
28
37
|
* Per-slot Float64 stride: room for {@link MAX_CONTACTS_PER_MANIFOLD} contacts.
|
|
29
38
|
* @type {number}
|
|
@@ -65,6 +74,7 @@ export class ManifoldStore {
|
|
|
65
74
|
__high_water: number;
|
|
66
75
|
__data: Float64Array;
|
|
67
76
|
__meta: Uint32Array;
|
|
77
|
+
__slot_axis: Float64Array;
|
|
68
78
|
__pair_index: PairUint32Map;
|
|
69
79
|
__live_slots: Uint32Array;
|
|
70
80
|
__live_pos: Int32Array;
|
|
@@ -137,10 +147,31 @@ export class ManifoldStore {
|
|
|
137
147
|
* @param {number} slot
|
|
138
148
|
*/
|
|
139
149
|
clear_contacts(slot: number): void;
|
|
150
|
+
/**
|
|
151
|
+
* Reset the contact count to zero so the next `set_contact(slot, 0, …)`
|
|
152
|
+
* lands at index 0, but DO NOT zero the data slab. Warm-start impulses
|
|
153
|
+
* (`j_n`, `j_t1`, `j_t2`) at each contact slot survive — the subsequent
|
|
154
|
+
* `set_contact` calls only overwrite the first 10 floats (position,
|
|
155
|
+
* normal, depth) per contact.
|
|
156
|
+
*
|
|
157
|
+
* Use this when narrowphase determines the pair still has overlap and
|
|
158
|
+
* is about to refill the slot with the new frame's contacts. Use
|
|
159
|
+
* {@link clear_contacts} instead when the pair has genuinely separated
|
|
160
|
+
* and the cached impulses should be evicted.
|
|
161
|
+
*
|
|
162
|
+
* @param {number} slot
|
|
163
|
+
*/
|
|
164
|
+
begin_refill(slot: number): void;
|
|
140
165
|
/**
|
|
141
166
|
* Write a contact point into a slot. Increments contact_count if `idx`
|
|
142
167
|
* exceeds the current count.
|
|
143
168
|
*
|
|
169
|
+
* Writes geometry (offsets 0..9) and the feature_id at offset 13.
|
|
170
|
+
* Deliberately does NOT touch the warm-start impulse fields (offsets
|
|
171
|
+
* 10..12) — the narrowphase match-and-merge pass uses this to refill
|
|
172
|
+
* a slot while preserving the cached impulses that the solver's
|
|
173
|
+
* warm-start needs.
|
|
174
|
+
*
|
|
144
175
|
* @param {number} slot
|
|
145
176
|
* @param {number} idx contact index in `[0, MAX_CONTACTS_PER_MANIFOLD)`
|
|
146
177
|
* @param {number} lax
|
|
@@ -153,8 +184,35 @@ export class ManifoldStore {
|
|
|
153
184
|
* @param {number} ny
|
|
154
185
|
* @param {number} nz
|
|
155
186
|
* @param {number} depth
|
|
187
|
+
* @param {number} feature_id stable cross-frame identifier of the
|
|
188
|
+
* geometric feature pair that produced this contact; 0 means
|
|
189
|
+
* "no info, fall back to position matching"
|
|
156
190
|
*/
|
|
157
|
-
set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number): void;
|
|
191
|
+
set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number, feature_id?: number): void;
|
|
192
|
+
/**
|
|
193
|
+
* Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
|
|
194
|
+
* index inside a slot. Called by the narrowphase match-and-merge pass
|
|
195
|
+
* after writing a candidate that did NOT match any prior-frame contact:
|
|
196
|
+
* the geometry at this index is "new" and inheriting impulses from
|
|
197
|
+
* whatever happened to be cached at this index last frame would
|
|
198
|
+
* destabilise the solver.
|
|
199
|
+
*
|
|
200
|
+
* Distinct from {@link clear_contacts} (which wipes the entire slot's
|
|
201
|
+
* data slab including geometry) — `clear_impulses` only touches the
|
|
202
|
+
* three impulse fields at one contact index, leaving the geometric
|
|
203
|
+
* fields and feature_id intact.
|
|
204
|
+
*
|
|
205
|
+
* @param {number} slot
|
|
206
|
+
* @param {number} idx
|
|
207
|
+
*/
|
|
208
|
+
clear_impulses(slot: number, idx: number): void;
|
|
209
|
+
/**
|
|
210
|
+
* Read the feature_id stored at one contact within a slot.
|
|
211
|
+
* @param {number} slot
|
|
212
|
+
* @param {number} idx
|
|
213
|
+
* @returns {number}
|
|
214
|
+
*/
|
|
215
|
+
feature_id_of(slot: number, idx: number): number;
|
|
158
216
|
/**
|
|
159
217
|
* Read a contact point. Writes 10 floats: lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth.
|
|
160
218
|
* @param {number} slot
|
|
@@ -184,6 +242,21 @@ export class ManifoldStore {
|
|
|
184
242
|
* @returns {Float64Array}
|
|
185
243
|
*/
|
|
186
244
|
get data_buffer(): Float64Array;
|
|
245
|
+
/**
|
|
246
|
+
* Per-slot cached GJK separating axis buffer. Hot-path GJK callers
|
|
247
|
+
* pass this together with {@link slot_axis_offset} to
|
|
248
|
+
* `gjk_with_axis(...)` so the cached axis seeds the next iteration.
|
|
249
|
+
* Buffer identity is stable until grow.
|
|
250
|
+
* @returns {Float64Array}
|
|
251
|
+
*/
|
|
252
|
+
get slot_axis_buffer(): Float64Array;
|
|
253
|
+
/**
|
|
254
|
+
* Float offset into {@link slot_axis_buffer} where this slot's
|
|
255
|
+
* cached axis lives (3 floats: x, y, z).
|
|
256
|
+
* @param {number} slot
|
|
257
|
+
* @returns {number}
|
|
258
|
+
*/
|
|
259
|
+
slot_axis_offset(slot: number): number;
|
|
187
260
|
/**
|
|
188
261
|
* Word offset into {@link data_buffer} where the first contact of `slot`
|
|
189
262
|
* begins. Subsequent contacts within the slot are at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C
|
|
1
|
+
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,6BAFU,MAAM,CAEiB;AAEjC;;;;GAIG;AACH,wCAFU,MAAM,CAE4B;AAE5C;;;GAGG;AACH,+BAFU,MAAM,CAE2D;AAyB3E;;;GAGG;AACH,mCAFU,MAAM,CAEuB;AAIvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAEI;;OAEG;IACH,+BAFW,MAAM,EA6BhB;IAzBG,mBAA+C;IAC/C,qBAAqB;IAErB,qBAAkE;IAClE,oBAAiE;IAQjE,0BAAwD;IAExD,4BAA0D;IAG1D,0BAAoD;IACpD,uBAAiD;IAEjD,qBAAqB;IAGrB,yBAAmD;IACnD,qBAAqB;IAGzB;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,aAJW,MAAM,OACN,MAAM,GACJ,MAAM,CA+BlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAFW,MAAM,QAQhB;IAED;;;;;;;;;;;;;OAaG;IACH,mBAFW,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,kBAhBW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,SACN,MAAM,eACN,MAAM,QAwBhB;IAED;;;;;;;;;;;;;;;OAeG;IACH,qBAHW,MAAM,OACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,oBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,kBALW,MAAM,OACN,MAAM,OACN,MAAM,EAAE,GAAC,YAAY,cACrB,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,oBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED;;;;;OAKG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,qCAEC;IAED;;;;;OAKG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,sBA6BC;IAED;;;OAGG;IACH,wBAcC;IAED;;;OAGG;IACH,uBAkBC;IAED;;OAEG;IACH,eA+BC;IAED;;;OAGG;IACH,oBAaC;IAED;;;OAGG;IACH,mBAqBC;CACJ;8BA9lBqD,2CAA2C"}
|