@stowkit/three-loader 0.1.6 → 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.
Files changed (2) hide show
  1. package/README.md +639 -639
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,640 +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
- // 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/)
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/)
640
640
  - [Basis Universal](https://github.com/BinomialLLC/basis_universal)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stowkit/three-loader",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Three.js loader for StowKit asset packs",
5
5
  "main": "dist/stowkit-three-loader.js",
6
6
  "module": "dist/stowkit-three-loader.esm.js",