@woosh/meep-engine 2.140.0 → 2.142.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 (74) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +21 -0
  3. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -0
  4. package/src/core/geom/3d/quaternion/quat3_multiply.js +25 -0
  5. package/src/engine/control/first-person/prototype_first_person_controller.js +5 -0
  6. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  7. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +67 -42
  8. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts +12 -22
  9. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts.map +1 -1
  10. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +340 -186
  11. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts +44 -0
  12. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts.map +1 -0
  13. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +151 -0
  14. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts +14 -0
  15. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts.map +1 -0
  16. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.js +78 -0
  17. package/src/engine/physics/PLAN.md +705 -461
  18. package/src/engine/physics/REVIEW_002.md +151 -0
  19. package/src/engine/physics/REVIEW_003.md +166 -0
  20. package/src/engine/physics/constraint/DofMode.d.ts +28 -0
  21. package/src/engine/physics/constraint/DofMode.d.ts.map +1 -0
  22. package/src/engine/physics/constraint/DofMode.js +35 -0
  23. package/src/engine/physics/constraint/solve_constraints.d.ts +38 -0
  24. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -0
  25. package/src/engine/physics/constraint/solve_constraints.js +673 -0
  26. package/src/engine/physics/ecs/Joint.d.ts +294 -0
  27. package/src/engine/physics/ecs/Joint.d.ts.map +1 -0
  28. package/src/engine/physics/ecs/Joint.js +402 -0
  29. package/src/engine/physics/ecs/PhysicsSystem.d.ts +52 -0
  30. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  31. package/src/engine/physics/ecs/PhysicsSystem.js +126 -4
  32. package/src/engine/physics/fluid/FluidField.d.ts +14 -10
  33. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  34. package/src/engine/physics/fluid/FluidField.js +14 -10
  35. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  36. package/src/engine/physics/fluid/FluidSimulator.js +0 -1
  37. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +17 -10
  38. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  39. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +18 -11
  40. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +13 -10
  41. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  42. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +18 -13
  43. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +4 -3
  44. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  45. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +15 -11
  46. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +24 -22
  47. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  48. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +26 -22
  49. package/src/engine/physics/island/IslandBuilder.d.ts +4 -1
  50. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  51. package/src/engine/physics/island/IslandBuilder.js +33 -16
  52. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  53. package/src/engine/physics/narrowphase/box_box_manifold.js +27 -1
  54. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +33 -0
  55. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  56. package/src/engine/physics/narrowphase/narrowphase_step.js +75 -0
  57. package/src/engine/physics/narrowphase/ray_shapes.d.ts +66 -0
  58. package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +1 -0
  59. package/src/engine/physics/narrowphase/ray_shapes.js +187 -0
  60. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +16 -0
  61. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -0
  62. package/src/engine/physics/narrowphase/refine_ray_concave.js +145 -0
  63. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +39 -0
  64. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -0
  65. package/src/engine/physics/narrowphase/refine_ray_hit.js +78 -0
  66. package/src/engine/physics/queries/raycast.d.ts +11 -9
  67. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  68. package/src/engine/physics/queries/raycast.js +108 -159
  69. package/src/engine/physics/solver/solve_contacts.d.ts +28 -0
  70. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  71. package/src/engine/physics/solver/solve_contacts.js +169 -1
  72. package/src/engine/physics/vehicle/RaycastVehicle.d.ts +114 -0
  73. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -0
  74. package/src/engine/physics/vehicle/RaycastVehicle.js +333 -0
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Sentinel for {@link Joint#_jointId} before the joint is linked into a
3
+ * {@link PhysicsSystem}.
4
+ * @type {number}
5
+ */
6
+ export const JOINT_UNALLOCATED: number;
7
+ /**
8
+ * `entityB` value meaning "the world" — body A is constrained to a fixed
9
+ * world anchor rather than to a second body.
10
+ * @type {number}
11
+ */
12
+ export const JOINT_WORLD: number;
13
+ /**
14
+ * A configurable 6-DOF constraint between two bodies (or a body and the
15
+ * world). Connects body A's anchor frame to body B's; each of the 6 relative
16
+ * degrees of freedom (3 linear, 3 angular) is independently
17
+ * locked / free / limited / springy / motorised ({@link DofMode}). One
18
+ * constraint type expresses the whole joint taxonomy — see DofMode.
19
+ *
20
+ * The default configuration is a **ball-socket** (point-to-point): the three
21
+ * linear DOFs LOCKED (the two anchor points are held coincident), the three
22
+ * angular DOFs FREE. That alone gives chains, ropes, and pendulums.
23
+ *
24
+ * Anchors live in body-local frames. When `entityB === JOINT_WORLD`,
25
+ * `localAnchorB` is interpreted as a fixed **world** point instead of a
26
+ * local anchor on a second body.
27
+ *
28
+ * Solver state ({@link dofImpulse}, the `_*` fields) is owned by the
29
+ * PhysicsSystem; user code must not write it.
30
+ *
31
+ * @author Alex Goldring
32
+ * @copyright Company Named Limited (c) 2026
33
+ */
34
+ export class Joint {
35
+ /**
36
+ * Entity owning body A (the constraint's first body — always a real body).
37
+ * @type {number}
38
+ */
39
+ entityA: number;
40
+ /**
41
+ * Entity owning body B, or {@link JOINT_WORLD} to anchor A to the world.
42
+ * @type {number}
43
+ */
44
+ entityB: number;
45
+ /**
46
+ * Anchor point on body A, in A's local frame.
47
+ * @readonly
48
+ * @type {Vector3}
49
+ */
50
+ readonly localAnchorA: Vector3;
51
+ /**
52
+ * Anchor point on body B in B's local frame — or, when
53
+ * `entityB === JOINT_WORLD`, a fixed world-space anchor point.
54
+ * @readonly
55
+ * @type {Vector3}
56
+ */
57
+ readonly localAnchorB: Vector3;
58
+ /**
59
+ * Orientation of the joint's reference frame on body A, in A's local
60
+ * space (a unit quaternion; identity = the joint frame is the body
61
+ * frame). The 3 linear and 3 angular DOFs are expressed in this frame:
62
+ * locked/limited/etc. axes are the frame's axes, not world axes. For a
63
+ * hinge, the free angular axis is one of these frame axes — orient the
64
+ * frame so that axis is the hinge axis.
65
+ * @readonly
66
+ * @type {Quaternion}
67
+ */
68
+ readonly localBasisA: Quaternion;
69
+ /**
70
+ * Orientation of the joint's reference frame on body B, in B's local
71
+ * space. At rest a fully-locked joint holds frame B aligned to frame A.
72
+ * When `entityB === JOINT_WORLD`, the world frame is treated as identity
73
+ * (angular locks hold A's frame to world axes).
74
+ * @readonly
75
+ * @type {Quaternion}
76
+ */
77
+ readonly localBasisB: Quaternion;
78
+ /**
79
+ * Per-DOF mode, 6 entries: `[lin x, lin y, lin z, ang x, ang y, ang z]`.
80
+ * Default = ball-socket (linear locked, angular free).
81
+ * @readonly
82
+ * @type {Uint8Array}
83
+ */
84
+ readonly dofMode: Uint8Array;
85
+ /**
86
+ * How the three angular DOF positions are measured. `false` (default) uses
87
+ * the per-axis small-angle rotation vector — exact at the rest pose and
88
+ * cheap, correct for welds, hinges, and modest angular limits. `true`
89
+ * switches to the **swing-twist** decomposition: angular X is the *twist*
90
+ * (rotation about the bone / X axis) and angular Y/Z are the *swing* (tilt
91
+ * off it), each measured as an exact angle. Use it for wide cone-twist
92
+ * range-of-motion (ragdoll shoulders) where the small-angle measure
93
+ * under-reports large angles. See {@link asConeTwist}.
94
+ * @type {boolean}
95
+ */
96
+ swingTwist: boolean;
97
+ /**
98
+ * Lower / upper bounds per DOF, used by {@link DofMode.LIMITED}. Linear in
99
+ * metres, angular in radians. (Reserved — consumed once the LIMITED mode
100
+ * lands.)
101
+ * @readonly
102
+ * @type {Float64Array}
103
+ */
104
+ readonly dofLowerLimit: Float64Array;
105
+ /** @readonly @type {Float64Array} */
106
+ readonly dofUpperLimit: Float64Array;
107
+ /**
108
+ * Spring stiffness / damping per DOF, used by {@link DofMode.SPRING}.
109
+ * (Reserved — consumed once the SPRING mode lands.)
110
+ * @readonly
111
+ * @type {Float64Array}
112
+ */
113
+ readonly dofStiffness: Float64Array;
114
+ /** @readonly @type {Float64Array} */
115
+ readonly dofDamping: Float64Array;
116
+ /**
117
+ * Motor target velocity / max force per DOF, used by
118
+ * {@link DofMode.MOTOR}. (Reserved — consumed once the MOTOR mode lands.)
119
+ * @readonly
120
+ * @type {Float64Array}
121
+ */
122
+ readonly dofMotorTargetVelocity: Float64Array;
123
+ /** @readonly @type {Float64Array} */
124
+ readonly dofMotorMaxForce: Float64Array;
125
+ /**
126
+ * Accumulated constraint impulse per DOF — warm-start state carried across
127
+ * frames. System-owned.
128
+ * @readonly
129
+ * @type {Float64Array}
130
+ */
131
+ readonly dofImpulse: Float64Array;
132
+ /**
133
+ * Resolved packed body id of A, set at link time. System-private.
134
+ * @type {number}
135
+ */
136
+ _bodyIdA: number;
137
+ /**
138
+ * Resolved packed body id of B, or {@link JOINT_WORLD}. System-private.
139
+ * @type {number}
140
+ */
141
+ _bodyIdB: number;
142
+ /**
143
+ * Packed joint handle assigned at link. System-private.
144
+ * @type {number}
145
+ */
146
+ _jointId: number;
147
+ /**
148
+ * Configure this joint as a ball-socket (point-to-point) between two
149
+ * anchors. Linear DOFs locked, angular free — the default, provided as a
150
+ * named helper for clarity at call sites.
151
+ * @returns {Joint} this
152
+ */
153
+ asBallSocket(): Joint;
154
+ /**
155
+ * Configure as a weld (fixed) joint: all 6 DOFs locked, holding the two
156
+ * bodies rigidly in their relative pose.
157
+ * @returns {Joint} this
158
+ */
159
+ asWeld(): Joint;
160
+ /**
161
+ * Configure as a hinge (revolute): 3 linear + 2 angular locked, free to
162
+ * rotate about a single frame axis. Orient {@link localBasisA} /
163
+ * {@link localBasisB} so that frame axis is the desired hinge axis.
164
+ *
165
+ * @param {number} [freeAngularAxis] which frame axis is free: 0 = x
166
+ * (default), 1 = y, 2 = z.
167
+ * @returns {Joint} this
168
+ */
169
+ asHinge(freeAngularAxis?: number): Joint;
170
+ /**
171
+ * Configure as a prismatic (slider): 2 linear + 3 angular locked, free to
172
+ * translate along a single frame axis. Orient {@link localBasisA} /
173
+ * {@link localBasisB} so that frame axis is the desired slide direction.
174
+ *
175
+ * @param {number} [freeLinearAxis] which frame axis is free: 0 = x
176
+ * (default), 1 = y, 2 = z.
177
+ * @returns {Joint} this
178
+ */
179
+ asPrismatic(freeLinearAxis?: number): Joint;
180
+ /**
181
+ * Configure as a cone-twist (ragdoll) joint: a ball-socket point (3 linear
182
+ * locked) whose angular motion is a limited **twist** about the bone (frame
183
+ * X) and a limited **swing** cone off it (frame Y / Z). Enables
184
+ * {@link swingTwist} so the limits are measured as exact angles, holding
185
+ * accurately at the wide ranges ragdoll shoulders need.
186
+ *
187
+ * Orient {@link localBasisA} / {@link localBasisB} so frame X runs along the
188
+ * bone. The swing cone is box-shaped: independent Y and Z half-angles
189
+ * (`swingLimitZ` defaults to `swingLimitY` for a circular cone).
190
+ *
191
+ * @param {number} twistLower @param {number} twistUpper twist bounds, rad.
192
+ * @param {number} swingLimitY swing half-angle about frame Y, rad.
193
+ * @param {number} [swingLimitZ] swing half-angle about frame Z, rad
194
+ * (defaults to `swingLimitY`).
195
+ * @returns {Joint} this
196
+ */
197
+ asConeTwist(twistLower: number, twistUpper: number, swingLimitY: number, swingLimitZ?: number): Joint;
198
+ /**
199
+ * Limit a linear DOF to a travel range along its frame axis (slider
200
+ * end-stops): the DOF slides freely within `[lower, upper]` metres and is
201
+ * resisted at the stops. Sets {@link DofMode.LIMITED} on that axis.
202
+ *
203
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
204
+ * @param {number} lower @param {number} upper travel bounds in metres
205
+ * (signed anchor-A offset from anchor-B along the frame axis).
206
+ * @returns {Joint} this
207
+ */
208
+ setLinearLimit(axis: number, lower: number, upper: number): Joint;
209
+ /**
210
+ * Limit an angular DOF to a rotation range about its frame axis (joint
211
+ * range-of-motion): free within `[lower, upper]` radians, resisted at the
212
+ * stops. Sets {@link DofMode.LIMITED} on that angular axis.
213
+ *
214
+ * The angular position is a first-order (small-angle) measure, so the
215
+ * effective stop is accurate for modest ranges (hinge / slider end-stops)
216
+ * but under-reports large angles — wide cones (ragdoll shoulders) want the
217
+ * swing-twist decomposition (already available as
218
+ * `Quaternion.computeSwingAndTwist` / `computeTwistAngle`). See the solver
219
+ * module docs.
220
+ *
221
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
222
+ * @param {number} lower @param {number} upper rotation bounds in radians.
223
+ * @returns {Joint} this
224
+ */
225
+ setAngularLimit(axis: number, lower: number, upper: number): Joint;
226
+ /**
227
+ * Drive a linear DOF at a target speed along its frame axis (piston,
228
+ * conveyor, powered slider). A force-limited velocity servo: it pulls the
229
+ * relative velocity toward `targetVelocity` as hard as `maxForce` allows.
230
+ * Sets {@link DofMode.MOTOR} on that axis.
231
+ *
232
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
233
+ * @param {number} targetVelocity desired relative anchor speed of A w.r.t.
234
+ * B along the frame axis, m/s (sign convention A − B).
235
+ * @param {number} maxForce force budget in newtons (`Infinity` for an
236
+ * ideal velocity drive; `0` makes the motor inert).
237
+ * @returns {Joint} this
238
+ */
239
+ setLinearMotor(axis: number, targetVelocity: number, maxForce: number): Joint;
240
+ /**
241
+ * Drive an angular DOF at a target rate about its frame axis (wheel drive,
242
+ * powered hinge / door, turntable). A force-(torque-)limited velocity servo
243
+ * pulling the relative angular velocity toward `targetVelocity`. Sets
244
+ * {@link DofMode.MOTOR} on that angular axis.
245
+ *
246
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
247
+ * @param {number} targetVelocity desired relative angular rate of B w.r.t.
248
+ * A about the frame axis, rad/s (sign convention B − A — so for a
249
+ * world-anchored motor a positive target spins body A negatively about
250
+ * the axis).
251
+ * @param {number} maxForce torque budget in newton-metres (`Infinity` for
252
+ * an ideal drive; `0` makes the motor inert).
253
+ * @returns {Joint} this
254
+ */
255
+ setAngularMotor(axis: number, targetVelocity: number, maxForce: number): Joint;
256
+ /**
257
+ * Make a linear DOF a compliant spring along its frame axis (suspension,
258
+ * bungee, soft-return slider). The spring drives the DOF toward its zero
259
+ * (the rest pose — place the anchors / basis so the relaxed position is
260
+ * `C·axis = 0`) with stiffness and damping, yielding under load rather than
261
+ * holding rigid. Sets {@link DofMode.SPRING} on that axis.
262
+ *
263
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
264
+ * @param {number} stiffness spring constant `k`, N/m (0 ⇒ pure damper).
265
+ * @param {number} damping damping coefficient `c`, N·s/m (0 ⇒ undamped).
266
+ * @returns {Joint} this
267
+ */
268
+ setLinearSpring(axis: number, stiffness: number, damping: number): Joint;
269
+ /**
270
+ * Make an angular DOF a compliant torsional spring about its frame axis
271
+ * (soft-return hinge, sway bar, soft ragdoll joint). Drives the relative
272
+ * rotation about the axis toward zero with stiffness and damping. Sets
273
+ * {@link DofMode.SPRING} on that angular axis. Uses the small-angle measure
274
+ * (see the solver module docs), so it is for modest ranges.
275
+ *
276
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
277
+ * @param {number} stiffness torsional spring constant `k`, N·m/rad
278
+ * (0 ⇒ pure damper).
279
+ * @param {number} damping torsional damping, N·m·s/rad (0 ⇒ undamped).
280
+ * @returns {Joint} this
281
+ */
282
+ setAngularSpring(axis: number, stiffness: number, damping: number): Joint;
283
+ /**
284
+ * @readonly
285
+ * @type {boolean}
286
+ */
287
+ readonly isJoint: boolean;
288
+ }
289
+ export namespace Joint {
290
+ let typeName: string;
291
+ }
292
+ import Vector3 from "../../../core/geom/Vector3.js";
293
+ import Quaternion from "../../../core/geom/Quaternion.js";
294
+ //# sourceMappingURL=Joint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Joint.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/Joint.js"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,gCAFU,MAAM,CAEoB;AAEpC;;;;GAIG;AACH,0BAFU,MAAM,CAEc;AAE9B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;IAEI;;;OAGG;IACH,SAFU,MAAM,CAEH;IAEb;;;OAGG;IACH,SAFU,MAAM,CAEM;IAEtB;;;;OAIG;IACH,uBAFU,OAAO,CAEmB;IAEpC;;;;;OAKG;IACH,uBAFU,OAAO,CAEmB;IAEpC;;;;;;;;;OASG;IACH,sBAFU,UAAU,CAEW;IAE/B;;;;;;;OAOG;IACH,sBAFU,UAAU,CAEW;IAE/B;;;;;OAKG;IACH,kBAFU,UAAU,CAKjB;IAEH;;;;;;;;;;OAUG;IACH,YAFU,OAAO,CAEE;IAEnB;;;;;;OAMG;IACH,wBAFU,YAAY,CAEc;IACpC,qCAAqC;IACrC,wBADqB,YAAY,CACG;IAEpC;;;;;OAKG;IACH,uBAFU,YAAY,CAEa;IACnC,qCAAqC;IACrC,qBADqB,YAAY,CACA;IAEjC;;;;;OAKG;IACH,iCAFU,YAAY,CAEuB;IAC7C,qCAAqC;IACrC,2BADqB,YAAY,CACM;IAEvC;;;;;OAKG;IACH,qBAFU,YAAY,CAEW;IAEjC;;;OAGG;IACH,UAFU,MAAM,CAEF;IACd;;;OAGG;IACH,UAFU,MAAM,CAEO;IACvB;;;OAGG;IACH,UAFU,MAAM,CAEa;IAE7B;;;;;OAKG;IACH,gBAFa,KAAK,CAUjB;IAED;;;;OAIG;IACH,UAFa,KAAK,CAKjB;IAED;;;;;;;;OAQG;IACH,0BAJW,MAAM,GAEJ,KAAK,CAWjB;IAED;;;;;;;;OAQG;IACH,6BAJW,MAAM,GAEJ,KAAK,CAWjB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,wBANW,MAAM,cAAqB,MAAM,eACjC,MAAM,gBACN,MAAM,GAEJ,KAAK,CAiBjB;IAED;;;;;;;;;OASG;IACH,qBALW,MAAM,SACN,MAAM,SAAgB,MAAM,GAE1B,KAAK,CAOjB;IAED;;;;;;;;;;;;;;;OAeG;IACH,sBAJW,MAAM,SACN,MAAM,SAAgB,MAAM,GAC1B,KAAK,CAOjB;IAED;;;;;;;;;;;;OAYG;IACH,qBAPW,MAAM,kBACN,MAAM,YAEN,MAAM,GAEJ,KAAK,CAOjB;IAED;;;;;;;;;;;;;;OAcG;IACH,sBATW,MAAM,kBACN,MAAM,YAIN,MAAM,GAEJ,KAAK,CAOjB;IAED;;;;;;;;;;;OAWG;IACH,sBALW,MAAM,aACN,MAAM,WACN,MAAM,GACJ,KAAK,CAOjB;IAED;;;;;;;;;;;;OAYG;IACH,uBANW,MAAM,aACN,MAAM,WAEN,MAAM,GACJ,KAAK,CAOjB;IASL;;;OAGG;IACH,kBAFU,OAAO,CAEM;CAZtB;;kBAIS,MAAM;;oBAzYI,+BAA+B;uBAC5B,kCAAkC"}
@@ -0,0 +1,402 @@
1
+ import Vector3 from "../../../core/geom/Vector3.js";
2
+ import Quaternion from "../../../core/geom/Quaternion.js";
3
+ import { DofMode } from "../constraint/DofMode.js";
4
+
5
+ /**
6
+ * Sentinel for {@link Joint#_jointId} before the joint is linked into a
7
+ * {@link PhysicsSystem}.
8
+ * @type {number}
9
+ */
10
+ export const JOINT_UNALLOCATED = -1;
11
+
12
+ /**
13
+ * `entityB` value meaning "the world" — body A is constrained to a fixed
14
+ * world anchor rather than to a second body.
15
+ * @type {number}
16
+ */
17
+ export const JOINT_WORLD = -1;
18
+
19
+ /**
20
+ * A configurable 6-DOF constraint between two bodies (or a body and the
21
+ * world). Connects body A's anchor frame to body B's; each of the 6 relative
22
+ * degrees of freedom (3 linear, 3 angular) is independently
23
+ * locked / free / limited / springy / motorised ({@link DofMode}). One
24
+ * constraint type expresses the whole joint taxonomy — see DofMode.
25
+ *
26
+ * The default configuration is a **ball-socket** (point-to-point): the three
27
+ * linear DOFs LOCKED (the two anchor points are held coincident), the three
28
+ * angular DOFs FREE. That alone gives chains, ropes, and pendulums.
29
+ *
30
+ * Anchors live in body-local frames. When `entityB === JOINT_WORLD`,
31
+ * `localAnchorB` is interpreted as a fixed **world** point instead of a
32
+ * local anchor on a second body.
33
+ *
34
+ * Solver state ({@link dofImpulse}, the `_*` fields) is owned by the
35
+ * PhysicsSystem; user code must not write it.
36
+ *
37
+ * @author Alex Goldring
38
+ * @copyright Company Named Limited (c) 2026
39
+ */
40
+ export class Joint {
41
+
42
+ /**
43
+ * Entity owning body A (the constraint's first body — always a real body).
44
+ * @type {number}
45
+ */
46
+ entityA = -1;
47
+
48
+ /**
49
+ * Entity owning body B, or {@link JOINT_WORLD} to anchor A to the world.
50
+ * @type {number}
51
+ */
52
+ entityB = JOINT_WORLD;
53
+
54
+ /**
55
+ * Anchor point on body A, in A's local frame.
56
+ * @readonly
57
+ * @type {Vector3}
58
+ */
59
+ localAnchorA = new Vector3(0, 0, 0);
60
+
61
+ /**
62
+ * Anchor point on body B in B's local frame — or, when
63
+ * `entityB === JOINT_WORLD`, a fixed world-space anchor point.
64
+ * @readonly
65
+ * @type {Vector3}
66
+ */
67
+ localAnchorB = new Vector3(0, 0, 0);
68
+
69
+ /**
70
+ * Orientation of the joint's reference frame on body A, in A's local
71
+ * space (a unit quaternion; identity = the joint frame is the body
72
+ * frame). The 3 linear and 3 angular DOFs are expressed in this frame:
73
+ * locked/limited/etc. axes are the frame's axes, not world axes. For a
74
+ * hinge, the free angular axis is one of these frame axes — orient the
75
+ * frame so that axis is the hinge axis.
76
+ * @readonly
77
+ * @type {Quaternion}
78
+ */
79
+ localBasisA = new Quaternion();
80
+
81
+ /**
82
+ * Orientation of the joint's reference frame on body B, in B's local
83
+ * space. At rest a fully-locked joint holds frame B aligned to frame A.
84
+ * When `entityB === JOINT_WORLD`, the world frame is treated as identity
85
+ * (angular locks hold A's frame to world axes).
86
+ * @readonly
87
+ * @type {Quaternion}
88
+ */
89
+ localBasisB = new Quaternion();
90
+
91
+ /**
92
+ * Per-DOF mode, 6 entries: `[lin x, lin y, lin z, ang x, ang y, ang z]`.
93
+ * Default = ball-socket (linear locked, angular free).
94
+ * @readonly
95
+ * @type {Uint8Array}
96
+ */
97
+ dofMode = Uint8Array.from([
98
+ DofMode.LOCKED, DofMode.LOCKED, DofMode.LOCKED,
99
+ DofMode.FREE, DofMode.FREE, DofMode.FREE,
100
+ ]);
101
+
102
+ /**
103
+ * How the three angular DOF positions are measured. `false` (default) uses
104
+ * the per-axis small-angle rotation vector — exact at the rest pose and
105
+ * cheap, correct for welds, hinges, and modest angular limits. `true`
106
+ * switches to the **swing-twist** decomposition: angular X is the *twist*
107
+ * (rotation about the bone / X axis) and angular Y/Z are the *swing* (tilt
108
+ * off it), each measured as an exact angle. Use it for wide cone-twist
109
+ * range-of-motion (ragdoll shoulders) where the small-angle measure
110
+ * under-reports large angles. See {@link asConeTwist}.
111
+ * @type {boolean}
112
+ */
113
+ swingTwist = false;
114
+
115
+ /**
116
+ * Lower / upper bounds per DOF, used by {@link DofMode.LIMITED}. Linear in
117
+ * metres, angular in radians. (Reserved — consumed once the LIMITED mode
118
+ * lands.)
119
+ * @readonly
120
+ * @type {Float64Array}
121
+ */
122
+ dofLowerLimit = new Float64Array(6);
123
+ /** @readonly @type {Float64Array} */
124
+ dofUpperLimit = new Float64Array(6);
125
+
126
+ /**
127
+ * Spring stiffness / damping per DOF, used by {@link DofMode.SPRING}.
128
+ * (Reserved — consumed once the SPRING mode lands.)
129
+ * @readonly
130
+ * @type {Float64Array}
131
+ */
132
+ dofStiffness = new Float64Array(6);
133
+ /** @readonly @type {Float64Array} */
134
+ dofDamping = new Float64Array(6);
135
+
136
+ /**
137
+ * Motor target velocity / max force per DOF, used by
138
+ * {@link DofMode.MOTOR}. (Reserved — consumed once the MOTOR mode lands.)
139
+ * @readonly
140
+ * @type {Float64Array}
141
+ */
142
+ dofMotorTargetVelocity = new Float64Array(6);
143
+ /** @readonly @type {Float64Array} */
144
+ dofMotorMaxForce = new Float64Array(6);
145
+
146
+ /**
147
+ * Accumulated constraint impulse per DOF — warm-start state carried across
148
+ * frames. System-owned.
149
+ * @readonly
150
+ * @type {Float64Array}
151
+ */
152
+ dofImpulse = new Float64Array(6);
153
+
154
+ /**
155
+ * Resolved packed body id of A, set at link time. System-private.
156
+ * @type {number}
157
+ */
158
+ _bodyIdA = -1;
159
+ /**
160
+ * Resolved packed body id of B, or {@link JOINT_WORLD}. System-private.
161
+ * @type {number}
162
+ */
163
+ _bodyIdB = JOINT_WORLD;
164
+ /**
165
+ * Packed joint handle assigned at link. System-private.
166
+ * @type {number}
167
+ */
168
+ _jointId = JOINT_UNALLOCATED;
169
+
170
+ /**
171
+ * Configure this joint as a ball-socket (point-to-point) between two
172
+ * anchors. Linear DOFs locked, angular free — the default, provided as a
173
+ * named helper for clarity at call sites.
174
+ * @returns {Joint} this
175
+ */
176
+ asBallSocket() {
177
+ this.dofMode[0] = DofMode.LOCKED;
178
+ this.dofMode[1] = DofMode.LOCKED;
179
+ this.dofMode[2] = DofMode.LOCKED;
180
+ this.dofMode[3] = DofMode.FREE;
181
+ this.dofMode[4] = DofMode.FREE;
182
+ this.dofMode[5] = DofMode.FREE;
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Configure as a weld (fixed) joint: all 6 DOFs locked, holding the two
188
+ * bodies rigidly in their relative pose.
189
+ * @returns {Joint} this
190
+ */
191
+ asWeld() {
192
+ for (let i = 0; i < 6; i++) this.dofMode[i] = DofMode.LOCKED;
193
+ return this;
194
+ }
195
+
196
+ /**
197
+ * Configure as a hinge (revolute): 3 linear + 2 angular locked, free to
198
+ * rotate about a single frame axis. Orient {@link localBasisA} /
199
+ * {@link localBasisB} so that frame axis is the desired hinge axis.
200
+ *
201
+ * @param {number} [freeAngularAxis] which frame axis is free: 0 = x
202
+ * (default), 1 = y, 2 = z.
203
+ * @returns {Joint} this
204
+ */
205
+ asHinge(freeAngularAxis = 0) {
206
+ this.dofMode[0] = DofMode.LOCKED;
207
+ this.dofMode[1] = DofMode.LOCKED;
208
+ this.dofMode[2] = DofMode.LOCKED;
209
+ this.dofMode[3] = DofMode.LOCKED;
210
+ this.dofMode[4] = DofMode.LOCKED;
211
+ this.dofMode[5] = DofMode.LOCKED;
212
+ this.dofMode[3 + freeAngularAxis] = DofMode.FREE;
213
+ return this;
214
+ }
215
+
216
+ /**
217
+ * Configure as a prismatic (slider): 2 linear + 3 angular locked, free to
218
+ * translate along a single frame axis. Orient {@link localBasisA} /
219
+ * {@link localBasisB} so that frame axis is the desired slide direction.
220
+ *
221
+ * @param {number} [freeLinearAxis] which frame axis is free: 0 = x
222
+ * (default), 1 = y, 2 = z.
223
+ * @returns {Joint} this
224
+ */
225
+ asPrismatic(freeLinearAxis = 0) {
226
+ this.dofMode[0] = DofMode.LOCKED;
227
+ this.dofMode[1] = DofMode.LOCKED;
228
+ this.dofMode[2] = DofMode.LOCKED;
229
+ this.dofMode[freeLinearAxis] = DofMode.FREE;
230
+ this.dofMode[3] = DofMode.LOCKED;
231
+ this.dofMode[4] = DofMode.LOCKED;
232
+ this.dofMode[5] = DofMode.LOCKED;
233
+ return this;
234
+ }
235
+
236
+ /**
237
+ * Configure as a cone-twist (ragdoll) joint: a ball-socket point (3 linear
238
+ * locked) whose angular motion is a limited **twist** about the bone (frame
239
+ * X) and a limited **swing** cone off it (frame Y / Z). Enables
240
+ * {@link swingTwist} so the limits are measured as exact angles, holding
241
+ * accurately at the wide ranges ragdoll shoulders need.
242
+ *
243
+ * Orient {@link localBasisA} / {@link localBasisB} so frame X runs along the
244
+ * bone. The swing cone is box-shaped: independent Y and Z half-angles
245
+ * (`swingLimitZ` defaults to `swingLimitY` for a circular cone).
246
+ *
247
+ * @param {number} twistLower @param {number} twistUpper twist bounds, rad.
248
+ * @param {number} swingLimitY swing half-angle about frame Y, rad.
249
+ * @param {number} [swingLimitZ] swing half-angle about frame Z, rad
250
+ * (defaults to `swingLimitY`).
251
+ * @returns {Joint} this
252
+ */
253
+ asConeTwist(twistLower, twistUpper, swingLimitY, swingLimitZ = swingLimitY) {
254
+ this.dofMode[0] = DofMode.LOCKED;
255
+ this.dofMode[1] = DofMode.LOCKED;
256
+ this.dofMode[2] = DofMode.LOCKED;
257
+ this.swingTwist = true;
258
+ this.dofMode[3] = DofMode.LIMITED;
259
+ this.dofLowerLimit[3] = twistLower;
260
+ this.dofUpperLimit[3] = twistUpper;
261
+ this.dofMode[4] = DofMode.LIMITED;
262
+ this.dofLowerLimit[4] = -swingLimitY;
263
+ this.dofUpperLimit[4] = swingLimitY;
264
+ this.dofMode[5] = DofMode.LIMITED;
265
+ this.dofLowerLimit[5] = -swingLimitZ;
266
+ this.dofUpperLimit[5] = swingLimitZ;
267
+ return this;
268
+ }
269
+
270
+ /**
271
+ * Limit a linear DOF to a travel range along its frame axis (slider
272
+ * end-stops): the DOF slides freely within `[lower, upper]` metres and is
273
+ * resisted at the stops. Sets {@link DofMode.LIMITED} on that axis.
274
+ *
275
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
276
+ * @param {number} lower @param {number} upper travel bounds in metres
277
+ * (signed anchor-A offset from anchor-B along the frame axis).
278
+ * @returns {Joint} this
279
+ */
280
+ setLinearLimit(axis, lower, upper) {
281
+ this.dofMode[axis] = DofMode.LIMITED;
282
+ this.dofLowerLimit[axis] = lower;
283
+ this.dofUpperLimit[axis] = upper;
284
+ return this;
285
+ }
286
+
287
+ /**
288
+ * Limit an angular DOF to a rotation range about its frame axis (joint
289
+ * range-of-motion): free within `[lower, upper]` radians, resisted at the
290
+ * stops. Sets {@link DofMode.LIMITED} on that angular axis.
291
+ *
292
+ * The angular position is a first-order (small-angle) measure, so the
293
+ * effective stop is accurate for modest ranges (hinge / slider end-stops)
294
+ * but under-reports large angles — wide cones (ragdoll shoulders) want the
295
+ * swing-twist decomposition (already available as
296
+ * `Quaternion.computeSwingAndTwist` / `computeTwistAngle`). See the solver
297
+ * module docs.
298
+ *
299
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
300
+ * @param {number} lower @param {number} upper rotation bounds in radians.
301
+ * @returns {Joint} this
302
+ */
303
+ setAngularLimit(axis, lower, upper) {
304
+ this.dofMode[3 + axis] = DofMode.LIMITED;
305
+ this.dofLowerLimit[3 + axis] = lower;
306
+ this.dofUpperLimit[3 + axis] = upper;
307
+ return this;
308
+ }
309
+
310
+ /**
311
+ * Drive a linear DOF at a target speed along its frame axis (piston,
312
+ * conveyor, powered slider). A force-limited velocity servo: it pulls the
313
+ * relative velocity toward `targetVelocity` as hard as `maxForce` allows.
314
+ * Sets {@link DofMode.MOTOR} on that axis.
315
+ *
316
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
317
+ * @param {number} targetVelocity desired relative anchor speed of A w.r.t.
318
+ * B along the frame axis, m/s (sign convention A − B).
319
+ * @param {number} maxForce force budget in newtons (`Infinity` for an
320
+ * ideal velocity drive; `0` makes the motor inert).
321
+ * @returns {Joint} this
322
+ */
323
+ setLinearMotor(axis, targetVelocity, maxForce) {
324
+ this.dofMode[axis] = DofMode.MOTOR;
325
+ this.dofMotorTargetVelocity[axis] = targetVelocity;
326
+ this.dofMotorMaxForce[axis] = maxForce;
327
+ return this;
328
+ }
329
+
330
+ /**
331
+ * Drive an angular DOF at a target rate about its frame axis (wheel drive,
332
+ * powered hinge / door, turntable). A force-(torque-)limited velocity servo
333
+ * pulling the relative angular velocity toward `targetVelocity`. Sets
334
+ * {@link DofMode.MOTOR} on that angular axis.
335
+ *
336
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
337
+ * @param {number} targetVelocity desired relative angular rate of B w.r.t.
338
+ * A about the frame axis, rad/s (sign convention B − A — so for a
339
+ * world-anchored motor a positive target spins body A negatively about
340
+ * the axis).
341
+ * @param {number} maxForce torque budget in newton-metres (`Infinity` for
342
+ * an ideal drive; `0` makes the motor inert).
343
+ * @returns {Joint} this
344
+ */
345
+ setAngularMotor(axis, targetVelocity, maxForce) {
346
+ this.dofMode[3 + axis] = DofMode.MOTOR;
347
+ this.dofMotorTargetVelocity[3 + axis] = targetVelocity;
348
+ this.dofMotorMaxForce[3 + axis] = maxForce;
349
+ return this;
350
+ }
351
+
352
+ /**
353
+ * Make a linear DOF a compliant spring along its frame axis (suspension,
354
+ * bungee, soft-return slider). The spring drives the DOF toward its zero
355
+ * (the rest pose — place the anchors / basis so the relaxed position is
356
+ * `C·axis = 0`) with stiffness and damping, yielding under load rather than
357
+ * holding rigid. Sets {@link DofMode.SPRING} on that axis.
358
+ *
359
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
360
+ * @param {number} stiffness spring constant `k`, N/m (0 ⇒ pure damper).
361
+ * @param {number} damping damping coefficient `c`, N·s/m (0 ⇒ undamped).
362
+ * @returns {Joint} this
363
+ */
364
+ setLinearSpring(axis, stiffness, damping) {
365
+ this.dofMode[axis] = DofMode.SPRING;
366
+ this.dofStiffness[axis] = stiffness;
367
+ this.dofDamping[axis] = damping;
368
+ return this;
369
+ }
370
+
371
+ /**
372
+ * Make an angular DOF a compliant torsional spring about its frame axis
373
+ * (soft-return hinge, sway bar, soft ragdoll joint). Drives the relative
374
+ * rotation about the axis toward zero with stiffness and damping. Sets
375
+ * {@link DofMode.SPRING} on that angular axis. Uses the small-angle measure
376
+ * (see the solver module docs), so it is for modest ranges.
377
+ *
378
+ * @param {number} axis frame axis: 0 = x, 1 = y, 2 = z.
379
+ * @param {number} stiffness torsional spring constant `k`, N·m/rad
380
+ * (0 ⇒ pure damper).
381
+ * @param {number} damping torsional damping, N·m·s/rad (0 ⇒ undamped).
382
+ * @returns {Joint} this
383
+ */
384
+ setAngularSpring(axis, stiffness, damping) {
385
+ this.dofMode[3 + axis] = DofMode.SPRING;
386
+ this.dofStiffness[3 + axis] = stiffness;
387
+ this.dofDamping[3 + axis] = damping;
388
+ return this;
389
+ }
390
+ }
391
+
392
+ /**
393
+ * @readonly
394
+ * @type {string}
395
+ */
396
+ Joint.typeName = "Joint";
397
+
398
+ /**
399
+ * @readonly
400
+ * @type {boolean}
401
+ */
402
+ Joint.prototype.isJoint = true;