@inweb/viewer-three 27.4.2 → 27.4.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "27.4.2",
3
+ "version": "27.4.4",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,10 +35,10 @@
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~27.4.2",
39
- "@inweb/eventemitter2": "~27.4.2",
40
- "@inweb/markup": "~27.4.2",
41
- "@inweb/viewer-core": "~27.4.2"
38
+ "@inweb/client": "~27.4.4",
39
+ "@inweb/eventemitter2": "~27.4.4",
40
+ "@inweb/markup": "~27.4.4",
41
+ "@inweb/viewer-core": "~27.4.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@streamparser/json": "^0.0.22",
@@ -19,7 +19,6 @@ import {
19
19
  MathUtils,
20
20
  PerspectiveCamera,
21
21
  OrthographicCamera,
22
- DoubleSide,
23
22
  NormalBlending,
24
23
  BufferAttribute,
25
24
  LineBasicMaterial,
@@ -141,13 +140,6 @@ export class DynamicGltfLoader {
141
140
  this.visibilityMaterials = new Set(); // Keep track of materials to update uniforms
142
141
  }
143
142
 
144
- // layout (1 matrix = 4 pixels)
145
- // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
146
- // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8)
147
- // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16)
148
- // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32)
149
- // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64)
150
-
151
143
  createDummyTexture() {
152
144
  // Create 1x1 dummy texture with identity matrix to prevent shader crash/black screen
153
145
  const data = new Float32Array(16); // 4x4 matrix
@@ -226,8 +218,6 @@ export class DynamicGltfLoader {
226
218
 
227
219
  updateMaterialUniforms() {
228
220
  // Only update if the texture actually changed
229
- // In three.js, setting `.value = this.transformTexture` triggers uniformity checks
230
- // We can avoid looping over all materials if the texture reference hasn't changed
231
221
  if (
232
222
  this._lastTransformTexture === this.transformTexture &&
233
223
  this._lastTransformTextureSize === this.transformTextureSize
@@ -272,7 +262,7 @@ export class DynamicGltfLoader {
272
262
 
273
263
  console.log(`Available memory set to ${Math.round(memoryLimit / (1024 * 1024 * 1024))}GB`);
274
264
  } catch (error) {
275
- console.warn("Error detecting available memory:", error);
265
+ console.warn("DynamicLoader: Error detecting available memory:", error);
276
266
  }
277
267
 
278
268
  return memoryLimit;
@@ -470,7 +460,7 @@ export class DynamicGltfLoader {
470
460
  if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
471
461
  }
472
462
  } catch (exp) {
473
- console.error("Error adding material to uniqueMaterialIds", exp);
463
+ console.warn("DynamicLoader: Error collecting material stats", exp);
474
464
  }
475
465
  }
476
466
  }
@@ -492,7 +482,7 @@ export class DynamicGltfLoader {
492
482
  this._webglInfoCache = { renderer: null, vendor: null };
493
483
  }
494
484
  } catch (e) {
495
- console.error("Error getting webgl info", e);
485
+ console.warn("DynamicLoader: Error getting webgl info", e);
496
486
  this._webglInfoCache = { renderer: null, vendor: null };
497
487
  }
498
488
  }
@@ -778,7 +768,7 @@ export class DynamicGltfLoader {
778
768
  return;
779
769
  }
780
770
 
781
- console.error(`Error loading node ${nodeId}:`, error);
771
+ console.warn(`DynamicLoader: Error loading node ${nodeId}:`, error);
782
772
  }
783
773
  }
784
774
 
@@ -842,7 +832,7 @@ export class DynamicGltfLoader {
842
832
  await structure.loadTextures();
843
833
  await structure.loadMaterials();
844
834
  } catch (error) {
845
- console.error("Error loading materials:", error);
835
+ console.error("DynamicLoader: Error loading materials:", error);
846
836
  throw error;
847
837
  }
848
838
  }
@@ -852,7 +842,7 @@ export class DynamicGltfLoader {
852
842
 
853
843
  async processSceneHierarchy() {
854
844
  if (this.structures.length === 0) {
855
- throw new Error("No GLTF structures loaded");
845
+ throw new Error("DynamicLoader: No GLTF structures loaded");
856
846
  }
857
847
 
858
848
  this.nodesToLoad = [];
@@ -863,7 +853,7 @@ export class DynamicGltfLoader {
863
853
  const gltf = structure.getJson();
864
854
 
865
855
  if (!gltf.scenes || !gltf.scenes.length) {
866
- console.warn("No scenes found in GLTF structure");
856
+ console.warn("DynamicLoader: No scenes found in GLTF structure");
867
857
  continue;
868
858
  }
869
859
 
@@ -1152,7 +1142,6 @@ export class DynamicGltfLoader {
1152
1142
  color: 0x808080,
1153
1143
  specular: 0x222222,
1154
1144
  shininess: 10,
1155
- side: DoubleSide,
1156
1145
  });
1157
1146
  }
1158
1147
  }
@@ -1383,7 +1372,6 @@ export class DynamicGltfLoader {
1383
1372
 
1384
1373
  //console.log("!vertex", shader.vertexShader);
1385
1374
  };
1386
-
1387
1375
  // Ensure the material recompiles to pick up changes
1388
1376
  material.needsUpdate = true;
1389
1377
  return material;
@@ -1653,7 +1641,6 @@ export class DynamicGltfLoader {
1653
1641
  // Clear previous optimization data
1654
1642
  this.objectIdToIndex.clear();
1655
1643
  this.maxObjectId = 0;
1656
-
1657
1644
  const structureGroups = new Map();
1658
1645
 
1659
1646
  this.dispatchEvent("optimizationprogress", {
@@ -1709,7 +1696,6 @@ export class DynamicGltfLoader {
1709
1696
  // This ensures that as we create merged objects, the texture is large enough
1710
1697
  // and populated with identity matrices, so objects don't disappear (scale 0).
1711
1698
  if (totalObjectsToMerge > 0) {
1712
- console.log(`Pre-allocating transform texture for ${totalObjectsToMerge} objects`);
1713
1699
  this.maxObjectId = totalObjectsToMerge;
1714
1700
  this.initTransformTexture();
1715
1701
  this.initializeObjectVisibility();
@@ -1776,7 +1762,6 @@ export class DynamicGltfLoader {
1776
1762
  });
1777
1763
 
1778
1764
  // Texture and visibility initialized at start of optimization
1779
-
1780
1765
  console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
1781
1766
 
1782
1767
  this.dispatchEvent("optimizationprogress", {
@@ -1793,7 +1778,7 @@ export class DynamicGltfLoader {
1793
1778
  let processedGroups = 0;
1794
1779
  for (const group of materialGroups) {
1795
1780
  if (!group.material) {
1796
- console.warn("Skipping mesh group with null material");
1781
+ console.warn("DynamicLoader: Skipping mesh group with null material");
1797
1782
  continue;
1798
1783
  }
1799
1784
 
@@ -1908,7 +1893,7 @@ export class DynamicGltfLoader {
1908
1893
  await this.yieldToUI();
1909
1894
  }
1910
1895
  } catch (error) {
1911
- console.error("Failed to merge meshes for material:", error);
1896
+ console.warn("DynamicLoader: Failed to merge meshes for material:", error);
1912
1897
  group.objects.forEach((mesh) => {
1913
1898
  mesh.visible = true;
1914
1899
  });
@@ -1922,7 +1907,7 @@ export class DynamicGltfLoader {
1922
1907
  if (group.objects.length === 0) continue;
1923
1908
 
1924
1909
  if (!group.material) {
1925
- console.warn("Skipping line group with null material");
1910
+ console.warn("DynamicLoader: Skipping line group with null material");
1926
1911
  continue;
1927
1912
  }
1928
1913
 
@@ -2058,7 +2043,7 @@ export class DynamicGltfLoader {
2058
2043
  let processedGroups = 0;
2059
2044
  for (const group of materialGroups) {
2060
2045
  if (!group.material) {
2061
- console.warn("Skipping line segment group with null material");
2046
+ console.warn("DynamicLoader: Skipping line segment group with null material");
2062
2047
  continue;
2063
2048
  }
2064
2049
 
@@ -2171,7 +2156,7 @@ export class DynamicGltfLoader {
2171
2156
  await this.yieldToUI();
2172
2157
  }
2173
2158
  } catch (error) {
2174
- console.warn("Failed to merge line segments for material:", error);
2159
+ console.warn("DynamicLoader: Failed to merge line segments for material:", error);
2175
2160
  group.objects.forEach((line) => {
2176
2161
  line.visible = true;
2177
2162
  });
@@ -2183,7 +2168,7 @@ export class DynamicGltfLoader {
2183
2168
  let processedGroups = 0;
2184
2169
  for (const group of materialGroups) {
2185
2170
  if (!group.material) {
2186
- console.warn("Skipping points group with null material");
2171
+ console.warn("DynamicLoader: Skipping points group with null material");
2187
2172
  continue;
2188
2173
  }
2189
2174
 
@@ -2221,7 +2206,6 @@ export class DynamicGltfLoader {
2221
2206
  const totalVertices = mergedGeometry.attributes.position.count;
2222
2207
  const objectIds = new Float32Array(totalVertices);
2223
2208
  let vertexOffset = 0;
2224
-
2225
2209
  group.objects.forEach((points) => {
2226
2210
  const handle = points.userData.handle;
2227
2211
  if (!this.objectIdToIndex.has(handle)) {
@@ -2275,7 +2259,7 @@ export class DynamicGltfLoader {
2275
2259
  await this.yieldToUI();
2276
2260
  }
2277
2261
  } catch (error) {
2278
- console.warn("Failed to merge points for material:", error);
2262
+ console.warn("DynamicLoader: Failed to merge points for material:", error);
2279
2263
  group.objects.forEach((points) => {
2280
2264
  points.visible = true;
2281
2265
  });
@@ -2326,7 +2310,6 @@ export class DynamicGltfLoader {
2326
2310
  });
2327
2311
 
2328
2312
  const finalGeometry = mergeGeometries(geometriesWithIndex, false);
2329
-
2330
2313
  // Add objectId attribute
2331
2314
  const totalVertices = finalGeometry.attributes.position.count;
2332
2315
  const objectIds = new Float32Array(totalVertices);
@@ -2373,7 +2356,7 @@ export class DynamicGltfLoader {
2373
2356
  obj.geometry.dispose();
2374
2357
  });
2375
2358
  } catch (error) {
2376
- console.error("Failed to merge geometries:", error);
2359
+ console.warn("DynamicLoader: Failed to merge geometries:", error);
2377
2360
  lineSegmentsArray.forEach((obj) => {
2378
2361
  obj.visible = true;
2379
2362
  rootGroup.add(obj);
@@ -2501,12 +2484,12 @@ export class DynamicGltfLoader {
2501
2484
 
2502
2485
  applyObjectTransforms(objectTransformMap) {
2503
2486
  if (this.mergedObjectMap.size === 0) {
2504
- console.warn("No merged objects to transform");
2487
+ console.warn("DynamicLoader: No merged objects to transform");
2505
2488
  return;
2506
2489
  }
2507
2490
 
2508
2491
  if (!this.transformData) {
2509
- console.warn("Transform texture not initialized");
2492
+ console.warn("DynamicLoader: Transform texture not initialized");
2510
2493
  return;
2511
2494
  }
2512
2495
 
@@ -2609,15 +2592,9 @@ export class DynamicGltfLoader {
2609
2592
 
2610
2593
  // Convert offset from structure-root-local to world space,
2611
2594
  // then from world space to parent-local space.
2612
- // The structure root's matrixWorld converts local→world.
2613
- // The parent's inverse matrixWorld converts world→parent-local.
2614
- // But we only need to transform a direction/offset (not a point),
2615
- // so we transform two points and subtract.
2616
2595
  if (object.userData.structureId) {
2617
2596
  const rootGroup = this.structureRoots.get(object.userData.structureId);
2618
2597
  if (rootGroup && object.parent && object.parent !== rootGroup) {
2619
- // Transform offset: structureRoot-local → world → parent-local
2620
- // For a vector (not point): apply rotation/scale only
2621
2598
  const origin = new Vector3(0, 0, 0);
2622
2599
  origin.applyMatrix4(rootGroup.matrixWorld);
2623
2600
  _offset.applyMatrix4(rootGroup.matrixWorld);
@@ -2633,7 +2610,6 @@ export class DynamicGltfLoader {
2633
2610
 
2634
2611
  object.position.add(_offset);
2635
2612
 
2636
- // Also update highlight wireframe if it exists right now
2637
2613
  if (object.userData.highlight) {
2638
2614
  object.userData.highlight.position.copy(object.position);
2639
2615
  }
@@ -2702,6 +2678,7 @@ export class DynamicGltfLoader {
2702
2678
  // Cache it
2703
2679
  obj.userData.explodeVector = explodeVector;
2704
2680
  }
2681
+
2705
2682
  const explodeVector = obj.userData.explodeVector;
2706
2683
  const distance = explodeVector.length();
2707
2684
 
@@ -2772,7 +2749,7 @@ export class DynamicGltfLoader {
2772
2749
 
2773
2750
  syncHiddenObjects() {
2774
2751
  if (this.mergedObjectMap.size === 0) {
2775
- console.log("No merged objects to sync");
2752
+ console.warn("DynamicLoader: No merged objects to sync");
2776
2753
  return;
2777
2754
  }
2778
2755
 
@@ -4,10 +4,36 @@ import {
4
4
  Color,
5
5
  DoubleSide,
6
6
  MeshPhongMaterial,
7
+ MeshStandardMaterial,
7
8
  PointsMaterial,
8
9
  LineBasicMaterial,
10
+ RepeatWrapping,
11
+ ClampToEdgeWrapping,
12
+ MirroredRepeatWrapping,
13
+ NearestFilter,
14
+ NearestMipmapNearestFilter,
15
+ NearestMipmapLinearFilter,
16
+ LinearFilter,
17
+ LinearMipmapNearestFilter,
18
+ LinearMipmapLinearFilter,
19
+ SRGBColorSpace,
9
20
  } from "three";
10
21
 
22
+ const GL_TO_THREE_WRAP = {
23
+ 33071: ClampToEdgeWrapping,
24
+ 33648: MirroredRepeatWrapping,
25
+ 10497: RepeatWrapping,
26
+ };
27
+
28
+ const GL_TO_THREE_FILTER = {
29
+ 9728: NearestFilter,
30
+ 9729: LinearFilter,
31
+ 9984: NearestMipmapNearestFilter,
32
+ 9985: LinearMipmapNearestFilter,
33
+ 9986: NearestMipmapLinearFilter,
34
+ 9987: LinearMipmapLinearFilter,
35
+ };
36
+
11
37
  export const GL_COMPONENT_TYPES = {
12
38
  5120: Int8Array,
13
39
  5121: Uint8Array,
@@ -54,6 +80,7 @@ export class GltfStructure {
54
80
  this.pendingRequests = [];
55
81
  this.batchTimeout = null;
56
82
  this.textureLoader = new TextureLoader();
83
+ this.textureLoader.crossOrigin = "anonymous";
57
84
  this.materials = new Map();
58
85
  this.textureCache = new Map();
59
86
  this.materialCache = new Map();
@@ -67,7 +94,7 @@ export class GltfStructure {
67
94
  const json = await this.loadController.loadJson();
68
95
 
69
96
  if (json.asset === undefined || json.asset.version[0] < 2) {
70
- throw new Error("GltfStructure: Unsupported asset. glTF versions >=2.0 are supported.");
97
+ throw new Error("DynamicLoader: Unsupported asset. glTF versions >=2.0 are supported.");
71
98
  }
72
99
 
73
100
  this.json = json;
@@ -101,7 +128,9 @@ export class GltfStructure {
101
128
  scheduleRequest(request) {
102
129
  return new Promise((resolve, reject) => {
103
130
  if (this.loadingAborted) {
104
- reject(this.criticalError || new Error("Structure loading has been aborted due to critical error"));
131
+ reject(
132
+ this.criticalError || new Error("DynamicLoader: Structure loading has been aborted due to critical error")
133
+ );
105
134
  return;
106
135
  }
107
136
  this.pendingRequests.push({
@@ -150,7 +179,7 @@ export class GltfStructure {
150
179
  }
151
180
 
152
181
  console.error(
153
- `❌ Critical error for structure "${this.id}". All further loading aborted.`,
182
+ `DynamicLoader: Critical error for structure "${this.id}". All further loading aborted.`,
154
183
  `\n Error: ${error.message || error}`,
155
184
  `\n Rejected ${requests.length} pending chunk requests.`
156
185
  );
@@ -224,7 +253,7 @@ export class GltfStructure {
224
253
  const promises = finalRanges.map(async (range, index) => {
225
254
  if (this.loadingAborted) {
226
255
  for (const req of range.requests) {
227
- req._reject(this.criticalError || new Error("Structure loading aborted"));
256
+ req._reject(this.criticalError || new Error("DynamicLoader: Structure loading aborted"));
228
257
  }
229
258
  return;
230
259
  }
@@ -251,7 +280,10 @@ export class GltfStructure {
251
280
  if (this.isCriticalHttpError(error)) {
252
281
  this.abortLoading(error);
253
282
  } else {
254
- console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
283
+ console.warn(
284
+ `DynamicLoader: Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`,
285
+ error
286
+ );
255
287
  }
256
288
  } finally {
257
289
  this.loader.releaseChunkSlot();
@@ -274,7 +306,7 @@ export class GltfStructure {
274
306
  createTypedArray(buffer, offset, length, componentType) {
275
307
  try {
276
308
  if (!buffer || !(buffer instanceof ArrayBuffer)) {
277
- throw new Error("Invalid buffer");
309
+ throw new Error("DynamicLoader: Invalid buffer");
278
310
  }
279
311
 
280
312
  let elementSize;
@@ -292,23 +324,25 @@ export class GltfStructure {
292
324
  elementSize = 4;
293
325
  break; // UNSIGNED_INT, FLOAT
294
326
  default:
295
- throw new Error(`Unsupported component type: ${componentType}`);
327
+ throw new Error(`DynamicLoader: Unsupported component type: ${componentType}`);
296
328
  }
297
329
 
298
330
  const numElements = length / elementSize;
299
331
  if (!Number.isInteger(numElements)) {
300
- throw new Error(`Invalid length ${length} for component type ${componentType}`);
332
+ throw new Error(`DynamicLoader: Invalid length ${length} for component type ${componentType}`);
301
333
  }
302
334
 
303
335
  if (length > buffer.byteLength) {
304
- throw new Error(`Buffer too small: need ${length} bytes, but buffer is ${buffer.byteLength} bytes`);
336
+ throw new Error(
337
+ `DynamicLoader: Buffer too small: need ${length} bytes, but buffer is ${buffer.byteLength} bytes`
338
+ );
305
339
  }
306
340
 
307
341
  const ArrayType = GL_COMPONENT_TYPES[componentType];
308
342
  return new ArrayType(buffer, offset, numElements);
309
343
  } catch (error) {
310
344
  if (error.name !== "AbortError") {
311
- console.error("Error creating typed array:", {
345
+ console.warn("DynamicLoader: Error creating typed array:", {
312
346
  bufferSize: buffer?.byteLength,
313
347
  offset,
314
348
  length,
@@ -322,7 +356,7 @@ export class GltfStructure {
322
356
 
323
357
  async createBufferAttribute(accessorIndex) {
324
358
  if (!this.json) {
325
- throw new Error("No GLTF structure loaded");
359
+ throw new Error("DynamicLoader: No GLTF structure loaded");
326
360
  }
327
361
 
328
362
  const gltf = this.json;
@@ -345,7 +379,7 @@ export class GltfStructure {
345
379
  return attribute;
346
380
  } catch (error) {
347
381
  if (error.name !== "AbortError") {
348
- console.error("Error creating buffer attribute:", {
382
+ console.warn("DynamicLoader: Error creating buffer attribute:", {
349
383
  error,
350
384
  accessor,
351
385
  bufferView,
@@ -368,7 +402,7 @@ export class GltfStructure {
368
402
  case 5126: // FLOAT
369
403
  return 4;
370
404
  default:
371
- throw new Error(`Unknown component type: ${componentType}`);
405
+ throw new Error(`DynamicLoader: Unknown component type: ${componentType}`);
372
406
  }
373
407
  }
374
408
 
@@ -389,7 +423,7 @@ export class GltfStructure {
389
423
  case "MAT4":
390
424
  return 16;
391
425
  default:
392
- throw new Error(`Unknown type: ${type}`);
426
+ throw new Error(`DynamicLoader: Unknown type: ${type}`);
393
427
  }
394
428
  }
395
429
 
@@ -397,27 +431,52 @@ export class GltfStructure {
397
431
  if (!this.json.textures) return;
398
432
 
399
433
  const loadTexture = async (imageIndex) => {
400
- const image = this.json.images[imageIndex];
401
-
402
- if (image.uri) {
403
- const fullUrl = await this.loadController.resolveURL(image.uri);
404
- return this.textureLoader.loadAsync(fullUrl);
405
- } else if (image.bufferView !== undefined) {
406
- const bufferView = this.json.bufferViews[image.bufferView];
407
- const array = await this.getBufferView(bufferView.byteOffset || 0, bufferView.byteLength, 5121);
408
- const blob = new Blob([array], { type: image.mimeType });
409
- const url = URL.createObjectURL(blob);
410
- const texture = await this.textureLoader.loadAsync(url);
411
- URL.revokeObjectURL(url);
412
- texture.flipY = false;
413
- return texture;
434
+ let textureName = `index_${imageIndex}`;
435
+ try {
436
+ const image = this.json.images[imageIndex];
437
+ textureName = image.uri || image.name || textureName;
438
+
439
+ if (image.uri) {
440
+ const fullUrl = await this.loadController.resolveURL(image.uri);
441
+ return await this.textureLoader.loadAsync(fullUrl);
442
+ } else if (image.bufferView !== undefined) {
443
+ const bufferView = this.json.bufferViews[image.bufferView];
444
+ const array = await this.getBufferView(bufferView.byteOffset || 0, bufferView.byteLength, 5121);
445
+ const blob = new Blob([array], { type: image.mimeType });
446
+ const url = URL.createObjectURL(blob);
447
+ const texture = await this.textureLoader.loadAsync(url);
448
+ URL.revokeObjectURL(url);
449
+ return texture;
450
+ }
451
+ } catch {
452
+ console.warn(`DynamicLoader: Error loading texture ${textureName}`);
414
453
  }
415
454
  };
416
455
 
417
456
  const texturePromises = [];
418
457
  for (let i = 0; i < this.json.textures.length; i++) {
419
458
  texturePromises.push(
420
- loadTexture(this.json.textures[i].source).then((texture) => this.textureCache.set(i, texture))
459
+ loadTexture(this.json.textures[i].source).then((texture) => {
460
+ if (texture) {
461
+ // Apply sampler settings, mapping WebGL constants to Three.js constants
462
+ const samplerDef =
463
+ this.json.textures[i].sampler !== undefined && this.json.samplers
464
+ ? this.json.samplers[this.json.textures[i].sampler]
465
+ : {};
466
+
467
+ texture.magFilter = GL_TO_THREE_FILTER[samplerDef.magFilter] || LinearFilter;
468
+ texture.minFilter = GL_TO_THREE_FILTER[samplerDef.minFilter] || LinearMipmapLinearFilter;
469
+
470
+ // glTF spec: default wrap is REPEAT (10497) when not specified
471
+ texture.wrapS = GL_TO_THREE_WRAP[samplerDef.wrapS] || RepeatWrapping;
472
+ texture.wrapT = GL_TO_THREE_WRAP[samplerDef.wrapT] || RepeatWrapping;
473
+
474
+ // glTF textures use non-flipped Y convention
475
+ texture.flipY = false;
476
+ texture.needsUpdate = true;
477
+ }
478
+ this.textureCache.set(i, texture);
479
+ })
421
480
  );
422
481
  }
423
482
  await Promise.all(texturePromises);
@@ -455,8 +514,17 @@ export class GltfStructure {
455
514
  params.opacity = pbr.baseColorFactor[3];
456
515
  if (params.opacity < 1.0) params.transparent = true;
457
516
  }
458
- if (pbr.baseColorTexture) {
459
- params.map = this.textureCache.get(pbr.baseColorTexture.index);
517
+
518
+ if (pbr.baseColorTexture !== undefined) {
519
+ const texture = this.textureCache.get(pbr.baseColorTexture.index);
520
+ if (texture) {
521
+ params.map = texture;
522
+ // Set color to white if map is present and baseColorFactor is not,
523
+ // or else texture will be tinted with default color
524
+ if (!pbr.baseColorFactor) {
525
+ params.color = new Color(0xffffff);
526
+ }
527
+ }
460
528
  }
461
529
  }
462
530
 
@@ -491,11 +559,73 @@ export class GltfStructure {
491
559
  params.polygonOffsetFactor = 1;
492
560
  params.polygonOffsetUnits = 1;
493
561
 
494
- if (materialDef.normalTexture) {
495
- params.normalMap = this.textureCache.get(materialDef.normalTexture.index);
562
+ // Ensure color is white if only map is present so it renders the texture properly
563
+ if (params.map && !materialDef.pbrMetallicRoughness?.baseColorFactor) {
564
+ params.color = new Color(0xffffff);
496
565
  }
497
566
 
498
- material = new MeshPhongMaterial(params);
567
+ if (materialDef.normalTexture !== undefined) {
568
+ const normalMap = this.textureCache.get(materialDef.normalTexture.index);
569
+ if (normalMap) {
570
+ params.normalMap = normalMap;
571
+ }
572
+ }
573
+
574
+ // Emissive texture
575
+ if (materialDef.emissiveTexture !== undefined) {
576
+ const emissiveMap = this.textureCache.get(materialDef.emissiveTexture.index);
577
+ if (emissiveMap) {
578
+ params.emissiveMap = emissiveMap;
579
+ }
580
+ }
581
+
582
+ // Metallic-roughness texture
583
+ // Check if we need MeshStandardMaterial for full PBR support or we can use MeshPhongMaterial
584
+ // Use MeshStandardMaterial only if there are PBR textures present to optimize rendering performance.
585
+ // If only factors are present, MeshPhongMaterial is enough for basic lighting.
586
+ const usePBR = materialDef.pbrMetallicRoughness?.metallicRoughnessTexture !== undefined;
587
+
588
+ if (usePBR) {
589
+ // Apply default values if not specified
590
+ params.metalness =
591
+ materialDef.pbrMetallicRoughness?.metallicFactor !== undefined
592
+ ? materialDef.pbrMetallicRoughness.metallicFactor
593
+ : 1.0;
594
+ params.roughness =
595
+ materialDef.pbrMetallicRoughness?.roughnessFactor !== undefined
596
+ ? materialDef.pbrMetallicRoughness.roughnessFactor
597
+ : 1.0;
598
+
599
+ if (materialDef.pbrMetallicRoughness?.metallicRoughnessTexture !== undefined) {
600
+ const map = this.textureCache.get(materialDef.pbrMetallicRoughness.metallicRoughnessTexture.index);
601
+ if (map) {
602
+ params.metalnessMap = map;
603
+ params.roughnessMap = map;
604
+ }
605
+ }
606
+ material = new MeshStandardMaterial(params);
607
+ } else {
608
+ material = new MeshPhongMaterial(params);
609
+ }
610
+
611
+ // Additional maps setup (needs to happen after instantiation for some properties)
612
+ if (material.map) material.map.colorSpace = SRGBColorSpace;
613
+ if (material.emissiveMap) material.emissiveMap.colorSpace = SRGBColorSpace;
614
+
615
+ if (material.normalMap) {
616
+ if (materialDef.normalTexture?.scale !== undefined) {
617
+ material.normalScale.set(materialDef.normalTexture.scale, materialDef.normalTexture.scale);
618
+ }
619
+ }
620
+ if (materialDef.occlusionTexture !== undefined) {
621
+ const aoMap = this.textureCache.get(materialDef.occlusionTexture.index);
622
+ if (aoMap) {
623
+ material.aoMap = aoMap;
624
+ if (materialDef.occlusionTexture.strength !== undefined) {
625
+ material.aoMapIntensity = materialDef.occlusionTexture.strength;
626
+ }
627
+ }
628
+ }
499
629
  }
500
630
 
501
631
  return material;