0
votes

I'm trying to get my 3D object to move in my Mapbox created scene. I'm using a custom layer to create my object on the map with Three.js, as shown in the following code:

var map = action.map;

        var orign = {
            lon: -8.34,
            lat: 41.21
        }

        var camera, scene;

        var fromLL = function (lon, lat) {

            var extent = 20037508.34;

            var x = lon * extent / 180;
            var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
            y = y * extent / 180;

            return [(x + extent) / (2 * extent), 1 - ((y + extent) / (2 * extent))];
        }

        var translate = fromLL(orign.lon, orign.lat);

        const transform = {
            translateX: translate[0],
            translateY: translate[1],
            translateZ: 0,
            rotateX: Math.PI / 2,
            rotateY: 0,
            rotateZ: 0,
            scale: 5.41843220338983e-6
        }

        // configuration of the custom layer for a 3D model per the CustomLayerInterface

        var customLayer = {
            id: '3d-model' + uuid(),
            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 GLTFLoader();
                const bus = 'https://threejsfundamentals.org/threejs/resources/models/animals/Horse.gltf';
                loader.load(
                    bus,
                    (gltf) => {
                        this.scene.add(gltf.scene);
                    },
                    (xhr) => {
                        console.log(`${(xhr.loaded / xhr.total * 100)}% loaded`);
                    },
                    (error) => {
                        // called when loading has errors
                        console.error('An error happened', error);
                    }
                );

                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) {
                const rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), transform.rotateX);
                const rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), transform.rotateY);
                const rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), transform.rotateZ);

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

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

            }
        }

The next step I'm taking after putting my object in the scene is to use the methods provided for Mapbox to control the camera. My doubt is how to make the "setInterval" function change the positioning values of my previously rendered object and not only the camera on the map, through the "WASD" keys. Below is the code used that I am trying to adapt for the movement of my object:

map.on('load', function () {

            map.addLayer(customLayer);

            var keys = {};

            window.onkeyup = function (e) { keys[e.keyCode] = false; }
            window.onkeydown = function (e) { keys[e.keyCode] = true; }

            var heading = 180;

            setInterval(function () {
                var speed = 0;

                if (keys[68]) {
                    heading += 2;
                }
                if (keys[65]) {
                    heading -= 2;
                }
                if (keys[87]) {
                    speed = 0.0002;
                }
                if (keys[83]) {
                    speed = -0.0002;
                }

                var rad = heading * 0.0174532925;

                orign.lat += Math.cos(rad) * speed;
                orign.lon += Math.sin(rad) * speed;

                map.setBearing(heading);
                map.setCenter([orign.lon, orign.lat]);
            }, 1000 / 60);
        });

So far what I have is this: 3D Object in map

1
Hi welcome to Stackoverflow! Please read the tour to get a better understanding about how to add a minimal reproducible example!0stone0
I have read the same thing in different forums from different users, all of them from Italy. Is this like a competition or something?jscastro
Thank you for helping @0stone0!! It's not a competition @jscastro ... In my case it is for a university project.kaiU
Then all of your class colleagues are looking for the same, one posted the same in my github repo and also here, and another one herejscastro

1 Answers

0
votes

If you still interested on this... here's the example I have built based on your request of a WASD game-like demo in Mapbox. For this example I have used threebox which simplifies all the interaction with three.js and mapbox, avoiding you to deal with cameras, matrixes, perspectives and many more.

I also added some extra features to your question, such inertia, acceleration params and a fill-extrusion layer to paint the buildings in red when you crash with them with your truck.

enter image description here

Relevant code is below:

        mapboxgl.accessToken = "PASTE YOUR TOKEN HERE";

        let minZoom = 12;
        let mapConfig = {
          map: {
            center: [-122.4301905, 37.7298202],
            zoom: 20,
            pitch: 60,
            bearing: 38
          },
          truck: {
            origin: [-122.4301905, 37.7298202, 0],
            type: 'mtl',
            model: 'https://unpkg.com/threebox-plugin/examples/models/Truck',
            rotation: {
              x: 90,
              y: 0,
              z: 0
            },
            scale: 3,
            startRotation: {
              x: 0,
              y: 0,
              z: -38
            },
            date: new Date(2020, 6, 19, 23)
          },
          names: {
            compositeSource: "composite",
            compositeSourceLayer: "building",
            compositeLayer: "3d-buildings"
          }
        }

        let map = new mapboxgl.Map({
          container: 'map',
          style: 'mapbox://styles/mapbox/satellite-streets-v11',
          zoom: mapConfig.map.zoom,
          center: mapConfig.map.center,
          pitch: mapConfig.map.pitch,
          bearing: mapConfig.map.bearing,
          antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
        });

        window.tb = new Threebox(
          map,
          map.getCanvas().getContext('webgl'), {
            realSunlight: true,
            enableSelectingObjects: true,
            enableDraggingObjects: true,
            enableRotatingObjects: true,
            enableTooltips: true
          }
        );

        tb.setSunlight(new Date(2020, 6, 19, 23), map.getCenter());

        // parameters to ensure the model is georeferenced correctly on the map
        let truck;

        function createCustomLayer(layerName) {
          let model;
          //create the layer
          let customLayer3D = {
            id: layerName,
            type: 'custom',
            renderingMode: '3d',
            onAdd: function(map, gl) {

              let options = {
                type: mapConfig.truck.type, //model type
                obj: mapConfig.truck.model + '.obj', //model .obj url
                mtl: mapConfig.truck.model + '.mtl', //model .mtl url
                units: 'meters', // in meters
                scale: mapConfig.truck.scale, //x3 times is real size for this model
                rotation: mapConfig.truck.rotation, //default rotation
                anchor: 'top'
              }
              tb.loadObj(options, function(model) {
                truck = model.setCoords(mapConfig.truck.origin);
                truck.setRotation(mapConfig.truck.startRotation); //turn it to the initial street way
                truck.addTooltip("Drive with WASD keys", true, truck.anchor, true, 2);
                truck.castShadow = true;
                truck.selected = true;
                truck.addEventListener('ObjectChanged', onObjectChanged, false);

                tb.add(truck);
                init();

              });


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

        };

        function easing(t) {
          return t * (2 - t);
        }

        let velocity = 0.0,
          speed = 0.0,
          ds = 0.01;
        let keys;

        map.on('style.load', function() {
          let l = mapConfig.names.compositeLayer;
          if (api.buildings) {
            if (!map.getLayer(l)) {
              map.addLayer(createCompositeLayer(l));
            }
          }
          map.addLayer(createCustomLayer('3d-model'), 'waterway-label');

          map.getCanvas().focus();

        });

        function createCompositeLayer(layerId) {
          let layer = {
            'id': layerId,
            'source': mapConfig.names.compositeSource,
            'source-layer': mapConfig.names.compositeSourceLayer,
            'filter': ['==', 'extrude', 'true'],
            'type': 'fill-extrusion',
            'minzoom': minZoom,
            'paint': {
              'fill-extrusion-color': [
                'case',
                ['boolean', ['feature-state', 'select'], false],
                "red",
                ['boolean', ['feature-state', 'hover'], false],
                "lightblue",
                '#aaa'
              ],

              // use an 'interpolate' expression to add a smooth transition effect to the
              // buildings as the user zooms in
              'fill-extrusion-height': [
                'interpolate',
                ['linear'],
                ['zoom'],
                minZoom,
                0,
                minZoom + 0.05,
                ['get', 'height']
              ],
              'fill-extrusion-base': [
                'interpolate',
                ['linear'],
                ['zoom'],
                minZoom,
                0,
                minZoom + 0.05,
                ['get', 'min_height']
              ],
              'fill-extrusion-opacity': 0.9
            }
          };
          return layer;
        }

        let api = {
          buildings: true,
          acceleration: 5,
          inertia: 3
        };

        function init() {

          keys = {
            a: false,
            s: false,
            d: false,
            w: false
          };

          document.body.addEventListener('keydown', function(e) {

            const key = e.code.replace('Key', '').toLowerCase();
            if (keys[key] !== undefined)
              keys[key] = true;
          });
          document.body.addEventListener('keyup', function(e) {

            const key = e.code.replace('Key', '').toLowerCase();
            if (keys[key] !== undefined)
              keys[key] = false;
          });

          animate();

        }

        function animate() {

          requestAnimationFrame(animate);
          speed = 0.0;

          if (!(keys.w || keys.s)) {
            if (velocity > 0) {
              speed = -api.inertia * ds
            } else if (velocity < 0) {
              speed = api.inertia * ds
            }
            if (velocity > -0.0008 && velocity < 0.0008) {
              speed = velocity = 0.0;
              return;
            }
          }

          if (keys.w)
            speed = api.acceleration * ds;
          else if (keys.s)
            speed = -api.acceleration * ds;

          velocity += (speed - velocity) * api.acceleration * ds;
          if (speed == 0.0) {
            velocity = 0;
            return;
          }

          truck.set({
            worldTranslate: new THREE.Vector3(0, -velocity, 0)
          });

          let options = {
            center: truck.coordinates,
            bearing: map.getBearing(),
            easing: easing
          };

          function toDeg(rad) {
            return rad / Math.PI * 180;
          }

          function toRad(deg) {
            return deg * Math.PI / 180;
          }

          let deg = 1;
          let rad = toRad(deg);
          let zAxis = new THREE.Vector3(0, 0, 1);

          if (keys.a || keys.d) {
            rad *= (keys.d ? -1 : 1);
            truck.set({
              quaternion: [zAxis, truck.rotation.z + rad]
            });
            options.bearing = -toDeg(truck.rotation.z);
          }

          map.jumpTo(options);
          tb.map.update = true;

        }

        function onObjectChanged(e) {
          let model = e.detail.object; //here's the object already modified
          if (api.buildings) {
            let c = model.coordinates;
            let point = map.project(c);
            let features = map.queryRenderedFeatures(point, {
              layers: [mapConfig.names.compositeLayer]
            });
            if (features.length > 0) {
              light(features[0]); // crash!
            }
          }
        }

        function light(feature) {
          fHover = feature;
          map.setFeatureState({
            source: fHover.source,
            sourceLayer: fHover.sourceLayer,
            id: fHover.id
          }, {
            select: true
          });
        }