> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/mapbox-gl-js/llms.txt)

# 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`](https://docs.mapbox.com/style-spec/reference/layers/#model) layer and [`model-source`](https://docs.mapbox.com/style-spec/reference/sources/#model).

The style-driven approach allows using the expression system, to control material properties and model part orientations from [`feature states`](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#setfeaturestate).

> **Related content (example): [Add 3D model with source-driven interactions](https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model-with-interactions-source-driven/)**
> 
> For an alternative method that updates the model source directly and efficiently, see this example.

> Example code:

**JavaScript**

```html
<!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.30.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.30.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>
    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({
        // TO MAKE THE MAP APPEAR YOU MUST
        // ADD YOUR ACCESS TOKEN FROM
        // https://account.mapbox.com
        accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
        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>
```