@volter/editor-threejs 0.5.66 → 0.5.68

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.
Files changed (99) hide show
  1. package/contributions/animation-timeline.utility.tsx +44 -0
  2. package/contributions/three-integration.service.ts +13 -0
  3. package/package.json +96 -6
  4. package/src/adapter/renderer-config.ts +3 -4
  5. package/src/adapter/three-contract.ts +72 -0
  6. package/src/ecs/object-marks.ts +1 -1
  7. package/src/ecs/user-data.ts +0 -9
  8. package/src/host-hierarchy-objects.ts +31 -0
  9. package/src/kit/animation/three-clips-subject.ts +190 -0
  10. package/src/kit/asset-compare.ts +294 -0
  11. package/src/kit/asset-preview-command.ts +265 -0
  12. package/src/kit/asset-preview-framing.ts +357 -0
  13. package/src/kit/asset-preview.ts +2802 -0
  14. package/src/kit/asset-workflow/model-inspection.ts +830 -0
  15. package/src/kit/authoring/component-instance-root.ts +171 -0
  16. package/src/kit/authoring/design-time-settle.ts +343 -0
  17. package/src/kit/authoring/live-object-transform.ts +62 -0
  18. package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
  19. package/src/kit/authoring/object3d-document-session.ts +1965 -0
  20. package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
  21. package/src/kit/authoring/quarks-particle-systems.ts +19 -0
  22. package/src/kit/authoring/shell-viewport-policy.ts +48 -0
  23. package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
  24. package/src/kit/authoring/three-projection-core.ts +226 -0
  25. package/src/kit/authoring/viewport-pick-context.ts +39 -0
  26. package/src/kit/authoring/viewport-raycast.ts +240 -0
  27. package/src/kit/authoring/world-hidden-viewport.ts +95 -0
  28. package/src/kit/camera-authoring.ts +175 -0
  29. package/src/kit/components/CameraInfo.tsx +56 -0
  30. package/src/kit/components/InspectorObjectPreview.tsx +57 -0
  31. package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
  32. package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
  33. package/src/kit/components/StageHost.tsx +2547 -0
  34. package/src/kit/components/StageOverlays.tsx +21 -0
  35. package/src/kit/components/StatsOverlay.tsx +78 -0
  36. package/src/kit/components/ToolObject3DPreview.tsx +39 -0
  37. package/src/kit/components/ViewportFurniture.tsx +655 -0
  38. package/src/kit/components/ViewportOverlay.tsx +215 -0
  39. package/src/kit/components/ViewportShadingMenu.tsx +340 -0
  40. package/src/kit/components/ViewportViewMenu.tsx +155 -0
  41. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
  42. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  43. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
  44. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
  45. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  46. package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
  47. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
  48. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  49. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
  50. package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
  51. package/src/kit/components/stage-keyboard.tsx +40 -0
  52. package/src/kit/components/stage-overlay-set.tsx +105 -0
  53. package/src/kit/components/stage-presence-markers.ts +482 -0
  54. package/src/kit/components/stage-transform-chrome.ts +30 -0
  55. package/src/kit/components/stage-transform-tools.tsx +73 -0
  56. package/src/kit/components/stage-view-name.ts +30 -0
  57. package/src/kit/components/standard-viewport-dressing.ts +1042 -0
  58. package/src/kit/components/world-root-binding.ts +64 -0
  59. package/src/kit/constraint-helper.ts +338 -0
  60. package/src/kit/editor-shell-store.ts +814 -0
  61. package/src/kit/editor-viewport.ts +6621 -0
  62. package/src/kit/entity-lod.ts +31 -0
  63. package/src/kit/entity-object.ts +92 -0
  64. package/src/kit/hierarchy-mark-reader.ts +74 -0
  65. package/src/kit/instanced-presentation.ts +164 -0
  66. package/src/kit/live-module-source.ts +230 -0
  67. package/src/kit/model-thumbnail.ts +539 -0
  68. package/src/kit/play-camera-flight.ts +300 -0
  69. package/src/kit/projection/three.ts +898 -0
  70. package/src/kit/reflection-probe-helper.ts +142 -0
  71. package/src/kit/scene-document-viewport.ts +51 -0
  72. package/src/kit/scene-framing.ts +315 -0
  73. package/src/kit/scene-view-fog.ts +89 -0
  74. package/src/kit/spatial-handle-visuals.ts +332 -0
  75. package/src/kit/stories/three-story-model.ts +66 -0
  76. package/src/kit/three-canvas-render.ts +44 -0
  77. package/src/kit/three-hierarchy-row-media.ts +26 -0
  78. package/src/kit/three-inspection-media.ts +73 -0
  79. package/src/kit/three-integration.ts +86 -0
  80. package/src/kit/three-state.ts +33 -0
  81. package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
  82. package/src/kit/three-viewport/camera-fit.ts +41 -0
  83. package/src/kit/three-viewport/interactive-renderer.ts +132 -0
  84. package/src/kit/three-viewport/selection-brackets.ts +355 -0
  85. package/src/kit/three-viewport/selection-outline.ts +333 -0
  86. package/src/kit/three-viewport/skeleton-helper.ts +61 -0
  87. package/src/kit/three-viewport/source-color.ts +197 -0
  88. package/src/kit/three-viewport/studio-environment.ts +96 -0
  89. package/src/kit/trigger-volume-helper.ts +116 -0
  90. package/src/kit/viewport-actions.ts +128 -0
  91. package/src/kit/viewport-authoring-policy.ts +154 -0
  92. package/src/kit/viewport-commands.ts +318 -0
  93. package/src/kit/viewport-hotkeys.ts +119 -0
  94. package/src/kit/viewport-shading-boundary.ts +12 -0
  95. package/src/kit/viewport-status-facet.ts +53 -0
  96. package/src/object3d-contributions.ts +494 -0
  97. package/src/render/viewport-shading.ts +6 -2
  98. package/src/viewport-api.ts +92 -0
  99. package/src/viewport-door.ts +237 -0
@@ -0,0 +1,116 @@
1
+ import { EDITOR_LAYER } from '@volter/editor-threejs/viewport/editor-layers';
2
+ import { triggerVolumeOf } from '@volter/editor-threejs/adapter/trigger-volume';
3
+ import { setUserData } from '@volter/editor-threejs/ecs/user-data';
4
+ import * as THREE from 'three';
5
+
6
+ const RING = new THREE.Color(0x4fd1c5);
7
+ const RING_SELECTED = new THREE.Color(0x9ff0e8);
8
+ const SEGMENTS = 64;
9
+ /** How far the corner ticks rise off the ring, in world units. */
10
+ const TICK_HEIGHT = 0.6;
11
+
12
+ /**
13
+ * Editor-only visualization for a project-owned trigger volume — the group
14
+ * whose radius decides where a hazard fires or a quest arrives. The game's
15
+ * source says nothing about being authored; it just carries the radius in
16
+ * `userData`, and this helper draws it: a ground circle in the source's local
17
+ * XZ plane that extends upward at ±X/±Z, which is what such a volume tests.
18
+ *
19
+ * Same contract as `ReflectionProbeHelper`: it follows the live Object3D but
20
+ * owns every line and material it draws, sits on EDITOR_LAYER, and therefore
21
+ * never enters the game's tree or its runtime camera.
22
+ */
23
+ export class TriggerVolumeHelper extends THREE.Group {
24
+ private signature = '';
25
+ private selected = false;
26
+
27
+ constructor(readonly source: THREE.Object3D) {
28
+ super();
29
+ this.name = 'Trigger Volume Helper';
30
+ this.matrixAutoUpdate = false;
31
+ this.renderOrder = 998;
32
+ setUserData(this, 'editorHelper', true);
33
+ setUserData(this, 'editorHelperType', 'trigger-volumes');
34
+ this.layers.set(EDITOR_LAYER);
35
+ this.refresh(true);
36
+ }
37
+
38
+ setSelected(selected: boolean): void {
39
+ if (this.selected === selected) return;
40
+ this.selected = selected;
41
+ this.refresh(true);
42
+ }
43
+
44
+ update(): void {
45
+ this.refresh(false);
46
+ this.source.updateWorldMatrix(true, false);
47
+ this.matrix.copy(this.source.matrixWorld);
48
+ this.matrixWorldNeedsUpdate = true;
49
+ }
50
+
51
+ dispose(): void {
52
+ this.clearVisuals();
53
+ this.removeFromParent();
54
+ }
55
+
56
+ private refresh(force: boolean): void {
57
+ const volume = triggerVolumeOf(this.source);
58
+ if (!volume) return;
59
+ const radius = volume.radius;
60
+ const next = String(radius);
61
+ if (!force && next === this.signature) return;
62
+ this.signature = next;
63
+ this.clearVisuals();
64
+
65
+ const color = this.selected ? RING_SELECTED : RING;
66
+ const opacity = this.selected ? 1 : 0.62;
67
+
68
+ const ring: number[] = [];
69
+ for (let i = 0; i < SEGMENTS; i += 1) {
70
+ const angle = (i / SEGMENTS) * Math.PI * 2;
71
+ ring.push(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
72
+ }
73
+ const ringGeometry = new THREE.BufferGeometry();
74
+ ringGeometry.setAttribute('position', new THREE.Float32BufferAttribute(ring, 3));
75
+ const loop = new THREE.LineLoop(ringGeometry, this.lineMaterial(color, opacity));
76
+ loop.renderOrder = this.renderOrder;
77
+ this.add(loop);
78
+
79
+ const ticks: number[] = [];
80
+ for (const [x, z] of [
81
+ [radius, 0],
82
+ [-radius, 0],
83
+ [0, radius],
84
+ [0, -radius],
85
+ ] as const) {
86
+ ticks.push(x, 0, z, x, TICK_HEIGHT, z);
87
+ }
88
+ const tickGeometry = new THREE.BufferGeometry();
89
+ tickGeometry.setAttribute('position', new THREE.Float32BufferAttribute(ticks, 3));
90
+ const tickLines = new THREE.LineSegments(tickGeometry, this.lineMaterial(color, opacity));
91
+ tickLines.renderOrder = this.renderOrder;
92
+ this.add(tickLines);
93
+
94
+ this.traverse((object) => object.layers.set(EDITOR_LAYER));
95
+ }
96
+
97
+ private lineMaterial(color: THREE.Color, opacity: number): THREE.LineBasicMaterial {
98
+ return new THREE.LineBasicMaterial({
99
+ color,
100
+ depthTest: false,
101
+ transparent: true,
102
+ opacity,
103
+ toneMapped: false,
104
+ });
105
+ }
106
+
107
+ private clearVisuals(): void {
108
+ for (const child of [...this.children]) {
109
+ child.removeFromParent();
110
+ const line = child as THREE.LineSegments;
111
+ line.geometry?.dispose();
112
+ const materials = Array.isArray(line.material) ? line.material : [line.material];
113
+ for (const material of materials) material?.dispose();
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * THE THREE VIEWPORT'S PALETTE ENTRIES — tool and space modes, grid, helpers,
3
+ * stats, snap, shading, the view presets, Frame and the camera verbs. They are
4
+ * the active three stage's, published as a palette contribution while that
5
+ * stage is bound (`components/stage-keyboard.tsx`), and they act on that
6
+ * stage's own store: the same stage its keys drive (`viewport-hotkeys.ts`).
7
+ */
8
+ import type { ActionContribution, ContributedAction } from '@volter/editor-sdk/chrome';
9
+ import {
10
+ alignCameraToViewport,
11
+ cameraAuthoringPresentation,
12
+ currentCameraAuthoringSubject,
13
+ leaveCameraView,
14
+ pilotCamera,
15
+ subscribeCameraAuthoring,
16
+ toggleCameraPreviewPin,
17
+ viewThroughCamera,
18
+ } from './camera-authoring';
19
+ import { registerContributedActions } from '@volter/editor-sdk/kit/chrome-registry';
20
+ import type { EditorShellStore } from './editor-shell-store';
21
+ import { object3DDocumentSession } from './authoring/object3d-document-session-registry';
22
+ import { requestTransformMode } from '@volter/editor-sdk/kit/transform-mode-request';
23
+ import { activeWorkspaceDocumentId } from '@volter/editor-sdk/kit/workspace-document-registry';
24
+ import {
25
+ setViewGridVisible,
26
+ viewGridVisible,
27
+ viewPresentationBinding,
28
+ } from '@volter/editor-sdk/kit/viewport-presentation';
29
+
30
+ function cameraActions(): ContributedAction[] {
31
+ const presentation = cameraAuthoringPresentation();
32
+ if (presentation.view) {
33
+ return [{ id: 'camera.exitView', label: 'Exit Camera View', execute: leaveCameraView }];
34
+ }
35
+ const subject = currentCameraAuthoringSubject();
36
+ if (!subject) return [];
37
+ return [
38
+ {
39
+ id: 'camera.viewThrough',
40
+ label: `View Through ${subject.name}`,
41
+ execute: () => viewThroughCamera(subject),
42
+ },
43
+ ...(subject.canAuthorPose
44
+ ? [
45
+ { id: 'camera.pilot', label: `Pilot ${subject.name}`, execute: () => pilotCamera(subject) },
46
+ {
47
+ id: 'camera.alignToView',
48
+ label: `Align ${subject.name} to Current View`,
49
+ execute: () => alignCameraToViewport(subject),
50
+ },
51
+ ]
52
+ : []),
53
+ {
54
+ id: 'camera.pinPreview',
55
+ label: presentation.previewPinned ? 'Unpin Camera Preview' : 'Pin Camera Preview',
56
+ execute: toggleCameraPreviewPin,
57
+ },
58
+ ];
59
+ }
60
+
61
+ function activeObject3DDocumentSession() {
62
+ const documentId = activeWorkspaceDocumentId();
63
+ return documentId ? object3DDocumentSession(documentId) : null;
64
+ }
65
+
66
+ /** A document stage paints the mode its SESSION holds; the store's is only what the menu shows
67
+ * where there is no session (`set-shading-mode` and the shading menu route the same way). */
68
+ function setShading(store: EditorShellStore, mode: Parameters<EditorShellStore['setShadingMode']>[0]): void {
69
+ const session = activeObject3DDocumentSession();
70
+ if (session) session.setMode(mode);
71
+ else store.setShadingMode(mode);
72
+ }
73
+
74
+ export function registerViewportActions(store: EditorShellStore): () => void {
75
+ const fixed: ContributedAction[] = [
76
+ { id: 'mode.select', label: 'Select Tool (no gizmo)', shortcut: 'transform.select', execute: () => requestTransformMode(store.shell, 'select') },
77
+ { id: 'mode.combined', label: 'Transform Mode (all handles)', shortcut: 'transform.combined', execute: () => requestTransformMode(store.shell, 'combined') },
78
+ { id: 'mode.translate', label: 'Translate Mode', shortcut: 'transform.translate', execute: () => requestTransformMode(store.shell, 'translate') },
79
+ { id: 'mode.rotate', label: 'Rotate Mode', shortcut: 'transform.rotate', execute: () => requestTransformMode(store.shell, 'rotate') },
80
+ { id: 'mode.scale', label: 'Scale Mode', shortcut: 'transform.scale', execute: () => requestTransformMode(store.shell, 'scale') },
81
+ { id: 'mode.world', label: 'World Space', execute: () => store.shell.setTransformSpace('world') },
82
+ { id: 'mode.local', label: 'Local Space', execute: () => store.shell.setTransformSpace('local') },
83
+ {
84
+ id: 'toggle.grid',
85
+ label: 'Toggle Grid',
86
+ execute: () => {
87
+ // The ACTIVE view's grid switch, one per view (`kit/viewport-presentation`).
88
+ const viewId = activeWorkspaceDocumentId();
89
+ if (viewId && viewPresentationBinding(viewId)) setViewGridVisible(viewId, !viewGridVisible(viewId));
90
+ },
91
+ },
92
+ { id: 'toggle.helpers', label: 'Toggle Helpers', execute: () => store.shell.toggleHelpers() },
93
+ { id: 'toggle.stats', label: 'Toggle Stats Overlay', execute: () => store.toggleStats() },
94
+ { id: 'toggle.snap', label: 'Toggle Snap', shortcut: 'viewport.toggleSnap', execute: () => store.shell.toggleSnap() },
95
+ { id: 'toggle.surface-snap', label: 'Toggle Surface Snap', execute: () => store.shell.toggleSnapToSurface() },
96
+ { id: 'shading.solid', label: 'Material Shading', execute: () => setShading(store, 'solid') },
97
+ { id: 'shading.clay', label: 'Solid Shading', execute: () => setShading(store, 'clay') },
98
+ { id: 'shading.wireframe', label: 'Wireframe Shading', execute: () => setShading(store, 'wireframe') },
99
+ { id: 'shading.unlit', label: 'Unlit Shading', execute: () => setShading(store, 'unlit') },
100
+ { id: 'shading.normals', label: 'Normal Shading', execute: () => setShading(store, 'normals') },
101
+ { id: 'shading.overdraw', label: 'Overdraw Shading', execute: () => setShading(store, 'overdraw') },
102
+ { id: 'view.top', label: 'Top View', shortcut: 'view.top', execute: () => store.shell.setViewPreset('top') },
103
+ { id: 'view.front', label: 'Front View', shortcut: 'view.front', execute: () => store.shell.setViewPreset('front') },
104
+ { id: 'view.right', label: 'Right View', shortcut: 'view.right', execute: () => store.shell.setViewPreset('right') },
105
+ { id: 'view.bottom', label: 'Bottom View', shortcut: 'view.bottom', execute: () => store.shell.setViewPreset('bottom') },
106
+ { id: 'view.back', label: 'Back View', shortcut: 'view.back', execute: () => store.shell.setViewPreset('back') },
107
+ { id: 'view.left', label: 'Left View', shortcut: 'view.left', execute: () => store.shell.setViewPreset('left') },
108
+ { id: 'view.orbitLeft', label: 'Orbit Left', shortcut: 'view.orbitLeft', execute: () => store.shell.stepView('orbit-left') },
109
+ { id: 'view.orbitRight', label: 'Orbit Right', shortcut: 'view.orbitRight', execute: () => store.shell.stepView('orbit-right') },
110
+ { id: 'view.orbitUp', label: 'Orbit Up', shortcut: 'view.orbitUp', execute: () => store.shell.stepView('orbit-up') },
111
+ { id: 'view.orbitDown', label: 'Orbit Down', shortcut: 'view.orbitDown', execute: () => store.shell.stepView('orbit-down') },
112
+ { id: 'view.opposite', label: 'Opposite View', shortcut: 'view.opposite', execute: () => store.shell.stepView('opposite') },
113
+ { id: 'view.rollLeft', label: 'Roll Left', shortcut: 'view.rollLeft', execute: () => store.shell.stepView('roll-left') },
114
+ { id: 'view.rollRight', label: 'Roll Right', shortcut: 'view.rollRight', execute: () => store.shell.stepView('roll-right') },
115
+ { id: 'view.all', label: 'Frame All', shortcut: 'view.all', execute: () => store.shell.frameAll() },
116
+ { id: 'view.zoomIn', label: 'Zoom In', shortcut: 'view.zoomIn', execute: () => store.shell.zoomView(1) },
117
+ { id: 'view.zoomOut', label: 'Zoom Out', shortcut: 'view.zoomOut', execute: () => store.shell.zoomView(-1) },
118
+ { id: 'view.perspective', label: 'Perspective View', shortcut: 'view.perspective', execute: () => store.shell.setViewPreset('perspective') },
119
+ { id: 'view.projection', label: 'Perspective/Orthographic', shortcut: 'view.projection', execute: () => store.shell.toggleProjection() },
120
+ { id: 'view.camera', label: 'Camera View', shortcut: 'view.camera', execute: () => store.shell.toggleCameraView() },
121
+ { id: 'focus.selected', label: 'Focus Selected', shortcut: 'viewport.frameSelection', execute: () => store.shell.focusOnSelection() },
122
+ ];
123
+ const contribution: ActionContribution = {
124
+ actions: () => [...fixed, ...cameraActions()],
125
+ subscribe: subscribeCameraAuthoring,
126
+ };
127
+ return registerContributedActions(contribution);
128
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * WHAT THE GIZMO VIEWPORT ASKS THE SHELL — the one collaborator
3
+ * `editor-viewport.ts` declares and the shell installs at boot.
4
+ *
5
+ * ARCHITECTURE-CORE §Editor chrome, "The viewport stack is separable": a
6
+ * surface below the shell never imports the shell's transport, a world
7
+ * authoring adapter, or project discovery. The viewport was importing six
8
+ * `authoring/` modules to ask four questions —
9
+ *
10
+ * · which adapter am I driving, and which one do the shared panels drive?
11
+ * · what is under the pointer?
12
+ * · which native root owns the current selection, and is it painted here?
13
+ * · which three root is world-hidden, and how do I apply that?
14
+ *
15
+ * — and every one of those answers is a COMPOSITE fact. `layered-pick.ts`
16
+ * walks `CompositeAuthoringAdapter.childAdapters()` in manifest z-order;
17
+ * `panel-authoring.ts` reaches the workspace-document registry;
18
+ * `world-hidden-viewport.ts` and `viewport-tool-context.ts` resolve the
19
+ * composite's live three child. The viewport itself knows about none of that:
20
+ * it raycasts a scene and drags a gizmo. Measured, those six edges were 96 of
21
+ * its 193 files.
22
+ *
23
+ * ## The default is the NO-SHELL answer, and it is deliberate
24
+ *
25
+ * {@link DEFAULT_VIEWPORT_AUTHORING_POLICY} is what a viewport mounted with no
26
+ * shell above it (a bounded host; a fixture) gets: no composite exists,
27
+ * so there are no layers to walk, no other panel to agree with, no world-group
28
+ * eye to honour. Each default states that outright rather than half-answering.
29
+ * The ONE default that is real logic is {@link adapterOnlyToolOwner} — the
30
+ * structural single-adapter fallback `resolveViewportToolContext` already used
31
+ * for non-composite adapters, which lives here now and is imported BACK by
32
+ * `authoring/viewport-tool-context.ts` so there is one copy of it.
33
+ *
34
+ * In the editor the shell policy is installed once by `EditorProvider`
35
+ * (`EditorContext.tsx`), before any viewport mounts — including the Asset Lab
36
+ * document's own, which is why that document keeps today's behaviour exactly.
37
+ * The install site is the PROVIDER rather than `the world root's stage` because a
38
+ * workspace can have an Asset Lab document open with no scene the world root's stage
39
+ * mounted at all, and that document's viewport must not silently fall to the
40
+ * defaults.
41
+ */
42
+
43
+ import type { AuthoringAdapter } from '@volter/editor-project/adapter';
44
+ import { adapterOnlyToolOwner, type ViewportToolOwner } from '@volter/editor-sdk/kit/authoring/viewport-tool-owner';
45
+
46
+ export { adapterOnlyToolOwner, type ViewportToolOwner };
47
+ import type * as THREE from 'three';
48
+ import { makeNoAuthoringAdapter } from '@volter/editor-sdk/kit/authoring/no-authoring-adapter';
49
+ import type { ShellStore } from '@volter/editor-sdk/kit/shell-store';
50
+
51
+
52
+ export interface ViewportAuthoringPolicy {
53
+ /** The adapter this viewport drives when its host injected none. */
54
+ activeAuthoring(store: ShellStore): AuthoringAdapter;
55
+ /**
56
+ * The adapter a selection SCOPE is keyed on — the Hierarchy's own resolution,
57
+ * so a viewport drill-in and the panel breadcrumb name the same scope.
58
+ */
59
+ panelAuthoring(store: ShellStore): AuthoringAdapter;
60
+ /** Topmost-first hit test across every visible, pick-unlocked layer. */
61
+ pick(
62
+ store: ShellStore,
63
+ clientX: number,
64
+ clientY: number,
65
+ intent: 'normal' | 'deep',
66
+ ): string | null;
67
+ /** Which native root the selection resolves to, or `null` for empty,
68
+ * organizational and cross-root selections. */
69
+ toolOwner(adapter: AuthoringAdapter, selectedIds: Iterable<string>): ViewportToolOwner | null;
70
+ /** Is that owner's surface actually PAINTED in this viewport right now? */
71
+ toolOwnerPainted(store: ShellStore, owner: ViewportToolOwner | null): boolean;
72
+ /** Is a THREE stage painted here at all, selection or not — the gate for
73
+ * the grid and axis lines (Blender's floor is persistent; only the gizmos
74
+ * need an owner). */
75
+ threeSurfaceShowing(store: ShellStore, adapter: AuthoringAdapter): boolean;
76
+ /** The one three world rendered here, for the world-hidden eye. */
77
+ threeViewportRootId(store: ShellStore): string | null;
78
+ /** Apply the eye to every entity object (a no-op unless that root is hidden). */
79
+ applyRootHiddenVisibility(
80
+ objectMap: ReadonlyMap<string, THREE.Object3D>,
81
+ threeRootId: string | null,
82
+ ): void;
83
+ /** The scene-level half of the eye: background, fog, IBL, env objects. */
84
+ suppressRootEnvironment(scene: THREE.Scene): void;
85
+ }
86
+
87
+
88
+ /** Memoized per store, because the viewport calls `activeAuthoring` per gesture
89
+ * and adapter IDENTITY keys the selection scope and the handle cache. Mirrors
90
+ * `authoring/active-adapter.ts`'s own per-store memo. */
91
+ const _noAuthoringByStore = new WeakMap<ShellStore, AuthoringAdapter>();
92
+
93
+ export const DEFAULT_VIEWPORT_AUTHORING_POLICY: ViewportAuthoringPolicy = {
94
+ activeAuthoring(store) {
95
+ let adapter = _noAuthoringByStore.get(store);
96
+ if (!adapter) {
97
+ adapter = makeNoAuthoringAdapter(store, 'No authoring adapter');
98
+ _noAuthoringByStore.set(store, adapter);
99
+ }
100
+ return adapter;
101
+ },
102
+ panelAuthoring(store) {
103
+ // No shell means no shared panels to agree with.
104
+ return DEFAULT_VIEWPORT_AUTHORING_POLICY.activeAuthoring(store);
105
+ },
106
+ pick() {
107
+ // No shell means no layer stack to walk. A host that mounts this viewport
108
+ // for one document supplies its own hit test (`EditorViewportOptions.pick`),
109
+ // which is what the Asset Lab document does.
110
+ return null;
111
+ },
112
+ toolOwner: adapterOnlyToolOwner,
113
+ toolOwnerPainted() {
114
+ // No world-group eyes exist without a composite, so nothing can be hidden.
115
+ return true;
116
+ },
117
+ threeSurfaceShowing(_store, adapter) {
118
+ return !adapter.rects && typeof adapter.hierarchy.object3D === 'function';
119
+ },
120
+ threeViewportRootId() {
121
+ return null;
122
+ },
123
+ applyRootHiddenVisibility() {
124
+ // Unreachable while `threeViewportRootId` is null — stated rather than
125
+ // implemented, so the no-shell story is one story.
126
+ },
127
+ suppressRootEnvironment() {},
128
+ };
129
+
130
+ let _policy: ViewportAuthoringPolicy = DEFAULT_VIEWPORT_AUTHORING_POLICY;
131
+
132
+ /**
133
+ * Install the shell's composition. One owner, same rule as the store's
134
+ * `attachHistory`/`attachStatePersistence`: re-installing the SAME policy is a
135
+ * no-op (React strict-mode double-invokes the provider body), a DIFFERENT one
136
+ * throws rather than letting two shells disagree about what is under a pointer.
137
+ */
138
+ export function installViewportAuthoringPolicy(policy: ViewportAuthoringPolicy): void {
139
+ if (_policy === policy) return;
140
+ if (_policy !== DEFAULT_VIEWPORT_AUTHORING_POLICY) {
141
+ throw new Error('A viewport authoring policy is already installed.');
142
+ }
143
+ _policy = policy;
144
+ }
145
+
146
+ /** The installed policy, or the no-shell default. */
147
+ export function viewportAuthoringPolicy(): ViewportAuthoringPolicy {
148
+ return _policy;
149
+ }
150
+
151
+ /** Test seam: drop the installed policy. */
152
+ export function __resetViewportAuthoringPolicyForTest(): void {
153
+ _policy = DEFAULT_VIEWPORT_AUTHORING_POLICY;
154
+ }