Draw the ground as an exercise mat that rotates with the figure

The floor rectangle was screen-locked, which broke the illusion the
moment the camera orbited. It is now a world-space quad on the ground
plane, sized to each motion's projected footprint across its key frames
and rotated through the same camera as the figure - a long rectangle in
profile, a parallelogram mid-orbit, end-on when face-on. Both renderers
in lockstep; fixtures unaffected (the mat is a pure addition).

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
2026-07-06 21:31:36 -04:00
parent b82054b81a
commit 6d9eff1a71
98 changed files with 177 additions and 107 deletions
@@ -109,13 +109,16 @@ struct ExerciseFigureView: View {
let geo = figure.geometry(at: time)
// The floor plane under the elevated camera: a rectangle straddling the
// ground line (a horizontal line is the pitch-zero degenerate case).
let floorHalf = MotionSolver.floorHalfDepth * sin(figure.timeline.pitch * .pi / 180)
let floor = CGRect(x: 16, y: Self.groundY + 4 - floorHalf,
width: Self.designSize.width - 32, height: 2 * floorHalf)
ctx.stroke(Path(roundedRect: floor, cornerRadius: 3), with: .color(.figureGround),
style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
// The exercise mat: a ground-plane quad sized to the motion's footprint,
// rotating with the camera about the figure.
if let floorQuad = geo.floor, let first = floorQuad.first {
var path = Path()
path.addLines(floorQuad)
path.addLine(to: first)
path.closeSubpath()
ctx.stroke(path, with: .color(.figureGround),
style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
}
// Equipment behind the figure: scene shapes and cables.
drawBackgroundProps(&ctx, geo)