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

Add an example demonstrating how to use with MapboxGL #426

Open
xiaxiangfeng opened this issue Dec 21, 2023 · 11 comments
Open

Add an example demonstrating how to use with MapboxGL #426

xiaxiangfeng opened this issue Dec 21, 2023 · 11 comments
Labels
enhancement New feature or request

Comments

@xiaxiangfeng
Copy link

xiaxiangfeng commented Dec 21, 2023

Describe the bug

After adding 3Dtiles to mapboxgl using 3DTilesRendererJS, the model disappears when the rotated map is greater than 180 degrees

To Reproduce

Steps to reproduce the behavior:

  1. Using mapboxgl to customize layers and add 3D tiles
  2. Use the right mouse button to rotate the map
  3. When the rotation is greater than 180 degrees, the model disappears

Code
The address of the code on GitHub

https://github.com/xiaxiangfeng/3DTilesRendererJS.Test/tree/main/docs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Display a map on a webpage</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>
    <script
      async
      src="https://unpkg.com/[email protected]/dist/es-module-shims.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/[email protected]/build/three.module.js",
          "three/examples/jsm/": "https://unpkg.com/[email protected]/examples/jsm/"
        }
      }
    </script>

    <script type="module">
      import * as THREE from "three";
      import { DebugTilesRenderer as TilesRenderer } from "./3DTilesRendererJS/index.js";

      window.THREE = THREE;

      var origin = [113.37796671195645, 22.994043025862794];

      mapboxgl.accessToken =
        "pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscWYyNjU3azByd3gya3JxOTVrc2NkY3UifQ.362MspMnDi9ZGH-D6P1CtQ";
      const map = new mapboxgl.Map({
        container: "map",
        style: "mapbox://styles/mapbox/dark-v9",
        center: origin,
        zoom: 17.86614600777933,
        pitch: 70,
        bearing: -40,
      });

      const marker1 = new mapboxgl.Marker().setLngLat(origin).addTo(map);

      let centerM;

      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;
      }

      map.on("load", () => {
        let renderer, scene, camera, world, tiles;
        const mixers = [];

        map.addLayer({
          id: "custom_layer",
          type: "custom",
          renderingMode: "3d",
          onAdd: function (map, mbxContext) {
            const center = map.getCenter();
            this.center = mapboxgl.MercatorCoordinate.fromLngLat(
              [center.lng, center.lat],
              0
            );
            centerM = this.center;

            const { x, y, z } = centerM;

            const scale = centerM.meterInMercatorCoordinateUnits();

            this.cameraTransform = new THREE.Matrix4()
              .makeTranslation(x, y, z)
              .scale(new THREE.Vector3(scale, -scale, scale));

            renderer = new THREE.WebGLRenderer({
              alpha: true,
              antialias: true,
              canvas: map.getCanvas(),
              context: mbxContext,
            });

            renderer.shadowMap.enabled = true;
            renderer.autoClear = false;

            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(
              28,
              window.innerWidth / window.innerHeight,
              0.000000000001,
              Infinity
            );

            const url = "./baowei21/tileset.json";

            tiles = new TilesRenderer(url);

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

              let position;
              let distanceToEllipsoidCenter;

              if (tiles.getOrientedBounds(box, matrix)) {
                position = new THREE.Vector3().setFromMatrixPosition(matrix);
                distanceToEllipsoidCenter = position.length();
              } else if (tiles.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
              );

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

              tiles.group.position.y = -distanceToEllipsoidCenter;
            };

            tiles.setCamera(camera);
            tiles.setResolutionFromRenderer(camera, renderer);

            world = new THREE.Group();

            world.add(tiles.group);
            scene.add(world);
          },

          render: function (gl, matrix) {
            const mercatorMatrix = new THREE.Matrix4().fromArray(matrix);
            camera.projectionMatrix = mercatorMatrix.multiply(
              this.cameraTransform
            );

            renderer.resetState();

            tiles.update();
            // Render the scene and repaint the map
            renderer.render(scene, camera);
            map.triggerRepaint();
          },
        });
      });
    </script>
  </body>
</html>

Live example
https://xiaxiangfeng.github.io/3DTilesRendererJS.Test/mapbox.three.camera.3dtiles.html

Expected behavior

Display normally during rotation

Screenshots
normal
image

bug
Disappears after rotating 180 degrees
image

Platform:

  • Device: [Desktop, Mobile, ...]
  • OS: [Windows, MacOS, Linux, Android, iOS, ...]
  • Browser: [Chrome, Firefox, Safari, Edge, ...]
  • Three.js version: [r135]
  • Library version: [new]
@xiaxiangfeng xiaxiangfeng added the bug Something isn't working label Dec 21, 2023
@gkjohnson gkjohnson added question Further information is requested and removed bug Something isn't working labels Dec 21, 2023
@gkjohnson
Copy link
Contributor

Without a live, editable example it's not possible to debug - but there must be sometihng wrong with your integration with Mapbox since the model works in the live example in this project:

image

That aside your initialized "near" and "far" values are not valid, though it's not clear if this would cause issues.

            camera = new THREE.PerspectiveCamera(
              28,
              window.innerWidth / window.innerHeight,
              0.000000000001,
              Infinity
            );

@xiaxiangfeng
Copy link
Author

https://github.com/xiaxiangfeng/3DTilesRendererJS.Test/tree/main/docs

I created this dome on the codepen. I guess it should be the same issue with mapboxgl syncing with threejs, but I don't know how to fix this, hope I can get your help

codepen

https://codepen.io/xiaxiangfeng/pen/KKEKmgq

@gkjohnson
Copy link
Contributor

gkjohnson commented Dec 23, 2023

I've taken a look at the codepen and these settings strike me as odd:

  • The camera scale is set to a negative scale on the X axis:
this.cameraTransform = new THREE.Matrix4()
  .makeTranslation(x, y, z)
  .scale(new THREE.Vector3(scale, -scale, scale));
  • And setting the three.js camera projection matrix to include a camera transformation (and whatever the passed in MapGL matrix is) does not look right all:
const mercatorMatrix = new THREE.Matrix4().fromArray(matrix);
camera.projectionMatrix = mercatorMatrix.multiply(this.cameraTransform);

I don't know how the render mapbox API works but please make sure the transformations and projection are set correctly on the threejs camera - these are required for the tiles renderer to function.

@xiaxiangfeng
Copy link
Author

I have resolved this issue and make a PR. Waiting for merge

@gkjohnson gkjohnson changed the title the model disappears when the rotated map is greater than 180 degrees Mapbox Integration: the model disappears when the rotated map is greater than 180 degrees Jan 9, 2024
@xiaxiangfeng
Copy link
Author

xiaxiangfeng commented Jan 10, 2024

I have resolved this issue and make a PR. Waiting for merge

Currently, there are still problems that have not been solved when using mapboxgl. The specific how to use it and the existing problems are in the PR at the address below. I hope someone can complete this PR or provide some help and suggestions in the future.

#435

@gkjohnson gkjohnson changed the title Mapbox Integration: the model disappears when the rotated map is greater than 180 degrees Add an example demonstrating how to use with MapboxGL Jan 11, 2024
@gkjohnson gkjohnson added enhancement New feature or request and removed question Further information is requested labels Jan 11, 2024
@myPc1994
Copy link

myPc1994 commented Nov 22, 2024

<!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/v3.8.0/mapbox-gl.css" rel="stylesheet">
	<script src="https://api.mapbox.com/mapbox-gl-js/v3.8.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/[email protected]/build/three.module.js",
        "three/examples/": "https://unpkg.com/[email protected]/examples/",
        "3d-tiles-renderer": "https://unpkg.com/[email protected]/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'

		mapboxgl.accessToken =
			"pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscWYyNjU3azByd3gya3JxOTVrc2NkY3UifQ.362MspMnDi9ZGH-D6P1CtQ";
		const CESIUM_TOKEN = "your cesium token";
		const map = new mapboxgl.Map({
			container: 'map',
			style: 'mapbox://styles/mapbox/streets-v12',
			center: [121, 31],
			zoom: 3,
			pitch: 12,
			bearing: 0,
			antialias: true
		});
		function radiansToDegrees(radians) {
			return radians * (180 / Math.PI);
		}
		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;
		}
		function getOriginMatrix4(tilesRenderer, position,other) {
			const cart = {};
			tilesRenderer.ellipsoid.getPositionToCartographic(position, cart);
			const lon = radiansToDegrees(cart.lon);
			const lat = radiansToDegrees(cart.lat);
			new mapboxgl.Marker().setLngLat([lon,lat]).addTo(map);
			new mapboxgl.Marker().setLngLat([lon-0.007,lat+0.066]).addTo(map);
			map.flyTo({
				center:[lon,lat],
				zoom:15
			})
			const modelRotate = other.rotate;
			const height = other.height || 	cart.height;
			const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat([lon, lat], height);
			const scale = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits();
			const rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelRotate[0]);
			const rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelRotate[1]);
			const rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelRotate[2]);
			return new THREE.Matrix4()
				.makeTranslation(
					modelAsMercatorCoordinate.x,
					modelAsMercatorCoordinate.y,
					modelAsMercatorCoordinate.z
				)
				.scale(new THREE.Vector3(scale, -scale, scale))
				.multiply(rotationX)
				.multiply(rotationY)
				.multiply(rotationZ);
		}
		
		function initThree(map, gl) {
			const camera = new THREE.Camera();
			// const {width,height} = map._canvas
			// const camera = new THREE.PerspectiveCamera(map.transform.fov, width / height, 0.001, 4000 );
		
			const scene = new THREE.Scene();
			// light
			const bgLight = new THREE.AmbientLight(0xffffff);
			scene.add(bgLight);

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

			const directionalLight2 = new THREE.DirectionalLight(0xbbbbbb);
			directionalLight2.position.set(-20, -70, 100).normalize();
			scene.add(directionalLight2);
			const renderer = new THREE.WebGLRenderer({
				canvas: map.getCanvas(),
				context: gl,
				antialias: true
			});
			renderer.autoClear = false;
			return { camera, scene, renderer }
		}
		function addLayer(url,height) {
			let tilesRenderer, camera, renderer, scene, originMatrix4 = new THREE.Matrix4();

			map.addLayer({
				id: '3d-model',
				type: 'custom',
				renderingMode: '3d',
				onAdd: function (map, gl) {
					const threeInfo = initThree(map, gl);
					camera = threeInfo.camera;
					scene = threeInfo.scene;
					renderer = threeInfo.renderer;
					tilesRenderer = new TilesRenderer(url);
					const gltfLoader = new GLTFLoader(tilesRenderer.manager);
					gltfLoader.register(() => new GLTFCesiumRTCExtension());

					const dracoLoader = new DRACOLoader();
					dracoLoader.setDecoderPath('https://unpkg.com/[email protected]/examples/jsm/libs/draco/gltf/');
					gltfLoader.setDRACOLoader(dracoLoader);
					tilesRenderer.manager.addHandler(/(\.gltf|\.glb)$/, gltfLoader);
					let isRootLoad = true;
					tilesRenderer.onLoadTileSet = (tileset) => {
						const box = new THREE.Box3();
						const sphere = new THREE.Sphere();
						const matrix = new THREE.Matrix4();

						let position;
						if (tilesRenderer.getOrientedBoundingBox(box, matrix)) {
							position = new THREE.Vector3().setFromMatrixPosition(matrix);
						} else if (tilesRenderer.getBoundingSphere(sphere)) {
							position = sphere.center.clone();
						}
						const distanceToEllipsoidCenter = position.length();
						if (isRootLoad) {
							isRootLoad = false;
							originMatrix4 = getOriginMatrix4(tilesRenderer, position,height);
						}
						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(camera);
					tilesRenderer.setResolutionFromRenderer(camera, renderer);
					scene.add(tilesRenderer.group);
				},
				render: function (gl, matrix) {
					const m = new THREE.Matrix4().fromArray(matrix);
					camera.projectionMatrix = m.multiply(originMatrix4)
					renderer.resetState();
					tilesRenderer.update();
					renderer.render(scene, camera);
					map.triggerRepaint();

				}
			});
		}
		map.on('style.load', async () => {
			//TODO Question 1: The y-axis of rotate has different rotation angles at different latitudes and longitudes. The following two examples show
			//TODO Question 2: When the position is far away from the center of the model, there will be position offset. In demo-1, I drew two marks, one is the center point of the model and the other is the farther position.
			
			// demo-1
			addLayer("http://data.mars3d.cn/3dtiles/jzw-shanghai/tileset.json",{
				rotate: [Math.PI / 2, 342 / 360 * Math.PI, 0]
			});
			// demo-2
			// addLayer("http://data.mars3d.cn/3dtiles/qx-dyt/tileset.json",{
			// 	height:10,
			// 	rotate: [Math.PI/2 ,348/360 *Math.PI, 0]
			// });
		});

	</script>

</body>

</html>
  1. Question 1: The y-axis of rotate has different rotation angles at different latitudes and longitudes. The following two examples show

  2. Question 2: When the position is far away from the center of the model, there will be position offset. In demo-1, I drew two marks, one is the center point of the model and the other is the farther position.

7
8

@gkjohnson
Copy link
Contributor

I'm not all that familiar with MapBox so I can't say exactly what the issues might be but I looked through your code briefly. I can't say if all of the math is right (I recommend using three.js' math functions rather than manually assigning quaternion fields, for example) and I don't know anything about the MapboxGL coordinate system but here are some thoughts:

Question 1: The y-axis of rotate has different rotation angles at different latitudes and longitudes. The following two examples show

I assume you're referring to the 342 / 360 factor you're using in rotate: [Math.PI / 2, 342 / 360 * Math.PI, 0]? The Cesium system defines the globe orientation using the WGS84 system meaning +X points in the direction of 0, 0 latitude and longitude. You'll have to make sure both your data set and Mapbox's globe definitions are aligned.

Question 2: When the position is far away from the center of the model, there will be position offset. In demo-1, I drew two marks, one is the center point of the model and the other is the farther position.

Similar to question 1 - it seems there is something out of sync regarding the generated data set and the Mapbox globe surface. Or something in the math setup is incorrect.

@myPc1994
Copy link

I have tried two canvases, and then the camera synchronization method (this reference is threebox), which is the same as above. I don't know how I should deal with it. I went to see the source code of mapbox, and the code for calculating the camera seems to be here https://github.com/mapbox/mapbox-gl-js/blob/main/src/geo/transform.ts#L2224

@myPc1994
Copy link

I have a puzzle point. I added an axesHelper to three, and I see that the 3dtiles data we loaded is shown in the figure, which is not exactly aligned with XYZ.
9

@gkjohnson
Copy link
Contributor

I see that the 3dtiles data we loaded is shown in the figure, which is not exactly aligned with XYZ.

Without seeing how the axis gizmo is added there's not much that can be said about it's alignment. The image looks reasonable to me, though, assuming it represents the root of the tile set. The tiles are just oriented and positioned so the sit on the surface of the globe.

I have tried two canvases, and then the camera synchronization method (this reference is threebox), which is the same as above. I don't know how I should deal with it.

Unfortunately I don't know that much about Mapbox and I don't have the bandwidth to investigate this in detail so I will have to leave this up to someone else to investigate. There are a few places where differences can lead to these kind of visual inconsistencies, including the original data sets being loaded, which should be verified and understood to make sure everything is displaying correctly. The 342 / 360 factor in the rotation seems indicative of a misunderstanding or misalignment in the source data sets being loaded. But again I'm not familiar with Mapbox GL data or the data provided by Mars3D.

@myPc1994
Copy link

Thank you very much for your help. I don't have the ability to solve this problem.

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

3 participants