@stowkit/three-loader 0.1.19 → 0.1.20

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,365 +1,361 @@
1
- # @stowkit/three-loader
2
-
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
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @stowkit/three-loader three
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ```typescript
14
- import { StowKitLoader, AssetType } from '@stowkit/three-loader';
15
-
16
- const pack = await StowKitLoader.load('assets.stow');
17
- // from memory
18
- const packFromMemory = await StowKitLoader.loadFromMemory(someData);
19
-
20
- const mesh = await pack.loadMesh('character');
21
- scene.add(mesh);
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
- }
34
- ```
35
-
36
- ## Features
37
-
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
46
-
47
- ## API Reference
48
-
49
- ### Asset Manifest
50
-
51
- #### `pack.listAssets(): AssetListItem[]`
52
-
53
- Get the complete manifest of all assets in the pack.
54
-
55
- ```typescript
56
- const pack = await StowKitLoader.load('assets.stow');
57
- const manifest = pack.listAssets();
58
-
59
- console.log(`Pack contains ${manifest.length} assets`);
60
-
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}`);
66
- });
67
-
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);
74
-
75
- console.log(`${meshes.length} meshes, ${textures.length} textures, ${animations.length} animations`);
76
- ```
77
-
78
- **AssetListItem structure:**
79
- ```typescript
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
- }
93
- ```
94
-
95
- #### `pack.getAssetCount(): number`
96
-
97
- Get total number of assets in the pack.
98
-
99
- ```typescript
100
- const count = pack.getAssetCount();
101
- console.log(`Pack has ${count} assets`);
102
- ```
103
-
104
- #### `pack.getAssetInfo(index: number): AssetInfo | null`
105
-
106
- Get detailed info about a specific asset.
107
-
108
- ```typescript
109
- const info = pack.getAssetInfo(5);
110
- if (info) {
111
- console.log(`Type: ${info.type}, Size: ${info.data_size} bytes`);
112
- }
113
- ```
114
-
115
- ### Loading Assets
116
-
117
- #### Static Meshes
118
-
119
- ```typescript
120
- // Load by string ID
121
- const mesh = await pack.loadMesh('models/building.mesh');
122
- scene.add(mesh);
123
-
124
- // Load by index
125
- const mesh = await pack.loadMeshByIndex(5);
126
- scene.add(mesh);
127
- ```
128
-
129
- Returns a `THREE.Group` containing the mesh hierarchy with materials and textures applied.
130
-
131
- #### Skinned Meshes
132
-
133
- ```typescript
134
- // Load by string ID
135
- const character = await pack.loadSkinnedMesh('characters/player.skinned');
136
- scene.add(character);
137
-
138
- // Load by index
139
- const character = await pack.loadSkinnedMeshByIndex(8);
140
- scene.add(character);
141
- ```
142
-
143
- Returns a `THREE.Group` containing the skinned mesh with skeleton and bones in bind pose.
144
-
145
- #### Animations
146
-
147
- ```typescript
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
- }
167
- ```
168
-
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
174
-
175
- #### Textures
176
-
177
- ```typescript
178
- // Load by string ID
179
- const texture = await pack.loadTexture('textures/wood.ktx2');
180
- material.map = texture;
181
-
182
- // Load by index
183
- const texture = await pack.loadTextureByIndex(2);
184
- ```
185
-
186
- Returns a `THREE.CompressedTexture` (KTX2/Basis Universal format).
187
-
188
- #### Audio
189
-
190
- ```typescript
191
- const listener = new THREE.AudioListener();
192
- camera.add(listener);
193
-
194
- // Load by string ID
195
- const bgm = await pack.loadAudio('sounds/music.ogg', listener);
196
- bgm.setLoop(true);
197
- bgm.play();
198
-
199
- // Or create HTML5 audio element for preview
200
- const audioElement = await pack.createAudioPreview(3);
201
- document.body.appendChild(audioElement);
202
- ```
203
-
204
- ### Metadata Helpers
205
-
206
- All metadata is parsed in WASM for performance and reliability.
207
-
208
- #### Animation Metadata
209
-
210
- ```typescript
211
- const animData = pack.getAnimationMetadata(index);
212
-
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
219
- ```
220
-
221
- #### Audio Metadata
222
-
223
- ```typescript
224
- const audioData = pack.getAudioMetadata('sounds/bgm.ogg');
225
-
226
- console.log(audioData.sampleRate); // 44100
227
- console.log(audioData.channels); // 2 (stereo)
228
- console.log(audioData.durationMs); // 180000 (3 minutes)
229
- ```
230
-
231
- #### Texture Metadata
232
-
233
- ```typescript
234
- const texData = pack.getTextureMetadata(index);
235
-
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)
240
- ```
241
-
242
- ### Asset Discovery
243
-
244
- ```typescript
245
- // List all assets in pack
246
- const assets = pack.listAssets();
247
-
248
- assets.forEach(asset => {
249
- console.log(`[${asset.index}] ${asset.name} (${getTypeName(asset.type)})`);
250
- });
251
-
252
- // Find asset by path
253
- const index = pack.reader.findAssetByPath('models/character.mesh');
254
-
255
- // Get asset count
256
- const count = pack.getAssetCount();
257
-
258
- // Get asset info
259
- const info = pack.getAssetInfo(5);
260
- ```
261
-
262
- ## Complete Example
263
-
264
- ```typescript
265
- import * as THREE from 'three';
266
- import { StowKitLoader } from '@stowkit/three-loader';
267
-
268
- // Setup Three.js
269
- const scene = new THREE.Scene();
270
- const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
271
- const renderer = new THREE.WebGLRenderer();
272
- const clock = new THREE.Clock();
273
-
274
- const listener = new THREE.AudioListener();
275
- camera.add(listener);
276
-
277
- // Load pack
278
- const pack = await StowKitLoader.load('game.stow');
279
-
280
- // Load static mesh
281
- const environment = await pack.loadMesh('levels/level1.mesh');
282
- scene.add(environment);
283
-
284
- // Load skinned character
285
- const character = await pack.loadSkinnedMesh('characters/player.skinned');
286
- character.position.set(0, 0, 0);
287
- scene.add(character);
288
-
289
- // Load and play animation
290
- const { mixer } = await pack.loadAnimation(character, 'animations/idle.anim');
291
-
292
- // Load audio
293
- const bgm = await pack.loadAudio('sounds/theme.ogg', listener);
294
- bgm.setLoop(true);
295
- bgm.play();
296
-
297
- // Animation loop
298
- function animate() {
299
- requestAnimationFrame(animate);
300
- mixer.update(clock.getDelta());
301
- renderer.render(scene, camera);
302
- }
303
-
304
- animate();
305
- ```
306
-
307
- ## Asset Types
308
-
309
- ```typescript
310
- import { AssetType } from '@stowkit/three-loader';
311
- ```
312
-
313
- | Enum | Value | Description |
314
- |------|-------|-------------|
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 |
321
-
322
- ## Public Folder Setup
323
-
324
- The package automatically copies required files on install:
325
-
326
- ```
327
- public/
328
- └── stowkit/
329
- ├── stowkit_reader.wasm # WASM reader module
330
- ├── basis/ # Basis Universal transcoder
331
- │ ├── basis_transcoder.js
332
- │ └── basis_transcoder.wasm
333
- └── draco/ # Draco decoder
334
- ├── draco_decoder.js
335
- ├── draco_decoder.wasm
336
- └── draco_wasm_wrapper.js
337
- ```
338
-
339
- Just run `npm install` and everything is set up automatically!
340
-
341
- ## Performance
342
-
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
348
-
349
- ## Troubleshooting
350
-
351
- ### Animations appear broken/offset from origin
352
-
353
- Make sure your `.stow` file was packed with the latest packer that writes bone parent indices correctly.
354
-
355
- ### Textures not loading
356
-
357
- Ensure `/stowkit/basis/` folder contains the Basis Universal transcoder files (auto-copied on install).
358
-
359
- ### Audio not playing
360
-
361
- Make sure you've created an `AudioListener` and attached it to your camera.
362
-
363
- ## License
364
-
365
- MIT
1
+ # @stowkit/three-loader
2
+
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
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @stowkit/three-loader three
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { StowKitLoader, AssetType } from '@stowkit/three-loader';
15
+
16
+ const pack = await StowKitLoader.load('assets.stow');
17
+ // from memory
18
+ const packFromMemory = await StowKitLoader.loadFromMemory(someData);
19
+
20
+ const mesh = await pack.loadMesh('character');
21
+ scene.add(mesh);
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
+ }
34
+ ```
35
+
36
+ ## Features
37
+
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
46
+
47
+ ## API Reference
48
+
49
+ ### Asset Manifest
50
+
51
+ #### `pack.listAssets(): AssetListItem[]`
52
+
53
+ Get the complete manifest of all assets in the pack.
54
+
55
+ ```typescript
56
+ const pack = await StowKitLoader.load('assets.stow');
57
+ const manifest = pack.listAssets();
58
+
59
+ console.log(`Pack contains ${manifest.length} assets`);
60
+
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}`);
66
+ });
67
+
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);
74
+
75
+ console.log(`${meshes.length} meshes, ${textures.length} textures, ${animations.length} animations`);
76
+ ```
77
+
78
+ **AssetListItem structure:**
79
+ ```typescript
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
+ }
93
+ ```
94
+
95
+ #### `pack.getAssetCount(): number`
96
+
97
+ Get total number of assets in the pack.
98
+
99
+ ```typescript
100
+ const count = pack.getAssetCount();
101
+ console.log(`Pack has ${count} assets`);
102
+ ```
103
+
104
+ #### `pack.getAssetInfo(index: number): AssetInfo | null`
105
+
106
+ Get detailed info about a specific asset.
107
+
108
+ ```typescript
109
+ const info = pack.getAssetInfo(5);
110
+ if (info) {
111
+ console.log(`Type: ${info.type}, Size: ${info.data_size} bytes`);
112
+ }
113
+ ```
114
+
115
+ ### Loading Assets
116
+
117
+ #### Static Meshes
118
+
119
+ ```typescript
120
+ // Load by string ID
121
+ const mesh = await pack.loadMesh('models/building.mesh');
122
+ scene.add(mesh);
123
+
124
+ // Load by index
125
+ const mesh = await pack.loadMeshByIndex(5);
126
+ scene.add(mesh);
127
+ ```
128
+
129
+ Returns a `THREE.Group` containing the mesh hierarchy with materials and textures applied.
130
+
131
+ #### Skinned Meshes
132
+
133
+ ```typescript
134
+ // Load by string ID
135
+ const character = await pack.loadSkinnedMesh('characters/player.skinned');
136
+ scene.add(character);
137
+
138
+ // Load by index
139
+ const character = await pack.loadSkinnedMeshByIndex(8);
140
+ scene.add(character);
141
+ ```
142
+
143
+ Returns a `THREE.Group` containing the skinned mesh with skeleton and bones in bind pose.
144
+
145
+ #### Animations
146
+
147
+ ```typescript
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
+ }
167
+ ```
168
+
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
174
+
175
+ #### Textures
176
+
177
+ ```typescript
178
+ // Load by string ID
179
+ const texture = await pack.loadTexture('textures/wood.ktx2');
180
+ material.map = texture;
181
+
182
+ // Load by index
183
+ const texture = await pack.loadTextureByIndex(2);
184
+ ```
185
+
186
+ Returns a `THREE.CompressedTexture` (KTX2/Basis Universal format).
187
+
188
+ #### Audio
189
+
190
+ ```typescript
191
+ const listener = new THREE.AudioListener();
192
+ camera.add(listener);
193
+
194
+ // Load by string ID
195
+ const bgm = await pack.loadAudio('sounds/music.ogg', listener);
196
+ bgm.setLoop(true);
197
+ bgm.play();
198
+
199
+ // Or create HTML5 audio element for preview
200
+ const audioElement = await pack.createAudioPreview(3);
201
+ document.body.appendChild(audioElement);
202
+ ```
203
+
204
+ ### Metadata Helpers
205
+
206
+ All metadata is parsed in WASM for performance and reliability.
207
+
208
+ #### Animation Metadata
209
+
210
+ ```typescript
211
+ const animData = pack.getAnimationMetadata(index);
212
+
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
219
+ ```
220
+
221
+ #### Audio Metadata
222
+
223
+ ```typescript
224
+ const audioData = pack.getAudioMetadata('sounds/bgm.ogg');
225
+
226
+ console.log(audioData.sampleRate); // 44100
227
+ console.log(audioData.channels); // 2 (stereo)
228
+ console.log(audioData.durationMs); // 180000 (3 minutes)
229
+ ```
230
+
231
+ #### Texture Metadata
232
+
233
+ ```typescript
234
+ const texData = pack.getTextureMetadata(index);
235
+
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)
240
+ ```
241
+
242
+ ### Asset Discovery
243
+
244
+ ```typescript
245
+ // List all assets in pack
246
+ const assets = pack.listAssets();
247
+
248
+ assets.forEach(asset => {
249
+ console.log(`[${asset.index}] ${asset.name} (${getTypeName(asset.type)})`);
250
+ });
251
+
252
+ // Find asset by path
253
+ const index = pack.reader.findAssetByPath('models/character.mesh');
254
+
255
+ // Get asset count
256
+ const count = pack.getAssetCount();
257
+
258
+ // Get asset info
259
+ const info = pack.getAssetInfo(5);
260
+ ```
261
+
262
+ ## Complete Example
263
+
264
+ ```typescript
265
+ import * as THREE from 'three';
266
+ import { StowKitLoader } from '@stowkit/three-loader';
267
+
268
+ // Setup Three.js
269
+ const scene = new THREE.Scene();
270
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
271
+ const renderer = new THREE.WebGLRenderer();
272
+ const clock = new THREE.Clock();
273
+
274
+ const listener = new THREE.AudioListener();
275
+ camera.add(listener);
276
+
277
+ // Load pack
278
+ const pack = await StowKitLoader.load('game.stow');
279
+
280
+ // Load static mesh
281
+ const environment = await pack.loadMesh('levels/level1.mesh');
282
+ scene.add(environment);
283
+
284
+ // Load skinned character
285
+ const character = await pack.loadSkinnedMesh('characters/player.skinned');
286
+ character.position.set(0, 0, 0);
287
+ scene.add(character);
288
+
289
+ // Load and play animation
290
+ const { mixer } = await pack.loadAnimation(character, 'animations/idle.anim');
291
+
292
+ // Load audio
293
+ const bgm = await pack.loadAudio('sounds/theme.ogg', listener);
294
+ bgm.setLoop(true);
295
+ bgm.play();
296
+
297
+ // Animation loop
298
+ function animate() {
299
+ requestAnimationFrame(animate);
300
+ mixer.update(clock.getDelta());
301
+ renderer.render(scene, camera);
302
+ }
303
+
304
+ animate();
305
+ ```
306
+
307
+ ## Asset Types
308
+
309
+ ```typescript
310
+ import { AssetType } from '@stowkit/three-loader';
311
+ ```
312
+
313
+ | Enum | Value | Description |
314
+ |------|-------|-------------|
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 |
321
+
322
+ ## Public Folder Setup
323
+
324
+ The package automatically copies required files on install:
325
+
326
+ ```
327
+ public/
328
+ └── stowkit/
329
+ ├── stowkit_reader.wasm # WASM reader module
330
+ ├── basis/ # Basis Universal transcoder
331
+ │ ├── basis_transcoder.js
332
+ │ └── basis_transcoder.wasm
333
+ └── draco/ # Draco decoder
334
+ ├── draco_decoder.js
335
+ ├── draco_decoder.wasm
336
+ └── draco_wasm_wrapper.js
337
+ ```
338
+
339
+ Just run `npm install` and everything is set up automatically!
340
+
341
+ ## Performance
342
+
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
348
+
349
+ ## Troubleshooting
350
+
351
+ ### Animations appear broken/offset from origin
352
+
353
+ Make sure your `.stow` file was packed with the latest packer that writes bone parent indices correctly.
354
+
355
+ ### Textures not loading
356
+
357
+ Ensure `/stowkit/basis/` folder contains the Basis Universal transcoder files (auto-copied on install).
358
+
359
+ ### Audio not playing
360
+
361
+ Make sure you've created an `AudioListener` and attached it to your camera.