@inweb/viewer-three 27.4.2 → 27.4.3
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/viewer-three.js +145 -52
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +3 -3
- package/dist/viewer-three.module.js +146 -53
- package/dist/viewer-three.module.js.map +1 -1
- package/package.json +5 -5
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +19 -40
- package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +164 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inweb/viewer-three",
|
|
3
|
-
"version": "27.4.
|
|
3
|
+
"version": "27.4.3",
|
|
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.
|
|
39
|
-
"@inweb/eventemitter2": "~27.4.
|
|
40
|
-
"@inweb/markup": "~27.4.
|
|
41
|
-
"@inweb/viewer-core": "~27.4.
|
|
38
|
+
"@inweb/client": "~27.4.3",
|
|
39
|
+
"@inweb/eventemitter2": "~27.4.3",
|
|
40
|
+
"@inweb/markup": "~27.4.3",
|
|
41
|
+
"@inweb/viewer-core": "~27.4.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@streamparser/json": "^0.0.22",
|
|
@@ -141,13 +141,6 @@ export class DynamicGltfLoader {
|
|
|
141
141
|
this.visibilityMaterials = new Set(); // Keep track of materials to update uniforms
|
|
142
142
|
}
|
|
143
143
|
|
|
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
144
|
createDummyTexture() {
|
|
152
145
|
// Create 1x1 dummy texture with identity matrix to prevent shader crash/black screen
|
|
153
146
|
const data = new Float32Array(16); // 4x4 matrix
|
|
@@ -226,8 +219,6 @@ export class DynamicGltfLoader {
|
|
|
226
219
|
|
|
227
220
|
updateMaterialUniforms() {
|
|
228
221
|
// 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
222
|
if (
|
|
232
223
|
this._lastTransformTexture === this.transformTexture &&
|
|
233
224
|
this._lastTransformTextureSize === this.transformTextureSize
|
|
@@ -272,7 +263,7 @@ export class DynamicGltfLoader {
|
|
|
272
263
|
|
|
273
264
|
console.log(`Available memory set to ${Math.round(memoryLimit / (1024 * 1024 * 1024))}GB`);
|
|
274
265
|
} catch (error) {
|
|
275
|
-
console.warn("Error detecting available memory:", error);
|
|
266
|
+
console.warn("DynamicLoader: Error detecting available memory:", error);
|
|
276
267
|
}
|
|
277
268
|
|
|
278
269
|
return memoryLimit;
|
|
@@ -470,7 +461,7 @@ export class DynamicGltfLoader {
|
|
|
470
461
|
if (entry?.lines?.uuid) uniqueMaterialIds.add(entry.lines.uuid);
|
|
471
462
|
}
|
|
472
463
|
} catch (exp) {
|
|
473
|
-
console.
|
|
464
|
+
console.warn("DynamicLoader: Error collecting material stats", exp);
|
|
474
465
|
}
|
|
475
466
|
}
|
|
476
467
|
}
|
|
@@ -492,7 +483,7 @@ export class DynamicGltfLoader {
|
|
|
492
483
|
this._webglInfoCache = { renderer: null, vendor: null };
|
|
493
484
|
}
|
|
494
485
|
} catch (e) {
|
|
495
|
-
console.
|
|
486
|
+
console.warn("DynamicLoader: Error getting webgl info", e);
|
|
496
487
|
this._webglInfoCache = { renderer: null, vendor: null };
|
|
497
488
|
}
|
|
498
489
|
}
|
|
@@ -778,7 +769,7 @@ export class DynamicGltfLoader {
|
|
|
778
769
|
return;
|
|
779
770
|
}
|
|
780
771
|
|
|
781
|
-
console.
|
|
772
|
+
console.warn(`DynamicLoader: Error loading node ${nodeId}:`, error);
|
|
782
773
|
}
|
|
783
774
|
}
|
|
784
775
|
|
|
@@ -842,7 +833,7 @@ export class DynamicGltfLoader {
|
|
|
842
833
|
await structure.loadTextures();
|
|
843
834
|
await structure.loadMaterials();
|
|
844
835
|
} catch (error) {
|
|
845
|
-
console.error("Error loading materials:", error);
|
|
836
|
+
console.error("DynamicLoader: Error loading materials:", error);
|
|
846
837
|
throw error;
|
|
847
838
|
}
|
|
848
839
|
}
|
|
@@ -852,7 +843,7 @@ export class DynamicGltfLoader {
|
|
|
852
843
|
|
|
853
844
|
async processSceneHierarchy() {
|
|
854
845
|
if (this.structures.length === 0) {
|
|
855
|
-
throw new Error("No GLTF structures loaded");
|
|
846
|
+
throw new Error("DynamicLoader: No GLTF structures loaded");
|
|
856
847
|
}
|
|
857
848
|
|
|
858
849
|
this.nodesToLoad = [];
|
|
@@ -863,7 +854,7 @@ export class DynamicGltfLoader {
|
|
|
863
854
|
const gltf = structure.getJson();
|
|
864
855
|
|
|
865
856
|
if (!gltf.scenes || !gltf.scenes.length) {
|
|
866
|
-
console.warn("No scenes found in GLTF structure");
|
|
857
|
+
console.warn("DynamicLoader: No scenes found in GLTF structure");
|
|
867
858
|
continue;
|
|
868
859
|
}
|
|
869
860
|
|
|
@@ -1383,7 +1374,6 @@ export class DynamicGltfLoader {
|
|
|
1383
1374
|
|
|
1384
1375
|
//console.log("!vertex", shader.vertexShader);
|
|
1385
1376
|
};
|
|
1386
|
-
|
|
1387
1377
|
// Ensure the material recompiles to pick up changes
|
|
1388
1378
|
material.needsUpdate = true;
|
|
1389
1379
|
return material;
|
|
@@ -1653,7 +1643,6 @@ export class DynamicGltfLoader {
|
|
|
1653
1643
|
// Clear previous optimization data
|
|
1654
1644
|
this.objectIdToIndex.clear();
|
|
1655
1645
|
this.maxObjectId = 0;
|
|
1656
|
-
|
|
1657
1646
|
const structureGroups = new Map();
|
|
1658
1647
|
|
|
1659
1648
|
this.dispatchEvent("optimizationprogress", {
|
|
@@ -1709,7 +1698,6 @@ export class DynamicGltfLoader {
|
|
|
1709
1698
|
// This ensures that as we create merged objects, the texture is large enough
|
|
1710
1699
|
// and populated with identity matrices, so objects don't disappear (scale 0).
|
|
1711
1700
|
if (totalObjectsToMerge > 0) {
|
|
1712
|
-
console.log(`Pre-allocating transform texture for ${totalObjectsToMerge} objects`);
|
|
1713
1701
|
this.maxObjectId = totalObjectsToMerge;
|
|
1714
1702
|
this.initTransformTexture();
|
|
1715
1703
|
this.initializeObjectVisibility();
|
|
@@ -1776,7 +1764,6 @@ export class DynamicGltfLoader {
|
|
|
1776
1764
|
});
|
|
1777
1765
|
|
|
1778
1766
|
// Texture and visibility initialized at start of optimization
|
|
1779
|
-
|
|
1780
1767
|
console.log(`Optimization complete. Total objects: ${this.maxObjectId}`);
|
|
1781
1768
|
|
|
1782
1769
|
this.dispatchEvent("optimizationprogress", {
|
|
@@ -1793,7 +1780,7 @@ export class DynamicGltfLoader {
|
|
|
1793
1780
|
let processedGroups = 0;
|
|
1794
1781
|
for (const group of materialGroups) {
|
|
1795
1782
|
if (!group.material) {
|
|
1796
|
-
console.warn("Skipping mesh group with null material");
|
|
1783
|
+
console.warn("DynamicLoader: Skipping mesh group with null material");
|
|
1797
1784
|
continue;
|
|
1798
1785
|
}
|
|
1799
1786
|
|
|
@@ -1908,7 +1895,7 @@ export class DynamicGltfLoader {
|
|
|
1908
1895
|
await this.yieldToUI();
|
|
1909
1896
|
}
|
|
1910
1897
|
} catch (error) {
|
|
1911
|
-
console.
|
|
1898
|
+
console.warn("DynamicLoader: Failed to merge meshes for material:", error);
|
|
1912
1899
|
group.objects.forEach((mesh) => {
|
|
1913
1900
|
mesh.visible = true;
|
|
1914
1901
|
});
|
|
@@ -1922,7 +1909,7 @@ export class DynamicGltfLoader {
|
|
|
1922
1909
|
if (group.objects.length === 0) continue;
|
|
1923
1910
|
|
|
1924
1911
|
if (!group.material) {
|
|
1925
|
-
console.warn("Skipping line group with null material");
|
|
1912
|
+
console.warn("DynamicLoader: Skipping line group with null material");
|
|
1926
1913
|
continue;
|
|
1927
1914
|
}
|
|
1928
1915
|
|
|
@@ -2058,7 +2045,7 @@ export class DynamicGltfLoader {
|
|
|
2058
2045
|
let processedGroups = 0;
|
|
2059
2046
|
for (const group of materialGroups) {
|
|
2060
2047
|
if (!group.material) {
|
|
2061
|
-
console.warn("Skipping line segment group with null material");
|
|
2048
|
+
console.warn("DynamicLoader: Skipping line segment group with null material");
|
|
2062
2049
|
continue;
|
|
2063
2050
|
}
|
|
2064
2051
|
|
|
@@ -2171,7 +2158,7 @@ export class DynamicGltfLoader {
|
|
|
2171
2158
|
await this.yieldToUI();
|
|
2172
2159
|
}
|
|
2173
2160
|
} catch (error) {
|
|
2174
|
-
console.warn("Failed to merge line segments for material:", error);
|
|
2161
|
+
console.warn("DynamicLoader: Failed to merge line segments for material:", error);
|
|
2175
2162
|
group.objects.forEach((line) => {
|
|
2176
2163
|
line.visible = true;
|
|
2177
2164
|
});
|
|
@@ -2183,7 +2170,7 @@ export class DynamicGltfLoader {
|
|
|
2183
2170
|
let processedGroups = 0;
|
|
2184
2171
|
for (const group of materialGroups) {
|
|
2185
2172
|
if (!group.material) {
|
|
2186
|
-
console.warn("Skipping points group with null material");
|
|
2173
|
+
console.warn("DynamicLoader: Skipping points group with null material");
|
|
2187
2174
|
continue;
|
|
2188
2175
|
}
|
|
2189
2176
|
|
|
@@ -2221,7 +2208,6 @@ export class DynamicGltfLoader {
|
|
|
2221
2208
|
const totalVertices = mergedGeometry.attributes.position.count;
|
|
2222
2209
|
const objectIds = new Float32Array(totalVertices);
|
|
2223
2210
|
let vertexOffset = 0;
|
|
2224
|
-
|
|
2225
2211
|
group.objects.forEach((points) => {
|
|
2226
2212
|
const handle = points.userData.handle;
|
|
2227
2213
|
if (!this.objectIdToIndex.has(handle)) {
|
|
@@ -2275,7 +2261,7 @@ export class DynamicGltfLoader {
|
|
|
2275
2261
|
await this.yieldToUI();
|
|
2276
2262
|
}
|
|
2277
2263
|
} catch (error) {
|
|
2278
|
-
console.warn("Failed to merge points for material:", error);
|
|
2264
|
+
console.warn("DynamicLoader: Failed to merge points for material:", error);
|
|
2279
2265
|
group.objects.forEach((points) => {
|
|
2280
2266
|
points.visible = true;
|
|
2281
2267
|
});
|
|
@@ -2326,7 +2312,6 @@ export class DynamicGltfLoader {
|
|
|
2326
2312
|
});
|
|
2327
2313
|
|
|
2328
2314
|
const finalGeometry = mergeGeometries(geometriesWithIndex, false);
|
|
2329
|
-
|
|
2330
2315
|
// Add objectId attribute
|
|
2331
2316
|
const totalVertices = finalGeometry.attributes.position.count;
|
|
2332
2317
|
const objectIds = new Float32Array(totalVertices);
|
|
@@ -2373,7 +2358,7 @@ export class DynamicGltfLoader {
|
|
|
2373
2358
|
obj.geometry.dispose();
|
|
2374
2359
|
});
|
|
2375
2360
|
} catch (error) {
|
|
2376
|
-
console.
|
|
2361
|
+
console.warn("DynamicLoader: Failed to merge geometries:", error);
|
|
2377
2362
|
lineSegmentsArray.forEach((obj) => {
|
|
2378
2363
|
obj.visible = true;
|
|
2379
2364
|
rootGroup.add(obj);
|
|
@@ -2501,12 +2486,12 @@ export class DynamicGltfLoader {
|
|
|
2501
2486
|
|
|
2502
2487
|
applyObjectTransforms(objectTransformMap) {
|
|
2503
2488
|
if (this.mergedObjectMap.size === 0) {
|
|
2504
|
-
console.warn("No merged objects to transform");
|
|
2489
|
+
console.warn("DynamicLoader: No merged objects to transform");
|
|
2505
2490
|
return;
|
|
2506
2491
|
}
|
|
2507
2492
|
|
|
2508
2493
|
if (!this.transformData) {
|
|
2509
|
-
console.warn("Transform texture not initialized");
|
|
2494
|
+
console.warn("DynamicLoader: Transform texture not initialized");
|
|
2510
2495
|
return;
|
|
2511
2496
|
}
|
|
2512
2497
|
|
|
@@ -2609,15 +2594,9 @@ export class DynamicGltfLoader {
|
|
|
2609
2594
|
|
|
2610
2595
|
// Convert offset from structure-root-local to world space,
|
|
2611
2596
|
// 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
2597
|
if (object.userData.structureId) {
|
|
2617
2598
|
const rootGroup = this.structureRoots.get(object.userData.structureId);
|
|
2618
2599
|
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
2600
|
const origin = new Vector3(0, 0, 0);
|
|
2622
2601
|
origin.applyMatrix4(rootGroup.matrixWorld);
|
|
2623
2602
|
_offset.applyMatrix4(rootGroup.matrixWorld);
|
|
@@ -2633,7 +2612,6 @@ export class DynamicGltfLoader {
|
|
|
2633
2612
|
|
|
2634
2613
|
object.position.add(_offset);
|
|
2635
2614
|
|
|
2636
|
-
// Also update highlight wireframe if it exists right now
|
|
2637
2615
|
if (object.userData.highlight) {
|
|
2638
2616
|
object.userData.highlight.position.copy(object.position);
|
|
2639
2617
|
}
|
|
@@ -2702,6 +2680,7 @@ export class DynamicGltfLoader {
|
|
|
2702
2680
|
// Cache it
|
|
2703
2681
|
obj.userData.explodeVector = explodeVector;
|
|
2704
2682
|
}
|
|
2683
|
+
|
|
2705
2684
|
const explodeVector = obj.userData.explodeVector;
|
|
2706
2685
|
const distance = explodeVector.length();
|
|
2707
2686
|
|
|
@@ -2772,7 +2751,7 @@ export class DynamicGltfLoader {
|
|
|
2772
2751
|
|
|
2773
2752
|
syncHiddenObjects() {
|
|
2774
2753
|
if (this.mergedObjectMap.size === 0) {
|
|
2775
|
-
console.
|
|
2754
|
+
console.warn("DynamicLoader: No merged objects to sync");
|
|
2776
2755
|
return;
|
|
2777
2756
|
}
|
|
2778
2757
|
|
|
@@ -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("
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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) =>
|
|
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
|
-
|
|
459
|
-
|
|
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
|
|
495
|
-
|
|
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
|
-
|
|
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;
|