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,275 @@
|
|
|
1
|
+
// The walk's hips, heel rise and swings (walk.js), hips first, the way a person walks:
|
|
2
|
+
// - fitPelvis: the hips' height, as high as the legs holding them allow with the knee a person keeps through
|
|
3
|
+
// stance. A leg reaching for the floor straightens in terminal swing (REACH_IN periods before its strike) and
|
|
4
|
+
// strikes a little bent; it gives as the weight comes on (loading, LOAD_SPAN periods) and is nearly straight at
|
|
5
|
+
// mid-stance. Once the hips pass over a planted foot it no longer holds them down: its heel rises instead
|
|
6
|
+
// (fitTrail). The height runs on a C2 cubic spline (no kink) through a trough at each heel strike and the highest
|
|
7
|
+
// point the limit allows a quarter to a half of the way to the next strike (a person's hips top out as they pass
|
|
8
|
+
// over the foot, then fall with the flat foot's reach); no half-step bends it harder than CURVE, and an extremum
|
|
9
|
+
// comes down until the whole curve stays under the limit. Across the floor the hips
|
|
10
|
+
// keep the walk's path (walk.js keys), blurred XZ_BLUR frames so their speed has no kink.
|
|
11
|
+
// - fitTrail: from the hips passing over a planted foot to its lift-off the heel rises, about the ball, just as
|
|
12
|
+
// far as the leg needs to reach with the stance knee and then the push-off knee (from walk.js's heel-off frame
|
|
13
|
+
// to KNEE_TOEOFF at lift-off), never lowered, never faster than PITCH_RATE a frame and never past PITCH_MAX
|
|
14
|
+
// (beyond about 60 deg the toes sit behind the ankle and QA reads a bent knee as bending backward); out of reach
|
|
15
|
+
// even then, the toe comes off the floor, the foot drawn toward the hip, as a person's does once the other foot
|
|
16
|
+
// is down.
|
|
17
|
+
// - fitSwing: each swing on a clamped cubic spline from that lift-off, through walk.js's swing keys, into the strike;
|
|
18
|
+
// the foot's pitch follows the shank on the way.
|
|
19
|
+
// The knee schedule eases with the walk's pace (brisk): a stroll strikes, loads and pushes off with straighter knees.
|
|
20
|
+
import { bakeChannels } from "./channels.js";
|
|
21
|
+
import { add, sub, scale, len, dot, apply, rx, ry, rz } from "./math.js";
|
|
22
|
+
|
|
23
|
+
// knee flexion (deg): at the strike, the loading peak, mid-stance, and at lift-off (TRAIL_POW shapes the push-off:
|
|
24
|
+
// slow while the other foot is in the air, fast once it lands); a quick walk (period down to FAST frames from SLOW)
|
|
25
|
+
// bends them FAST_MORE further, as a person's knees give more the faster they walk
|
|
26
|
+
const KNEE_STRIKE = 18, KNEE_LOAD = 22, KNEE_MID = 7, KNEE_TOEOFF = 45, TRAIL_POW = 1, LOAD_SPAN = 0.4, PITCH_MAX = 50, PITCH_RATE = 15;
|
|
27
|
+
const SLOW = 13, FAST = 9, FAST_MORE = { strike: 4, load: 4, mid: 3, toeOff: 5 };
|
|
28
|
+
// a slow walk keeps the knees straighter (a person strolling strikes and loads nearly straight-legged): the schedule
|
|
29
|
+
// eases from STROLL at a pace of PACE[0] (walk.js's pace: 1 = 0.5 m per 20 frames) up to the one above at PACE[1]
|
|
30
|
+
const STROLL = { strike: 2, load: 14, mid: 7, toeOff: 48, pow: 1.8 }, PACE = [1, 2.2];
|
|
31
|
+
/** 0 at a stroll's pace, 1 at a brisk walk's, smooth between. */
|
|
32
|
+
export const brisk = pace => { const t = Math.min(1, Math.max(0, ((pace ?? PACE[1]) - PACE[0]) / (PACE[1] - PACE[0]))); return t * t * (3 - 2 * t); };
|
|
33
|
+
const SOFT = 0.004, EASE = 8, BELOW_STAND = 0.008, REACH_IN = 0.35, MARGIN = 0.001, XZ_BLUR = 2;
|
|
34
|
+
// a strike's trough is the lowest limit from 2 frames before it to TROUGH_SPAN periods after (its loading)
|
|
35
|
+
const TROUGH_SPAN = 0.25, PEAK = [0.25, 0.5], CURVE = 0.008, smoother = t => t * t * t * (t * (6 * t - 15) + 10);
|
|
36
|
+
// the keys fitSwing writes, so a second pass reads walk.js's swing keys only
|
|
37
|
+
const SWUNG = new WeakSet();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Appends a root key per frame between f0 and end to `out` (the walk's keys). ground: per foot, its spans on
|
|
41
|
+
* the ground, { a, b, strike, heel }: from a (a heel strike, or f0) to b (its lift-off), heel the frame its
|
|
42
|
+
* heel starts up before that lift; start the src of keys before the first step. The keys keep the root's path across the
|
|
43
|
+
* floor; only its height changes.
|
|
44
|
+
*/
|
|
45
|
+
/** A foot pitched `deg` about a point on the ground (its heel or ball), heading yw: where the ankle goes. */
|
|
46
|
+
export const pivot = (p, yw, deg, from) => add(p, apply(ry(yw), apply(rx(deg), apply(ry(-yw), sub(from, p)))));
|
|
47
|
+
|
|
48
|
+
/** The leg's reach at a knee flexion, the hip joints from the hips, and the knee schedule of a walk of this period. */
|
|
49
|
+
function legOf(sk, period, pace) {
|
|
50
|
+
const I = sk.index, P = sk.restWorldPos;
|
|
51
|
+
const a = len(sub(P[I.LeftLeg], P[I.LeftUpLeg])), b = len(sub(P[I.LeftFoot], P[I.LeftLeg]));
|
|
52
|
+
const q = Math.min(1, Math.max(0, (SLOW - period) / (SLOW - FAST))), u = brisk(pace), mix = (s, k) => s + (k - s) * u;
|
|
53
|
+
const strike = mix(STROLL.strike, KNEE_STRIKE) + q * FAST_MORE.strike, load = mix(STROLL.load, KNEE_LOAD) + q * FAST_MORE.load;
|
|
54
|
+
const mid = mix(STROLL.mid, KNEE_MID) + q * FAST_MORE.mid, toeOff = mix(STROLL.toeOff, KNEE_TOEOFF) + q * FAST_MORE.toeOff;
|
|
55
|
+
return {
|
|
56
|
+
reach: deg => Math.sqrt(a * a + b * b + 2 * a * b * Math.cos((deg * Math.PI) / 180)),
|
|
57
|
+
hipOff: { l: sub(P[I.LeftUpLeg], P[I.Hips]), r: sub(P[I.RightUpLeg], P[I.Hips]) },
|
|
58
|
+
strike, load, mid, trail: u => mid + (toeOff - mid) * Math.min(1, Math.max(0, u)) ** mix(STROLL.pow, TRAIL_POW),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const hipAt = (ch, f, off) => add(ch.root[f], apply(ry(ch.yaw?.[f] ?? 0), apply(rz(ch.roll?.[f] ?? 0), off)));
|
|
62
|
+
|
|
63
|
+
export function fitPelvis(sk, out, { f0, end, period, pace, ground, start }) {
|
|
64
|
+
const I = sk.index, P = sk.restWorldPos;
|
|
65
|
+
const { reach, hipOff, strike, load, mid, trail } = legOf(sk, period, pace);
|
|
66
|
+
const lead = Math.max(2, Math.round(REACH_IN * period));
|
|
67
|
+
const knee = (g, f) => {
|
|
68
|
+
if (f < g.a) return strike;
|
|
69
|
+
if (g.heel !== undefined && f >= g.heel) return trail((f - g.heel) / Math.max(1, g.b - g.heel));
|
|
70
|
+
// loading: from the strike's knee up to the loading peak half way through LOAD_SPAN, then down to mid-stance's
|
|
71
|
+
const u = (f - g.a) / period / LOAD_SPAN;
|
|
72
|
+
return g.strike && u < 1 ? (u < 0.5 ? strike : mid) + (load - (u < 0.5 ? strike : mid)) * Math.sin(Math.PI * u) : mid;
|
|
73
|
+
};
|
|
74
|
+
const ch = bakeChannels(out, end + 1), top = P[I.Hips][1] - BELOW_STAND;
|
|
75
|
+
// the path across the floor, blurred twice (ends held), so the forward speed has no kink
|
|
76
|
+
let xz = ch.root.map(r => [r[0], r[2]]);
|
|
77
|
+
for (let pass = 0; pass < 2; pass++) xz = xz.map((p, f) => {
|
|
78
|
+
if (f <= f0 || f >= end) return p;
|
|
79
|
+
let sx = 0, sz = 0, w = 0;
|
|
80
|
+
for (let d = -3 * XZ_BLUR; d <= 3 * XZ_BLUR; d++) { const g = Math.min(end, Math.max(f0, f + d)), k = Math.exp(-(d * d) / (2 * XZ_BLUR * XZ_BLUR)); sx += k * xz[g][0]; sz += k * xz[g][1]; w += k; }
|
|
81
|
+
const t = Math.min(1, (f - f0) / EASE, (end - f) / EASE), e = t * t * (3 - 2 * t);
|
|
82
|
+
return [p[0] + (sx / w - p[0]) * e, p[1] + (sz / w - p[1]) * e];
|
|
83
|
+
});
|
|
84
|
+
// the highest the hips may be at frame f, eased in from the pose before the walk and out into the stand after it
|
|
85
|
+
const limit = f => {
|
|
86
|
+
const root = [xz[f][0], ch.root[f][1], xz[f][1]], yaw = ch.yaw?.[f] ?? 0, roll = ch.roll?.[f] ?? 0, ys = [];
|
|
87
|
+
for (const k of ["l", "r"]) {
|
|
88
|
+
const g = ground[k].find(x => f >= x.a - (x.strike ? lead : 0) && f <= x.b);
|
|
89
|
+
// once the hips pass over a planted foot it no longer holds them down: its heel rises as far as it must and
|
|
90
|
+
// past PITCH_MAX its toe leaves the floor (fitTrail)
|
|
91
|
+
if (!g || (g.free !== undefined && f >= g.free)) continue;
|
|
92
|
+
const ankle = ch[`foot_${k}`][f], off = apply(ry(yaw), apply(rz(roll), hipOff[k]));
|
|
93
|
+
const d = reach(knee(g, f)), dh = Math.hypot(root[0] + off[0] - ankle[0], root[2] + off[2] - ankle[2]);
|
|
94
|
+
if (dh < d) ys.push(ankle[1] + Math.sqrt(d * d - dh * dh) - off[1]);
|
|
95
|
+
}
|
|
96
|
+
if (!ys.length) return top;
|
|
97
|
+
const m = Math.min(...ys), fit = Math.min(top, m - SOFT * Math.log(ys.reduce((s, y) => s + Math.exp(-(y - m) / SOFT), 0))) - MARGIN;
|
|
98
|
+
const t = Math.min(1, (f - f0) / EASE, (end - f) / EASE), w = t * t * (3 - 2 * t);
|
|
99
|
+
return root[1] + (fit - root[1]) * w;
|
|
100
|
+
};
|
|
101
|
+
// the frame the hips pass over each planted foot (its ankle along the foot's heading)
|
|
102
|
+
for (const k of ["l", "r"]) for (const g of ground[k]) {
|
|
103
|
+
if (g.heel === undefined || !g.flat) continue;
|
|
104
|
+
const fw = apply(ry(g.yw), [0, 0, 1]);
|
|
105
|
+
let f = Math.max(f0 + 1, g.a);
|
|
106
|
+
while (f < g.heel && (xz[f][0] - g.flat[0]) * fw[0] + (xz[f][1] - g.flat[2]) * fw[2] < 0) f++;
|
|
107
|
+
g.free = f;
|
|
108
|
+
}
|
|
109
|
+
const low = [];
|
|
110
|
+
for (let f = f0; f <= end; f++) low[f] = limit(f);
|
|
111
|
+
// extrema: the pose before the walk, a trough over each strike's loading, the highest point between, the stand after
|
|
112
|
+
const loadSpan = Math.max(2, Math.round(TROUGH_SPAN * period)), lowest = (a, b) => { let m = a; for (let f = a; f <= b; f++) if (low[f] < low[m]) m = f; return m; };
|
|
113
|
+
const strikes = [...new Set(["l", "r"].flatMap(k => ground[k].filter(g => g.strike && g.a > f0 + 2 && g.a < end - 2).map(g => g.a)))].sort((p, q) => p - q);
|
|
114
|
+
const knots = [{ f: f0, y: low[f0] }];
|
|
115
|
+
strikes.forEach((a, n) => {
|
|
116
|
+
const t = lowest(Math.max(knots.at(-1).f + 2, a - 2), Math.min(end - 2, a + loadSpan));
|
|
117
|
+
// the peak between two troughs: of the frames PEAK of the way between them, the one whose highest smootherstep rise
|
|
118
|
+
// from the last trough and fall to this one both stay under the limit is highest (smootherstep stands in for the
|
|
119
|
+
// spline here: it is the flatter of the two at each end)
|
|
120
|
+
if (n > 0) {
|
|
121
|
+
const A = knots.at(-1), B = { f: t, y: low[t] }, span = B.f - A.f;
|
|
122
|
+
let best = null;
|
|
123
|
+
for (let c = Math.ceil(A.f + PEAK[0] * span); c <= Math.floor(A.f + PEAK[1] * span); c++) {
|
|
124
|
+
let top = low[c];
|
|
125
|
+
for (let f = A.f + 1; f < c; f++) { const w = smoother((f - A.f) / (c - A.f)); if (w > 1e-6) top = Math.min(top, A.y + (low[f] - A.y) / w); }
|
|
126
|
+
for (let f = c + 1; f < B.f; f++) { const w = smoother((f - c) / (B.f - c)); if (w < 1 - 1e-6) top = Math.min(top, (low[f] - B.y * w) / (1 - w)); }
|
|
127
|
+
if (!best || top > best.y) best = { f: c, y: top };
|
|
128
|
+
}
|
|
129
|
+
if (best && best.y > Math.max(A.y, B.y)) knots.push(best);
|
|
130
|
+
}
|
|
131
|
+
if (t > knots.at(-1).f) knots.push({ f: t, y: low[t] });
|
|
132
|
+
});
|
|
133
|
+
knots.push({ f: end, y: low[end] });
|
|
134
|
+
// no half-step bends the hips harder than CURVE (a spline's turn is about 2 rise / frames^2): a peak
|
|
135
|
+
// too high for the frames it has to the next trough comes down
|
|
136
|
+
for (let n = 1; n + 1 < knots.length; n++) {
|
|
137
|
+
const K = knots[n], room = Math.min(...[knots[n - 1], knots[n + 1]].map(o => o.y + (CURVE * (K.f - o.f) ** 2) / 2));
|
|
138
|
+
if (K.y > Math.max(knots[n - 1].y, knots[n + 1].y)) K.y = Math.min(K.y, room);
|
|
139
|
+
}
|
|
140
|
+
// the spline through the knots; a segment that rises past the limit lowers its higher knot by the excess
|
|
141
|
+
let y = [];
|
|
142
|
+
for (let pass = 0; pass < 30; pass++) {
|
|
143
|
+
let worst = 0;
|
|
144
|
+
const S = spline(knots.map(K => K.f), knots.map(K => K.y), 0, 0);
|
|
145
|
+
for (let n = 0; n + 1 < knots.length; n++) {
|
|
146
|
+
const A = knots[n], B = knots[n + 1];
|
|
147
|
+
let ex = 0;
|
|
148
|
+
for (let f = A.f; f <= B.f; f++) { y[f] = S(f); ex = Math.max(ex, y[f] - low[f]); }
|
|
149
|
+
if (ex > 0.0003) { const hi = A.y > B.y && n > 0 ? A : B.f === end ? A : B; if (hi.f !== f0 && hi.f !== end) { hi.y -= ex; worst = Math.max(worst, ex); } }
|
|
150
|
+
}
|
|
151
|
+
if (!worst) break;
|
|
152
|
+
}
|
|
153
|
+
for (let f = f0; f <= end; f++) y[f] = Math.min(y[f], low[f]);
|
|
154
|
+
// each key names the step it falls in, so QA's causes and hints still point at that step of the walk
|
|
155
|
+
const steps = out.filter(k => / step \d+$/.test(k.src ?? "")).sort((p, q) => p.f - q.f);
|
|
156
|
+
let i = 0, src = start;
|
|
157
|
+
for (let f = f0 + 1; f < end; f++) {
|
|
158
|
+
while (i < steps.length && steps[i].f <= f) src = steps[i++].src;
|
|
159
|
+
out.push({ f, set: { root: [xz[f][0], y[f], xz[f][1]] }, src });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The planted foot's heel rise, once the hips are set (fitPelvis): each frame from the hips passing over it to lift-off
|
|
165
|
+
* pitches the foot about its ball just far enough that the leg reaches it with the knee the stance keeps (mid-stance's,
|
|
166
|
+
* then the push-off's from walk.js's heel-off frame), so the heel comes up when the leg needs it to; never lowering the
|
|
167
|
+
* heel again and never past PITCH_MAX. Its keys replace walk.js's scheduled rise on the same frames.
|
|
168
|
+
*/
|
|
169
|
+
export function fitTrail(sk, out, { end, period, pace, ground }) {
|
|
170
|
+
const { reach, hipOff, trail } = legOf(sk, period, pace), ch = bakeChannels(out, end + 1);
|
|
171
|
+
const srcAt = new Map(out.map(x => [x.f, x.src]));
|
|
172
|
+
for (const k of ["l", "r"]) for (const g of ground[k]) {
|
|
173
|
+
if (g.free === undefined || !Number.isFinite(g.b) || g.b > end) continue;
|
|
174
|
+
// the pitch each frame needs (never lowering), then raised ahead of any rise faster than PITCH_RATE a frame
|
|
175
|
+
const need = [], frames = [];
|
|
176
|
+
let prev = 0;
|
|
177
|
+
for (let f = g.free; f <= g.b; f++) {
|
|
178
|
+
const hip = hipAt(ch, f, hipOff[k]), want = reach(trail((f - g.heel) / Math.max(1, g.b - g.heel)));
|
|
179
|
+
const far = p => len(sub(hip, pivot(g.ball, g.yw, p, g.flat))) - want;
|
|
180
|
+
let lo = prev, hi = PITCH_MAX;
|
|
181
|
+
if (far(lo) <= 0) hi = lo;
|
|
182
|
+
else if (far(hi) <= 0) for (let n = 0; n < 24; n++) { const m = (lo + hi) / 2; if (far(m) > 0) lo = m; else hi = m; }
|
|
183
|
+
prev = hi; need.push(hi); frames.push({ f, hip, want });
|
|
184
|
+
}
|
|
185
|
+
let pitch = need.map((_, i) => Math.max(...need.slice(i).map((p, j) => p - j * PITCH_RATE)));
|
|
186
|
+
// eased (never under the need): the rise starts and ends without a kink, which would jolt the knee
|
|
187
|
+
for (let pass = 0; pass < 2; pass++) pitch = pitch.map((p, i) => Math.max(need[i], ((pitch[i - 1] ?? 0) + p + (pitch[i + 1] ?? p)) / 3));
|
|
188
|
+
frames.forEach(({ f, hip, want }, i) => {
|
|
189
|
+
// out of reach even at PITCH_MAX: the toe comes off the floor, the foot drawn toward the hip to the push-off knee
|
|
190
|
+
let ankle = pivot(g.ball, g.yw, pitch[i], g.flat);
|
|
191
|
+
const gap = len(sub(hip, ankle)) - want;
|
|
192
|
+
if (gap > 0) ankle = add(ankle, scale(sub(hip, ankle), gap / len(sub(hip, ankle))));
|
|
193
|
+
out.push({ f, set: { [`foot_${k}`]: ankle, [`fpitch_${k}`]: pitch[i], [`fyaw_${k}`]: g.yw }, src: srcAt.get(f) });
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A clamped cubic spline through (xs, ys) with end slopes d0, dn: C2 inside, so a path through it has no kinks. */
|
|
199
|
+
function spline(xs, ys, d0, dn) {
|
|
200
|
+
const n = xs.length - 1, h = xs.slice(1).map((x, i) => x - xs[i]), a = [], b = [], c = [], r = [];
|
|
201
|
+
for (let i = 0; i <= n; i++) {
|
|
202
|
+
if (i === 0) { a[i] = 0; b[i] = 2 * h[0]; c[i] = h[0]; r[i] = 6 * ((ys[1] - ys[0]) / h[0] - d0); }
|
|
203
|
+
else if (i === n) { a[i] = h[n - 1]; b[i] = 2 * h[n - 1]; c[i] = 0; r[i] = 6 * (dn - (ys[n] - ys[n - 1]) / h[n - 1]); }
|
|
204
|
+
else { a[i] = h[i - 1]; b[i] = 2 * (h[i - 1] + h[i]); c[i] = h[i]; r[i] = 6 * ((ys[i + 1] - ys[i]) / h[i] - (ys[i] - ys[i - 1]) / h[i - 1]); }
|
|
205
|
+
}
|
|
206
|
+
for (let i = 1; i <= n; i++) { const m = a[i] / b[i - 1]; b[i] -= m * c[i - 1]; r[i] -= m * r[i - 1]; }
|
|
207
|
+
const M = []; M[n] = r[n] / b[n];
|
|
208
|
+
for (let i = n - 1; i >= 0; i--) M[i] = (r[i] - c[i] * M[i + 1]) / b[i];
|
|
209
|
+
return x => {
|
|
210
|
+
let i = 0; while (i < n - 1 && x > xs[i + 1]) i++;
|
|
211
|
+
const t = x - xs[i], H = h[i], A = (xs[i + 1] - x) / H, B = t / H;
|
|
212
|
+
return A * ys[i] + B * ys[i + 1] + ((A ** 3 - A) * M[i] + (B ** 3 - B) * M[i + 1]) * H * H / 6;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// the swing foot keeps the ankle near neutral, as a person's does: its pitch follows the shank (SWING_DORSI deg toes-up
|
|
217
|
+
// of it), eased in from the toe-off's pitch over FOLLOW_IN of the swing and out into the strike's over FOLLOW_OUT, the
|
|
218
|
+
// ball held BALL_CLEAR m over its flat height away from the ends, the pitch no more than PITCH_MAX (a foot kept level would flap at the ankle as the shank swings)
|
|
219
|
+
const SWING_DORSI = 3, FOLLOW_IN = 0.35, FOLLOW_OUT = 0.2, BALL_CLEAR = 0.03, HEEL_FREE = 0.04, HEEL_LOW = 0.025, deg = r => (r * 180) / Math.PI;
|
|
220
|
+
const ease = x => { const t = Math.min(1, Math.max(0, x)); return t * t * (3 - 2 * t); };
|
|
221
|
+
|
|
222
|
+
/** The shank's tilt (deg, + with the ankle behind the knee) of a leg from hip H to ankle A, thigh a, shank b, knee forward fw. */
|
|
223
|
+
function shankTilt(H, A, fw, a, b) {
|
|
224
|
+
const d = sub(A, H), L = Math.min(a + b - 1e-6, Math.max(Math.abs(a - b) + 1e-6, len(d))), u = scale(d, 1 / len(d));
|
|
225
|
+
let v = sub(fw, scale(u, dot(fw, u)));
|
|
226
|
+
v = scale(v, 1 / (len(v) || 1));
|
|
227
|
+
const c = (a * a + L * L - b * b) / (2 * a * L), K = add(H, add(scale(u, a * c), scale(v, a * Math.sqrt(Math.max(0, 1 - c * c))))), sh = sub(A, K);
|
|
228
|
+
return deg(Math.atan2(-dot(sh, fw), -sh[1]));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Each swing (lift-off to the next strike of that foot), once the toe-off is set (fitTrail): the ankle runs on a
|
|
233
|
+
* clamped cubic spline from the lift-off, at the speed the push-off leaves it with, through walk.js's swing keys
|
|
234
|
+
* (the heel kicked up behind, mid-swing, over the strike) into the strike at the heel rocker's speed, keyed every
|
|
235
|
+
* frame: the knee and the foot move without the kinks per-channel keys leave where the path turns. Given the skeleton
|
|
236
|
+
* (the last pass, the hips set), each frame's foot pitch follows the shank too.
|
|
237
|
+
*/
|
|
238
|
+
export function fitSwing(out, { end, ground, sk }) {
|
|
239
|
+
const ch = bakeChannels(out, end + 2), srcAt = new Map(out.map(x => [x.f, x.src]));
|
|
240
|
+
const I = sk?.index, P = sk?.restWorldPos, a = sk && len(sub(P[I.LeftLeg], P[I.LeftUpLeg])), b = sk && len(sub(P[I.LeftFoot], P[I.LeftLeg]));
|
|
241
|
+
for (const k of ["l", "r"]) {
|
|
242
|
+
const spans = ground[k], side = k === "l" ? "Left" : "Right";
|
|
243
|
+
const hipOff = sk && sub(P[I[`${side}UpLeg`]], P[I.Hips]), ball = sk && sub(P[I[`${side}ToeBase`]], P[I[`${side}Foot`]]), ballY = sk && P[I[`${side}ToeBase`]][1];
|
|
244
|
+
// QA's heel point from the ankle (qa/geometry.js solePoints)
|
|
245
|
+
const heel = sk && [0, 0.005 - P[I[`${side}Foot`]][1], -0.045];
|
|
246
|
+
for (let n = 0; n + 1 < spans.length; n++) {
|
|
247
|
+
const g = spans[n], h = spans[n + 1];
|
|
248
|
+
if (!Number.isFinite(g.b) || !h.strike || h.a > end || h.a - g.b < 3) continue;
|
|
249
|
+
const via = [...new Set(out.filter(x => !SWUNG.has(x) && x.f > g.b && x.f < h.a && x.set[`foot_${k}`]).map(x => x.f))].sort((p, q) => p - q);
|
|
250
|
+
const xs = [g.b, ...via, h.a], foot = ch[`foot_${k}`], at = f => foot[Math.max(0, Math.min(foot.length - 1, f))];
|
|
251
|
+
const v0 = sub(at(g.b), at(g.b - 1)), vn = sub(at(h.a + 1), at(h.a));
|
|
252
|
+
const axes = [0, 1, 2].map(i => spline(xs, xs.map(f => at(f)[i]), v0[i], vn[i]));
|
|
253
|
+
const yawAt = f => ch[`fyaw_${k}`]?.[f] ?? 0, pitchAt = f => ch[`fpitch_${k}`]?.[f] ?? 0;
|
|
254
|
+
const follow = (f, p) => shankTilt(hipAt(ch, f, hipOff), p, apply(ry(yawAt(f)), [0, 0, 1]), a, b) - SWING_DORSI;
|
|
255
|
+
const span = h.a - g.b, off0 = sk && pitchAt(g.b) - follow(g.b, at(g.b)), off1 = sk && pitchAt(h.a) - follow(h.a, at(h.a));
|
|
256
|
+
for (let f = g.b + 1; f < h.a; f++) {
|
|
257
|
+
const p = axes.map(e => e(f)), set = { [`foot_${k}`]: p };
|
|
258
|
+
if (sk) {
|
|
259
|
+
// never past PITCH_MAX (QA would read the bent knee as bending backward)
|
|
260
|
+
let pitch = Math.min(PITCH_MAX, follow(f, p) + off0 * (1 - ease((f - g.b) / (FOLLOW_IN * span))) + off1 * (1 - ease((h.a - f) / (FOLLOW_OUT * span))));
|
|
261
|
+
// the ball no lower than its flat height plus BALL_CLEAR (eased in and out over 3 frames at the ends)
|
|
262
|
+
const floor = ballY + BALL_CLEAR * ease(Math.min(f - g.b, h.a - f) / 3), ballAt = d => p[1] + apply(rx(d), ball)[1];
|
|
263
|
+
if (ballAt(pitch) < floor) { let lo = -30, hi = pitch; for (let it = 0; it < 20; it++) { const m = (lo + hi) / 2; if (ballAt(m) < floor) hi = m; else lo = m; } pitch = lo; }
|
|
264
|
+
// toes up lowers the heel: it comes no lower than HEEL_LOW (or the walk's keys, if they had it lower), so the strike
|
|
265
|
+
// lands when it did and the heel never skims the floor
|
|
266
|
+
const was = pitchAt(f), heelAt = d => p[1] + apply(rx(d), heel)[1], low = Math.min(HEEL_FREE, Math.max(heelAt(was), HEEL_LOW));
|
|
267
|
+
if (pitch < was && heelAt(pitch) < low) { let lo = pitch, hi = was; for (let it = 0; it < 20; it++) { const m = (lo + hi) / 2; if (heelAt(m) < low) lo = m; else hi = m; } pitch = hi; }
|
|
268
|
+
set[`fpitch_${k}`] = pitch;
|
|
269
|
+
}
|
|
270
|
+
const key = { f, set, src: srcAt.get(f) };
|
|
271
|
+
SWUNG.add(key); out.push(key);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// Generators expand into ordinary keys before baking. "walk" plans footsteps along a curving
|
|
2
|
+
// path from the pose the earlier keys leave at f0: weight shift over the stance foot, pelvis
|
|
3
|
+
// bob and twist, toe-off, arm swing, feet together at the end. The steps keep a natural rhythm
|
|
4
|
+
// (gait-rhythm.js): period, swing, step, lift and armSwing are the averages, vary: 0 makes every
|
|
5
|
+
// step exactly those.
|
|
6
|
+
//
|
|
7
|
+
// { type: "walk", f0, steps: 8, step: 0.5, period: 20, swing: 14, halfWidth: 0.08, lift: 0.08,
|
|
8
|
+
// heading: [h0, h1], turnDistance: 0.4, pelvisHigh: dy, pelvisLow: dy, firstStep: 0.4, armSwing: 14, vary: 1 }
|
|
9
|
+
// heading in degrees (yaw convention); pelvis heights are offsets from the standing hips height.
|
|
10
|
+
// The feet roll as a person's do, heel first (the three rockers of gait): the heel strikes with the
|
|
11
|
+
// toes up and the foot rolls down about the heel to flat; it stays flat while the shin passes over
|
|
12
|
+
// it; the heel comes up well before lift-off and the foot rolls over the ball, toes flat, to toe-off;
|
|
13
|
+
// in the air the foot follows the shin, the ankle near neutral (walk-pelvis.js fitSwing), into the next strike.
|
|
14
|
+
// The heel strikes with the toes up STRIKE_DF (a stroll's, then a brisk walk's), half that on the closing step (a walk generated from motion
|
|
15
|
+
// capture lands with the foot nearly flat: the heel touches a frame or two before the sole); TOE_OFF is the push-off pitch at a full stride (STRIDE_REF m of foot travel), less for
|
|
16
|
+
// a shorter one. A pivot is the very point QA reads (qa/geometry.js solePoints), so a rolling foot
|
|
17
|
+
// never counts as sliding.
|
|
18
|
+
// The upper body leads the legs and its sizes follow the pace (the speed against the default walk's 0.5 m per 20 frames),
|
|
19
|
+
// as a person's do: the slower the walk, the less the arms swing and the more the trunk dips and sways. Each arm peaks
|
|
20
|
+
// forward or back at a step's mid-swing (the arm's flow lag puts the visible peak about a quarter of a step before the
|
|
21
|
+
// strike), ARM_SWING deg a unit of pace (armSwing, when not given); each elbow bends with its swing, most with the arm
|
|
22
|
+
// forward and least behind (ELBOW +- ELBOW_SWING, both growing with the pace), and the hands hang with the wrists a
|
|
23
|
+
// little flexed (WRIST). The pelvis and chest turns, the side sway and the dip each peak at their own phase of the step
|
|
24
|
+
// (the constants below). All of it fades in over the first steps and back out on the closing one.
|
|
25
|
+
import { add, sub, scale, len, apply, ry, clamp } from "./math.js";
|
|
26
|
+
import { walkRhythm } from "./gait-rhythm.js";
|
|
27
|
+
import { fitPelvis, fitTrail, fitSwing, pivot, brisk } from "./walk-pelvis.js";
|
|
28
|
+
|
|
29
|
+
const STRIKE_DF = [6, 4], TOE_OFF = 50, STRIDE_REF = 1.1, ELBOW = [18, 3], ELBOW_SWING = [6, 3], WRIST = 8;
|
|
30
|
+
// The trunk over the stepping hips (degrees at a full stride, less for a shorter one): the pelvis drops on the
|
|
31
|
+
// swing side in single support (LIST), the trunk held over it but for SWAY of it; the pelvis turns with the leading
|
|
32
|
+
// leg (PELVIS_TURN) and the chest the other way (CHEST_TURN); the trunk dips forward before each strike and back in
|
|
33
|
+
// single support (LEAN).
|
|
34
|
+
const LIST = 4.5, SWAY = 0.26, PELVIS_TURN = 4.5, CHEST_TURN = 5.5, MID_SLOW = 0.015, STRIKE_AHEAD = [0.52, 0.64], HEEL_OFF = 0.3, HEEL_MIN = 4;
|
|
35
|
+
// the upper body by the pace p (module doc), measured against motion-capture walks of 0.7 and 1.4 m/s: the arm swing
|
|
36
|
+
// (ARM_SWING deg a unit of pace, at the strike ARM_FALL of its wave: a hanging arm falls back from its peak a little
|
|
37
|
+
// ahead of a sine); the chest turn peaking CHEST_AT(p) steps before the strike and the pelvis turn PELVIS_AT(p)
|
|
38
|
+
// steps after it (at a stroll the two turn nearly together, at a brisk walk against each other); the side sway SIDE(p)
|
|
39
|
+
// deg peaking SIDE_AT steps before the strike (just after the last); the dip LEAN(p) deg with its peaks LEAN_AT steps
|
|
40
|
+
// before the strike, about a forward lean of WALK_LEAN deg a unit of pace
|
|
41
|
+
const ARM_SWING = 6.4, ARM_FALL = 0.8, CHEST_AT = p => clamp(0.66 - 0.18 * p, 0.2, 0.48), PELVIS_AT = p => clamp(0.32 - 0.11 * p, 0, 0.45);
|
|
42
|
+
const SIDE = p => clamp(2.8 - 0.45 * p, 1.2, 3), SIDE_AT = 0.92, LEAN = p => clamp(4.7 - 1.1 * p, 1.8, 4), LEAN_AT = [0.2, 0.6], WALK_LEAN = 0.9;
|
|
43
|
+
// the swing's path: the heel kicks up right after toe-off (EARLY_AT of the swing, 2 frames at least: EARLY_FWD of the
|
|
44
|
+
// way to mid-swing, EARLY_LIFT m up at a full stride and a swing of KICK_AIR frames or more, KICK_STROLL of that at a stroll's
|
|
45
|
+
// pace: a person strolling folds the knee less), so the knee folds as the
|
|
46
|
+
// toe leaves; it comes down LAND_BACK of the stride behind and LAND_ABOVE m over the strike LAND_AT of the swing before
|
|
47
|
+
// it. A swing too short for a key leaves it out (the foot would jolt between keys a frame apart), and the closing step
|
|
48
|
+
// sets down without either. The heel takes HEEL_MIN frames at least to rise and rises no higher than PITCH_MAX: past about 60 deg
|
|
49
|
+
// the toes sit behind the ankle and QA reads a bent knee as bending backward.
|
|
50
|
+
const EARLY_AT = 0.18, EARLY_FWD = 0.35, EARLY_LIFT = 0.18, KICK_STROLL = 0.55, KICK_AIR = 10, LAND_AT = 0.22, LAND_BACK = 0.12, LAND_ABOVE = 0.03, PITCH_MAX = 50;
|
|
51
|
+
const fwd = h => apply(ry(h), [0, 0, 1]);
|
|
52
|
+
const leftOf = h => apply(ry(h), [1, 0, 0]);
|
|
53
|
+
const smoothstep = x => { const t = Math.min(1, Math.max(0, x)); return t * t * (3 - 2 * t); };
|
|
54
|
+
|
|
55
|
+
/** Last keyed value of every channel at or before frame f. */
|
|
56
|
+
export function stateAt(keys, f) {
|
|
57
|
+
const st = {};
|
|
58
|
+
for (const k of [...keys].sort((a, b) => a.f - b.f)) if (k.f <= f) Object.assign(st, k.set);
|
|
59
|
+
return st;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** gen -> keys; every key carries src (tag + step number) so QA can name the step it came from. */
|
|
63
|
+
export function walk(gen, keys, sk, tag = "walk") {
|
|
64
|
+
let src = `${tag} start`;
|
|
65
|
+
const out = [], at = (f, set) => { const key = { f, set, src }; out.push(key); return key; };
|
|
66
|
+
const st = stateAt(keys, gen.f0);
|
|
67
|
+
if (!st.foot_l || !st.foot_r || !st.root) throw new Error("MOTION_SPEC_INVALID: walk needs foot_l, foot_r and root keyed before f0");
|
|
68
|
+
if (gen.vary !== undefined && !(Number.isFinite(gen.vary) && gen.vary >= 0 && gen.vary <= 2)) throw new Error(`MOTION_SPEC_INVALID: ${tag} vary must be a number from 0 (every step the same) to 2, got ${JSON.stringify(gen.vary)}`);
|
|
69
|
+
const I = sk.index, P = sk.restWorldPos;
|
|
70
|
+
const ank = P[I.LeftFoot][1], standY = P[I.Hips][1];
|
|
71
|
+
const footRest = { l: sub(P[I.LeftToeBase], P[I.LeftFoot]), r: sub(P[I.RightToeBase], P[I.RightFoot]) };
|
|
72
|
+
// QA's heel point from the ankle of a flat foot at yaw 0; a foot pitched `deg` about a point on the ground
|
|
73
|
+
const heelOff = [0, 0.005 - ank, -0.045];
|
|
74
|
+
const flatAt = { l: -Infinity, r: -Infinity };
|
|
75
|
+
// Pelvis heights from the leg length: straight-ish over the stance foot, low enough at
|
|
76
|
+
// double support that the trailing leg still reaches (2% knee slack).
|
|
77
|
+
const leg = len(sub(P[I.LeftLeg], P[I.LeftUpLeg])) + len(sub(P[I.LeftFoot], P[I.LeftLeg]));
|
|
78
|
+
const hipDrop = P[I.Hips][1] - P[I.LeftUpLeg][1], step0 = gen.step ?? 0.5, hw = gen.halfWidth ?? 0.08;
|
|
79
|
+
const hipsAt = horiz => Math.sqrt(Math.max(0, (0.98 * leg) ** 2 - horiz ** 2)) + ank + hipDrop - standY;
|
|
80
|
+
const W = { steps: 8, step: step0, period: 20, swing: 14, halfWidth: hw, lift: 0.08, turnDistance: 0.4, firstStep: 0.4,
|
|
81
|
+
pelvisHigh: Math.min(-0.005, hipsAt(Math.hypot(0.03, hw - 0.035))), pelvisLow: hipsAt(Math.hypot(step0 / 2, hw)), vary: 1, ...gen };
|
|
82
|
+
const pace = (W.step / W.period) / (0.5 / 20);
|
|
83
|
+
W.armSwing = gen.armSwing ?? clamp(ARM_SWING * pace, 4, 22);
|
|
84
|
+
const chestAt = CHEST_AT(pace), pelvisAt = PELVIS_AT(pace), sideSway = SIDE(pace), dipBy = LEAN(pace), walkLean = clamp(WALK_LEAN * pace, 0, 3);
|
|
85
|
+
const elbow = ELBOW[0] + ELBOW[1] * Math.min(pace, 2.5), elbowSwing = ELBOW_SWING[0] + ELBOW_SWING[1] * Math.min(pace, 2.5);
|
|
86
|
+
const [h0, h1] = W.heading ?? [st.yaw ?? 0, st.yaw ?? 0];
|
|
87
|
+
const hs = s => h0 + (h1 - h0) * smoothstep(s / W.turnDistance);
|
|
88
|
+
const c0 = scale(add(st.foot_l, st.foot_r), 0.5); c0[1] = 0;
|
|
89
|
+
const path = [c0];
|
|
90
|
+
for (let i = 0; i < 4000; i++) path.push(add(path[i], scale(fwd(hs(i * 0.01)), 0.01)));
|
|
91
|
+
const C = s => path[Math.max(0, Math.min(path.length - 1, Math.round(s / 0.01)))];
|
|
92
|
+
const footAt = (s, k) => { const p = add(C(s), scale(leftOf(hs(s)), (k === "l" ? 1 : -1) * W.halfWidth)); p[1] = ank; return p; };
|
|
93
|
+
const stance = { l: -0.07, r: 0.07 }, state = { foot_l: st.foot_l, foot_r: st.foot_r, fyaw_l: st.fyaw_l ?? h0, fyaw_r: st.fyaw_r ?? h0 };
|
|
94
|
+
const holdAll = ["root", "yaw", "roll", "foot_l", "foot_r", "fyaw_l", "fyaw_r", "spine_yaw", "spine_roll", "spine_pitch", "aswing_l", "aswing_r", "abend_l", "abend_r",
|
|
95
|
+
"wflex_l", "wflex_r", "head_yaw"];
|
|
96
|
+
const r0 = st.roll ?? 0, sr0 = st.spine_roll ?? 0, sp0 = st.spine_pitch ?? 0;
|
|
97
|
+
// each foot's spans on the ground (fitPelvis): from a heel strike (or f0) to its lift-off, heel the frame its heel comes up
|
|
98
|
+
const ground = { l: [{ a: gen.f0, b: Infinity, strike: false }], r: [{ a: gen.f0, b: Infinity, strike: false }] };
|
|
99
|
+
at(gen.f0, Object.fromEntries(holdAll.map(ch => [ch, st[ch] ?? state[ch] ?? (ch === "yaw" ? h0 : 0)])));
|
|
100
|
+
let sLast = stance.r, prev = null;
|
|
101
|
+
const R = walkRhythm(W, { s0: sLast, turn: (a, b) => hs(b) - hs(a),
|
|
102
|
+
seed: [gen.f0, W.steps, W.step, W.period, W.swing, W.firstStep, h0, h1, c0[0].toFixed(3), c0[2].toFixed(3)].join("|") });
|
|
103
|
+
for (let i = 0; i < W.steps; i++) {
|
|
104
|
+
src = `${tag} step ${i + 1}`;
|
|
105
|
+
const k = i % 2 === 0 ? "l" : "r", other = k === "l" ? "r" : "l", last = i === W.steps - 1;
|
|
106
|
+
const sNew = last ? sLast : sLast + R.inc[i];
|
|
107
|
+
const ts = gen.f0 + 16 + R.lift[i], tl = ts + R.air[i], tm = ts + Math.floor(R.air[i] / 2);
|
|
108
|
+
// gait initiation: move the weight over the stance foot before the first heel comes up
|
|
109
|
+
if (i === 0) {
|
|
110
|
+
const pre = add(C(stance[other]), scale(leftOf(h0), (other === "l" ? 1 : -1) * W.halfWidth * 0.75)); pre[1] = st.root[1];
|
|
111
|
+
at(ts - 4, { root: pre, yaw: h0 });
|
|
112
|
+
at(ts, { arm_hang: 1 });
|
|
113
|
+
}
|
|
114
|
+
// a longer stride pushes off more; the closing step pushes and strikes less
|
|
115
|
+
const size = clamp(Math.abs(sNew - stance[k]) / STRIDE_REF, 0.3, 1.15), toeOff = TOE_OFF * (0.55 + 0.45 * size) * (last ? 0.8 : 1);
|
|
116
|
+
// forefoot rocker: the heel comes up HEEL_OFF of a step before lift-off (a little sooner on the first
|
|
117
|
+
// step, the weight only just over the other foot) and the foot rolls over the ball, slowly
|
|
118
|
+
// while the other foot is still in the air, then fast to toe-off
|
|
119
|
+
const ankle = state[`foot_${k}`], yw = state[`fyaw_${k}`], ball = add(ankle, apply(ry(yw), footRest[k]));
|
|
120
|
+
const tHeel = Math.max(ts - Math.max(HEEL_MIN, Math.round((i === 0 ? 0.35 : HEEL_OFF) * W.period)), flatAt[k] + 1, gen.f0 + 1);
|
|
121
|
+
Object.assign(ground[k].at(-1), { b: ts, heel: tHeel, ball, yw, flat: ankle });
|
|
122
|
+
for (let f = tHeel; f <= ts; f++) {
|
|
123
|
+
const d = toeOff * ((f - tHeel) / Math.max(1, ts - tHeel)) ** 1.8;
|
|
124
|
+
at(f, { [`foot_${k}`]: pivot(ball, yw, d, ankle), [`fpitch_${k}`]: d, [`fyaw_${k}`]: yw });
|
|
125
|
+
}
|
|
126
|
+
// swing: the foot rises behind first (the knee folds right after toe-off), the toes trailing down, level at
|
|
127
|
+
// mid-swing, and comes down onto the heel from above (the toes up) instead of skimming the floor
|
|
128
|
+
const lead = Math.max(1, Math.round(LAND_AT * R.air[i]));
|
|
129
|
+
const mid = footAt((stance[k] + sNew) / 2, k); mid[1] += R.height[i];
|
|
130
|
+
const off = pivot(ball, yw, Math.min(toeOff, PITCH_MAX), ankle), rise = add(off, scale(sub(mid, off), EARLY_FWD));
|
|
131
|
+
// a shorter swing kicks lower (the same rise in fewer frames would jolt the foot): by its length against KICK_AIR
|
|
132
|
+
// and with the step's own lift (gait-rhythm), so no two swings fold the knee alike
|
|
133
|
+
rise[1] = off[1] + EARLY_LIFT * size * Math.min(1, (R.air[i] / KICK_AIR) ** 2) * (KICK_STROLL + (1 - KICK_STROLL) * brisk(pace)) * (R.height[i] / W.lift);
|
|
134
|
+
// in a turn the line from the toe-off to mid-swing cuts in toward the other foot: the kick stays out at mid-swing's side
|
|
135
|
+
const outward = scale(leftOf(hs(stance[other])), k === "l" ? 1 : -1), foot = state[`foot_${other}`];
|
|
136
|
+
const side = p => (p[0] - foot[0]) * outward[0] + (p[2] - foot[2]) * outward[2], inside = side(mid) - side(rise);
|
|
137
|
+
if (inside > 0) { rise[0] += outward[0] * inside; rise[2] += outward[2] * inside; }
|
|
138
|
+
const early = ts + Math.max(2, Math.round(EARLY_AT * R.air[i]));
|
|
139
|
+
if (!last && early < tm - 1) at(early, { [`foot_${k}`]: rise, [`fpitch_${k}`]: 0.7 * Math.min(toeOff, PITCH_MAX) });
|
|
140
|
+
at(tm, { [`foot_${k}`]: mid, [`fyaw_${k}`]: hs((stance[k] + sNew) / 2), [`fpitch_${k}`]: 0 });
|
|
141
|
+
// at the strike the hips are past the middle of the stride (STRIKE_AHEAD: about half way at a stroll, well past it
|
|
142
|
+
// at a brisk walk, so a slow walk's hips dip between spread legs), over the landing foot's side;
|
|
143
|
+
// between strikes they cross the floor at an even pace, a little slower over mid-stance (MID_SLOW of the step)
|
|
144
|
+
const sLand = stance[other] + (STRIKE_AHEAD[0] + (STRIKE_AHEAD[1] - STRIKE_AHEAD[0]) * brisk(pace)) * (sNew - stance[other]);
|
|
145
|
+
const sEven = prev ? prev.s + (sLand - prev.s) * ((tm - prev.f) / (tl - prev.f)) : null;
|
|
146
|
+
const sMid = sEven === null ? stance[other] + 0.03 : sEven - MID_SLOW * Math.abs(sNew - stance[k]) / 2, shift = i === 0 ? 0.015 : 0.032;
|
|
147
|
+
const pm = add(C(sMid), scale(leftOf(hs(sMid)), (other === "l" ? 1 : -1) * shift)); pm[1] = standY + W.pelvisHigh;
|
|
148
|
+
// a stride longer than the nominal one drops the hips a little more at double support, so the trailing leg still reaches
|
|
149
|
+
const low = gen.pelvisLow ?? (R.inc[i] > step0 ? Math.min(W.pelvisLow, hipsAt(Math.hypot(R.inc[i] / 2, hw))) : W.pelvisLow);
|
|
150
|
+
const pl = C(sLand).slice(); pl[1] = standY + (last ? W.pelvisHigh : low);
|
|
151
|
+
prev = { f: tl, s: sLand };
|
|
152
|
+
// heel rocker: the toes come up before the strike, then the foot rolls down about the heel to flat
|
|
153
|
+
const land = footAt(sNew, k), lyaw = hs(sNew), heel = add(land, apply(ry(lyaw), heelOff));
|
|
154
|
+
const strike = -(STRIKE_DF[0] + (STRIKE_DF[1] - STRIKE_DF[0]) * brisk(pace)) * (last ? 0.5 : 1), nFlat = Math.max(2, Math.round(0.2 * W.period));
|
|
155
|
+
const touch = pivot(heel, lyaw, strike, land), above = add(touch, scale(fwd(lyaw), -LAND_BACK * Math.abs(sNew - stance[k])));
|
|
156
|
+
above[1] += LAND_ABOVE;
|
|
157
|
+
if (!last && lead > 1 && tl - lead > tm + 1) at(tl - lead, { [`foot_${k}`]: above, [`fpitch_${k}`]: 0.6 * strike });
|
|
158
|
+
for (let j = 0; j <= nFlat; j++) {
|
|
159
|
+
const d = strike * (1 - smoothstep(j / nFlat));
|
|
160
|
+
at(tl + j, { [`foot_${k}`]: pivot(heel, lyaw, d, land), [`fpitch_${k}`]: d, [`fyaw_${k}`]: lyaw });
|
|
161
|
+
}
|
|
162
|
+
flatAt[k] = tl + nFlat;
|
|
163
|
+
ground[k].push({ a: tl, b: Infinity, strike: true });
|
|
164
|
+
state[`foot_${k}`] = land; state[`fyaw_${k}`] = lyaw; stance[k] = sNew;
|
|
165
|
+
const swing = (last ? 0 : i < 3 ? [0, 0.3, 0.7][i] : 1) * R.arm[i], sg = k === "l" ? 1 : -1;
|
|
166
|
+
const sp = clamp(size, 0.4, 1.1), ramp = last ? 0 : i === 0 ? 0.5 : 1, list = LIST * sp * ramp * (other === "l" ? 1 : -1);
|
|
167
|
+
// the upper body leads the legs (module doc): the arms, the turns and the side sway are waves keyed at mid-swing (tm, dm
|
|
168
|
+
// of a step before the strike) and at the strike, the arms peaking at tm: a wave peaking `peak` steps before the strike,
|
|
169
|
+
// scaled so the larger of its two keys is 1 (the interpolation does not overshoot its keys); the dip is keyed at its peaks
|
|
170
|
+
const dm = (tl - tm) / W.period, wave = peak => { const v = [Math.cos(Math.PI * (peak - dm)), Math.cos(Math.PI * peak)]; return v.map(x => x / Math.max(...v.map(Math.abs))); };
|
|
171
|
+
const pelvis = wave(-pelvisAt).map(v => -PELVIS_TURN * sp * sg * swing * v), chest = wave(chestAt).map(v => CHEST_TURN * sp * sg * swing * v);
|
|
172
|
+
const sway = wave(SIDE_AT).map(v => -Math.sign(list) * sideSway * sp * ramp * v);
|
|
173
|
+
const arm = wave(dm).map((v, j) => (j ? ARM_FALL * v : v)), armAt = v => ({ aswing_l: -0.85 * W.armSwing * sg * swing * v,
|
|
174
|
+
aswing_r: W.armSwing * sg * swing * v, abend_l: swing * (elbow - sg * elbowSwing * v), abend_r: swing * (elbow + sg * elbowSwing * v) });
|
|
175
|
+
at(tm, { root: pm, yaw: hs(sMid) + pelvis[0], roll: r0 + list, spine_roll: sr0 - list * (1 + SWAY) + sway[0], spine_yaw: chest[0] - pelvis[0],
|
|
176
|
+
...armAt(arm[0]), wflex_l: WRIST * swing, wflex_r: WRIST * swing });
|
|
177
|
+
at(tl, { root: pl, yaw: hs(sLand) + pelvis[1], roll: r0, spine_roll: sr0 + sway[1], spine_yaw: chest[1] - pelvis[1], ...armAt(arm[1]), head_yaw: 0 });
|
|
178
|
+
const lean = sp0 + walkLean * ramp, dip = dipBy * sp * ramp;
|
|
179
|
+
at(Math.max(gen.f0 + 1, tl - Math.round(LEAN_AT[1] * W.period)), { spine_pitch: lean - 0.6 * dip });
|
|
180
|
+
at(Math.max(gen.f0 + 1, tl - Math.round(LEAN_AT[0] * W.period)), { spine_pitch: lean + dip });
|
|
181
|
+
sLast = sNew;
|
|
182
|
+
}
|
|
183
|
+
const tend = gen.f0 + 16 + W.steps * W.period + 18;
|
|
184
|
+
// the hips' height from the legs on the ground, unless the walk gives its own pelvis heights
|
|
185
|
+
// (only up to the walk's last key: after it the settle just holds, and a take may end there, walkTo)
|
|
186
|
+
const settle = Math.max(...out.map(k => k.f));
|
|
187
|
+
// each swing's path (from the planned toe-off), the hips over it, the heel rise they leave the trailing foot, and
|
|
188
|
+
// each swing again from where that foot now lifts (its approach to the strike, which the hips saw, unchanged)
|
|
189
|
+
if (gen.pelvisHigh === undefined && gen.pelvisLow === undefined) {
|
|
190
|
+
fitSwing(out, { end: settle, ground });
|
|
191
|
+
fitPelvis(sk, out, { f0: gen.f0, end: settle, period: W.period, pace, ground, start: `${tag} start` });
|
|
192
|
+
fitTrail(sk, out, { end: settle, period: W.period, pace, ground });
|
|
193
|
+
fitSwing(out, { end: settle, ground, sk });
|
|
194
|
+
}
|
|
195
|
+
src = `${tag} end`;
|
|
196
|
+
at(tend, stateAt(out, tend));
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function expandGenerators(spec, sk) {
|
|
201
|
+
let keys = (spec.keys ?? []).map((k, i) => (k.src ? k : { ...k, src: `keys[${i}]` }));
|
|
202
|
+
(spec.generators ?? []).forEach((gen, i) => {
|
|
203
|
+
if (gen.type !== "walk") throw new Error(`MOTION_SPEC_INVALID: generators[${i}] has unknown type "${gen.type}" (only "walk")`);
|
|
204
|
+
keys = keys.concat(walk(gen, keys, sk, `generators[${i}] walk`));
|
|
205
|
+
});
|
|
206
|
+
return keys;
|
|
207
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The wrist an elbow turn leaves (fix.self, lane selfwrist). fix/self.js turns an elbow about the shoulder-wrist
|
|
2
|
+
// line to take an arm out of the body, and limbs.js swivelElbow keeps the hand's world turn while it does. With
|
|
3
|
+
// the elbow bent (a cigarette at the lips) that line runs well off the forearm, so the turn lands on the wrist
|
|
4
|
+
// as flex and deviation: the car exit's smoking hand went 57 deg toward the thumb (limit 20) on 88 frames, the
|
|
5
|
+
// squat smoke 56, the kits 26-57 (QA WRIST). No turn of the elbow both clears the body and keeps that wrist in
|
|
6
|
+
// range (the car exit at f500: 45 deg leaves the forearm 2.8 cm in the chest at 31 deg, 75 deg clears it at 57).
|
|
7
|
+
// So the wrist stops at its limits and the hand turns on with the elbow for the rest, as a wrist does; a fit
|
|
8
|
+
// (the filter at the lips) then moves the wrist until its point is back on its target (compile.js runs the turn
|
|
9
|
+
// in the fit's passes too). A wrist the turn leaves within its limits is not touched: the hand keeps its world
|
|
10
|
+
// turn exactly as before.
|
|
11
|
+
import { add, sub, scale, norm, mul, T, apply, axisRot } from "./math.js";
|
|
12
|
+
import { rotateSubtree } from "./skeleton.js";
|
|
13
|
+
import { palmAxes } from "./limbs.js";
|
|
14
|
+
import { WRIST_LIMITS, wristNeed } from "./arm-orient.js";
|
|
15
|
+
|
|
16
|
+
const clampTo = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* clampWrist(sk, s, side): the hand of `side` turned back within WRIST_LIMITS on its forearm (twist, flex and
|
|
20
|
+
* deviation each clamped, the hand alone: the forearm bone keeps its turn). Returns { need, got } in degrees
|
|
21
|
+
* when it turned the hand, null when the wrist was within its limits.
|
|
22
|
+
*/
|
|
23
|
+
export function clampWrist(sk, s, side) {
|
|
24
|
+
const I = sk.index, h = I[`${side}Hand`], fa = I[`${side}ForeArm`], rw = sk.restWorldRot;
|
|
25
|
+
const axT = norm(sub(s.P[h], s.P[fa])), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[h]));
|
|
26
|
+
const { axF, axD, ...need } = wristNeed(neutral, s.R[h], axT, apply(mul(neutral, T(s.R[h])), palmAxes(sk, s, side).l));
|
|
27
|
+
const got = Object.fromEntries(Object.entries(need).map(([k, v]) => [k, clampTo(v, WRIST_LIMITS[k])]));
|
|
28
|
+
if (Object.keys(need).every(k => Math.abs(need[k] - got[k]) < 1e-6)) return null;
|
|
29
|
+
const want = mul(mul(axisRot(add(scale(axF, got.flex), scale(axD, got.dev)), Math.hypot(got.flex, got.dev)), axisRot(axT, got.twist)), neutral);
|
|
30
|
+
rotateSubtree(sk, s, h, s.P[h].slice(), mul(want, T(s.R[h])));
|
|
31
|
+
return { need, got };
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// C7. What changed against a base spec's QA (lane D2). Groups match by severity.js groupKey
|
|
2
|
+
// (kind + subject with the numbers as "#"); groups that share a key are summed into one entry.
|
|
3
|
+
// resolved a key in the base only
|
|
4
|
+
// new a key in this compile only
|
|
5
|
+
// worse a key in both whose severity rose by DELTA_STEP (5 %) or more of the base severity
|
|
6
|
+
// better a key in both whose severity fell by DELTA_STEP or more
|
|
7
|
+
// keys lists at most DELTA_KEYS groupKeys of resolved, new and worse, most severe first; the
|
|
8
|
+
// counts are always complete.
|
|
9
|
+
import { groupKey, severityOf } from "./qa/severity.js";
|
|
10
|
+
|
|
11
|
+
const DELTA_STEP = 0.05;
|
|
12
|
+
const DELTA_KEYS = 6;
|
|
13
|
+
|
|
14
|
+
/** groups -> Map(groupKey -> summed severity), in first-seen order. */
|
|
15
|
+
function byKey(groups) {
|
|
16
|
+
const m = new Map();
|
|
17
|
+
for (const g of groups ?? []) {
|
|
18
|
+
const k = groupKey(g);
|
|
19
|
+
m.set(k, (m.get(k) ?? 0) + (Number.isFinite(g.severity) ? g.severity : severityOf(g)));
|
|
20
|
+
}
|
|
21
|
+
return m;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The first DELTA_KEYS keys of [key, weight] rows, the heaviest first (ties keep their order). */
|
|
25
|
+
const top = rows => rows.map((r, i) => [...r, i]).sort((a, b) => b[1] - a[1] || a[2] - b[2]).slice(0, DELTA_KEYS).map(r => r[0]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* deltaOf(baseGroups, groups, baseId) ->
|
|
29
|
+
* { base, resolved, new, worse, better, keys: { resolved: [], new: [], worse: [] } }
|
|
30
|
+
* @param {object[]} baseGroups the base spec's full qa.groups (spec store parts)
|
|
31
|
+
* @param {object[]} groups this compile's qa.groups
|
|
32
|
+
* @param {string} baseId
|
|
33
|
+
*/
|
|
34
|
+
export function deltaOf(baseGroups, groups, baseId) {
|
|
35
|
+
const before = byKey(baseGroups), after = byKey(groups);
|
|
36
|
+
const resolved = [], added = [], worse = [];
|
|
37
|
+
let better = 0;
|
|
38
|
+
for (const [k, s] of before) if (!after.has(k)) resolved.push([k, s]);
|
|
39
|
+
for (const [k, s] of after) {
|
|
40
|
+
if (!before.has(k)) { added.push([k, s]); continue; }
|
|
41
|
+
const b = before.get(k), step = DELTA_STEP * Math.max(Math.abs(b), 0.01);
|
|
42
|
+
if (s - b >= step - 1e-9) worse.push([k, s - b]);
|
|
43
|
+
else if (b - s >= step - 1e-9) better += 1;
|
|
44
|
+
}
|
|
45
|
+
return { base: baseId, resolved: resolved.length, new: added.length, worse: worse.length, better,
|
|
46
|
+
keys: { resolved: top(resolved), new: top(added), worse: top(worse) } };
|
|
47
|
+
}
|