@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 +361 -365
- package/dist/stowkit-three-loader.esm.js.map +1 -1
- package/dist/stowkit-three-loader.js.map +1 -1
- package/package.json +36 -36
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.
|