@series-inc/rundot-syncplay 5.28.0-beta.2 → 5.28.0-beta.3
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/dist/physics3d-joints.d.ts +5 -0
- package/dist/physics3d-joints.js +104 -1
- package/dist/physics3d-solver.d.ts +2 -0
- package/dist/physics3d-solver.js +7 -0
- package/dist/runner.d.ts +10 -0
- package/dist/runner.js +20 -1
- package/package.json +1 -1
|
@@ -101,6 +101,11 @@ export type DeterministicPhysicsJoint3D = {
|
|
|
101
101
|
readonly targetZ: number;
|
|
102
102
|
readonly maxForce: number;
|
|
103
103
|
readonly damping?: number;
|
|
104
|
+
readonly targetRotation?: Quat3D;
|
|
105
|
+
readonly maxTorque?: number;
|
|
106
|
+
readonly angularDamping?: number;
|
|
107
|
+
readonly breakDistance?: number;
|
|
108
|
+
readonly breakImpulse?: number;
|
|
104
109
|
};
|
|
105
110
|
/** Build solver joint objects from joint definitions, seeded from the joint cache. */
|
|
106
111
|
export declare function createSolverJoints(joints: readonly DeterministicPhysicsJoint3D[], indexById: Map<string, number>, jointCache: readonly SolverJointCacheEntry[]): SolverJointSolve[];
|
package/dist/physics3d-joints.js
CHANGED
|
@@ -639,12 +639,33 @@ function grabJoint(joint, bodyIndex, warm) {
|
|
|
639
639
|
let desiredVel = { x: 0, y: 0, z: 0 };
|
|
640
640
|
const target = { x: joint.targetX, y: joint.targetY, z: joint.targetZ };
|
|
641
641
|
const maxImpulse = joint.maxForce * 1;
|
|
642
|
+
const targetRotation = joint.targetRotation !== undefined ? normalizeQuat(joint.targetRotation) : undefined;
|
|
643
|
+
const maxTorque = joint.maxTorque ?? 0;
|
|
644
|
+
const angularDamping = joint.angularDamping ?? joint.damping ?? 1;
|
|
645
|
+
let desiredAngVel = { x: 0, y: 0, z: 0 };
|
|
646
|
+
let angMassMat;
|
|
647
|
+
let angMassDiag;
|
|
648
|
+
let broken = false;
|
|
649
|
+
let torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
642
650
|
return {
|
|
643
651
|
bodyIndices: [bodyIndex],
|
|
644
652
|
prepare(ctx) {
|
|
653
|
+
if (broken) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
645
657
|
const body = ctx.bodies[bodyIndex];
|
|
646
658
|
r = jointLever(body, joint.anchor);
|
|
647
659
|
const worldAnchor = addVec3(body.c, r);
|
|
660
|
+
if (joint.breakDistance !== undefined) {
|
|
661
|
+
const delta = subVec3(target, worldAnchor);
|
|
662
|
+
const d = solverSqrt(dotVec3(delta, delta));
|
|
663
|
+
if (d > joint.breakDistance) {
|
|
664
|
+
broken = true;
|
|
665
|
+
state.impulse = { x: 0, y: 0, z: 0 };
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
648
669
|
const imB = ctx.invMass[bodyIndex];
|
|
649
670
|
const linear = mat3FromDiag(imB);
|
|
650
671
|
const skewR = skewMat3(r);
|
|
@@ -653,8 +674,36 @@ function grabJoint(joint, bodyIndex, warm) {
|
|
|
653
674
|
const damping = joint.damping ?? 1;
|
|
654
675
|
desiredVel = scaleVec3(subVec3(target, worldAnchor), damping / ctx.dt);
|
|
655
676
|
ctx.applyToBody(bodyIndex, r, state.impulse);
|
|
677
|
+
if (targetRotation !== undefined) {
|
|
678
|
+
const errorRotation = quatMultiply(quatConjugate(targetRotation), body.q);
|
|
679
|
+
const sign = errorRotation.w < 0 ? -1 : 1;
|
|
680
|
+
const error = {
|
|
681
|
+
x: 2 * sign * errorRotation.x,
|
|
682
|
+
y: 2 * sign * errorRotation.y,
|
|
683
|
+
z: 2 * sign * errorRotation.z,
|
|
684
|
+
};
|
|
685
|
+
desiredAngVel = scaleVec3(error, -angularDamping / ctx.dt);
|
|
686
|
+
const invI = ctx.invInertia[bodyIndex];
|
|
687
|
+
const fullInv = invertMat3(invI);
|
|
688
|
+
const isSingular = fullInv[0] === 0 && fullInv[4] === 0 && fullInv[8] === 0;
|
|
689
|
+
if (!isSingular) {
|
|
690
|
+
angMassMat = fullInv;
|
|
691
|
+
angMassDiag = undefined;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
angMassMat = undefined;
|
|
695
|
+
angMassDiag = {
|
|
696
|
+
x: invI[0] > 1e-9 ? 1 / invI[0] : 0,
|
|
697
|
+
y: invI[4] > 1e-9 ? 1 / invI[4] : 0,
|
|
698
|
+
z: invI[8] > 1e-9 ? 1 / invI[8] : 0,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
656
702
|
},
|
|
657
703
|
iterate(ctx) {
|
|
704
|
+
if (broken) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
658
707
|
const body = ctx.bodies[bodyIndex];
|
|
659
708
|
const currentVel = addVec3(body.v, crossVec3(body.w, r));
|
|
660
709
|
const dImpulse = mulMat3Vec(kInv, subVec3(desiredVel, currentVel));
|
|
@@ -663,9 +712,36 @@ function grabJoint(joint, bodyIndex, warm) {
|
|
|
663
712
|
const applied = subVec3(clamped, state.impulse);
|
|
664
713
|
state.impulse = clamped;
|
|
665
714
|
ctx.applyToBody(bodyIndex, r, applied);
|
|
715
|
+
if (targetRotation !== undefined && maxTorque > 0) {
|
|
716
|
+
const deltaW = subVec3(desiredAngVel, body.w);
|
|
717
|
+
const dTorque = angMassMat !== undefined
|
|
718
|
+
? mulMat3Vec(angMassMat, deltaW)
|
|
719
|
+
: { x: deltaW.x * angMassDiag.x, y: deltaW.y * angMassDiag.y, z: deltaW.z * angMassDiag.z };
|
|
720
|
+
const targetTorque = addVec3(torqueImpulse, dTorque);
|
|
721
|
+
const clampedTorque = clampMagnitude(targetTorque, maxTorque * ctx.dt);
|
|
722
|
+
const appliedTorque = subVec3(clampedTorque, torqueImpulse);
|
|
723
|
+
torqueImpulse = clampedTorque;
|
|
724
|
+
ctx.applyAngularToBody(bodyIndex, appliedTorque);
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
postSubstep(_ctx) {
|
|
728
|
+
if (!broken) {
|
|
729
|
+
state.stepImpulse = addVec3(state.stepImpulse, state.impulse);
|
|
730
|
+
}
|
|
731
|
+
torqueImpulse = { x: 0, y: 0, z: 0 };
|
|
666
732
|
},
|
|
667
733
|
emitCache() {
|
|
668
|
-
|
|
734
|
+
const reaction2 = dotVec3(state.stepImpulse, state.stepImpulse);
|
|
735
|
+
const isBroken = broken || (joint.breakImpulse !== undefined && reaction2 > joint.breakImpulse * joint.breakImpulse);
|
|
736
|
+
return {
|
|
737
|
+
jointId: joint.id,
|
|
738
|
+
impulseX: isBroken ? 0 : state.impulse.x,
|
|
739
|
+
impulseY: isBroken ? 0 : state.impulse.y,
|
|
740
|
+
impulseZ: isBroken ? 0 : state.impulse.z,
|
|
741
|
+
axialImpulse: 0,
|
|
742
|
+
hingeAngle: 0,
|
|
743
|
+
...(isBroken ? { broken: true } : {}),
|
|
744
|
+
};
|
|
669
745
|
},
|
|
670
746
|
};
|
|
671
747
|
}
|
|
@@ -687,6 +763,33 @@ function validateJoint(joint) {
|
|
|
687
763
|
if (!Number.isFinite(joint.targetX) || !Number.isFinite(joint.targetY) || !Number.isFinite(joint.targetZ)) {
|
|
688
764
|
throw new Error(`physics3d joint ${joint.id}: target must be finite`);
|
|
689
765
|
}
|
|
766
|
+
if (joint.damping !== undefined && (!Number.isFinite(joint.damping) || joint.damping < 0)) {
|
|
767
|
+
throw new Error(`physics3d joint ${joint.id}: damping must be non-negative`);
|
|
768
|
+
}
|
|
769
|
+
if (joint.targetRotation !== undefined) {
|
|
770
|
+
const target = joint.targetRotation;
|
|
771
|
+
if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(target.w)
|
|
772
|
+
|| target.x * target.x + target.y * target.y + target.z * target.z + target.w * target.w === 0) {
|
|
773
|
+
throw new Error(`physics3d joint ${joint.id}: targetRotation must be a finite non-zero quaternion`);
|
|
774
|
+
}
|
|
775
|
+
if (joint.maxTorque === undefined || !Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
|
|
776
|
+
throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
else if (joint.maxTorque !== undefined) {
|
|
780
|
+
if (!Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
|
|
781
|
+
throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (joint.angularDamping !== undefined && (!Number.isFinite(joint.angularDamping) || joint.angularDamping < 0)) {
|
|
785
|
+
throw new Error(`physics3d joint ${joint.id}: angularDamping must be non-negative`);
|
|
786
|
+
}
|
|
787
|
+
if (joint.breakDistance !== undefined && (!Number.isFinite(joint.breakDistance) || joint.breakDistance <= 0)) {
|
|
788
|
+
throw new Error(`physics3d joint ${joint.id}: breakDistance must be positive`);
|
|
789
|
+
}
|
|
790
|
+
if (joint.breakImpulse !== undefined && (!Number.isFinite(joint.breakImpulse) || joint.breakImpulse <= 0)) {
|
|
791
|
+
throw new Error(`physics3d joint ${joint.id}: breakImpulse must be positive`);
|
|
792
|
+
}
|
|
690
793
|
return;
|
|
691
794
|
}
|
|
692
795
|
assertFiniteVec(joint.anchorA, 'anchorA', joint.id);
|
|
@@ -222,6 +222,8 @@ export interface SolverJointContext {
|
|
|
222
222
|
apply(a: number, b: number, rA: Point3D, rB: Point3D, impulse: Point3D): void;
|
|
223
223
|
/** Angular-only impulse (no linear component), e.g. hinge motor/limit rows. */
|
|
224
224
|
applyAngular(a: number, b: number, angularImpulse: Point3D): void;
|
|
225
|
+
/** Apply an angular impulse to a single body (grab joint orientation drive). */
|
|
226
|
+
applyAngularToBody(index: number, angularImpulse: Point3D): void;
|
|
225
227
|
/** Apply an impulse to a single body at anchor `r` (grab / world-target joints). */
|
|
226
228
|
applyToBody(index: number, r: Point3D, impulse: Point3D): void;
|
|
227
229
|
/** Split-impulse position correction on pseudo-velocities (no real momentum). */
|
package/dist/physics3d-solver.js
CHANGED
|
@@ -1685,6 +1685,13 @@ export function solveRigidStep(bodies, options) {
|
|
|
1685
1685
|
B.w = addVec3(B.w, mulMat3Vec(invInertia[b], angularImpulse));
|
|
1686
1686
|
}
|
|
1687
1687
|
},
|
|
1688
|
+
applyAngularToBody: (index, angularImpulse) => {
|
|
1689
|
+
const body = bodies[index];
|
|
1690
|
+
if (!solid(body)) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
body.w = addVec3(body.w, mulMat3Vec(invInertia[index], angularImpulse));
|
|
1694
|
+
},
|
|
1688
1695
|
applyToBody: (index, r, impulse) => {
|
|
1689
1696
|
const body = bodies[index];
|
|
1690
1697
|
if (!solid(body)) {
|
package/dist/runner.d.ts
CHANGED
|
@@ -85,7 +85,17 @@ export interface SyncplayRunnerOfflineOptions<Input> {
|
|
|
85
85
|
}
|
|
86
86
|
export interface SyncplayRunnerSnapshot<RenderState> {
|
|
87
87
|
readonly status: SyncplayRunnerStatus;
|
|
88
|
+
/**
|
|
89
|
+
* Discrete tick render state. Note: to prevent 30Hz display judder on
|
|
90
|
+
* high-refresh displays, prefer interpolatedRenderState or runner.getRenderState().
|
|
91
|
+
*/
|
|
88
92
|
readonly renderState?: Readonly<RenderState>;
|
|
93
|
+
/**
|
|
94
|
+
* Smooth presentation state interpolated between previous and current
|
|
95
|
+
* render states at the current `renderAlpha`. If no interpolator is
|
|
96
|
+
* configured or interpolation throws, falls back to `renderState`.
|
|
97
|
+
*/
|
|
98
|
+
readonly interpolatedRenderState?: Readonly<RenderState>;
|
|
89
99
|
readonly renderAlpha: number;
|
|
90
100
|
readonly localSlot: number;
|
|
91
101
|
readonly rollbackCount: number;
|
package/dist/runner.js
CHANGED
|
@@ -68,9 +68,28 @@ export function createSyncplayRunner(config) {
|
|
|
68
68
|
return localSession !== undefined ? localSession.currentFrame : 0;
|
|
69
69
|
}
|
|
70
70
|
function snapshot() {
|
|
71
|
+
let interpolatedRender;
|
|
72
|
+
if (currentRender !== undefined) {
|
|
73
|
+
if (previousRender !== undefined && config.presentation.interpolate !== undefined) {
|
|
74
|
+
try {
|
|
75
|
+
interpolatedRender = config.presentation.interpolate(previousRender, currentRender, renderAlpha);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
interpolatedRender = currentRender;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
interpolatedRender = currentRender;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
71
85
|
return Object.freeze({
|
|
72
86
|
status,
|
|
73
|
-
...(currentRender === undefined
|
|
87
|
+
...(currentRender === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
renderState: currentRender,
|
|
91
|
+
interpolatedRenderState: interpolatedRender,
|
|
92
|
+
}),
|
|
74
93
|
renderAlpha,
|
|
75
94
|
localSlot,
|
|
76
95
|
rollbackCount: networkClient?.rollbacks ?? 0,
|
package/package.json
CHANGED