@series-inc/stowkit-three-loader
v0.1.40
Published
Three.js loader for StowKit asset packs
Keywords
Readme
@series-inc/stowkit-three-loader
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.
Installation
npm install @series-inc/stowkit-three-loader threeQuick Start
import { StowKitLoader, AssetType } from '@series-inc/stowkit-three-loader';
const pack = await StowKitLoader.load('assets.stow');
const mesh = await pack.loadMesh('character');
scene.add(mesh);
const character = await pack.loadSkinnedMesh('player');
scene.add(character);
const { mixer } = await pack.loadAnimation(character, 'walk');
function animate() {
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}Features
- Static Meshes - Draco-compressed meshes with automatic material/texture loading
- Skinned Meshes - Skeletal meshes with bone hierarchy
- Animations - Skeletal animations with automatic mixer setup
- Textures - KTX2/Basis Universal GPU-compressed textures
- Audio - OGG/MP3 audio with Three.js Audio integration
- Multiple Packs - Load multiple .stow files simultaneously with isolated state
- WASM Parsing - All binary parsing done in WASM for performance
- Type Safe - Full TypeScript support
- Zero Config - Works out of the box
API Reference
Exports
export { StowKitLoader } from '@series-inc/stowkit-three-loader';
export { StowKitPack } from '@series-inc/stowkit-three-loader';
export { AssetMemoryCache } from '@series-inc/stowkit-three-loader';
export { AssetType, PerfLogger } from '@series-inc/stowkit-three-loader';
export type { StowKitLoaderOptions } from '@series-inc/stowkit-three-loader';
export type { MeshGeometryInfo, MaterialPropertyValue, MaterialData, Node, MeshMetadata } from '@series-inc/stowkit-three-loader';StowKitLoader
Static loader class for opening .stow pack files.
StowKitLoader.load(url, options?)
Loads a .stow pack from a URL.
const pack = await StowKitLoader.load('assets.stow', {
wasmPath: '/stowkit/stowkit_reader.wasm',
basisPath: '/stowkit/basis/',
dracoPath: '/stowkit/draco/'
});Options:
wasmPath(string) - Path to WASM reader module (default:'/stowkit/stowkit_reader.wasm')basisPath(string) - Path to Basis Universal transcoder files (default:'/stowkit/basis/')dracoPath(string) - Path to Draco decoder files (default:'/stowkit/draco/')
Returns: Promise<StowKitPack>
StowKitLoader.loadFromMemory(data, options?, cacheKey?)
Loads a .stow pack from memory.
const response = await fetch('assets.stow');
const buffer = await response.arrayBuffer();
const pack = await StowKitLoader.loadFromMemory(buffer);
// With a cache key for CDN-loaded files
const pack = await StowKitLoader.loadFromMemory(buffer, undefined, 'https://cdn.example.com/assets.stow');Parameters:
data(ArrayBuffer | Blob | File) - The.stowfile dataoptions(StowKitLoaderOptions, optional) - Loader optionscacheKey(string, optional) - Unique identifier for caching (recommended for CDN-loaded files)
Returns: Promise<StowKitPack>
StowKitPack
Represents an opened .stow pack with methods to load assets.
Asset Manifest
pack.listAssets(): AssetListItem[]
Get the complete manifest of all assets in the pack.
const pack = await StowKitLoader.load('assets.stow');
const manifest = pack.listAssets();
console.log(`Pack contains ${manifest.length} assets`);
manifest.forEach(asset => {
console.log(`[${asset.index}] ${asset.name || asset.id}`);
console.log(` Type: ${asset.type}`);
console.log(` Size: ${asset.dataSize} bytes`);
console.log(` Has Metadata: ${asset.hasMetadata}`);
});
const meshes = manifest.filter(a => a.type === AssetType.STATIC_MESH);
const textures = manifest.filter(a => a.type === AssetType.TEXTURE_2D);
const animations = manifest.filter(a => a.type === AssetType.ANIMATION_CLIP);pack.getAssetCount(): number
Get total number of assets in the pack.
pack.getAssetInfo(index: number): AssetInfo | null
Get detailed info about a specific asset.
pack.readAssetData(index: number): Uint8Array | null
Read raw binary data of an asset by index.
pack.readAssetMetadata(index: number): Uint8Array | null
Read raw metadata bytes of an asset by index.
Loading Assets
Static Meshes
// Load by string ID
const mesh = await pack.loadMesh('models/building.mesh');
scene.add(mesh);
// Load by index
const mesh = await pack.loadMeshByIndex(5);
scene.add(mesh);Returns a THREE.Group containing the mesh hierarchy with materials and textures applied.
Skinned Meshes
// Load by string ID
const character = await pack.loadSkinnedMesh('characters/player.skinned');
scene.add(character);
// Load by index
const character = await pack.loadSkinnedMeshByIndex(8);
scene.add(character);Returns a THREE.Group containing the skinned mesh with skeleton and bones in bind pose.
Animations
// Load and play animation (returns mixer, action, clip)
const { mixer, action, clip } = await pack.loadAnimation(
skinnedMeshGroup,
'animations/walk.anim'
);
// Or by index
const { mixer, action, clip } = await pack.loadAnimationByIndex(
skinnedMeshGroup,
9
);
// Update in your animation loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}The loadAnimation methods automatically:
- Create an
AnimationMixeron the correct root object - Set the animation to loop infinitely
- Start playing immediately
- Retarget bone names if they don't match exactly
- Return the mixer, action, and clip
Animation Clips (Without Playback)
// Load just the clip by string ID
const clip = await pack.loadAnimationClip('animations/walk.anim');
// Or by index
const clip = await pack.loadAnimationClipByIndex(9);Returns a THREE.AnimationClip without creating a mixer or starting playback.
Textures
// Load by string ID
const texture = await pack.loadTexture('textures/wood.ktx2');
material.map = texture;
// Load by index
const texture = await pack.loadTextureByIndex(2);Returns a THREE.CompressedTexture (KTX2/Basis Universal format). Textures are cached per-pack so loading the same texture twice returns the same instance.
Audio
const listener = new THREE.AudioListener();
camera.add(listener);
// Load by string ID
const bgm = await pack.loadAudio('sounds/music.ogg', listener);
bgm.setLoop(true);
bgm.play();
// Or create HTML5 audio element for preview
const audioElement = await pack.createAudioPreview(3);
document.body.appendChild(audioElement);Metadata Helpers
All metadata is parsed in WASM for performance and reliability.
Animation Metadata
const animData = pack.getAnimationMetadata(index);
console.log(animData.stringId); // "Clip_Walking"
console.log(animData.targetMeshId); // "Character_Skinned_Tpose"
console.log(animData.duration); // 0.97
console.log(animData.ticksPerSecond); // 30
console.log(animData.channelCount); // 104
console.log(animData.boneCount); // 65Audio Metadata
const audioData = pack.getAudioMetadata('sounds/bgm.ogg');
console.log(audioData.sampleRate); // 44100
console.log(audioData.channels); // 2 (stereo)
console.log(audioData.durationMs); // 180000 (3 minutes)Texture Metadata
const texData = pack.getTextureMetadata(index);
console.log(texData.width); // 1024
console.log(texData.height); // 1024
console.log(texData.channels); // 3 (RGB) or 4 (RGBA)
console.log(texData.channelFormat); // 1 (RGB) or 2 (RGBA)Cleanup
pack.dispose()
Close the pack and free resources. Clears the texture cache and closes the underlying WASM reader.
pack.dispose();Complete Example
import * as THREE from 'three';
import { StowKitLoader } from '@series-inc/stowkit-three-loader';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
const clock = new THREE.Clock();
const listener = new THREE.AudioListener();
camera.add(listener);
const pack = await StowKitLoader.load('game.stow');
const environment = await pack.loadMesh('levels/level1.mesh');
scene.add(environment);
const character = await pack.loadSkinnedMesh('characters/player.skinned');
character.position.set(0, 0, 0);
scene.add(character);
const { mixer } = await pack.loadAnimation(character, 'animations/idle.anim');
const bgm = await pack.loadAudio('sounds/theme.ogg', listener);
bgm.setLoop(true);
bgm.play();
function animate() {
requestAnimationFrame(animate);
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}
animate();Loading Multiple Packs
Each pack is fully isolated with its own WASM reader instance. You can load multiple packs simultaneously without interference:
const [environmentPack, characterPack, audioPack] = await Promise.all([
StowKitLoader.load('environment.stow'),
StowKitLoader.load('characters.stow'),
StowKitLoader.load('audio.stow')
]);
const level = await environmentPack.loadMesh('level1');
const player = await characterPack.loadSkinnedMesh('player');
const bgm = await audioPack.loadAudio('theme', listener);
scene.add(level);
scene.add(player);
bgm.play();
console.log(`Environment: ${environmentPack.getAssetCount()} assets`);
console.log(`Characters: ${characterPack.getAssetCount()} assets`);
console.log(`Audio: ${audioPack.getAssetCount()} assets`);Dispose packs when no longer needed:
environmentPack.dispose();Asset Types
import { AssetType } from '@series-inc/stowkit-three-loader';| Enum | Value | Description |
|------|-------|-------------|
| AssetType.STATIC_MESH | 1 | Draco-compressed 3D models |
| AssetType.TEXTURE_2D | 2 | KTX2/Basis Universal textures |
| AssetType.AUDIO | 3 | OGG/MP3 audio files |
| AssetType.MATERIAL_SCHEMA | 4 | Material template definitions |
| AssetType.SKINNED_MESH | 5 | Skeletal meshes with bones |
| AssetType.ANIMATION_CLIP | 6 | Bone animation keyframes |
Public Folder Setup
The package automatically copies required files on install:
public/
└── stowkit/
├── stowkit_reader.wasm
├── basis/
│ ├── basis_transcoder.js
│ └── basis_transcoder.wasm
└── draco/
├── draco_decoder.js
├── draco_decoder.wasm
└── draco_wasm_wrapper.jsJust run npm install and everything is set up automatically.
Performance
- WASM Parsing: All binary parsing done in WASM (10-50x faster than JavaScript)
- Draco Compression: Meshes are 80-90% smaller than uncompressed
- KTX2 Textures: GPU-native compression, fast loading and rendering
- Lazy Loading: Assets loaded on-demand, not all at once
- Memory Efficient: Minimal copying between WASM and JavaScript
- Texture Caching: Duplicate texture loads return cached promises
Troubleshooting
Animations appear broken/offset from origin
Make sure your .stow file was packed with the latest packer that writes bone parent indices correctly.
Textures not loading
Ensure /stowkit/basis/ folder contains the Basis Universal transcoder files (auto-copied on install).
Audio not playing
Make sure you've created an AudioListener and attached it to your camera.
