Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo/Example for using with Mapbox GL JS #295

Closed
Rakiah opened this issue Oct 13, 2022 · 7 comments
Closed

Demo/Example for using with Mapbox GL JS #295

Rakiah opened this issue Oct 13, 2022 · 7 comments
Labels
enhancement New feature or request

Comments

@Rakiah
Copy link

Rakiah commented Oct 13, 2022

Is your feature request related to a problem? Please describe.

Is there any sample code available for this project to work with mapbox gl js ?

Describe the solution you'd like

Just a sample code to see how it is possible to glue the camera from mapbox gl js to this project

Describe alternatives you've considered

Currently using deck.gl, which has many problems and bad computation of 3d tiles geometric error

@Rakiah Rakiah added the enhancement New feature or request label Oct 13, 2022
@gkjohnson
Copy link
Contributor

Hello! There is not - if you'd like to add an example demonstrating how to use the 3d tiles renderer to mapbox please feel free to make a PR! I'm happy to provide any pointers.

@chunlampang
Copy link

chunlampang commented Apr 7, 2023

Here is an example to add a cesium asset to mapbox.
However, there is a bug where the model will be hidden when pitch=0. Hope someone can solve it.

sample 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">
  <link href="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v2.13.0/mapbox-gl.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <script type="importmap">
    {
      "imports": {
        "three": "https://unpkg.com/three@0.150.1/build/three.module.js",
        "three/examples/": "https://unpkg.com/three@0.150.1/examples/",
        "3d-tiles-renderer": "https://unpkg.com/3d-tiles-renderer@0.3.17/src/index.js"
      }
    }
  </script>

  <script type="module">
    import { TilesRenderer, GLTFCesiumRTCExtension } from '3d-tiles-renderer';
    import * as THREE from 'three';
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
    import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

    const CESIUM_TOKEN = "your cesium token";
    mapboxgl.accessToken = "your mapbox token";

    const map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/light-v11',
      center: [114.18819431019777, 22.31391077950837],
      zoom: 16.5,
      pitch: 26.9,
      bearing: 48.5,
      antialias: true
    });

    map.on('style.load', async () => {
      const customLayer = await createCesiumLayer(1393025, [114.1876210, 22.31245660], 70);
      map.addLayer(customLayer, 'waterway-label');
    });

    async function createCesiumLayer(assetId, modelOrigin, modelAltitude) {
      const response = await fetch(`https://api.cesium.com/v1/assets/${assetId}/endpoint?access_token=` + CESIUM_TOKEN);
      const result = await response.json();

      let tilesRenderer = new TilesRenderer(result.url);
      tilesRenderer.fetchOptions.headers = { Authorization: `Bearer ${result.CESIUM_TOKEN}` };
      tilesRenderer.fetchOptions.mode = 'cors';

      const gltfLoader = new GLTFLoader(tilesRenderer.manager);
      gltfLoader.register(() => new GLTFCesiumRTCExtension());

      const dracoLoader = new DRACOLoader();
      dracoLoader.setDecoderPath('https://unpkg.com/three@0.150.1/examples/jsm/libs/draco/gltf/');
      gltfLoader.setDRACOLoader(dracoLoader);
      tilesRenderer.manager.addHandler(/(\.gltf|\.glb)$/, gltfLoader);

      const modelRotate = [Math.PI / 2, 0, 0];

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

      // transformation parameters to position, rotate and scale the 3D model onto the map
      const modelTransform = {
        translateX: modelAsMercatorCoordinate.x,
        translateY: modelAsMercatorCoordinate.y,
        translateZ: modelAsMercatorCoordinate.z,
        rotateX: modelRotate[0],
        rotateY: modelRotate[1],
        rotateZ: modelRotate[2],
        /* Since the 3D model is in real world meters, a scale transform needs to be
        * applied since the CustomLayerInterface expects units in MercatorCoordinates.
        */
        scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
      };
      const rotationX = new THREE.Matrix4().makeRotationAxis(
        new THREE.Vector3(1, 0, 0),
        modelTransform.rotateX
      );
      const rotationY = new THREE.Matrix4().makeRotationAxis(
        new THREE.Vector3(0, 1, 0),
        modelTransform.rotateY
      );
      const rotationZ = new THREE.Matrix4().makeRotationAxis(
        new THREE.Vector3(0, 0, 1),
        modelTransform.rotateZ
      );
      const 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);

      function rotationBetweenDirections(dir1, dir2) {
        const rotation = new THREE.Quaternion();
        const a = new THREE.Vector3().crossVectors(dir1, dir2);
        rotation.x = a.x;
        rotation.y = a.y;
        rotation.z = a.z;
        rotation.w = 1 + dir1.clone().dot(dir2);
        rotation.normalize();

        return rotation;
      }

      return {
        id: '3d-model',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function (map, gl) {
          this.camera = new THREE.Camera();
          this.scene = new THREE.Scene();

          // light
          const bgLight = new THREE.AmbientLight(0xffffff);
          this.scene.add(bgLight);

          const directionalLight = new THREE.DirectionalLight(0xbbbbbb);
          directionalLight.position.set(20, -70, -100).normalize();
          this.scene.add(directionalLight);

          const directionalLight2 = new THREE.DirectionalLight(0xbbbbbb);
          directionalLight2.position.set(-20, -70, 100).normalize();
          this.scene.add(directionalLight2);

          this.map = map;

          this.renderer = new THREE.WebGLRenderer({
            canvas: map.getCanvas(),
            context: gl,
            antialias: true
          });
          this.renderer.autoClear = false;

          tilesRenderer.onLoadTileSet = (tileset) => {
            const box = new THREE.Box3();
            const sphere = new THREE.Sphere();
            const matrix = new THREE.Matrix4();

            let position;
            let distanceToEllipsoidCenter;

            if (tilesRenderer.getOrientedBounds(box, matrix)) {
              position = new THREE.Vector3().setFromMatrixPosition(matrix);
              distanceToEllipsoidCenter = position.length();
            } else if (tilesRenderer.getBoundingSphere(sphere)) {
              position = sphere.center.clone();
              distanceToEllipsoidCenter = position.length();
            }

            const surfaceDirection = position.normalize();
            const up = new THREE.Vector3(0, 1, 0);
            const rotationToNorthPole = rotationBetweenDirections(surfaceDirection, up);

            tilesRenderer.group.quaternion.x = rotationToNorthPole.x;
            tilesRenderer.group.quaternion.y = rotationToNorthPole.y;
            tilesRenderer.group.quaternion.z = rotationToNorthPole.z;
            tilesRenderer.group.quaternion.w = rotationToNorthPole.w;

            tilesRenderer.group.position.y = -distanceToEllipsoidCenter;

          }
          tilesRenderer.setCamera(this.camera);
          tilesRenderer.setResolutionFromRenderer(this.camera, this.renderer);
          this.scene.add(tilesRenderer.group);
        },
        render: function (gl, matrix) {
          const m = new THREE.Matrix4().fromArray(matrix);
          this.camera.projectionMatrix = m.multiply(l);

          this.renderer.resetState();
          tilesRenderer.update();
          this.renderer.render(this.scene, this.camera);
          this.map.triggerRepaint();
        }
      };
    }
  </script>

</body>

</html>

@gkjohnson
Copy link
Contributor

However, there is a bug where the model will be hidden on some camera angle. Hope someone can solve it.

Please provide a JSFiddle or Code Sandbox demonstrating the issue so it's easier for me to check and understand what's happening.

@chunlampang
Copy link

chunlampang commented Apr 14, 2023

I think that example html file is able to run on any web server.
If you are asking for the token, I cannot expose the token to public.

@gkjohnson
Copy link
Contributor

Understood - where possible it's best to provide live examples with everything needed and unfortunately I don't have an API key. If you're able to reproduce the issue without mapbox (ie extract the camera matrix, tiles transform, etc passed into the tilesrenderer) I can take a look at the issue in a JSFiddle. Otherwise someone more familiar with MapBox will have to provide some info or when I have more availability I will have to dive into the mapbox project.

@tangerren
Copy link

@chunlampang Maybe the altitude of your model's central position is setting too low.
image

@gkjohnson
Copy link
Contributor

Closing in favor of #426

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants