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