@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.
- package/contributions/animation-timeline.utility.tsx +44 -0
- package/contributions/three-integration.service.ts +13 -0
- package/package.json +96 -6
- package/src/adapter/renderer-config.ts +3 -4
- package/src/adapter/three-contract.ts +72 -0
- package/src/ecs/object-marks.ts +1 -1
- package/src/ecs/user-data.ts +0 -9
- 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-api.ts +92 -0
- package/src/viewport-door.ts +237 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { ToolObject3DPreviewContext, ToolObject3DPreviewExtension } from '../../../object3d-contributions';
|
|
2
|
+
import { themeVars } from '@volter/editor-sdk/widgets';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
6
|
+
import {
|
|
7
|
+
createOwnedObjectPreviewSnapshot,
|
|
8
|
+
createPerspectiveAssetPreviewCamera,
|
|
9
|
+
disposeProjectAssetModel,
|
|
10
|
+
loadProjectAssetModel,
|
|
11
|
+
measureAssetPreview,
|
|
12
|
+
type NativeObjectPreviewSource,
|
|
13
|
+
type OwnedObjectPreviewSnapshot,
|
|
14
|
+
} from '../../asset-preview';
|
|
15
|
+
import { registerPreviewResource } from '@volter/editor-sdk/kit/asset-workflow/preview-resource-lifetime';
|
|
16
|
+
import { registerDesignTimeSurface } from '@volter/editor-sdk/kit/coverage/design-time-surfaces';
|
|
17
|
+
import {
|
|
18
|
+
isEditorPresentationActive,
|
|
19
|
+
subscribeEditorPresentationActivity,
|
|
20
|
+
} from '@volter/editor-sdk/kit/editor-presentation-activity';
|
|
21
|
+
import {
|
|
22
|
+
createModelPreviewSource,
|
|
23
|
+
loadModelThumbnailObject,
|
|
24
|
+
modelThumbnailFormat,
|
|
25
|
+
} from '../../model-thumbnail';
|
|
26
|
+
|
|
27
|
+
export interface Object3DPreviewProps {
|
|
28
|
+
readonly assetPath?: string | undefined;
|
|
29
|
+
readonly materialPath?: string | undefined;
|
|
30
|
+
readonly displayName?: string | undefined;
|
|
31
|
+
readonly active?: boolean | undefined;
|
|
32
|
+
readonly sourceFactory?: (() => NativeObjectPreviewSource) | undefined;
|
|
33
|
+
readonly showSkeleton?: boolean | undefined;
|
|
34
|
+
readonly background?: THREE.ColorRepresentation | undefined;
|
|
35
|
+
readonly cameraDirection?: readonly [number, number, number] | undefined;
|
|
36
|
+
readonly showGrid?: boolean | undefined;
|
|
37
|
+
readonly useDefaultLighting?: boolean | undefined;
|
|
38
|
+
readonly exposure?: number | undefined;
|
|
39
|
+
readonly setupPreview?:
|
|
40
|
+
| ((context: ToolObject3DPreviewContext) => ToolObject3DPreviewExtension | undefined)
|
|
41
|
+
| undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pose the model at its first clip's frame ZERO and HOLD it there. A preview
|
|
46
|
+
* card is a design-time surface: presentation (orbit damping, a project
|
|
47
|
+
* `setupPreview` extension's own chrome) keeps running, content time does not
|
|
48
|
+
* (`coverage/design-time-surfaces.ts`). There is no option to play it — the one
|
|
49
|
+
* place a clip runs on a design-time surface is the animation workspace's
|
|
50
|
+
* transport, on the document surface, under a human's hand.
|
|
51
|
+
*/
|
|
52
|
+
function poseAtFirstFrame(loaded: THREE.Object3D): THREE.AnimationMixer | null {
|
|
53
|
+
const clip = loaded.animations[0];
|
|
54
|
+
if (!clip) return null;
|
|
55
|
+
const mixer = new THREE.AnimationMixer(loaded);
|
|
56
|
+
const action = mixer.clipAction(clip).play();
|
|
57
|
+
action.paused = true;
|
|
58
|
+
mixer.update(0);
|
|
59
|
+
return mixer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Preview cards are anonymous (several can show the same asset at once), so the
|
|
63
|
+
* Edit-static row needs a per-MOUNT identity to name one by. */
|
|
64
|
+
let previewCardSequence = 0;
|
|
65
|
+
|
|
66
|
+
/** Compact, non-authoring Object3D preview for cards, catalogs, and thumbnails. */
|
|
67
|
+
export function Object3DPreview({
|
|
68
|
+
assetPath,
|
|
69
|
+
materialPath,
|
|
70
|
+
displayName = 'Object3D preview',
|
|
71
|
+
active = true,
|
|
72
|
+
sourceFactory,
|
|
73
|
+
showSkeleton = false,
|
|
74
|
+
background,
|
|
75
|
+
cameraDirection,
|
|
76
|
+
showGrid = true,
|
|
77
|
+
useDefaultLighting = true,
|
|
78
|
+
exposure = 1,
|
|
79
|
+
setupPreview,
|
|
80
|
+
}: Object3DPreviewProps) {
|
|
81
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
82
|
+
const [error, setError] = useState<string | null>(null);
|
|
83
|
+
const [loading, setLoading] = useState(true);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!active) return;
|
|
87
|
+
const container = containerRef.current;
|
|
88
|
+
if (!container) return;
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
// This card holds its content time at zero for its whole life — nothing
|
|
91
|
+
// below advances a mixer or a source tick — and says so out loud, so
|
|
92
|
+
// Edit-static speaks for preview cards too instead of skipping them
|
|
93
|
+
// (`coverage/design-time-surfaces.ts`).
|
|
94
|
+
const unregisterDesignTimeSurface = registerDesignTimeSurface({
|
|
95
|
+
id: `preview-card:${++previewCardSequence}`,
|
|
96
|
+
label: `${displayName} (preview card)`,
|
|
97
|
+
contentClock: () => 0,
|
|
98
|
+
});
|
|
99
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
100
|
+
const releaseContext = registerPreviewResource('webglContext');
|
|
101
|
+
const releaseAnimationFrame = registerPreviewResource('animationFrame');
|
|
102
|
+
const releaseResizeObserver = registerPreviewResource('resizeObserver');
|
|
103
|
+
const scene = new THREE.Scene();
|
|
104
|
+
const camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
|
|
105
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
106
|
+
const clock = new THREE.Clock();
|
|
107
|
+
let mixer: THREE.AnimationMixer | null = null;
|
|
108
|
+
let ownedModel: THREE.Object3D | null = null;
|
|
109
|
+
let ownedFactory: OwnedObjectPreviewSnapshot | null = null;
|
|
110
|
+
let extension: ToolObject3DPreviewExtension | undefined;
|
|
111
|
+
let sparkRenderer: SparkRenderer | null = null;
|
|
112
|
+
let framing: ReturnType<typeof measureAssetPreview> | null = null;
|
|
113
|
+
const previewHelpers: THREE.Object3D[] = [];
|
|
114
|
+
let frame = 0;
|
|
115
|
+
let disposed = false;
|
|
116
|
+
|
|
117
|
+
setError(null);
|
|
118
|
+
setLoading(true);
|
|
119
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
120
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
121
|
+
renderer.toneMappingExposure = exposure;
|
|
122
|
+
renderer.shadowMap.enabled = true;
|
|
123
|
+
renderer.setClearColor(background ?? 0x20242a, 1);
|
|
124
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
125
|
+
// The backing buffer scales with DPR, but layout must remain exactly the
|
|
126
|
+
// size of the preview surface. Without an explicit CSS footprint a DPR 2
|
|
127
|
+
// canvas lays out at twice the intended dimensions and overflow clips it
|
|
128
|
+
// to the top-left quadrant.
|
|
129
|
+
renderer.domElement.style.width = '100%';
|
|
130
|
+
renderer.domElement.style.height = '100%';
|
|
131
|
+
renderer.domElement.style.display = 'block';
|
|
132
|
+
container.appendChild(renderer.domElement);
|
|
133
|
+
controls.enableDamping = true;
|
|
134
|
+
controls.dampingFactor = 0.1;
|
|
135
|
+
|
|
136
|
+
if (useDefaultLighting) {
|
|
137
|
+
scene.add(new THREE.HemisphereLight(0xffffff, 0x354052, 1.8));
|
|
138
|
+
const key = new THREE.DirectionalLight(0xffffff, 2.2);
|
|
139
|
+
key.position.set(3, 5, 4);
|
|
140
|
+
scene.add(key);
|
|
141
|
+
}
|
|
142
|
+
if (showGrid) {
|
|
143
|
+
const grid = new THREE.GridHelper(10, 20, 0x51606f, 0x343b44);
|
|
144
|
+
scene.add(grid);
|
|
145
|
+
previewHelpers.push(grid);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fitCamera = (width: number, height: number) => {
|
|
149
|
+
if (!framing) return;
|
|
150
|
+
const requestedDirection = cameraDirection
|
|
151
|
+
? new THREE.Vector3(...cameraDirection)
|
|
152
|
+
: undefined;
|
|
153
|
+
const fitted = createPerspectiveAssetPreviewCamera(
|
|
154
|
+
framing,
|
|
155
|
+
Math.max(width, 1) / Math.max(height, 1),
|
|
156
|
+
requestedDirection,
|
|
157
|
+
);
|
|
158
|
+
camera.copy(fitted, false);
|
|
159
|
+
controls.target.copy(framing.center);
|
|
160
|
+
controls.update();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const install = async (loaded: THREE.Object3D) => {
|
|
164
|
+
if (disposed) return;
|
|
165
|
+
scene.add(loaded);
|
|
166
|
+
let containsSplat = false;
|
|
167
|
+
loaded.traverse((object) => {
|
|
168
|
+
if (hasUserData(object, 'gaussianSplat')) containsSplat = true;
|
|
169
|
+
});
|
|
170
|
+
if (containsSplat) {
|
|
171
|
+
const { SparkRenderer } = await import('@sparkjsdev/spark');
|
|
172
|
+
if (disposed) return;
|
|
173
|
+
sparkRenderer = new SparkRenderer({ renderer, enableLod: false });
|
|
174
|
+
}
|
|
175
|
+
framing = measureAssetPreview(loaded, 'perspective');
|
|
176
|
+
fitCamera(container.clientWidth, container.clientHeight);
|
|
177
|
+
mixer = poseAtFirstFrame(loaded);
|
|
178
|
+
if (showSkeleton) {
|
|
179
|
+
const helper = new THREE.SkeletonHelper(loaded);
|
|
180
|
+
helper.name = '__compact_preview_skeleton';
|
|
181
|
+
scene.add(helper);
|
|
182
|
+
previewHelpers.push(helper);
|
|
183
|
+
}
|
|
184
|
+
extension = setupPreview?.({ scene, camera, renderer, model: loaded });
|
|
185
|
+
extension?.resize?.(
|
|
186
|
+
Math.max(container.clientWidth, 1),
|
|
187
|
+
Math.max(container.clientHeight, 1),
|
|
188
|
+
renderer.getPixelRatio(),
|
|
189
|
+
);
|
|
190
|
+
setLoading(false);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const load = async () => {
|
|
194
|
+
try {
|
|
195
|
+
if (sourceFactory) {
|
|
196
|
+
ownedFactory = createOwnedObjectPreviewSnapshot(sourceFactory);
|
|
197
|
+
await install(ownedFactory.root);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!assetPath) throw new Error('Compact Object3D preview has no source.');
|
|
201
|
+
const format = modelThumbnailFormat(assetPath);
|
|
202
|
+
if (!format) throw new Error(`No native Three.js loader supports ${assetPath}.`);
|
|
203
|
+
const loaded =
|
|
204
|
+
format === 'glb' || format === 'gltf' || format === 'spz'
|
|
205
|
+
? await loadProjectAssetModel(assetPath, controller.signal)
|
|
206
|
+
: await loadModelThumbnailObject({
|
|
207
|
+
url: assetPath,
|
|
208
|
+
format,
|
|
209
|
+
...(materialPath ? { materialUrl: materialPath } : {}),
|
|
210
|
+
});
|
|
211
|
+
ownedModel = createModelPreviewSource(loaded);
|
|
212
|
+
if (controller.signal.aborted || disposed) {
|
|
213
|
+
disposeProjectAssetModel(ownedModel);
|
|
214
|
+
ownedModel = null;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
await install(ownedModel);
|
|
218
|
+
} catch (caught) {
|
|
219
|
+
if (!controller.signal.aborted && !disposed) {
|
|
220
|
+
setError(caught instanceof Error ? caught.message : String(caught));
|
|
221
|
+
setLoading(false);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
void load();
|
|
226
|
+
|
|
227
|
+
const resize = () => {
|
|
228
|
+
const width = Math.max(container.clientWidth, 1);
|
|
229
|
+
const height = Math.max(container.clientHeight, 1);
|
|
230
|
+
renderer.setSize(width, height, false);
|
|
231
|
+
if (framing && !extension) fitCamera(width, height);
|
|
232
|
+
else {
|
|
233
|
+
camera.aspect = width / height;
|
|
234
|
+
camera.updateProjectionMatrix();
|
|
235
|
+
}
|
|
236
|
+
extension?.resize?.(width, height, renderer.getPixelRatio());
|
|
237
|
+
};
|
|
238
|
+
const resizeObserver = new ResizeObserver(resize);
|
|
239
|
+
resizeObserver.observe(container);
|
|
240
|
+
resize();
|
|
241
|
+
|
|
242
|
+
const render = () => {
|
|
243
|
+
frame = 0;
|
|
244
|
+
if (disposed || !isEditorPresentationActive()) return;
|
|
245
|
+
// PRESENTATION ONLY. The mixer is never advanced here — see
|
|
246
|
+
// `poseAtFirstFrame`; `delta` drives the project's own presentation
|
|
247
|
+
// extension and the orbit damping, both editor-owned.
|
|
248
|
+
const delta = Math.min(clock.getDelta(), 0.1);
|
|
249
|
+
controls.update();
|
|
250
|
+
if (sparkRenderer) scene.add(sparkRenderer);
|
|
251
|
+
try {
|
|
252
|
+
if (extension?.render) extension.render(delta);
|
|
253
|
+
else renderer.render(scene, camera);
|
|
254
|
+
} finally {
|
|
255
|
+
sparkRenderer?.removeFromParent();
|
|
256
|
+
}
|
|
257
|
+
frame = requestAnimationFrame(render);
|
|
258
|
+
};
|
|
259
|
+
const unsubscribeActivity = subscribeEditorPresentationActivity(() => {
|
|
260
|
+
if (!isEditorPresentationActive()) {
|
|
261
|
+
cancelAnimationFrame(frame);
|
|
262
|
+
frame = 0;
|
|
263
|
+
} else if (!disposed && frame === 0) {
|
|
264
|
+
clock.getDelta();
|
|
265
|
+
render();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (isEditorPresentationActive()) frame = requestAnimationFrame(render);
|
|
269
|
+
|
|
270
|
+
return () => {
|
|
271
|
+
disposed = true;
|
|
272
|
+
unsubscribeActivity();
|
|
273
|
+
unregisterDesignTimeSurface();
|
|
274
|
+
controller.abort(new Error('Compact Object3D preview closed.'));
|
|
275
|
+
cancelAnimationFrame(frame);
|
|
276
|
+
resizeObserver.disconnect();
|
|
277
|
+
mixer?.stopAllAction();
|
|
278
|
+
extension?.dispose();
|
|
279
|
+
controls.dispose();
|
|
280
|
+
if (sparkRenderer) {
|
|
281
|
+
sparkRenderer.removeFromParent();
|
|
282
|
+
disposeSparkRendererWhenIdle(sparkRenderer);
|
|
283
|
+
sparkRenderer = null;
|
|
284
|
+
}
|
|
285
|
+
renderer.dispose();
|
|
286
|
+
renderer.forceContextLoss();
|
|
287
|
+
for (const helper of previewHelpers) {
|
|
288
|
+
const drawable = helper as THREE.Line;
|
|
289
|
+
drawable.geometry?.dispose();
|
|
290
|
+
const material = drawable.material;
|
|
291
|
+
if (Array.isArray(material)) {
|
|
292
|
+
for (const entry of material) entry.dispose();
|
|
293
|
+
} else material?.dispose();
|
|
294
|
+
}
|
|
295
|
+
if (ownedFactory) ownedFactory.dispose();
|
|
296
|
+
if (ownedModel) disposeProjectAssetModel(ownedModel);
|
|
297
|
+
if (container.contains(renderer.domElement)) container.removeChild(renderer.domElement);
|
|
298
|
+
releaseResizeObserver();
|
|
299
|
+
releaseAnimationFrame();
|
|
300
|
+
releaseContext();
|
|
301
|
+
};
|
|
302
|
+
}, [
|
|
303
|
+
active,
|
|
304
|
+
assetPath,
|
|
305
|
+
background,
|
|
306
|
+
cameraDirection,
|
|
307
|
+
exposure,
|
|
308
|
+
materialPath,
|
|
309
|
+
setupPreview,
|
|
310
|
+
showGrid,
|
|
311
|
+
showSkeleton,
|
|
312
|
+
sourceFactory,
|
|
313
|
+
useDefaultLighting,
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div
|
|
318
|
+
ref={containerRef}
|
|
319
|
+
data-testid="object3d-compact-preview"
|
|
320
|
+
role="region"
|
|
321
|
+
aria-label={`Interactive 3D preview: ${displayName}`}
|
|
322
|
+
aria-busy={loading}
|
|
323
|
+
style={{
|
|
324
|
+
width: '100%',
|
|
325
|
+
height: '100%',
|
|
326
|
+
minHeight: 220,
|
|
327
|
+
overflow: 'hidden',
|
|
328
|
+
position: 'relative',
|
|
329
|
+
background: themeVars.surface.inset,
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
{loading && !error ? (
|
|
333
|
+
<span style={{ position: 'absolute', padding: 10, color: themeVars.content.muted }}>
|
|
334
|
+
Loading…
|
|
335
|
+
</span>
|
|
336
|
+
) : null}
|
|
337
|
+
{!active ? (
|
|
338
|
+
<span style={{ position: 'absolute', padding: 10, color: themeVars.content.muted }}>
|
|
339
|
+
Preview suspended while inactive.
|
|
340
|
+
</span>
|
|
341
|
+
) : null}
|
|
342
|
+
{error ? (
|
|
343
|
+
<div
|
|
344
|
+
role="alert"
|
|
345
|
+
style={{ position: 'absolute', padding: 10, color: themeVars.semantic.danger }}
|
|
346
|
+
>
|
|
347
|
+
{error}
|
|
348
|
+
</div>
|
|
349
|
+
) : null}
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
import type { SparkRenderer } from '@sparkjsdev/spark';
|
|
355
|
+
import { hasUserData } from '@volter/editor-threejs/ecs/user-data';
|
|
356
|
+
import { disposeSparkRendererWhenIdle } from '@volter/editor-threejs/render/spark-renderer-lifecycle';
|