Give cross-body bars a true 3D axis and fix the goblet squat hold

The orbit was always a real camera orbit — figure and props share one
rigid rotation — but a bar's screen-space angle authored the wrong 3D
rod: the default horizontal encoded a rod along the body axis, so
barbells hovered fixed on screen and vanished at the head-on view
where they should span widest. Line props now take "axis": "z" (both
renderers in lockstep, fixture-pinned): the world left-right direction
projects through the camera pitch like the floor quad — end-on plates
in profile, full span face-on, swinging with the hands in between.
Applied to the ten cross-body bars; vertical handles were already
orbit-invariant.

Goblet Squat's hand pins sat so close to the shoulders that the
two-bone IK was degenerate, flipping between a chicken-wing and an
elbow-behind solve; re-pinned level with the shoulders so the elbows
tuck straight down through the whole rep.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-08 12:49:35 -04:00
parent 60927b5d1f
commit fd2deaa9c7
79 changed files with 190 additions and 143 deletions
+2 -2
View File
@@ -131,7 +131,7 @@ struct ExerciseMotionTests {
bundle.url(forResource: "figure-fixtures", withExtension: "json"),
"figure-fixtures.json must be bundled as a WorkoutsTests resource (see project.yml)")
let fixtures = try JSONDecoder().decode(FigureFixtures.self, from: Data(contentsOf: url))
#expect(fixtures.exercises.count == 47)
#expect(fixtures.exercises.count == 64)
for exercise in fixtures.exercises {
let resources = try #require(ExerciseMotionLibrary.resources(for: exercise.name),
@@ -164,7 +164,7 @@ struct ExerciseMotionTests {
let rotation = MotionSolver.propRotation(pitch: fixtures.pitch, yawOffset: orbit.yaw - cam)
let (bg, fg) = MotionSolver.resolveProps(
resources.motion.props ?? [], geo: geo, authored: authored,
anchor: norms[0].rootPos, rotation: rotation)
anchor: norms[0].rootPos, rotation: rotation, pitch: fixtures.pitch)
expectPrims(bg, propFixture.bg, "\(exercise.name) orbit \(orbit.yaw) bg")
expectPrims(fg, propFixture.fg, "\(exercise.name) orbit \(orbit.yaw) fg")
}
File diff suppressed because one or more lines are too long