three-cad-viewer 4.3.5 → 4.3.7
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/dist/three-cad-viewer.esm.js +8 -5
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +1 -1
- package/dist/three-cad-viewer.js +8 -5
- package/dist/three-cad-viewer.min.js +1 -1
- package/package.json +2 -3
- package/src/_version.ts +0 -1
- package/src/camera/camera.ts +0 -445
- package/src/camera/controls/CADOrbitControls.ts +0 -241
- package/src/camera/controls/CADTrackballControls.ts +0 -598
- package/src/camera/controls.ts +0 -380
- package/src/core/patches.ts +0 -16
- package/src/core/studio-manager.ts +0 -652
- package/src/core/types.ts +0 -892
- package/src/core/viewer-state.ts +0 -784
- package/src/core/viewer.ts +0 -4821
- package/src/index.ts +0 -151
- package/src/rendering/environment.ts +0 -840
- package/src/rendering/light-detection.ts +0 -327
- package/src/rendering/material-factory.ts +0 -735
- package/src/rendering/material-presets.ts +0 -289
- package/src/rendering/raycast.ts +0 -291
- package/src/rendering/room-environment.ts +0 -192
- package/src/rendering/studio-composer.ts +0 -577
- package/src/rendering/studio-floor.ts +0 -108
- package/src/rendering/texture-cache.ts +0 -324
- package/src/rendering/tree-model.ts +0 -542
- package/src/rendering/triplanar.ts +0 -329
- package/src/scene/animation.ts +0 -343
- package/src/scene/axes.ts +0 -108
- package/src/scene/bbox.ts +0 -223
- package/src/scene/clipping.ts +0 -650
- package/src/scene/grid.ts +0 -864
- package/src/scene/nestedgroup.ts +0 -1448
- package/src/scene/objectgroup.ts +0 -866
- package/src/scene/orientation.ts +0 -259
- package/src/scene/render-shape.ts +0 -634
- package/src/tools/cad_tools/measure.ts +0 -811
- package/src/tools/cad_tools/select.ts +0 -100
- package/src/tools/cad_tools/tools.ts +0 -231
- package/src/tools/cad_tools/ui.ts +0 -454
- package/src/tools/cad_tools/zebra.ts +0 -369
- package/src/types/html.d.ts +0 -5
- package/src/types/n8ao.d.ts +0 -28
- package/src/types/three-augmentation.d.ts +0 -60
- package/src/ui/display.ts +0 -3295
- package/src/ui/index.html +0 -505
- package/src/ui/info.ts +0 -177
- package/src/ui/slider.ts +0 -206
- package/src/ui/toolbar.ts +0 -347
- package/src/ui/treeview.ts +0 -945
- package/src/utils/decode-instances.ts +0 -233
- package/src/utils/font.ts +0 -60
- package/src/utils/gpu-tracker.ts +0 -265
- package/src/utils/logger.ts +0 -92
- package/src/utils/sizeof.ts +0 -116
- package/src/utils/timer.ts +0 -69
- package/src/utils/utils.ts +0 -446
|
@@ -1,652 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StudioManager — orchestrates Studio mode rendering.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from viewer.ts to reduce its complexity. Owns the Studio-specific
|
|
5
|
-
* resources (composer, floor, shadow lights, environment manager) and handles
|
|
6
|
-
* all Studio subscriptions and mode enter/leave logic.
|
|
7
|
-
*
|
|
8
|
-
* Communicates with the Viewer via the StudioManagerContext interface to avoid
|
|
9
|
-
* a circular dependency.
|
|
10
|
-
*/
|
|
11
|
-
import * as THREE from "three";
|
|
12
|
-
import type { ClippingState } from "../scene/clipping.js";
|
|
13
|
-
import { EnvironmentManager } from "../rendering/environment.js";
|
|
14
|
-
import { StudioFloor } from "../rendering/studio-floor.js";
|
|
15
|
-
import { StudioComposer } from "../rendering/studio-composer.js";
|
|
16
|
-
import { ViewerState } from "./viewer-state.js";
|
|
17
|
-
import { logger } from "../utils/logger.js";
|
|
18
|
-
import { scaleLight } from "../utils/utils.js";
|
|
19
|
-
import type { NestedGroup, ObjectGroup } from "../scene/nestedgroup.js";
|
|
20
|
-
import { isObjectGroup } from "../scene/nestedgroup.js";
|
|
21
|
-
import type { Camera } from "../camera/camera.js";
|
|
22
|
-
import type { Clipping } from "../scene/clipping.js";
|
|
23
|
-
import type { BoundingBox } from "../scene/bbox.js";
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Context interface — what StudioManager needs from Viewer
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Abstraction over Viewer that StudioManager uses. Keeps the dependency
|
|
31
|
-
* one-directional (StudioManager → context, never StudioManager → Viewer).
|
|
32
|
-
*/
|
|
33
|
-
export interface StudioManagerContext {
|
|
34
|
-
renderer: THREE.WebGLRenderer;
|
|
35
|
-
state: ViewerState;
|
|
36
|
-
|
|
37
|
-
/** Whether viewer has rendered content. */
|
|
38
|
-
isRendered(): boolean;
|
|
39
|
-
|
|
40
|
-
// Scene graph access (throw if not rendered)
|
|
41
|
-
getScene(): THREE.Scene;
|
|
42
|
-
getCamera(): Camera;
|
|
43
|
-
getAmbientLight(): THREE.AmbientLight;
|
|
44
|
-
getDirectLight(): THREE.DirectionalLight;
|
|
45
|
-
getNestedGroup(): NestedGroup;
|
|
46
|
-
getClipping(): Clipping;
|
|
47
|
-
getBbox(): BoundingBox | null;
|
|
48
|
-
getLastBboxId(): string | null;
|
|
49
|
-
|
|
50
|
-
// Viewer actions
|
|
51
|
-
setAxes(flag: boolean, notify?: boolean): void;
|
|
52
|
-
setGrids(grids: [boolean, boolean, boolean], notify?: boolean): void;
|
|
53
|
-
setOrtho(flag: boolean, notify?: boolean): void;
|
|
54
|
-
|
|
55
|
-
// Callbacks
|
|
56
|
-
update(updateMarker: boolean, notify?: boolean): void;
|
|
57
|
-
dispatchEvent(event: Event): void;
|
|
58
|
-
onSelectionChanged(id: string | null): void;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// StudioManager
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
class StudioManager {
|
|
66
|
-
readonly envManager: EnvironmentManager;
|
|
67
|
-
readonly floor: StudioFloor;
|
|
68
|
-
|
|
69
|
-
private _composer: StudioComposer | null = null;
|
|
70
|
-
private _active: boolean = false;
|
|
71
|
-
private _savedClippingState: ClippingState | null = null;
|
|
72
|
-
private _shadowLights: THREE.DirectionalLight[] = [];
|
|
73
|
-
private _ctx: StudioManagerContext;
|
|
74
|
-
|
|
75
|
-
constructor(ctx: StudioManagerContext) {
|
|
76
|
-
this._ctx = ctx;
|
|
77
|
-
this.envManager = new EnvironmentManager();
|
|
78
|
-
this.floor = new StudioFloor();
|
|
79
|
-
this._setupSubscriptions();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// -------------------------------------------------------------------------
|
|
83
|
-
// Public API
|
|
84
|
-
// -------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
get isActive(): boolean {
|
|
87
|
-
return this._active && this._ctx.isRendered();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
get hasComposer(): boolean {
|
|
91
|
-
return this._composer !== null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Render via composer (call from Viewer.update / _animate). */
|
|
95
|
-
render(): void {
|
|
96
|
-
this._composer?.render();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Update composer camera (call from Viewer.switchCamera). */
|
|
100
|
-
setCamera(camera: THREE.Camera): void {
|
|
101
|
-
this._composer?.setCamera(camera);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Resize composer (call from Viewer.resize). */
|
|
105
|
-
setSize(width: number, height: number): void {
|
|
106
|
-
this._composer?.setSize(width, height);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Whether env background needs per-frame update. */
|
|
110
|
-
get isEnvBackgroundActive(): boolean {
|
|
111
|
-
return this.envManager.isEnvBackgroundActive;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Update env background (ortho workaround, call per frame). */
|
|
115
|
-
updateEnvBackground(renderer: THREE.WebGLRenderer, camera: THREE.Camera): void {
|
|
116
|
-
this.envManager.updateEnvBackground(renderer, camera);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Check if an environment name is a Poly Haven preset. */
|
|
120
|
-
isEnvPreset(name: string): boolean {
|
|
121
|
-
return this.envManager.isPreset(name);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get the ObjectGroup and path for the currently selected object.
|
|
126
|
-
* Returns null if nothing selected or Studio mode inactive.
|
|
127
|
-
*/
|
|
128
|
-
getSelectedObjectGroup(): { object: ObjectGroup; path: string } | null {
|
|
129
|
-
if (!this._active || this._ctx.getLastBboxId() == null) {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
const id = this._ctx.getLastBboxId()!;
|
|
133
|
-
const entry = this._ctx.getNestedGroup().groups[id];
|
|
134
|
-
if (!isObjectGroup(entry)) {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
return { object: entry, path: id };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// -------------------------------------------------------------------------
|
|
141
|
-
// Mode enter/leave
|
|
142
|
-
// -------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
enterStudioMode = async (): Promise<void> => {
|
|
145
|
-
if (!this._ctx.isRendered()) return;
|
|
146
|
-
this._active = true;
|
|
147
|
-
|
|
148
|
-
const { renderer, state } = this._ctx;
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
// 1. Save and disable clipping
|
|
152
|
-
const clipping = this._ctx.getClipping();
|
|
153
|
-
this._savedClippingState = clipping.saveState();
|
|
154
|
-
renderer.localClippingEnabled = false;
|
|
155
|
-
clipping.setVisible(false);
|
|
156
|
-
if (clipping.planeHelpers) {
|
|
157
|
-
clipping.planeHelpers.visible = false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 2. Build/swap studio materials (async due to textures)
|
|
161
|
-
const nestedGroup = this._ctx.getNestedGroup();
|
|
162
|
-
const unresolvedTags = await nestedGroup.enterStudioMode(state.get("studioTextureMapping"));
|
|
163
|
-
if (!this._active) return;
|
|
164
|
-
|
|
165
|
-
// Surface unresolved material tags to the UI
|
|
166
|
-
if (unresolvedTags.length > 0) {
|
|
167
|
-
this._ctx.dispatchEvent(
|
|
168
|
-
new CustomEvent("tcv-material-warnings", { detail: unresolvedTags }),
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 3. Load environment map
|
|
173
|
-
const envName = state.get("studioEnvironment");
|
|
174
|
-
await this.envManager.loadEnvironment(envName, renderer);
|
|
175
|
-
if (!this._active) return;
|
|
176
|
-
|
|
177
|
-
// 4. Apply ALL rendering changes atomically
|
|
178
|
-
const scene = this._ctx.getScene();
|
|
179
|
-
const camera = this._ctx.getCamera();
|
|
180
|
-
this.envManager.apply(
|
|
181
|
-
scene,
|
|
182
|
-
state.get("studioEnvIntensity"),
|
|
183
|
-
state.get("studioBackground"),
|
|
184
|
-
state.get("up") === "Z",
|
|
185
|
-
camera.ortho,
|
|
186
|
-
state.get("studioEnvRotation"),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Lighting: disable CAD lights; environment IBL provides all illumination
|
|
190
|
-
this._ctx.getAmbientLight().intensity = 0;
|
|
191
|
-
this._ctx.getDirectLight().intensity = 0;
|
|
192
|
-
|
|
193
|
-
// Floor
|
|
194
|
-
this._configureFloor();
|
|
195
|
-
|
|
196
|
-
// Create composer (must be before shadows)
|
|
197
|
-
if (!this._composer) {
|
|
198
|
-
this._composer = new StudioComposer(
|
|
199
|
-
renderer,
|
|
200
|
-
scene,
|
|
201
|
-
camera.getCamera(),
|
|
202
|
-
state.get("cadWidth"),
|
|
203
|
-
state.get("height"),
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Shadows (requires composer)
|
|
208
|
-
if (state.get("studioShadowIntensity") > 0) {
|
|
209
|
-
this._setShadowsEnabled(true);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Tone mapping
|
|
213
|
-
this._composer.setToneMapping(
|
|
214
|
-
state.get("studioToneMapping"),
|
|
215
|
-
state.get("studioExposure"),
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
// Background protection
|
|
219
|
-
const bg = scene.background;
|
|
220
|
-
this._composer.setBackgroundProtect(
|
|
221
|
-
bg instanceof THREE.Color ? bg : null,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Ambient Occlusion
|
|
225
|
-
const aoIntensity = state.get("studioAOIntensity");
|
|
226
|
-
this._composer.setAOIntensity(aoIntensity);
|
|
227
|
-
this._composer.setAOEnabled(aoIntensity > 0);
|
|
228
|
-
|
|
229
|
-
// Edges are always hidden in Studio mode
|
|
230
|
-
nestedGroup.setStudioShowEdges(false);
|
|
231
|
-
|
|
232
|
-
this._ctx.update(true, false);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
if (this._composer) {
|
|
235
|
-
this._composer.dispose();
|
|
236
|
-
this._composer = null;
|
|
237
|
-
}
|
|
238
|
-
this._active = false;
|
|
239
|
-
logger.error("Unexpected error entering studio mode", err);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
leaveStudioMode = (): void => {
|
|
244
|
-
if (!this._ctx.isRendered()) return;
|
|
245
|
-
|
|
246
|
-
const { renderer, state } = this._ctx;
|
|
247
|
-
const nestedGroup = this._ctx.getNestedGroup();
|
|
248
|
-
|
|
249
|
-
// 1. Restore materials
|
|
250
|
-
nestedGroup.leaveStudioMode();
|
|
251
|
-
|
|
252
|
-
// 2. Tear down composer
|
|
253
|
-
if (this._composer) {
|
|
254
|
-
this._composer.dispose();
|
|
255
|
-
this._composer = null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// 3. Remove environment, disable shadows
|
|
259
|
-
this.envManager.remove(this._ctx.getScene());
|
|
260
|
-
this._setShadowsEnabled(false);
|
|
261
|
-
|
|
262
|
-
// 4. Restore lighting
|
|
263
|
-
this._ctx.getAmbientLight().intensity = scaleLight(state.get("ambientIntensity"));
|
|
264
|
-
this._ctx.getDirectLight().intensity = scaleLight(state.get("directIntensity"));
|
|
265
|
-
|
|
266
|
-
// 5. Disable tone mapping
|
|
267
|
-
renderer.toneMapping = THREE.NoToneMapping;
|
|
268
|
-
renderer.toneMappingExposure = 1.0;
|
|
269
|
-
|
|
270
|
-
// 6. Restore clipping state
|
|
271
|
-
if (this._savedClippingState) {
|
|
272
|
-
this._ctx.getClipping().restoreState(this._savedClippingState);
|
|
273
|
-
this._savedClippingState = null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 7. Clear active flag; edges restored by ObjectGroup.leaveStudioMode()
|
|
277
|
-
this._active = false;
|
|
278
|
-
|
|
279
|
-
this._ctx.update(true, false);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
resetStudio = (): void => {
|
|
283
|
-
const defaults = ViewerState.STUDIO_MODE_DEFAULTS;
|
|
284
|
-
const state = this._ctx.state;
|
|
285
|
-
state.set("studioEnvironment", defaults.studioEnvironment);
|
|
286
|
-
state.set("studioEnvIntensity", defaults.studioEnvIntensity);
|
|
287
|
-
state.set("studioBackground", defaults.studioBackground);
|
|
288
|
-
state.set("studioToneMapping", defaults.studioToneMapping);
|
|
289
|
-
state.set("studioExposure", defaults.studioExposure);
|
|
290
|
-
state.set("studio4kEnvMaps", defaults.studio4kEnvMaps);
|
|
291
|
-
state.set("studioTextureMapping", defaults.studioTextureMapping);
|
|
292
|
-
state.set("studioEnvRotation", defaults.studioEnvRotation);
|
|
293
|
-
state.set("studioShadowIntensity", defaults.studioShadowIntensity);
|
|
294
|
-
state.set("studioShadowSoftness", defaults.studioShadowSoftness);
|
|
295
|
-
state.set("studioAOIntensity", defaults.studioAOIntensity);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Dispose all Studio resources. Called from Viewer.dispose().
|
|
300
|
-
* Must be called BEFORE renderer.dispose().
|
|
301
|
-
*/
|
|
302
|
-
dispose(): void {
|
|
303
|
-
if (this._composer) {
|
|
304
|
-
this._composer.dispose();
|
|
305
|
-
this._composer = null;
|
|
306
|
-
}
|
|
307
|
-
this._removeShadowLights();
|
|
308
|
-
this.envManager.dispose();
|
|
309
|
-
this.floor.dispose();
|
|
310
|
-
this._active = false;
|
|
311
|
-
this._savedClippingState = null;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// -------------------------------------------------------------------------
|
|
315
|
-
// Private — subscriptions
|
|
316
|
-
// -------------------------------------------------------------------------
|
|
317
|
-
|
|
318
|
-
private _setupSubscriptions(): void {
|
|
319
|
-
const isActive = (): boolean => {
|
|
320
|
-
return this._active && this._ctx.isRendered();
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
const reapplyEnv = (orthoOverride?: boolean): void => {
|
|
324
|
-
this.envManager.apply(
|
|
325
|
-
this._ctx.getScene(),
|
|
326
|
-
this._ctx.state.get("studioEnvIntensity"),
|
|
327
|
-
this._ctx.state.get("studioBackground"),
|
|
328
|
-
this._ctx.state.get("up") === "Z",
|
|
329
|
-
orthoOverride ?? this._ctx.getCamera().ortho,
|
|
330
|
-
this._ctx.state.get("studioEnvRotation"),
|
|
331
|
-
);
|
|
332
|
-
if (this._composer) {
|
|
333
|
-
const bg = this._ctx.getScene().background;
|
|
334
|
-
this._composer.setBackgroundProtect(
|
|
335
|
-
bg instanceof THREE.Color ? bg : null,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
const state = this._ctx.state;
|
|
341
|
-
|
|
342
|
-
state.subscribe("studioEnvironment", (change) => {
|
|
343
|
-
if (!isActive()) return;
|
|
344
|
-
this.envManager.loadEnvironment(change.new, this._ctx.renderer).then(() => {
|
|
345
|
-
if (!isActive()) return;
|
|
346
|
-
reapplyEnv();
|
|
347
|
-
if (state.get("studioShadowIntensity") > 0) {
|
|
348
|
-
this._configureShadowLights();
|
|
349
|
-
}
|
|
350
|
-
this._ctx.update(true, false);
|
|
351
|
-
this._ctx.dispatchEvent(new Event("tcv-studio-ready"));
|
|
352
|
-
}).catch((err) => {
|
|
353
|
-
logger.error("Unexpected error loading studio environment", err);
|
|
354
|
-
this._ctx.dispatchEvent(new Event("tcv-studio-ready"));
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
state.subscribe("studioEnvIntensity", () => {
|
|
359
|
-
if (!isActive()) return;
|
|
360
|
-
reapplyEnv();
|
|
361
|
-
this._ctx.update(true, false);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
state.subscribe("studioEnvRotation", () => {
|
|
365
|
-
if (!isActive()) return;
|
|
366
|
-
reapplyEnv();
|
|
367
|
-
if (state.get("studioShadowIntensity") > 0) {
|
|
368
|
-
this._configureShadowLights();
|
|
369
|
-
}
|
|
370
|
-
this._ctx.update(true, false);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
state.subscribe("studioShadowIntensity", (change) => {
|
|
374
|
-
if (!isActive()) return;
|
|
375
|
-
const intensity = change.new;
|
|
376
|
-
const wasEnabled = change.old != null && change.old > 0;
|
|
377
|
-
const nowEnabled = intensity > 0;
|
|
378
|
-
|
|
379
|
-
if (nowEnabled && !wasEnabled) {
|
|
380
|
-
this._setShadowsEnabled(true);
|
|
381
|
-
} else if (!nowEnabled && wasEnabled) {
|
|
382
|
-
this._setShadowsEnabled(false);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (nowEnabled) {
|
|
386
|
-
for (const light of this._shadowLights) {
|
|
387
|
-
light.shadow.intensity = intensity;
|
|
388
|
-
}
|
|
389
|
-
this.floor.setShadowIntensity(intensity);
|
|
390
|
-
if (this._composer) {
|
|
391
|
-
this._composer.setShadowMaskIntensity(intensity * 0.75);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
this._ctx.update(true, false);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
state.subscribe("studioShadowSoftness", (change) => {
|
|
398
|
-
if (!isActive()) return;
|
|
399
|
-
if (state.get("studioShadowIntensity") <= 0) return;
|
|
400
|
-
if (this._composer) {
|
|
401
|
-
this._composer.setShadowSoftness(change.new);
|
|
402
|
-
}
|
|
403
|
-
this._ctx.update(true, false);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
state.subscribe("studioAOIntensity", (change) => {
|
|
407
|
-
if (!isActive()) return;
|
|
408
|
-
if (this._composer) {
|
|
409
|
-
const intensity = change.new;
|
|
410
|
-
this._composer.setAOEnabled(intensity > 0);
|
|
411
|
-
this._composer.setAOIntensity(intensity);
|
|
412
|
-
}
|
|
413
|
-
this._ctx.update(true, false);
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
state.subscribe("studioBackground", () => {
|
|
417
|
-
if (!isActive()) return;
|
|
418
|
-
reapplyEnv();
|
|
419
|
-
this._ctx.update(true, false);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
state.subscribe("ortho", (change) => {
|
|
423
|
-
if (!isActive()) return;
|
|
424
|
-
reapplyEnv(change.new as boolean);
|
|
425
|
-
this._ctx.update(true, false);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
state.subscribe("studioToneMapping", () => {
|
|
429
|
-
if (!isActive()) return;
|
|
430
|
-
this._applyToneMapping();
|
|
431
|
-
this._ctx.update(true, false);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
state.subscribe("studioExposure", () => {
|
|
435
|
-
if (!isActive()) return;
|
|
436
|
-
this._applyToneMapping();
|
|
437
|
-
this._ctx.update(true, false);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
state.subscribe("studio4kEnvMaps", (change) => {
|
|
441
|
-
if (!isActive()) return;
|
|
442
|
-
const envName = state.get("studioEnvironment");
|
|
443
|
-
this.envManager.setUse4kEnvMaps(change.new, envName, this._ctx.renderer).then(() => {
|
|
444
|
-
if (!isActive()) return;
|
|
445
|
-
reapplyEnv();
|
|
446
|
-
if (state.get("studioShadowIntensity") > 0) {
|
|
447
|
-
this._configureShadowLights();
|
|
448
|
-
}
|
|
449
|
-
this._ctx.update(true, false);
|
|
450
|
-
this._ctx.dispatchEvent(new Event("tcv-studio-ready"));
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
state.subscribe("studioTextureMapping", () => {
|
|
455
|
-
if (!isActive()) return;
|
|
456
|
-
this._rebuildMaterials().finally(() => {
|
|
457
|
-
this._ctx.dispatchEvent(new Event("tcv-studio-ready"));
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// -------------------------------------------------------------------------
|
|
463
|
-
// Private — floor, shadows, tone mapping, material rebuild
|
|
464
|
-
// -------------------------------------------------------------------------
|
|
465
|
-
|
|
466
|
-
private _configureFloor(): void {
|
|
467
|
-
const bbox = this._ctx.getBbox();
|
|
468
|
-
if (!bbox) return;
|
|
469
|
-
const maxExtent = Math.max(
|
|
470
|
-
bbox.max.x - bbox.min.x,
|
|
471
|
-
bbox.max.y - bbox.min.y,
|
|
472
|
-
bbox.max.z - bbox.min.z,
|
|
473
|
-
);
|
|
474
|
-
const zPosition = bbox.min.z - maxExtent * 0.001;
|
|
475
|
-
this.floor.configure(zPosition, maxExtent);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private _configureShadowLights(): void {
|
|
479
|
-
this._removeShadowLights();
|
|
480
|
-
|
|
481
|
-
const bbox = this._ctx.getBbox();
|
|
482
|
-
if (!bbox || !this._ctx.isRendered()) return;
|
|
483
|
-
|
|
484
|
-
const state = this._ctx.state;
|
|
485
|
-
const envName = state.get("studioEnvironment");
|
|
486
|
-
const detection = this.envManager.getLightDetection(envName);
|
|
487
|
-
if (!detection || detection.lights.length === 0) return;
|
|
488
|
-
|
|
489
|
-
const bboxCenter = new THREE.Vector3(
|
|
490
|
-
(bbox.min.x + bbox.max.x) / 2,
|
|
491
|
-
(bbox.min.y + bbox.max.y) / 2,
|
|
492
|
-
(bbox.min.z + bbox.max.z) / 2,
|
|
493
|
-
);
|
|
494
|
-
const maxExtent = Math.max(
|
|
495
|
-
bbox.max.x - bbox.min.x,
|
|
496
|
-
bbox.max.y - bbox.min.y,
|
|
497
|
-
bbox.max.z - bbox.min.z,
|
|
498
|
-
);
|
|
499
|
-
|
|
500
|
-
const isZUp = state.get("up") === "Z";
|
|
501
|
-
const envRotationRad = (state.get("studioEnvRotation") * Math.PI) / 180;
|
|
502
|
-
|
|
503
|
-
this._ctx.renderer.shadowMap.enabled = true;
|
|
504
|
-
this._ctx.renderer.shadowMap.type = THREE.PCFShadowMap;
|
|
505
|
-
|
|
506
|
-
const scene = this._ctx.getScene();
|
|
507
|
-
|
|
508
|
-
for (const detected of detection.lights) {
|
|
509
|
-
let [dx, dy, dz] = detected.direction;
|
|
510
|
-
|
|
511
|
-
if (isZUp) {
|
|
512
|
-
const oy = dy;
|
|
513
|
-
const oz = dz;
|
|
514
|
-
dy = -oz;
|
|
515
|
-
dz = oy;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (envRotationRad !== 0) {
|
|
519
|
-
if (isZUp) {
|
|
520
|
-
const cosR = Math.cos(envRotationRad);
|
|
521
|
-
const sinR = Math.sin(envRotationRad);
|
|
522
|
-
const nx = dx * cosR - dy * sinR;
|
|
523
|
-
const ny = dx * sinR + dy * cosR;
|
|
524
|
-
dx = nx;
|
|
525
|
-
dy = ny;
|
|
526
|
-
} else {
|
|
527
|
-
const cosR = Math.cos(envRotationRad);
|
|
528
|
-
const sinR = Math.sin(envRotationRad);
|
|
529
|
-
const nx = dx * cosR + dz * sinR;
|
|
530
|
-
const nz = -dx * sinR + dz * cosR;
|
|
531
|
-
dx = nx;
|
|
532
|
-
dz = nz;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const dir = new THREE.Vector3(dx, dy, dz).normalize();
|
|
537
|
-
const light = new THREE.DirectionalLight(0xffffff, 0.01);
|
|
538
|
-
|
|
539
|
-
light.position.copy(bboxCenter).addScaledVector(dir, maxExtent * 3);
|
|
540
|
-
light.target.position.copy(bboxCenter);
|
|
541
|
-
|
|
542
|
-
light.castShadow = true;
|
|
543
|
-
const frustumSize = maxExtent * 6.0;
|
|
544
|
-
light.shadow.camera.left = -frustumSize;
|
|
545
|
-
light.shadow.camera.right = frustumSize;
|
|
546
|
-
light.shadow.camera.top = frustumSize;
|
|
547
|
-
light.shadow.camera.bottom = -frustumSize;
|
|
548
|
-
light.shadow.camera.near = maxExtent * 0.1;
|
|
549
|
-
light.shadow.camera.far = maxExtent * 7;
|
|
550
|
-
light.shadow.mapSize.set(4096, 4096);
|
|
551
|
-
light.shadow.bias = -0.001;
|
|
552
|
-
light.shadow.intensity = state.get("studioShadowIntensity");
|
|
553
|
-
|
|
554
|
-
scene.add(light);
|
|
555
|
-
scene.add(light.target);
|
|
556
|
-
this._shadowLights.push(light);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (this._composer) {
|
|
560
|
-
this._composer.setShadowMaskEnabled(true);
|
|
561
|
-
this._composer.setShadowSoftness(state.get("studioShadowSoftness"));
|
|
562
|
-
this._composer.setShadowMaskIntensity(state.get("studioShadowIntensity") * 0.75);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
private _removeShadowLights(): void {
|
|
567
|
-
if (this._ctx.isRendered()) {
|
|
568
|
-
const scene = this._ctx.getScene();
|
|
569
|
-
for (const light of this._shadowLights) {
|
|
570
|
-
scene.remove(light);
|
|
571
|
-
scene.remove(light.target);
|
|
572
|
-
light.shadow.map?.dispose();
|
|
573
|
-
light.dispose();
|
|
574
|
-
}
|
|
575
|
-
} else {
|
|
576
|
-
for (const light of this._shadowLights) {
|
|
577
|
-
light.shadow.map?.dispose();
|
|
578
|
-
light.dispose();
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
this._shadowLights = [];
|
|
582
|
-
|
|
583
|
-
if (this._composer) {
|
|
584
|
-
this._composer.setShadowMaskEnabled(false);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
private _setShadowsEnabled(enabled: boolean): void {
|
|
589
|
-
if (!this._ctx.isRendered()) return;
|
|
590
|
-
|
|
591
|
-
const nestedGroup = this._ctx.getNestedGroup();
|
|
592
|
-
|
|
593
|
-
if (enabled) {
|
|
594
|
-
this._configureShadowLights();
|
|
595
|
-
|
|
596
|
-
nestedGroup.rootGroup?.traverse((obj) => {
|
|
597
|
-
if (obj instanceof THREE.Mesh) {
|
|
598
|
-
obj.castShadow = true;
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
this.floor.setShadowsEnabled(true);
|
|
603
|
-
|
|
604
|
-
this._ctx.getScene().traverse((obj) => {
|
|
605
|
-
if (obj instanceof THREE.Mesh && obj.material) {
|
|
606
|
-
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
|
607
|
-
for (const m of mats) {
|
|
608
|
-
m.needsUpdate = true;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
} else {
|
|
613
|
-
this._removeShadowLights();
|
|
614
|
-
this._ctx.renderer.shadowMap.enabled = false;
|
|
615
|
-
|
|
616
|
-
nestedGroup.rootGroup?.traverse((obj) => {
|
|
617
|
-
if (obj instanceof THREE.Mesh) {
|
|
618
|
-
obj.castShadow = false;
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
this.floor.setShadowsEnabled(false);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private _applyToneMapping(): void {
|
|
627
|
-
if (this._composer) {
|
|
628
|
-
this._composer.setToneMapping(
|
|
629
|
-
this._ctx.state.get("studioToneMapping"),
|
|
630
|
-
this._ctx.state.get("studioExposure"),
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
private _rebuildMaterials = async (): Promise<void> => {
|
|
636
|
-
const nestedGroup = this._ctx.getNestedGroup();
|
|
637
|
-
nestedGroup.leaveStudioMode();
|
|
638
|
-
nestedGroup.clearStudioMaterialCache();
|
|
639
|
-
const unresolvedTags = await nestedGroup.enterStudioMode(this._ctx.state.get("studioTextureMapping"));
|
|
640
|
-
if (this._active) {
|
|
641
|
-
nestedGroup.setStudioShowEdges(false);
|
|
642
|
-
if (unresolvedTags.length > 0) {
|
|
643
|
-
this._ctx.dispatchEvent(
|
|
644
|
-
new CustomEvent("tcv-material-warnings", { detail: unresolvedTags }),
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
this._ctx.update(true, false);
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
export { StudioManager };
|