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,127 @@
|
|
|
1
|
+
// C4. The compiled motion as beats, for the agent (output.timeline) and the beats contact sheet.
|
|
2
|
+
// At most 12 entries, ordered by f[0]:
|
|
3
|
+
// { beat: skill name | "walk" | "keys", src: "use[i]" | "generators[i]" | "keys",
|
|
4
|
+
// f: [first, last] (inclusive, absolute frames),
|
|
5
|
+
// end: { root: [x,y,z], yaw, hand_l: [x,y,z], hand_r: [x,y,z], on_l: ref | null, on_r: ref | null },
|
|
6
|
+
// planted: { l: [[f0, f1], ...], r: [...] }, // at most 4 per foot
|
|
7
|
+
// events: [{ f, object, event: "grab" | "release" | "show" | "hide" }] }
|
|
8
|
+
// Metres to 3 decimals, degrees to 1. Beats come from the skills expandUse ran (used), the
|
|
9
|
+
// generators, and the runs of keys no skill or generator covers; a retime (out.timeMap) moves
|
|
10
|
+
// them with the keys. end is the pose at f[1]: root = the Hips joint, hand_* = the wrists,
|
|
11
|
+
// on_* = the "object:<id>" that hand holds there (object-pose.js inHandAt), else the
|
|
12
|
+
// "anchor:<a>" whose w_<a>_<k> > 0.5 (the largest). planted = the heel or the ball below
|
|
13
|
+
// QA_LIMITS.plantH over the ground (the floor or an object's top, qa/ground.js) and still (a foot on its ball, heel up, is planted), for 3 frames or more (the first, the last and the longest others
|
|
14
|
+
// when there are more than 4). Events: grab/release when an object enters or leaves a hand,
|
|
15
|
+
// show/hide when a visibility channel (a carry's or a switch's `visible`, or any *_vis channel)
|
|
16
|
+
// crosses 0.5; `object` is the object id (one event per object the channel shows), or the
|
|
17
|
+
// channel's name when no object owns it.
|
|
18
|
+
import { QA_LIMITS } from "./qa/checks.js";
|
|
19
|
+
import { groundSole } from "./qa/ground.js";
|
|
20
|
+
import { inHandAt } from "./object-pose.js";
|
|
21
|
+
|
|
22
|
+
const MAX_BEATS = 12, PER_FOOT = 4, MIN_PLANT = 3, STILL_Y = 0.0005;
|
|
23
|
+
const r3 = v => Math.round(v * 1000) / 1000, r1 = v => Math.round(v * 10) / 10;
|
|
24
|
+
const vec = p => p.map(r3);
|
|
25
|
+
|
|
26
|
+
/** The beats' frame ranges before their poses are read: [{ beat, src, f }]. */
|
|
27
|
+
function beatsOf(out, used, spec) {
|
|
28
|
+
const last = out.frameCount - 1, map = out.timeMap ?? (f => f);
|
|
29
|
+
const beats = used.map((u, i) => ({ beat: u.skill, src: `use[${i}]`, f: [u.f0, u.end ?? u.f0] }));
|
|
30
|
+
(spec.generators ?? []).forEach((g, i) => beats.push({ beat: g.type, src: `generators[${i}]`, f: [g.f0, g.f0 + 16 + (g.steps ?? 8) * (g.period ?? 20) + 18] }));
|
|
31
|
+
const covered = f => beats.some(b => f >= b.f[0] && f <= b.f[1]);
|
|
32
|
+
const loose = [...new Set((spec.keys ?? []).map(k => k.f))].filter(f => Number.isFinite(f) && !covered(f)).sort((a, b) => a - b);
|
|
33
|
+
let run = null;
|
|
34
|
+
for (const f of loose) {
|
|
35
|
+
if (run && !beats.some(b => b.src !== "keys" && b.f[1] > run.f[1] && b.f[0] < f)) run.f[1] = f;
|
|
36
|
+
else beats.push(run = { beat: "keys", src: "keys", f: [f, f] });
|
|
37
|
+
}
|
|
38
|
+
const clamp = f => Math.max(0, Math.min(last, Math.round(map(f))));
|
|
39
|
+
for (const b of beats) b.f = [clamp(b.f[0]), clamp(b.f[1])];
|
|
40
|
+
return beats.sort((a, b) => a.f[0] - b.f[0]).slice(0, MAX_BEATS);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** planted[side][f]: the heel or the ball low and still at absolute frame f (false outside the window). */
|
|
44
|
+
function plantedFlags(out) {
|
|
45
|
+
const n = out.frameCount, a = out.f0 ?? 0, flags = { l: new Uint8Array(n), r: new Uint8Array(n) };
|
|
46
|
+
for (const [k, S] of [["l", "Left"], ["r", "Right"]]) {
|
|
47
|
+
const sole = out.frames.map((fr, i) => (fr ? groundSole(out, i, S) : null));
|
|
48
|
+
sole.forEach((p, i) => {
|
|
49
|
+
const q = sole[i > 0 ? i - 1 : i + 1];
|
|
50
|
+
if (!p || !q) return;
|
|
51
|
+
const ok = ["heel", "ball"].some(key => p[key][1] < QA_LIMITS.plantH && Math.abs(p[key][1] - q[key][1]) < STILL_Y
|
|
52
|
+
&& Math.hypot(p[key][0] - q[key][0], p[key][2] - q[key][2]) <= QA_LIMITS.slide);
|
|
53
|
+
flags[k][a + i] = ok ? 1 : 0;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return flags;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function plantedIn(flags, [f0, f1]) {
|
|
60
|
+
const runs = [];
|
|
61
|
+
let start = null;
|
|
62
|
+
for (let f = f0; f <= f1 + 1; f++) {
|
|
63
|
+
const on = f <= f1 && flags[f] === 1;
|
|
64
|
+
if (on && start === null) start = f;
|
|
65
|
+
if (!on && start !== null) { if (f - start >= MIN_PLANT) runs.push([start, f - 1]); start = null; }
|
|
66
|
+
}
|
|
67
|
+
if (runs.length <= PER_FOOT) return runs;
|
|
68
|
+
const inner = runs.slice(1, -1).sort((x, y) => (y[1] - y[0]) - (x[1] - x[0]) || x[0] - y[0]).slice(0, PER_FOOT - 2);
|
|
69
|
+
return [runs[0], ...inner, runs[runs.length - 1]].sort((x, y) => x[0] - y[0]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Every grab/release/show/hide of the take, by absolute frame. */
|
|
73
|
+
function eventsOf(out, spec) {
|
|
74
|
+
const n = out.frameCount, events = [], objects = Object.entries(spec.objects ?? {});
|
|
75
|
+
for (const [id, o] of objects) for (let f = 1; f < n; f++) {
|
|
76
|
+
const was = inHandAt(o, f - 1), is = inHandAt(o, f);
|
|
77
|
+
if (is && !was) events.push({ f, object: id, event: "grab" });
|
|
78
|
+
if (was && !is) events.push({ f: f - 1, object: id, event: "release" });
|
|
79
|
+
}
|
|
80
|
+
// every object a channel shows (a carry's or a switch's visible) gets its own event
|
|
81
|
+
const owners = {};
|
|
82
|
+
for (const [id, o] of objects) if (typeof o?.visible === "string") (owners[o.visible] ??= []).push(id);
|
|
83
|
+
for (const ch of new Set([...Object.keys(owners), ...Object.keys(out.baked ?? {}).filter(c => c.endsWith("_vis"))])) {
|
|
84
|
+
const v = out.baked?.[ch];
|
|
85
|
+
if (!v) continue;
|
|
86
|
+
for (let f = 1; f < n; f++) {
|
|
87
|
+
const was = v[f - 1] > 0.5, is = v[f] > 0.5;
|
|
88
|
+
if (was !== is) for (const object of owners[ch] ?? [ch]) events.push({ f, object, event: is ? "show" : "hide" });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return events.sort((x, y) => x.f - y.f);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** "object:<id>" held by hand k at f, else the "anchor:<a>" with the largest weight > 0.5, else null. */
|
|
95
|
+
function onOf(spec, c, k, f) {
|
|
96
|
+
for (const [id, o] of Object.entries(spec.objects ?? {})) if (spec.props?.[o?.prop]?.hand === k && inHandAt(o, f)) return `object:${id}`;
|
|
97
|
+
let best = null, bw = 0.5;
|
|
98
|
+
for (const [ch, w] of Object.entries(c)) {
|
|
99
|
+
const m = /^w_(.+)_([lr])$/.exec(ch);
|
|
100
|
+
if (m && m[2] === k && w > bw) { best = m[1]; bw = w; }
|
|
101
|
+
}
|
|
102
|
+
return best && `anchor:${best}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function endOf(out, spec, f) {
|
|
106
|
+
const fr = out.frames[f - (out.f0 ?? 0)];
|
|
107
|
+
if (!fr) return null;
|
|
108
|
+
const { s, c } = fr, I = out.ctx.sk.index;
|
|
109
|
+
return { root: vec(s.P[I.Hips]), yaw: r1(c.yaw ?? 0), hand_l: vec(s.P[I.LeftHand]), hand_r: vec(s.P[I.RightHand]),
|
|
110
|
+
on_l: onOf(spec, c, "l", f), on_r: onOf(spec, c, "r", f) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* timelineOf(out, { used, spec }) -> entries
|
|
115
|
+
* @param {object} out compileMotion result
|
|
116
|
+
* @param {{ used: { skill: string, f0: number, end: number }[], spec: object }} ctx used = the
|
|
117
|
+
* skills expandUse ran, spec = the expanded spec
|
|
118
|
+
* @returns {object[]}
|
|
119
|
+
*/
|
|
120
|
+
export function timelineOf(out, { used, spec }) {
|
|
121
|
+
const beats = beatsOf(out, used ?? [], spec);
|
|
122
|
+
if (!beats.length) return [];
|
|
123
|
+
const flags = plantedFlags(out), events = eventsOf(out, spec);
|
|
124
|
+
return beats.map(b => ({ ...b, end: endOf(out, spec, b.f[1]),
|
|
125
|
+
planted: { l: plantedIn(flags.l, b.f), r: plantedIn(flags.r, b.f) },
|
|
126
|
+
events: events.filter(e => e.f >= b.f[0] && e.f <= b.f[1]) }));
|
|
127
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// C11 own views: the cameras of a verify_result contact_sheet with `views`. They are the
|
|
2
|
+
// agent's own cameras around the subject, never the user's shot camera. A view stays at one
|
|
3
|
+
// place for every frame of the sheet and frames what matters over those frames (the focus
|
|
4
|
+
// refs, else the whole character, plus any trail), so a small action fills the tile and
|
|
5
|
+
// motion reads as change inside one still frame. Pure over a compile result (compileSpec .out).
|
|
6
|
+
// front in front of the character, 10 degrees up
|
|
7
|
+
// side from the side the focus is on (the right when centred), 10 degrees up
|
|
8
|
+
// three-quarter between front and that side, 25 degrees up
|
|
9
|
+
// top straight down, the character's forward up the image
|
|
10
|
+
// depthViews adds a view when the asked ones would hide depth (see APART below).
|
|
11
|
+
import { refPoint } from "./refs.js";
|
|
12
|
+
import { objectBoxesAt } from "./object-pose.js";
|
|
13
|
+
import { resolveBone } from "./bones.js";
|
|
14
|
+
import { add, sub, scale, dot, cross, norm, apply, quatFromMat } from "./compiler/math.js";
|
|
15
|
+
|
|
16
|
+
export const VIEW_NAMES = ["front", "side", "three-quarter", "top"];
|
|
17
|
+
// FOV is the widest lens; a deep subject (a body seen from above) moves the camera back until
|
|
18
|
+
// its far side is at most DEPTH_RATIO times as far as its near side, and the lens narrows (down
|
|
19
|
+
// to MIN_FOV) to fill the tile again, so perspective stays mild.
|
|
20
|
+
const FOV = 35, MIN_FOV = 12, DEPTH_RATIO = 1.8, MARGIN = 1.12, PAD = 0.06, MIN_HALF = 0.15, MIN_DEPTH = 0.35, UP = [0, 1, 0];
|
|
21
|
+
const ELEVATION = { front: 10, side: 10, "three-quarter": 25 };
|
|
22
|
+
const BARE_OBJECT = /^object:(.+?)(\.(?:center|top|base))?$/s;
|
|
23
|
+
const rad = d => (d * Math.PI) / 180;
|
|
24
|
+
const mean = points => scale(points.reduce(add, [0, 0, 0]), 1 / Math.max(1, points.length));
|
|
25
|
+
|
|
26
|
+
const jointsAt = (out, f) => out.frames?.[f - (out.f0 ?? 0)]?.s.P ?? [];
|
|
27
|
+
const corners = b => [-1, 1].flatMap(x => [0, 2].flatMap(y => [-1, 1].map(z => add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]])))));
|
|
28
|
+
|
|
29
|
+
/** World points of a ref at frame f: a bare object:<id> is its part boxes' corners, any other ref its point. */
|
|
30
|
+
function refPoints(out, f, ref) {
|
|
31
|
+
const bare = BARE_OBJECT.exec(ref);
|
|
32
|
+
if (bare && !bare[2]) return (objectBoxesAt(out, bare[1], f) ?? []).flatMap(corners);
|
|
33
|
+
const p = refPoint(out, f, ref);
|
|
34
|
+
return p ? [p] : [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The character's horizontal left and forward over the frames (the hip line, summed). */
|
|
38
|
+
function facingOf(out, frames) {
|
|
39
|
+
const sk = out.ctx.sk, l = sk.index[resolveBone(sk, "LeftUpLeg").bone], r = sk.index[resolveBone(sk, "RightUpLeg").bone];
|
|
40
|
+
let v = [0, 0, 0];
|
|
41
|
+
for (const f of frames) { const P = jointsAt(out, f); if (P.length) { const d = sub(P[l], P[r]); v = add(v, [d[0], 0, d[2]]); } }
|
|
42
|
+
const left = Math.hypot(v[0], v[2]) > 1e-6 ? norm(v) : [1, 0, 0];
|
|
43
|
+
return { left, forward: cross(left, UP) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Path of a ref over absolute frames lo..hi: [{ f, p }] where it is known. */
|
|
47
|
+
export function trailPoints(out, ref, [lo, hi]) {
|
|
48
|
+
const points = [];
|
|
49
|
+
for (let f = lo; f <= hi; f++) { const p = refPoint(out, f, ref); if (p) points.push({ f, p }); }
|
|
50
|
+
return points;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A camera at `back` (unit, target to eye) whose frustum holds every point (with PAD) at MARGIN. */
|
|
54
|
+
function fit(name, points, back, upHint, aspect) {
|
|
55
|
+
const right = norm(cross(upHint, back)), up = cross(back, right);
|
|
56
|
+
const c0 = mean(points), rel = points.map(p => sub(p, c0)), xs = rel.map(q => dot(q, right)), ys = rel.map(q => dot(q, up)), zs = rel.map(q => dot(q, back));
|
|
57
|
+
const cx = (Math.min(...xs) + Math.max(...xs)) / 2, cy = (Math.min(...ys) + Math.max(...ys)) / 2;
|
|
58
|
+
const target = add(c0, add(scale(right, cx), scale(up, cy)));
|
|
59
|
+
// The vertical half-angle tangent each point needs from distance d (x through the aspect).
|
|
60
|
+
const need = d => Math.max(...rel.map((q, i) => MARGIN * Math.max(Math.max(Math.abs(xs[i] - cx) + PAD, MIN_HALF) / aspect, Math.max(Math.abs(ys[i] - cy) + PAD, MIN_HALF)) / (d - zs[i])));
|
|
61
|
+
const near = Math.max(...zs), far = Math.min(...zs);
|
|
62
|
+
let d = Math.max(near + MIN_DEPTH, near + (near - far) / (DEPTH_RATIO - 1)), tanV = need(d);
|
|
63
|
+
if (tanV > Math.tan(rad(FOV / 2))) {
|
|
64
|
+
// Too close for the widest lens: back off at FOV until every point fits.
|
|
65
|
+
tanV = Math.tan(rad(FOV / 2));
|
|
66
|
+
d = Math.max(d, ...rel.map((q, i) => zs[i] + MARGIN * Math.max(Math.max(Math.abs(xs[i] - cx) + PAD, MIN_HALF) / aspect, Math.max(Math.abs(ys[i] - cy) + PAD, MIN_HALF)) / tanV));
|
|
67
|
+
} else tanV = Math.max(tanV, Math.tan(rad(MIN_FOV / 2)));
|
|
68
|
+
const eye = add(target, scale(back, d)), fovDeg = Math.round((2 * Math.atan(tanV) * 180 / Math.PI) * 100) / 100;
|
|
69
|
+
tanV = Math.tan(rad(fovDeg / 2));
|
|
70
|
+
const quaternion = quatFromMat([0, 1, 2].map(i => [right[i], up[i], back[i]]));
|
|
71
|
+
return { name, eye, target, quaternion, fovDeg, aspect, right, up, back, tanV, tanH: tanV * aspect };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* One camera per view name over `frames`: framed on the focus refs' points at those frames
|
|
76
|
+
* (none, or none known there: every joint of the character) and every trail point. Answers
|
|
77
|
+
* [{ name, eye, target, quaternion [x,y,z,w], fovDeg, aspect, right, up, back, tanV, tanH }].
|
|
78
|
+
* Deterministic: the same compile, frames and arguments give the same cameras.
|
|
79
|
+
*/
|
|
80
|
+
export function viewCameras(out, { views, frames, focus = [], trails = [], aspect = 1 }) {
|
|
81
|
+
let points = frames.flatMap(f => focus.flatMap(ref => refPoints(out, f, ref)));
|
|
82
|
+
const body = frames.flatMap(f => jointsAt(out, f));
|
|
83
|
+
if (!points.length) points = body;
|
|
84
|
+
points = [...points, ...trails.flatMap(t => t.points.map(q => q.p))];
|
|
85
|
+
if (!points.length) return [];
|
|
86
|
+
const { left, forward } = facingOf(out, frames), hips = mean(frames.map(f => jointsAt(out, f)[out.ctx.sk.index.Hips]).filter(Boolean));
|
|
87
|
+
const side = focus.length && dot(sub(mean(points), hips), left) > 0.02 ? left : scale(left, -1);
|
|
88
|
+
return views.map(name => {
|
|
89
|
+
if (name === "top") return fit(name, points, UP, forward, aspect);
|
|
90
|
+
const e = rad(ELEVATION[name]), level = name === "front" ? forward : name === "side" ? side : norm(add(forward, side));
|
|
91
|
+
return fit(name, points, add(scale(level, Math.cos(e)), scale(UP, Math.sin(e))), UP, aspect);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A picture hides distance along its own line of sight, so a sheet holds two views at least APART
|
|
96
|
+
// degrees apart. Front and three-quarter (45 degrees apart) look along nearly one line: the
|
|
97
|
+
// guitarist's right hand 20 cm in front of the guitar (session df388a9b) showed in neither.
|
|
98
|
+
// LOOK is each view's nominal direction: azimuth from the character's forward, elevation.
|
|
99
|
+
const LOOK = { front: [0, 10], side: [90, 10], "three-quarter": [45, 25], top: [0, 90] }, APART = 60;
|
|
100
|
+
const lookDir = name => { const [a, e] = LOOK[name].map(rad); return [Math.cos(e) * Math.sin(a), Math.sin(e), Math.cos(e) * Math.cos(a)]; };
|
|
101
|
+
const apartDeg = (a, b) => Math.round(Math.acos(Math.min(1, dot(lookDir(a), lookDir(b)))) * 180 / Math.PI);
|
|
102
|
+
const spread = views => views.some((a, i) => views.slice(i + 1).some(b => apartDeg(a, b) >= APART));
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The views a sheet renders: the asked ones when two of them are APART degrees apart, else the
|
|
106
|
+
* asked ones plus the first of side, front and top that makes it so. Answers { views, added }:
|
|
107
|
+
* added is null, or { view, reason } for visualRefs.
|
|
108
|
+
*/
|
|
109
|
+
export function depthViews(views) {
|
|
110
|
+
if (spread(views)) return { views, added: null };
|
|
111
|
+
const view = ["side", "front", "top"].find(name => !views.includes(name) && spread([...views, name]));
|
|
112
|
+
const line = views.length > 1 ? `${views.join(" and ")} look along nearly one line (${apartDeg(views[0], views[1])} degrees apart)` : `${views[0]} alone looks along one line`;
|
|
113
|
+
return { views: [...views, view], added: { view, reason: `${line}, so a gap along it (toward or away from the camera) does not show; ${view} looks across it` } };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Pixel [x, y] of world point p in a width x height image of cam, y counted from the bottom row; null behind it. */
|
|
117
|
+
export function projectPoint(cam, p, width, height) {
|
|
118
|
+
const rel = sub(p, cam.eye), depth = -dot(rel, cam.back);
|
|
119
|
+
if (depth < 0.05) return null;
|
|
120
|
+
return [((dot(rel, cam.right) / (depth * cam.tanH) + 1) / 2) * width, ((dot(rel, cam.up) / (depth * cam.tanV) + 1) / 2) * height];
|
|
121
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// walkTo { to, arrive }: the engine picks the cadence instead of the agent sweeping period and step by
|
|
2
|
+
// hand. A use[i] walkTo with arrive (the frame the walk should end by, feet together: f0 + 16 + steps *
|
|
3
|
+
// period + 18) and none of steps, period, step, swing gets them planned here, before the skills expand:
|
|
4
|
+
// candidates every steps n and period p (frames per step) whose walk ends by arrive, the strides
|
|
5
|
+
// covering the distance from where the feet are at f0 to `to` (the first stride ~0.8 of
|
|
6
|
+
// the others, as walkTo's defaults), a stride at most STRIDE_MAX, the foot in the air
|
|
7
|
+
// SWING of the period;
|
|
8
|
+
// order a person's walk ratio first (stride x cadence: stride m * period frames ~ WALK_RATIO, the
|
|
9
|
+
// default 0.5 m every 20 frames), then using the time given (a walk that ends long before
|
|
10
|
+
// arrive stands still the rest), and no stroll slower than SLOW m/s;
|
|
11
|
+
// checked each candidate is compiled on a probe spec (the keys and skills up to the walk, the walk,
|
|
12
|
+
// nothing after it; no goals, checks or moving objects) and the first with no POP, REACH,
|
|
13
|
+
// SLIDE, FLOAT, FLIP or KNEE issue between f0 and its end wins (at most TRIALS compiles): the
|
|
14
|
+
// best on-time ones, then the top few again with a longer swing, a longer first stride or a
|
|
15
|
+
// lower lift (a quick walk often needs one of them), then the late ones, earliest first.
|
|
16
|
+
// When no walk ends by arrive (the distance is too far for the frames), the earliest clean one is
|
|
17
|
+
// taken and the note says the speed the request needs and the frame it ends. The spec keeps arrive (a
|
|
18
|
+
// recompile plans the same walk again, cached); observations.planned reads what was chosen.
|
|
19
|
+
import { expandUse } from "./skills/index.js";
|
|
20
|
+
import { stateAt } from "./compiler/walk.js";
|
|
21
|
+
|
|
22
|
+
const WALK_RATIO = 9.7, STRIDE_MIN = 0.25, STRIDE_MAX = 0.75, SWING = 0.88, SLOW = 0.5, TRIALS = 22, LATE = 4, FPS = 24;
|
|
23
|
+
const PERIODS = Array.from({ length: 16 }, (_, i) => 9 + i);
|
|
24
|
+
const PLANNED = ["steps", "period", "step", "swing"];
|
|
25
|
+
const WALK_KINDS = new Set(["POP", "REACH", "SLIDE", "FLOAT", "FLIP", "KNEE"]);
|
|
26
|
+
const cache = new Map();
|
|
27
|
+
const r2 = x => Math.round(x * 100) / 100;
|
|
28
|
+
|
|
29
|
+
/** The walkTo uses that plan their cadence: [{ i, u }]. */
|
|
30
|
+
const toPlan = spec => (Array.isArray(spec?.use) ? spec.use : []).map((u, i) => ({ i, u }))
|
|
31
|
+
.filter(({ u }) => u?.skill === "walkTo" && Number.isInteger(u.args?.arrive) && Array.isArray(u.args?.to) && !PLANNED.some(k => u.args[k] !== undefined)
|
|
32
|
+
&& Object.values(u.args).every(v => !(v && typeof v === "object" && !Array.isArray(v))));
|
|
33
|
+
|
|
34
|
+
/** The strides of an n-step walk over d m: { first, step } or null when no stride fits. */
|
|
35
|
+
function strides(d, n) {
|
|
36
|
+
if (n === 2) return d >= STRIDE_MIN && d <= STRIDE_MAX ? { first: d, step: d } : null;
|
|
37
|
+
const first = Math.min(0.62, Math.max(0.3, (0.8 * d) / (n - 2 + 0.8))), step = (d - first) / (n - 2);
|
|
38
|
+
return step >= STRIDE_MIN && step <= STRIDE_MAX ? { first, step } : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Candidates for a walk of d m from f0, best first: on time (ending by arrive) by their score, then late ones by their end. */
|
|
42
|
+
function candidates(d, f0, arrive) {
|
|
43
|
+
const all = [];
|
|
44
|
+
for (const period of PERIODS) for (let n = 2; n <= 40; n++) {
|
|
45
|
+
const s = strides(d, n);
|
|
46
|
+
if (!s) continue;
|
|
47
|
+
const end = f0 + 16 + n * period + 18, v = (s.step * FPS) / period;
|
|
48
|
+
const ratio = Math.abs(Math.log((s.step * period) / WALK_RATIO)), idle = Math.max(0, arrive - end) / Math.max(1, arrive - f0);
|
|
49
|
+
all.push({ args: { steps: n, period, swing: Math.round(SWING * period), step: r2(s.step), firstStep: r2(s.first) }, end, v, late: end > arrive,
|
|
50
|
+
score: ratio + 1.5 * idle + Math.max(0, SLOW - v) * 4 });
|
|
51
|
+
}
|
|
52
|
+
const onTime = all.filter(c => !c.late).sort((a, b) => a.score - b.score), late = all.filter(c => c.late).sort((a, b) => a.end - b.end || a.score - b.score);
|
|
53
|
+
return { onTime, late };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A quick walk's other shapes: a longer swing, with a longer first stride, a lower lift, or both. */
|
|
57
|
+
function variants(c, d) {
|
|
58
|
+
const { steps: n, period } = c.args, swing = Math.round(0.8 * period), long = n > 2 && c.args.firstStep < 0.62 ? { firstStep: 0.62, step: r2((d - 0.62) / (n - 2)) } : null;
|
|
59
|
+
return [{}, long, { lift: 0.06 }, long && { ...long, lift: 0.06 }].filter(Boolean).map(x => ({ ...c, args: { ...c.args, swing, ...x } }))
|
|
60
|
+
.filter(v => v.args.step >= STRIDE_MIN && v.args.step <= STRIDE_MAX);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Issues in the walk's own frames (the kinds a cadence causes) of a compiled probe: a count. */
|
|
64
|
+
const walkIssues = (qa, f0, end) => qa.groups.filter(g => WALK_KINDS.has(g.kind) && g.last >= f0 && g.first <= end).reduce((n, g) => n + g.count, 0);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* planWalks(spec, { sk, scene, trial }) -> [{ i, args, note }] for every walkTo with arrive (applyWalkPlans puts
|
|
68
|
+
* the args in). trial(spec) -> QA of a compile of that spec (pipeline.js), or throws.
|
|
69
|
+
*/
|
|
70
|
+
export function planWalks(spec, { sk, scene, trial }) {
|
|
71
|
+
const plans = [];
|
|
72
|
+
for (const { i, u } of toPlan(spec)) {
|
|
73
|
+
const key = JSON.stringify([spec.keys, spec.use.slice(0, i + 1), spec.anchors, spec.props, spec.fits, spec.fix, scene?.objects ?? null]);
|
|
74
|
+
if (cache.has(key)) { plans.push(cache.get(key)); continue; }
|
|
75
|
+
const before = spec.use.slice(0, i).filter(x => x.f0 < u.f0);
|
|
76
|
+
let state, ends = [];
|
|
77
|
+
try {
|
|
78
|
+
const { spec: exp, used } = expandUse({ ...spec, frames: 20000, use: before }, { sk, scene, lenient: true });
|
|
79
|
+
state = stateAt(exp.keys ?? [], u.f0); ends = used.map(x => x.end ?? 0);
|
|
80
|
+
} catch { state = null; }
|
|
81
|
+
if (!state?.foot_l || !state?.foot_r) continue;
|
|
82
|
+
const c0 = [(state.foot_l[0] + state.foot_r[0]) / 2, (state.foot_l[2] + state.foot_r[2]) / 2], to = u.args.to, d = Math.hypot(to[0] - c0[0], to[to.length - 1] - c0[1]);
|
|
83
|
+
const { onTime, late } = candidates(d, u.f0, u.args.arrive);
|
|
84
|
+
const probe = c => {
|
|
85
|
+
const frames = Math.max(c.end, ...ends) + 1;
|
|
86
|
+
return { schema: spec.schema, fps: spec.fps ?? FPS, frames, keys: (spec.keys ?? []).filter(k => k.f < frames), use: [...before, { ...u, args: { ...u.args, ...c.args } }],
|
|
87
|
+
...(spec.anchors ? { anchors: spec.anchors } : {}), ...(spec.props ? { props: spec.props } : {}), ...(spec.fits ? { fits: spec.fits } : {}), ...(spec.fix !== undefined ? { fix: spec.fix } : {}) };
|
|
88
|
+
};
|
|
89
|
+
let chosen = null, tried = 0, best = null, onTried = 0;
|
|
90
|
+
const order = [...onTime.slice(0, 6), ...onTime.slice(0, 3).flatMap(c => variants(c, d))].slice(0, TRIALS - LATE);
|
|
91
|
+
for (const c of [...order, ...late.slice(0, TRIALS - order.length)]) {
|
|
92
|
+
let issues;
|
|
93
|
+
try { issues = walkIssues(trial(probe(c)), u.f0, c.end); } catch { issues = Infinity; }
|
|
94
|
+
tried++; onTried += c.late ? 0 : 1;
|
|
95
|
+
if (!best || issues < best.issues) best = { c, issues };
|
|
96
|
+
if (issues === 0) { chosen = c; break; }
|
|
97
|
+
}
|
|
98
|
+
chosen ??= best?.issues < Infinity ? best.c : onTime[0] ?? late[0];
|
|
99
|
+
if (!chosen) continue;
|
|
100
|
+
const a = chosen.args, A = u.args.arrive;
|
|
101
|
+
const what = `use[${i}] walkTo arrive f${A}: steps ${a.steps}, period ${a.period}, swing ${a.swing}, step ${a.step} m, firstStep ${a.firstStep} m${a.lift ? `, lift ${a.lift} m` : ""} (${r2(chosen.v)} m/s), ends f${chosen.end}`;
|
|
102
|
+
const why = onTime.length ? `the ${onTried} walks tried that end by f${A} pop or miss a foot`
|
|
103
|
+
: `even the quickest walk over ${r2(d)} m (a step every ${PERIODS[0]} frames, strides up to ${STRIDE_MAX} m, 34 frames to start and stop) ends f${late[0]?.end}`;
|
|
104
|
+
const late1 = chosen.late ? `; f${A} is too soon: ${why}; give arrive >= f${chosen.end} or a shorter distance` : "";
|
|
105
|
+
const dirty = best && best.c === chosen && best.issues > 0 ? `; no candidate walked clean (${best.issues} issues in its frames)` : "";
|
|
106
|
+
const plan = { i, args: a, end: chosen.end, note: `${what}${late1}${dirty} (${tried} checked)` };
|
|
107
|
+
if (cache.size >= 64) cache.delete(cache.keys().next().value);
|
|
108
|
+
cache.set(key, plan);
|
|
109
|
+
plans.push(plan);
|
|
110
|
+
}
|
|
111
|
+
return plans;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The spec with every planned walk's args filled in (arrive leaves them: walkTo ignores it); the spec itself when there is none. */
|
|
115
|
+
export function applyWalkPlans(spec, plans) {
|
|
116
|
+
if (!plans.length) return spec;
|
|
117
|
+
const use = spec.use.map((u, i) => { const p = plans.find(x => x.i === i); return p && u?.skill === "walkTo" ? { ...u, args: { ...u.args, ...p.args } } : u; });
|
|
118
|
+
return { ...spec, use };
|
|
119
|
+
}
|
package/src/object-catalog.jsx
CHANGED
|
@@ -5,6 +5,8 @@ import { EMPTY_KIND, OBJECT_MENU_ENTRIES } from "./scene-objects.js";
|
|
|
5
5
|
const GROUP_LABELS_KO = {
|
|
6
6
|
Primitives: "기본 도형",
|
|
7
7
|
"Set pieces": "세트 소품",
|
|
8
|
+
Lights: "조명",
|
|
9
|
+
Effects: "효과",
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const OBJECT_LABELS_KO = {
|
|
@@ -18,6 +20,10 @@ const OBJECT_LABELS_KO = {
|
|
|
18
20
|
Chair: "의자",
|
|
19
21
|
Car: "자동차",
|
|
20
22
|
"Plane (aircraft)": "비행기",
|
|
23
|
+
"Point light": "점 조명",
|
|
24
|
+
"Spot light": "스포트 조명",
|
|
25
|
+
Light: "조명",
|
|
26
|
+
Effect: "효과",
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
function displayObjectGroupName(name) {
|
|
@@ -67,7 +73,7 @@ export function CatalogueEntries({ onPick }) {
|
|
|
67
73
|
<span className={`add-object-swatch ${entry.kind}`} style={{ background: entry.color }} aria-hidden="true" />
|
|
68
74
|
<span>{displayObjectLabel(entry.label)}</span>
|
|
69
75
|
{/* An empty has no size to quote: say what it is instead of "0 × 0 m". */}
|
|
70
|
-
<small>{entry.kind === EMPTY_KIND ? ko("node only", "노드 전용") : `${entry.footprint.width} × ${entry.footprint.depth} m`}</small>
|
|
76
|
+
<small>{entry.light ? ko("practical light", "실제 조명") : entry.kind === EMPTY_KIND ? ko("node only", "노드 전용") : entry.kind === "effect" ? ko("smoke, fire, rain…", "연기, 불, 비…") : `${entry.footprint.width} × ${entry.footprint.depth} m`}</small>
|
|
71
77
|
</button>
|
|
72
78
|
))}
|
|
73
79
|
</div>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inspector's object colours: the blockout palette, what a typed colour
|
|
3
|
+
* means, and the per-browser memory of hand-mixed tints. Pure, so the record
|
|
4
|
+
* normalizers and the light/glow fields read the same hex rules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Blockout grey: what a primitive is until someone says otherwise. */
|
|
8
|
+
export const GREY_BOX = "#c2c6c8";
|
|
9
|
+
|
|
10
|
+
/** Blockout greys first, then the clay accents, then the blocking chromatics,
|
|
11
|
+
* for re-tinting from the inspector. The greys lead because a blockout starts
|
|
12
|
+
* grey; the saturated four exist so a shot can say "the red ball, the blue
|
|
13
|
+
* box" at a glance — the inspector had no way to say that at all (#88). They
|
|
14
|
+
* are muted off pure RGB so they still read as clay under the set's lighting.
|
|
15
|
+
* Any other colour is authored through the inspector's free colour input. */
|
|
16
|
+
export const OBJECT_COLORS = [
|
|
17
|
+
"#e2e5e6",
|
|
18
|
+
GREY_BOX,
|
|
19
|
+
"#9aa1a5",
|
|
20
|
+
"#767d81",
|
|
21
|
+
"#d9b18c",
|
|
22
|
+
"#8fae9b",
|
|
23
|
+
"#d94a4a",
|
|
24
|
+
"#4a7bd9",
|
|
25
|
+
"#e2c04a",
|
|
26
|
+
"#4fa86a",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** How many hand-mixed colours the inspector remembers. Six is one palette
|
|
30
|
+
* row: enough to keep a scene's custom tints reachable, short enough that the
|
|
31
|
+
* popover never turns into a second, unruly palette. */
|
|
32
|
+
const RECENT_OBJECT_COLORS_MAX = 6;
|
|
33
|
+
/** Session memory of hand-mixed tints; versioned like every other key here. */
|
|
34
|
+
const RECENT_OBJECT_COLORS_KEY = "cozyclay.objectColors.recent";
|
|
35
|
+
|
|
36
|
+
const HEX_SHORT = /^#?([0-9a-f]{3})$/i;
|
|
37
|
+
const HEX_LONG = /^#?([0-9a-f]{6})$/i;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What a colour input typed by a human means, or null if it means nothing.
|
|
41
|
+
*
|
|
42
|
+
* The hex field accepts what people actually type — `#f36`, `ff3366`, upper
|
|
43
|
+
* case, stray spaces — and folds it to the one form the record stores, so a
|
|
44
|
+
* colour typed by hand and the same colour picked from the native swatch are
|
|
45
|
+
* byte-identical (and therefore compare equal against the palette). Anything
|
|
46
|
+
* else returns null rather than throwing or guessing: a half-typed `#12` is a
|
|
47
|
+
* keystroke on the way somewhere, not an instruction to repaint the prop.
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeObjectColor(value) {
|
|
50
|
+
if (typeof value !== "string") return null;
|
|
51
|
+
const text = value.trim();
|
|
52
|
+
const short = HEX_SHORT.exec(text);
|
|
53
|
+
if (short) return `#${short[1].toLowerCase().replace(/./g, (digit) => digit + digit)}`;
|
|
54
|
+
const long = HEX_LONG.exec(text);
|
|
55
|
+
return long ? `#${long[1].toLowerCase()}` : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The recent-colours list after mixing `hex`: newest first, no duplicates,
|
|
60
|
+
* capped, presets excluded.
|
|
61
|
+
*
|
|
62
|
+
* Pure so the list can be proved without a browser, and so a corrupt stored
|
|
63
|
+
* list is repaired on the way through instead of painting a swatch with junk.
|
|
64
|
+
* Presets are skipped because they are already one row up — remembering them
|
|
65
|
+
* would push the author's own colours off the end with colours they never
|
|
66
|
+
* mixed. Returns the SAME array when nothing changes, so a re-pick of an
|
|
67
|
+
* unchanged colour cannot trigger a storage write or a re-render.
|
|
68
|
+
*/
|
|
69
|
+
export function rememberObjectColor(list, hex) {
|
|
70
|
+
const clean = Array.isArray(list)
|
|
71
|
+
? list.map(normalizeObjectColor).filter((color, index, all) => color && all.indexOf(color) === index && !OBJECT_COLORS.includes(color))
|
|
72
|
+
: [];
|
|
73
|
+
const color = normalizeObjectColor(hex);
|
|
74
|
+
const next = color && !OBJECT_COLORS.includes(color) ? [color, ...clean.filter((entry) => entry !== color)] : clean;
|
|
75
|
+
const capped = next.slice(0, RECENT_OBJECT_COLORS_MAX);
|
|
76
|
+
const unchanged = Array.isArray(list) && capped.length === list.length && capped.every((entry, index) => entry === list[index]);
|
|
77
|
+
return unchanged ? list : capped;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Recent colours as last left by this browser; storage can be unavailable
|
|
81
|
+
* (private mode), and a corrupt body must not take the inspector down with
|
|
82
|
+
* it — either way the palette simply starts without a memory. */
|
|
83
|
+
export function readStoredObjectColors(storage) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(storage?.getItem(RECENT_OBJECT_COLORS_KEY) ?? "[]");
|
|
86
|
+
return rememberObjectColor(Array.isArray(parsed) ? parsed : [], null);
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function writeStoredObjectColors(storage, list) {
|
|
93
|
+
try {
|
|
94
|
+
storage?.setItem(RECENT_OBJECT_COLORS_KEY, JSON.stringify(list));
|
|
95
|
+
} catch {
|
|
96
|
+
// Storage can be unavailable (private mode); the colours simply stop persisting.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { objectSize } from "./scene-objects.js";
|
|
3
|
+
|
|
4
|
+
// The ObjectGizmo's fixed parts: handle layout, sizes, colours and the two
|
|
5
|
+
// placement rules (where the gizmo stands, and which way its rings face).
|
|
6
|
+
export const AXES = [
|
|
7
|
+
{ axis: "x", dir: new THREE.Vector3(1, 0, 0), color: "#ff5340" },
|
|
8
|
+
{ axis: "y", dir: new THREE.Vector3(0, 1, 0), color: "#54e05c" },
|
|
9
|
+
{ axis: "z", dir: new THREE.Vector3(0, 0, 1), color: "#3d8bff" },
|
|
10
|
+
];
|
|
11
|
+
/** the same three directions, addressable by the record's axis key */
|
|
12
|
+
export const AXIS_VECTORS = { x: AXES[0].dir, y: AXES[1].dir, z: AXES[2].dir };
|
|
13
|
+
/** Move-tool plane handles. Unity colours each square after the axis it LOCKS,
|
|
14
|
+
* so the blue square (z locked) slides in xy. `normal` is the pinned axis. */
|
|
15
|
+
export const PLANES = [
|
|
16
|
+
{ id: "xz", axes: ["x", "z"], normal: "y", color: "#54e05c" },
|
|
17
|
+
{ id: "xy", axes: ["x", "y"], normal: "z", color: "#3d8bff" },
|
|
18
|
+
{ id: "yz", axes: ["y", "z"], normal: "x", color: "#ff5340" },
|
|
19
|
+
];
|
|
20
|
+
export const PLANE_OFFSET = 0.26; // where the square sits along each of its two axes
|
|
21
|
+
export const PLANE_SIZE = 0.2;
|
|
22
|
+
export const ARROW_LEN = 0.62;
|
|
23
|
+
export const SHAFT_R = 0.016;
|
|
24
|
+
export const TIP_R = 0.05;
|
|
25
|
+
export const TIP_LEN = 0.14;
|
|
26
|
+
export const RING_R = 0.52;
|
|
27
|
+
/**
|
|
28
|
+
* The axis rings are drawn as two half-tori instead of one full torus. A full
|
|
29
|
+
* ring is misleading: the half facing away from the camera is pixel-identical
|
|
30
|
+
* to the near half, so a drag started on the far side reads backwards.
|
|
31
|
+
* Depth-sorting the ring against the object would only help when the object
|
|
32
|
+
* is big enough to occlude the far half — the gizmo sits at the object's
|
|
33
|
+
* centre and the ring is only 0.52 m across, so for the small props a
|
|
34
|
+
* blocking tool is full of, the far half projects outside the silhouette and
|
|
35
|
+
* stays ambiguous. Fading it instead (FAR_HALF_ALPHA vs the near half's
|
|
36
|
+
* 0.95) is unambiguous at every object size and camera angle. Only the drawn
|
|
37
|
+
* halves fade — the pick proxy stays one full invisible torus per ring.
|
|
38
|
+
*/
|
|
39
|
+
export const FAR_HALF_ALPHA = 0.28;
|
|
40
|
+
/** the outer screen-space ring: larger than the axis rings (RING_R) so it
|
|
41
|
+
* encloses them from every angle, and far enough out that its pick band
|
|
42
|
+
* [0.61, 0.75] clears the axis rings' [0.45, 0.59] — the two can never fight
|
|
43
|
+
* for a grab. (§3.3: "a larger circle enclosing the sphere, drawn flat to
|
|
44
|
+
* the viewer" — rotates about the view axis.) */
|
|
45
|
+
export const SCREEN_RING_R = 0.68;
|
|
46
|
+
/** Unity's yellow for the handle being dragged — and, after release, for the
|
|
47
|
+
* last-dragged one, until the selection or the tool changes (§3.1). */
|
|
48
|
+
export const ACTIVE_COLOR = "#ffd23d";
|
|
49
|
+
export const RING_TUBE = 0.022;
|
|
50
|
+
export const BOX_SIZE = 0.09; // scale-mode axis knobs
|
|
51
|
+
export const CENTRE_BOX = 0.11; // uniform-scale knob at the pivot
|
|
52
|
+
/** Pick proxies. A drawn 1.6 cm shaft is ~3 screen pixels — nothing anyone can
|
|
53
|
+
* reliably grab — so hit testing runs against invisible fat volumes around each
|
|
54
|
+
* arrow, ring and knob, the way every DCC gizmo does it. */
|
|
55
|
+
export const PICK_SHAFT_R = 0.09;
|
|
56
|
+
export const PICK_TUBE_R = 0.07;
|
|
57
|
+
export const PICK_CENTRE_R = 0.16;
|
|
58
|
+
/** metres of gizmo per metre of camera distance: keeps it a constant size on
|
|
59
|
+
* screen whether the object is at the lens or across the room */
|
|
60
|
+
export const SCREEN_SCALE = 0.16;
|
|
61
|
+
|
|
62
|
+
/** the object's own centre height, where the gizmo sits */
|
|
63
|
+
function gizmoHeight(object) {
|
|
64
|
+
return (object.y ?? 0) + Math.max(objectSize(object).height / 2, 0.25);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** the handles-Map key a pick entry was registered under — the same
|
|
68
|
+
* expression __gizmoPick reports, so highlights and QA hooks agree on handle
|
|
69
|
+
* identity */
|
|
70
|
+
export function handleKey(entry) {
|
|
71
|
+
if (entry.corner) return `corner:${entry.corner.id}`;
|
|
72
|
+
return entry.axis ?? (entry.plane ? `plane:${entry.plane.id}` : "centre");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The four corners of a cutout card, in the card's own local space.
|
|
77
|
+
*
|
|
78
|
+
* A standee is a picture, and a picture is resized by its corners — grabbing
|
|
79
|
+
* one and pulling is the gesture everybody already has for "make this bigger",
|
|
80
|
+
* and unlike an axis knob it says which way is bigger without reading a label.
|
|
81
|
+
* `sx`/`sy` are which way each corner lies from the centre, so the drag can
|
|
82
|
+
* measure travel along the diagonal that corner actually points down.
|
|
83
|
+
*/
|
|
84
|
+
export const CARD_CORNERS = [
|
|
85
|
+
{ id: "tr", sx: 1, sy: 1 },
|
|
86
|
+
{ id: "tl", sx: -1, sy: 1 },
|
|
87
|
+
{ id: "br", sx: 1, sy: -1 },
|
|
88
|
+
{ id: "bl", sx: -1, sy: -1 },
|
|
89
|
+
];
|
|
90
|
+
export const CORNER_BOX = 0.085;
|
|
91
|
+
export const PICK_CORNER_R = 0.13;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Screen-space ring result: the object rotated `deltaDeg` about the world
|
|
95
|
+
* `viewAxis`, written back as all three Euler channels in one patch. The
|
|
96
|
+
* stored channels are Euler degrees per world axis, so the drag-start
|
|
97
|
+
* orientation is composed with the delta in quaternion space and decomposed
|
|
98
|
+
* again in the same order the renderer uses — exact for any start
|
|
99
|
+
* orientation, where a single-channel approximation would drift. Snap
|
|
100
|
+
* quantizes the accumulated delta to the same 5° grid the axis rings snap
|
|
101
|
+
* their start+delta to (start values are always on the grid themselves).
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
// Path authoring intersects presses with the set floor, exactly where a
|
|
105
|
+
// dropped pin will live.
|
|
106
|
+
export const GROUND = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
|
107
|
+
/** Where the gizmo stands: on the pose a take draws the object at (`held.pose`, held-object-edit.jsx), else at the authored centre. */
|
|
108
|
+
export function gizmoOrigin(object, held, out) {
|
|
109
|
+
return held?.pose ? out.fromArray(held.pose.centre) : out.set(object.x, gizmoHeight(object), object.z);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The rotation the handles take: the drawn object's own axes while a held-object drag edits it in rotate mode, else none (world axes). */
|
|
113
|
+
export function gizmoTurn(held, mode) {
|
|
114
|
+
return mode === "rotate" && held?.editable ? held.pose.quat : null;
|
|
115
|
+
}
|