@inweb/viewer-three 26.11.0 → 26.12.0
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/extensions/components/AxesHelperComponent.js +1 -1
- package/dist/extensions/components/AxesHelperComponent.js.map +1 -1
- package/dist/extensions/components/AxesHelperComponent.min.js +1 -1
- package/dist/extensions/components/AxesHelperComponent.module.js +1 -1
- package/dist/extensions/components/AxesHelperComponent.module.js.map +1 -1
- package/dist/extensions/components/InfoPanelComponent.js +170 -0
- package/dist/extensions/components/InfoPanelComponent.js.map +1 -0
- package/dist/extensions/components/InfoPanelComponent.min.js +24 -0
- package/dist/extensions/components/InfoPanelComponent.module.js +164 -0
- package/dist/extensions/components/InfoPanelComponent.module.js.map +1 -0
- package/dist/extensions/components/StatsPanelComponent.js +9 -3
- package/dist/extensions/components/StatsPanelComponent.js.map +1 -1
- package/dist/extensions/components/StatsPanelComponent.min.js +1 -1
- package/dist/extensions/components/StatsPanelComponent.module.js +9 -3
- package/dist/extensions/components/StatsPanelComponent.module.js.map +1 -1
- package/dist/extensions/loaders/PotreeLoader.js +55 -4
- package/dist/extensions/loaders/PotreeLoader.js.map +1 -1
- package/dist/extensions/loaders/PotreeLoader.min.js +1 -1
- package/dist/extensions/loaders/PotreeLoader.module.js +52 -0
- package/dist/extensions/loaders/PotreeLoader.module.js.map +1 -1
- package/dist/viewer-three.js +402 -5
- package/dist/viewer-three.js.map +1 -1
- package/dist/viewer-three.min.js +8 -3
- package/dist/viewer-three.module.js +358 -7
- package/dist/viewer-three.module.js.map +1 -1
- package/extensions/components/AxesHelperComponent.ts +1 -1
- package/extensions/components/InfoPanelComponent.ts +197 -0
- package/extensions/components/StatsPanelComponent.ts +10 -3
- package/extensions/loaders/Potree/PotreeModelImpl.ts +72 -0
- package/lib/Viewer/Viewer.d.ts +2 -1
- package/lib/Viewer/components/InfoComponent.d.ts +22 -0
- package/lib/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.d.ts +2 -0
- package/lib/Viewer/models/IModelImpl.d.ts +2 -1
- package/lib/Viewer/models/ModelImpl.d.ts +2 -0
- package/package.json +5 -5
- package/src/Viewer/Viewer.ts +7 -0
- package/src/Viewer/components/InfoComponent.ts +187 -0
- package/src/Viewer/components/index.ts +2 -0
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +16 -8
- package/src/Viewer/loaders/DynamicGltfLoader/DynamicModelImpl.ts +25 -0
- package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +67 -1
- package/src/Viewer/loaders/RangesLoader.ts +11 -1
- package/src/Viewer/models/IModelImpl.ts +3 -1
- package/src/Viewer/models/ModelImpl.ts +158 -0
|
@@ -117,7 +117,7 @@ export class DynamicGltfLoader {
|
|
|
117
117
|
this.mergedObjectMap = new Map(); // objectId -> {mergedObject, startIndex, endIndex, vertexCount}
|
|
118
118
|
this.mergedGeometryVisibility = new Map(); // mergedObject -> visibility array
|
|
119
119
|
|
|
120
|
-
this._webglInfoCache = null;
|
|
120
|
+
this._webglInfoCache = null;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
setVisibleEdges(visible) {
|
|
@@ -604,10 +604,17 @@ export class DynamicGltfLoader {
|
|
|
604
604
|
onLoadFinishCb();
|
|
605
605
|
}
|
|
606
606
|
} catch (error) {
|
|
607
|
-
if (error.name !== "AbortError") {
|
|
608
|
-
console.error(`Error loading node ${nodeId}:`, error);
|
|
609
|
-
}
|
|
610
607
|
node.loading = false;
|
|
608
|
+
|
|
609
|
+
if (error.name === "AbortError") {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (node.structure && node.structure.loadingAborted) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
console.error(`Error loading node ${nodeId}:`, error);
|
|
611
618
|
}
|
|
612
619
|
}
|
|
613
620
|
|
|
@@ -1099,7 +1106,6 @@ export class DynamicGltfLoader {
|
|
|
1099
1106
|
}
|
|
1100
1107
|
|
|
1101
1108
|
createVisibilityMaterial(material) {
|
|
1102
|
-
// Apply shader directly to the original material
|
|
1103
1109
|
material.onBeforeCompile = (shader) => {
|
|
1104
1110
|
shader.vertexShader = shader.vertexShader.replace(
|
|
1105
1111
|
"#include <common>",
|
|
@@ -1134,8 +1140,6 @@ export class DynamicGltfLoader {
|
|
|
1134
1140
|
`
|
|
1135
1141
|
);
|
|
1136
1142
|
};
|
|
1137
|
-
|
|
1138
|
-
// Force recompilation of material
|
|
1139
1143
|
material.needsUpdate = true;
|
|
1140
1144
|
|
|
1141
1145
|
return material;
|
|
@@ -1170,7 +1174,6 @@ export class DynamicGltfLoader {
|
|
|
1170
1174
|
});
|
|
1171
1175
|
this.nodes.clear();
|
|
1172
1176
|
|
|
1173
|
-
// Clear all loaded meshes
|
|
1174
1177
|
this.loadedMeshes.forEach((mesh) => {
|
|
1175
1178
|
if (mesh.geometry) mesh.geometry.dispose();
|
|
1176
1179
|
if (mesh.material) {
|
|
@@ -1578,6 +1581,7 @@ export class DynamicGltfLoader {
|
|
|
1578
1581
|
const visibilityMaterial = this.createVisibilityMaterial(group.material);
|
|
1579
1582
|
|
|
1580
1583
|
const mergedMesh = new Mesh(mergedGeometry, visibilityMaterial);
|
|
1584
|
+
mergedMesh.userData.isOptimized = true;
|
|
1581
1585
|
rootGroup.add(mergedMesh);
|
|
1582
1586
|
|
|
1583
1587
|
this.mergedMesh.add(mergedMesh);
|
|
@@ -1710,6 +1714,7 @@ export class DynamicGltfLoader {
|
|
|
1710
1714
|
|
|
1711
1715
|
const mergedLine = new LineSegments(geometry, visibilityMaterial);
|
|
1712
1716
|
mergedLine.userData.isEdge = isEdge;
|
|
1717
|
+
mergedLine.userData.isOptimized = true;
|
|
1713
1718
|
|
|
1714
1719
|
const mergedObjects = [mergedLine];
|
|
1715
1720
|
if (this.useVAO) {
|
|
@@ -1806,6 +1811,7 @@ export class DynamicGltfLoader {
|
|
|
1806
1811
|
|
|
1807
1812
|
const mergedLine = new LineSegments(mergedGeometry, visibilityMaterial);
|
|
1808
1813
|
mergedLine.userData.isEdge = isEdge;
|
|
1814
|
+
mergedLine.userData.isOptimized = true;
|
|
1809
1815
|
|
|
1810
1816
|
if (this.useVAO) {
|
|
1811
1817
|
this.createVAO(mergedLine);
|
|
@@ -1878,6 +1884,7 @@ export class DynamicGltfLoader {
|
|
|
1878
1884
|
if (geometries.length > 0) {
|
|
1879
1885
|
const mergedGeometry = mergeGeometries(geometries, false);
|
|
1880
1886
|
const mergedPoints = new Points(mergedGeometry, group.material);
|
|
1887
|
+
mergedPoints.userData.isOptimized = true;
|
|
1881
1888
|
|
|
1882
1889
|
if (this.useVAO) {
|
|
1883
1890
|
this.createVAO(mergedPoints);
|
|
@@ -1970,6 +1977,7 @@ export class DynamicGltfLoader {
|
|
|
1970
1977
|
|
|
1971
1978
|
const mergedLine = new LineSegments(finalGeometry, material);
|
|
1972
1979
|
mergedLine.userData.structureId = structureId;
|
|
1980
|
+
mergedLine.userData.isOptimized = true;
|
|
1973
1981
|
rootGroup.add(mergedLine);
|
|
1974
1982
|
this.mergedLineSegments.add(mergedLine);
|
|
1975
1983
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
///////////////////////////////////////////////////////////////////////////////
|
|
23
23
|
|
|
24
24
|
import { Box3, Object3D } from "three";
|
|
25
|
+
import { IInfo, Info } from "@inweb/viewer-core";
|
|
25
26
|
|
|
26
27
|
import { ModelImpl } from "../../models/ModelImpl";
|
|
27
28
|
import { DynamicGltfLoader } from "./DynamicGltfLoader.js";
|
|
@@ -31,6 +32,30 @@ import { DynamicGltfLoader } from "./DynamicGltfLoader.js";
|
|
|
31
32
|
export class DynamicModelImpl extends ModelImpl {
|
|
32
33
|
public gltfLoader: DynamicGltfLoader;
|
|
33
34
|
|
|
35
|
+
override getInfo(): IInfo {
|
|
36
|
+
const stats = this.gltfLoader.getStats();
|
|
37
|
+
|
|
38
|
+
const info = new Info();
|
|
39
|
+
|
|
40
|
+
info.scene.objects = stats.scene.beforeOptimization.objects;
|
|
41
|
+
info.scene.triangles = stats.scene.beforeOptimization.triangles;
|
|
42
|
+
info.scene.lines = stats.scene.beforeOptimization.lines;
|
|
43
|
+
info.scene.edges = stats.scene.beforeOptimization.edges;
|
|
44
|
+
|
|
45
|
+
info.optimizedScene.objects = stats.scene.afterOptimization.objects;
|
|
46
|
+
info.optimizedScene.triangles = stats.scene.afterOptimization.triangles;
|
|
47
|
+
info.optimizedScene.lines = stats.scene.afterOptimization.lines;
|
|
48
|
+
info.optimizedScene.edges = stats.scene.afterOptimization.edges;
|
|
49
|
+
|
|
50
|
+
info.memory.geometries = stats.memory.geometries.count;
|
|
51
|
+
info.memory.geometryBytes = stats.memory.geometries.bytes;
|
|
52
|
+
info.memory.textures = stats.memory.textures.count;
|
|
53
|
+
info.memory.materials = stats.memory.materials.count;
|
|
54
|
+
info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes;
|
|
55
|
+
|
|
56
|
+
return info;
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
override getExtents(target: Box3): Box3 {
|
|
35
60
|
return target.union(this.gltfLoader.getTotalGeometryExtent());
|
|
36
61
|
}
|
|
@@ -60,6 +60,8 @@ export class GltfStructure {
|
|
|
60
60
|
this.materialCache = new Map();
|
|
61
61
|
this.uri = "";
|
|
62
62
|
this._nextObjectId = 0;
|
|
63
|
+
this.loadingAborted = false;
|
|
64
|
+
this.criticalError = null;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
async initialize(loader) {
|
|
@@ -85,6 +87,8 @@ export class GltfStructure {
|
|
|
85
87
|
|
|
86
88
|
this.activeChunkLoads = 0;
|
|
87
89
|
this.chunkQueue = [];
|
|
90
|
+
this.loadingAborted = false;
|
|
91
|
+
this.criticalError = null;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
getJson() {
|
|
@@ -93,6 +97,10 @@ export class GltfStructure {
|
|
|
93
97
|
|
|
94
98
|
scheduleRequest(request) {
|
|
95
99
|
return new Promise((resolve, reject) => {
|
|
100
|
+
if (this.loadingAborted) {
|
|
101
|
+
reject(this.criticalError || new Error("Structure loading has been aborted due to critical error"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
96
104
|
this.pendingRequests.push({
|
|
97
105
|
...request,
|
|
98
106
|
_resolve: resolve,
|
|
@@ -101,6 +109,52 @@ export class GltfStructure {
|
|
|
101
109
|
});
|
|
102
110
|
}
|
|
103
111
|
|
|
112
|
+
isCriticalHttpError(error) {
|
|
113
|
+
if (!error) return false;
|
|
114
|
+
|
|
115
|
+
const status = error.status || error.statusCode || error.code;
|
|
116
|
+
|
|
117
|
+
if (typeof status === "number") {
|
|
118
|
+
return status >= 400 && status < 600;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (error.message) {
|
|
122
|
+
const match = error.message.match(/HTTP\s+(\d{3})/i);
|
|
123
|
+
if (match) {
|
|
124
|
+
const code = parseInt(match[1], 10);
|
|
125
|
+
return code >= 400 && code < 600;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
abortLoading(error) {
|
|
133
|
+
if (this.loadingAborted) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.loadingAborted = true;
|
|
138
|
+
this.criticalError = error;
|
|
139
|
+
|
|
140
|
+
const requests = [...this.pendingRequests];
|
|
141
|
+
this.pendingRequests = [];
|
|
142
|
+
|
|
143
|
+
for (const req of requests) {
|
|
144
|
+
if (req._reject) {
|
|
145
|
+
req._reject(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.error(
|
|
150
|
+
`❌ Critical error for structure "${this.id}". All further loading aborted.`,
|
|
151
|
+
`\n Error: ${error.message || error}`,
|
|
152
|
+
`\n Rejected ${requests.length} pending chunk requests.`
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
|
|
104
158
|
async flushBufferRequests() {
|
|
105
159
|
if (!this.pendingRequests || this.pendingRequests.length === 0) return;
|
|
106
160
|
const requests = [...this.pendingRequests];
|
|
@@ -165,6 +219,13 @@ export class GltfStructure {
|
|
|
165
219
|
}
|
|
166
220
|
|
|
167
221
|
const promises = finalRanges.map(async (range, index) => {
|
|
222
|
+
if (this.loadingAborted) {
|
|
223
|
+
for (const req of range.requests) {
|
|
224
|
+
req._reject(this.criticalError || new Error("Structure loading aborted"));
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
168
229
|
await this.loader.waitForChunkSlot();
|
|
169
230
|
|
|
170
231
|
try {
|
|
@@ -183,7 +244,12 @@ export class GltfStructure {
|
|
|
183
244
|
for (const req of range.requests) {
|
|
184
245
|
req._reject(error);
|
|
185
246
|
}
|
|
186
|
-
|
|
247
|
+
|
|
248
|
+
if (this.isCriticalHttpError(error)) {
|
|
249
|
+
this.abortLoading(error);
|
|
250
|
+
} else {
|
|
251
|
+
console.warn(`Failed to load chunk ${index + 1}/${finalRanges.length} (${range.start}-${range.end}):`, error);
|
|
252
|
+
}
|
|
187
253
|
} finally {
|
|
188
254
|
this.loader.releaseChunkSlot();
|
|
189
255
|
}
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
// acknowledge and accept the above terms.
|
|
22
22
|
///////////////////////////////////////////////////////////////////////////////
|
|
23
23
|
|
|
24
|
+
class FetchError extends Error {
|
|
25
|
+
public status: number;
|
|
26
|
+
|
|
27
|
+
constructor(status: number, message: string) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "FetchError";
|
|
30
|
+
this.status = status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
export interface Range {
|
|
25
35
|
offset: number;
|
|
26
36
|
length: number;
|
|
@@ -61,7 +71,7 @@ export class RangesLoader {
|
|
|
61
71
|
|
|
62
72
|
const response = await fetch(url, init);
|
|
63
73
|
if (!response.ok) {
|
|
64
|
-
throw new
|
|
74
|
+
throw new FetchError(response.status, `Failed to fetch "${url}", status ${response.status}`);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
if (response.status !== 206) {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
///////////////////////////////////////////////////////////////////////////////
|
|
23
23
|
|
|
24
24
|
import { Box3, Object3D } from "three";
|
|
25
|
-
import { IModel } from "@inweb/viewer-core";
|
|
25
|
+
import { IInfo, IModel } from "@inweb/viewer-core";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Basic model implementation.
|
|
@@ -38,6 +38,8 @@ export interface IModelImpl extends IModel {
|
|
|
38
38
|
|
|
39
39
|
getPrecision(): number;
|
|
40
40
|
|
|
41
|
+
getInfo(): IInfo;
|
|
42
|
+
|
|
41
43
|
getExtents(target: Box3): Box3;
|
|
42
44
|
|
|
43
45
|
getObjects(): Object3D[];
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
///////////////////////////////////////////////////////////////////////////////
|
|
23
23
|
|
|
24
24
|
import { Box3, Object3D, Vector3 } from "three";
|
|
25
|
+
import { IInfo, Info } from "@inweb/viewer-core";
|
|
26
|
+
|
|
25
27
|
import { IModelImpl } from "./IModelImpl";
|
|
26
28
|
import { convertUnits } from "../measurement/UnitConverter";
|
|
27
29
|
import { getDisplayUnit } from "../measurement/UnitFormatter";
|
|
@@ -91,6 +93,162 @@ export class ModelImpl implements IModelImpl {
|
|
|
91
93
|
return 2;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
getInfo(): IInfo {
|
|
97
|
+
// ===================== AI-CODE-START ======================
|
|
98
|
+
// Source: Claude Sonnet 4.5
|
|
99
|
+
// Date: 2025-10-02
|
|
100
|
+
// Reviewer: roman.mochalov@opendesign.com
|
|
101
|
+
// Issue: CLOUD-5738
|
|
102
|
+
|
|
103
|
+
const geometries = new Set();
|
|
104
|
+
const materials = new Set();
|
|
105
|
+
const textures = new Set();
|
|
106
|
+
|
|
107
|
+
let totalObjects = 0;
|
|
108
|
+
let totalTriangles = 0;
|
|
109
|
+
let totalPoints = 0;
|
|
110
|
+
let totalLines = 0;
|
|
111
|
+
let totalEdges = 0;
|
|
112
|
+
let geometryBytes = 0;
|
|
113
|
+
let textureBytes = 0;
|
|
114
|
+
|
|
115
|
+
this.scene.traverse((object: any) => {
|
|
116
|
+
totalObjects++;
|
|
117
|
+
|
|
118
|
+
if (object.geometry) {
|
|
119
|
+
const geometry = object.geometry;
|
|
120
|
+
|
|
121
|
+
if (!geometries.has(geometry)) {
|
|
122
|
+
geometries.add(geometry);
|
|
123
|
+
|
|
124
|
+
if (geometry.attributes) {
|
|
125
|
+
for (const name in geometry.attributes) {
|
|
126
|
+
const attribute = geometry.attributes[name];
|
|
127
|
+
if (attribute && attribute.array) {
|
|
128
|
+
geometryBytes += attribute.array.byteLength;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (geometry.index && geometry.index.array) {
|
|
134
|
+
geometryBytes += geometry.index.array.byteLength;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (geometry.index) {
|
|
139
|
+
const indexCount = geometry.index.count;
|
|
140
|
+
|
|
141
|
+
if (object.isLine || object.isLineSegments) {
|
|
142
|
+
totalLines += indexCount / 2;
|
|
143
|
+
} else if (object.isPoints) {
|
|
144
|
+
totalPoints += indexCount;
|
|
145
|
+
} else {
|
|
146
|
+
totalTriangles += indexCount / 3;
|
|
147
|
+
}
|
|
148
|
+
} else if (geometry.attributes && geometry.attributes.position) {
|
|
149
|
+
const positionCount = geometry.attributes.position.count;
|
|
150
|
+
|
|
151
|
+
if (object.isLine || object.isLineSegments) {
|
|
152
|
+
totalLines += positionCount / 2;
|
|
153
|
+
} else if (object.isPoints) {
|
|
154
|
+
totalPoints += positionCount;
|
|
155
|
+
} else {
|
|
156
|
+
totalTriangles += positionCount / 3;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (object.isLineSegments && geometry.attributes.position) {
|
|
161
|
+
totalEdges += geometry.attributes.position.count / 2;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (object.material) {
|
|
166
|
+
const materialsArray = Array.isArray(object.material) ? object.material : [object.material];
|
|
167
|
+
|
|
168
|
+
materialsArray.forEach((material: any) => {
|
|
169
|
+
materials.add(material);
|
|
170
|
+
|
|
171
|
+
if (material.map && !textures.has(material.map)) {
|
|
172
|
+
textures.add(material.map);
|
|
173
|
+
textureBytes += estimateTextureSize(material.map);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const textureProps = [
|
|
177
|
+
"alphaMap",
|
|
178
|
+
"aoMap",
|
|
179
|
+
"bumpMap",
|
|
180
|
+
"displacementMap",
|
|
181
|
+
"emissiveMap",
|
|
182
|
+
"envMap",
|
|
183
|
+
"lightMap",
|
|
184
|
+
"metalnessMap",
|
|
185
|
+
"normalMap",
|
|
186
|
+
"roughnessMap",
|
|
187
|
+
"specularMap",
|
|
188
|
+
"clearcoatMap",
|
|
189
|
+
"clearcoatNormalMap",
|
|
190
|
+
"clearcoatRoughnessMap",
|
|
191
|
+
"iridescenceMap",
|
|
192
|
+
"sheenColorMap",
|
|
193
|
+
"sheenRoughnessMap",
|
|
194
|
+
"thicknessMap",
|
|
195
|
+
"transmissionMap",
|
|
196
|
+
"anisotropyMap",
|
|
197
|
+
"gradientMap",
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
textureProps.forEach((prop) => {
|
|
201
|
+
const texture = material[prop];
|
|
202
|
+
if (texture && !textures.has(texture)) {
|
|
203
|
+
textures.add(texture);
|
|
204
|
+
textureBytes += estimateTextureSize(texture);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
function estimateTextureSize(texture: any): number {
|
|
212
|
+
if (!texture.image) return 0;
|
|
213
|
+
|
|
214
|
+
const width = texture.image.width || 0;
|
|
215
|
+
const height = texture.image.height || 0;
|
|
216
|
+
|
|
217
|
+
// Estimate bytes per pixel (RGBA = 4 bytes)
|
|
218
|
+
const bytesPerPixel = 4;
|
|
219
|
+
|
|
220
|
+
// Account for mipmaps (adds ~33% more memory)
|
|
221
|
+
const mipmapMultiplier = texture.generateMipmaps ? 1.33 : 1;
|
|
222
|
+
|
|
223
|
+
return width * height * bytesPerPixel * mipmapMultiplier;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ===================== AI-CODE-END ======================
|
|
227
|
+
|
|
228
|
+
const info = new Info();
|
|
229
|
+
|
|
230
|
+
info.scene.objects = totalObjects;
|
|
231
|
+
info.scene.triangles = Math.floor(totalTriangles);
|
|
232
|
+
info.scene.points = Math.floor(totalPoints);
|
|
233
|
+
info.scene.lines = Math.floor(totalLines);
|
|
234
|
+
info.scene.edges = Math.floor(totalEdges);
|
|
235
|
+
|
|
236
|
+
info.memory.geometries = geometries.size;
|
|
237
|
+
info.memory.geometryBytes = geometryBytes;
|
|
238
|
+
info.memory.textures = textures.size;
|
|
239
|
+
info.memory.textureBytes = Math.floor(textureBytes);
|
|
240
|
+
info.memory.materials = materials.size;
|
|
241
|
+
info.memory.totalEstimatedGpuBytes = geometryBytes + Math.floor(textureBytes);
|
|
242
|
+
|
|
243
|
+
info.optimizedScene.objects = info.scene.objects;
|
|
244
|
+
info.optimizedScene.triangles = info.scene.triangles;
|
|
245
|
+
info.optimizedScene.points = info.scene.points;
|
|
246
|
+
info.optimizedScene.lines = info.scene.lines;
|
|
247
|
+
info.optimizedScene.edges = info.scene.edges;
|
|
248
|
+
|
|
249
|
+
return info;
|
|
250
|
+
}
|
|
251
|
+
|
|
94
252
|
getExtents(target: Box3): Box3 {
|
|
95
253
|
this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
|
|
96
254
|
return target;
|