Skip to main content

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:
Threebox.js:113-116
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:
Threebox.js:917-931
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:
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

Threebox.js:876-888
// 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:
Threebox.js:830-837
// 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:
Threebox.js:496-499
this.onZoom = function (e) {
  this.tb.zoomLayers.forEach((l) => {
    this.tb.toggleLayer(l);
  });
  this.tb.setObjectsScale();
}

Layer Management Methods

setLayoutProperty
function
Replicates map.setLayoutProperty for custom layers
Threebox.js:820-828
tb.setLayoutProperty(layerId, 'visibility', 'visible');
removeLayer
function
Removes a layer and clears its 3D objects
Threebox.js:974-978
tb.removeLayer(layerId);
setLayerHeigthProperty
function
Sets the height of all objects in a layer
Threebox.js:840-861
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

Build docs developers (and LLMs) love