@stowkit/three-loader 0.1.28 → 0.1.30
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.
|
@@ -212,39 +212,24 @@ class MeshParser {
|
|
|
212
212
|
* Create Three.js BufferGeometry from Draco compressed mesh data
|
|
213
213
|
*/
|
|
214
214
|
static async createGeometry(geoInfo, dataBlob, dracoLoader) {
|
|
215
|
-
const startTime = performance.now();
|
|
216
215
|
// Extract the Draco compressed buffer
|
|
217
216
|
if (geoInfo.compressedBufferOffset + geoInfo.compressedBufferSize > dataBlob.length) {
|
|
218
217
|
throw new Error(`Compressed buffer out of bounds: offset=${geoInfo.compressedBufferOffset}, size=${geoInfo.compressedBufferSize}, dataLength=${dataBlob.length}`);
|
|
219
218
|
}
|
|
220
219
|
const compressedData = dataBlob.slice(geoInfo.compressedBufferOffset, geoInfo.compressedBufferOffset + geoInfo.compressedBufferSize);
|
|
221
|
-
const extractTime = performance.now();
|
|
222
|
-
reader.PerfLogger.log(`[Perf] Draco extract: ${(extractTime - startTime).toFixed(2)}ms (${geoInfo.compressedBufferSize} bytes)`);
|
|
223
220
|
// Decode using Draco
|
|
224
|
-
// Create a blob URL for the Draco data (DRACOLoader expects a URL)
|
|
225
221
|
const arrayBuffer = compressedData.buffer.slice(compressedData.byteOffset, compressedData.byteOffset + compressedData.byteLength);
|
|
226
222
|
const blob = new Blob([arrayBuffer]);
|
|
227
223
|
const url = URL.createObjectURL(blob);
|
|
228
|
-
const decodeStart = performance.now();
|
|
229
224
|
const geometry = await new Promise((resolve, reject) => {
|
|
230
225
|
dracoLoader.load(url, (decoded) => {
|
|
231
226
|
URL.revokeObjectURL(url);
|
|
232
|
-
const decodeEnd = performance.now();
|
|
233
|
-
reader.PerfLogger.log(`[Perf] Draco decode: ${(decodeEnd - decodeStart).toFixed(2)}ms (vertices: ${geoInfo.vertexCount}, indices: ${geoInfo.indexCount})`);
|
|
234
227
|
resolve(decoded);
|
|
235
228
|
}, undefined, (error) => {
|
|
236
229
|
URL.revokeObjectURL(url);
|
|
237
230
|
reject(new Error(`Failed to decode Draco geometry: ${error}`));
|
|
238
231
|
});
|
|
239
232
|
});
|
|
240
|
-
// Compute bounding volumes
|
|
241
|
-
const boundsStart = performance.now();
|
|
242
|
-
geometry.computeBoundingSphere();
|
|
243
|
-
geometry.computeBoundingBox();
|
|
244
|
-
const boundsEnd = performance.now();
|
|
245
|
-
reader.PerfLogger.log(`[Perf] Bounds computation: ${(boundsEnd - boundsStart).toFixed(2)}ms`);
|
|
246
|
-
const totalTime = performance.now();
|
|
247
|
-
reader.PerfLogger.log(`[Perf] Total geometry creation: ${(totalTime - startTime).toFixed(2)}ms`);
|
|
248
233
|
return geometry;
|
|
249
234
|
}
|
|
250
235
|
/**
|
|
@@ -254,6 +239,13 @@ class MeshParser {
|
|
|
254
239
|
const root = new THREE__namespace.Group();
|
|
255
240
|
root.name = 'StowKitMesh';
|
|
256
241
|
const { geometries, materials, nodes, meshIndices } = parsedData;
|
|
242
|
+
// Pre-load ALL geometries in parallel for maximum speed
|
|
243
|
+
const dracoStart = performance.now();
|
|
244
|
+
const geometryPromises = geometries.map(geoInfo => this.createGeometry(geoInfo, dataBlob, dracoLoader));
|
|
245
|
+
const loadedGeometries = await Promise.all(geometryPromises);
|
|
246
|
+
const dracoTime = performance.now() - dracoStart;
|
|
247
|
+
const totalVerts = geometries.reduce((sum, g) => sum + g.vertexCount, 0);
|
|
248
|
+
reader.PerfLogger.log(`[Perf] Draco decode: ${dracoTime.toFixed(2)}ms (${geometries.length} meshes, ${totalVerts} total vertices)`);
|
|
257
249
|
// Create all Three.js objects for nodes
|
|
258
250
|
const nodeObjects = [];
|
|
259
251
|
for (const node of nodes) {
|
|
@@ -269,7 +261,7 @@ class MeshParser {
|
|
|
269
261
|
const meshIndex = meshIndices[meshIndexArrayPos];
|
|
270
262
|
if (meshIndex < geometries.length) {
|
|
271
263
|
const geoInfo = geometries[meshIndex];
|
|
272
|
-
const geometry =
|
|
264
|
+
const geometry = loadedGeometries[meshIndex];
|
|
273
265
|
// Use assigned material if valid, otherwise create default
|
|
274
266
|
let material;
|
|
275
267
|
if (geoInfo.materialIndex < materials.length) {
|
|
@@ -300,11 +292,11 @@ class MeshParser {
|
|
|
300
292
|
root.add(obj);
|
|
301
293
|
}
|
|
302
294
|
}
|
|
303
|
-
// If no nodes, create
|
|
295
|
+
// If no nodes, create meshes from all geometries
|
|
304
296
|
if (nodes.length === 0 && geometries.length > 0) {
|
|
305
297
|
for (let index = 0; index < geometries.length; index++) {
|
|
306
298
|
const geoInfo = geometries[index];
|
|
307
|
-
const geometry =
|
|
299
|
+
const geometry = loadedGeometries[index];
|
|
308
300
|
// Use assigned material if valid, otherwise create default
|
|
309
301
|
let material;
|
|
310
302
|
if (geoInfo.materialIndex < materials.length) {
|
|
@@ -673,17 +665,13 @@ class StowKitPack {
|
|
|
673
665
|
async loadMesh(assetPath) {
|
|
674
666
|
const totalStart = performance.now();
|
|
675
667
|
// Find asset by path
|
|
676
|
-
const findStart = performance.now();
|
|
677
668
|
const assetIndex = this.reader.findAssetByPath(assetPath);
|
|
678
669
|
if (assetIndex < 0) {
|
|
679
670
|
throw new Error(`Mesh not found: ${assetPath}`);
|
|
680
671
|
}
|
|
681
|
-
reader.PerfLogger.log(`[Perf] Find asset: ${(performance.now() - findStart).toFixed(2)}ms`);
|
|
682
672
|
// Read mesh data and metadata
|
|
683
|
-
const readStart = performance.now();
|
|
684
673
|
const data = this.reader.readAssetData(assetIndex);
|
|
685
674
|
const metadata = this.reader.readAssetMetadata(assetIndex);
|
|
686
|
-
reader.PerfLogger.log(`[Perf] Read mesh data: ${(performance.now() - readStart).toFixed(2)}ms (data: ${data?.byteLength || 0} bytes, metadata: ${metadata?.byteLength || 0} bytes)`);
|
|
687
675
|
if (!data) {
|
|
688
676
|
throw new Error(`Failed to read mesh data: ${assetPath}`);
|
|
689
677
|
}
|
|
@@ -691,18 +679,18 @@ class StowKitPack {
|
|
|
691
679
|
throw new Error(`No metadata available: ${assetPath}`);
|
|
692
680
|
}
|
|
693
681
|
// Parse mesh data
|
|
694
|
-
const parseStart = performance.now();
|
|
695
682
|
const parsedData = MeshParser.parseMeshData(metadata, data);
|
|
696
|
-
reader.PerfLogger.log(`[Perf] Parse mesh metadata: ${(performance.now() - parseStart).toFixed(2)}ms`);
|
|
697
683
|
// Load textures for materials
|
|
698
684
|
const textureStart = performance.now();
|
|
699
685
|
const textureNames = await this.loadMaterialTextures(parsedData.materialData, parsedData.materials);
|
|
700
|
-
|
|
686
|
+
const textureTime = performance.now() - textureStart;
|
|
687
|
+
if (textureNames.length > 0) {
|
|
688
|
+
reader.PerfLogger.log(`[Perf] Textures: ${textureTime.toFixed(2)}ms (${textureNames.join(', ')})`);
|
|
689
|
+
}
|
|
701
690
|
// Build Three.js scene with Draco decoder
|
|
702
|
-
const buildStart = performance.now();
|
|
703
691
|
const scene = await MeshParser.buildScene(parsedData, data, this.dracoLoader);
|
|
704
|
-
|
|
705
|
-
reader.PerfLogger.log(`[Perf]
|
|
692
|
+
const totalTime = performance.now() - totalStart;
|
|
693
|
+
reader.PerfLogger.log(`[Perf] === Mesh "${assetPath}": ${totalTime.toFixed(2)}ms total ===`);
|
|
706
694
|
return scene;
|
|
707
695
|
}
|
|
708
696
|
/**
|
|
@@ -711,7 +699,6 @@ class StowKitPack {
|
|
|
711
699
|
async loadTexture(assetPath) {
|
|
712
700
|
// Check cache first
|
|
713
701
|
if (this.textureCache.has(assetPath)) {
|
|
714
|
-
reader.PerfLogger.log(`[Perf] Texture cache hit: ${assetPath}`);
|
|
715
702
|
return this.textureCache.get(assetPath);
|
|
716
703
|
}
|
|
717
704
|
// Cache the promise to avoid duplicate loads
|
|
@@ -727,7 +714,7 @@ class StowKitPack {
|
|
|
727
714
|
throw new Error(`Failed to read texture data: ${assetPath}`);
|
|
728
715
|
}
|
|
729
716
|
// Load as KTX2
|
|
730
|
-
return await this.loadKTX2Texture(data);
|
|
717
|
+
return await this.loadKTX2Texture(data, assetPath);
|
|
731
718
|
})();
|
|
732
719
|
this.textureCache.set(assetPath, loadPromise);
|
|
733
720
|
return loadPromise;
|
|
@@ -1048,19 +1035,15 @@ class StowKitPack {
|
|
|
1048
1035
|
}
|
|
1049
1036
|
return Array.from(uniqueTextures);
|
|
1050
1037
|
}
|
|
1051
|
-
async loadKTX2Texture(data) {
|
|
1052
|
-
|
|
1038
|
+
async loadKTX2Texture(data, textureName) {
|
|
1039
|
+
// Create blob URL - KTX2Loader requires a URL, can't use ArrayBuffer directly
|
|
1053
1040
|
const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
1054
1041
|
const blob = new Blob([arrayBuffer]);
|
|
1055
1042
|
const url = URL.createObjectURL(blob);
|
|
1056
|
-
reader.PerfLogger.log(`[Perf] KTX2/Basis texture size: ${data.byteLength} bytes`);
|
|
1057
1043
|
try {
|
|
1058
1044
|
return await new Promise((resolve, reject) => {
|
|
1059
|
-
const loadStart = performance.now();
|
|
1060
1045
|
this.ktx2Loader.load(url, (texture) => {
|
|
1061
1046
|
URL.revokeObjectURL(url);
|
|
1062
|
-
const loadEnd = performance.now();
|
|
1063
|
-
reader.PerfLogger.log(`[Perf] Basis transcode: ${(loadEnd - loadStart).toFixed(2)}ms (resolution: ${texture.image?.width || '?'}x${texture.image?.height || '?'})`);
|
|
1064
1047
|
texture.needsUpdate = true;
|
|
1065
1048
|
resolve(texture);
|
|
1066
1049
|
}, undefined, (error) => {
|