cozyclay 2.1.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +43 -116
- package/bin/agent/agent-runner.mjs +14 -15
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/session-store.mjs +2 -2
- package/bin/agent/studio-context-budget.mjs +93 -0
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-BWCqdgWA.js +1 -0
- package/dist/assets/app-DfTCxaKk.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-BxKXh07s.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
- package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-D78eiKTa.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +151 -2625
- package/src/agent-progress.js +80 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +12 -2
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +10 -8
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +214 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +55 -13
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +53 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +47 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +60 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +79 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +113 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +197 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +177 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +144 -0
- package/src/motion3/compiler/validate-parts.js +118 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +74 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +177 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +204 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/people.js +82 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +125 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +195 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +44 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/people-checks.js +37 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +228 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +129 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +113 -0
- package/src/motion3/schema/types.js +149 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/settle.js +136 -0
- package/src/motion3/skills/basic.js +83 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/flip.js +200 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/impact.js +109 -0
- package/src/motion3/skills/index.js +85 -0
- package/src/motion3/skills/info.js +80 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/qa-label.js +25 -0
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +16 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +47 -77
- package/src/studio-app-binding.js +27 -47
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +15 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +109 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +9 -125
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Two-bone limb IK, foot orientation, wrist and finger posing on a world-space state.
|
|
2
|
+
// Every edit rotates a bone's subtree about that bone's own joint, so the result maps
|
|
3
|
+
// exactly back to local quaternions (skeleton.js toLocal).
|
|
4
|
+
import { add, sub, scale, dot, len, norm, cross, clamp, apply, T, align, axisRot, ry, rx } from "./math.js";
|
|
5
|
+
import { rotateSubtree } from "./skeleton.js";
|
|
6
|
+
|
|
7
|
+
/** Direction fixed to bone b that points along `restDir` in the rest pose. */
|
|
8
|
+
export const carried = (sk, s, b, restDir) => apply(s.R[b], apply(T(sk.restWorldRot[b]), restDir));
|
|
9
|
+
|
|
10
|
+
// Elbow flexion: degrees between the upper arm and the forearm (0 straight), read on the joints as QA
|
|
11
|
+
// reads it (Arm->ForeArm against ForeArm->Hand). Active flexion ends near 145 deg (clinical norms
|
|
12
|
+
// 140-150: AMA 140, Kendall 145, AAOS 150), where the forearm's muscle meets the upper arm's; the
|
|
13
|
+
// passive fold stops a few degrees on. An arm IK follows its wrist target freely to ELBOW_FOLD, eases
|
|
14
|
+
// onto ELBOW_STOP (armReach) and leaves a target nearer the shoulder as a residual (REACH), like one
|
|
15
|
+
// out of reach. Before, twoBone folded an arm flat: the forearm lay in the upper arm and the bend plane
|
|
16
|
+
// was any plane at all (session df6acdcc, 171 deg).
|
|
17
|
+
export const ELBOW_FOLD = 145, ELBOW_STOP = 150;
|
|
18
|
+
// Knee flexion, read on the joints as QA reads it (UpLeg->Leg against Leg->Foot, 0 straight). Active
|
|
19
|
+
// flexion ends near 135-140 deg (AAOS 135); past it the knee folds only passively, under the body's
|
|
20
|
+
// weight, until the thigh rests on the calf: a deep squat with the heels down reaches 150-160 deg, and
|
|
21
|
+
// passive flexion stops near 160 (the heel at the buttock). A leg IK follows its ankle target freely to
|
|
22
|
+
// KNEE_FOLD (the squats the sessions keyed bend 125-138 deg), eases onto KNEE_STOP as the thigh presses
|
|
23
|
+
// on the calf, and leaves a target nearer the hip as a residual (REACH leg_miss). Before, twoBone folded
|
|
24
|
+
// a knee flat: the shin went into the thigh.
|
|
25
|
+
export const KNEE_FOLD = 150, KNEE_STOP = 160;
|
|
26
|
+
/** The flexion (deg) that puts the wrist d metres from the shoulder of an arm with bones a, b (180 at |a - b| or nearer). */
|
|
27
|
+
export const flexionAt = (a, b, d) => (Math.acos(clamp((d * d - a * a - b * b) / (2 * a * b), -1, 1)) * 180) / Math.PI;
|
|
28
|
+
/** The shoulder-wrist distance of an arm with bones a, b folded `deg` degrees. */
|
|
29
|
+
export const reachAt = (a, b, deg) => Math.sqrt(a * a + b * b + 2 * a * b * Math.cos((deg * Math.PI) / 180));
|
|
30
|
+
/**
|
|
31
|
+
* The shoulder-wrist distance an arm with bones a, b takes for a wrist target d metres away: d while
|
|
32
|
+
* the elbow folds ELBOW_FOLD or less (d >= k), then d + (k - d)^2 / 4w (w = k - the ELBOW_STOP distance)
|
|
33
|
+
* down to d = k - 2w, and the stop beyond. The wrist slows at a constant rate (C1, no kink as it
|
|
34
|
+
* engages: half the peak deceleration of an exponential ease as wide) and sits on the stop once the
|
|
35
|
+
* target is 2w inside. A leg passes KNEE_FOLD and KNEE_STOP (the hip-ankle distance).
|
|
36
|
+
*/
|
|
37
|
+
export function armReach(a, b, d, fold = ELBOW_FOLD, stop = ELBOW_STOP) {
|
|
38
|
+
const k = reachAt(a, b, fold);
|
|
39
|
+
if (d >= k) return d;
|
|
40
|
+
const w = k - reachAt(a, b, stop);
|
|
41
|
+
return d > k - 2 * w ? d + ((k - d) * (k - d)) / (4 * w) : k - w;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The wrist point an arm (shoulder S, bones a, b) reaches for a wrist target T: T while the elbow
|
|
45
|
+
* folds ELBOW_FOLD or less, else the point armReach(|T - S|) from the shoulder on the ray from the fold
|
|
46
|
+
* centre C through T. No point can stand for every target inside the stop and still move smoothly (a
|
|
47
|
+
* ball has no continuous retraction onto its boundary): the nearest point (the ray from the shoulder)
|
|
48
|
+
* whips round the shoulder as a target passes near it (session df6acdcc: 120 deg in 2 frames). So C
|
|
49
|
+
* sits on the stop's sphere straight behind the shoulder (`back`, the chest's -Z), in the back, where
|
|
50
|
+
* no hand reaches, and a target passing in front of, above, below or beside the shoulder glides.
|
|
51
|
+
* A leg passes the knee's fold and stop, and `back` is the pelvis's up (upOf): no foot goes into the
|
|
52
|
+
* pelvis, and a squat's ankle target under the hip goes on down from it.
|
|
53
|
+
*/
|
|
54
|
+
export function foldPoint(S, T, a, b, back, fold = ELBOW_FOLD, stop = ELBOW_STOP) {
|
|
55
|
+
const k = reachAt(a, b, fold), d = len(sub(T, S));
|
|
56
|
+
if (d >= k) return T;
|
|
57
|
+
const r = armReach(a, b, d, fold, stop), m = scale(back, reachAt(a, b, stop)), C = add(S, m), e = sub(T, C);
|
|
58
|
+
const u = len(e) > 1e-9 ? norm(e) : scale(back, -1), em = dot(u, m);
|
|
59
|
+
return add(C, scale(u, -em + Math.sqrt(Math.max(0, em * em - dot(m, m) + r * r))));
|
|
60
|
+
}
|
|
61
|
+
/** The direction behind the shoulders on pose s (the chest bone's rest -Z), where foldPoint's rays start. */
|
|
62
|
+
export const backOf = (sk, s) => carried(sk, s, sk.index.Spine2 ?? sk.index.Hips, [0, 0, -1]);
|
|
63
|
+
/** The direction up from the hips on pose s (the pelvis's rest +Y), where a knee's foldPoint rays start. */
|
|
64
|
+
const upOf = (sk, s) => carried(sk, s, sk.index.Hips, [0, 1, 0]);
|
|
65
|
+
/** The flexion (deg) an arm with bones a, b takes when `deg` is asked (armReach as an angle). */
|
|
66
|
+
export const elbowFold = (a, b, deg) => flexionAt(a, b, armReach(a, b, reachAt(a, b, deg)));
|
|
67
|
+
/** The flexion (deg) a wrist target asks of the arm of `side` on pose s (a leg's ankle target: bones UpLeg, Leg, Foot). */
|
|
68
|
+
export function askedFold(sk, s, side, target, [u, m, e] = ["Arm", "ForeArm", "Hand"]) {
|
|
69
|
+
const S = s.P[sk.index[`${side}${u}`]], E = s.P[sk.index[`${side}${m}`]], W = s.P[sk.index[`${side}${e}`]];
|
|
70
|
+
return flexionAt(len(sub(E, S)), len(sub(W, E)), len(sub(target, S)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Ease an elbow folded past ELBOW_FOLD (an FK abend) back as the IK would (elbowFold), the forearm turning in its bend plane. */
|
|
74
|
+
export function limitFold(sk, s, side) {
|
|
75
|
+
const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], up = sub(s.P[fa], s.P[a]), down = sub(s.P[sk.index[`${side}Hand`]], s.P[fa]);
|
|
76
|
+
const deg = (Math.acos(clamp(dot(norm(up), norm(down)), -1, 1)) * 180) / Math.PI, n = cross(up, down);
|
|
77
|
+
if (deg > ELBOW_FOLD && len(n) > 1e-12) rotateSubtree(sk, s, fa, s.P[fa].slice(), axisRot(n, elbowFold(len(up), len(down), deg) - deg));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The stops twoBone keeps to: the free fold and the stop (deg), and the fold centre's direction on pose s. */
|
|
81
|
+
const STOPS = { elbow: [ELBOW_FOLD, ELBOW_STOP, backOf], knee: [KNEE_FOLD, KNEE_STOP, upOf] };
|
|
82
|
+
|
|
83
|
+
/** Solve root-mid-end so `end` lands on `target` (clamped to reach), bending toward `pole`.
|
|
84
|
+
* `pole0` is the limb's current bend reference carried by the upper bone. Returns the miss (m).
|
|
85
|
+
* `stop`: "elbow" or "knee", the mid joint folds no further than armReach allows with that joint's
|
|
86
|
+
* fold and stop (foldPoint), and the miss is the residual either way (out of reach, or nearer the
|
|
87
|
+
* shoulder or hip than the fold). */
|
|
88
|
+
export function twoBone(sk, s, root, mid, end, target, pole, restBend, stop = null) {
|
|
89
|
+
const Rt = s.P[root], M = s.P[mid], E = s.P[end];
|
|
90
|
+
const a = len(sub(M, Rt)), b = len(sub(E, M)), lim = STOPS[stop];
|
|
91
|
+
// a joint folds no further than armReach lets it: the end goes to foldPoint, and misses the target by the rest
|
|
92
|
+
const W = lim ? foldPoint(Rt, target, a, b, lim[2](sk, s), lim[0], lim[1]) : target, inside = W === target ? 0 : len(sub(W, target));
|
|
93
|
+
const v = sub(W, Rt), d = len(v), u = scale(v, 1 / Math.max(d, 1e-9));
|
|
94
|
+
const dc = clamp(d, Math.abs(a - b) + 1e-4, (a + b) * 0.9995);
|
|
95
|
+
const l = (a * a - b * b + dc * dc) / (2 * dc), h = Math.sqrt(Math.max(a * a - l * l, 0));
|
|
96
|
+
const pv = norm(sub(pole, scale(u, dot(pole, u))));
|
|
97
|
+
const Mn = add(Rt, add(scale(u, l), scale(pv, h)));
|
|
98
|
+
const p0 = carried(sk, s, root, restBend);
|
|
99
|
+
rotateSubtree(sk, s, root, Rt.slice(), align(sub(M, Rt), p0, sub(Mn, Rt), pv));
|
|
100
|
+
const p1 = carried(sk, s, mid, restBend);
|
|
101
|
+
const Tn = add(Rt, scale(u, dc));
|
|
102
|
+
rotateSubtree(sk, s, mid, s.P[mid].slice(), align(sub(s.P[end], s.P[mid]), p1, sub(Tn, s.P[mid]), pv));
|
|
103
|
+
return inside || d - dc;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Aim a foot: Foot->ToeBase along yaw/pitch of its rest direction, sole kept level. With `tilt` (pose.js tiltFrame: a body
|
|
108
|
+
* turned well past upright) the yaw and pitch are read in that frame and the sole levels to its up, so a foot in a flip
|
|
109
|
+
* points with the shin instead of staying flat to the floor; no toe-off then (the toes are off the ground).
|
|
110
|
+
*/
|
|
111
|
+
export function orientFoot(sk, s, side, yawDeg, pitchDeg, tilt = null) {
|
|
112
|
+
const foot = sk.index[`${side}Foot`], toe = sk.index[`${side}ToeBase`];
|
|
113
|
+
const rest = sub(sk.restWorldPos[toe], sk.restWorldPos[foot]);
|
|
114
|
+
const want = tilt ? apply(tilt.R, apply(ry(yawDeg - tilt.yaw), apply(rx(pitchDeg), rest))) : apply(ry(yawDeg), apply(rx(pitchDeg), rest));
|
|
115
|
+
const up = tilt ? apply(tilt.R, [0, 1, 0]) : [0, 1, 0];
|
|
116
|
+
// level the sole: the foot's own carried up vector goes to world up (no roll from the leg IK)
|
|
117
|
+
rotateSubtree(sk, s, foot, s.P[foot].slice(), align(sub(s.P[toe], s.P[foot]), carried(sk, s, foot, [0, 1, 0]), want, up));
|
|
118
|
+
// Toe-off: bend the toe joint so the toes stay flat on the ground while the heel rises.
|
|
119
|
+
const tip = sk.index[`${side}Toe_End`];
|
|
120
|
+
if (!tilt && pitchDeg > 0 && tip !== undefined) {
|
|
121
|
+
const seg = sub(s.P[tip], s.P[toe]), flat = [seg[0], 0, seg[2]];
|
|
122
|
+
if (len(flat) > 1e-6) rotateSubtree(sk, s, toe, s.P[toe].slice(), align(seg, [0, 1, 0], flat, [0, 1, 0]));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function palmAxes(sk, s, side) {
|
|
127
|
+
const h = sk.index[`${side}Hand`], m1 = sk.index[`${side}HandMiddle1`];
|
|
128
|
+
const d = norm(sub(s.P[m1], s.P[h]));
|
|
129
|
+
let l = sub(s.P[sk.index[`${side}HandIndex1`]], s.P[sk.index[`${side}HandPinky1`]]);
|
|
130
|
+
l = norm(sub(l, scale(d, dot(l, d))));
|
|
131
|
+
return { d, l, n: cross(d, l) };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Turn the elbow `deg` about the shoulder-wrist line: the hand keeps its place and its world turn. */
|
|
135
|
+
export function swivelElbow(sk, s, side, deg) {
|
|
136
|
+
const a = sk.index[`${side}Arm`], h = sk.index[`${side}Hand`], R = axisRot(sub(s.P[h], s.P[a]), deg);
|
|
137
|
+
rotateSubtree(sk, s, a, s.P[a].slice(), R);
|
|
138
|
+
rotateSubtree(sk, s, h, s.P[h].slice(), T(R));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Wrist twist (about the forearm) and flex (about the knuckle line), degrees, clamped. */
|
|
142
|
+
export function wrist(sk, s, side, twistDeg, flexDeg) {
|
|
143
|
+
if (!twistDeg && !flexDeg) return;
|
|
144
|
+
const h = sk.index[`${side}Hand`], fa = sk.index[`${side}ForeArm`];
|
|
145
|
+
const axT = norm(sub(s.P[h], s.P[fa]));
|
|
146
|
+
const { l } = palmAxes(sk, s, side);
|
|
147
|
+
const axF = norm(sub(l, scale(axT, dot(l, axT))));
|
|
148
|
+
rotateSubtree(sk, s, h, s.P[h].slice(), axisRot(axF, clamp(flexDeg, -70, 70)));
|
|
149
|
+
rotateSubtree(sk, s, h, s.P[h].slice(), axisRot(axT, clamp(twistDeg, -85, 85)));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Finger curl presets, degrees per phalanx (1, 2, 3); thumb is [opposition, curl, curl]. A fist and a grip keep the
|
|
153
|
+
// relaxed thumb here and close it with THUMB_WRAP: these thumb curls turn about the knuckle line and the hand's long axis,
|
|
154
|
+
// which only swings a thumb out to the side (a fist read as a thumbs-up).
|
|
155
|
+
const FINGER_PRESETS = {
|
|
156
|
+
relaxed: { Index: [12, 18, 10], Middle: [15, 20, 12], Ring: [18, 22, 12], Pinky: [20, 24, 14], Thumb: [15, 10, 8] },
|
|
157
|
+
fist: { Index: [80, 95, 60], Middle: [85, 95, 60], Ring: [85, 95, 60], Pinky: [85, 95, 60], Thumb: [15, 10, 8] },
|
|
158
|
+
grip: { Index: [55, 60, 35], Middle: [60, 62, 35], Ring: [62, 62, 35], Pinky: [65, 62, 35], Thumb: [15, 10, 8] },
|
|
159
|
+
cigarette: { Index: [8, 10, 6], Middle: [10, 12, 8], Ring: [60, 75, 45], Pinky: [65, 80, 45], Thumb: [35, 25, 20] },
|
|
160
|
+
point: { Index: [0, 0, 0], Middle: [80, 95, 60], Ring: [85, 95, 60], Pinky: [85, 95, 60], Thumb: [40, 30, 30] },
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Blend presets by weight over `relaxed` and curl every finger toward the palm. */
|
|
164
|
+
export function fingers(sk, s, side, weights) {
|
|
165
|
+
const base = FINGER_PRESETS.relaxed, sign = side === "Left" ? -1 : 1;
|
|
166
|
+
const curl = (f, k) => base[f][k] + Object.entries(weights ?? {}).reduce((acc, [p, w]) => acc + (w || 0) * ((FINGER_PRESETS[p]?.[f]?.[k] ?? base[f][k]) - base[f][k]), 0);
|
|
167
|
+
for (const f of ["Index", "Middle", "Ring", "Pinky", "Thumb"]) {
|
|
168
|
+
for (let k = 0; k < 3; k++) {
|
|
169
|
+
const j = sk.index[`${side}Hand${f}${k + 1}`];
|
|
170
|
+
if (j === undefined) continue;
|
|
171
|
+
const { l, d } = palmAxes(sk, s, side);
|
|
172
|
+
const ax = f === "Thumb" && k === 0 ? d : l;
|
|
173
|
+
rotateSubtree(sk, s, j, s.P[j].slice(), axisRot(ax, sign * curl(f, k)));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const wrap = [0, 1, 2, 3].map(i => Object.entries(weights ?? {}).reduce((acc, [p, w]) => acc + (w || 0) * (THUMB_WRAP[p]?.[i] ?? 0), 0));
|
|
177
|
+
if (wrap.some(Boolean)) wrapThumb(sk, s, side, wrap);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// A closing hand's thumb, by preset weight after the relaxed one, degrees: turned in about the hand's long axis toward the
|
|
181
|
+
// palm and across it, then flexed at its two joints about their own axes (bone x palm normal). The fist's thumb lies over
|
|
182
|
+
// the index and middle fingers' middle phalanges; the grip's closes around a handle against the index finger. Searched on
|
|
183
|
+
// both rigs within the joints' ranges (thumb tip to the index's middle phalanx: fist 8.4 -> 3.7 cm Y Bot, 6.9 -> 2.4 X Bot).
|
|
184
|
+
const THUMB_WRAP = { fist: [-70, -5, 5, 80], grip: [-30, -30, 30, 10] };
|
|
185
|
+
function wrapThumb(sk, s, side, [oppose, across, flex2, flex3]) {
|
|
186
|
+
const sign = side === "Left" ? -1 : 1, J = k => sk.index[`${side}HandThumb${k}`];
|
|
187
|
+
if (J(1) === undefined) return;
|
|
188
|
+
const { d, n } = palmAxes(sk, s, side);
|
|
189
|
+
rotateSubtree(sk, s, J(1), s.P[J(1)].slice(), axisRot(d, sign * oppose));
|
|
190
|
+
// n = d x l flips with the mirror, so the turn across the palm keeps its sign on both hands
|
|
191
|
+
rotateSubtree(sk, s, J(1), s.P[J(1)].slice(), axisRot(n, across));
|
|
192
|
+
for (const [k, deg] of [[2, flex2], [3, flex3]]) {
|
|
193
|
+
const ax = norm(cross(norm(sub(s.P[J(k + 1)], s.P[J(k)])), n));
|
|
194
|
+
rotateSubtree(sk, s, J(k), s.P[J(k)].slice(), axisRot(ax, sign * deg));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Small dependency-free vector / 3x3 / quaternion helpers for the motion3 compiler.
|
|
2
|
+
// Vectors are [x, y, z]; matrices are row-major [[...],[...],[...]] acting on column
|
|
3
|
+
// vectors (R v); quaternions are three.js order [x, y, z, w].
|
|
4
|
+
|
|
5
|
+
export const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
6
|
+
export const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
7
|
+
export const scale = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
|
|
8
|
+
export const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
9
|
+
export const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
10
|
+
export const len = a => Math.hypot(a[0], a[1], a[2]);
|
|
11
|
+
export const dist = (a, b) => len(sub(a, b));
|
|
12
|
+
export function norm(a) {
|
|
13
|
+
const l = len(a);
|
|
14
|
+
return l > 1e-12 ? [a[0] / l, a[1] / l, a[2] / l] : [0, 0, 0];
|
|
15
|
+
}
|
|
16
|
+
export const clamp = (x, lo, hi) => Math.min(hi, Math.max(lo, x));
|
|
17
|
+
const rad = d => d * Math.PI / 180;
|
|
18
|
+
const deg = r => r * 180 / Math.PI;
|
|
19
|
+
|
|
20
|
+
export const I3 = () => [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
|
21
|
+
export function mul(A, B) {
|
|
22
|
+
const C = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
|
|
23
|
+
for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) C[i][j] = A[i][0] * B[0][j] + A[i][1] * B[1][j] + A[i][2] * B[2][j];
|
|
24
|
+
return C;
|
|
25
|
+
}
|
|
26
|
+
export const apply = (R, v) => [R[0][0] * v[0] + R[0][1] * v[1] + R[0][2] * v[2], R[1][0] * v[0] + R[1][1] * v[1] + R[1][2] * v[2], R[2][0] * v[0] + R[2][1] * v[1] + R[2][2] * v[2]];
|
|
27
|
+
export const T = R => [[R[0][0], R[1][0], R[2][0]], [R[0][1], R[1][1], R[2][1]], [R[0][2], R[1][2], R[2][2]]];
|
|
28
|
+
const cols = (a, b, c) => [[a[0], b[0], c[0]], [a[1], b[1], c[1]], [a[2], b[2], c[2]]];
|
|
29
|
+
|
|
30
|
+
export function rx(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[1, 0, 0], [0, c, -s], [0, s, c]]; }
|
|
31
|
+
export function ry(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[c, 0, s], [0, 1, 0], [-s, 0, c]]; }
|
|
32
|
+
export function rz(d) { const t = rad(d), c = Math.cos(t), s = Math.sin(t); return [[c, -s, 0], [s, c, 0], [0, 0, 1]]; }
|
|
33
|
+
|
|
34
|
+
/** Rotation by `d` degrees about unit-normalised axis `ax` (Rodrigues). */
|
|
35
|
+
export function axisRot(ax, d) {
|
|
36
|
+
const [x, y, z] = norm(ax), t = rad(d), c = Math.cos(t), s = Math.sin(t), k = 1 - c;
|
|
37
|
+
return [[c + x * x * k, x * y * k - z * s, x * z * k + y * s], [y * x * k + z * s, c + y * y * k, y * z * k - x * s], [z * x * k - y * s, z * y * k + x * s, c + z * z * k]];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Orthonormal frame whose first column is d and second lies toward p. */
|
|
41
|
+
function frame(d, p) {
|
|
42
|
+
const a = norm(d);
|
|
43
|
+
let b = sub(p, scale(a, dot(p, a)));
|
|
44
|
+
if (len(b) < 1e-9) b = sub([0, 1, 0], scale(a, a[1])), len(b) < 1e-9 && (b = sub([1, 0, 0], scale(a, a[0])));
|
|
45
|
+
b = norm(b);
|
|
46
|
+
return cols(a, b, cross(a, b));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Rotation taking frame (d0, p0) onto frame (d1, p1). */
|
|
50
|
+
export const align = (d0, p0, d1, p1) => mul(frame(d1, p1), T(frame(d0, p0)));
|
|
51
|
+
|
|
52
|
+
export function quatFromMat(R) {
|
|
53
|
+
const m00 = R[0][0], m11 = R[1][1], m22 = R[2][2], tr = m00 + m11 + m22;
|
|
54
|
+
let x, y, z, w;
|
|
55
|
+
if (tr > 0) { const s = 0.5 / Math.sqrt(tr + 1); w = 0.25 / s; x = (R[2][1] - R[1][2]) * s; y = (R[0][2] - R[2][0]) * s; z = (R[1][0] - R[0][1]) * s; }
|
|
56
|
+
else if (m00 > m11 && m00 > m22) { const s = 2 * Math.sqrt(1 + m00 - m11 - m22); w = (R[2][1] - R[1][2]) / s; x = 0.25 * s; y = (R[0][1] + R[1][0]) / s; z = (R[0][2] + R[2][0]) / s; }
|
|
57
|
+
else if (m11 > m22) { const s = 2 * Math.sqrt(1 + m11 - m00 - m22); w = (R[0][2] - R[2][0]) / s; x = (R[0][1] + R[1][0]) / s; y = 0.25 * s; z = (R[1][2] + R[2][1]) / s; }
|
|
58
|
+
else { const s = 2 * Math.sqrt(1 + m22 - m00 - m11); w = (R[1][0] - R[0][1]) / s; x = (R[0][2] + R[2][0]) / s; y = (R[1][2] + R[2][1]) / s; z = 0.25 * s; }
|
|
59
|
+
const l = Math.hypot(x, y, z, w);
|
|
60
|
+
return [x / l, y / l, z / l, w / l];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function matFromQuat([x, y, z, w]) {
|
|
64
|
+
return [
|
|
65
|
+
[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
|
|
66
|
+
[2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
|
|
67
|
+
[2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Angle in degrees between two directions. */
|
|
72
|
+
export const angle = (a, b) => deg(Math.acos(clamp(dot(norm(a), norm(b)), -1, 1)));
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// One frame of the body: channel values -> world-space bone state.
|
|
2
|
+
// Conventions (CozyClay): metres, +Y up, yaw 0 faces +Z, positive yaw turns toward +X,
|
|
3
|
+
// the character's left is +X at yaw 0. Body-frame vectors (poles, body-local targets) are
|
|
4
|
+
// rotated by the root yaw only.
|
|
5
|
+
import { add, sub, scale, norm, len, apply, mul, rx, ry, rz } from "./math.js";
|
|
6
|
+
import { restState, rotateSubtree } from "./skeleton.js";
|
|
7
|
+
import { twoBone, orientFoot, wrist, fingers, limitFold, askedFold, swivelElbow, ELBOW_FOLD, KNEE_FOLD } from "./limbs.js";
|
|
8
|
+
import { holdHand } from "./hold.js";
|
|
9
|
+
import { clampWrist } from "./wrist-clamp.js";
|
|
10
|
+
import { ELBOW_REST } from "./arm-orient.js";
|
|
11
|
+
import { wristBody, bodyDistance } from "../body.js";
|
|
12
|
+
|
|
13
|
+
export const SIDES = { l: "Left", r: "Right" };
|
|
14
|
+
/** How much of an auto-fix elbow turn an arm at IK weight w takes (fix/self.js plans with the same). */
|
|
15
|
+
export const turnWeight = w => { const x = Math.min(1, Math.max(0, w)); return x * x * (3 - 2 * x); };
|
|
16
|
+
const KNEE_REST = [0, 0, 1], LEG = ["UpLeg", "Leg", "Foot"];
|
|
17
|
+
|
|
18
|
+
const v3 = (x, d = [0, 0, 0]) => (Array.isArray(x) || ArrayBuffer.isView(x) ? [x[0], x[1], x[2]] : d);
|
|
19
|
+
const num = (x, d = 0) => (typeof x === "number" && Number.isFinite(x) ? x : d);
|
|
20
|
+
|
|
21
|
+
/** Split a body-frame rotation over a chain of bones, each turning about its own joint. */
|
|
22
|
+
function spread(sk, s, bones, yaw, pitch, roll) {
|
|
23
|
+
const k = bones.length;
|
|
24
|
+
for (const name of bones) {
|
|
25
|
+
const b = sk.index[name];
|
|
26
|
+
rotateSubtree(sk, s, b, s.P[b].slice(), mul(ry(yaw / k), mul(rx(pitch / k), rz(roll / k))));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Soft reach (spec.fix.reach): a wrist target up to 5 cm beyond the arm's reach is pulled onto
|
|
31
|
+
// the reach sphere through a C1 soft knee, so the arm never snaps straight; the overshoot is
|
|
32
|
+
// recorded. Farther targets are staging errors and stay for QA (REACH).
|
|
33
|
+
const SOFT_KNEE = 0.99, SOFT_MAX = 0.9995;
|
|
34
|
+
export const REACH_FIX_MAX = 0.05;
|
|
35
|
+
function softReach(sk, s, a, fa, h, target, diag, k) {
|
|
36
|
+
const R = len(sub(s.P[fa], s.P[a])) + len(sub(s.P[h], s.P[fa])), v = sub(target, s.P[a]), d = len(v);
|
|
37
|
+
const k0 = SOFT_KNEE * R, w = (SOFT_MAX - SOFT_KNEE) * R;
|
|
38
|
+
if (d <= k0 || d - SOFT_MAX * R > REACH_FIX_MAX) return target;
|
|
39
|
+
const dc = k0 + w * (1 - Math.exp(-(d - k0) / w));
|
|
40
|
+
diag[`reach_over_${k}`] = Math.max(0, d - SOFT_MAX * R);
|
|
41
|
+
return add(s.P[a], scale(v, dc / d));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* hooks.handTarget(side, s) -> { target:[x,y,z] } world wrist target before the FK/IK blend
|
|
46
|
+
* hooks.afterArm(side, s, diag) lets the caller measure props carried by that hand.
|
|
47
|
+
* hooks.push(side) -> [x,y,z] | undefined, a world offset added to the IK target (auto-fix); hooks.skinPush(side) the same
|
|
48
|
+
* for the self fix (fix/self.js), taken as far as the arm is on its IK.
|
|
49
|
+
* hooks.softReach clamps unreachable wrist targets smoothly (auto-fix).
|
|
50
|
+
* hooks.foldFree lets the arm IK fold past the elbow's stop (a fit's measuring passes, compile.js).
|
|
51
|
+
* hooks.hold(side, s) -> { w, palm, measure, pole?, second? } | null: a held object's keys lead the hand (hold.js).
|
|
52
|
+
* hooks.keepOut(side, s, target) -> target: the blended wrist target kept out of the body (auto-fix, compile.js).
|
|
53
|
+
* hooks.swivel(side) -> deg | undefined, an elbow turn about the shoulder-wrist line, the hand kept (auto-fix, fix/self.js).
|
|
54
|
+
* diag.arm_fold_<k>: the flexion (deg) the wrist target asked when past ELBOW_FOLD (arm_miss_<k> is then how far inside).
|
|
55
|
+
* diag.leg_fold_<k>: the knee flexion (deg) the ankle target asked when past KNEE_FOLD (leg_miss_<k> is then how far inside).
|
|
56
|
+
*/
|
|
57
|
+
// arm_hang (keyed by standIdle and the walk): a free FK forearm hangs plumb from the elbow, as a person's does,
|
|
58
|
+
// instead of carrying on the upper arm's slant out from the body (the hands sat about 9 cm wider than a person's);
|
|
59
|
+
// a working arm (on IK, or a hand that holds something: compile.js hangoff, idle.js busyHands) takes none of it. A rig whose thigh comes near (the X Bot) stops HANG_BACK
|
|
60
|
+
// degrees short of the first angle that brings its forearm or hand within HANG_CLEAR of the skin standing, so the
|
|
61
|
+
// walk's sway does not carry it into the thigh. Faded out as arm_rest raises the arms (none at 45); once per rig and rest.
|
|
62
|
+
const HANG_CLEAR = 0.095, HANG_BACK = 2, hangs = new Map();
|
|
63
|
+
function hangOf(sk, down) {
|
|
64
|
+
const key = `${sk.model}|${down}`;
|
|
65
|
+
if (hangs.has(key)) return hangs.get(key);
|
|
66
|
+
const full = (90 - down) * Math.min(1, Math.max(0, (down - 45) / 27)), wb = wristBody(sk);
|
|
67
|
+
const clearAt = h => {
|
|
68
|
+
const s = restState(sk);
|
|
69
|
+
return [["Left", -1], ["Right", 1]].every(([side, sg]) => {
|
|
70
|
+
const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], hd = sk.index[`${side}Hand`], tip = sk.index[`${side}HandMiddle1`];
|
|
71
|
+
rotateSubtree(sk, s, a, s.P[a].slice(), rz(sg * down));
|
|
72
|
+
rotateSubtree(sk, s, fa, s.P[fa].slice(), rz(sg * h));
|
|
73
|
+
const pts = [0.5, 0.75, 1].map(t => add(s.P[fa], scale(sub(s.P[hd], s.P[fa]), t))).concat([s.P[tip]]);
|
|
74
|
+
return pts.every(p => bodyDistance(wb.body, s, p, wb.skip).d >= HANG_CLEAR);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
let hang = full;
|
|
78
|
+
if (wb && full > 0) {
|
|
79
|
+
for (let h = 0; h <= full; h += 0.5) if (!clearAt(h)) { hang = Math.max(0, h - HANG_BACK); break; }
|
|
80
|
+
}
|
|
81
|
+
hangs.set(key, hang);
|
|
82
|
+
return hang;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The frame the knee and elbow poles and the feet are read in once the whole body turns well past upright (a flip, a
|
|
87
|
+
* cartwheel): the root's yaw, pitch and roll, the tilt eased in from TILT_FROM to TILT_FULL degrees (the larger of the
|
|
88
|
+
* wrapped pitch and roll). Under TILT_FROM it is null and the poles turn with the yaw only, as they always have, so an
|
|
89
|
+
* upright take is unchanged.
|
|
90
|
+
*/
|
|
91
|
+
const TILT_FROM = 60, TILT_FULL = 120;
|
|
92
|
+
const wrap180 = a => a - 360 * Math.round(a / 360);
|
|
93
|
+
export function tiltFrame(yaw, pitch, roll) {
|
|
94
|
+
const p = wrap180(pitch), r = wrap180(roll), u = (Math.max(Math.abs(p), Math.abs(r)) - TILT_FROM) / (TILT_FULL - TILT_FROM);
|
|
95
|
+
if (u <= 0) return null;
|
|
96
|
+
const w = u >= 1 ? 1 : u * u * (3 - 2 * u);
|
|
97
|
+
return { yaw, R: mul(ry(yaw), mul(rx(p * w), rz(r * w))) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function pose(sk, c, hooks = {}) {
|
|
101
|
+
const s = restState(sk);
|
|
102
|
+
const diag = {};
|
|
103
|
+
// Base: arms down from the bind T-pose (body frame, before everything else).
|
|
104
|
+
const down = num(c.arm_rest, 72), hang = num(c.arm_hang) && num(c.arm_hang) * hangOf(sk, down);
|
|
105
|
+
for (const [side, sg, k] of [["Left", -1, "l"], ["Right", 1, "r"]]) {
|
|
106
|
+
const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], h = hang * (1 - Math.min(1, Math.max(0, num(c[`armw_${k}`]) + num(c[`hangoff_${k}`]))));
|
|
107
|
+
rotateSubtree(sk, s, a, s.P[a].slice(), rz(sg * down));
|
|
108
|
+
if (h) rotateSubtree(sk, s, fa, s.P[fa].slice(), rz(sg * h));
|
|
109
|
+
}
|
|
110
|
+
// FK arm swing (forward +) and elbow bend, body frame.
|
|
111
|
+
for (const [k, side] of Object.entries(SIDES)) {
|
|
112
|
+
const bd = num(c[`abend_${k}`]), sw = num(c[`aswing_${k}`]);
|
|
113
|
+
const fa = sk.index[`${side}ForeArm`], a = sk.index[`${side}Arm`];
|
|
114
|
+
// an FK bend past the fold eases to its stop like the IK (limbs.js elbowFold)
|
|
115
|
+
if (bd) { rotateSubtree(sk, s, fa, s.P[fa].slice(), rx(-bd)); limitFold(sk, s, side); }
|
|
116
|
+
if (sw) rotateSubtree(sk, s, a, s.P[a].slice(), rx(-sw));
|
|
117
|
+
}
|
|
118
|
+
spread(sk, s, ["Neck", "Head"], num(c.head_yaw), num(c.head_pitch), num(c.head_roll));
|
|
119
|
+
spread(sk, s, ["Spine", "Spine1", "Spine2"], num(c.spine_yaw), num(c.spine_pitch), num(c.spine_roll));
|
|
120
|
+
// Root: turn about the hips joint, then put the hips at `root`.
|
|
121
|
+
const hips = sk.index.Hips, yaw = num(c.yaw);
|
|
122
|
+
rotateSubtree(sk, s, hips, s.P[hips].slice(), mul(ry(yaw), mul(rx(num(c.pitch)), rz(num(c.roll)))));
|
|
123
|
+
const shift = sub(v3(c.root, s.P[hips]), s.P[hips]);
|
|
124
|
+
for (let j = 0; j < s.P.length; j++) s.P[j] = add(s.P[j], shift);
|
|
125
|
+
const tilt = tiltFrame(yaw, num(c.pitch), num(c.roll)), body = tilt?.R ?? ry(yaw);
|
|
126
|
+
// Legs: two-bone IK to the ankle targets, the knee no further folded than its stop (limbs.js KNEE_STOP), then aim the feet.
|
|
127
|
+
for (const [k, side] of Object.entries(SIDES)) {
|
|
128
|
+
if (!c[`foot_${k}`]) continue;
|
|
129
|
+
const target = v3(c[`foot_${k}`]);
|
|
130
|
+
diag[`leg_miss_${k}`] = twoBone(sk, s, sk.index[`${side}UpLeg`], sk.index[`${side}Leg`], sk.index[`${side}Foot`],
|
|
131
|
+
target, apply(body, v3(c[`knee_${k}`], KNEE_REST)), KNEE_REST, "knee");
|
|
132
|
+
// a target nearer the hip than the knee folds leaves a residual (leg_miss); leg_fold says what it asked
|
|
133
|
+
const fold = askedFold(sk, s, side, target, LEG);
|
|
134
|
+
if (fold > KNEE_FOLD) diag[`leg_fold_${k}`] = fold;
|
|
135
|
+
orientFoot(sk, s, side, num(c[`fyaw_${k}`], yaw), num(c[`fpitch_${k}`]), tilt);
|
|
136
|
+
}
|
|
137
|
+
// Arms: blend the FK hand toward the hook's target, poles blended the same way.
|
|
138
|
+
for (const k of hooks.armOrder ?? ["r", "l"]) {
|
|
139
|
+
const side = SIDES[k], w = num(c[`armw_${k}`]);
|
|
140
|
+
const a = sk.index[`${side}Arm`], fa = sk.index[`${side}ForeArm`], h = sk.index[`${side}Hand`];
|
|
141
|
+
const poleIk = norm(apply(body, v3(c[`elbow_${k}`], [0, -1, -0.3])));
|
|
142
|
+
diag[`arm_miss_${k}`] = 0;
|
|
143
|
+
if (w > 1e-4 && hooks.handTarget) {
|
|
144
|
+
const fkHand = s.P[h].slice();
|
|
145
|
+
const goal = hooks.handTarget(k, s, diag) ?? fkHand;
|
|
146
|
+
let target = add(fkHand, scale(sub(goal, fkHand), w));
|
|
147
|
+
if (hooks.keepOut) target = hooks.keepOut(k, s, target);
|
|
148
|
+
const push = hooks.push?.(k);
|
|
149
|
+
if (push) target = add(target, push);
|
|
150
|
+
// the self fix's push rides the IK as far as the arm is on it (an arm coming in from FK would jump)
|
|
151
|
+
const skinPush = hooks.skinPush?.(k);
|
|
152
|
+
if (skinPush) target = add(target, scale(skinPush, turnWeight(w)));
|
|
153
|
+
if (hooks.softReach) target = softReach(sk, s, a, fa, h, target, diag, k);
|
|
154
|
+
const poleFk = norm(sub(s.P[fa], scale(add(s.P[a], fkHand), 0.5)));
|
|
155
|
+
const pole = add(scale(poleFk, 1 - w), scale(poleIk, w));
|
|
156
|
+
// a target nearer the shoulder than the elbow folds leaves a residual too; arm_fold says what it asked
|
|
157
|
+
diag[`arm_miss_${k}`] = twoBone(sk, s, a, fa, h, target, pole, ELBOW_REST, hooks.foldFree ? null : "elbow");
|
|
158
|
+
const fold = askedFold(sk, s, side, target);
|
|
159
|
+
if (fold > ELBOW_FOLD && !hooks.foldFree) diag[`arm_fold_${k}`] = fold;
|
|
160
|
+
}
|
|
161
|
+
wrist(sk, s, side, num(c[`wtwist_${k}`]), num(c[`wflex_${k}`]));
|
|
162
|
+
fingers(sk, s, side, c[`fingers_${k}`]);
|
|
163
|
+
const held = hooks.hold?.(k, s);
|
|
164
|
+
// a keyed carry's hand follows its object; else a fit's hand takes a grip on the body under its anchor (grip.js),
|
|
165
|
+
// and while the keys only ease in (before the first key) the arm they ease from takes it too
|
|
166
|
+
if (!held || held.w < 1) hooks.grip?.(k, s, diag);
|
|
167
|
+
if (held) holdHand(sk, s, side, held, ELBOW_REST, diag, k);
|
|
168
|
+
// after a held object's grip, so its hand keeps the grip the hold solved; an arm coming in from FK turns
|
|
169
|
+
// no more than its IK weight (the IK elbow already lands somewhere else than the FK one)
|
|
170
|
+
const turn = hooks.swivel?.(k);
|
|
171
|
+
// the turn keeps the hand's world turn; a wrist it takes past its limits stops there and the hand turns with the elbow
|
|
172
|
+
// (wrist-clamp.js), unless keys lead what the hand holds: that hand keeps the object's grip (hold.js limits its wrist)
|
|
173
|
+
if (turn) { swivelElbow(sk, s, side, turn * turnWeight(w)); if (!held) clampWrist(sk, s, side); }
|
|
174
|
+
hooks.afterArm?.(k, s, diag);
|
|
175
|
+
}
|
|
176
|
+
return { s, diag };
|
|
177
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Quaternion helpers for the held-object keys (../object-keys.js) and the hand-from-object solve
|
|
2
|
+
// (hold.js). Quaternions are three.js order [x, y, z, w], as math.js quatFromMat / matFromQuat;
|
|
3
|
+
// a rotation vector is axis * radians.
|
|
4
|
+
|
|
5
|
+
export const qmul = (a, b) => [
|
|
6
|
+
a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
|
|
7
|
+
a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
|
|
8
|
+
a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
|
|
9
|
+
a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2],
|
|
10
|
+
];
|
|
11
|
+
export const qconj = q => [-q[0], -q[1], -q[2], q[3]];
|
|
12
|
+
export const qdot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
|
|
13
|
+
export function qnorm(q) {
|
|
14
|
+
const l = Math.hypot(q[0], q[1], q[2], q[3]);
|
|
15
|
+
return l > 1e-12 ? [q[0] / l, q[1] / l, q[2] / l, q[3] / l] : [0, 0, 0, 1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Rotation vector of unit q, the short way round (|v| <= pi). */
|
|
19
|
+
export function qlog(q) {
|
|
20
|
+
const sg = q[3] < 0 ? -1 : 1, x = q[0] * sg, y = q[1] * sg, z = q[2] * sg, w = q[3] * sg, s = Math.hypot(x, y, z);
|
|
21
|
+
if (s < 1e-12) return [2 * x, 2 * y, 2 * z];
|
|
22
|
+
const a = 2 * Math.atan2(s, w) / s;
|
|
23
|
+
return [x * a, y * a, z * a];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Unit quaternion of rotation vector v. */
|
|
27
|
+
export function qexp(v) {
|
|
28
|
+
const a = Math.hypot(v[0], v[1], v[2]);
|
|
29
|
+
if (a < 1e-12) return qnorm([v[0] / 2, v[1] / 2, v[2] / 2, 1]);
|
|
30
|
+
const s = Math.sin(a / 2) / a;
|
|
31
|
+
return [v[0] * s, v[1] * s, v[2] * s, Math.cos(a / 2)];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Rotate vector v by unit q. */
|
|
35
|
+
export const qrot = (q, v) => qmul(qmul(q, [v[0], v[1], v[2], 0]), qconj(q)).slice(0, 3);
|
|
36
|
+
|
|
37
|
+
/** The shortest turn taking unit direction a onto unit direction b (half a turn about a normal of a when they are opposite). */
|
|
38
|
+
export function arcQuat(a, b) {
|
|
39
|
+
const w = 1 + a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
40
|
+
if (w > 1e-9) return qnorm([a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], w]);
|
|
41
|
+
const n = Math.abs(a[0]) < 0.9 ? [0, a[2], -a[1]] : [-a[2], 0, a[0]], l = Math.hypot(...n);
|
|
42
|
+
return [n[0] / l, n[1] / l, n[2] / l, 0];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The turn by `deg` degrees about unit axis `u` (right-handed). */
|
|
46
|
+
export const axisQuat = (u, deg) => { const h = deg * Math.PI / 360, s = Math.sin(h); return [u[0] * s, u[1] * s, u[2] * s, Math.cos(h)]; };
|
|
47
|
+
|
|
48
|
+
/** a -> b by t along the short arc (t = 0: a, t = 1: b). */
|
|
49
|
+
export const slerp = (a, b, t) => qmul(a, qexp(qlog(qmul(qconj(a), b)).map(c => c * t)));
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Rig skeleton for the motion3 compiler: bind data from src/motion3/rigs/<model>.json
|
|
2
|
+
// (extracted by tools/motion3/extract-rig.mjs), in METRES, rig root at the origin
|
|
3
|
+
// facing +Z with +X on the character's left.
|
|
4
|
+
import { apply, add, sub, mul, T, matFromQuat, quatFromMat } from "./math.js";
|
|
5
|
+
|
|
6
|
+
const CM = 0.01;
|
|
7
|
+
|
|
8
|
+
export function createSkeleton(rigJson) {
|
|
9
|
+
const bones = rigJson.bones;
|
|
10
|
+
const names = bones.map(b => b.name);
|
|
11
|
+
const index = Object.fromEntries(names.map((n, i) => [n, i]));
|
|
12
|
+
const parent = bones.map(b => b.parent);
|
|
13
|
+
const children = names.map(() => []);
|
|
14
|
+
parent.forEach((p, i) => { if (p >= 0) children[p].push(i); });
|
|
15
|
+
// subtree[i] = i and every descendant, parents before children
|
|
16
|
+
const subtree = names.map((_, i) => {
|
|
17
|
+
const out = [], stack = [i];
|
|
18
|
+
while (stack.length) { const j = stack.shift(); out.push(j); stack.unshift(...children[j]); }
|
|
19
|
+
return out;
|
|
20
|
+
});
|
|
21
|
+
const restLocalPos = bones.map(b => b.localPos.map(v => v * CM));
|
|
22
|
+
const restLocalRot = bones.map(b => matFromQuat(b.localQuat));
|
|
23
|
+
const restWorldPos = bones.map(b => b.worldPos.map(v => v * CM));
|
|
24
|
+
const restWorldRot = bones.map(b => matFromQuat(b.worldQuat));
|
|
25
|
+
const L = rigJson.landmarks;
|
|
26
|
+
const head = index.Head;
|
|
27
|
+
// Landmarks re-expressed in the Head bone's rest frame, so they ride the head.
|
|
28
|
+
const toHead = p => apply(T(restWorldRot[head]), sub(p.map(v => v * CM), restWorldPos[head]));
|
|
29
|
+
const landmarks = {
|
|
30
|
+
mouth: toHead(L.mouth),
|
|
31
|
+
headCentre: toHead(L.headEllipsoid.centre),
|
|
32
|
+
headRadii: L.headEllipsoid.radii.map(v => v * CM),
|
|
33
|
+
// how far the skin's sole lies below the ToeBase / Toe_End joints at bind (m; qa/geometry.js solePoints)
|
|
34
|
+
sole: { ball: (L.sole?.ball ?? 0) * CM, tip: (L.sole?.tip ?? 0) * CM },
|
|
35
|
+
};
|
|
36
|
+
return { model: rigJson.model, names, index, parent, children, subtree, restLocalPos, restLocalRot, restWorldPos, restWorldRot, landmarks };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Rest pose as a mutable world-space state { P, R }. */
|
|
40
|
+
export function restState(sk) {
|
|
41
|
+
return { P: sk.restWorldPos.map(p => p.slice()), R: sk.restWorldRot.map(r => r.map(row => row.slice())) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Rigidly rotate bone `b`'s subtree by world rotation R about world point `pivot`. */
|
|
45
|
+
export function rotateSubtree(sk, s, b, pivot, R) {
|
|
46
|
+
for (const j of sk.subtree[b]) {
|
|
47
|
+
s.P[j] = add(pivot, apply(R, sub(s.P[j], pivot)));
|
|
48
|
+
s.R[j] = mul(R, s.R[j]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** World state -> per-bone LOCAL quaternions [x,y,z,w] plus the hips local position (m). */
|
|
53
|
+
export function toLocal(sk, s) {
|
|
54
|
+
const quats = sk.names.map((_, j) => {
|
|
55
|
+
const p = sk.parent[j];
|
|
56
|
+
return quatFromMat(p < 0 ? s.R[j] : mul(T(s.R[p]), s.R[j]));
|
|
57
|
+
});
|
|
58
|
+
return { quats, hips: s.P[sk.index.Hips].slice() };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Forward kinematics from local quaternions + hips position back to a world state. */
|
|
62
|
+
export function fk(sk, quats, hips) {
|
|
63
|
+
const P = [], R = [];
|
|
64
|
+
for (const j of sk.subtree[sk.index.Hips]) {
|
|
65
|
+
const p = sk.parent[j], Lr = matFromQuat(quats[j]);
|
|
66
|
+
if (p < 0) { R[j] = Lr; P[j] = hips.slice(); continue; }
|
|
67
|
+
R[j] = mul(R[p], Lr);
|
|
68
|
+
P[j] = add(P[p], apply(R[p], sk.restLocalPos[j]));
|
|
69
|
+
}
|
|
70
|
+
return { P, R };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Point fixed to a bone, given in that bone's own frame (e.g. a landmark). */
|
|
74
|
+
export const bonePoint = (s, b, local) => add(s.P[b], apply(s.R[b], local));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The checks every part of the validator shares: a bone, a scene object, a check point.
|
|
2
|
+
// Messages are exact (the agent fixes them in one edit). See validate.js.
|
|
3
|
+
import { didYouMean } from "./vocab.js";
|
|
4
|
+
import { partsOf } from "../object-pose.js";
|
|
5
|
+
|
|
6
|
+
export function boneOk(err, sk, at, bone) {
|
|
7
|
+
if (!sk || sk.index[bone] !== undefined) return true;
|
|
8
|
+
err(`${at} names unknown bone "${bone}"${didYouMean(String(bone), sk.names)}`);
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A scene object id. A group (an Empty: no box of its own) passes only with { groups: true }
|
|
14
|
+
* (carry, hinge, object: refs and object anchors take groups, see C3); elsewhere the message lists its parts.
|
|
15
|
+
*/
|
|
16
|
+
export function objectOk(err, scene, at, id, { groups = false } = {}) {
|
|
17
|
+
const ids = Object.keys(scene?.objects ?? {});
|
|
18
|
+
if (!ids.length || ids.includes(id)) return true;
|
|
19
|
+
const parts = partsOf(scene, id);
|
|
20
|
+
if (parts.length && groups) return true;
|
|
21
|
+
if (parts.length) err(`${at} names "${id}", a group (Empty) with no body of its own; its parts are ${parts.join(", ")}. Name one part`);
|
|
22
|
+
else err(`${at} names object "${id}", which is not in the scene${didYouMean(String(id), [...new Set([...ids, ...ids.flatMap(k => scene.objects[k].ancestors ?? []), ...Object.keys(scene.others ?? {})])])}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function refOk(spec, err, sk, at, ref) {
|
|
27
|
+
const [kind, rest] = String(ref).split(/:(.*)/s);
|
|
28
|
+
if (kind === "joint") return boneOk(err, sk, at, rest);
|
|
29
|
+
if (kind === "anchor") return spec.anchors?.[rest] ? true : err(`${at} "${ref}" names no anchor`);
|
|
30
|
+
if (kind === "prop") { const [p, pt] = (rest ?? "").split("."); return spec.props?.[p]?.points?.[pt] || pt === "origin" ? true : err(`${at} "${ref}" names no prop point`); }
|
|
31
|
+
err(`${at} "${ref}" must be joint:<Bone>, prop:<prop>.<point> or anchor:<name>`);
|
|
32
|
+
}
|