Add 3D model with source-driven interactions
This example illustrates a source-driven method for overriding the materials and orientation of model parts using model layer and model-source.
To control material properties and model part orientations, the model source is updated directly using setModels.
This approach is efficient, particularly for bulk updates.
EXAMPLE
Add 3D model with style-driven interactions
For an alternative method that declares model overrides using expressions directly in the style, see this example.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add 3D model with source-driven interactions</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.17.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.17.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.4/dist/tweakpane.min.js"></script>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const style = {
'version': 8,
'glyphs': 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
'lights': [
{
'type': 'ambient',
'id': 'environment',
'properties': {
'intensity': 0.4
}
},
{
'type': 'directional',
'id': 'sun_light',
'properties': {
'cast-shadows': true
}
}
],
'sources': {
// Set up model source
'3d-model-source': {
'type': 'model',
'models': {
'car': {
'uri': 'https://docs.mapbox.com/mapbox-gl-js/assets/ego_car.glb',
'position': [-74.0138, 40.7154],
'orientation': [0, 0, 0]
}
}
}
},
'layers': [
{
'id': 'background',
'type': 'background',
'paint': {
'background-color': 'lightgray'
}
},
// Model layer using model source '3d-model-source'.
{
'id': '3d-model-layer-for-source-based-updates',
'type': 'model',
'source': '3d-model-source',
'paint': {
'model-scale': [10, 10, 10],
'model-type': 'location-indicator'
}
}
]
};
const map = (window.map = new mapboxgl.Map({
container: 'map',
devtools: true,
projection: 'globe',
style,
pitch: 60,
zoom: 19.3,
bearing: 45,
center: [-74.01372, 40.71535]
}));
const vehicleParams = {
doorsFrontLeft: 0.5,
doorsFrontRight: 0,
trunk: 0,
hood: 0,
brakeLights: 0,
color: { r: 255, g: 255, b: 255, a: 1 }
};
function mix(t, a, b) {
return b * t - a * (t - 1);
}
function updateModelState() {
const doorOpeningDegMax = 80;
// Update contents of model source
const modelSource = map.getSource('3d-model-source');
if (modelSource) {
const modelsSpec = {
'car': {
'uri': 'https://docs.mapbox.com/mapbox-gl-js/assets/ego_car.glb',
'position': [-74.0138, 40.7154],
'orientation': [0, 0, 0],
'materialOverrides': {
'body': {
'model-color': [
vehicleParams.color.r / 255.0,
vehicleParams.color.g / 255.0,
vehicleParams.color.b / 255.0
],
'model-color-mix-intensity': 1.0
},
'lights_brakes': {
'model-color': [0.88, 0.0, 0.0],
'model-color-mix-intensity':
vehicleParams.brakeLights,
'model-emissive-strength': vehicleParams.brakeLights
},
'lights-brakes_reverse': {
'model-color': [0.88, 0.0, 0.0],
'model-color-mix-intensity':
vehicleParams.brakeLights,
'model-emissive-strength': vehicleParams.brakeLights
},
'lights_brakes_volume': {
'model-color': [0.88, 0.0, 0.0],
'model-color-mix-intensity': 1.0,
'model-emissive-strength': 0.8,
'model-opacity': vehicleParams.brakeLights
},
'lights-brakes_reverse_volume': {
'model-color': [0.88, 0.0, 0.0],
'model-color-mix-intensity': 1.0,
'model-emissive-strength': 0.8,
'model-opacity': vehicleParams.brakeLights
}
},
'nodeOverrides': {
'doors_front-left': {
'orientation': [
0.0,
mix(
vehicleParams.doorsFrontLeft,
0,
-doorOpeningDegMax
),
0.0
]
},
'doors_front-right': {
'orientation': [
0.0,
mix(
vehicleParams.doorsFrontRight,
0,
doorOpeningDegMax
),
0.0
]
},
'hood': {
'orientation': [
mix(vehicleParams.hood, 0, 45),
0.0,
0.0
]
},
'trunk': {
'orientation': [
mix(vehicleParams.trunk, 0, -60),
0.0,
0.0
]
}
}
}
};
modelSource.setModels(modelsSpec);
}
}
// Set up UI for parameters
{
// eslint-disable-next-line no-undef
const pane = new Tweakpane.Pane();
const carFolder = pane.addFolder({ title: 'Car' });
carFolder
.addInput(vehicleParams, 'color', {
label: 'Vehicle Color',
picker: 'inline'
})
.on('change', updateModelState);
carFolder
.addInput(vehicleParams, 'trunk', {
label: 'Trunk',
min: 0,
max: 1.0
})
.on('change', updateModelState);
carFolder
.addInput(vehicleParams, 'hood', {
label: 'Hood',
min: 0,
max: 1.0
})
.on('change', updateModelState);
carFolder
.addInput(vehicleParams, 'doorsFrontLeft', {
label: 'Front Left',
min: 0,
max: 1.0
})
.on('change', updateModelState);
carFolder
.addInput(vehicleParams, 'doorsFrontRight', {
label: 'Front Right',
min: 0,
max: 1.0
})
.on('change', updateModelState);
carFolder
.addInput(vehicleParams, 'brakeLights', {
label: 'Brake',
min: 0,
max: 1.0
})
.on('change', updateModelState);
}
map.on('load', () => {
updateModelState();
});
</script>
</body>
</html>
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN with an access token from your Mapbox account.Was this example helpful?