Add presentation-only camera.zoom so standing motions fit the canvas

The rig is ~211 canvas units tall standing but the canvas has 152 above
the ground line - every motion to date is seated or on the floor. A
per-motion "camera": {"zoom": ...} now scales the drawn output (figure,
props, mat, stroke widths) about the ground-center anchor in both the
reference renderer and the in-app view. Pure view transform: pins, prop
coordinates, and the Swift-solver fixtures stay in full-size authored
units; zoom 1 is byte-identical to before.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
2026-07-07 11:43:00 -04:00
parent eadaa075c4
commit e4bedc5bda
4 changed files with 101 additions and 22 deletions
@@ -24,6 +24,9 @@ struct FigureAnimation {
let working: Set<String>
/// Equipment layer (see SYSTEM.md "The props layer").
let props: [MotionProp]
/// Presentation zoom about the ground-center anchor standing motions author
/// full-size anatomy taller than the canvas and zoom out to fit (see SYSTEM.md).
let zoom: Double
init?(exerciseName: String) {
guard
@@ -33,6 +36,7 @@ struct FigureAnimation {
self.timeline = timeline
self.working = Set(resources.motion.working ?? [])
self.props = resources.motion.props ?? []
self.zoom = resources.motion.camera?.zoom ?? 1
}
/// Seconds per full camera revolution.
@@ -96,6 +100,14 @@ struct ExerciseFigureView: View {
y: (size.height - Self.designSize.height * scale) / 2)
ctx.scaleBy(x: scale, y: scale)
// Presentation zoom about the ground-center anchor, mirroring the reference
// renderer's `apply_zoom` (stroke widths scale with the geometry).
if figure.zoom != 1 {
ctx.translateBy(x: Self.designSize.width / 2, y: Self.groundY)
ctx.scaleBy(x: figure.zoom, y: figure.zoom)
ctx.translateBy(x: -Self.designSize.width / 2, y: -Self.groundY)
}
let geo = figure.geometry(at: time)
// The exercise mat: a ground-plane quad sized to the motion's footprint,
@@ -106,6 +106,10 @@ struct MotionCamera: Codable {
let yaw: Double?
/// Camera elevation override; nil uses the standard slightly-raised viewpoint.
let pitch: Double?
/// Presentation-only zoom about the ground-center anchor (nil = 1). Standing
/// motions author full-size anatomy taller than the canvas and zoom out
/// to fit; the solver and fixtures stay in full-size canvas units.
let zoom: Double?
}
/// A prop's joint reference: one joint (`"hand_r"`, `"knee_l"`, `"elbow_r"`, )