@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 = await this.createGeometry(geoInfo, dataBlob, dracoLoader);
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 a single mesh from all geometries
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 = await this.createGeometry(geoInfo, dataBlob, dracoLoader);
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
- reader.PerfLogger.log(`[Perf] Load textures: ${(performance.now() - textureStart).toFixed(2)}ms (${textureNames.length > 0 ? textureNames.join(', ') : 'none'})`);
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
- reader.PerfLogger.log(`[Perf] Build scene: ${(performance.now() - buildStart).toFixed(2)}ms`);
705
- reader.PerfLogger.log(`[Perf] ===== Total mesh load: ${(performance.now() - totalStart).toFixed(2)}ms =====`);
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
- performance.now();
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) => {