@volter/editor-core 0.5.57
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/BUNDLED_NOTICES +1527 -0
- package/LICENSE +686 -0
- package/LICENSE-APACHE +202 -0
- package/NOTICE +18 -0
- package/README.md +24 -0
- package/dist/build/chunk-JS4D4XZ3.js +175 -0
- package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
- package/dist/build/vite-plugin-product-contributions.js +100 -0
- package/dist/build/vite-plugin-product-contributions.js.map +7 -0
- package/dist/build/vite-plugin-shared-react.js +25 -0
- package/dist/build/vite-plugin-shared-react.js.map +7 -0
- package/dist/build/vite-plugin-shared-three.js +86 -0
- package/dist/build/vite-plugin-shared-three.js.map +7 -0
- package/dist/server/chunk-KMJC7AGO.js +98 -0
- package/dist/server/chunk-KMJC7AGO.js.map +7 -0
- package/dist/server/chunk-PSILUMGS.js +46 -0
- package/dist/server/chunk-PSILUMGS.js.map +7 -0
- package/dist/server/frame-proxy.js +255 -0
- package/dist/server/frame-proxy.js.map +7 -0
- package/dist/server/open-browser.js +53 -0
- package/dist/server/open-browser.js.map +7 -0
- package/dist/server/process-shutdown.js +127 -0
- package/dist/server/process-shutdown.js.map +7 -0
- package/dist/server/session-registry.js +141 -0
- package/dist/server/session-registry.js.map +7 -0
- package/dist/server/spawn-opener.js +11 -0
- package/dist/server/spawn-opener.js.map +7 -0
- package/dist/server/worktree-identity.js +11 -0
- package/dist/server/worktree-identity.js.map +7 -0
- package/dist-server/packaged.mjs +88816 -0
- package/package.json +118 -0
- package/scripts/build-plugins.mjs +14 -0
- package/scripts/build-server.mjs +26 -0
- package/scripts/build-session.mjs +9 -0
- package/server/account-credentials.ts +60 -0
- package/server/account-service.ts +1434 -0
- package/server/adapter-region-includes.ts +47 -0
- package/server/asset-catalog-v2.ts +175 -0
- package/server/asset-history-snapshots.ts +196 -0
- package/server/asset-ledger-store.ts +195 -0
- package/server/asset-library-routes.ts +1595 -0
- package/server/blender-wasm-artifact.ts +430 -0
- package/server/boot-timings.ts +106 -0
- package/server/canonical-path.ts +50 -0
- package/server/checkout-workspace-preflight.ts +349 -0
- package/server/cloud-asset-catalog.ts +158 -0
- package/server/coding-inference-launch.ts +237 -0
- package/server/collaboration-account-client.ts +202 -0
- package/server/collaboration-attribution.ts +122 -0
- package/server/collaboration-session.ts +934 -0
- package/server/comfyui-bridge.ts +248 -0
- package/server/console-ledger.ts +499 -0
- package/server/creation-site-transform.ts +304 -0
- package/server/creation-site-write.ts +648 -0
- package/server/data-file-serialize.ts +158 -0
- package/server/editor-brand-html.ts +72 -0
- package/server/editor-control-socket.ts +366 -0
- package/server/editor-server-options.ts +190 -0
- package/server/editor-server.ts +1483 -0
- package/server/editor-sse.ts +352 -0
- package/server/engine-provenance.ts +169 -0
- package/server/engine-source-restart.ts +117 -0
- package/server/frame-bridge.ts +115 -0
- package/server/frame-proxy.ts +490 -0
- package/server/frame-workbench.ts +259 -0
- package/server/frontend-handoff.ts +229 -0
- package/server/game-globals-shadow.ts +113 -0
- package/server/gameplay-session-retention.ts +149 -0
- package/server/gameplay-sessions.ts +123 -0
- package/server/generation-jobs.ts +201 -0
- package/server/generation-reconciler.ts +137 -0
- package/server/generative-execution-context.ts +59 -0
- package/server/git-workflow.ts +612 -0
- package/server/harness-chat-caller.ts +36 -0
- package/server/harness-chat-service.ts +1681 -0
- package/server/idle-shutdown.ts +170 -0
- package/server/js-profiling-policy.ts +52 -0
- package/server/launcher-settings.ts +84 -0
- package/server/local-asset-catalog.ts +410 -0
- package/server/managed-account-defaults.ts +22 -0
- package/server/mock-control-plane.ts +540 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/native-credential-store.ts +162 -0
- package/server/open-browser.ts +118 -0
- package/server/packaged.ts +1192 -0
- package/server/play-stall.ts +126 -0
- package/server/process-shutdown.ts +218 -0
- package/server/product-presets.ts +55 -0
- package/server/project-build-artifact.ts +95 -0
- package/server/project-components.ts +100 -0
- package/server/project-dependency-invalidation.ts +114 -0
- package/server/project-file-scan.ts +235 -0
- package/server/project-hmr-files.ts +215 -0
- package/server/project-install-roots.ts +210 -0
- package/server/project-kinds.ts +71 -0
- package/server/project-mcp-servers.ts +82 -0
- package/server/project-module-freshness.ts +109 -0
- package/server/project-module-instance.ts +375 -0
- package/server/project-optimize-deps-entries.ts +827 -0
- package/server/project-output-writer.ts +679 -0
- package/server/project-package-origin.ts +63 -0
- package/server/project-root-surface.ts +831 -0
- package/server/project-scratch-path.ts +47 -0
- package/server/project-script-hmr.ts +619 -0
- package/server/project-serving-plugins.ts +236 -0
- package/server/project-tools.ts +653 -0
- package/server/project-validation.ts +250 -0
- package/server/project-verbs.ts +127 -0
- package/server/project-view.ts +52 -0
- package/server/project-watch.ts +1134 -0
- package/server/project-work-coordinator.ts +288 -0
- package/server/provider-credentials.ts +331 -0
- package/server/recent-projects-store.ts +98 -0
- package/server/redact-secrets.ts +71 -0
- package/server/repository-presence.ts +267 -0
- package/server/routes/account.ts +357 -0
- package/server/routes/agents.ts +60 -0
- package/server/routes/assets.ts +154 -0
- package/server/routes/build.ts +13 -0
- package/server/routes/collaboration.ts +595 -0
- package/server/routes/configurations.ts +419 -0
- package/server/routes/context.ts +264 -0
- package/server/routes/control-plane.ts +1811 -0
- package/server/routes/logs.ts +272 -0
- package/server/routes/project-identity.ts +302 -0
- package/server/routes/project-open.ts +429 -0
- package/server/routes/project-source.ts +686 -0
- package/server/routes/project-state.ts +383 -0
- package/server/routes/relay.ts +1029 -0
- package/server/routes/route-helpers.ts +29 -0
- package/server/routes/served-modules.ts +138 -0
- package/server/routes/session-tabs.ts +196 -0
- package/server/routes/settings.ts +121 -0
- package/server/routes/share-control.ts +486 -0
- package/server/routes/themes.ts +116 -0
- package/server/routes/tools.ts +230 -0
- package/server/routes/worktrees.ts +595 -0
- package/server/scoped-game-css.ts +461 -0
- package/server/server-utils.ts +1194 -0
- package/server/session-product.ts +112 -0
- package/server/session-registry.ts +230 -0
- package/server/share-claims.ts +138 -0
- package/server/share-host.ts +526 -0
- package/server/share-session-gateway.ts +1362 -0
- package/server/share-tunnel.ts +123 -0
- package/server/spawn-opener.ts +74 -0
- package/server/support/play/log-format.ts +76 -0
- package/server/support/play/session-record.ts +199 -0
- package/server/support/project/build-discipline.ts +718 -0
- package/server/support/project/inspection-node.ts +119 -0
- package/server/support/project/log-naming.ts +54 -0
- package/server/support/project/provenance.ts +290 -0
- package/server/support/project/run-name.ts +18 -0
- package/server/support/project/session-journal.ts +830 -0
- package/server/support/project/shared.ts +327 -0
- package/server/tab-bootstrap.ts +62 -0
- package/server/tab-heartbeat.ts +477 -0
- package/server/tab-lifecycle.ts +550 -0
- package/server/tab-presence.ts +1508 -0
- package/server/team-agent-mirror.ts +95 -0
- package/server/twin-auth.ts +138 -0
- package/server/vendored-lock-recorder.ts +968 -0
- package/server/worktree-identity.ts +121 -0
- package/server/worktree-management.ts +398 -0
- package/server/worktree-retention.d.mts +13 -0
- package/server/worktree-retention.mjs +205 -0
- package/src/EditorContext.tsx +169 -0
- package/src/account.ts +333 -0
- package/src/action-registry.ts +532 -0
- package/src/active-product.ts +74 -0
- package/src/active-project.ts +155 -0
- package/src/adapter-editor-config.ts +25 -0
- package/src/adapter-observation.ts +49 -0
- package/src/animation/stage-transport.ts +379 -0
- package/src/animation/three-clips-subject.ts +172 -0
- package/src/api/asset-library-wire.ts +45 -0
- package/src/api/assets.ts +365 -0
- package/src/api/base.ts +10 -0
- package/src/api/build.ts +99 -0
- package/src/api/git-wire.ts +56 -0
- package/src/api/logs.ts +92 -0
- package/src/api/project-identity.ts +74 -0
- package/src/api/project-open.ts +355 -0
- package/src/api/project-source.ts +162 -0
- package/src/api/project-state.ts +107 -0
- package/src/api/relay.ts +268 -0
- package/src/api/settings.ts +36 -0
- package/src/api/themes.ts +45 -0
- package/src/api/worktrees.ts +257 -0
- package/src/asset-compare-core.ts +171 -0
- package/src/asset-compare.ts +294 -0
- package/src/asset-editor-context.tsx +100 -0
- package/src/asset-events.ts +97 -0
- package/src/asset-inspector-actions.ts +87 -0
- package/src/asset-preview-framing.ts +357 -0
- package/src/asset-preview.ts +2802 -0
- package/src/asset-selection-viewer-registry.ts +113 -0
- package/src/asset-selection.ts +133 -0
- package/src/asset-workflow/asset-capabilities.ts +344 -0
- package/src/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/asset-workflow/asset-ledger.ts +156 -0
- package/src/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/asset-workflow/asset-types.ts +142 -0
- package/src/asset-workflow/asset-workflow-quality.ts +61 -0
- package/src/asset-workflow/audio-preview-player.ts +193 -0
- package/src/asset-workflow/audio-waveform.ts +22 -0
- package/src/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/asset-workflow/folder-preview.ts +345 -0
- package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/asset-workflow/image-view-scale.ts +32 -0
- package/src/asset-workflow/import-contract.ts +124 -0
- package/src/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/asset-workflow/model-inspection.ts +818 -0
- package/src/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +139 -0
- package/src/asset-workflow/project-asset-operations.ts +154 -0
- package/src/asset-workflow/project-asset-roots.ts +68 -0
- package/src/asset-workflow/project-content.ts +288 -0
- package/src/asset-workflow/project-source-index.ts +550 -0
- package/src/asset-workflow/thumbnail-system.ts +256 -0
- package/src/authoring/active-adapter.ts +199 -0
- package/src/authoring/active-systems.ts +422 -0
- package/src/authoring/adapter-key.ts +18 -0
- package/src/authoring/authoring-asset-url.ts +27 -0
- package/src/authoring/bootstrap-state.ts +49 -0
- package/src/authoring/boundary-authoring-adapter.ts +184 -0
- package/src/authoring/breakpoint-state.ts +43 -0
- package/src/authoring/canvas-scene-guides.ts +76 -0
- package/src/authoring/component-instance-root.ts +171 -0
- package/src/authoring/composite-authoring-adapter.ts +2110 -0
- package/src/authoring/consumer-actions.ts +520 -0
- package/src/authoring/css-numeric-style.ts +97 -0
- package/src/authoring/design-time-layers.ts +849 -0
- package/src/authoring/design-time-mount-registry.ts +235 -0
- package/src/authoring/design-time-settle.ts +343 -0
- package/src/authoring/edit-mode-authoring.ts +619 -0
- package/src/authoring/eyedropper-session.ts +60 -0
- package/src/authoring/instance-source-menu-register.ts +13 -0
- package/src/authoring/instance-source-menu.ts +135 -0
- package/src/authoring/layered-pick.ts +183 -0
- package/src/authoring/live-gesture-lock.ts +50 -0
- package/src/authoring/live-object-transform.ts +62 -0
- package/src/authoring/mount-failure-report.ts +154 -0
- package/src/authoring/mounted-root-subjects.ts +144 -0
- package/src/authoring/no-authoring-adapter.ts +55 -0
- package/src/authoring/null-inspection-subjects.tsx +75 -0
- package/src/authoring/object3d-document-persistence.ts +106 -0
- package/src/authoring/object3d-document-session-registry.ts +120 -0
- package/src/authoring/object3d-document-session.ts +1326 -0
- package/src/authoring/object3d-gesture-controller.ts +113 -0
- package/src/authoring/panel-authoring.ts +121 -0
- package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
- package/src/authoring/prefab-instance-section-model.ts +55 -0
- package/src/authoring/project-authoring-session.ts +105 -0
- package/src/authoring/provenance.ts +99 -0
- package/src/authoring/quarks-particle-systems.ts +19 -0
- package/src/authoring/react-canvas-navigation.ts +255 -0
- package/src/authoring/react-design-canvas-style.ts +20 -0
- package/src/authoring/react-story-board.ts +937 -0
- package/src/authoring/selection-scope.ts +195 -0
- package/src/authoring/shell-document-ops.ts +169 -0
- package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
- package/src/authoring/shell-viewport-policy.ts +48 -0
- package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/authoring/stories-scope.ts +35 -0
- package/src/authoring/story-board-chrome-fit.ts +107 -0
- package/src/authoring/story-board-presentation.ts +111 -0
- package/src/authoring/three-projection-core.ts +226 -0
- package/src/authoring/viewport-pick-context.ts +40 -0
- package/src/authoring/viewport-raycast.ts +240 -0
- package/src/authoring/viewport-tool-context.ts +73 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +152 -0
- package/src/authoring/world-pan-state.ts +198 -0
- package/src/authoring/world-session-state.ts +101 -0
- package/src/authoring/write-pipe.ts +173 -0
- package/src/availability-tick.ts +66 -0
- package/src/bitmap-label.ts +120 -0
- package/src/blender-tab-metrics.ts +161 -0
- package/src/board-open-actions.ts +20 -0
- package/src/boot-routing.ts +391 -0
- package/src/build-identity.ts +15 -0
- package/src/bytes-codec.ts +62 -0
- package/src/camera-authoring.ts +175 -0
- package/src/cancellation-reason.ts +58 -0
- package/src/canvas-preview-frames.ts +482 -0
- package/src/capture-camera-pose.ts +77 -0
- package/src/chrome-registry.ts +159 -0
- package/src/chrome-slot-registry.ts +91 -0
- package/src/collaboration-client.ts +264 -0
- package/src/collaboration-presence.ts +41 -0
- package/src/command-dispatch.ts +19 -0
- package/src/command-listener.ts +2743 -0
- package/src/command-registry.ts +70 -0
- package/src/component-board-registry.ts +205 -0
- package/src/component-states-registry.ts +200 -0
- package/src/components/AgentPresentationNotice.tsx +75 -0
- package/src/components/AlignToolbar.tsx +204 -0
- package/src/components/AppRoot.tsx +394 -0
- package/src/components/ApplicationMenus.tsx +415 -0
- package/src/components/AssetBrowser.tsx +2843 -0
- package/src/components/AssetEditorShell.tsx +216 -0
- package/src/components/AssetInspectorToolSection.tsx +124 -0
- package/src/components/BoardRulers.tsx +354 -0
- package/src/components/CameraInfo.tsx +81 -0
- package/src/components/CanvasSceneViewport.tsx +556 -0
- package/src/components/CapabilityCoverageSection.tsx +185 -0
- package/src/components/CenterDocuments.tsx +22 -0
- package/src/components/ChromeSlot.tsx +20 -0
- package/src/components/CodeView.tsx +470 -0
- package/src/components/CollaborationHeaderControl.css +74 -0
- package/src/components/CompactInspectorCard.tsx +190 -0
- package/src/components/CompactInspectorShell.tsx +39 -0
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +270 -0
- package/src/components/DefaultEditorLayout.tsx +106 -0
- package/src/components/DocumentHeaderStrip.tsx +96 -0
- package/src/components/DocumentShelfRail.tsx +45 -0
- package/src/components/DocumentThumbnail.tsx +345 -0
- package/src/components/EditorLeaseGuard.tsx +372 -0
- package/src/components/ErrorBanner.tsx +123 -0
- package/src/components/FolderPreviewTile.tsx +159 -0
- package/src/components/GameHierarchy.tsx +3842 -0
- package/src/components/GameplaySessionTimeline.tsx +295 -0
- package/src/components/InspectionProjection.tsx +968 -0
- package/src/components/Inspector.tsx +233 -0
- package/src/components/InspectorCanvasPreview.tsx +35 -0
- package/src/components/InspectorFieldsSection.tsx +290 -0
- package/src/components/InspectorObjectPreview.tsx +57 -0
- package/src/components/InspectorStoriesSection.tsx +92 -0
- package/src/components/InspectorToolSection.tsx +96 -0
- package/src/components/InspectorTransformSection.tsx +245 -0
- package/src/components/LightExplorerPanel.tsx +433 -0
- package/src/components/MediaProperties.tsx +145 -0
- package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
- package/src/components/Object3DDocumentToolbar.css +100 -0
- package/src/components/Object3DDocumentToolbar.tsx +454 -0
- package/src/components/Object3DDocumentViewport.tsx +58 -0
- package/src/components/ProjectHeader.css +617 -0
- package/src/components/ProjectHeader.tsx +328 -0
- package/src/components/ProjectLayout.tsx +249 -0
- package/src/components/ReactCanvasControls.tsx +284 -0
- package/src/components/RootSelectionOverlay.tsx +3162 -0
- package/src/components/RootTextEditor.tsx +79 -0
- package/src/components/SaveStatus.tsx +70 -0
- package/src/components/StageHost.tsx +1954 -0
- package/src/components/StageOverlays.tsx +21 -0
- package/src/components/StartupErrorScreen.tsx +76 -0
- package/src/components/StartupLoadingScreen.tsx +65 -0
- package/src/components/StatsOverlay.tsx +78 -0
- package/src/components/SurfaceStateOverlay.tsx +24 -0
- package/src/components/ToolContributionSurfaces.tsx +65 -0
- package/src/components/ToolHost.tsx +370 -0
- package/src/components/ToolObject3DPreview.tsx +39 -0
- package/src/components/Toolbar.tsx +595 -0
- package/src/components/TransientHint.tsx +44 -0
- package/src/components/TransportStrip.tsx +174 -0
- package/src/components/VersionControlSection.tsx +470 -0
- package/src/components/VgaiLogo.css +83 -0
- package/src/components/VgaiLogo.tsx +35 -0
- package/src/components/ViewportControlsHint.tsx +60 -0
- package/src/components/ViewportFurniture.tsx +401 -0
- package/src/components/ViewportOverlay.tsx +145 -0
- package/src/components/ViewportOverlaysMenu.tsx +153 -0
- package/src/components/ViewportShadingMenu.tsx +300 -0
- package/src/components/ViewportViewMenu.tsx +101 -0
- package/src/components/WorkspaceDocumentSurface.tsx +204 -0
- package/src/components/WorkspaceUtilitySurface.tsx +22 -0
- package/src/components/WorktreeSwitcher.css +239 -0
- package/src/components/WorktreeSwitcher.tsx +890 -0
- package/src/components/account-documents.tsx +1162 -0
- package/src/components/asset-documents.tsx +812 -0
- package/src/components/asset-editor-persistence.ts +216 -0
- package/src/components/asset-selection-section.tsx +535 -0
- package/src/components/asset-thumbnails.tsx +312 -0
- package/src/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
- package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
- package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
- package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
- package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
- package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
- package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
- package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/components/asset-viewers/shader-source.ts +144 -0
- package/src/components/asset-workflow.css +731 -0
- package/src/components/board-guides.ts +150 -0
- package/src/components/compact-inspector.css +539 -0
- package/src/components/core-utilities.tsx +90 -0
- package/src/components/editor-notifications.css +40 -0
- package/src/components/engine-workspace.css +274 -0
- package/src/components/inspector-preview-section.tsx +238 -0
- package/src/components/inspector-property-grouping.ts +64 -0
- package/src/components/inspector-revert-label.ts +20 -0
- package/src/components/inspector-selection.ts +42 -0
- package/src/components/inspector-stories-gating.ts +171 -0
- package/src/components/inspector-transform-subject.ts +11 -0
- package/src/components/inspector-transform.ts +75 -0
- package/src/components/kind-documents.tsx +500 -0
- package/src/components/palette-action-publisher.tsx +140 -0
- package/src/components/primitives/DraftColorInput.tsx +74 -0
- package/src/components/product-shell.css +1829 -0
- package/src/components/project-tool-documents.tsx +410 -0
- package/src/components/scene-documents.tsx +223 -0
- package/src/components/stage-keyboard.tsx +37 -0
- package/src/components/stage-overlay-set.tsx +107 -0
- package/src/components/stage-presence-markers.ts +478 -0
- package/src/components/standard-viewport-dressing.ts +417 -0
- package/src/components/status-contributions.tsx +403 -0
- package/src/components/tool-documents.tsx +309 -0
- package/src/components/tool-schema-form.tsx +262 -0
- package/src/components/use-after-paint.ts +41 -0
- package/src/components/use-project-image-assets.ts +86 -0
- package/src/components/viewport-header-controls.css +90 -0
- package/src/components/viewport-surface-status.tsx +55 -0
- package/src/components/workspace-history.ts +32 -0
- package/src/components/workspace-static-panel-registry.tsx +152 -0
- package/src/components/workspace-surfaces.css +621 -0
- package/src/components/world-documents.tsx +586 -0
- package/src/components/world-overlay-gestures.ts +1694 -0
- package/src/composite-screenshot.ts +1641 -0
- package/src/console-sync.ts +131 -0
- package/src/constraint-helper.ts +338 -0
- package/src/content-entry-source-registry.ts +184 -0
- package/src/coverage/authoring-seam-evidence.ts +300 -0
- package/src/coverage/canvas-reveal.ts +192 -0
- package/src/coverage/design-time-surfaces.ts +101 -0
- package/src/coverage/live-seam-evidence.ts +11 -0
- package/src/coverage/ontology-invariants.ts +466 -0
- package/src/coverage/session-vitals.ts +501 -0
- package/src/coverage/system-seam-evidence.ts +72 -0
- package/src/crash-null-boundary.ts +36 -0
- package/src/creation-site-edit.ts +1479 -0
- package/src/creation-site-registry.ts +160 -0
- package/src/delegate-harness-registry.ts +143 -0
- package/src/document-context-registry.ts +177 -0
- package/src/document-open-registry.ts +200 -0
- package/src/document-preview-source.ts +20 -0
- package/src/document-renderer-session.ts +138 -0
- package/src/editor-api.ts +46 -0
- package/src/editor-chrome-capture.ts +139 -0
- package/src/editor-commands.ts +164 -0
- package/src/editor-console.ts +563 -0
- package/src/editor-current-view.ts +82 -0
- package/src/editor-document-probe.ts +881 -0
- package/src/editor-git-client.ts +115 -0
- package/src/editor-host-door.ts +462 -0
- package/src/editor-hotkeys.ts +842 -0
- package/src/editor-lease-view.ts +39 -0
- package/src/editor-lease.ts +415 -0
- package/src/editor-mode.ts +19 -0
- package/src/editor-notifications.ts +140 -0
- package/src/editor-presence.ts +563 -0
- package/src/editor-presentation-activity.ts +50 -0
- package/src/editor-presentation-notice.ts +42 -0
- package/src/editor-runtime.tsx +145 -0
- package/src/editor-server-response.ts +86 -0
- package/src/editor-session-attribution.ts +75 -0
- package/src/editor-session-mode.ts +54 -0
- package/src/editor-shell-store.ts +1304 -0
- package/src/editor-state-facets.ts +74 -0
- package/src/editor-styles.css +33 -0
- package/src/editor-view-presentation.ts +810 -0
- package/src/editor-viewport.ts +5302 -0
- package/src/entity-lod.ts +31 -0
- package/src/entity-object.ts +91 -0
- package/src/files/file-provider.ts +62 -0
- package/src/files/project-files.ts +264 -0
- package/src/finders/index.ts +136 -0
- package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/frame/bridge.tsx +1408 -0
- package/src/frame/product.ts +77 -0
- package/src/gameplay-dom-recording.ts +319 -0
- package/src/gameplay-export-state.ts +14 -0
- package/src/gameplay-replay.ts +417 -0
- package/src/gameplay-session-time.ts +9 -0
- package/src/gameplay-sessions.ts +204 -0
- package/src/harness-chat-types.ts +253 -0
- package/src/hierarchy-component-marks.ts +298 -0
- package/src/hierarchy-drop.ts +91 -0
- package/src/hierarchy-expansion-state.ts +80 -0
- package/src/hierarchy-header-slot.ts +52 -0
- package/src/hierarchy-internals.ts +197 -0
- package/src/hierarchy-kind-icon.ts +217 -0
- package/src/hierarchy-mark-reader.ts +73 -0
- package/src/hierarchy-menu-registry.ts +67 -0
- package/src/hierarchy-node-rows.ts +307 -0
- package/src/hierarchy-panel-view.ts +280 -0
- package/src/hierarchy-projection.ts +76 -0
- package/src/hierarchy-row-cache.ts +243 -0
- package/src/hierarchy-row-model.ts +308 -0
- package/src/hierarchy-rows.ts +11 -0
- package/src/hierarchy-walk.ts +86 -0
- package/src/history/editor-session.ts +25 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-delegate.ts +163 -0
- package/src/history/history-limit-notices.ts +43 -0
- package/src/history/history-service.ts +1173 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +386 -0
- package/src/history/project-root-history-backends.ts +139 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +103 -0
- package/src/history/source-history-backend.ts +546 -0
- package/src/history/types.ts +124 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hotkeys.ts +188 -0
- package/src/inference-diagnostics.ts +69 -0
- package/src/initial-project.ts +80 -0
- package/src/inspection/active-subject.ts +578 -0
- package/src/inspection/active-surface.ts +142 -0
- package/src/inspection/compose.ts +1064 -0
- package/src/inspection/display.ts +170 -0
- package/src/inspection/document-subject.ts +109 -0
- package/src/inspection/game-subject.ts +85 -0
- package/src/inspection/model.ts +542 -0
- package/src/inspection/null-subject.ts +115 -0
- package/src/inspection/serialize.ts +357 -0
- package/src/inspection/use-active-inspection.ts +180 -0
- package/src/inspector-presentation.ts +201 -0
- package/src/inspector-section-registry.ts +221 -0
- package/src/instance-source-actions.ts +163 -0
- package/src/instanced-presentation.ts +164 -0
- package/src/js-heap.ts +71 -0
- package/src/key-actions.ts +91 -0
- package/src/keymap-presets.ts +401 -0
- package/src/layout-policy.ts +31 -0
- package/src/learn-links.ts +73 -0
- package/src/light-explorer-model.ts +134 -0
- package/src/live-canvas-frame.ts +55 -0
- package/src/live-document.ts +279 -0
- package/src/live-module-source.ts +230 -0
- package/src/live-session-registry.ts +220 -0
- package/src/live-transition.ts +633 -0
- package/src/manifest-project.ts +107 -0
- package/src/model-thumbnail.ts +523 -0
- package/src/native-selection-style.ts +202 -0
- package/src/object3d-document-write-policy.ts +137 -0
- package/src/packaged-runtime.ts +108 -0
- package/src/palettes/maya.palette.json +57 -0
- package/src/palettes/substance.palette.json +57 -0
- package/src/pasteboard-module.ts +81 -0
- package/src/performance-profiler.ts +367 -0
- package/src/performance-sources.ts +69 -0
- package/src/play-boot-phase.ts +145 -0
- package/src/presentation-surface.ts +248 -0
- package/src/project-adapter.ts +1143 -0
- package/src/project-asset-refresh.ts +26 -0
- package/src/project-declaration-refresh.ts +31 -0
- package/src/project-local-state.ts +118 -0
- package/src/project-manager.ts +243 -0
- package/src/project-module-changes.ts +163 -0
- package/src/project-module-split.ts +266 -0
- package/src/project-provenance.ts +115 -0
- package/src/project-ready.ts +42 -0
- package/src/project-session-reset.ts +67 -0
- package/src/project-shape.ts +68 -0
- package/src/project-tool-discovery.ts +66 -0
- package/src/project-tools.ts +107 -0
- package/src/project-work-types.ts +149 -0
- package/src/projection/three.ts +898 -0
- package/src/projection/types.ts +44 -0
- package/src/readiness.ts +113 -0
- package/src/reflection-probe-helper.ts +142 -0
- package/src/reported-play-state.ts +90 -0
- package/src/resolve-relative-specifier.ts +33 -0
- package/src/scene-document-plan.ts +315 -0
- package/src/scene-framing.ts +315 -0
- package/src/scene-live-open.ts +210 -0
- package/src/scene-view-fog.ts +89 -0
- package/src/scoped-game-css.ts +152 -0
- package/src/session-orphan-record.ts +193 -0
- package/src/session-tombstone.ts +126 -0
- package/src/settings/settings-provider.ts +82 -0
- package/src/settings-store.ts +345 -0
- package/src/shared-view-restore.ts +42 -0
- package/src/shell-store-door.ts +45 -0
- package/src/source-conflict.ts +122 -0
- package/src/spatial-handle-visuals.ts +332 -0
- package/src/stage-context.ts +341 -0
- package/src/stage-store-registry.ts +63 -0
- package/src/stale-chunk-recovery.ts +34 -0
- package/src/state-report-deferral.ts +73 -0
- package/src/storage/host-files-storage.ts +97 -0
- package/src/storage/http-storage.ts +174 -0
- package/src/storage/index.ts +75 -0
- package/src/storage/mem-storage.ts +149 -0
- package/src/storage/path-lock.ts +44 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/types.ts +116 -0
- package/src/stories/StoryComponentThumbnail.tsx +184 -0
- package/src/stories/StoryPreviewMount.tsx +306 -0
- package/src/stories/component-content-source.tsx +106 -0
- package/src/stories/component-states-source.ts +78 -0
- package/src/stories/compose-project-stories.ts +255 -0
- package/src/stories/pixi-story-model.ts +30 -0
- package/src/stories/prefabs-finder.ts +54 -0
- package/src/stories/prefabs-from-stories.ts +182 -0
- package/src/stories/project-story-discovery.ts +78 -0
- package/src/stories/project-story-regions.ts +24 -0
- package/src/stories/story-actions.ts +54 -0
- package/src/stories/story-capture-command.ts +124 -0
- package/src/stories/story-capture.ts +584 -0
- package/src/stories/story-declared-medium.ts +126 -0
- package/src/stories/story-discovery.ts +176 -0
- package/src/stories/story-document-openers.ts +36 -0
- package/src/stories/story-dom-runtime.ts +78 -0
- package/src/stories/story-grouping.ts +111 -0
- package/src/stories/story-lane.ts +88 -0
- package/src/stories/story-mount-turn.ts +27 -0
- package/src/stories/story-opener.ts +260 -0
- package/src/stories/story-pixi-preview.ts +408 -0
- package/src/stories/story-presentation.ts +215 -0
- package/src/stories/story-registry.ts +517 -0
- package/src/stories/story-three-preview.ts +807 -0
- package/src/stories/three-story-model.ts +96 -0
- package/src/story-three-preview-runtime.ts +56 -0
- package/src/surface-keyboard.ts +112 -0
- package/src/surface-state.ts +124 -0
- package/src/tab-bootstrap.js +628 -0
- package/src/tab-census.ts +198 -0
- package/src/tab-lifecycle-client.ts +196 -0
- package/src/theme-library.ts +844 -0
- package/src/theme-preference.ts +378 -0
- package/src/theme.css +3437 -0
- package/src/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/three-viewport/camera-fit.ts +41 -0
- package/src/three-viewport/interactive-renderer.ts +132 -0
- package/src/three-viewport/selection-brackets.ts +310 -0
- package/src/three-viewport/selection-outline.ts +183 -0
- package/src/three-viewport/skeleton-helper.ts +61 -0
- package/src/three-viewport/source-color.ts +167 -0
- package/src/three-viewport/studio-environment.ts +96 -0
- package/src/three-viewport-presentation.ts +23 -0
- package/src/tool-contribution-play.ts +74 -0
- package/src/tool-loader.ts +1798 -0
- package/src/transform-mode-request.ts +57 -0
- package/src/transient-hint.ts +78 -0
- package/src/trigger-volume-helper.ts +116 -0
- package/src/ui-source/adapter-region-includes.ts +241 -0
- package/src/ui-source/ensure-import.ts +132 -0
- package/src/ui-source/file-region-resolver.ts +302 -0
- package/src/ui-source/inspect.ts +775 -0
- package/src/ui-source/oid-transform.ts +1845 -0
- package/src/ui-source/plan-csf-story.ts +196 -0
- package/src/ui-source/plan-extract-component.ts +421 -0
- package/src/ui-source/plan-fork-component.ts +621 -0
- package/src/ui-source/plan-named-style.ts +95 -0
- package/src/ui-source/plan-source-edit.ts +353 -0
- package/src/ui-source/r3f-contract-resolver.ts +327 -0
- package/src/ui-source/r3f-diagnostic-index.ts +165 -0
- package/src/ui-source/r3f-environment-binding.ts +133 -0
- package/src/ui-source/r3f-joint-binding.ts +251 -0
- package/src/ui-source/r3f-lod-binding.ts +91 -0
- package/src/ui-source/r3f-particle-binding.ts +235 -0
- package/src/ui-source/r3f-physics-binding.ts +483 -0
- package/src/ui-source/r3f-project-contracts.ts +444 -0
- package/src/ui-source/relative-import-specifier.ts +34 -0
- package/src/ui-source/reparent-guard.ts +544 -0
- package/src/ui-source/source-edit-request.ts +97 -0
- package/src/ui-source/source-write-backend.ts +618 -0
- package/src/ui-source/syntactic-prop-specs.ts +296 -0
- package/src/ui-source/tier-source-write-backend.ts +279 -0
- package/src/ui-source/ts-ast.ts +171 -0
- package/src/ui-source/utility-class-support.ts +169 -0
- package/src/ui-source/write-component-default.ts +133 -0
- package/src/ui-source/writer.ts +1989 -0
- package/src/viewport-activation-timings.ts +840 -0
- package/src/viewport-authoring-policy.ts +186 -0
- package/src/viewport-controls-hint.ts +57 -0
- package/src/viewport-door.ts +237 -0
- package/src/viewport-shading-boundary.ts +12 -0
- package/src/vite-error-surface.ts +141 -0
- package/src/wait-until.ts +37 -0
- package/src/workspace-areas.ts +156 -0
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-available-documents.ts +127 -0
- package/src/workspace-core-utilities.ts +31 -0
- package/src/workspace-document-ids.ts +59 -0
- package/src/workspace-document-registry.ts +612 -0
- package/src/workspace-document-restore.ts +145 -0
- package/src/workspace-host-commands.ts +141 -0
- package/src/workspace-persistence-gate.ts +40 -0
- package/src/workspace-play-utilities.ts +44 -0
- package/src/workspace-presets.ts +425 -0
- package/src/workspace-regions.ts +294 -0
- package/src/workspace-state-persistence.ts +536 -0
- package/src/workspace-static-panels.ts +73 -0
- package/src/workspace-status-registry.ts +121 -0
- package/src/workspace-style.ts +239 -0
- package/src/workspace-utility-commands.ts +74 -0
- package/src/workspace-utility-registry.ts +263 -0
- package/src/workspace-viewport-rect.ts +97 -0
- package/src/world-adoption.ts +115 -0
- package/src/world-document-routing.ts +104 -0
- package/vite-plugin-creation-site-write.ts +184 -0
- package/vite-plugin-creation-site.ts +80 -0
- package/vite-plugin-game-static.ts +303 -0
- package/vite-plugin-module-doorways.ts +336 -0
- package/vite-plugin-product-contributions.ts +197 -0
- package/vite-plugin-project-game-static.ts +125 -0
- package/vite-plugin-project-jsx-js.ts +65 -0
- package/vite-plugin-project-root-absolute-assets.ts +359 -0
- package/vite-plugin-shared-react.ts +420 -0
- package/vite-plugin-shared-three.ts +203 -0
- package/vite-plugin-ui-oid.ts +2106 -0
|
@@ -0,0 +1,1304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EditorShellStore — the format-NEUTRAL half of the editor's central store.
|
|
3
|
+
*
|
|
4
|
+
* It holds exactly the format-neutral state: selection, the
|
|
5
|
+
* scene-adoption stack, the project-history handle, tool/viewport/play state, and the
|
|
6
|
+
* viewport action bus. It knows about `THREE.Object3D`s and ids — never about
|
|
7
|
+
* any document model or its persistence: an adapter's document is that
|
|
8
|
+
* adapter's private business, and the store names no document type at all
|
|
9
|
+
* (`authoring-inversion-guard.test.ts` is the tripwire).
|
|
10
|
+
*
|
|
11
|
+
* MEASURED: every authoring adapter touches exactly eight store members —
|
|
12
|
+
* `selectedEntityIds`, `selectMultiple`, `subscribe`, `notifyIngestEdit`,
|
|
13
|
+
* `objectMap`, `projectHistory`, `scene`, and `playState`. All eight are here.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ViewportTab } from '@volter/editor-sdk';
|
|
17
|
+
import { viewportCaptureOutputPass } from '@volter/editor-threejs/capture/output-pass';
|
|
18
|
+
import { isEditorOwnedObject } from '@volter/editor-threejs/viewport/editor-layers';
|
|
19
|
+
import type { WorldRendererConfig } from '@volter/editor-threejs/adapter/renderer-config';
|
|
20
|
+
import { getUserData, setUserData } from '@volter/editor-threejs/ecs/user-data';
|
|
21
|
+
import type { ViewportShadingMode } from '@volter/editor-threejs/render/viewport-shading';
|
|
22
|
+
import * as THREE from 'three';
|
|
23
|
+
import type { BatchedRenderer } from 'three.quarks';
|
|
24
|
+
import { findEntityLod } from './entity-lod';
|
|
25
|
+
import { entityIdOf } from './entity-object';
|
|
26
|
+
import type { HistoryService } from './history/history-service';
|
|
27
|
+
import { withSceneFogNeutralized } from './scene-view-fog';
|
|
28
|
+
|
|
29
|
+
/** The store's persistence collaborator — installed by the shell, never
|
|
30
|
+
* imported by the store (see `attachStatePersistence`). */
|
|
31
|
+
export interface EditorStatePersistence {
|
|
32
|
+
save(state: Record<string, unknown>): unknown;
|
|
33
|
+
saveThumbnail?(dataUrl: string): unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* WHICH TOOL THE SHELF HAS ARMED — four that draw a transform gizmo, and one
|
|
38
|
+
* that draws none.
|
|
39
|
+
*
|
|
40
|
+
* `'select'` is Blender's Select Box, and it is a TOOL rather than an absence:
|
|
41
|
+
* its shelf opens on it (`space_toolsystem_toolbar.py`,
|
|
42
|
+
* `_defs_view3d_generic.select_box` first in the Object Mode `_tools_default`;
|
|
43
|
+
* photographed at the engine's pin in `gizmo-select-box.png`), so a selected
|
|
44
|
+
* object shows its outline and nothing else until Move / Rotate / Scale /
|
|
45
|
+
* Transform is armed. Before it there was no "no gizmo" state at all — this
|
|
46
|
+
* store's default was `'combined'` and every selection drew three tools'
|
|
47
|
+
* handles at once — which is why it is a member here rather than a flag beside
|
|
48
|
+
* it. `editor-viewport.ts` detaches on it, through the same branch a
|
|
49
|
+
* non-writable selection already took.
|
|
50
|
+
*
|
|
51
|
+
* Which member a STAGE is born with is the look's (`workspace-regions.ts`'s
|
|
52
|
+
* `shelfTool`); arming one is always a click in the shelf.
|
|
53
|
+
*/
|
|
54
|
+
export type TransformMode = 'select' | 'combined' | 'translate' | 'rotate' | 'scale';
|
|
55
|
+
export type TransformSpace = 'world' | 'local';
|
|
56
|
+
export type PivotMode = 'active-element' | 'median-point' | 'individual-origins';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* WHERE the transform gizmo is drawn for a single selection — the standard DCC
|
|
60
|
+
* affordance, and PRESENTATION ONLY: an edit made about the bounds centre writes
|
|
61
|
+
* the same source values it would about the pivot (`editor-viewport.ts`).
|
|
62
|
+
*
|
|
63
|
+
* `auto` is the default because the right answer is a property of the SUBJECT,
|
|
64
|
+
* not a standing preference: an ordinary node's pivot is where its content is,
|
|
65
|
+
* and a world-anchored instanced system's is not (`instanced-presentation.ts`) —
|
|
66
|
+
* that one draws its gizmo at (0,0,0) with every unit the reader can see
|
|
67
|
+
* somewhere else. Both explicit values remain reachable, because "put the gizmo
|
|
68
|
+
* back on the actual origin" is a legitimate thing to want on exactly that node.
|
|
69
|
+
*/
|
|
70
|
+
export type GizmoAnchor = 'auto' | 'pivot' | 'center';
|
|
71
|
+
|
|
72
|
+
export type ShadingMode = ViewportShadingMode;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What a store notification says CHANGED.
|
|
76
|
+
*
|
|
77
|
+
* `'selection'` is the narrow one and it is claimed by exactly five writers
|
|
78
|
+
* (`select`, `selectMultiple`, `addToSelection`, `toggleSelection`,
|
|
79
|
+
* `applySelectionBeforePresentation`), each of which touches
|
|
80
|
+
* `_viewportSelections` and nothing else. `'content'` is the default and means
|
|
81
|
+
* "assume anything may have changed", so a mutation that forgets to classify
|
|
82
|
+
* itself is safe by construction — the failure mode of a wrong claim is a
|
|
83
|
+
* stale panel, so the burden of proof sits on the narrow value.
|
|
84
|
+
* See {@link EditorShellStore.contentVersion}.
|
|
85
|
+
*/
|
|
86
|
+
export type NotifyScope = 'content' | 'selection';
|
|
87
|
+
type NotifyConcern = 'shell' | 'object-map';
|
|
88
|
+
|
|
89
|
+
/** Exact structural delta supplied by a live Three projection mutation. */
|
|
90
|
+
export interface IngestObjectMapStructureDelta {
|
|
91
|
+
readonly changedParentIds: ReadonlySet<string>;
|
|
92
|
+
readonly rootsChanged: boolean;
|
|
93
|
+
/** Exact projected membership changes; omission keeps legacy callers conservative. */
|
|
94
|
+
readonly addedIds?: ReadonlySet<string>;
|
|
95
|
+
readonly removedIds?: ReadonlySet<string>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** All projected parent rows changed after a previously observed gizmo epoch. */
|
|
99
|
+
export interface IngestObjectMapChanges {
|
|
100
|
+
readonly epoch: number;
|
|
101
|
+
readonly changedParentIds: ReadonlySet<string>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Exact object-map identities whose membership changed after an observed gizmo epoch. */
|
|
105
|
+
export interface IngestObjectMapMembershipChanges {
|
|
106
|
+
readonly epoch: number;
|
|
107
|
+
readonly changedIds: ReadonlySet<string>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Enough exact structural epochs to span deferred React consumers; older readers rebuild. */
|
|
111
|
+
const OBJECT_MAP_MEMBERSHIP_HISTORY = 256;
|
|
112
|
+
|
|
113
|
+
/** The scope a coalesced pair of notifications must fire with: `'content'`
|
|
114
|
+
* unless BOTH were selection-only. */
|
|
115
|
+
function widenNotifyScope(current: NotifyScope, incoming: NotifyScope): NotifyScope {
|
|
116
|
+
return current === 'selection' && incoming === 'selection' ? 'selection' : 'content';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Which persistence regime the current play session exposes. D19 makes play
|
|
121
|
+
* edits ephemeral for every world count, and since the `.vgai/` overlay
|
|
122
|
+
* sidecar was deleted outright (2026-08-02) `ephemeral` is the ONLY regime
|
|
123
|
+
* there is. `null` while not playing.
|
|
124
|
+
*/
|
|
125
|
+
export type PlayEditRegime = 'ephemeral' | null;
|
|
126
|
+
|
|
127
|
+
/** The two viewport tabs, Edit · Play. Defined ONCE, in the SDK's shared
|
|
128
|
+
* vocabulary (`@volter/editor-sdk`), because the control API speaks it too;
|
|
129
|
+
* re-exported here so editor modules keep one import site. */
|
|
130
|
+
export type { ViewportTab };
|
|
131
|
+
|
|
132
|
+
export type AssetKind = 'model' | 'image' | 'video' | 'audio' | 'json' | 'source';
|
|
133
|
+
|
|
134
|
+
export interface OnlineAssetInfo {
|
|
135
|
+
source: 'polyhaven' | 'ambientcg' | 'local';
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
thumbnailUrl: string;
|
|
139
|
+
type: string;
|
|
140
|
+
tags: string[];
|
|
141
|
+
license?: string;
|
|
142
|
+
author?: string;
|
|
143
|
+
sourceUrl?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface HelperVisibility {
|
|
147
|
+
bounds: boolean;
|
|
148
|
+
lights: boolean;
|
|
149
|
+
cameras: boolean;
|
|
150
|
+
colliders: boolean;
|
|
151
|
+
joints: boolean;
|
|
152
|
+
particles: boolean;
|
|
153
|
+
lod: boolean;
|
|
154
|
+
audio: boolean;
|
|
155
|
+
splines: boolean;
|
|
156
|
+
navmesh: boolean;
|
|
157
|
+
constraints: boolean;
|
|
158
|
+
reflectionProbes: boolean;
|
|
159
|
+
triggerVolumes: boolean;
|
|
160
|
+
skeletons: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* The WEIGHT display — a mesh coloured by its active vertex group
|
|
163
|
+
* (`@volter/editor-blender`'s `blender-runtime-weights.ts`). Added 2026-09-19 (I4)
|
|
164
|
+
* because nothing in this set stood for it: `skeletons` is the bones, and
|
|
165
|
+
* Blender's own viewport overlay has a Bones checkbox but reaches weight
|
|
166
|
+
* colours through Weight Paint MODE, which an inspection surface has no
|
|
167
|
+
* brushes to enter. OFF by default, the way Blender shows no weights until
|
|
168
|
+
* you ask for them.
|
|
169
|
+
*/
|
|
170
|
+
weights: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export type ViewportAction =
|
|
174
|
+
| { type: 'focus-entity'; id: string }
|
|
175
|
+
| { type: 'focus-selection' }
|
|
176
|
+
| { type: 'focus-scene'; gameCamera?: () => THREE.Camera | null }
|
|
177
|
+
| { type: 'snap-selection-to-floor' }
|
|
178
|
+
| { type: 'set-view-preset'; preset: 'top' | 'front' | 'right' | 'perspective' }
|
|
179
|
+
| {
|
|
180
|
+
type: 'set-camera-pose';
|
|
181
|
+
position: { x: number; y: number; z: number };
|
|
182
|
+
target: { x: number; y: number; z: number };
|
|
183
|
+
fov?: number;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export class EditorShellStore {
|
|
187
|
+
// --- Scene graph references (set via bindScene) ---
|
|
188
|
+
protected _scene: THREE.Scene | null = null;
|
|
189
|
+
/** Adoption stack (see enterPlayScene): each frame is the scene state that
|
|
190
|
+
* was active when an adoption replaced it, restored in LIFO order by
|
|
191
|
+
* exitPlayScene. A STACK (not a single slot) because adoptions nest for
|
|
192
|
+
* R3F projects: the design session adopts its fiber scene at design time,
|
|
193
|
+
* and play adopts the live game scene OVER it while the play-entry
|
|
194
|
+
* transition still needs the design scene alive — the design session then
|
|
195
|
+
* unwinds its own frame from the middle via releaseAdoptedScene. */
|
|
196
|
+
protected _adoptionStack: Array<{
|
|
197
|
+
scene: THREE.Scene;
|
|
198
|
+
objectMap: Map<string, THREE.Object3D>;
|
|
199
|
+
/** Opaque payload from {@link _captureAdoptionExtras} — a document half's
|
|
200
|
+
* own per-adoption snapshot. The shell never inspects it. */
|
|
201
|
+
extras: unknown;
|
|
202
|
+
selection: Set<string>;
|
|
203
|
+
/** The image config that was active BEFORE this adoption — same LIFO
|
|
204
|
+
* discipline as `scene`, so a frame spliced out of the middle simply
|
|
205
|
+
* stops being a restore point. See {@link _applyAdoptedImageConfig}. */
|
|
206
|
+
imageConfig: WorldRendererConfig | undefined;
|
|
207
|
+
}> = [];
|
|
208
|
+
protected _objectMap = new Map<string, THREE.Object3D>();
|
|
209
|
+
protected _renderer: THREE.WebGLRenderer | null = null;
|
|
210
|
+
protected _camera: THREE.Camera | null = null;
|
|
211
|
+
protected _batchedRenderer: BatchedRenderer | null = null;
|
|
212
|
+
protected _lastThumbnailTime = 0;
|
|
213
|
+
protected _lastEditorStateSaveTime = 0;
|
|
214
|
+
protected _orbitTarget: THREE.Vector3 | null = null;
|
|
215
|
+
|
|
216
|
+
// --- UI state (not stored in scene graph) ---
|
|
217
|
+
/** Selection is viewport-local. The public selection accessors always expose
|
|
218
|
+
* the ACTIVE viewport's set, so Edit and Play never leak subjects into one
|
|
219
|
+
* another while existing authoring adapters keep the same store contract. */
|
|
220
|
+
protected _viewportSelections: Record<ViewportTab, Set<string>> = {
|
|
221
|
+
edit: new Set<string>(),
|
|
222
|
+
play: new Set<string>(),
|
|
223
|
+
};
|
|
224
|
+
protected _transformMode: TransformMode = 'combined';
|
|
225
|
+
protected _transformSpace: TransformSpace = 'world';
|
|
226
|
+
protected _snapEnabled = false;
|
|
227
|
+
protected _snapValues = { translate: 1, rotate: 15, scale: 0.25 };
|
|
228
|
+
protected _snapToSurface = false;
|
|
229
|
+
protected _preserveChildrenTransform = false;
|
|
230
|
+
protected _pivotMode: PivotMode = 'active-element';
|
|
231
|
+
protected _gizmoAnchor: GizmoAnchor = 'auto';
|
|
232
|
+
protected _showGrid = true;
|
|
233
|
+
protected _showHelpers = true;
|
|
234
|
+
protected _helperVisibility: HelperVisibility = {
|
|
235
|
+
bounds: false,
|
|
236
|
+
lights: true,
|
|
237
|
+
cameras: true,
|
|
238
|
+
colliders: true,
|
|
239
|
+
joints: true,
|
|
240
|
+
particles: true,
|
|
241
|
+
lod: true,
|
|
242
|
+
audio: true,
|
|
243
|
+
splines: true,
|
|
244
|
+
navmesh: true,
|
|
245
|
+
constraints: true,
|
|
246
|
+
reflectionProbes: true,
|
|
247
|
+
triggerVolumes: true,
|
|
248
|
+
skeletons: false,
|
|
249
|
+
weights: false,
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Bumped whenever gizmos must be re-applied without a structural signature
|
|
253
|
+
* change (W3a B1): a single-entity rebuild (`_rebuildEntity` →
|
|
254
|
+
* `updateEntityPreview`) disposes the old Object3D WITH all its helper
|
|
255
|
+
* children, but leaves the objectMap identity/size and every toggle
|
|
256
|
+
* untouched — the viewport's `gizmoSig` gate would otherwise skip both
|
|
257
|
+
* `applyEntityGizmos` and the per-type visibility pass until an unrelated
|
|
258
|
+
* event.
|
|
259
|
+
*/
|
|
260
|
+
protected _gizmoEpoch = 0;
|
|
261
|
+
/** Last structural epoch at which each projected parent's direct children changed. */
|
|
262
|
+
protected _objectMapParentChangedAt = new Map<string, number>();
|
|
263
|
+
/** A root change (or an unclassified legacy call) requires a full hierarchy read. */
|
|
264
|
+
protected _objectMapFullChangeEpoch = 0;
|
|
265
|
+
/** Last exact add/remove epoch for each projected id, independently consumed by the viewport. */
|
|
266
|
+
protected _objectMapMembershipChangedAt = new Map<string, number>();
|
|
267
|
+
/** Missing membership detail requires the viewport to reconcile its complete helper indexes. */
|
|
268
|
+
protected _objectMapMembershipFullChangeEpoch = 0;
|
|
269
|
+
/** Epochs older than this were compacted from the bounded membership history. */
|
|
270
|
+
protected _objectMapMembershipHistoryFloor = 0;
|
|
271
|
+
protected _showStats = false;
|
|
272
|
+
protected _shadingMode: ShadingMode = 'solid';
|
|
273
|
+
protected _history: HistoryService | null = null;
|
|
274
|
+
protected _statePersistence: EditorStatePersistence | null = null;
|
|
275
|
+
protected _playState: 'stopped' | 'playing' | 'paused' = 'stopped';
|
|
276
|
+
/** See {@link PlayEditRegime} — set by play-mode.ts on play-enter, cleared on play-exit. */
|
|
277
|
+
protected _playEditRegime: PlayEditRegime = null;
|
|
278
|
+
protected _activeViewportTab: ViewportTab = 'edit';
|
|
279
|
+
protected _vertexSnapActive = false;
|
|
280
|
+
protected _listeners = new Set<() => void>();
|
|
281
|
+
protected _viewportActionListeners = new Set<(action: ViewportAction) => void>();
|
|
282
|
+
/** A relayed selection command applies its value synchronously but lets the
|
|
283
|
+
* hierarchy/Inspector render on the next task, after the control ack has
|
|
284
|
+
* left the page. Ordinary UI writes never use this timer. */
|
|
285
|
+
protected _deferredNotifyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
286
|
+
/** The scope the pending deferred notify will fire with. A coalescing window
|
|
287
|
+
* that swallowed even one `'content'` write must fire as `'content'` — see
|
|
288
|
+
* {@link widenNotifyScope}. */
|
|
289
|
+
protected _deferredNotifyScope: NotifyScope = 'content';
|
|
290
|
+
protected _version = 0;
|
|
291
|
+
/**
|
|
292
|
+
* Stable across live object-map churn that cannot change shell presentation.
|
|
293
|
+
* Hierarchy/viewport consumers keep using {@link getSnapshot}; chrome that
|
|
294
|
+
* reads play state, selection, tools, or document state uses this narrower
|
|
295
|
+
* snapshot and therefore does not rerender for every runtime projectile.
|
|
296
|
+
*/
|
|
297
|
+
protected _shellVersion = 0;
|
|
298
|
+
/**
|
|
299
|
+
* Bumped only when post-processing-relevant state changes (environment / scene
|
|
300
|
+
* structure / play-stop scene swap). The viewport rebuilds its EffectComposer
|
|
301
|
+
* only when this changes — NOT on every notify — so a transform drag/scrub (which
|
|
302
|
+
* notifies per frame but never touches these) no longer recompiles shaders 60×/s.
|
|
303
|
+
*/
|
|
304
|
+
protected _composerVersion = 0;
|
|
305
|
+
/**
|
|
306
|
+
* Bumped on every notify EXCEPT one whose only change was WHICH NODES ARE
|
|
307
|
+
* SELECTED — see {@link contentVersion}.
|
|
308
|
+
*/
|
|
309
|
+
protected _contentVersion = 0;
|
|
310
|
+
/**
|
|
311
|
+
* Bumped when existing hierarchy-row presentation facets may have changed.
|
|
312
|
+
* Exact object-map membership deltas do not move it: newly admitted rows
|
|
313
|
+
* compute their facets on first render, while surviving rows keep the same
|
|
314
|
+
* source editability, warnings, and reflected property values.
|
|
315
|
+
*/
|
|
316
|
+
protected _hierarchyRowFacetVersion = 0;
|
|
317
|
+
bindScene(
|
|
318
|
+
scene: THREE.Scene,
|
|
319
|
+
renderer?: THREE.WebGLRenderer,
|
|
320
|
+
batchedRenderer?: BatchedRenderer | null,
|
|
321
|
+
camera?: THREE.Camera,
|
|
322
|
+
): void {
|
|
323
|
+
this._scene = scene;
|
|
324
|
+
this._renderer = renderer ?? null;
|
|
325
|
+
this._batchedRenderer = batchedRenderer ?? null;
|
|
326
|
+
if (camera) this._camera = camera;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Set the orbit controls target reference for camera state persistence. */
|
|
330
|
+
setOrbitTarget(target: THREE.Vector3): void {
|
|
331
|
+
this._orbitTarget = target;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Update only the editor presentation camera (for perspective/orthographic
|
|
335
|
+
* switching) without rebinding or replacing an adopted game scene. */
|
|
336
|
+
setViewportCamera(camera: THREE.Camera): void {
|
|
337
|
+
this._camera = camera;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// --- React useSyncExternalStore integration ---
|
|
341
|
+
subscribe = (listener: () => void): (() => void) => {
|
|
342
|
+
this._listeners.add(listener);
|
|
343
|
+
return () => this._listeners.delete(listener);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
getSnapshot = (): number => this._version;
|
|
347
|
+
|
|
348
|
+
getShellSnapshot = (): number => this._shellVersion;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The version a TREE-SCALE derivation caches against.
|
|
352
|
+
*
|
|
353
|
+
* `_version` moves on every notify, selection included, so a panel that keys
|
|
354
|
+
* a whole-tree walk on it re-walks the tree every time a human clicks a node
|
|
355
|
+
* — measured on the scale harness as 100-430ms of main-thread block per
|
|
356
|
+
* `select` on a 20 000-node world, in a verb whose reply came back in 3ms.
|
|
357
|
+
* The tree the walk reads did not change; only the highlight did.
|
|
358
|
+
*
|
|
359
|
+
* This counter therefore advances on every notify EXCEPT one whose sole
|
|
360
|
+
* change was the contents of `_viewportSelections` — the five selection
|
|
361
|
+
* writers below, and nothing else, notify with `'selection'`. Every other
|
|
362
|
+
* mutation (including `notifyIngestEdit`, history restores, play-state
|
|
363
|
+
* changes and viewport-tab switches) advances it, so a cache keyed on it can
|
|
364
|
+
* only ever be stale for a change that never happened. Subscribers still
|
|
365
|
+
* receive a selection notify and still re-render — this decides only what
|
|
366
|
+
* they are allowed to REUSE while doing so.
|
|
367
|
+
*/
|
|
368
|
+
get contentVersion(): number {
|
|
369
|
+
return this._contentVersion;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** See {@link _hierarchyRowFacetVersion}. */
|
|
373
|
+
get hierarchyRowFacetVersion(): number {
|
|
374
|
+
return this._hierarchyRowFacetVersion;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
get projectHistory(): HistoryService | null {
|
|
378
|
+
return this._history;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
protected _notify(
|
|
382
|
+
scope: NotifyScope = 'content',
|
|
383
|
+
concern: NotifyConcern = 'shell',
|
|
384
|
+
affectsHierarchyRowFacets = scope !== 'selection',
|
|
385
|
+
): void {
|
|
386
|
+
if (this._deferredNotifyTimer !== null) {
|
|
387
|
+
clearTimeout(this._deferredNotifyTimer);
|
|
388
|
+
this._deferredNotifyTimer = null;
|
|
389
|
+
}
|
|
390
|
+
this._beforeNotify();
|
|
391
|
+
this._version++;
|
|
392
|
+
if (concern === 'shell') this._shellVersion++;
|
|
393
|
+
if (scope !== 'selection') this._contentVersion++;
|
|
394
|
+
if (affectsHierarchyRowFacets) this._hierarchyRowFacetVersion++;
|
|
395
|
+
for (const fn of this._listeners) fn();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Coalesce control-plane presentation behind the command-result microtask.
|
|
399
|
+
* The state mutation has already landed when this is called. */
|
|
400
|
+
protected _notifyDeferred(scope: NotifyScope = 'content'): void {
|
|
401
|
+
if (this._deferredNotifyTimer !== null) {
|
|
402
|
+
this._deferredNotifyScope = widenNotifyScope(this._deferredNotifyScope, scope);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this._deferredNotifyScope = scope;
|
|
406
|
+
this._deferredNotifyTimer = setTimeout(() => {
|
|
407
|
+
this._deferredNotifyTimer = null;
|
|
408
|
+
const pending = this._deferredNotifyScope;
|
|
409
|
+
this._deferredNotifyScope = 'content';
|
|
410
|
+
this._notify(pending);
|
|
411
|
+
}, 0);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
attachHistory(history: HistoryService): void {
|
|
415
|
+
if (this._history === history) return;
|
|
416
|
+
if (this._history) throw new Error('EditorStore is already attached to project history.');
|
|
417
|
+
this._history = history;
|
|
418
|
+
this._onHistoryAttached();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Where view/tool state and the autosave thumbnail go. The shell installs
|
|
423
|
+
* its transport here (`EditorContext` binds the server SDK); the store
|
|
424
|
+
* itself names no endpoint, so a document-local store (Asset Lab) or a
|
|
425
|
+
* fixture store simply never persists. Same collaborator shape as
|
|
426
|
+
* `attachHistory`, same one-owner rule.
|
|
427
|
+
*/
|
|
428
|
+
attachStatePersistence(persistence: EditorStatePersistence): void {
|
|
429
|
+
if (this._statePersistence === persistence) return;
|
|
430
|
+
if (this._statePersistence) {
|
|
431
|
+
throw new Error('EditorStore is already attached to a state persistence.');
|
|
432
|
+
}
|
|
433
|
+
this._statePersistence = persistence;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
onViewportAction = (fn: (action: ViewportAction) => void): (() => void) => {
|
|
437
|
+
this._viewportActionListeners.add(fn);
|
|
438
|
+
return () => this._viewportActionListeners.delete(fn);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
protected _emitViewportAction(action: ViewportAction): void {
|
|
442
|
+
for (const fn of this._viewportActionListeners) fn(action);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Focus the viewport camera on a specific entity. */
|
|
446
|
+
focusOnEntity(id: string): void {
|
|
447
|
+
this._emitViewportAction({ type: 'focus-entity', id });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Focus the viewport camera on the current selection. */
|
|
451
|
+
focusOnSelection(): void {
|
|
452
|
+
this._emitViewportAction({ type: 'focus-selection' });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Frame the whole active scene — the first thing a reader should see when a
|
|
457
|
+
* world they did not author is adopted (an ingest mount). The viewport keeps
|
|
458
|
+
* asking until the world HAS content, because a game builds itself
|
|
459
|
+
* asynchronously; any camera input from the reader cancels it (see
|
|
460
|
+
* `the world root's stage`). A no-op for a scene the reader already has a camera on.
|
|
461
|
+
*
|
|
462
|
+
* `gameCamera` is a LIVE LOOKUP, not a camera: an adopted game's own camera
|
|
463
|
+
* may not exist yet on the frame it is captured on, so the viewport asks
|
|
464
|
+
* again while its window is open. When one turns up, its view is adopted
|
|
465
|
+
* instead of any measurement — see `scene-framing.ts`.
|
|
466
|
+
*/
|
|
467
|
+
focusOnScene(gameCamera?: () => THREE.Camera | null): void {
|
|
468
|
+
this._emitViewportAction({
|
|
469
|
+
type: 'focus-scene',
|
|
470
|
+
...(gameCamera ? { gameCamera } : {}),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Switch the viewport camera to a preset view (top, front, right, perspective). */
|
|
475
|
+
setViewPreset(preset: 'top' | 'front' | 'right' | 'perspective'): void {
|
|
476
|
+
this._emitViewportAction({ type: 'set-view-preset', preset });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Move the viewport camera to an arbitrary position/target/fov pose. */
|
|
480
|
+
setCameraPose(
|
|
481
|
+
position: { x: number; y: number; z: number },
|
|
482
|
+
target: { x: number; y: number; z: number },
|
|
483
|
+
fov?: number,
|
|
484
|
+
): void {
|
|
485
|
+
this._emitViewportAction({
|
|
486
|
+
type: 'set-camera-pose',
|
|
487
|
+
position,
|
|
488
|
+
target,
|
|
489
|
+
...(fov !== undefined ? { fov } : {}),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Live viewport camera pose, read straight from the bound Three.js camera +
|
|
495
|
+
* orbit target (`bindScene`/`setOrbitTarget` — wired by `world-root-stage.ts`).
|
|
496
|
+
* Null only when nothing has bound a camera yet (e.g. a headless store in a
|
|
497
|
+
* unit test that never mounted `the world root's stage`).
|
|
498
|
+
*/
|
|
499
|
+
get cameraPose(): { position: THREE.Vector3; target: THREE.Vector3; fov: number } | null {
|
|
500
|
+
if (!this._camera) return null;
|
|
501
|
+
const fov = (this._camera as THREE.PerspectiveCamera).fov;
|
|
502
|
+
return {
|
|
503
|
+
position: this._camera.position.clone(),
|
|
504
|
+
target: this._orbitTarget ? this._orbitTarget.clone() : new THREE.Vector3(),
|
|
505
|
+
fov: typeof fov === 'number' ? fov : 0,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// --- Scene graph accessors ---
|
|
510
|
+
|
|
511
|
+
/** The object map: entity ID → Three.js Object3D. */
|
|
512
|
+
get objectMap(): Map<string, THREE.Object3D> {
|
|
513
|
+
return this._objectMap;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** The bound Three.js scene. */
|
|
517
|
+
get scene(): THREE.Scene | null {
|
|
518
|
+
return this._scene;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Monotonic counter bumped when post-processing inputs change (see field doc). */
|
|
522
|
+
get composerVersion(): number {
|
|
523
|
+
return this._composerVersion;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// --- Selection ---
|
|
527
|
+
get selectedEntityId(): string | null {
|
|
528
|
+
const selection = this._viewportSelections[this._activeViewportTab];
|
|
529
|
+
if (selection.size === 0) return null;
|
|
530
|
+
return [...selection].at(-1)!;
|
|
531
|
+
}
|
|
532
|
+
get selectedEntityIds(): ReadonlySet<string> {
|
|
533
|
+
return this._viewportSelections[this._activeViewportTab];
|
|
534
|
+
}
|
|
535
|
+
get transformMode(): TransformMode {
|
|
536
|
+
return this._transformMode;
|
|
537
|
+
}
|
|
538
|
+
get transformSpace(): TransformSpace {
|
|
539
|
+
return this._transformSpace;
|
|
540
|
+
}
|
|
541
|
+
get snapEnabled(): boolean {
|
|
542
|
+
return this._snapEnabled;
|
|
543
|
+
}
|
|
544
|
+
get snapValues() {
|
|
545
|
+
return this._snapValues;
|
|
546
|
+
}
|
|
547
|
+
get snapToSurface(): boolean {
|
|
548
|
+
return this._snapToSurface;
|
|
549
|
+
}
|
|
550
|
+
get preserveChildrenTransform(): boolean {
|
|
551
|
+
return this._preserveChildrenTransform;
|
|
552
|
+
}
|
|
553
|
+
get pivotMode(): PivotMode {
|
|
554
|
+
return this._pivotMode;
|
|
555
|
+
}
|
|
556
|
+
get gizmoAnchor(): GizmoAnchor {
|
|
557
|
+
return this._gizmoAnchor;
|
|
558
|
+
}
|
|
559
|
+
get canUndo(): boolean {
|
|
560
|
+
return this._history?.getSnapshot().canUndo ?? false;
|
|
561
|
+
}
|
|
562
|
+
get canRedo(): boolean {
|
|
563
|
+
return this._history?.getSnapshot().canRedo ?? false;
|
|
564
|
+
}
|
|
565
|
+
get showGrid(): boolean {
|
|
566
|
+
return this._showGrid;
|
|
567
|
+
}
|
|
568
|
+
get showHelpers(): boolean {
|
|
569
|
+
return this._showHelpers;
|
|
570
|
+
}
|
|
571
|
+
get helperVisibility(): Readonly<HelperVisibility> {
|
|
572
|
+
return this._helperVisibility;
|
|
573
|
+
}
|
|
574
|
+
/** See {@link _gizmoEpoch} — folded into the viewport's gizmo signature. */
|
|
575
|
+
get gizmoEpoch(): number {
|
|
576
|
+
return this._gizmoEpoch;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Return the exact projected parents changed after `epoch`, or `null` when a
|
|
581
|
+
* root/unclassified change crossed that boundary and callers must rebuild.
|
|
582
|
+
* Parent timestamps make this safe across React batching: no consumer drains
|
|
583
|
+
* a shared queue, and several projectile commits collapse into one union.
|
|
584
|
+
*/
|
|
585
|
+
ingestObjectMapChangesSince(epoch: number): IngestObjectMapChanges | null {
|
|
586
|
+
if (epoch < 0 || epoch > this._gizmoEpoch || epoch < this._objectMapFullChangeEpoch) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
const changedParentIds = new Set<string>();
|
|
590
|
+
for (const [id, changedAt] of this._objectMapParentChangedAt) {
|
|
591
|
+
if (changedAt > epoch) changedParentIds.add(id);
|
|
592
|
+
}
|
|
593
|
+
return { epoch: this._gizmoEpoch, changedParentIds };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Return exact added/removed identities since `epoch`, or `null` across an unclassified edit. */
|
|
597
|
+
ingestObjectMapMembershipChangesSince(epoch: number): IngestObjectMapMembershipChanges | null {
|
|
598
|
+
if (
|
|
599
|
+
epoch < 0 ||
|
|
600
|
+
epoch > this._gizmoEpoch ||
|
|
601
|
+
epoch < this._objectMapMembershipFullChangeEpoch ||
|
|
602
|
+
epoch < this._objectMapMembershipHistoryFloor
|
|
603
|
+
) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
const changedIds = new Set<string>();
|
|
607
|
+
for (const [id, changedAt] of this._objectMapMembershipChangedAt) {
|
|
608
|
+
if (changedAt > epoch) changedIds.add(id);
|
|
609
|
+
}
|
|
610
|
+
return { epoch: this._gizmoEpoch, changedIds };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
isSkeletonVisible(id: string): boolean {
|
|
614
|
+
return Boolean(getUserData(this._objectMap.get(id), 'skeletonVisible'));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
setSkeletonVisible(id: string, visible: boolean): void {
|
|
618
|
+
const object = this._objectMap.get(id);
|
|
619
|
+
if (!object) return;
|
|
620
|
+
setUserData(object, 'skeletonVisible', visible);
|
|
621
|
+
object.traverse((child) => {
|
|
622
|
+
if (getUserData(child, 'editorHelperType') !== 'skeletons') return;
|
|
623
|
+
setUserData(child, 'skeletonEnabled', visible);
|
|
624
|
+
child.visible = visible || this._helperVisibility.skeletons;
|
|
625
|
+
});
|
|
626
|
+
this._notify();
|
|
627
|
+
}
|
|
628
|
+
get showStats(): boolean {
|
|
629
|
+
return this._showStats;
|
|
630
|
+
}
|
|
631
|
+
get shadingMode(): ShadingMode {
|
|
632
|
+
return this._shadingMode;
|
|
633
|
+
}
|
|
634
|
+
/** Notify subscribers after an ingest adapter mutated a live foreign object
|
|
635
|
+
* (material/visibility). Runtime-only — no dirty/autosave, because the edit
|
|
636
|
+
* lives on the live `Object3D` and nothing here owns a document. */
|
|
637
|
+
notifyIngestEdit(): void {
|
|
638
|
+
this._notify();
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Notify after a live adapter changed `objectMap` membership.
|
|
642
|
+
*
|
|
643
|
+
* Unlike an ordinary reflected-property edit, a membership change requires
|
|
644
|
+
* the viewport to re-apply native helper visibility. The map is mutated in
|
|
645
|
+
* place, and a remove+add burst can keep its size unchanged, so neither map
|
|
646
|
+
* identity nor size is a complete signal. `_gizmoEpoch` is the viewport's
|
|
647
|
+
* existing explicit rebuild input; bump it only at this structural seam. */
|
|
648
|
+
notifyIngestObjectMapEdit(delta?: IngestObjectMapStructureDelta): void {
|
|
649
|
+
this._gizmoEpoch++;
|
|
650
|
+
if (delta === undefined || delta.rootsChanged) {
|
|
651
|
+
this._objectMapFullChangeEpoch = this._gizmoEpoch;
|
|
652
|
+
this._objectMapParentChangedAt.clear();
|
|
653
|
+
} else {
|
|
654
|
+
for (const id of delta.changedParentIds) {
|
|
655
|
+
this._objectMapParentChangedAt.set(id, this._gizmoEpoch);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (delta?.addedIds === undefined || delta.removedIds === undefined) {
|
|
659
|
+
this._objectMapMembershipFullChangeEpoch = this._gizmoEpoch;
|
|
660
|
+
this._objectMapMembershipChangedAt.clear();
|
|
661
|
+
} else {
|
|
662
|
+
for (const id of delta.addedIds) this._objectMapMembershipChangedAt.set(id, this._gizmoEpoch);
|
|
663
|
+
for (const id of delta.removedIds) {
|
|
664
|
+
this._objectMapMembershipChangedAt.set(id, this._gizmoEpoch);
|
|
665
|
+
}
|
|
666
|
+
const historyFloor = Math.max(0, this._gizmoEpoch - OBJECT_MAP_MEMBERSHIP_HISTORY);
|
|
667
|
+
if (historyFloor > this._objectMapMembershipHistoryFloor) {
|
|
668
|
+
this._objectMapMembershipHistoryFloor = historyFloor;
|
|
669
|
+
for (const [id, changedAt] of this._objectMapMembershipChangedAt) {
|
|
670
|
+
if (changedAt <= historyFloor) this._objectMapMembershipChangedAt.delete(id);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const affectsShell =
|
|
675
|
+
delta === undefined ||
|
|
676
|
+
delta.rootsChanged ||
|
|
677
|
+
[...(delta.addedIds ?? []), ...(delta.removedIds ?? [])].some((id) =>
|
|
678
|
+
this.selectedEntityIds.has(id),
|
|
679
|
+
);
|
|
680
|
+
this._notify('content', affectsShell ? 'shell' : 'object-map', false);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// --- Play mode scene swap ---
|
|
684
|
+
|
|
685
|
+
/** Callback to sync transform edits to game ECS. Set by play-mode, cleared on exit.
|
|
686
|
+
* Receives the NODE ID as well as the live object: the `PhysicsAdapter` seam
|
|
687
|
+
* it ultimately feeds is id-keyed since P-4, and the id is already in hand
|
|
688
|
+
* at the one call site — so nothing downstream has to map an object back. */
|
|
689
|
+
protected _ecsSyncTransform: ((id: string, obj: THREE.Object3D) => void) | null = null;
|
|
690
|
+
|
|
691
|
+
/** Set a callback that syncs editor transform changes to the game's ECS state. */
|
|
692
|
+
setEcsSyncTransform(fn: ((id: string, obj: THREE.Object3D) => void) | null): void {
|
|
693
|
+
this._ecsSyncTransform = fn;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Swap the store's active scene to the game scene. Editor scene is preserved.
|
|
698
|
+
*
|
|
699
|
+
* Also snapshots whatever the active document half hands back from
|
|
700
|
+
* {@link _captureAdoptionExtras}. A half that keeps mutating its LIVE
|
|
701
|
+
* metadata during play (so its inspector fields keep working) must have
|
|
702
|
+
* those edits discarded when play stops — otherwise a play-time edit
|
|
703
|
+
* silently persists with `isDirty` false. `exitPlayScene` restores the
|
|
704
|
+
* snapshot.
|
|
705
|
+
*/
|
|
706
|
+
enterPlayScene(
|
|
707
|
+
gameScene: THREE.Scene,
|
|
708
|
+
imageConfig?: WorldRendererConfig | undefined,
|
|
709
|
+
projectedObjects?: ReadonlyMap<string, THREE.Object3D>,
|
|
710
|
+
): void {
|
|
711
|
+
if (!this._scene) return;
|
|
712
|
+
this._adoptionStack.push({
|
|
713
|
+
scene: this._scene,
|
|
714
|
+
objectMap: this._objectMap,
|
|
715
|
+
extras: this._captureAdoptionExtras(),
|
|
716
|
+
selection: new Set(this.selectedEntityIds),
|
|
717
|
+
imageConfig: this._adoptedImageConfig,
|
|
718
|
+
});
|
|
719
|
+
this._scene = gameScene;
|
|
720
|
+
this._applyAdoptedImageConfig(imageConfig);
|
|
721
|
+
|
|
722
|
+
// The store INDEXES an adopted scene; it no longer models it. A foreign
|
|
723
|
+
// adapter supplies its pure projection explicitly, so editor identity is
|
|
724
|
+
// never written into the game's Object3Ds. Source-backed/adopted worlds
|
|
725
|
+
// already carry native authoring stamps and retain the fallback walk.
|
|
726
|
+
const gameMap = projectedObjects
|
|
727
|
+
? new Map(projectedObjects)
|
|
728
|
+
: (() => {
|
|
729
|
+
const stamped = new Map<string, THREE.Object3D>();
|
|
730
|
+
const visit = (obj: THREE.Object3D): void => {
|
|
731
|
+
if (isEditorOwnedObject(obj)) return;
|
|
732
|
+
const eid = entityIdOf(obj);
|
|
733
|
+
if (eid !== undefined) stamped.set(eid, obj);
|
|
734
|
+
for (const child of obj.children) visit(child);
|
|
735
|
+
};
|
|
736
|
+
for (const child of gameScene.children) visit(child);
|
|
737
|
+
return stamped;
|
|
738
|
+
})();
|
|
739
|
+
this._objectMap = gameMap;
|
|
740
|
+
|
|
741
|
+
this._viewportSelections[this._activeViewportTab] = new Set(
|
|
742
|
+
[...this.selectedEntityIds].filter((id) => gameMap.has(id)),
|
|
743
|
+
);
|
|
744
|
+
// A viewport LOD pin must not follow the entity id into the adopted game
|
|
745
|
+
// scene — there `WebGLRenderer.projectObject` owns level visibility (it
|
|
746
|
+
// calls `lod.update(camera)` for every visible LOD whose `autoUpdate` is
|
|
747
|
+
// still true, which is what a pin turns OFF), and per-frame enforcement
|
|
748
|
+
// would fight it.
|
|
749
|
+
this._lodForcedLevels.clear();
|
|
750
|
+
this._composerVersion++; // scene swapped → composer must rebuild against the game scene
|
|
751
|
+
this._notify();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** Restore the previously-adopted scene (LIFO — see enterPlayScene). */
|
|
755
|
+
exitPlayScene(): void {
|
|
756
|
+
const frame = this._adoptionStack.pop();
|
|
757
|
+
if (!frame) return;
|
|
758
|
+
this._scene = frame.scene;
|
|
759
|
+
this._objectMap = frame.objectMap;
|
|
760
|
+
this._applyAdoptedImageConfig(frame.imageConfig);
|
|
761
|
+
// Discard any play-time env/name/ui edits — restore the pre-play snapshot.
|
|
762
|
+
this._restoreAdoptionExtras(frame.extras);
|
|
763
|
+
this._viewportSelections[this._activeViewportTab] = new Set(frame.selection);
|
|
764
|
+
this._composerVersion++; // scene swapped back → composer must rebuild against the editor scene
|
|
765
|
+
this._notify();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Release ONE adopter's adoption, wherever it sits in the stack. When
|
|
770
|
+
* `scene` is the ACTIVE scene this is exactly `exitPlayScene`; when a later
|
|
771
|
+
* adoption (play over the R3F design session) has already replaced it, the
|
|
772
|
+
* frame that would restore `scene` is unlinked from the middle of the stack
|
|
773
|
+
* instead — nothing visible changes now, and the LATER adopter's exit
|
|
774
|
+
* restores the state underneath as if this adoption never happened. No-op
|
|
775
|
+
* when `scene` was never adopted (or was already released).
|
|
776
|
+
*/
|
|
777
|
+
releaseAdoptedScene(scene: THREE.Scene): void {
|
|
778
|
+
if (this._scene === scene) {
|
|
779
|
+
this.exitPlayScene();
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const index = this._adoptionStack.findIndex((frame) => frame.scene === scene);
|
|
783
|
+
if (index === -1) return;
|
|
784
|
+
this._adoptionStack.splice(index, 1);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* THE ADOPTED CONTENT'S OWN COLOUR PIPELINE — what it is, not where it is
|
|
789
|
+
* applied.
|
|
790
|
+
*
|
|
791
|
+
* A mounted world may report the pipeline it was authored for
|
|
792
|
+
* (`MountedThreeRoot.rendererConfig`), and `applyWorldRendererConfig` puts it
|
|
793
|
+
* on the renderer its own host handed it. In the editor those are two
|
|
794
|
+
* DIFFERENT renderers: the design session mounts against a non-rasterizing
|
|
795
|
+
* one (`authoring/design-time-renderer.ts`) and the viewport draws the
|
|
796
|
+
* mounted scene with its own. So the declaration landed on a surface with no
|
|
797
|
+
* pixels while the surface with pixels kept the engine's defaults — measured
|
|
798
|
+
* on the translated Godot platformer, whose roughness-0 metal reads as chrome
|
|
799
|
+
* under the editor's ACES while the game renders it under its own curve.
|
|
800
|
+
*
|
|
801
|
+
* The store only REMEMBERS the declaration and bumps `_composerVersion` when
|
|
802
|
+
* it changes; `the world root's stage.rebuildComposer` is the one place that derives
|
|
803
|
+
* the viewport renderer's whole configuration, and it applies this last. That
|
|
804
|
+
* split is why there is no restore bookkeeping here: a rebuild always resets
|
|
805
|
+
* the renderer to the engine defaults first, so "unwind" is just the next
|
|
806
|
+
* rebuild seeing a different (or absent) config.
|
|
807
|
+
*/
|
|
808
|
+
protected _adoptedImageConfig: WorldRendererConfig | undefined;
|
|
809
|
+
|
|
810
|
+
/** What the active adopted content declared about the renderer that draws it,
|
|
811
|
+
* or `undefined` when it declared nothing (leave the editor's own alone). */
|
|
812
|
+
get adoptedImageConfig(): WorldRendererConfig | undefined {
|
|
813
|
+
return this._adoptedImageConfig;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
protected _applyAdoptedImageConfig(next: WorldRendererConfig | undefined): void {
|
|
817
|
+
if (next === this._adoptedImageConfig) return;
|
|
818
|
+
this._adoptedImageConfig = next;
|
|
819
|
+
this._composerVersion++;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** True while an ADOPTED live game scene is the active scene — every
|
|
823
|
+
* `enterPlayScene` caller: play adoption, ingest mounts, and the R3F
|
|
824
|
+
* design session. Adopted scenes own their lighting/rendering completely;
|
|
825
|
+
* the viewport uses this to keep editor-fabricated presentation (the
|
|
826
|
+
* helper light rig) out of them. */
|
|
827
|
+
get isAdoptedSceneActive(): boolean {
|
|
828
|
+
return this._adoptionStack.length > 0;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// --- Play mode state ---
|
|
832
|
+
/**
|
|
833
|
+
* WHICH MODE THE EDITOR IS IN — the shell's own intent, not a claim that a
|
|
834
|
+
* game is running. Read it for anything whose answer must be true from the
|
|
835
|
+
* MOMENT play is entered: the structural-edit block, the design session's
|
|
836
|
+
* suspend, the Play bar, the viewport tab, the tool `when` predicates.
|
|
837
|
+
*
|
|
838
|
+
* It is NOT the answer to "is something actually running", and it must never
|
|
839
|
+
* be reported as one. `'playing'` is written by three subsystems — play mode,
|
|
840
|
+
* ingest and module mode — and every one of them writes it BEFORE its session
|
|
841
|
+
* exists: `enterPlayMode` sets it ~130 lines and three awaits before
|
|
842
|
+
* `_instance.session` is assigned, and an ingest mount sets it before the
|
|
843
|
+
* mount is even attempted. That is deliberate (it is what swaps the viewport
|
|
844
|
+
* onto the game surface and freezes edit-mode work while the boot runs), and
|
|
845
|
+
* it is exactly why reporting the flag verbatim once printed
|
|
846
|
+
* `playState: "playing"` for a mount that had already thrown.
|
|
847
|
+
*
|
|
848
|
+
* The other question has its own door and only one caller may use it:
|
|
849
|
+
* `reported-play-state.ts`'s `deriveReportedPlayState`, which resolves the
|
|
850
|
+
* live session slots and is what `collectState` (`vgai status`) reports. If
|
|
851
|
+
* you are about to send this value outside the editor, you want that instead.
|
|
852
|
+
*/
|
|
853
|
+
get playState(): 'stopped' | 'playing' | 'paused' {
|
|
854
|
+
return this._playState;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Guard structural/history mutations while a game is running. During play the
|
|
859
|
+
* store's `_scene` is the LIVE game scene (see enterPlayScene), so undo/redo
|
|
860
|
+
* (which call clearPreview + buildPreview and rebuild the whole scene) or
|
|
861
|
+
* delete/duplicate (which splice the scene graph and objectMap) would tear the
|
|
862
|
+
* scene out from under the running GameSession. Returns true (and warns) when
|
|
863
|
+
* the named op must be blocked. The intended live-edit path during play is the
|
|
864
|
+
* incremental ECS-sync transform path, not these full-scene mutations.
|
|
865
|
+
*/
|
|
866
|
+
protected _blockedDuringPlay(op: string): boolean {
|
|
867
|
+
if (this._playState === 'stopped') return false;
|
|
868
|
+
console.warn(`EditorStore: "${op}" is disabled during play mode — stop the game first.`);
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
setPlayState(state: 'stopped' | 'playing' | 'paused'): void {
|
|
872
|
+
const wasActive = this._playState !== 'stopped';
|
|
873
|
+
this._playState = state;
|
|
874
|
+
if (state === 'playing' && this._activeViewportTab === 'edit') {
|
|
875
|
+
this._activeViewportTab = 'play';
|
|
876
|
+
}
|
|
877
|
+
if (state === 'stopped') {
|
|
878
|
+
this._activeViewportTab = 'edit';
|
|
879
|
+
// Apply anything deferred while play/ingest was active. play-mode.ts /
|
|
880
|
+
// ingest/mount-ingest-root.ts call exitPlayScene() (restoring the editor scene) BEFORE
|
|
881
|
+
// this — so by the time we get here `_scene` is already the editor scene,
|
|
882
|
+
// never the just-torn-down game scene (see applyExternalUpdate/applyAssetMove).
|
|
883
|
+
if (wasActive) this._onPlayStopped();
|
|
884
|
+
}
|
|
885
|
+
this._notify();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/** See {@link PlayEditRegime}. Null while not playing. */
|
|
889
|
+
get playEditRegime(): PlayEditRegime {
|
|
890
|
+
return this._playEditRegime;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** Set by play-mode.ts's enterPlayMode/exitPlayMode — see {@link PlayEditRegime}. */
|
|
894
|
+
setPlayEditRegime(regime: PlayEditRegime): void {
|
|
895
|
+
this._playEditRegime = regime;
|
|
896
|
+
this._notify();
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// --- Viewport tab ---
|
|
900
|
+
get activeViewportTab(): ViewportTab {
|
|
901
|
+
return this._activeViewportTab;
|
|
902
|
+
}
|
|
903
|
+
setActiveViewportTab(tab: ViewportTab): void {
|
|
904
|
+
if (tab === this._activeViewportTab) return;
|
|
905
|
+
this._activeViewportTab = tab;
|
|
906
|
+
this._notify();
|
|
907
|
+
}
|
|
908
|
+
// --- Vertex snap ---
|
|
909
|
+
get vertexSnapActive(): boolean {
|
|
910
|
+
return this._vertexSnapActive;
|
|
911
|
+
}
|
|
912
|
+
setVertexSnapActive(active: boolean): void {
|
|
913
|
+
this._vertexSnapActive = active;
|
|
914
|
+
this._notify();
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// --- Selection ---
|
|
918
|
+
// Every writer below notifies with `'selection'` because every writer below
|
|
919
|
+
// mutates `_viewportSelections` and NOTHING ELSE. That is the whole warrant
|
|
920
|
+
// for the scope — see `contentVersion`.
|
|
921
|
+
select(id: string | null, notification: 'immediate' | 'deferred' = 'immediate'): void {
|
|
922
|
+
const selection = this._viewportSelections[this._activeViewportTab];
|
|
923
|
+
selection.clear();
|
|
924
|
+
if (id) selection.add(id);
|
|
925
|
+
if (notification === 'deferred') this._notifyDeferred('selection');
|
|
926
|
+
else this._notify('selection');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
selectMultiple(ids: string[], notification: 'immediate' | 'deferred' = 'immediate'): void {
|
|
930
|
+
this._viewportSelections[this._activeViewportTab] = new Set(ids);
|
|
931
|
+
if (notification === 'deferred') this._notifyDeferred('selection');
|
|
932
|
+
else this._notify('selection');
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Apply a control-API selection NOW and return the notification that lets presentation catch up.
|
|
937
|
+
*
|
|
938
|
+
* The normal UI methods above remain synchronous. A relayed selection is different: its caller
|
|
939
|
+
* is waiting for an acknowledgement, while notifying React can synchronously reveal hundreds of
|
|
940
|
+
* hierarchy rows and build an inspector preview before `select()` returns. The command listener
|
|
941
|
+
* uses this method so `selectedEntityIds` already answers the new truth when it acks, then runs
|
|
942
|
+
* the returned notification only after the result POST has settled. No selection is optimistic:
|
|
943
|
+
* the set is mutated before this method returns; only presentation is deferred.
|
|
944
|
+
*/
|
|
945
|
+
applySelectionBeforePresentation(ids: readonly string[]): () => void {
|
|
946
|
+
this._viewportSelections[this._activeViewportTab] = new Set(ids);
|
|
947
|
+
let presented = false;
|
|
948
|
+
return () => {
|
|
949
|
+
if (presented) return;
|
|
950
|
+
presented = true;
|
|
951
|
+
this._notify('selection');
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
addToSelection(id: string): void {
|
|
956
|
+
this._viewportSelections[this._activeViewportTab].add(id);
|
|
957
|
+
this._notify('selection');
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
toggleSelection(id: string): void {
|
|
961
|
+
const selection = this._viewportSelections[this._activeViewportTab];
|
|
962
|
+
if (selection.has(id)) {
|
|
963
|
+
selection.delete(id);
|
|
964
|
+
} else {
|
|
965
|
+
selection.add(id);
|
|
966
|
+
}
|
|
967
|
+
this._notify('selection');
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
protected static readonly THUMBNAIL_INTERVAL_MS = 60_000;
|
|
971
|
+
protected static readonly THUMBNAIL_SIZE = 256;
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Render the bound camera's current view to a PNG data URL at `size`x`size`.
|
|
975
|
+
* Autosave thumbnails request game content only; an explicit SDK/editor
|
|
976
|
+
* viewport capture requests every camera layer so grid/helpers/gizmos are
|
|
977
|
+
* represented honestly. Both use the same offscreen render-target technique,
|
|
978
|
+
* so neither needs `preserveDrawingBuffer` on the live WebGL canvas.
|
|
979
|
+
*
|
|
980
|
+
* THE COLOUR PIPELINE IS THE WHOLE REASON THERE ARE TWO TARGETS. Three
|
|
981
|
+
* applies `renderer.toneMapping` and `renderer.outputColorSpace` ONLY when
|
|
982
|
+
* the destination is the canvas: rendering into an ordinary
|
|
983
|
+
* `WebGLRenderTarget` compiles every material with `NoToneMapping` and a
|
|
984
|
+
* LINEAR output transfer (`WebGLPrograms.getParameters` and
|
|
985
|
+
* `WebGLRenderer.setProgram` both gate on `currentRenderTarget === null`).
|
|
986
|
+
* So the single-target version of this function answered "what does the
|
|
987
|
+
* viewport look like" with a raw linear frame — measurably darker and
|
|
988
|
+
* flatter than the pixels on screen, and immune to every tone-mapping
|
|
989
|
+
* change (verified live: flipping the viewport to `NoToneMapping` moved the
|
|
990
|
+
* captured PNG by 0 bytes). That made this door — the only look at the EDIT
|
|
991
|
+
* viewport, and the source of every project thumbnail — unusable as
|
|
992
|
+
* evidence about the image.
|
|
993
|
+
*
|
|
994
|
+
* The fix is three's own: render the scene into a HALF-FLOAT linear buffer
|
|
995
|
+
* (so highlights survive to be tone-mapped) and resolve it through
|
|
996
|
+
* `OutputPass`, which reads `toneMapping`/`toneMappingExposure`/
|
|
997
|
+
* `outputColorSpace` off the renderer and applies exactly what the on-screen
|
|
998
|
+
* frame gets.
|
|
999
|
+
*/
|
|
1000
|
+
protected _renderViewportImage(
|
|
1001
|
+
size: number | { width: number; height: number },
|
|
1002
|
+
includeEditorLayers: boolean,
|
|
1003
|
+
): string | null {
|
|
1004
|
+
if (!this._renderer || !this._scene || !this._camera) return null;
|
|
1005
|
+
// A number is a SQUARE of that size — the default shape; `{width, height}`
|
|
1006
|
+
// renders the buffer and the camera at that aspect so a shaped look needs
|
|
1007
|
+
// no crop (`@volter/editor-sdk`'s `CaptureDimensions`).
|
|
1008
|
+
const width = typeof size === 'number' ? size : size.width;
|
|
1009
|
+
const height = typeof size === 'number' ? size : size.height;
|
|
1010
|
+
// The same floor `authoring/object3d-document-session.ts`'s `captureImage`
|
|
1011
|
+
// already sets, for the same reason: the doubled size is handed straight to
|
|
1012
|
+
// `createImageData`, so a non-finite size throws
|
|
1013
|
+
// `TypeError: Value is not of type 'long'` from the middle of the render
|
|
1014
|
+
// path instead of failing where the value entered.
|
|
1015
|
+
if (!Number.isFinite(width) || width < 1) return null;
|
|
1016
|
+
if (!Number.isFinite(height) || height < 1) return null;
|
|
1017
|
+
|
|
1018
|
+
// render at 2x then downscale for crisp result
|
|
1019
|
+
const renderWidth = width * 2;
|
|
1020
|
+
const renderHeight = height * 2;
|
|
1021
|
+
const renderer = this._renderer;
|
|
1022
|
+
const sceneTarget = new THREE.WebGLRenderTarget(renderWidth, renderHeight, {
|
|
1023
|
+
type: THREE.HalfFloatType,
|
|
1024
|
+
});
|
|
1025
|
+
const renderTarget = new THREE.WebGLRenderTarget(renderWidth, renderHeight);
|
|
1026
|
+
const prevTarget = renderer.getRenderTarget();
|
|
1027
|
+
const prevPixelRatio = renderer.getPixelRatio();
|
|
1028
|
+
|
|
1029
|
+
const camera = this._camera;
|
|
1030
|
+
const prevLayers = camera.layers.mask;
|
|
1031
|
+
const perspectiveCamera = camera instanceof THREE.PerspectiveCamera ? camera : null;
|
|
1032
|
+
const previousAspect = perspectiveCamera?.aspect;
|
|
1033
|
+
if (includeEditorLayers) {
|
|
1034
|
+
camera.layers.enableAll();
|
|
1035
|
+
} else {
|
|
1036
|
+
camera.layers.disableAll();
|
|
1037
|
+
camera.layers.enable(0); // clean project thumbnail: game content only
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
try {
|
|
1041
|
+
if (perspectiveCamera) {
|
|
1042
|
+
perspectiveCamera.aspect = width / height;
|
|
1043
|
+
perspectiveCamera.updateProjectionMatrix();
|
|
1044
|
+
}
|
|
1045
|
+
renderer.setPixelRatio(1);
|
|
1046
|
+
renderer.setRenderTarget(sceneTarget);
|
|
1047
|
+
// The same view policy the on-screen draw applies (`scene-view-fog.ts`).
|
|
1048
|
+
// This capture is the ONLY look anyone gets at the Edit viewport — the
|
|
1049
|
+
// `vgai screenshot` evidence and every project thumbnail — so a capture
|
|
1050
|
+
// that renders the game's fog while the viewport does not would make the
|
|
1051
|
+
// one door onto the image disagree with the image.
|
|
1052
|
+
withSceneFogNeutralized(this._scene, () =>
|
|
1053
|
+
renderer.render(this._scene as THREE.Scene, camera),
|
|
1054
|
+
);
|
|
1055
|
+
viewportCaptureOutputPass().render(renderer, renderTarget, sceneTarget, 0, false);
|
|
1056
|
+
} finally {
|
|
1057
|
+
renderer.setRenderTarget(prevTarget);
|
|
1058
|
+
renderer.setPixelRatio(prevPixelRatio);
|
|
1059
|
+
camera.layers.mask = prevLayers;
|
|
1060
|
+
if (perspectiveCamera && previousAspect !== undefined) {
|
|
1061
|
+
perspectiveCamera.aspect = previousAspect;
|
|
1062
|
+
perspectiveCamera.updateProjectionMatrix();
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Read pixels from the render target
|
|
1067
|
+
const rowBytes = renderWidth * 4;
|
|
1068
|
+
const pixels = new Uint8Array(rowBytes * renderHeight);
|
|
1069
|
+
renderer.readRenderTargetPixels(renderTarget, 0, 0, renderWidth, renderHeight, pixels);
|
|
1070
|
+
renderTarget.dispose();
|
|
1071
|
+
sceneTarget.dispose();
|
|
1072
|
+
|
|
1073
|
+
// Flip vertically (WebGL reads bottom-up) and write to a full-size canvas
|
|
1074
|
+
const fullCanvas = document.createElement('canvas');
|
|
1075
|
+
fullCanvas.width = renderWidth;
|
|
1076
|
+
fullCanvas.height = renderHeight;
|
|
1077
|
+
const fullCtx = fullCanvas.getContext('2d')!;
|
|
1078
|
+
const imageData = fullCtx.createImageData(renderWidth, renderHeight);
|
|
1079
|
+
for (let y = 0; y < renderHeight; y++) {
|
|
1080
|
+
const srcRow = (renderHeight - 1 - y) * rowBytes;
|
|
1081
|
+
const dstRow = y * rowBytes;
|
|
1082
|
+
imageData.data.set(pixels.subarray(srcRow, srcRow + rowBytes), dstRow);
|
|
1083
|
+
}
|
|
1084
|
+
fullCtx.putImageData(imageData, 0, 0);
|
|
1085
|
+
|
|
1086
|
+
// Downscale to the requested size
|
|
1087
|
+
const canvas = document.createElement('canvas');
|
|
1088
|
+
canvas.width = width;
|
|
1089
|
+
canvas.height = height;
|
|
1090
|
+
const ctx2d = canvas.getContext('2d')!;
|
|
1091
|
+
ctx2d.drawImage(fullCanvas, 0, 0, width, height);
|
|
1092
|
+
|
|
1093
|
+
return canvas.toDataURL('image/png');
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
protected _captureThumbnail(): void {
|
|
1097
|
+
const now = Date.now();
|
|
1098
|
+
if (now - this._lastThumbnailTime < EditorShellStore.THUMBNAIL_INTERVAL_MS) return;
|
|
1099
|
+
this._lastThumbnailTime = now;
|
|
1100
|
+
const dataUrl = this._renderViewportImage(EditorShellStore.THUMBNAIL_SIZE, false);
|
|
1101
|
+
if (dataUrl) void this._statePersistence?.saveThumbnail?.(dataUrl);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Fresh, on-demand viewport capture (unthrottled, unlike the periodic
|
|
1106
|
+
* autosave thumbnail above) — backs the `capture-viewport` relay command.
|
|
1107
|
+
* Returns null when no renderer/scene/camera is bound yet (e.g. before
|
|
1108
|
+
* `the world root's stage` has mounted).
|
|
1109
|
+
*/
|
|
1110
|
+
captureViewportImage(
|
|
1111
|
+
size: number | { width: number; height: number } = EditorShellStore.THUMBNAIL_SIZE,
|
|
1112
|
+
): string | null {
|
|
1113
|
+
return this._renderViewportImage(size, true);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/** The live Scene viewport's own `<canvas>`, or null before a renderer is
|
|
1117
|
+
* bound. A photograph of the whole editor page (`editor-chrome-capture.ts`)
|
|
1118
|
+
* needs to recognise it: its WebGL context has no `preserveDrawingBuffer`,
|
|
1119
|
+
* so its pixels come from {@link captureViewportImage}, not a late read. */
|
|
1120
|
+
viewportCanvas(): HTMLCanvasElement | null {
|
|
1121
|
+
return this._renderer?.domElement ?? null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// --- Editor state persistence ---
|
|
1125
|
+
|
|
1126
|
+
protected static readonly EDITOR_STATE_INTERVAL_MS = 60_000;
|
|
1127
|
+
|
|
1128
|
+
protected _saveEditorStateIfDue(): void {
|
|
1129
|
+
if (!this._statePersistence) return;
|
|
1130
|
+
const now = Date.now();
|
|
1131
|
+
if (now - this._lastEditorStateSaveTime < EditorShellStore.EDITOR_STATE_INTERVAL_MS) return;
|
|
1132
|
+
this._lastEditorStateSaveTime = now;
|
|
1133
|
+
|
|
1134
|
+
const state: Record<string, unknown> = {
|
|
1135
|
+
transformMode: this._transformMode,
|
|
1136
|
+
transformSpace: this._transformSpace,
|
|
1137
|
+
snapEnabled: this._snapEnabled,
|
|
1138
|
+
snapValues: this._snapValues,
|
|
1139
|
+
preserveChildrenTransform: this._preserveChildrenTransform,
|
|
1140
|
+
pivotMode: this._pivotMode,
|
|
1141
|
+
gizmoAnchor: this._gizmoAnchor,
|
|
1142
|
+
showGrid: this._showGrid,
|
|
1143
|
+
showHelpers: this._showHelpers,
|
|
1144
|
+
helperVisibility: { ...this._helperVisibility },
|
|
1145
|
+
showStats: this._showStats,
|
|
1146
|
+
shadingMode: this._shadingMode,
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
Object.assign(state, this._editorStateExtras());
|
|
1150
|
+
|
|
1151
|
+
if (this._camera) {
|
|
1152
|
+
const cam = this._camera as THREE.PerspectiveCamera;
|
|
1153
|
+
state['camera'] = {
|
|
1154
|
+
position: cam.position.toArray(),
|
|
1155
|
+
target: this._orbitTarget ? this._orbitTarget.toArray() : [0, 0, 0],
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
void this._statePersistence.save(state);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// --- Mutations ---
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* EDITOR-ONLY level-preview override (W2b): entity id → index into the
|
|
1166
|
+
* live THREE.LOD's (ascending-by-distance) levels to pin in the viewport.
|
|
1167
|
+
* Pure UI state — never written to the descriptor, so it can't leak into
|
|
1168
|
+
* the saved scene or the undo history. Enforced per frame by
|
|
1169
|
+
* `EditorViewport.update` (the glTF loads async and entity rebuilds replace
|
|
1170
|
+
* the THREE.LOD instance, so a one-shot apply would silently un-pin).
|
|
1171
|
+
*/
|
|
1172
|
+
protected _lodForcedLevels = new Map<string, number>();
|
|
1173
|
+
|
|
1174
|
+
get lodForcedLevels(): ReadonlyMap<string, number> {
|
|
1175
|
+
return this._lodForcedLevels;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
getLodForcedLevel(id: string): number | null {
|
|
1179
|
+
return this._lodForcedLevels.get(id) ?? null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/** Pin an entity's viewport LOD display to one level, or `null` for Auto
|
|
1183
|
+
* (distance-based selection — the renderer's own `LOD.update(camera)`). */
|
|
1184
|
+
setLodForcedLevel(id: string, level: number | null): void {
|
|
1185
|
+
if (level === null) {
|
|
1186
|
+
this._lodForcedLevels.delete(id);
|
|
1187
|
+
// Hand control back to the renderer's distance-based selection.
|
|
1188
|
+
const obj = this._objectMap.get(id);
|
|
1189
|
+
const projectedObjects = new Set(this._objectMap.values());
|
|
1190
|
+
const lodObj = obj ? findEntityLod(obj, (node) => projectedObjects.has(node)) : null;
|
|
1191
|
+
if (lodObj) lodObj.autoUpdate = true;
|
|
1192
|
+
} else {
|
|
1193
|
+
this._lodForcedLevels.set(id, level);
|
|
1194
|
+
}
|
|
1195
|
+
this._notify();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// --- Transform mode ---
|
|
1199
|
+
setTransformMode(mode: TransformMode): void {
|
|
1200
|
+
this._transformMode = mode;
|
|
1201
|
+
this._notify();
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
setTransformSpace(space: TransformSpace): void {
|
|
1205
|
+
this._transformSpace = space;
|
|
1206
|
+
this._notify();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
toggleSnap(): void {
|
|
1210
|
+
this._snapEnabled = !this._snapEnabled;
|
|
1211
|
+
this._notify();
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
setSnapValues(values: Partial<{ translate: number; rotate: number; scale: number }>): void {
|
|
1215
|
+
this._snapValues = { ...this._snapValues, ...values };
|
|
1216
|
+
this._notify();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
toggleSnapToSurface(): void {
|
|
1220
|
+
this._snapToSurface = !this._snapToSurface;
|
|
1221
|
+
this._notify();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
togglePreserveChildrenTransform(): void {
|
|
1225
|
+
this._preserveChildrenTransform = !this._preserveChildrenTransform;
|
|
1226
|
+
this._notify();
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
snapSelectionToFloor(): void {
|
|
1230
|
+
this._emitViewportAction({ type: 'snap-selection-to-floor' });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
setPivotMode(mode: PivotMode): void {
|
|
1234
|
+
this._pivotMode = mode;
|
|
1235
|
+
this._notify();
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
setGizmoAnchor(anchor: GizmoAnchor): void {
|
|
1239
|
+
this._gizmoAnchor = anchor;
|
|
1240
|
+
this._notify();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
toggleGrid(): void {
|
|
1244
|
+
this._showGrid = !this._showGrid;
|
|
1245
|
+
this._notify();
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
toggleHelpers(): void {
|
|
1249
|
+
// The eye is a visibility gate, not a reset of the category checkboxes.
|
|
1250
|
+
this._showHelpers = !this._showHelpers;
|
|
1251
|
+
this._notify();
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
toggleHelperType(type: keyof HelperVisibility): void {
|
|
1255
|
+
this._helperVisibility[type] = !this._helperVisibility[type];
|
|
1256
|
+
if (this._helperVisibility[type]) this._showHelpers = true;
|
|
1257
|
+
this._notify();
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
toggleStats(): void {
|
|
1261
|
+
this._showStats = !this._showStats;
|
|
1262
|
+
this._notify();
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
setShadingMode(mode: ShadingMode): void {
|
|
1266
|
+
this._shadingMode = mode;
|
|
1267
|
+
// View-only state. The viewport applies it during its own render and
|
|
1268
|
+
// restores native materials before returning to editor/game code.
|
|
1269
|
+
this._notify();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// --- Subclass hooks -------------------------------------------------------
|
|
1273
|
+
// The ONLY way the shell reaches into a document half. Each is a no-op here,
|
|
1274
|
+
// so the shell runs standalone; a subclass that owns a document overrides the
|
|
1275
|
+
// ones it needs (`store-notify-scope.test.ts` exercises the deferral hooks).
|
|
1276
|
+
|
|
1277
|
+
/** Runs at the top of every `_notify()`. A document half uses it to close a
|
|
1278
|
+
* pending history edit. */
|
|
1279
|
+
protected _beforeNotify(): void {}
|
|
1280
|
+
|
|
1281
|
+
/** Runs after `attachHistory` binds the project `HistoryService`. A document
|
|
1282
|
+
* half uses it to register its document resource driver. */
|
|
1283
|
+
protected _onHistoryAttached(): void {}
|
|
1284
|
+
|
|
1285
|
+
/** Captured into the adoption frame by `enterPlayScene`, handed back by
|
|
1286
|
+
* `exitPlayScene`. A document half snapshots its metadata here so
|
|
1287
|
+
* play-time edits to it are discarded on exit. */
|
|
1288
|
+
protected _captureAdoptionExtras(): unknown {
|
|
1289
|
+
return undefined;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** Restore counterpart of {@link _captureAdoptionExtras}. */
|
|
1293
|
+
protected _restoreAdoptionExtras(_extras: unknown): void {}
|
|
1294
|
+
|
|
1295
|
+
/** Runs when `setPlayState('stopped')` ends an active play/ingest session.
|
|
1296
|
+
* A document half applies mutations it deferred while play was running. */
|
|
1297
|
+
protected _onPlayStopped(): void {}
|
|
1298
|
+
|
|
1299
|
+
/** Extra keys folded into the periodic editor-state save. A document half
|
|
1300
|
+
* contributes whatever it needs restored on the next boot. */
|
|
1301
|
+
protected _editorStateExtras(): Record<string, unknown> {
|
|
1302
|
+
return {};
|
|
1303
|
+
}
|
|
1304
|
+
}
|