@woosh/meep-engine 2.140.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/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/engine/physics/PLAN.md +152 -35
- package/src/engine/physics/REVIEW_002.md +151 -0
- 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/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 +52 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +126 -4
- 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.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidSimulator.js +0 -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 +24 -22
- 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 +26 -22
- 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/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +27 -1
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +33 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +75 -0
- package/src/engine/physics/solver/solve_contacts.d.ts +28 -0
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +169 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
* Lower / upper bounds per DOF, used by {@link DofMode.LIMITED}. Linear in
|
|
87
|
+
* metres, angular in radians. (Reserved — consumed once the LIMITED mode
|
|
88
|
+
* lands.)
|
|
89
|
+
* @readonly
|
|
90
|
+
* @type {Float64Array}
|
|
91
|
+
*/
|
|
92
|
+
readonly dofLowerLimit: Float64Array;
|
|
93
|
+
/** @readonly @type {Float64Array} */
|
|
94
|
+
readonly dofUpperLimit: Float64Array;
|
|
95
|
+
/**
|
|
96
|
+
* Spring stiffness / damping per DOF, used by {@link DofMode.SPRING}.
|
|
97
|
+
* (Reserved — consumed once the SPRING mode lands.)
|
|
98
|
+
* @readonly
|
|
99
|
+
* @type {Float64Array}
|
|
100
|
+
*/
|
|
101
|
+
readonly dofStiffness: Float64Array;
|
|
102
|
+
/** @readonly @type {Float64Array} */
|
|
103
|
+
readonly dofDamping: Float64Array;
|
|
104
|
+
/**
|
|
105
|
+
* Motor target velocity / max force per DOF, used by
|
|
106
|
+
* {@link DofMode.MOTOR}. (Reserved — consumed once the MOTOR mode lands.)
|
|
107
|
+
* @readonly
|
|
108
|
+
* @type {Float64Array}
|
|
109
|
+
*/
|
|
110
|
+
readonly dofMotorTargetVelocity: Float64Array;
|
|
111
|
+
/** @readonly @type {Float64Array} */
|
|
112
|
+
readonly dofMotorMaxForce: Float64Array;
|
|
113
|
+
/**
|
|
114
|
+
* Accumulated constraint impulse per DOF — warm-start state carried across
|
|
115
|
+
* frames. System-owned.
|
|
116
|
+
* @readonly
|
|
117
|
+
* @type {Float64Array}
|
|
118
|
+
*/
|
|
119
|
+
readonly dofImpulse: Float64Array;
|
|
120
|
+
/**
|
|
121
|
+
* Resolved packed body id of A, set at link time. System-private.
|
|
122
|
+
* @type {number}
|
|
123
|
+
*/
|
|
124
|
+
_bodyIdA: number;
|
|
125
|
+
/**
|
|
126
|
+
* Resolved packed body id of B, or {@link JOINT_WORLD}. System-private.
|
|
127
|
+
* @type {number}
|
|
128
|
+
*/
|
|
129
|
+
_bodyIdB: number;
|
|
130
|
+
/**
|
|
131
|
+
* Packed joint handle assigned at link. System-private.
|
|
132
|
+
* @type {number}
|
|
133
|
+
*/
|
|
134
|
+
_jointId: number;
|
|
135
|
+
/**
|
|
136
|
+
* Configure this joint as a ball-socket (point-to-point) between two
|
|
137
|
+
* anchors. Linear DOFs locked, angular free — the default, provided as a
|
|
138
|
+
* named helper for clarity at call sites.
|
|
139
|
+
* @returns {Joint} this
|
|
140
|
+
*/
|
|
141
|
+
asBallSocket(): Joint;
|
|
142
|
+
/**
|
|
143
|
+
* Configure as a weld (fixed) joint: all 6 DOFs locked, holding the two
|
|
144
|
+
* bodies rigidly in their relative pose.
|
|
145
|
+
* @returns {Joint} this
|
|
146
|
+
*/
|
|
147
|
+
asWeld(): Joint;
|
|
148
|
+
/**
|
|
149
|
+
* Configure as a hinge (revolute): 3 linear + 2 angular locked, free to
|
|
150
|
+
* rotate about a single frame axis. Orient {@link localBasisA} /
|
|
151
|
+
* {@link localBasisB} so that frame axis is the desired hinge axis.
|
|
152
|
+
*
|
|
153
|
+
* @param {number} [freeAngularAxis] which frame axis is free: 0 = x
|
|
154
|
+
* (default), 1 = y, 2 = z.
|
|
155
|
+
* @returns {Joint} this
|
|
156
|
+
*/
|
|
157
|
+
asHinge(freeAngularAxis?: number): Joint;
|
|
158
|
+
/**
|
|
159
|
+
* Configure as a prismatic (slider): 2 linear + 3 angular locked, free to
|
|
160
|
+
* translate along a single frame axis. Orient {@link localBasisA} /
|
|
161
|
+
* {@link localBasisB} so that frame axis is the desired slide direction.
|
|
162
|
+
*
|
|
163
|
+
* @param {number} [freeLinearAxis] which frame axis is free: 0 = x
|
|
164
|
+
* (default), 1 = y, 2 = z.
|
|
165
|
+
* @returns {Joint} this
|
|
166
|
+
*/
|
|
167
|
+
asPrismatic(freeLinearAxis?: number): Joint;
|
|
168
|
+
/**
|
|
169
|
+
* @readonly
|
|
170
|
+
* @type {boolean}
|
|
171
|
+
*/
|
|
172
|
+
readonly isJoint: boolean;
|
|
173
|
+
}
|
|
174
|
+
export namespace Joint {
|
|
175
|
+
let typeName: string;
|
|
176
|
+
}
|
|
177
|
+
import Vector3 from "../../../core/geom/Vector3.js";
|
|
178
|
+
import Quaternion from "../../../core/geom/Quaternion.js";
|
|
179
|
+
//# 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;;;;;;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"}
|