Skip to main content
Threebox supports loading external 3D models in various formats. This guide covers model loading, configuration, and advanced features like shadows and sunlight.

Supported Formats

Threebox supports the following 3D model formats:
  • GLTF/GLB - Recommended format, supports animations
  • OBJ/MTL - Legacy format, widely compatible
  • FBX - Supported via Three.js loaders
GLB is the binary version of GLTF and is the recommended format for web applications.

Basic Model Loading

Here’s a complete example loading a GLB model:
map.on('style.load', function() {
  map.addLayer({
    id: 'custom_layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, mbxContext) {
      window.tb = new Threebox(
        map,
        mbxContext,
        {
          defaultLights: true,
          enableSelectingObjects: true,
          enableTooltips: true
        }
      );

      // Model loading options
      var options = {
        obj: 'models/soldier.glb',
        type: 'gltf',
        scale: 20,
        units: 'meters',
        rotation: { x: 90, y: 0, z: 0 },
        anchor: 'center'
      };

      tb.loadObj(options, function(model) {
        // Position the model
        model.setCoords([-122.4340, 37.7353, 0]);
        
        // Add to scene
        tb.add(model);
      });
    },

    render: function(gl, matrix) {
      tb.update();
    }
  });
});

Load Options

Required Options

OptionTypeDescription
objstringPath to the model file
typestringModel type: 'gltf', 'mtl', or 'fbx'

Transform Options

OptionTypeDefaultDescription
scalenumber/object1Uniform scale or scale object
rotationobject(0,0,0)Rotation in degrees
unitsstring’meters’Scale units
anchorstring’center’Anchor point: ‘center’, ‘bottom’, ‘top’

Visual Options

OptionTypeDefaultDescription
bboxbooleantrueShow bounding box
clonebooleanfalseCreate instance for reuse
normalizebooleantrueAuto-normalize size

Model Examples

Load an animated soldier model:
var options = {
  obj: 'models/soldier.glb',
  type: 'gltf',
  scale: 20,
  units: 'meters',
  rotation: { x: 90, y: 0, z: 0 },
  anchor: 'center'
};

tb.loadObj(options, function(model) {
  var soldier = model.setCoords([-122.4340, 37.7353, 0]);
  
  // Add tooltip
  soldier.addTooltip("Soldier", true);
  
  tb.add(soldier);
  
  // Play embedded animation
  soldier.playAnimation({
    animation: 1,
    duration: 10000
  });
});

Model Positioning

Set Coordinates

// Set position using lng, lat, altitude
model.setCoords([longitude, latitude, altitude]);

// Get current position
var coords = model.coordinates;
console.log(coords); // [lng, lat, alt]

Anchor Points

Control where the model’s origin is positioned:
var options = {
  obj: 'model.glb',
  type: 'gltf',
  anchor: 'bottom'  // 'center', 'bottom', 'top'
};
  • center - Model center at coordinates (default)
  • bottom - Model base at coordinates (good for buildings, vehicles)
  • top - Model top at coordinates

Scaling Models

Uniform Scaling

var options = {
  obj: 'model.glb',
  type: 'gltf',
  scale: 100,  // Scale uniformly
  units: 'meters'
};

Non-Uniform Scaling

var options = {
  obj: 'model.glb',
  type: 'gltf',
  scale: { x: 50, y: 100, z: 50 },
  units: 'meters'
};

Real-World Scale

For models with real-world dimensions:
// Space Needle model - use actual scale
var options = {
  obj: './models/landmarks/spaceneedle.glb',
  type: 'gltf',
  scale: 3134.71,  // Real-world scale factor
  units: 'meters'
};

Rotation

Rotate models to orient them correctly:
var options = {
  obj: 'model.glb',
  type: 'gltf',
  rotation: {
    x: 90,   // Pitch (degrees)
    y: 0,    // Yaw
    z: 180   // Roll
  }
};
Most GLB models exported from modeling software need x: 90 rotation to stand upright on the map.

Shadows and Lighting

Enable Shadow Casting

map.on('style.load', function() {
  // Initialize with real sunlight
  window.tb = new Threebox(
    map,
    map.getCanvas().getContext('webgl'),
    {
      realSunlight: true,
      sky: true,
      enableSelectingObjects: true,
      enableTooltips: true
    }
  );

  map.addLayer({
    id: 'custom_layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, mbxContext) {
      var options = {
        type: 'gltf',
        obj: './models/radar/34M_17.glb',
        scale: 333.22,
        rotation: { x: 90, y: 180, z: 0 },
        anchor: 'center'
      };

      tb.loadObj(options, function(model) {
        var origin = [148.9819, -35.39847];
        model.setCoords(origin);
        
        // Enable shadow casting
        model.castShadow = true;
        
        // Make this model the light target
        tb.lights.dirLight.target = model;
        
        tb.add(model);
      });
    },

    render: function(gl, matrix) {
      // Set sun position based on date/time
      var date = new Date();
      var origin = [148.9819, -35.39847];
      tb.setSunlight(date, origin);
      tb.update();
    }
  });
});

Dynamic Sunlight

Create realistic time-based lighting:
let date = new Date();
let time = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();

// Add time slider
let timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => {
  time = +timeInput.value;
  date.setHours(Math.floor(time / 60 / 60));
  date.setMinutes(Math.floor(time / 60) % 60);
  date.setSeconds(time % 60);
  map.triggerRepaint();
};

// In render function
render: function(gl, matrix) {
  tb.setSunlight(date, modelOrigin);
  tb.update();
}

Automatic Day/Night Styles

let styles = {
  day: 'satellite-streets-v9',
  night: 'dark-v10'
};

// Get sun times for location
let sunTimes = tb.getSunTimes(date, origin);
let currentStyle = styles.day;

if (date < sunTimes.sunrise || date > sunTimes.sunset) {
  currentStyle = styles.night;
}

map.setStyle('mapbox://styles/mapbox/' + currentStyle);

Tooltips and Labels

Add Tooltips

tb.loadObj(options, function(model) {
  model.setCoords(origin);
  
  // Add simple tooltip
  model.addTooltip("This is a 3D model", true);
  
  tb.add(model);
});

Custom HTML Labels

function createLabelIcon(text) {
  let popup = document.createElement('div');
  popup.innerHTML = '<span title="' + text + '" style="font-size: 30px; color: yellow;">&#9762;</span>';
  return popup;
}

tb.loadObj(options, function(model) {
  model.setCoords(origin);
  model.addLabel(createLabelIcon("Status: Radioactive"), true);
  tb.add(model);
});

Multiple Models

Load multiple models efficiently:
map.addLayer({
  id: 'custom_layer',
  type: 'custom',
  renderingMode: '3d',
  onAdd: function(map, mbxContext) {
    window.tb = new Threebox(
      map,
      mbxContext,
      {
        realSunlight: true,
        enableSelectingObjects: true,
        enableTooltips: true
      }
    );

    // Space Needle
    let options1 = {
      obj: './models/landmarks/spaceneedle.glb',
      type: 'gltf',
      scale: 3134.71,
      units: 'meters',
      rotation: { x: 90, y: 0, z: 0 },
      anchor: 'center'
    };

    tb.loadObj(options1, function(model) {
      model.setCoords([-122.349291, 47.620522]);
      model.castShadow = true;
      model.addTooltip("Space Needle");
      tb.add(model);
    });

    // Soldier
    let options2 = {
      obj: './models/soldier.glb',
      type: 'gltf',
      scale: 100,
      units: 'meters',
      rotation: { x: 90, y: 0, z: 0 },
      anchor: 'center'
    };

    tb.loadObj(options2, function(model) {
      let soldier = model.setCoords([-122.347732, 47.6207, 0]);
      soldier.castShadow = true;
      soldier.addTooltip("Running soldier");
      tb.add(soldier);
      soldier.playAnimation({ animation: 1, duration: 10000 });
    });
  },

  render: function(gl, matrix) {
    tb.update();
  }
});

Model Cloning

Reuse models for better performance:
var options = {
  obj: 'model.glb',
  type: 'gltf',
  scale: 10,
  clone: true  // Enable cloning
};

tb.loadObj(options, function(original) {
  // Add original
  original.setCoords([lng1, lat1, alt1]);
  tb.add(original);
  
  // Create clones
  for (let i = 0; i < 100; i++) {
    let clone = original.duplicate();
    clone.setCoords([lng + i * 0.001, lat, alt]);
    tb.add(clone);
  }
});

Event Listeners

Attach event handlers to models:
tb.loadObj(options, function(model) {
  model.setCoords(origin);
  
  // Selection events
  model.addEventListener('SelectedChange', function(e) {
    console.log('Selected:', e.detail.selected);
  }, false);
  
  // Mouse events
  model.addEventListener('ObjectMouseOver', function(e) {
    console.log('Mouse over:', e.detail.name);
  }, false);
  
  model.addEventListener('ObjectMouseOut', function(e) {
    console.log('Mouse out:', e.detail.name);
  }, false);
  
  // Interaction events
  model.addEventListener('ObjectDragged', function(e) {
    console.log('Dragged:', e.detail.draggedAction);
  }, false);
  
  model.addEventListener('ObjectChanged', function(e) {
    console.log('Changed:', e.detail.action);
  }, false);
  
  // Wireframe toggle
  model.addEventListener('Wireframed', function(e) {
    console.log('Wireframe:', e.detail.wireframe);
  }, false);
  
  // Animation events
  model.addEventListener('IsPlayingChanged', function(e) {
    console.log('Playing:', e.detail.isPlaying);
  }, false);
  
  tb.add(model);
});

Wireframe Mode

Toggle between solid and wireframe rendering:
tb.loadObj(options, function(model) {
  model.setCoords(origin);
  tb.add(model);
  
  // Toggle wireframe
  model.wireframe = true;  // Enable wireframe
  model.wireframe = false; // Disable wireframe
});

Complete Example: Landmark with Shadows

Here’s a complete example showing the Eiffel Tower with realistic shadows:
<!doctype html>
<head>
  <title>3D Model with Shadows</title>
  <link href="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v2.2.0/mapbox-gl.js"></script>
  <script src="../dist/threebox.js"></script>
  <link href="../dist/threebox.css" rel="stylesheet" />
</head>
<body>
  <div id='map' style='width: 100%; height: 100%;'></div>

  <script>
    mapboxgl.accessToken = 'YOUR_TOKEN';

    var map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/satellite-v9',
      center: [2.2945, 48.8584],  // Eiffel Tower
      zoom: 17,
      pitch: 60,
      antialias: true
    });

    map.on('style.load', function() {
      // Initialize with real sunlight
      window.tb = new Threebox(
        map,
        map.getCanvas().getContext('webgl'),
        {
          realSunlight: true,
          sky: true,
          enableSelectingObjects: true,
          enableTooltips: true
        }
      );

      map.addLayer({
        id: '3d-model',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function(map, gl) {
          let options = {
            type: 'gltf',
            obj: './models/landmarks/eiffel.glb',
            units: 'meters',
            scale: 324,  // Actual height in meters
            rotation: { x: 90, y: 0, z: 0 },
            anchor: 'bottom'
          };

          tb.loadObj(options, function(model) {
            let origin = [2.2945, 48.8584, 0];
            model.setCoords(origin);
            model.castShadow = true;
            model.addTooltip("Tour Eiffel", true);
            tb.lights.dirLight.target = model;
            tb.add(model);
          });
        },

        render: function(gl, matrix) {
          let date = new Date();
          let origin = [2.2945, 48.8584];
          tb.setSunlight(date, origin);
          tb.update();
        }
      });
    });
  </script>
</body>

MIME Type Configuration

GLB files may not load without proper MIME type configuration on your web server.

Apache (.htaccess)

AddType model/gltf-binary .glb
AddType model/gltf+json .gltf

Nginx

types {
  model/gltf-binary glb;
  model/gltf+json gltf;
}

Express.js

const express = require('express');
const app = express();

express.static.mime.define({'model/gltf-binary': ['glb']});
express.static.mime.define({'model/gltf+json': ['gltf']});

Troubleshooting

Model not appearing?
  • Check file path is correct
  • Verify MIME type configuration
  • Ensure scale is appropriate for the map zoom level
  • Check console for loading errors
Model is too large/small?
  • Adjust the scale parameter
  • Use appropriate units setting
  • Consider the model’s original size
Model is upside down or rotated incorrectly?
  • Adjust rotation.x (typically 90 degrees for upright)
  • Try different anchor settings
  • Check model’s coordinate system in 3D software
Shadows not working?
  • Enable realSunlight: true in Threebox options
  • Set model.castShadow = true
  • Call tb.setSunlight() in render function
  • Ensure renderingMode: '3d' is set

Next Steps

Animations

Animate models along paths

Interactions

Enable selection and dragging

Examples

View all examples

API Reference

Object3D API documentation

Build docs developers (and LLMs) love