@stowkit/three-loader 0.1.5 → 0.1.7

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