generating spatials in the browser with spatialjs (advanced)

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.

when to generate spatials in the browser

browser-based spatial generation opens up exciting possibilities:

  • user-generated content: let users create and customize voxel videos interactively
  • real-time simulations: generate spatials based on live data or user input
  • dynamic visualizations: create data-driven voxel animations on demand
  • interactive applications: build tools where users manipulate 3d voxel scenes
  • procedural content: generate infinite variations of voxel content algorithmically

while more computationally intensive than server-side generation, browser-based creation provides immediate feedback and eliminates the need for backend processing.

understanding the virtual filesystem

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.

setting up persistent storage

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
    }
});

understanding the sync process

the virtual filesystem requires explicit synchronization:

  • FS.syncfs(true, callback): load data from indexeddb into memory
  • FS.syncfs(false, callback): save memory data to indexeddb

creating your first browser-generated spatial

let's create a simple but complete example that generates a pulsating sphere animation entirely in javascript:

pulsating-sphere.js
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');
            }
        });
    });
}

breaking down the code

encoder creation

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.

mathematical animation

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.

loading and playing your generated spatial

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);
    }
}

performance considerations and optimization

browser-based voxel generation can be computationally intensive. here are key optimization strategies:

1. use efficient algorithms

// 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);
        }
    }
}

2. optimize loop structure

// 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);
            }
        }
    }
}

file management and download

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;
    }
}

the full spatial technology stack

congratulations! you've now mastered the complete spatial technology ecosystem:

  • spatialstudio (python): creating sophisticated voxel videos with powerful encoding tools
  • spatialjs playback: embedding and controlling spatial players on the web
  • spatialjs generation: creating spatials entirely in the browser with javascript

you can now:

  • create in python for complex, high-performance batch processing
  • display on the web with beautiful, interactive players
  • generate in browsers for user-driven, real-time content creation
  • combine all approaches in hybrid applications

future exploration

the techniques you've learned open doors to many exciting possibilities:

  • vr/ar integration: display spatials in immersive environments
  • real-time data visualization: generate voxel videos from live data streams
  • collaborative creation: build multi-user voxel editing applications
  • ai-generated content: use machine learning to create procedural spatials
  • gaming applications: integrate voxel videos into interactive experiences

community and resources

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! 🚀