Skip to main content

Overview

Threadbox provides a comprehensive interaction system for 3D objects and fill-extrusion features. Enable mouse interactions to allow users to select, drag, rotate, and interact with objects on your map.

Enabling Interactions

Configure interactions when creating your Threebox instance:
const tb = new Threebox(map, gl, {
  defaultLights: true,
  enableSelectingObjects: true,    // Enable object selection
  enableDraggingObjects: true,      // Enable object dragging
  enableRotatingObjects: true,      // Enable object rotation
  enableSelectingFeatures: true,    // Enable fill-extrusion selection
  enableTooltips: true,             // Enable default tooltips
  enableHelpTooltips: true          // Enable help tooltips during interactions
});
enableSelectingObjects must be true for dragging and rotating to work.

Object Selection

Basic Selection

Click on objects to select them:
const model = await tb.loadObj({
  obj: '/models/building.glb',
  type: 'gltf',
  scale: 1,
  units: 'meters'
});

model.setCoords([-122.4194, 37.7749]);
tb.add(model);

// Object can now be selected by clicking

Selection Events

Listen to selection state changes:
model.addEventListener('SelectedChange', (e) => {
  const isSelected = e.detail.selected;
  
  if (isSelected) {
    console.log('Object selected:', e.detail.uuid);
    
    // Fly camera to object
    if (e.detail.userData.feature) {
      map.flyTo({
        center: e.detail.userData.feature.properties.camera,
        zoom: 18,
        pitch: 60
      });
    }
  } else {
    console.log('Object deselected');
  }
}, false);

Programmatic Selection

Select/deselect objects programmatically:
// Select object
model.selected = true;

// Deselect object
model.selected = false;

// Check selection state
if (model.selected) {
  console.log('Object is selected');
}

Object Dragging

Horizontal Dragging

Hold Shift + Click & Drag to move objects horizontally:
const tb = new Threebox(map, gl, {
  enableSelectingObjects: true,
  enableDraggingObjects: true
});

// Object can now be dragged horizontally
model.addEventListener('ObjectDragged', (e) => {
  const action = e.detail.draggedAction;
  const coords = e.detail.coordinates;
  
  if (action === 'translate') {
    console.log('Object moved to:', coords);
  }
}, false);

Vertical Dragging

Hold Ctrl + Click & Drag to change object altitude:
model.addEventListener('ObjectDragged', (e) => {
  const action = e.detail.draggedAction;
  
  if (action === 'altitude') {
    const altitude = e.detail.coordinates[2];
    console.log('Object altitude:', altitude, 'meters');
  }
}, false);

Drag Configuration

Configure dragging behavior:
// Set grid step (precision in decimal places)
tb.gridStep = 6; // 6 decimals = ~11.1cm precision

// Set altitude step
tb.altitudeStep = 0.1; // 0.1 meters = 10cm

Object Rotation

Hold Alt + Click & Drag to rotate objects around their Z-axis:
const tb = new Threebox(map, gl, {
  enableSelectingObjects: true,
  enableRotatingObjects: true
});

model.addEventListener('ObjectDragged', (e) => {
  const action = e.detail.draggedAction;
  
  if (action === 'rotate') {
    const rotation = e.detail.rotation;
    console.log('Object rotated to:', rotation.z, 'degrees');
  }
}, false);

// Configure rotation step size
tb.rotationStep = 5; // 5 degrees per step

Mouse Hover Events

Mouse Over

Detect when the mouse enters an object:
model.addEventListener('ObjectMouseOver', (e) => {
  console.log('Mouse over object:', e.detail.uuid);
  
  // Change cursor
  map.getCanvasContainer().style.cursor = 'pointer';
  
  // Show additional info
  displayObjectInfo(e.detail);
}, false);

Mouse Out

Detect when the mouse leaves an object:
model.addEventListener('ObjectMouseOut', (e) => {
  console.log('Mouse left object');
  
  // Reset cursor
  map.getCanvasContainer().style.cursor = tb.defaultCursor;
  
  // Hide info
  hideObjectInfo();
}, false);

Hover State

Access and control hover state:
// Check if object is being hovered
if (model.over) {
  console.log('Mouse is over this object');
}

// Programmatically set hover state (not recommended)
model.over = true;

Fill-Extrusion Features

Interact with Mapbox fill-extrusion layers:
const tb = new Threebox(map, gl, {
  enableSelectingFeatures: true,
  enableTooltips: true
});

// Listen for feature selection
map.on('SelectedFeatureChange', (e) => {
  const feature = e.detail;
  
  console.log('Selected building:', feature.properties.name);
  console.log('Height:', feature.properties.height);
});

Bounding Boxes

Visual feedback for selected/hovered objects:
// Enable bounding box on object creation
tb.loadObj({
  obj: '/models/car.glb',
  type: 'gltf',
  bbox: true // Show bounding box when selected/hovered
}, (model) => {
  model.setCoords(origin);
  tb.add(model);
});

// Access bounding box elements
if (model.boundingBox) {
  console.log('Bounding box exists');
}

if (model.boundingBoxShadow) {
  console.log('Shadow box exists');
}

Custom Cursor

Customize the default cursor:
// Set default cursor
tb.defaultCursor = 'grab';

// Change cursor on interaction
model.addEventListener('ObjectMouseOver', () => {
  map.getCanvasContainer().style.cursor = 'pointer';
});

model.addEventListener('ObjectMouseOut', () => {
  map.getCanvasContainer().style.cursor = tb.defaultCursor;
});

Raycasted Property

Control whether objects can be selected:
// Disable raycasting for specific object
model.raycasted = false; // Object won't be selectable

// Re-enable raycasting
model.raycasted = true;

// Check raycasting state
if (model.raycasted) {
  console.log('Object can be selected');
}

Complete Interaction Example

map.on('load', () => {
  const tb = new Threebox(map, gl, {
    defaultLights: true,
    enableSelectingObjects: true,
    enableDraggingObjects: true,
    enableRotatingObjects: true,
    enableTooltips: true,
    enableHelpTooltips: true
  });
  
  map.addLayer({
    id: 'custom-layer',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function(map, gl) {
      tb.loadObj({
        obj: '/models/building.glb',
        type: 'gltf',
        scale: 1,
        units: 'meters',
        rotation: { x: 90, y: 0, z: 0 },
        bbox: true,
        tooltip: true
      }, (model) => {
        const origin = [-122.4194, 37.7749, 0];
        model.setCoords(origin);
        tb.add(model);
        
        // Selection event
        model.addEventListener('SelectedChange', (e) => {
          if (e.detail.selected) {
            console.log('Building selected');
            
            // Update UI
            document.getElementById('info-panel').style.display = 'block';
            document.getElementById('building-name').textContent = 'Custom Building';
          } else {
            document.getElementById('info-panel').style.display = 'none';
          }
        });
        
        // Drag event
        model.addEventListener('ObjectDragged', (e) => {
          const action = e.detail.draggedAction;
          const coords = e.detail.coordinates;
          
          switch(action) {
            case 'translate':
              console.log('Moved to:', coords);
              updateCoordinatesDisplay(coords);
              break;
            case 'altitude':
              console.log('Altitude:', coords[2]);
              updateAltitudeDisplay(coords[2]);
              break;
            case 'rotate':
              console.log('Rotation:', e.detail.rotation.z);
              updateRotationDisplay(e.detail.rotation.z);
              break;
          }
          
          // Save new position
          saveObjectPosition(model.uuid, coords, e.detail.rotation);
        });
        
        // Mouse over event
        model.addEventListener('ObjectMouseOver', () => {
          map.getCanvasContainer().style.cursor = 'pointer';
          showBuildingHighlight(model);
        });
        
        // Mouse out event
        model.addEventListener('ObjectMouseOut', () => {
          if (!model.selected) {
            map.getCanvasContainer().style.cursor = tb.defaultCursor;
            hideBuildingHighlight(model);
          }
        });
      });
    },
    render: function(gl, matrix) {
      tb.update();
    }
  });
});

Keyboard Controls Summary

Shift + Drag

Move object horizontally (X/Y plane)

Ctrl + Drag

Move object vertically (altitude)

Alt + Drag

Rotate object around Z-axis

Performance Considerations

Enabling interactions adds event listeners and raycasting. For scenes with many objects:
  • Only enable interactions when needed
  • Use raycasted: false for non-interactive objects
  • Consider disabling bbox for objects that don’t need visual feedback

See Also

Build docs developers (and LLMs) love