three-cad-viewer 4.3.4 → 4.3.6

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 (59) hide show
  1. package/dist/scene/clipping.d.ts +6 -0
  2. package/dist/three-cad-viewer.esm.js +20 -5
  3. package/dist/three-cad-viewer.esm.js.map +1 -1
  4. package/dist/three-cad-viewer.esm.min.js +1 -1
  5. package/dist/three-cad-viewer.js +20 -5
  6. package/dist/three-cad-viewer.min.js +1 -1
  7. package/package.json +2 -3
  8. package/src/_version.ts +0 -1
  9. package/src/camera/camera.ts +0 -445
  10. package/src/camera/controls/CADOrbitControls.ts +0 -241
  11. package/src/camera/controls/CADTrackballControls.ts +0 -598
  12. package/src/camera/controls.ts +0 -380
  13. package/src/core/patches.ts +0 -16
  14. package/src/core/studio-manager.ts +0 -652
  15. package/src/core/types.ts +0 -892
  16. package/src/core/viewer-state.ts +0 -784
  17. package/src/core/viewer.ts +0 -4821
  18. package/src/index.ts +0 -151
  19. package/src/rendering/environment.ts +0 -840
  20. package/src/rendering/light-detection.ts +0 -327
  21. package/src/rendering/material-factory.ts +0 -735
  22. package/src/rendering/material-presets.ts +0 -289
  23. package/src/rendering/raycast.ts +0 -291
  24. package/src/rendering/room-environment.ts +0 -192
  25. package/src/rendering/studio-composer.ts +0 -577
  26. package/src/rendering/studio-floor.ts +0 -108
  27. package/src/rendering/texture-cache.ts +0 -324
  28. package/src/rendering/tree-model.ts +0 -542
  29. package/src/rendering/triplanar.ts +0 -329
  30. package/src/scene/animation.ts +0 -343
  31. package/src/scene/axes.ts +0 -108
  32. package/src/scene/bbox.ts +0 -223
  33. package/src/scene/clipping.ts +0 -640
  34. package/src/scene/grid.ts +0 -864
  35. package/src/scene/nestedgroup.ts +0 -1444
  36. package/src/scene/objectgroup.ts +0 -866
  37. package/src/scene/orientation.ts +0 -259
  38. package/src/scene/render-shape.ts +0 -634
  39. package/src/tools/cad_tools/measure.ts +0 -811
  40. package/src/tools/cad_tools/select.ts +0 -100
  41. package/src/tools/cad_tools/tools.ts +0 -231
  42. package/src/tools/cad_tools/ui.ts +0 -454
  43. package/src/tools/cad_tools/zebra.ts +0 -369
  44. package/src/types/html.d.ts +0 -5
  45. package/src/types/n8ao.d.ts +0 -28
  46. package/src/types/three-augmentation.d.ts +0 -60
  47. package/src/ui/display.ts +0 -3295
  48. package/src/ui/index.html +0 -505
  49. package/src/ui/info.ts +0 -177
  50. package/src/ui/slider.ts +0 -206
  51. package/src/ui/toolbar.ts +0 -347
  52. package/src/ui/treeview.ts +0 -945
  53. package/src/utils/decode-instances.ts +0 -233
  54. package/src/utils/font.ts +0 -60
  55. package/src/utils/gpu-tracker.ts +0 -265
  56. package/src/utils/logger.ts +0 -92
  57. package/src/utils/sizeof.ts +0 -116
  58. package/src/utils/timer.ts +0 -69
  59. package/src/utils/utils.ts +0 -446
@@ -1,735 +0,0 @@
1
- import * as THREE from "three";
2
- import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
3
- import type { ColorValue, MaterialAppearance } from "../core/types.js";
4
- import { gpuTracker } from "../utils/gpu-tracker.js";
5
- import { logger } from "../utils/logger.js";
6
- import { getColorSpaceForMap } from "./texture-cache.js";
7
-
8
- /** threejs-materials property keys that hold [r,g,b] color arrays (linear RGB). */
9
- const COLOR_ARRAY_KEYS = new Set([
10
- "color", "specularColor", "sheenColor", "emissive", "attenuationColor",
11
- ]);
12
-
13
- /** Map from threejs-materials property names to Three.js texture map property names. */
14
- const PROPERTY_TO_MAP: Record<string, string> = {
15
- color: "map",
16
- metalness: "metalnessMap",
17
- roughness: "roughnessMap",
18
- normal: "normalMap",
19
- emissive: "emissiveMap",
20
- specularIntensity: "specularIntensityMap",
21
- specularColor: "specularColorMap",
22
- clearcoat: "clearcoatMap",
23
- clearcoatRoughness: "clearcoatRoughnessMap",
24
- clearcoatNormal: "clearcoatNormalMap",
25
- transmission: "transmissionMap",
26
- sheenColor: "sheenColorMap",
27
- sheenRoughness: "sheenRoughnessMap",
28
- anisotropy: "anisotropyMap",
29
- iridescence: "iridescenceMap",
30
- iridescenceThickness: "iridescenceThicknessMap",
31
- ao: "aoMap",
32
- occlusion: "aoMap",
33
- thickness: "thicknessMap",
34
- opacity: "alphaMap",
35
- };
36
-
37
- /**
38
- * Options for MaterialFactory constructor
39
- */
40
- interface MaterialFactoryOptions {
41
- defaultOpacity?: number;
42
- metalness?: number;
43
- roughness?: number;
44
- edgeColor?: number;
45
- transparent?: boolean;
46
- }
47
-
48
- /**
49
- * Base material properties
50
- */
51
- interface BaseProps {
52
- polygonOffset: boolean;
53
- polygonOffsetFactor: number;
54
- polygonOffsetUnits: number;
55
- transparent: boolean;
56
- opacity: number;
57
- depthWrite: boolean;
58
- depthTest: boolean;
59
- clipIntersection: boolean;
60
- }
61
-
62
- /**
63
- * Options for face materials
64
- */
65
- interface FaceMaterialOptions {
66
- color: ColorValue;
67
- alpha: number;
68
- visible?: boolean;
69
- }
70
-
71
- /**
72
- * Options for back face materials
73
- */
74
- interface BackFaceMaterialOptions extends FaceMaterialOptions {
75
- polygonOffsetUnits?: number;
76
- }
77
-
78
- /**
79
- * Options for edge materials
80
- */
81
- interface EdgeMaterialOptions {
82
- lineWidth: number;
83
- color?: ColorValue | null;
84
- vertexColors?: boolean;
85
- visible?: boolean;
86
- resolution?: { width: number; height: number };
87
- }
88
-
89
- /**
90
- * Options for simple edge materials
91
- */
92
- interface SimpleEdgeMaterialOptions {
93
- color?: ColorValue | null;
94
- visible?: boolean;
95
- }
96
-
97
- /**
98
- * Options for vertex materials
99
- */
100
- interface VertexMaterialOptions {
101
- size: number;
102
- color?: ColorValue | null;
103
- visible?: boolean;
104
- }
105
-
106
- /**
107
- * Options for texture materials
108
- */
109
- interface TextureMaterialOptions {
110
- texture: THREE.Texture;
111
- visible?: boolean;
112
- }
113
-
114
- /**
115
- * Interface for the TextureCache dependency.
116
- * The actual TextureCache class is defined in texture-cache.ts.
117
- * We depend only on its get() method for loose coupling.
118
- */
119
- interface TextureCacheInterface {
120
- get(ref: string, textureRole: string): Promise<THREE.Texture | null>;
121
- }
122
-
123
- /**
124
- * Options for Studio mode materials.
125
- */
126
- interface StudioMaterialOptions {
127
- /** Resolved MaterialAppearance definition (already looked up from materials table / presets) */
128
- materialDef: MaterialAppearance;
129
- /** Fallback CSS hex color from the leaf node (e.g., "#cc0000") */
130
- fallbackColor: ColorValue;
131
- /** Fallback alpha from the leaf node (0-1) */
132
- fallbackAlpha: number;
133
- /** TextureCache for resolving texture references */
134
- textureCache: TextureCacheInterface | null;
135
- }
136
-
137
- /**
138
- * Options for updating factory settings
139
- */
140
- interface UpdateOptions {
141
- metalness?: number;
142
- roughness?: number;
143
- transparent?: boolean;
144
- defaultOpacity?: number;
145
- edgeColor?: number;
146
- }
147
-
148
- /**
149
- * Factory for creating THREE.js materials with consistent CAD viewer settings.
150
- * Centralizes material creation for testability and consistency.
151
- */
152
- class MaterialFactory {
153
- defaultOpacity: number;
154
- metalness: number;
155
- roughness: number;
156
- edgeColor: number;
157
- transparent: boolean;
158
-
159
- /**
160
- * Create a MaterialFactory instance.
161
- */
162
- constructor(options: MaterialFactoryOptions = {}) {
163
- this.defaultOpacity = options.defaultOpacity ?? 1.0;
164
- this.metalness = options.metalness ?? 0.3;
165
- this.roughness = options.roughness ?? 0.65;
166
- this.edgeColor = options.edgeColor ?? 0x707070;
167
- this.transparent = options.transparent ?? false;
168
- }
169
-
170
- /**
171
- * Create base properties shared by all face materials.
172
- */
173
- private _createBaseProps(alpha: number): BaseProps {
174
- return {
175
- polygonOffset: true,
176
- polygonOffsetFactor: 1.0,
177
- polygonOffsetUnits: 1.0,
178
- transparent: true,
179
- opacity: this.transparent ? this.defaultOpacity * alpha : alpha,
180
- depthWrite: !this.transparent,
181
- depthTest: true,
182
- clipIntersection: false,
183
- };
184
- }
185
-
186
- /**
187
- * Create a standard material for front faces with PBR properties.
188
- */
189
- createFrontFaceMaterial({ color, alpha, visible = true }: FaceMaterialOptions, label?: string): THREE.MeshStandardMaterial {
190
- const material = new THREE.MeshStandardMaterial({
191
- ...this._createBaseProps(alpha),
192
- color: color,
193
- metalness: this.metalness,
194
- roughness: this.roughness,
195
- flatShading: false,
196
- side: THREE.FrontSide,
197
- visible: visible,
198
- });
199
- gpuTracker.track("material", material, label ?? "MeshStandardMaterial (front face)");
200
- return material;
201
- }
202
-
203
- /**
204
- * Create a standard material for back faces with PBR properties.
205
- * Used for polygon rendering where back faces need full shading.
206
- */
207
- createBackFaceStandardMaterial({ color, alpha, polygonOffsetUnits = 2.0, visible = true }: BackFaceMaterialOptions, label?: string): THREE.MeshStandardMaterial {
208
- const material = new THREE.MeshStandardMaterial({
209
- ...this._createBaseProps(alpha),
210
- color: color,
211
- metalness: this.metalness,
212
- roughness: this.roughness,
213
- flatShading: false,
214
- side: THREE.BackSide,
215
- visible: visible,
216
- });
217
- material.polygonOffsetUnits = polygonOffsetUnits;
218
- gpuTracker.track("material", material, label ?? "MeshStandardMaterial (back face)");
219
- return material;
220
- }
221
-
222
- /**
223
- * Create a basic material for back faces (no lighting/PBR).
224
- * Used for shape rendering where back faces are simple fills.
225
- */
226
- createBackFaceBasicMaterial({ color, alpha, polygonOffsetUnits = 2.0, visible = true }: BackFaceMaterialOptions, label?: string): THREE.MeshBasicMaterial {
227
- const material = new THREE.MeshBasicMaterial({
228
- ...this._createBaseProps(alpha),
229
- color: color,
230
- side: THREE.BackSide,
231
- visible: visible,
232
- });
233
- material.polygonOffsetUnits = polygonOffsetUnits;
234
- gpuTracker.track("material", material, label ?? "MeshBasicMaterial (back face)");
235
- return material;
236
- }
237
-
238
- /**
239
- * Create a basic front face material (no PBR, for simple shapes).
240
- */
241
- createBasicFaceMaterial({ color, alpha, visible = true }: FaceMaterialOptions, label?: string): THREE.MeshBasicMaterial {
242
- const material = new THREE.MeshBasicMaterial({
243
- ...this._createBaseProps(alpha),
244
- color: color,
245
- side: THREE.FrontSide,
246
- visible: visible,
247
- });
248
- gpuTracker.track("material", material, label ?? "MeshBasicMaterial (front face)");
249
- return material;
250
- }
251
-
252
- /**
253
- * Create a fat line material (LineMaterial from Three.js examples).
254
- */
255
- createEdgeMaterial({ lineWidth, color, vertexColors = false, visible = true, resolution }: EdgeMaterialOptions, label?: string): LineMaterial {
256
- const material = new LineMaterial({
257
- linewidth: lineWidth,
258
- transparent: true,
259
- depthWrite: !this.transparent,
260
- depthTest: !this.transparent,
261
- clipIntersection: false,
262
- vertexColors: vertexColors, // boolean, not string "VertexColors"
263
- toneMapped: false, // critical for correct vertex colors
264
- });
265
-
266
- if (!vertexColors) {
267
- material.color = new THREE.Color(color ?? this.edgeColor);
268
- }
269
- material.visible = visible;
270
-
271
- if (resolution) {
272
- material.resolution.set(resolution.width, resolution.height);
273
- }
274
-
275
- gpuTracker.track("material", material, label ?? "LineMaterial (edges)");
276
- return material;
277
- }
278
-
279
- /**
280
- * Create a basic line material for simple edges (e.g., polygon outlines).
281
- */
282
- createSimpleEdgeMaterial({ color, visible = true }: SimpleEdgeMaterialOptions, label?: string): THREE.LineBasicMaterial {
283
- const material = new THREE.LineBasicMaterial({
284
- color: color ?? this.edgeColor,
285
- depthWrite: !this.transparent,
286
- depthTest: !this.transparent,
287
- visible: visible,
288
- });
289
- gpuTracker.track("material", material, label ?? "LineBasicMaterial (simple edges)");
290
- return material;
291
- }
292
-
293
- /**
294
- * Create a point material for vertex rendering.
295
- */
296
- createVertexMaterial({ size, color, visible = true }: VertexMaterialOptions, label?: string): THREE.PointsMaterial {
297
- const material = new THREE.PointsMaterial({
298
- color: color ?? this.edgeColor,
299
- sizeAttenuation: false,
300
- size: size,
301
- transparent: true,
302
- clipIntersection: false,
303
- visible: visible,
304
- });
305
- gpuTracker.track("material", material, label ?? "PointsMaterial (vertices)");
306
- return material;
307
- }
308
-
309
- /**
310
- * Create a basic material for texture-mapped surfaces.
311
- */
312
- createTextureMaterial({ texture, visible = true }: TextureMaterialOptions, label?: string): THREE.MeshBasicMaterial {
313
- const material = new THREE.MeshBasicMaterial({
314
- color: "#ffffff",
315
- map: texture,
316
- side: THREE.DoubleSide,
317
- visible: visible,
318
- });
319
- gpuTracker.track("material", material, label ?? "MeshBasicMaterial (textured)");
320
- return material;
321
- }
322
-
323
- /**
324
- * Create a Studio mode material from a resolved MaterialAppearance.
325
- *
326
- * Always creates MeshPhysicalMaterial (except when `unlit: true`, which
327
- * uses MeshBasicMaterial). MeshPhysicalMaterial is a superset of
328
- * MeshStandardMaterial; when advanced features are off (transmission=0,
329
- * clearcoat=0, sheen=0, etc.), the shader compiles to essentially the
330
- * same cost.
331
- *
332
- * @param options - Studio material options
333
- * @param label - Optional label for GPU tracking
334
- * @returns Configured MeshPhysicalMaterial (or MeshBasicMaterial if unlit)
335
- */
336
- async createStudioMaterial(
337
- { materialDef, fallbackColor, fallbackAlpha, textureCache }: StudioMaterialOptions,
338
- label?: string,
339
- ): Promise<THREE.MeshPhysicalMaterial | THREE.MeshBasicMaterial> {
340
- const def = materialDef;
341
- const side = def.doubleSided ? THREE.DoubleSide : THREE.FrontSide;
342
-
343
- // --- Resolve base color and opacity ---
344
- let baseColor: THREE.Color;
345
- let opacity: number;
346
-
347
- if (def.color) {
348
- if (typeof def.color === "string") {
349
- // CSS hex string (e.g. "#55a0e3") — THREE.Color parses as sRGB
350
- baseColor = new THREE.Color(def.color);
351
- opacity = 1.0;
352
- } else {
353
- // sRGB RGBA tuple [R, G, B, A?] (0-1)
354
- baseColor = new THREE.Color().setRGB(
355
- def.color[0], def.color[1], def.color[2],
356
- THREE.SRGBColorSpace,
357
- );
358
- opacity = def.color[3] ?? 1.0;
359
- }
360
- } else {
361
- // Fall back to leaf node's CSS hex color + alpha.
362
- // THREE.Color constructor with a hex number or CSS string produces
363
- // linear-space values in Three.js r152+.
364
- baseColor = new THREE.Color(fallbackColor);
365
- opacity = fallbackAlpha;
366
- }
367
-
368
- // --- Unlit path: MeshBasicMaterial ---
369
- if (def.unlit) {
370
- const basicMat = new THREE.MeshBasicMaterial({
371
- ...this._createBaseProps(opacity),
372
- color: baseColor,
373
- side,
374
- });
375
- // Apply alpha mode to basic material too
376
- this._applyAlphaMode(basicMat, def, opacity);
377
- // Resolve base color texture
378
- if (def.map && textureCache) {
379
- const tex = await textureCache.get(def.map, "baseColorTexture");
380
- if (tex) basicMat.map = tex;
381
- }
382
- gpuTracker.track("material", basicMat, label ?? "MeshBasicMaterial (studio unlit)");
383
- return basicMat;
384
- }
385
-
386
- // --- PBR path: MeshPhysicalMaterial ---
387
- // Studio materials default to opaque (transparent: false). Unlike CAD
388
- // mode, Studio mode has no clipping and doesn't need the global
389
- // transparent:true flag. Only BLEND alpha mode enables transparency.
390
- const isBlend = def.alphaMode === "BLEND" || (!def.alphaMode && opacity < 1.0);
391
- const material = new THREE.MeshPhysicalMaterial({
392
- color: baseColor,
393
- metalness: def.metalness ?? 0.0,
394
- roughness: def.roughness ?? 0.5,
395
- flatShading: false,
396
- side,
397
- transparent: isBlend,
398
- opacity: opacity,
399
- depthWrite: !isBlend,
400
- depthTest: true,
401
- polygonOffset: true,
402
- polygonOffsetFactor: 1.0,
403
- polygonOffsetUnits: 1.0,
404
- });
405
-
406
- // --- Alpha mode ---
407
- this._applyAlphaMode(material, def, opacity);
408
-
409
- // --- Emissive ---
410
- if (def.emissive) {
411
- material.emissive = new THREE.Color(def.emissive[0], def.emissive[1], def.emissive[2]);
412
- }
413
- if (def.emissiveIntensity !== undefined) {
414
- material.emissiveIntensity = def.emissiveIntensity;
415
- }
416
-
417
- // --- Transmission (glass, water) ---
418
- // Transmission uses a separate render target in Three.js and must NOT
419
- // be combined with alpha blending (transparent: true). Override here
420
- // so users don't need to set alphaMode: "OPAQUE" manually.
421
- if (def.transmission !== undefined) {
422
- material.transmission = def.transmission;
423
- if (def.transmission > 0) {
424
- material.transparent = false;
425
- material.opacity = 1.0;
426
- material.depthWrite = true;
427
- }
428
- }
429
-
430
- // --- Clearcoat (car paint, varnish) ---
431
- if (def.clearcoat !== undefined) {
432
- material.clearcoat = def.clearcoat;
433
- }
434
- if (def.clearcoatRoughness !== undefined) {
435
- material.clearcoatRoughness = def.clearcoatRoughness;
436
- }
437
-
438
- // --- Volume (subsurface: jade, wax, skin) ---
439
- if (def.thickness !== undefined) {
440
- material.thickness = def.thickness;
441
- }
442
- if (def.attenuationDistance !== undefined) {
443
- material.attenuationDistance = def.attenuationDistance;
444
- }
445
- if (def.attenuationColor) {
446
- material.attenuationColor = new THREE.Color(
447
- def.attenuationColor[0], def.attenuationColor[1], def.attenuationColor[2],
448
- );
449
- }
450
-
451
- // --- IOR ---
452
- if (def.ior !== undefined) {
453
- material.ior = def.ior;
454
- }
455
-
456
- // --- Specular ---
457
- if (def.specularIntensity !== undefined) {
458
- material.specularIntensity = def.specularIntensity;
459
- }
460
- if (def.specularColor) {
461
- material.specularColor = new THREE.Color(
462
- def.specularColor[0], def.specularColor[1], def.specularColor[2],
463
- );
464
- }
465
-
466
- // --- Sheen (fabric, velvet) ---
467
- // sheen > 0 enables the sheen layer in Three.js
468
- if (def.sheen !== undefined && def.sheen > 0) {
469
- material.sheen = def.sheen;
470
- if (def.sheenColor) {
471
- material.sheenColor = new THREE.Color(
472
- def.sheenColor[0], def.sheenColor[1], def.sheenColor[2],
473
- );
474
- }
475
- if (def.sheenRoughness !== undefined) {
476
- material.sheenRoughness = def.sheenRoughness;
477
- }
478
- }
479
-
480
- // --- Anisotropy (brushed metal) ---
481
- if (def.anisotropy !== undefined && def.anisotropy > 0) {
482
- material.anisotropy = def.anisotropy;
483
- if (def.anisotropyRotation !== undefined) {
484
- material.anisotropyRotation = def.anisotropyRotation;
485
- }
486
- }
487
-
488
- // --- Textures ---
489
- // Resolve all texture references via TextureCache.
490
- // The TextureCache determines colorSpace internally from the texture role name.
491
- if (textureCache) {
492
- await this._applyStudioTextures(material, def, textureCache);
493
- }
494
-
495
- gpuTracker.track("material", material, label ?? "MeshPhysicalMaterial (studio)");
496
- return material;
497
- }
498
-
499
- /**
500
- * Create a Studio mode material from a threejs-materials format entry.
501
- *
502
- * threejs-materials `properties` uses simplified property names (e.g., "color",
503
- * "roughness", "normal") where each entry has an optional `value` (scalar or
504
- * [r,g,b] array in **linear RGB**) and/or `texture` (inline data URI).
505
- *
506
- * @param values - Scalar PBR values from threejs-materials
507
- * @param textures - Texture map references from threejs-materials
508
- * @param textureRepeat - Optional [u, v] texture tiling applied to all loaded textures
509
- * @param textureCache - TextureCache for resolving data URI textures
510
- * @param label - Optional label for GPU tracking
511
- * @returns Configured MeshPhysicalMaterial
512
- */
513
- async createStudioMaterialFromMaterialX(
514
- values: Record<string, unknown>,
515
- textures: Record<string, string>,
516
- textureRepeat: [number, number] | undefined,
517
- textureCache: TextureCacheInterface | null,
518
- label?: string,
519
- ): Promise<THREE.MeshPhysicalMaterial> {
520
- // --- Build material options from scalar values ---
521
- const matOptions: Record<string, unknown> = {
522
- flatShading: false,
523
- side: THREE.FrontSide,
524
- polygonOffset: true,
525
- polygonOffsetFactor: 1.0,
526
- polygonOffsetUnits: 1.0,
527
- depthTest: true,
528
- };
529
-
530
- // Warn once if displacement data is present (not supported in Studio)
531
- if (textures.displacement || values.displacementScale !== undefined) {
532
- logger.warn("Displacement not supported by the Studio");
533
- }
534
-
535
- for (const [key, value] of Object.entries(values)) {
536
- // Skip displacement properties (not supported, would waste GPU memory)
537
- if (key === "displacement" || key === "displacementScale" || key === "displacementBias") continue;
538
-
539
- // Color arrays → THREE.Color (already linear, no sRGB conversion)
540
- if (COLOR_ARRAY_KEYS.has(key) && Array.isArray(value)) {
541
- const [r, g, b] = value as number[];
542
- matOptions[key] = new THREE.Color(r, g, b);
543
- } else if ((key === "normalScale" || key === "clearcoatNormalScale") && Array.isArray(value)) {
544
- matOptions[key] = new THREE.Vector2(value[0], value[1]);
545
- } else if (key === "iridescenceThicknessRange" && Array.isArray(value)) {
546
- matOptions[key] = value;
547
- } else {
548
- matOptions[key] = value;
549
- }
550
- }
551
-
552
- // --- Handle transmission ---
553
- const transmissionVal = values.transmission;
554
- const opacityVal = values.opacity;
555
- const transparentVal = values.transparent;
556
- if (typeof transmissionVal === "number" && transmissionVal > 0) {
557
- matOptions.transparent = false;
558
- matOptions.opacity = 1.0;
559
- matOptions.depthWrite = true;
560
- } else if (transparentVal === true || (typeof opacityVal === "number" && opacityVal < 1.0)) {
561
- matOptions.transparent = true;
562
- matOptions.depthWrite = false;
563
- } else {
564
- matOptions.transparent = false;
565
- matOptions.depthWrite = true;
566
- }
567
-
568
- const material = new THREE.MeshPhysicalMaterial(matOptions);
569
-
570
- // --- Resolve textures ---
571
- let hasTextures = false;
572
- if (textureCache) {
573
- for (const [key, textureRef] of Object.entries(textures)) {
574
- const mapName = PROPERTY_TO_MAP[key];
575
- if (!mapName) continue;
576
-
577
- // Determine color space from the Three.js map property name.
578
- // TextureCache.get() expects a role name to decide colorSpace.
579
- // Bridge from Three.js map name → colorSpace → a proxy role name.
580
- const colorSpace = getColorSpaceForMap(mapName);
581
- const roleForCache = colorSpace === THREE.SRGBColorSpace
582
- ? "baseColorTexture"
583
- : "normalTexture";
584
- const tex = await textureCache.get(textureRef, roleForCache);
585
- if (tex) {
586
- if (textureRepeat) {
587
- tex.repeat.set(textureRepeat[0], textureRepeat[1]);
588
- }
589
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
590
- (material as any)[mapName] = tex;
591
- hasTextures = true;
592
- }
593
- }
594
- }
595
-
596
- // Enable alpha cutout when an alphaMap is present
597
- if (material.alphaMap) {
598
- material.alphaTest = 0.5;
599
- material.side = THREE.DoubleSide;
600
- }
601
-
602
- // Force shader recompile if textures were assigned post-construction
603
- if (hasTextures) {
604
- material.needsUpdate = true;
605
- }
606
-
607
- gpuTracker.track("material", material, label ?? "MeshPhysicalMaterial (threejs-materials)");
608
- return material;
609
- }
610
-
611
- /**
612
- * Apply alpha mode settings to a material.
613
- *
614
- * - OPAQUE: fully opaque, no transparency
615
- * - MASK: alpha testing with cutoff threshold
616
- * - BLEND: standard alpha blending
617
- * - Default (no alphaMode): transparent: true (current viewer behavior)
618
- */
619
- private _applyAlphaMode(
620
- material: THREE.Material & { opacity: number },
621
- def: MaterialAppearance,
622
- opacity: number,
623
- ): void {
624
- switch (def.alphaMode) {
625
- case "OPAQUE":
626
- material.transparent = false;
627
- material.opacity = 1.0;
628
- material.depthWrite = true;
629
- break;
630
- case "MASK":
631
- material.transparent = false;
632
- material.alphaTest = def.alphaCutoff ?? 0.5;
633
- material.opacity = opacity;
634
- material.depthWrite = true;
635
- break;
636
- case "BLEND":
637
- material.transparent = true;
638
- material.opacity = opacity;
639
- material.depthWrite = false;
640
- break;
641
- // default: no alphaMode set -- keep _createBaseProps defaults (transparent: true)
642
- }
643
- }
644
-
645
- /**
646
- * Resolve and apply texture references from a MaterialAppearance onto a
647
- * MeshPhysicalMaterial via the TextureCache.
648
- *
649
- * Color-data textures (base color, emissive, sheen color, specular color)
650
- * are requested with SRGBColorSpace. All other textures (normal, metallic-
651
- * roughness, occlusion, roughness maps, transmission, thickness) are
652
- * requested with LinearSRGBColorSpace (the default).
653
- *
654
- * The metallicRoughnessTexture is a single combined texture where
655
- * B channel = metalness and G channel = roughness. It is assigned to
656
- * both metalnessMap and roughnessMap on the material.
657
- */
658
- private async _applyStudioTextures(
659
- material: THREE.MeshPhysicalMaterial,
660
- def: MaterialAppearance,
661
- textureCache: TextureCacheInterface,
662
- ): Promise<void> {
663
- // Helper to resolve a texture reference. The TextureCache determines
664
- // colorSpace internally from the textureRole name (sRGB for color-data
665
- // textures like baseColorTexture, linear for non-color data like normalTexture).
666
- const resolve = async (key: string | undefined, textureRole: string): Promise<THREE.Texture | null> => {
667
- if (!key) return null;
668
- return textureCache.get(key, textureRole);
669
- };
670
-
671
- // --- sRGB color-data textures ---
672
- const baseColorTex = await resolve(def.map, "baseColorTexture");
673
- if (baseColorTex) material.map = baseColorTex;
674
-
675
- const emissiveTex = await resolve(def.emissiveMap, "emissiveTexture");
676
- if (emissiveTex) material.emissiveMap = emissiveTex;
677
-
678
- const sheenColorTex = await resolve(def.sheenColorMap, "sheenColorTexture");
679
- if (sheenColorTex) material.sheenColorMap = sheenColorTex;
680
-
681
- const specularColorTex = await resolve(def.specularColorMap, "specularColorTexture");
682
- if (specularColorTex) material.specularColorMap = specularColorTex;
683
-
684
- // --- Linear non-color data textures ---
685
- const normalTex = await resolve(def.normalMap, "normalTexture");
686
- if (normalTex) material.normalMap = normalTex;
687
-
688
- const occlusionTex = await resolve(def.aoMap, "occlusionTexture");
689
- if (occlusionTex) material.aoMap = occlusionTex;
690
-
691
- const metalnessTex = await resolve(def.metalnessMap, "metallicRoughnessTexture");
692
- if (metalnessTex) material.metalnessMap = metalnessTex;
693
-
694
- const roughnessTex = await resolve(def.roughnessMap, "metallicRoughnessTexture");
695
- if (roughnessTex) material.roughnessMap = roughnessTex;
696
-
697
- const transmissionTex = await resolve(def.transmissionMap, "transmissionTexture");
698
- if (transmissionTex) material.transmissionMap = transmissionTex;
699
-
700
- const thicknessTex = await resolve(def.thicknessMap, "thicknessTexture");
701
- if (thicknessTex) material.thicknessMap = thicknessTex;
702
-
703
- const clearcoatTex = await resolve(def.clearcoatMap, "clearcoatTexture");
704
- if (clearcoatTex) material.clearcoatMap = clearcoatTex;
705
-
706
- const clearcoatRoughnessTex = await resolve(def.clearcoatRoughnessMap, "clearcoatRoughnessTexture");
707
- if (clearcoatRoughnessTex) material.clearcoatRoughnessMap = clearcoatRoughnessTex;
708
-
709
- const clearcoatNormalTex = await resolve(def.clearcoatNormalMap, "clearcoatNormalTexture");
710
- if (clearcoatNormalTex) material.clearcoatNormalMap = clearcoatNormalTex;
711
-
712
- const specularIntensityTex = await resolve(def.specularIntensityMap, "specularIntensityTexture");
713
- if (specularIntensityTex) material.specularIntensityMap = specularIntensityTex;
714
-
715
- const sheenRoughnessTex = await resolve(def.sheenRoughnessMap, "sheenRoughnessTexture");
716
- if (sheenRoughnessTex) material.sheenRoughnessMap = sheenRoughnessTex;
717
-
718
- const anisotropyTex = await resolve(def.anisotropyMap, "anisotropyTexture");
719
- if (anisotropyTex) material.anisotropyMap = anisotropyTex;
720
- }
721
-
722
- /**
723
- * Update global settings.
724
- */
725
- update(options: UpdateOptions): void {
726
- if (options.metalness !== undefined) this.metalness = options.metalness;
727
- if (options.roughness !== undefined) this.roughness = options.roughness;
728
- if (options.transparent !== undefined) this.transparent = options.transparent;
729
- if (options.defaultOpacity !== undefined) this.defaultOpacity = options.defaultOpacity;
730
- if (options.edgeColor !== undefined) this.edgeColor = options.edgeColor;
731
- }
732
- }
733
-
734
- export { MaterialFactory };
735
- export type { MaterialFactoryOptions, UpdateOptions, StudioMaterialOptions, TextureCacheInterface };