Give cross-body bars a true 3D axis and fix the goblet squat hold
The orbit was always a real camera orbit — figure and props share one rigid rotation — but a bar's screen-space angle authored the wrong 3D rod: the default horizontal encoded a rod along the body axis, so barbells hovered fixed on screen and vanished at the head-on view where they should span widest. Line props now take "axis": "z" (both renderers in lockstep, fixture-pinned): the world left-right direction projects through the camera pitch like the floor quad — end-on plates in profile, full span face-on, swinging with the hands in between. Applied to the ten cross-body bars; vertical handles were already orbit-invariant. Goblet Squat's hand pins sat so close to the shoulders that the two-bone IK was degenerate, flipping between a chicken-wing and an elbow-behind solve; re-pinned level with the shoulders so the elbows tuck straight down through the whole rep. Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
This commit is contained in:
@@ -340,7 +340,7 @@ def joint_points(geo, ref):
|
||||
sum(p[1] for p in pts) / len(pts)), direction)
|
||||
|
||||
|
||||
def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
|
||||
def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None, pitch=CAMERA_PITCH):
|
||||
"""Props -> drawable primitives for one frame: (background, foreground).
|
||||
|
||||
`geo` is the frame's drawn (possibly orbit-rotated) geometry, `auth_geo`
|
||||
@@ -348,7 +348,9 @@ def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
|
||||
frame's root canvas anchor, and `rot` the `prop_rotation` between them.
|
||||
Joint positions come from `geo`; everything authored — scene points,
|
||||
cable anchors, bar angles, pad perpendiculars, roller offsets — resolves
|
||||
against `auth_geo` and rotates through `rot`.
|
||||
against `auth_geo` and rotates through `rot`. `pitch` is the camera
|
||||
elevation, needed by `axis` props whose world-space direction projects
|
||||
through it (like the floor quad).
|
||||
"""
|
||||
auth = auth_geo if auth_geo is not None else geo
|
||||
|
||||
@@ -415,11 +417,21 @@ def resolve_props(props, geo, anchor, rot=K.IDENTITY, auth_geo=None):
|
||||
_, d = joint_points(auth, p["at"])
|
||||
if not c or not d:
|
||||
continue
|
||||
if t == "bar" or "angle" in p:
|
||||
if p.get("axis") == "z":
|
||||
# A cross-body rod (barbell, pull-up bar): its true 3D axis is
|
||||
# the world left-right axis, projected through the camera
|
||||
# elevation like the floor quad — a small vertical sliver
|
||||
# end-on in a profile view (the plates read nearly concentric,
|
||||
# seen from slightly above), full span face-on, swinging with
|
||||
# the figure in between, symmetric at 0 and 180.
|
||||
pr = math.radians(pitch)
|
||||
ux, uy = swing((0.0, math.sin(pr)), math.cos(pr))
|
||||
elif t == "bar" or "angle" in p:
|
||||
axis = dirv(p.get("angle", 0)) # fixed authored-view angle
|
||||
ux, uy = swing(axis) # foreshortens under orbit
|
||||
else:
|
||||
axis = (-d[1], d[0]) # perpendicular to the lower bone
|
||||
ux, uy = swing(axis) # foreshortens under orbit
|
||||
ux, uy = swing(axis)
|
||||
h = p.get("halfLen", {"bar": 24, "dumbbell": 7, "pad": 8}[t])
|
||||
a = (c[0] - ux * h, c[1] - uy * h)
|
||||
b = (c[0] + ux * h, c[1] + uy * h)
|
||||
@@ -662,7 +674,7 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
for nf in norms:
|
||||
out, geo, order, shade = frame_geometry(nf, prof, cam, flip, pitch, mat)
|
||||
resolved.append(out)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"], pitch=pitch)
|
||||
geo, prims = apply_zoom(geo, prims, zoom)
|
||||
key_cells.append((geo, order, shade, prims))
|
||||
|
||||
@@ -682,7 +694,7 @@ def render_exercise(folder, figure="neutral", flip=False, strict=False):
|
||||
imgs = []
|
||||
for nf in timeline(resolved):
|
||||
geo, order, shade = geometry(nf)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"], pitch=pitch)
|
||||
geo, prims = apply_zoom(geo, prims, zoom)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
|
||||
imgs[0].save(folder / "preview.gif", save_all=True, append_images=imgs[1:],
|
||||
@@ -716,7 +728,7 @@ def render_orbit(folder, figure="neutral"):
|
||||
posed["pins"] = {}
|
||||
_, geo, order, shade = frame_geometry(posed, prof, cam + off, pitch=pitch, mat=mat)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"],
|
||||
prop_rotation(pitch, off), auth_geo)
|
||||
prop_rotation(pitch, off), auth_geo, pitch=pitch)
|
||||
geo, prims = apply_zoom(geo, prims, zoom)
|
||||
imgs.append(draw_geo(geo, order, shade, working, colors, font=font, prims=prims))
|
||||
imgs[0].save(folder / "orbit.gif", save_all=True, append_images=imgs[1:],
|
||||
@@ -734,7 +746,7 @@ def contact_sheet(folders, figure="neutral", out=None):
|
||||
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)
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"])
|
||||
prims = resolve_props(props, geo, nf["root"]["pos"], pitch=pitch)
|
||||
geo, prims = apply_zoom(geo, prims, zoom)
|
||||
cells.append((f"{motion['name']} {i}/{len(norms)}",
|
||||
draw_geo(geo, order, shade, working, PALETTES["default"],
|
||||
@@ -760,7 +772,7 @@ def demo_sheet(folder):
|
||||
norms, prof, cam, pitch, props, zoom = prepare(motion, figure, flip)
|
||||
mat = mat_bounds(norms, prof, cam, pitch)
|
||||
_, geo, order, shade = frame_geometry(norms[idx], prof, cam, flip, pitch, mat)
|
||||
prims = resolve_props(props, geo, norms[idx]["root"]["pos"])
|
||||
prims = resolve_props(props, geo, norms[idx]["root"]["pos"], pitch=pitch)
|
||||
geo, prims = apply_zoom(geo, prims, zoom)
|
||||
cells.append((f"{motion['name']} — {label}",
|
||||
draw_geo(geo, order, shade, working, PALETTES[palette],
|
||||
|
||||
Reference in New Issue
Block a user