cozyclay 2.1.0 → 3.0.0
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 +65 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- 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/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 +22 -6
- 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-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/llms.txt +14 -0
- 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 +174 -2639
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- 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 +8 -6
- 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 +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- 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 +51 -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 +46 -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 +59 -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 +48 -419
- 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 +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -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 +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -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 +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -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 +174 -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 +173 -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/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 +121 -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 +179 -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 +42 -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/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -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 +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -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/PoseImagePanel.jsx +1 -1
- 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/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 +11 -53
- package/src/shell/CompositionGuideButton.jsx +20 -0
- 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/ShotPreviewHeader.jsx +17 -0
- package/src/shell/TopBar.jsx +5 -83
- package/src/shell/ViewportToolbar.jsx +23 -93
- 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/shell/viewport.css +12 -0
- 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 +12 -14
- package/src/studio-agent-commands.js +46 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +46 -73
- package/src/studio-app-binding.js +29 -45
- 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 +8 -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 +107 -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/headless-studio.mjs +85 -0
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +33 -83
- 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 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-B41FmPYI.js +0 -1
- package/dist/assets/app-Do-Og5Pu.css +0 -1
- package/dist/assets/app-DwP-tl2x.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-Dt0qZfS7.js +0 -1
- package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-jqMhqL88.js +0 -1
- package/dist/assets/workflow-Br-K7Srs.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 -159
- 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,117 @@
|
|
|
1
|
+
// Open parameters: any number in a spec (generator params, key channel values or vector
|
|
2
|
+
// components, anchor offsets, skill args) may be written as { range: [lo, hi] } (or
|
|
3
|
+
// { search: [lo, hi] }, + int: true). motion.compile then searches the values deterministically:
|
|
4
|
+
// the midpoint first, a grid (one parameter) or a Halton set (several), then a shrinking pattern
|
|
5
|
+
// search around the best, at most spec.search.budget (default 32, max 64) compiles, stopping at
|
|
6
|
+
// the first value with QA 0. Each trial compiles only the frames the parameters can change.
|
|
7
|
+
import { compileMotion } from "./compiler/compile.js";
|
|
8
|
+
import { bakeChannels, flowOptions } from "./compiler/channels.js";
|
|
9
|
+
import { expandGenerators } from "./compiler/walk.js";
|
|
10
|
+
import { runMotionQa } from "./qa/index.js";
|
|
11
|
+
import { weightOf } from "./qa/severity.js";
|
|
12
|
+
|
|
13
|
+
const INT_FIELDS = new Set(["f", "f0", "steps", "period", "swing", "until", "reach", "hold", "release"]);
|
|
14
|
+
const isOpen = v => v && typeof v === "object" && !Array.isArray(v) && (v.range ?? v.search) !== undefined && Object.keys(v).every(k => ["range", "search", "int"].includes(k));
|
|
15
|
+
const pathText = path => path.reduce((s, p) => (typeof p === "number" ? `${s}[${p}]` : s ? `${s}.${p}` : p), "");
|
|
16
|
+
|
|
17
|
+
/** Every { range } in the spec: [{ path, lo, hi, int }]. */
|
|
18
|
+
export function findParams(spec) {
|
|
19
|
+
const out = [];
|
|
20
|
+
const walk = (v, path) => {
|
|
21
|
+
if (isOpen(v)) {
|
|
22
|
+
const r = v.range ?? v.search;
|
|
23
|
+
if (!(Array.isArray(r) && r.length === 2 && r.every(Number.isFinite) && r[0] < r[1])) throw new Error(`MOTION_SPEC_INVALID: ${pathText(path)} range must be [lo, hi] with lo < hi, got ${JSON.stringify(r)}`);
|
|
24
|
+
out.push({ path, lo: r[0], hi: r[1], int: v.int ?? INT_FIELDS.has(path[path.length - 1]) });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(v)) v.forEach((x, i) => walk(x, [...path, i]));
|
|
28
|
+
else if (v && typeof v === "object") for (const [k, x] of Object.entries(v)) walk(x, [...path, k]);
|
|
29
|
+
};
|
|
30
|
+
for (const [k, v] of Object.entries(spec ?? {})) if (!["schema", "fps", "frames", "search"].includes(k)) walk(v, [k]);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const snap = (p, x) => { const v = Math.min(p.hi, Math.max(p.lo, x)); return p.int ? Math.round(v) : Math.round(v * 1e4) / 1e4; };
|
|
35
|
+
|
|
36
|
+
function resolveParams(spec, params, values) {
|
|
37
|
+
const s = structuredClone(spec);
|
|
38
|
+
params.forEach((p, i) => { let o = s; for (const k of p.path.slice(0, -1)) o = o[k]; o[p.path[p.path.length - 1]] = values[i]; });
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** QA groups -> one number: the sum of their severities (qa/severity.js, unrounded). */
|
|
43
|
+
const costOf = qa => qa.groups.reduce((c, g) => c + weightOf(g), 0);
|
|
44
|
+
|
|
45
|
+
/** Frames a parameter can change (baked channels differ between its ends), or null for all. */
|
|
46
|
+
function dirtyWindow(spec, params, mid, prepare, sk) {
|
|
47
|
+
const expand = values => prepare(resolveParams(spec, params, values));
|
|
48
|
+
const base = expand(mid), parts = s => JSON.stringify({ ...s, keys: undefined, generators: undefined });
|
|
49
|
+
const bake = s => bakeChannels(expandGenerators(s, sk), s.frames, flowOptions(s.flow));
|
|
50
|
+
const b0 = bake(base);
|
|
51
|
+
let a = Infinity, b = -Infinity;
|
|
52
|
+
for (let i = 0; i < params.length; i++) for (const end of [params[i].lo, params[i].hi]) {
|
|
53
|
+
const s = expand(mid.map((v, j) => (j === i ? snap(params[i], end) : v)));
|
|
54
|
+
if (parts(s) !== parts(base)) return null;
|
|
55
|
+
const b1 = bake(s);
|
|
56
|
+
for (const ch of new Set([...Object.keys(b0), ...Object.keys(b1)])) {
|
|
57
|
+
const x = b0[ch], y = b1[ch];
|
|
58
|
+
for (let f = 0; f < spec.frames; f++) if (JSON.stringify(x?.[f]) !== JSON.stringify(y?.[f])) { a = Math.min(a, f); b = Math.max(b, f); }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return a > b ? null : [Math.max(0, a - 8), Math.min(spec.frames - 1, b + 8)];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Halton point i (1-based) in [0, 1]^k. */
|
|
65
|
+
const PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
|
|
66
|
+
const halton = (i, k) => Array.from({ length: k }, (_, d) => { let f = 1, r = 0, n = i; const p = PRIMES[d % PRIMES.length]; while (n > 0) { f /= p; r += f * (n % p); n = Math.floor(n / p); } return r; });
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* prepare(spec) -> expanded + validated plain spec (pipeline.js). Returns the spec with the
|
|
70
|
+
* chosen values and a report { values: { path: value }, evals, cost (0 = clean), window, ms }.
|
|
71
|
+
*/
|
|
72
|
+
export function searchParams(spec, params, { sk, scene }, prepare) {
|
|
73
|
+
const t0 = performance.now(), budget = Math.max(1, Math.min(64, Math.round(spec.search?.budget ?? 32)));
|
|
74
|
+
const mid = params.map(p => snap(p, (p.lo + p.hi) / 2));
|
|
75
|
+
const window = dirtyWindow(spec, params, mid, prepare, sk);
|
|
76
|
+
const cache = new Map();
|
|
77
|
+
let evals = 0, best = null;
|
|
78
|
+
const evaluate = values => {
|
|
79
|
+
const key = values.join(",");
|
|
80
|
+
if (cache.has(key)) return cache.get(key);
|
|
81
|
+
if (evals >= budget) return Infinity;
|
|
82
|
+
evals++;
|
|
83
|
+
let cost;
|
|
84
|
+
try {
|
|
85
|
+
const exp = prepare(resolveParams(spec, params, values));
|
|
86
|
+
cost = costOf(runMotionQa(sk, compileMotion({ fps: 24, ...exp }, { sk, scene }, { window }), exp, { explain: false }));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!/MOTION_SPEC_INVALID/.test(error.message)) throw error;
|
|
89
|
+
cost = 1e9;
|
|
90
|
+
}
|
|
91
|
+
cache.set(key, cost);
|
|
92
|
+
if (!best || cost < best.cost) best = { values, cost };
|
|
93
|
+
return cost;
|
|
94
|
+
};
|
|
95
|
+
const done = () => best?.cost === 0 || evals >= budget;
|
|
96
|
+
const at = u => params.map((p, d) => snap(p, p.lo + (p.hi - p.lo) * u[d]));
|
|
97
|
+
evaluate(mid);
|
|
98
|
+
// coarse pass: a 9-point grid from the middle outward (one parameter) or Halton points
|
|
99
|
+
const coarse = params.length === 1 ? [4, 3, 5, 2, 6, 1, 7, 0, 8].map(i => [i / 8]) : Array.from({ length: Math.min(budget >> 1, 8 * params.length) }, (_, i) => halton(i + 1, params.length));
|
|
100
|
+
for (const u of coarse) { if (done()) break; evaluate(at(u)); }
|
|
101
|
+
// pattern search around the best, halving the step
|
|
102
|
+
let step = params.map(p => (p.hi - p.lo) / 8);
|
|
103
|
+
while (!done() && step.some((s, d) => s > (params[d].int ? 0.5 : (params[d].hi - params[d].lo) * 1e-3))) {
|
|
104
|
+
let moved = false;
|
|
105
|
+
for (let d = 0; d < params.length && !done(); d++) for (const sg of [-1, 1]) {
|
|
106
|
+
const v = best.values.slice(); v[d] = snap(params[d], v[d] + sg * step[d]);
|
|
107
|
+
const before = best.cost;
|
|
108
|
+
evaluate(v);
|
|
109
|
+
if (best.cost < before) moved = true;
|
|
110
|
+
}
|
|
111
|
+
if (!moved) step = step.map(s => s / 2);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
spec: resolveParams(spec, params, best.values),
|
|
115
|
+
search: { values: Object.fromEntries(params.map((p, i) => [pathText(p.path), best.values[i]])), evals, cost: Math.round(best.cost * 100) / 100, window, ms: Math.round(performance.now() - t0) },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// The second hand on a held object (C14, lane K8). While keys lead a carry the right hand holds
|
|
2
|
+
// (object-keys.js), a left hand anchored to that prop (w_<anchor>_l on a kind "prop" anchor of
|
|
3
|
+
// the same prop, with or without a fit) turns with the object too. The wrist stays where the
|
|
4
|
+
// anchor puts it (with a fit, the palm stays where the fit puts it). The hand takes the holding
|
|
5
|
+
// hand's grip mirrored across the plane of the object's axis and the holding fingers (the object's
|
|
6
|
+
// +Y runs through the left palm frame as through the right one, knuckles in line), turned about
|
|
7
|
+
// the axis to the roll its arm holds most easily (planned over the frames, object-keys.js and
|
|
8
|
+
// hold-plan.js). The arm follows as for the holding hand (compiler/hold.js): the elbow swivels
|
|
9
|
+
// about the shoulder-wrist axis (lane N1; it stayed in the plane the anchor's IK gave it, and the
|
|
10
|
+
// default pole, down and back, bent a left arm reaching across the chest into the torso), the
|
|
11
|
+
// forearm and the wrist turn. Each roll is scored by the wrist's strain at the best of POLES poles,
|
|
12
|
+
// plus CLEAR per squared centimetre the upper arm, the forearm or the hand goes into the torso
|
|
13
|
+
// capsules (the BODY check's), and DEEP per squared centimetre the hand goes past what QA allows a
|
|
14
|
+
// hand: into the torso past the BODY allowance, into the holding hand past the SELF one (lane Q1:
|
|
15
|
+
// at CLEAR alone, session 4db5d447 put the hand 0.9 cm into torso_lo at f35 to save 1 of strain,
|
|
16
|
+
// QA BODY). The elbow's path is planned like the holding elbow's. The hold
|
|
17
|
+
// eases in with the keys' weight times the anchor weight, and a grip the arm cannot hold fades out.
|
|
18
|
+
// Only a hand solved after the holding hand can follow it (pose.js solves the right arm first), so
|
|
19
|
+
// the second hand is the left.
|
|
20
|
+
import { palmAxes } from "./compiler/limbs.js";
|
|
21
|
+
import { SIDES } from "./compiler/pose.js";
|
|
22
|
+
import { add, sub, scale, norm, dot, cross, mul, T, apply, axisRot, len, clamp } from "./compiler/math.js";
|
|
23
|
+
import { landscape, ELBOW_REST } from "./compiler/arm-orient.js";
|
|
24
|
+
import { bendPlane } from "./compiler/hold.js";
|
|
25
|
+
import { CAPSULES } from "./qa/geometry.js";
|
|
26
|
+
import { QA_LIMITS } from "./qa/checks.js";
|
|
27
|
+
import { bodyFor, segmentDepth } from "./body.js";
|
|
28
|
+
|
|
29
|
+
const ROLLS = 36, TEMP = 0.05, PULL = 0.3, EASY = 3, SLACK = 10, POLES = 36, CLEAR = 1, DEEP = 100;
|
|
30
|
+
const TORSO = CAPSULES.filter(c => c[0].startsWith("torso")), RADIUS = Object.fromEntries(CAPSULES.map(c => [c[0], c[3]]));
|
|
31
|
+
const cols = (a, b, c) => [[a[0], b[0], c[0]], [a[1], b[1], c[1]], [a[2], b[2], c[2]]];
|
|
32
|
+
const col = (R, i) => [R[0][i], R[1][i], R[2][i]];
|
|
33
|
+
|
|
34
|
+
/** The distance between segments p1-q1 and p2-q2 (closest points, clamped to both). */
|
|
35
|
+
function segSeg(p1, q1, p2, q2) {
|
|
36
|
+
const d1 = sub(q1, p1), d2 = sub(q2, p2), r = sub(p1, p2), a = dot(d1, d1), e = dot(d2, d2), f = dot(d2, r);
|
|
37
|
+
if (a < 1e-12 || e < 1e-12) return len(r);
|
|
38
|
+
const c = dot(d1, r), b = dot(d1, d2), den = a * e - b * b;
|
|
39
|
+
let t = den > 1e-12 ? clamp((b * f - c * e) / den, 0, 1) : 0, u = (b * t + f) / e;
|
|
40
|
+
if (u < 0) { u = 0; t = clamp(-c / a, 0, 1); } else if (u > 1) { u = 1; t = clamp((b - c) / a, 0, 1); }
|
|
41
|
+
return len(sub(add(p1, scale(d1, t)), add(p2, scale(d2, u))));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The palm frame of `side` on pose s: the finger middles, axes d, l, n as columns. */
|
|
45
|
+
function palmFrame(sk, s, side) {
|
|
46
|
+
const I = sk.index, ax = palmAxes(sk, s, side);
|
|
47
|
+
return { pos: scale(add(s.P[I[`${side}HandIndex2`]], s.P[I[`${side}HandMiddle2`]]), 0.5), R: cols(ax.d, ax.l, ax.n) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The left hand's grip on carry `id` at this frame, or null when the left hand is not anchored to
|
|
52
|
+
* its prop: s is the pose with the holding hand done and the left arm on its plain target, c the
|
|
53
|
+
* frame's channels, axis the object's +Y as drawn. The grip is the holding hand's mirrored (knuckles
|
|
54
|
+
* in line), turned `roll` radians about the axis; arm(roll) is the left arm holding it: at(th) ->
|
|
55
|
+
* { pv, elbow, cost } for the elbow th radians about the shoulder-wrist axis from its plane (the
|
|
56
|
+
* swivel landscape plus the torso price) and best { th, pole, cost }; cost(roll) is best's cost
|
|
57
|
+
* (pulled lightly toward the mirror), point(roll) the end of the hand that moves (the palm with the
|
|
58
|
+
* wrist pinned, the wrist with the palm pinned), softmin() the easiest roll.
|
|
59
|
+
*/
|
|
60
|
+
export function secondGrip(sk, spec, id, s, c, axis) {
|
|
61
|
+
const o = spec.objects[id];
|
|
62
|
+
if (spec.props[o.prop].hand !== "r") return null;
|
|
63
|
+
const weight = Object.entries(spec.anchors ?? {}).reduce((sum, [name, a]) => sum + (a.kind === "prop" && a.prop === o.prop ? Math.max(0, c[`w_${name}_l`] ?? 0) : 0), 0);
|
|
64
|
+
if (!(weight > 1e-4)) return null;
|
|
65
|
+
// the mirror of the holding palm frame across the plane of the axis and the holding fingers
|
|
66
|
+
const R = palmFrame(sk, s, SIDES.r).R, d = col(R, 0), p = norm(cross(axis, sub(d, scale(axis, dot(d, axis))))), mirror = v => sub(v, scale(p, 2 * dot(v, p)));
|
|
67
|
+
const base = cols(mirror(d), mirror(col(R, 1)), scale(mirror(col(R, 2)), -1));
|
|
68
|
+
const left = palmFrame(sk, s, SIDES.l), I = sk.index, h = I.LeftHand, wrist = s.P[h], local = mul(T(s.R[h]), left.R), localP = apply(T(s.R[h]), sub(left.pos, wrist));
|
|
69
|
+
// with a fit the palm (its effector) stays where the fit put it, else the wrist stays on the anchor
|
|
70
|
+
const pin = (spec.fits ?? []).some(x => x.side === "l" && spec.anchors?.[x.anchor]?.kind === "prop" && spec.anchors[x.anchor].prop === o.prop && c[`w_${x.anchor}_l`] > 1e-4) ? "palm" : "wrist";
|
|
71
|
+
const rolled = r => mul(axisRot(axis, r * 180 / Math.PI), base), hand = r => mul(rolled(r), T(local));
|
|
72
|
+
const point = r => (pin === "wrist" ? add(wrist, apply(hand(r), localP)) : sub(left.pos, apply(hand(r), localP)));
|
|
73
|
+
// the wrist for a roll, the knuckles off it, and how deep a segment goes into the torso (m)
|
|
74
|
+
const wristAt = r => (pin === "wrist" ? wrist : point(r)), knuckle = apply(T(s.R[h]), sub(s.P[I.LeftHandMiddle3], wrist)), S = s.P[I.LeftArm], plane = bendPlane(sk, s, SIDES.l);
|
|
75
|
+
// on the fitted skin when the rig has one and spec.fix.self is on (body.js, as QA's BODY measures it), else on the torso capsules
|
|
76
|
+
const skin = spec.fix === false || spec.fix?.self === false ? null : bodyFor(sk), BONE = { uarm_l: I.LeftArm, farm_l: I.LeftForeArm, hand_l: I.LeftHand };
|
|
77
|
+
const into = (a, b, part) => (skin ? segmentDepth(skin, sk, s, BONE[part], a, b) : Math.max(0, ...TORSO.map(([, ta, tb, rt]) => RADIUS[part] + rt - segSeg(a, b, s.P[I[ta]], s.P[I[tb]]))));
|
|
78
|
+
const arms = new Map();
|
|
79
|
+
const arm = r => {
|
|
80
|
+
if (arms.has(r)) return arms.get(r);
|
|
81
|
+
const W = wristAt(r), land = landscape(sk, s, SIDES.l, hand(r), W, plane, ELBOW_REST);
|
|
82
|
+
const at = th => { const q = land.at(th); return { pv: q.pv, elbow: q.elbow, cost: q.cost + CLEAR * ((100 * into(S, q.elbow, "uarm_l")) ** 2 + (100 * into(q.elbow, W, "farm_l")) ** 2) }; };
|
|
83
|
+
let best = { th: 0, cost: Infinity };
|
|
84
|
+
for (let i = 0; i < POLES; i++) { const th = (2 * Math.PI * i) / POLES - Math.PI, v = at(th).cost; if (v < best.cost) best = { th, cost: v }; }
|
|
85
|
+
const K = add(W, apply(hand(r), knuckle)), deep = into(W, K, "hand_l"), past = Math.max(0, deep - QA_LIMITS.body);
|
|
86
|
+
// the holding hand is done: how far this hand's capsule goes into it (QA SELF "hands overlap")
|
|
87
|
+
const touch = 2 * RADIUS.hand_l - segSeg(W, K, s.P[I.RightHand], s.P[I.RightHandMiddle3]), over = Math.max(0, touch - QA_LIMITS.hands);
|
|
88
|
+
const fixed = CLEAR * ((100 * deep) ** 2 + (100 * land.miss) ** 2) + (past > 0 ? DEEP * (100 * past) ** 2 : 0) + (over > 0 ? DEEP * (100 * over) ** 2 : 0);
|
|
89
|
+
const a = { at, best: { th: best.th, pole: at(best.th).pv, cost: best.cost + fixed } };
|
|
90
|
+
arms.set(r, a);
|
|
91
|
+
return a;
|
|
92
|
+
};
|
|
93
|
+
const cost = r => arm(r).best.cost + PULL * (1 - Math.cos(r));
|
|
94
|
+
const softmin = () => {
|
|
95
|
+
const all = Array.from({ length: ROLLS }, (_, i) => { const r = (2 * Math.PI * i) / ROLLS - Math.PI; return [r, cost(r)]; }), low = Math.min(...all.map(x => x[1]));
|
|
96
|
+
const m = all.reduce((acc, [r, v]) => { const e = Math.exp(-(v - low) / TEMP); return [acc[0] + e * Math.cos(r), acc[1] + e * Math.sin(r)]; }, [0, 0]);
|
|
97
|
+
return Math.atan2(m[1], m[0]);
|
|
98
|
+
};
|
|
99
|
+
return { id, weight, pin, at: left.pos, rolled, arm, cost, point, softmin, measure: s2 => palmFrame(sk, s2, SIDES.l) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** How much of a grip at `roll` the arm can take: 1 while its cost is under EASY, fading by SLACK past it. */
|
|
103
|
+
export const gripGate = (grip, roll) => Math.exp(-Math.max(0, grip.cost(roll) - EASY) / SLACK);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The hold ({ w, palm, measure, pole, pin, second } for compiler/hold.js) of a grip at `roll`
|
|
107
|
+
* radians with the elbow toward `pole` (a world direction), w the keys' lead weight, gate the
|
|
108
|
+
* planned gripGate: a grip the arm cannot hold fades out, so the hand stays nearer where the plain
|
|
109
|
+
* arm put it instead of turning to a limit and flipping there.
|
|
110
|
+
*/
|
|
111
|
+
export const secondHold = (grip, w, roll, gate, pole) => ({ w: w * Math.min(1, grip.weight) * gate, second: grip.id, pin: grip.pin,
|
|
112
|
+
pole, palm: { pos: grip.at, R: grip.rolled(roll) }, measure: grip.measure });
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Basic skills: stand, walk, reach-and-grip. Each reads the channel state the earlier keys
|
|
2
|
+
// leave at its f0 and returns keys plus whatever anchors, props, fits and checks it needs.
|
|
3
|
+
import { add, apply, ry } from "../compiler/math.js";
|
|
4
|
+
import { walk } from "../compiler/walk.js";
|
|
5
|
+
import { keyBook, skillError } from "./book.js";
|
|
6
|
+
import { walkEnd } from "./info.js";
|
|
7
|
+
|
|
8
|
+
/** standIdle({ at: [x, z], heading = 0, until }): a neutral standing pose, arms down (FK). */
|
|
9
|
+
export function standIdle(ctx) {
|
|
10
|
+
const { sk, args, state } = ctx, I = sk.index, P = sk.restWorldPos, ank = P[I.LeftFoot][1];
|
|
11
|
+
const h = args.heading ?? state.yaw ?? 0, base = args.at ?? (state.root ? [state.root[0], state.root[2]] : null);
|
|
12
|
+
if (!base) skillError(ctx.tag, "needs args.at: [x, z] (no root is keyed before f0)");
|
|
13
|
+
const c = [base[0], 0, base[base.length - 1]], R = ry(h), foot = v => { const p = add(c, apply(R, v)); p[1] = ank; return p; };
|
|
14
|
+
const set = { root: [c[0], P[I.Hips][1] - 0.01, c[2]], yaw: h, pitch: 0, roll: 0, spine_yaw: 0, spine_pitch: 0, spine_roll: 0, head_yaw: 0, head_pitch: 0,
|
|
15
|
+
foot_l: foot([0.09, 0, -0.03]), foot_r: foot([-0.09, 0, 0.03]), fyaw_l: h, fyaw_r: h, fpitch_l: 0, fpitch_r: 0, knee_l: [0.05, 0, 1], knee_r: [-0.05, 0, 1],
|
|
16
|
+
armw_l: 0, armw_r: 0, aswing_l: 0, aswing_r: 0, abend_l: 0, abend_r: 0, arm_hang: 1 };
|
|
17
|
+
const keys = [{ f: ctx.f0, set }];
|
|
18
|
+
if (args.until !== undefined) keys.push({ f: args.until, set: structuredClone(set) });
|
|
19
|
+
return { keys, end: args.until ?? ctx.f0 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* walkTo({ to: [x, z] } | { heading: h | [h0, h1] }, steps, step = 0.5, turnDistance, ...): the
|
|
24
|
+
* walk generator from the pose at f0. With `to` the heading turns toward the point and the step
|
|
25
|
+
* count covers the distance. Ends 16 + steps * period + 18 frames after f0, feet together; a
|
|
26
|
+
* take that ends after the last step holds that pose to its end. The steps keep a natural rhythm
|
|
27
|
+
* (compiler/gait-rhythm.js); vary: 0 makes every step the same.
|
|
28
|
+
*/
|
|
29
|
+
export function walkTo(ctx) {
|
|
30
|
+
const { args, state } = ctx;
|
|
31
|
+
if (!state.foot_l || !state.foot_r || !state.root) skillError(ctx.tag, "needs a standing pose keyed before f0 (use standIdle or another skill first)");
|
|
32
|
+
const gen = { type: "walk", f0: ctx.f0, ...args };
|
|
33
|
+
delete gen.to; delete gen.arrive;
|
|
34
|
+
const h0 = state.yaw ?? 0, step = args.step ?? 0.5, first = args.firstStep ?? 0.4;
|
|
35
|
+
if (args.to) {
|
|
36
|
+
const c0 = [(state.foot_l[0] + state.foot_r[0]) / 2, (state.foot_l[2] + state.foot_r[2]) / 2];
|
|
37
|
+
const dx = args.to[0] - c0[0], dz = args.to[args.to.length - 1] - c0[1], d = Math.hypot(dx, dz);
|
|
38
|
+
let h1 = Math.atan2(dx, dz) * 180 / Math.PI;
|
|
39
|
+
while (h1 - h0 > 180) h1 -= 360;
|
|
40
|
+
while (h1 - h0 < -180) h1 += 360;
|
|
41
|
+
gen.heading = args.heading ?? [h0, h1];
|
|
42
|
+
gen.steps = args.steps ?? Math.max(2, 2 + Math.round((d - first) / step));
|
|
43
|
+
}
|
|
44
|
+
if (typeof gen.heading === "number") gen.heading = [h0, gen.heading];
|
|
45
|
+
const keys = walk(gen, ctx.keys, ctx.sk, ctx.tag), end = walkEnd(gen), last = ctx.frames - 1;
|
|
46
|
+
// the settle after the last step only holds the pose: a take that ends in it ends the walk there
|
|
47
|
+
if (end > last && Math.max(...keys.filter(k => k.f < end).map(k => k.f)) <= last) return { keys: keys.filter(k => k.f <= last), end: last, cut: `settle cut at f${last} (hold to the end)` };
|
|
48
|
+
return { keys, end };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* reachGrip({ anchor, side, reach = 20, hold = 22, release = 12, elbow, fingers = "grip",
|
|
53
|
+
* define, orient = true }): the hand goes to the anchor (defined elsewhere, or by `define`) over
|
|
54
|
+
* `reach` frames with the fingers closing, holds, and lets go. Adds a fit of the fingertips on the
|
|
55
|
+
* anchor (on a body it also turns the hand into a grip, compiler/grip.js; orient: false leaves the
|
|
56
|
+
* hand's turn alone) and a contact check. A take that ends during the release holds the grip to its end.
|
|
57
|
+
*/
|
|
58
|
+
export function reachGrip(ctx) {
|
|
59
|
+
const { args, state } = ctx, k = args.side, name = args.anchor;
|
|
60
|
+
if (k !== "l" && k !== "r") skillError(ctx.tag, `args.side must be "l" or "r", got ${JSON.stringify(k)}`);
|
|
61
|
+
if (typeof name !== "string" || /_[lr]$/.test(name)) skillError(ctx.tag, "args.anchor must name an anchor (not ending in _l/_r)");
|
|
62
|
+
const reach = args.reach ?? 20, hold = args.hold ?? 22, release = args.release ?? 12, preset = args.fingers ?? "grip";
|
|
63
|
+
const w = `w_${name}_${k}`, fg = `fingers_${k}`, armw = `armw_${k}`, el = `elbow_${k}`, wasFk = !(state[armw] > 0.5);
|
|
64
|
+
// a take that ends after the hold but before the hand lets go cuts the release: the grip holds to the last frame
|
|
65
|
+
const last = ctx.frames - 1, cut = ctx.f0 + reach + hold <= last && ctx.f0 + reach + hold + release > last;
|
|
66
|
+
for (const [c, v] of [[w, 0], [fg, { [preset]: 0 }], [armw, 0]]) if (state[c] === undefined) state[c] = v;
|
|
67
|
+
const b = keyBook(state, ctx.f0);
|
|
68
|
+
b.hold(0, w, fg, armw, el);
|
|
69
|
+
b.at(reach, { [w]: 1, [fg]: { ...state[fg], [preset]: 1 }, [armw]: 1, ...(args.elbow ? { [el]: args.elbow } : {}) });
|
|
70
|
+
b.hold(reach + hold, w, fg, armw, el);
|
|
71
|
+
if (!cut) b.at(reach + hold + release, { [w]: 0, [fg]: { ...state[fg], [preset]: 0 }, ...(wasFk ? { [armw]: 0 } : {}) });
|
|
72
|
+
const S = k === "l" ? "Left" : "Right", prop = `${name}Grip`;
|
|
73
|
+
const def = args.define ? { [name]: args.define } : {};
|
|
74
|
+
const obj = args.define?.kind === "object" ? args.define.object : ctx.anchors?.[name]?.kind === "object" ? ctx.anchors[name].object : null;
|
|
75
|
+
return {
|
|
76
|
+
keys: b.keys, end: cut ? last : ctx.f0 + reach + hold + release, ...(cut ? { cut: `release cut at f${last} (hold to the end)` } : {}), anchors: def,
|
|
77
|
+
props: { [prop]: { hand: k, origin: [`${S}HandIndex2`, `${S}HandMiddle2`], points: { fingers: [0, 0, 0] } } },
|
|
78
|
+
fits: [{ side: k, anchor: name, effector: { prop, point: "fingers" }, ...(args.orient === false ? { orient: false } : {}) }],
|
|
79
|
+
checks: { contacts: [{ name: `fingers on ${name}`, point: `prop:${prop}.fingers`, target: `anchor:${name}`, tol: 0.04, when: w }],
|
|
80
|
+
allow: obj ? [{ part: "hand", object: obj, depth: 0.04 }] : [] },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Skill plumbing: a key book that tracks the channel state while a skill writes keys, and the
|
|
2
|
+
// placement of choreography written in a template frame (e.g. a car's own frame, driver side
|
|
3
|
+
// on +X) into the scene: translate, turn by a yaw, and mirror left/right for the other side.
|
|
4
|
+
import { add, apply, ry } from "../compiler/math.js";
|
|
5
|
+
|
|
6
|
+
const SIDE_SWAP = { _l: "_r", _r: "_l" };
|
|
7
|
+
const NEGATE_ON_MIRROR = /^(yaw|roll|spine_yaw|spine_roll|head_yaw|head_roll|fyaw_[lr]|wtwist_[lr]|wflex_[lr])$/;
|
|
8
|
+
const POINT = /^(root|foot_[lr]|hand_[lr])$/, POLE = /^(knee|elbow)_[lr]$/, YAW = /^(yaw|fyaw_[lr])$/;
|
|
9
|
+
|
|
10
|
+
const clone = v => (v && typeof v === "object" ? structuredClone(v) : v);
|
|
11
|
+
export const swapSide = name => name.replace(/_(l|r)$/, m => SIDE_SWAP[m]).replace(/^(Left|Right)/, m => (m === "Left" ? "Right" : "Left"));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A frame: { origin: [x,y,z], yaw (deg), mirror (bool), negate: Set of extra channels (hinge
|
|
15
|
+
* angles) to negate, names: { anchor: mirroredAnchor } }. Template points p map to
|
|
16
|
+
* origin + ry(yaw)(mirror ? [-x, y, z] : p).
|
|
17
|
+
*/
|
|
18
|
+
export const identityFrame = () => ({ origin: [0, 0, 0], yaw: 0, mirror: false, negate: new Set(), names: {} });
|
|
19
|
+
|
|
20
|
+
const placePoint = (fr, p) => add(fr.origin, apply(ry(fr.yaw), fr.mirror ? [-p[0], p[1], p[2]] : p));
|
|
21
|
+
export const placeDir = (fr, v) => (fr.mirror ? [-v[0], v[1], v[2]] : v.slice());
|
|
22
|
+
|
|
23
|
+
/** A channel name in the placed frame (sides swap and anchor names rename when mirrored). */
|
|
24
|
+
export function placeChannel(fr, ch) {
|
|
25
|
+
if (!fr.mirror) return ch;
|
|
26
|
+
const w = /^w_(.+)_([lr])$/.exec(ch);
|
|
27
|
+
if (w) return `w_${fr.names[w[1]] ?? w[1]}_${w[2] === "l" ? "r" : "l"}`;
|
|
28
|
+
return /_[lr]$/.test(ch) ? swapSide(ch) : ch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function placeSet(fr, set) {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const [ch, v0] of Object.entries(set)) {
|
|
34
|
+
let v = clone(v0);
|
|
35
|
+
if (POINT.test(ch)) v = placePoint(fr, v);
|
|
36
|
+
else if (POLE.test(ch)) v = placeDir(fr, v);
|
|
37
|
+
else if (typeof v === "number") {
|
|
38
|
+
if (fr.mirror && (NEGATE_ON_MIRROR.test(ch) || fr.negate.has(ch))) v = -v;
|
|
39
|
+
if (YAW.test(ch)) v += fr.yaw;
|
|
40
|
+
}
|
|
41
|
+
out[placeChannel(fr, ch)] = v;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Keys relative to f0 (book.at(12, ...) writes frame f0 + 12). `at` and `step` take template
|
|
48
|
+
* values and place them; `hold` re-keys the current (already placed) values.
|
|
49
|
+
*/
|
|
50
|
+
export function keyBook(state, f0 = 0, fr = identityFrame()) {
|
|
51
|
+
const keys = [], name = ch => placeChannel(fr, ch);
|
|
52
|
+
return {
|
|
53
|
+
state, keys, frame: fr,
|
|
54
|
+
at(f, set) { const placed = placeSet(fr, set); Object.assign(state, clone(placed)); keys.push({ f: f0 + f, set: placed }); },
|
|
55
|
+
hold(f, ...names) { keys.push({ f: f0 + f, set: Object.fromEntries(names.filter(n => state[name(n)] !== undefined).map(n => [name(n), clone(state[name(n)])])) }); },
|
|
56
|
+
step(f, ch, value) {
|
|
57
|
+
const n = name(ch);
|
|
58
|
+
keys.push({ f: f0 + f - 1, set: { [n]: state[n] } });
|
|
59
|
+
state[n] = value;
|
|
60
|
+
keys.push({ f: f0 + f, set: { [n]: value }, step: [n] });
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Throws the skill's own MOTION_SPEC_INVALID message. */
|
|
66
|
+
export const skillError = (tag, msg) => { throw new Error(`MOTION_SPEC_INVALID: ${tag}: ${msg}`); };
|
|
67
|
+
|
|
68
|
+
/** Scene box by id or a skill error naming the arg. */
|
|
69
|
+
export function sceneBox(ctx, arg, id) {
|
|
70
|
+
const box = ctx.scene?.objects?.[id];
|
|
71
|
+
if (!box) skillError(ctx.tag, `args.${arg} "${id}" is not a scene object`);
|
|
72
|
+
return box;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Yaw (deg) of a box's rotation matrix about +Y. */
|
|
76
|
+
export const yawOf = R => Math.atan2(R[0][2], R[2][2]) * 180 / Math.PI;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Car skills, extracted from the car-exit fixture (test/motion3/fixtures/exitcar-scene.mjs).
|
|
2
|
+
// The choreography is written in the template car's frame: the car centred on the origin facing
|
|
3
|
+
// +Z, its driver door on +X, seat base at y 0.4. The skills place it from the scene: the seat
|
|
4
|
+
// gives the car's position and yaw, the door gives the hinge, side "r" mirrors everything.
|
|
5
|
+
// Tuned on the X Bot: seated heights are the template's, standing heights follow the rig.
|
|
6
|
+
import { add, sub, apply, ry } from "../compiler/math.js";
|
|
7
|
+
import { keyBook, identityFrame, placeChannel, placeDir, sceneBox, skillError, yawOf, swapSide } from "./book.js";
|
|
8
|
+
|
|
9
|
+
const SEAT_REF = [0.54, 0.4, -0.075], FLOOR = 0.4, X_HIPS = 1.0427487182617188;
|
|
10
|
+
const SIDES = new Set(["l", "r"]);
|
|
11
|
+
|
|
12
|
+
/** Seat, door and handle ids from args ({ car: "Car" } fills the template's names). */
|
|
13
|
+
function carIds(args, side = "l") {
|
|
14
|
+
const car = args.car;
|
|
15
|
+
return {
|
|
16
|
+
seat: args.seat ?? (car && `${car}_Seat_Driver`), seatBack: args.seatBack ?? (car && `${car}_Seat_Driver_Back`),
|
|
17
|
+
door: args.door ?? (car && `${car}_Door_${side.toUpperCase()}`), handle: args.handle ?? (car && `${car}_Door_Handle`),
|
|
18
|
+
wheel: args.wheel ?? (car && `${car}_Steering_Wheel`),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The template-to-scene frame for a car whose driver seat is `seat`. */
|
|
23
|
+
function carFrame(ctx, seat, side) {
|
|
24
|
+
const box = sceneBox(ctx, "seat", seat);
|
|
25
|
+
if (Math.abs(box.pos[1] - FLOOR) > 0.05) skillError(ctx.tag, `seat "${seat}" stands at y ${box.pos[1]}; the car skills are tuned for a seat base at y ${FLOOR} (a car on the ground)`);
|
|
26
|
+
const fr = identityFrame();
|
|
27
|
+
fr.yaw = yawOf(box.R); fr.mirror = side === "r";
|
|
28
|
+
const o = sub(box.pos, apply(ry(fr.yaw), fr.mirror ? [-SEAT_REF[0], SEAT_REF[1], SEAT_REF[2]] : SEAT_REF));
|
|
29
|
+
fr.origin = [o[0], 0, o[2]];
|
|
30
|
+
fr.names = { lthigh: "rthigh", rthigh: "lthigh" };
|
|
31
|
+
return fr;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The hands on the thighs, pushing to stand up; letting go as the thighs turn under them, the palms and forearms
|
|
35
|
+
* sweep a few centimetres into them for a few frames (the open case of fix/self.js: it moves wrists, not palms);
|
|
36
|
+
* the hands, both on the thighs over knees close together, touch. */
|
|
37
|
+
const THIGH_CONTACT = [["hand", "thigh*", 0.07], ["farm", "thigh*", 0.07], ["hand", "hips", 0.07], ["farm", "hips", 0.07], ["hand_l", "hand_r", 0.035]]
|
|
38
|
+
.map(([part, object, depth]) => ({ part, object, depth }));
|
|
39
|
+
|
|
40
|
+
const rigHeights = sk => ({ A: sk.restWorldPos[sk.index.LeftFoot][1], H: y => y + (sk.restWorldPos[sk.index.Hips][1] - X_HIPS) });
|
|
41
|
+
|
|
42
|
+
/** sitInSeat({ car | seat (+ seatBack, wheel), offset = [0, 0.17, 0.02], floor }): seated, hands forward at the wheel. */
|
|
43
|
+
export function sitInSeat(ctx) {
|
|
44
|
+
const { args, sk } = ctx, ids = carIds(args), box = sceneBox(ctx, "seat", ids.seat), { A } = rigHeights(sk);
|
|
45
|
+
const Y = yawOf(box.R), turn = v => apply(ry(Y), v), off = args.offset ?? [0, 0.17, 0.02], floor = args.floor ?? box.pos[1];
|
|
46
|
+
const root = add(add(box.pos, apply(box.R, [0, 2 * box.half[1], 0])), turn(off));
|
|
47
|
+
const foot = x => { const p = add(root, turn([x, 0, 0.452])); p[1] = floor + A; return p; };
|
|
48
|
+
const hand = x => add(root, turn([x, 0.348, 0.324]));
|
|
49
|
+
const set = {
|
|
50
|
+
root, yaw: Y, pitch: 0, spine_pitch: -12, spine_yaw: 0, spine_roll: 0, head_yaw: 0, head_pitch: 10,
|
|
51
|
+
foot_l: foot(0.17), foot_r: foot(-0.17), fyaw_l: Y, fyaw_r: Y, fpitch_l: 0, fpitch_r: 0, knee_l: [0.15, 0.2, 1], knee_r: [-0.15, 0.2, 1],
|
|
52
|
+
armw_l: 1, armw_r: 1, hand_l: hand(0.22), hand_r: hand(-0.22), elbow_l: [0.6, -1, -0.3], elbow_r: [-0.6, -1, -0.3],
|
|
53
|
+
aswing_l: 0, aswing_r: 0, abend_l: 0, abend_r: 0, wtwist_l: 0, wtwist_r: 0, wflex_l: 0, wflex_r: 0,
|
|
54
|
+
};
|
|
55
|
+
const allow = [{ part: "torso_lo", object: ids.seat, depth: 0.04 }, { part: "hips", object: ids.seat, depth: 0.04 }, { part: "thigh", object: ids.seat, depth: 0.045 }];
|
|
56
|
+
if (ids.seatBack && ctx.scene.objects[ids.seatBack]) allow.push({ part: "torso", object: ids.seatBack, depth: 0.03 });
|
|
57
|
+
if (ids.wheel && ctx.scene.objects[ids.wheel]) allow.push({ part: "hand", object: ids.wheel, depth: 0.05 });
|
|
58
|
+
return { keys: [{ f: ctx.f0, set }], checks: { supports: [{ object: ids.seat, part: "hips", tol: 0.04 }], allow }, end: ctx.f0 };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* exitCar({ car | seat+door+handle, side = "l", channel = "door" }): reach the inner handle, pull
|
|
63
|
+
* and push the door, let it swing open, swivel, legs out, stand up with the hands on the thighs,
|
|
64
|
+
* two steps away; ends at f0 + 246 standing on a walk start stance heading car yaw + 125.
|
|
65
|
+
*/
|
|
66
|
+
export function exitCar(ctx) {
|
|
67
|
+
const side = ctx.args.side ?? "l";
|
|
68
|
+
if (!SIDES.has(side)) skillError(ctx.tag, `args.side must be "l" or "r", got ${JSON.stringify(side)}`);
|
|
69
|
+
const ids = carIds(ctx.args, side), fr = carFrame(ctx, ids.seat, side), ch = ctx.args.channel ?? "door";
|
|
70
|
+
for (const [arg, id] of [["door", ids.door], ["handle", ids.handle]]) sceneBox(ctx, arg, id);
|
|
71
|
+
fr.negate.add(ch);
|
|
72
|
+
const defaults = { [ch]: 0, w_handle_l: 0, w_lthigh_l: 0, w_rthigh_r: 0, fingers_l: { grip: 0, fist: 0 } };
|
|
73
|
+
const k = keyBook(ctx.state, ctx.f0, fr), fresh = {};
|
|
74
|
+
for (const [c, v] of Object.entries(defaults)) if (ctx.state[placeChannel(fr, c)] === undefined) fresh[c] = v;
|
|
75
|
+
if (Object.keys(fresh).length) k.at(0, fresh);
|
|
76
|
+
choreography(k, ch, rigHeights(ctx.sk));
|
|
77
|
+
const door = ctx.scene.objects[ids.door], hx = (fr.mirror ? -1 : 1) * door.half[0];
|
|
78
|
+
const hinge = { pivot: add(door.pos, apply(door.R, [hx, 0, door.half[2]])), axis: [0, 1, 0], channel: ch };
|
|
79
|
+
const L = s => (fr.mirror ? swapSide(s) : s), n = s => (fr.mirror ? { lthigh: "rthigh", rthigh: "lthigh" }[s] ?? s : s);
|
|
80
|
+
const seg = (S, reach) => ({ kind: "segment", from: L(`${S}UpLeg`), to: L(`${S}Leg`), t: 0.45, tMin: 0.15, offset: [0, 0.07, 0], reach: fr.mirror ? (reach === "l" ? "r" : "l") : reach });
|
|
81
|
+
const hand = fr.mirror ? "r" : "l";
|
|
82
|
+
return {
|
|
83
|
+
keys: k.keys,
|
|
84
|
+
objects: { [ids.door]: hinge, [ids.handle]: hinge },
|
|
85
|
+
anchors: { handle: { kind: "object", object: ids.handle, local: placeDir(fr, [-0.005, 0.015, 0]) }, [n("lthigh")]: seg("Left", "l"), [n("rthigh")]: seg("Right", "r") },
|
|
86
|
+
props: { grip: { hand, origin: [L("LeftHandIndex2"), L("LeftHandMiddle2")], points: { fingers: [0, 0, 0] } } },
|
|
87
|
+
fits: [{ side: hand, anchor: "handle", effector: { prop: "grip", point: "fingers" } }],
|
|
88
|
+
checks: {
|
|
89
|
+
contacts: [{ name: "fingers on the door handle", point: "prop:grip.fingers", target: "anchor:handle", tol: 0.04, when: `w_handle_${hand}` }],
|
|
90
|
+
// the hands rest on the thighs and push on them to stand up: a contact the body check allows (qa/body-checks.js)
|
|
91
|
+
allow: [{ part: "hand", object: ids.door, depth: 0.03 }, { part: "hand", object: ids.handle, depth: 0.04 }, ...THIGH_CONTACT],
|
|
92
|
+
},
|
|
93
|
+
end: ctx.f0 + 246,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function choreography(k, door, { A, H }) {
|
|
98
|
+
// 1. reach for the inner handle, pull, push the door a little (f12-50)
|
|
99
|
+
k.hold(12, "w_handle_l", "fingers_l", "head_yaw", "spine_yaw", "spine_roll", "elbow_l", "hand_l");
|
|
100
|
+
k.at(32, { w_handle_l: 1, fingers_l: { grip: 1, fist: 0 }, head_yaw: 35, spine_yaw: 10, spine_roll: -8, elbow_l: [0.3, -1, -0.6], hand_l: [1.0, 0.96, 0.06] });
|
|
101
|
+
k.hold(32, door);
|
|
102
|
+
k.at(38, { [door]: -3 });
|
|
103
|
+
k.at(50, { [door]: -10, spine_roll: -16, spine_yaw: 14 });
|
|
104
|
+
// 2. let go (the door holds still), then it swings open; hands rest on the thighs (f54-84)
|
|
105
|
+
k.hold(54, "w_handle_l", "fingers_l", "w_lthigh_l", "elbow_l", door);
|
|
106
|
+
k.at(66, { w_handle_l: 0, fingers_l: { grip: 0, fist: 0 }, [door]: -10 });
|
|
107
|
+
k.at(70, { w_lthigh_l: 1, elbow_l: [0.5, -0.3, -1], spine_roll: -3, spine_yaw: 6 });
|
|
108
|
+
k.at(84, { [door]: -68, head_yaw: 40 });
|
|
109
|
+
k.hold(64, "w_rthigh_r", "elbow_r", "spine_pitch");
|
|
110
|
+
k.at(80, { w_rthigh_r: 1, elbow_r: [-0.5, -0.3, -1], spine_roll: 0, spine_yaw: 0, spine_pitch: 0 });
|
|
111
|
+
// 3. swivel; the left leg goes out over the sill and down to the ground (f84-118)
|
|
112
|
+
k.hold(84, "yaw", "root", "foot_l", "foot_r", "fyaw_l", "fyaw_r", "knee_l", "knee_r");
|
|
113
|
+
k.at(92, { foot_r: [0.4, FLOOR + A + 0.03, 0.36], fyaw_r: 15 });
|
|
114
|
+
k.at(97, { foot_l: [0.96, FLOOR + 0.22, 0.32], fyaw_l: 40, knee_l: [0.5, 0.4, 1], yaw: 30, root: [0.64, 0.915, -0.06] });
|
|
115
|
+
k.at(101, { foot_r: [0.56, FLOOR + A, 0.34], fyaw_r: 35 });
|
|
116
|
+
k.at(105, { foot_l: [1.18, FLOOR + 0.1, 0.1], fyaw_l: 60 });
|
|
117
|
+
k.at(116, { foot_l: [1.28, A, -0.12], fyaw_l: 75, knee_l: [0.4, 0.5, 1], yaw: 62, root: [0.82, 0.9, -0.06], head_yaw: 20, knee_r: [0.1, 0.6, 1], spine_pitch: 10 });
|
|
118
|
+
// 4. right leg out (f120-144)
|
|
119
|
+
k.hold(120, "foot_r", "fyaw_r");
|
|
120
|
+
k.at(129, { foot_r: [0.94, FLOOR + 0.24, 0.36], fyaw_r: 65, knee_r: [0, 0.5, 1], yaw: 80, root: [0.86, 0.9, -0.03] });
|
|
121
|
+
k.at(135, { foot_r: [1.18, FLOOR + 0.1, 0.26], fyaw_r: 85 });
|
|
122
|
+
k.hold(142, "foot_l", "fyaw_l");
|
|
123
|
+
k.at(144, { foot_r: [1.29, A, 0.22], fyaw_r: 90, knee_r: [-0.1, 0.4, 1], yaw: 90, root: [0.88, 0.9, 0.02], head_yaw: 0, knee_l: [0.1, 0.4, 1], spine_pitch: 16 });
|
|
124
|
+
// 5. lean the weight over the feet with the hands pushing on the thighs, stand up (f144-196)
|
|
125
|
+
k.hold(144, "head_pitch", "pitch", "elbow_l", "elbow_r");
|
|
126
|
+
k.at(152, { spine_pitch: 28, head_pitch: -8, root: [0.91, 0.9, 0.02], elbow_l: [0.6, -0.2, -1], elbow_r: [-0.6, -0.2, -1] });
|
|
127
|
+
k.at(162, { spine_pitch: 50, pitch: 22, head_pitch: -36, root: [0.99, 0.91, 0.02] });
|
|
128
|
+
k.at(174, { root: [1.09, 0.96, 0.02], spine_pitch: 36, pitch: 12, head_pitch: -24 });
|
|
129
|
+
k.hold(166, "armw_l", "armw_r");
|
|
130
|
+
k.at(186, { root: [1.19, H(1.0), 0.02], spine_pitch: 12, pitch: 3, head_pitch: -8, armw_l: 0, armw_r: 0 });
|
|
131
|
+
k.hold(187, "w_lthigh_l", "w_rthigh_r");
|
|
132
|
+
k.at(190, { w_lthigh_l: 0, w_rthigh_r: 0 });
|
|
133
|
+
k.at(196, { root: [1.21, H(1.005), 0.02], spine_pitch: 0, pitch: 0, head_pitch: 0 });
|
|
134
|
+
// 6. two steps away from the car with a weight shift (f196-246), onto the walk's start stance
|
|
135
|
+
k.at(205, { root: [1.26, H(0.985), 0.165] });
|
|
136
|
+
k.hold(208, "foot_l", "fyaw_l");
|
|
137
|
+
k.at(212, { root: [1.3, H(0.985), 0.16] });
|
|
138
|
+
k.at(214, { foot_l: [1.4, A + 0.06, -0.12] });
|
|
139
|
+
k.at(220, { foot_l: [1.535, A, -0.132], fyaw_l: 110, root: [1.38, H(0.98), 0.05] });
|
|
140
|
+
k.at(230, { root: [1.5, H(0.975), -0.08] });
|
|
141
|
+
k.hold(232, "foot_r", "fyaw_r");
|
|
142
|
+
k.at(236, { root: [1.52, H(0.975), -0.085] });
|
|
143
|
+
k.at(239, { foot_r: [1.53, A + 0.07, 0.09] });
|
|
144
|
+
k.at(246, { foot_r: [1.765, A, -0.048], fyaw_r: 125, yaw: 110, root: [1.62, H(0.98), -0.08] });
|
|
145
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// frames: "auto". The spec's length becomes the latest of every skill end, generator end and key
|
|
2
|
+
// f, plus 1. Open parameters ({ range: [lo, hi] }) count at their upper bound, so every value the
|
|
3
|
+
// search tries fits. The answer is a copy with the number, so the stored spec (and a saved take)
|
|
4
|
+
// keeps it; the spec store remembers the "auto" intent beside it (spec-store.js markFramesAuto).
|
|
5
|
+
import { findParams } from "../search.js";
|
|
6
|
+
import { expandUse } from "./index.js";
|
|
7
|
+
import { walkEnd } from "./info.js";
|
|
8
|
+
|
|
9
|
+
const MAX_FRAMES = 20000;
|
|
10
|
+
|
|
11
|
+
/** The spec with every open parameter at its upper bound. */
|
|
12
|
+
function atUpperBound(spec) {
|
|
13
|
+
const s = structuredClone(spec);
|
|
14
|
+
for (const p of findParams(spec)) {
|
|
15
|
+
let o = s;
|
|
16
|
+
for (const k of p.path.slice(0, -1)) o = o[k];
|
|
17
|
+
o[p.path[p.path.length - 1]] = p.hi;
|
|
18
|
+
}
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** resolveFrames(spec, { sk, scene, lenient }) -> spec (a copy when frames was "auto", else spec itself). */
|
|
23
|
+
export function resolveFrames(spec, { sk, scene, lenient = false } = {}) {
|
|
24
|
+
if (spec?.frames !== "auto") return spec;
|
|
25
|
+
const { spec: full, used } = expandUse({ ...atUpperBound(spec), frames: MAX_FRAMES }, { sk, scene, lenient, auto: true });
|
|
26
|
+
const ends = [...(Array.isArray(full.keys) ? full.keys.map(k => k?.f) : []), ...used.map(u => u.end),
|
|
27
|
+
...(Array.isArray(full.generators) ? full.generators.map(g => (g && Number.isFinite(g.f0) ? walkEnd(g) : null)) : [])];
|
|
28
|
+
return { ...spec, frames: Math.max(0, ...ends.filter(Number.isFinite)) + 1 };
|
|
29
|
+
}
|