# Exercise Visual System Exercise visuals are produced by an **articulated 2D rig**: one shared stick body posed per exercise by joint angles. Nothing is drawn by hand — a body profile plus a motion script resolve through forward kinematics into every frame, so figures are always in proportion, and the whole library can be re-proportioned (male/female), flipped, rotated in-plane, or re-themed by changing data, never artwork. ## The rig - **`body.json`** — proportion profiles. Each profile is a table of bone lengths: `headR`, `neck`, `spine1`/`spine2` (two chained segments so the spine can curve), `upperArm`/`foreArm`, `thigh`/`shin`, plus `leftOffset` (the small offset that separates left-limb attachments visually). `neutral` is the default; add profiles to add figures. - **`/motion.json`** — the exercise script: key frames of **absolute joint angles** (degrees, y-up: `0`=forward/right, `90`=up, `180`=back/left, `-90`=down), a `root` pelvis position, and timing. ```json { "name": "Bird Dog", "primary": 2, // 1-based frame used for visual.svg "working": ["arm_r", "leg_l"], // parts drawn in the accent color "hide": [], // limbs fully occluded in this view "frames": [ { "hold": 0.5, // seconds held at this key frame "tween": 0.8, // seconds animating to the NEXT frame "root": [190, 106], // pelvis, canvas coords "spine": [171, 171], // pelvis→mid, mid→neck angles "neck": 187, "gaze": 205, // head direction; nose tick direction "arm_r": [-90, -90], // upper-arm, forearm angles "arm_l": [-90, -90], "leg_r": [-83, 0], // thigh, shin angles "leg_l": [-83, 0], "pins": {"hand_r": [105, 152], "hand_l": [111, 154]} } ] } ``` - **Pins (IK)** — a planted hand/foot names a target point (`hand_r`/`hand_l`/`foot_r`/`foot_l`); the renderer solves the two-bone chain analytically so the extremity holds that point exactly, using the authored angles only to pick the elbow/knee bend direction. A pin active in two consecutive key frames stays planted *throughout the tween* (plank forearms, side-plank support arm); a pin present in only one frame releases naturally (bird-dog arm lifting off). - **Tweening** happens in angle space (shortest path), so limbs swing in natural arcs and bone lengths never distort. The last frame tweens back to the first (looping). Asymmetric timing carries technique: leg raises lower slowly (`tween` 1.4 s down, 0.6 s up). - The same math is the plan for the app: a small SwiftUI renderer consumes `body.json` + `motion.json` and tweens angles on the lower half of the exercise screen (the paged timer flow occupies only the top half). ## The visual language - **Right vs left limb** — the one rule that never bends: the figure's **right**-side limbs are dark (`#3a3f4b`), its **left**-side limbs are light (`#a9afba`) and drawn *behind* the body. Working limbs keep the split: right = teal `#0d9488`, left = light teal `#86cfc5`. Every render embeds a small `R —` / `L —` legend, so opposite-limb moves (bird dog, dead bug) are visibly opposite: one dark-teal limb, one light-teal limb. - **Facing / front-of-torso** — the head carries a **nose tick** (`gaze` angle); the belly is on that side. Prone noses point at the floor, supine at the ceiling. The head is drawn last, filled opaque, so overhead arms pass behind the face. - **Spine** — rendered as a smooth curve through pelvis → mid → neck; teal when the trunk is the working part. - Canvas 320×180, ground line at y = 152. Limbs listed in `hide` are fully occluded in this view and not drawn. ## Rendering ```sh cd "Exercise Library" python3 render.py # all exercises: frames/*.svg, preview.gif, visual.svg python3 render.py "Bird Dog" # one exercise python3 render.py --sheet # + contact-sheet.png of every key frame python3 render.py --demo # + demo-sheet.png: profile / flip / theme variants python3 render.py --figure=female # render with another body profile python3 render.py --flip # mirror the figure (faces the other way) python3 render.py --export # copy body.json + .motion.json app resources ``` `render.py` needs only Pillow (for GIFs/sheets; the SVGs have no dependency). The library lives at the repo root, outside every target's source folders — same-named files per entry (`info.md`, `visual.svg`) would collide in Xcode's flat resource copy, so the library itself never enters the app bundle. Only the `--export` copies ship: `body.json` plus uniquely-named `.motion.json` files in `Workouts/Resources/ExerciseMotions/`, consumed by the in-app SwiftUI renderer (`Workouts/ExerciseFigure/`). Re-run `python3 render.py --export` after editing any motion; the library stays the source of truth.