Draw both arms on the supine motions and retire the hide list
Hollow Body Hold, Leg Raises, and Reverse Crunch hid the far arm because it was never authored - a leftover from the planar rig. Both arms are now posed, so the far arm reads as the standard light member behind the near one, the same visual language every other exercise uses, and the figures stay truthful from any viewpoint. With no remaining users, the hide mechanism is deleted from both renderers, the motion schema, and the docs. Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
@@ -1,6 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||
<title>Hollow Body Hold</title>
|
||||
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path id="arm_l" d="M 105.9 148.1 L 75.9 148.1 L 45.9 148.1" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_l" d="M 190.9 148.8 L 236.9 148.8 L 276.9 148.8 L 276.9 137.9" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="girdle" d="M 105.9 148.1 L 100.0 148.0 L 100.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,6 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||
<title>Hollow Body Hold</title>
|
||||
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path id="arm_l" d="M 108.7 129.5 L 81.5 117.0 L 54.3 104.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -2,7 +2,6 @@
|
||||
"name": "Hollow Body Hold",
|
||||
"primary": 2,
|
||||
"working": ["spine"],
|
||||
"hide": ["arm_l"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.4, "tween": 0.8,
|
||||
@@ -10,6 +9,7 @@
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": 180, "elbow_r": 0,
|
||||
"shoulder_l": 180, "elbow_l": 0,
|
||||
"hip_r": 0, "knee_r": 0,
|
||||
"hip_l": 0, "knee_l": 0
|
||||
},
|
||||
@@ -19,6 +19,7 @@
|
||||
"spine": [0, 14],
|
||||
"neck": 20,
|
||||
"shoulder_r": 175, "elbow_r": 0,
|
||||
"shoulder_l": 175, "elbow_l": 0,
|
||||
"hip_r": 18, "knee_r": 0,
|
||||
"hip_l": 18, "knee_l": 0
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 124 KiB |
@@ -1,6 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180" fill="none">
|
||||
<title>Hollow Body Hold</title>
|
||||
<path d="M 28.0 161.1 L 288.9 161.1 L 288.9 150.7 L 28.0 150.7 Z" fill="none" stroke="#b9bec9" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path id="arm_l" d="M 108.7 129.5 L 81.5 117.0 L 54.3 104.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_l" d="M 190.9 148.8 L 235.9 139.3 L 275.0 131.1 L 272.7 120.5" stroke="#a9afba" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="girdle" d="M 108.7 129.5 L 102.8 129.4 L 102.8 131.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -6,6 +6,7 @@
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -7,6 +7,7 @@
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 185.0 149.2 L 230.6 142.9 L 270.2 137.4 L 268.6 126.7" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
<g font-family="-apple-system, Helvetica, sans-serif" font-size="11" fill="#6b7180">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -2,13 +2,13 @@
|
||||
"name": "Leg Raises",
|
||||
"primary": 1,
|
||||
"working": ["leg_r", "leg_l"],
|
||||
"hide": ["arm_r"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.5, "tween": 1.4,
|
||||
"root": {"pos": [185, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 60, "knee_r": 0,
|
||||
"hip_l": 60, "knee_l": 0
|
||||
@@ -18,6 +18,7 @@
|
||||
"root": {"pos": [185, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 8, "knee_r": 0,
|
||||
"hip_l": 8, "knee_l": 0
|
||||
|
||||
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 111 KiB |
@@ -6,6 +6,7 @@
|
||||
<path id="pelvisBar" d="M 190.9 148.8 L 185.0 148.0 L 185.0 149.2" stroke="#3a3f4b" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 185.0 148.0 Q 141.5 148.0 100.0 148.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_l" d="M 190.9 148.8 L 213.9 109.5 L 233.9 75.4 L 224.4 70.0" stroke="#86cfc5" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 100.0 149.9 L 130.0 150.9 L 160.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 185.0 149.2 L 208.0 110.0 L 228.0 75.9 L 218.5 70.5" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="80.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="80.4" y1="133.9" x2="80.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -6,6 +6,7 @@
|
||||
<path id="girdle" d="M 92.9 148.1 L 87.0 148.0 L 87.0 149.9" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 177.9 148.8 L 172.0 148.0 L 172.0 149.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 172.0 148.0 Q 128.5 148.0 87.0 148.0" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 87.0 149.9 L 117.0 150.9 L 147.0 152.0" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 172.0 149.2 L 164.0 104.6 L 201.6 118.1 L 205.4 107.9" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="67.4" cy="143.9" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="67.4" y1="133.9" x2="67.4" y2="127.0" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -6,6 +6,7 @@
|
||||
<path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 82.6 145.3 L 112.6 146.3 L 142.6 147.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -2,13 +2,13 @@
|
||||
"name": "Reverse Crunch",
|
||||
"primary": 2,
|
||||
"working": ["spine"],
|
||||
"hide": ["arm_r"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.5, "tween": 0.7,
|
||||
"root": {"pos": [172, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 100, "knee_r": 120,
|
||||
"hip_l": 100, "knee_l": 120
|
||||
@@ -18,6 +18,7 @@
|
||||
"root": {"pos": [166, 136], "pitch": -105},
|
||||
"spine": [0, 20],
|
||||
"neck": -7, "head": 2,
|
||||
"shoulder_r": 3, "elbow_r": 0,
|
||||
"shoulder_l": 3, "elbow_l": 0,
|
||||
"hip_r": 100, "knee_r": 125,
|
||||
"hip_l": 100, "knee_l": 125
|
||||
|
||||
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 128 KiB |
@@ -6,6 +6,7 @@
|
||||
<path id="girdle" d="M 88.5 143.4 L 82.6 143.4 L 82.6 145.3" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="pelvisBar" d="M 171.9 136.8 L 166.0 136.0 L 166.0 137.2" stroke="#0d9488" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="spine" d="M 166.0 136.0 Q 124.6 154.2 82.6 143.4" stroke="#0d9488" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="arm_r" d="M 82.6 145.3 L 112.6 146.3 L 142.6 147.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path id="leg_r" d="M 166.0 137.2 L 146.6 96.2 L 186.0 103.0 L 187.9 92.3" stroke="#3a3f4b" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle id="head" cx="62.6" cy="144.0" r="10" fill="white" stroke="#3a3f4b" stroke-width="6"/>
|
||||
<line id="nose" x1="62.6" y1="134.0" x2="62.6" y2="127.1" stroke="#3a3f4b" stroke-width="4" stroke-linecap="round"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -45,7 +45,6 @@ never touches a motion script** — proportions are the skeleton's problem.
|
||||
"primary": 2, // 1-based frame used for visual.svg
|
||||
"camera": {"yaw": 0}, // 0 = side view (default), 90 = face-on
|
||||
"working": ["arm_l", "leg_r"], // parts drawn in the accent color
|
||||
"hide": [], // limbs occluded by convention, not drawn
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.5, "tween": 0.8, // seconds held / animating to the next frame
|
||||
@@ -115,8 +114,9 @@ never touches a motion script** — proportions are the skeleton's problem.
|
||||
shoulder girdle and pelvis drawn as bars across the attach points (near-full
|
||||
width face-on, a shoulder/hip nub in profile); teal when the trunk is the
|
||||
working part.
|
||||
- Canvas 320×180, ground line at y = 152. Limbs listed in `hide` are
|
||||
occluded by convention in this view and not drawn.
|
||||
- Canvas 320×180, ground line at y = 152. Every limb is always drawn — a
|
||||
supine figure's far arm reads as the light far member, never a hidden one,
|
||||
so any viewpoint (and the orbit) stays truthful.
|
||||
|
||||
## The props layer
|
||||
|
||||
@@ -172,8 +172,7 @@ authored yaw nothing moves, so the authored look is exact.
|
||||
and `pad` default to perpendicular to the lower bone (forearm/shin), or
|
||||
take an explicit `angle`. Under orbit the segment rotates with the scene
|
||||
and foreshortens naturally. `plateR` puts filled discs on both ends
|
||||
(dumbbells default to 4.5). A prop whose limb is hidden that frame simply
|
||||
isn't drawn.
|
||||
(dumbbells default to 4.5).
|
||||
- **`roller`** — a machine roller pad seen end-on: a filled disc riding the
|
||||
limb's lower bone near the joint in `at`, on the `side` (+1/−1) of the bone
|
||||
it presses — a leg extension's instep roller (`side: 1`), a leg curl's
|
||||
|
||||
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 303 KiB |
@@ -602,23 +602,18 @@ def prepare(motion, figure="neutral", flip=False, strict=False):
|
||||
def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
motion = load_motion(folder)
|
||||
working = set(motion.get("working", []))
|
||||
hide = set(motion.get("hide", []))
|
||||
norms, prof, cam, pitch, props = prepare(motion, figure, flip, strict)
|
||||
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
|
||||
def geometry(nf):
|
||||
_, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
return geo, order, shade
|
||||
|
||||
resolved = []
|
||||
key_cells = []
|
||||
for nf in norms:
|
||||
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
resolved.append(out)
|
||||
key_cells.append((geo, order, shade, resolve_props(props, geo, nf["root"]["pos"])))
|
||||
|
||||
@@ -653,7 +648,6 @@ def render_orbit(folder, figure="neutral"):
|
||||
motion authors one). Props rotate with the figure about the root anchor."""
|
||||
motion = load_motion(folder)
|
||||
working = set(motion.get("working", []))
|
||||
hide = set(motion.get("hide", []))
|
||||
norms, prof, cam, pitch, props = prepare(motion, figure)
|
||||
resolved = [frame_geometry(nf, prof, cam, pitch=pitch)[0] for nf in norms]
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
@@ -671,8 +665,6 @@ def render_orbit(folder, figure="neutral"):
|
||||
posed, auth_geo, _, _ = frame_geometry(nf, prof, cam, pitch=pitch)
|
||||
posed["pins"] = {}
|
||||
_, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"],
|
||||
prop_rotation(pitch, off), auth_geo)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
|
||||
@@ -686,13 +678,11 @@ def contact_sheet(folders, figure="neutral", out=None):
|
||||
cells = []
|
||||
for folder in folders:
|
||||
motion = load_motion(folder)
|
||||
working, hide = set(motion.get("working", [])), set(motion.get("hide", []))
|
||||
working = set(motion.get("working", []))
|
||||
norms, prof, cam, pitch, props = prepare(motion, figure)
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
for i, nf in enumerate(norms, start=1):
|
||||
_, geo, order, shade = frame_geometry(nf, prof, cam, pitch=pitch, mat=mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
cells.append((f"{motion['name']} {i}/{len(norms)}",
|
||||
draw_geo(geo, order, shade, working, PALETTES["default"],
|
||||
@@ -705,7 +695,7 @@ def demo_sheet(folder):
|
||||
opens: neutral / female / male profiles (same motion script, different
|
||||
proportions), the flipped camera, an alternate theme."""
|
||||
motion = load_motion(folder)
|
||||
working, hide = set(motion.get("working", [])), set(motion.get("hide", []))
|
||||
working = set(motion.get("working", []))
|
||||
idx = motion.get("primary", 1) - 1
|
||||
font = legend_font()
|
||||
variants = [("neutral", "neutral", False, "default"),
|
||||
@@ -718,8 +708,6 @@ def demo_sheet(folder):
|
||||
norms, prof, cam, pitch, props = prepare(motion, figure, flip)
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
_, geo, order, shade = frame_geometry(norms[idx], prof, cam, flip, pitch, mat)
|
||||
for limb in hide:
|
||||
geo.pop(limb, None)
|
||||
prims = resolve_props(props, geo, norms[idx]["root"]["pos"])
|
||||
cells.append((f"{motion['name']} — {label}",
|
||||
draw_geo(geo, order, shade, working, PALETTES[palette],
|
||||
|
||||
@@ -22,8 +22,6 @@ struct FigureAnimation {
|
||||
let timeline: MotionTimeline
|
||||
/// Parts drawn in the working accent color (`arm_r`, `leg_l`, `spine`, …).
|
||||
let working: Set<String>
|
||||
/// Limbs fully occluded in this view — never drawn.
|
||||
let hide: Set<String>
|
||||
/// Equipment layer (see SYSTEM.md "The props layer").
|
||||
let props: [MotionProp]
|
||||
|
||||
@@ -34,28 +32,20 @@ struct FigureAnimation {
|
||||
else { return nil }
|
||||
self.timeline = timeline
|
||||
self.working = Set(resources.motion.working ?? [])
|
||||
self.hide = Set(resources.motion.hide ?? [])
|
||||
self.props = resources.motion.props ?? []
|
||||
}
|
||||
|
||||
/// Seconds per full camera revolution for orbiting motions.
|
||||
private static let orbitPeriod: Double = 24
|
||||
|
||||
/// Prop-free, hide-free motions slowly orbit the camera while animating — the
|
||||
/// bodyweight moves. Scene equipment is a view-locked billboard and a `hide` list
|
||||
/// describes one authored viewpoint, so machines keep their fixed camera.
|
||||
var orbits: Bool { props.isEmpty && hide.isEmpty }
|
||||
/// Prop-free motions slowly orbit the camera while animating — the bodyweight
|
||||
/// moves. Machines keep their fixed camera until their equipment rotates too.
|
||||
var orbits: Bool { props.isEmpty }
|
||||
|
||||
/// Drawable geometry at `time` — the figure plus the resolved equipment layers —
|
||||
/// with limbs listed in `hide` dropped so neither they nor any prop riding them
|
||||
/// are drawn.
|
||||
/// Drawable geometry at `time` — the figure plus the resolved equipment layers.
|
||||
func geometry(at time: Double) -> FigureGeometry {
|
||||
let yaw = orbits ? 360 * (time / Self.orbitPeriod) : 0
|
||||
var geo = timeline.geometry(at: time, yawOffset: yaw, props: props)
|
||||
for name in hide {
|
||||
if let limb = FigureLimb(rawValue: name) { geo.limbs[limb] = nil }
|
||||
}
|
||||
return geo
|
||||
return timeline.geometry(at: time, yawOffset: yaw, props: props)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,8 +235,6 @@ struct ExerciseMotion: Codable {
|
||||
let camera: MotionCamera?
|
||||
/// Parts (`arm_r`, `leg_l`, `spine`, …) drawn in the working accent color.
|
||||
let working: [String]?
|
||||
/// Limbs fully occluded in this view — never drawn.
|
||||
let hide: [String]?
|
||||
/// Equipment layer: scene shapes and cables behind the figure, joint-attached
|
||||
/// items (bar/dumbbell/pad) over the limbs. See SYSTEM.md "The props layer".
|
||||
let props: [MotionProp]?
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"name": "Hollow Body Hold",
|
||||
"primary": 2,
|
||||
"working": ["spine"],
|
||||
"hide": ["arm_l"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.4, "tween": 0.8,
|
||||
@@ -10,6 +9,7 @@
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": 180, "elbow_r": 0,
|
||||
"shoulder_l": 180, "elbow_l": 0,
|
||||
"hip_r": 0, "knee_r": 0,
|
||||
"hip_l": 0, "knee_l": 0
|
||||
},
|
||||
@@ -19,6 +19,7 @@
|
||||
"spine": [0, 14],
|
||||
"neck": 20,
|
||||
"shoulder_r": 175, "elbow_r": 0,
|
||||
"shoulder_l": 175, "elbow_l": 0,
|
||||
"hip_r": 18, "knee_r": 0,
|
||||
"hip_l": 18, "knee_l": 0
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"name": "Leg Raises",
|
||||
"primary": 1,
|
||||
"working": ["leg_r", "leg_l"],
|
||||
"hide": ["arm_r"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.5, "tween": 1.4,
|
||||
"root": {"pos": [185, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 60, "knee_r": 0,
|
||||
"hip_l": 60, "knee_l": 0
|
||||
@@ -18,6 +18,7 @@
|
||||
"root": {"pos": [185, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 8, "knee_r": 0,
|
||||
"hip_l": 8, "knee_l": 0
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"name": "Reverse Crunch",
|
||||
"primary": 2,
|
||||
"working": ["spine"],
|
||||
"hide": ["arm_r"],
|
||||
"frames": [
|
||||
{
|
||||
"hold": 0.5, "tween": 0.7,
|
||||
"root": {"pos": [172, 148], "pitch": -90},
|
||||
"spine": [0, 0],
|
||||
"neck": 12, "head": -12,
|
||||
"shoulder_r": -2, "elbow_r": 0,
|
||||
"shoulder_l": -2, "elbow_l": 0,
|
||||
"hip_r": 100, "knee_r": 120,
|
||||
"hip_l": 100, "knee_l": 120
|
||||
@@ -18,6 +18,7 @@
|
||||
"root": {"pos": [166, 136], "pitch": -105},
|
||||
"spine": [0, 20],
|
||||
"neck": -7, "head": 2,
|
||||
"shoulder_r": 3, "elbow_r": 0,
|
||||
"shoulder_l": 3, "elbow_l": 0,
|
||||
"hip_r": 100, "knee_r": 125,
|
||||
"hip_l": 100, "knee_l": 125
|
||||
|
||||
@@ -80,8 +80,8 @@ 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 (view-locked scene shapes) or a hide list keep their authored view.
|
||||
/// 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)
|
||||
|
||||