Body step 2: the player is a muscle-driven ragdoll (Project TX)
PlayerRagdoll: 9 bodies, spine/neck ball joints, shoulder drives (girdle + muscle), elbow hinges, torque-capped motor PDs toward the True Body, chest->hand guides, implicit balance motor, mass-scaled ground control. PlayerRig: XR driver with stick move / snap turn and Bonelab-style push-back (eye->camera sphere sweep). Replaces the chase bodies in main.tscn; RagdollSpike regression (14 phases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0b50fbc41
commit
d83273dd2f
16 files changed
+1487
-28
No files matched your search
+25
-1
@@ -26,8 +26,13 @@ public sealed class TrueBodySolver
|
||||
public const float WristOffset = 0.06f;
|
||||
|
||||
public Vector3 Eye, NeckPivot, NeckBase, UpperChest, Pelvis;
|
||||
public Vector3 PelvisTarget; // un-lagged pelvis (the ragdoll's ground control adds its own lag)
|
||||
public Basis ChestBasis = Basis.Identity;
|
||||
public Basis PelvisBasis = Basis.Identity;
|
||||
public Basis YawBasis = Basis.Identity;
|
||||
public Basis HeadBasis = Basis.Identity;
|
||||
public float Lean { get; private set; }
|
||||
public readonly Vector3[] Pole = new Vector3[2]; // per arm, see ArmSolver.Pole
|
||||
public readonly Vector3[] Shoulder = new Vector3[2];
|
||||
public readonly Vector3[] Elbow = new Vector3[2];
|
||||
public readonly Vector3[] Wrist = new Vector3[2];
|
||||
@@ -40,6 +45,18 @@ public sealed class TrueBodySolver
|
||||
private Vector3 _pelvisVelocity;
|
||||
private float _yaw;
|
||||
|
||||
// A discrete move of the whole player (teleport, snap turn): carry the lagging state along
|
||||
// instead of letting it swing across the gap.
|
||||
public void Shift(Transform3D delta)
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
Pelvis = delta * Pelvis;
|
||||
_pelvisVelocity = delta.Basis * _pelvisVelocity;
|
||||
Vector3 f = delta.Basis * new Basis(Vector3.Up, _yaw) * Vector3.Forward;
|
||||
_yaw = Mathf.Atan2(-f.X, -f.Z);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Initialized = false;
|
||||
@@ -52,6 +69,7 @@ public sealed class TrueBodySolver
|
||||
{
|
||||
Eye = head.Origin;
|
||||
Basis headBasis = head.Basis.Orthonormalized();
|
||||
HeadBasis = headBasis;
|
||||
NeckPivot = Eye + headBasis * EyeToNeckPivot;
|
||||
WristTarget[0] = left * new Vector3(0f, 0f, WristOffset);
|
||||
WristTarget[1] = right * new Vector3(0f, 0f, WristOffset);
|
||||
@@ -73,6 +91,7 @@ public sealed class TrueBodySolver
|
||||
_yaw = hmdYaw;
|
||||
_yaw = Mathf.LerpAngle(_yaw, targetYaw, 1f - Mathf.Exp(-dt * YawSmoothRate));
|
||||
Basis yawBasis = new Basis(Vector3.Up, _yaw);
|
||||
YawBasis = yawBasis;
|
||||
|
||||
// Crouch lean: measured at the NECK PIVOT (nodding moves the eyes ~10 cm but not the pivot),
|
||||
// below its standing height the chest pitches forward; looking down adds a little.
|
||||
@@ -81,6 +100,7 @@ public sealed class TrueBodySolver
|
||||
Crouch = Mathf.Clamp((standingPivot - pivotAboveFloor) / (CrouchRange * p.EyeHeight), 0f, 1f);
|
||||
float headPitchDown = Mathf.Max(0f, -Mathf.Asin(Mathf.Clamp(headFwd.Y, -1f, 1f)));
|
||||
float lean = Crouch * ChestLeanMax + headPitchDown * HeadPitchToChest;
|
||||
Lean = lean;
|
||||
ChestBasis = yawBasis * new Basis(Vector3.Right, -lean);
|
||||
|
||||
// Neck: from the head pivot down along the average of chest-up and head-up, sized so a
|
||||
@@ -96,6 +116,7 @@ public sealed class TrueBodySolver
|
||||
// Pelvis: hangs below the upper chest along the trunk; follows through a critically damped
|
||||
// spring so it lags quick head motion (hip sway) instead of being rigidly attached.
|
||||
Vector3 pelvisTarget = UpperChest - ChestBasis.Y * (TrunkRatio * p.Stature);
|
||||
PelvisTarget = pelvisTarget;
|
||||
if (!Initialized)
|
||||
Pelvis = pelvisTarget;
|
||||
float w = Mathf.Tau * PelvisFrequency;
|
||||
@@ -138,6 +159,7 @@ public sealed class TrueBodySolver
|
||||
|
||||
_arms[side].Solve(Shoulder[side], WristTarget[side], ChestBasis, outward, handBasis, p.UpperArm, p.Forearm, dt);
|
||||
Elbow[side] = _arms[side].Elbow;
|
||||
Pole[side] = _arms[side].Pole;
|
||||
Wrist[side] = _arms[side].Wrist;
|
||||
}
|
||||
}
|
||||
@@ -157,6 +179,8 @@ public partial class TrueBody : Node3D
|
||||
private readonly System.Collections.Generic.List<MeshInstance3D> _joints = new();
|
||||
|
||||
public TrueBodySolver Solver { get; } = new();
|
||||
// Set by PlayerRig, which solves at the right point of its own tick (after moving the origin).
|
||||
public bool ExternallySolved;
|
||||
public bool HasPose => Solver.Initialized;
|
||||
|
||||
public override void _Ready()
|
||||
@@ -178,7 +202,7 @@ public partial class TrueBody : Node3D
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
BodyProfile profile = BodyProfile.Current;
|
||||
if (profile == null)
|
||||
if (profile == null || ExternallySolved)
|
||||
return;
|
||||
Solver.Solve(profile, _camera.GlobalTransform, _left.GlobalTransform, _right.GlobalTransform, _origin.GlobalPosition.Y, (float)delta);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user