Slowly orbit the camera around every form guide

With the equipment layer carrying world-space 3D form, the prop-free
gate comes off: machines now get the same slow orbit as the bodyweight
moves, their seats, cables, bars, and rollers turning with the figure.
Closes out the orbit-for-all-exercises plan.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-06 22:22:31 -04:00
parent b8c3b326c1
commit 20b8e6d82e
6 changed files with 31 additions and 111 deletions
+18 -11
View File
@@ -80,23 +80,30 @@ struct ExerciseMotionTests {
#expect(hypot(releasedHand.x - releasedHand0.x, releasedHand.y - releasedHand0.y) > 1)
}
/// Prop-free bodyweight motions slowly orbit the camera while looping; motions
/// with equipment keep their authored view (until machines orbit too).
@Test func orbitAppliesOnlyToPropFreeMotions() throws {
let birdDog = try #require(FigureAnimation(exerciseName: "Bird Dog"))
#expect(birdDog.orbits)
/// Every motion slowly orbits the camera while looping bodyweight moves and
/// machines alike, since the equipment layer rotates with the figure.
@Test func everyMotionOrbits() throws {
// Same loop phase seconds apart: the orbit yaw differs, so the projected head
// moves even though the pose is identical.
let duration = birdDog.timeline.duration
let birdDog = try #require(FigureAnimation(exerciseName: "Bird Dog"))
let head0 = birdDog.geometry(at: 0).headCenter
let head1 = birdDog.geometry(at: duration * 2).headCenter
let head1 = birdDog.geometry(at: birdDog.timeline.duration * 2).headCenter
#expect(hypot(head0.x - head1.x, head0.y - head1.y) > 1)
// A machine orbits too, and its equipment turns with the figure: the seat
// slab's projected points move between the same two loop phases.
let legPress = try #require(FigureAnimation(exerciseName: "Leg Press"))
#expect(!legPress.orbits)
let fixed0 = legPress.geometry(at: 0).headCenter
let fixed1 = legPress.geometry(at: legPress.timeline.duration * 2).headCenter
#expect(abs(fixed0.x - fixed1.x) < 1e-9 && abs(fixed0.y - fixed1.y) < 1e-9)
let geo0 = legPress.geometry(at: 0)
let geo1 = legPress.geometry(at: legPress.timeline.duration * 2)
#expect(hypot(geo0.headCenter.x - geo1.headCenter.x,
geo0.headCenter.y - geo1.headCenter.y) > 1)
guard case .poly(let seat0, _, _) = geo0.propsBackground.first,
case .poly(let seat1, _, _) = geo1.propsBackground.first,
let s0 = seat0.first, let s1 = seat1.first else {
Issue.record("expected the Leg Press backrest slab as the first background prop")
return
}
#expect(hypot(s0.x - s1.x, s0.y - s1.y) > 1)
}
/// Every exported motion in the bundle decodes and builds a playable timeline.