or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

boundaries-constraints.mdcamera-fitting.mdcamera-movement.mdcore-controls.mdevent-system.mdindex.mdinput-configuration.mdstate-management.md
tile.json

state-management.mddocs/

State Management

Camera state persistence, serialization, restoration, and accessor functionality for saving and loading camera configurations.

Capabilities

State Persistence

Save and restore camera states for later use or reset functionality.

/**
 * Save current camera state as the reset point
 * Stores position, target, zoom, and other camera parameters
 */
saveState(): void;

/**
 * Reset camera to saved state or initial position
 * @param enableTransition - Whether to animate the transition
 * @returns Promise array that resolves when all movements complete
 */
reset(enableTransition?: boolean): Promise<void[]>;

Usage Examples:

// Save initial camera state
cameraControls.saveState();

// Move camera around...
await cameraControls.moveTo(10, 5, 15, true);
await cameraControls.rotateTo(Math.PI / 4, Math.PI / 3, true);

// Reset to saved state with animation
await cameraControls.reset(true);

// Reset instantly
await cameraControls.reset(false);

// Save new state after positioning
await cameraControls.fitToBox(mesh, true);
cameraControls.saveState(); // New reset point

Serialization

Convert camera state to/from JSON for persistence across sessions.

/**
 * Serialize current camera state to JSON string
 * @returns JSON string containing complete camera state
 */
toJSON(): string;

/**
 * Load camera state from JSON string
 * @param json - JSON string from previous toJSON() call
 * @param enableTransition - Whether to animate the transition
 */
fromJSON(json: string, enableTransition?: boolean): void;

Usage Examples:

// Save camera state to localStorage
const cameraState = cameraControls.toJSON();
localStorage.setItem('cameraState', cameraState);

// Load camera state from localStorage
const savedState = localStorage.getItem('cameraState');
if (savedState) {
  cameraControls.fromJSON(savedState, true);
}

// Save to file or database
const stateData = {
  timestamp: Date.now(),
  cameraState: cameraControls.toJSON(),
  sceneName: 'main-scene'
};

// Send to server
fetch('/api/save-camera-state', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(stateData)
});

// Load from server response
const response = await fetch('/api/load-camera-state');
const loadedData = await response.json();
cameraControls.fromJSON(loadedData.cameraState, true);

State Accessors

Retrieve current camera state values for analysis or custom operations.

/**
 * Get current camera target position
 * @param out - Vector3 to write result to
 * @param receiveEndValue - Whether to get end value of transition (true) or current value (false)
 * @returns Vector3 containing target position
 */
getTarget(out: THREE.Vector3, receiveEndValue?: boolean): THREE.Vector3;

/**
 * Get current camera position
 * @param out - Vector3 to write result to  
 * @param receiveEndValue - Whether to get end value of transition (true) or current value (false)
 * @returns Vector3 containing camera position
 */
getPosition(out: THREE.Vector3, receiveEndValue?: boolean): THREE.Vector3;

/**
 * Get current spherical coordinates
 * @param out - Spherical to write result to
 * @param receiveEndValue - Whether to get end value of transition (true) or current value (false)
 * @returns Spherical containing camera's spherical coordinates
 */
getSpherical(out: THREE.Spherical, receiveEndValue?: boolean): THREE.Spherical;

/**
 * Get current focal offset
 * @param out - Vector3 to write result to
 * @param receiveEndValue - Whether to get end value of transition (true) or current value (false)
 * @returns Vector3 containing focal offset
 */
getFocalOffset(out: THREE.Vector3, receiveEndValue?: boolean): THREE.Vector3;

Usage Examples:

import * as THREE from 'three';

// Get current camera state
const currentPosition = new THREE.Vector3();
const currentTarget = new THREE.Vector3();
const currentSpherical = new THREE.Spherical();

cameraControls.getPosition(currentPosition);
cameraControls.getTarget(currentTarget);
cameraControls.getSpherical(currentSpherical);

console.log('Camera Position:', currentPosition);
console.log('Target Position:', currentTarget);
console.log('Spherical Coords:', {
  radius: currentSpherical.radius,
  phi: currentSpherical.phi,
  theta: currentSpherical.theta
});

// Get end values during transition (where camera is moving to)
const endPosition = new THREE.Vector3();
const endTarget = new THREE.Vector3();

cameraControls.getPosition(endPosition, true);  // End position
cameraControls.getTarget(endTarget, true);      // End target

// Calculate distance between current and end positions
const distance = currentPosition.distanceTo(endPosition);
console.log('Remaining distance to travel:', distance);

Advanced State Management

State Comparison and Validation:

// Compare two camera states
function compareCameraStates(state1: string, state2: string): boolean {
  try {
    const parsed1 = JSON.parse(state1);
    const parsed2 = JSON.parse(state2);
    
    // Compare key properties (simplified)
    return (
      parsed1.target && parsed2.target &&
      parsed1.position && parsed2.position &&
      Math.abs(parsed1.target.x - parsed2.target.x) < 0.001 &&
      Math.abs(parsed1.target.y - parsed2.target.y) < 0.001 &&
      Math.abs(parsed1.target.z - parsed2.target.z) < 0.001
    );
  } catch {
    return false;
  }
}

// Validate state before loading
function isValidCameraState(json: string): boolean {
  try {
    const state = JSON.parse(json);
    return state && typeof state === 'object' && 
           state.target && state.position;
  } catch {
    return false;
  }
}

// Safe state loading
const savedState = localStorage.getItem('cameraState');
if (savedState && isValidCameraState(savedState)) {
  cameraControls.fromJSON(savedState, true);
}

Multiple State Management:

// State manager class for multiple camera states
class CameraStateManager {
  private states = new Map<string, string>();
  
  saveState(name: string, controls: CameraControls): void {
    this.states.set(name, controls.toJSON());
  }
  
  loadState(name: string, controls: CameraControls, animate = true): boolean {
    const state = this.states.get(name);
    if (state) {
      controls.fromJSON(state, animate);
      return true;
    }
    return false;
  }
  
  deleteState(name: string): boolean {
    return this.states.delete(name);
  }
  
  listStates(): string[] {
    return Array.from(this.states.keys());
  }
}

// Usage
const stateManager = new CameraStateManager();

// Save named states
stateManager.saveState('overview', cameraControls);
stateManager.saveState('closeup', cameraControls);
stateManager.saveState('top-view', cameraControls);

// Load specific state
stateManager.loadState('overview', cameraControls, true);

Transition Monitoring:

// Monitor state changes during transitions
function monitorTransition() {
  const startPosition = new THREE.Vector3();
  const currentPosition = new THREE.Vector3();
  const endPosition = new THREE.Vector3();
  
  cameraControls.getPosition(startPosition, false);  // Current
  cameraControls.getPosition(endPosition, true);     // Target
  
  const interval = setInterval(() => {
    cameraControls.getPosition(currentPosition, false);
    
    const remainingDistance = currentPosition.distanceTo(endPosition);
    const totalDistance = startPosition.distanceTo(endPosition);
    const progress = 1 - (remainingDistance / totalDistance);
    
    console.log(`Transition progress: ${(progress * 100).toFixed(1)}%`);
    
    if (remainingDistance < 0.01) { // Close enough
      clearInterval(interval);
      console.log('Transition complete');
    }
  }, 100);
}

// Start monitoring before transition
monitorTransition();
await cameraControls.moveTo(10, 5, 15, true);

State Interpolation:

// Interpolate between two saved states
async function interpolateStates(
  state1: string, 
  state2: string, 
  t: number, // 0-1 interpolation factor
  controls: CameraControls,
  animate = true
): Promise<void> {
  const parsed1 = JSON.parse(state1);
  const parsed2 = JSON.parse(state2);
  
  // Interpolate position
  const pos1 = new THREE.Vector3(parsed1.position.x, parsed1.position.y, parsed1.position.z);
  const pos2 = new THREE.Vector3(parsed2.position.x, parsed2.position.y, parsed2.position.z);
  const interpolatedPos = pos1.clone().lerp(pos2, t);
  
  // Interpolate target
  const target1 = new THREE.Vector3(parsed1.target.x, parsed1.target.y, parsed1.target.z);
  const target2 = new THREE.Vector3(parsed2.target.x, parsed2.target.y, parsed2.target.z);
  const interpolatedTarget = target1.clone().lerp(target2, t);
  
  // Apply interpolated state
  await Promise.all([
    controls.setPosition(interpolatedPos.x, interpolatedPos.y, interpolatedPos.z, animate),
    controls.setTarget(interpolatedTarget.x, interpolatedTarget.y, interpolatedTarget.z, animate)
  ]);
}

// Usage: animate between states
for (let i = 0; i <= 10; i++) {
  const t = i / 10;
  await interpolateStates(overviewState, closeupState, t, cameraControls, true);
  await new Promise(resolve => setTimeout(resolve, 200));
}