@holoscript/engine 6.0.3 → 6.0.4
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/AutoMesher-CK47F6AV.js +17 -0
- package/dist/GPUBuffers-2LHBCD7X.js +9 -0
- package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
- package/dist/animation/index.cjs +38 -38
- package/dist/animation/index.d.cts +1 -1
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.js +1 -1
- package/dist/audio/index.cjs +16 -6
- package/dist/audio/index.d.cts +1 -1
- package/dist/audio/index.d.ts +1 -1
- package/dist/audio/index.js +1 -1
- package/dist/camera/index.cjs +23 -23
- package/dist/camera/index.d.cts +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/character/index.cjs +6 -4
- package/dist/character/index.js +1 -1
- package/dist/choreography/index.cjs +1194 -0
- package/dist/choreography/index.d.cts +687 -0
- package/dist/choreography/index.d.ts +687 -0
- package/dist/choreography/index.js +1156 -0
- package/dist/chunk-2CSNRI2N.js +217 -0
- package/dist/chunk-33T2WINR.js +266 -0
- package/dist/chunk-35R73OFM.js +1257 -0
- package/dist/chunk-4MMDSUNP.js +1256 -0
- package/dist/chunk-5V6HOU72.js +319 -0
- package/dist/chunk-6QOP6PYF.js +1038 -0
- package/dist/chunk-7KMJVHIL.js +8944 -0
- package/dist/chunk-7VPUC62U.js +1106 -0
- package/dist/chunk-A2Y6RCAT.js +1878 -0
- package/dist/chunk-AHM42MK6.js +8944 -0
- package/dist/chunk-BL7IDTHE.js +218 -0
- package/dist/chunk-CITOMSWL.js +10462 -0
- package/dist/chunk-CXDPKW2K.js +8944 -0
- package/dist/chunk-CXZPLD4S.js +223 -0
- package/dist/chunk-CZYJE7IH.js +5169 -0
- package/dist/chunk-D2OP7YC7.js +6325 -0
- package/dist/chunk-EDRVQHUU.js +1544 -0
- package/dist/chunk-EJSLOOW2.js +3589 -0
- package/dist/chunk-F53SFGW5.js +1878 -0
- package/dist/chunk-HCFPELPY.js +919 -0
- package/dist/chunk-HNEE36PY.js +93 -0
- package/dist/chunk-HYXNV36F.js +1256 -0
- package/dist/chunk-IB7KHVFY.js +821 -0
- package/dist/chunk-IBBO7YYG.js +690 -0
- package/dist/chunk-ILIBGINU.js +5470 -0
- package/dist/chunk-IS4MHLKN.js +5479 -0
- package/dist/chunk-JT2PFKWD.js +5479 -0
- package/dist/chunk-K4CUB4NY.js +1038 -0
- package/dist/chunk-KATDQXRJ.js +10462 -0
- package/dist/chunk-KBQE6ZFJ.js +8944 -0
- package/dist/chunk-KBVD5K7E.js +560 -0
- package/dist/chunk-KCDPVQRY.js +4088 -0
- package/dist/chunk-KN4QJPKN.js +8944 -0
- package/dist/chunk-KWJ3ROSI.js +8944 -0
- package/dist/chunk-L45VF6DD.js +919 -0
- package/dist/chunk-LY4T37YK.js +307 -0
- package/dist/chunk-MDN5WZXA.js +1544 -0
- package/dist/chunk-MGCDP6VU.js +928 -0
- package/dist/chunk-NCX7X6G2.js +8681 -0
- package/dist/chunk-OF54BPVD.js +913 -0
- package/dist/chunk-OWSN2Q3Q.js +690 -0
- package/dist/chunk-PRRB5TTA.js +406 -0
- package/dist/chunk-PXWVQF76.js +4086 -0
- package/dist/chunk-PYCOIDT2.js +812 -0
- package/dist/chunk-PZCSADOV.js +928 -0
- package/dist/chunk-Q2XBVS2K.js +1038 -0
- package/dist/chunk-QDZRXWN5.js +1776 -0
- package/dist/chunk-RNWOZ6WQ.js +913 -0
- package/dist/chunk-ROLFT4CJ.js +1693 -0
- package/dist/chunk-SLTJRZ2N.js +266 -0
- package/dist/chunk-SRUS5XSU.js +4088 -0
- package/dist/chunk-TKCA3WZ5.js +5409 -0
- package/dist/chunk-TNRMXYI2.js +1650 -0
- package/dist/chunk-TQB3GJGM.js +9763 -0
- package/dist/chunk-TUFGXG6K.js +510 -0
- package/dist/chunk-U6KMTGQJ.js +632 -0
- package/dist/chunk-VMGJQST6.js +8681 -0
- package/dist/chunk-X4F4TCG4.js +5470 -0
- package/dist/chunk-ZIFROE75.js +1544 -0
- package/dist/chunk-ZIJQYHSQ.js +1204 -0
- package/dist/combat/index.cjs +4 -4
- package/dist/combat/index.d.cts +1 -1
- package/dist/combat/index.d.ts +1 -1
- package/dist/combat/index.js +1 -1
- package/dist/ecs/index.cjs +1 -1
- package/dist/ecs/index.js +1 -1
- package/dist/environment/index.cjs +14 -14
- package/dist/environment/index.d.cts +1 -1
- package/dist/environment/index.d.ts +1 -1
- package/dist/environment/index.js +1 -1
- package/dist/gpu/index.cjs +4810 -0
- package/dist/gpu/index.js +3714 -0
- package/dist/hologram/index.cjs +27 -1
- package/dist/hologram/index.js +1 -1
- package/dist/index-B2PIsAmR.d.cts +2180 -0
- package/dist/index-B2PIsAmR.d.ts +2180 -0
- package/dist/index-BHySEPX7.d.cts +2921 -0
- package/dist/index-BJV21zuy.d.cts +341 -0
- package/dist/index-BJV21zuy.d.ts +341 -0
- package/dist/index-BQutTphC.d.cts +790 -0
- package/dist/index-ByIq2XrS.d.cts +3910 -0
- package/dist/index-BysHjDSO.d.cts +224 -0
- package/dist/index-BysHjDSO.d.ts +224 -0
- package/dist/index-CKwAJGck.d.ts +455 -0
- package/dist/index-CUl3QstQ.d.cts +3006 -0
- package/dist/index-CUl3QstQ.d.ts +3006 -0
- package/dist/index-CmYtNiI-.d.cts +953 -0
- package/dist/index-CmYtNiI-.d.ts +953 -0
- package/dist/index-CnRzWxi_.d.cts +522 -0
- package/dist/index-CnRzWxi_.d.ts +522 -0
- package/dist/index-CwRWbSC7.d.ts +2921 -0
- package/dist/index-CxKIBstO.d.ts +790 -0
- package/dist/index-DJ6-R8vh.d.cts +455 -0
- package/dist/index-DQKisbcI.d.cts +4968 -0
- package/dist/index-DQKisbcI.d.ts +4968 -0
- package/dist/index-DRT2zJez.d.ts +3910 -0
- package/dist/index-DfNLiAka.d.cts +192 -0
- package/dist/index-DfNLiAka.d.ts +192 -0
- package/dist/index-nMvkoRm8.d.cts +405 -0
- package/dist/index-nMvkoRm8.d.ts +405 -0
- package/dist/index-s9yOFU37.d.cts +604 -0
- package/dist/index-s9yOFU37.d.ts +604 -0
- package/dist/index.cjs +22966 -6960
- package/dist/index.d.cts +864 -20
- package/dist/index.d.ts +864 -20
- package/dist/index.js +3062 -48
- package/dist/input/index.cjs +1 -1
- package/dist/input/index.js +1 -1
- package/dist/orbital/index.cjs +3 -3
- package/dist/orbital/index.d.cts +1 -1
- package/dist/orbital/index.d.ts +1 -1
- package/dist/orbital/index.js +1 -1
- package/dist/particles/index.cjs +16 -16
- package/dist/particles/index.d.cts +1 -1
- package/dist/particles/index.d.ts +1 -1
- package/dist/particles/index.js +1 -1
- package/dist/physics/index.cjs +2377 -21
- package/dist/physics/index.d.cts +1 -1
- package/dist/physics/index.d.ts +1 -1
- package/dist/physics/index.js +35 -1
- package/dist/postfx/index.cjs +3491 -0
- package/dist/postfx/index.js +93 -0
- package/dist/procedural/index.cjs +1 -1
- package/dist/procedural/index.js +1 -1
- package/dist/puppeteer-5VF6KDVO.js +52197 -0
- package/dist/puppeteer-IZVZ3SG4.js +52197 -0
- package/dist/rendering/index.cjs +33 -32
- package/dist/rendering/index.d.cts +1 -1
- package/dist/rendering/index.d.ts +1 -1
- package/dist/rendering/index.js +8 -6
- package/dist/runtime/index.cjs +23 -13
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +8 -6
- package/dist/runtime/protocols/index.cjs +349 -0
- package/dist/runtime/protocols/index.js +15 -0
- package/dist/scene/index.cjs +8 -8
- package/dist/scene/index.d.cts +1 -1
- package/dist/scene/index.d.ts +1 -1
- package/dist/scene/index.js +1 -1
- package/dist/shader/index.cjs +3087 -0
- package/dist/shader/index.js +3044 -0
- package/dist/simulation/index.cjs +10680 -0
- package/dist/simulation/index.d.cts +3 -0
- package/dist/simulation/index.d.ts +3 -0
- package/dist/simulation/index.js +307 -0
- package/dist/spatial/index.cjs +2443 -0
- package/dist/spatial/index.d.cts +1545 -0
- package/dist/spatial/index.d.ts +1545 -0
- package/dist/spatial/index.js +2400 -0
- package/dist/terrain/index.cjs +1 -1
- package/dist/terrain/index.d.cts +1 -1
- package/dist/terrain/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/transformers.node-4NKAPD5U.js +45620 -0
- package/dist/vm/index.cjs +7 -8
- package/dist/vm/index.d.cts +1 -1
- package/dist/vm/index.d.ts +1 -1
- package/dist/vm/index.js +1 -1
- package/dist/vm-bridge/index.cjs +2 -2
- package/dist/vm-bridge/index.d.cts +2 -2
- package/dist/vm-bridge/index.d.ts +2 -2
- package/dist/vm-bridge/index.js +1 -1
- package/dist/vr/index.cjs +6 -6
- package/dist/vr/index.js +1 -1
- package/dist/world/index.cjs +3 -3
- package/dist/world/index.d.cts +1 -1
- package/dist/world/index.d.ts +1 -1
- package/dist/world/index.js +1 -1
- package/package.json +53 -21
- package/LICENSE +0 -21
package/dist/physics/index.cjs
CHANGED
|
@@ -22,10 +22,16 @@ var physics_exports = {};
|
|
|
22
22
|
__export(physics_exports, {
|
|
23
23
|
ActivationTriggerType: () => ActivationTriggerType,
|
|
24
24
|
COLLISION_GROUPS: () => COLLISION_GROUPS,
|
|
25
|
+
ClothSim: () => ClothSim,
|
|
26
|
+
ConstraintSolver: () => ConstraintSolver,
|
|
25
27
|
DEFAULT_ACTIVATION_CONFIG: () => DEFAULT_ACTIVATION_CONFIG,
|
|
26
28
|
DEFAULT_INTENSITY_CURVE: () => DEFAULT_INTENSITY_CURVE,
|
|
27
29
|
DEFAULT_LOCOMOTION_CONFIG: () => DEFAULT_LOCOMOTION_CONFIG,
|
|
30
|
+
DeformableMesh: () => DeformableMesh,
|
|
31
|
+
FluidSim: () => FluidSim,
|
|
32
|
+
HUMANOID_PRESET: () => HUMANOID_PRESET,
|
|
28
33
|
IslandDetector: () => IslandDetector,
|
|
34
|
+
JointSystem: () => JointSystem,
|
|
29
35
|
MLSMPMFluid: () => MLSMPMFluid,
|
|
30
36
|
PBDSolverCPU: () => PBDSolverCPU,
|
|
31
37
|
PBD_ATTACHMENT_SHADER: () => PBD_ATTACHMENT_SHADER,
|
|
@@ -48,14 +54,23 @@ __export(physics_exports, {
|
|
|
48
54
|
PhysicsSyncReceiver: () => PhysicsSyncReceiver,
|
|
49
55
|
PhysicsSyncSender: () => PhysicsSyncSender,
|
|
50
56
|
PhysicsWorldImpl: () => PhysicsWorldImpl,
|
|
57
|
+
QUADRUPED_PRESET: () => QUADRUPED_PRESET,
|
|
58
|
+
RagdollController: () => RagdollController,
|
|
59
|
+
RagdollSystem: () => RagdollSystem,
|
|
60
|
+
RaycastSystem: () => RaycastSystem,
|
|
51
61
|
RigidBody: () => RigidBody,
|
|
62
|
+
RopeSystem: () => RopeSystem,
|
|
52
63
|
SOFT_BODY_PRESETS: () => SOFT_BODY_PRESETS,
|
|
53
64
|
ScalarArithmetic: () => ScalarArithmetic,
|
|
54
65
|
SoftBodyAdapter: () => SoftBodyAdapter,
|
|
55
66
|
SoftBodyGrabController: () => SoftBodyGrabController,
|
|
56
67
|
SoftBodySolver: () => SoftBodySolver,
|
|
68
|
+
SpatialHash: () => SpatialHash,
|
|
69
|
+
TriggerZoneSystem: () => TriggerZoneSystem,
|
|
57
70
|
UnifiedParticleBuffer: () => UnifiedParticleBuffer,
|
|
71
|
+
VRPhysicsBridge: () => VRPhysicsBridge,
|
|
58
72
|
Vector3Arithmetic: () => Vector3Arithmetic,
|
|
73
|
+
VehicleSystem: () => VehicleSystem,
|
|
59
74
|
VelocityRingBuffer: () => VelocityRingBuffer,
|
|
60
75
|
VelocitySmoother: () => VelocitySmoother,
|
|
61
76
|
WindZoneManager: () => WindZoneManager,
|
|
@@ -65,10 +80,12 @@ __export(physics_exports, {
|
|
|
65
80
|
colorConstraints: () => colorConstraints,
|
|
66
81
|
computeRestLengths: () => computeRestLengths,
|
|
67
82
|
computeSelfWind: () => computeSelfWind,
|
|
83
|
+
createDefaultCar: () => createDefaultCar,
|
|
68
84
|
createPBDSolver: () => createPBDSolver,
|
|
69
85
|
createPIDControllerTrait: () => createPIDControllerTrait,
|
|
70
86
|
createPhysicsWorld: () => createPhysicsWorld,
|
|
71
87
|
createScalarPIDController: () => createScalarPIDController,
|
|
88
|
+
createTruck: () => createTruck,
|
|
72
89
|
createVector3PIDController: () => createVector3PIDController,
|
|
73
90
|
defaultMaterial: () => defaultMaterial,
|
|
74
91
|
defaultPIDConfig: () => defaultPIDConfig,
|
|
@@ -2662,11 +2679,11 @@ function extractBendingPairs(indices, positions) {
|
|
|
2662
2679
|
const v0 = parseInt(minStr);
|
|
2663
2680
|
const v1 = parseInt(maxStr);
|
|
2664
2681
|
const v2 = tris[0].oppositeVertex;
|
|
2665
|
-
const
|
|
2682
|
+
const v32 = tris[1].oppositeVertex;
|
|
2666
2683
|
const p0x = positions[v0 * 3], p0y = positions[v0 * 3 + 1], p0z = positions[v0 * 3 + 2];
|
|
2667
2684
|
const p1x = positions[v1 * 3], p1y = positions[v1 * 3 + 1], p1z = positions[v1 * 3 + 2];
|
|
2668
2685
|
const p2x = positions[v2 * 3], p2y = positions[v2 * 3 + 1], p2z = positions[v2 * 3 + 2];
|
|
2669
|
-
const p3x = positions[
|
|
2686
|
+
const p3x = positions[v32 * 3], p3y = positions[v32 * 3 + 1], p3z = positions[v32 * 3 + 2];
|
|
2670
2687
|
const ex = p1x - p0x, ey = p1y - p0y, ez = p1z - p0z;
|
|
2671
2688
|
const eLen = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
|
2672
2689
|
if (eLen < 1e-7) continue;
|
|
@@ -2689,7 +2706,7 @@ function extractBendingPairs(indices, positions) {
|
|
|
2689
2706
|
const cy = n1nz * n2nx - n1nx * n2nz;
|
|
2690
2707
|
const cz = n1nx * n2ny - n1ny * n2nx;
|
|
2691
2708
|
const sinTheta = cx * enx + cy * eny + cz * enz;
|
|
2692
|
-
bendingList.push([v0, v1, v2,
|
|
2709
|
+
bendingList.push([v0, v1, v2, v32]);
|
|
2693
2710
|
angleList.push(Math.atan2(sinTheta, cosTheta));
|
|
2694
2711
|
}
|
|
2695
2712
|
const constraints = new Uint32Array(bendingList.length * 4);
|
|
@@ -2872,11 +2889,11 @@ var PBDSolverCPU = class {
|
|
|
2872
2889
|
const v0 = tetIndices[t * 4];
|
|
2873
2890
|
const v1 = tetIndices[t * 4 + 1];
|
|
2874
2891
|
const v2 = tetIndices[t * 4 + 2];
|
|
2875
|
-
const
|
|
2876
|
-
const vol = this.computeTetVolume(v0, v1, v2,
|
|
2892
|
+
const v32 = tetIndices[t * 4 + 3];
|
|
2893
|
+
const vol = this.computeTetVolume(v0, v1, v2, v32, positions);
|
|
2877
2894
|
this.volumeConstraints.push({
|
|
2878
2895
|
type: "volume",
|
|
2879
|
-
vertices: [v0, v1, v2,
|
|
2896
|
+
vertices: [v0, v1, v2, v32],
|
|
2880
2897
|
restVolume: vol,
|
|
2881
2898
|
compliance: compliance * 0.1
|
|
2882
2899
|
});
|
|
@@ -2902,11 +2919,11 @@ var PBDSolverCPU = class {
|
|
|
2902
2919
|
});
|
|
2903
2920
|
}
|
|
2904
2921
|
}
|
|
2905
|
-
computeTetVolume(v0, v1, v2,
|
|
2922
|
+
computeTetVolume(v0, v1, v2, v32, pos) {
|
|
2906
2923
|
const p0x = pos[v0 * 3], p0y = pos[v0 * 3 + 1], p0z = pos[v0 * 3 + 2];
|
|
2907
2924
|
const d1x = pos[v1 * 3] - p0x, d1y = pos[v1 * 3 + 1] - p0y, d1z = pos[v1 * 3 + 2] - p0z;
|
|
2908
2925
|
const d2x = pos[v2 * 3] - p0x, d2y = pos[v2 * 3 + 1] - p0y, d2z = pos[v2 * 3 + 2] - p0z;
|
|
2909
|
-
const d3x = pos[
|
|
2926
|
+
const d3x = pos[v32 * 3] - p0x, d3y = pos[v32 * 3 + 1] - p0y, d3z = pos[v32 * 3 + 2] - p0z;
|
|
2910
2927
|
const crx = d2y * d3z - d2z * d3y;
|
|
2911
2928
|
const cry = d2z * d3x - d2x * d3z;
|
|
2912
2929
|
const crz = d2x * d3y - d2y * d3x;
|
|
@@ -3089,11 +3106,11 @@ var PBDSolverCPU = class {
|
|
|
3089
3106
|
solveVolumeConstraint(c, dt) {
|
|
3090
3107
|
const pred = this.state.predicted;
|
|
3091
3108
|
const masses = this.config.masses;
|
|
3092
|
-
const [v0, v1, v2,
|
|
3109
|
+
const [v0, v1, v2, v32] = c.vertices;
|
|
3093
3110
|
const p0x = pred[v0 * 3], p0y = pred[v0 * 3 + 1], p0z = pred[v0 * 3 + 2];
|
|
3094
3111
|
const p1x = pred[v1 * 3], p1y = pred[v1 * 3 + 1], p1z = pred[v1 * 3 + 2];
|
|
3095
3112
|
const p2x = pred[v2 * 3], p2y = pred[v2 * 3 + 1], p2z = pred[v2 * 3 + 2];
|
|
3096
|
-
const p3x = pred[
|
|
3113
|
+
const p3x = pred[v32 * 3], p3y = pred[v32 * 3 + 1], p3z = pred[v32 * 3 + 2];
|
|
3097
3114
|
const d1x = p1x - p0x, d1y = p1y - p0y, d1z = p1z - p0z;
|
|
3098
3115
|
const d2x = p2x - p0x, d2y = p2y - p0y, d2z = p2z - p0z;
|
|
3099
3116
|
const d3x = p3x - p0x, d3y = p3y - p0y, d3z = p3z - p0z;
|
|
@@ -3112,7 +3129,7 @@ var PBDSolverCPU = class {
|
|
|
3112
3129
|
const w0 = masses[v0] > 0 ? 1 / masses[v0] : 0;
|
|
3113
3130
|
const w1 = masses[v1] > 0 ? 1 / masses[v1] : 0;
|
|
3114
3131
|
const w2 = masses[v2] > 0 ? 1 / masses[v2] : 0;
|
|
3115
|
-
const w3 = masses[
|
|
3132
|
+
const w3 = masses[v32] > 0 ? 1 / masses[v32] : 0;
|
|
3116
3133
|
const denom = w0 * (g0x * g0x + g0y * g0y + g0z * g0z) + w1 * (g1x * g1x + g1y * g1y + g1z * g1z) + w2 * (g2x * g2x + g2y * g2y + g2z * g2z) + w3 * (g3x * g3x + g3y * g3y + g3z * g3z);
|
|
3117
3134
|
if (denom < 1e-7) return;
|
|
3118
3135
|
const alpha = c.compliance / (dt * dt);
|
|
@@ -3133,19 +3150,19 @@ var PBDSolverCPU = class {
|
|
|
3133
3150
|
pred[v2 * 3 + 2] += g2z * lambda * w2;
|
|
3134
3151
|
}
|
|
3135
3152
|
if (w3 > 0) {
|
|
3136
|
-
pred[
|
|
3137
|
-
pred[
|
|
3138
|
-
pred[
|
|
3153
|
+
pred[v32 * 3] += g3x * lambda * w3;
|
|
3154
|
+
pred[v32 * 3 + 1] += g3y * lambda * w3;
|
|
3155
|
+
pred[v32 * 3 + 2] += g3z * lambda * w3;
|
|
3139
3156
|
}
|
|
3140
3157
|
}
|
|
3141
3158
|
solveBendingConstraint(c, dt) {
|
|
3142
3159
|
const pred = this.state.predicted;
|
|
3143
3160
|
const masses = this.config.masses;
|
|
3144
|
-
const [v0, v1, v2,
|
|
3161
|
+
const [v0, v1, v2, v32] = c.vertices;
|
|
3145
3162
|
const p0x = pred[v0 * 3], p0y = pred[v0 * 3 + 1], p0z = pred[v0 * 3 + 2];
|
|
3146
3163
|
const p1x = pred[v1 * 3], p1y = pred[v1 * 3 + 1], p1z = pred[v1 * 3 + 2];
|
|
3147
3164
|
const p2x = pred[v2 * 3], p2y = pred[v2 * 3 + 1], p2z = pred[v2 * 3 + 2];
|
|
3148
|
-
const p3x = pred[
|
|
3165
|
+
const p3x = pred[v32 * 3], p3y = pred[v32 * 3 + 1], p3z = pred[v32 * 3 + 2];
|
|
3149
3166
|
const ex = p1x - p0x, ey = p1y - p0y, ez = p1z - p0z;
|
|
3150
3167
|
const eLen = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
|
3151
3168
|
if (eLen < 1e-7) return;
|
|
@@ -3184,7 +3201,7 @@ var PBDSolverCPU = class {
|
|
|
3184
3201
|
const w0 = masses[v0] > 0 ? 1 / masses[v0] : 0;
|
|
3185
3202
|
const w1 = masses[v1] > 0 ? 1 / masses[v1] : 0;
|
|
3186
3203
|
const w2 = masses[v2] > 0 ? 1 / masses[v2] : 0;
|
|
3187
|
-
const w3 = masses[
|
|
3204
|
+
const w3 = masses[v32] > 0 ? 1 / masses[v32] : 0;
|
|
3188
3205
|
const denom = w0 * (g0x * g0x + g0y * g0y + g0z * g0z) + w1 * (g1x2 * g1x2 + g1y2 * g1y2 + g1z2 * g1z2) + w2 * (g2x * g2x + g2y * g2y + g2z * g2z) + w3 * (g3x * g3x + g3y * g3y + g3z * g3z);
|
|
3189
3206
|
const alpha = c.compliance / (dt * dt);
|
|
3190
3207
|
if (denom + alpha < 1e-10) return;
|
|
@@ -3205,9 +3222,9 @@ var PBDSolverCPU = class {
|
|
|
3205
3222
|
pred[v2 * 3 + 2] += g2z * lambda * w2;
|
|
3206
3223
|
}
|
|
3207
3224
|
if (w3 > 0) {
|
|
3208
|
-
pred[
|
|
3209
|
-
pred[
|
|
3210
|
-
pred[
|
|
3225
|
+
pred[v32 * 3] += g3x * lambda * w3;
|
|
3226
|
+
pred[v32 * 3 + 1] += g3y * lambda * w3;
|
|
3227
|
+
pred[v32 * 3 + 2] += g3z * lambda * w3;
|
|
3211
3228
|
}
|
|
3212
3229
|
}
|
|
3213
3230
|
solveAttachmentConstraint(c, dt) {
|
|
@@ -5066,6 +5083,13 @@ var PIDLoop = class {
|
|
|
5066
5083
|
this.filteredDerivative = math.zero();
|
|
5067
5084
|
this.prevOutput = math.zero();
|
|
5068
5085
|
}
|
|
5086
|
+
gains;
|
|
5087
|
+
outputLimit;
|
|
5088
|
+
integralLimit;
|
|
5089
|
+
filterAlpha;
|
|
5090
|
+
derivativeOnMeasurement;
|
|
5091
|
+
backCalcAntiWindup;
|
|
5092
|
+
backCalcGain;
|
|
5069
5093
|
math;
|
|
5070
5094
|
integral;
|
|
5071
5095
|
prevError;
|
|
@@ -6244,7 +6268,7 @@ var SoftBodyAdapter = class {
|
|
|
6244
6268
|
});
|
|
6245
6269
|
this.vertexMapping.push(i);
|
|
6246
6270
|
}
|
|
6247
|
-
const
|
|
6271
|
+
const _width = Math.sqrt(particles.length);
|
|
6248
6272
|
for (let i = 0; i < particles.length - 1; i++) {
|
|
6249
6273
|
constraints.push({
|
|
6250
6274
|
p1: i,
|
|
@@ -6269,14 +6293,2335 @@ var SoftBodyAdapter = class {
|
|
|
6269
6293
|
this.node.geometry.needsUpdate = true;
|
|
6270
6294
|
}
|
|
6271
6295
|
};
|
|
6296
|
+
|
|
6297
|
+
// src/physics/VRPhysicsBridge.ts
|
|
6298
|
+
var VRPhysicsBridge = class {
|
|
6299
|
+
world;
|
|
6300
|
+
handBodies = /* @__PURE__ */ new Map();
|
|
6301
|
+
// handId -> physicsBodyId
|
|
6302
|
+
lastPositions = /* @__PURE__ */ new Map();
|
|
6303
|
+
lastRotations = /* @__PURE__ */ new Map();
|
|
6304
|
+
onHaptic;
|
|
6305
|
+
constructor(world, onHaptic) {
|
|
6306
|
+
this.world = world;
|
|
6307
|
+
this.onHaptic = onHaptic || (() => {
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
update(vrContext, delta) {
|
|
6311
|
+
this.updateHand(vrContext.hands.left, "left", delta);
|
|
6312
|
+
this.updateHand(vrContext.hands.right, "right", delta);
|
|
6313
|
+
this.checkCollisions();
|
|
6314
|
+
}
|
|
6315
|
+
checkCollisions() {
|
|
6316
|
+
const contacts = this.world.getContacts();
|
|
6317
|
+
for (const contact of contacts) {
|
|
6318
|
+
if (contact.type === "begin") {
|
|
6319
|
+
if (contact.bodyA === "hand_left" || contact.bodyB === "hand_left") {
|
|
6320
|
+
this.onHaptic("left", 0.5, 50);
|
|
6321
|
+
}
|
|
6322
|
+
if (contact.bodyA === "hand_right" || contact.bodyB === "hand_right") {
|
|
6323
|
+
this.onHaptic("right", 0.5, 50);
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
// Make public for testing
|
|
6329
|
+
updateHand(hand, side, delta) {
|
|
6330
|
+
const bodyId = `hand_${side}`;
|
|
6331
|
+
if (!hand) {
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
let body = this.world.getBody(bodyId);
|
|
6335
|
+
if (!body) {
|
|
6336
|
+
const config = {
|
|
6337
|
+
id: bodyId,
|
|
6338
|
+
type: "kinematic",
|
|
6339
|
+
mass: 1,
|
|
6340
|
+
// Infinite mass for kinematic
|
|
6341
|
+
transform: {
|
|
6342
|
+
position: { x: hand.position?.x ?? 0, y: hand.position?.y ?? 0, z: hand.position?.z ?? 0 },
|
|
6343
|
+
rotation: {
|
|
6344
|
+
x: hand.rotation?.x ?? 0,
|
|
6345
|
+
y: hand.rotation?.y ?? 0,
|
|
6346
|
+
z: hand.rotation?.z ?? 0,
|
|
6347
|
+
w: 1
|
|
6348
|
+
}
|
|
6349
|
+
},
|
|
6350
|
+
shape: {
|
|
6351
|
+
type: "sphere",
|
|
6352
|
+
radius: 0.05
|
|
6353
|
+
// 5cm palm radius
|
|
6354
|
+
},
|
|
6355
|
+
material: {
|
|
6356
|
+
friction: 0.5,
|
|
6357
|
+
restitution: 0
|
|
6358
|
+
}
|
|
6359
|
+
};
|
|
6360
|
+
this.world.createBody(config);
|
|
6361
|
+
body = this.world.getBody(bodyId);
|
|
6362
|
+
}
|
|
6363
|
+
if (body) {
|
|
6364
|
+
const safePosX = hand.position?.x ?? 0;
|
|
6365
|
+
const safePosY = hand.position?.y ?? 0;
|
|
6366
|
+
const safePosZ = hand.position?.z ?? 0;
|
|
6367
|
+
const prevPos = this.lastPositions.get(bodyId) || {
|
|
6368
|
+
x: safePosX,
|
|
6369
|
+
y: safePosY,
|
|
6370
|
+
z: safePosZ
|
|
6371
|
+
};
|
|
6372
|
+
const safeDelta = delta > 1e-3 ? delta : 0.016;
|
|
6373
|
+
const rawVelocity = {
|
|
6374
|
+
x: (safePosX - prevPos.x) / safeDelta,
|
|
6375
|
+
y: (safePosY - prevPos.y) / safeDelta,
|
|
6376
|
+
z: (safePosZ - prevPos.z) / safeDelta
|
|
6377
|
+
};
|
|
6378
|
+
const prevVel = body.linearVelocity || { x: 0, y: 0, z: 0 };
|
|
6379
|
+
const smoothingFactor = 0.5;
|
|
6380
|
+
const smoothedVelocity = {
|
|
6381
|
+
x: prevVel.x * (1 - smoothingFactor) + rawVelocity.x * smoothingFactor,
|
|
6382
|
+
y: prevVel.y * (1 - smoothingFactor) + rawVelocity.y * smoothingFactor,
|
|
6383
|
+
z: prevVel.z * (1 - smoothingFactor) + rawVelocity.z * smoothingFactor
|
|
6384
|
+
};
|
|
6385
|
+
this.world.setPosition(bodyId, { x: safePosX, y: safePosY, z: safePosZ });
|
|
6386
|
+
this.world.setLinearVelocity(bodyId, smoothedVelocity);
|
|
6387
|
+
this.lastPositions.set(bodyId, { x: safePosX, y: safePosY, z: safePosZ });
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
getHandBodyId(side) {
|
|
6391
|
+
const id = `hand_${side}`;
|
|
6392
|
+
return this.world.getBody(id) ? id : null;
|
|
6393
|
+
}
|
|
6394
|
+
};
|
|
6395
|
+
|
|
6396
|
+
// src/physics/ClothSim.ts
|
|
6397
|
+
var ClothSim = class {
|
|
6398
|
+
particles = [];
|
|
6399
|
+
constraints = [];
|
|
6400
|
+
config;
|
|
6401
|
+
width = 0;
|
|
6402
|
+
height = 0;
|
|
6403
|
+
constructor(config) {
|
|
6404
|
+
this.config = {
|
|
6405
|
+
gravity: -9.81,
|
|
6406
|
+
damping: 0.99,
|
|
6407
|
+
iterations: 5,
|
|
6408
|
+
wind: { x: 0, y: 0, z: 0 },
|
|
6409
|
+
...config
|
|
6410
|
+
};
|
|
6411
|
+
}
|
|
6412
|
+
// ---------------------------------------------------------------------------
|
|
6413
|
+
// Grid
|
|
6414
|
+
// ---------------------------------------------------------------------------
|
|
6415
|
+
createGrid(width, height, spacing) {
|
|
6416
|
+
this.width = width;
|
|
6417
|
+
this.height = height;
|
|
6418
|
+
this.particles = [];
|
|
6419
|
+
this.constraints = [];
|
|
6420
|
+
for (let row = 0; row < height; row++) {
|
|
6421
|
+
for (let col = 0; col < width; col++) {
|
|
6422
|
+
this.particles.push({
|
|
6423
|
+
x: col * spacing,
|
|
6424
|
+
y: 0,
|
|
6425
|
+
z: row * spacing,
|
|
6426
|
+
prevX: col * spacing,
|
|
6427
|
+
prevY: 0,
|
|
6428
|
+
prevZ: row * spacing,
|
|
6429
|
+
mass: 1,
|
|
6430
|
+
pinned: false
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
for (let row = 0; row < height; row++) {
|
|
6435
|
+
for (let col = 0; col < width; col++) {
|
|
6436
|
+
const idx = row * width + col;
|
|
6437
|
+
if (col < width - 1) {
|
|
6438
|
+
this.addConstraint(idx, idx + 1, spacing);
|
|
6439
|
+
}
|
|
6440
|
+
if (row < height - 1) {
|
|
6441
|
+
this.addConstraint(idx, idx + width, spacing);
|
|
6442
|
+
}
|
|
6443
|
+
if (col < width - 1 && row < height - 1) {
|
|
6444
|
+
const diag = spacing * Math.SQRT2;
|
|
6445
|
+
this.addConstraint(idx, idx + width + 1, diag);
|
|
6446
|
+
this.addConstraint(idx + 1, idx + width, diag);
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
addConstraint(a, b, restLength, stiffness = 1) {
|
|
6452
|
+
this.constraints.push({ particleA: a, particleB: b, restLength, stiffness });
|
|
6453
|
+
}
|
|
6454
|
+
// ---------------------------------------------------------------------------
|
|
6455
|
+
// Pinning
|
|
6456
|
+
// ---------------------------------------------------------------------------
|
|
6457
|
+
pin(index) {
|
|
6458
|
+
if (this.particles[index]) this.particles[index].pinned = true;
|
|
6459
|
+
}
|
|
6460
|
+
unpin(index) {
|
|
6461
|
+
if (this.particles[index]) this.particles[index].pinned = false;
|
|
6462
|
+
}
|
|
6463
|
+
pinTopRow() {
|
|
6464
|
+
for (let col = 0; col < this.width; col++) this.pin(col);
|
|
6465
|
+
}
|
|
6466
|
+
// ---------------------------------------------------------------------------
|
|
6467
|
+
// Update
|
|
6468
|
+
// ---------------------------------------------------------------------------
|
|
6469
|
+
update(dt) {
|
|
6470
|
+
for (const p of this.particles) {
|
|
6471
|
+
if (p.pinned) continue;
|
|
6472
|
+
const vx = (p.x - p.prevX) * this.config.damping;
|
|
6473
|
+
const vy = (p.y - p.prevY) * this.config.damping;
|
|
6474
|
+
const vz = (p.z - p.prevZ) * this.config.damping;
|
|
6475
|
+
p.prevX = p.x;
|
|
6476
|
+
p.prevY = p.y;
|
|
6477
|
+
p.prevZ = p.z;
|
|
6478
|
+
p.x += vx + this.config.wind.x * dt * dt / p.mass;
|
|
6479
|
+
p.y += vy + this.config.gravity * dt * dt;
|
|
6480
|
+
p.z += vz + this.config.wind.z * dt * dt / p.mass;
|
|
6481
|
+
}
|
|
6482
|
+
for (let iter = 0; iter < this.config.iterations; iter++) {
|
|
6483
|
+
for (const c of this.constraints) {
|
|
6484
|
+
const a = this.particles[c.particleA];
|
|
6485
|
+
const b = this.particles[c.particleB];
|
|
6486
|
+
const dx = b.x - a.x;
|
|
6487
|
+
const dy = b.y - a.y;
|
|
6488
|
+
const dz = b.z - a.z;
|
|
6489
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
6490
|
+
if (dist === 0) continue;
|
|
6491
|
+
const diff = (c.restLength - dist) / dist * c.stiffness * 0.5;
|
|
6492
|
+
const ox = dx * diff;
|
|
6493
|
+
const oy = dy * diff;
|
|
6494
|
+
const oz = dz * diff;
|
|
6495
|
+
if (!a.pinned) {
|
|
6496
|
+
a.x -= ox;
|
|
6497
|
+
a.y -= oy;
|
|
6498
|
+
a.z -= oz;
|
|
6499
|
+
}
|
|
6500
|
+
if (!b.pinned) {
|
|
6501
|
+
b.x += ox;
|
|
6502
|
+
b.y += oy;
|
|
6503
|
+
b.z += oz;
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
// ---------------------------------------------------------------------------
|
|
6509
|
+
// Wind
|
|
6510
|
+
// ---------------------------------------------------------------------------
|
|
6511
|
+
setWind(x, y, z) {
|
|
6512
|
+
this.config.wind = { x, y, z };
|
|
6513
|
+
}
|
|
6514
|
+
// ---------------------------------------------------------------------------
|
|
6515
|
+
// Queries
|
|
6516
|
+
// ---------------------------------------------------------------------------
|
|
6517
|
+
getParticle(index) {
|
|
6518
|
+
return this.particles[index];
|
|
6519
|
+
}
|
|
6520
|
+
getParticleCount() {
|
|
6521
|
+
return this.particles.length;
|
|
6522
|
+
}
|
|
6523
|
+
getConstraintCount() {
|
|
6524
|
+
return this.constraints.length;
|
|
6525
|
+
}
|
|
6526
|
+
getGridSize() {
|
|
6527
|
+
return { width: this.width, height: this.height };
|
|
6528
|
+
}
|
|
6529
|
+
getAABB() {
|
|
6530
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
6531
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
6532
|
+
for (const p of this.particles) {
|
|
6533
|
+
minX = Math.min(minX, p.x);
|
|
6534
|
+
minY = Math.min(minY, p.y);
|
|
6535
|
+
minZ = Math.min(minZ, p.z);
|
|
6536
|
+
maxX = Math.max(maxX, p.x);
|
|
6537
|
+
maxY = Math.max(maxY, p.y);
|
|
6538
|
+
maxZ = Math.max(maxZ, p.z);
|
|
6539
|
+
}
|
|
6540
|
+
return { min: { x: minX, y: minY, z: minZ }, max: { x: maxX, y: maxY, z: maxZ } };
|
|
6541
|
+
}
|
|
6542
|
+
};
|
|
6543
|
+
|
|
6544
|
+
// src/physics/ConstraintSolver.ts
|
|
6545
|
+
function v3(x, y, z) {
|
|
6546
|
+
return { x, y, z };
|
|
6547
|
+
}
|
|
6548
|
+
function v3Add(a, b) {
|
|
6549
|
+
return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
|
|
6550
|
+
}
|
|
6551
|
+
function v3Sub(a, b) {
|
|
6552
|
+
return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
|
6553
|
+
}
|
|
6554
|
+
function v3Scale(v, s) {
|
|
6555
|
+
return { x: v.x * s, y: v.y * s, z: v.z * s };
|
|
6556
|
+
}
|
|
6557
|
+
function v3Dot(a, b) {
|
|
6558
|
+
return a.x * b.x + a.y * b.y + a.z * b.z;
|
|
6559
|
+
}
|
|
6560
|
+
function v3Length(v) {
|
|
6561
|
+
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
|
6562
|
+
}
|
|
6563
|
+
function v3Normalize(v) {
|
|
6564
|
+
const len = v3Length(v);
|
|
6565
|
+
return len > 1e-10 ? v3Scale(v, 1 / len) : v3(0, 0, 0);
|
|
6566
|
+
}
|
|
6567
|
+
function v3Zero() {
|
|
6568
|
+
return { x: 0, y: 0, z: 0 };
|
|
6569
|
+
}
|
|
6570
|
+
var DEFAULT_CONFIG2 = {
|
|
6571
|
+
iterations: 10,
|
|
6572
|
+
baumgarte: 0.2,
|
|
6573
|
+
warmStarting: true,
|
|
6574
|
+
slop: 5e-3
|
|
6575
|
+
};
|
|
6576
|
+
var ConstraintSolver = class {
|
|
6577
|
+
config;
|
|
6578
|
+
solved = [];
|
|
6579
|
+
constructor(config = {}) {
|
|
6580
|
+
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
6581
|
+
}
|
|
6582
|
+
// ---------------------------------------------------------------------------
|
|
6583
|
+
// Public API
|
|
6584
|
+
// ---------------------------------------------------------------------------
|
|
6585
|
+
/**
|
|
6586
|
+
* Add a constraint to be solved.
|
|
6587
|
+
*/
|
|
6588
|
+
addConstraint(constraint, bodyA, bodyB = null) {
|
|
6589
|
+
this.solved.push({
|
|
6590
|
+
constraint,
|
|
6591
|
+
bodyA,
|
|
6592
|
+
bodyB,
|
|
6593
|
+
accumulatedImpulse: v3Zero(),
|
|
6594
|
+
accumulatedAngularImpulse: v3Zero(),
|
|
6595
|
+
broken: false
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
/**
|
|
6599
|
+
* Remove a constraint by ID.
|
|
6600
|
+
*/
|
|
6601
|
+
removeConstraint(constraintId) {
|
|
6602
|
+
const idx = this.solved.findIndex((s) => s.constraint.id === constraintId);
|
|
6603
|
+
if (idx < 0) return false;
|
|
6604
|
+
this.solved.splice(idx, 1);
|
|
6605
|
+
return true;
|
|
6606
|
+
}
|
|
6607
|
+
/**
|
|
6608
|
+
* Get all active constraints.
|
|
6609
|
+
*/
|
|
6610
|
+
getConstraints() {
|
|
6611
|
+
return this.solved.filter((s) => !s.broken).map((s) => s.constraint);
|
|
6612
|
+
}
|
|
6613
|
+
/**
|
|
6614
|
+
* Solve all constraints for one timestep.
|
|
6615
|
+
* Returns velocity corrections to apply to rigid bodies.
|
|
6616
|
+
*/
|
|
6617
|
+
solve(dt) {
|
|
6618
|
+
const corrections = /* @__PURE__ */ new Map();
|
|
6619
|
+
if (this.config.warmStarting) {
|
|
6620
|
+
for (const sc of this.solved) {
|
|
6621
|
+
if (sc.broken) continue;
|
|
6622
|
+
this.applyWarmStart(sc, corrections);
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
for (let iter = 0; iter < this.config.iterations; iter++) {
|
|
6626
|
+
for (const sc of this.solved) {
|
|
6627
|
+
if (sc.broken) continue;
|
|
6628
|
+
this.solveConstraint(sc, dt, corrections);
|
|
6629
|
+
}
|
|
6630
|
+
}
|
|
6631
|
+
for (const sc of this.solved) {
|
|
6632
|
+
if (sc.broken) continue;
|
|
6633
|
+
const breakForce = sc.constraint.breakForce;
|
|
6634
|
+
if (breakForce !== void 0 && breakForce > 0) {
|
|
6635
|
+
const impulseLen = v3Length(sc.accumulatedImpulse) / Math.max(dt, 1e-6);
|
|
6636
|
+
if (impulseLen > breakForce) {
|
|
6637
|
+
sc.broken = true;
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
return corrections;
|
|
6642
|
+
}
|
|
6643
|
+
/**
|
|
6644
|
+
* Get broken constraint IDs.
|
|
6645
|
+
*/
|
|
6646
|
+
getBrokenConstraints() {
|
|
6647
|
+
return this.solved.filter((s) => s.broken).map((s) => s.constraint.id);
|
|
6648
|
+
}
|
|
6649
|
+
/**
|
|
6650
|
+
* Clear all constraints.
|
|
6651
|
+
*/
|
|
6652
|
+
clear() {
|
|
6653
|
+
this.solved = [];
|
|
6654
|
+
}
|
|
6655
|
+
// ---------------------------------------------------------------------------
|
|
6656
|
+
// Internal: Dispatch
|
|
6657
|
+
// ---------------------------------------------------------------------------
|
|
6658
|
+
solveConstraint(sc, dt, corrections) {
|
|
6659
|
+
switch (sc.constraint.type) {
|
|
6660
|
+
case "fixed":
|
|
6661
|
+
this.solveFixed(sc, dt, corrections);
|
|
6662
|
+
break;
|
|
6663
|
+
case "distance":
|
|
6664
|
+
this.solveDistance(
|
|
6665
|
+
sc,
|
|
6666
|
+
dt,
|
|
6667
|
+
corrections
|
|
6668
|
+
);
|
|
6669
|
+
break;
|
|
6670
|
+
case "spring":
|
|
6671
|
+
this.solveSpring(
|
|
6672
|
+
sc,
|
|
6673
|
+
dt,
|
|
6674
|
+
corrections
|
|
6675
|
+
);
|
|
6676
|
+
break;
|
|
6677
|
+
case "hinge":
|
|
6678
|
+
this.solveHinge(sc, dt, corrections);
|
|
6679
|
+
break;
|
|
6680
|
+
case "ball":
|
|
6681
|
+
this.solveBall(sc, dt, corrections);
|
|
6682
|
+
break;
|
|
6683
|
+
case "slider":
|
|
6684
|
+
this.solveSlider(
|
|
6685
|
+
sc,
|
|
6686
|
+
dt,
|
|
6687
|
+
corrections
|
|
6688
|
+
);
|
|
6689
|
+
break;
|
|
6690
|
+
case "cone":
|
|
6691
|
+
this.solveCone(sc, dt, corrections);
|
|
6692
|
+
break;
|
|
6693
|
+
case "generic6dof":
|
|
6694
|
+
this.solveGeneric6DOF(
|
|
6695
|
+
sc,
|
|
6696
|
+
dt,
|
|
6697
|
+
corrections
|
|
6698
|
+
);
|
|
6699
|
+
break;
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
// ---------------------------------------------------------------------------
|
|
6703
|
+
// Constraint Solvers
|
|
6704
|
+
// ---------------------------------------------------------------------------
|
|
6705
|
+
solveFixed(sc, dt, corrections) {
|
|
6706
|
+
const c = sc.constraint;
|
|
6707
|
+
const pivotWorld = v3Add(sc.bodyA.position, c.pivotA);
|
|
6708
|
+
const targetWorld = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : pivotWorld;
|
|
6709
|
+
const error = v3Sub(targetWorld, pivotWorld);
|
|
6710
|
+
const correction = v3Scale(error, this.config.baumgarte / Math.max(dt, 1e-6));
|
|
6711
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, correction, v3Zero());
|
|
6712
|
+
if (sc.bodyB) {
|
|
6713
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(correction, -1), v3Zero());
|
|
6714
|
+
}
|
|
6715
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, correction);
|
|
6716
|
+
}
|
|
6717
|
+
solveDistance(sc, dt, corrections) {
|
|
6718
|
+
const c = sc.constraint;
|
|
6719
|
+
const anchorA = v3Add(sc.bodyA.position, c.pivotA);
|
|
6720
|
+
const anchorB = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : v3Add(sc.bodyA.position, v3(c.distance, 0, 0));
|
|
6721
|
+
const delta = v3Sub(anchorB, anchorA);
|
|
6722
|
+
const currentDist = v3Length(delta);
|
|
6723
|
+
const dir = currentDist > 1e-10 ? v3Scale(delta, 1 / currentDist) : v3(1, 0, 0);
|
|
6724
|
+
const penetration = currentDist - c.distance;
|
|
6725
|
+
if (Math.abs(penetration) < this.config.slop) return;
|
|
6726
|
+
const stiffness = c.stiffness ?? 1;
|
|
6727
|
+
const baumgarte = this.config.baumgarte * stiffness;
|
|
6728
|
+
const impulse = v3Scale(dir, penetration * baumgarte / Math.max(dt, 1e-6));
|
|
6729
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, impulse, v3Zero());
|
|
6730
|
+
if (sc.bodyB) {
|
|
6731
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(impulse, -1), v3Zero());
|
|
6732
|
+
}
|
|
6733
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, impulse);
|
|
6734
|
+
}
|
|
6735
|
+
solveSpring(sc, dt, corrections) {
|
|
6736
|
+
const c = sc.constraint;
|
|
6737
|
+
const anchorA = v3Add(sc.bodyA.position, c.pivotA);
|
|
6738
|
+
const anchorB = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : v3Add(sc.bodyA.position, v3(c.restLength, 0, 0));
|
|
6739
|
+
const delta = v3Sub(anchorB, anchorA);
|
|
6740
|
+
const currentLen = v3Length(delta);
|
|
6741
|
+
const dir = currentLen > 1e-10 ? v3Scale(delta, 1 / currentLen) : v3(1, 0, 0);
|
|
6742
|
+
const displacement = currentLen - c.restLength;
|
|
6743
|
+
const springForce = -c.stiffness * displacement;
|
|
6744
|
+
const relVel = sc.bodyB ? v3Sub(sc.bodyB.linearVelocity, sc.bodyA.linearVelocity) : v3Scale(sc.bodyA.linearVelocity, -1);
|
|
6745
|
+
const dampingForce = -c.damping * v3Dot(relVel, dir);
|
|
6746
|
+
const totalForce = springForce + dampingForce;
|
|
6747
|
+
const impulse = v3Scale(dir, totalForce * dt);
|
|
6748
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, v3Scale(impulse, -1), v3Zero());
|
|
6749
|
+
if (sc.bodyB) {
|
|
6750
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, impulse, v3Zero());
|
|
6751
|
+
}
|
|
6752
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, impulse);
|
|
6753
|
+
}
|
|
6754
|
+
solveHinge(sc, dt, corrections) {
|
|
6755
|
+
const c = sc.constraint;
|
|
6756
|
+
const pivotWorld = v3Add(sc.bodyA.position, c.pivotA);
|
|
6757
|
+
const targetWorld = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : pivotWorld;
|
|
6758
|
+
const posError = v3Sub(targetWorld, pivotWorld);
|
|
6759
|
+
const posCorrection = v3Scale(posError, this.config.baumgarte / Math.max(dt, 1e-6));
|
|
6760
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, posCorrection, v3Zero());
|
|
6761
|
+
if (sc.bodyB) {
|
|
6762
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(posCorrection, -1), v3Zero());
|
|
6763
|
+
}
|
|
6764
|
+
if (c.motor) {
|
|
6765
|
+
const axis = v3Normalize(c.axisA);
|
|
6766
|
+
const currentAngVel = v3Dot(sc.bodyA.angularVelocity, axis);
|
|
6767
|
+
const velError = c.motor.targetVelocity - currentAngVel;
|
|
6768
|
+
const motorImpulse = Math.min(Math.abs(velError * dt), c.motor.maxForce * dt) * Math.sign(velError);
|
|
6769
|
+
const angImpulse = v3Scale(axis, motorImpulse);
|
|
6770
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, v3Zero(), angImpulse);
|
|
6771
|
+
}
|
|
6772
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, posCorrection);
|
|
6773
|
+
}
|
|
6774
|
+
solveBall(sc, dt, corrections) {
|
|
6775
|
+
const c = sc.constraint;
|
|
6776
|
+
const pivotWorld = v3Add(sc.bodyA.position, c.pivotA);
|
|
6777
|
+
const targetWorld = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : pivotWorld;
|
|
6778
|
+
const error = v3Sub(targetWorld, pivotWorld);
|
|
6779
|
+
const correction = v3Scale(error, this.config.baumgarte / Math.max(dt, 1e-6));
|
|
6780
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, correction, v3Zero());
|
|
6781
|
+
if (sc.bodyB) {
|
|
6782
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(correction, -1), v3Zero());
|
|
6783
|
+
}
|
|
6784
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, correction);
|
|
6785
|
+
}
|
|
6786
|
+
solveSlider(sc, dt, corrections) {
|
|
6787
|
+
const c = sc.constraint;
|
|
6788
|
+
const axis = v3Normalize(c.axisA);
|
|
6789
|
+
const pivotWorld = v3Add(sc.bodyA.position, c.pivotA);
|
|
6790
|
+
const targetWorld = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : pivotWorld;
|
|
6791
|
+
const delta = v3Sub(targetWorld, pivotWorld);
|
|
6792
|
+
const onAxis = v3Dot(delta, axis);
|
|
6793
|
+
const perp = v3Sub(delta, v3Scale(axis, onAxis));
|
|
6794
|
+
const perpCorrection = v3Scale(perp, this.config.baumgarte / Math.max(dt, 1e-6));
|
|
6795
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, perpCorrection, v3Zero());
|
|
6796
|
+
if (sc.bodyB) {
|
|
6797
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(perpCorrection, -1), v3Zero());
|
|
6798
|
+
}
|
|
6799
|
+
if (c.limits) {
|
|
6800
|
+
if (onAxis < c.limits.low) {
|
|
6801
|
+
const limitImpulse = v3Scale(
|
|
6802
|
+
axis,
|
|
6803
|
+
(c.limits.low - onAxis) * this.config.baumgarte / Math.max(dt, 1e-6)
|
|
6804
|
+
);
|
|
6805
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, limitImpulse, v3Zero());
|
|
6806
|
+
} else if (onAxis > c.limits.high) {
|
|
6807
|
+
const limitImpulse = v3Scale(
|
|
6808
|
+
axis,
|
|
6809
|
+
(c.limits.high - onAxis) * this.config.baumgarte / Math.max(dt, 1e-6)
|
|
6810
|
+
);
|
|
6811
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, limitImpulse, v3Zero());
|
|
6812
|
+
}
|
|
6813
|
+
}
|
|
6814
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, perpCorrection);
|
|
6815
|
+
}
|
|
6816
|
+
solveCone(sc, dt, corrections) {
|
|
6817
|
+
const c = sc.constraint;
|
|
6818
|
+
const pivotWorld = v3Add(sc.bodyA.position, c.pivotA);
|
|
6819
|
+
const targetWorld = sc.bodyB ? v3Add(sc.bodyB.position, c.pivotB || v3Zero()) : pivotWorld;
|
|
6820
|
+
const posError = v3Sub(targetWorld, pivotWorld);
|
|
6821
|
+
const posCorrection = v3Scale(posError, this.config.baumgarte / Math.max(dt, 1e-6));
|
|
6822
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, posCorrection, v3Zero());
|
|
6823
|
+
if (sc.bodyB) {
|
|
6824
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(posCorrection, -1), v3Zero());
|
|
6825
|
+
}
|
|
6826
|
+
const twistAxis = v3Normalize(c.axisA);
|
|
6827
|
+
const twistVel = v3Dot(sc.bodyA.angularVelocity, twistAxis);
|
|
6828
|
+
if (Math.abs(twistVel) > c.twistSpan) {
|
|
6829
|
+
const twistCorrection = v3Scale(twistAxis, -twistVel * 0.5);
|
|
6830
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, v3Zero(), twistCorrection);
|
|
6831
|
+
}
|
|
6832
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, posCorrection);
|
|
6833
|
+
}
|
|
6834
|
+
solveGeneric6DOF(sc, dt, corrections) {
|
|
6835
|
+
const c = sc.constraint;
|
|
6836
|
+
const pos = sc.bodyA.position;
|
|
6837
|
+
const linCorrection = v3Zero();
|
|
6838
|
+
if (pos.x < c.linearLowerLimit.x)
|
|
6839
|
+
linCorrection.x = (c.linearLowerLimit.x - pos.x) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6840
|
+
if (pos.x > c.linearUpperLimit.x)
|
|
6841
|
+
linCorrection.x = (c.linearUpperLimit.x - pos.x) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6842
|
+
if (pos.y < c.linearLowerLimit.y)
|
|
6843
|
+
linCorrection.y = (c.linearLowerLimit.y - pos.y) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6844
|
+
if (pos.y > c.linearUpperLimit.y)
|
|
6845
|
+
linCorrection.y = (c.linearUpperLimit.y - pos.y) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6846
|
+
if (pos.z < c.linearLowerLimit.z)
|
|
6847
|
+
linCorrection.z = (c.linearLowerLimit.z - pos.z) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6848
|
+
if (pos.z > c.linearUpperLimit.z)
|
|
6849
|
+
linCorrection.z = (c.linearUpperLimit.z - pos.z) * this.config.baumgarte / Math.max(dt, 1e-6);
|
|
6850
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, linCorrection, v3Zero());
|
|
6851
|
+
sc.accumulatedImpulse = v3Add(sc.accumulatedImpulse, linCorrection);
|
|
6852
|
+
}
|
|
6853
|
+
// ---------------------------------------------------------------------------
|
|
6854
|
+
// Helpers
|
|
6855
|
+
// ---------------------------------------------------------------------------
|
|
6856
|
+
applyWarmStart(sc, corrections) {
|
|
6857
|
+
const scaled = v3Scale(sc.accumulatedImpulse, 0.8);
|
|
6858
|
+
this.accumulateCorrection(corrections, sc.bodyA.id, scaled, v3Zero());
|
|
6859
|
+
if (sc.bodyB) {
|
|
6860
|
+
this.accumulateCorrection(corrections, sc.bodyB.id, v3Scale(scaled, -1), v3Zero());
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
accumulateCorrection(corrections, bodyId, linear, angular) {
|
|
6864
|
+
const existing = corrections.get(bodyId) || {
|
|
6865
|
+
linearVelocity: v3Zero(),
|
|
6866
|
+
angularVelocity: v3Zero()
|
|
6867
|
+
};
|
|
6868
|
+
corrections.set(bodyId, {
|
|
6869
|
+
linearVelocity: v3Add(existing.linearVelocity, linear),
|
|
6870
|
+
angularVelocity: v3Add(existing.angularVelocity, angular)
|
|
6871
|
+
});
|
|
6872
|
+
}
|
|
6873
|
+
};
|
|
6874
|
+
|
|
6875
|
+
// src/physics/DeformableMesh.ts
|
|
6876
|
+
var DeformableMesh = class {
|
|
6877
|
+
vertices = [];
|
|
6878
|
+
springs = [];
|
|
6879
|
+
config;
|
|
6880
|
+
restCentroid = { x: 0, y: 0, z: 0 };
|
|
6881
|
+
constructor(config) {
|
|
6882
|
+
this.config = {
|
|
6883
|
+
stiffness: 100,
|
|
6884
|
+
damping: 0.95,
|
|
6885
|
+
shapeMatchingStrength: 0.5,
|
|
6886
|
+
maxDisplacement: 5,
|
|
6887
|
+
plasticity: 0,
|
|
6888
|
+
...config
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
// ---------------------------------------------------------------------------
|
|
6892
|
+
// Mesh Setup
|
|
6893
|
+
// ---------------------------------------------------------------------------
|
|
6894
|
+
setVertices(positions) {
|
|
6895
|
+
this.vertices = positions.map((p) => ({
|
|
6896
|
+
rest: { ...p },
|
|
6897
|
+
current: { ...p },
|
|
6898
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
6899
|
+
mass: 1,
|
|
6900
|
+
locked: false
|
|
6901
|
+
}));
|
|
6902
|
+
this.computeRestCentroid();
|
|
6903
|
+
}
|
|
6904
|
+
addSpring(a, b, stiffness, damping) {
|
|
6905
|
+
const pa = this.vertices[a].rest, pb = this.vertices[b].rest;
|
|
6906
|
+
const dx = pb.x - pa.x, dy = pb.y - pa.y, dz = pb.z - pa.z;
|
|
6907
|
+
this.springs.push({
|
|
6908
|
+
a,
|
|
6909
|
+
b,
|
|
6910
|
+
restLength: Math.sqrt(dx * dx + dy * dy + dz * dz),
|
|
6911
|
+
stiffness: stiffness ?? this.config.stiffness,
|
|
6912
|
+
damping: damping ?? 5
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6915
|
+
autoConnectRadius(radius) {
|
|
6916
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
6917
|
+
for (let j = i + 1; j < this.vertices.length; j++) {
|
|
6918
|
+
const a = this.vertices[i].rest, b = this.vertices[j].rest;
|
|
6919
|
+
const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
|
|
6920
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
6921
|
+
if (dist <= radius) this.addSpring(i, j);
|
|
6922
|
+
}
|
|
6923
|
+
}
|
|
6924
|
+
}
|
|
6925
|
+
computeRestCentroid() {
|
|
6926
|
+
let cx = 0, cy = 0, cz = 0;
|
|
6927
|
+
for (const v of this.vertices) {
|
|
6928
|
+
cx += v.rest.x;
|
|
6929
|
+
cy += v.rest.y;
|
|
6930
|
+
cz += v.rest.z;
|
|
6931
|
+
}
|
|
6932
|
+
const n = this.vertices.length || 1;
|
|
6933
|
+
this.restCentroid = { x: cx / n, y: cy / n, z: cz / n };
|
|
6934
|
+
}
|
|
6935
|
+
// ---------------------------------------------------------------------------
|
|
6936
|
+
// Deformation
|
|
6937
|
+
// ---------------------------------------------------------------------------
|
|
6938
|
+
applyImpact(center, radius, force) {
|
|
6939
|
+
for (const v of this.vertices) {
|
|
6940
|
+
if (v.locked) continue;
|
|
6941
|
+
const dx = v.current.x - center.x;
|
|
6942
|
+
const dy = v.current.y - center.y;
|
|
6943
|
+
const dz = v.current.z - center.z;
|
|
6944
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
6945
|
+
if (dist > radius || dist === 0) continue;
|
|
6946
|
+
const falloff = 1 - dist / radius;
|
|
6947
|
+
const strength = force * falloff / v.mass;
|
|
6948
|
+
const n = dist;
|
|
6949
|
+
v.velocity.x += dx / n * strength;
|
|
6950
|
+
v.velocity.y += dy / n * strength;
|
|
6951
|
+
v.velocity.z += dz / n * strength;
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
// ---------------------------------------------------------------------------
|
|
6955
|
+
// Simulation
|
|
6956
|
+
// ---------------------------------------------------------------------------
|
|
6957
|
+
update(dt) {
|
|
6958
|
+
for (const s of this.springs) {
|
|
6959
|
+
const a = this.vertices[s.a], b = this.vertices[s.b];
|
|
6960
|
+
const dx = b.current.x - a.current.x;
|
|
6961
|
+
const dy = b.current.y - a.current.y;
|
|
6962
|
+
const dz = b.current.z - a.current.z;
|
|
6963
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1e-4;
|
|
6964
|
+
const stretch = dist - s.restLength;
|
|
6965
|
+
const fx = dx / dist * stretch * s.stiffness;
|
|
6966
|
+
const fy = dy / dist * stretch * s.stiffness;
|
|
6967
|
+
const fz = dz / dist * stretch * s.stiffness;
|
|
6968
|
+
const dvx = b.velocity.x - a.velocity.x;
|
|
6969
|
+
const dvy = b.velocity.y - a.velocity.y;
|
|
6970
|
+
const dvz = b.velocity.z - a.velocity.z;
|
|
6971
|
+
if (!a.locked) {
|
|
6972
|
+
a.velocity.x += (fx + dvx * s.damping) * dt / a.mass;
|
|
6973
|
+
a.velocity.y += (fy + dvy * s.damping) * dt / a.mass;
|
|
6974
|
+
a.velocity.z += (fz + dvz * s.damping) * dt / a.mass;
|
|
6975
|
+
}
|
|
6976
|
+
if (!b.locked) {
|
|
6977
|
+
b.velocity.x -= (fx + dvx * s.damping) * dt / b.mass;
|
|
6978
|
+
b.velocity.y -= (fy + dvy * s.damping) * dt / b.mass;
|
|
6979
|
+
b.velocity.z -= (fz + dvz * s.damping) * dt / b.mass;
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6982
|
+
if (this.config.shapeMatchingStrength > 0) {
|
|
6983
|
+
let cx = 0, cy = 0, cz = 0;
|
|
6984
|
+
for (const v of this.vertices) {
|
|
6985
|
+
cx += v.current.x;
|
|
6986
|
+
cy += v.current.y;
|
|
6987
|
+
cz += v.current.z;
|
|
6988
|
+
}
|
|
6989
|
+
const n = this.vertices.length || 1;
|
|
6990
|
+
cx /= n;
|
|
6991
|
+
cy /= n;
|
|
6992
|
+
cz /= n;
|
|
6993
|
+
for (const v of this.vertices) {
|
|
6994
|
+
if (v.locked) continue;
|
|
6995
|
+
const goalX = v.rest.x - this.restCentroid.x + cx;
|
|
6996
|
+
const goalY = v.rest.y - this.restCentroid.y + cy;
|
|
6997
|
+
const goalZ = v.rest.z - this.restCentroid.z + cz;
|
|
6998
|
+
v.velocity.x += (goalX - v.current.x) * this.config.shapeMatchingStrength * dt * 10;
|
|
6999
|
+
v.velocity.y += (goalY - v.current.y) * this.config.shapeMatchingStrength * dt * 10;
|
|
7000
|
+
v.velocity.z += (goalZ - v.current.z) * this.config.shapeMatchingStrength * dt * 10;
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
for (const v of this.vertices) {
|
|
7004
|
+
if (v.locked) continue;
|
|
7005
|
+
v.velocity.x *= this.config.damping;
|
|
7006
|
+
v.velocity.y *= this.config.damping;
|
|
7007
|
+
v.velocity.z *= this.config.damping;
|
|
7008
|
+
v.current.x += v.velocity.x * dt;
|
|
7009
|
+
v.current.y += v.velocity.y * dt;
|
|
7010
|
+
v.current.z += v.velocity.z * dt;
|
|
7011
|
+
const dx = v.current.x - v.rest.x;
|
|
7012
|
+
const dy = v.current.y - v.rest.y;
|
|
7013
|
+
const dz = v.current.z - v.rest.z;
|
|
7014
|
+
const disp = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
7015
|
+
if (disp > this.config.maxDisplacement) {
|
|
7016
|
+
const scale = this.config.maxDisplacement / disp;
|
|
7017
|
+
v.current.x = v.rest.x + dx * scale;
|
|
7018
|
+
v.current.y = v.rest.y + dy * scale;
|
|
7019
|
+
v.current.z = v.rest.z + dz * scale;
|
|
7020
|
+
}
|
|
7021
|
+
if (this.config.plasticity > 0 && disp > 0.01) {
|
|
7022
|
+
v.rest.x += dx * this.config.plasticity * dt;
|
|
7023
|
+
v.rest.y += dy * this.config.plasticity * dt;
|
|
7024
|
+
v.rest.z += dz * this.config.plasticity * dt;
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
}
|
|
7028
|
+
// ---------------------------------------------------------------------------
|
|
7029
|
+
// Queries
|
|
7030
|
+
// ---------------------------------------------------------------------------
|
|
7031
|
+
getVertices() {
|
|
7032
|
+
return this.vertices;
|
|
7033
|
+
}
|
|
7034
|
+
getVertex(index) {
|
|
7035
|
+
return this.vertices[index];
|
|
7036
|
+
}
|
|
7037
|
+
getVertexCount() {
|
|
7038
|
+
return this.vertices.length;
|
|
7039
|
+
}
|
|
7040
|
+
getSpringCount() {
|
|
7041
|
+
return this.springs.length;
|
|
7042
|
+
}
|
|
7043
|
+
getDisplacement(index) {
|
|
7044
|
+
const v = this.vertices[index];
|
|
7045
|
+
if (!v) return 0;
|
|
7046
|
+
const dx = v.current.x - v.rest.x, dy = v.current.y - v.rest.y, dz = v.current.z - v.rest.z;
|
|
7047
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
7048
|
+
}
|
|
7049
|
+
getMaxDisplacement() {
|
|
7050
|
+
let max = 0;
|
|
7051
|
+
for (let i = 0; i < this.vertices.length; i++) max = Math.max(max, this.getDisplacement(i));
|
|
7052
|
+
return max;
|
|
7053
|
+
}
|
|
7054
|
+
};
|
|
7055
|
+
|
|
7056
|
+
// src/physics/FluidSim.ts
|
|
7057
|
+
var FluidSim = class {
|
|
7058
|
+
particles = [];
|
|
7059
|
+
config;
|
|
7060
|
+
constructor(config) {
|
|
7061
|
+
this.config = {
|
|
7062
|
+
restDensity: 1e3,
|
|
7063
|
+
gasConstant: 2e3,
|
|
7064
|
+
viscosity: 200,
|
|
7065
|
+
surfaceTension: 0.5,
|
|
7066
|
+
gravity: { x: 0, y: -9.81, z: 0 },
|
|
7067
|
+
smoothingRadius: 1,
|
|
7068
|
+
timeStep: 0.016,
|
|
7069
|
+
boundaryMin: { x: -10, y: -10, z: -10 },
|
|
7070
|
+
boundaryMax: { x: 10, y: 10, z: 10 },
|
|
7071
|
+
boundaryDamping: 0.3,
|
|
7072
|
+
...config
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
// ---------------------------------------------------------------------------
|
|
7076
|
+
// Particle Management
|
|
7077
|
+
// ---------------------------------------------------------------------------
|
|
7078
|
+
addParticle(position, velocity) {
|
|
7079
|
+
this.particles.push({
|
|
7080
|
+
position: { ...position },
|
|
7081
|
+
velocity: velocity ? { ...velocity } : { x: 0, y: 0, z: 0 },
|
|
7082
|
+
density: this.config.restDensity,
|
|
7083
|
+
pressure: 0,
|
|
7084
|
+
mass: 1
|
|
7085
|
+
});
|
|
7086
|
+
}
|
|
7087
|
+
addBlock(min, max, spacing) {
|
|
7088
|
+
let count = 0;
|
|
7089
|
+
for (let x = min.x; x <= max.x; x += spacing) {
|
|
7090
|
+
for (let y = min.y; y <= max.y; y += spacing) {
|
|
7091
|
+
for (let z = min.z; z <= max.z; z += spacing) {
|
|
7092
|
+
this.addParticle([x, y, z]);
|
|
7093
|
+
count++;
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
return count;
|
|
7098
|
+
}
|
|
7099
|
+
// ---------------------------------------------------------------------------
|
|
7100
|
+
// SPH Kernels
|
|
7101
|
+
// ---------------------------------------------------------------------------
|
|
7102
|
+
poly6(r2, h) {
|
|
7103
|
+
if (r2 >= h * h) return 0;
|
|
7104
|
+
const h2 = h * h;
|
|
7105
|
+
const diff = h2 - r2;
|
|
7106
|
+
return 315 / (64 * Math.PI * Math.pow(h, 9)) * diff * diff * diff;
|
|
7107
|
+
}
|
|
7108
|
+
spikyGrad(r, h) {
|
|
7109
|
+
if (r >= h || r === 0) return 0;
|
|
7110
|
+
const diff = h - r;
|
|
7111
|
+
return -(45 / (Math.PI * Math.pow(h, 6))) * diff * diff;
|
|
7112
|
+
}
|
|
7113
|
+
viscosityLaplacian(r, h) {
|
|
7114
|
+
if (r >= h) return 0;
|
|
7115
|
+
return 45 / (Math.PI * Math.pow(h, 6)) * (h - r);
|
|
7116
|
+
}
|
|
7117
|
+
// ---------------------------------------------------------------------------
|
|
7118
|
+
// Simulation Step
|
|
7119
|
+
// ---------------------------------------------------------------------------
|
|
7120
|
+
update() {
|
|
7121
|
+
const h = this.config.smoothingRadius;
|
|
7122
|
+
const dt = this.config.timeStep;
|
|
7123
|
+
for (const pi of this.particles) {
|
|
7124
|
+
pi.density = 0;
|
|
7125
|
+
for (const pj of this.particles) {
|
|
7126
|
+
const dx = pj.position[0] - pi.position[0];
|
|
7127
|
+
const dy = pj.position[1] - pi.position[1];
|
|
7128
|
+
const dz = pj.position[2] - pi.position[2];
|
|
7129
|
+
const r2 = dx * dx + dy * dy + dz * dz;
|
|
7130
|
+
pi.density += pj.mass * this.poly6(r2, h);
|
|
7131
|
+
}
|
|
7132
|
+
pi.pressure = this.config.gasConstant * (pi.density - this.config.restDensity);
|
|
7133
|
+
}
|
|
7134
|
+
for (const pi of this.particles) {
|
|
7135
|
+
let fx = 0, fy = 0, fz = 0;
|
|
7136
|
+
for (const pj of this.particles) {
|
|
7137
|
+
if (pi === pj) continue;
|
|
7138
|
+
const dx = pj.position[0] - pi.position[0];
|
|
7139
|
+
const dy = pj.position[1] - pi.position[1];
|
|
7140
|
+
const dz = pj.position[2] - pi.position[2];
|
|
7141
|
+
const r = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
7142
|
+
if (r < h && r > 0) {
|
|
7143
|
+
const pressGrad = this.spikyGrad(r, h);
|
|
7144
|
+
const pressScale = -pj.mass * (pi.pressure + pj.pressure) / (2 * pj.density || 1) * pressGrad;
|
|
7145
|
+
fx += dx / r * pressScale;
|
|
7146
|
+
fy += dy / r * pressScale;
|
|
7147
|
+
fz += dz / r * pressScale;
|
|
7148
|
+
const viscLap = this.viscosityLaplacian(r, h);
|
|
7149
|
+
const viscScale = this.config.viscosity * pj.mass / (pj.density || 1) * viscLap;
|
|
7150
|
+
fx += (pj.velocity.x - pi.velocity.x) * viscScale;
|
|
7151
|
+
fy += (pj.velocity.y - pi.velocity.y) * viscScale;
|
|
7152
|
+
fz += (pj.velocity.z - pi.velocity.z) * viscScale;
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
fx += this.config.gravity.x * pi.density;
|
|
7156
|
+
fy += this.config.gravity.y * pi.density;
|
|
7157
|
+
fz += this.config.gravity.z * pi.density;
|
|
7158
|
+
const invDensity = 1 / (pi.density || 1);
|
|
7159
|
+
pi.velocity.x += fx * invDensity * dt;
|
|
7160
|
+
pi.velocity.y += fy * invDensity * dt;
|
|
7161
|
+
pi.velocity.z += fz * invDensity * dt;
|
|
7162
|
+
pi.position[0] += pi.velocity[0] * dt;
|
|
7163
|
+
pi.position[1] += pi.velocity[1] * dt;
|
|
7164
|
+
pi.position[2] += pi.velocity[2] * dt;
|
|
7165
|
+
}
|
|
7166
|
+
this.enforceBoundaries();
|
|
7167
|
+
}
|
|
7168
|
+
enforceBoundaries() {
|
|
7169
|
+
const { boundaryMin: mn, boundaryMax: mx, boundaryDamping: d } = this.config;
|
|
7170
|
+
for (const p of this.particles) {
|
|
7171
|
+
if (p.position[0] < mn[0]) {
|
|
7172
|
+
p.position[0] = mn[0];
|
|
7173
|
+
p.velocity.x *= -d;
|
|
7174
|
+
}
|
|
7175
|
+
if (p.position[0] > mx[0]) {
|
|
7176
|
+
p.position[0] = mx[0];
|
|
7177
|
+
p.velocity.x *= -d;
|
|
7178
|
+
}
|
|
7179
|
+
if (p.position[1] < mn[1]) {
|
|
7180
|
+
p.position[1] = mn[1];
|
|
7181
|
+
p.velocity.y *= -d;
|
|
7182
|
+
}
|
|
7183
|
+
if (p.position[1] > mx[1]) {
|
|
7184
|
+
p.position[1] = mx[1];
|
|
7185
|
+
p.velocity.y *= -d;
|
|
7186
|
+
}
|
|
7187
|
+
if (p.position[2] < mn[2]) {
|
|
7188
|
+
p.position[2] = mn[2];
|
|
7189
|
+
p.velocity.z *= -d;
|
|
7190
|
+
}
|
|
7191
|
+
if (p.position[2] > mx[2]) {
|
|
7192
|
+
p.position[2] = mx[2];
|
|
7193
|
+
p.velocity.z *= -d;
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
// ---------------------------------------------------------------------------
|
|
7198
|
+
// Queries
|
|
7199
|
+
// ---------------------------------------------------------------------------
|
|
7200
|
+
getParticles() {
|
|
7201
|
+
return this.particles;
|
|
7202
|
+
}
|
|
7203
|
+
getParticleCount() {
|
|
7204
|
+
return this.particles.length;
|
|
7205
|
+
}
|
|
7206
|
+
getAverageDensity() {
|
|
7207
|
+
if (this.particles.length === 0) return 0;
|
|
7208
|
+
return this.particles.reduce((s, p) => s + p.density, 0) / this.particles.length;
|
|
7209
|
+
}
|
|
7210
|
+
getKineticEnergy() {
|
|
7211
|
+
return this.particles.reduce((s, p) => {
|
|
7212
|
+
return s + 0.5 * p.mass * (p.velocity.x ** 2 + p.velocity.y ** 2 + p.velocity.z ** 2);
|
|
7213
|
+
}, 0);
|
|
7214
|
+
}
|
|
7215
|
+
clear() {
|
|
7216
|
+
this.particles = [];
|
|
7217
|
+
}
|
|
7218
|
+
setConfig(config) {
|
|
7219
|
+
Object.assign(this.config, config);
|
|
7220
|
+
}
|
|
7221
|
+
};
|
|
7222
|
+
|
|
7223
|
+
// src/physics/JointSystem.ts
|
|
7224
|
+
var _jointId = 0;
|
|
7225
|
+
var JointSystem = class {
|
|
7226
|
+
joints = /* @__PURE__ */ new Map();
|
|
7227
|
+
states = /* @__PURE__ */ new Map();
|
|
7228
|
+
bodyJoints = /* @__PURE__ */ new Map();
|
|
7229
|
+
// body → joint ids
|
|
7230
|
+
// ---------------------------------------------------------------------------
|
|
7231
|
+
// Joint Creation
|
|
7232
|
+
// ---------------------------------------------------------------------------
|
|
7233
|
+
createJoint(type, bodyA, bodyB, config) {
|
|
7234
|
+
const id = config?.id ?? `joint_${_jointId++}`;
|
|
7235
|
+
const joint = {
|
|
7236
|
+
id,
|
|
7237
|
+
type,
|
|
7238
|
+
bodyA,
|
|
7239
|
+
bodyB,
|
|
7240
|
+
anchorA: config?.anchorA ?? { x: 0, y: 0, z: 0 },
|
|
7241
|
+
anchorB: config?.anchorB ?? { x: 0, y: 0, z: 0 },
|
|
7242
|
+
axis: config?.axis ?? { x: 0, y: 1, z: 0 },
|
|
7243
|
+
limits: config?.limits,
|
|
7244
|
+
breakForce: config?.breakForce ?? Infinity,
|
|
7245
|
+
stiffness: config?.stiffness ?? 1,
|
|
7246
|
+
damping: config?.damping ?? 0.1,
|
|
7247
|
+
motorSpeed: config?.motorSpeed ?? 0,
|
|
7248
|
+
motorForce: config?.motorForce ?? 0,
|
|
7249
|
+
broken: false,
|
|
7250
|
+
enabled: true
|
|
7251
|
+
};
|
|
7252
|
+
this.joints.set(id, joint);
|
|
7253
|
+
this.states.set(id, { currentForce: 0, currentAngle: 0, currentDistance: 0 });
|
|
7254
|
+
for (const body of [bodyA, bodyB]) {
|
|
7255
|
+
if (!this.bodyJoints.has(body)) this.bodyJoints.set(body, /* @__PURE__ */ new Set());
|
|
7256
|
+
this.bodyJoints.get(body).add(id);
|
|
7257
|
+
}
|
|
7258
|
+
return joint;
|
|
7259
|
+
}
|
|
7260
|
+
removeJoint(id) {
|
|
7261
|
+
const joint = this.joints.get(id);
|
|
7262
|
+
if (!joint) return false;
|
|
7263
|
+
this.bodyJoints.get(joint.bodyA)?.delete(id);
|
|
7264
|
+
this.bodyJoints.get(joint.bodyB)?.delete(id);
|
|
7265
|
+
this.states.delete(id);
|
|
7266
|
+
return this.joints.delete(id);
|
|
7267
|
+
}
|
|
7268
|
+
// ---------------------------------------------------------------------------
|
|
7269
|
+
// Constraint Solving
|
|
7270
|
+
// ---------------------------------------------------------------------------
|
|
7271
|
+
solve(dt) {
|
|
7272
|
+
for (const [id, joint] of this.joints) {
|
|
7273
|
+
if (!joint.enabled || joint.broken) continue;
|
|
7274
|
+
const state = this.states.get(id);
|
|
7275
|
+
switch (joint.type) {
|
|
7276
|
+
case "spring": {
|
|
7277
|
+
const restLength = this.distance3D(joint.anchorA, joint.anchorB);
|
|
7278
|
+
const force = joint.stiffness * (state.currentDistance - restLength) + joint.damping * state.currentForce;
|
|
7279
|
+
state.currentForce = force;
|
|
7280
|
+
break;
|
|
7281
|
+
}
|
|
7282
|
+
case "distance": {
|
|
7283
|
+
const target = this.distance3D(joint.anchorA, joint.anchorB);
|
|
7284
|
+
state.currentDistance = target;
|
|
7285
|
+
state.currentForce = joint.stiffness * Math.abs(state.currentDistance - target);
|
|
7286
|
+
break;
|
|
7287
|
+
}
|
|
7288
|
+
case "hinge": {
|
|
7289
|
+
if (joint.limits) {
|
|
7290
|
+
state.currentAngle = Math.max(
|
|
7291
|
+
joint.limits.min,
|
|
7292
|
+
Math.min(joint.limits.max, state.currentAngle)
|
|
7293
|
+
);
|
|
7294
|
+
}
|
|
7295
|
+
if (joint.motorForce > 0) {
|
|
7296
|
+
state.currentAngle += joint.motorSpeed * dt;
|
|
7297
|
+
}
|
|
7298
|
+
state.currentForce = Math.abs(joint.stiffness * state.currentAngle);
|
|
7299
|
+
break;
|
|
7300
|
+
}
|
|
7301
|
+
case "slider": {
|
|
7302
|
+
if (joint.limits) {
|
|
7303
|
+
state.currentDistance = Math.max(
|
|
7304
|
+
joint.limits.min,
|
|
7305
|
+
Math.min(joint.limits.max, state.currentDistance)
|
|
7306
|
+
);
|
|
7307
|
+
}
|
|
7308
|
+
state.currentForce = joint.stiffness * Math.abs(state.currentDistance);
|
|
7309
|
+
break;
|
|
7310
|
+
}
|
|
7311
|
+
default:
|
|
7312
|
+
state.currentForce = 0;
|
|
7313
|
+
break;
|
|
7314
|
+
}
|
|
7315
|
+
if (state.currentForce > joint.breakForce) {
|
|
7316
|
+
joint.broken = true;
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
// ---------------------------------------------------------------------------
|
|
7321
|
+
// Controls
|
|
7322
|
+
// ---------------------------------------------------------------------------
|
|
7323
|
+
setMotor(id, speed, force) {
|
|
7324
|
+
const joint = this.joints.get(id);
|
|
7325
|
+
if (joint) {
|
|
7326
|
+
joint.motorSpeed = speed;
|
|
7327
|
+
joint.motorForce = force;
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
setEnabled(id, enabled) {
|
|
7331
|
+
const joint = this.joints.get(id);
|
|
7332
|
+
if (joint) joint.enabled = enabled;
|
|
7333
|
+
}
|
|
7334
|
+
setAngle(id, angle) {
|
|
7335
|
+
const state = this.states.get(id);
|
|
7336
|
+
if (state) state.currentAngle = angle;
|
|
7337
|
+
}
|
|
7338
|
+
setDistance(id, dist) {
|
|
7339
|
+
const state = this.states.get(id);
|
|
7340
|
+
if (state) state.currentDistance = dist;
|
|
7341
|
+
}
|
|
7342
|
+
// ---------------------------------------------------------------------------
|
|
7343
|
+
// Queries
|
|
7344
|
+
// ---------------------------------------------------------------------------
|
|
7345
|
+
getJoint(id) {
|
|
7346
|
+
return this.joints.get(id);
|
|
7347
|
+
}
|
|
7348
|
+
getState(id) {
|
|
7349
|
+
return this.states.get(id);
|
|
7350
|
+
}
|
|
7351
|
+
getJointCount() {
|
|
7352
|
+
return this.joints.size;
|
|
7353
|
+
}
|
|
7354
|
+
getBrokenJoints() {
|
|
7355
|
+
return [...this.joints.values()].filter((j) => j.broken);
|
|
7356
|
+
}
|
|
7357
|
+
getJointsForBody(bodyId) {
|
|
7358
|
+
const ids = this.bodyJoints.get(bodyId);
|
|
7359
|
+
if (!ids) return [];
|
|
7360
|
+
return [...ids].map((id) => this.joints.get(id)).filter(Boolean);
|
|
7361
|
+
}
|
|
7362
|
+
// ---------------------------------------------------------------------------
|
|
7363
|
+
// Helpers
|
|
7364
|
+
// ---------------------------------------------------------------------------
|
|
7365
|
+
distance3D(a, b) {
|
|
7366
|
+
const dx = b.x - a.x, dy = b.y - a.y, dz = b.z - a.z;
|
|
7367
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
7368
|
+
}
|
|
7369
|
+
};
|
|
7370
|
+
|
|
7371
|
+
// src/physics/RagdollController.ts
|
|
7372
|
+
var RagdollController = class {
|
|
7373
|
+
bones = /* @__PURE__ */ new Map();
|
|
7374
|
+
rootBone = null;
|
|
7375
|
+
state = "active";
|
|
7376
|
+
blendFactor = 0;
|
|
7377
|
+
// 0 = animated, 1 = ragdoll
|
|
7378
|
+
blendSpeed = 2;
|
|
7379
|
+
config;
|
|
7380
|
+
constructor(config) {
|
|
7381
|
+
this.config = { gravity: -9.81, damping: 0.98, iterations: 4, ...config };
|
|
7382
|
+
}
|
|
7383
|
+
// ---------------------------------------------------------------------------
|
|
7384
|
+
// Bone Chain Setup
|
|
7385
|
+
// ---------------------------------------------------------------------------
|
|
7386
|
+
addBone(name, parentId, mass, length, limits) {
|
|
7387
|
+
const bone = {
|
|
7388
|
+
id: name,
|
|
7389
|
+
name,
|
|
7390
|
+
parentId,
|
|
7391
|
+
position: [0, 0, 0],
|
|
7392
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
7393
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
7394
|
+
angularVelocity: { x: 0, y: 0, z: 0 },
|
|
7395
|
+
mass,
|
|
7396
|
+
length,
|
|
7397
|
+
jointLimits: limits ?? { min: { x: -1, y: -1, z: -1 }, max: { x: 1, y: 1, z: 1 } }
|
|
7398
|
+
};
|
|
7399
|
+
this.bones.set(name, bone);
|
|
7400
|
+
if (!parentId) this.rootBone = name;
|
|
7401
|
+
return bone;
|
|
7402
|
+
}
|
|
7403
|
+
removeBone(name) {
|
|
7404
|
+
return this.bones.delete(name);
|
|
7405
|
+
}
|
|
7406
|
+
// ---------------------------------------------------------------------------
|
|
7407
|
+
// State Control
|
|
7408
|
+
// ---------------------------------------------------------------------------
|
|
7409
|
+
activate() {
|
|
7410
|
+
this.state = "active";
|
|
7411
|
+
this.blendFactor = 0;
|
|
7412
|
+
}
|
|
7413
|
+
goRagdoll() {
|
|
7414
|
+
this.state = "ragdoll";
|
|
7415
|
+
this.blendFactor = 1;
|
|
7416
|
+
}
|
|
7417
|
+
startBlend(toRagdoll = true) {
|
|
7418
|
+
this.state = "blending";
|
|
7419
|
+
this.blendFactor = toRagdoll ? 0 : 1;
|
|
7420
|
+
}
|
|
7421
|
+
getState() {
|
|
7422
|
+
return this.state;
|
|
7423
|
+
}
|
|
7424
|
+
getBlendFactor() {
|
|
7425
|
+
return this.blendFactor;
|
|
7426
|
+
}
|
|
7427
|
+
// ---------------------------------------------------------------------------
|
|
7428
|
+
// Physics Update
|
|
7429
|
+
// ---------------------------------------------------------------------------
|
|
7430
|
+
update(dt) {
|
|
7431
|
+
if (this.state === "blending") {
|
|
7432
|
+
this.blendFactor = Math.min(1, this.blendFactor + this.blendSpeed * dt);
|
|
7433
|
+
if (this.blendFactor >= 1) this.state = "ragdoll";
|
|
7434
|
+
}
|
|
7435
|
+
if (this.state === "active") return;
|
|
7436
|
+
for (const bone of this.bones.values()) {
|
|
7437
|
+
bone.velocity.y += this.config.gravity * dt * this.blendFactor;
|
|
7438
|
+
bone.velocity.x *= this.config.damping;
|
|
7439
|
+
bone.velocity.y *= this.config.damping;
|
|
7440
|
+
bone.velocity.z *= this.config.damping;
|
|
7441
|
+
bone.position[0] += bone.velocity[0] * dt;
|
|
7442
|
+
bone.position[1] += bone.velocity[1] * dt;
|
|
7443
|
+
bone.position[2] += bone.velocity[2] * dt;
|
|
7444
|
+
}
|
|
7445
|
+
for (let iter = 0; iter < this.config.iterations; iter++) {
|
|
7446
|
+
for (const bone of this.bones.values()) {
|
|
7447
|
+
if (!bone.parentId) continue;
|
|
7448
|
+
const parent = this.bones.get(bone.parentId);
|
|
7449
|
+
if (!parent) continue;
|
|
7450
|
+
const dx = bone.position[0] - parent.position[0];
|
|
7451
|
+
const dy = bone.position[1] - parent.position[1];
|
|
7452
|
+
const dz = bone.position[2] - parent.position[2];
|
|
7453
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
7454
|
+
if (dist > 0 && dist !== bone.length) {
|
|
7455
|
+
const diff = (dist - bone.length) / dist;
|
|
7456
|
+
const mx = dx * diff * 0.5;
|
|
7457
|
+
const my = dy * diff * 0.5;
|
|
7458
|
+
const mz = dz * diff * 0.5;
|
|
7459
|
+
bone.position[0] -= mx;
|
|
7460
|
+
bone.position[1] -= my;
|
|
7461
|
+
bone.position[2] -= mz;
|
|
7462
|
+
parent.position[0] += mx;
|
|
7463
|
+
parent.position[1] += my;
|
|
7464
|
+
parent.position[2] += mz;
|
|
7465
|
+
}
|
|
7466
|
+
bone.rotation.x = Math.max(
|
|
7467
|
+
bone.jointLimits.min.x,
|
|
7468
|
+
Math.min(bone.jointLimits.max.x, bone.rotation.x)
|
|
7469
|
+
);
|
|
7470
|
+
bone.rotation.y = Math.max(
|
|
7471
|
+
bone.jointLimits.min.y,
|
|
7472
|
+
Math.min(bone.jointLimits.max.y, bone.rotation.y)
|
|
7473
|
+
);
|
|
7474
|
+
bone.rotation.z = Math.max(
|
|
7475
|
+
bone.jointLimits.min.z,
|
|
7476
|
+
Math.min(bone.jointLimits.max.z, bone.rotation.z)
|
|
7477
|
+
);
|
|
7478
|
+
}
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
// ---------------------------------------------------------------------------
|
|
7482
|
+
// Impulse
|
|
7483
|
+
// ---------------------------------------------------------------------------
|
|
7484
|
+
applyImpulse(boneId, impulse) {
|
|
7485
|
+
const bone = this.bones.get(boneId);
|
|
7486
|
+
if (!bone) return;
|
|
7487
|
+
bone.velocity.x += impulse.x / bone.mass;
|
|
7488
|
+
bone.velocity.y += impulse.y / bone.mass;
|
|
7489
|
+
bone.velocity.z += impulse.z / bone.mass;
|
|
7490
|
+
}
|
|
7491
|
+
// ---------------------------------------------------------------------------
|
|
7492
|
+
// Queries
|
|
7493
|
+
// ---------------------------------------------------------------------------
|
|
7494
|
+
getBone(id) {
|
|
7495
|
+
return this.bones.get(id);
|
|
7496
|
+
}
|
|
7497
|
+
getBoneCount() {
|
|
7498
|
+
return this.bones.size;
|
|
7499
|
+
}
|
|
7500
|
+
getRootBone() {
|
|
7501
|
+
return this.rootBone ? this.bones.get(this.rootBone) : void 0;
|
|
7502
|
+
}
|
|
7503
|
+
getChildren(boneId) {
|
|
7504
|
+
return [...this.bones.values()].filter((b) => b.parentId === boneId);
|
|
7505
|
+
}
|
|
7506
|
+
};
|
|
7507
|
+
|
|
7508
|
+
// src/physics/RagdollSystem.ts
|
|
7509
|
+
var HUMANOID_PRESET = [
|
|
7510
|
+
// Torso
|
|
7511
|
+
{
|
|
7512
|
+
id: "pelvis",
|
|
7513
|
+
length: 0.25,
|
|
7514
|
+
radius: 0.12,
|
|
7515
|
+
mass: 15,
|
|
7516
|
+
localOffset: { x: 0, y: 0, z: 0 },
|
|
7517
|
+
jointType: "cone"
|
|
7518
|
+
},
|
|
7519
|
+
{
|
|
7520
|
+
id: "spine",
|
|
7521
|
+
parentBone: "pelvis",
|
|
7522
|
+
length: 0.3,
|
|
7523
|
+
radius: 0.1,
|
|
7524
|
+
mass: 12,
|
|
7525
|
+
localOffset: { x: 0, y: 0.25, z: 0 },
|
|
7526
|
+
jointType: "cone",
|
|
7527
|
+
jointLimits: { swingSpan1: 0.3, swingSpan2: 0.3, twistSpan: 0.2 }
|
|
7528
|
+
},
|
|
7529
|
+
{
|
|
7530
|
+
id: "chest",
|
|
7531
|
+
parentBone: "spine",
|
|
7532
|
+
length: 0.25,
|
|
7533
|
+
radius: 0.12,
|
|
7534
|
+
mass: 10,
|
|
7535
|
+
localOffset: { x: 0, y: 0.3, z: 0 },
|
|
7536
|
+
jointType: "cone",
|
|
7537
|
+
jointLimits: { swingSpan1: 0.2, swingSpan2: 0.2, twistSpan: 0.15 }
|
|
7538
|
+
},
|
|
7539
|
+
{
|
|
7540
|
+
id: "head",
|
|
7541
|
+
parentBone: "chest",
|
|
7542
|
+
length: 0.2,
|
|
7543
|
+
radius: 0.1,
|
|
7544
|
+
mass: 5,
|
|
7545
|
+
localOffset: { x: 0, y: 0.25, z: 0 },
|
|
7546
|
+
jointType: "cone",
|
|
7547
|
+
jointLimits: { swingSpan1: 0.5, swingSpan2: 0.3, twistSpan: 0.4 }
|
|
7548
|
+
},
|
|
7549
|
+
// Left arm
|
|
7550
|
+
{
|
|
7551
|
+
id: "l_upper_arm",
|
|
7552
|
+
parentBone: "chest",
|
|
7553
|
+
length: 0.28,
|
|
7554
|
+
radius: 0.04,
|
|
7555
|
+
mass: 3,
|
|
7556
|
+
localOffset: { x: -0.18, y: 0.15, z: 0 },
|
|
7557
|
+
jointType: "cone",
|
|
7558
|
+
jointLimits: { swingSpan1: 1.5, swingSpan2: 1, twistSpan: 0.8 }
|
|
7559
|
+
},
|
|
7560
|
+
{
|
|
7561
|
+
id: "l_forearm",
|
|
7562
|
+
parentBone: "l_upper_arm",
|
|
7563
|
+
length: 0.25,
|
|
7564
|
+
radius: 0.035,
|
|
7565
|
+
mass: 2,
|
|
7566
|
+
localOffset: { x: 0, y: -0.28, z: 0 },
|
|
7567
|
+
jointType: "hinge",
|
|
7568
|
+
jointLimits: { low: 0, high: 2.5 }
|
|
7569
|
+
},
|
|
7570
|
+
{
|
|
7571
|
+
id: "l_hand",
|
|
7572
|
+
parentBone: "l_forearm",
|
|
7573
|
+
length: 0.1,
|
|
7574
|
+
radius: 0.03,
|
|
7575
|
+
mass: 0.5,
|
|
7576
|
+
localOffset: { x: 0, y: -0.25, z: 0 },
|
|
7577
|
+
jointType: "cone",
|
|
7578
|
+
jointLimits: { swingSpan1: 0.5, swingSpan2: 0.3, twistSpan: 0.3 }
|
|
7579
|
+
},
|
|
7580
|
+
// Right arm
|
|
7581
|
+
{
|
|
7582
|
+
id: "r_upper_arm",
|
|
7583
|
+
parentBone: "chest",
|
|
7584
|
+
length: 0.28,
|
|
7585
|
+
radius: 0.04,
|
|
7586
|
+
mass: 3,
|
|
7587
|
+
localOffset: { x: 0.18, y: 0.15, z: 0 },
|
|
7588
|
+
jointType: "cone",
|
|
7589
|
+
jointLimits: { swingSpan1: 1.5, swingSpan2: 1, twistSpan: 0.8 }
|
|
7590
|
+
},
|
|
7591
|
+
{
|
|
7592
|
+
id: "r_forearm",
|
|
7593
|
+
parentBone: "r_upper_arm",
|
|
7594
|
+
length: 0.25,
|
|
7595
|
+
radius: 0.035,
|
|
7596
|
+
mass: 2,
|
|
7597
|
+
localOffset: { x: 0, y: -0.28, z: 0 },
|
|
7598
|
+
jointType: "hinge",
|
|
7599
|
+
jointLimits: { low: 0, high: 2.5 }
|
|
7600
|
+
},
|
|
7601
|
+
{
|
|
7602
|
+
id: "r_hand",
|
|
7603
|
+
parentBone: "r_forearm",
|
|
7604
|
+
length: 0.1,
|
|
7605
|
+
radius: 0.03,
|
|
7606
|
+
mass: 0.5,
|
|
7607
|
+
localOffset: { x: 0, y: -0.25, z: 0 },
|
|
7608
|
+
jointType: "cone",
|
|
7609
|
+
jointLimits: { swingSpan1: 0.5, swingSpan2: 0.3, twistSpan: 0.3 }
|
|
7610
|
+
},
|
|
7611
|
+
// Left leg
|
|
7612
|
+
{
|
|
7613
|
+
id: "l_thigh",
|
|
7614
|
+
parentBone: "pelvis",
|
|
7615
|
+
length: 0.4,
|
|
7616
|
+
radius: 0.06,
|
|
7617
|
+
mass: 8,
|
|
7618
|
+
localOffset: { x: -0.1, y: -0.15, z: 0 },
|
|
7619
|
+
jointType: "cone",
|
|
7620
|
+
jointLimits: { swingSpan1: 1.2, swingSpan2: 0.5, twistSpan: 0.3 }
|
|
7621
|
+
},
|
|
7622
|
+
{
|
|
7623
|
+
id: "l_shin",
|
|
7624
|
+
parentBone: "l_thigh",
|
|
7625
|
+
length: 0.38,
|
|
7626
|
+
radius: 0.05,
|
|
7627
|
+
mass: 5,
|
|
7628
|
+
localOffset: { x: 0, y: -0.4, z: 0 },
|
|
7629
|
+
jointType: "hinge",
|
|
7630
|
+
jointLimits: { low: -2.5, high: 0 }
|
|
7631
|
+
},
|
|
7632
|
+
{
|
|
7633
|
+
id: "l_foot",
|
|
7634
|
+
parentBone: "l_shin",
|
|
7635
|
+
length: 0.15,
|
|
7636
|
+
radius: 0.04,
|
|
7637
|
+
mass: 1,
|
|
7638
|
+
localOffset: { x: 0, y: -0.38, z: 0.05 },
|
|
7639
|
+
jointType: "hinge",
|
|
7640
|
+
jointLimits: { low: -0.5, high: 0.5 }
|
|
7641
|
+
},
|
|
7642
|
+
// Right leg
|
|
7643
|
+
{
|
|
7644
|
+
id: "r_thigh",
|
|
7645
|
+
parentBone: "pelvis",
|
|
7646
|
+
length: 0.4,
|
|
7647
|
+
radius: 0.06,
|
|
7648
|
+
mass: 8,
|
|
7649
|
+
localOffset: { x: 0.1, y: -0.15, z: 0 },
|
|
7650
|
+
jointType: "cone",
|
|
7651
|
+
jointLimits: { swingSpan1: 1.2, swingSpan2: 0.5, twistSpan: 0.3 }
|
|
7652
|
+
},
|
|
7653
|
+
{
|
|
7654
|
+
id: "r_shin",
|
|
7655
|
+
parentBone: "r_thigh",
|
|
7656
|
+
length: 0.38,
|
|
7657
|
+
radius: 0.05,
|
|
7658
|
+
mass: 5,
|
|
7659
|
+
localOffset: { x: 0, y: -0.4, z: 0 },
|
|
7660
|
+
jointType: "hinge",
|
|
7661
|
+
jointLimits: { low: -2.5, high: 0 }
|
|
7662
|
+
},
|
|
7663
|
+
{
|
|
7664
|
+
id: "r_foot",
|
|
7665
|
+
parentBone: "r_shin",
|
|
7666
|
+
length: 0.15,
|
|
7667
|
+
radius: 0.04,
|
|
7668
|
+
mass: 1,
|
|
7669
|
+
localOffset: { x: 0, y: -0.38, z: 0.05 },
|
|
7670
|
+
jointType: "hinge",
|
|
7671
|
+
jointLimits: { low: -0.5, high: 0.5 }
|
|
7672
|
+
}
|
|
7673
|
+
];
|
|
7674
|
+
var QUADRUPED_PRESET = [
|
|
7675
|
+
// Body
|
|
7676
|
+
{
|
|
7677
|
+
id: "body",
|
|
7678
|
+
length: 0.6,
|
|
7679
|
+
radius: 0.15,
|
|
7680
|
+
mass: 20,
|
|
7681
|
+
localOffset: { x: 0, y: 0, z: 0 },
|
|
7682
|
+
jointType: "cone"
|
|
7683
|
+
},
|
|
7684
|
+
{
|
|
7685
|
+
id: "neck",
|
|
7686
|
+
parentBone: "body",
|
|
7687
|
+
length: 0.2,
|
|
7688
|
+
radius: 0.06,
|
|
7689
|
+
mass: 3,
|
|
7690
|
+
localOffset: { x: 0, y: 0.1, z: 0.3 },
|
|
7691
|
+
jointType: "cone",
|
|
7692
|
+
jointLimits: { swingSpan1: 0.8, swingSpan2: 0.4, twistSpan: 0.3 }
|
|
7693
|
+
},
|
|
7694
|
+
{
|
|
7695
|
+
id: "head",
|
|
7696
|
+
parentBone: "neck",
|
|
7697
|
+
length: 0.15,
|
|
7698
|
+
radius: 0.08,
|
|
7699
|
+
mass: 2,
|
|
7700
|
+
localOffset: { x: 0, y: 0.05, z: 0.2 },
|
|
7701
|
+
jointType: "cone",
|
|
7702
|
+
jointLimits: { swingSpan1: 0.6, swingSpan2: 0.4, twistSpan: 0.3 }
|
|
7703
|
+
},
|
|
7704
|
+
{
|
|
7705
|
+
id: "tail",
|
|
7706
|
+
parentBone: "body",
|
|
7707
|
+
length: 0.3,
|
|
7708
|
+
radius: 0.02,
|
|
7709
|
+
mass: 0.5,
|
|
7710
|
+
localOffset: { x: 0, y: 0.05, z: -0.35 },
|
|
7711
|
+
jointType: "cone",
|
|
7712
|
+
jointLimits: { swingSpan1: 1, swingSpan2: 0.5, twistSpan: 0.5 }
|
|
7713
|
+
},
|
|
7714
|
+
// Front legs
|
|
7715
|
+
{
|
|
7716
|
+
id: "fl_upper",
|
|
7717
|
+
parentBone: "body",
|
|
7718
|
+
length: 0.25,
|
|
7719
|
+
radius: 0.03,
|
|
7720
|
+
mass: 2,
|
|
7721
|
+
localOffset: { x: -0.12, y: -0.15, z: 0.2 },
|
|
7722
|
+
jointType: "cone",
|
|
7723
|
+
jointLimits: { swingSpan1: 0.8, swingSpan2: 0.4, twistSpan: 0.2 }
|
|
7724
|
+
},
|
|
7725
|
+
{
|
|
7726
|
+
id: "fl_lower",
|
|
7727
|
+
parentBone: "fl_upper",
|
|
7728
|
+
length: 0.2,
|
|
7729
|
+
radius: 0.025,
|
|
7730
|
+
mass: 1.5,
|
|
7731
|
+
localOffset: { x: 0, y: -0.25, z: 0 },
|
|
7732
|
+
jointType: "hinge",
|
|
7733
|
+
jointLimits: { low: -2, high: 0 }
|
|
7734
|
+
},
|
|
7735
|
+
{
|
|
7736
|
+
id: "fr_upper",
|
|
7737
|
+
parentBone: "body",
|
|
7738
|
+
length: 0.25,
|
|
7739
|
+
radius: 0.03,
|
|
7740
|
+
mass: 2,
|
|
7741
|
+
localOffset: { x: 0.12, y: -0.15, z: 0.2 },
|
|
7742
|
+
jointType: "cone",
|
|
7743
|
+
jointLimits: { swingSpan1: 0.8, swingSpan2: 0.4, twistSpan: 0.2 }
|
|
7744
|
+
},
|
|
7745
|
+
{
|
|
7746
|
+
id: "fr_lower",
|
|
7747
|
+
parentBone: "fr_upper",
|
|
7748
|
+
length: 0.2,
|
|
7749
|
+
radius: 0.025,
|
|
7750
|
+
mass: 1.5,
|
|
7751
|
+
localOffset: { x: 0, y: -0.25, z: 0 },
|
|
7752
|
+
jointType: "hinge",
|
|
7753
|
+
jointLimits: { low: -2, high: 0 }
|
|
7754
|
+
},
|
|
7755
|
+
// Hind legs
|
|
7756
|
+
{
|
|
7757
|
+
id: "hl_upper",
|
|
7758
|
+
parentBone: "body",
|
|
7759
|
+
length: 0.28,
|
|
7760
|
+
radius: 0.04,
|
|
7761
|
+
mass: 3,
|
|
7762
|
+
localOffset: { x: -0.12, y: -0.15, z: -0.2 },
|
|
7763
|
+
jointType: "cone",
|
|
7764
|
+
jointLimits: { swingSpan1: 1, swingSpan2: 0.5, twistSpan: 0.2 }
|
|
7765
|
+
},
|
|
7766
|
+
{
|
|
7767
|
+
id: "hl_lower",
|
|
7768
|
+
parentBone: "hl_upper",
|
|
7769
|
+
length: 0.22,
|
|
7770
|
+
radius: 0.03,
|
|
7771
|
+
mass: 2,
|
|
7772
|
+
localOffset: { x: 0, y: -0.28, z: 0 },
|
|
7773
|
+
jointType: "hinge",
|
|
7774
|
+
jointLimits: { low: 0, high: 2 }
|
|
7775
|
+
},
|
|
7776
|
+
{
|
|
7777
|
+
id: "hr_upper",
|
|
7778
|
+
parentBone: "body",
|
|
7779
|
+
length: 0.28,
|
|
7780
|
+
radius: 0.04,
|
|
7781
|
+
mass: 3,
|
|
7782
|
+
localOffset: { x: 0.12, y: -0.15, z: -0.2 },
|
|
7783
|
+
jointType: "cone",
|
|
7784
|
+
jointLimits: { swingSpan1: 1, swingSpan2: 0.5, twistSpan: 0.2 }
|
|
7785
|
+
},
|
|
7786
|
+
{
|
|
7787
|
+
id: "hr_lower",
|
|
7788
|
+
parentBone: "hr_upper",
|
|
7789
|
+
length: 0.22,
|
|
7790
|
+
radius: 0.03,
|
|
7791
|
+
mass: 2,
|
|
7792
|
+
localOffset: { x: 0, y: -0.28, z: 0 },
|
|
7793
|
+
jointType: "hinge",
|
|
7794
|
+
jointLimits: { low: 0, high: 2 }
|
|
7795
|
+
}
|
|
7796
|
+
];
|
|
7797
|
+
var RagdollSystem = class {
|
|
7798
|
+
ragdolls = /* @__PURE__ */ new Map();
|
|
7799
|
+
/**
|
|
7800
|
+
* Create a ragdoll from a definition at a given world position.
|
|
7801
|
+
*/
|
|
7802
|
+
createRagdoll(definition, rootPosition) {
|
|
7803
|
+
const bodies = [];
|
|
7804
|
+
const constraints = [];
|
|
7805
|
+
const bonePositions = /* @__PURE__ */ new Map();
|
|
7806
|
+
for (const bone of definition.bones) {
|
|
7807
|
+
const parentPos = bone.parentBone ? bonePositions.get(bone.parentBone) || rootPosition : rootPosition;
|
|
7808
|
+
const worldPos = {
|
|
7809
|
+
x: parentPos.x + bone.localOffset.x,
|
|
7810
|
+
y: parentPos.y + bone.localOffset.y,
|
|
7811
|
+
z: parentPos.z + bone.localOffset.z
|
|
7812
|
+
};
|
|
7813
|
+
bonePositions.set(bone.id, worldPos);
|
|
7814
|
+
const bodyConfig = {
|
|
7815
|
+
id: `${definition.id}_${bone.id}`,
|
|
7816
|
+
type: "dynamic",
|
|
7817
|
+
transform: {
|
|
7818
|
+
position: worldPos,
|
|
7819
|
+
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
|
7820
|
+
scale: { x: 1, y: 1, z: 1 }
|
|
7821
|
+
},
|
|
7822
|
+
shape: {
|
|
7823
|
+
type: "capsule",
|
|
7824
|
+
radius: bone.radius,
|
|
7825
|
+
height: bone.length
|
|
7826
|
+
},
|
|
7827
|
+
mass: bone.mass,
|
|
7828
|
+
material: {
|
|
7829
|
+
friction: 0.6,
|
|
7830
|
+
restitution: 0.1
|
|
7831
|
+
},
|
|
7832
|
+
linearDamping: 0.05,
|
|
7833
|
+
angularDamping: 0.85
|
|
7834
|
+
};
|
|
7835
|
+
bodies.push(bodyConfig);
|
|
7836
|
+
}
|
|
7837
|
+
for (const bone of definition.bones) {
|
|
7838
|
+
if (!bone.parentBone) continue;
|
|
7839
|
+
const bodyAId = `${definition.id}_${bone.parentBone}`;
|
|
7840
|
+
const bodyBId = `${definition.id}_${bone.id}`;
|
|
7841
|
+
const constraintId = `${definition.id}_joint_${bone.parentBone}_${bone.id}`;
|
|
7842
|
+
if (bone.jointType === "cone") {
|
|
7843
|
+
const coneConstraint = {
|
|
7844
|
+
type: "cone",
|
|
7845
|
+
id: constraintId,
|
|
7846
|
+
bodyA: bodyAId,
|
|
7847
|
+
bodyB: bodyBId,
|
|
7848
|
+
pivotA: bone.localOffset,
|
|
7849
|
+
pivotB: { x: 0, y: 0, z: 0 },
|
|
7850
|
+
axisA: { x: 0, y: 1, z: 0 },
|
|
7851
|
+
axisB: { x: 0, y: 1, z: 0 },
|
|
7852
|
+
swingSpan1: bone.jointLimits?.swingSpan1 ?? 0.5,
|
|
7853
|
+
swingSpan2: bone.jointLimits?.swingSpan2 ?? 0.3,
|
|
7854
|
+
twistSpan: bone.jointLimits?.twistSpan ?? 0.2
|
|
7855
|
+
};
|
|
7856
|
+
constraints.push(coneConstraint);
|
|
7857
|
+
} else {
|
|
7858
|
+
const hingeConstraint = {
|
|
7859
|
+
type: "hinge",
|
|
7860
|
+
id: constraintId,
|
|
7861
|
+
bodyA: bodyAId,
|
|
7862
|
+
bodyB: bodyBId,
|
|
7863
|
+
pivotA: bone.localOffset,
|
|
7864
|
+
pivotB: { x: 0, y: 0, z: 0 },
|
|
7865
|
+
axisA: { x: 1, y: 0, z: 0 },
|
|
7866
|
+
axisB: { x: 1, y: 0, z: 0 },
|
|
7867
|
+
limits: {
|
|
7868
|
+
low: bone.jointLimits?.low ?? -1,
|
|
7869
|
+
high: bone.jointLimits?.high ?? 1
|
|
7870
|
+
}
|
|
7871
|
+
};
|
|
7872
|
+
constraints.push(hingeConstraint);
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
const instance = {
|
|
7876
|
+
id: definition.id,
|
|
7877
|
+
definition,
|
|
7878
|
+
bodies,
|
|
7879
|
+
constraints,
|
|
7880
|
+
rootPosition
|
|
7881
|
+
};
|
|
7882
|
+
this.ragdolls.set(definition.id, instance);
|
|
7883
|
+
return instance;
|
|
7884
|
+
}
|
|
7885
|
+
/**
|
|
7886
|
+
* Create humanoid ragdoll from preset.
|
|
7887
|
+
*/
|
|
7888
|
+
createHumanoid(id, rootPosition) {
|
|
7889
|
+
return this.createRagdoll({ id, bones: HUMANOID_PRESET }, rootPosition);
|
|
7890
|
+
}
|
|
7891
|
+
/**
|
|
7892
|
+
* Create quadruped ragdoll from preset.
|
|
7893
|
+
*/
|
|
7894
|
+
createQuadruped(id, rootPosition) {
|
|
7895
|
+
return this.createRagdoll({ id, bones: QUADRUPED_PRESET }, rootPosition);
|
|
7896
|
+
}
|
|
7897
|
+
/**
|
|
7898
|
+
* Get a ragdoll instance by ID.
|
|
7899
|
+
*/
|
|
7900
|
+
getRagdoll(id) {
|
|
7901
|
+
return this.ragdolls.get(id);
|
|
7902
|
+
}
|
|
7903
|
+
/**
|
|
7904
|
+
* Remove a ragdoll.
|
|
7905
|
+
*/
|
|
7906
|
+
removeRagdoll(id) {
|
|
7907
|
+
return this.ragdolls.delete(id);
|
|
7908
|
+
}
|
|
7909
|
+
/**
|
|
7910
|
+
* Get total mass of a ragdoll.
|
|
7911
|
+
*/
|
|
7912
|
+
getTotalMass(id) {
|
|
7913
|
+
const ragdoll = this.ragdolls.get(id);
|
|
7914
|
+
if (!ragdoll) return 0;
|
|
7915
|
+
return ragdoll.definition.bones.reduce((sum, bone) => sum + bone.mass, 0);
|
|
7916
|
+
}
|
|
7917
|
+
};
|
|
7918
|
+
|
|
7919
|
+
// src/physics/RaycastSystem.ts
|
|
7920
|
+
var RaycastSystem = class {
|
|
7921
|
+
colliders = /* @__PURE__ */ new Map();
|
|
7922
|
+
// ---------------------------------------------------------------------------
|
|
7923
|
+
// Registration
|
|
7924
|
+
// ---------------------------------------------------------------------------
|
|
7925
|
+
addCollider(collider) {
|
|
7926
|
+
this.colliders.set(collider.entityId, collider);
|
|
7927
|
+
}
|
|
7928
|
+
removeCollider(entityId) {
|
|
7929
|
+
this.colliders.delete(entityId);
|
|
7930
|
+
}
|
|
7931
|
+
getColliderCount() {
|
|
7932
|
+
return this.colliders.size;
|
|
7933
|
+
}
|
|
7934
|
+
// ---------------------------------------------------------------------------
|
|
7935
|
+
// Raycasting
|
|
7936
|
+
// ---------------------------------------------------------------------------
|
|
7937
|
+
raycast(ray, maxDistance = Infinity, layerMask = 4294967295) {
|
|
7938
|
+
const hits = this.raycastAll(ray, maxDistance, layerMask);
|
|
7939
|
+
return hits.length > 0 ? hits[0] : null;
|
|
7940
|
+
}
|
|
7941
|
+
raycastAll(ray, maxDistance = Infinity, layerMask = 4294967295) {
|
|
7942
|
+
const hits = [];
|
|
7943
|
+
const dir = this.normalize(ray.direction);
|
|
7944
|
+
for (const collider of this.colliders.values()) {
|
|
7945
|
+
if ((collider.layer & layerMask) === 0) continue;
|
|
7946
|
+
let hit = null;
|
|
7947
|
+
switch (collider.type) {
|
|
7948
|
+
case "aabb":
|
|
7949
|
+
hit = this.rayAABB(ray.origin, dir, collider.shape, collider.entityId);
|
|
7950
|
+
break;
|
|
7951
|
+
case "sphere":
|
|
7952
|
+
hit = this.raySphere(ray.origin, dir, collider.shape, collider.entityId);
|
|
7953
|
+
break;
|
|
7954
|
+
case "plane":
|
|
7955
|
+
hit = this.rayPlane(ray.origin, dir, collider.shape, collider.entityId);
|
|
7956
|
+
break;
|
|
7957
|
+
}
|
|
7958
|
+
if (hit && hit.distance <= maxDistance) hits.push(hit);
|
|
7959
|
+
}
|
|
7960
|
+
return hits.sort((a, b) => a.distance - b.distance);
|
|
7961
|
+
}
|
|
7962
|
+
// ---------------------------------------------------------------------------
|
|
7963
|
+
// Intersection Tests
|
|
7964
|
+
// ---------------------------------------------------------------------------
|
|
7965
|
+
rayAABB(origin, dir, aabb, entityId) {
|
|
7966
|
+
let tmin = -Infinity, tmax = Infinity;
|
|
7967
|
+
const axes = ["x", "y", "z"];
|
|
7968
|
+
let hitNormal = { x: 0, y: 0, z: 0 };
|
|
7969
|
+
for (const axis of axes) {
|
|
7970
|
+
if (Math.abs(dir[axis]) < 1e-10) {
|
|
7971
|
+
if (origin[axis] < aabb.min[axis] || origin[axis] > aabb.max[axis]) return null;
|
|
7972
|
+
continue;
|
|
7973
|
+
}
|
|
7974
|
+
const t1 = (aabb.min[axis] - origin[axis]) / dir[axis];
|
|
7975
|
+
const t2 = (aabb.max[axis] - origin[axis]) / dir[axis];
|
|
7976
|
+
const tNear = Math.min(t1, t2);
|
|
7977
|
+
const tFar = Math.max(t1, t2);
|
|
7978
|
+
if (tNear > tmin) {
|
|
7979
|
+
tmin = tNear;
|
|
7980
|
+
hitNormal = { x: 0, y: 0, z: 0 };
|
|
7981
|
+
hitNormal[axis] = dir[axis] > 0 ? -1 : 1;
|
|
7982
|
+
}
|
|
7983
|
+
tmax = Math.min(tmax, tFar);
|
|
7984
|
+
if (tmin > tmax || tmax < 0) return null;
|
|
7985
|
+
}
|
|
7986
|
+
const t = tmin >= 0 ? tmin : tmax;
|
|
7987
|
+
if (t < 0) return null;
|
|
7988
|
+
return {
|
|
7989
|
+
entityId,
|
|
7990
|
+
distance: t,
|
|
7991
|
+
point: { x: origin.x + dir.x * t, y: origin.y + dir.y * t, z: origin.z + dir.z * t },
|
|
7992
|
+
normal: hitNormal
|
|
7993
|
+
};
|
|
7994
|
+
}
|
|
7995
|
+
raySphere(origin, dir, sphere, entityId) {
|
|
7996
|
+
const ox = origin.x - sphere.center.x, oy = origin.y - sphere.center.y, oz = origin.z - sphere.center.z;
|
|
7997
|
+
const a = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
|
|
7998
|
+
const b = 2 * (ox * dir.x + oy * dir.y + oz * dir.z);
|
|
7999
|
+
const c = ox * ox + oy * oy + oz * oz - sphere.radius * sphere.radius;
|
|
8000
|
+
const disc = b * b - 4 * a * c;
|
|
8001
|
+
if (disc < 0) return null;
|
|
8002
|
+
const sqrtDisc = Math.sqrt(disc);
|
|
8003
|
+
let t = (-b - sqrtDisc) / (2 * a);
|
|
8004
|
+
if (t < 0) t = (-b + sqrtDisc) / (2 * a);
|
|
8005
|
+
if (t < 0) return null;
|
|
8006
|
+
const point = { x: origin.x + dir.x * t, y: origin.y + dir.y * t, z: origin.z + dir.z * t };
|
|
8007
|
+
const nx = point.x - sphere.center.x, ny = point.y - sphere.center.y, nz = point.z - sphere.center.z;
|
|
8008
|
+
const nLen = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
|
|
8009
|
+
return {
|
|
8010
|
+
entityId,
|
|
8011
|
+
distance: t,
|
|
8012
|
+
point,
|
|
8013
|
+
normal: { x: nx / nLen, y: ny / nLen, z: nz / nLen }
|
|
8014
|
+
};
|
|
8015
|
+
}
|
|
8016
|
+
rayPlane(origin, dir, plane, entityId) {
|
|
8017
|
+
const denom = plane.normal.x * dir.x + plane.normal.y * dir.y + plane.normal.z * dir.z;
|
|
8018
|
+
if (Math.abs(denom) < 1e-10) return null;
|
|
8019
|
+
const t = -(plane.normal.x * origin.x + plane.normal.y * origin.y + plane.normal.z * origin.z + plane.distance) / denom;
|
|
8020
|
+
if (t < 0) return null;
|
|
8021
|
+
return {
|
|
8022
|
+
entityId,
|
|
8023
|
+
distance: t,
|
|
8024
|
+
point: { x: origin.x + dir.x * t, y: origin.y + dir.y * t, z: origin.z + dir.z * t },
|
|
8025
|
+
normal: { ...plane.normal }
|
|
8026
|
+
};
|
|
8027
|
+
}
|
|
8028
|
+
// ---------------------------------------------------------------------------
|
|
8029
|
+
// Helpers
|
|
8030
|
+
// ---------------------------------------------------------------------------
|
|
8031
|
+
normalize(v) {
|
|
8032
|
+
const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) || 1;
|
|
8033
|
+
return { x: v.x / len, y: v.y / len, z: v.z / len };
|
|
8034
|
+
}
|
|
8035
|
+
};
|
|
8036
|
+
|
|
8037
|
+
// src/physics/RopeSystem.ts
|
|
8038
|
+
var RopeSystem = class {
|
|
8039
|
+
ropes = /* @__PURE__ */ new Map();
|
|
8040
|
+
// ---------------------------------------------------------------------------
|
|
8041
|
+
// Creation
|
|
8042
|
+
// ---------------------------------------------------------------------------
|
|
8043
|
+
createRope(id, start, end, config) {
|
|
8044
|
+
const segCount = config?.segmentCount ?? 10;
|
|
8045
|
+
const dx = end.x - start.x, dy = end.y - start.y, dz = end.z - start.z;
|
|
8046
|
+
const totalLength = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
8047
|
+
const autoSegmentLength = totalLength / segCount;
|
|
8048
|
+
const cfg = {
|
|
8049
|
+
segmentCount: segCount,
|
|
8050
|
+
segmentLength: autoSegmentLength,
|
|
8051
|
+
gravity: { x: 0, y: -9.81, z: 0 },
|
|
8052
|
+
damping: 0.98,
|
|
8053
|
+
iterations: 8,
|
|
8054
|
+
elasticity: 1,
|
|
8055
|
+
...config
|
|
8056
|
+
};
|
|
8057
|
+
const nodes = [];
|
|
8058
|
+
for (let i = 0; i <= cfg.segmentCount; i++) {
|
|
8059
|
+
const t = i / cfg.segmentCount;
|
|
8060
|
+
nodes.push({
|
|
8061
|
+
position: [start.x + (end.x - start.x) * t, start.y + (end.y - start.y) * t, start.z + (end.z - start.z) * t],
|
|
8062
|
+
previous: {
|
|
8063
|
+
x: start.x + (end.x - start.x) * t,
|
|
8064
|
+
y: start.y + (end.y - start.y) * t,
|
|
8065
|
+
z: start.z + (end.z - start.z) * t
|
|
8066
|
+
},
|
|
8067
|
+
mass: 1,
|
|
8068
|
+
pinned: false
|
|
8069
|
+
});
|
|
8070
|
+
}
|
|
8071
|
+
this.ropes.set(id, { nodes, config: cfg, attachments: [] });
|
|
8072
|
+
}
|
|
8073
|
+
// ---------------------------------------------------------------------------
|
|
8074
|
+
// Pin / Attach
|
|
8075
|
+
// ---------------------------------------------------------------------------
|
|
8076
|
+
pinNode(ropeId, nodeIndex) {
|
|
8077
|
+
const rope = this.ropes.get(ropeId);
|
|
8078
|
+
if (rope?.nodes[nodeIndex]) rope.nodes[nodeIndex].pinned = true;
|
|
8079
|
+
}
|
|
8080
|
+
unpinNode(ropeId, nodeIndex) {
|
|
8081
|
+
const rope = this.ropes.get(ropeId);
|
|
8082
|
+
if (rope?.nodes[nodeIndex]) rope.nodes[nodeIndex].pinned = false;
|
|
8083
|
+
}
|
|
8084
|
+
attach(ropeId, attachment) {
|
|
8085
|
+
const rope = this.ropes.get(ropeId);
|
|
8086
|
+
if (rope) rope.attachments.push(attachment);
|
|
8087
|
+
}
|
|
8088
|
+
// ---------------------------------------------------------------------------
|
|
8089
|
+
// Simulation
|
|
8090
|
+
// ---------------------------------------------------------------------------
|
|
8091
|
+
update(dt) {
|
|
8092
|
+
const dt2 = dt * dt;
|
|
8093
|
+
for (const rope of this.ropes.values()) {
|
|
8094
|
+
const { nodes, config } = rope;
|
|
8095
|
+
for (const n of nodes) {
|
|
8096
|
+
if (n.pinned) continue;
|
|
8097
|
+
const vx = (n.position[0] - n.previous[0]) * config.damping;
|
|
8098
|
+
const vy = (n.position[1] - n.previous[1]) * config.damping;
|
|
8099
|
+
const vz = (n.position[2] - n.previous[2]) * config.damping;
|
|
8100
|
+
n.previous = { ...n.position };
|
|
8101
|
+
n.position[0] += vx + config.gravity[0] * dt2;
|
|
8102
|
+
n.position[1] += vy + config.gravity[1] * dt2;
|
|
8103
|
+
n.position[2] += vz + config.gravity[2] * dt2;
|
|
8104
|
+
}
|
|
8105
|
+
for (let iter = 0; iter < config.iterations; iter++) {
|
|
8106
|
+
for (let i = 0; i < nodes.length - 1; i++) {
|
|
8107
|
+
const a = nodes[i], b = nodes[i + 1];
|
|
8108
|
+
const dx = b.position[0] - a.position[0];
|
|
8109
|
+
const dy = b.position[1] - a.position[1];
|
|
8110
|
+
const dz = b.position[2] - a.position[2];
|
|
8111
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1e-4;
|
|
8112
|
+
const diff = (config.segmentLength - dist) / dist * config.elasticity * 0.5;
|
|
8113
|
+
const ox = dx * diff, oy = dy * diff, oz = dz * diff;
|
|
8114
|
+
if (!a.pinned) {
|
|
8115
|
+
a.position[0] -= ox;
|
|
8116
|
+
a.position[1] -= oy;
|
|
8117
|
+
a.position[2] -= oz;
|
|
8118
|
+
}
|
|
8119
|
+
if (!b.pinned) {
|
|
8120
|
+
b.position[0] += ox;
|
|
8121
|
+
b.position[1] += oy;
|
|
8122
|
+
b.position[2] += oz;
|
|
8123
|
+
}
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
// ---------------------------------------------------------------------------
|
|
8129
|
+
// Queries
|
|
8130
|
+
// ---------------------------------------------------------------------------
|
|
8131
|
+
getRopeNodes(ropeId) {
|
|
8132
|
+
return this.ropes.get(ropeId)?.nodes ?? [];
|
|
8133
|
+
}
|
|
8134
|
+
getRopeLength(ropeId) {
|
|
8135
|
+
const nodes = this.ropes.get(ropeId)?.nodes;
|
|
8136
|
+
if (!nodes || nodes.length < 2) return 0;
|
|
8137
|
+
let len = 0;
|
|
8138
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
8139
|
+
const a = nodes[i - 1].position, b = nodes[i].position;
|
|
8140
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
|
|
8141
|
+
len += Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
8142
|
+
}
|
|
8143
|
+
return len;
|
|
8144
|
+
}
|
|
8145
|
+
getTension(ropeId, nodeIndex) {
|
|
8146
|
+
const rope = this.ropes.get(ropeId);
|
|
8147
|
+
if (!rope || nodeIndex < 0 || nodeIndex >= rope.nodes.length - 1) return 0;
|
|
8148
|
+
const a = rope.nodes[nodeIndex].position, b = rope.nodes[nodeIndex + 1].position;
|
|
8149
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
|
|
8150
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
8151
|
+
return Math.abs(dist - rope.config.segmentLength) / rope.config.segmentLength;
|
|
8152
|
+
}
|
|
8153
|
+
getRopeCount() {
|
|
8154
|
+
return this.ropes.size;
|
|
8155
|
+
}
|
|
8156
|
+
removeRope(id) {
|
|
8157
|
+
this.ropes.delete(id);
|
|
8158
|
+
}
|
|
8159
|
+
};
|
|
8160
|
+
|
|
8161
|
+
// src/physics/TriggerZone.ts
|
|
8162
|
+
var TriggerZoneSystem = class {
|
|
8163
|
+
zones = /* @__PURE__ */ new Map();
|
|
8164
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
8165
|
+
// Per zone: set of entity IDs currently inside
|
|
8166
|
+
occupants = /* @__PURE__ */ new Map();
|
|
8167
|
+
// ---------------------------------------------------------------------------
|
|
8168
|
+
// Zone Management
|
|
8169
|
+
// ---------------------------------------------------------------------------
|
|
8170
|
+
addZone(config) {
|
|
8171
|
+
this.zones.set(config.id, config);
|
|
8172
|
+
this.occupants.set(config.id, /* @__PURE__ */ new Set());
|
|
8173
|
+
}
|
|
8174
|
+
removeZone(id) {
|
|
8175
|
+
this.zones.delete(id);
|
|
8176
|
+
this.occupants.delete(id);
|
|
8177
|
+
this.callbacks.delete(id);
|
|
8178
|
+
}
|
|
8179
|
+
enableZone(id, enabled) {
|
|
8180
|
+
const z = this.zones.get(id);
|
|
8181
|
+
if (z) z.enabled = enabled;
|
|
8182
|
+
}
|
|
8183
|
+
// ---------------------------------------------------------------------------
|
|
8184
|
+
// Callbacks
|
|
8185
|
+
// ---------------------------------------------------------------------------
|
|
8186
|
+
onTrigger(zoneId, callback) {
|
|
8187
|
+
if (!this.callbacks.has(zoneId)) this.callbacks.set(zoneId, []);
|
|
8188
|
+
this.callbacks.get(zoneId).push(callback);
|
|
8189
|
+
}
|
|
8190
|
+
fire(zoneId, entityId, event) {
|
|
8191
|
+
const cbs = this.callbacks.get(zoneId);
|
|
8192
|
+
if (cbs) for (const cb of cbs) cb(entityId, zoneId, event);
|
|
8193
|
+
}
|
|
8194
|
+
// ---------------------------------------------------------------------------
|
|
8195
|
+
// Update (test entities against zones)
|
|
8196
|
+
// ---------------------------------------------------------------------------
|
|
8197
|
+
update(entities) {
|
|
8198
|
+
for (const [zoneId, zone] of this.zones) {
|
|
8199
|
+
if (!zone.enabled) continue;
|
|
8200
|
+
const current = this.occupants.get(zoneId);
|
|
8201
|
+
const nowInside = /* @__PURE__ */ new Set();
|
|
8202
|
+
for (const entity of entities) {
|
|
8203
|
+
if (this.overlaps(zone.shape, entity.position, entity.radius ?? 0)) {
|
|
8204
|
+
nowInside.add(entity.id);
|
|
8205
|
+
if (current.has(entity.id)) {
|
|
8206
|
+
this.fire(zoneId, entity.id, "stay");
|
|
8207
|
+
} else {
|
|
8208
|
+
this.fire(zoneId, entity.id, "enter");
|
|
8209
|
+
}
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8212
|
+
for (const prevId of current) {
|
|
8213
|
+
if (!nowInside.has(prevId)) {
|
|
8214
|
+
this.fire(zoneId, prevId, "exit");
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
this.occupants.set(zoneId, nowInside);
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
// ---------------------------------------------------------------------------
|
|
8221
|
+
// Overlap Tests
|
|
8222
|
+
// ---------------------------------------------------------------------------
|
|
8223
|
+
overlaps(shape, pos, entityRadius) {
|
|
8224
|
+
if (shape.type === "sphere" && shape.radius !== void 0) {
|
|
8225
|
+
const dx = pos[0] - shape.position[0], dy = pos[1] - shape.position[1], dz = pos[2] - shape.position[2];
|
|
8226
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
8227
|
+
return dist <= shape.radius + entityRadius;
|
|
8228
|
+
}
|
|
8229
|
+
if (shape.type === "box" && shape.halfExtents) {
|
|
8230
|
+
const he = shape.halfExtents;
|
|
8231
|
+
const dx = Math.abs(pos[0] - shape.position[0]), dy = Math.abs(pos[1] - shape.position[1]), dz = Math.abs(pos[2] - shape.position[2]);
|
|
8232
|
+
return dx <= he.x + entityRadius && dy <= he.y + entityRadius && dz <= he.z + entityRadius;
|
|
8233
|
+
}
|
|
8234
|
+
return false;
|
|
8235
|
+
}
|
|
8236
|
+
// ---------------------------------------------------------------------------
|
|
8237
|
+
// Queries
|
|
8238
|
+
// ---------------------------------------------------------------------------
|
|
8239
|
+
isInside(entityId, zoneId) {
|
|
8240
|
+
return this.occupants.get(zoneId)?.has(entityId) ?? false;
|
|
8241
|
+
}
|
|
8242
|
+
getOccupants(zoneId) {
|
|
8243
|
+
const occ = this.occupants.get(zoneId);
|
|
8244
|
+
return occ ? [...occ] : [];
|
|
8245
|
+
}
|
|
8246
|
+
getZonesForEntity(entityId) {
|
|
8247
|
+
const zones = [];
|
|
8248
|
+
for (const [zoneId, occ] of this.occupants) {
|
|
8249
|
+
if (occ.has(entityId)) zones.push(zoneId);
|
|
8250
|
+
}
|
|
8251
|
+
return zones;
|
|
8252
|
+
}
|
|
8253
|
+
getZoneCount() {
|
|
8254
|
+
return this.zones.size;
|
|
8255
|
+
}
|
|
8256
|
+
};
|
|
8257
|
+
|
|
8258
|
+
// src/physics/VehicleSystem.ts
|
|
8259
|
+
function createDefaultCar(id) {
|
|
8260
|
+
const wheelConfig = (x, z, isSteering, isDriving) => ({
|
|
8261
|
+
id: `${id}_wheel_${x > 0 ? "r" : "l"}_${z > 0 ? "f" : "r"}`,
|
|
8262
|
+
connectionPoint: { x, y: -0.3, z },
|
|
8263
|
+
direction: { x: 0, y: -1, z: 0 },
|
|
8264
|
+
axle: { x: 1, y: 0, z: 0 },
|
|
8265
|
+
suspensionRestLength: 0.3,
|
|
8266
|
+
suspensionStiffness: 25,
|
|
8267
|
+
suspensionDamping: 4.4,
|
|
8268
|
+
maxSuspensionTravel: 0.2,
|
|
8269
|
+
wheelRadius: 0.35,
|
|
8270
|
+
frictionSlip: 1.2,
|
|
8271
|
+
isSteering,
|
|
8272
|
+
isDriving,
|
|
8273
|
+
rollInfluence: 0.1
|
|
8274
|
+
});
|
|
8275
|
+
return {
|
|
8276
|
+
id,
|
|
8277
|
+
chassisMass: 1500,
|
|
8278
|
+
chassisSize: { x: 1.8, y: 0.6, z: 4.2 },
|
|
8279
|
+
wheels: [
|
|
8280
|
+
wheelConfig(-0.8, 1.4, true, false),
|
|
8281
|
+
// Front Left
|
|
8282
|
+
wheelConfig(0.8, 1.4, true, false),
|
|
8283
|
+
// Front Right
|
|
8284
|
+
wheelConfig(-0.8, -1.4, false, true),
|
|
8285
|
+
// Rear Left
|
|
8286
|
+
wheelConfig(0.8, -1.4, false, true)
|
|
8287
|
+
// Rear Right
|
|
8288
|
+
],
|
|
8289
|
+
maxSteerAngle: 0.5,
|
|
8290
|
+
maxEngineForce: 3e3,
|
|
8291
|
+
maxBrakeForce: 100
|
|
8292
|
+
};
|
|
8293
|
+
}
|
|
8294
|
+
function createTruck(id) {
|
|
8295
|
+
const wheelConfig = (x, z, isSteering, isDriving) => ({
|
|
8296
|
+
id: `${id}_wheel_${x > 0 ? "r" : "l"}_${z.toFixed(0)}`,
|
|
8297
|
+
connectionPoint: { x, y: -0.5, z },
|
|
8298
|
+
direction: { x: 0, y: -1, z: 0 },
|
|
8299
|
+
axle: { x: 1, y: 0, z: 0 },
|
|
8300
|
+
suspensionRestLength: 0.4,
|
|
8301
|
+
suspensionStiffness: 40,
|
|
8302
|
+
suspensionDamping: 6,
|
|
8303
|
+
maxSuspensionTravel: 0.3,
|
|
8304
|
+
wheelRadius: 0.5,
|
|
8305
|
+
frictionSlip: 1.5,
|
|
8306
|
+
isSteering,
|
|
8307
|
+
isDriving,
|
|
8308
|
+
rollInfluence: 0.05
|
|
8309
|
+
});
|
|
8310
|
+
return {
|
|
8311
|
+
id,
|
|
8312
|
+
chassisMass: 5e3,
|
|
8313
|
+
chassisSize: { x: 2.4, y: 1, z: 7 },
|
|
8314
|
+
wheels: [
|
|
8315
|
+
wheelConfig(-1, 2.8, true, false),
|
|
8316
|
+
wheelConfig(1, 2.8, true, false),
|
|
8317
|
+
wheelConfig(-1, -1.5, false, true),
|
|
8318
|
+
wheelConfig(1, -1.5, false, true),
|
|
8319
|
+
wheelConfig(-1, -2.8, false, true),
|
|
8320
|
+
wheelConfig(1, -2.8, false, true)
|
|
8321
|
+
],
|
|
8322
|
+
maxSteerAngle: 0.4,
|
|
8323
|
+
maxEngineForce: 6e3,
|
|
8324
|
+
maxBrakeForce: 200
|
|
8325
|
+
};
|
|
8326
|
+
}
|
|
8327
|
+
var VehicleSystem = class {
|
|
8328
|
+
vehicles = /* @__PURE__ */ new Map();
|
|
8329
|
+
/**
|
|
8330
|
+
* Create a vehicle from a definition at a given position.
|
|
8331
|
+
*/
|
|
8332
|
+
createVehicle(definition, position) {
|
|
8333
|
+
const wheels = definition.wheels.map((wc) => ({
|
|
8334
|
+
config: wc,
|
|
8335
|
+
suspensionLength: wc.suspensionRestLength,
|
|
8336
|
+
suspensionForce: 0,
|
|
8337
|
+
contactPoint: null,
|
|
8338
|
+
isGrounded: false,
|
|
8339
|
+
rotation: 0,
|
|
8340
|
+
steerAngle: 0,
|
|
8341
|
+
skidFactor: 0
|
|
8342
|
+
}));
|
|
8343
|
+
const state = {
|
|
8344
|
+
id: definition.id,
|
|
8345
|
+
definition,
|
|
8346
|
+
position: { ...position },
|
|
8347
|
+
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
|
8348
|
+
linearVelocity: { x: 0, y: 0, z: 0 },
|
|
8349
|
+
angularVelocity: { x: 0, y: 0, z: 0 },
|
|
8350
|
+
wheels,
|
|
8351
|
+
speed: 0,
|
|
8352
|
+
engineForce: 0,
|
|
8353
|
+
brakeForce: 0,
|
|
8354
|
+
steerAngle: 0
|
|
8355
|
+
};
|
|
8356
|
+
this.vehicles.set(definition.id, state);
|
|
8357
|
+
return state;
|
|
8358
|
+
}
|
|
8359
|
+
/**
|
|
8360
|
+
* Update vehicle physics for one timestep.
|
|
8361
|
+
*/
|
|
8362
|
+
update(vehicleId, dt) {
|
|
8363
|
+
const vehicle = this.vehicles.get(vehicleId);
|
|
8364
|
+
if (!vehicle) return null;
|
|
8365
|
+
const def = vehicle.definition;
|
|
8366
|
+
for (const wheel of vehicle.wheels) {
|
|
8367
|
+
if (wheel.config.isSteering) {
|
|
8368
|
+
wheel.steerAngle = vehicle.steerAngle;
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8371
|
+
let totalSuspensionForce = 0;
|
|
8372
|
+
const forwardDir = this.getForwardVector(vehicle);
|
|
8373
|
+
for (const wheel of vehicle.wheels) {
|
|
8374
|
+
const wheelWorldY = vehicle.position.y + wheel.config.connectionPoint.y;
|
|
8375
|
+
const rayEnd = wheelWorldY - wheel.config.suspensionRestLength - wheel.config.wheelRadius;
|
|
8376
|
+
if (rayEnd <= 0) {
|
|
8377
|
+
wheel.isGrounded = true;
|
|
8378
|
+
const compression = -rayEnd;
|
|
8379
|
+
wheel.suspensionLength = Math.max(
|
|
8380
|
+
wheel.config.suspensionRestLength - compression,
|
|
8381
|
+
wheel.config.suspensionRestLength - wheel.config.maxSuspensionTravel
|
|
8382
|
+
);
|
|
8383
|
+
const springDelta = wheel.config.suspensionRestLength - wheel.suspensionLength;
|
|
8384
|
+
const springForce = springDelta * wheel.config.suspensionStiffness;
|
|
8385
|
+
const relVel = vehicle.linearVelocity.y;
|
|
8386
|
+
const dampForce = -relVel * wheel.config.suspensionDamping;
|
|
8387
|
+
wheel.suspensionForce = Math.max(0, springForce + dampForce);
|
|
8388
|
+
totalSuspensionForce += wheel.suspensionForce;
|
|
8389
|
+
wheel.contactPoint = {
|
|
8390
|
+
x: vehicle.position.x + wheel.config.connectionPoint.x,
|
|
8391
|
+
y: 0,
|
|
8392
|
+
z: vehicle.position.z + wheel.config.connectionPoint.z
|
|
8393
|
+
};
|
|
8394
|
+
} else {
|
|
8395
|
+
wheel.isGrounded = false;
|
|
8396
|
+
wheel.suspensionForce = 0;
|
|
8397
|
+
wheel.contactPoint = null;
|
|
8398
|
+
wheel.suspensionLength = wheel.config.suspensionRestLength;
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8401
|
+
for (const wheel of vehicle.wheels) {
|
|
8402
|
+
if (!wheel.isGrounded) continue;
|
|
8403
|
+
if (wheel.config.isDriving && vehicle.engineForce !== 0) {
|
|
8404
|
+
const driveForce = vehicle.engineForce / vehicle.wheels.filter((w) => w.config.isDriving).length;
|
|
8405
|
+
vehicle.linearVelocity.x += forwardDir.x * driveForce / def.chassisMass * dt;
|
|
8406
|
+
vehicle.linearVelocity.z += forwardDir.z * driveForce / def.chassisMass * dt;
|
|
8407
|
+
}
|
|
8408
|
+
if (vehicle.brakeForce > 0) {
|
|
8409
|
+
const speed = Math.sqrt(vehicle.linearVelocity.x ** 2 + vehicle.linearVelocity.z ** 2);
|
|
8410
|
+
if (speed > 0.01) {
|
|
8411
|
+
const brakeDec = Math.min(vehicle.brakeForce / def.chassisMass * dt, speed);
|
|
8412
|
+
const factor = 1 - brakeDec / speed;
|
|
8413
|
+
vehicle.linearVelocity.x *= factor;
|
|
8414
|
+
vehicle.linearVelocity.z *= factor;
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
const wheelSpeed = Math.sqrt(vehicle.linearVelocity.x ** 2 + vehicle.linearVelocity.z ** 2);
|
|
8418
|
+
wheel.rotation += wheelSpeed / wheel.config.wheelRadius * dt;
|
|
8419
|
+
const lateralSpeed = Math.abs(vehicle.angularVelocity.y * 0.5);
|
|
8420
|
+
wheel.skidFactor = Math.min(lateralSpeed / (wheel.config.frictionSlip + 1e-3), 1);
|
|
8421
|
+
}
|
|
8422
|
+
const isGrounded = vehicle.wheels.some((w) => w.isGrounded);
|
|
8423
|
+
if (!isGrounded) {
|
|
8424
|
+
vehicle.linearVelocity.y -= 9.81 * dt;
|
|
8425
|
+
} else {
|
|
8426
|
+
const suspensionAccel = totalSuspensionForce / def.chassisMass;
|
|
8427
|
+
vehicle.linearVelocity.y += (suspensionAccel - 9.81) * dt;
|
|
8428
|
+
const refWheel = vehicle.wheels[0].config;
|
|
8429
|
+
if (vehicle.position.y <= refWheel.wheelRadius + refWheel.suspensionRestLength) {
|
|
8430
|
+
vehicle.linearVelocity.y = Math.max(vehicle.linearVelocity.y, 0);
|
|
8431
|
+
}
|
|
8432
|
+
}
|
|
8433
|
+
if (vehicle.steerAngle !== 0 && isGrounded) {
|
|
8434
|
+
const speed = Math.sqrt(vehicle.linearVelocity.x ** 2 + vehicle.linearVelocity.z ** 2);
|
|
8435
|
+
const turnRate = vehicle.steerAngle * speed * 0.5;
|
|
8436
|
+
vehicle.angularVelocity.y = turnRate;
|
|
8437
|
+
} else {
|
|
8438
|
+
vehicle.angularVelocity.y *= 0.95;
|
|
8439
|
+
}
|
|
8440
|
+
vehicle.position.x += vehicle.linearVelocity.x * dt;
|
|
8441
|
+
vehicle.position.y += vehicle.linearVelocity.y * dt;
|
|
8442
|
+
vehicle.position.z += vehicle.linearVelocity.z * dt;
|
|
8443
|
+
vehicle.speed = Math.sqrt(vehicle.linearVelocity.x ** 2 + vehicle.linearVelocity.z ** 2) * 3.6;
|
|
8444
|
+
return vehicle;
|
|
8445
|
+
}
|
|
8446
|
+
// ---------------------------------------------------------------------------
|
|
8447
|
+
// Controls
|
|
8448
|
+
// ---------------------------------------------------------------------------
|
|
8449
|
+
setThrottle(vehicleId, throttle) {
|
|
8450
|
+
const v = this.vehicles.get(vehicleId);
|
|
8451
|
+
if (!v) return;
|
|
8452
|
+
v.engineForce = throttle * v.definition.maxEngineForce;
|
|
8453
|
+
}
|
|
8454
|
+
setBrake(vehicleId, brake) {
|
|
8455
|
+
const v = this.vehicles.get(vehicleId);
|
|
8456
|
+
if (!v) return;
|
|
8457
|
+
v.brakeForce = brake * v.definition.maxBrakeForce;
|
|
8458
|
+
}
|
|
8459
|
+
setSteering(vehicleId, steering) {
|
|
8460
|
+
const v = this.vehicles.get(vehicleId);
|
|
8461
|
+
if (!v) return;
|
|
8462
|
+
v.steerAngle = Math.max(-1, Math.min(1, steering)) * v.definition.maxSteerAngle;
|
|
8463
|
+
}
|
|
8464
|
+
getVehicle(vehicleId) {
|
|
8465
|
+
return this.vehicles.get(vehicleId);
|
|
8466
|
+
}
|
|
8467
|
+
removeVehicle(vehicleId) {
|
|
8468
|
+
return this.vehicles.delete(vehicleId);
|
|
8469
|
+
}
|
|
8470
|
+
// ---------------------------------------------------------------------------
|
|
8471
|
+
// Helpers
|
|
8472
|
+
// ---------------------------------------------------------------------------
|
|
8473
|
+
getForwardVector(vehicle) {
|
|
8474
|
+
const yaw = vehicle.angularVelocity.y;
|
|
8475
|
+
return { x: Math.sin(yaw), y: 0, z: Math.cos(yaw) };
|
|
8476
|
+
}
|
|
8477
|
+
};
|
|
8478
|
+
|
|
8479
|
+
// src/physics/SpatialHash.ts
|
|
8480
|
+
var SpatialHash = class {
|
|
8481
|
+
cellSize;
|
|
8482
|
+
cells = /* @__PURE__ */ new Map();
|
|
8483
|
+
entries = /* @__PURE__ */ new Map();
|
|
8484
|
+
constructor(cellSize) {
|
|
8485
|
+
this.cellSize = cellSize;
|
|
8486
|
+
}
|
|
8487
|
+
// ---------------------------------------------------------------------------
|
|
8488
|
+
// Insert / Remove
|
|
8489
|
+
// ---------------------------------------------------------------------------
|
|
8490
|
+
insert(entry) {
|
|
8491
|
+
this.entries.set(entry.id, entry);
|
|
8492
|
+
const cells = this.getCellsForEntry(entry);
|
|
8493
|
+
for (const key of cells) {
|
|
8494
|
+
if (!this.cells.has(key)) this.cells.set(key, /* @__PURE__ */ new Set());
|
|
8495
|
+
this.cells.get(key).add(entry.id);
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
remove(id) {
|
|
8499
|
+
const entry = this.entries.get(id);
|
|
8500
|
+
if (!entry) return;
|
|
8501
|
+
const cells = this.getCellsForEntry(entry);
|
|
8502
|
+
for (const key of cells) {
|
|
8503
|
+
const cell = this.cells.get(key);
|
|
8504
|
+
if (cell) {
|
|
8505
|
+
cell.delete(id);
|
|
8506
|
+
if (cell.size === 0) this.cells.delete(key);
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
this.entries.delete(id);
|
|
8510
|
+
}
|
|
8511
|
+
update(id, x, y, z) {
|
|
8512
|
+
this.remove(id);
|
|
8513
|
+
const entry = this.entries.get(id) ?? { id, x, y, z, radius: 0 };
|
|
8514
|
+
entry.x = x;
|
|
8515
|
+
entry.y = y;
|
|
8516
|
+
entry.z = z;
|
|
8517
|
+
this.insert(entry);
|
|
8518
|
+
}
|
|
8519
|
+
// ---------------------------------------------------------------------------
|
|
8520
|
+
// Queries
|
|
8521
|
+
// ---------------------------------------------------------------------------
|
|
8522
|
+
queryPoint(x, y, z) {
|
|
8523
|
+
const key = this.cellKey(
|
|
8524
|
+
Math.floor(x / this.cellSize),
|
|
8525
|
+
Math.floor(y / this.cellSize),
|
|
8526
|
+
Math.floor(z / this.cellSize)
|
|
8527
|
+
);
|
|
8528
|
+
const cell = this.cells.get(key);
|
|
8529
|
+
return cell ? [...cell] : [];
|
|
8530
|
+
}
|
|
8531
|
+
queryRadius(x, y, z, radius) {
|
|
8532
|
+
const results = /* @__PURE__ */ new Set();
|
|
8533
|
+
const minCx = Math.floor((x - radius) / this.cellSize);
|
|
8534
|
+
const maxCx = Math.floor((x + radius) / this.cellSize);
|
|
8535
|
+
const minCy = Math.floor((y - radius) / this.cellSize);
|
|
8536
|
+
const maxCy = Math.floor((y + radius) / this.cellSize);
|
|
8537
|
+
const minCz = Math.floor((z - radius) / this.cellSize);
|
|
8538
|
+
const maxCz = Math.floor((z + radius) / this.cellSize);
|
|
8539
|
+
for (let cx = minCx; cx <= maxCx; cx++) {
|
|
8540
|
+
for (let cy = minCy; cy <= maxCy; cy++) {
|
|
8541
|
+
for (let cz = minCz; cz <= maxCz; cz++) {
|
|
8542
|
+
const cell = this.cells.get(this.cellKey(cx, cy, cz));
|
|
8543
|
+
if (!cell) continue;
|
|
8544
|
+
for (const id of cell) {
|
|
8545
|
+
const entry = this.entries.get(id);
|
|
8546
|
+
if (entry) {
|
|
8547
|
+
const dx = entry.x - x, dy = entry.y - y, dz = entry.z - z;
|
|
8548
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
8549
|
+
if (dist <= radius + entry.radius) results.add(id);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
}
|
|
8554
|
+
}
|
|
8555
|
+
return [...results];
|
|
8556
|
+
}
|
|
8557
|
+
getNearbyPairs() {
|
|
8558
|
+
const pairs = [];
|
|
8559
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8560
|
+
for (const cell of this.cells.values()) {
|
|
8561
|
+
const ids = [...cell];
|
|
8562
|
+
for (let i = 0; i < ids.length; i++) {
|
|
8563
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
8564
|
+
const key = ids[i] < ids[j] ? `${ids[i]}:${ids[j]}` : `${ids[j]}:${ids[i]}`;
|
|
8565
|
+
if (!seen.has(key)) {
|
|
8566
|
+
seen.add(key);
|
|
8567
|
+
pairs.push([ids[i], ids[j]]);
|
|
8568
|
+
}
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
return pairs;
|
|
8573
|
+
}
|
|
8574
|
+
// ---------------------------------------------------------------------------
|
|
8575
|
+
// Internals
|
|
8576
|
+
// ---------------------------------------------------------------------------
|
|
8577
|
+
getCellsForEntry(entry) {
|
|
8578
|
+
const keys = [];
|
|
8579
|
+
const minCx = Math.floor((entry.x - entry.radius) / this.cellSize);
|
|
8580
|
+
const maxCx = Math.floor((entry.x + entry.radius) / this.cellSize);
|
|
8581
|
+
const minCy = Math.floor((entry.y - entry.radius) / this.cellSize);
|
|
8582
|
+
const maxCy = Math.floor((entry.y + entry.radius) / this.cellSize);
|
|
8583
|
+
const minCz = Math.floor((entry.z - entry.radius) / this.cellSize);
|
|
8584
|
+
const maxCz = Math.floor((entry.z + entry.radius) / this.cellSize);
|
|
8585
|
+
for (let cx = minCx; cx <= maxCx; cx++) {
|
|
8586
|
+
for (let cy = minCy; cy <= maxCy; cy++) {
|
|
8587
|
+
for (let cz = minCz; cz <= maxCz; cz++) {
|
|
8588
|
+
keys.push(this.cellKey(cx, cy, cz));
|
|
8589
|
+
}
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
return keys;
|
|
8593
|
+
}
|
|
8594
|
+
cellKey(cx, cy, cz) {
|
|
8595
|
+
return `${cx}:${cy}:${cz}`;
|
|
8596
|
+
}
|
|
8597
|
+
// ---------------------------------------------------------------------------
|
|
8598
|
+
// Stats
|
|
8599
|
+
// ---------------------------------------------------------------------------
|
|
8600
|
+
getEntryCount() {
|
|
8601
|
+
return this.entries.size;
|
|
8602
|
+
}
|
|
8603
|
+
getCellCount() {
|
|
8604
|
+
return this.cells.size;
|
|
8605
|
+
}
|
|
8606
|
+
clear() {
|
|
8607
|
+
this.cells.clear();
|
|
8608
|
+
this.entries.clear();
|
|
8609
|
+
}
|
|
8610
|
+
};
|
|
6272
8611
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6273
8612
|
0 && (module.exports = {
|
|
6274
8613
|
ActivationTriggerType,
|
|
6275
8614
|
COLLISION_GROUPS,
|
|
8615
|
+
ClothSim,
|
|
8616
|
+
ConstraintSolver,
|
|
6276
8617
|
DEFAULT_ACTIVATION_CONFIG,
|
|
6277
8618
|
DEFAULT_INTENSITY_CURVE,
|
|
6278
8619
|
DEFAULT_LOCOMOTION_CONFIG,
|
|
8620
|
+
DeformableMesh,
|
|
8621
|
+
FluidSim,
|
|
8622
|
+
HUMANOID_PRESET,
|
|
6279
8623
|
IslandDetector,
|
|
8624
|
+
JointSystem,
|
|
6280
8625
|
MLSMPMFluid,
|
|
6281
8626
|
PBDSolverCPU,
|
|
6282
8627
|
PBD_ATTACHMENT_SHADER,
|
|
@@ -6299,14 +8644,23 @@ var SoftBodyAdapter = class {
|
|
|
6299
8644
|
PhysicsSyncReceiver,
|
|
6300
8645
|
PhysicsSyncSender,
|
|
6301
8646
|
PhysicsWorldImpl,
|
|
8647
|
+
QUADRUPED_PRESET,
|
|
8648
|
+
RagdollController,
|
|
8649
|
+
RagdollSystem,
|
|
8650
|
+
RaycastSystem,
|
|
6302
8651
|
RigidBody,
|
|
8652
|
+
RopeSystem,
|
|
6303
8653
|
SOFT_BODY_PRESETS,
|
|
6304
8654
|
ScalarArithmetic,
|
|
6305
8655
|
SoftBodyAdapter,
|
|
6306
8656
|
SoftBodyGrabController,
|
|
6307
8657
|
SoftBodySolver,
|
|
8658
|
+
SpatialHash,
|
|
8659
|
+
TriggerZoneSystem,
|
|
6308
8660
|
UnifiedParticleBuffer,
|
|
8661
|
+
VRPhysicsBridge,
|
|
6309
8662
|
Vector3Arithmetic,
|
|
8663
|
+
VehicleSystem,
|
|
6310
8664
|
VelocityRingBuffer,
|
|
6311
8665
|
VelocitySmoother,
|
|
6312
8666
|
WindZoneManager,
|
|
@@ -6316,10 +8670,12 @@ var SoftBodyAdapter = class {
|
|
|
6316
8670
|
colorConstraints,
|
|
6317
8671
|
computeRestLengths,
|
|
6318
8672
|
computeSelfWind,
|
|
8673
|
+
createDefaultCar,
|
|
6319
8674
|
createPBDSolver,
|
|
6320
8675
|
createPIDControllerTrait,
|
|
6321
8676
|
createPhysicsWorld,
|
|
6322
8677
|
createScalarPIDController,
|
|
8678
|
+
createTruck,
|
|
6323
8679
|
createVector3PIDController,
|
|
6324
8680
|
defaultMaterial,
|
|
6325
8681
|
defaultPIDConfig,
|