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.
Overview
Threebox works by adding a Three.js scene to Mapbox GL JS through the CustomLayerInterface . This interface allows you to render custom 3D content using WebGL directly on the map.
CustomLayerInterface
Mapbox GL JS custom layers require three key methods:
id : Unique layer identifier
type : Must be 'custom'
renderingMode : Should be '3d' for Threebox
onAdd(map, gl) : Initialize resources when layer is added
render(gl, matrix) : Called on every frame to render content
onRemove(map, gl) (optional): Clean up resources when layer is removed
Basic Implementation
map . addLayer ({
id: 'custom_layer' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
// Create Threebox instance
window . tb = new Threebox (
map ,
gl ,
{ defaultLights: true }
);
// Add 3D objects to the scene
var geometry = new THREE . BoxGeometry ( 30 , 60 , 120 );
let cube = new THREE . Mesh (
geometry ,
new THREE . MeshPhongMaterial ({ color: 0x660000 })
);
cube = tb . Object3D ({ obj: cube , units: 'meters' });
cube . setCoords ([ - 122.4194 , 37.7749 ]);
tb . add ( cube );
},
render : function ( gl , matrix ) {
// Update and render the Threebox scene
tb . update ();
}
});
The onAdd Method
The onAdd method is called when the layer is added to the map. This is where you:
Create the Threebox instance
Load 3D models
Add objects to the scene
Set up event listeners
Example with 3D Model Loading
onAdd : function ( map , gl ) {
window . tb = new Threebox (
map ,
gl ,
{
defaultLights: true ,
enableSelectingObjects: true ,
enableDraggingObjects: true
}
);
// Load a GLTF model
tb . loadObj ({
obj: '/models/soldier/soldier.glb' ,
type: 'gltf' ,
scale: 1 ,
units: 'meters' ,
rotation: { x: 90 , y: 0 , z: 0 }
}, function ( model ) {
model . setCoords ([ - 122.4194 , 37.7749 ]);
// Add event listeners
model . addEventListener ( 'SelectedChange' , onSelectedChange , false );
model . addEventListener ( 'ObjectDragged' , onObjectDragged , false );
tb . add ( model );
});
}
The render Method
The render method is called every frame. It must call tb.update() to:
Update animations via AnimationManager
Reset renderer state
Render the Three.js scene : renderer.render(scene, camera)
Render CSS2D labels
Trigger repaint if passiveRendering: false
render : function ( gl , matrix ) {
tb . update ();
}
What tb.update() Does
From the source code (Threebox.js:897-915):
update : function () {
if ( this . map . repaint ) this . map . repaint = false
var timestamp = Date . now ();
// Update any animations
this . objects . animationManager . update ( timestamp );
this . updateLightHelper ();
// Render the scene and repaint the map
this . renderer . resetState ();
this . renderer . render ( this . scene , this . camera );
// Render any label
this . labelRenderer . render ( this . scene , this . camera );
if ( this . options . passiveRendering === false )
this . map . triggerRepaint ();
}
Multi-Layer Setup
When using multiple custom layers, enable the multiLayer option to avoid calling tb.update() in each layer:
// Initialize Threebox with multiLayer option
window . tb = new Threebox (
map ,
map . getCanvas (). getContext ( 'webgl' ),
{
defaultLights: true ,
multiLayer: true // Creates default layer to manage updates
}
);
map . on ( 'style.load' , function () {
// Default layer 'threebox_layer' is created automatically
// It handles tb.update() calls
map . addLayer ({
id: 'buildings_layer' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
// Add building models
},
render : function ( gl , matrix ) {
// No need to call tb.update() here
}
});
map . addLayer ({
id: 'vehicles_layer' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
// Add vehicle models
},
render : function ( gl , matrix ) {
// No need to call tb.update() here
}
});
});
From source (Threebox.js:113-116):
if ( this . tb . options . multiLayer )
this . addLayer ({
id: "threebox_layer" ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { },
render : function ( gl , matrix ) {
this . map . tb . update ();
}
})
Layer Positioning
Place custom layers in the layer stack using the optional beforeId parameter:
// Add custom layer before labels
map . addLayer ({
id: 'custom_layer' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { /* ... */ },
render : function ( gl , matrix ) { tb . update (); }
}, 'waterway-label' ); // beforeId parameter
Layer Management
Toggle Layer Visibility
// Hide/show layer and its 3D objects
tb . toggleLayer ( 'custom_layer' , false ); // Hide
tb . toggleLayer ( 'custom_layer' , true ); // Show
// Or use setLayoutProperty
tb . setLayoutProperty ( 'custom_layer' , 'visibility' , 'none' );
tb . setLayoutProperty ( 'custom_layer' , 'visibility' , 'visible' );
Set Zoom Range
Custom layers don’t natively support minzoom and maxzoom. Use Threebox’s helper:
// Show layer only between zoom 10 and 18
tb . setLayerZoomRange ( 'custom_layer' , 10 , 18 );
From source (Threebox.js:831-836):
setLayerZoomRange : function ( layerId , minZoomLayer , maxZoomLayer ) {
if ( this . map . getLayer ( layerId )) {
this . map . setLayerZoomRange ( layerId , minZoomLayer , maxZoomLayer );
if ( ! this . zoomLayers . includes ( layerId )) this . zoomLayers . push ( layerId );
this . toggleLayer ( layerId );
}
}
Remove Layer
// Remove layer and dispose all objects
tb . removeLayer ( 'custom_layer' );
This calls tb.clear(layerId, true) to dispose resources before removing the layer.
Layer-Specific Objects
Assign objects to specific layers for better organization:
// Add object to specific layer
tb . add ( obj , 'custom_layer' , 'source_id' );
// Object now has layer and source properties
obj . layer ; // 'custom_layer'
obj . source ; // 'source_id'
From source (Threebox.js:917-931):
add : function ( obj , layerId , sourceId ) {
if ( ! this . enableTooltips && obj . tooltip ) {
obj . tooltip . visibility = false
};
this . world . add ( obj );
if ( layerId ) {
obj . layer = layerId ;
obj . source = sourceId ;
let l = this . map . getLayer ( layerId );
if ( l ) {
let v = l . visibility ;
let u = typeof v === 'undefined' ;
obj . visibility = ( u || v === 'visible' ? true : false );
}
}
}
Clear Layer Objects
// Clear all objects from a specific layer
await tb . clear ( 'custom_layer' , true ); // true = dispose resources
// Clear all objects from all layers
await tb . clear ();
Common Patterns
Pattern 1: Single Layer Application
map . on ( 'style.load' , function () {
map . addLayer ({
id: 'custom_layer' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
window . tb = new Threebox ( map , gl , { defaultLights: true });
// Add all objects here
},
render : function ( gl , matrix ) {
tb . update ();
}
});
});
Pattern 2: Multiple Layers (Manual)
window . tb = new Threebox (
map ,
map . getCanvas (). getContext ( 'webgl' ),
{ defaultLights: true }
);
map . addLayer ({
id: 'layer1' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { /* Add objects */ },
render : function ( gl , matrix ) { tb . update (); }
});
map . addLayer ({
id: 'layer2' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { /* Add objects */ },
render : function ( gl , matrix ) { /* Don't call update twice */ }
});
Pattern 3: Multiple Layers (Automatic)
window . tb = new Threebox (
map ,
map . getCanvas (). getContext ( 'webgl' ),
{ defaultLights: true , multiLayer: true }
);
map . addLayer ({
id: 'layer1' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { /* Add objects */ },
render : function ( gl , matrix ) { /* Auto-updated */ }
});
map . addLayer ({
id: 'layer2' ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) { /* Add objects */ },
render : function ( gl , matrix ) { /* Auto-updated */ }
});
Style Changes
When changing map styles, custom layers and their objects are removed:
// Use tb.setStyle to clean up properly
tb . setStyle ( 'mapbox://styles/mapbox/dark-v10' );
// This calls tb.clear(true) internally to dispose resources
From source (Threebox.js:869-873):
setStyle : function ( styleId , options ) {
this . clear (). then (() => {
this . map . setStyle ( styleId , options );
});
}
Call tb.update() only once per frame
Use passiveRendering: true (default) unless continuous animation is needed
Dispose of objects when removing layers
Use multiLayer: true for multiple custom layers
Next Steps
Camera Synchronization Learn how the camera stays synchronized between Three.js and Mapbox
Coordinate Systems Understand coordinate conversions in Threebox