Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jscastro76/threebox/llms.txt
Use this file to discover all available pages before exploring further.
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