@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.
Files changed (112) hide show
  1. package/NOTICE +2 -0
  2. package/contributions/animation-mixers.service.ts +20 -0
  3. package/contributions/animation-timeline.utility.tsx +44 -0
  4. package/contributions/three-integration.service.ts +13 -0
  5. package/dist-node/serving.mjs +405 -0
  6. package/package.json +113 -5
  7. package/serving/animation-live-module.ts +70 -0
  8. package/serving/animation-stamp.ts +88 -0
  9. package/serving/index.ts +14 -0
  10. package/serving/model-import-conversion.ts +344 -0
  11. package/src/adapter/ingest/scene-capture.ts +1 -23
  12. package/src/adapter/renderer-config.ts +3 -4
  13. package/src/adapter/three-contract.ts +72 -0
  14. package/src/animation/live-mixers.ts +55 -0
  15. package/src/ecs/object-marks.ts +1 -1
  16. package/src/ecs/user-data.ts +0 -16
  17. package/src/host-hierarchy-objects.ts +31 -0
  18. package/src/kit/animation/three-clips-subject.ts +190 -0
  19. package/src/kit/asset-compare.ts +294 -0
  20. package/src/kit/asset-preview-command.ts +265 -0
  21. package/src/kit/asset-preview-framing.ts +357 -0
  22. package/src/kit/asset-preview.ts +2802 -0
  23. package/src/kit/asset-workflow/model-inspection.ts +830 -0
  24. package/src/kit/authoring/component-instance-root.ts +171 -0
  25. package/src/kit/authoring/design-time-settle.ts +343 -0
  26. package/src/kit/authoring/live-object-transform.ts +62 -0
  27. package/src/kit/authoring/object3d-document-session-registry.ts +154 -0
  28. package/src/kit/authoring/object3d-document-session.ts +1965 -0
  29. package/src/kit/authoring/object3d-gesture-controller.ts +113 -0
  30. package/src/kit/authoring/quarks-particle-systems.ts +19 -0
  31. package/src/kit/authoring/shell-viewport-policy.ts +48 -0
  32. package/src/kit/authoring/source-object3d-authoring-adapter.ts +526 -0
  33. package/src/kit/authoring/three-projection-core.ts +226 -0
  34. package/src/kit/authoring/viewport-pick-context.ts +39 -0
  35. package/src/kit/authoring/viewport-raycast.ts +240 -0
  36. package/src/kit/authoring/world-hidden-viewport.ts +95 -0
  37. package/src/kit/camera-authoring.ts +175 -0
  38. package/src/kit/components/CameraInfo.tsx +56 -0
  39. package/src/kit/components/InspectorObjectPreview.tsx +57 -0
  40. package/src/kit/components/Object3DDocumentToolbar.tsx +549 -0
  41. package/src/kit/components/Object3DDocumentViewport.tsx +58 -0
  42. package/src/kit/components/StageHost.tsx +2547 -0
  43. package/src/kit/components/StageOverlays.tsx +21 -0
  44. package/src/kit/components/StatsOverlay.tsx +78 -0
  45. package/src/kit/components/ToolObject3DPreview.tsx +39 -0
  46. package/src/kit/components/ViewportFurniture.tsx +655 -0
  47. package/src/kit/components/ViewportOverlay.tsx +215 -0
  48. package/src/kit/components/ViewportShadingMenu.tsx +340 -0
  49. package/src/kit/components/ViewportViewMenu.tsx +155 -0
  50. package/src/kit/components/asset-viewers/EntityModelDocument.tsx +121 -0
  51. package/src/kit/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
  52. package/src/kit/components/asset-viewers/LiveModuleDocument.tsx +395 -0
  53. package/src/kit/components/asset-viewers/LutAssetDocument.tsx +444 -0
  54. package/src/kit/components/asset-viewers/ModelAssetDocument.tsx +105 -0
  55. package/src/kit/components/asset-viewers/Object3DPreview.tsx +356 -0
  56. package/src/kit/components/asset-viewers/QuarksAssetDocument.tsx +527 -0
  57. package/src/kit/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
  58. package/src/kit/components/asset-viewers/three-asset-viewers.tsx +132 -0
  59. package/src/kit/components/object3d-contribution-surfaces.tsx +33 -0
  60. package/src/kit/components/stage-keyboard.tsx +40 -0
  61. package/src/kit/components/stage-overlay-set.tsx +105 -0
  62. package/src/kit/components/stage-presence-markers.ts +482 -0
  63. package/src/kit/components/stage-transform-chrome.ts +30 -0
  64. package/src/kit/components/stage-transform-tools.tsx +73 -0
  65. package/src/kit/components/stage-view-name.ts +30 -0
  66. package/src/kit/components/standard-viewport-dressing.ts +1042 -0
  67. package/src/kit/components/world-root-binding.ts +64 -0
  68. package/src/kit/constraint-helper.ts +338 -0
  69. package/src/kit/editor-shell-store.ts +814 -0
  70. package/src/kit/editor-viewport.ts +6621 -0
  71. package/src/kit/entity-lod.ts +31 -0
  72. package/src/kit/entity-object.ts +92 -0
  73. package/src/kit/hierarchy-mark-reader.ts +74 -0
  74. package/src/kit/instanced-presentation.ts +164 -0
  75. package/src/kit/live-module-source.ts +230 -0
  76. package/src/kit/model-thumbnail.ts +539 -0
  77. package/src/kit/play-camera-flight.ts +300 -0
  78. package/src/kit/projection/three.ts +898 -0
  79. package/src/kit/reflection-probe-helper.ts +142 -0
  80. package/src/kit/scene-document-viewport.ts +51 -0
  81. package/src/kit/scene-framing.ts +315 -0
  82. package/src/kit/scene-view-fog.ts +89 -0
  83. package/src/kit/spatial-handle-visuals.ts +332 -0
  84. package/src/kit/stories/three-story-model.ts +66 -0
  85. package/src/kit/three-canvas-render.ts +44 -0
  86. package/src/kit/three-hierarchy-row-media.ts +26 -0
  87. package/src/kit/three-inspection-media.ts +73 -0
  88. package/src/kit/three-integration.ts +86 -0
  89. package/src/kit/three-state.ts +33 -0
  90. package/src/kit/three-viewport/bone-selection-highlight.ts +119 -0
  91. package/src/kit/three-viewport/camera-fit.ts +41 -0
  92. package/src/kit/three-viewport/interactive-renderer.ts +132 -0
  93. package/src/kit/three-viewport/selection-brackets.ts +355 -0
  94. package/src/kit/three-viewport/selection-outline.ts +333 -0
  95. package/src/kit/three-viewport/skeleton-helper.ts +61 -0
  96. package/src/kit/three-viewport/source-color.ts +197 -0
  97. package/src/kit/three-viewport/studio-environment.ts +96 -0
  98. package/src/kit/trigger-volume-helper.ts +116 -0
  99. package/src/kit/viewport-actions.ts +128 -0
  100. package/src/kit/viewport-authoring-policy.ts +154 -0
  101. package/src/kit/viewport-commands.ts +318 -0
  102. package/src/kit/viewport-hotkeys.ts +119 -0
  103. package/src/kit/viewport-shading-boundary.ts +12 -0
  104. package/src/kit/viewport-status-facet.ts +53 -0
  105. package/src/object3d-contributions.ts +494 -0
  106. package/src/render/viewport-shading.ts +6 -2
  107. package/src/viewport/content-bounds.ts +38 -4
  108. package/src/viewport/environment.ts +16 -0
  109. package/src/viewport-api.ts +92 -0
  110. package/src/viewport-door.ts +237 -0
  111. package/src/animation/animation-clock.ts +0 -479
  112. package/src/animation/runtime-inspection.ts +0 -45
@@ -0,0 +1,494 @@
1
+ /**
2
+ * THE OBJECT3D CONTRIBUTION CONTRACT — Three's own public API for project and package
3
+ * contributions that hand the editor a native three.js graph: the preview and authoring surfaces'
4
+ * props, the document state serializers receive, and the gestures a document owns. A contribution
5
+ * mounts the surfaces from its `surfaces` prop; this module adds them to
6
+ * `ToolContributionSurfaces`, so a contribution that imports these types sees them there, and
7
+ * the Three integration registers what renders them (`kit/three-integration.ts`).
8
+ */
9
+
10
+ import type { AuthoringAdapter } from '@volter/editor-project/adapter';
11
+ import type {
12
+ DocumentPersistenceBinding,
13
+ DocumentPersistenceResource,
14
+ } from '@volter/editor-sdk/kit/authoring/object3d-document-persistence';
15
+ import type { PresentationLayer } from '@volter/editor-sdk/kit/viewport-presentation';
16
+ import type { ComponentType } from 'react';
17
+ import type {
18
+ AnimationClip,
19
+ Camera,
20
+ ColorRepresentation,
21
+ Group,
22
+ Intersection,
23
+ Object3D,
24
+ Ray,
25
+ Scene,
26
+ ToneMapping,
27
+ WebGLRenderer,
28
+ } from 'three';
29
+
30
+ /**
31
+ * One disposable native Three.js build supplied to the editor's generic
32
+ * preview surface. This is a host-lifetime boundary, not a model format or
33
+ * procedural-asset base class: project code still constructs ordinary
34
+ * Object3D and AnimationClip instances directly.
35
+ */
36
+ export interface ToolObject3DPreviewSource {
37
+ readonly root: Object3D;
38
+ /**
39
+ * Optional authored roots for the document hierarchy when `root` is a
40
+ * presentation/lifetime container rather than authored content itself.
41
+ * Framing, ticking and disposal still own `root`; this list only scopes the
42
+ * shared Hierarchy. Omitted means `root`, preserving the native tree.
43
+ */
44
+ readonly hierarchyRoots?: readonly Object3D[];
45
+ readonly animations?: readonly AnimationClip[];
46
+ /**
47
+ * The source's OWN tick, and the host drives it EXACTLY ONCE per mount — for
48
+ * the bounded design-time settle that gives a physics-owned body the pose it
49
+ * actually rests in (`authoring/design-time-settle.ts`). It is never a frame
50
+ * loop: content time does not advance on a design-time surface, so a source
51
+ * that animates from here shows its settled pose and then holds it. Play is
52
+ * where a world runs.
53
+ */
54
+ update?(deltaSeconds: number): void;
55
+ /**
56
+ * EVERY CHANGE TO WHAT THE SOURCE DRAWS, announced: the host calls
57
+ * `listener` whenever the source's graph, materials, textures or poses may
58
+ * look different, and returns nothing from it. A source that offers this is
59
+ * drawn only when something changed -- this, the camera, input on the
60
+ * stage, the editor's own state, playback -- so an idle document costs no
61
+ * frames. A source without it is drawn every frame.
62
+ */
63
+ onChange?(listener: () => void): () => void;
64
+ dispose(): void;
65
+ }
66
+
67
+ /** Direct native handles for optional project-owned preview presentation. */
68
+ export interface ToolObject3DPreviewContext {
69
+ readonly scene: Scene;
70
+ readonly camera: Camera;
71
+ readonly renderer: WebGLRenderer;
72
+ /**
73
+ * A cloned Object3D graph, never the source root itself. Geometry,
74
+ * materials, and textures are shared read-only with the disposable source;
75
+ * clone a resource before applying preview-only mutations to it.
76
+ */
77
+ readonly model: Object3D;
78
+ }
79
+
80
+ export interface ToolObject3DPreviewExtension {
81
+ /** When present, replaces the viewer's default render call for this frame. */
82
+ render?(deltaSeconds: number): void;
83
+ /** Resize project-owned composers and render targets with the host viewport. */
84
+ resize?(width: number, height: number, pixelRatio: number): void;
85
+ /** Release every light, ground mesh, pass, target, and listener added here. */
86
+ dispose(): void;
87
+ }
88
+
89
+ export interface ToolObject3DPreviewProps {
90
+ /** Build a fresh source. The host disposes it after its preview snapshot. */
91
+ readonly build: () => ToolObject3DPreviewSource;
92
+ readonly displayName?: string;
93
+ readonly height?: number | string;
94
+ readonly background?: ColorRepresentation;
95
+ readonly showSkeleton?: boolean;
96
+ readonly cameraDirection?: readonly [number, number, number];
97
+ /** Host diagnostic grid. Defaults off with setupPreview, on otherwise. */
98
+ readonly showGrid?: boolean;
99
+ /** Host studio lights. Defaults off with setupPreview, on otherwise. */
100
+ readonly useDefaultLighting?: boolean;
101
+ /** Explicit renderer exposure. Custom presentation otherwise owns it. */
102
+ readonly exposure?: number;
103
+ /**
104
+ * Optional raw Three.js presentation hook for project-specific lights,
105
+ * ground, or postprocessing. Host grid/studio lights default off when this
106
+ * is present. It is preview chrome and is never exported.
107
+ */
108
+ readonly setupPreview?: (
109
+ context: ToolObject3DPreviewContext,
110
+ ) => ToolObject3DPreviewExtension | undefined;
111
+ readonly active?: boolean;
112
+ }
113
+
114
+ /**
115
+ * Per-document overrides of the editor's standard viewport dressing — explicit
116
+ * opt-outs and opt-ins, never re-implementations. Omitted means the standard
117
+ * look (`standard-viewport-dressing.ts`).
118
+ */
119
+ export interface ToolViewportDressing {
120
+ /** `false` skips the RoomEnvironment IBL — and, with it, the stage's
121
+ * environment INTENSITY: a document that opts out of image-based lighting
122
+ * is lit by its own lights alone. Default on. */
123
+ readonly environment?: boolean;
124
+ /** `false` skips the gradient backdrop. Default on (an explicit flat
125
+ * `background` color or the asset studio stage also skips it). */
126
+ readonly background?: boolean;
127
+ /** `false` skips the key light — and the editor's own design-time light
128
+ * rig with it, because both answer the same question and a document that
129
+ * says "I light myself" is answering it. Default on; a source that
130
+ * authors its own lights skips the key regardless — authored lighting
131
+ * always wins. */
132
+ readonly keyLight?: boolean;
133
+ /** `true` adds the ground grid (content standing on y=0). Default off. */
134
+ readonly grid?: boolean;
135
+ /**
136
+ * An object the STAGE parents to its own camera, so everything under it
137
+ * turns with the view instead of standing still in the world.
138
+ *
139
+ * It exists for VIEW-SPACE LIGHTING — Blender's Solid mode has no world
140
+ * light at all, only four studio lights stated in view space, which is why
141
+ * its shading reads the same however the model is orbited. A directional
142
+ * light here states its direction in the camera's own space (three's camera
143
+ * looks down −Z) and keeps its `target` under this object at the origin.
144
+ *
145
+ * The stage owns the parenting AND the teardown: a document hands the
146
+ * object over and its own `dispose` never touches the camera.
147
+ */
148
+ readonly viewLocked?: Object3D;
149
+ /**
150
+ * THE STAGE'S VIEW TRANSFORM — three's `WebGLRenderer.toneMapping`. Omitted
151
+ * means the editor's own, ACES.
152
+ *
153
+ * A document whose source system states its own transform passes that one
154
+ * instead: Blender's factory scene is AgX (`view_settings.view_transform`),
155
+ * and this product's Blender RENDER path already photographs through
156
+ * `AgXToneMapping` — so before this existed the viewport was the one
157
+ * surface in the chain running a different curve from the thing it frames.
158
+ * It is not a look preference: the curve decides how a shading range lands
159
+ * on screen, and two curves over one radiance are two different pictures.
160
+ */
161
+ readonly toneMapping?: ToneMapping;
162
+ }
163
+
164
+ /**
165
+ * A native source graph hosted by the editor's normal Three.js authoring
166
+ * viewport, hierarchy, selection, and Inspector. Unlike Object3DPreview this
167
+ * surface exposes the live graph as the active workspace document context.
168
+ */
169
+ export interface ToolObject3DAuthoringProps {
170
+ readonly documentId: string;
171
+ readonly sourcePath: string;
172
+ /**
173
+ * Produce the graph to author. Called ONCE PER ACTIVATION, not once per
174
+ * document: the host tears its scene down whenever the document goes inactive
175
+ * (an ordinary tab switch) and calls this again on the way back, disposing
176
+ * whatever the previous call returned. So a caller must pick one of two
177
+ * shapes, and there is no third:
178
+ *
179
+ * - a FACTORY — build a fresh graph every call, and let the returned
180
+ * `dispose` free it (what the model/entity asset documents do); or
181
+ * - an OWNED graph — return the same root every call with an EMPTY `dispose`,
182
+ * and free it from the caller's own lifetime instead (what the 3D
183
+ * components board and the story turntable do, because their graph is
184
+ * async to create).
185
+ *
186
+ * Returning a graph you cannot rebuild synchronously AND a `dispose` that
187
+ * really frees it is the third shape, and it renders a black panel on the
188
+ * second activation.
189
+ */
190
+ readonly build: () => ToolObject3DPreviewSource;
191
+ readonly displayName?: string;
192
+ /** Explicit flat scene background. Omit it to inherit the editor's standard
193
+ * viewport dressing (environment, gradient backdrop, key light). */
194
+ readonly background?: ColorRepresentation;
195
+ /**
196
+ * The Asset Lab's IMPORT AUDIT on this document's inspector — the Geometry
197
+ * block (triangle counts, GPU estimate, LODs, collision, invalid values)
198
+ * and the Source row. Default on: it is what an imported model's inspector
199
+ * is for. A modeling document that carries its own data panel (the mesh
200
+ * document's Data / Modifiers sections) passes `false`, the way Blender's
201
+ * Object Data tab is the mesh's own, not an importer's report.
202
+ */
203
+ readonly audit?: boolean;
204
+ readonly cameraDirection?: readonly [number, number, number];
205
+ /** Per-document overrides of the standard viewport dressing. */
206
+ readonly dressing?: ToolViewportDressing;
207
+ /** The kind of stage this view is, for its starting presentation
208
+ * (`@volter/editor-sdk/kit/viewport-presentation`): the document's own kind (`'model'`).
209
+ * Without it the kind is read off the document id's prefix. */
210
+ readonly stageKind?: string;
211
+ /** How far the OPENING view stands back from a fit of the content: `1` fills the view (the
212
+ * default). Frame (numpad .) still fits exactly. */
213
+ readonly openingFit?: number;
214
+ /**
215
+ * THE OPENING VIEW STATED OUTRIGHT, for a document whose file saved one (a .blend's 3D View,
216
+ * where Blender opens it): the pivot, the direction from the pivot to the eye, the view's up,
217
+ * the distance and the projection, in the stage's frame. In place of `cameraDirection` and
218
+ * `openingFit` when given; Frame still fits. The saved field of view is the document's
219
+ * `presentation` (`camera.fov`).
220
+ */
221
+ readonly openingView?: {
222
+ readonly target: readonly [number, number, number];
223
+ readonly direction: readonly [number, number, number];
224
+ /** The view's own up, which carries its roll. */
225
+ readonly up?: readonly [number, number, number];
226
+ readonly distance: number;
227
+ readonly projection?: 'perspective' | 'orthographic';
228
+ } | null;
229
+ /**
230
+ * THE DOCUMENT'S OWN PRESENTATION LAYER, over the stage's starting values and under a person's
231
+ * choices (`kit/viewport-presentation`): what the file itself says about how it is seen — a
232
+ * `.blend`'s saved lens as `camera.fov`. Read when the document binds.
233
+ */
234
+ readonly presentation?: PresentationLayer | null;
235
+ /**
236
+ * Optional binding from live native clips back to ordinary project source.
237
+ * Project code only serializes its own TypeScript shape; the editor owns the
238
+ * checksum-guarded write and records it in the canonical project history.
239
+ * Without this binding the animation workspace remains honestly read-only.
240
+ */
241
+ /**
242
+ * Project-owned serialization for this authored document. Each serializer
243
+ * returns the exact bytes of one ordinary project file; the host writes all
244
+ * resources as one checksum-guarded, failure-atomic history transaction.
245
+ * The project owns the format and the host never interprets its contents.
246
+ */
247
+ readonly persistence?: ToolObject3DDocumentPersistence;
248
+ /** Project-owned serialization of this document into ONE source module,
249
+ * written through the editor's source seam (see the binding's docs). */
250
+ readonly documentSource?: ToolDocumentSourceBinding;
251
+ /** Replace the default native-tree projection with a semantic adapter. The
252
+ * default adapter is provided for delegation, so a project can add terrain
253
+ * layers, bones, or mesh elements without rebuilding Object3D projection. */
254
+ readonly authoring?: ToolObject3DDocumentAuthoringFactory;
255
+ /** Project-owned direct manipulation hosted by the editor's input and
256
+ * transient-overlay lifecycle. Completed gestures commit `persistence`; an
257
+ * Escape, unmount, thrown callback, or failed write calls `cancel`. */
258
+ readonly interaction?: ToolObject3DDocumentInteraction;
259
+ /**
260
+ * The editor's orange SELECTION SILHOUETTE around whatever the hierarchy
261
+ * has selected. Default on. A document that owns a sub-object editing mode
262
+ * passes `false` while that mode is live: the silhouette is an OBJECT-level
263
+ * affordance, and Blender draws none in Edit Mode. It is live — a document
264
+ * may flip it as its own mode changes, and the outline appears or goes with
265
+ * the next frame.
266
+ */
267
+ readonly selectionOutline?: boolean;
268
+ readonly active?: boolean;
269
+ /**
270
+ * This document's own counts, drawn in the VIEWPORT OVERLAY under the view
271
+ * and subject lines — Blender's home for them (`sculpting.png`: `Vertices
272
+ * 8` / `Faces 6` below `User Perspective` / `(1) Cube | Cube`, never in the
273
+ * header band). The host owns the block's geometry and ink; the document
274
+ * owns which counts exist and what they are called, because only it knows
275
+ * what it is counting. Omit it and no block is drawn.
276
+ */
277
+ readonly statistics?: readonly ToolViewportStatistic[];
278
+ /**
279
+ * This document's own SUBJECT LINE, the overlay's second line, drawn as given in place of the
280
+ * host's `<document> | <active object>`. For a document whose source system composes that line
281
+ * itself (Blender's `(1) Collection | Cube`, `draw_selected_name`). Omit it and the host's is
282
+ * drawn.
283
+ */
284
+ readonly subject?: string;
285
+ /**
286
+ * The name of the grid's step under the subject line, for the world units per DEVICE pixel the
287
+ * view is drawn at, or null for none (Blender's `10 Centimeters`, `draw_grid_unit_name`). The
288
+ * host asks only in an axis-aligned orthographic view, where Blender draws it. Omit it and no
289
+ * line is drawn.
290
+ */
291
+ readonly gridScale?: (worldPerDevicePixel: number) => string | null;
292
+ /**
293
+ * A CAMERA VIEW: looking through one of the document's own cameras, the way Blender's
294
+ * `view3d.view_camera` (numpad 0, the navigation cluster's camera button) does. The document
295
+ * says which camera and how its view fits a region; the stage owns the toggle, the frame's
296
+ * zoom and offset, and leaving the view. Omit it and the stage has no camera view.
297
+ */
298
+ readonly cameraView?: ToolCameraViewSource;
299
+ }
300
+
301
+ /** See {@link ToolObject3DAuthoringProps.cameraView}. */
302
+ export interface ToolCameraViewSource {
303
+ /** The camera a camera view entered now would look through, or null when there is none. */
304
+ readonly camera: () => string | null;
305
+ /** The frame zoom a camera view opens at, and the range it keeps to. */
306
+ readonly zoom: { readonly opening: number; readonly min: number; readonly max: number };
307
+ /**
308
+ * The pan after the pointer moves `dx`, `dy` (fractions of the region's width and height,
309
+ * down and right positive) at zoom `zoom`, from `offset`: the source's own pan state, which a
310
+ * camera view opens at `[0, 0]` and hands back to `view` unread.
311
+ */
312
+ readonly pan: (
313
+ offset: readonly [number, number],
314
+ zoom: number,
315
+ dx: number,
316
+ dy: number,
317
+ ) => readonly [number, number];
318
+ /** Told the camera a camera view is looking through (null when none), so the document can
319
+ * stand down its own drawing of it: the view's frame is its border. */
320
+ readonly showing?: (camera: string | null) => void;
321
+ /**
322
+ * Move `camera` to a pose in the stage's frame (the eye, and its rotation looking down -Z),
323
+ * keeping its scale: what a LOCKED camera view does as it is navigated (Blender's
324
+ * `View3D.lock_camera`, `ED_view3d_camera_lock_sync`). `final` is the navigation's end, the
325
+ * one write a history records. Omit it and the view has no lock.
326
+ */
327
+ readonly setPose?: (
328
+ camera: string,
329
+ position: readonly [number, number, number],
330
+ quaternion: readonly [number, number, number, number],
331
+ final: boolean,
332
+ ) => void | Promise<void>;
333
+ /**
334
+ * `camera`'s view on a region `width` × `height` pixels, at frame zoom `zoom` and pan
335
+ * `offset`; null when that camera is gone.
336
+ */
337
+ readonly view: (
338
+ camera: string,
339
+ region: { readonly width: number; readonly height: number },
340
+ zoom: number,
341
+ offset: readonly [number, number],
342
+ ) => ToolCameraView | null;
343
+ }
344
+
345
+ /** One camera view, in the stage's frame. */
346
+ export interface ToolCameraView {
347
+ readonly name: string;
348
+ /** The eye, looking down its own -Z with +Y up. */
349
+ readonly position: readonly [number, number, number];
350
+ readonly quaternion: readonly [number, number, number, number];
351
+ readonly projection: 'perspective' | 'orthographic';
352
+ /** The window: at unit distance for a perspective view, in world units for an orthographic
353
+ * one; up positive. */
354
+ readonly window: { readonly left: number; readonly right: number; readonly top: number; readonly bottom: number };
355
+ readonly near: number;
356
+ readonly far: number;
357
+ /** The camera's frame on the region, as fractions of its width and height from the top left. */
358
+ readonly frame: { readonly left: number; readonly top: number; readonly width: number; readonly height: number };
359
+ /** Outside the frame: its colour and opacity (0 draws none). */
360
+ readonly passepartout: { readonly color: string; readonly opacity: number };
361
+ /** The frame's edge: a solid line under a dashed one, and the colour of the dashed box one
362
+ * pixel outside them while the view is locked to the camera; each a CSS colour. */
363
+ readonly border: { readonly solid: string; readonly dashed: string; readonly locked: string };
364
+ }
365
+
366
+ /** One row of the viewport overlay's statistics block: Blender's label column
367
+ * and its value column. */
368
+ export interface ToolViewportStatistic {
369
+ /** Stable row identity — the React key, never drawn. */
370
+ readonly id: string;
371
+ /** Blender's own noun where one exists (`Vertices`, `Faces`). */
372
+ readonly label: string;
373
+ readonly value: string;
374
+ }
375
+
376
+ /** The native document state handed back to project-owned serializers. */
377
+ export interface ToolObject3DDocumentState {
378
+ readonly root: Object3D;
379
+ readonly animations: readonly AnimationClip[];
380
+ }
381
+
382
+ /** One project-owned artifact participating in an Asset Lab commit. */
383
+ export type ToolObject3DDocumentResource = DocumentPersistenceResource<ToolObject3DDocumentState>;
384
+
385
+ export type ToolObject3DDocumentPersistence = DocumentPersistenceBinding<ToolObject3DDocumentState>;
386
+
387
+ export interface ToolObject3DDocumentAuthoringContext extends ToolObject3DDocumentState {
388
+ readonly documentId: string;
389
+ readonly sourcePath: string;
390
+ readonly scene: Scene;
391
+ readonly defaultAdapter: AuthoringAdapter;
392
+ /**
393
+ * Serialize this project-owned document through its declared persistence
394
+ * resources and record the change in canonical history. Rejects when the
395
+ * document is read-only or the atomic write fails.
396
+ */
397
+ readonly commit: (label?: string) => Promise<boolean>;
398
+ }
399
+
400
+ export interface ToolObject3DDocumentAuthoring {
401
+ readonly adapter: AuthoringAdapter;
402
+ dispose?(): void;
403
+ }
404
+
405
+ export type ToolObject3DDocumentAuthoringFactory = (
406
+ context: ToolObject3DDocumentAuthoringContext,
407
+ ) => ToolObject3DDocumentAuthoring;
408
+
409
+ /** Adapter-native hit data for a project-owned Asset Lab gesture. */
410
+ export interface ToolObject3DPointerEvent {
411
+ readonly pointerId: number;
412
+ readonly button: number;
413
+ readonly buttons: number;
414
+ readonly clientX: number;
415
+ readonly clientY: number;
416
+ readonly altKey: boolean;
417
+ readonly ctrlKey: boolean;
418
+ readonly metaKey: boolean;
419
+ readonly shiftKey: boolean;
420
+ readonly ray: Ray;
421
+ readonly hits: readonly Intersection<Object3D>[];
422
+ }
423
+
424
+ /** One gesture owns exact rollback to its pre-begin document state. */
425
+ export interface ToolObject3DGesture {
426
+ readonly label?: string;
427
+ update(event: ToolObject3DPointerEvent): void;
428
+ /** Finalize the project-owned native state before the host serializes it. */
429
+ commit(event: ToolObject3DPointerEvent): void | Promise<void>;
430
+ /** Restore exact pre-begin native state. Must be safe after partial commit. */
431
+ cancel(): void | Promise<void>;
432
+ }
433
+
434
+ export interface ToolObject3DInteractionContext {
435
+ readonly root: Object3D;
436
+ readonly scene: Scene;
437
+ readonly renderer: WebGLRenderer;
438
+ /** Host-owned group removed on document teardown. Project code owns GPU
439
+ * resources it adds and releases them from the extension's `dispose`. */
440
+ readonly overlay: Group;
441
+ /** Live camera getter; projection can change while the document is open. */
442
+ readonly camera: () => Camera;
443
+ /** False means gestures will not begin because no persistence binding exists. */
444
+ readonly writable: boolean;
445
+ }
446
+
447
+ export interface ToolObject3DInteractionExtension {
448
+ /** Return null to yield this pointer to the ordinary viewport controls. */
449
+ begin(event: ToolObject3DPointerEvent): ToolObject3DGesture | null;
450
+ /**
451
+ * Optional pre-sample phase. Restore any transforms changed by the previous
452
+ * frame's refinement here; the host advances content time after this call
453
+ * and invokes {@link update} with the resulting native pose.
454
+ */
455
+ prepareFrame?(deltaSeconds: number): void;
456
+ /**
457
+ * Optional per-frame refinement after the host has advanced content time.
458
+ * Use this for project-owned constraints and overlay presentation.
459
+ */
460
+ update?(deltaSeconds: number): void;
461
+ dispose(): void;
462
+ }
463
+
464
+ export interface ToolObject3DDocumentInteraction {
465
+ setup(context: ToolObject3DInteractionContext): ToolObject3DInteractionExtension;
466
+ }
467
+
468
+ /**
469
+ * Project-owned serialization of a WHOLE Asset Lab document into one ordinary
470
+ * source file, for a
471
+ * document whose truth is a TypeScript module (a modeling session's mesh
472
+ * module). The editor owns the checksum-guarded whole-file write through the
473
+ * source seam and records it in canonical history; executable source never
474
+ * travels through {@link ToolObject3DAuthoringProps.persistence}'s resource
475
+ * route, which the server refuses by rule. A document declares either this or
476
+ * `persistence`, never both. MUST be referentially stable across renders.
477
+ */
478
+ export interface ToolDocumentSourceBinding {
479
+ /** Project-relative source file below src/. */
480
+ readonly path: string;
481
+ /** Undo/redo label shown by the editor history. */
482
+ readonly label?: string;
483
+ /** The exact bytes of the module for this document state — or `null` for
484
+ * "nothing changed, write nothing", so a gesture that ended without an
485
+ * edit leaves the file byte-for-byte alone. */
486
+ readonly serialize: (document: ToolObject3DDocumentState) => string | null;
487
+ }
488
+
489
+ declare module '@volter/editor-sdk/contributions' {
490
+ interface ToolContributionSurfaces {
491
+ readonly Object3DPreview: ComponentType<ToolObject3DPreviewProps>;
492
+ readonly Object3DAuthoring: ComponentType<ToolObject3DAuthoringProps>;
493
+ }
494
+ }
@@ -4,6 +4,10 @@ import { createNeutralMatcapTexture } from './matcap-texture';
4
4
  /** Temporary developer-facing shading modes. These never become scene data. */
5
5
  export type ViewportShadingMode =
6
6
  | 'solid'
7
+ /** Authored materials, as `solid`; a stage lights it by a preview (Blender's Material Preview). */
8
+ | 'preview'
9
+ /** Authored materials, as `solid`; a stage lights it by the scene (Blender's Rendered). */
10
+ | 'rendered'
7
11
  | 'clay'
8
12
  | 'unlit'
9
13
  | 'wireframe'
@@ -65,7 +69,7 @@ export class ViewportShadingRenderer {
65
69
  draw: () => void,
66
70
  include: (mesh: THREE.Mesh) => boolean = () => true,
67
71
  ): void {
68
- if (mode === 'solid') {
72
+ if (mode === 'solid' || mode === 'preview' || mode === 'rendered') {
69
73
  draw();
70
74
  return;
71
75
  }
@@ -107,7 +111,7 @@ export class ViewportShadingRenderer {
107
111
 
108
112
  private _materialFor(
109
113
  material: THREE.Material,
110
- mode: Exclude<ViewportShadingMode, 'solid'>,
114
+ mode: Exclude<ViewportShadingMode, 'solid' | 'preview' | 'rendered'>,
111
115
  ): THREE.Material {
112
116
  if (mode === 'normals') return this._normals;
113
117
  if (mode === 'overdraw') return this._overdraw;
@@ -71,6 +71,7 @@ export function traverseContent(
71
71
  /** Scratch for one node's transformed geometry box — this module is synchronous
72
72
  * and single-threaded, so one instance serves every call. */
73
73
  const nodeBox = new THREE.Box3();
74
+ const frameMatrix = new THREE.Matrix4();
74
75
 
75
76
  /**
76
77
  * Is this box a measurement at all?
@@ -139,10 +140,15 @@ function expandByInstances(
139
140
  target: THREE.Box3,
140
141
  node: THREE.InstancedMesh,
141
142
  geometryBox: THREE.Box3,
143
+ frame: THREE.Matrix4 | null,
142
144
  ): void {
143
145
  for (let index = 0; index < node.count; index++) {
144
146
  node.getMatrixAt(index, instanceMatrix);
145
- instanceBox.copy(geometryBox).applyMatrix4(instanceMatrix).applyMatrix4(node.matrixWorld);
147
+ // ONE transform into the target space: a box carried through two is re-boxed twice, and a
148
+ // rotated object's box grows at each step.
149
+ instanceMatrix.premultiply(node.matrixWorld);
150
+ if (frame) instanceMatrix.premultiply(frame);
151
+ instanceBox.copy(geometryBox).applyMatrix4(instanceMatrix);
146
152
  if (!isFiniteBox(instanceBox)) continue;
147
153
  target.union(instanceBox);
148
154
  }
@@ -162,16 +168,26 @@ function expandByInstances(
162
168
  * `InstancedMesh` is the one node three's own rule gets wrong for a LIVE world;
163
169
  * see {@link expandByInstances}.
164
170
  */
165
- function expandByNodeGeometry(target: THREE.Box3, node: THREE.Object3D): void {
171
+ function expandByNodeGeometry(
172
+ target: THREE.Box3,
173
+ node: THREE.Object3D,
174
+ frame: THREE.Matrix4 | null = null,
175
+ ): void {
166
176
  const geometry = (node as THREE.Mesh).geometry as THREE.BufferGeometry | undefined;
167
177
  if (!geometry) return;
168
178
  const local = localRenderableBox(node, geometry);
169
179
  if (!local) return;
170
180
  if ((node as THREE.InstancedMesh).isInstancedMesh) {
171
- expandByInstances(target, node as THREE.InstancedMesh, local);
181
+ expandByInstances(target, node as THREE.InstancedMesh, local, frame);
172
182
  return;
173
183
  }
174
- nodeBox.copy(local).applyMatrix4(node.matrixWorld);
184
+ if (frame) {
185
+ // One transform, for the same reason as the instances above.
186
+ frameMatrix.multiplyMatrices(frame, node.matrixWorld);
187
+ nodeBox.copy(local).applyMatrix4(frameMatrix);
188
+ } else {
189
+ nodeBox.copy(local).applyMatrix4(node.matrixWorld);
190
+ }
175
191
  if (!isFiniteBox(nodeBox)) return;
176
192
  target.union(nodeBox);
177
193
  }
@@ -329,6 +345,24 @@ export function contentWorldBounds(
329
345
  return expandBoxByContent(target, object);
330
346
  }
331
347
 
348
+ /**
349
+ * `object`'s content box in a FRAME — `worldToFrame` carries world space into it (the
350
+ * inverse of the object's own `matrixWorld` gives its own axes, the box a rotated object's
351
+ * selection turns with in Godot). Same walk as {@link contentWorldBounds}.
352
+ */
353
+ export function contentBoundsInFrame(
354
+ object: THREE.Object3D,
355
+ worldToFrame: THREE.Matrix4,
356
+ target: THREE.Box3 = new THREE.Box3(),
357
+ ): THREE.Box3 {
358
+ target.makeEmpty();
359
+ traverseContent(object, (node) => {
360
+ node.updateWorldMatrix(false, false);
361
+ expandByNodeGeometry(target, node, worldToFrame);
362
+ });
363
+ return target;
364
+ }
365
+
332
366
  /**
333
367
  * The Bounds diagnostic's twelve-edge box, drawn on {@link contentWorldBounds}
334
368
  * rather than `BoxHelper`'s own `setFromObject`.
@@ -1,5 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
  import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
3
+ import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
4
+ import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
3
5
 
4
6
  /** A baked IBL environment and the handle that frees its render target. */
5
7
  export interface StandardEnvironment {
@@ -24,3 +26,17 @@ export function createStandardEnvironment(renderer: THREE.WebGLRenderer): Standa
24
26
  pmrem.dispose();
25
27
  }
26
28
  }
29
+
30
+ /**
31
+ * An environment image (`@volter/editor-sdk/kit/environment-images`) as an equirectangular
32
+ * texture in scene-referred light. Read as half float: a sun past white stays bright, and half
33
+ * float filters linearly where full float cannot (phones).
34
+ */
35
+ export async function loadEnvironmentImage(url: string, format: 'exr' | 'hdr'): Promise<THREE.DataTexture> {
36
+ const loader = format === 'exr' ? new EXRLoader() : new HDRLoader();
37
+ loader.setDataType(THREE.HalfFloatType);
38
+ const texture = await loader.loadAsync(url);
39
+ texture.mapping = THREE.EquirectangularReflectionMapping;
40
+ texture.colorSpace = THREE.LinearSRGBColorSpace;
41
+ return texture;
42
+ }