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
package/src/object-gizmo.jsx
CHANGED
|
@@ -3,15 +3,11 @@ import { useFrame, useThree } from "@react-three/fiber";
|
|
|
3
3
|
import * as THREE from "three";
|
|
4
4
|
import { GIZMO_LAYER, SHOT_ASPECT, fitAspect } from "./dualview.jsx";
|
|
5
5
|
import { HANDLE_PROXY_FLAG, claimsPress } from "./gizmo-claim.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
screenRotatePatch,
|
|
12
|
-
translatePatch,
|
|
13
|
-
wrapAngle,
|
|
14
|
-
} from "./scene-objects.js";
|
|
6
|
+
import { CUTOUT_KIND, rotatePatch, scalePatch, screenRotatePatch, translatePatch, wrapAngle } from "./scene-objects.js";
|
|
7
|
+
import { dragTravel, dragTurn } from "./held-object-pose.js";
|
|
8
|
+
import { HeldTrail } from "./held-object-edit.jsx";
|
|
9
|
+
import { ACTIVE_COLOR, ARROW_LEN, AXES, AXIS_VECTORS, BOX_SIZE, CARD_CORNERS, CENTRE_BOX, CORNER_BOX, FAR_HALF_ALPHA, GROUND, PICK_CENTRE_R, PICK_CORNER_R, PICK_SHAFT_R, PICK_TUBE_R,
|
|
10
|
+
PLANES, PLANE_OFFSET, PLANE_SIZE, RING_R, RING_TUBE, SCREEN_RING_R, SCREEN_SCALE, SHAFT_R, TIP_LEN, TIP_R, gizmoOrigin, gizmoTurn, handleKey } from "./object-gizmo-parts.js";
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
13
|
* The transform gizmo for the selected scene object — the thing every 3D tool
|
|
@@ -46,109 +42,8 @@ import {
|
|
|
46
42
|
* space and decomposed back into the same channels (screenRotatePatch).
|
|
47
43
|
*/
|
|
48
44
|
|
|
49
|
-
const AXES = [
|
|
50
|
-
{ axis: "x", dir: new THREE.Vector3(1, 0, 0), color: "#ff5340" },
|
|
51
|
-
{ axis: "y", dir: new THREE.Vector3(0, 1, 0), color: "#54e05c" },
|
|
52
|
-
{ axis: "z", dir: new THREE.Vector3(0, 0, 1), color: "#3d8bff" },
|
|
53
|
-
];
|
|
54
|
-
/** the same three directions, addressable by the record's axis key */
|
|
55
|
-
const AXIS_VECTORS = { x: AXES[0].dir, y: AXES[1].dir, z: AXES[2].dir };
|
|
56
|
-
/** Move-tool plane handles. Unity colours each square after the axis it LOCKS,
|
|
57
|
-
* so the blue square (z locked) slides in xy. `normal` is the pinned axis. */
|
|
58
|
-
const PLANES = [
|
|
59
|
-
{ id: "xz", axes: ["x", "z"], normal: "y", color: "#54e05c" },
|
|
60
|
-
{ id: "xy", axes: ["x", "y"], normal: "z", color: "#3d8bff" },
|
|
61
|
-
{ id: "yz", axes: ["y", "z"], normal: "x", color: "#ff5340" },
|
|
62
|
-
];
|
|
63
|
-
const PLANE_OFFSET = 0.26; // where the square sits along each of its two axes
|
|
64
|
-
const PLANE_SIZE = 0.2;
|
|
65
|
-
const ARROW_LEN = 0.62;
|
|
66
|
-
const SHAFT_R = 0.016;
|
|
67
|
-
const TIP_R = 0.05;
|
|
68
|
-
const TIP_LEN = 0.14;
|
|
69
|
-
const RING_R = 0.52;
|
|
70
|
-
/**
|
|
71
|
-
* The axis rings are drawn as two half-tori instead of one full torus. A full
|
|
72
|
-
* ring is misleading: the half facing away from the camera is pixel-identical
|
|
73
|
-
* to the near half, so a drag started on the far side reads backwards.
|
|
74
|
-
* Depth-sorting the ring against the object would only help when the object
|
|
75
|
-
* is big enough to occlude the far half — the gizmo sits at the object's
|
|
76
|
-
* centre and the ring is only 0.52 m across, so for the small props a
|
|
77
|
-
* blocking tool is full of, the far half projects outside the silhouette and
|
|
78
|
-
* stays ambiguous. Fading it instead (FAR_HALF_ALPHA vs the near half's
|
|
79
|
-
* 0.95) is unambiguous at every object size and camera angle. Only the drawn
|
|
80
|
-
* halves fade — the pick proxy stays one full invisible torus per ring.
|
|
81
|
-
*/
|
|
82
|
-
const FAR_HALF_ALPHA = 0.28;
|
|
83
|
-
/** the outer screen-space ring: larger than the axis rings (RING_R) so it
|
|
84
|
-
* encloses them from every angle, and far enough out that its pick band
|
|
85
|
-
* [0.61, 0.75] clears the axis rings' [0.45, 0.59] — the two can never fight
|
|
86
|
-
* for a grab. (§3.3: "a larger circle enclosing the sphere, drawn flat to
|
|
87
|
-
* the viewer" — rotates about the view axis.) */
|
|
88
|
-
const SCREEN_RING_R = 0.68;
|
|
89
|
-
/** Unity's yellow for the handle being dragged — and, after release, for the
|
|
90
|
-
* last-dragged one, until the selection or the tool changes (§3.1). */
|
|
91
|
-
const ACTIVE_COLOR = "#ffd23d";
|
|
92
|
-
const RING_TUBE = 0.022;
|
|
93
|
-
const BOX_SIZE = 0.09; // scale-mode axis knobs
|
|
94
|
-
const CENTRE_BOX = 0.11; // uniform-scale knob at the pivot
|
|
95
|
-
/** Pick proxies. A drawn 1.6 cm shaft is ~3 screen pixels — nothing anyone can
|
|
96
|
-
* reliably grab — so hit testing runs against invisible fat volumes around each
|
|
97
|
-
* arrow, ring and knob, the way every DCC gizmo does it. */
|
|
98
|
-
const PICK_SHAFT_R = 0.09;
|
|
99
|
-
const PICK_TUBE_R = 0.07;
|
|
100
|
-
const PICK_CENTRE_R = 0.16;
|
|
101
|
-
/** metres of gizmo per metre of camera distance: keeps it a constant size on
|
|
102
|
-
* screen whether the object is at the lens or across the room */
|
|
103
|
-
const SCREEN_SCALE = 0.16;
|
|
104
|
-
|
|
105
|
-
/** the object's own centre height, where the gizmo sits */
|
|
106
|
-
function gizmoHeight(object) {
|
|
107
|
-
return (object.y ?? 0) + Math.max(objectSize(object).height / 2, 0.25);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** the handles-Map key a pick entry was registered under — the same
|
|
111
|
-
* expression __gizmoPick reports, so highlights and QA hooks agree on handle
|
|
112
|
-
* identity */
|
|
113
|
-
function handleKey(entry) {
|
|
114
|
-
if (entry.corner) return `corner:${entry.corner.id}`;
|
|
115
|
-
return entry.axis ?? (entry.plane ? `plane:${entry.plane.id}` : "centre");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* The four corners of a cutout card, in the card's own local space.
|
|
120
|
-
*
|
|
121
|
-
* A standee is a picture, and a picture is resized by its corners — grabbing
|
|
122
|
-
* one and pulling is the gesture everybody already has for "make this bigger",
|
|
123
|
-
* and unlike an axis knob it says which way is bigger without reading a label.
|
|
124
|
-
* `sx`/`sy` are which way each corner lies from the centre, so the drag can
|
|
125
|
-
* measure travel along the diagonal that corner actually points down.
|
|
126
|
-
*/
|
|
127
|
-
const CARD_CORNERS = [
|
|
128
|
-
{ id: "tr", sx: 1, sy: 1 },
|
|
129
|
-
{ id: "tl", sx: -1, sy: 1 },
|
|
130
|
-
{ id: "br", sx: 1, sy: -1 },
|
|
131
|
-
{ id: "bl", sx: -1, sy: -1 },
|
|
132
|
-
];
|
|
133
|
-
const CORNER_BOX = 0.085;
|
|
134
|
-
const PICK_CORNER_R = 0.13;
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Screen-space ring result: the object rotated `deltaDeg` about the world
|
|
138
|
-
* `viewAxis`, written back as all three Euler channels in one patch. The
|
|
139
|
-
* stored channels are Euler degrees per world axis, so the drag-start
|
|
140
|
-
* orientation is composed with the delta in quaternion space and decomposed
|
|
141
|
-
* again in the same order the renderer uses — exact for any start
|
|
142
|
-
* orientation, where a single-channel approximation would drift. Snap
|
|
143
|
-
* quantizes the accumulated delta to the same 5° grid the axis rings snap
|
|
144
|
-
* their start+delta to (start values are always on the grid themselves).
|
|
145
|
-
*/
|
|
146
|
-
|
|
147
|
-
// Path authoring intersects presses with the set floor, exactly where a
|
|
148
|
-
// dropped pin will live.
|
|
149
|
-
const GROUND = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
|
150
45
|
|
|
151
|
-
export default function ObjectGizmo({ object, objects = [], mode = "move", snap = true, enabled, pickOnly = false, paneRef, camRef, shotAspect = SHOT_ASPECT, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer }) {
|
|
46
|
+
export default function ObjectGizmo({ object, objects = [], mode = "move", snap = true, enabled, pickOnly = false, paneRef, camRef, shotAspect = SHOT_ASPECT, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer, held = null }) {
|
|
152
47
|
const { gl, scene } = useThree();
|
|
153
48
|
// shotAspect null = the camera fills the pane (the editor working view):
|
|
154
49
|
// pointer mapping uses the pane rect itself instead of a letterboxed
|
|
@@ -174,7 +69,10 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
174
69
|
const hoverRef = useRef(null); // last hovered key: pointer moves that keep it skip the re-render
|
|
175
70
|
const dragRef = useRef(null);
|
|
176
71
|
const stateRef = useRef(null);
|
|
177
|
-
|
|
72
|
+
// `held` (held-object-edit.jsx): the object is drawn by a compiled take. The
|
|
73
|
+
// gizmo stands on that drawn pose, and while a hand holds the object a move
|
|
74
|
+
// or rotate drag edits its motion through `held` instead of the record.
|
|
75
|
+
stateRef.current = { object, objects, mode, snap, onChange, onSelect, onGroundClick, onDragStart, onDragEnd, claimPointer, held };
|
|
178
76
|
const tools = useMemo(
|
|
179
77
|
() => ({
|
|
180
78
|
raycaster: new THREE.Raycaster(),
|
|
@@ -347,6 +245,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
347
245
|
// the moment the pointer crosses the ring's ±180° seam.
|
|
348
246
|
drag.turned += wrapAngle(((angle - drag.lastAngle) * 180) / Math.PI);
|
|
349
247
|
drag.lastAngle = angle;
|
|
248
|
+
if (drag.held) return void stateRef.current.held?.rotate(drag.dir, dragTurn(drag.turned, snapping ? 5 : 0));
|
|
350
249
|
if (drag.axis === "screen") {
|
|
351
250
|
// The screen ring spins about the view axis: compose the
|
|
352
251
|
// delta onto the drag-start orientation in quaternion space
|
|
@@ -399,6 +298,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
399
298
|
return;
|
|
400
299
|
}
|
|
401
300
|
tools.delta.subVectors(tools.hit, drag.hitStart);
|
|
301
|
+
if (drag.held) return void stateRef.current.held?.move(dragTravel(tools.delta, drag.planeAxes ? drag.planeAxes.map((axis) => AXIS_VECTORS[axis]) : [drag.dir], snapping ? 0.05 : 0));
|
|
402
302
|
if (drag.planeAxes) {
|
|
403
303
|
// Plane handle: two axes travel, the third is pinned. This is what
|
|
404
304
|
// replaced body-dragging — the XZ square is how a set gets dressed.
|
|
@@ -438,7 +338,8 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
438
338
|
const end = stateRef.current.onDragEnd;
|
|
439
339
|
const token = drag.token;
|
|
440
340
|
teardownDrag();
|
|
441
|
-
if (
|
|
341
|
+
if (drag.held) stateRef.current.held?.end(commit);
|
|
342
|
+
else if (token != null && end) end(token, { commit });
|
|
442
343
|
};
|
|
443
344
|
|
|
444
345
|
/** pointerup and pointercancel both commit: the travel already applied
|
|
@@ -482,7 +383,8 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
482
383
|
// (a jump of however far the car has driven). The pivot, and the yaw
|
|
483
384
|
// the card's corners are seen at, are the shown ones.
|
|
484
385
|
const live = stateRef.current.objects.find((entry) => entry.id === shown.id) ?? shown;
|
|
485
|
-
|
|
386
|
+
const heldEdit = stateRef.current.held;
|
|
387
|
+
gizmoOrigin(shown, heldEdit, tools.origin);
|
|
486
388
|
camera.getWorldDirection(tools.eye);
|
|
487
389
|
let plane;
|
|
488
390
|
let drag;
|
|
@@ -491,7 +393,9 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
491
393
|
// pivot. The screen ring has no axis of its own: it spins
|
|
492
394
|
// about the camera's own forward, so its plane is the
|
|
493
395
|
// camera-facing plane through the pivot.
|
|
494
|
-
|
|
396
|
+
// A held object turns about its own axes (its rings are drawn turned too).
|
|
397
|
+
const turn = gizmoTurn(heldEdit, kind);
|
|
398
|
+
const spinDir = axis === "screen" ? tools.eye : turn ? dir.clone().applyQuaternion(new THREE.Quaternion(...turn)) : dir;
|
|
495
399
|
plane = new THREE.Plane().setFromNormalAndCoplanarPoint(spinDir, tools.origin.clone());
|
|
496
400
|
const hit = new THREE.Vector3();
|
|
497
401
|
if (!tools.raycaster.ray.intersectPlane(plane, hit)) return false;
|
|
@@ -577,12 +481,14 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
577
481
|
// (§6.1). The `cancel` handed over is teardown-only — the
|
|
578
482
|
// coordinator owns the close, and a close attempted from inside it
|
|
579
483
|
// is inert because the token is retired first (§5.2).
|
|
580
|
-
|
|
484
|
+
// A held-object drag edits the motion, not the record: no scene transaction.
|
|
485
|
+
const heldDrag = Boolean(heldEdit?.editable && (kind === "rotate" || (!heldEdit.hinge && (kind === "move" || kind === "plane"))) && heldEdit.begin());
|
|
486
|
+
const token = heldDrag ? undefined : stateRef.current.onDragStart?.({
|
|
581
487
|
owner: "gizmo",
|
|
582
488
|
cancel: teardownDrag,
|
|
583
489
|
});
|
|
584
490
|
const corner = kind === "corner" ? dir : null;
|
|
585
|
-
dragRef.current = { ...drag, id: live.id, axis, plane, corner, start: { ...live }, token };
|
|
491
|
+
dragRef.current = { ...drag, id: live.id, axis, plane, corner, start: { ...live }, token, held: heldDrag };
|
|
586
492
|
// the grabbed handle turns yellow now and stays yellow after
|
|
587
493
|
// release, until the selection or the tool changes (§3.1)
|
|
588
494
|
setActiveHandle(handleKey({ axis, plane, corner }));
|
|
@@ -593,7 +499,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
593
499
|
// Only a real transaction gets the extra lifecycle edges: blur and
|
|
594
500
|
// Escape would otherwise change mid-drag behaviour for callers that
|
|
595
501
|
// pass no transaction props (the byte-for-byte fallback path).
|
|
596
|
-
if (token != null) {
|
|
502
|
+
if (token != null || heldDrag) {
|
|
597
503
|
window.addEventListener("blur", onBlur);
|
|
598
504
|
window.addEventListener("keydown", onKeyDown, true);
|
|
599
505
|
}
|
|
@@ -753,7 +659,7 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
753
659
|
const camera = camRef?.current;
|
|
754
660
|
if (!root || !camera || !object) return;
|
|
755
661
|
camera.getWorldPosition(tools.camPos);
|
|
756
|
-
|
|
662
|
+
gizmoOrigin(object, held, tools.origin);
|
|
757
663
|
root.scale.setScalar(Math.max(0.35, tools.camPos.distanceTo(tools.origin) * SCREEN_SCALE));
|
|
758
664
|
if (mode !== "rotate") return;
|
|
759
665
|
// The rotate rings are view-dependent, so their orientation is frame
|
|
@@ -764,6 +670,14 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
764
670
|
camera.getWorldDirection(tools.eye);
|
|
765
671
|
const screen = screenRingRef.current;
|
|
766
672
|
if (screen) screen.quaternion.copy(camera.quaternion);
|
|
673
|
+
// Turned rings (a held object's own axes): the near/far split and the
|
|
674
|
+
// billboard are worked out in the gizmo's own frame.
|
|
675
|
+
const turn = gizmoTurn(held, mode);
|
|
676
|
+
if (turn) {
|
|
677
|
+
const unturn = new THREE.Quaternion(...turn).invert();
|
|
678
|
+
tools.eye.applyQuaternion(unturn);
|
|
679
|
+
screen?.quaternion.premultiply(unturn);
|
|
680
|
+
}
|
|
767
681
|
for (const { axis, u, v } of ringBases) {
|
|
768
682
|
const ring = ringGroupsRef.current[axis];
|
|
769
683
|
if (!ring) continue;
|
|
@@ -855,8 +769,9 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
855
769
|
const screenHovered = hoveredHandle === "screen";
|
|
856
770
|
const centreActive = activeHandle === "centre";
|
|
857
771
|
const centreHovered = hoveredHandle === "centre";
|
|
858
|
-
return (
|
|
859
|
-
<
|
|
772
|
+
return (<>
|
|
773
|
+
<HeldTrail trail={held?.trail ?? null} />
|
|
774
|
+
<group ref={rootRef} position={gizmoOrigin(object, held, new THREE.Vector3()).toArray()} quaternion={gizmoTurn(held, mode) ?? [0, 0, 0, 1]} renderOrder={999}>
|
|
860
775
|
{AXES.map(({ axis, dir, color }) => {
|
|
861
776
|
const quat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
|
|
862
777
|
// The handle under a drag — and, after release, the last one —
|
|
@@ -1033,5 +948,5 @@ export default function ObjectGizmo({ object, objects = [], mode = "move", snap
|
|
|
1033
948
|
</>
|
|
1034
949
|
)}
|
|
1035
950
|
</group>
|
|
1036
|
-
);
|
|
951
|
+
</>);
|
|
1037
952
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glow: a record that gives off light of its own, drawn in one pass.
|
|
3
|
+
*
|
|
4
|
+
* There is no post-processing anywhere (the viewport, the shot preview,
|
|
5
|
+
* capture_frame, contact sheets and the video export all render the scene with
|
|
6
|
+
* one gl.render), so a bloom pass would have to be bolted onto every path.
|
|
7
|
+
* Instead a glow is two single-pass parts, both on the default layer:
|
|
8
|
+
* - an emissive colour on every material the record draws, which no stage
|
|
9
|
+
* light can darken, and
|
|
10
|
+
* - an additive, camera-facing halo sprite centred on the record, `halo` ×
|
|
11
|
+
* the record's radius. It writes no depth, casts no shadow and is never
|
|
12
|
+
* raycast, so it cannot occlude, shadow or be picked.
|
|
13
|
+
* Both scale with `gain` (the take's level, times a light's flicker).
|
|
14
|
+
* A glowing record is self-lit, so its meshes cast no shadow while it glows: a
|
|
15
|
+
* practical light placed inside a glowing bulb or flame lights the set instead
|
|
16
|
+
* of being shut in by the bulb's own faces (light-blockers.js).
|
|
17
|
+
*
|
|
18
|
+
* State is kept per group in a WeakMap, never in `userData` (React replaces a
|
|
19
|
+
* group's userData on every render). Removing the glow restores each
|
|
20
|
+
* material's own emissive and drops the halo.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import * as THREE from "three";
|
|
24
|
+
|
|
25
|
+
/** Halo brightness per unit of glow intensity. */
|
|
26
|
+
const HALO_GAIN = 0.2;
|
|
27
|
+
/** Radius used for a record with no size of its own (a light, an Empty), metres. */
|
|
28
|
+
const POINT_RADIUS = 0.02;
|
|
29
|
+
const noRaycast = () => {};
|
|
30
|
+
const glowStates = new WeakMap();
|
|
31
|
+
|
|
32
|
+
/** True while the group carries a glow that a later frame may have to remove. */
|
|
33
|
+
export function hasGlow(group) {
|
|
34
|
+
return glowStates.has(group);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let haloMap = null;
|
|
38
|
+
/** A white disc whose alpha is a bright core over a long soft tail. */
|
|
39
|
+
function haloTexture() {
|
|
40
|
+
if (haloMap) return haloMap;
|
|
41
|
+
const size = 64;
|
|
42
|
+
const data = new Uint8Array(size * size * 4);
|
|
43
|
+
for (let y = 0; y < size; y += 1) {
|
|
44
|
+
for (let x = 0; x < size; x += 1) {
|
|
45
|
+
const r = Math.hypot(x + 0.5 - size / 2, y + 0.5 - size / 2) / (size / 2);
|
|
46
|
+
const tail = r >= 1 ? 0 : (1 - r) ** 1.6;
|
|
47
|
+
const alpha = Math.min(1, 0.7 * Math.exp(-((r / 0.25) ** 2)) + 0.5 * tail);
|
|
48
|
+
data.set([255, 255, 255, Math.round(alpha * 255)], (y * size + x) * 4);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
haloMap = new THREE.DataTexture(data, size, size);
|
|
52
|
+
haloMap.magFilter = THREE.LinearFilter;
|
|
53
|
+
haloMap.minFilter = THREE.LinearFilter;
|
|
54
|
+
haloMap.needsUpdate = true;
|
|
55
|
+
return haloMap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeHalo() {
|
|
59
|
+
const material = new THREE.SpriteMaterial({ map: haloTexture(), blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, fog: false });
|
|
60
|
+
const halo = new THREE.Sprite(material);
|
|
61
|
+
halo.userData.glowHalo = true;
|
|
62
|
+
// Beauty render only: the ink prepass and the depth/normal passes draw with
|
|
63
|
+
// an override material, which would turn the soft halo into a solid quad.
|
|
64
|
+
halo.userData.beautyOnly = true;
|
|
65
|
+
halo.raycast = noRaycast;
|
|
66
|
+
halo.castShadow = false;
|
|
67
|
+
halo.receiveShadow = false;
|
|
68
|
+
halo.renderOrder = 2;
|
|
69
|
+
return halo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Each mesh the record draws on the default layer, and each of its lit
|
|
73
|
+
* materials (editor markers and the halo itself are left alone). */
|
|
74
|
+
function forEachGlowMesh(group, visitMesh, visitMaterial) {
|
|
75
|
+
group.traverse((node) => {
|
|
76
|
+
if (!node.isMesh || !node.layers.isEnabled(0)) return;
|
|
77
|
+
visitMesh(node);
|
|
78
|
+
for (const material of Array.isArray(node.material) ? node.material : [node.material]) {
|
|
79
|
+
if (material?.emissive?.isColor) visitMaterial(material);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function clearGlow(group, state) {
|
|
85
|
+
for (const [material, base] of state.bases) {
|
|
86
|
+
material.emissive.copy(base.emissive);
|
|
87
|
+
material.emissiveIntensity = base.intensity;
|
|
88
|
+
}
|
|
89
|
+
for (const mesh of state.shadowless) mesh.castShadow = true;
|
|
90
|
+
state.halo.removeFromParent();
|
|
91
|
+
state.halo.material.dispose();
|
|
92
|
+
glowStates.delete(group);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The record's centre (local) and its radius (world): half its largest
|
|
96
|
+
* extent, so a sphere's radius is exact; a record with no size (a light, an
|
|
97
|
+
* Empty) counts as POINT_RADIUS. Size comes from the record, the same numbers
|
|
98
|
+
* the selection cage and the plan use. */
|
|
99
|
+
function recordSphere(object, scale) {
|
|
100
|
+
const width = (object.footprint?.width ?? 0) * Math.abs(scale.x);
|
|
101
|
+
const height = (object.height ?? 0) * Math.abs(scale.y);
|
|
102
|
+
const depth = (object.footprint?.depth ?? 0) * Math.abs(scale.z);
|
|
103
|
+
const radius = Math.max(width, height, depth) / 2;
|
|
104
|
+
return { centreY: (object.height ?? 0) / 2, radius: radius > 0 ? radius : POINT_RADIUS };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Apply, update or remove the record's glow for this frame. */
|
|
108
|
+
export function placeObjectGlow(group, object, gain) {
|
|
109
|
+
const glow = object.glow;
|
|
110
|
+
let state = glowStates.get(group);
|
|
111
|
+
if (!glow) {
|
|
112
|
+
if (state) clearGlow(group, state);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!state) {
|
|
116
|
+
state = { halo: makeHalo(), bases: new Map(), shadowless: new Set() };
|
|
117
|
+
glowStates.set(group, state);
|
|
118
|
+
}
|
|
119
|
+
if (state.halo.parent !== group) group.add(state.halo);
|
|
120
|
+
const strength = glow.intensity * gain;
|
|
121
|
+
forEachGlowMesh(group, (mesh) => {
|
|
122
|
+
// a mesh rebuilt while glowing (a new kind, a loaded file) casts again until this pass
|
|
123
|
+
if (!mesh.castShadow) return;
|
|
124
|
+
mesh.castShadow = false;
|
|
125
|
+
state.shadowless.add(mesh);
|
|
126
|
+
}, (material) => {
|
|
127
|
+
if (!state.bases.has(material)) state.bases.set(material, { emissive: material.emissive.clone(), intensity: material.emissiveIntensity });
|
|
128
|
+
material.emissive.set(glow.color);
|
|
129
|
+
material.emissiveIntensity = strength;
|
|
130
|
+
});
|
|
131
|
+
const { centreY, radius } = recordSphere(object, group.scale);
|
|
132
|
+
const size = 2 * radius * glow.halo * gain;
|
|
133
|
+
const halo = state.halo;
|
|
134
|
+
halo.visible = size > 0 && strength > 0;
|
|
135
|
+
halo.position.set(0, centreY, 0);
|
|
136
|
+
// The sprite inherits the record's scale; divide it out so the halo stays round.
|
|
137
|
+
halo.scale.set(size / (Math.abs(group.scale.x) || 1), size / (Math.abs(group.scale.y) || 1), 1);
|
|
138
|
+
halo.material.color.set(glow.color).multiplyScalar(HALO_GAIN * strength);
|
|
139
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Effective visibility of a scene record through its parents and its carrying
|
|
2
|
+
// character. Pure and dependency-free, so the renderers, the agent commands and
|
|
3
|
+
// the effects can share it; scene-objects.js re-exports it.
|
|
4
|
+
|
|
5
|
+
/** True when this record, a parent above it, or a character carrying any of
|
|
6
|
+
* those records is hidden. A cycle in parent links ends the walk. When the
|
|
7
|
+
* id is in `objects`, that stored record is what the walk reads, so a display
|
|
8
|
+
* copy cannot disagree with the authored flag. Nothing in the lists is changed. */
|
|
9
|
+
export function isEffectivelyHidden(entity, objects = [], characters = []) {
|
|
10
|
+
if (!entity || typeof entity !== "object") return false;
|
|
11
|
+
const byId = new Map();
|
|
12
|
+
for (const object of objects) {
|
|
13
|
+
if (object && typeof object.id === "string") byId.set(object.id, object);
|
|
14
|
+
}
|
|
15
|
+
const characterHidden = (id) => characters.some((item) => item && typeof item === "object" && item.id === id && item.hidden === true);
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
let current = entity;
|
|
18
|
+
while (current && typeof current === "object") {
|
|
19
|
+
const id = typeof current.id === "string" ? current.id : null;
|
|
20
|
+
if (id) {
|
|
21
|
+
if (seen.has(id)) break;
|
|
22
|
+
seen.add(id);
|
|
23
|
+
if (byId.has(id)) current = byId.get(id);
|
|
24
|
+
}
|
|
25
|
+
if (current.hidden === true) return true;
|
|
26
|
+
const characterId = current.attach && typeof current.attach.characterId === "string" ? current.attach.characterId : null;
|
|
27
|
+
if (characterId && characterHidden(characterId)) return true;
|
|
28
|
+
const parentId = typeof current.parent === "string" ? current.parent : null;
|
|
29
|
+
if (!parentId) break;
|
|
30
|
+
current = byId.get(parentId) ?? null;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Practical lights and glow: the record fields, their defaults and clamps, and
|
|
3
|
+
* the per-frame numbers the renderer reads (flicker, take level, shadow cap).
|
|
4
|
+
*
|
|
5
|
+
* Pure on purpose: no three.js and no React, so the agent sidecar, the record
|
|
6
|
+
* normalizer and Node tests all read the same rules.
|
|
7
|
+
*
|
|
8
|
+
* A light is a record with renderer "light". It is created from one of two
|
|
9
|
+
* catalogue kinds, `point-light` and `spot-light`, which differ only in the
|
|
10
|
+
* `light.type` they start with. Like an Empty it has no size: no collision, no
|
|
11
|
+
* surface, no footprint on the plan.
|
|
12
|
+
*
|
|
13
|
+
* `glow` can sit on any record. It makes the record's materials emissive and
|
|
14
|
+
* adds a camera-facing additive halo, so an ember or a bulb reads as a light
|
|
15
|
+
* source in the shot render without any post-processing pass.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { normalizeObjectColor } from "./object-colors.js";
|
|
19
|
+
|
|
20
|
+
export const LIGHT_KIND = "light";
|
|
21
|
+
|
|
22
|
+
/** three.js intensity per authored unit. Authored values are candela-like
|
|
23
|
+
* (decay 2: lighter flame 0.3-0.6, candle 0.3, desk lamp 5, street lamp 40).
|
|
24
|
+
* The scale is tuned against the default stage key (directional, 1.12) so a
|
|
25
|
+
* street lamp's pool at 4 m reads about as bright as half the key, and a
|
|
26
|
+
* lighter 25 cm from a face lifts it to roughly the key. */
|
|
27
|
+
export const LIGHT_INTENSITY_SCALE = 0.35;
|
|
28
|
+
|
|
29
|
+
/** At most this many practical lights render shadows (each point shadow is six
|
|
30
|
+
* extra depth passes). The rest still light the scene and are reported. */
|
|
31
|
+
export const PRACTICAL_SHADOW_CAP = 2;
|
|
32
|
+
|
|
33
|
+
const LIGHT_DEFAULTS = Object.freeze({
|
|
34
|
+
point: Object.freeze({ type: "point", color: "#ffc787", intensity: 5, range: 0, angle: 30, softness: 0.3, shadow: false, flicker: 0, aim: null }),
|
|
35
|
+
spot: Object.freeze({ type: "spot", color: "#ffe0b2", intensity: 40, range: 0, angle: 30, softness: 0.3, shadow: true, flicker: 0, aim: null }),
|
|
36
|
+
});
|
|
37
|
+
const GLOW_DEFAULTS = Object.freeze({ color: "#ff9a3c", intensity: 4, halo: 1.5 });
|
|
38
|
+
|
|
39
|
+
const LIGHT_SIZE = { footprint: { width: 0, depth: 0 }, height: 0 };
|
|
40
|
+
/** The record entry every light normalizes through. */
|
|
41
|
+
const LIGHT_ENTRY = Object.freeze({ kind: LIGHT_KIND, label: "Light", group: "Lights", ...LIGHT_SIZE, color: "#ffd38a" });
|
|
42
|
+
/** The two creatable kinds: the add menu and the agent catalogue list these.
|
|
43
|
+
* `placeY` is where a fresh one hangs, so it lights something on arrival. */
|
|
44
|
+
export const LIGHT_MENU_ENTRIES = Object.freeze([
|
|
45
|
+
Object.freeze({ kind: "point-light", renderer: LIGHT_KIND, label: "Point light", group: "Lights", ...LIGHT_SIZE, color: "#ffc787", placeY: 1.5, light: LIGHT_DEFAULTS.point }),
|
|
46
|
+
Object.freeze({ kind: "spot-light", renderer: LIGHT_KIND, label: "Spot light", group: "Lights", ...LIGHT_SIZE, color: "#ffe0b2", placeY: 3, light: LIGHT_DEFAULTS.spot }),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** The library entry for a light kind or the light renderer, or null. */
|
|
50
|
+
export function lightLibraryEntry(kind) {
|
|
51
|
+
if (kind === LIGHT_KIND) return LIGHT_ENTRY;
|
|
52
|
+
return LIGHT_MENU_ENTRIES.find((entry) => entry.kind === kind) ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
56
|
+
const finite = (value, fallback, min, max) => {
|
|
57
|
+
const n = typeof value === "number" || typeof value === "string" ? Number(value) : NaN;
|
|
58
|
+
return Number.isFinite(n) ? clamp(n, min, max) : fallback;
|
|
59
|
+
};
|
|
60
|
+
const hex = (value, fallback) => normalizeObjectColor(value) ?? fallback;
|
|
61
|
+
const point = (value) => {
|
|
62
|
+
if (!value || typeof value !== "object") return null;
|
|
63
|
+
const out = { x: Number(value.x), y: Number(value.y), z: Number(value.z) };
|
|
64
|
+
return Object.values(out).every(Number.isFinite) ? out : null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** A stored or requested light repaired into the full record. `type` falls back
|
|
68
|
+
* to `typeHint` (the kind it was created as), then to "point". */
|
|
69
|
+
export function normalizeObjectLight(value, typeHint = "point") {
|
|
70
|
+
const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
71
|
+
const type = source.type === "spot" || source.type === "point" ? source.type : typeHint === "spot" ? "spot" : "point";
|
|
72
|
+
const base = LIGHT_DEFAULTS[type];
|
|
73
|
+
return {
|
|
74
|
+
type,
|
|
75
|
+
color: hex(source.color, base.color),
|
|
76
|
+
intensity: finite(source.intensity, base.intensity, 0, 1000),
|
|
77
|
+
range: finite(source.range, base.range, 0, 100),
|
|
78
|
+
angle: finite(source.angle, base.angle, 1, 89),
|
|
79
|
+
softness: finite(source.softness, base.softness, 0, 1),
|
|
80
|
+
shadow: typeof source.shadow === "boolean" ? source.shadow : base.shadow,
|
|
81
|
+
flicker: finite(source.flicker, base.flicker, 0, 1),
|
|
82
|
+
aim: point(source.aim),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A glow repaired into `{ color, intensity, halo }`, or null for none. */
|
|
87
|
+
export function normalizeObjectGlow(value) {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
89
|
+
return {
|
|
90
|
+
color: hex(value.color, GLOW_DEFAULTS.color),
|
|
91
|
+
intensity: finite(value.intensity, GLOW_DEFAULTS.intensity, 0, 20),
|
|
92
|
+
halo: finite(value.halo, GLOW_DEFAULTS.halo, 0, 4),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The light/glow fields a normalized record carries. A light renderer always
|
|
97
|
+
* has a light; any record keeps a glow only when it has one, so records written
|
|
98
|
+
* before glow existed come back byte-for-byte unchanged. */
|
|
99
|
+
export function objectLookFields(record, entry) {
|
|
100
|
+
const out = {};
|
|
101
|
+
if ((entry.renderer ?? entry.kind) === LIGHT_KIND) out.light = normalizeObjectLight(record.light, record.light?.type ?? entry.light?.type);
|
|
102
|
+
const glow = normalizeObjectGlow(record.glow);
|
|
103
|
+
if (glow) out.glow = glow;
|
|
104
|
+
// A kit part a take switches on (a muzzle flash and its light) stays hidden without one (object-travel.js trackShown).
|
|
105
|
+
if (record.showOnlyInTake === true) out.showOnlyInTake = true;
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The light/glow part of an object.update patch: each field merges over the
|
|
110
|
+
* record's own and is repaired; `glow: null` removes the glow. Only changes are
|
|
111
|
+
* returned. A light on a record that is not a light is ignored. */
|
|
112
|
+
export function objectLookPatch(object, patch) {
|
|
113
|
+
const update = {};
|
|
114
|
+
if (patch?.light && typeof patch.light === "object" && object.renderer === LIGHT_KIND) {
|
|
115
|
+
const next = normalizeObjectLight({ ...object.light, ...patch.light }, object.light?.type);
|
|
116
|
+
if (JSON.stringify(next) !== JSON.stringify(object.light)) update.light = next;
|
|
117
|
+
}
|
|
118
|
+
if (patch?.glow !== undefined) {
|
|
119
|
+
const next = patch.glow === null ? null : normalizeObjectGlow({ ...(object.glow ?? {}), ...patch.glow });
|
|
120
|
+
if (JSON.stringify(next) !== JSON.stringify(object.glow ?? null)) update.glow = next;
|
|
121
|
+
}
|
|
122
|
+
return update;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** 32-bit FNV-1a of the record id: the flicker's seed. */
|
|
126
|
+
function seedOf(id) {
|
|
127
|
+
let hash = 0x811c9dc5;
|
|
128
|
+
for (const char of String(id)) {
|
|
129
|
+
hash ^= char.codePointAt(0);
|
|
130
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
131
|
+
}
|
|
132
|
+
return hash;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Lattice value in [-1, 1] for an integer cell of a seeded stream. */
|
|
136
|
+
function lattice(seed, cell) {
|
|
137
|
+
let h = Math.imul(seed ^ Math.imul(cell, 0x27d4eb2d), 0x165667b1) >>> 0;
|
|
138
|
+
h = Math.imul(h ^ (h >>> 15), 0x85ebca6b) >>> 0;
|
|
139
|
+
h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35) >>> 0;
|
|
140
|
+
return ((h ^ (h >>> 16)) >>> 0) / 0x7fffffff - 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Smooth value noise in [-1, 1] at time `t` (cells of 1). */
|
|
144
|
+
function noise(seed, t) {
|
|
145
|
+
const cell = Math.floor(t);
|
|
146
|
+
const f = t - cell;
|
|
147
|
+
const s = f * f * (3 - 2 * f);
|
|
148
|
+
return lattice(seed, cell) * (1 - s) + lattice(seed, cell + 1) * s;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The flicker multiplier of a light at `frame`: a pure function of the id, the
|
|
153
|
+
* frame and the amount, so scrubbing, playback and export see the same value.
|
|
154
|
+
* 0 is steady (exactly 1); 0.3 wavers like a candle; 0.6 jumps like a lighter.
|
|
155
|
+
*/
|
|
156
|
+
export function flickerAt(id, frame, amount, fps = 24) {
|
|
157
|
+
const depth = finite(amount, 0, 0, 1);
|
|
158
|
+
if (!(depth > 0)) return 1;
|
|
159
|
+
const seed = seedOf(id);
|
|
160
|
+
const t = (Number(frame) || 0) / (fps > 0 ? fps : 24);
|
|
161
|
+
const n = 0.55 * noise(seed, t * 7) + 0.3 * noise(seed ^ 0x9e3779b9, t * 15) + 0.15 * noise(seed ^ 0x7f4a7c15, t * 31);
|
|
162
|
+
return Math.max(0, 1 + depth * 0.65 * n);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Which shown lights that ask for a shadow get one: the first
|
|
167
|
+
* PRACTICAL_SHADOW_CAP in record order. `capped` lists the rest by id.
|
|
168
|
+
* `isHidden(record)` drops records the stage does not draw.
|
|
169
|
+
*/
|
|
170
|
+
export function practicalShadows(objects, isHidden = (object) => object?.hidden === true) {
|
|
171
|
+
const asking = (objects ?? []).filter((object) => object?.renderer === LIGHT_KIND && object.light?.shadow === true && !isHidden(object));
|
|
172
|
+
return { casting: asking.slice(0, PRACTICAL_SHADOW_CAP).map((object) => object.id), capped: asking.slice(PRACTICAL_SHADOW_CAP).map((object) => object.id) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The records as the stage draws them: lights past the shadow cap render with
|
|
176
|
+
* `shadow: false` and `shadowCapped: true`. The same array when none is capped. */
|
|
177
|
+
export function capPracticalShadows(objects) {
|
|
178
|
+
const capped = new Set(practicalShadows(objects).capped);
|
|
179
|
+
if (!capped.size) return objects;
|
|
180
|
+
return objects.map((object) => (capped.has(object.id) ? { ...object, light: { ...object.light, shadow: false }, shadowCapped: true } : object));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Receipt warnings, one per light that asked for a shadow but is past the cap,
|
|
184
|
+
* limited to `ids` when given (the records an edit touched). */
|
|
185
|
+
function shadowCapWarnings(objects, ids = null, isHidden = undefined) {
|
|
186
|
+
const { casting, capped } = practicalShadows(objects, isHidden);
|
|
187
|
+
return capped.filter((id) => !ids || ids.includes(id)).slice(0, 4).map((id) => ({ code: "SHADOW_CAP", id,
|
|
188
|
+
message: `Renders without a shadow: only ${PRACTICAL_SHADOW_CAP} lights cast shadows (${casting.join(", ")}).`.slice(0, 120) }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** A command result with the shadow-cap warnings for the records it touched
|
|
192
|
+
* appended, so a receipt says when a requested shadow is not drawn. */
|
|
193
|
+
export function withShadowCapWarnings(result, objects) {
|
|
194
|
+
const warnings = shadowCapWarnings(objects, result?.affectedIds ?? []);
|
|
195
|
+
return warnings.length ? { ...result, warnings: [...(result.warnings ?? []), ...warnings] } : result;
|
|
196
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// A leaf module: the motion compiler's scene reader (motion3/scene.js) needs an object's size without
|
|
2
|
+
// scene-objects.js, which imports three; the agent sidecar reaches motion3 through motion.describe and must not.
|
|
3
|
+
/** The object's world size along each axis, for the gizmo and the plan board. */
|
|
4
|
+
export function objectSize(object) {
|
|
5
|
+
return {
|
|
6
|
+
width: (object.footprint?.width ?? 1) * (object.scaleX ?? 1),
|
|
7
|
+
height: (object.height ?? 1) * (object.scaleY ?? 1),
|
|
8
|
+
depth: (object.footprint?.depth ?? 1) * (object.scaleZ ?? 1),
|
|
9
|
+
};
|
|
10
|
+
}
|