@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/dist/viewer-three.js +145 -53
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +3 -3
- package/dist/viewer-three.module.js +146 -54
- package/dist/viewer-three.module.js.map +1 -1
- package/package.json +5 -5
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +19 -42
- 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.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.
|
|
39
|
-
"@inweb/eventemitter2": "~27.4.
|
|
40
|
-
"@inweb/markup": "~27.4.
|
|
41
|
-
"@inweb/viewer-core": "~27.4.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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("
|
|
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;
|