@woosh/meep-engine 2.141.0 → 2.143.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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +1 -1
  3. package/src/core/geom/3d/shape/CapsuleShape3D.js +1 -1
  4. package/src/core/geom/3d/shape/SphereShape3D.d.ts +47 -0
  5. package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -0
  6. package/src/core/geom/3d/shape/SphereShape3D.js +127 -0
  7. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +30 -18
  8. package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
  9. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -92
  10. package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
  11. package/src/core/geom/3d/shape/json/shape_to_type.js +4 -2
  12. package/src/core/geom/3d/shape/json/type_adapters.d.ts +12 -3
  13. package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
  14. package/src/core/geom/3d/shape/json/type_adapters.js +16 -4
  15. package/src/core/geom/3d/shape/util/shape_to_visual_entity.js +2 -2
  16. package/src/engine/control/first-person/DESIGN_COLLISION.md +255 -0
  17. package/src/engine/control/first-person/prototype_first_person_controller.js +5 -0
  18. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  19. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +70 -43
  20. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts +12 -22
  21. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts.map +1 -1
  22. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +345 -186
  23. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts +44 -0
  24. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts.map +1 -0
  25. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +151 -0
  26. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts +14 -0
  27. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts.map +1 -0
  28. package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.js +78 -0
  29. package/src/engine/physics/PLAN.md +705 -578
  30. package/src/engine/physics/REVIEW_003.md +166 -0
  31. package/src/engine/physics/constraint/solve_constraints.d.ts +24 -2
  32. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  33. package/src/engine/physics/constraint/solve_constraints.js +402 -165
  34. package/src/engine/physics/ecs/Joint.d.ts +115 -0
  35. package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
  36. package/src/engine/physics/ecs/Joint.js +168 -0
  37. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +29 -0
  38. package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -0
  39. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -0
  40. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  41. package/src/engine/physics/narrowphase/narrowphase_step.js +20 -13
  42. package/src/engine/physics/narrowphase/ray_shapes.d.ts +66 -0
  43. package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +1 -0
  44. package/src/engine/physics/narrowphase/ray_shapes.js +187 -0
  45. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +16 -0
  46. package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -0
  47. package/src/engine/physics/narrowphase/refine_ray_concave.js +145 -0
  48. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +39 -0
  49. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -0
  50. package/src/engine/physics/narrowphase/refine_ray_hit.js +78 -0
  51. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +8 -7
  52. package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -1
  53. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +8 -7
  54. package/src/engine/physics/queries/raycast.d.ts +11 -9
  55. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  56. package/src/engine/physics/queries/raycast.js +108 -159
  57. package/src/engine/physics/vehicle/RaycastVehicle.d.ts +114 -0
  58. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -0
  59. package/src/engine/physics/vehicle/RaycastVehicle.js +333 -0
@@ -82,6 +82,18 @@ export class Joint {
82
82
  * @type {Uint8Array}
83
83
  */
84
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;
85
97
  /**
86
98
  * Lower / upper bounds per DOF, used by {@link DofMode.LIMITED}. Linear in
87
99
  * metres, angular in radians. (Reserved — consumed once the LIMITED mode
@@ -165,6 +177,109 @@ export class Joint {
165
177
  * @returns {Joint} this
166
178
  */
167
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;
168
283
  /**
169
284
  * @readonly
170
285
  * @type {boolean}
@@ -1 +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;;;;;;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;IASL;;;OAGG;IACH,kBAFU,OAAO,CAEM;CAZtB;;kBAIS,MAAM;;oBAjOI,+BAA+B;uBAC5B,kCAAkC"}
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"}
@@ -99,6 +99,19 @@ export class Joint {
99
99
  DofMode.FREE, DofMode.FREE, DofMode.FREE,
100
100
  ]);
101
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
+
102
115
  /**
103
116
  * Lower / upper bounds per DOF, used by {@link DofMode.LIMITED}. Linear in
104
117
  * metres, angular in radians. (Reserved — consumed once the LIMITED mode
@@ -219,6 +232,161 @@ export class Joint {
219
232
  this.dofMode[5] = DofMode.LOCKED;
220
233
  return this;
221
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
+ }
222
390
  }
223
391
 
224
392
  /**
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Binary serialization for {@link Joint}.
3
+ *
4
+ * Writes the configurable / persistent fields: the two anchor entities, the
5
+ * local anchor points and basis frames, and the per-DOF mode + limit / spring /
6
+ * motor config. Transient runtime state owned by the {@link PhysicsSystem} —
7
+ * the warm-start accumulator (`dofImpulse`) and the resolved `_bodyIdA` /
8
+ * `_bodyIdB` / `_jointId` — is deliberately excluded; it is recomputed on
9
+ * `link_joint`, and serialising it would tie the stream to a particular world
10
+ * instance.
11
+ */
12
+ export class JointSerializationAdapter extends BinaryClassSerializationAdapter<any> {
13
+ constructor();
14
+ klass: typeof Joint;
15
+ version: number;
16
+ /**
17
+ * @param {BinaryBuffer} buffer
18
+ * @param {Joint} value
19
+ */
20
+ serialize(buffer: BinaryBuffer, value: Joint): void;
21
+ /**
22
+ * @param {BinaryBuffer} buffer
23
+ * @param {Joint} value
24
+ */
25
+ deserialize(buffer: BinaryBuffer, value: Joint): void;
26
+ }
27
+ import { BinaryClassSerializationAdapter } from "../../ecs/storage/binary/BinaryClassSerializationAdapter.js";
28
+ import { Joint } from "./Joint.js";
29
+ //# sourceMappingURL=JointSerializationAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JointSerializationAdapter.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/JointSerializationAdapter.js"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH;;IAEI,oBAAc;IACd,gBAAY;IAEZ;;;OAGG;IACH,uCAFW,KAAK,QAwBf;IAED;;;OAGG;IACH,yCAFW,KAAK,QAqBf;CACJ;gDAvE+C,6DAA6D;sBACvF,YAAY"}
@@ -0,0 +1,72 @@
1
+ import { BinaryClassSerializationAdapter } from "../../ecs/storage/binary/BinaryClassSerializationAdapter.js";
2
+ import { Joint } from "./Joint.js";
3
+
4
+ /**
5
+ * Binary serialization for {@link Joint}.
6
+ *
7
+ * Writes the configurable / persistent fields: the two anchor entities, the
8
+ * local anchor points and basis frames, and the per-DOF mode + limit / spring /
9
+ * motor config. Transient runtime state owned by the {@link PhysicsSystem} —
10
+ * the warm-start accumulator (`dofImpulse`) and the resolved `_bodyIdA` /
11
+ * `_bodyIdB` / `_jointId` — is deliberately excluded; it is recomputed on
12
+ * `link_joint`, and serialising it would tie the stream to a particular world
13
+ * instance.
14
+ */
15
+ export class JointSerializationAdapter extends BinaryClassSerializationAdapter {
16
+
17
+ klass = Joint;
18
+ version = 0;
19
+
20
+ /**
21
+ * @param {BinaryBuffer} buffer
22
+ * @param {Joint} value
23
+ */
24
+ serialize(buffer, value) {
25
+ // Anchor entities — entityB may be JOINT_WORLD (−1), so signed.
26
+ buffer.writeInt32(value.entityA);
27
+ buffer.writeInt32(value.entityB);
28
+
29
+ const la = value.localAnchorA, lb = value.localAnchorB;
30
+ buffer.writeFloat64(la.x); buffer.writeFloat64(la.y); buffer.writeFloat64(la.z);
31
+ buffer.writeFloat64(lb.x); buffer.writeFloat64(lb.y); buffer.writeFloat64(lb.z);
32
+
33
+ const ba = value.localBasisA, bb = value.localBasisB;
34
+ buffer.writeFloat64(ba[0]); buffer.writeFloat64(ba[1]); buffer.writeFloat64(ba[2]); buffer.writeFloat64(ba[3]);
35
+ buffer.writeFloat64(bb[0]); buffer.writeFloat64(bb[1]); buffer.writeFloat64(bb[2]); buffer.writeFloat64(bb[3]);
36
+
37
+ for (let i = 0; i < 6; i++) buffer.writeUint8(value.dofMode[i]);
38
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofLowerLimit[i]);
39
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofUpperLimit[i]);
40
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofStiffness[i]);
41
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofDamping[i]);
42
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofMotorTargetVelocity[i]);
43
+ for (let i = 0; i < 6; i++) buffer.writeFloat64(value.dofMotorMaxForce[i]);
44
+
45
+ buffer.writeUint8(value.swingTwist ? 1 : 0);
46
+ }
47
+
48
+ /**
49
+ * @param {BinaryBuffer} buffer
50
+ * @param {Joint} value
51
+ */
52
+ deserialize(buffer, value) {
53
+ value.entityA = buffer.readInt32();
54
+ value.entityB = buffer.readInt32();
55
+
56
+ value.localAnchorA.set(buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64());
57
+ value.localAnchorB.set(buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64());
58
+
59
+ value.localBasisA.set(buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64());
60
+ value.localBasisB.set(buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64(), buffer.readFloat64());
61
+
62
+ for (let i = 0; i < 6; i++) value.dofMode[i] = buffer.readUint8();
63
+ for (let i = 0; i < 6; i++) value.dofLowerLimit[i] = buffer.readFloat64();
64
+ for (let i = 0; i < 6; i++) value.dofUpperLimit[i] = buffer.readFloat64();
65
+ for (let i = 0; i < 6; i++) value.dofStiffness[i] = buffer.readFloat64();
66
+ for (let i = 0; i < 6; i++) value.dofDamping[i] = buffer.readFloat64();
67
+ for (let i = 0; i < 6; i++) value.dofMotorTargetVelocity[i] = buffer.readFloat64();
68
+ for (let i = 0; i < 6; i++) value.dofMotorMaxForce[i] = buffer.readFloat64();
69
+
70
+ value.swingTwist = buffer.readUint8() !== 0;
71
+ }
72
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AA6uCA;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QAyJlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAiD3D"}
1
+ {"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AAovCA;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QAyJlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAiD3D"}
@@ -358,8 +358,10 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
358
358
  const shapeA = colA.shape;
359
359
  const shapeB = colB.shape;
360
360
 
361
- const isSphereA = shapeA.isUnitSphereShape3D === true;
362
- const isSphereB = shapeB.isUnitSphereShape3D === true;
361
+ // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
362
+ // SphereShape3D (arbitrary radius). Both expose `radius`.
363
+ const isSphereA = shapeA.isSphereShape3D === true;
364
+ const isSphereB = shapeB.isSphereShape3D === true;
363
365
  // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
364
366
  // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
365
367
  const isBoxA = shapeA.isBoxShape3D === true;
@@ -369,12 +371,13 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
369
371
 
370
372
  // sphere-sphere
371
373
  if (isSphereA && isSphereB) {
374
+ const ra = shapeA.radius, rb = shapeB.radius;
372
375
 
373
376
  const ok = sphere_sphere_contact(
374
377
  sphere_result,
375
378
  trA.position.x, trA.position.y, trA.position.z,
376
379
  trB.position.x, trB.position.y, trB.position.z,
377
- 1, 1
380
+ ra, rb
378
381
  );
379
382
 
380
383
  if (!ok) return count;
@@ -383,23 +386,25 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
383
386
 
384
387
  // Sphere-sphere produces exactly one contact per pair; fid = 1
385
388
  // identifies it as a real feature (distinguishes from "no info" = 0)
386
- // and is trivially stable across frames.
389
+ // and is trivially stable across frames. Witnesses are the surface
390
+ // points along the (unit) normal, scaled by each sphere's radius.
387
391
  return append_contact(count,
388
- trA.position.x - nx, trA.position.y - ny, trA.position.z - nz,
389
- trB.position.x + nx, trB.position.y + ny, trB.position.z + nz,
392
+ trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
393
+ trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
390
394
  nx, ny, nz, sphere_result[3], 1);
391
395
  }
392
396
 
393
397
  // sphere ↔ box
394
398
  if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
395
- const sphereTr = isSphereA ? trA : trB;
399
+ const sphereTr = isSphereA ? trA : trB;
400
+ const sphereShape = isSphereA ? shapeA : shapeB;
396
401
  const boxTr = isSphereA ? trB : trA;
397
402
  const boxShape = isSphereA ? shapeB : shapeA;
398
403
  const bh = boxShape.half_extents;
399
404
 
400
405
  const ok = sphere_box_contact(
401
406
  closed_form_result,
402
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, 1,
407
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
403
408
  boxTr.position.x, boxTr.position.y, boxTr.position.z,
404
409
  boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
405
410
  bh.x, bh.y, bh.z
@@ -491,12 +496,13 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
491
496
  const capsuleTr = isCapsuleA ? trA : trB;
492
497
  const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
493
498
  const sphereTr = isCapsuleA ? trB : trA;
499
+ const sphereShape = isCapsuleA ? colB.shape : colA.shape;
494
500
  const ok = capsule_sphere_contact(
495
501
  closed_form_result,
496
502
  capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
497
503
  capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
498
504
  capsuleShape.radius, capsuleShape.height * 0.5,
499
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, 1
505
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
500
506
  );
501
507
  if (!ok) return count;
502
508
  let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
@@ -639,14 +645,15 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
639
645
  const c_pos_y = concave_tr.position.y;
640
646
  const c_pos_z = concave_tr.position.z;
641
647
 
642
- // Sphere fast-path: when the convex side is a UnitSphereShape3D we
643
- // bypass GJK+EPA entirely per triangle and use the closed-form
648
+ // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
649
+ // entirely per triangle and use the closed-form
644
650
  // {@link sphere_triangle_contact}. This avoids the EPA precision
645
651
  // wall on Triangle3D (whose support function is degenerate along
646
652
  // the face normal — all 3 vertices project to the same value),
647
653
  // which was producing noisy depths at small penetrations and
648
654
  // letting dropped spheres tunnel through heightmaps / meshes.
649
- const isSphereConvex = convex_col.shape.isUnitSphereShape3D === true;
655
+ const isSphereConvex = convex_col.shape.isSphereShape3D === true;
656
+ const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
650
657
 
651
658
  // Box fast-path: closed-form {@link box_triangle_contact} via SAT
652
659
  // over 13 axes + polygon clipping for face-vs-face contacts.
@@ -743,7 +750,7 @@ function dispatch_pair(count, colA, trA, colB, trB, gjk_axis_buf = null, gjk_axi
743
750
 
744
751
  const ok = sphere_triangle_contact(
745
752
  sphere_triangle_result,
746
- convex_wx, convex_wy, convex_wz, 1,
753
+ convex_wx, convex_wy, convex_wz, sphere_radius,
747
754
  ax_w, ay_w, az_w,
748
755
  bx_w, by_w, bz_w,
749
756
  cx_w, cy_w, cz_w
@@ -0,0 +1,66 @@
1
+ /**
2
+ * # Local-frame ray ↔ primitive intersections (raycast narrowphase)
3
+ *
4
+ * Each function intersects a ray, **expressed in the shape's local frame**,
5
+ * against a canonical primitive at the origin (sphere at the origin; box
6
+ * axis-aligned spanning `[-h, +h]`; capsule along the local Y axis). They
7
+ * return the hit distance `t` along the ray (`Infinity` on a miss) and write
8
+ * the **local** outward surface normal (unit) into `outNormal[0..2]`.
9
+ *
10
+ * The ray-narrowphase dispatch transforms the world ray into the body's local
11
+ * frame once (rotate by the inverse body rotation — a unit direction stays
12
+ * unit, so `t` is preserved), calls the matching primitive, then rotates the
13
+ * returned local normal back to world. Keeping the primitives canonical lets
14
+ * the box and capsule tests be axis-aligned (cheap slab / cylinder math) and
15
+ * shares a single transform across them.
16
+ *
17
+ * Conventions:
18
+ * - the ray direction is unit length (the caller guarantees it);
19
+ * - the first surface crossing at or after the origin within `tMax` is
20
+ * returned; a ray starting inside the shape returns its exit crossing;
21
+ * - the normal is the geometric outward surface normal at the hit.
22
+ *
23
+ * @author Alex Goldring
24
+ * @copyright Company Named Limited (c) 2026
25
+ */
26
+ /**
27
+ * Ray vs a sphere of radius `r` centred at the local origin.
28
+ *
29
+ * @param {Float64Array} outNormal length-3, written on hit
30
+ * @param {number} ox @param {number} oy @param {number} oz ray origin (local)
31
+ * @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
32
+ * @param {number} tMax
33
+ * @param {number} r sphere radius
34
+ * @returns {number} hit distance, or `Infinity` on miss
35
+ */
36
+ export function ray_sphere_local(outNormal: Float64Array, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, tMax: number, r: number): number;
37
+ /**
38
+ * Ray vs an axis-aligned box spanning `[-hx,hx] × [-hy,hy] × [-hz,hz]` at the
39
+ * local origin (the canonical pose of {@link BoxShape3D}). Slab method, with
40
+ * the entry (or, origin-inside, exit) face's outward normal.
41
+ *
42
+ * @param {Float64Array} outNormal length-3, written on hit
43
+ * @param {number} ox @param {number} oy @param {number} oz ray origin (local)
44
+ * @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
45
+ * @param {number} tMax
46
+ * @param {number} hx @param {number} hy @param {number} hz half-extents
47
+ * @returns {number} hit distance, or `Infinity` on miss
48
+ */
49
+ export function ray_box_local(outNormal: Float64Array, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, tMax: number, hx: number, hy: number, hz: number): number;
50
+ /**
51
+ * Ray vs a capsule along the local Y axis: a cylinder of radius `r` over
52
+ * `y ∈ [−hh, hh]` capped by hemispheres of radius `r` at `(0, ±hh, 0)` — the
53
+ * canonical pose of {@link CapsuleShape3D} (`hh = height/2`). Tests the
54
+ * infinite cylinder (clamped to the segment) and the two cap spheres, taking
55
+ * the nearest valid crossing.
56
+ *
57
+ * @param {Float64Array} outNormal length-3, written on hit
58
+ * @param {number} ox @param {number} oy @param {number} oz ray origin (local)
59
+ * @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
60
+ * @param {number} tMax
61
+ * @param {number} r capsule radius
62
+ * @param {number} hh half-height of the cylindrical section (`height/2`)
63
+ * @returns {number} hit distance, or `Infinity` on miss
64
+ */
65
+ export function ray_capsule_local(outNormal: Float64Array, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, tMax: number, r: number, hh: number): number;
66
+ //# sourceMappingURL=ray_shapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ray_shapes.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/ray_shapes.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;GASG;AACH,4CAPW,YAAY,MACZ,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,KACN,MAAM,GACJ,MAAM,CAiBlB;AAED;;;;;;;;;;;GAWG;AACH,yCAPW,YAAY,MACZ,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,MACN,MAAM,MAAa,MAAM,MAAa,MAAM,GAC1C,MAAM,CA+ClB;AAED;;;;;;;;;;;;;;GAcG;AACH,6CARW,YAAY,MACZ,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,KACN,MAAM,MACN,MAAM,GACJ,MAAM,CA6DlB"}