Add 3D model with style-driven interactions
This example illustrates a style-driven method for overriding the materials and orientation of model parts using the model layer and model-source.
The style-driven approach allows using the expression system, to control material properties and model part orientations from feature states.
EXAMPLE
Add 3D model with source-driven interactions
For an alternative method that updates the model source directly and efficiently, see this example.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add 3D model with style-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 for style-driven method.
'3d-model-source': {
'type': 'model',
'models': {
'car': {
'uri': 'https://docs.mapbox.com/mapbox-gl-js/assets/ego_car.glb',
'position': [-74.0135, 40.7153],
'orientation': [0, 0, 0],
// List of material names to be overridden in paint expressions.
'materialOverrideNames': [
'body',
'lights_brakes',
'lights-brakes_reverse',
'lights_brakes_volume',
'lights-brakes_reverse_volume'
],
// List of node names to be overridden in paint expressions.
'nodeOverrideNames': [
'doors_front-left',
'doors_front-right',
'hood',
'trunk'
]
}
}
}
},
'layers': [
{
'id': 'background',
'type': 'background',
'paint': {
'background-color': 'lightgray'
}
},
// Model layer utilizing feature 'state' to customize model materials/nodes
{
'id': '3d-model-layer',
'type': 'model',
'source': '3d-model-source',
'paint': {
'model-scale': [10, 10, 10],
'model-type': 'location-indicator',
'model-color': [
'match',
['get', 'part'],
'lights_brakes',
['feature-state', 'brake-light-color'],
'lights-brakes_reverse',
['feature-state', 'brake-light-color'],
'lights_brakes_volume',
['feature-state', 'brake-light-color'],
'lights-brakes_reverse_volume',
['feature-state', 'brake-light-color'],
['feature-state', 'vehicle-color']
],
'model-color-mix-intensity': [
'match',
['get', 'part'],
'body',
1.0,
'lights_brakes',
['feature-state', 'brake-light-emission'],
'lights-brakes_reverse',
['feature-state', 'brake-light-emission'],
'lights_brakes_volume',
['feature-state', 'brake-light-emission'],
'lights-brakes_reverse_volume',
['feature-state', 'brake-light-emission'],
0.0
],
'model-emissive-strength': [
'match',
['get', 'part'],
'lights_brakes',
['feature-state', 'brake-light-emission'],
'lights_brakes_volume',
['feature-state', 'brake-light-emission'],
'lights-brakes_reverse',
['feature-state', 'brake-light-emission'],
'lights-brakes_reverse_volume',
['feature-state', 'brake-light-emission'],
0.0
],
'model-opacity': [
'match',
['get', 'part'],
'lights_brakes_volume',
['feature-state', 'brake-light-emission'],
'lights-brakes_reverse_volume',
['feature-state', 'brake-light-emission'],
1.0
],
// Control rotation of model and model nodes.
// Node names listed in `overrideNodeNames` are matched using `['get','part']' expression.
// Rotation value is obtained from feature state.
'model-rotation': [
'match',
['get', 'part'],
'doors_front-left',
['feature-state', 'doors-front-left'],
'doors_front-right',
['feature-state', 'doors-front-right'],
'hood',
['feature-state', 'hood'],
'trunk',
['feature-state', 'trunk'],
[0.0, 0.0, 0.0]
]
}
}
]
};
const map = (window.map = new mapboxgl.Map({
container: 'map',
devtools: true,
projection: 'globe',
style,
pitch: 60,
zoom: 19.4,
bearing: 35,
center: [-74.0134, 40.7153]
}));
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 feature state used by expressions in style
const vehicleFeatureState = {
'vehicle-color': `rgba(${vehicleParams.color.r}, ${vehicleParams.color.g}, ${vehicleParams.color.b}, 1)`,
'brake-light-color': 'rgba(225, 0, 0, 1)',
'brake-light-emission': vehicleParams.brakeLights,
'doors-front-left': [
0.0,
mix(vehicleParams.doorsFrontLeft, 0, -doorOpeningDegMax),
0.0
],
'doors-front-right': [
0.0,
mix(vehicleParams.doorsFrontRight, 0, doorOpeningDegMax),
0.0
],
'hood': [mix(vehicleParams.hood, 0, 45), 0.0, 0.0],
'trunk': [mix(vehicleParams.trunk, 0, -60), 0.0, 0.0]
};
map.setFeatureState(
{ source: '3d-model-source', sourceLayer: '', id: 'car' },
vehicleFeatureState
);
}
// 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.once('style.load', () => {
updateModelState();
});
</script>
</body>
</html>
このコードスニペットは、
YOUR_MAPBOX_ACCESS_TOKENをあなたのMapboxアカウントのアクセストークンに置き換えるまで、期待通りに動作しません。このexampleは役に立ちましたか?