Threebox supports loading external 3D models in various formats. This guide covers model loading, configuration, and advanced features like shadows and sunlight.
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
| Option | Type | Description |
|---|
obj | string | Path to the model file |
type | string | Model type: 'gltf', 'mtl', or 'fbx' |
| Option | Type | Default | Description |
|---|
scale | number/object | 1 | Uniform scale or scale object |
rotation | object | (0,0,0) | Rotation in degrees |
units | string | ’meters’ | Scale units |
anchor | string | ’center’ | Anchor point: ‘center’, ‘bottom’, ‘top’ |
Visual Options
| Option | Type | Default | Description |
|---|
bbox | boolean | true | Show bounding box |
clone | boolean | false | Create instance for reuse |
normalize | boolean | true | Auto-normalize size |
Model Examples
Soldier (GLB)
Truck (OBJ)
Radar Dish
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
});
});
Load a truck model in OBJ format:var options = {
type: 'mtl',
obj: 'models/vehicles/truck.obj',
mtl: 'models/vehicles/truck.mtl',
scale: 40,
units: 'meters',
anchor: 'bottom',
rotation: { x: 90, y: 90, z: 0 }
};
tb.loadObj(options, function(model) {
var truck = model.setCoords([-122.4340, 37.7353, 0]);
tb.add(truck);
});
Large-scale industrial model:var options = {
type: 'gltf',
obj: './models/radar/34M_17.glb',
units: 'meters',
scale: 333.22,
rotation: { x: 90, y: 180, z: 0 },
anchor: 'center'
};
tb.loadObj(options, function(model) {
model.setCoords([148.9819, -35.39847, 0]);
model.addTooltip("A radar in the middle of nowhere", true);
model.castShadow = true;
tb.add(model);
});
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
var options = {
obj: 'model.glb',
type: 'gltf',
scale: 100, // Scale uniformly
units: 'meters'
};
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);
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;">☢</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