Updating 3d Model location location with mapbox

Viewed 1329

I have a 3D model placed in a map using mapbox with javascript (using this tutorial) and I'm trying to change the object's location when a button is clicked. The object isn't moving, though, and I don't really know how to fix it.

This is the function I wrote to make the location change (I can see all those console logs when I hit the button, so I know the part that isn't working is the actual location update itself):

function updateObjectLocation(id,newTransform) {
    let objectLayer = map.getLayer(id);
    if (typeof objectLayer === 'undefined') {
        console.log('layer ' + id + ' does not exist');
    } else {
        console.log(objectLayer);
        console.log(newTransform);
        objectLayer.render = function(gl, matrix) {
            var rotationX = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(1,0,0),
                newTransform.rotateX
            );
            var rotationY = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(0,1,0),
                newTransform.rotateY
            );
            var rotationZ = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(0,0,1),
                newTransform.rotateZ
            );

            var m = new THREE.Matrix4().fromArray(matrix);
            var l = new THREE.Matrix4()
                .makeTranslation(
                    newTransform.translateX,
                    newTransform.translateY,
                    newTransform.translateZ
                )
                .scale(
                    new THREE.Vector3(
                        newTransform.scale,
                        - newTransform.scale,
                        newTransform.scale
                    )
                )
                .multiply(rotationX)
                .multiply(rotationY)
                .multiply(rotationZ);

            this.camera.projectionMatrix = m.multiply(l);
            this.renderer.state.reset();
            this.renderer.render(this.scene, this.camera);
            this.map.triggerRepaint();
        };
    }
}
1 Answers

Welcome to Stackoverflow. The solution is much simpler, you only have to modify the transformation, no need to rewrite the render method. Here you have a fiddle with the solution how to move a 3D model

And here the full code

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Add a 3D model</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.11.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.11.1/mapbox-gl.css" rel="stylesheet" />
<style>
    body {
        margin: 0;
        padding: 0;
    }

    #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
    }
    #move {
        display: block;
        position: relative;
        margin: 0px auto;
        width: 50%;
        height: 40px;
        padding: 10px;
        border: none;
        border-radius: 3px;
        font-size: 12px;
        text-align: center;
        color: #fff;
        background: #ee8a65;
    }
</style>
</head>
<body>
<script src="https://unpkg.com/three@0.106.2/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader.js"> 
</script>
<div id="map"></div>

<button id="move">Move</button>
<script>
    mapboxgl.accessToken = 'pk.eyJ1IjoianNjYXN0cm8iLCJhIjoiY2s2YzB6Z25kMDVhejNrbXNpcmtjNGtpbiJ9.28ynPf1Y5Q8EyB_moOHylw';
    var map = (window.map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/light-v10',
        zoom: 18,
        center: [148.9819, -35.3981],
        pitch: 60,
        antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
    }));

    // parameters to ensure the model is georeferenced correctly on the map
    var modelOrigin = [148.9819, -35.39847];
    var modelOrigin2 = [148.9816, -35.39851];
    var modelAltitude = 0;
    var modelRotate = [Math.PI / 2, 0, 0];

    var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
        modelOrigin,
        modelAltitude
    );

    // transformation parameters to position, rotate and scale the 3D model onto the map
    var modelTransform = {
        translateX: modelAsMercatorCoordinate.x,
        translateY: modelAsMercatorCoordinate.y,
        translateZ: modelAsMercatorCoordinate.z,
        rotateX: modelRotate[0],
        rotateY: modelRotate[1],
        rotateZ: modelRotate[2],
        /* Since our 3D model is in real world meters, a scale transform needs to be
        * applied since the CustomLayerInterface expects units in MercatorCoordinates.
        */
        scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    };

    var THREE = window.THREE;

    // configuration of the custom layer for a 3D model per the CustomLayerInterface
    var customLayer = {
        id: '3d-model',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function (map, gl) {
            this.camera = new THREE.Camera();
            this.scene = new THREE.Scene();

            // create two three.js lights to illuminate the model
            var directionalLight = new THREE.DirectionalLight(0xffffff);
            directionalLight.position.set(0, -70, 100).normalize();
            this.scene.add(directionalLight);

            var directionalLight2 = new THREE.DirectionalLight(0xffffff);
            directionalLight2.position.set(0, 70, 100).normalize();
            this.scene.add(directionalLight2);

            // use the three.js GLTF loader to add the 3D model to the three.js scene
            var loader = new THREE.GLTFLoader();
            loader.load(
                'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
                function (gltf) {
                    this.scene.add(gltf.scene);
                }.bind(this)
            );
            this.map = map;

            // use the Mapbox GL JS map canvas for three.js
            this.renderer = new THREE.WebGLRenderer({
                canvas: map.getCanvas(),
                context: gl,
                antialias: true
            });

            this.renderer.autoClear = false;
        },
        render: function (gl, matrix) {
            var rotationX = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(1, 0, 0),
                modelTransform.rotateX
            );
            var rotationY = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(0, 1, 0),
                modelTransform.rotateY
            );
            var rotationZ = new THREE.Matrix4().makeRotationAxis(
                new THREE.Vector3(0, 0, 1),
                modelTransform.rotateZ
            );

            var m = new THREE.Matrix4().fromArray(matrix);
            var l = new THREE.Matrix4()
                .makeTranslation(
                    modelTransform.translateX,
                    modelTransform.translateY,
                    modelTransform.translateZ
                )
                .scale(
                    new THREE.Vector3(
                        modelTransform.scale,
                        -modelTransform.scale,
                        modelTransform.scale
                    )
                )
                .multiply(rotationX)
                .multiply(rotationY)
                .multiply(rotationZ);

            this.camera.projectionMatrix = m.multiply(l);
            this.renderer.state.reset();
            this.renderer.render(this.scene, this.camera);
            this.map.triggerRepaint();
        }
    };

    map.on('style.load', function () {
        map.addLayer(customLayer, 'waterway-label');
    });

    document.getElementById('move').addEventListener('click', function () {
        // creating an event listener to modify the position to modelOrigin2
        modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
            modelOrigin2,
            modelAltitude
        );

        // transformation parameters to position, rotate and scale the 3D model onto the map
        modelTransform = {
            translateX: modelAsMercatorCoordinate.x,
            translateY: modelAsMercatorCoordinate.y,
            translateZ: modelAsMercatorCoordinate.z,
            rotateX: modelRotate[0],
            rotateY: modelRotate[1],
            rotateZ: modelRotate[2],
            /* Since our 3D model is in real world meters, a scale transform needs to be
            * applied since the CustomLayerInterface expects units in MercatorCoordinates.
            */
            scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
        };

    });
</script>

</body>
</html>

BTW, if you are trying to create a much more interactive experience between Mapbox and Three.js, but I would recommend you to check out the latest version of threebox as it enables you to add as many models and 3D layers as you want but also to do things like these below with only a few lines of code

        map.on('style.load', function () {
        map.addLayer({
            id: 'custom_layer',
            type: 'custom',
            renderingMode: '3d',
            onAdd: function (map, mbxContext) {

                window.tb = new Threebox(
                    map,
                    mbxContext,
                    { defaultLights: true }
                );

                var options = {
                    obj: '/3D/soldier/soldier.glb',
                    type: 'gltf',
                    scale: 1,
                    units: 'meters',
                    rotation: { x: 90, y: 0, z: 0 } //default rotation
                }

                tb.loadObj(options, function (model) {
                    soldier = model.setCoords(origin);
                    tb.add(soldier);
                })

            },
            render: function (gl, matrix) {
                tb.update();
            }
        });
    })

- 3D models built-in and custom animations

3D models built-in and custom animations

- Full raycast support MouseOver/Mouseout, Selected, Drag&Drop, Drag&Rotate, Wireframe

MouseOver/Mouseout, Selected, Drag&Drop, Drag&Rotate, Wireframe

- CSS2D Tooltips and Labels that consider altitude

CSS2D Tooltips and Labels that consider altitude

**- Three.js and Mapbox cameras sync with depth adjustment **

Three.js and Mapbox cameras sync with depth adjustment

- Include geolocated models of monuments

Eiffel tower gif

Related