@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.
Files changed (133) hide show
  1. package/NOTICE +7 -2
  2. package/README.md +8 -13
  3. package/contributions/animation-mixers.service.ts +2 -4
  4. package/contributions/animation-timeline.utility.tsx +1 -2
  5. package/contributions/three.page.ts +50 -0
  6. package/dist-node/serving.mjs +84 -3
  7. package/dist-node/shared-three.mjs +145 -0
  8. package/package.json +296 -16
  9. package/serving/animation-stamp.ts +1 -1
  10. package/serving/index.ts +22 -5
  11. package/serving/model-import-conversion.ts +6 -3
  12. package/serving/shared-three.ts +176 -0
  13. package/src/adapter/constraint.ts +1 -18
  14. package/src/adapter/object3d-authoring-subject.ts +1 -12
  15. package/src/adapter/reflection-probe.ts +1 -9
  16. package/src/adapter/three-contract.ts +6 -60
  17. package/src/animation/live-mixers.ts +0 -4
  18. package/src/api.ts +7 -0
  19. package/src/asset-loaders.ts +2 -178
  20. package/src/capture/photograph-sampling.ts +333 -0
  21. package/src/capture/scene.ts +1 -1
  22. package/src/ecs/object-marks.ts +6 -14
  23. package/src/ecs/user-data.ts +3 -138
  24. package/src/kit/animation/three-clips-subject.ts +19 -42
  25. package/src/kit/asset-compare-core.ts +161 -0
  26. package/src/kit/asset-compare.ts +8 -27
  27. package/src/kit/asset-preview-command.ts +14 -19
  28. package/src/kit/asset-preview-framing.ts +8 -67
  29. package/src/kit/asset-preview.ts +109 -342
  30. package/src/kit/asset-workflow/model-inspection.ts +11 -154
  31. package/src/kit/asset-workflow/preview-resource-lifetime.ts +25 -0
  32. package/src/kit/authoring/component-instance-root.ts +3 -39
  33. package/src/kit/authoring/live-object-transform.ts +1 -30
  34. package/src/kit/authoring/object3d-document-session-registry.ts +7 -15
  35. package/src/kit/authoring/object3d-document-session.ts +471 -208
  36. package/src/kit/authoring/shell-viewport-policy.ts +18 -24
  37. package/src/kit/authoring/source-object3d-authoring-adapter.ts +6 -48
  38. package/src/kit/authoring/three-projection-core.ts +11 -40
  39. package/src/kit/authoring/viewport-pick-context.ts +5 -12
  40. package/src/kit/authoring/viewport-raycast.ts +15 -93
  41. package/src/kit/authoring/world-hidden-viewport.ts +8 -61
  42. package/src/kit/capture-camera-pose.ts +68 -0
  43. package/src/kit/collaboration-presence.ts +41 -0
  44. package/src/kit/components/CameraInfo.tsx +4 -6
  45. package/src/kit/components/InspectorObjectPreview.tsx +3 -30
  46. package/src/kit/components/Object3DDocumentToolbar.css +75 -0
  47. package/src/kit/components/Object3DDocumentToolbar.tsx +18 -84
  48. package/src/kit/components/Object3DDocumentViewport.tsx +5 -8
  49. package/src/kit/components/StageHost.css +40 -0
  50. package/src/kit/components/StageHost.tsx +181 -404
  51. package/src/kit/components/StageOverlays.tsx +1 -2
  52. package/src/kit/components/StatsOverlay.tsx +5 -17
  53. package/src/kit/components/ViewportFurniture.tsx +27 -154
  54. package/src/kit/components/ViewportOverlay.tsx +13 -17
  55. package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
  56. package/src/kit/components/ViewportShadingMenu.tsx +2 -42
  57. package/src/kit/components/ViewportViewMenu.tsx +7 -18
  58. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +4 -13
  59. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +5 -5
  60. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +24 -102
  61. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +6 -6
  62. package/src/kit/components/asset-viewers/Object3DPreview.tsx +10 -45
  63. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +2 -2
  64. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +11 -10
  65. package/src/kit/components/asset-viewers/shader-source.ts +144 -0
  66. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +2 -14
  67. package/src/kit/components/object3d-contribution-surfaces.tsx +1 -1
  68. package/src/kit/components/stage-keyboard.tsx +6 -12
  69. package/src/kit/components/stage-overlay-set.tsx +11 -20
  70. package/src/kit/components/stage-presence-markers.ts +9 -32
  71. package/src/kit/components/stage-transform-chrome.ts +4 -4
  72. package/src/kit/components/stage-transform-tools.tsx +7 -14
  73. package/src/kit/components/standard-viewport-dressing.ts +47 -167
  74. package/src/kit/components/world-root-binding.ts +2 -7
  75. package/src/kit/constraint-helper.ts +1 -1
  76. package/src/kit/document-renderer-session.ts +114 -0
  77. package/src/kit/editor-presentation-activity.ts +58 -0
  78. package/src/kit/editor-shell-store.ts +31 -403
  79. package/src/kit/editor-viewport.ts +476 -1762
  80. package/src/kit/entity-lod.ts +3 -16
  81. package/src/kit/entity-object.ts +1 -39
  82. package/src/kit/hierarchy-mark-reader.ts +13 -30
  83. package/src/kit/instanced-presentation.ts +1 -47
  84. package/src/kit/live-gesture-lock.ts +35 -0
  85. package/src/kit/live-module-source.ts +12 -52
  86. package/src/kit/model-thumbnail.ts +7 -35
  87. package/src/kit/play-camera-flight.ts +7 -16
  88. package/src/kit/projection/three.ts +13 -366
  89. package/src/kit/retained-document-states.ts +91 -0
  90. package/src/kit/scene-document-viewport.ts +3 -3
  91. package/src/kit/scene-framing.ts +11 -152
  92. package/src/kit/scene-view-fog.ts +2 -47
  93. package/src/kit/spatial-handle-visuals.ts +19 -28
  94. package/src/kit/stories/three-story-model.ts +5 -33
  95. package/src/kit/three-canvas-render.ts +3 -4
  96. package/src/kit/three-hierarchy-row-media.ts +3 -11
  97. package/src/kit/three-inspection-media.ts +6 -6
  98. package/src/kit/three-integration.ts +18 -18
  99. package/src/kit/three-state.ts +2 -19
  100. package/src/kit/three-viewport/camera-fit.ts +2 -6
  101. package/src/kit/three-viewport/selection-brackets.ts +5 -20
  102. package/src/kit/three-viewport/selection-outline.ts +4 -56
  103. package/src/kit/three-viewport/skeleton-helper.ts +1 -32
  104. package/src/kit/three-viewport/source-color.ts +1 -33
  105. package/src/kit/three-viewport/studio-environment.ts +4 -55
  106. package/src/kit/three-viewport-presentation.ts +23 -0
  107. package/src/kit/transport-strip.tsx +147 -0
  108. package/src/kit/viewport-actions.ts +8 -50
  109. package/src/kit/viewport-authoring-policy.ts +5 -52
  110. package/src/kit/viewport-commands.ts +28 -57
  111. package/src/kit/viewport-hotkeys.ts +29 -31
  112. package/src/kit/viewport-status-facet.ts +4 -5
  113. package/src/kit/viewport-surface-status.tsx +44 -0
  114. package/src/loader.ts +8 -71
  115. package/src/object3d-contributions.ts +18 -34
  116. package/src/viewport/clip-planes.ts +4 -32
  117. package/src/viewport/content-bounds.ts +13 -142
  118. package/src/viewport/editor-layers.ts +4 -26
  119. package/src/viewport/environment.ts +1 -1
  120. package/src/viewport/preview-renderer.ts +7 -45
  121. package/src/viewport/renderer-ownership.ts +14 -62
  122. package/src/viewport-api.ts +14 -33
  123. package/src/viewport-door.ts +16 -86
  124. package/contributions/three-integration.service.ts +0 -13
  125. package/src/adapter/hierarchy-marks.ts +0 -156
  126. package/src/adapter/ingest/scene-capture.ts +0 -843
  127. package/src/adapter/ingest/structural-ids.ts +0 -139
  128. package/src/adapter/ingest/visible-capture-window.ts +0 -302
  129. package/src/adapter/renderer-config.ts +0 -115
  130. package/src/asset-parse-error.ts +0 -29
  131. package/src/host-hierarchy-objects.ts +0 -31
  132. package/src/kit/authoring/design-time-settle.ts +0 -343
  133. package/src/kit/camera-authoring.ts +0 -175
@@ -1,28 +1,16 @@
1
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.
2
+ * Registry for document stages' viewport rigs, helper displays and frame callbacks. Each mounted
3
+ * 3D stage binds under its document id and runs its frame hook from its own RAF loop.
9
4
  *
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/sdk/host`'s `viewport` member, which is this.
5
+ * `viewportRig` and `onViewportFrame` follow the primary stage: the focused document's bound
6
+ * stage, else the first bound. The camera-flight seam reads that rig and uses its frame clock.
7
+ * `viewportStages()` exposes every bound stage so helpers can be attached to each separately.
8
+ * Stage subscriptions and helpers survive canvas remounts under the same document id.
20
9
  */
21
10
 
22
- import type { ViewportDoor, ViewportPresentation, ViewportRig, ViewportRoot, ViewportStage } from './viewport-api';
23
- import { activeWorkspaceDocumentId } from '@volter/sdk/kit/workspace-document-registry';
11
+ import type { ViewportDoor, ViewportRig, ViewportStage } from './viewport-api';
12
+ import { activeWorkspaceDocumentId } from '@volter/sdk/workspace-document-registry';
24
13
 
25
- type Presenter = (roots: readonly ViewportRoot[]) => ViewportPresentation | null;
26
14
  /** An editor-only helper object, typed by the host door it is shown through. */
27
15
  type HelperObject = Parameters<ViewportDoor['setHelper']>[1];
28
16
  type HelperSink = (kind: string, object: HelperObject) => void;
@@ -30,25 +18,16 @@ type HelperSink = (kind: string, object: HelperObject) => void;
30
18
  interface Binding {
31
19
  readonly documentId: string;
32
20
  readonly rig: ViewportRig;
33
- readonly present: Presenter;
34
21
  readonly setHelper: HelperSink;
35
22
  readonly frameListeners: Set<(dtSeconds: number) => void>;
36
23
  /** Helpers set on THIS stage by kind, so a re-bind restores them. */
37
24
  readonly helpers: Map<string, HelperObject>;
38
- /** A presentation of live roots is alive on this stage right now. */
39
- presenting: boolean;
40
25
  }
41
26
 
42
27
  /** Every bound stage, keyed by the document it draws; insertion-ordered, so
43
28
  * "the first bound" is a real answer. */
44
29
  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
- */
30
+ /** Retain frame subscribers and helper assignments by document across viewport remounts and before binding. */
52
31
  const stageStateByDocument = new Map<
53
32
  string,
54
33
  {
@@ -75,9 +54,8 @@ function stageStateFor(documentId: string): {
75
54
  const primaryFrameListeners = new Set<(dtSeconds: number) => void>();
76
55
  const stageListeners = new Set<(stages: readonly ViewportStage[]) => void>();
77
56
 
78
- /** THE PRIMARY STAGE: presenting live roots > focused > first bound. */
57
+ /** THE PRIMARY STAGE: focused > first bound. */
79
58
  function primary(): Binding | null {
80
- for (const binding of bound.values()) if (binding.presenting) return binding;
81
59
  const focused = activeWorkspaceDocumentId();
82
60
  if (focused !== null) {
83
61
  const match = bound.get(focused);
@@ -115,17 +93,13 @@ function stageDoor(documentId: string, rig: ViewportRig): ViewportStage {
115
93
  };
116
94
  }
117
95
 
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. */
96
+ /**
97
+ * Bind a document stage’s rig and helper display under its id. Rebinding replaces the prior
98
+ * binding; a stale unbind cannot remove its successor. Restore helpers set for this stage before it
99
+ * bound. Per-document helper assignments remain queued across bindings.
100
+ */
126
101
  export function bindViewportRig(
127
102
  rig: ViewportRig,
128
- present: Presenter,
129
103
  setHelper: HelperSink,
130
104
  options: { readonly documentId: string },
131
105
  ): () => void {
@@ -133,11 +107,9 @@ export function bindViewportRig(
133
107
  const next: Binding = {
134
108
  documentId: options.documentId,
135
109
  rig,
136
- present,
137
110
  setHelper,
138
111
  frameListeners: state.frameListeners,
139
112
  helpers: state.helpers,
140
- presenting: false,
141
113
  };
142
114
  bound.set(options.documentId, next);
143
115
  for (const [kind, object] of next.helpers) setHelper(kind, object);
@@ -149,42 +121,10 @@ export function bindViewportRig(
149
121
  };
150
122
  }
151
123
 
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
124
  export function viewportRig(): ViewportRig | null {
163
125
  return primary()?.rig ?? null;
164
126
  }
165
127
 
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
128
  /** Runs after the PRIMARY stage's own per-frame update, and RE-TARGETS: the
189
129
  * listener follows whichever stage is primary, not the one that was primary
190
130
  * when it subscribed. */
@@ -202,17 +142,7 @@ export function viewportStages(): readonly ViewportStage[] {
202
142
  return stagesCache;
203
143
  }
204
144
 
205
- /**
206
- * WHICH HELPER KINDS A STAGE IS HOLDING — the kinds something has actually
207
- * shown on it through `setHelper` (`@volter/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
- */
145
+ /** Helper kinds assigned through this package's ViewportStage.setHelper, with cleared entries excluded. */
216
146
  export function viewportStageHelperKinds(documentId: string): readonly string[] {
217
147
  const state = stageStateByDocument.get(documentId);
218
148
  if (!state) return [];
@@ -1,13 +0,0 @@
1
- /**
2
- * The Three integration, installed at boot (`workspace.service`): the Object3D surfaces
3
- * contributions mount, the viewport's relay verbs, Asset Lab viewers, inspection and hierarchy
4
- * media, captures, the authoring policy and the session's view-state persistence
5
- * (`src/kit/three-integration.ts`). Every viewport also ensures it before it mounts.
6
- */
7
- import { ensureThreeIntegration } from '../src/kit/three-integration';
8
-
9
- export const point = 'workspace.service';
10
-
11
- export function start(): () => void {
12
- return ensureThreeIntegration();
13
- }
@@ -1,156 +0,0 @@
1
- /**
2
- * The hierarchy-presentation convention: how a game tells the editor which
3
- * parts of its LIVE scene graph are one component instance, and which parts are
4
- * implementation rather than content.
5
- *
6
- * ## Why marks and not a second tree
7
- *
8
- * The live tree IS the display surface. A hierarchy derived from source would
9
- * be a mirror layer — a second model of the world that drifts from the one on
10
- * screen — so the boundaries a reader needs have to live ON the nodes. That is
11
- * what these marks are, and it is the whole mechanism: a component WRAPS its
12
- * functionality and MARKS its root; the editor supports the convention
13
- * generically and never learns which library, importer or lane produced the
14
- * node.
15
- *
16
- * ## STRUCTURE IS THE TREE'S JOB. MARKS ONLY CLASSIFY.
17
- *
18
- * There is deliberately no mark that says "show this row somewhere else". A
19
- * group owns its children by CONTAINING them; a prefab owns its parts by
20
- * CONTAINING them. If an object reads as belonging under something it is not
21
- * parented to, the tree is wrong and the tree is what to fix — because the
22
- * panel is not the only reader. `getWorldPosition`, raycasting, disposal,
23
- * `traverse`, every physics or gameplay walk and every person reading the
24
- * source all follow real parenthood, and a presentation-only "logical parent"
25
- * would be true for exactly one of them.
26
- *
27
- * The usual reason a world gets this wrong is that three's parent chain is a
28
- * TRANSFORM chain: `matrixWorld` is unconditionally `parent × local`, so an
29
- * object that must not inherit its owner's pose — a physics-driven body writing
30
- * a world transform every step, a world-space trail or decal, a follow camera
31
- * that must escape its target's rotation — gets mounted at the scene root, and
32
- * ownership is lost as a side effect. **That trade is not necessary.** Set
33
- * `object.matrixWorldAutoUpdate = false` (three r144+) and the parent's
34
- * propagation skips it: the object stays parented where it belongs and its
35
- * `matrixWorld` becomes yours to write. Keep it parented, own the matrix, and
36
- * the hierarchy needs no help.
37
- *
38
- * These two marks then have exactly one job left, which is CLASSIFICATION:
39
- * which of the objects that really are here is the game, and which is the
40
- * machinery underneath it.
41
- *
42
- * ## What the hierarchy is FOR
43
- *
44
- * It shows YOUR GAME: the structure you authored, plus the objects your game
45
- * makes while it runs. Nothing else. Three consequences, and every rule below
46
- * follows from them:
47
- *
48
- * - **A spawned object is content.** A bullet you fired, a wave of enemies, a
49
- * prop pulled out of a pool and parented into the level — these are rows.
50
- * They exist, you can select them, and they are usually the exact things you
51
- * opened the panel to look at. An object your game frees stops being a row,
52
- * because it stopped existing.
53
- * - **An implementation artifact is not.** The bones behind a rig, the batched
54
- * renderer behind a particle system, the instanced-mesh pools behind a tile
55
- * field, the `target` object three needs in order to aim a directional
56
- * light: machinery a library or a helper builds so one thing you DID author
57
- * can work. It belongs UNDER that thing, folded, not beside it.
58
- * - **"Not in the source file" is the wrong test for either.** Half of a
59
- * running world is not in the source file — that is what running means.
60
- * Folding spawned gameplay because a document does not name it is the same
61
- * defect as listing bones, in the more damaging direction: the rows it loses
62
- * are the ones you were looking for.
63
- *
64
- * ## The two marks
65
- *
66
- * - {@link markComponentRoot} — "this subtree is one instance of `<name>`."
67
- * The editor renders it as ONE row, closed, expandable on demand. Put it on
68
- * the EARLIEST point the node exists: the constructor that makes it, or the
69
- * ref callback that receives it. A component whose root is a plain `<mesh>`
70
- * marks that mesh.
71
- * - {@link markBuiltInternal} — "this is IMPLEMENTATION, not content." It is
72
- * **subtree-scoped**: marking the root of a built subtree marks everything
73
- * under it, so a 60-bone rig costs one call and folding is transitive. The
74
- * editor hides these rows and offers them behind the same "Reveal Internals"
75
- * action a source-backed instance's internals already use — revealed rows
76
- * are read-only, because there is no authored source for a rename or a
77
- * reparent to write to.
78
- *
79
- * An instance therefore reads as the parent of everything meaningful beneath
80
- * it: content nested inside an implementation node is listed under the instance
81
- * rather than lost with it (the editor's `hierarchy-component-marks.ts` calls
82
- * that promotion), so marking a rig does not hide the mesh skinned to it.
83
- *
84
- * ## Mark it where the subtree is ATTACHED, by whoever attaches it
85
- *
86
- * A mark belongs at the attach seam: the code that puts an implementation
87
- * subtree into the world marks it in the same breath, because that code is the
88
- * only thing that knows the subtree is implementation. Not a later sweep of the
89
- * tree — "everything present at time T that the source did not name" is the
90
- * wrong predicate twice over. It arrives too early for anything attached
91
- * asynchronously (a model clone landing after its bytes resolve, a node gated
92
- * on an audio decode) and too late in principle for anything attached
93
- * afterwards (every spawn) — and were it re-run to catch those, it would
94
- * classify each spawned object as implementation, which is the second bullet
95
- * above inverted. Attach-seam marking has neither failure: it fires once, at
96
- * the moment the answer is known, and an unmarked node is content by default.
97
- *
98
- * The subtree scope is what keeps this cheap — one call per attached root, not
99
- * one per node, so a rig or a pool costs one line at the one place that builds
100
- * it.
101
- *
102
- * ## What is NOT built-internal
103
- *
104
- * A node your game means as content — even one a class rather than JSX
105
- * constructs — is content. `volterBuiltInternal` is not "the editor cannot write
106
- * this"; that question is `TransformProvider.editability`'s, answered per
107
- * channel with a sentence. This mark answers "is this implementation", and
108
- * getting it wrong hides real content.
109
- *
110
- * Both keys live in the structural mark registry (`../ecs/object-marks.ts`),
111
- * which the full typed `userData` registry includes, so
112
- * their literal strings are spelled exactly once; this module is the only
113
- * reader/writer, which is why the helpers below exist at all rather than
114
- * `setUserData` calls scattered through emitters and prefabs.
115
- */
116
-
117
- import type * as THREE from 'three';
118
- import { getObjectMark, setObjectMark } from '../ecs/object-marks';
119
-
120
- /**
121
- * Declare `node` the root of one component instance, displayed under `name`.
122
- *
123
- * `name` is the COMPONENT's name (`'Coin'`, `'HeroBox'`), not the instance's —
124
- * the instance is already named by `node.name`, and the editor prints both.
125
- */
126
- export function markComponentRoot(node: THREE.Object3D, name: string): void {
127
- setObjectMark(node, 'volterComponentRoot', name);
128
- }
129
-
130
- /** The component name `node` is the root of, or `undefined` for an ordinary node. */
131
- export function componentRootName(node: THREE.Object3D | null | undefined): string | undefined {
132
- const name = getObjectMark(node, 'volterComponentRoot');
133
- return typeof name === 'string' && name ? name : undefined;
134
- }
135
-
136
- /** Whether `node` is a marked component-instance root. */
137
- export function isComponentRoot(node: THREE.Object3D | null | undefined): boolean {
138
- return componentRootName(node) !== undefined;
139
- }
140
-
141
- /**
142
- * Declare `node` (and everything under it) IMPLEMENTATION of whatever node it
143
- * sits under, rather than content of its own. Call it on the ROOT of the built
144
- * subtree only, at the seam that attaches it — see this module's header for why
145
- * the seam and not a sweep.
146
- */
147
- export function markBuiltInternal(node: THREE.Object3D): void {
148
- setObjectMark(node, 'volterBuiltInternal', true);
149
- }
150
-
151
- /** Whether `node` itself carries the implementation mark. Ancestry is the
152
- * CALLER's walk — see this module's header for why the mark is subtree-scoped
153
- * but not restamped onto descendants. */
154
- export function isBuiltInternal(node: THREE.Object3D | null | undefined): boolean {
155
- return getObjectMark(node, 'volterBuiltInternal') === true;
156
- }