Overview
The multiLayer option enables Threebox to manage multiple custom 3D layers without requiring manual tb.update() calls in each layer’s render function.
Enabling Multi-Layer Mode
Initialization
window . tb = new Threebox (
map ,
map . getCanvas (). getContext ( 'webgl' ),
{
defaultLights: true ,
enableSelectingObjects: true ,
multiLayer: true // Enable automatic update management
}
);
How It Works
When multiLayer: true, Threebox automatically creates a default custom layer on style load:
map . on ( 'style.load' , function () {
this . tb . zoomLayers = [];
// Create default layer to manage tb.update
if ( this . tb . options . multiLayer ) {
this . addLayer ({
id: "threebox_layer" ,
type: 'custom' ,
renderingMode: '3d' ,
map: this ,
onAdd : function ( map , gl ) { },
render : function ( gl , matrix ) {
this . map . tb . update ();
}
})
}
});
With multiLayer: true, you don’t need to call tb.update() in your custom layers’ render functions.
Creating Multiple Layers
Basic Multi-Layer Setup
map . on ( 'style.load' , function () {
// Create multiple custom layers
for ( let i = 1 ; i <= 5 ; i ++ ) {
map . addLayer ({
id: '3d-model-layer-' + i ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
// Add 3D objects for this layer
addObjectsToLayer ( '3d-model-layer-' + i );
},
render : function ( gl , matrix ) {
// tb.update() not needed when multiLayer: true
}
});
}
});
Layer-Specific Objects
Objects can be associated with specific layers:
tb . add ( obj , layerId , sourceId ) {
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 );
}
}
}
Multi-Floor Design Pattern
The multi-layer feature is perfect for indoor mapping and multi-floor buildings:
Floor-Based Layers
Floor Object Loading
const floors = [
{ id: 'floor-1' , level: 0 , minZoom: 16 , maxZoom: 22 },
{ id: 'floor-2' , level: 4 , minZoom: 17 , maxZoom: 22 },
{ id: 'floor-3' , level: 8 , minZoom: 18 , maxZoom: 22 }
];
floors . forEach ( floor => {
map . addLayer ({
id: floor . id ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
loadFloorObjects ( floor . id , floor . level );
},
render : function ( gl , matrix ) {
// No tb.update() needed
}
});
// Set zoom range for each floor
tb . setLayerZoomRange ( floor . id , floor . minZoom , floor . maxZoom );
});
Layer Visibility Management
Toggle Layer Visibility
// Toggle a layer
tb . toggleLayer ( layerId , visible );
// Example: Toggle with buttons
function createLayerToggle ( layerId ) {
link . onclick = function ( e ) {
e . preventDefault ();
var visibility = map . getLayoutProperty ( layerId , 'visibility' );
if ( visibility === 'visible' ) {
tb . toggleLayer ( layerId , false );
} else {
tb . toggleLayer ( layerId , true );
}
};
}
Zoom-Based Visibility
Control layer visibility based on zoom level:
// Set zoom range for layer
tb . setLayerZoomRange ( layerId , minZoom , maxZoom );
// Example: Show different detail levels at different zooms
tb . setLayerZoomRange ( '3d-model-1' , 16 , 18 );
tb . setLayerZoomRange ( '3d-model-2' , 17 , 19 );
tb . setLayerZoomRange ( '3d-model-3' , 18 , 22 );
The zoom range is automatically enforced:
this . onZoom = function ( e ) {
this . tb . zoomLayers . forEach (( l ) => {
this . tb . toggleLayer ( l );
});
this . tb . setObjectsScale ();
}
Layer Management Methods
Replicates map.setLayoutProperty for custom layers tb . setLayoutProperty ( layerId , 'visibility' , 'visible' );
Removes a layer and clears its 3D objects
Sets the height of all objects in a layer tb . setLayerHeigthProperty ( layerId , level );
Complete Multi-Layer Example
mapboxgl . accessToken = 'YOUR_TOKEN' ;
var map = new mapboxgl . Map ({
container: 'map' ,
style: 'mapbox://styles/mapbox/outdoors-v11' ,
center: [ 148.981427 , - 35.398307 ],
zoom: 18 ,
pitch: 60
});
window . tb = new Threebox (
map ,
map . getCanvas (). getContext ( 'webgl' ),
{
defaultLights: true ,
enableSelectingObjects: true ,
enableTooltips: true ,
multiLayer: true // Critical for multi-layer support
}
);
map . on ( 'style.load' , function () {
// Create 5 separate layers
for ( let i = 1 ; i <= 5 ; i ++ ) {
const origin = [ 148.9819 - (( i - 1 ) * 0.0003 ), - 35.39847 ];
const minZoom = 16 + ( i * 0.4 );
const maxZoom = 18 + ( i * 0.4 );
map . addLayer ({
id: '3d-layer-' + i ,
type: 'custom' ,
renderingMode: '3d' ,
onAdd : function ( map , gl ) {
tb . loadObj ({
type: 'gltf' ,
obj: './models/building.glb' ,
units: 'meters' ,
scale: 333.22 / i ,
rotation: { x: 90 , y: 180 , z: 0 },
anchor: 'center'
}, function ( model ) {
model . setCoords ( origin );
model . addTooltip ( `Zoom: ${ minZoom } - ${ maxZoom } ` , true );
tb . add ( model , '3d-layer-' + i );
});
},
render : function ( gl , matrix ) {
// No tb.update() call needed!
}
}, 'waterway-label' );
// Set zoom visibility range
tb . setLayerZoomRange ( '3d-layer-' + i , minZoom , maxZoom );
}
});
Without multiLayer: true, you must call tb.update() in each layer’s render function, which can cause performance issues.
Benefits
Single Update Call All layers share one tb.update() call, reducing overhead
Layer Independence Each layer can be toggled independently
Zoom Control Different layers at different zoom levels
Cleaner Code No need for update calls in each render function