@stowkit/three-loader 0.1.2 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @stowkit/three-loader
2
2
 
3
- Three.js loader for StowKit asset packs. Provides an easy-to-use API for loading meshes, textures, and audio from `.stow` files into Three.js scenes.
3
+ Three.js loader for StowKit asset packs. Provides a simple, high-level API for loading meshes, textures, and audio from `.stow` files into Three.js applications.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,156 +8,624 @@ Three.js loader for StowKit asset packs. Provides an easy-to-use API for loading
8
8
  npm install @stowkit/three-loader three
9
9
  ```
10
10
 
11
- ## Usage
12
-
13
- ### Basic Mesh Loading
11
+ ## Quick Start
14
12
 
15
13
  ```typescript
16
14
  import * as THREE from 'three';
17
15
  import { StowKitLoader } from '@stowkit/three-loader';
18
16
 
17
+ // Create loader (auto-configured with defaults)
19
18
  const loader = new StowKitLoader();
20
- loader.setTranscoderPath('/basis/'); // Path to Basis Universal transcoder
21
19
 
22
- // Load a mesh
23
- const scene = await loader.loadMesh('assets.stow', 'models/character.mesh');
20
+ // Optional: Set custom decoder paths
21
+ // loader.setDracoDecoderPath('/custom-draco-path/');
22
+ // loader.setTranscoderPath('/custom-basis-path/');
23
+
24
+ // Open a .stow pack file
25
+ await loader.openPack('assets.stow');
26
+
27
+ // Load a mesh with automatic textures and materials
28
+ const scene = await loader.loadMesh('', 'models/character.mesh');
24
29
  threeScene.add(scene);
25
30
  ```
26
31
 
27
- ### Loading Multiple Assets from Same Pack
32
+ ## Features
33
+
34
+ - ✅ **Zero configuration** - Works out of the box with sensible defaults
35
+ - ✅ **Automatic texture loading** - Materials reference textures by ID, loader handles everything
36
+ - ✅ **Material system** - Full support for material schemas, properties, and assignments
37
+ - ✅ **KTX2/Basis Universal** - GPU-compressed textures with automatic transcoding
38
+ - ✅ **Scene hierarchy** - Preserves transforms, parent-child relationships, and node structure
39
+ - ✅ **Multi-material meshes** - Supports meshes with multiple materials/submeshes
40
+ - ✅ **Type-safe** - Full TypeScript definitions included
41
+
42
+ ## API Reference
43
+
44
+ ### Constructor
45
+
46
+ ```typescript
47
+ new StowKitLoader(manager?: THREE.LoadingManager, parameters?: StowKitLoaderParameters)
48
+ ```
49
+
50
+ **Parameters (all optional):**
51
+ ```typescript
52
+ interface StowKitLoaderParameters {
53
+ transcoderPath?: string; // Path to Basis Universal transcoder (default: '/basis/')
54
+ wasmPath?: string; // Path to WASM reader module (default: '/stowkit_reader.wasm')
55
+ reader?: StowKitReader; // Custom reader instance (advanced)
56
+ }
57
+ ```
28
58
 
59
+ **Example:**
29
60
  ```typescript
61
+ // Default configuration
30
62
  const loader = new StowKitLoader();
31
- loader.setTranscoderPath('/basis/');
32
63
 
33
- // Open pack once
64
+ // Custom paths
65
+ const loader = new StowKitLoader(undefined, {
66
+ transcoderPath: '/my-basis-path/',
67
+ wasmPath: '/custom/stowkit_reader.wasm'
68
+ });
69
+ ```
70
+
71
+ ### Opening Packs
72
+
73
+ #### `openPack(url: string): Promise<void>`
74
+
75
+ Open a .stow pack file from a URL. Call this once, then load multiple assets.
76
+
77
+ ```typescript
34
78
  await loader.openPack('assets.stow');
79
+ ```
80
+
81
+ #### `open(fileOrBuffer: ArrayBuffer | File): Promise<boolean>`
35
82
 
36
- // Load multiple assets (no need to pass URL again)
37
- const character = await loader.loadMesh('', 'models/character.mesh');
38
- const weapon = await loader.loadMesh('', 'models/weapon.mesh');
39
- const texture = await loader.loadTexture('', 'textures/diffuse.ktx2');
83
+ Open a pack from a File or ArrayBuffer.
40
84
 
41
- threeScene.add(character);
42
- threeScene.add(weapon);
85
+ ```typescript
86
+ // From file input
87
+ const file = input.files[0];
88
+ await loader.open(file);
89
+
90
+ // From fetch
91
+ const response = await fetch('assets.stow');
92
+ const buffer = await response.arrayBuffer();
93
+ await loader.open(buffer);
43
94
  ```
44
95
 
45
- ### Loading Textures
96
+ ### Loading Meshes
97
+
98
+ #### `loadMesh(url: string, assetPath: string, onLoad?, onProgress?, onError?): Promise<THREE.Group>`
99
+
100
+ Load a mesh asset with automatic texture and material loading.
46
101
 
102
+ **Parameters:**
103
+ - `url` - URL to .stow file (empty string if already opened)
104
+ - `assetPath` - Canonical path to mesh within pack
105
+ - `onLoad` - Optional callback when loading completes
106
+ - `onProgress` - Optional progress callback
107
+ - `onError` - Optional error callback
108
+
109
+ **Returns:** `THREE.Group` containing the mesh hierarchy
110
+
111
+ **Example:**
47
112
  ```typescript
48
- const loader = new StowKitLoader();
49
- loader.setTranscoderPath('/basis/');
113
+ // Simple async/await
114
+ const mesh = await loader.loadMesh('', 'models/character.mesh');
115
+ scene.add(mesh);
116
+
117
+ // With callbacks (Three.js style)
118
+ loader.loadMesh('assets.stow', 'models/tree.mesh',
119
+ (scene) => {
120
+ threeScene.add(scene);
121
+ },
122
+ (progress) => console.log('Loading...', progress),
123
+ (error) => console.error('Failed:', error)
124
+ );
125
+ ```
126
+
127
+ **What you get:**
128
+ - Complete scene hierarchy with nodes
129
+ - All geometries (vertices, normals, UVs, indices)
130
+ - Materials with properties applied (colors, etc.)
131
+ - Textures automatically loaded and applied to materials
132
+ - Transforms (position, rotation, scale) preserved
133
+
134
+ ### Loading Textures
135
+
136
+ #### `loadTexture(url: string, assetPath: string, onLoad?, onProgress?, onError?): Promise<THREE.CompressedTexture>`
50
137
 
51
- const texture = await loader.loadTexture('assets.stow', 'textures/wood.ktx2');
138
+ Load a texture asset (KTX2 format).
139
+
140
+ ```typescript
141
+ const texture = await loader.loadTexture('', 'textures/wood.ktx2');
52
142
  material.map = texture;
53
143
  material.needsUpdate = true;
54
144
  ```
55
145
 
146
+ #### `loadTextureByIndex(index: number, onLoad?, onProgress?, onError?): Promise<THREE.CompressedTexture>`
147
+
148
+ Load a texture by its asset index (when you don't have the path).
149
+
150
+ ```typescript
151
+ const texture = await loader.loadTextureByIndex(5);
152
+ ```
153
+
56
154
  ### Loading Audio
57
155
 
156
+ #### `loadAudio(url: string, assetPath: string, listener: THREE.AudioListener, onLoad?, onProgress?, onError?): Promise<THREE.Audio>`
157
+
158
+ Load an audio asset as a Three.js Audio object.
159
+
58
160
  ```typescript
59
161
  const listener = new THREE.AudioListener();
60
162
  camera.add(listener);
61
163
 
62
- const loader = new StowKitLoader();
63
- const audio = await loader.loadAudio('assets.stow', 'sounds/bgm.ogg', listener);
164
+ const audio = await loader.loadAudio('', 'sounds/bgm.ogg', listener);
165
+ audio.setLoop(true);
64
166
  audio.play();
65
167
  ```
66
168
 
67
- ### With Callbacks (Three.js style)
169
+ #### `createAudioPreview(index: number): Promise<HTMLAudioElement>`
170
+
171
+ Create an HTML5 audio element for previewing audio (simpler than Three.js Audio).
172
+
173
+ ```typescript
174
+ const audioElement = await loader.createAudioPreview(index);
175
+ audioElement.autoplay = true;
176
+ container.appendChild(audioElement);
177
+ ```
178
+
179
+ ### Material Information
180
+
181
+ #### `loadMaterialSchema(assetPath: string): object | null`
182
+
183
+ Load material schema definition.
184
+
185
+ ```typescript
186
+ const schema = loader.loadMaterialSchema('shaders/DefaultShader.stowschema');
187
+
188
+ console.log(schema.schemaName); // "Default Shader"
189
+ console.log(schema.fields); // Array of field definitions
190
+
191
+ schema.fields.forEach(field => {
192
+ console.log(`${field.name}: ${field.type}`);
193
+ // mainTex: Texture
194
+ // tint: Color
195
+ // roughness: Float
196
+ });
197
+ ```
198
+
199
+ #### `loadMaterialSchemaByIndex(index: number): object | null`
200
+
201
+ Load material schema by asset index.
202
+
203
+ ```typescript
204
+ const schema = loader.loadMaterialSchemaByIndex(6);
205
+ ```
206
+
207
+ **Schema Structure:**
208
+ ```typescript
209
+ {
210
+ stringId: string; // Schema canonical path
211
+ schemaName: string; // Human-readable name
212
+ fields: Array<{
213
+ name: string; // Field name (e.g., "mainTex", "tint")
214
+ type: 'Texture' | 'Color' | 'Float' | 'Vec2' | 'Vec3' | 'Vec4' | 'Int';
215
+ previewFlag: 'None' | 'MainTex' | 'Tint';
216
+ defaultValue: [number, number, number, number];
217
+ }>;
218
+ }
219
+ ```
220
+
221
+ #### `getMeshMaterials(assetPath: string): array | null`
222
+
223
+ Get material information from a mesh (properties and texture references).
68
224
 
69
225
  ```typescript
70
- loader.loadMesh(
71
- 'assets.stow',
72
- 'models/tree.mesh',
73
- (scene) => {
74
- // onLoad
75
- threeScene.add(scene);
76
- },
77
- (progress) => {
78
- // onProgress
79
- console.log('Loading...', progress);
80
- },
81
- (error) => {
82
- // onError
83
- console.error('Failed to load:', error);
226
+ const materials = loader.getMeshMaterials('models/character.mesh');
227
+
228
+ materials.forEach(mat => {
229
+ console.log(`Material: ${mat.name}`);
230
+ console.log(`Schema: ${mat.schemaId}`);
231
+
232
+ mat.properties.forEach(prop => {
233
+ if (prop.textureId) {
234
+ console.log(`${prop.fieldName}: ${prop.textureId}`); // mainTex: textures/skin.ktx2
235
+ } else {
236
+ console.log(`${prop.fieldName}:`, prop.value); // tint: [1, 0.8, 0.6, 1]
84
237
  }
85
- );
238
+ });
239
+ });
86
240
  ```
87
241
 
88
- ### Getting Metadata
242
+ ### Metadata Helpers
243
+
244
+ #### `getTextureMetadata(assetPath: string): object | null`
245
+
246
+ Get texture metadata (dimensions, format, etc.).
89
247
 
90
248
  ```typescript
91
- // Get texture info
92
- const texInfo = loader.getTextureMetadata('textures/wood.ktx2');
93
- console.log(`Texture: ${texInfo.width}x${texInfo.height}`);
249
+ const info = loader.getTextureMetadata('textures/wood.ktx2');
94
250
 
95
- // Get audio info
96
- const audioInfo = loader.getAudioMetadata('sounds/bgm.ogg');
97
- console.log(`Duration: ${audioInfo.durationMs}ms`);
251
+ console.log(`${info.width}x${info.height}`); // 1024x1024
252
+ console.log(`Channels: ${info.channels}`); // 3 (RGB) or 4 (RGBA)
253
+ console.log(`KTX2: ${info.isKtx2}`); // true
98
254
  ```
99
255
 
100
- ### Listing Assets
256
+ #### `getAudioMetadata(assetPath: string): object | null`
257
+
258
+ Get audio metadata (sample rate, duration, etc.).
259
+
260
+ ```typescript
261
+ const info = loader.getAudioMetadata('sounds/bgm.ogg');
262
+
263
+ console.log(`Sample Rate: ${info.sampleRate} Hz`);
264
+ console.log(`Channels: ${info.channels}`);
265
+ console.log(`Duration: ${info.durationMs / 1000}s`);
266
+ ```
267
+
268
+ ### Asset Listing
269
+
270
+ #### `listAssets(): array`
271
+
272
+ List all assets in the opened pack.
101
273
 
102
274
  ```typescript
103
- await loader.openPack('assets.stow');
104
275
  const assets = loader.listAssets();
105
276
 
106
277
  assets.forEach(asset => {
107
- console.log(`${asset.name} (${asset.type}): ${asset.dataSize} bytes`);
278
+ console.log(`[${asset.index}] ${asset.name}`);
279
+ console.log(` Type: ${asset.type}, Size: ${asset.dataSize} bytes`);
108
280
  });
109
281
  ```
110
282
 
111
- ## Features
283
+ #### `getAssetCount(): number`
112
284
 
113
- - **Automatic texture loading** - Materials with `mainTex` properties automatically load and apply textures
114
- - ✅ **Material schema support** - Reads tint colors, texture references, and other material properties
115
- - ✅ **KTX2/Basis Universal** - Compressed texture support with GPU transcoding
116
- - ✅ **Scene hierarchy** - Preserves node transforms and parent-child relationships
117
- - ✅ **Multi-material meshes** - Supports meshes with multiple materials/submeshes
118
- - ✅ **Type-safe** - Full TypeScript definitions
285
+ Get total number of assets.
119
286
 
120
- ## API Reference
287
+ ```typescript
288
+ const count = loader.getAssetCount();
289
+ ```
290
+
291
+ #### `getAssetInfo(index: number): object`
121
292
 
122
- ### StowKitLoader
293
+ Get detailed info about a specific asset.
123
294
 
124
- #### Constructor
125
295
  ```typescript
126
- new StowKitLoader(manager?: THREE.LoadingManager, parameters?: StowKitLoaderParameters)
296
+ const info = loader.getAssetInfo(0);
297
+ console.log(info.type, info.data_size, info.metadata_size);
127
298
  ```
128
299
 
129
- #### Methods
300
+ ### Utility Methods
130
301
 
131
- ##### `setTranscoderPath(path: string): this`
132
- Set the path to the Basis Universal transcoder (required for KTX2 textures).
302
+ #### `readAssetData(index: number): Uint8Array | null`
133
303
 
134
- ##### `detectSupport(renderer: THREE.WebGLRenderer): this`
135
- Detect GPU compressed texture format support.
304
+ Read raw asset data by index.
136
305
 
137
- ##### `openPack(url: string): Promise<void>`
138
- Open a .stow pack file. Call this once when loading multiple assets from the same pack.
306
+ ```typescript
307
+ const data = loader.readAssetData(5);
308
+ ```
139
309
 
140
- ##### `loadMesh(url, assetPath, onLoad?, onProgress?, onError?): Promise<THREE.Group>`
141
- Load a mesh asset. Returns a Three.js Group containing the mesh hierarchy.
310
+ #### `readAssetMetadata(index: number): Uint8Array | null`
142
311
 
143
- ##### `loadTexture(url, assetPath, onLoad?, onProgress?, onError?): Promise<THREE.CompressedTexture>`
144
- Load a texture asset. Returns a Three.js CompressedTexture.
312
+ Read raw asset metadata by index.
145
313
 
146
- ##### `loadAudio(url, assetPath, listener, onLoad?, onProgress?, onError?): Promise<THREE.Audio>`
147
- Load an audio asset. Returns a Three.js Audio object.
314
+ ```typescript
315
+ const metadata = loader.readAssetMetadata(5);
316
+ ```
148
317
 
149
- ##### `getTextureMetadata(assetPath): object | null`
150
- Get texture metadata (width, height, format, etc.).
318
+ #### `dispose(): void`
151
319
 
152
- ##### `getAudioMetadata(assetPath): object | null`
153
- Get audio metadata (sample rate, channels, duration).
320
+ Clean up resources and free memory.
154
321
 
155
- ##### `listAssets(): array`
156
- List all assets in the opened pack.
322
+ ```typescript
323
+ loader.dispose();
324
+ ```
157
325
 
158
- ##### `dispose(): void`
159
- Clean up resources and free memory.
326
+ ## Complete Example
327
+
328
+ ```typescript
329
+ import * as THREE from 'three';
330
+ import { StowKitLoader } from '@stowkit/three-loader';
331
+
332
+ // Setup Three.js scene
333
+ const scene = new THREE.Scene();
334
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
335
+ const renderer = new THREE.WebGLRenderer();
336
+ renderer.setSize(window.innerWidth, window.innerHeight);
337
+ document.body.appendChild(renderer.domElement);
338
+
339
+ // Setup audio
340
+ const listener = new THREE.AudioListener();
341
+ camera.add(listener);
342
+
343
+ // Initialize loader
344
+ const loader = new StowKitLoader();
345
+
346
+ // Load assets
347
+ async function loadAssets() {
348
+ await loader.openPack('game-assets.stow');
349
+
350
+ // Load main character
351
+ const character = await loader.loadMesh('', 'models/player.mesh');
352
+ character.position.set(0, 0, 0);
353
+ scene.add(character);
354
+
355
+ // Load environment
356
+ const environment = await loader.loadMesh('', 'models/level1.mesh');
357
+ scene.add(environment);
358
+
359
+ // Load background music
360
+ const bgm = await loader.loadAudio('', 'audio/theme.ogg', listener);
361
+ bgm.setLoop(true);
362
+ bgm.setVolume(0.5);
363
+ bgm.play();
364
+
365
+ // Log what was loaded
366
+ console.log(`Loaded ${loader.getAssetCount()} assets`);
367
+
368
+ // Start rendering
369
+ animate();
370
+ }
371
+
372
+ function animate() {
373
+ requestAnimationFrame(animate);
374
+ renderer.render(scene, camera);
375
+ }
376
+
377
+ loadAssets();
378
+ ```
379
+
380
+ ## Material System
381
+
382
+ The loader automatically handles material assignments:
383
+
384
+ ```typescript
385
+ // When you load a mesh, materials are created with:
386
+ // 1. Colors from 'tint' properties
387
+ // 2. Textures from 'mainTex', 'normalMap', etc.
388
+ // 3. Other properties (roughness, metallic, etc.)
389
+
390
+ const mesh = await loader.loadMesh('', 'models/character.mesh');
391
+
392
+ // Materials are automatically applied to geometries based on material indices
393
+ // Textures are automatically loaded by their string IDs and applied to materials
394
+ // No manual texture loading needed!
395
+ ```
396
+
397
+ **Supported material properties:**
398
+ - `mainTex` / `diffuse` / `albedo` → `material.map`
399
+ - `tint` / `color` → `material.color`
400
+ - `normalMap` → `material.normalMap`
401
+ - `metallicMap` → `material.metalnessMap`
402
+ - `roughnessMap` → `material.roughnessMap`
403
+
404
+ ## Advanced Usage
405
+
406
+ ### Multiple Assets from Same Pack
407
+
408
+ ```typescript
409
+ // Open once
410
+ await loader.openPack('assets.stow');
411
+
412
+ // Load many (no URL needed)
413
+ const mesh1 = await loader.loadMesh('', 'models/char1.mesh');
414
+ const mesh2 = await loader.loadMesh('', 'models/char2.mesh');
415
+ const tex1 = await loader.loadTexture('', 'textures/t1.ktx2');
416
+ const tex2 = await loader.loadTexture('', 'textures/t2.ktx2');
417
+ ```
418
+
419
+ ### Inspect Before Loading
420
+
421
+ ```typescript
422
+ await loader.openPack('assets.stow');
423
+
424
+ // List all assets
425
+ const assets = loader.listAssets();
426
+ const meshes = assets.filter(a => a.type === 1); // Type 1 = Static Mesh
427
+ const textures = assets.filter(a => a.type === 2); // Type 2 = Texture
428
+
429
+ // Get material info before loading
430
+ const materials = loader.getMeshMaterials('models/character.mesh');
431
+ materials.forEach(mat => {
432
+ console.log(`Material "${mat.name}" needs:`, mat.properties.map(p => p.textureId).filter(Boolean));
433
+ });
434
+
435
+ // Preload textures
436
+ for (const mat of materials) {
437
+ for (const prop of mat.properties) {
438
+ if (prop.textureId) {
439
+ await loader.loadTexture('', prop.textureId);
440
+ }
441
+ }
442
+ }
443
+
444
+ // Now load the mesh (textures already cached)
445
+ const mesh = await loader.loadMesh('', 'models/character.mesh');
446
+ ```
447
+
448
+ ### Error Handling
449
+
450
+ ```typescript
451
+ try {
452
+ const mesh = await loader.loadMesh('assets.stow', 'models/character.mesh');
453
+ scene.add(mesh);
454
+ } catch (error) {
455
+ console.error('Failed to load mesh:', error.message);
456
+ }
457
+
458
+ // Or with callbacks
459
+ loader.loadMesh('assets.stow', 'models/tree.mesh',
460
+ (scene) => threeScene.add(scene),
461
+ undefined,
462
+ (error) => console.error('Error:', error)
463
+ );
464
+ ```
465
+
466
+ ## Asset Types
467
+
468
+ | Type | Value | Description |
469
+ |------|-------|-------------|
470
+ | Static Mesh | 1 | 3D models with geometry, materials, and hierarchy |
471
+ | Texture 2D | 2 | KTX2 compressed textures |
472
+ | Audio | 3 | Audio files (OGG, MP3, etc.) |
473
+ | Material Schema | 4 | Material template definitions |
474
+
475
+ ## File Structure
476
+
477
+ `.stow` files contain:
478
+ - **File Header** - Magic number, version, asset count
479
+ - **Asset Data** - Binary geometry, texture, audio data
480
+ - **Asset Directory** - Index of all assets with offsets
481
+ - **Metadata** - Asset-specific information (dimensions, transforms, etc.)
482
+
483
+ ## Requirements
484
+
485
+ ### Public Folder Setup
486
+
487
+ Place these files in your public folder:
488
+
489
+ ```
490
+ public/
491
+ ├── stowkit_reader.wasm # WASM reader module
492
+ ├── basis/ # Basis Universal transcoder (for textures)
493
+ │ ├── basis_transcoder.js
494
+ │ └── basis_transcoder.wasm
495
+ └── draco/ # Draco decoder (for meshes)
496
+ ├── draco_decoder.js
497
+ └── draco_decoder.wasm
498
+ ```
499
+
500
+ **Get the files:**
501
+ - `stowkit_reader.wasm` - Included with `@stowkit/reader` package
502
+ - Basis transcoder - From [three.js examples](https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/basis)
503
+ - Draco decoder - From [three.js examples](https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/draco)
504
+
505
+ ### Three.js Version
506
+
507
+ Requires Three.js r150 or later.
508
+
509
+ ```bash
510
+ npm install three@^0.150.0
511
+ ```
512
+
513
+ ## Examples
514
+
515
+ ### Loading a Character with Equipment
516
+
517
+ ```typescript
518
+ const loader = new StowKitLoader();
519
+ await loader.openPack('characters.stow');
520
+
521
+ // Load character base
522
+ const character = await loader.loadMesh('', 'characters/knight.mesh');
523
+
524
+ // Load and attach weapon
525
+ const weapon = await loader.loadMesh('', 'weapons/sword.mesh');
526
+ const rightHand = character.getObjectByName('RightHand');
527
+ if (rightHand) {
528
+ rightHand.add(weapon);
529
+ }
530
+
531
+ scene.add(character);
532
+ ```
533
+
534
+ ### Dynamic Material Updates
535
+
536
+ ```typescript
537
+ // Load mesh
538
+ const mesh = await loader.loadMesh('', 'models/car.mesh');
539
+
540
+ // Get material info
541
+ const materials = loader.getMeshMaterials('models/car.mesh');
542
+
543
+ // Find and modify specific material
544
+ mesh.traverse((child) => {
545
+ if (child instanceof THREE.Mesh) {
546
+ const mat = child.material as THREE.MeshStandardMaterial;
547
+ if (mat.name === 'Paint') {
548
+ // Change car color
549
+ mat.color.set(0xff0000); // Red
550
+ }
551
+ }
552
+ });
553
+ ```
554
+
555
+ ### Texture Swapping
556
+
557
+ ```typescript
558
+ // Load model
559
+ const character = await loader.loadMesh('', 'characters/player.mesh');
560
+
561
+ // Load alternate texture
562
+ const winterSkin = await loader.loadTexture('', 'textures/winter-skin.ktx2');
563
+
564
+ // Apply to character
565
+ character.traverse((child) => {
566
+ if (child instanceof THREE.Mesh) {
567
+ const mat = child.material as THREE.MeshStandardMaterial;
568
+ mat.map = winterSkin;
569
+ mat.needsUpdate = true;
570
+ }
571
+ });
572
+ ```
573
+
574
+ ### Audio with Spatial Sound
575
+
576
+ ```typescript
577
+ const listener = new THREE.AudioListener();
578
+ camera.add(listener);
579
+
580
+ // Load positional audio
581
+ const sound = await loader.loadAudio('', 'sounds/waterfall.ogg', listener);
582
+ const positionalAudio = new THREE.PositionalAudio(listener);
583
+ positionalAudio.setBuffer(sound.buffer);
584
+ positionalAudio.setRefDistance(20);
585
+ positionalAudio.setLoop(true);
586
+
587
+ // Attach to a mesh
588
+ waterfall.add(positionalAudio);
589
+ positionalAudio.play();
590
+ ```
591
+
592
+ ## Troubleshooting
593
+
594
+ ### Textures appear upside down
595
+
596
+ The loader automatically handles UV orientation for KTX2 textures. If textures still appear flipped, check your source files.
597
+
598
+ ### "Missing initialization with .detectSupport()"
599
+
600
+ The loader automatically calls `detectSupport()` on initialization. If you see this error, ensure you're creating the loader before loading textures.
601
+
602
+ ### "Asset not found"
603
+
604
+ Make sure:
605
+ - The pack file is opened before loading assets
606
+ - Asset paths match exactly (case-sensitive)
607
+ - The asset exists in the pack (use `listAssets()` to verify)
608
+
609
+ ### Textures not loading on meshes
610
+
611
+ Check console for texture loading messages. Common issues:
612
+ - Texture string ID in material doesn't match any asset
613
+ - Texture asset is missing from pack
614
+ - Basis transcoder files not in `/basis/` folder
615
+
616
+ ## Performance Tips
617
+
618
+ 1. **Reuse loader instance** - Create one loader, load many assets
619
+ 2. **Open pack once** - Use `openPack()` then load multiple assets
620
+ 3. **Dispose when done** - Call `loader.dispose()` to free memory
621
+ 4. **Use compressed textures** - KTX2 provides GPU-native compression
160
622
 
161
623
  ## License
162
624
 
163
625
  MIT
626
+
627
+ ## Links
628
+
629
+ - [npm package](https://www.npmjs.com/package/@stowkit/three-loader)
630
+ - [Three.js documentation](https://threejs.org/docs/)
631
+ - [Basis Universal](https://github.com/BinomialLLC/basis_universal)