Author the machine exercise circuit with props and corrected hip machine motions
Fourteen Planet Fitness machine exercises join the rig library, each with authored motion, info page, and schematic equipment via the new props layer (scene shapes, cables, bars, pads) rendered in lockstep by render.py and the in-app figure renderer. Abductor and Adductor are authored face-on with the real seated machine motion — knees-bent legs swinging apart/together against knee pads — replacing the earlier middle-split depiction. The watch target now bundles the figure renderer and motion rigs too. Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
This commit is contained in:
@@ -60,7 +60,8 @@ struct FigurePose {
|
||||
var root: CGPoint
|
||||
var spine: [Double]
|
||||
var neck: Double
|
||||
var gaze: Double
|
||||
/// Absent for figures facing the viewer — no nose tick.
|
||||
var gaze: Double?
|
||||
var limbs: [FigureLimb: [Double]]
|
||||
var pins: [String: CGPoint]
|
||||
var hold: Double
|
||||
@@ -89,8 +90,9 @@ struct FigureAttachments {
|
||||
struct FigureGeometry {
|
||||
var headCenter: CGPoint
|
||||
var headRadius: Double
|
||||
var noseStart: CGPoint
|
||||
var noseEnd: CGPoint
|
||||
/// Nil when the pose has no gaze (face-on view).
|
||||
var noseStart: CGPoint?
|
||||
var noseEnd: CGPoint?
|
||||
/// Quadratic Bézier through pelvis → mid → neck (control = 2·mid − (pelvis+neck)/2).
|
||||
var spineStart: CGPoint
|
||||
var spineControl: CGPoint
|
||||
@@ -213,7 +215,7 @@ enum MotionSolver {
|
||||
y: a.root.y + (b.root.y - a.root.y) * t),
|
||||
spine: zip(a.spine, b.spine).map { lerpAngle($0, $1, t) },
|
||||
neck: lerpAngle(a.neck, b.neck, t),
|
||||
gaze: lerpAngle(a.gaze, b.gaze, t),
|
||||
gaze: (a.gaze != nil && b.gaze != nil) ? lerpAngle(a.gaze!, b.gaze!, t) : nil,
|
||||
limbs: limbs, pins: pins, hold: a.hold, tween: a.tween
|
||||
)
|
||||
}
|
||||
@@ -223,12 +225,16 @@ enum MotionSolver {
|
||||
static func geometry(of pose: FigurePose, body: ExerciseBodyProfile, hide: Set<String>) -> FigureGeometry {
|
||||
let at = attachments(root: pose.root, spine: pose.spine, body: body)
|
||||
let head = walk(from: at.neck, angles: [pose.neck], lengths: [body.neck])[1]
|
||||
let noseTip = walk(from: head, angles: [pose.gaze], lengths: [body.headR + 7])[1]
|
||||
// Nose tick: 7pt outward from the head rim along the gaze direction.
|
||||
let d = max(hypot(noseTip.x - head.x, noseTip.y - head.y), 1)
|
||||
let ux = (noseTip.x - head.x) / d
|
||||
let uy = (noseTip.y - head.y) / d
|
||||
let r = body.headR
|
||||
// Nose tick: 7pt outward from the head rim along the gaze direction
|
||||
// (omitted for face-on poses without a gaze).
|
||||
var noseStart: CGPoint?
|
||||
var noseEnd: CGPoint?
|
||||
if let gaze = pose.gaze {
|
||||
let d = direction(gaze)
|
||||
noseStart = CGPoint(x: head.x + d.dx * r, y: head.y + d.dy * r)
|
||||
noseEnd = CGPoint(x: head.x + d.dx * (r + 7), y: head.y + d.dy * (r + 7))
|
||||
}
|
||||
|
||||
var limbs: [FigureLimb: [CGPoint]] = [:]
|
||||
for (limb, authored) in pose.limbs where !hide.contains(limb.rawValue) {
|
||||
@@ -244,8 +250,8 @@ enum MotionSolver {
|
||||
return FigureGeometry(
|
||||
headCenter: head,
|
||||
headRadius: r,
|
||||
noseStart: CGPoint(x: head.x + ux * r, y: head.y + uy * r),
|
||||
noseEnd: CGPoint(x: head.x + ux * (r + 7), y: head.y + uy * (r + 7)),
|
||||
noseStart: noseStart,
|
||||
noseEnd: noseEnd,
|
||||
spineStart: at.pelvis,
|
||||
spineControl: CGPoint(x: 2 * at.mid.x - (at.pelvis.x + at.neck.x) / 2,
|
||||
y: 2 * at.mid.y - (at.pelvis.y + at.neck.y) / 2),
|
||||
|
||||
Reference in New Issue
Block a user