cozyclay 2.1.1 → 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 +60 -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 +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-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-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.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/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 +150 -2624
- 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 +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 +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/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 +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 +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -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/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 +8 -124
- 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,211 @@
|
|
|
1
|
+
// Self-collision auto-fix (spec.fix.self, on unless false): the arms keep out of the rest of the body,
|
|
2
|
+
// on the fitted skin the BODY check measures (body.js, qa/body-checks.js). Wrist targets inside the
|
|
3
|
+
// body already went out onto the skin when they were solved (compile.js keepOut); what is left here:
|
|
4
|
+
// - elbow: an upper arm or forearm inside the body turns the elbow about the shoulder-wrist line
|
|
5
|
+
// (ctx.swivel; the hand keeps its place and turn). The turn is planned over the whole take at once,
|
|
6
|
+
// absolute: each frame scores every turn in TURNS by the depth left, the size of the turn and how
|
|
7
|
+
// far the elbow rises past LOW under the shoulder (no chicken wing), plus the change from the frame before, and the cheapest
|
|
8
|
+
// path wins (Viterbi), so the elbow eases in and out instead of snapping;
|
|
9
|
+
// - hand: a hand or forearm still a little inside moves its wrist IK target out along the skin's
|
|
10
|
+
// normal (ctx.skinPush), at most STEP a pass and REACH in all, eased over neighbouring frames. Deeper hits, and the
|
|
11
|
+
// head and neck (a hand at the mouth is on purpose), stay for QA.
|
|
12
|
+
// Frames whose arm is FK (armw 0) only get the elbow turn. The report says what moved.
|
|
13
|
+
import { bodyFor, bodyNormal, limbDepth, segmentDepth } from "../body.js";
|
|
14
|
+
import { add, sub, scale, len, apply, axisRot } from "../compiler/math.js";
|
|
15
|
+
import { CAPSULES, segPoints, sdBox } from "../qa/geometry.js";
|
|
16
|
+
import { round4 } from "./ease.js";
|
|
17
|
+
import { turnWeight } from "../compiler/pose.js";
|
|
18
|
+
import { allowance } from "../qa/checks.js";
|
|
19
|
+
import { regionOf } from "../qa/body-checks.js";
|
|
20
|
+
|
|
21
|
+
const TURNS = Array.from({ length: 19 }, (_, i) => -90 + i * 10);
|
|
22
|
+
const PASSES = 3, EASE = 5, MARGIN = 0.003, ALLOW = 0.003, STEP = 0.03, REACH = 0.08;
|
|
23
|
+
/** The most of a declared allowance the push leaves alone (m): a touch (a hand in a pocket), not a skill's leeway for a sweep it cannot avoid. */
|
|
24
|
+
const TOUCH = 0.03;
|
|
25
|
+
/** Plan weights: per cm^2 of depth, per (10 deg)^2 of turn, per cm^2 of elbow over the shoulder, per (10 deg)^2 of change a frame. */
|
|
26
|
+
const DEEP = 40, TURN = 0.05, RISE = 4, CHANGE = 2;
|
|
27
|
+
/** How far (m) under the shoulder the elbow may come before RISE counts. Where the spec declares a chicken-wing check
|
|
28
|
+
* on this elbow (checks.below, joint <Side>ForeArm under <Side>Arm: the smoke skill's), rising past its min costs
|
|
29
|
+
* WING_PRICE: a smoking elbow the turn raised past it broke that check once the wrist stopped at its limit (wrist-clamp.js). */
|
|
30
|
+
const LOW = 0.09, WING_PRICE = 400;
|
|
31
|
+
|
|
32
|
+
/** The spec's chicken-wing min (m) for the elbow of `side` on frame i (a below check on it at least half on), else null. */
|
|
33
|
+
function wingMin(out, i, side) {
|
|
34
|
+
let min = null;
|
|
35
|
+
for (const b of out.ctx.spec.checks?.below ?? []) {
|
|
36
|
+
const joint = String(b.joint).replace(/^joint:/, ""), ref = String(b.ref).replace(/^joint:/, ""), f = out.f0 + i;
|
|
37
|
+
const on = Array.isArray(b.when) ? f >= b.when[0] && f <= b.when[1] : b.when ? (out.frames[i].c[b.when] ?? 0) > 0.5 : true;
|
|
38
|
+
if (on && joint === `${side}ForeArm` && ref === `${side}Arm` && Number.isFinite(b.min)) min = Math.max(min ?? 0, b.min);
|
|
39
|
+
}
|
|
40
|
+
return min;
|
|
41
|
+
}
|
|
42
|
+
/** The most the turn changes in one frame (deg): a fast arm turned faster than this pops. */
|
|
43
|
+
const STEP_TURN = 10;
|
|
44
|
+
const SIDES = { l: "Left", r: "Right" };
|
|
45
|
+
|
|
46
|
+
/** How much hand k is on a measured target at channels c: a fit of its own (the mouth), or a fit of the
|
|
47
|
+
* other hand anchored to a prop k holds (the flame under k's cigarette). Such a hand is not moved. */
|
|
48
|
+
export function fitWeight(spec, c, k) {
|
|
49
|
+
let w = 0;
|
|
50
|
+
for (const fit of spec.fits ?? []) {
|
|
51
|
+
const a = spec.anchors?.[fit.anchor], on = c[`w_${fit.anchor}_${fit.side}`] ?? 0;
|
|
52
|
+
if (fit.side === k || (a?.kind === "prop" && spec.props?.[a.prop]?.hand === k)) w = Math.max(w, on);
|
|
53
|
+
}
|
|
54
|
+
return Math.min(1, w);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const CAP = Object.fromEntries(CAPSULES.map(c => [c[0], c[3]]));
|
|
58
|
+
/** How deep (m) segment A-B of radius r goes into the scene's boxes (QA's COLLIDE on the same capsules). */
|
|
59
|
+
function sceneDepth(boxes, A, B, r) {
|
|
60
|
+
let worst = 0;
|
|
61
|
+
for (const box of boxes) worst = Math.max(worst, r - Math.min(...segPoints(A, B, 5).map(p => sdBox(p, box))));
|
|
62
|
+
return worst;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Deepest skin depth (m) of the upper arm and forearm with the elbow turned `deg` more about the shoulder-wrist line,
|
|
66
|
+
* and how deep they go into the scene's boxes (an elbow turned out of the body must not turn into a steering wheel). */
|
|
67
|
+
function elbowDepth(body, sk, s, side, deg, boxes = []) {
|
|
68
|
+
const I = sk.index, a = I[`${side}Arm`], fa = I[`${side}ForeArm`], S = s.P[a], W = s.P[I[`${side}Hand`]];
|
|
69
|
+
const E = deg ? add(S, apply(axisRot(sub(W, S), deg), sub(s.P[fa], S))) : s.P[fa];
|
|
70
|
+
const worst = Math.max(segmentDepth(body, sk, s, a, S, E), segmentDepth(body, sk, s, fa, E, W));
|
|
71
|
+
const k = side === "Left" ? "l" : "r", scene = boxes.length ? Math.max(sceneDepth(boxes, S, E, CAP[`uarm_${k}`]), sceneDepth(boxes, E, W, CAP[`farm_${k}`])) : 0;
|
|
72
|
+
return { depth: worst, scene, rise: Math.max(0, E[1] - (S[1] - LOW)), under: S[1] - E[1] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Shoulder to wrist at full stretch (m), at bind. */
|
|
76
|
+
const armLength = (sk, side) => { const I = sk.index, P = sk.restWorldPos; return len(sub(P[I[`${side}ForeArm`]], P[I[`${side}Arm`]])) + len(sub(P[I[`${side}Hand`]], P[I[`${side}ForeArm`]])); };
|
|
77
|
+
|
|
78
|
+
/** Absolute elbow turns (deg) for side k over the take, or null when no frame needs or has one. */
|
|
79
|
+
function planTurns(out, body, k) {
|
|
80
|
+
const { sk } = out.ctx, side = SIDES[k], n = out.frames.length, now = out.ctx.swivel[k];
|
|
81
|
+
const cost = out.frames.map(({ s, c }, i) => {
|
|
82
|
+
const tw = turnWeight(c[`armw_${k}`] ?? 0), cur = (now.get(out.f0 + i) ?? 0) * tw;
|
|
83
|
+
if (!cur && elbowDepth(body, sk, s, side, 0).depth <= ALLOW) return null;
|
|
84
|
+
// only the boxes the arm can reach this frame (their bounding sphere within the arm's length of the shoulder)
|
|
85
|
+
const S = s.P[sk.index[`${side}Arm`]], reach = armLength(sk, side) + 0.15;
|
|
86
|
+
const boxes = Object.values(out.objectsAt(i)).filter(box => box.half && !box.ignore && len(sub(add(box.pos, apply(box.R, [0, box.half[1], 0])), S)) - len(box.half) < reach);
|
|
87
|
+
const base = elbowDepth(body, sk, s, side, -cur, boxes).scene, wmin = wingMin(out, i, side);
|
|
88
|
+
return TURNS.map(deg => {
|
|
89
|
+
const { depth, scene, rise, under } = elbowDepth(body, sk, s, side, deg * tw - cur, boxes), cm = Math.max(0, depth - ALLOW) * 100;
|
|
90
|
+
const wing = wmin === null ? 0 : Math.max(0, wmin - under);
|
|
91
|
+
// a box the arm was already in at no turn is QA's COLLIDE to report, not this plan's to avoid
|
|
92
|
+
const hit = Math.max(0, scene - Math.max(base, ALLOW)) * 100;
|
|
93
|
+
return DEEP * (cm * cm + hit * hit) + TURN * (deg / 10) ** 2 + RISE * (rise * 100) ** 2 + WING_PRICE * (wing * 100) ** 2;
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
if (cost.every(c => !c)) return null;
|
|
97
|
+
// a frame that needs no turn has none; the take may start already turned
|
|
98
|
+
const zero = TURNS.indexOf(0), still = TURNS.map((_, j) => (j === zero ? 0 : Infinity));
|
|
99
|
+
let acc = TURNS.map(() => 0);
|
|
100
|
+
const back = [];
|
|
101
|
+
for (let i = 0; i < n; i++) {
|
|
102
|
+
const here = cost[i] ?? still, prev = acc, from = [];
|
|
103
|
+
acc = here.map((h, j) => {
|
|
104
|
+
let best = Infinity, arg = zero;
|
|
105
|
+
for (let p = 0; p < TURNS.length; p++) {
|
|
106
|
+
if (Math.abs(TURNS[j] - TURNS[p]) > STEP_TURN) continue;
|
|
107
|
+
const v = prev[p] + CHANGE * ((TURNS[j] - TURNS[p]) / 10) ** 2;
|
|
108
|
+
if (v < best) { best = v; arg = p; }
|
|
109
|
+
}
|
|
110
|
+
from.push(arg);
|
|
111
|
+
return best + h;
|
|
112
|
+
});
|
|
113
|
+
back.push(from);
|
|
114
|
+
}
|
|
115
|
+
const path = new Array(n);
|
|
116
|
+
let j = acc.indexOf(Math.min(...acc));
|
|
117
|
+
for (let i = n - 1; i >= 0; i--) { path[i] = TURNS[j]; j = back[i][j]; }
|
|
118
|
+
// the 10-degree steps, smoothed
|
|
119
|
+
return path.map((_, i) => (path[Math.max(0, i - 1)] + 2 * path[i] + path[Math.min(n - 1, i + 1)]) / 4);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Per-frame wrist pushes for side k: a hand or forearm a little inside the skin (not the head or neck) goes out along its normal. */
|
|
123
|
+
function handPushes(out, body, k, stats) {
|
|
124
|
+
const { sk } = out.ctx, side = SIDES[k], I = sk.index, n = out.frames.length, v = new Array(n).fill(null);
|
|
125
|
+
const face = [I.Head, I.Neck, I.HeadTop_End].filter(b => b !== undefined);
|
|
126
|
+
for (let i = 0; i < n; i++) {
|
|
127
|
+
const { s, c } = out.frames[i], f = out.f0 + i;
|
|
128
|
+
// a hand on a measured target (a fit's anchor: the mouth, the flame) stays where its goal needs it; on its way
|
|
129
|
+
// there the push fades out with the target's weight
|
|
130
|
+
const free = 1 - fitWeight(out.ctx.spec, c, k);
|
|
131
|
+
if (free < 0.05) continue;
|
|
132
|
+
// past what the spec allows (a hand in a pocket touches the hip: checks.allow), as QA's BODY measures it
|
|
133
|
+
let hit = null;
|
|
134
|
+
for (const b of [I[`${side}Hand`], I[`${side}ForeArm`]]) {
|
|
135
|
+
const h = limbDepth(body, sk, s, b);
|
|
136
|
+
if (h.into < 0 || face.includes(h.into)) continue;
|
|
137
|
+
const allowed = Math.min(TOUCH, allowance(out.ctx.spec.checks?.allow, regionOf(sk, b), regionOf(sk, h.into)) ?? 0), past = h.depth - Math.max(allowed, ALLOW);
|
|
138
|
+
if (past > 0 && (!hit || past > hit.past)) hit = { ...h, b, past };
|
|
139
|
+
}
|
|
140
|
+
if (!hit) continue;
|
|
141
|
+
if (!(c[`armw_${k}`] > 1e-4)) { stats.skipped++; continue; }
|
|
142
|
+
const done = len(out.ctx.skinPush[k].get(f) ?? [0, 0, 0]);
|
|
143
|
+
if (done >= REACH) { stats.deep++; continue; }
|
|
144
|
+
const nrm = bodyNormal(body, s, hit.p, new Set([...body.skip.get(hit.b), ...face]));
|
|
145
|
+
if (nrm) v[i] = scale(nrm, free * Math.min(STEP, REACH - done, hit.past + MARGIN));
|
|
146
|
+
}
|
|
147
|
+
// eased dilation: neighbours get the strongest nearby push, scaled down
|
|
148
|
+
const eased = new Array(n).fill(null);
|
|
149
|
+
v.forEach((p, i) => {
|
|
150
|
+
if (!p) return;
|
|
151
|
+
for (let j = Math.max(0, i - EASE - 1); j <= Math.min(n - 1, i + EASE + 1); j++) {
|
|
152
|
+
const t = 1 - Math.abs(i - j) / (EASE + 1), w = scale(p, t * t * (3 - 2 * t));
|
|
153
|
+
if (!eased[j] || len(w) > len(eased[j])) eased[j] = w;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return eased;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function selfClear(out, engine) {
|
|
160
|
+
const body = bodyFor(out.ctx.sk);
|
|
161
|
+
if (!body) return null;
|
|
162
|
+
const n = out.frames.length, stats = { skipped: 0, deep: 0 }, turned = new Set(), pushed = new Set();
|
|
163
|
+
let maxTurn = 0, maxPush = 0;
|
|
164
|
+
for (let pass = 0; pass < PASSES; pass++) {
|
|
165
|
+
let touched = new Set();
|
|
166
|
+
for (const k of ["r", "l"]) {
|
|
167
|
+
const plan = planTurns(out, body, k);
|
|
168
|
+
if (!plan) continue;
|
|
169
|
+
plan.forEach((deg, i) => {
|
|
170
|
+
const f = out.f0 + i, cur = out.ctx.swivel[k].get(f) ?? 0, next = Math.abs(deg) < 0.5 ? 0 : deg;
|
|
171
|
+
if (Math.abs(next - cur) < 0.5) return;
|
|
172
|
+
if (next) out.ctx.swivel[k].set(f, next); else out.ctx.swivel[k].delete(f);
|
|
173
|
+
maxTurn = Math.max(maxTurn, Math.abs(next)); if (next) turned.add(`${k}${i}`); touched.add(f);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (touched.size) engine.solve([...touched]);
|
|
177
|
+
touched = new Set();
|
|
178
|
+
stats.skipped = 0; stats.deep = 0;
|
|
179
|
+
for (const k of ["r", "l"]) {
|
|
180
|
+
handPushes(out, body, k, stats).forEach((p, i) => {
|
|
181
|
+
if (!p) return;
|
|
182
|
+
const f = out.f0 + i, next = add(out.ctx.skinPush[k].get(f) ?? [0, 0, 0], p);
|
|
183
|
+
out.ctx.skinPush[k].set(f, next);
|
|
184
|
+
maxPush = Math.max(maxPush, len(next)); pushed.add(`${k}${i}`); touched.add(f);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (!touched.size) break;
|
|
188
|
+
engine.solve([...touched]);
|
|
189
|
+
}
|
|
190
|
+
// the pushes add up pass by pass from frames that read their normals apart: smoothed over time, so the hand
|
|
191
|
+
// does not jerk (a 1-4-6-4-1 kernel, twice)
|
|
192
|
+
if (pushed.size) {
|
|
193
|
+
const touched = new Set();
|
|
194
|
+
for (const k of ["r", "l"]) {
|
|
195
|
+
const at = i => out.ctx.skinPush[k].get(out.f0 + i) ?? [0, 0, 0];
|
|
196
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
197
|
+
const was = Array.from({ length: n }, (_, i) => at(i));
|
|
198
|
+
was.forEach((_, i) => {
|
|
199
|
+
const sm = [-2, -1, 0, 1, 2].reduce((acc, d, j) => add(acc, scale(was[Math.min(n - 1, Math.max(0, i + d))], [1, 4, 6, 4, 1][j] / 16)), [0, 0, 0]);
|
|
200
|
+
if (len(sm) < 1e-5 && !out.ctx.skinPush[k].has(out.f0 + i)) return;
|
|
201
|
+
out.ctx.skinPush[k].set(out.f0 + i, sm); touched.add(out.f0 + i);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (touched.size) engine.solve([...touched]);
|
|
206
|
+
}
|
|
207
|
+
if (!turned.size && !pushed.size && !stats.skipped) return null;
|
|
208
|
+
return { elbowFrames: turned.size, maxTurn: Math.round(maxTurn), handFrames: pushed.size, maxPush: round4(maxPush), skipped: stats.skipped, tooDeep: stats.deep,
|
|
209
|
+
how: "elbow turned about the shoulder-wrist line; a hand a little inside pushed out along the skin" };
|
|
210
|
+
}
|
|
211
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Planted-foot lock: while a heel or ball is planted (QA's own definition, over the ground under
|
|
2
|
+
// it: the floor or an object's top, qa/ground.js) and drifts less than
|
|
3
|
+
// 1 cm, the ankle target is shifted so the planted point stays where it landed. The foot pose
|
|
4
|
+
// depends only on the ankle target, fyaw and fpitch, so the shift moves the sole rigidly.
|
|
5
|
+
// A pivot (the planted point is the only one down: the heel is up over a planted ball, or the
|
|
6
|
+
// ball over a planted heel) is held whatever the drift: fyaw and fpitch then turn the foot about
|
|
7
|
+
// that point, as a batter's back foot turns on its ball, instead of about the ankle. A pivot that
|
|
8
|
+
// held more than 1 cm keeps holding while its point stays under PIVOT_H (a hover, not a step) and
|
|
9
|
+
// lets go no faster than EASE_STEP a frame.
|
|
10
|
+
import { add, sub } from "../compiler/math.js";
|
|
11
|
+
import { QA_LIMITS } from "../qa/checks.js";
|
|
12
|
+
import { groundSole } from "../qa/ground.js";
|
|
13
|
+
import { round4 } from "./ease.js";
|
|
14
|
+
|
|
15
|
+
const MAX_DRIFT = 0.01, DECAY = 4, PIVOT_H = 0.03, EASE_STEP = 0.02;
|
|
16
|
+
|
|
17
|
+
export function lockFeet(out, engine) {
|
|
18
|
+
const n = out.frames.length, H = QA_LIMITS.plantH;
|
|
19
|
+
let plants = 0, pivots = 0, worst = 0;
|
|
20
|
+
const touched = new Set();
|
|
21
|
+
for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
|
|
22
|
+
const ch = out.baked[`foot_${k}`];
|
|
23
|
+
if (!ch) continue;
|
|
24
|
+
const sole = out.frames.map((_, i) => groundSole(out, i, S));
|
|
25
|
+
const planted = (i, key) => sole[i][key][1] < H && sole[i - 1][key][1] < H && Math.abs(sole[i][key][1] - sole[i - 1][key][1]) < 0.0005;
|
|
26
|
+
const delta = Array.from({ length: n }, () => null);
|
|
27
|
+
for (let i = 1; i < n;) {
|
|
28
|
+
if (!planted(i, "ball") && !planted(i, "heel")) { i++; continue; }
|
|
29
|
+
// one contact run: the ball wins over the heel; a switch re-anchors on the new point
|
|
30
|
+
let key = null, ref = null, j = i, max = 0, last = [0, 0, 0], pivot = false;
|
|
31
|
+
const run = [];
|
|
32
|
+
const hold = jj => {
|
|
33
|
+
const d = sub(ref, sole[jj][key]); d[1] = 0;
|
|
34
|
+
last = d;
|
|
35
|
+
run.push([jj, d]);
|
|
36
|
+
max = Math.max(max, Math.hypot(d[0], d[2]));
|
|
37
|
+
};
|
|
38
|
+
for (; j < n && (planted(j, "ball") || planted(j, "heel")); j++) {
|
|
39
|
+
const kj = planted(j, "ball") ? "ball" : "heel";
|
|
40
|
+
pivot ||= sole[j][kj === "ball" ? "heel" : "ball"][1] >= H;
|
|
41
|
+
// on a switch the new point is anchored where the previous frame's shift put it
|
|
42
|
+
if (kj !== key) { key = kj; ref = add(sole[j - 1][key], last); }
|
|
43
|
+
hold(j);
|
|
44
|
+
}
|
|
45
|
+
if (pivot && max >= MAX_DRIFT) for (; j < n && sole[j][key][1] < PIVOT_H; j++) hold(j);
|
|
46
|
+
if (max > 2e-4 && (max < MAX_DRIFT || pivot)) {
|
|
47
|
+
plants++; worst = Math.max(worst, max);
|
|
48
|
+
if (max >= MAX_DRIFT) pivots++;
|
|
49
|
+
for (const [jj, d] of run) delta[jj] = d;
|
|
50
|
+
const decay = Math.max(DECAY, Math.ceil(Math.hypot(last[0], last[2]) / EASE_STEP));
|
|
51
|
+
for (let m = 1; m <= decay && j - 1 + m < n; m++) {
|
|
52
|
+
if (delta[j - 1 + m]) break;
|
|
53
|
+
const t = m / (decay + 1), e = 1 - t * t * (3 - 2 * t);
|
|
54
|
+
delta[j - 1 + m] = [last[0] * e, 0, last[2] * e];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
i = j;
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
if (!delta[i]) continue;
|
|
61
|
+
const f = out.f0 + i;
|
|
62
|
+
ch[f] = add(ch[f], delta[i]);
|
|
63
|
+
touched.add(f);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!plants) return null;
|
|
67
|
+
engine.solve([...touched]);
|
|
68
|
+
return { plants, ...(pivots ? { pivots } : {}), frames: touched.size, max: round4(worst),
|
|
69
|
+
how: pivots ? "planted heel/ball held in place; turning feet pivot on it" : "planted heel/ball held in place" };
|
|
70
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// A planted flat foot keeps its heading (lane Q1). While both its heel and its ball are planted
|
|
2
|
+
// (QA's definition: under plantH over the ground, qa/ground.js, and still), the foot cannot turn without one of them sliding: a
|
|
3
|
+
// flat foot under the body's weight grips the floor. A turn the channels ask for then (fyaw_<k>, or
|
|
4
|
+
// the body's yaw when fyaw_<k> is not keyed) waits: the foot keeps the heading it planted with
|
|
5
|
+
// until its heel or its toes come up or it lifts, and then eases onto the keyed heading at about
|
|
6
|
+
// TURN_RATE degrees a frame. With the heel up it turns on its ball (slide.js holds the ball and
|
|
7
|
+
// pivots the foot there), in the air it turns freely. Session 4db5d447: fyaw_r keyed 0 at f0 and
|
|
8
|
+
// 30 at f37 turned the flat back foot 9 deg over the load (f10-19), and its heel slid 2.2 cm.
|
|
9
|
+
// A foot that turns only while a heel or ball is up, and a flat foot that does not turn, are left
|
|
10
|
+
// as they were.
|
|
11
|
+
import { QA_LIMITS } from "../qa/checks.js";
|
|
12
|
+
import { groundSole } from "../qa/ground.js";
|
|
13
|
+
import { round4 } from "./ease.js";
|
|
14
|
+
|
|
15
|
+
const TURN_RATE = 6, DECAY = 4, TOL = 1;
|
|
16
|
+
const smooth = t => t * t * (3 - 2 * t);
|
|
17
|
+
|
|
18
|
+
/** Holds the heading of flat planted feet (fyaw_<k>), eases it onto the keys once a heel or ball is up; re-solves those frames. */
|
|
19
|
+
export function holdTurns(out, engine) {
|
|
20
|
+
const n = out.frames.length, H = QA_LIMITS.plantH, touched = new Set();
|
|
21
|
+
let worst = 0;
|
|
22
|
+
for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
|
|
23
|
+
const keyed = out.baked[`fyaw_${k}`] ?? out.baked.yaw;
|
|
24
|
+
if (!out.baked[`foot_${k}`] || !keyed) continue;
|
|
25
|
+
const sole = out.frames.map((_, i) => groundSole(out, i, S));
|
|
26
|
+
const down = (i, key) => sole[i][key][1] < H && sole[i - 1][key][1] < H && Math.abs(sole[i][key][1] - sole[i - 1][key][1]) < 0.0005;
|
|
27
|
+
const flat = i => i > 0 && i < n && down(i, "heel") && down(i, "ball");
|
|
28
|
+
const want = Array.from({ length: n }, (_, i) => keyed[out.f0 + i]), yaw = want.slice();
|
|
29
|
+
for (let i = 1; i < n; i++) {
|
|
30
|
+
if (!flat(i)) continue;
|
|
31
|
+
const held = yaw[i - 1];
|
|
32
|
+
let j = i, turn = 0;
|
|
33
|
+
for (; flat(j); j++) turn = Math.max(turn, Math.abs(yaw[j] - held));
|
|
34
|
+
// a turn within TOL degrees (the heel moves under 3 mm) is left as it is
|
|
35
|
+
if (turn <= TOL) { i = j; continue; }
|
|
36
|
+
worst = Math.max(worst, turn);
|
|
37
|
+
for (let m = i; m < j; m++) yaw[m] = held;
|
|
38
|
+
// then onto the keyed heading, as long as the foot does not plant flat again
|
|
39
|
+
const gap = j < n ? want[j] - held : 0, ease = Math.max(DECAY, Math.ceil(Math.abs(gap) / TURN_RATE));
|
|
40
|
+
for (let m = 0; gap && m < ease && j + m < n && !flat(j + m); m++) yaw[j + m] = want[j + m] - gap * (1 - smooth((m + 1) / (ease + 1)));
|
|
41
|
+
i = j;
|
|
42
|
+
}
|
|
43
|
+
const moved = yaw.map((v, i) => v !== want[i]);
|
|
44
|
+
if (!moved.some(Boolean)) continue;
|
|
45
|
+
const ch = (out.baked[`fyaw_${k}`] = keyed.slice());
|
|
46
|
+
moved.forEach((m, i) => { if (m) { ch[out.f0 + i] = yaw[i]; touched.add(out.f0 + i); } });
|
|
47
|
+
}
|
|
48
|
+
if (!touched.size) return null;
|
|
49
|
+
engine.solve([...touched]);
|
|
50
|
+
return { frames: touched.size, max: round4(worst) };
|
|
51
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// C16. Follow entries: spec.objects[id] = { follow: <ref>, offset?, aim?, visible?, level? } puts a
|
|
2
|
+
// record (a body, a group root, or a record with no box: an effect, a light, an Empty) on a point
|
|
3
|
+
// of the body at every frame. The ref is any point ref (refs.js): landmark:mouth, joint:<Bone>,
|
|
4
|
+
// prop:<prop>.<point>, anchor:<name>, object:<id> (an object a follow entry does not move).
|
|
5
|
+
// aim where the record's +Y points (effects emit along it): "up" (default, world up), "face"
|
|
6
|
+
// (the face's forward direction, the Head bone's rest +Z, as checks.orient "face"), a
|
|
7
|
+
// world direction [x, y, z], or a ref (toward that point). The record turns the shortest
|
|
8
|
+
// way from upright onto it.
|
|
9
|
+
// offset metres along the record's own axes after aim (+Y along aim), from the ref point.
|
|
10
|
+
// The take bakes a world-space track at every frame (take.js: pos, quat and hold on every frame,
|
|
11
|
+
// so object-travel.js cuts the carry of the record's own parents), so the renderer and the effects
|
|
12
|
+
// (born at the emitter's pose at the birth frame) see the moving path. A follow entry is not an
|
|
13
|
+
// obstacle (compile.js marks its bodies carried) and QA checks it against nothing: it rides the body.
|
|
14
|
+
import { refPoint } from "./refs.js";
|
|
15
|
+
import { carried } from "./compiler/limbs.js";
|
|
16
|
+
import { add, sub, apply, norm, len, matFromQuat } from "./compiler/math.js";
|
|
17
|
+
import { arcQuat } from "./compiler/quat.js";
|
|
18
|
+
|
|
19
|
+
const UP = [0, 1, 0];
|
|
20
|
+
|
|
21
|
+
/** The unit direction aim gives at absolute frame f (inside the window), `from` the followed point. */
|
|
22
|
+
function aimDir(out, f, aim = "up", from) {
|
|
23
|
+
if (Array.isArray(aim)) return norm(aim);
|
|
24
|
+
if (aim === "face") { const { sk } = out.ctx; return norm(carried(sk, out.frames[f - (out.f0 ?? 0)].s, sk.index.Head, [0, 0, 1])); }
|
|
25
|
+
const to = aim === "up" ? null : refPoint(out, f, aim), d = to ? sub(to, from) : UP;
|
|
26
|
+
return len(d) > 1e-9 ? norm(d) : UP;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* World pose { pos, R } of the record follow entry o puts at absolute frame f of compile result
|
|
31
|
+
* out (a frame outside the compiled window takes the nearest one in it), or null when the ref
|
|
32
|
+
* has no point there.
|
|
33
|
+
*/
|
|
34
|
+
export function followPose(out, o, f) {
|
|
35
|
+
const lo = out.f0 ?? 0, ff = Math.max(lo, Math.min(lo + out.frames.length - 1, f)), p = refPoint(out, ff, o.follow);
|
|
36
|
+
if (!p) return null;
|
|
37
|
+
const R = matFromQuat(arcQuat(UP, aimDir(out, ff, o.aim, p)));
|
|
38
|
+
return { pos: add(p, apply(R, o.offset ?? [0, 0, 0])), R };
|
|
39
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// C17. Objects nobody holds (a can inside a machine, a box on a shelf) move by keys and fall.
|
|
2
|
+
// spec.objects[id] with no prop, follow or hinge field, for a body or a group (its root):
|
|
3
|
+
// keys [{ f, pos, rot | aim (+ roll) }] the world pose of its root (a body's base pivot, a group's
|
|
4
|
+
// origin) at frame f, on the curve held keys use (object-keys.js keyedPose, C14). aim turns the
|
|
5
|
+
// scene pose's +Y onto aim, then roll degrees about it. Before the first key of a run it stays
|
|
6
|
+
// where it was (its scene pose, or where it landed), after the last it holds the last key.
|
|
7
|
+
// fall { at, onto?, bounce? } or a list from where it is at frame `at` it drops straight down
|
|
8
|
+
// under gravity (G m/s^2, no tumbling) onto the top of the highest still body under the centre of
|
|
9
|
+
// its parts (onto: that object's top; nothing below: the floor, y 0), bounces at most once (bounce:
|
|
10
|
+
// the restitution 0..0.6, default 0.25, never higher than BOUNCE_MAX m) and rests there.
|
|
11
|
+
// Keys and falls chain in frame order: a fall starts from the pose the keys before it leave (a key on the
|
|
12
|
+
// fall's own frame comes first), keys after a fall take over at their first frame.
|
|
13
|
+
// A carry (C3) takes fall too: before its grab (the hand finds it where it landed) or from its release on.
|
|
14
|
+
// A free object moves like a hinged one (compiler/compile.js objectTracks): an obstacle at its pose that
|
|
15
|
+
// frame, read through object-pose.js by refs, goals, probes, observations.near and QA. Pure over the spec,
|
|
16
|
+
// the scene and the time map (a retime moves its keys and falls with the body's keys): the body never
|
|
17
|
+
// moves it, and nothing it lands on moves (bodies a spec.objects entry moves are no support).
|
|
18
|
+
import { isCarried, compose, relative, movesRecord } from "./carried.js";
|
|
19
|
+
import { groupsOf } from "./scene.js";
|
|
20
|
+
import { curveOf, keyedPose } from "./object-keys.js";
|
|
21
|
+
import { add, apply, sub, T, quatFromMat } from "./compiler/math.js";
|
|
22
|
+
|
|
23
|
+
/** Gravity, m/s^2. */
|
|
24
|
+
export const G = 9.8;
|
|
25
|
+
/** The highest a bounce goes, metres. */
|
|
26
|
+
export const BOUNCE_MAX = 0.03;
|
|
27
|
+
const BOUNCE = 0.25, TOUCH = 0.005;
|
|
28
|
+
const HELD_OR_HINGED = ["prop", "follow", "pivot", "axis", "channel"];
|
|
29
|
+
|
|
30
|
+
/** A free entry (C17): no prop, follow or hinge field, and keys or fall. */
|
|
31
|
+
export const isFree = o => !!o && typeof o === "object" && !Array.isArray(o) && HELD_OR_HINGED.every(k => o[k] === undefined) && (o.keys !== undefined || o.fall !== undefined);
|
|
32
|
+
/** The falls of an entry (fall is one or a list), as given. */
|
|
33
|
+
const fallsOf = o => (o?.fall === undefined ? [] : Array.isArray(o.fall) ? o.fall : [o.fall]);
|
|
34
|
+
|
|
35
|
+
const restOf = (scene, id) => scene?.objects?.[id] ?? groupsOf(scene)[id] ?? null;
|
|
36
|
+
const partsIn = (scene, id) => Object.keys(scene?.objects ?? {}).filter(k => k === id || scene.objects[k].ancestors?.includes(id));
|
|
37
|
+
|
|
38
|
+
/** The 8 corners of a base-pivot box { pos, R, half }. */
|
|
39
|
+
function corners(box) {
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) out.push(add(box.pos, apply(box.R, [x * box.half[0], y * box.half[1], z * box.half[2]])));
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
const topOf = box => Math.max(...corners(box).map(p => p[1]));
|
|
45
|
+
/** True when the vertical through x/z point p crosses the footprint of box. */
|
|
46
|
+
const under = (box, p) => { const q = apply(T(box.R), sub([p[0], box.pos[1] + box.half[1], p[2]], box.pos)); return Math.abs(q[0]) <= box.half[0] && Math.abs(q[2]) <= box.half[2]; };
|
|
47
|
+
|
|
48
|
+
/** Bodies a spec.objects entry moves (itself or a group above it): they hold nothing up. */
|
|
49
|
+
function moversIn(spec, scene) {
|
|
50
|
+
const out = new Set();
|
|
51
|
+
for (const [id, o] of Object.entries(scene?.objects ?? {})) if ([id, ...(o.ancestors ?? [])].some(k => movesRecord(spec.objects?.[k]))) out.add(id);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The part boxes of `id` with its root at world pose `root`. */
|
|
56
|
+
function boxesAtRoot(scene, id, root) {
|
|
57
|
+
const rest = restOf(scene, id);
|
|
58
|
+
return partsIn(scene, id).map(p => ({ id: p, ...compose(root, relative(rest, scene.objects[p])), half: scene.objects[p].half }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** What `id` lands on from pose `start`: { support (a body id, or null for the floor), top, h (the drop, m) }. */
|
|
62
|
+
export function landingOf(spec, scene, id, start, onto = null) {
|
|
63
|
+
const boxes = boxesAtRoot(scene, id, start), own = new Set(boxes.map(b => b.id)), movers = moversIn(spec, scene);
|
|
64
|
+
if (!boxes.length) return { support: null, top: 0, h: 0 };
|
|
65
|
+
const low = Math.min(...boxes.flatMap(b => corners(b).map(p => p[1])));
|
|
66
|
+
const c = boxes.flatMap(corners).reduce((s, p) => add(s, p), [0, 0, 0]).map(v => v / (boxes.length * 8));
|
|
67
|
+
const pool = (onto ? partsIn(scene, onto) : Object.keys(scene.objects)).filter(k => !own.has(k) && !movers.has(k));
|
|
68
|
+
let best = null;
|
|
69
|
+
for (const k of pool) {
|
|
70
|
+
const box = scene.objects[k], top = topOf(box);
|
|
71
|
+
if (under(box, c) && top <= low + TOUCH && (!best || top > best.top)) best = { support: k, top };
|
|
72
|
+
}
|
|
73
|
+
// onto an object that is not right below: its highest top, the agent placed it
|
|
74
|
+
if (!best && onto) for (const k of pool) { const top = topOf(scene.objects[k]); if (top <= low + TOUCH && (!best || top > best.top)) best = { support: k, top }; }
|
|
75
|
+
best ??= { support: null, top: 0 };
|
|
76
|
+
return { ...best, h: Math.max(0, low - best.top) };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A fall from root pose `start` at (mapped) frame t0: its timing, its pose at a frame, where it rests. */
|
|
80
|
+
function fallPlan(spec, scene, id, start, t0, fall, fps) {
|
|
81
|
+
const land = landingOf(spec, scene, id, start, fall.onto ?? null), h = land.h, tl = Math.sqrt(2 * h / G);
|
|
82
|
+
const e = Math.min(0.6, Math.max(0, fall.bounce ?? BOUNCE)), vb = Math.min(e * G * tl, Math.sqrt(2 * G * BOUNCE_MAX)), tb = 2 * vb / G;
|
|
83
|
+
const rest = { pos: [start.pos[0], start.pos[1] - h, start.pos[2]], R: start.R };
|
|
84
|
+
const at = f => {
|
|
85
|
+
const t = (f - t0) / fps;
|
|
86
|
+
if (t <= 0) return start;
|
|
87
|
+
const dy = t <= tl ? -0.5 * G * t * t : t <= tl + tb ? -h + vb * (t - tl) - 0.5 * G * (t - tl) ** 2 : -h;
|
|
88
|
+
return { pos: [start.pos[0], start.pos[1] + Math.min(0, dy), start.pos[2]], R: start.R };
|
|
89
|
+
};
|
|
90
|
+
return { t0, land: t0 + tl * fps, end: t0 + (tl + tb) * fps, h, support: land.support, top: land.top, at, rest };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The events of an entry's falls and key runs in frame order (keys split into runs by the falls between them). */
|
|
94
|
+
function eventsOf(o, map, from = -Infinity, to = Infinity) {
|
|
95
|
+
// a carry's keys pose it in hand (C14); only its falls are free
|
|
96
|
+
const keys = isCarried(o) ? [] : [...(o.keys ?? [])].sort((a, b) => a.f - b.f);
|
|
97
|
+
const falls = fallsOf(o).map(fl => ({ ...fl, t: map(fl.at) })).filter(fl => fl.t >= from && fl.t < to).sort((a, b) => a.t - b.t);
|
|
98
|
+
const events = [];
|
|
99
|
+
let run = [], i = 0;
|
|
100
|
+
for (const k of keys) {
|
|
101
|
+
while (i < falls.length && falls[i].t < map(k.f)) { if (run.length) events.push({ run }); run = []; events.push({ fall: falls[i++] }); }
|
|
102
|
+
run.push(k);
|
|
103
|
+
}
|
|
104
|
+
if (run.length) events.push({ run });
|
|
105
|
+
while (i < falls.length) events.push({ fall: falls[i++] });
|
|
106
|
+
return events;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The pose events give at mapped frame f, starting from pose `start`; `keep` caches each fall's plan on its event. */
|
|
110
|
+
function walk(events, f, start, plan, keep = true) {
|
|
111
|
+
let pose = start;
|
|
112
|
+
for (const e of events) {
|
|
113
|
+
if (e.run) {
|
|
114
|
+
const { t } = e.curve;
|
|
115
|
+
if (f < t[0]) return pose;
|
|
116
|
+
if (f <= t[t.length - 1]) return keyedPose(e.curve, f);
|
|
117
|
+
pose = keyedPose(e.curve, t[t.length - 1]);
|
|
118
|
+
} else {
|
|
119
|
+
if (f < e.fall.t) return pose;
|
|
120
|
+
const p = keep ? (e.plan ??= plan(pose, e.fall)) : plan(pose, e.fall);
|
|
121
|
+
if (f < p.end) return p.at(f);
|
|
122
|
+
pose = p.rest;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return pose;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const timelines = new WeakMap();
|
|
129
|
+
/** The events of entry o under time map `map`, cached per scene, entry and span (key curves built once). */
|
|
130
|
+
function timelineOf(ctx, id, o, map, part) {
|
|
131
|
+
if (!timelines.has(ctx.scene)) timelines.set(ctx.scene, new Map());
|
|
132
|
+
const cache = timelines.get(ctx.scene), key = `${id}|${part}|${ctx.grab}|${ctx.release}`, had = cache.get(key);
|
|
133
|
+
if (had && had.map === map && had.o === o && had.spec === ctx.spec) return had.events;
|
|
134
|
+
const rest = restOf(ctx.scene, id), m = map ?? (f => f), [from, to] = part === "before" ? [-Infinity, ctx.grab] : part === "after" ? [ctx.release, Infinity] : [-Infinity, Infinity];
|
|
135
|
+
const events = eventsOf(o, m, from, to);
|
|
136
|
+
for (const e of events) if (e.run) e.curve = curveOf({ keys: e.run }, m, () => quatFromMat(rest.R), null);
|
|
137
|
+
cache.set(key, { map, o, spec: ctx.spec, events });
|
|
138
|
+
return events;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const planner = (ctx, id) => (start, fall) => fallPlan(ctx.spec, ctx.scene, id, start, fall.t, fall, ctx.spec.fps ?? 30);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* World pose { pos, R } of the root of free entry `id` at absolute frame f (ctx = { spec, scene,
|
|
145
|
+
* timeMap }): keys and falls (C17) from its scene pose. null for an id the scene does not know.
|
|
146
|
+
*/
|
|
147
|
+
export function freePoseAt(ctx, id, f) {
|
|
148
|
+
const rest = restOf(ctx.scene, id), o = ctx.spec.objects?.[id];
|
|
149
|
+
if (!rest || !o) return null;
|
|
150
|
+
return walk(timelineOf(ctx, id, o, ctx.timeMap, "all"), f, { pos: rest.pos, R: rest.R }, planner(ctx, id));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Where carry `id` (entry o) is before its grab at absolute f: its scene pose, or where a fall before grab left it. */
|
|
154
|
+
export function beforeGrab(ctx, id, o, f, grab) {
|
|
155
|
+
const rest = restOf(ctx.scene, id);
|
|
156
|
+
if (!rest || !fallsOf(o).length) return rest && { pos: rest.pos, R: rest.R };
|
|
157
|
+
return walk(timelineOf({ ...ctx, grab }, id, o, ctx.timeMap, "before"), f, { pos: rest.pos, R: rest.R }, planner(ctx, id));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Where carry `id` (entry o) is at absolute f after its release, from its release pose; null without a fall from the release on. */
|
|
161
|
+
export function afterRelease(ctx, id, o, f, release, releasePose) {
|
|
162
|
+
const events = fallsOf(o).length ? timelineOf({ ...ctx, release }, id, o, ctx.timeMap, "after") : [];
|
|
163
|
+
// the release pose comes from the solved body, which a rebake changes: plan these falls afresh
|
|
164
|
+
return events.length ? walk(events, f, releasePose, planner(ctx, id), false) : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The falls of entry `id` as planned under the current time map, for readers (timeline, tests): [{ t0, land, end, h, support, top }]. */
|
|
168
|
+
export function fallPlans(ctx, id) {
|
|
169
|
+
const o = ctx.spec.objects?.[id];
|
|
170
|
+
if (!isFree(o)) return [];
|
|
171
|
+
freePoseAt(ctx, id, Infinity);
|
|
172
|
+
return timelineOf(ctx, id, o, ctx.timeMap, "all").filter(e => e.plan).map(({ plan: { t0, land, end, h, support, top } }) => ({ t0, land, end, h, support, top }));
|
|
173
|
+
}
|