Clambering: capsule hips (Nimso), footprint takes the edge while pressing, knee-up memory
The box pelvis caught on ledge lips; a sideways capsule rides over. While clambering (pressing, just pressed, legs tucked) the stance footprint may take higher ground the hips are only partly over. Spike measures the free hand's error while climbing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0bddcf4517
commit
83186f9676
2 files changed
+39
-10
No files matched your search
+24
-10
@@ -257,8 +257,11 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
_eyeLocal -= headCenterFromPivot; // eye relative to the head body's centre
|
_eyeLocal -= headCenterFromPivot; // eye relative to the head body's centre
|
||||||
|
|
||||||
Color skin = new(0.85f, 0.72f, 0.6f), shirt = new(0.3f, 0.45f, 0.7f), pants = new(0.25f, 0.27f, 0.32f);
|
Color skin = new(0.85f, 0.72f, 0.6f), shirt = new(0.3f, 0.45f, 0.7f), pants = new(0.25f, 0.27f, 0.32f);
|
||||||
Pelvis = MakeBody("Pelvis", new BoxShape3D { Size = pelvisSize }, (0.142f + 0.139f + 0.322f) * m - LegsMass, BodyLayer, WorldMask, pants,
|
// Hips = a sideways capsule (Nimso: "hip collider: a capsule, ~27 cm diameter"): a box's bottom
|
||||||
c * new Transform3D(Basis.Identity, hip + _pelvisCenterLocal), friction: TorsoFriction);
|
// edge caught on ledge lips a centimetre below it, so the hips couldn't slide over onto them.
|
||||||
|
float hipRadius = Mathf.Min(0.5f * Mathf.Max(pelvisSize.Y, pelvisSize.Z), 0.5f * pelvisSize.X);
|
||||||
|
Pelvis = MakeBody("Pelvis", new CapsuleShape3D { Radius = hipRadius, Height = pelvisSize.X }, (0.142f + 0.139f + 0.322f) * m - LegsMass, BodyLayer, WorldMask, pants,
|
||||||
|
c * new Transform3D(Basis.Identity, hip + _pelvisCenterLocal), friction: TorsoFriction, shapeBasis: new Basis(Vector3.Back, Mathf.Pi / 2f));
|
||||||
float standingHip = pose.PelvisTarget.Y - (pose.Eye.Y - p.EyeHeight);
|
float standingHip = pose.PelvisTarget.Y - (pose.Eye.Y - p.EyeHeight);
|
||||||
_standingHip = standingHip + _pelvisCenterLocal.Y; // of the pelvis BODY (what ground control holds)
|
_standingHip = standingHip + _pelvisCenterLocal.Y; // of the pelvis BODY (what ground control holds)
|
||||||
_legsHeight = Mathf.Max(0.2f, standingHip - MaxStepHeight);
|
_legsHeight = Mathf.Max(0.2f, standingHip - MaxStepHeight);
|
||||||
@@ -372,14 +375,22 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
|
|
||||||
// NOTE (Box3D wrapper): friction is read only when the body's shapes are created — the material
|
// NOTE (Box3D wrapper): friction is read only when the body's shapes are created — the material
|
||||||
// must be set before AddChild (setting it later silently does nothing).
|
// must be set before AddChild (setting it later silently does nothing).
|
||||||
private RigidBody3D MakeBody(string name, Shape3D shape, float mass, uint layer, uint mask, Color color, Transform3D at, bool visible = true, float friction = 1f)
|
private RigidBody3D MakeBody(string name, Shape3D shape, float mass, uint layer, uint mask, Color color, Transform3D at, bool visible = true, float friction = 1f,
|
||||||
|
Basis? shapeBasis = null)
|
||||||
{
|
{
|
||||||
RigidBody3D body = new() { Name = name, Mass = mass, CollisionLayer = layer, CollisionMask = mask, Transform = at, ContactMonitor = true, MaxContactsReported = 4,
|
RigidBody3D body = new() { Name = name, Mass = mass, CollisionLayer = layer, CollisionMask = mask, Transform = at, ContactMonitor = true, MaxContactsReported = 4,
|
||||||
PhysicsMaterialOverride = new PhysicsMaterial { Friction = friction } };
|
PhysicsMaterialOverride = new PhysicsMaterial { Friction = friction } };
|
||||||
body.AddChild(new CollisionShape3D { Shape = shape });
|
Transform3D shapeAt = new(shapeBasis ?? Basis.Identity, Vector3.Zero);
|
||||||
if (shape is BoxShape3D box && layer != 0 && visible)
|
body.AddChild(new CollisionShape3D { Shape = shape, Transform = shapeAt });
|
||||||
|
Mesh visual = shape switch
|
||||||
{
|
{
|
||||||
MeshInstance3D mesh = new() { Mesh = new BoxMesh { Size = box.Size } };
|
BoxShape3D box => new BoxMesh { Size = box.Size },
|
||||||
|
CapsuleShape3D cap => new CapsuleMesh { Radius = cap.Radius, Height = cap.Height },
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
if (visual != null && layer != 0 && visible)
|
||||||
|
{
|
||||||
|
MeshInstance3D mesh = new() { Mesh = visual, Transform = shapeAt };
|
||||||
mesh.SetSurfaceOverrideMaterial(0, new StandardMaterial3D { AlbedoColor = color, Roughness = 0.6f });
|
mesh.SetSurfaceOverrideMaterial(0, new StandardMaterial3D { AlbedoColor = color, Roughness = 0.6f });
|
||||||
body.AddChild(mesh);
|
body.AddChild(mesh);
|
||||||
}
|
}
|
||||||
@@ -762,7 +773,7 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
bool deep = center + _legsHeight * 0.5f >= hip.Y + LegsOffAbove;
|
bool deep = center + _legsHeight * 0.5f >= hip.Y + LegsOffAbove;
|
||||||
// Hands braced while the hips are held back by an obstacle = getting a knee up onto it (a
|
// Hands braced while the hips are held back by an obstacle = getting a knee up onto it (a
|
||||||
// thigh-high crate or ledge the straight arms can't press down on while standing).
|
// thigh-high crate or ledge the straight arms can't press down on while standing).
|
||||||
bool kneeUp = Braced && HeldBack != Vector3.Zero;
|
bool kneeUp = (Braced || _sinceBraced < ClamberMemory) && HeldBack != Vector3.Zero;
|
||||||
bool wantTuck = deep || _lifted || kneeUp || (!_legsSolid && Braced);
|
bool wantTuck = deep || _lifted || kneeUp || (!_legsSolid && Braced);
|
||||||
_tuckFree = wantTuck ? 0f : _tuckFree + _dt;
|
_tuckFree = wantTuck ? 0f : _tuckFree + _dt;
|
||||||
Vector3 untucked = new(hip.X, center, hip.Z);
|
Vector3 untucked = new(hip.X, center, hip.Z);
|
||||||
@@ -863,7 +874,8 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
bool wasGrounded = Grounded;
|
bool wasGrounded = Grounded;
|
||||||
Grounded = false;
|
Grounded = false;
|
||||||
_groundBody = null;
|
_groundBody = null;
|
||||||
if (ProbeGround(pos, hipAboveFloor + GroundProbeExtra + 0.5f, wasGrounded, out Vector3 point, out Vector3 normal, out GodotObject groundObject, out bool underHips))
|
bool clambering = Braced || _lifted || _sinceBraced < ClamberMemory || !_legsSolid;
|
||||||
|
if (ProbeGround(pos, hipAboveFloor + GroundProbeExtra + 0.5f, wasGrounded || clambering ? (clambering ? 2 : 1) : 0, out Vector3 point, out Vector3 normal, out GodotObject groundObject, out bool underHips))
|
||||||
{
|
{
|
||||||
_groundBody = groundObject as RigidBody3D;
|
_groundBody = groundObject as RigidBody3D;
|
||||||
_groundPoint = point;
|
_groundPoint = point;
|
||||||
@@ -1099,7 +1111,9 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
// reported via underHips for the pressure rule.
|
// reported via underHips for the pressure rule.
|
||||||
public const float FootprintRadius = 0.12f;
|
public const float FootprintRadius = 0.12f;
|
||||||
|
|
||||||
private bool ProbeGround(Vector3 pos, float length, bool wasGrounded, out Vector3 point, out Vector3 normal, out GodotObject collider, out bool underHips)
|
// mode: 0 airborne, 1 standing, 2 clambering (pressing onto something: the footprint may take
|
||||||
|
// higher ground the hips are only partly over — the capsule hips then ride up onto its edge).
|
||||||
|
private bool ProbeGround(Vector3 pos, float length, int mode, out Vector3 point, out Vector3 normal, out GodotObject collider, out bool underHips)
|
||||||
{
|
{
|
||||||
PhysicsDirectSpaceState3D space = GetWorld3D().DirectSpaceState;
|
PhysicsDirectSpaceState3D space = GetWorld3D().DirectSpaceState;
|
||||||
point = normal = Vector3.Zero;
|
point = normal = Vector3.Zero;
|
||||||
@@ -1135,7 +1149,7 @@ public partial class PlayerRagdoll : Node3D
|
|||||||
return haveRay;
|
return haveRay;
|
||||||
// The sphere wins where the centre ray found nothing, nothing walkable, or only lower ground
|
// The sphere wins where the centre ray found nothing, nothing walkable, or only lower ground
|
||||||
// within the stance (a plank's edge under the hips) — but never higher ground than a step.
|
// within the stance (a plank's edge under the hips) — but never higher ground than a step.
|
||||||
bool sameLevel = sPoint.Y <= _footY + MaxStepHeight || !wasGrounded;
|
bool sameLevel = sPoint.Y <= _footY + MaxStepHeight || mode != 1;
|
||||||
bool better = !haveRay || normal.Y <= Mathf.Cos(Mathf.DegToRad(MaxSlopeDegrees)) || sPoint.Y > point.Y + 0.01f;
|
bool better = !haveRay || normal.Y <= Mathf.Cos(Mathf.DegToRad(MaxSlopeDegrees)) || sPoint.Y > point.Y + 0.01f;
|
||||||
if (sameLevel && better)
|
if (sameLevel && better)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1142,6 +1142,15 @@ public partial class RagdollSpike : Node3D
|
|||||||
}
|
}
|
||||||
if (t > 1f)
|
if (t > 1f)
|
||||||
_climbEyeErr = Mathf.Max(_climbEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
_climbEyeErr = Mathf.Max(_climbEyeErr, _ragdoll.Eye.DistanceTo(Camera.Origin));
|
||||||
|
// The free hand reaching for the next hold while hanging on the other (tipped body): it
|
||||||
|
// should stay on its controller (owner: "tracking off when the body is tilted").
|
||||||
|
for (int side = 0; side < 2; side++)
|
||||||
|
if (_climbWeld[side] == null && _climbStep is 2 or 3 && _ragdoll.Hand(side).GetContactCount() == 0)
|
||||||
|
{
|
||||||
|
Vector3 ctrl = (side == 0 ? Left : Right).Origin;
|
||||||
|
_climbFreeErr += _ragdoll.Hand(side).GlobalPosition.DistanceTo(ctrl);
|
||||||
|
_climbFreeN++;
|
||||||
|
}
|
||||||
|
|
||||||
void Next(int step)
|
void Next(int step)
|
||||||
{
|
{
|
||||||
@@ -1158,9 +1167,15 @@ public partial class RagdollSpike : Node3D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float _climbFreeErr;
|
||||||
|
private int _climbFreeN;
|
||||||
|
|
||||||
private bool FinishClimbWall()
|
private bool FinishClimbWall()
|
||||||
{
|
{
|
||||||
float rise = _ragdoll.Pelvis.GlobalPosition.Y - _climbStartPelvis;
|
float rise = _ragdoll.Pelvis.GlobalPosition.Y - _climbStartPelvis;
|
||||||
|
GD.Print($"RAGDOLL: wall climb: free hand vs its controller while reaching {(_climbFreeN > 0 ? _climbFreeErr / _climbFreeN : 0f):F3} m mean");
|
||||||
|
_climbFreeErr = 0f;
|
||||||
|
_climbFreeN = 0;
|
||||||
GD.Print($"RAGDOLL: wall climb: reached step {_climbStep}/5, pelvis rose {rise:F3} m (> 0.35), holding {_climbWeld[0] != null}/{_climbWeld[1] != null}, eye vs camera max {_climbEyeErr:F3} (< {PlayerRagdoll.MaxViewLead + 0.03f:F2}), snaps {NewSnaps}");
|
GD.Print($"RAGDOLL: wall climb: reached step {_climbStep}/5, pelvis rose {rise:F3} m (> 0.35), holding {_climbWeld[0] != null}/{_climbWeld[1] != null}, eye vs camera max {_climbEyeErr:F3} (< {PlayerRagdoll.MaxViewLead + 0.03f:F2}), snaps {NewSnaps}");
|
||||||
Grip(0, false);
|
Grip(0, false);
|
||||||
Grip(1, false);
|
Grip(1, false);
|
||||||
|
|||||||
Reference in new issue
Block a user