welcome to the final tutorial in our spatial development series! you've mastered creating voxel content with spatialstudio and displaying it with spatialjs. now, we'll explore the most advanced capability: generating spatial videos entirely within the browser using javascript and webassembly.
browser-based spatial generation opens up exciting possibilities:
while more computationally intensive than server-side generation, browser-based creation provides immediate feedback and eliminates the need for backend processing.
since browsers don't have direct file system access, spatialjs uses emscripten's virtual filesystem (backed by indexeddb) to handle file operations. this creates a persistent, browser-based storage system for your generated .splv
files.
before creating any spatials, you need to initialize the virtual filesystem:
// Import SpatialJS and access the filesystem
import SPLV from 'spatial-player';
// Create a persistent directory
FS.mkdir('/persistent');
// Mount IndexedDB-backed storage
FS.mount(IDBFS, {}, '/persistent');
// Synchronize with IndexedDB (load existing files)
FS.syncfs(true, (err) => {
if (err) {
console.error('filesystem initialization failed:', err);
} else {
console.log('virtual filesystem ready');
// Now safe to create and save files
}
});
the virtual filesystem requires explicit synchronization:
FS.syncfs(true, callback)
: load data from indexeddb into memoryFS.syncfs(false, callback)
: save memory data to indexeddblet's create a simple but complete example that generates a pulsating sphere animation entirely in javascript:
import SPLV from 'spatial-player';
// Initialize the virtual filesystem first
function initializeFilesystem() {
return new Promise((resolve, reject) => {
FS.mkdir('/persistent');
FS.mount(IDBFS, {}, '/persistent');
FS.syncfs(true, (err) => {
if (err) reject(err);
else resolve();
});
});
}
// Generate a pulsating sphere Spatial
async function createPulsatingSphere() {
// Wait for filesystem to be ready
await initializeFilesystem();
// Set up voxel space dimensions
const width = 64, height = 64, depth = 64;
const fps = 24.0;
// Create encoder with output to virtual filesystem
const encoder = new SPLV.Encoder(width, height, depth, fps, '/persistent/pulsating_sphere.splv');
// Generate 60 frames (2.5 seconds at 24fps)
for (let t = 0; t < 60; t++) {
const frame = new SPLV.Frame(width, height, depth);
// Calculate pulsating radius
const radius = Math.abs(Math.sin(t * Math.PI / 30)) * 15 + 8;
// Center point of our sphere
const centerX = width / 2;
const centerY = height / 2;
const centerZ = depth / 2;
// Create sphere by checking distance from center
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
for (let z = 0; z < depth; z++) {
// Calculate distance from center
const dx = x - centerX;
const dy = y - centerY;
const dz = z - centerZ;
const distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
// If within radius, set voxel with color based on time
if (distance < radius) {
const red = Math.floor(100 + t * 2.5);
const green = Math.floor(50 + Math.sin(t * 0.2) * 30);
const blue = 200;
frame.setVoxel(x, y, z, { r: red, g: green, b: blue });
}
}
}
}
// Encode this frame
encoder.encode(frame);
// Optional: Show progress
if (t % 10 === 0) {
console.log(`progress: ${Math.round((t / 59) * 100)}%`);
}
}
// Finalize the spatial file
encoder.finish();
// Sync to persistent storage
return new Promise((resolve, reject) => {
FS.syncfs(false, (err) => {
if (err) {
console.error('failed to save spatial:', err);
reject(err);
} else {
console.log('pulsating sphere spatial saved successfully!');
resolve('/persistent/pulsating_sphere.splv');
}
});
});
}
const encoder = new SPLV.Encoder(width, height, depth, fps, '/persistent/pulsating_sphere.splv');
creates an encoder that outputs to the virtual filesystem - the file will be saved in browser storage.
const radius = Math.abs(Math.sin(t * Math.PI / 30)) * 15 + 8;
uses sine waves to create smooth pulsating motion, with radius varying between 8 and 23 voxels.
once your spatial is created and saved to the virtual filesystem, you can immediately load it into a player without any server upload:
// Load the generated spatial into a player
function loadGeneratedSpatial() {
try {
// Read the file from virtual filesystem
const fileData = FS.readFile('/persistent/pulsating_sphere.splv');
// Get player element (assumes <splv-player id="generated-player"> exists)
const playerEl = document.getElementById('generated-player');
// Load the ArrayBuffer directly into the player
playerEl.spatial_set(fileData.buffer);
console.log('generated spatial loaded into player');
} catch (error) {
console.error('failed to load generated spatial:', error);
}
}
browser-based voxel generation can be computationally intensive. here are key optimization strategies:
// Efficient: Use fill() for large regions
frame.fill(x1, y1, z1, x2, y2, z2, color);
// Less efficient: Individual voxel setting for large areas
for (let x = x1; x < x2; x++) {
for (let y = y1; y < y2; y++) {
for (let z = z1; z < z2; z++) {
frame.setVoxel(x, y, z, color);
}
}
}
// Better: Minimize calculations inside loops
const centerX = width / 2;
const centerY = height / 2;
const centerZ = depth / 2;
for (let x = 0; x < width; x++) {
const dx = x - centerX;
const dx2 = dx * dx; // Pre-calculate square
for (let y = 0; y < height; y++) {
const dy = y - centerY;
const dy2 = dy * dy;
for (let z = 0; z < depth; z++) {
const dz = z - centerZ;
const distance2 = dx2 + dy2 + dz * dz; // Avoid sqrt when possible
if (distance2 < radius2) { // Compare squared distances
frame.setVoxel(x, y, z, color);
}
}
}
}
allow users to download their generated spatials:
// Create download link for generated spatial
function createDownloadLink(virtualPath, filename) {
try {
// Read file from virtual filesystem
const fileData = FS.readFile(virtualPath);
// Create blob and download link
const blob = new Blob([fileData], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename || 'generated_spatial.splv';
link.textContent = `download ${filename}`;
document.body.appendChild(link);
// Clean up URL after download
link.addEventListener('click', () => {
setTimeout(() => URL.revokeObjectURL(url), 1000);
});
return link;
} catch (error) {
console.error('failed to create download link:', error);
return null;
}
}
congratulations! you've now mastered the complete spatial technology ecosystem:
you can now:
the techniques you've learned open doors to many exciting possibilities:
continue your journey with these resources:
the future of volumetric content is in your hands. start building, experimenting, and pushing the boundaries of what's possible with 4d voxel videos. we can't wait to see what you create!
happy coding, and welcome to the spatial revolution! 🚀