cozyclay 2.1.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +43 -116
- package/bin/agent/agent-runner.mjs +14 -15
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/session-store.mjs +2 -2
- package/bin/agent/studio-context-budget.mjs +93 -0
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-BWCqdgWA.js +1 -0
- package/dist/assets/app-DfTCxaKk.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-BxKXh07s.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
- package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-D78eiKTa.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +151 -2625
- package/src/agent-progress.js +80 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +12 -2
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +10 -8
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +214 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +55 -13
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +53 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +47 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +60 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +79 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +113 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +197 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +177 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +144 -0
- package/src/motion3/compiler/validate-parts.js +118 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +74 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +177 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +204 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/people.js +82 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +125 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +195 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +44 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/people-checks.js +37 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +228 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +129 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +113 -0
- package/src/motion3/schema/types.js +149 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/settle.js +136 -0
- package/src/motion3/skills/basic.js +83 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/flip.js +200 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/impact.js +109 -0
- package/src/motion3/skills/index.js +85 -0
- package/src/motion3/skills/info.js +80 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/qa-label.js +25 -0
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +16 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +47 -77
- package/src/studio-app-binding.js +27 -47
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +15 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +109 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +9 -125
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// The arm turns a held object, not the wrist alone (docs/motion-api-v3.1.md C14, lane K8). When a
|
|
2
|
+
// hand must take a world rotation (the object leads, hold.js), most of a held object's angle comes
|
|
3
|
+
// from the elbow's swivel about the shoulder-wrist axis and from the forearm's turn about its own
|
|
4
|
+
// axis (pronation and supination, the twist); the wrist adds the rest (flex and deviation).
|
|
5
|
+
// swivel the elbow on the circle twoBone can put it on for this wrist: POLES poles are scored
|
|
6
|
+
// and their soft minimum (a Boltzmann-weighted mean pole) taken, so two near-equal
|
|
7
|
+
// poles blend instead of flipping; ../hold-plan.js smooths it over the frames
|
|
8
|
+
// score each turn's squared share of its range, plus a steep price past the limits, plus a
|
|
9
|
+
// light pull toward the pole the channels gave (a keyed elbow_<side> is a preference)
|
|
10
|
+
// and the same steep price past SWING degrees from it (the upper arm's own roll limit:
|
|
11
|
+
// a straight arm swung half a turn would twist the shoulder, not hold the object)
|
|
12
|
+
// twist the forearm bone takes FOREARM_SHARE of the turn about the forearm and the hand the
|
|
13
|
+
// rest, so the skin twists along the forearm instead of at the wrist alone
|
|
14
|
+
// Everything here is analytic on the pose (no bone moves): twoBone's result does not depend on the
|
|
15
|
+
// arm it starts from, so the forearm's frame for a pole is its rest frame aligned onto it.
|
|
16
|
+
import { add, sub, scale, norm, dot, cross, len, mul, T, apply, align, clamp, quatFromMat, matFromQuat } from "./math.js";
|
|
17
|
+
import { qmul, qconj, qlog, qexp } from "./quat.js";
|
|
18
|
+
import { palmAxes, foldPoint, backOf } from "./limbs.js";
|
|
19
|
+
|
|
20
|
+
/** Wrist limits in degrees, relative to the hand at rest on the forearm (twist: the forearm's turn). */
|
|
21
|
+
export const WRIST_LIMITS = { twist: [-85, 85], flex: [-70, 70], dev: [-35, 20] };
|
|
22
|
+
/**
|
|
23
|
+
* The comfortable part of each range, what observations.wrist counts frames past (qa/wrist-obs.js):
|
|
24
|
+
* daily tasks keep the wrist within about 40 deg of flex and extension and 25 ulnar / 15 radial
|
|
25
|
+
* deviation (Ryu et al.; Gates et al. 2016) and the forearm within 50 deg of pronation and supination
|
|
26
|
+
* (Morrey 1981).
|
|
27
|
+
*/
|
|
28
|
+
export const WRIST_COMFORT = { twist: [-50, 50], flex: [-40, 40], dev: [-25, 15] };
|
|
29
|
+
/** The share of the turn about the forearm the forearm bone takes (the hand takes the rest). */
|
|
30
|
+
export const FOREARM_SHARE = 0.5;
|
|
31
|
+
/** The direction an arm's elbow points at rest, carried by the bones (pose.js, twoBone). */
|
|
32
|
+
export const ELBOW_REST = [0, 0, -1];
|
|
33
|
+
const POLES = 72, TEMP = 0.02, OVER = 400, PULL = 0.5, SWING = 100 / (180 / Math.PI), DEG = 180 / Math.PI;
|
|
34
|
+
/**
|
|
35
|
+
* The shoulder's comfort (lane elbow): upper-arm elevation, degrees from hanging straight down (90 level, more
|
|
36
|
+
* the elbow over the shoulder). Daily handling (drinking, eating, a cup or a can at the mouth) keeps the upper
|
|
37
|
+
* arm low and forward, well below the level (Magermans et al. 2005); only overhead work lifts it. A pole pays
|
|
38
|
+
* SHOULDER_PRICE times the squared share of SHOULDER_SPAN it lifts the elbow past SHOULDER_COMFORT on the comfort
|
|
39
|
+
* landscape only (a carry whose aim keys may lean, key-aim.js): priced on every hold, it moved the bench's recompiled
|
|
40
|
+
* cup and can takes into wrist limits and QA (their keys were written for the elbow-up hold).
|
|
41
|
+
*/
|
|
42
|
+
const SHOULDER_COMFORT = 70, SHOULDER_SPAN = 30, SHOULDER_PRICE = 2;
|
|
43
|
+
/** A wrist need's price against the comfortable range first and the limits second (the shape grip-choice.js prices a grip with): 1 at the comfortable edge, steep past it. */
|
|
44
|
+
export function comfortPrice(need, steep = 8, over = 400) {
|
|
45
|
+
let cost = 0;
|
|
46
|
+
for (const k of ["twist", "flex", "dev"]) {
|
|
47
|
+
const v = need[k], c = v >= 0 ? WRIST_COMFORT[k][1] : -WRIST_COMFORT[k][0], L = v >= 0 ? WRIST_LIMITS[k][1] : -WRIST_LIMITS[k][0], a = Math.abs(v);
|
|
48
|
+
cost += a <= c ? (a / c) ** 2 : 1 + steep * ((Math.min(a, L) - c) / Math.max(1e-6, L - c)) ** 2 + (a > L ? over * ((a - L) / L) ** 2 : 0);
|
|
49
|
+
}
|
|
50
|
+
return cost;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The shoulder's price for an elbow at E with the shoulder at S (SHOULDER_COMFORT above). */
|
|
54
|
+
export function shoulderPrice(S, E) {
|
|
55
|
+
const u = norm(sub(E, S)), el = Math.acos(clamp(-u[1], -1, 1)) * DEG;
|
|
56
|
+
return SHOULDER_PRICE * (Math.max(0, el - SHOULDER_COMFORT) / SHOULDER_SPAN) ** 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What the hand needs from neutral (its rest on the forearm) to goal, degrees { twist, flex, dev }; axT the forearm axis, l the palm's l axis at neutral. */
|
|
60
|
+
export function wristNeed(neutral, goal, axT, l) {
|
|
61
|
+
let q = quatFromMat(mul(goal, T(neutral)));
|
|
62
|
+
if (q[3] < 0) q = q.map(v => -v);
|
|
63
|
+
const twist = 2 * Math.atan2(dot([q[0], q[1], q[2]], axT), q[3]), spin = qexp(scale(axT, twist));
|
|
64
|
+
const swing = qlog(qmul(q, qconj(spin))), lTwisted = apply(matFromQuat(spin), l);
|
|
65
|
+
const axF = norm(sub(lTwisted, scale(axT, dot(lTwisted, axT)))), axD = cross(axT, axF);
|
|
66
|
+
return { twist: twist * DEG, flex: dot(swing, axF) * DEG, dev: dot(swing, axD) * DEG, axF, axD };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The strain of a wrist need: each turn's squared share of its half range, plus OVER times the
|
|
71
|
+
* squared share past a limit; `margin` (a share of the half range) moves the limits that far in.
|
|
72
|
+
*/
|
|
73
|
+
function strainOf(need, margin = 0) {
|
|
74
|
+
let cost = 0;
|
|
75
|
+
for (const [k, [lo, hi]] of Object.entries(WRIST_LIMITS)) {
|
|
76
|
+
const z = (need[k] - (lo + hi) / 2) / ((hi - lo) / 2), over = Math.max(0, Math.abs(z) - 1 + margin);
|
|
77
|
+
cost += z * z + OVER * over * over;
|
|
78
|
+
}
|
|
79
|
+
return cost;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The arm's swivel landscape for hand rotation `goal` with the wrist at W, on pose s (the plain
|
|
84
|
+
* arm of `side`): at(th) -> { pv, need, elbow, cost } for the elbow th radians about the shoulder-wrist
|
|
85
|
+
* axis from `ref` (the bend direction the channels gave), angleOf(world direction) -> th,
|
|
86
|
+
* softmin() -> the th of the Boltzmann-weighted mean of POLES poles, miss the metres the arm is
|
|
87
|
+
* short of W (a wrist nearer the shoulder than the elbow folds is no miss here; hold.js holdCost
|
|
88
|
+
* prices it). `margin` tightens the wrist limits the strain prices (strainOf).
|
|
89
|
+
*/
|
|
90
|
+
export function landscape(sk, s, side, goal, W, ref, restBend, margin = 0, comfort = false) {
|
|
91
|
+
const I = sk.index, a = I[`${side}Arm`], fa = I[`${side}ForeArm`], h = I[`${side}Hand`], S = s.P[a];
|
|
92
|
+
const A = len(sub(s.P[fa], S)), B = len(sub(s.P[h], s.P[fa]));
|
|
93
|
+
// the elbow where twoBone puts it: the wrist no further folded than limbs.js foldPoint lets it
|
|
94
|
+
const v = sub(foldPoint(S, W, A, B, backOf(sk, s)), S), d = len(v), u = scale(v, 1 / Math.max(d, 1e-9)), dc = clamp(d, Math.abs(A - B) + 1e-4, (A + B) * 0.9995);
|
|
95
|
+
const along = (A * A - B * B + dc * dc) / (2 * dc), rad = Math.sqrt(Math.max(A * A - along * along, 0));
|
|
96
|
+
const off = x => sub(x, scale(u, dot(x, u))), e1 = norm(len(off(ref)) > 1e-6 ? off(ref) : cross(u, Math.abs(u[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0]));
|
|
97
|
+
const e2 = cross(u, e1), Wn = add(S, scale(u, dc));
|
|
98
|
+
const rw = sk.restWorldRot, faDir = apply(T(rw[fa]), sub(sk.restWorldPos[h], sk.restWorldPos[fa])), faBend = apply(T(rw[fa]), restBend);
|
|
99
|
+
const neutralRel = mul(T(rw[fa]), rw[h]), lLocal = apply(T(s.R[h]), palmAxes(sk, s, side).l);
|
|
100
|
+
const at = th => {
|
|
101
|
+
const pv = add(scale(e1, Math.cos(th)), scale(e2, Math.sin(th))), fdir = sub(Wn, add(S, add(scale(u, along), scale(pv, rad))));
|
|
102
|
+
const neutral = mul(align(faDir, faBend, fdir, pv), neutralRel), need = wristNeed(neutral, goal, norm(fdir), apply(neutral, lLocal));
|
|
103
|
+
const z = Math.abs(Math.atan2(Math.sin(th), Math.cos(th))) / SWING;
|
|
104
|
+
const elbow = add(S, add(scale(u, along), scale(pv, rad)));
|
|
105
|
+
// comfort (a carry that lets its aim lean, key-aim.js): the wrist priced against its comfortable range and the shoulder's price
|
|
106
|
+
return { pv, need, elbow, cost: (comfort ? comfortPrice(need) + shoulderPrice(S, elbow) : strainOf(need, margin)) + PULL * (1 - Math.cos(th)) + OVER * Math.max(0, z - 1) ** 2 };
|
|
107
|
+
};
|
|
108
|
+
const angleOf = x => (len(off(x)) > 1e-6 ? Math.atan2(dot(x, e2), dot(x, e1)) : 0);
|
|
109
|
+
const softmin = () => {
|
|
110
|
+
const all = Array.from({ length: POLES }, (_, i) => at((2 * Math.PI * i) / POLES)), low = Math.min(...all.map(p => p.cost));
|
|
111
|
+
const mean = all.reduce((m, p) => add(m, scale(p.pv, Math.exp(-(p.cost - low) / TEMP))), [0, 0, 0]);
|
|
112
|
+
return len(mean) > 1e-3 ? angleOf(mean) : (2 * Math.PI * all.findIndex(p => p.cost === low)) / POLES;
|
|
113
|
+
};
|
|
114
|
+
return { at, angleOf, softmin, miss: Math.max(0, d - dc) };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The elbow pole for hand rotation `goal` with the wrist at W (landscape above). opts.toward, a
|
|
119
|
+
* world direction, sets the pole instead of the soft minimum (a planned pole, ../hold-plan.js);
|
|
120
|
+
* opts.w (0..1, the keys' blend weight) scales the swing from ref, so the elbow eases out of and
|
|
121
|
+
* back into the plain arm; opts.margin as for landscape. Returns { pole, need, cost, miss }: pole
|
|
122
|
+
* a world direction for twoBone.
|
|
123
|
+
*/
|
|
124
|
+
export function swivel(sk, s, side, goal, W, ref, restBend, { toward, w = 1, margin = 0, comfort = false } = {}) {
|
|
125
|
+
const land = landscape(sk, s, side, goal, W, ref, restBend, margin, comfort), best = toward ? land.angleOf(toward) : land.softmin();
|
|
126
|
+
const pick = land.at(w * Math.atan2(Math.sin(best), Math.cos(best)));
|
|
127
|
+
return { pole: pick.pv, need: pick.need, cost: pick.cost, miss: land.miss, elbow: pick.elbow };
|
|
128
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Keyed channels -> per-frame values. A channel is interpolated only through the keys that
|
|
2
|
+
// set it; before its first key and after its last it holds. Numbers, number arrays and
|
|
3
|
+
// {preset: weight} objects are supported. `step` keys jump at that frame (visibility switches
|
|
4
|
+
// and the like).
|
|
5
|
+
// Flow (spec.flow): the channels that place the body against the world or a target (feet,
|
|
6
|
+
// root, yaw, hand targets and weights, poles, fingers, objects) are monotone cubic / PCHIP and on
|
|
7
|
+
// time, so a held value never overshoots and a contact lands on its key. The body's own swing
|
|
8
|
+
// (FLOW_SMOOTH: spine, head, arm swing and bend, wrist) carries its speed through a key instead of
|
|
9
|
+
// stopping on it (Catmull-Rom, the overshoot past a key bounded), and trails the body
|
|
10
|
+
// (FLOW_LAG frames: the head behind the body, the arms behind both), the trail easing out over the
|
|
11
|
+
// take's last frames so every value is reached. An arm on its IK (armw) reaches for a target, so
|
|
12
|
+
// flow is gone by the time the arm is a quarter of the way onto it: an arm's swing and bend by its
|
|
13
|
+
// own armw, the spine (which carries the shoulders) and the head (the hands work near it) by the
|
|
14
|
+
// larger of the two; a reach or a held prop lands as keyed. A hold (two equal keys in a row) stays
|
|
15
|
+
// exactly flat either way.
|
|
16
|
+
|
|
17
|
+
function pchipSlopes(x, y) {
|
|
18
|
+
const n = y.length, m = new Array(n).fill(0);
|
|
19
|
+
for (let i = 1; i < n - 1; i++) {
|
|
20
|
+
const h0 = x[i] - x[i - 1], h1 = x[i + 1] - x[i];
|
|
21
|
+
const d0 = (y[i] - y[i - 1]) / h0, d1 = (y[i + 1] - y[i]) / h1;
|
|
22
|
+
if (d0 * d1 > 0) {
|
|
23
|
+
const w1 = 2 * h1 + h0, w2 = h1 + 2 * h0;
|
|
24
|
+
m[i] = (w1 + w2) / (w1 / d0 + w2 / d1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return m;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function pchipEval(x, y, m, q) {
|
|
31
|
+
if (x.length === 1 || q <= x[0]) return y[0];
|
|
32
|
+
if (q >= x[x.length - 1]) return y[y.length - 1];
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (q > x[i + 1]) i++;
|
|
35
|
+
const h = x[i + 1] - x[i], t = (q - x[i]) / h;
|
|
36
|
+
const t2 = t * t, t3 = t2 * t;
|
|
37
|
+
return (2 * t3 - 3 * t2 + 1) * y[i] + (t3 - 2 * t2 + t) * h * m[i] + (-2 * t3 + 3 * t2) * y[i + 1] + (t3 - t2) * h * m[i + 1];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const FLOW_SMOOTH = /^(spine|head)_(yaw|pitch|roll)$|^(aswing|abend)_[lr]$/;
|
|
41
|
+
const FLOW_LAG = { spine: 0, head: 2, arms: 3 };
|
|
42
|
+
const OVER_FRAC = 0.15, OVER_MAX = 6; // a smooth segment's overshoot: at most this share of its change, and degrees
|
|
43
|
+
const ikSides = ch => (/^(spine|head)_/.test(ch) ? ["l", "r"] : /^(aswing|abend)_([lr])$/.test(ch) ? [ch.slice(-1)] : []);
|
|
44
|
+
const lagFamily = ch => (/^spine_/.test(ch) ? "spine" : /^head_/.test(ch) ? "head" : /^(aswing|abend)_[lr]$/.test(ch) ? "arms" : null);
|
|
45
|
+
|
|
46
|
+
/** spec.flow -> { smooth, lag: { spine, head, arms } }: undefined is the default, false turns both off. */
|
|
47
|
+
export function flowOptions(flow) {
|
|
48
|
+
if (flow === false) return { smooth: false, lag: { spine: 0, head: 0, arms: 0 } };
|
|
49
|
+
const f = flow ?? {}, lag = f.lag === false ? 0 : f.lag;
|
|
50
|
+
const each = typeof lag === "number" ? { spine: lag, head: lag, arms: lag } : { ...FLOW_LAG, ...(lag ?? {}) };
|
|
51
|
+
return { smooth: f.smooth !== false, lag: each };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Catmull-Rom slopes, flat at a hold and at the ends, each segment's overshoot bounded. */
|
|
55
|
+
function smoothSlopes(x, y) {
|
|
56
|
+
const n = y.length, m = new Array(n).fill(0);
|
|
57
|
+
for (let i = 1; i < n - 1; i++) if (y[i] !== y[i - 1] && y[i] !== y[i + 1]) m[i] = (y[i + 1] - y[i - 1]) / (x[i + 1] - x[i - 1]);
|
|
58
|
+
for (let i = 0; i < n - 1; i++) {
|
|
59
|
+
const h = x[i + 1] - x[i], lo = Math.min(y[i], y[i + 1]), hi = Math.max(y[i], y[i + 1]), limit = Math.min(OVER_MAX, OVER_FRAC * (hi - lo));
|
|
60
|
+
for (let tries = 0; tries < 12; tries++) {
|
|
61
|
+
let over = 0;
|
|
62
|
+
for (let k = 1; k < 16; k++) {
|
|
63
|
+
const v = pchipEval([x[i], x[i + 1]], [y[i], y[i + 1]], [m[i], m[i + 1]], x[i] + (h * k) / 16);
|
|
64
|
+
over = Math.max(over, v - hi, lo - v);
|
|
65
|
+
}
|
|
66
|
+
if (over <= limit + 1e-9) break;
|
|
67
|
+
m[i] *= 0.7; m[i + 1] *= 0.7;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return m;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const shapeOf = v => (Array.isArray(v) ? "vec" : v && typeof v === "object" ? "map" : "num");
|
|
74
|
+
|
|
75
|
+
/** keys: [{ f, set: { channel: value }, step?: [channel...] }] -> { channel: Array(frameCount) }; flow: flowOptions(spec.flow). */
|
|
76
|
+
export function bakeChannels(keys, frameCount, flow = flowOptions()) {
|
|
77
|
+
const byChannel = new Map();
|
|
78
|
+
for (const key of [...keys].sort((a, b) => a.f - b.f)) {
|
|
79
|
+
for (const [ch, value] of Object.entries(key.set ?? {})) {
|
|
80
|
+
if (!byChannel.has(ch)) byChannel.set(ch, []);
|
|
81
|
+
byChannel.get(ch).push({ f: key.f, value, step: key.step?.includes(ch) });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const out = {}, plain = {};
|
|
85
|
+
for (const [ch, list] of byChannel) {
|
|
86
|
+
// later keys at the same frame win
|
|
87
|
+
const pts = [...new Map(list.map(k => [k.f, k])).values()];
|
|
88
|
+
const shape = shapeOf(pts[0].value);
|
|
89
|
+
const comps = shape === "vec" ? pts[0].value.length : 0;
|
|
90
|
+
const series = shape === "num" ? [pts.map(p => p.value)]
|
|
91
|
+
: shape === "vec" ? Array.from({ length: comps }, (_, i) => pts.map(p => p.value[i]))
|
|
92
|
+
: [...new Set(pts.flatMap(p => Object.keys(p.value)))].map(name => ({ name, y: pts.map(p => p.value[name] ?? 0) }));
|
|
93
|
+
const x = pts.map(p => p.f), smooth = flow.smooth && FLOW_SMOOTH.test(ch), lag = flow.lag[lagFamily(ch)] ?? 0;
|
|
94
|
+
// the trail eases out over the last 2 * lag frames, so the take ends on the last value
|
|
95
|
+
const at = f => (lag > 0 ? f - lag * Math.min(1, (frameCount - 1 - f) / (2 * lag)) : f);
|
|
96
|
+
const evalSeries = (y, flowing = true) => {
|
|
97
|
+
const m = smooth && flowing ? smoothSlopes(x, y) : pchipSlopes(x, y);
|
|
98
|
+
return q => {
|
|
99
|
+
const f = flowing ? at(q) : q;
|
|
100
|
+
// a step key holds the previous value right up to its frame
|
|
101
|
+
for (let i = 1; i < pts.length; i++) if (pts[i].step && f >= x[i - 1] && f < x[i]) return y[i - 1];
|
|
102
|
+
return pchipEval(x, y, m, f);
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const frames = new Array(frameCount);
|
|
106
|
+
if (shape === "num") { const e = evalSeries(series[0]); for (let f = 0; f < frameCount; f++) frames[f] = e(f); }
|
|
107
|
+
else if (shape === "vec") { const es = series.map(evalSeries); for (let f = 0; f < frameCount; f++) frames[f] = es.map(e => e(f)); }
|
|
108
|
+
else {
|
|
109
|
+
const es = series.map(({ name, y }) => [name, evalSeries(y)]);
|
|
110
|
+
for (let f = 0; f < frameCount; f++) frames[f] = Object.fromEntries(es.map(([n, e]) => [n, e(f)]));
|
|
111
|
+
}
|
|
112
|
+
out[ch] = frames;
|
|
113
|
+
if (shape === "num" && (smooth || lag > 0) && ikSides(ch).length) { const e = evalSeries(series[0], false); plain[ch] = Array.from({ length: frameCount }, (_, f) => e(f)); }
|
|
114
|
+
}
|
|
115
|
+
// flow fades out as the arm goes onto its IK (the spine with either arm)
|
|
116
|
+
for (const [ch, keyed] of Object.entries(plain)) {
|
|
117
|
+
const ws = ikSides(ch).map(k => out[`armw_${k}`]).filter(Boolean);
|
|
118
|
+
if (!ws.length) continue;
|
|
119
|
+
out[ch] = out[ch].map((v, f) => { const w = Math.min(1, 4 * Math.max(0, ...ws.map(a => a[f]))); return v + (keyed[f] - v) * w * (2 - w); });
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Frame f's channel dict. */
|
|
125
|
+
export const channelsAt = (baked, f) => Object.fromEntries(Object.entries(baked).map(([k, v]) => [k, v[f]]));
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Motion spec -> baked frames. Deterministic and pure: the same spec and scene give the same
|
|
2
|
+
// frames, so the agent can edit keys and recompile in a loop.
|
|
3
|
+
//
|
|
4
|
+
// spec = { schema: "cozyclay.motion-spec.v1", fps, frames, keys, anchors, props, fits, objects, generators, checks }
|
|
5
|
+
// keys: [{ f, set: { channel: value }, step?: [channel] }] (see pose.js for channel names)
|
|
6
|
+
// hand mix: hand_<l|r> (world point) blended with w_<anchor>_<l|r> weights (anchors.js)
|
|
7
|
+
// fits: [{ side, anchor, effector: { prop, point } | { joints: [...] }, offset? }]
|
|
8
|
+
// moves the wrist so the effector lands on the anchor whenever that weight is on
|
|
9
|
+
// objects: { id: { pivot: [x,y,z], axis: [x,y,z], channel: "door" } } hinge angle in degrees
|
|
10
|
+
// { id: { prop, grab?, release?, at?, along?, visible?, keys? } } carried (../object-pose.js);
|
|
11
|
+
// keys lead the holding hand (../object-keys.js, hold.js)
|
|
12
|
+
// { id: { visible?, level? } } a switch: shown/hidden and dimmed, never moved (C15)
|
|
13
|
+
// { id: { follow, offset?, aim?, visible?, level? } } rides a point of the body (C16, ../follow.js)
|
|
14
|
+
// { id: { keys?, fall?, visible?, level? } } moves on its own: keys and gravity falls (C17, ../free-motion.js)
|
|
15
|
+
// id is a body or a group (Empty); a group moves every body under it
|
|
16
|
+
// generators: [{ type: "walk", f0, ... }] (walk.js)
|
|
17
|
+
// fix: { reach, slide, penetration, retime } deterministic auto-fixes (../fix/)
|
|
18
|
+
// flow: { smooth, lag } | false the body's swing carries through keys and trails (channels.js)
|
|
19
|
+
// idle: false turns off the idle layer (idle.js: a held pose breathes, drifts its head, shifts its weight)
|
|
20
|
+
// ctx = { sk, scene: { objects: { id: { pos, R, half, ancestors } } } }; out.ctx.scene keeps the scene (object-pose.js)
|
|
21
|
+
import { add, sub, scale, norm, len, dot, apply, axisRot } from "./math.js";
|
|
22
|
+
import { isCarried, isFollow, hingePose, movesRecord, compose, relative } from "../carried.js";
|
|
23
|
+
import { isFree, freePoseAt } from "../free-motion.js";
|
|
24
|
+
import { groupsOf } from "../scene.js";
|
|
25
|
+
import { pose, SIDES } from "./pose.js";
|
|
26
|
+
import { gripHand } from "./grip.js";
|
|
27
|
+
import { bakeChannels, channelsAt, flowOptions } from "./channels.js";
|
|
28
|
+
import { idleLayer, withIdle, busyHands } from "./idle.js";
|
|
29
|
+
import { resolveAnchor, measureProps } from "./anchors.js";
|
|
30
|
+
import { expandGenerators } from "./walk.js";
|
|
31
|
+
import { steadyHead } from "./head-steady.js";
|
|
32
|
+
import { fixOptions, applyFixes } from "../fix/index.js";
|
|
33
|
+
import { fitWeight } from "../fix/self.js";
|
|
34
|
+
import { holdHook } from "../object-keys.js";
|
|
35
|
+
import { HAND_CLEAR, keepOut, wristBody } from "../body.js";
|
|
36
|
+
import { objectPoseAt, handOf, restingAt } from "../object-pose.js";
|
|
37
|
+
|
|
38
|
+
const smooth = x => x * x * (3 - 2 * x);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Scene boxes by frame. A body moves with its own spec.objects entry or its nearest group's (a
|
|
42
|
+
* switch entry moves nothing; hinge: turned; free (C17): where its keys and falls put it, under the time map `mapOf()`;
|
|
43
|
+
* carry and follow: left at its scene pose and marked `carried` with the moving id). `obstacles` drops the carried
|
|
44
|
+
* ones: QA, refs and object anchors read a carry where it rides (object-pose.js objectPoseAt, qa/object-checks.js);
|
|
45
|
+
* a follow entry rides the body and is no obstacle.
|
|
46
|
+
*/
|
|
47
|
+
function objectTracks(spec, scene, baked, mapOf = () => undefined) {
|
|
48
|
+
const cache = new Map(), kept = new Map();
|
|
49
|
+
let map = mapOf();
|
|
50
|
+
const all = f => {
|
|
51
|
+
// a retime maps free keys and falls too: frames read before it are read again
|
|
52
|
+
if (mapOf() !== map) { map = mapOf(); cache.clear(); kept.clear(); }
|
|
53
|
+
if (cache.has(f)) return cache.get(f);
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [id, o] of Object.entries(scene?.objects ?? {})) {
|
|
56
|
+
const mover = [id, ...(o.ancestors ?? [])].find(k => movesRecord(spec.objects?.[k])), entry = spec.objects?.[mover];
|
|
57
|
+
if (!entry) out[id] = o;
|
|
58
|
+
else if (isCarried(entry) || isFollow(entry)) out[id] = { ...o, carried: mover };
|
|
59
|
+
else if (isFree(entry)) out[id] = { ...o, ...compose(freePoseAt({ spec, scene, timeMap: map }, mover, f), relative(scene.objects[mover] ?? groupsOf(scene)[mover], o)) };
|
|
60
|
+
else out[id] = { ...o, ...hingePose(entry, o, baked, f) };
|
|
61
|
+
}
|
|
62
|
+
cache.set(f, out);
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
const obstacles = f => {
|
|
66
|
+
if (mapOf() !== map) all(f);
|
|
67
|
+
if (!kept.has(f)) kept.set(f, Object.fromEntries(Object.entries(all(f)).filter(([, box]) => !box.carried)));
|
|
68
|
+
return kept.get(f);
|
|
69
|
+
};
|
|
70
|
+
return { all, obstacles };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function effectorPoint(sk, s, diag, eff) {
|
|
74
|
+
if (eff.prop) return diag.props?.[eff.prop]?.[eff.point];
|
|
75
|
+
const js = eff.joints ?? ["RightHandIndex2", "RightHandMiddle2"];
|
|
76
|
+
return scale(js.map(j => s.P[sk.index[j]]).reduce((a, p) => add(a, p), [0, 0, 0]), 1 / js.length);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** `lead` false solves without the object keys; a fit's own hand is solved without them (the keys override it). */
|
|
80
|
+
function hooksFor(ctx, c, f, inFit = false, lead = true, fitSide = null) {
|
|
81
|
+
const { sk } = ctx;
|
|
82
|
+
return {
|
|
83
|
+
softReach: ctx.softReach,
|
|
84
|
+
// a fit's passes find where the wrist must go with the elbow unstopped; the final pose stops its fold (limbs.js) and leaves the residual
|
|
85
|
+
foldFree: inFit,
|
|
86
|
+
hold: lead && ctx.hold ? (k, s) => (k === fitSide ? null : ctx.hold.lead(k, f, s, c)) : undefined,
|
|
87
|
+
// auto-fix offsets ride on the final pose only, so a fit never cancels them
|
|
88
|
+
push: inFit ? undefined : k => ctx.push?.[k]?.get(f),
|
|
89
|
+
swivel: k => ctx.swivel?.[k]?.get(f), // in a fit's passes too: a turn that stops at the wrist's limit moves the fit's point (wrist-clamp.js)
|
|
90
|
+
skinPush: inFit ? undefined : k => ctx.skinPush?.[k]?.get(f),
|
|
91
|
+
handTarget(k, s, diag) {
|
|
92
|
+
diag.frame = f;
|
|
93
|
+
const fkHand = s.P[sk.index[k === "l" ? "LeftHand" : "RightHand"]];
|
|
94
|
+
const terms = [];
|
|
95
|
+
for (const [ch, w] of Object.entries(c)) {
|
|
96
|
+
const m = /^w_(.+)_([lr])$/.exec(ch);
|
|
97
|
+
if (!m || m[2] !== k || !(w > 1e-6)) continue;
|
|
98
|
+
terms.push([w, add(resolveAnchor(ctx, m[1], s, diag, c, k), c[`fit_${m[1]}_${k}`] ?? [0, 0, 0])]);
|
|
99
|
+
}
|
|
100
|
+
let tot = terms.reduce((a, [w]) => a + w, 0);
|
|
101
|
+
if (tot > 1) { for (const t of terms) t[0] /= tot; tot = 1; }
|
|
102
|
+
let target = scale(c[`hand_${k}`] ?? fkHand, 1 - tot);
|
|
103
|
+
for (const [w, p] of terms) target = add(target, scale(p, w));
|
|
104
|
+
// Between targets, arc around the torso instead of cutting through it.
|
|
105
|
+
const trans = Math.max(0, ...terms.map(([w]) => smooth(Math.min(1, Math.min(w, 1 - w) * 3))));
|
|
106
|
+
if (trans > 0) {
|
|
107
|
+
const a = s.P[sk.index.Spine], b = s.P[sk.index.Neck], u = sub(b, a);
|
|
108
|
+
const t = Math.min(1, Math.max(0, dot(sub(target, a), u) / dot(u, u))), q = add(a, scale(u, t));
|
|
109
|
+
const r = ctx.spec.detourRadius ?? 0.25, dv = sub(target, q), d = len(dv);
|
|
110
|
+
if (d < r) {
|
|
111
|
+
const fwd = apply(axisRot([0, 1, 0], c.yaw ?? 0), [0, 0, 1]);
|
|
112
|
+
const dir = norm(add(d > 1e-6 ? scale(dv, 1 / d) : fwd, scale(fwd, 0.6)));
|
|
113
|
+
target = add(target, scale(sub(add(q, scale(dir, r)), target), trans));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// a wrist target inside the body (a pocket on a squatting thigh, a path through the chest) goes out onto its skin
|
|
117
|
+
return target;
|
|
118
|
+
},
|
|
119
|
+
// the wrist target, blended with the FK hand, inside the body goes out onto its skin, twice at most (out of
|
|
120
|
+
// one bone, then the next): a pocket the squat moved into the thigh, a path through the chest, a hand leaving
|
|
121
|
+
// a thigh for its FK rest. A measured target (a fit's anchor: the mouth; or a prop another hand's fit is on:
|
|
122
|
+
// the cigarette under the flame) stays where its goal needs it, and the move fades in and out with its weight;
|
|
123
|
+
// an arm coming in from FK moves it no more than its IK weight.
|
|
124
|
+
keepOut: ctx.keepOut && !inFit ? (k, s, target) => {
|
|
125
|
+
const free = (1 - fitWeight(ctx.spec, c, k)) * Math.min(1, Math.max(0, c[`armw_${k}`] ?? 0));
|
|
126
|
+
for (let i = 0; i < 2 && free > 1e-3; i++) {
|
|
127
|
+
const moved = keepOut(ctx.keepOut.body, sk, s, target, HAND_CLEAR, ctx.keepOut.skip);
|
|
128
|
+
if (moved === target) break;
|
|
129
|
+
target = add(target, scale(sub(moved, target), free));
|
|
130
|
+
}
|
|
131
|
+
return target;
|
|
132
|
+
} : undefined,
|
|
133
|
+
afterArm: (k, s, diag) => measureProps(ctx, k, s, diag),
|
|
134
|
+
// a fit's passes only place the wrist; the final pose (and a plain one) turns the hand onto the body
|
|
135
|
+
grip: inFit ? undefined : (k, s, diag) => gripHand(ctx, sk, s, SIDES[k], k, c, f, diag),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One frame with every active fit solved (fits run in spec order); lead false leaves the object keys out. */
|
|
140
|
+
export function solveFrame(ctx, c0, f, lead = true) {
|
|
141
|
+
const c = { ...withIdle(c0, ctx.idle, f) };
|
|
142
|
+
// a working hand keeps its forearm's slant (pose.js arm_hang)
|
|
143
|
+
for (const k of ["l", "r"]) if (ctx.busy?.[k]) c[`hangoff_${k}`] = ctx.busy[k][f];
|
|
144
|
+
for (const fit of ctx.spec.fits ?? []) {
|
|
145
|
+
const wch = `w_${fit.anchor}_${fit.side}`;
|
|
146
|
+
if (!(c[wch] > 1e-4)) continue;
|
|
147
|
+
const full = { ...c, [wch]: 1, [`armw_${fit.side}`]: 1 };
|
|
148
|
+
for (const ch of Object.keys(full)) if (ch !== wch && new RegExp(`^w_.+_${fit.side}$`).test(ch)) full[ch] = 0;
|
|
149
|
+
let corr = [0, 0, 0];
|
|
150
|
+
for (let i = 0; i < 6; i++) {
|
|
151
|
+
full[`fit_${fit.anchor}_${fit.side}`] = corr;
|
|
152
|
+
const { s, diag } = pose(ctx.sk, full, hooksFor(ctx, full, f, true, lead, fit.side));
|
|
153
|
+
const eff = effectorPoint(ctx.sk, s, diag, fit.effector);
|
|
154
|
+
const goal = add(resolveAnchor(ctx, fit.anchor, s, diag, full, fit.side), fit.offset ?? [0, 0, 0]);
|
|
155
|
+
if (!eff) break;
|
|
156
|
+
corr = add(corr, sub(goal, eff));
|
|
157
|
+
}
|
|
158
|
+
c[`fit_${fit.anchor}_${fit.side}`] = corr;
|
|
159
|
+
}
|
|
160
|
+
const { s, diag } = pose(ctx.sk, c, hooksFor(ctx, c, f, false, lead));
|
|
161
|
+
// readers of the solved frame (refs, checks, hints) resolve its anchors at f, IK arms or not
|
|
162
|
+
diag.frame = f;
|
|
163
|
+
return { s, diag, c };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* opts.window = [a, b] solves only frames a..b (out.f0 = a, out.frames[i] is frame a + i);
|
|
168
|
+
* opts.keys replaces the generator expansion; opts.fixes = false skips the auto-fix pass.
|
|
169
|
+
*/
|
|
170
|
+
export function compileMotion(spec, { sk, scene }, opts = {}) {
|
|
171
|
+
if (spec?.schema !== "cozyclay.motion-spec.v1") throw new Error("MOTION_SPEC_INVALID: schema must be cozyclay.motion-spec.v1");
|
|
172
|
+
const frameCount = Math.round(spec.frames);
|
|
173
|
+
if (!(frameCount >= 1 && frameCount <= 20000)) throw new Error("MOTION_SPEC_INVALID: frames must be 1..20000");
|
|
174
|
+
const fix = fixOptions(spec.fix);
|
|
175
|
+
const [a, b] = opts.window ? [Math.max(0, opts.window[0]), Math.min(frameCount - 1, opts.window[1])] : [0, frameCount - 1];
|
|
176
|
+
const ctx = { sk, spec, scene, softReach: fix.reach, push: { l: new Map(), r: new Map() }, swivel: { l: new Map(), r: new Map() }, skinPush: { l: new Map(), r: new Map() }, keepOut: fix.self ? wristBody(sk) : null };
|
|
177
|
+
const out = { ctx, fps: spec.fps ?? 30, frameCount, f0: a, frames: [] };
|
|
178
|
+
// object anchors read the pose refs read (object-pose.js), on the frame being solved
|
|
179
|
+
ctx.objectPose = (id, f, now) => objectPoseAt(out, id, f, now);
|
|
180
|
+
ctx.objectHand = (id, f) => handOf(out, id, f);
|
|
181
|
+
// a keyed carry's grab offset is taken where it rests at grab (object-keys.js; a fall before grab: where it landed)
|
|
182
|
+
ctx.restingAt = (id, f) => restingAt(out, id, f);
|
|
183
|
+
const engine = {
|
|
184
|
+
rebake(keys) {
|
|
185
|
+
out.keys = keys;
|
|
186
|
+
out.baked = bakeChannels(keys, frameCount, flowOptions(spec.flow));
|
|
187
|
+
steadyHead(out.baked, keys, frameCount);
|
|
188
|
+
// a held pose breathes and shifts its weight (idle.js): offsets solveFrame adds, contacts and out.baked stay as keyed
|
|
189
|
+
ctx.idle = idleLayer(out.baked, { spec, sk, frameCount });
|
|
190
|
+
ctx.busy = busyHands(out.baked, spec, frameCount, out.fps);
|
|
191
|
+
const tracks = objectTracks(spec, scene, out.baked, () => out.timeMap);
|
|
192
|
+
ctx.objectsAt = tracks.all;
|
|
193
|
+
out.objectsAt = a ? f => tracks.obstacles(f + a) : tracks.obstacles;
|
|
194
|
+
},
|
|
195
|
+
solve(frames) { for (const f of frames) if (f >= a && f <= b) out.frames[f - a] = solveFrame(ctx, channelsAt(out.baked, f), f); },
|
|
196
|
+
all: () => Array.from({ length: b - a + 1 }, (_, i) => a + i),
|
|
197
|
+
};
|
|
198
|
+
ctx.hold = holdHook(ctx, out, f => solveFrame(ctx, channelsAt(out.baked, f), f, false), f => solveFrame(ctx, channelsAt(out.baked, f), f));
|
|
199
|
+
engine.rebake(opts.keys ?? expandGenerators(spec, sk));
|
|
200
|
+
engine.solve(engine.all());
|
|
201
|
+
out.fixed = opts.fixes === false ? {} : applyFixes(out, fix, engine, { whole: !opts.window });
|
|
202
|
+
// a retime maps the key frames only once it is done: solve the keyed frames again on the final map
|
|
203
|
+
if (ctx.hold && out.timeMap) engine.solve(engine.all());
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// The walk's rhythm (walk.js): when each step lifts off, how long the foot is in the air, how far
|
|
2
|
+
// it goes, how high it lifts and how much the arms swing. A person does not give every step the
|
|
3
|
+
// same time: the first steps build up, the last one slows into the stop, one side is a little
|
|
4
|
+
// quicker than the other, no two steps are alike, and a turn takes slower, shorter steps.
|
|
5
|
+
// Deterministic (a hash of the walk's own parameters seeds the step-to-step part) and zero-sum:
|
|
6
|
+
// the lift-offs are moved inside the walk's span (the walk still ends 16 + steps * period + 18
|
|
7
|
+
// frames after f0) and the middle strides keep their sum (the walk ends where it did). The first
|
|
8
|
+
// stride stays firstStep and the last step still brings the feet together.
|
|
9
|
+
// A person's strides also differ in length from one another as wholes (both steps a little longer or shorter),
|
|
10
|
+
// which the step-to-step terms alone do not show: their left/right part cancels over a stride. LENGTH.stride gives
|
|
11
|
+
// each middle stride its own length. (A stride time term is left out: any more time variation lowers the hips'
|
|
12
|
+
// rise and fall that walk-pelvis.js fits under the legs below a person's ~5 cm.)
|
|
13
|
+
// vary scales every term: 0 is the metronome (every step period frames apart, swing frames in the
|
|
14
|
+
// air, step metres long), 1 the default, 2 twice as uneven.
|
|
15
|
+
const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
|
|
16
|
+
|
|
17
|
+
/** text -> a generator of numbers in [-1, 1) (FNV-1a hash, then mulberry32). */
|
|
18
|
+
function noiseOf(text) {
|
|
19
|
+
let h = 2166136261;
|
|
20
|
+
for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
21
|
+
let a = h >>> 0;
|
|
22
|
+
return () => {
|
|
23
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
24
|
+
let t = Math.imul(a ^ (a >>> 15), a | 1);
|
|
25
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
26
|
+
return (((t ^ (t >>> 14)) >>> 0) / 4294967296) * 2 - 1;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Sizes at vary 1, as fractions of the step's time or length.
|
|
31
|
+
const TIME = { noise: 0.02, side: 0.03, first: 0.22, second: 0.08, last: 0.22, beforeLast: 0.08, turn: 0 };
|
|
32
|
+
const LENGTH = { noise: 0.025, side: 0.012, stride: 0.035, second: 0.05, beforeLast: 0.06, turn: 0.06 };
|
|
33
|
+
const LIFT = { noise: 0.1, first: 0.1, last: 0.25 }, ARM = 0.1;
|
|
34
|
+
const CLOSE = 3; // frames the closing step may swing longer than swing
|
|
35
|
+
const TURN_FULL = 60; // degrees turned in one step at which a step is as slow and short as a turn makes it
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* walkRhythm(W, { s0, turn, seed }) -> per step i (0-based, W.steps of them):
|
|
39
|
+
* lift[i] frames after f0 + 16 at which the foot lifts off (lift[0] = 0)
|
|
40
|
+
* air[i] frames that foot is in the air
|
|
41
|
+
* inc[i] metres it advances along the path (inc[0] = firstStep, the last one 0)
|
|
42
|
+
* height[i] its lift, m; arm[i] a factor on the arm swing it lands with
|
|
43
|
+
* W: the walk's fields with defaults; s0 the path position the first step starts from; turn(sa, sb)
|
|
44
|
+
* the degrees the heading turns between two path positions; seed a text naming this walk.
|
|
45
|
+
*/
|
|
46
|
+
export function walkRhythm(W, { s0, turn, seed }) {
|
|
47
|
+
const n = W.steps, v = clamp(W.vary ?? 1, 0, 2), next = noiseOf(seed);
|
|
48
|
+
const rows = Array.from({ length: n }, () => ({ time: next(), length: next(), lift: next(), arm: next() }));
|
|
49
|
+
const quick = next() < 0 ? 1 : -1, side = i => (i % 2 === 0 ? quick : -quick);
|
|
50
|
+
// one draw per stride (steps 1-2, 3-4, ...), after the step draws so those stay as they were
|
|
51
|
+
const strides = Array.from({ length: Math.ceil(n / 2) }, () => next()), strideOf = i => strides[Math.floor((i - 1) / 2)];
|
|
52
|
+
const inc0 = Array.from({ length: n }, (_, i) => (i === n - 1 ? 0 : i === 0 ? W.firstStep : W.step));
|
|
53
|
+
let s = s0;
|
|
54
|
+
const turned = inc0.map(d => { const deg = Math.abs(turn(s, s + d)); s += d; return Math.min(1, deg / TURN_FULL); });
|
|
55
|
+
const long = n >= 4;
|
|
56
|
+
// time: a share of the walk's steps * period frames per step, the shares summing to steps
|
|
57
|
+
const share = rows.map((r, i) => {
|
|
58
|
+
if (n < 2) return 1;
|
|
59
|
+
// the first and the last step vary half as much: their build-up and slow-down should show
|
|
60
|
+
const ends = i === 0 || i === n - 1 ? 0.5 : 1;
|
|
61
|
+
let x = ends * (TIME.noise * r.time + TIME.side * side(i)) + TIME.turn * turned[i];
|
|
62
|
+
if (i === 0) x += TIME.first * (1 - turned[0]);
|
|
63
|
+
if (i === 1 && long) x += TIME.second;
|
|
64
|
+
if (i === n - 1) x += TIME.last;
|
|
65
|
+
if (i === n - 2 && long) x += TIME.beforeLast;
|
|
66
|
+
return 1 + v * x;
|
|
67
|
+
});
|
|
68
|
+
const total = share.reduce((a, b) => a + b, 0), span = n * W.period;
|
|
69
|
+
const lift = [];
|
|
70
|
+
for (let i = 0, t = 0; i < n; i++) { lift.push(Math.round(t)); t += (span * share[i]) / total; }
|
|
71
|
+
// air: a long step swings slower, keeping the double support the walk gives every step (period -
|
|
72
|
+
// swing), so the trailing foot leaves the ground no further behind the hips than in an even walk (its
|
|
73
|
+
// knee would snap straight at toe-off); a short step keeps its swing and shortens the double
|
|
74
|
+
// support instead, the trailing heel rising before the other foot lands. The closing step swings at
|
|
75
|
+
// most CLOSE frames longer: the hips go ahead over the other foot while it swings, and a slower
|
|
76
|
+
// trailing foot falls out of the leg's reach; the time left over is standing still
|
|
77
|
+
const support = W.period - W.swing;
|
|
78
|
+
const air = lift.map((t, i) => {
|
|
79
|
+
if (v === 0) return W.swing;
|
|
80
|
+
const gap = (i < n - 1 ? lift[i + 1] : span) - t;
|
|
81
|
+
return Math.min(gap - 2, Math.max(W.swing, gap - support), i === n - 1 ? W.swing + CLOSE : Infinity);
|
|
82
|
+
});
|
|
83
|
+
// length: the middle strides (not the first, not the closing one) keep their sum
|
|
84
|
+
const mid = inc0.map((_, i) => i > 0 && i < n - 1);
|
|
85
|
+
const g = rows.map((r, i) => {
|
|
86
|
+
if (!mid[i]) return 1;
|
|
87
|
+
let x = LENGTH.noise * (0.7 * r.length + 0.3 * r.time) + LENGTH.side * side(i) + LENGTH.stride * strideOf(i) - LENGTH.turn * turned[i];
|
|
88
|
+
if (i === 1 && long) x -= LENGTH.second;
|
|
89
|
+
if (i === n - 2 && long) x -= LENGTH.beforeLast;
|
|
90
|
+
return 1 + v * x;
|
|
91
|
+
});
|
|
92
|
+
const count = mid.filter(Boolean).length, sum = g.reduce((a, b, i) => a + (mid[i] ? b : 0), 0);
|
|
93
|
+
const inc = inc0.map((d, i) => (mid[i] && v > 0 ? (d * g[i] * count) / sum : d));
|
|
94
|
+
const height = rows.map((r, i) => W.lift * (1 + v * (LIFT.noise * r.lift - (i === 0 ? LIFT.first : 0) - (i === n - 1 && n > 1 ? LIFT.last : 0))));
|
|
95
|
+
const arm = rows.map(r => 1 + v * ARM * r.arm);
|
|
96
|
+
return { lift, air, inc, height, arm };
|
|
97
|
+
}
|