Skip to main content

Overview

Threadbox provides a powerful animation system that supports both custom animations (movement, rotation, scaling) and built-in model animations from GLTF/GLB files. The AnimationManager handles animation queues, timelines, and smooth interpolation.

AnimationManager

Every object added to Threebox is automatically enrolled in the AnimationManager, which provides animation capabilities:
const soldier = await tb.loadObj({
  obj: '/models/soldier.glb',
  type: 'gltf',
  scale: 1,
  units: 'meters'
});

// Object is automatically enrolled and ready for animations
soldier.setCoords([-122.4194, 37.7749]);
tb.add(soldier);

Custom Animations

Movement Animation

Animate object position changes with smooth interpolation:
// Animate to new coordinates over 2 seconds
soldier.set({
  coords: [-122.4200, 37.7750, 10], // lng, lat, altitude
  duration: 2000 // milliseconds
});

Rotation Animation

Animate object rotation:
// Rotate 90 degrees on Z-axis over 1 second
soldier.set({
  rotation: { x: 0, y: 0, z: 90 },
  duration: 1000
});

Scale Animation

Animate object scaling:
// Scale to 2x size over 1.5 seconds
soldier.set({
  scale: { x: 2, y: 2, z: 2 },
  duration: 1500
});

Combined Animations

Combine multiple animation properties:
soldier.set({
  coords: [-122.4200, 37.7750, 20],
  rotation: { x: 0, y: 0, z: 180 },
  scale: { x: 1.5, y: 1.5, z: 1.5 },
  duration: 3000
});

Path Following

Animate objects along a path with automatic heading:
const path = [
  [-122.4194, 37.7749, 0],
  [-122.4200, 37.7750, 5],
  [-122.4210, 37.7755, 10],
  [-122.4220, 37.7760, 5]
];

soldier.followPath({
  path: path,
  duration: 5000,
  trackHeading: true // Automatically rotate to face direction of movement
}, () => {
  console.log('Animation complete!');
});
The followPath method uses Catmull-Rom spline interpolation for smooth curves.

Model Animations (GLTF/GLB)

Playing Default Animation

Many GLTF/GLB models include built-in animations. Threebox automatically detects and manages them:
tb.loadObj({
  obj: '/models/character.glb',
  type: 'gltf',
  units: 'meters',
  defaultAnimation: 0 // Index of animation to play by default
}, (model) => {
  model.setCoords(origin);
  tb.add(model);
  
  // Play default animation
  model.playDefault({
    duration: 0, // 0 = loop indefinitely
    speed: 1.0   // Playback speed multiplier
  });
});

Playing Specific Animation

// Play animation by index
model.playAnimation({
  animation: 2, // Animation index
  duration: 5000,
  speed: 1.5
});

Animation Control Methods

// Stop all animations
model.stop();

Idle Frame

Advance animation by one tick (useful for initialization):
model.idle();

Animation Events

Listen to animation state changes:
model.addEventListener('IsPlayingChanged', (e) => {
  const isPlaying = e.detail.isPlaying;
  console.log('Animation playing:', isPlaying);
  
  // Update UI, trigger other animations, etc.
  if (isPlaying) {
    playButton.textContent = 'Pause';
  } else {
    playButton.textContent = 'Play';
  }
}, false);

Animation Properties

isPlaying

Check if model animations are currently playing:
if (model.isPlaying) {
  console.log('Model is animating');
}

Animation Mixer

Access the underlying Three.js AnimationMixer for advanced control:
if (model.mixer) {
  // Access mixer directly
  model.mixer.timeScale = 2.0; // Double speed
}

Animation Actions

Access individual animation actions:
if (model.actions && model.actions.length > 0) {
  const firstAction = model.actions[0];
  firstAction.setEffectiveWeight(0.5); // Blend weight
}

Instant State Changes

Set object state immediately without animation:
// Set rotation instantly (duration = 0)
soldier.setRotation({ x: 0, y: 0, z: 90 });

// Or use set() with duration 0
soldier.set({
  coords: [-122.4200, 37.7750],
  rotation: { x: 0, y: 0, z: 45 },
  duration: 0 // Immediate change
});

Animation Queue

Threadbox maintains an animation queue for each object. Animations are executed sequentially:
// First animation
soldier.set({ coords: [-122.420, 37.775], duration: 2000 });

// Second animation (starts after first completes)
soldier.set({ rotation: { z: 90 }, duration: 1000 });

// Third animation
soldier.set({ scale: { x: 2, y: 2, z: 2 }, duration: 1500 });

Callbacks

Execute code when animations complete:
soldier.followPath({
  path: pathCoordinates,
  duration: 3000
}, () => {
  console.log('Reached destination!');
  
  // Start another animation
  soldier.playAnimation({
    animation: 1,
    duration: 2000
  });
});

Performance Tips

Optimize Animations

  • Use duration: 0 for instant state changes when animation isn’t needed
  • Stop animations when objects are off-screen
  • Use model.mixer.timeScale to globally control animation speed
  • Dispose of objects properly with model.dispose() to clean up animation resources

Complete Example

map.on('load', () => {
  const tb = new Threebox(map, map.getCanvas().getContext('webgl'), {
    defaultLights: true
  });
  
  map.addLayer({
    id: 'custom-layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, gl) {
      const origin = [-122.4194, 37.7749, 0];
      
      tb.loadObj({
        obj: '/models/soldier.glb',
        type: 'gltf',
        scale: 3,
        units: 'meters',
        rotation: { x: 90, y: 0, z: 0 },
        defaultAnimation: 0
      }, (model) => {
        model.setCoords(origin);
        tb.add(model);
        
        // Listen for animation events
        model.addEventListener('IsPlayingChanged', (e) => {
          console.log('Playing:', e.detail.isPlaying);
        });
        
        // Define patrol path
        const path = [
          origin,
          [-122.4200, 37.7750, 0],
          [-122.4210, 37.7755, 0],
          origin
        ];
        
        // Start patrol with model animation
        model.playDefault({ duration: 0, speed: 1.0 });
        model.followPath({
          path: path,
          duration: 10000,
          trackHeading: true
        }, () => {
          console.log('Patrol complete');
        });
      });
    },
    render: function(gl, matrix) {
      tb.update();
    }
  });
});

See Also

Build docs developers (and LLMs) love