@volter/editor-threejs 0.5.65 → 0.5.67
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 +2 -0
- package/contributions/animation-mixers.service.ts +20 -0
- package/contributions/animation-timeline.utility.tsx +44 -0
- package/contributions/three-integration.service.ts +13 -0
- package/dist-node/serving.mjs +405 -0
- package/package.json +113 -5
- package/serving/animation-live-module.ts +70 -0
- package/serving/animation-stamp.ts +88 -0
- package/serving/index.ts +14 -0
- package/serving/model-import-conversion.ts +344 -0
- package/src/adapter/ingest/scene-capture.ts +1 -23
- package/src/adapter/renderer-config.ts +3 -4
- package/src/adapter/three-contract.ts +72 -0
- package/src/animation/live-mixers.ts +55 -0
- package/src/ecs/object-marks.ts +1 -1
- package/src/ecs/user-data.ts +0 -16
- package/src/host-hierarchy-objects.ts +31 -0
- package/src/kit/animation/three-clips-subject.ts +190 -0
- package/src/kit/asset-compare.ts +294 -0
- package/src/kit/asset-preview-command.ts +265 -0
- package/src/kit/asset-preview-framing.ts +357 -0
- package/src/kit/asset-preview.ts +2802 -0
- package/src/kit/asset-workflow/model-inspection.ts +830 -0
- package/src/kit/authoring/component-instance-root.ts +171 -0
- package/src/kit/authoring/design-time-settle.ts +343 -0
- package/src/kit/authoring/live-object-transform.ts +62 -0
- package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
- package/src/kit/authoring/object3d-document-session.ts +1965 -0
- package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
- package/src/kit/authoring/quarks-particle-systems.ts +19 -0
- package/src/kit/authoring/shell-viewport-policy.ts +48 -0
- package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/kit/authoring/three-projection-core.ts +226 -0
- package/src/kit/authoring/viewport-pick-context.ts +39 -0
- package/src/kit/authoring/viewport-raycast.ts +240 -0
- package/src/kit/authoring/world-hidden-viewport.ts +95 -0
- package/src/kit/camera-authoring.ts +175 -0
- package/src/kit/components/CameraInfo.tsx +56 -0
- package/src/kit/components/InspectorObjectPreview.tsx +57 -0
- package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
- package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
- package/src/kit/components/StageHost.tsx +2547 -0
- package/src/kit/components/StageOverlays.tsx +21 -0
- package/src/kit/components/StatsOverlay.tsx +78 -0
- package/src/kit/components/ToolObject3DPreview.tsx +39 -0
- package/src/kit/components/ViewportFurniture.tsx +655 -0
- package/src/kit/components/ViewportOverlay.tsx +215 -0
- package/src/kit/components/ViewportShadingMenu.tsx +340 -0
- package/src/kit/components/ViewportViewMenu.tsx +155 -0
- package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
- package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
- package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
- package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
- package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
- package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
- package/src/kit/components/stage-keyboard.tsx +40 -0
- package/src/kit/components/stage-overlay-set.tsx +105 -0
- package/src/kit/components/stage-presence-markers.ts +482 -0
- package/src/kit/components/stage-transform-chrome.ts +30 -0
- package/src/kit/components/stage-transform-tools.tsx +73 -0
- package/src/kit/components/stage-view-name.ts +30 -0
- package/src/kit/components/standard-viewport-dressing.ts +1042 -0
- package/src/kit/components/world-root-binding.ts +64 -0
- package/src/kit/constraint-helper.ts +338 -0
- package/src/kit/editor-shell-store.ts +814 -0
- package/src/kit/editor-viewport.ts +6621 -0
- package/src/kit/entity-lod.ts +31 -0
- package/src/kit/entity-object.ts +92 -0
- package/src/kit/hierarchy-mark-reader.ts +74 -0
- package/src/kit/instanced-presentation.ts +164 -0
- package/src/kit/live-module-source.ts +230 -0
- package/src/kit/model-thumbnail.ts +539 -0
- package/src/kit/play-camera-flight.ts +300 -0
- package/src/kit/projection/three.ts +898 -0
- package/src/kit/reflection-probe-helper.ts +142 -0
- package/src/kit/scene-document-viewport.ts +51 -0
- package/src/kit/scene-framing.ts +315 -0
- package/src/kit/scene-view-fog.ts +89 -0
- package/src/kit/spatial-handle-visuals.ts +332 -0
- package/src/kit/stories/three-story-model.ts +66 -0
- package/src/kit/three-canvas-render.ts +44 -0
- package/src/kit/three-hierarchy-row-media.ts +26 -0
- package/src/kit/three-inspection-media.ts +73 -0
- package/src/kit/three-integration.ts +86 -0
- package/src/kit/three-state.ts +33 -0
- package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/kit/three-viewport/camera-fit.ts +41 -0
- package/src/kit/three-viewport/interactive-renderer.ts +132 -0
- package/src/kit/three-viewport/selection-brackets.ts +355 -0
- package/src/kit/three-viewport/selection-outline.ts +333 -0
- package/src/kit/three-viewport/skeleton-helper.ts +61 -0
- package/src/kit/three-viewport/source-color.ts +197 -0
- package/src/kit/three-viewport/studio-environment.ts +96 -0
- package/src/kit/trigger-volume-helper.ts +116 -0
- package/src/kit/viewport-actions.ts +128 -0
- package/src/kit/viewport-authoring-policy.ts +154 -0
- package/src/kit/viewport-commands.ts +318 -0
- package/src/kit/viewport-hotkeys.ts +119 -0
- package/src/kit/viewport-shading-boundary.ts +12 -0
- package/src/kit/viewport-status-facet.ts +53 -0
- package/src/object3d-contributions.ts +494 -0
- package/src/render/viewport-shading.ts +6 -2
- package/src/viewport/content-bounds.ts +38 -4
- package/src/viewport/environment.ts +16 -0
- package/src/viewport-api.ts +92 -0
- package/src/viewport-door.ts +237 -0
- package/src/animation/animation-clock.ts +0 -479
- package/src/animation/runtime-inspection.ts +0 -45
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE AUTHORED THREE VIEWPORT'S PUBLIC SHAPE — what a lane that mounts something reaches through
|
|
3
|
+
* `viewport-door.ts`: the rig a camera flight drives, the frame loop it rides, the helpers it
|
|
4
|
+
* shows, every mounted stage, and the one verb that swaps the viewport's subject for a live
|
|
5
|
+
* instance's roots (Play's adoption of the running scene).
|
|
6
|
+
*/
|
|
7
|
+
import type { MountedRoot } from '@volter/editor-project/adapter/root-adapter';
|
|
8
|
+
import type * as THREE from 'three';
|
|
9
|
+
|
|
10
|
+
/** A live instance's roots, presented as the authored viewport's subject. */
|
|
11
|
+
export interface ViewportPresentation {
|
|
12
|
+
/** The root whose native subject the viewport shows. */
|
|
13
|
+
readonly worldId: string;
|
|
14
|
+
/** Restores the viewport's prior subject; idempotent. */
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The authored viewport's camera rig, while a viewport is mounted. */
|
|
19
|
+
export interface ViewportRig {
|
|
20
|
+
readonly camera: THREE.PerspectiveCamera;
|
|
21
|
+
/** The camera the stage draws with NOW: {@link camera}, or the orthographic one in an
|
|
22
|
+
* orthographic view. What a document lighting or picking in step with the picture needs. */
|
|
23
|
+
drawCamera(): THREE.Camera;
|
|
24
|
+
readonly orbit: { readonly target: THREE.Vector3; enabled: boolean; update(): void };
|
|
25
|
+
/** The editor's own scene (helpers live on its editor layer). */
|
|
26
|
+
readonly scene: THREE.Scene;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ONE MOUNTED 3D STAGE, named by the document it draws. Every 3D document
|
|
31
|
+
* mounts a stage of its own (ARCHITECTURE-CORE §One stage); this is the door
|
|
32
|
+
* to a particular one, where the viewport door's singular members
|
|
33
|
+
* are the door to whichever is primary.
|
|
34
|
+
*/
|
|
35
|
+
export interface ViewportStage {
|
|
36
|
+
/** The workspace document this stage draws. */
|
|
37
|
+
readonly documentId: string;
|
|
38
|
+
rig(): ViewportRig;
|
|
39
|
+
/** As the viewport door's `setHelper`, on this stage alone. */
|
|
40
|
+
setHelper(kind: string, object: THREE.Object3D | null): void;
|
|
41
|
+
/** Runs after THIS stage's own per-frame update; the return unsubscribes. */
|
|
42
|
+
onFrame(fn: (dtSeconds: number) => void): () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* THE AUTHORED VIEWPORT, as a lane that mounts something reaches it: the rig
|
|
47
|
+
* a camera flight drives, the frame loop it rides, and the one verb that
|
|
48
|
+
* swaps the viewport's subject for a live instance's roots (Play's adoption
|
|
49
|
+
* of the running scene). Which native surface the viewport renders is the
|
|
50
|
+
* host's; a lane that finds no rig (a headless tab, a project with no Three
|
|
51
|
+
* root) mounts without one.
|
|
52
|
+
*
|
|
53
|
+
* THE SINGULAR MEMBERS ARE THE PRIMARY STAGE — the stage presenting live
|
|
54
|
+
* roots if one is, else the FOCUSED stage, else the first bound. A camera
|
|
55
|
+
* flight and Play's adoption want exactly that one, which is why they read
|
|
56
|
+
* here and never enumerate.
|
|
57
|
+
*
|
|
58
|
+
* {@link stages} is EVERY mounted 3D stage. A helper is an `Object3D` and an
|
|
59
|
+
* `Object3D` has one parent, so there is no "set it once for all stages": a
|
|
60
|
+
* helper wanted on every stage is set on each stage through `stages()`, and
|
|
61
|
+
* `onStages` is how a lane keeps up with stages that mount and unmount after
|
|
62
|
+
* it made that pass.
|
|
63
|
+
*/
|
|
64
|
+
/** A mounted adapter surface offered to the viewport. Execution, input and
|
|
65
|
+
* ticking stay with the caller; presentation requires only identity and mount.
|
|
66
|
+
* A game's richer root instance can satisfy this interface directly.
|
|
67
|
+
*/
|
|
68
|
+
export interface ViewportRoot {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly mounted: MountedRoot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ViewportDoor {
|
|
74
|
+
rig(): ViewportRig | null;
|
|
75
|
+
/** Null when the viewport has no subject among `roots` (no Three root). */
|
|
76
|
+
presentRoots(roots: readonly ViewportRoot[]): ViewportPresentation | null;
|
|
77
|
+
/** Runs after the viewport's own per-frame update; the return unsubscribes. */
|
|
78
|
+
onFrame(fn: (dtSeconds: number) => void): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Show an editor-only helper object of `kind` in the authored viewport (a
|
|
81
|
+
* baked navmesh's debug mesh), replacing the previous one of that kind;
|
|
82
|
+
* null clears it. Visibility follows the host's Helpers menu: a kind the
|
|
83
|
+
* menu lists (`navmesh`) toggles on its own, any other follows the master
|
|
84
|
+
* toggle. Kept across viewport remounts.
|
|
85
|
+
*/
|
|
86
|
+
setHelper(kind: string, object: THREE.Object3D | null): void;
|
|
87
|
+
/** Every mounted 3D stage, in bind order. */
|
|
88
|
+
stages(): readonly ViewportStage[];
|
|
89
|
+
/** Fires whenever a stage mounts or unmounts; the return unsubscribes. */
|
|
90
|
+
onStages(fn: (stages: readonly ViewportStage[]) => void): () => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE AUTHORED VIEWPORT'S DOOR — the host's side of `host.viewport` (WORK.md
|
|
3
|
+
* §Play leaves the host, P2). Every mounted 3D STAGE binds its rig and its
|
|
4
|
+
* presenter here under its own document id (ARCHITECTURE-CORE §One stage:
|
|
5
|
+
* there is one stage host, mounted once per 3D document, and the door is per
|
|
6
|
+
* stage) and drives its frame hook from its own RAF loop; the lanes that
|
|
7
|
+
* mount something (Play's adoption of the live scene, the play-entry camera
|
|
8
|
+
* flight) read here and never hold a panel.
|
|
9
|
+
*
|
|
10
|
+
* The SINGULAR members — `viewportRig`, `presentViewportRoots`,
|
|
11
|
+
* `setViewportHelper`, `onViewportFrame` — resolve THE PRIMARY STAGE at call
|
|
12
|
+
* time: the stage presenting live roots if one is, else the FOCUSED stage
|
|
13
|
+
* (`activeWorkspaceDocumentId()`'s), else the first bound. Focus and
|
|
14
|
+
* presentation are the two facts; nothing here is "the scene viewport" by
|
|
15
|
+
* name. `viewportStages()` is every stage, for the lane that must touch each
|
|
16
|
+
* one (a helper `Object3D` has one parent, so it is set per stage).
|
|
17
|
+
*
|
|
18
|
+
* Until the lanes move, the host's own lanes import this registry directly; a
|
|
19
|
+
* package reads `@volter/editor-sdk/host`'s `viewport` member, which is this.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ViewportDoor, ViewportPresentation, ViewportRig, ViewportRoot, ViewportStage } from './viewport-api';
|
|
23
|
+
import { activeWorkspaceDocumentId } from '@volter/editor-sdk/kit/workspace-document-registry';
|
|
24
|
+
|
|
25
|
+
type Presenter = (roots: readonly ViewportRoot[]) => ViewportPresentation | null;
|
|
26
|
+
/** An editor-only helper object, typed by the host door it is shown through. */
|
|
27
|
+
type HelperObject = Parameters<ViewportDoor['setHelper']>[1];
|
|
28
|
+
type HelperSink = (kind: string, object: HelperObject) => void;
|
|
29
|
+
|
|
30
|
+
interface Binding {
|
|
31
|
+
readonly documentId: string;
|
|
32
|
+
readonly rig: ViewportRig;
|
|
33
|
+
readonly present: Presenter;
|
|
34
|
+
readonly setHelper: HelperSink;
|
|
35
|
+
readonly frameListeners: Set<(dtSeconds: number) => void>;
|
|
36
|
+
/** Helpers set on THIS stage by kind, so a re-bind restores them. */
|
|
37
|
+
readonly helpers: Map<string, HelperObject>;
|
|
38
|
+
/** A presentation of live roots is alive on this stage right now. */
|
|
39
|
+
presenting: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Every bound stage, keyed by the document it draws; insertion-ordered, so
|
|
43
|
+
* "the first bound" is a real answer. */
|
|
44
|
+
const bound = new Map<string, Binding>();
|
|
45
|
+
/**
|
|
46
|
+
* What belongs to the STAGE rather than to its current canvas, held by
|
|
47
|
+
* document id so it OUTLIVES a binding: its frame subscribers and its
|
|
48
|
+
* helpers. A stage's viewport remounts (a dock re-layout, HMR) without its
|
|
49
|
+
* baked navmesh having to be re-baked — the `setHelper` contract the SDK
|
|
50
|
+
* states — and without a lane's `onFrame` going quiet.
|
|
51
|
+
*/
|
|
52
|
+
const stageStateByDocument = new Map<
|
|
53
|
+
string,
|
|
54
|
+
{
|
|
55
|
+
readonly frameListeners: Set<(dtSeconds: number) => void>;
|
|
56
|
+
readonly helpers: Map<string, HelperObject>;
|
|
57
|
+
}
|
|
58
|
+
>();
|
|
59
|
+
|
|
60
|
+
function stageStateFor(documentId: string): {
|
|
61
|
+
readonly frameListeners: Set<(dtSeconds: number) => void>;
|
|
62
|
+
readonly helpers: Map<string, HelperObject>;
|
|
63
|
+
} {
|
|
64
|
+
let state = stageStateByDocument.get(documentId);
|
|
65
|
+
if (!state) {
|
|
66
|
+
state = { frameListeners: new Set(), helpers: new Map() };
|
|
67
|
+
stageStateByDocument.set(documentId, state);
|
|
68
|
+
}
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
/** Listeners on the singular `onViewportFrame`: they follow the PRIMARY
|
|
72
|
+
* stage, so they are held here and fanned in rather than added to a
|
|
73
|
+
* binding's own set, which would strand them on a stage that stops being
|
|
74
|
+
* primary (or unmounts). */
|
|
75
|
+
const primaryFrameListeners = new Set<(dtSeconds: number) => void>();
|
|
76
|
+
const stageListeners = new Set<(stages: readonly ViewportStage[]) => void>();
|
|
77
|
+
|
|
78
|
+
/** THE PRIMARY STAGE: presenting live roots > focused > first bound. */
|
|
79
|
+
function primary(): Binding | null {
|
|
80
|
+
for (const binding of bound.values()) if (binding.presenting) return binding;
|
|
81
|
+
const focused = activeWorkspaceDocumentId();
|
|
82
|
+
if (focused !== null) {
|
|
83
|
+
const match = bound.get(focused);
|
|
84
|
+
if (match) return match;
|
|
85
|
+
}
|
|
86
|
+
return bound.values().next().value ?? null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let stagesCache: readonly ViewportStage[] | null = null;
|
|
90
|
+
|
|
91
|
+
function notifyStages(): void {
|
|
92
|
+
stagesCache = null;
|
|
93
|
+
const snapshot = viewportStages();
|
|
94
|
+
for (const fn of stageListeners) fn(snapshot);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A stage door resolves its binding BY ID on every call, so a door held
|
|
98
|
+
* across that stage's remount keeps driving the live canvas rather than the
|
|
99
|
+
* disposed one. */
|
|
100
|
+
function stageDoor(documentId: string, rig: ViewportRig): ViewportStage {
|
|
101
|
+
const state = stageStateFor(documentId);
|
|
102
|
+
return {
|
|
103
|
+
documentId,
|
|
104
|
+
rig: () => bound.get(documentId)?.rig ?? rig,
|
|
105
|
+
setHelper: (kind, object) => {
|
|
106
|
+
state.helpers.set(kind, object);
|
|
107
|
+
bound.get(documentId)?.setHelper(kind, object);
|
|
108
|
+
},
|
|
109
|
+
onFrame: (fn) => {
|
|
110
|
+
state.frameListeners.add(fn);
|
|
111
|
+
return () => {
|
|
112
|
+
state.frameListeners.delete(fn);
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A mounted stage binds its rig, how it presents live roots and how it shows
|
|
119
|
+
* a helper, under the id of the document it draws; the return unbinds (a
|
|
120
|
+
* re-bind for the same id REPLACES, and a stale binding's unbind never
|
|
121
|
+
* removes its successor). Helpers set through the singular door before the
|
|
122
|
+
* bind are applied now, the way they always were — but "before the bind" is
|
|
123
|
+
* now per stage: a helper shown on THIS document's stage returns to it, and
|
|
124
|
+
* a helper set while nothing is bound anywhere is dropped rather than
|
|
125
|
+
* appearing on whichever stage happens to mount next. */
|
|
126
|
+
export function bindViewportRig(
|
|
127
|
+
rig: ViewportRig,
|
|
128
|
+
present: Presenter,
|
|
129
|
+
setHelper: HelperSink,
|
|
130
|
+
options: { readonly documentId: string },
|
|
131
|
+
): () => void {
|
|
132
|
+
const state = stageStateFor(options.documentId);
|
|
133
|
+
const next: Binding = {
|
|
134
|
+
documentId: options.documentId,
|
|
135
|
+
rig,
|
|
136
|
+
present,
|
|
137
|
+
setHelper,
|
|
138
|
+
frameListeners: state.frameListeners,
|
|
139
|
+
helpers: state.helpers,
|
|
140
|
+
presenting: false,
|
|
141
|
+
};
|
|
142
|
+
bound.set(options.documentId, next);
|
|
143
|
+
for (const [kind, object] of next.helpers) setHelper(kind, object);
|
|
144
|
+
notifyStages();
|
|
145
|
+
return () => {
|
|
146
|
+
if (bound.get(options.documentId) !== next) return;
|
|
147
|
+
bound.delete(options.documentId);
|
|
148
|
+
notifyStages();
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Show (or clear with null) the editor-only helper of `kind` on the PRIMARY
|
|
153
|
+
* stage. A helper wanted on every stage is set per stage through
|
|
154
|
+
* {@link viewportStages} — an `Object3D` has one parent. */
|
|
155
|
+
export function setViewportHelper(kind: string, object: HelperObject): void {
|
|
156
|
+
const stage = primary();
|
|
157
|
+
if (!stage) return;
|
|
158
|
+
stage.helpers.set(kind, object);
|
|
159
|
+
stage.setHelper(kind, object);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function viewportRig(): ViewportRig | null {
|
|
163
|
+
return primary()?.rig ?? null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function presentViewportRoots(roots: readonly ViewportRoot[]): ViewportPresentation | null {
|
|
167
|
+
const stage = primary();
|
|
168
|
+
if (!stage) return null;
|
|
169
|
+
const presentation = stage.present(roots);
|
|
170
|
+
if (!presentation) return null;
|
|
171
|
+
// The stage stays PRIMARY for as long as it is showing live roots — that is
|
|
172
|
+
// what makes Play's own camera flight and frame hook keep finding it while
|
|
173
|
+
// focus moves to the live document.
|
|
174
|
+
stage.presenting = true;
|
|
175
|
+
let released = false;
|
|
176
|
+
return {
|
|
177
|
+
worldId: presentation.worldId,
|
|
178
|
+
dispose: () => {
|
|
179
|
+
if (!released) {
|
|
180
|
+
released = true;
|
|
181
|
+
stage.presenting = false;
|
|
182
|
+
}
|
|
183
|
+
presentation.dispose();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Runs after the PRIMARY stage's own per-frame update, and RE-TARGETS: the
|
|
189
|
+
* listener follows whichever stage is primary, not the one that was primary
|
|
190
|
+
* when it subscribed. */
|
|
191
|
+
export function onViewportFrame(fn: (dtSeconds: number) => void): () => void {
|
|
192
|
+
primaryFrameListeners.add(fn);
|
|
193
|
+
return () => {
|
|
194
|
+
primaryFrameListeners.delete(fn);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Every bound stage, in bind order. */
|
|
199
|
+
export function viewportStages(): readonly ViewportStage[] {
|
|
200
|
+
if (!stagesCache)
|
|
201
|
+
stagesCache = [...bound.values()].map((binding) => stageDoor(binding.documentId, binding.rig));
|
|
202
|
+
return stagesCache;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* WHICH HELPER KINDS A STAGE IS HOLDING — the kinds something has actually
|
|
207
|
+
* shown on it through `setHelper` (`@volter/editor-sdk/host`,
|
|
208
|
+
* `ViewportStage.setHelper`), cleared ones excluded.
|
|
209
|
+
*
|
|
210
|
+
* It exists because a 3D document's own OVERLAYS MENU must follow what the
|
|
211
|
+
* document put there and nothing else (`Object3DDocumentToolbar`): Blender's
|
|
212
|
+
* viewport overlay popover lists Bones, and ours may not list a Bones row over
|
|
213
|
+
* a document that has no bones to show. Same rule as the Outliner's restriction
|
|
214
|
+
* columns — a control follows what the thing behind it ANSWERS, never a knob.
|
|
215
|
+
*/
|
|
216
|
+
export function viewportStageHelperKinds(documentId: string): readonly string[] {
|
|
217
|
+
const state = stageStateByDocument.get(documentId);
|
|
218
|
+
if (!state) return [];
|
|
219
|
+
return [...state.helpers].filter(([, object]) => object !== null).map(([kind]) => kind);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function onViewportStages(fn: (stages: readonly ViewportStage[]) => void): () => void {
|
|
223
|
+
stageListeners.add(fn);
|
|
224
|
+
return () => {
|
|
225
|
+
stageListeners.delete(fn);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** A stage's RAF loop calls this after its own update each frame. The stage's
|
|
230
|
+
* own listeners always run; the primary-stage listeners run here only when
|
|
231
|
+
* this stage IS the primary. */
|
|
232
|
+
export function runViewportFrame(documentId: string, dtSeconds: number): void {
|
|
233
|
+
const binding = bound.get(documentId);
|
|
234
|
+
if (!binding) return;
|
|
235
|
+
for (const fn of binding.frameListeners) fn(dtSeconds);
|
|
236
|
+
if (primary() === binding) for (const fn of primaryFrameListeners) fn(dtSeconds);
|
|
237
|
+
}
|