@stowkit/three-loader 0.1.14 → 0.1.16

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