@volter/editor-threejs 0.5.210 → 0.5.212
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/NOTICE +7 -2
- package/README.md +8 -13
- package/contributions/animation-mixers.service.ts +2 -4
- package/contributions/animation-timeline.utility.tsx +1 -2
- package/contributions/three.page.ts +50 -0
- package/dist-node/serving.mjs +84 -3
- package/dist-node/shared-three.mjs +145 -0
- package/package.json +296 -16
- package/serving/animation-stamp.ts +1 -1
- package/serving/index.ts +22 -5
- package/serving/model-import-conversion.ts +6 -3
- package/serving/shared-three.ts +176 -0
- package/src/adapter/constraint.ts +1 -18
- package/src/adapter/object3d-authoring-subject.ts +1 -12
- package/src/adapter/reflection-probe.ts +1 -9
- package/src/adapter/three-contract.ts +6 -60
- package/src/animation/live-mixers.ts +0 -4
- package/src/api.ts +7 -0
- package/src/asset-loaders.ts +2 -178
- package/src/capture/photograph-sampling.ts +333 -0
- package/src/capture/scene.ts +1 -1
- package/src/ecs/object-marks.ts +6 -14
- package/src/ecs/user-data.ts +3 -138
- package/src/kit/animation/three-clips-subject.ts +19 -42
- package/src/kit/asset-compare-core.ts +161 -0
- package/src/kit/asset-compare.ts +8 -27
- package/src/kit/asset-preview-command.ts +14 -19
- package/src/kit/asset-preview-framing.ts +8 -67
- package/src/kit/asset-preview.ts +109 -342
- package/src/kit/asset-workflow/model-inspection.ts +11 -154
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
- package/src/kit/authoring/component-instance-root.ts +3 -39
- package/src/kit/authoring/live-object-transform.ts +1 -30
- package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
- package/src/kit/authoring/object3d-document-session.ts +471 -208
- package/src/kit/authoring/shell-viewport-policy.ts +18 -24
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
- package/src/kit/authoring/three-projection-core.ts +11 -40
- package/src/kit/authoring/viewport-pick-context.ts +5 -12
- package/src/kit/authoring/viewport-raycast.ts +15 -93
- package/src/kit/authoring/world-hidden-viewport.ts +8 -61
- package/src/kit/capture-camera-pose.ts +68 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/components/CameraInfo.tsx +4 -6
- package/src/kit/components/InspectorObjectPreview.tsx +3 -30
- package/src/kit/components/Object3DDocumentToolbar.css +75 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
- package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
- package/src/kit/components/StageHost.css +40 -0
- package/src/kit/components/StageHost.tsx +181 -404
- package/src/kit/components/StageOverlays.tsx +1 -2
- package/src/kit/components/StatsOverlay.tsx +5 -17
- package/src/kit/components/ViewportFurniture.tsx +27 -154
- package/src/kit/components/ViewportOverlay.tsx +13 -17
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/ViewportShadingMenu.tsx +2 -42
- package/src/kit/components/ViewportViewMenu.tsx +7 -18
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
- package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
- package/src/kit/components/stage-keyboard.tsx +6 -12
- package/src/kit/components/stage-overlay-set.tsx +11 -20
- package/src/kit/components/stage-presence-markers.ts +9 -32
- package/src/kit/components/stage-transform-chrome.ts +4 -4
- package/src/kit/components/stage-transform-tools.tsx +7 -14
- package/src/kit/components/standard-viewport-dressing.ts +47 -167
- package/src/kit/components/world-root-binding.ts +2 -7
- package/src/kit/constraint-helper.ts +1 -1
- package/src/kit/document-renderer-session.ts +114 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-shell-store.ts +31 -403
- package/src/kit/editor-viewport.ts +476 -1762
- package/src/kit/entity-lod.ts +3 -16
- package/src/kit/entity-object.ts +1 -39
- package/src/kit/hierarchy-mark-reader.ts +13 -30
- package/src/kit/instanced-presentation.ts +1 -47
- package/src/kit/live-gesture-lock.ts +35 -0
- package/src/kit/live-module-source.ts +12 -52
- package/src/kit/model-thumbnail.ts +7 -35
- package/src/kit/play-camera-flight.ts +7 -16
- package/src/kit/projection/three.ts +13 -366
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-viewport.ts +3 -3
- package/src/kit/scene-framing.ts +11 -152
- package/src/kit/scene-view-fog.ts +2 -47
- package/src/kit/spatial-handle-visuals.ts +19 -28
- package/src/kit/stories/three-story-model.ts +5 -33
- package/src/kit/three-canvas-render.ts +3 -4
- package/src/kit/three-hierarchy-row-media.ts +3 -11
- package/src/kit/three-inspection-media.ts +6 -6
- package/src/kit/three-integration.ts +18 -18
- package/src/kit/three-state.ts +2 -19
- package/src/kit/three-viewport/camera-fit.ts +2 -6
- package/src/kit/three-viewport/selection-brackets.ts +5 -20
- package/src/kit/three-viewport/selection-outline.ts +4 -56
- package/src/kit/three-viewport/skeleton-helper.ts +1 -32
- package/src/kit/three-viewport/source-color.ts +1 -33
- package/src/kit/three-viewport/studio-environment.ts +4 -55
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/transport-strip.tsx +147 -0
- package/src/kit/viewport-actions.ts +8 -50
- package/src/kit/viewport-authoring-policy.ts +5 -52
- package/src/kit/viewport-commands.ts +28 -57
- package/src/kit/viewport-hotkeys.ts +29 -31
- package/src/kit/viewport-status-facet.ts +4 -5
- package/src/kit/viewport-surface-status.tsx +44 -0
- package/src/loader.ts +8 -71
- package/src/object3d-contributions.ts +18 -34
- package/src/viewport/clip-planes.ts +4 -32
- package/src/viewport/content-bounds.ts +13 -142
- package/src/viewport/editor-layers.ts +4 -26
- package/src/viewport/environment.ts +1 -1
- package/src/viewport/preview-renderer.ts +7 -45
- package/src/viewport/renderer-ownership.ts +14 -62
- package/src/viewport-api.ts +14 -33
- package/src/viewport-door.ts +16 -86
- package/contributions/three-integration.service.ts +0 -13
- package/src/adapter/hierarchy-marks.ts +0 -156
- package/src/adapter/ingest/scene-capture.ts +0 -843
- package/src/adapter/ingest/structural-ids.ts +0 -139
- package/src/adapter/ingest/visible-capture-window.ts +0 -302
- package/src/adapter/renderer-config.ts +0 -115
- package/src/asset-parse-error.ts +0 -29
- package/src/host-hierarchy-objects.ts +0 -31
- package/src/kit/authoring/design-time-settle.ts +0 -343
- package/src/kit/camera-authoring.ts +0 -175
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EditorShellStore — the shell store's THREE half: the
|
|
2
|
+
* EditorShellStore — the shell store's THREE half: the scene and its object map, the
|
|
3
3
|
* renderer and camera, viewport tools (transform, snapping, pivot, gizmo), helper and shading
|
|
4
|
-
* state, LOD pins, capture and the periodic editor-state save. It
|
|
5
|
-
*
|
|
6
|
-
* the kit for `@volter/editor-threejs` with the viewport (ARCHITECTURE.md §The plan, unit 3).
|
|
4
|
+
* state, LOD pins, capture and the periodic editor-state save. It wraps the media-neutral
|
|
5
|
+
* SDK `ShellStore` that the kit's other modules type against.
|
|
7
6
|
* It knows `THREE.Object3D`s and ids, never a document model: an adapter's document is that
|
|
8
|
-
* adapter's private business
|
|
7
|
+
* adapter's private business.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
|
-
import type { ViewportTab } from '@volter/sdk';
|
|
12
10
|
import { viewportCaptureOutputPass } from '@volter/editor-threejs/capture/output-pass';
|
|
13
|
-
import { isEditorOwnedObject } from '@volter/editor-threejs/viewport/editor-layers';
|
|
14
|
-
import type { WorldRendererConfig } from '@volter/editor-threejs/adapter/renderer-config';
|
|
15
11
|
import { getUserData, setUserData } from '@volter/editor-threejs/ecs/user-data';
|
|
16
12
|
import type { ViewportShadingMode } from '@volter/editor-threejs/render/viewport-shading';
|
|
17
13
|
import * as THREE from 'three';
|
|
18
|
-
import type { BatchedRenderer } from 'three.quarks';
|
|
19
14
|
import { findEntityLod } from './entity-lod';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
export type { NotifyScope, PlayEditRegime } from '@volter/sdk/kit/shell-store';
|
|
15
|
+
import { ShellStore } from '@volter/sdk/shell-store';
|
|
16
|
+
export type { NotifyScope } from '@volter/sdk/shell-store';
|
|
23
17
|
import { withSceneFogNeutralized } from './scene-view-fog';
|
|
24
18
|
|
|
25
19
|
/** The store's persistence collaborator — installed by the shell, never
|
|
@@ -30,74 +24,17 @@ export interface EditorStatePersistence {
|
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `'select'` is Blender's Select Box, and it is a TOOL rather than an absence:
|
|
37
|
-
* its shelf opens on it (`space_toolsystem_toolbar.py`,
|
|
38
|
-
* `_defs_view3d_generic.select_box` first in the Object Mode `_tools_default`;
|
|
39
|
-
* photographed at the engine's pin in `gizmo-select-box.png`), so a selected
|
|
40
|
-
* object shows its outline and nothing else until Move / Rotate / Scale /
|
|
41
|
-
* Transform is armed. Before it there was no "no gizmo" state at all — this
|
|
42
|
-
* store's default was `'combined'` and every selection drew three tools'
|
|
43
|
-
* handles at once — which is why it is a member here rather than a flag beside
|
|
44
|
-
* it. `editor-viewport.ts` detaches on it, through the same branch a
|
|
45
|
-
* non-writable selection already took.
|
|
46
|
-
*
|
|
47
|
-
* Which member a STAGE is born with is its presentation's (`interaction.bootTool`,
|
|
48
|
-
* `kit/viewport-presentation`); arming one is always a click in the shelf.
|
|
27
|
+
* Re-export the shell’s transform-tool choices. Select shows selection outlines without a transform
|
|
28
|
+
* gizmo; Move, Rotate, Scale and Transform arm their corresponding handles. Stage presentation
|
|
29
|
+
* supplies interaction.bootTool.
|
|
49
30
|
*/
|
|
50
|
-
export type { GizmoAnchor, PivotMode, TransformMode, TransformSpace } from '@volter/sdk/
|
|
31
|
+
export type { GizmoAnchor, PivotMode, TransformMode, TransformSpace } from '@volter/sdk/shell-store';
|
|
51
32
|
|
|
52
33
|
export type ShadingMode = ViewportShadingMode;
|
|
53
34
|
|
|
54
|
-
|
|
55
|
-
* What a store notification says CHANGED.
|
|
56
|
-
*
|
|
57
|
-
* `'selection'` is the narrow one and it is claimed by exactly five writers
|
|
58
|
-
* (`select`, `selectMultiple`, `addToSelection`, `toggleSelection`,
|
|
59
|
-
* `applySelectionBeforePresentation`), each of which touches
|
|
60
|
-
* `_viewportSelections` and nothing else. `'content'` is the default and means
|
|
61
|
-
* "assume anything may have changed", so a mutation that forgets to classify
|
|
62
|
-
* itself is safe by construction — the failure mode of a wrong claim is a
|
|
63
|
-
* stale panel, so the burden of proof sits on the narrow value.
|
|
64
|
-
* See {@link EditorShellStore.contentVersion}.
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/** Exact structural delta supplied by a live Three projection mutation. */
|
|
68
|
-
export interface IngestObjectMapStructureDelta {
|
|
69
|
-
readonly changedParentIds: ReadonlySet<string>;
|
|
70
|
-
readonly rootsChanged: boolean;
|
|
71
|
-
/** Exact projected membership changes; omission keeps legacy callers conservative. */
|
|
72
|
-
readonly addedIds?: ReadonlySet<string>;
|
|
73
|
-
readonly removedIds?: ReadonlySet<string>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** All projected parent rows changed after a previously observed gizmo epoch. */
|
|
77
|
-
export interface IngestObjectMapChanges {
|
|
78
|
-
readonly epoch: number;
|
|
79
|
-
readonly changedParentIds: ReadonlySet<string>;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Exact object-map identities whose membership changed after an observed gizmo epoch. */
|
|
83
|
-
export interface IngestObjectMapMembershipChanges {
|
|
84
|
-
readonly epoch: number;
|
|
85
|
-
readonly changedIds: ReadonlySet<string>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Enough exact structural epochs to span deferred React consumers; older readers rebuild. */
|
|
89
|
-
const OBJECT_MAP_MEMBERSHIP_HISTORY = 256;
|
|
90
|
-
|
|
35
|
+
export type { HelperVisibility } from '@volter/sdk/shell-store';
|
|
91
36
|
|
|
92
|
-
|
|
93
|
-
/** The two viewport tabs, Edit · Play. Defined ONCE, in the SDK's shared
|
|
94
|
-
* vocabulary (`@volter/sdk`), because the control API speaks it too;
|
|
95
|
-
* re-exported here so editor modules keep one import site. */
|
|
96
|
-
export type { ViewportTab };
|
|
97
|
-
|
|
98
|
-
export type { HelperVisibility } from '@volter/sdk/kit/shell-store';
|
|
99
|
-
|
|
100
|
-
export type { ViewportAction } from '@volter/sdk/kit/shell-store';
|
|
37
|
+
export type { ViewportAction } from '@volter/sdk/shell-store';
|
|
101
38
|
|
|
102
39
|
export class EditorShellStore {
|
|
103
40
|
/** Made by `threeStateOf`, once per shell store. */
|
|
@@ -105,55 +42,19 @@ export class EditorShellStore {
|
|
|
105
42
|
|
|
106
43
|
// --- Scene graph references (set via bindScene) ---
|
|
107
44
|
protected _scene: THREE.Scene | null = null;
|
|
108
|
-
/** Adoption stack (see enterPlayScene): each frame is the scene state that
|
|
109
|
-
* was active when an adoption replaced it, restored in LIFO order by
|
|
110
|
-
* exitPlayScene. A STACK (not a single slot) because adoptions nest for
|
|
111
|
-
* R3F projects: the design session adopts its fiber scene at design time,
|
|
112
|
-
* and play adopts the live game scene OVER it while the play-entry
|
|
113
|
-
* transition still needs the design scene alive — the design session then
|
|
114
|
-
* unwinds its own frame from the middle via releaseAdoptedScene. */
|
|
115
|
-
protected _adoptionStack: Array<{
|
|
116
|
-
/** `null` when the adoption replaced no scene: a session with no Scene stage plays too. */
|
|
117
|
-
scene: THREE.Scene | null;
|
|
118
|
-
objectMap: Map<string, THREE.Object3D>;
|
|
119
|
-
/** Opaque payload from {@link _captureAdoptionExtras} — a document half's
|
|
120
|
-
* own per-adoption snapshot. The shell never inspects it. */
|
|
121
|
-
extras: unknown;
|
|
122
|
-
selection: Set<string>;
|
|
123
|
-
/** The image config that was active BEFORE this adoption — same LIFO
|
|
124
|
-
* discipline as `scene`, so a frame spliced out of the middle simply
|
|
125
|
-
* stops being a restore point. See {@link _applyAdoptedImageConfig}. */
|
|
126
|
-
imageConfig: WorldRendererConfig | undefined;
|
|
127
|
-
}> = [];
|
|
128
45
|
protected _objectMap = new Map<string, THREE.Object3D>();
|
|
129
46
|
protected _renderer: THREE.WebGLRenderer | null = null;
|
|
130
47
|
protected _camera: THREE.Camera | null = null;
|
|
131
|
-
protected _batchedRenderer: BatchedRenderer | null = null;
|
|
132
48
|
protected _lastThumbnailTime = 0;
|
|
133
49
|
protected _lastEditorStateSaveTime = 0;
|
|
134
50
|
protected _orbitTarget: THREE.Vector3 | null = null;
|
|
135
51
|
|
|
136
52
|
// --- UI state (not stored in scene graph) ---
|
|
137
53
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* `updateEntityPreview`) disposes the old Object3D WITH all its helper
|
|
141
|
-
* children, but leaves the objectMap identity/size and every toggle
|
|
142
|
-
* untouched — the viewport's `gizmoSig` gate would otherwise skip both
|
|
143
|
-
* `applyEntityGizmos` and the per-type visibility pass until an unrelated
|
|
144
|
-
* event.
|
|
54
|
+
* Advance when object helpers must be reapplied without changing object-map identity, size or
|
|
55
|
+
* visibility toggles, including single-object rebuilds.
|
|
145
56
|
*/
|
|
146
57
|
protected _gizmoEpoch = 0;
|
|
147
|
-
/** Last structural epoch at which each projected parent's direct children changed. */
|
|
148
|
-
protected _objectMapParentChangedAt = new Map<string, number>();
|
|
149
|
-
/** A root change (or an unclassified legacy call) requires a full hierarchy read. */
|
|
150
|
-
protected _objectMapFullChangeEpoch = 0;
|
|
151
|
-
/** Last exact add/remove epoch for each projected id, independently consumed by the viewport. */
|
|
152
|
-
protected _objectMapMembershipChangedAt = new Map<string, number>();
|
|
153
|
-
/** Missing membership detail requires the viewport to reconcile its complete helper indexes. */
|
|
154
|
-
protected _objectMapMembershipFullChangeEpoch = 0;
|
|
155
|
-
/** Epochs older than this were compacted from the bounded membership history. */
|
|
156
|
-
protected _objectMapMembershipHistoryFloor = 0;
|
|
157
58
|
protected _showStats = false;
|
|
158
59
|
protected _shadingMode: ShadingMode = 'solid';
|
|
159
60
|
protected _statePersistence: EditorStatePersistence | null = null;
|
|
@@ -162,22 +63,16 @@ export class EditorShellStore {
|
|
|
162
63
|
* Bumped only when post-processing-relevant state changes (environment / scene
|
|
163
64
|
* structure / play-stop scene swap). The viewport rebuilds its EffectComposer
|
|
164
65
|
* only when this changes — NOT on every notify — so a transform drag/scrub (which
|
|
165
|
-
* notifies per frame but never touches these)
|
|
66
|
+
* notifies per frame but never touches these) never recompiles shaders 60×/s.
|
|
166
67
|
*/
|
|
167
68
|
protected _composerVersion = 0;
|
|
168
69
|
bindScene(
|
|
169
70
|
scene: THREE.Scene,
|
|
170
71
|
renderer?: THREE.WebGLRenderer,
|
|
171
|
-
batchedRenderer?: BatchedRenderer | null,
|
|
172
72
|
camera?: THREE.Camera,
|
|
173
73
|
): void {
|
|
174
|
-
|
|
175
|
-
// Stop restores it; the adopted scene stays the active one.
|
|
176
|
-
const base = this._adoptionStack[0];
|
|
177
|
-
if (base) base.scene = scene;
|
|
178
|
-
else this._scene = scene;
|
|
74
|
+
this._scene = scene;
|
|
179
75
|
this._renderer = renderer ?? null;
|
|
180
|
-
this._batchedRenderer = batchedRenderer ?? null;
|
|
181
76
|
if (camera) this._camera = camera;
|
|
182
77
|
}
|
|
183
78
|
|
|
@@ -187,18 +82,14 @@ export class EditorShellStore {
|
|
|
187
82
|
}
|
|
188
83
|
|
|
189
84
|
/** Update only the editor presentation camera (for perspective/orthographic
|
|
190
|
-
* switching) without rebinding
|
|
85
|
+
* switching) without rebinding the scene. */
|
|
191
86
|
setViewportCamera(camera: THREE.Camera): void {
|
|
192
87
|
this._camera = camera;
|
|
193
88
|
}
|
|
194
89
|
|
|
195
|
-
|
|
196
90
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* itself names no endpoint, so a document-local store (Asset Lab) or a
|
|
200
|
-
* fixture store simply never persists. Same collaborator shape as
|
|
201
|
-
* `attachHistory`, same one-owner rule.
|
|
91
|
+
* Attach the shell’s view-state and thumbnail persistence transport. A document-local store may
|
|
92
|
+
* omit it. One collaborator owns persistence for the store.
|
|
202
93
|
*/
|
|
203
94
|
attachStatePersistence(persistence: EditorStatePersistence): void {
|
|
204
95
|
if (this._statePersistence === persistence) return;
|
|
@@ -217,12 +108,12 @@ export class EditorShellStore {
|
|
|
217
108
|
|
|
218
109
|
/**
|
|
219
110
|
* Frame the whole active scene — the first thing a reader should see when a
|
|
220
|
-
* world they did not author
|
|
111
|
+
* world they did not author opens. The viewport keeps
|
|
221
112
|
* asking until the world HAS content, because a game builds itself
|
|
222
113
|
* asynchronously; any camera input from the reader cancels it (see
|
|
223
114
|
* `the world root's stage`). A no-op for a scene the reader already has a camera on.
|
|
224
115
|
*
|
|
225
|
-
* `gameCamera` is a LIVE LOOKUP, not a camera:
|
|
116
|
+
* `gameCamera` is a LIVE LOOKUP, not a camera: a game's own camera
|
|
226
117
|
* may not exist yet on the frame it is captured on, so the viewport asks
|
|
227
118
|
* again while its window is open. When one turns up, its view is adopted
|
|
228
119
|
* instead of any measurement — see `scene-framing.ts`.
|
|
@@ -232,10 +123,9 @@ export class EditorShellStore {
|
|
|
232
123
|
this.shell.focusOnScene();
|
|
233
124
|
}
|
|
234
125
|
|
|
235
|
-
|
|
236
126
|
/**
|
|
237
127
|
* Live viewport camera pose, read straight from the bound Three.js camera +
|
|
238
|
-
* orbit target (`bindScene`/`setOrbitTarget`
|
|
128
|
+
* orbit target (`bindScene`/`setOrbitTarget`).
|
|
239
129
|
* Null only when nothing has bound a camera yet (e.g. a headless store in a
|
|
240
130
|
* unit test that never mounted `the world root's stage`).
|
|
241
131
|
*/
|
|
@@ -271,40 +161,6 @@ export class EditorShellStore {
|
|
|
271
161
|
return this._gizmoEpoch;
|
|
272
162
|
}
|
|
273
163
|
|
|
274
|
-
/**
|
|
275
|
-
* Return the exact projected parents changed after `epoch`, or `null` when a
|
|
276
|
-
* root/unclassified change crossed that boundary and callers must rebuild.
|
|
277
|
-
* Parent timestamps make this safe across React batching: no consumer drains
|
|
278
|
-
* a shared queue, and several projectile commits collapse into one union.
|
|
279
|
-
*/
|
|
280
|
-
ingestObjectMapChangesSince(epoch: number): IngestObjectMapChanges | null {
|
|
281
|
-
if (epoch < 0 || epoch > this._gizmoEpoch || epoch < this._objectMapFullChangeEpoch) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
const changedParentIds = new Set<string>();
|
|
285
|
-
for (const [id, changedAt] of this._objectMapParentChangedAt) {
|
|
286
|
-
if (changedAt > epoch) changedParentIds.add(id);
|
|
287
|
-
}
|
|
288
|
-
return { epoch: this._gizmoEpoch, changedParentIds };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** Return exact added/removed identities since `epoch`, or `null` across an unclassified edit. */
|
|
292
|
-
ingestObjectMapMembershipChangesSince(epoch: number): IngestObjectMapMembershipChanges | null {
|
|
293
|
-
if (
|
|
294
|
-
epoch < 0 ||
|
|
295
|
-
epoch > this._gizmoEpoch ||
|
|
296
|
-
epoch < this._objectMapMembershipFullChangeEpoch ||
|
|
297
|
-
epoch < this._objectMapMembershipHistoryFloor
|
|
298
|
-
) {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
const changedIds = new Set<string>();
|
|
302
|
-
for (const [id, changedAt] of this._objectMapMembershipChangedAt) {
|
|
303
|
-
if (changedAt > epoch) changedIds.add(id);
|
|
304
|
-
}
|
|
305
|
-
return { epoch: this._gizmoEpoch, changedIds };
|
|
306
|
-
}
|
|
307
|
-
|
|
308
164
|
isSkeletonVisible(id: string): boolean {
|
|
309
165
|
return Boolean(getUserData(this._objectMap.get(id), 'skeletonVisible'));
|
|
310
166
|
}
|
|
@@ -326,10 +182,6 @@ export class EditorShellStore {
|
|
|
326
182
|
get shadingMode(): ShadingMode {
|
|
327
183
|
return this._shadingMode;
|
|
328
184
|
}
|
|
329
|
-
/** Notify subscribers after an ingest adapter mutated a live foreign object
|
|
330
|
-
* (material/visibility). Runtime-only — no dirty/autosave, because the edit
|
|
331
|
-
* lives on the live `Object3D` and nothing here owns a document. */
|
|
332
|
-
|
|
333
185
|
/** Notify after a live adapter changed `objectMap` membership.
|
|
334
186
|
*
|
|
335
187
|
* Unlike an ordinary reflected-property edit, a membership change requires
|
|
@@ -337,191 +189,9 @@ export class EditorShellStore {
|
|
|
337
189
|
* place, and a remove+add burst can keep its size unchanged, so neither map
|
|
338
190
|
* identity nor size is a complete signal. `_gizmoEpoch` is the viewport's
|
|
339
191
|
* existing explicit rebuild input; bump it only at this structural seam. */
|
|
340
|
-
notifyIngestObjectMapEdit(
|
|
192
|
+
notifyIngestObjectMapEdit(): void {
|
|
341
193
|
this._gizmoEpoch++;
|
|
342
|
-
|
|
343
|
-
this._objectMapFullChangeEpoch = this._gizmoEpoch;
|
|
344
|
-
this._objectMapParentChangedAt.clear();
|
|
345
|
-
} else {
|
|
346
|
-
for (const id of delta.changedParentIds) {
|
|
347
|
-
this._objectMapParentChangedAt.set(id, this._gizmoEpoch);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (delta?.addedIds === undefined || delta.removedIds === undefined) {
|
|
351
|
-
this._objectMapMembershipFullChangeEpoch = this._gizmoEpoch;
|
|
352
|
-
this._objectMapMembershipChangedAt.clear();
|
|
353
|
-
} else {
|
|
354
|
-
for (const id of delta.addedIds) this._objectMapMembershipChangedAt.set(id, this._gizmoEpoch);
|
|
355
|
-
for (const id of delta.removedIds) {
|
|
356
|
-
this._objectMapMembershipChangedAt.set(id, this._gizmoEpoch);
|
|
357
|
-
}
|
|
358
|
-
const historyFloor = Math.max(0, this._gizmoEpoch - OBJECT_MAP_MEMBERSHIP_HISTORY);
|
|
359
|
-
if (historyFloor > this._objectMapMembershipHistoryFloor) {
|
|
360
|
-
this._objectMapMembershipHistoryFloor = historyFloor;
|
|
361
|
-
for (const [id, changedAt] of this._objectMapMembershipChangedAt) {
|
|
362
|
-
if (changedAt <= historyFloor) this._objectMapMembershipChangedAt.delete(id);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
const affectsShell =
|
|
367
|
-
delta === undefined ||
|
|
368
|
-
delta.rootsChanged ||
|
|
369
|
-
[...(delta.addedIds ?? []), ...(delta.removedIds ?? [])].some((id) =>
|
|
370
|
-
this.shell.selectedEntityIds.has(id),
|
|
371
|
-
);
|
|
372
|
-
this.shell.notifyChange('content', affectsShell ? 'shell' : 'object-map', false);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// --- Play mode scene swap ---
|
|
376
|
-
|
|
377
|
-
/** Callback to sync transform edits to game ECS. Set by play-mode, cleared on exit.
|
|
378
|
-
* Receives the NODE ID as well as the live object: the `PhysicsAdapter` seam
|
|
379
|
-
* it ultimately feeds is id-keyed since P-4, and the id is already in hand
|
|
380
|
-
* at the one call site — so nothing downstream has to map an object back. */
|
|
381
|
-
protected _ecsSyncTransform: ((id: string, obj: THREE.Object3D) => void) | null = null;
|
|
382
|
-
|
|
383
|
-
/** Set a callback that syncs editor transform changes to the game's ECS state. */
|
|
384
|
-
setEcsSyncTransform(fn: ((id: string, obj: THREE.Object3D) => void) | null): void {
|
|
385
|
-
this._ecsSyncTransform = fn;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Swap the store's active scene to the game scene. Editor scene is preserved.
|
|
390
|
-
*
|
|
391
|
-
* Also snapshots whatever the active document half hands back from
|
|
392
|
-
* {@link _captureAdoptionExtras}. A half that keeps mutating its LIVE
|
|
393
|
-
* metadata during play (so its inspector fields keep working) must have
|
|
394
|
-
* those edits discarded when play stops — otherwise a play-time edit
|
|
395
|
-
* silently persists with `isDirty` false. `exitPlayScene` restores the
|
|
396
|
-
* snapshot.
|
|
397
|
-
*/
|
|
398
|
-
enterPlayScene(
|
|
399
|
-
gameScene: THREE.Scene,
|
|
400
|
-
imageConfig?: WorldRendererConfig | undefined,
|
|
401
|
-
projectedObjects?: ReadonlyMap<string, THREE.Object3D>,
|
|
402
|
-
): void {
|
|
403
|
-
this._adoptionStack.push({
|
|
404
|
-
scene: this._scene,
|
|
405
|
-
objectMap: this._objectMap,
|
|
406
|
-
extras: this._captureAdoptionExtras(),
|
|
407
|
-
selection: new Set(this.shell.selectedEntityIds),
|
|
408
|
-
imageConfig: this._adoptedImageConfig,
|
|
409
|
-
});
|
|
410
|
-
this._scene = gameScene;
|
|
411
|
-
this._applyAdoptedImageConfig(imageConfig);
|
|
412
|
-
|
|
413
|
-
// The store INDEXES an adopted scene; it no longer models it. A foreign
|
|
414
|
-
// adapter supplies its pure projection explicitly, so editor identity is
|
|
415
|
-
// never written into the game's Object3Ds. Source-backed/adopted worlds
|
|
416
|
-
// already carry native authoring stamps and retain the fallback walk.
|
|
417
|
-
const gameMap = projectedObjects
|
|
418
|
-
? new Map(projectedObjects)
|
|
419
|
-
: (() => {
|
|
420
|
-
const stamped = new Map<string, THREE.Object3D>();
|
|
421
|
-
const visit = (obj: THREE.Object3D): void => {
|
|
422
|
-
if (isEditorOwnedObject(obj)) return;
|
|
423
|
-
const eid = entityIdOf(obj);
|
|
424
|
-
if (eid !== undefined) stamped.set(eid, obj);
|
|
425
|
-
for (const child of obj.children) visit(child);
|
|
426
|
-
};
|
|
427
|
-
for (const child of gameScene.children) visit(child);
|
|
428
|
-
return stamped;
|
|
429
|
-
})();
|
|
430
|
-
this._objectMap = gameMap;
|
|
431
|
-
|
|
432
|
-
this.shell.applySelectionBeforePresentation(
|
|
433
|
-
[...this.shell.selectedEntityIds].filter((id) => gameMap.has(id)),
|
|
434
|
-
);
|
|
435
|
-
// A viewport LOD pin must not follow the entity id into the adopted game
|
|
436
|
-
// scene — there `WebGLRenderer.projectObject` owns level visibility (it
|
|
437
|
-
// calls `lod.update(camera)` for every visible LOD whose `autoUpdate` is
|
|
438
|
-
// still true, which is what a pin turns OFF), and per-frame enforcement
|
|
439
|
-
// would fight it.
|
|
440
|
-
this._lodForcedLevels.clear();
|
|
441
|
-
this._composerVersion++; // scene swapped → composer must rebuild against the game scene
|
|
442
|
-
this.shell.notifyChange();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** Whether the active scene is one another document adopted (`enterPlayScene`). */
|
|
446
|
-
get hasAdoptedScene(): boolean {
|
|
447
|
-
return this._adoptionStack.length > 0;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/** Restore the previously-adopted scene (LIFO — see enterPlayScene). */
|
|
451
|
-
exitPlayScene(): void {
|
|
452
|
-
const frame = this._adoptionStack.pop();
|
|
453
|
-
if (!frame) return;
|
|
454
|
-
this._scene = frame.scene;
|
|
455
|
-
this._objectMap = frame.objectMap;
|
|
456
|
-
this._applyAdoptedImageConfig(frame.imageConfig);
|
|
457
|
-
// Discard any play-time env/name/ui edits — restore the pre-play snapshot.
|
|
458
|
-
this._restoreAdoptionExtras(frame.extras);
|
|
459
|
-
this.shell.applySelectionBeforePresentation([...frame.selection]);
|
|
460
|
-
this._composerVersion++; // scene swapped back → composer must rebuild against the editor scene
|
|
461
|
-
this.shell.notifyChange();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Release ONE adopter's adoption, wherever it sits in the stack. When
|
|
466
|
-
* `scene` is the ACTIVE scene this is exactly `exitPlayScene`; when a later
|
|
467
|
-
* adoption (play over the R3F design session) has already replaced it, the
|
|
468
|
-
* frame that would restore `scene` is unlinked from the middle of the stack
|
|
469
|
-
* instead — nothing visible changes now, and the LATER adopter's exit
|
|
470
|
-
* restores the state underneath as if this adoption never happened. No-op
|
|
471
|
-
* when `scene` was never adopted (or was already released).
|
|
472
|
-
*/
|
|
473
|
-
releaseAdoptedScene(scene: THREE.Scene): void {
|
|
474
|
-
if (this._scene === scene) {
|
|
475
|
-
this.exitPlayScene();
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
const index = this._adoptionStack.findIndex((frame) => frame.scene === scene);
|
|
479
|
-
if (index === -1) return;
|
|
480
|
-
this._adoptionStack.splice(index, 1);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* THE ADOPTED CONTENT'S OWN COLOUR PIPELINE — what it is, not where it is
|
|
485
|
-
* applied.
|
|
486
|
-
*
|
|
487
|
-
* A mounted world may report the pipeline it was authored for
|
|
488
|
-
* (`MountedThreeRoot.rendererConfig`), and `applyWorldRendererConfig` puts it
|
|
489
|
-
* on the renderer its own host handed it. In the editor those are two
|
|
490
|
-
* DIFFERENT renderers: the design session mounts against a non-rasterizing
|
|
491
|
-
* one (`authoring/design-time-renderer.ts`) and the viewport draws the
|
|
492
|
-
* mounted scene with its own. So the declaration landed on a surface with no
|
|
493
|
-
* pixels while the surface with pixels kept the engine's defaults — measured
|
|
494
|
-
* on the translated Godot platformer, whose roughness-0 metal reads as chrome
|
|
495
|
-
* under the editor's ACES while the game renders it under its own curve.
|
|
496
|
-
*
|
|
497
|
-
* The store only REMEMBERS the declaration and bumps `_composerVersion` when
|
|
498
|
-
* it changes; `the world root's stage.rebuildComposer` is the one place that derives
|
|
499
|
-
* the viewport renderer's whole configuration, and it applies this last. That
|
|
500
|
-
* split is why there is no restore bookkeeping here: a rebuild always resets
|
|
501
|
-
* the renderer to the engine defaults first, so "unwind" is just the next
|
|
502
|
-
* rebuild seeing a different (or absent) config.
|
|
503
|
-
*/
|
|
504
|
-
protected _adoptedImageConfig: WorldRendererConfig | undefined;
|
|
505
|
-
|
|
506
|
-
/** What the active adopted content declared about the renderer that draws it,
|
|
507
|
-
* or `undefined` when it declared nothing (leave the editor's own alone). */
|
|
508
|
-
get adoptedImageConfig(): WorldRendererConfig | undefined {
|
|
509
|
-
return this._adoptedImageConfig;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
protected _applyAdoptedImageConfig(next: WorldRendererConfig | undefined): void {
|
|
513
|
-
if (next === this._adoptedImageConfig) return;
|
|
514
|
-
this._adoptedImageConfig = next;
|
|
515
|
-
this._composerVersion++;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/** True while an ADOPTED live game scene is the active scene — every
|
|
519
|
-
* `enterPlayScene` caller: play adoption, ingest mounts, and the R3F
|
|
520
|
-
* design session. Adopted scenes own their lighting/rendering completely;
|
|
521
|
-
* the viewport uses this to keep editor-fabricated presentation (the
|
|
522
|
-
* helper light rig) out of them. */
|
|
523
|
-
get isAdoptedSceneActive(): boolean {
|
|
524
|
-
return this._adoptionStack.length > 0;
|
|
194
|
+
this.shell.notifyChange('content', 'shell', false);
|
|
525
195
|
}
|
|
526
196
|
|
|
527
197
|
// --- Vertex snap ---
|
|
@@ -543,25 +213,10 @@ export class EditorShellStore {
|
|
|
543
213
|
* represented honestly. Both use the same offscreen render-target technique,
|
|
544
214
|
* so neither needs `preserveDrawingBuffer` on the live WebGL canvas.
|
|
545
215
|
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* the destination is the canvas
|
|
549
|
-
*
|
|
550
|
-
* LINEAR output transfer (`WebGLPrograms.getParameters` and
|
|
551
|
-
* `WebGLRenderer.setProgram` both gate on `currentRenderTarget === null`).
|
|
552
|
-
* So the single-target version of this function answered "what does the
|
|
553
|
-
* viewport look like" with a raw linear frame — measurably darker and
|
|
554
|
-
* flatter than the pixels on screen, and immune to every tone-mapping
|
|
555
|
-
* change (verified live: flipping the viewport to `NoToneMapping` moved the
|
|
556
|
-
* captured PNG by 0 bytes). That made this door — the only look at the EDIT
|
|
557
|
-
* viewport, and the source of every project thumbnail — unusable as
|
|
558
|
-
* evidence about the image.
|
|
559
|
-
*
|
|
560
|
-
* The fix is three's own: render the scene into a HALF-FLOAT linear buffer
|
|
561
|
-
* (so highlights survive to be tone-mapped) and resolve it through
|
|
562
|
-
* `OutputPass`, which reads `toneMapping`/`toneMappingExposure`/
|
|
563
|
-
* `outputColorSpace` off the renderer and applies exactly what the on-screen
|
|
564
|
-
* frame gets.
|
|
216
|
+
* Render into a half-float linear target to preserve highlights, then resolve through
|
|
217
|
+
* `OutputPass` using the renderer’s toneMapping, toneMappingExposure and outputColorSpace.
|
|
218
|
+
* Three applies those settings directly only when the destination is the canvas, so the
|
|
219
|
+
* output pass gives captures the same colour transform as the on-screen viewport.
|
|
565
220
|
*/
|
|
566
221
|
protected _renderViewportImage(
|
|
567
222
|
size: number | { width: number; height: number },
|
|
@@ -711,8 +366,6 @@ export class EditorShellStore {
|
|
|
711
366
|
shadingMode: this._shadingMode,
|
|
712
367
|
};
|
|
713
368
|
|
|
714
|
-
Object.assign(state, this._editorStateExtras());
|
|
715
|
-
|
|
716
369
|
if (this._camera) {
|
|
717
370
|
const cam = this._camera as THREE.PerspectiveCamera;
|
|
718
371
|
state['camera'] = {
|
|
@@ -727,12 +380,9 @@ export class EditorShellStore {
|
|
|
727
380
|
// --- Mutations ---
|
|
728
381
|
|
|
729
382
|
/**
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
* the saved scene or the undo history. Enforced per frame by
|
|
734
|
-
* `EditorViewport.update` (the glTF loads async and entity rebuilds replace
|
|
735
|
-
* the THREE.LOD instance, so a one-shot apply would silently un-pin).
|
|
383
|
+
* Editor-only pinned LOD-level index per entity, ordered by distance. Keep this view state out of
|
|
384
|
+
* saved descriptors and undo history. EditorViewport reapplies it each frame as asynchronous
|
|
385
|
+
* loads and entity rebuilds replace LOD instances.
|
|
736
386
|
*/
|
|
737
387
|
protected _lodForcedLevels = new Map<string, number>();
|
|
738
388
|
|
|
@@ -772,29 +422,7 @@ export class EditorShellStore {
|
|
|
772
422
|
this.shell.notifyChange();
|
|
773
423
|
}
|
|
774
424
|
|
|
775
|
-
// --- Subclass hooks -------------------------------------------------------
|
|
776
|
-
// The ONLY way the shell reaches into a document half. Each is a no-op here,
|
|
777
|
-
// so the shell runs standalone; a subclass that owns a document overrides the
|
|
778
|
-
// ones it needs (`store-notify-scope.test.ts` exercises the deferral hooks).
|
|
779
|
-
|
|
780
425
|
|
|
781
|
-
|
|
782
|
-
/** Captured into the adoption frame by `enterPlayScene`, handed back by
|
|
783
|
-
* `exitPlayScene`. A document half snapshots its metadata here so
|
|
784
|
-
* play-time edits to it are discarded on exit. */
|
|
785
|
-
protected _captureAdoptionExtras(): unknown {
|
|
786
|
-
return undefined;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/** Restore counterpart of {@link _captureAdoptionExtras}. */
|
|
790
|
-
protected _restoreAdoptionExtras(_extras: unknown): void {}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
/** Extra keys folded into the periodic editor-state save. A document half
|
|
794
|
-
* contributes whatever it needs restored on the next boot. */
|
|
795
|
-
protected _editorStateExtras(): Record<string, unknown> {
|
|
796
|
-
return {};
|
|
797
|
-
}
|
|
798
426
|
}
|
|
799
427
|
|
|
800
428
|
const threeStates = new WeakMap<ShellStore, EditorShellStore>();
|