Files
VR-Mess/scripts/PropShop.cs
T
FoxyzandClaude Opus 5.5 7589b13aba Body physics Stage 2: physical arm segments (chase-driven upper arm + forearm, hierarchical budgets, layer 5)
- PhysicsArms: per side 2.0 kg upper arm + 1.2 kg forearm RigidBody3D PD-chasing their
  analytic 2-bone IK pose, solved per physics tick from the physical chest shoulders to the
  physical hands; no joints (Box3D has no usable shoulder/wrist joint; AGENTS forbids joints
  between tracked bodies). Budgets from a 60 kg mass stat: hand 90 < forearm 180 < upper
  arm 360 < chest 2400 N. Owns calibration + reach budget (moved from BodyRig); 1 m snap,
  non-finite reset, BodyMath.RotationError servo.
- Layer 5 = player arms (mask 1); props/table/floor masks include it; no self-collision.
- BodyRig: visuals only, arms drawn shoulder -> physical elbow -> wrist.
- ArmSpike: track gap 0, swing lag <= 6 cm, arm pushes prop with hand clear, forearm blocked
  by bar without tunneling and quiet when pinned, teleport snap, NaN reset — PASS x3.
- CollapseSpike now runs the full body (49/49 x3); all six spikes PASS x3.
- HANDOFF/AGENTS: Stage 2 entry (item 10), layers, tuning table, Stage 2b next steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:04:07 +03:00

144 lines
5.3 KiB
C#

using Godot;
public partial class PropShop : Node3D
{
private struct PropSpec
{
public string Name;
public float Radius;
public Vector3 Box;
public float Mass;
public float Bounce;
public float Friction;
public Color Color;
public Vector3 Position;
}
private static readonly PropSpec[] Specs =
{
new() { Name = "Ping pong · 2.7 g", Radius = 0.02f, Mass = 0.0027f, Bounce = 0.9f, Friction = 0.3f, Color = new(1f, 1f, 1f), Position = new(-0.5f, 0.78f, -0.55f) },
new() { Name = "Bouncy ball · 50 g", Radius = 0.03f, Mass = 0.05f, Bounce = 0.85f, Friction = 0.4f, Color = new(1f, 0.4f, 0.7f), Position = new(-0.3f, 0.79f, -0.45f) },
new() { Name = "Apple · 150 g", Radius = 0.045f, Mass = 0.15f, Bounce = 0.3f, Friction = 0.6f, Color = new(0.85f, 0.15f, 0.1f), Position = new(-0.08f, 0.81f, -0.55f) },
new() { Name = "Crate · 2 kg", Box = new(0.16f, 0.16f, 0.16f), Mass = 2f, Bounce = 0.1f, Friction = 0.8f, Color = new(0.7f, 0.5f, 0.25f), Position = new(0.18f, 0.84f, -0.5f) },
new() { Name = "Brick · 3.5 kg", Box = new(0.2f, 0.06f, 0.095f), Mass = 3.5f, Bounce = 0.05f, Friction = 0.9f, Color = new(0.6f, 0.3f, 0.2f), Position = new(-0.32f, 0.79f, -0.25f) },
new() { Name = "Bowling ball · 7.2 kg", Radius = 0.11f, Mass = 7.2f, Bounce = 0.1f, Friction = 0.5f, Color = new(0.1f, 0.2f, 0.5f), Position = new(0.05f, 0.87f, -0.25f) },
new() { Name = "Anvil · 50 kg", Box = new(0.28f, 0.16f, 0.13f), Mass = 50f, Bounce = 0f, Friction = 0.95f, Color = new(0.25f, 0.25f, 0.28f), Position = new(0.38f, 0.84f, -0.35f) },
};
public override void _Ready()
{
BuildTable();
foreach (PropSpec spec in Specs)
SpawnProp(spec);
}
public void Respawn()
{
foreach (Node node in GetTree().GetNodesInGroup("prop"))
node.QueueFree();
foreach (PropSpec spec in Specs)
SpawnProp(spec);
GD.Print("PropShop: table reset");
}
private void BuildTable()
{
Color wood = new(0.45f, 0.3f, 0.18f);
StaticBody3D table = new();
table.CollisionMask = 0b11101; // world + player body + hands + arms
table.AddChild(new CollisionShape3D
{
Position = new Vector3(0, 0.725f, -0.6f),
Shape = new BoxShape3D { Size = new Vector3(1.4f, 0.05f, 0.8f) },
});
MeshInstance3D top = new()
{
Position = new Vector3(0, 0.725f, -0.6f),
Mesh = new BoxMesh { Size = new Vector3(1.4f, 0.05f, 0.8f) },
};
top.SetSurfaceOverrideMaterial(0, MakeMaterial(wood));
table.AddChild(top);
Vector3[] legOffsets =
{
new(-0.62f, 0, 0.32f),
new(0.62f, 0, 0.32f),
new(-0.62f, 0, -0.32f),
new(0.62f, 0, -0.32f),
};
foreach (Vector3 offset in legOffsets)
{
Vector3 pos = new(offset.X, 0.3625f, -0.6f + offset.Z);
table.AddChild(new CollisionShape3D
{
Position = pos,
Shape = new BoxShape3D { Size = new Vector3(0.06f, 0.725f, 0.06f) },
});
MeshInstance3D leg = new()
{
Position = pos,
Mesh = new BoxMesh { Size = new Vector3(0.06f, 0.725f, 0.06f) },
};
leg.SetSurfaceOverrideMaterial(0, MakeMaterial(wood * 0.8f));
table.AddChild(leg);
}
AddChild(table);
}
private void SpawnProp(PropSpec spec)
{
RigidBody3D body = new()
{
Name = spec.Name,
Mass = spec.Mass,
Position = spec.Position,
PhysicsMaterialOverride = new PhysicsMaterial { Bounce = spec.Bounce, Friction = spec.Friction },
ContinuousCd = spec.Mass < 0.1f,
ContactMonitor = true,
MaxContactsReported = 4,
CollisionMask = 0b11101, // world + player body + hands + arms
};
body.AddToGroup("prop");
if (spec.Radius > 0f)
{
body.AddChild(new CollisionShape3D { Shape = new SphereShape3D { Radius = spec.Radius } });
MeshInstance3D mesh = new()
{
Mesh = new SphereMesh { Radius = spec.Radius, Height = spec.Radius * 2f },
};
mesh.SetSurfaceOverrideMaterial(0, MakeMaterial(spec.Color));
body.AddChild(mesh);
}
else
{
body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = spec.Box } });
MeshInstance3D mesh = new()
{
Mesh = new BoxMesh { Size = spec.Box },
};
mesh.SetSurfaceOverrideMaterial(0, MakeMaterial(spec.Color));
body.AddChild(mesh);
}
float halfHeight = spec.Radius > 0f ? spec.Radius : spec.Box.Y * 0.5f;
body.AddChild(new Label3D
{
Text = spec.Name,
FontSize = 64,
PixelSize = 0.0004f,
OutlineSize = 8,
Billboard = BaseMaterial3D.BillboardModeEnum.Enabled,
Position = new Vector3(0, halfHeight + 0.1f, 0),
});
AddChild(body);
}
private static StandardMaterial3D MakeMaterial(Color color) => new()
{
AlbedoColor = color,
Roughness = 0.7f,
};
}