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
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import { findBone, ikEvaluate, solveIk, solveHipsTranslate } from "./ik.js";
|
|
3
|
-
import { footLockTrack, relaxToePath } from "./foot-lock.js";
|
|
4
|
-
import { computeCenterOfMass } from "./auto-physics.js";
|
|
5
|
-
import { relaxPhysicsRoot } from "./physics-temporal.js";
|
|
6
|
-
import { refinePhysicsCandidate } from "./physics-refine.js";
|
|
7
|
-
import { createSurfaceSampler } from "./physics-surface.js";
|
|
8
|
-
import { createDynamicsSampler, inferSupportAlignment, supportDiagnostics } from "./physics-support.js";
|
|
9
|
-
import { createGroundSampler } from "./ground.js";
|
|
10
|
-
|
|
11
|
-
// A reviewable correction pass. The source key map is never modified; preview,
|
|
12
|
-
// strength, protected frames, and commit all use the same evaluated candidate.
|
|
13
|
-
export const SUPPORT_SITES = [
|
|
14
|
-
{ id: "leftFoot", label: "Left foot", ko: "왼발", bone: "LeftFoot", chain: "leftFoot", kind: "foot", match: /Left(Foot|Toe)/ },
|
|
15
|
-
{ id: "rightFoot", label: "Right foot", ko: "오른발", bone: "RightFoot", chain: "rightFoot", kind: "foot", match: /Right(Foot|Toe)/ },
|
|
16
|
-
{ id: "leftHand", label: "Left hand", ko: "왼손", bone: "LeftHand", chain: "leftHand", kind: "hand", match: /LeftHand/ },
|
|
17
|
-
{ id: "rightHand", label: "Right hand", ko: "오른손", bone: "RightHand", chain: "rightHand", kind: "hand", match: /RightHand/ },
|
|
18
|
-
{ id: "leftKnee", label: "Left knee", ko: "왼무릎", bone: "LeftLeg", chain: "leftFoot", kind: "knee", match: /Left(UpLeg|Leg)$/ },
|
|
19
|
-
{ id: "rightKnee", label: "Right knee", ko: "오른무릎", bone: "RightLeg", chain: "rightFoot", kind: "knee", match: /Right(UpLeg|Leg)$/ },
|
|
20
|
-
];
|
|
21
|
-
export const PHYSICS_LIMITS = Object.freeze({ root: 0.18, pull: 0.10, tilt: Math.PI / 60, floor: 0.005, slide: 0.015, float: 0.025 });
|
|
22
|
-
const V = () => new THREE.Vector3();
|
|
23
|
-
const Q = () => new THREE.Quaternion();
|
|
24
|
-
const clamp = THREE.MathUtils.clamp;
|
|
25
|
-
const smooth = (x) => { x = clamp(x, 0, 1); return x * x * (3 - 2 * x); };
|
|
26
|
-
const quantile = (a, t) => { const s = a.filter(Number.isFinite).sort((x, y) => x - y); return s[Math.floor((s.length - 1) * t)] ?? 0; };
|
|
27
|
-
|
|
28
|
-
export function copyPhysicsKeys(keys) {
|
|
29
|
-
return new Map([...keys].map(([f, entries]) => [f, new Map([...entries].map(([id, k]) => [id, {
|
|
30
|
-
...k, q: k.q?.map((q) => q.clone()) ?? null, p: k.p?.clone() ?? null,
|
|
31
|
-
...(k.baseQ ? { baseQ: k.baseQ.map((q) => q.clone()) } : {}),
|
|
32
|
-
...(k.basePos ? { basePos: k.basePos.clone() } : {}),
|
|
33
|
-
...(k.chainP ? { chainP: k.chainP.map((p) => p.clone()) } : {}),
|
|
34
|
-
...(k.correctionRange ? { correctionRange: { ...k.correctionRange } } : {}),
|
|
35
|
-
}]))]));
|
|
36
|
-
}
|
|
37
|
-
export function physicsKeyStamp(keys) {
|
|
38
|
-
return JSON.stringify([...keys].sort((a, b) => a[0] - b[0]).map(([f, e]) => [f, [...e].sort(([a], [b]) => a.localeCompare(b)).map(([id, k]) => [id, k.p?.toArray(), k.q?.map((q) => q.toArray()), k.basePos?.toArray(), k.baseQ?.map((q) => q.toArray()), k.keepTranslations, k.chainP?.map((p) => p.toArray()), k.correctionRange])]));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Measured skin, shared exact transforms; never a cached bind-pose sole. */
|
|
42
|
-
export function createSupportSampler(rig) { return createSurfaceSampler(rig, SUPPORT_SITES); }
|
|
43
|
-
|
|
44
|
-
function sitePoint(row, site) {
|
|
45
|
-
return site.kind === "foot" ? (row.toes?.[site.id] ?? row.support[site.id]?.position) : row.support[site.id]?.position;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function supportIntervals(samples, fps, overrides = [], floorY = 0, groundAt = null) {
|
|
49
|
-
const spans = [], rejected = [], count = samples.length;
|
|
50
|
-
const masks = Array.from({ length: count }, () => new Map());
|
|
51
|
-
for (const site of SUPPORT_SITES) {
|
|
52
|
-
const floor = quantile(samples.map((r) => r.support[site.id]?.floor), 0.08);
|
|
53
|
-
const flags = samples.map((r, i) => {
|
|
54
|
-
const a = samples[Math.max(0, i - 2)].support[site.id], b = samples[Math.min(count - 1, i + 2)].support[site.id], c = r.support[site.id];
|
|
55
|
-
const pa = sitePoint(samples[Math.max(0, i - 2)], site), pb = sitePoint(samples[Math.min(count - 1, i + 2)], site);
|
|
56
|
-
if (!a || !b || !c || !pa || !pb) return false;
|
|
57
|
-
const dt = (Math.min(count - 1, i + 2) - Math.max(0, i - 2)) / fps || 1 / fps;
|
|
58
|
-
const localGround = r.ground?.[site.id] ?? (groundAt ? groundAt((sitePoint(r, site) ?? c.position).x, (sitePoint(r, site) ?? c.position).z, c.position.y + 0.001) : floorY);
|
|
59
|
-
return c.floor < localGround + (site.kind === "foot" ? 0.15 : 0.07)
|
|
60
|
-
&& c.floor < floor + 0.055 && Math.abs(b.position.y - a.position.y) / dt < 0.25
|
|
61
|
-
&& Math.hypot(pb.x - pa.x, pb.z - pa.z) / dt < 0.16;
|
|
62
|
-
});
|
|
63
|
-
const forced = new Set();
|
|
64
|
-
for (const edit of overrides.filter((o) => o.site === site.id)) {
|
|
65
|
-
for (let f = Math.max(0, edit.start); f <= Math.min(count - 1, edit.end); f += 1) {
|
|
66
|
-
flags[f] = edit.mode === "plant";
|
|
67
|
-
if (flags[f]) forced.add(f); else forced.delete(f);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
let start = -1;
|
|
71
|
-
for (let f = 0; f <= count; f += 1) {
|
|
72
|
-
if (flags[f] && start < 0) start = f;
|
|
73
|
-
if ((f === count || !flags[f]) && start >= 0) {
|
|
74
|
-
const manual = [...forced].some((i) => i >= start && i < f);
|
|
75
|
-
if (f - start >= Math.max(3, Math.round(fps * 0.12)) || manual) {
|
|
76
|
-
const rows = samples.slice(start, f).map((r) => sitePoint(r, site));
|
|
77
|
-
const anchor = new THREE.Vector3(quantile(rows.map((r) => r.x), 0.5), 0, quantile(rows.map((r) => r.z), 0.5));
|
|
78
|
-
const wander = Math.max(...rows.map((r) => Math.hypot(r.x - anchor.x, r.z - anchor.z)));
|
|
79
|
-
if (site.kind === "foot") anchor.y = groundAt ? groundAt(anchor.x, anchor.z, quantile(rows.map((r) => r.y), 0.5) + 0.001) : quantile(rows.map((r) => r.y), 0.5);
|
|
80
|
-
const span = { id: `${site.id}:${start}`, site: site.id, start, end: f - 1, anchor, manual, wander };
|
|
81
|
-
if (wander <= PHYSICS_LIMITS.pull || manual) {
|
|
82
|
-
spans.push(span);
|
|
83
|
-
for (let i = start; i < f; i += 1) masks[i].set(site.id, span);
|
|
84
|
-
} else rejected.push({ ...span, reason: "moving-contact" });
|
|
85
|
-
}
|
|
86
|
-
start = -1;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return { spans, masks, rejected };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function smoothPhysicsTrack(values, radius = 3, protectedFrames = []) {
|
|
94
|
-
const locks = new Set(protectedFrames);
|
|
95
|
-
return values.map((v, f) => {
|
|
96
|
-
if (locks.has(f)) return v.map(() => 0);
|
|
97
|
-
let total = 0; const result = v.map(() => 0);
|
|
98
|
-
for (let i = Math.max(0, f - radius); i <= Math.min(values.length - 1, f + radius); i += 1) {
|
|
99
|
-
const weight = radius + 1 - Math.abs(f - i);
|
|
100
|
-
values[i].forEach((x, j) => { result[j] += x * weight; }); total += weight;
|
|
101
|
-
}
|
|
102
|
-
let influence = 1;
|
|
103
|
-
for (const p of locks) influence = Math.min(influence, smooth(Math.abs(f - p) / (radius + 1)));
|
|
104
|
-
return result.map((x) => x / total * influence);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function poseSnapshot(chains, hips) {
|
|
109
|
-
return { hips: { p: hips.position.clone(), q: hips.quaternion.clone() },
|
|
110
|
-
chains: new Map([...chains].map(([id, c]) => [id, c.bones.map((b) => ({ p: b.position.clone(), q: b.quaternion.clone() }))])) };
|
|
111
|
-
}
|
|
112
|
-
function applySnapshot(pose, rig, chains, hips) {
|
|
113
|
-
hips.position.copy(pose.hips.p); hips.quaternion.copy(pose.hips.q);
|
|
114
|
-
for (const [id, values] of pose.chains) chains.get(id).bones.forEach((b, i) => { b.position.copy(values[i].p); b.quaternion.copy(values[i].q); });
|
|
115
|
-
rig.updateMatrixWorld(true);
|
|
116
|
-
}
|
|
117
|
-
function kneeAngle(c) {
|
|
118
|
-
const [a, b, d] = c.bones.map((b) => b.getWorldPosition(V()));
|
|
119
|
-
return a.sub(b).angleTo(d.sub(b)) * 180 / Math.PI;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Small constrained root solve, coupled to the contact limb reach. The
|
|
123
|
-
* translation and tilt compete against preserving the supplied performance;
|
|
124
|
-
* this is a kinematic approximation, not a ground-reaction-force simulator. */
|
|
125
|
-
function fitRoot(sample, targets, seed, preserveShape = true) {
|
|
126
|
-
const constraints = targets.map((t) => {
|
|
127
|
-
const c = sample.chains[t.site.chain];
|
|
128
|
-
return { ...t, offset: c.root.clone().sub(sample.root), max: t.site.kind === "knee" ? c.lengths[0] : t.site.kind === "foot" ? Math.min((c.lengths[0] + c.lengths[1]) * 0.998, sample.chains[t.site.chain].distance + 0.01) : (c.lengths[0] + c.lengths[1]) * 0.998 };
|
|
129
|
-
});
|
|
130
|
-
const cost = (x) => {
|
|
131
|
-
const rotation = Q().setFromEuler(new THREE.Euler(x[3], 0, x[4]));
|
|
132
|
-
let score = 2 * ((x[0] - seed[0]) ** 2 + (x[1] - seed[1]) ** 2 + (x[2] - seed[2]) ** 2) + 0.8 * (x[3] ** 2 + x[4] ** 2);
|
|
133
|
-
for (const c of constraints) {
|
|
134
|
-
const root = c.offset.clone().applyQuaternion(rotation).add(sample.root).add(new THREE.Vector3(...x));
|
|
135
|
-
const d = root.distanceTo(c.target);
|
|
136
|
-
const err = c.site.kind === "knee" ? d - c.max : Math.max(0, d - c.max);
|
|
137
|
-
const original = sample.chains[c.site.chain].distance;
|
|
138
|
-
const shape = c.site.kind === "knee" ? 0 : d - original;
|
|
139
|
-
score += 1500 * err * err + (preserveShape ? 0.2 * shape * shape : 0);
|
|
140
|
-
}
|
|
141
|
-
return score;
|
|
142
|
-
};
|
|
143
|
-
let x = [...seed], best = cost(x);
|
|
144
|
-
for (const step of [0.015, 0.006, 0.002, 0.0007]) {
|
|
145
|
-
for (let pass = 0; pass < 5; pass += 1) {
|
|
146
|
-
let improved = false;
|
|
147
|
-
for (let j = 0; j < 5; j += 1) for (const direction of [-1, 1]) {
|
|
148
|
-
const next = [...x]; next[j] += direction * step;
|
|
149
|
-
next[j] = clamp(next[j], j < 3 ? -PHYSICS_LIMITS.root : -PHYSICS_LIMITS.tilt, j < 3 ? PHYSICS_LIMITS.root : PHYSICS_LIMITS.tilt);
|
|
150
|
-
const value = cost(next);
|
|
151
|
-
if (value + 1e-12 < best) { x = next; best = value; improved = true; }
|
|
152
|
-
}
|
|
153
|
-
if (!improved) break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return x;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function physicsMetrics(samples, masks, fps, floorY = 0, groundAt = null) {
|
|
160
|
-
const m = { penetration: 0, penetrationFrame: 0, float: 0, floatFrame: 0, slide: 0, slideFrame: 0, meanSlide: 0, kneeStep: 0, kneeStepFrame: 0, kneeAcceleration: 0, rootAcceleration: 0, count: 0, surfaceMeasured: samples.every((s) => SUPPORT_SITES.slice(0, 2).every((site) => s.support[site.id]?.vertices > 0)) };
|
|
161
|
-
for (let f = 0; f < samples.length; f += 1) {
|
|
162
|
-
for (const [id, point] of Object.entries(samples[f].support)) if (point.vertices > 0) { const g = samples[f].ground?.[id] ?? floorY; if (g - point.floor > m.penetration) { m.penetration = g - point.floor; m.penetrationFrame = f; } }
|
|
163
|
-
for (const site of SUPPORT_SITES) {
|
|
164
|
-
const r = samples[f].support[site.id]; if (!r) continue;
|
|
165
|
-
const ground = samples[f].ground?.[site.id] ?? floorY;
|
|
166
|
-
const depth = Math.max(0, ground - r.floor);
|
|
167
|
-
if (depth > m.penetration) { m.penetration = depth; m.penetrationFrame = f; }
|
|
168
|
-
const span = masks[f]?.get(site.id);
|
|
169
|
-
if (span) {
|
|
170
|
-
const point = sitePoint(samples[f], site);
|
|
171
|
-
const slide = Math.hypot(point.x - span.anchor.x, point.z - span.anchor.z);
|
|
172
|
-
if (slide > m.slide) { m.slide = slide; m.slideFrame = f; }
|
|
173
|
-
const gap = Math.max(0, r.floor - ground);
|
|
174
|
-
if (gap > m.float) { m.float = gap; m.floatFrame = f; }
|
|
175
|
-
m.meanSlide += slide; m.count += 1;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
for (const side of ["leftFoot", "rightFoot"]) {
|
|
179
|
-
if (f > 0) {
|
|
180
|
-
const step = Math.abs(samples[f].knees[side] - samples[f - 1].knees[side]);
|
|
181
|
-
if (step > m.kneeStep) { m.kneeStep = step; m.kneeStepFrame = f; }
|
|
182
|
-
}
|
|
183
|
-
if (f > 1) {
|
|
184
|
-
const a = Math.abs(samples[f].knees[side] - 2 * samples[f - 1].knees[side] + samples[f - 2].knees[side]) * fps * fps;
|
|
185
|
-
if (a > m.kneeAcceleration) { m.kneeAcceleration = a; m.kneeAccelerationFrame = f; }
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (f > 1) {
|
|
189
|
-
const a = samples[f].root.clone().add(samples[f - 2].root).addScaledVector(samples[f - 1].root, -2).length() * fps * fps;
|
|
190
|
-
if (a > m.rootAcceleration) { m.rootAcceleration = a; m.rootAccelerationFrame = f; }
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
m.meanSlide /= m.count || 1;
|
|
194
|
-
return m;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function reviewWarnings(before, after, replayErrors = []) {
|
|
198
|
-
const warnings = [];
|
|
199
|
-
if (after.penetration > PHYSICS_LIMITS.floor) warnings.push({ frame: after.penetrationFrame, reason: "floor", value: after.penetration });
|
|
200
|
-
if (after.float > PHYSICS_LIMITS.float) warnings.push({ frame: after.floatFrame, reason: "float", value: after.float });
|
|
201
|
-
if (after.slide > PHYSICS_LIMITS.slide) warnings.push({ frame: after.slideFrame, reason: "slide", value: after.slide });
|
|
202
|
-
if (after.kneeStep > Math.max(before.kneeStep + 2, 12)) warnings.push({ frame: after.kneeStepFrame, reason: "knee-pop", value: after.kneeStep });
|
|
203
|
-
if (after.kneeAcceleration > Math.max(1e-6, before.kneeAcceleration * 1.1)) warnings.push({ frame: after.kneeAccelerationFrame, reason: "knee-acceleration", value: after.kneeAcceleration });
|
|
204
|
-
if (after.rootAcceleration > Math.max(1e-6, before.rootAcceleration * 1.1)) warnings.push({ frame: after.rootAccelerationFrame, reason: "root-acceleration", value: after.rootAcceleration });
|
|
205
|
-
if (replayErrors.length) warnings.push({ frame: replayErrors[0].frame, reason: "replay", value: replayErrors[0].error });
|
|
206
|
-
return warnings;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function reviewCandidate({ rig, motion, chains, fkJoints, sourceKeys, applyRaw, sceneObjects = [], overrides = [], protectedFrames = [], strength = 1, onProgress = () => {}, yieldFrame = () => Promise.resolve(), floorY = 0, cache = null, alignmentScale = 1 }) {
|
|
210
|
-
const started = performance.now(), timings = {};
|
|
211
|
-
const hips = fkJoints?.get("hips")?.bone;
|
|
212
|
-
const toeBones = { leftFoot: findBone(rig, "mixamorigLeftToeBase"), rightFoot: findBone(rig, "mixamorigRightToeBase") };
|
|
213
|
-
if (!hips || !motion || !chains) throw new Error("A loaded motion and complete rig are required");
|
|
214
|
-
const count = motion.frames, fps = motion.fps || 24;
|
|
215
|
-
const sample = createSupportSampler(rig), dynamics = createDynamicsSampler(rig), groundAt = createGroundSampler(sceneObjects, { floorY }), source = { keys: copyPhysicsKeys(sourceKeys), tracked: new Set([...sourceKeys.values()].flatMap((e) => [...e.keys()])) };
|
|
216
|
-
const hasSceneGround = sceneObjects.length > 0;
|
|
217
|
-
let grounding = null;
|
|
218
|
-
const applyBase = (f) => { applyRaw(f); ikEvaluate(chains, source, f, fkJoints, 6); if (grounding?.shifts[f]) solveHipsTranslate(fkJoints.get("hips"), new THREE.Vector3(0, grounding.shifts[f], 0), hips.position.clone()); rig.updateMatrixWorld(true); };
|
|
219
|
-
let raw = [], base = [], samples = [];
|
|
220
|
-
const read = () => {
|
|
221
|
-
const support = sample();
|
|
222
|
-
const toes = Object.fromEntries(Object.entries(toeBones).map(([id, bone]) => [id, (bone ?? chains.get(id).bones[2]).getWorldPosition(V())]));
|
|
223
|
-
const ground = Object.fromEntries(SUPPORT_SITES.map((site) => { const p = sitePoint({ support, toes }, site) ?? support[site.id]?.position; return [site.id, p ? groundAt(p.x, p.z, support[site.id]?.position.y + 0.001) : floorY]; }));
|
|
224
|
-
return { support, toes, ground, root: hips.getWorldPosition(V()), com: computeCenterOfMass(rig), dynamics: dynamics(), knees: Object.fromEntries(["leftFoot", "rightFoot"].map((id) => [id, kneeAngle(chains.get(id))])) };
|
|
225
|
-
};
|
|
226
|
-
rig.updateMatrixWorld(true);
|
|
227
|
-
const stamp = physicsKeyStamp(sourceKeys), matrixStamp = rig.matrixWorld.elements.join(",");
|
|
228
|
-
const environmentKey = JSON.stringify({ floorY, sceneObjects: sceneObjects.map((o) => ({ id: o.id, x: o.x, y: o.y, z: o.z, rot: o.rot, rotX: o.rotX, rotZ: o.rotZ, scaleX: o.scaleX, scaleY: o.scaleY, scaleZ: o.scaleZ, path: o.path ?? null })) });
|
|
229
|
-
const cached = cache?.value;
|
|
230
|
-
const cacheHit = cached?.schema === 5 && cached?.rig === rig && cached?.motion === motion && cached?.stamp === stamp && cached?.matrixStamp === matrixStamp && cached?.environmentKey === environmentKey;
|
|
231
|
-
if (cacheHit) { raw = cached.raw; base = cached.base; samples = cached.samples; }
|
|
232
|
-
else {
|
|
233
|
-
for (let f = 0; f < count; f += 1) {
|
|
234
|
-
applyRaw(f); raw.push(poseSnapshot(chains, hips)); applyBase(f); base.push(poseSnapshot(chains, hips));
|
|
235
|
-
const row = read(); row.chains = Object.fromEntries([...chains].map(([id, c]) => {
|
|
236
|
-
const p = c.bones.map((b) => b.getWorldPosition(V()));
|
|
237
|
-
return [id, { root: p[0], lengths: [p[0].distanceTo(p[1]), p[1].distanceTo(p[2])], distance: p[0].distanceTo(p[2]) }];
|
|
238
|
-
})); samples.push(row);
|
|
239
|
-
if (f % 12 === 0) { onProgress(Math.round(25 * f / count)); await yieldFrame(); }
|
|
240
|
-
}
|
|
241
|
-
if (cache) cache.value = { schema: 5, rig, motion, stamp, matrixStamp, environmentKey, raw, base, samples };
|
|
242
|
-
}
|
|
243
|
-
timings.sourceMs = performance.now() - started;
|
|
244
|
-
const sourceSamples = samples;
|
|
245
|
-
if (strength <= 0) {
|
|
246
|
-
const contacts = supportIntervals(samples, fps, overrides, floorY, groundAt);
|
|
247
|
-
const metrics = physicsMetrics(samples, contacts.masks, fps, floorY, groundAt);
|
|
248
|
-
onProgress(100);
|
|
249
|
-
const support = supportDiagnostics(samples, fps, floorY);
|
|
250
|
-
metrics.unsupportedFrames = support.unsupportedFrames; metrics.unsupportedGap = support.unsupportedGap;
|
|
251
|
-
metrics.forceResidual = support.forceResidual; metrics.momentResidual = support.momentResidual;
|
|
252
|
-
const warnings = reviewWarnings(metrics, metrics);
|
|
253
|
-
if (!support.frames.some((row) => row.measured) && samples.length) warnings.push({ frame: 0, reason: "support-unmeasured", value: 0 });
|
|
254
|
-
if (support.unsupportedFrames) warnings.push({ frame: support.frames.find((row) => row.measured && !row.flight && row.clearance > .025 && row.forceResidual > .22)?.frame ?? 0, reason: "unsupported", value: support.unsupportedGap });
|
|
255
|
-
return { candidate: source, contacts, before: metrics, after: { ...metrics }, samples, evaluated: samples, support: { before: support, after: support },
|
|
256
|
-
warnings, unresolved: support.unresolved.map(row => ({ frame: row.frame, reason: "support-force", error: row.residual })), changedFrames: [], skippedAir: [], replayErrors: [], flightFrames: [],
|
|
257
|
-
protectedFrames: [...protectedFrames], strength: 0, sourceStamp: stamp, motion, rig, performance: { ...timings, totalMs: performance.now() - started, cacheHit, ...sample.stats } };
|
|
258
|
-
}
|
|
259
|
-
grounding = inferSupportAlignment(samples, fps, { floorY, overrides, protectedFrames, strength: strength * alignmentScale });
|
|
260
|
-
if (grounding.shifts.some((v) => Math.abs(v) > 1e-7)) {
|
|
261
|
-
// Source cache is immutable: options may change without resampling the
|
|
262
|
-
// original. Rebuild only the shifted baseline used by the coupled IK.
|
|
263
|
-
base = []; samples = [];
|
|
264
|
-
for (let f = 0; f < count; f += 1) {
|
|
265
|
-
applyBase(f); base.push(poseSnapshot(chains, hips));
|
|
266
|
-
const row = read(); row.chains = Object.fromEntries([...chains].map(([id, c]) => { const p = c.bones.map((b) => b.getWorldPosition(V())); return [id, { root: p[0], lengths: [p[0].distanceTo(p[1]), p[1].distanceTo(p[2])], distance: p[0].distanceTo(p[2]) }]; })); samples.push(row);
|
|
267
|
-
if (f % 12 === 0) { onProgress(25 + Math.round(5 * f / count)); await yieldFrame(); }
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
const contacts = supportIntervals(samples, fps, overrides, floorY, groundAt);
|
|
271
|
-
timings.supportMs = performance.now() - started - timings.sourceMs;
|
|
272
|
-
const preserveFrames = [...new Set([...protectedFrames, ...grounding.flight])];
|
|
273
|
-
// Holden's lock is causal (it starts blending AT contact), which leaves the
|
|
274
|
-
// blend's first frames inside the span as measurable slide. Offline we know
|
|
275
|
-
// every span ahead of time, so start the lock one frame early. One frame is
|
|
276
|
-
// the measured sweet spot on the reference takes: it brings peak in-span
|
|
277
|
-
// slide under the origin baseline, while a 2-3 frame lead trips the knee
|
|
278
|
-
// acceleration gate on x-bot at the preceding foot's release.
|
|
279
|
-
const lockBlendTime = 0.1, lockLead = 1;
|
|
280
|
-
const toeTracks = Object.fromEntries(SUPPORT_SITES.filter((s) => s.kind === "foot").map((site) => {
|
|
281
|
-
const spanAt = (f) => contacts.masks[f].get(site.id) ?? contacts.spans.find((s) => s.site === site.id && f >= s.start - lockLead && f < s.start);
|
|
282
|
-
const leads = samples.map((r, f) => spanAt(f));
|
|
283
|
-
const anchors = leads.map((span, f) => span ? span.anchor.clone().setY(samples[f].toes[site.id].y) : null);
|
|
284
|
-
return [site.id, footLockTrack(samples.map((r) => r.toes[site.id]), leads.map(Boolean), { fps, blendTime: lockBlendTime, anchors })];
|
|
285
|
-
}));
|
|
286
|
-
const influenceAt = (f) => clamp(strength, 0, 1) * Math.min(1, ...preserveFrames.map((p) => smooth(Math.abs(f - p) / 4)));
|
|
287
|
-
const targets = samples.map((r, f) => {
|
|
288
|
-
const out = [];
|
|
289
|
-
for (const site of SUPPORT_SITES) {
|
|
290
|
-
const point = r.support[site.id]; if (!point) continue;
|
|
291
|
-
const supportOnly = alignmentScale > 0 && grounding.frames[f].mode === "align" && grounding.frames[f].hints?.includes(site.id);
|
|
292
|
-
const planted = contacts.masks[f].get(site.id);
|
|
293
|
-
const explicitlyFree = overrides.some((o) => o.site === site.id && o.mode === "free" && f >= o.start && f <= o.end);
|
|
294
|
-
const span = planted ?? (!explicitlyFree && contacts.spans.find((s) => s.site === site.id && f >= s.start - 4 && f <= s.end + 4));
|
|
295
|
-
const localGround = hasSceneGround ? (r.ground?.[site.id] ?? floorY) : floorY;
|
|
296
|
-
if (!span && !supportOnly && point.floor >= localGround + 0.002) continue;
|
|
297
|
-
// Hold the actual support interval; ease in the neighbouring swing
|
|
298
|
-
// frames, not INSIDE the interval where easing is visible foot slip.
|
|
299
|
-
const edge = planted ? 1 : span ? smooth(1 - Math.max(span.start - f, f - span.end) / 5) : 0;
|
|
300
|
-
const toe = r.toes?.[site.id] ?? point.position;
|
|
301
|
-
const target = point.position.clone();
|
|
302
|
-
if (span) {
|
|
303
|
-
const toeTarget = site.kind === "foot" ? toeTracks[site.id][f] : span.anchor;
|
|
304
|
-
target.x += clamp(toeTarget.x - toe.x, -PHYSICS_LIMITS.pull, PHYSICS_LIMITS.pull) * (site.kind === "foot" ? 1 : edge);
|
|
305
|
-
target.z += clamp(toeTarget.z - toe.z, -PHYSICS_LIMITS.pull, PHYSICS_LIMITS.pull) * (site.kind === "foot" ? 1 : edge);
|
|
306
|
-
}
|
|
307
|
-
target.y += clamp(localGround + 0.002 - point.floor, -0.10, 0.15) * (planted || !span ? 1 : edge);
|
|
308
|
-
out.push({ site, target, edge, contact: !!planted, supportOnly, rotation: point.rotation });
|
|
309
|
-
}
|
|
310
|
-
return out;
|
|
311
|
-
});
|
|
312
|
-
const protectedSet = new Set(preserveFrames);
|
|
313
|
-
const seeds = samples.map((r, f) => {
|
|
314
|
-
const active = targets[f];
|
|
315
|
-
const lifts = active.map((t) => t.target.y - r.support[t.site.id].position.y);
|
|
316
|
-
const y = quantile(lifts, 0.5);
|
|
317
|
-
return [0, clamp(y, -0.10, PHYSICS_LIMITS.root), 0, 0, 0];
|
|
318
|
-
});
|
|
319
|
-
let roots = smoothPhysicsTrack(seeds, 4, protectedFrames);
|
|
320
|
-
for (let pass = 0; pass < 2; pass += 1) {
|
|
321
|
-
roots = roots.map((seed, f) => protectedSet.has(f) ? [0, 0, 0, 0, 0] : fitRoot(samples[f], targets[f], seed));
|
|
322
|
-
roots = smoothPhysicsTrack(roots, 2, protectedFrames);
|
|
323
|
-
}
|
|
324
|
-
// Solve a trajectory, not disconnected poses. Smooth the root and swing
|
|
325
|
-
// feet in world space; planted points are exact constraints. Smoothing
|
|
326
|
-
// only the correction quaternion lagged a fast knee swing at release.
|
|
327
|
-
const rootPath = smoothPhysicsTrack(roots.map((r, f) => samples[f].root.toArray().map((v, j) => v + r[j])), 2);
|
|
328
|
-
roots = roots.map((r, f) => protectedSet.has(f) ? [0, 0, 0, 0, 0] : [...rootPath[f].map((v, j) => v - samples[f].root.toArray()[j]), r[3], r[4]]);
|
|
329
|
-
// Relax the TOE offline while keeping the solved pelvis fixed. This absorbs
|
|
330
|
-
// contact drift without replacing the source gait or pulling hips downward.
|
|
331
|
-
await yieldFrame();
|
|
332
|
-
const pelvisPath = samples.map((r, f) => r.root.clone().add(new THREE.Vector3(...roots[f])));
|
|
333
|
-
for (const site of SUPPORT_SITES.filter((s) => s.kind === "foot")) {
|
|
334
|
-
const toeInput = samples.map((r) => r.toes[site.id]);
|
|
335
|
-
const inertialToeTrack = toeTracks[site.id];
|
|
336
|
-
const lowest = hasSceneGround ? Math.min(...samples.map((r) => r.ground?.[site.id] ?? floorY)) : Math.min(...samples.map((r) => r.support[site.id].floor));
|
|
337
|
-
const lowestFrame = hasSceneGround ? samples.findIndex((r) => (r.ground?.[site.id] ?? floorY) === lowest) : samples.findIndex((r) => r.support[site.id].floor === lowest);
|
|
338
|
-
const toeOffset = toeInput[lowestFrame].y - lowest;
|
|
339
|
-
const relaxed = relaxToePath(toeTracks[site.id], pelvisPath, samples.map((r, f) => contacts.masks[f].has(site.id)), {
|
|
340
|
-
iterations: 120, toeMinHeight: hasSceneGround ? samples.map((r) => (r.ground?.[site.id] ?? floorY) + 0.002 - toeOffset) : floorY + 0.002 - toeOffset,
|
|
341
|
-
restLengths: samples.map((r, f) => r.chains[site.chain].root.distanceTo(toeInput[f])), relaxPelvis: false,
|
|
342
|
-
});
|
|
343
|
-
toeTracks[site.id] = relaxed.toes;
|
|
344
|
-
for (let f = 0; f < count; f += 1) {
|
|
345
|
-
const prior = targets[f].find((t) => t.site.id === site.id);
|
|
346
|
-
if (!prior) continue;
|
|
347
|
-
const ankle = samples[f].support[site.id].position, toe = toeInput[f], targetToe = contacts.masks[f].has(site.id) || prior.edge > 0 ? inertialToeTrack[f] : relaxed.toes[f];
|
|
348
|
-
prior.target.x = ankle.x + clamp(targetToe.x - toe.x, -PHYSICS_LIMITS.pull, PHYSICS_LIMITS.pull);
|
|
349
|
-
prior.target.z = ankle.z + clamp(targetToe.z - toe.z, -PHYSICS_LIMITS.pull, PHYSICS_LIMITS.pull);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
await yieldFrame();
|
|
353
|
-
roots = relaxPhysicsRoot(samples, targets, roots, protectedFrames, PHYSICS_LIMITS.root);
|
|
354
|
-
const solutions = [];
|
|
355
|
-
for (let f = 0; f < count; f += 1) {
|
|
356
|
-
applyBase(f);
|
|
357
|
-
if (!protectedSet.has(f)) {
|
|
358
|
-
solveHipsTranslate(fkJoints.get("hips"), new THREE.Vector3(...roots[f]), base[f].hips.p);
|
|
359
|
-
const parent = hips.parent.getWorldQuaternion(Q());
|
|
360
|
-
const world = parent.clone().multiply(hips.quaternion);
|
|
361
|
-
const tilt = Q().setFromEuler(new THREE.Euler(roots[f][3], 0, roots[f][4]));
|
|
362
|
-
hips.quaternion.copy(parent.invert().multiply(tilt.multiply(world))); rig.updateMatrixWorld(true);
|
|
363
|
-
for (const t of targets[f]) {
|
|
364
|
-
const chain = chains.get(t.site.chain), original = base[f].chains.get(t.site.chain);
|
|
365
|
-
if (t.site.kind === "knee") {
|
|
366
|
-
const b = chain.bones[0], a = b.getWorldPosition(V()), d = chain.bones[1].getWorldPosition(V()).sub(a).normalize();
|
|
367
|
-
const rotation = Q().setFromUnitVectors(d, t.target.clone().sub(a).normalize());
|
|
368
|
-
b.quaternion.copy(b.parent.getWorldQuaternion(Q()).invert().multiply(rotation).multiply(b.getWorldQuaternion(Q())));
|
|
369
|
-
} else {
|
|
370
|
-
solveIk({ ...chain, bindPositions: original.map((b) => b.p), lengths: samples[f].chains[t.site.chain].lengths }, t.target, t.site.kind === "foot" ? { maxExtension: samples[f].chains[t.site.chain].distance + 0.01, softening: 0.01 } : undefined);
|
|
371
|
-
}
|
|
372
|
-
// A planted sole/palm keeps its world orientation. Letting the
|
|
373
|
-
// ankle inherit the knee rotation turns a position fix into toe dip.
|
|
374
|
-
const end = chain.bones[2]; rig.updateMatrixWorld(true);
|
|
375
|
-
const desired = t.site.kind === "knee" ? samples[f].support[t.site.chain].rotation : t.rotation;
|
|
376
|
-
end.quaternion.copy(end.parent.getWorldQuaternion(Q()).invert().multiply(desired)); rig.updateMatrixWorld(true);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
solutions.push(poseSnapshot(chains, hips));
|
|
380
|
-
if (f % 12 === 0) { onProgress(30 + Math.round(25 * f / count)); await yieldFrame(); }
|
|
381
|
-
}
|
|
382
|
-
// Filter correction rotations, not the original performance. Key every
|
|
383
|
-
// frame in the candidate so disconnected islands cannot interpolate into
|
|
384
|
-
// a swing or a protected pose. Joint translations remain the source's.
|
|
385
|
-
for (const id of chains.keys()) for (let joint = 0; joint < 3; joint += 1) {
|
|
386
|
-
const radius = id.endsWith("Foot") ? 0 : 2;
|
|
387
|
-
const deltas = solutions.map((s, f) => base[f].chains.get(id)[joint].q.clone().invert().multiply(s.chains.get(id)[joint].q));
|
|
388
|
-
for (let f = 0; f < count; f += 1) {
|
|
389
|
-
let q = deltas[f].clone(), sum = 1;
|
|
390
|
-
for (let d = 1; d <= radius; d += 1) for (const side of [-1, 1]) {
|
|
391
|
-
const i = clamp(f + side * d, 0, count - 1), w = (radius + 1 - d) / (radius + 1);
|
|
392
|
-
q.slerp(deltas[i], w / (sum + w)); sum += w;
|
|
393
|
-
}
|
|
394
|
-
const weight = influenceAt(f);
|
|
395
|
-
q = Q().slerp(q, weight);
|
|
396
|
-
solutions[f].chains.get(id)[joint].q.copy(base[f].chains.get(id)[joint].q).multiply(q);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
const candidate = { keys: copyPhysicsKeys(sourceKeys), tracked: new Set(source.tracked) };
|
|
400
|
-
const after = [], changed = [], unresolved = [], replayErrors = [];
|
|
401
|
-
for (let f = 0; f < count; f += 1) {
|
|
402
|
-
const pose = solutions[f];
|
|
403
|
-
const influence = influenceAt(f);
|
|
404
|
-
pose.hips.p.lerpVectors(base[f].hips.p, pose.hips.p, influence);
|
|
405
|
-
pose.hips.q.slerpQuaternions(base[f].hips.q, pose.hips.q, influence);
|
|
406
|
-
applyBase(f); applySnapshot(pose, rig, chains, hips);
|
|
407
|
-
// Project the temporally filtered solution back onto ALL active support
|
|
408
|
-
// surfaces. A late whole-body lift floats the palms in a crawl; move the
|
|
409
|
-
// pelvis only when the limb cannot reach, then re-solve its supports.
|
|
410
|
-
if (!protectedSet.has(f) && strength > 0) {
|
|
411
|
-
for (let pass = 0; pass < 4; pass += 1) {
|
|
412
|
-
const surfaces = sample(), root = hips.getWorldPosition(V());
|
|
413
|
-
const projection = SUPPORT_SITES.flatMap((site) => {
|
|
414
|
-
const current = surfaces[site.id], original = samples[f].support[site.id];
|
|
415
|
-
const contact = targets[f].find((t) => t.site.id === site.id && (t.contact || t.edge > 0 || t.supportOnly));
|
|
416
|
-
// The swing trajectory already contains its transition ramp.
|
|
417
|
-
// Project onto THAT height, not a second independently faded
|
|
418
|
-
// floor target which can disagree by centimetres at release.
|
|
419
|
-
const localGround = samples[f].ground?.[site.id] ?? floorY;
|
|
420
|
-
const contactFloor = contact ? original.floor + contact.target.y - original.position.y : localGround + .002;
|
|
421
|
-
const wantedFloor = Math.max(
|
|
422
|
-
THREE.MathUtils.lerp(Math.min(original.floor, localGround + .002), localGround + .002, influence),
|
|
423
|
-
THREE.MathUtils.lerp(original.floor, contactFloor, influence),
|
|
424
|
-
);
|
|
425
|
-
if (!current || (!contact && current.floor >= Math.max(wantedFloor, localGround + 0.001))) return [];
|
|
426
|
-
const target = current.position.clone();
|
|
427
|
-
if (contact) { target.x = THREE.MathUtils.lerp(original.position.x, contact.target.x, influence); target.z = THREE.MathUtils.lerp(original.position.z, contact.target.z, influence); }
|
|
428
|
-
target.y += clamp(wantedFloor - current.floor, -PHYSICS_LIMITS.pull, PHYSICS_LIMITS.pull);
|
|
429
|
-
return [{ site, target, rotation: original.rotation }];
|
|
430
|
-
});
|
|
431
|
-
const currentSample = { root, chains: Object.fromEntries([...chains].map(([id, c]) => {
|
|
432
|
-
const points = c.bones.map((b) => b.getWorldPosition(V()));
|
|
433
|
-
return [id, { root: points[0], lengths: samples[f].chains[id].lengths, distance: samples[f].chains[id].distance }];
|
|
434
|
-
})) };
|
|
435
|
-
const delta = fitRoot(currentSample, projection, [0, 0, 0, 0, 0], false);
|
|
436
|
-
const offset = root.clone().add(new THREE.Vector3(...delta)).sub(samples[f].root).clampLength(0, PHYSICS_LIMITS.root * influence);
|
|
437
|
-
const safeDelta = samples[f].root.clone().add(offset).sub(root);
|
|
438
|
-
solveHipsTranslate(fkJoints.get("hips"), safeDelta, hips.position.clone());
|
|
439
|
-
for (const t of projection) {
|
|
440
|
-
const c = chains.get(t.site.chain);
|
|
441
|
-
if (t.site.kind === "knee") {
|
|
442
|
-
const b = c.bones[0], a = b.getWorldPosition(V()), d = c.bones[1].getWorldPosition(V()).sub(a).normalize();
|
|
443
|
-
const rotation = Q().setFromUnitVectors(d, t.target.clone().sub(a).normalize());
|
|
444
|
-
b.quaternion.copy(b.parent.getWorldQuaternion(Q()).invert().multiply(rotation).multiply(b.getWorldQuaternion(Q())));
|
|
445
|
-
} else solveIk({ ...c, bindPositions: base[f].chains.get(t.site.chain).map((b) => b.p), lengths: samples[f].chains[t.site.chain].lengths }, t.target, t.site.kind === "foot" ? { maxExtension: samples[f].chains[t.site.chain].distance + 0.01, softening: 0.01 } : undefined);
|
|
446
|
-
rig.updateMatrixWorld(true);
|
|
447
|
-
const end = c.bones[2], rotation = t.site.kind === "knee" ? samples[f].support[t.site.chain].rotation : t.rotation;
|
|
448
|
-
end.quaternion.copy(end.parent.getWorldQuaternion(Q()).invert().multiply(rotation)); rig.updateMatrixWorld(true);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const finalPose = poseSnapshot(chains, hips);
|
|
453
|
-
const projected = read();
|
|
454
|
-
let changedFrame = Math.abs(grounding.shifts[f]) > 1e-5 || finalPose.hips.p.distanceTo(base[f].hips.p) > 1e-5 || finalPose.hips.q.angleTo(base[f].hips.q) > 1e-5;
|
|
455
|
-
const entry = candidate.keys.get(f) ?? new Map();
|
|
456
|
-
entry.set("hips", { q: [finalPose.hips.q], p: finalPose.hips.p, baseQ: [raw[f].hips.q], basePos: raw[f].hips.p }); candidate.tracked.add("hips");
|
|
457
|
-
for (const [id, values] of finalPose.chains) {
|
|
458
|
-
if (values.some((v, i) => v.q.angleTo(base[f].chains.get(id)[i].q) > 1e-5)) changedFrame = true;
|
|
459
|
-
entry.set(id, { q: values.map((v) => v.q), p: null, baseQ: raw[f].chains.get(id).map((b) => b.q), chainP: values.map((v) => v.p), keepTranslations: true }); candidate.tracked.add(id);
|
|
460
|
-
}
|
|
461
|
-
candidate.keys.set(f, entry);
|
|
462
|
-
if (changedFrame) changed.push(f);
|
|
463
|
-
applyRaw(f); ikEvaluate(chains, candidate, f, fkJoints, 6); const row = read(); after.push(row);
|
|
464
|
-
const replayError = Math.max(...SUPPORT_SITES.map((s) => Math.abs(row.support[s.id].floor - projected.support[s.id].floor)));
|
|
465
|
-
if (replayError > 1e-5) replayErrors.push({ frame: f, error: replayError, expected: projected, actual: row });
|
|
466
|
-
for (const t of targets[f].filter((t) => t.contact)) {
|
|
467
|
-
const surface = row.support[t.site.id], error = surface.position.distanceTo(t.target);
|
|
468
|
-
if (error > 0.03) unresolved.push({ frame: f, site: t.site.id, reason: "contact-residual", error });
|
|
469
|
-
}
|
|
470
|
-
if (f % 12 === 0) { onProgress(55 + Math.round(25 * f / count)); await yieldFrame(); }
|
|
471
|
-
}
|
|
472
|
-
await refinePhysicsCandidate({ rig, chains, fkJoints, samples, after, contacts, candidate, base, roots,
|
|
473
|
-
sites: SUPPORT_SITES, applyRaw, read, strength, protectedFrames: preserveFrames, yieldFrame, floorY, onProgress });
|
|
474
|
-
// Exact all-body safety projection after temporal IK. A smooth upper
|
|
475
|
-
// envelope clears small residual penetrations without a one-frame lift;
|
|
476
|
-
// it changes no joint angles or planted XZ position. Protected/flight
|
|
477
|
-
// frames remain source-exact and retain any unresolved warning.
|
|
478
|
-
const clearance = after.map((r) => Math.max(0, Math.max(...Object.entries(r.support).filter(([, p]) => p.vertices > 0).map(([id, p]) => (r.ground?.[id] ?? floorY) + .001 - p.floor))));
|
|
479
|
-
const liftRadius = Math.max(3, Math.round(fps * .2));
|
|
480
|
-
for (let f = 0; f < count; f += 1) {
|
|
481
|
-
if (protectedSet.has(f)) continue;
|
|
482
|
-
let lift = 0;
|
|
483
|
-
for (let i = Math.max(0, f - 3 * liftRadius); i <= Math.min(count - 1, f + 3 * liftRadius); i += 1) lift = Math.max(lift, Math.min(.06, clearance[i]) * Math.exp(-(((f - i) / liftRadius) ** 2)));
|
|
484
|
-
lift *= influenceAt(f);
|
|
485
|
-
if (lift > 1e-6) {
|
|
486
|
-
applyRaw(f); ikEvaluate(chains, candidate, f, fkJoints, 6);
|
|
487
|
-
solveHipsTranslate(fkJoints.get("hips"), new THREE.Vector3(0, lift, 0), hips.position.clone());
|
|
488
|
-
candidate.keys.get(f).get("hips").p.copy(hips.position);
|
|
489
|
-
applyRaw(f); ikEvaluate(chains, candidate, f, fkJoints, 6); after[f] = read();
|
|
490
|
-
changed.push(f);
|
|
491
|
-
}
|
|
492
|
-
if (f % 12 === 0) await yieldFrame();
|
|
493
|
-
}
|
|
494
|
-
onProgress(98);
|
|
495
|
-
// Already-ballistic CoM motion is evidence to preserve, not a trajectory
|
|
496
|
-
// to refit with a different endpoint taper. Uncertain unsupported motion
|
|
497
|
-
// stays in the support diagnostics instead of being declared a jump.
|
|
498
|
-
const air = { keyedFrames: [], skippedSpans: [] };
|
|
499
|
-
const beforeSupport = supportDiagnostics(sourceSamples, fps, floorY), afterSupport = supportDiagnostics(after, fps, floorY);
|
|
500
|
-
const beforeMetrics = { ...physicsMetrics(sourceSamples, contacts.masks, fps, floorY, groundAt), unsupportedFrames: beforeSupport.unsupportedFrames, unsupportedGap: beforeSupport.unsupportedGap, forceResidual: beforeSupport.forceResidual, momentResidual: beforeSupport.momentResidual };
|
|
501
|
-
const afterMetrics = { ...physicsMetrics(after, contacts.masks, fps, floorY, groundAt), unsupportedFrames: afterSupport.unsupportedFrames, unsupportedGap: afterSupport.unsupportedGap, forceResidual: afterSupport.forceResidual, momentResidual: afterSupport.momentResidual };
|
|
502
|
-
// Report the final evaluated surfaces, including protected or unreachable
|
|
503
|
-
// contacts, not the residual of an earlier solve that has since changed.
|
|
504
|
-
unresolved.length = 0;
|
|
505
|
-
for (let f = 0; f < count; f += 1) for (const site of SUPPORT_SITES) {
|
|
506
|
-
const point = after[f].support[site.id], span = contacts.masks[f].get(site.id);
|
|
507
|
-
if (!point) continue;
|
|
508
|
-
const localGround = after[f].ground?.[site.id] ?? floorY;
|
|
509
|
-
if (localGround - point.floor > PHYSICS_LIMITS.floor) unresolved.push({ frame: f, site: site.id, reason: "floor", error: localGround - point.floor });
|
|
510
|
-
if (span && point.floor - localGround > PHYSICS_LIMITS.float) unresolved.push({ frame: f, site: site.id, reason: "float", error: point.floor - localGround });
|
|
511
|
-
// Anchors are TOE points for feet, so measure drift from the same point
|
|
512
|
-
// physicsMetrics uses; the ankle sits ~14 cm behind the toe anchor.
|
|
513
|
-
const drifted = sitePoint(after[f], site);
|
|
514
|
-
const drift = span ? Math.hypot(drifted.x - span.anchor.x, drifted.z - span.anchor.z) : 0;
|
|
515
|
-
if (drift > PHYSICS_LIMITS.slide) unresolved.push({ frame: f, site: site.id, reason: "slide", error: drift });
|
|
516
|
-
}
|
|
517
|
-
const warnings = reviewWarnings(beforeMetrics, afterMetrics, replayErrors);
|
|
518
|
-
if (afterSupport.unsupportedFrames) warnings.push({ frame: afterSupport.frames.find((f) => f.measured && !f.flight && f.clearance > .025 && f.forceResidual > .22).frame, reason: "unsupported", value: afterSupport.unsupportedGap });
|
|
519
|
-
for (const row of afterSupport.unresolved) unresolved.push({ frame: row.frame, reason: "support-force", error: row.residual });
|
|
520
|
-
onProgress(100);
|
|
521
|
-
return { candidate, contacts, before: beforeMetrics, after: afterMetrics, samples: sourceSamples, evaluated: after, warnings, unresolved, support: { before: beforeSupport, after: afterSupport, alignment: grounding.frames, shifts: [...grounding.shifts] },
|
|
522
|
-
changedFrames: [...new Set([...changed, ...(strength > 0 ? air.keyedFrames : [])])], skippedAir: air.skippedSpans,
|
|
523
|
-
protectedFrames: [...protectedFrames], strength, sourceStamp: physicsKeyStamp(sourceKeys), motion, rig,
|
|
524
|
-
replayErrors,
|
|
525
|
-
flightFrames: strength > 0 ? air.keyedFrames : [],
|
|
526
|
-
performance: { ...timings, totalMs: performance.now() - started, cacheHit, ...sample.stats },
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/** Bounded whole-clip QA/backtracking, not per-pose exceptions. If a stronger
|
|
531
|
-
* support hypothesis regresses motion continuity, compare a gentler candidate
|
|
532
|
-
* using the same cached original. Never silently call a failed candidate clean. */
|
|
533
|
-
export async function reviewAutoPhysics(options) {
|
|
534
|
-
const start = performance.now(), cache = options.cache ?? { value: null }, progress = options.onProgress ?? (() => {});
|
|
535
|
-
let best = null, bestScore = Infinity, attempts = 0, initialCacheHit = false, sourceMs = 0, supportMs = 0;
|
|
536
|
-
const scales = options.alignmentScale !== undefined ? [options.alignmentScale] : [1, .5, 0];
|
|
537
|
-
for (const scale of scales) {
|
|
538
|
-
const attempt = attempts++, lo = [0, 85, 94][attempt], range = [85, 9, 5][attempt];
|
|
539
|
-
const result = await reviewCandidate({ ...options, cache, alignmentScale: scale, onProgress: (p) => progress(Math.round(lo + p * range / 100)) });
|
|
540
|
-
if (attempt === 0) initialCacheHit = result.performance.cacheHit;
|
|
541
|
-
sourceMs += result.performance.sourceMs; supportMs += result.performance.supportMs ?? 0;
|
|
542
|
-
const b = result.before, a = result.after;
|
|
543
|
-
const safe = a.penetration <= PHYSICS_LIMITS.floor && a.slide <= PHYSICS_LIMITS.slide && a.float <= PHYSICS_LIMITS.float
|
|
544
|
-
&& a.kneeAcceleration <= Math.max(1e-6, b.kneeAcceleration * 1.1) && a.rootAcceleration <= Math.max(1e-6, b.rootAcceleration * 1.1);
|
|
545
|
-
// Compare severity, not a binary pass flag: a sub-millimetre contact
|
|
546
|
-
// miss must not win by restoring an entire floating body. Keep every
|
|
547
|
-
// remaining limit violation visible in warnings/unresolved.
|
|
548
|
-
const excess = (value, limit) => Math.max(0, value / limit - 1);
|
|
549
|
-
const score = 10 * (a.unsupportedFrames ?? 0)
|
|
550
|
-
+ 100 * excess(a.penetration, PHYSICS_LIMITS.floor)
|
|
551
|
-
+ 20 * (excess(a.slide, PHYSICS_LIMITS.slide) + excess(a.float, PHYSICS_LIMITS.float))
|
|
552
|
-
+ 100 * (excess(a.kneeAcceleration, Math.max(1e-6, b.kneeAcceleration * 1.1)) + excess(a.rootAcceleration, Math.max(1e-6, b.rootAcceleration * 1.1)))
|
|
553
|
-
+ a.kneeAcceleration / Math.max(1, b.kneeAcceleration);
|
|
554
|
-
if (score < bestScore) { best = result; bestScore = score; best.performance.alignmentScale = scale; }
|
|
555
|
-
// Weaker alignment can repair overshoot/continuity, but cannot fill a
|
|
556
|
-
// remaining gap (especially a user-protected floating pose). Do not
|
|
557
|
-
// rerun the entire clip for an immutable or under-corrected contact.
|
|
558
|
-
const needsRelaxation = a.penetration > PHYSICS_LIMITS.floor
|
|
559
|
-
|| a.kneeAcceleration > Math.max(1e-6, b.kneeAcceleration * 1.1)
|
|
560
|
-
|| a.rootAcceleration > Math.max(1e-6, b.rootAcceleration * 1.1);
|
|
561
|
-
if (safe || !needsRelaxation || options.strength === 0) break;
|
|
562
|
-
}
|
|
563
|
-
best.performance.totalMs = performance.now() - start; best.performance.attempts = attempts;
|
|
564
|
-
best.performance.cacheHit = initialCacheHit; best.performance.sourceMs = sourceMs; best.performance.supportMs = supportMs;
|
|
565
|
-
progress(100);
|
|
566
|
-
return best;
|
|
567
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
|
|
3
|
-
/** Optimize the WHOLE root track against limb reach and knee acceleration.
|
|
4
|
-
* Neighbour costs are re-evaluated together, so a locally perfect plant may
|
|
5
|
-
* not buy itself a knee pop or a root jump on the following frame. */
|
|
6
|
-
export function relaxPhysicsRoot(samples, targets, roots, protectedFrames = [], limit = .18) {
|
|
7
|
-
const locks = new Set(protectedFrames), n = samples.length;
|
|
8
|
-
const seed = roots.map((r) => r.slice(0, 3)), values = seed.map((r) => [...r]);
|
|
9
|
-
const constraints = samples.map((s, f) => targets[f].map((t) => {
|
|
10
|
-
const c = s.chains[t.site.chain];
|
|
11
|
-
const offset = c.root.clone().sub(s.root).applyQuaternion(new THREE.Quaternion().setFromEuler(new THREE.Euler(roots[f][3], 0, roots[f][4]))).add(s.root);
|
|
12
|
-
return { id: t.site.id, kind: t.site.kind, start: offset.toArray(), end: t.target.toArray(), lengths: c.lengths };
|
|
13
|
-
}));
|
|
14
|
-
const derived = (f) => {
|
|
15
|
-
let reach = 0; const knees = {};
|
|
16
|
-
for (const c of constraints[f]) {
|
|
17
|
-
const d = Math.hypot(...c.start.map((v, j) => v + values[f][j] - c.end[j]));
|
|
18
|
-
const [a, b] = c.lengths;
|
|
19
|
-
const error = c.kind === "knee" ? d - a : Math.max(0, d - a - b + .0005);
|
|
20
|
-
reach += error * error;
|
|
21
|
-
if (c.kind === "foot") knees[c.id] = locks.has(f) ? samples[f].knees[c.id] * Math.PI / 180 : Math.acos(THREE.MathUtils.clamp((a * a + b * b - d * d) / (2 * a * b), -1, 1));
|
|
22
|
-
}
|
|
23
|
-
return { reach, knees };
|
|
24
|
-
};
|
|
25
|
-
let cache = values.map((_, f) => derived(f));
|
|
26
|
-
const cost = (f) => {
|
|
27
|
-
let value = 20000 * cache[f].reach + 2 * values[f].reduce((s, v, j) => s + (v - seed[f][j]) ** 2, 0);
|
|
28
|
-
for (let i = Math.max(1, f - 1); i <= Math.min(n - 2, f + 1); i += 1) {
|
|
29
|
-
for (let j = 0; j < 3; j += 1) {
|
|
30
|
-
const axis = ["x", "y", "z"][j];
|
|
31
|
-
const a = samples[i - 1].root[axis] + values[i - 1][j], b = samples[i].root[axis] + values[i][j], c = samples[i + 1].root[axis] + values[i + 1][j];
|
|
32
|
-
value += 600 * (a - 2 * b + c) ** 2;
|
|
33
|
-
}
|
|
34
|
-
for (const id of ["leftFoot", "rightFoot"]) {
|
|
35
|
-
const a = cache[i - 1].knees[id], b = cache[i].knees[id], c = cache[i + 1].knees[id];
|
|
36
|
-
if ([a, b, c].every(Number.isFinite)) value += .3 * (a - 2 * b + c) ** 2;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return value;
|
|
40
|
-
};
|
|
41
|
-
for (const step of [.008, .003, .001, .0003]) for (let sweep = 0; sweep < 4; sweep += 1) {
|
|
42
|
-
for (let k = 0; k < n; k += 1) {
|
|
43
|
-
const f = sweep % 2 ? n - 1 - k : k; if (locks.has(f)) continue;
|
|
44
|
-
let best = cost(f);
|
|
45
|
-
for (let j = 0; j < 3; j += 1) for (const sign of [-1, 1]) {
|
|
46
|
-
const old = values[f][j], oldCache = cache[f]; values[f][j] += sign * step;
|
|
47
|
-
if (Math.hypot(...values[f]) > limit) { values[f][j] = old; continue; }
|
|
48
|
-
cache[f] = derived(f); const next = cost(f);
|
|
49
|
-
if (next < best) best = next; else { values[f][j] = old; cache[f] = oldCache; }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return roots.map((r, f) => [...values[f], r[3], r[4]]);
|
|
54
|
-
}
|