From 5a2ffe281e12f9beef1bfbcc5e3679f32af884b5 Mon Sep 17 00:00:00 2001
From: xxf <876029169@qq.com>
Date: Fri, 29 Dec 2023 18:28:42 +0800
Subject: [PATCH 1/5] Add Mapbox GL JS example
---
example/mapboxExample.html | 48 ++++++
example/mapboxExample.js | 145 +++++++++++++++++
example/mapboxExampleCamera.js | 287 +++++++++++++++++++++++++++++++++
3 files changed, 480 insertions(+)
create mode 100644 example/mapboxExample.html
create mode 100644 example/mapboxExample.js
create mode 100644 example/mapboxExampleCamera.js
diff --git a/example/mapboxExample.html b/example/mapboxExample.html
new file mode 100644
index 00000000..1de2d29b
--- /dev/null
+++ b/example/mapboxExample.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ 3D Tiles Mapbox GL JS Example
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/mapboxExample.js b/example/mapboxExample.js
new file mode 100644
index 00000000..05526de6
--- /dev/null
+++ b/example/mapboxExample.js
@@ -0,0 +1,145 @@
+import * as THREE from 'three';
+import CameraSync, { projectToWorld, projectedUnitsPerMeter } from './mapboxExampleCamera.js';
+import { DebugTilesRenderer as TilesRenderer } from '../src/index.js';
+
+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;
+
+}
+
+const origin = [ 116, 35 ];
+
+// TO MAKE THE MAP APPEAR YOU MUST
+// ADD YOUR ACCESS TOKEN FROM
+// https://account.mapbox.com
+// This is my private key, it is prohibited to use in the project
+window.mapboxgl.accessToken =
+ 'pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscTRzc245NTA5c3cya3BhdXA4MDY0NWQifQ._9T_J9z7Hvcd00nQL3UuSw';
+const map = new window.mapboxgl.Map( {
+ container: 'map',
+ style: 'mapbox://styles/mapbox/dark-v9',
+ zoom: 17.86614600777933,
+ pitch: 70,
+ bearing: - 40,
+ center: origin,
+} );
+
+map.on( 'load', () => {
+
+ let renderer, scene, camera, world, tiles, tilesGroup;
+
+ map.addLayer( {
+ id: 'custom_layer',
+ type: 'custom',
+ onAdd: function ( map, mbxContext ) {
+
+ 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.Camera();
+
+ world = new THREE.Group();
+ scene.add( world );
+
+ // lights
+ const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
+ dirLight.position.set( 1, 2, 3 );
+ world.add( dirLight );
+
+ const ambLight = new THREE.AmbientLight( 0xffffff, 1 );
+ world.add( ambLight );
+
+ tilesGroup = new THREE.Group();
+
+ world.add( tilesGroup );
+
+ new CameraSync( map, camera, world );
+
+ const url = 'https://xiaxiangfeng.github.io/3DTilesRendererJS.Test/baowei21/tileset.json';
+
+ tiles = new TilesRenderer( url );
+
+ tiles.setCamera( camera );
+ tiles.setResolutionFromRenderer( camera, renderer );
+
+ 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;
+
+ };
+
+ tilesGroup.add( tiles.group );
+
+ const pos = projectToWorld( origin );
+ tilesGroup.position.copy( pos );
+
+ const scale = projectedUnitsPerMeter( origin[ 1 ] );
+ tilesGroup.scale.set(
+ scale,
+ scale,
+ scale
+ );
+
+ tilesGroup.rotation.x = Math.PI / 2;
+
+ },
+
+ render: function ( gl, matrix ) {
+
+ renderer.resetState();
+ tiles.update();
+ renderer.render( scene, camera );
+ map.triggerRepaint();
+
+ },
+ } );
+
+} );
diff --git a/example/mapboxExampleCamera.js b/example/mapboxExampleCamera.js
new file mode 100644
index 00000000..2a47075a
--- /dev/null
+++ b/example/mapboxExampleCamera.js
@@ -0,0 +1,287 @@
+import * as THREE from 'three';
+
+const utils = {
+ clamp: function ( n, min, max ) {
+
+ return Math.min( max, Math.max( min, n ) );
+
+ },
+ makePerspectiveMatrix: function ( fovy, aspect, near, far ) {
+
+ var out = new THREE.Matrix4();
+ var f = 1.0 / Math.tan( fovy / 2 ),
+ nf = 1 / ( near - far );
+ var newMatrix = [
+ f / aspect,
+ 0,
+ 0,
+ 0,
+ 0,
+ f,
+ 0,
+ 0,
+ 0,
+ 0,
+ ( far + near ) * nf,
+ - 1,
+ 0,
+ 0,
+ 2 * far * near * nf,
+ 0,
+ ];
+
+ out.elements = newMatrix;
+ return out;
+
+ },
+};
+
+const WORLD_SIZE = 10240000;
+const FOV_ORTHO = ( 0.1 / 180 ) * Math.PI;
+const FOV = Math.atan( 3 / 4 );
+const EARTH_RADIUS = 6371008.8;
+const EARTH_CIRCUMFERENCE_EQUATOR = 40075017;
+const MapConstants = {
+ WORLD_SIZE: WORLD_SIZE,
+ PROJECTION_WORLD_SIZE: WORLD_SIZE / ( EARTH_RADIUS * Math.PI * 2 ),
+ MERCATOR_A: EARTH_RADIUS,
+ DEG2RAD: Math.PI / 180,
+ RAD2DEG: 180 / Math.PI,
+ EARTH_RADIUS: EARTH_RADIUS,
+ EARTH_CIRCUMFERENCE: 2 * Math.PI * EARTH_RADIUS,
+ EARTH_CIRCUMFERENCE_EQUATOR: EARTH_CIRCUMFERENCE_EQUATOR,
+ FOV_ORTHO: FOV_ORTHO,
+ FOV: FOV,
+ FOV_DEGREES: ( FOV * 180 ) / Math.PI,
+ TILE_SIZE: 512,
+};
+
+const Constants = MapConstants;
+
+function projectToWorld( coords ) {
+
+ var projected = [
+ - Constants.MERCATOR_A *
+ Constants.DEG2RAD *
+ coords[ 0 ] *
+ Constants.PROJECTION_WORLD_SIZE,
+ - Constants.MERCATOR_A *
+ Math.log( Math.tan( Math.PI * 0.25 + 0.5 * Constants.DEG2RAD * coords[ 1 ] ) ) *
+ Constants.PROJECTION_WORLD_SIZE,
+ ];
+
+ if ( ! coords[ 2 ] ) projected.push( 0 );
+ else {
+
+ var pixelsPerMeter = projectedUnitsPerMeter( coords[ 1 ] );
+ projected.push( coords[ 2 ] * pixelsPerMeter );
+
+ }
+
+ var result = new THREE.Vector3( projected[ 0 ], projected[ 1 ], projected[ 2 ] );
+
+ return result;
+
+}
+
+function projectedUnitsPerMeter( latitude ) {
+
+ return Math.abs(
+ Constants.WORLD_SIZE /
+ Math.cos( Constants.DEG2RAD * latitude ) /
+ Constants.EARTH_CIRCUMFERENCE
+ );
+
+}
+
+class CameraSync {
+
+ constructor( map, camera, world ) {
+
+ this.map = map;
+ this.camera = camera;
+ this.active = true;
+
+ this.camera.matrixAutoUpdate = false;
+
+ this.world = world || new THREE.Group();
+ this.world.position.x = this.world.position.y =
+ MapConstants.WORLD_SIZE / 2;
+ this.world.matrixAutoUpdate = false;
+
+ this.state = {
+ translateCenter: new THREE.Matrix4().makeTranslation(
+ MapConstants.WORLD_SIZE / 2,
+ - MapConstants.WORLD_SIZE / 2,
+ 0
+ ),
+ worldSizeRatio: MapConstants.TILE_SIZE / MapConstants.WORLD_SIZE,
+ worldSize: MapConstants.TILE_SIZE * this.map.transform.scale,
+ };
+
+ const _this = this;
+ this.map
+ .on( 'move', function () {
+
+ _this.updateCamera();
+
+ } )
+ .on( 'resize', function () {
+
+ _this.setupCamera();
+
+ } );
+
+ this.setupCamera();
+
+ }
+
+ setupCamera() {
+
+ const t = this.map.transform;
+ this.camera.aspect = t.width / t.height;
+ this.halfFov = t._fov / 2;
+ this.cameraToCenterDistance = ( 0.5 / Math.tan( this.halfFov ) ) * t.height;
+ const maxPitch = ( t._maxPitch * Math.PI ) / 180;
+ this.acuteAngle = Math.PI / 2 - maxPitch;
+ this.updateCamera();
+
+ }
+
+ updateCamera( ev ) {
+
+ if ( ! this.camera ) {
+
+ console.log( 'nocamera' );
+ return;
+
+ }
+
+ const t = this.map.transform;
+
+ this.camera.aspect = t.width / t.height;
+ let farZ = 0;
+ let furthestDistance = 0;
+ this.halfFov = t._fov / 2;
+ const groundAngle = Math.PI / 2 + t._pitch;
+ const pitchAngle = Math.cos( Math.PI / 2 - t._pitch );
+ this.cameraToCenterDistance = ( 0.5 / Math.tan( this.halfFov ) ) * t.height;
+
+ let pixelsPerMeter = 1;
+ const worldSize = this.worldSize();
+
+ pixelsPerMeter = this.mercatorZfromAltitude( 1, t.center.lat ) * worldSize;
+ const fovAboveCenter = t._fov * ( 0.5 + t.centerOffset.y / t.height );
+
+
+ const minElevationInPixels = t.elevation
+ ? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter
+ : 0;
+
+ const cameraToSeaLevelDistance =
+ ( t._camera.position[ 2 ] * worldSize - minElevationInPixels ) /
+ Math.cos( t._pitch );
+
+ const topHalfSurfaceDistance =
+ ( Math.sin( fovAboveCenter ) * cameraToSeaLevelDistance ) /
+ Math.sin(
+ utils.clamp(
+ Math.PI - groundAngle - fovAboveCenter,
+ 0.01,
+ Math.PI - 0.01
+ )
+ );
+
+
+ furthestDistance =
+ pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
+
+ const horizonDistance = cameraToSeaLevelDistance * ( 1 / t._horizonShift );
+ farZ = Math.min( furthestDistance * 1.01, horizonDistance );
+
+ this.cameraTranslateZ = new THREE.Matrix4().makeTranslation(
+ 0,
+ 0,
+ this.cameraToCenterDistance
+ );
+
+ const nz = t.height / 50;
+ const nearZ = Math.max( nz * pitchAngle, nz );
+
+ const h = t.height;
+ const w = t.width;
+ this.camera.projectionMatrix = utils.makePerspectiveMatrix(
+ t._fov,
+ w / h,
+ nearZ,
+ farZ
+ );
+
+ const cameraWorldMatrix = this.calcCameraMatrix( t._pitch, t.angle );
+
+ if ( t.elevation )
+ cameraWorldMatrix.elements[ 14 ] = t._camera.position[ 2 ] * worldSize;
+
+ this.camera.matrixWorld.copy( cameraWorldMatrix );
+
+ const zoomPow = t.scale * this.state.worldSizeRatio;
+ const scale = new THREE.Matrix4();
+ const translateMap = new THREE.Matrix4();
+ const rotateMap = new THREE.Matrix4();
+
+ scale.makeScale( zoomPow, zoomPow, zoomPow );
+
+ const x = t.x || t.point.x;
+ const y = t.y || t.point.y;
+ translateMap.makeTranslation( - x, y, 0 );
+ rotateMap.makeRotationZ( Math.PI );
+
+ this.world.matrix = new THREE.Matrix4()
+ .premultiply( rotateMap )
+ .premultiply( this.state.translateCenter )
+ .premultiply( scale )
+ .premultiply( translateMap );
+
+ }
+
+ worldSize() {
+
+ const t = this.map.transform;
+ return t.tileSize * t.scale;
+
+ }
+
+ mercatorZfromAltitude( altitude, lat ) {
+
+ return altitude / this.circumferenceAtLatitude( lat );
+
+ }
+
+ circumferenceAtLatitude( latitude ) {
+
+ return (
+ MapConstants.EARTH_CIRCUMFERENCE *
+ Math.cos( ( latitude * Math.PI ) / 180 )
+ );
+
+ }
+
+ calcCameraMatrix( pitch, angle, trz ) {
+
+ const t = this.map.transform;
+ const _pitch = pitch === undefined ? t._pitch : pitch;
+ const _angle = angle === undefined ? t.angle : angle;
+ const _trz = trz === undefined ? this.cameraTranslateZ : trz;
+
+ return new THREE.Matrix4()
+ .premultiply( _trz )
+ .premultiply( new THREE.Matrix4().makeRotationX( _pitch ) )
+ .premultiply( new THREE.Matrix4().makeRotationZ( _angle ) );
+
+ }
+
+}
+
+export { projectToWorld, projectedUnitsPerMeter };
+
+export default CameraSync;
From 5c17cffb847cbb51b32611fd4ce365670daef8a3 Mon Sep 17 00:00:00 2001
From: xxf <876029169@qq.com>
Date: Tue, 2 Jan 2024 18:36:47 +0800
Subject: [PATCH 2/5] Update mapboxExample
---
example/mapboxExample.js | 209 +++++++++++++++++++--------------
example/mapboxExampleCamera.js | 44 ++++---
2 files changed, 147 insertions(+), 106 deletions(-)
diff --git a/example/mapboxExample.js b/example/mapboxExample.js
index 05526de6..20a9e7ce 100644
--- a/example/mapboxExample.js
+++ b/example/mapboxExample.js
@@ -1,6 +1,26 @@
import * as THREE from 'three';
-import CameraSync, { projectToWorld, projectedUnitsPerMeter } from './mapboxExampleCamera.js';
+import CameraSync, {
+ projectToWorld,
+ projectedUnitsPerMeter,
+} from './mapboxExampleCamera.js';
import { DebugTilesRenderer as TilesRenderer } from '../src/index.js';
+import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
+
+const params = {
+
+ 'accessToken': 'pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscTRzc245NTA5c3cya3BhdXA4MDY0NWQifQ._9T_J9z7Hvcd00nQL3UuSw',
+ 'reload': reload,
+
+};
+
+// GUI
+const gui = new GUI();
+gui.width = 300;
+
+const mapboxglOptions = gui.addFolder( 'mapboxgl' );
+mapboxglOptions.add( params, 'accessToken' );
+mapboxglOptions.add( params, 'reload' );
+mapboxglOptions.open();
function rotationBetweenDirections( dir1, dir2 ) {
@@ -16,130 +36,141 @@ function rotationBetweenDirections( dir1, dir2 ) {
}
+let map;
const origin = [ 116, 35 ];
-// TO MAKE THE MAP APPEAR YOU MUST
-// ADD YOUR ACCESS TOKEN FROM
-// https://account.mapbox.com
-// This is my private key, it is prohibited to use in the project
-window.mapboxgl.accessToken =
- 'pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscTRzc245NTA5c3cya3BhdXA4MDY0NWQifQ._9T_J9z7Hvcd00nQL3UuSw';
-const map = new window.mapboxgl.Map( {
- container: 'map',
- style: 'mapbox://styles/mapbox/dark-v9',
- zoom: 17.86614600777933,
- pitch: 70,
- bearing: - 40,
- center: origin,
-} );
+init();
+
+function init() {
+
+ // TO MAKE THE MAP APPEAR YOU MUST
+ // ADD YOUR ACCESS TOKEN FROM
+ // https://account.mapbox.com
+ window.mapboxgl.accessToken = params.accessToken;
+ map = new window.mapboxgl.Map( {
+ container: 'map',
+ style: 'mapbox://styles/mapbox/dark-v9',
+ zoom: 17.86614600777933,
+ pitch: 70,
+ bearing: - 40,
+ center: origin,
+ } );
+
+ map.on( 'load', () => {
-map.on( 'load', () => {
+ let renderer, scene, camera, world, tiles, tilesGroup;
- let renderer, scene, camera, world, tiles, tilesGroup;
+ map.addLayer( {
+ id: 'custom_layer',
+ type: 'custom',
+ onAdd: function ( map, mbxContext ) {
- map.addLayer( {
- id: 'custom_layer',
- type: 'custom',
- onAdd: function ( map, mbxContext ) {
+ renderer = new THREE.WebGLRenderer( {
+ alpha: true,
+ antialias: true,
+ canvas: map.getCanvas(),
+ context: mbxContext,
+ } );
- renderer = new THREE.WebGLRenderer( {
- alpha: true,
- antialias: true,
- canvas: map.getCanvas(),
- context: mbxContext,
- } );
+ renderer.shadowMap.enabled = true;
+ renderer.autoClear = false;
- renderer.shadowMap.enabled = true;
- renderer.autoClear = false;
+ scene = new THREE.Scene();
+ camera = new THREE.Camera();
- scene = new THREE.Scene();
- camera = new THREE.Camera();
+ world = new THREE.Group();
+ scene.add( world );
- world = new THREE.Group();
- scene.add( world );
+ // lights
+ const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
+ dirLight.position.set( 1, 2, 3 );
+ world.add( dirLight );
- // lights
- const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
- dirLight.position.set( 1, 2, 3 );
- world.add( dirLight );
+ const ambLight = new THREE.AmbientLight( 0xffffff, 1 );
+ world.add( ambLight );
- const ambLight = new THREE.AmbientLight( 0xffffff, 1 );
- world.add( ambLight );
+ tilesGroup = new THREE.Group();
- tilesGroup = new THREE.Group();
+ world.add( tilesGroup );
- world.add( tilesGroup );
+ // Synchronize Camera change information to Threejs
+ new CameraSync( map, camera, world );
- new CameraSync( map, camera, world );
+ const url = 'https://raw.githubusercontent.com/NASA-AMMOS/3DTilesSampleData/master/msl-dingo-gap/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize/0528_0260184_to_s64o256_colorize_tileset.json';
- const url = 'https://xiaxiangfeng.github.io/3DTilesRendererJS.Test/baowei21/tileset.json';
+ tiles = new TilesRenderer( url );
- tiles = new TilesRenderer( url );
+ tiles.setCamera( camera );
+ tiles.setResolutionFromRenderer( camera, renderer );
- tiles.setCamera( camera );
- tiles.setResolutionFromRenderer( camera, renderer );
+ tiles.onLoadTileSet = ( tileset ) => {
- tiles.onLoadTileSet = ( tileset ) => {
+ const box = new THREE.Box3();
+ const sphere = new THREE.Sphere();
+ const matrix = new THREE.Matrix4();
- const box = new THREE.Box3();
- const sphere = new THREE.Sphere();
- const matrix = new THREE.Matrix4();
+ let position;
+ let distanceToEllipsoidCenter;
- let position;
- let distanceToEllipsoidCenter;
+ if ( tiles.getOrientedBounds( box, matrix ) ) {
- if ( tiles.getOrientedBounds( box, matrix ) ) {
+ position = new THREE.Vector3().setFromMatrixPosition( matrix );
+ distanceToEllipsoidCenter = position.length();
- position = new THREE.Vector3().setFromMatrixPosition( matrix );
- distanceToEllipsoidCenter = position.length();
+ } else if ( tiles.getBoundingSphere( sphere ) ) {
- } else if ( tiles.getBoundingSphere( sphere ) ) {
+ position = sphere.center.clone();
+ distanceToEllipsoidCenter = position.length();
- position = sphere.center.clone();
- distanceToEllipsoidCenter = position.length();
+ }
- }
+ const surfaceDirection = position.normalize();
+ const up = new THREE.Vector3( 0, 0, 0 );
+ const rotationToNorthPole = rotationBetweenDirections(
+ surfaceDirection,
+ up
+ );
- 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.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.group.position.y = - distanceToEllipsoidCenter;
+ };
- };
+ tilesGroup.add( tiles.group );
- tilesGroup.add( tiles.group );
+ const pos = projectToWorld( origin );
+ tilesGroup.position.copy( pos );
- const pos = projectToWorld( origin );
- tilesGroup.position.copy( pos );
+ // Since the 3D model is in real world meters, a scale transform needs to be
+ // applied since the CustomLayerInterface expects units in MercatorCoordinates.
+ const scale = projectedUnitsPerMeter( origin[ 1 ] );
+ tilesGroup.scale.set( scale, scale, scale );
- const scale = projectedUnitsPerMeter( origin[ 1 ] );
- tilesGroup.scale.set(
- scale,
- scale,
- scale
- );
+ tilesGroup.rotation.x = Math.PI;
- tilesGroup.rotation.x = Math.PI / 2;
+ },
- },
+ render: function ( gl, matrix ) {
- render: function ( gl, matrix ) {
+ renderer.resetState();
+ tiles.update();
+ renderer.render( scene, camera );
+ map.triggerRepaint();
- renderer.resetState();
- tiles.update();
- renderer.render( scene, camera );
- map.triggerRepaint();
+ },
+ } );
- },
} );
-} );
+}
+
+function reload() {
+
+ map.remove();
+ init();
+
+}
diff --git a/example/mapboxExampleCamera.js b/example/mapboxExampleCamera.js
index 2a47075a..a54fc4af 100644
--- a/example/mapboxExampleCamera.js
+++ b/example/mapboxExampleCamera.js
@@ -36,11 +36,13 @@ const utils = {
},
};
+// 512 * 2000
+// 512: TILE_SIZE
const WORLD_SIZE = 10240000;
-const FOV_ORTHO = ( 0.1 / 180 ) * Math.PI;
-const FOV = Math.atan( 3 / 4 );
-const EARTH_RADIUS = 6371008.8;
-const EARTH_CIRCUMFERENCE_EQUATOR = 40075017;
+const FOV_ORTHO = ( 0.1 / 180 ) * Math.PI; // Mapbox doesn't accept 0 as FOV
+const FOV = Math.atan( 3 / 4 ); // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/main/src/geo/transform.js#L93
+const EARTH_RADIUS = 6371008.8; // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L11
+const EARTH_CIRCUMFERENCE_EQUATOR = 40075017; // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L117
const MapConstants = {
WORLD_SIZE: WORLD_SIZE,
PROJECTION_WORLD_SIZE: WORLD_SIZE / ( EARTH_RADIUS * Math.PI * 2 ),
@@ -60,6 +62,7 @@ const Constants = MapConstants;
function projectToWorld( coords ) {
+ // Spherical mercator forward projection, re-scaling to WORLD_SIZE
var projected = [
- Constants.MERCATOR_A *
Constants.DEG2RAD *
@@ -70,6 +73,7 @@ function projectToWorld( coords ) {
Constants.PROJECTION_WORLD_SIZE,
];
+ // z dimension, defaulting to 0 if not provided
if ( ! coords[ 2 ] ) projected.push( 0 );
else {
@@ -101,14 +105,14 @@ class CameraSync {
this.map = map;
this.camera = camera;
this.active = true;
-
+ // We're in charge of the camera now!
this.camera.matrixAutoUpdate = false;
-
+ // Postion and configure the world group so we can scale it appropriately when the camera zooms
this.world = world || new THREE.Group();
this.world.position.x = this.world.position.y =
MapConstants.WORLD_SIZE / 2;
this.world.matrixAutoUpdate = false;
-
+ // set up basic camera state
this.state = {
translateCenter: new THREE.Matrix4().makeTranslation(
MapConstants.WORLD_SIZE / 2,
@@ -118,7 +122,7 @@ class CameraSync {
worldSizeRatio: MapConstants.TILE_SIZE / MapConstants.WORLD_SIZE,
worldSize: MapConstants.TILE_SIZE * this.map.transform.scale,
};
-
+ // Listen for move events from the map and update the Three.js camera
const _this = this;
this.map
.on( 'move', function () {
@@ -139,6 +143,7 @@ class CameraSync {
setupCamera() {
const t = this.map.transform;
+ // if aspect is not reset raycast will fail on map resize
this.camera.aspect = t.width / t.height;
this.halfFov = t._fov / 2;
this.cameraToCenterDistance = ( 0.5 / Math.tan( this.halfFov ) ) * t.height;
@@ -164,6 +169,7 @@ class CameraSync {
let furthestDistance = 0;
this.halfFov = t._fov / 2;
const groundAngle = Math.PI / 2 + t._pitch;
+ // pitch seems to influence heavily the depth calculation and cannot be more than 60 = PI/3 < v1 and 85 > v2
const pitchAngle = Math.cos( Math.PI / 2 - t._pitch );
this.cameraToCenterDistance = ( 0.5 / Math.tan( this.halfFov ) ) * t.height;
@@ -173,7 +179,8 @@ class CameraSync {
pixelsPerMeter = this.mercatorZfromAltitude( 1, t.center.lat ) * worldSize;
const fovAboveCenter = t._fov * ( 0.5 + t.centerOffset.y / t.height );
-
+ // Adjust distance to MSL by the minimum possible elevation visible on screen,
+ // this way the far plane is pushed further in the case of negative elevation.
const minElevationInPixels = t.elevation
? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter
: 0;
@@ -192,10 +199,10 @@ class CameraSync {
)
);
-
+ // Calculate z distance of the farthest fragment that should be rendered.
furthestDistance =
pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
-
+ // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
const horizonDistance = cameraToSeaLevelDistance * ( 1 / t._horizonShift );
farZ = Math.min( furthestDistance * 1.01, horizonDistance );
@@ -204,9 +211,10 @@ class CameraSync {
0,
this.cameraToCenterDistance
);
-
- const nz = t.height / 50;
- const nearZ = Math.max( nz * pitchAngle, nz );
+ // someday @ansis set further near plane to fix precision for deckgl,so we should fix it to use mapbox-gl v1.3+ correctly
+ // https://github.com/mapbox/mapbox-gl-js/commit/5cf6e5f523611bea61dae155db19a7cb19eb825c#diff-5dddfe9d7b5b4413ee54284bc1f7966d
+ const nz = t.height / 50; // min near z as coded by @ansis
+ const nearZ = Math.max( nz * pitchAngle, nz ); // on changes in the pitch nz could be too low
const h = t.height;
const w = t.width;
@@ -216,15 +224,17 @@ class CameraSync {
nearZ,
farZ
);
-
+ // Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
+ // If this is applied directly to the projection matrix, it will work OK but break raycasting
const cameraWorldMatrix = this.calcCameraMatrix( t._pitch, t.angle );
-
+ // When terrain layers are included, height of 3D layers must be modified from t_camera.z * worldSize
if ( t.elevation )
cameraWorldMatrix.elements[ 14 ] = t._camera.position[ 2 ] * worldSize;
-
+ //this.camera.matrixWorld.elements is equivalent to t._camera._transform
this.camera.matrixWorld.copy( cameraWorldMatrix );
const zoomPow = t.scale * this.state.worldSizeRatio;
+ // Handle scaling and translation of objects in the map in the world's matrix transform, not the camera
const scale = new THREE.Matrix4();
const translateMap = new THREE.Matrix4();
const rotateMap = new THREE.Matrix4();
From 9f5a7895fff18d7b8c50a57f8a1f7d368fe5d589 Mon Sep 17 00:00:00 2001
From: xxf <876029169@qq.com>
Date: Tue, 2 Jan 2024 19:22:00 +0800
Subject: [PATCH 3/5] Update mapboxExampleCamera
---
example/mapboxExampleCamera.js | 37 +++++++++++-----------------------
1 file changed, 12 insertions(+), 25 deletions(-)
diff --git a/example/mapboxExampleCamera.js b/example/mapboxExampleCamera.js
index a54fc4af..3e8b63ef 100644
--- a/example/mapboxExampleCamera.js
+++ b/example/mapboxExampleCamera.js
@@ -64,13 +64,8 @@ function projectToWorld( coords ) {
// Spherical mercator forward projection, re-scaling to WORLD_SIZE
var projected = [
- - Constants.MERCATOR_A *
- Constants.DEG2RAD *
- coords[ 0 ] *
- Constants.PROJECTION_WORLD_SIZE,
- - Constants.MERCATOR_A *
- Math.log( Math.tan( Math.PI * 0.25 + 0.5 * Constants.DEG2RAD * coords[ 1 ] ) ) *
- Constants.PROJECTION_WORLD_SIZE,
+ - Constants.MERCATOR_A * Constants.DEG2RAD * coords[ 0 ] * Constants.PROJECTION_WORLD_SIZE,
+ - Constants.MERCATOR_A * Math.log( Math.tan( Math.PI * 0.25 + 0.5 * Constants.DEG2RAD * coords[ 1 ] ) ) * Constants.PROJECTION_WORLD_SIZE,
];
// z dimension, defaulting to 0 if not provided
@@ -91,10 +86,7 @@ function projectToWorld( coords ) {
function projectedUnitsPerMeter( latitude ) {
return Math.abs(
- Constants.WORLD_SIZE /
- Math.cos( Constants.DEG2RAD * latitude ) /
- Constants.EARTH_CIRCUMFERENCE
- );
+ Constants.WORLD_SIZE / Math.cos( Constants.DEG2RAD * latitude ) / Constants.EARTH_CIRCUMFERENCE );
}
@@ -185,23 +177,18 @@ class CameraSync {
? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter
: 0;
- const cameraToSeaLevelDistance =
- ( t._camera.position[ 2 ] * worldSize - minElevationInPixels ) /
- Math.cos( t._pitch );
+ const cameraToSeaLevelDistance = ( t._camera.position[ 2 ] * worldSize - minElevationInPixels ) / Math.cos( t._pitch );
- const topHalfSurfaceDistance =
- ( Math.sin( fovAboveCenter ) * cameraToSeaLevelDistance ) /
- Math.sin(
- utils.clamp(
- Math.PI - groundAngle - fovAboveCenter,
- 0.01,
- Math.PI - 0.01
- )
- );
+ const topHalfSurfaceDistance = ( Math.sin( fovAboveCenter ) * cameraToSeaLevelDistance ) / Math.sin(
+ utils.clamp(
+ Math.PI - groundAngle - fovAboveCenter,
+ 0.01,
+ Math.PI - 0.01
+ )
+ );
// Calculate z distance of the farthest fragment that should be rendered.
- furthestDistance =
- pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
+ furthestDistance = pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
const horizonDistance = cameraToSeaLevelDistance * ( 1 / t._horizonShift );
farZ = Math.min( furthestDistance * 1.01, horizonDistance );
From 67cc2cab31281da29af0e70d4ca6f618f1cdda3a Mon Sep 17 00:00:00 2001
From: xxf <876029169@qq.com>
Date: Mon, 8 Jan 2024 14:42:21 +0800
Subject: [PATCH 4/5] Update mapboxExample
---
example/mapboxExample.js | 17 +++---
example/mapboxExampleCamera.js | 100 ++++-----------------------------
2 files changed, 20 insertions(+), 97 deletions(-)
diff --git a/example/mapboxExample.js b/example/mapboxExample.js
index 20a9e7ce..f5d231fd 100644
--- a/example/mapboxExample.js
+++ b/example/mapboxExample.js
@@ -6,9 +6,11 @@ import CameraSync, {
import { DebugTilesRenderer as TilesRenderer } from '../src/index.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
+// To use any of Mapbox's tools, APIs, or SDKs, you'll need a Mapbox access token
+// https://docs.mapbox.com/help/getting-started/access-tokens/
const params = {
- 'accessToken': 'pk.eyJ1IjoieGlheGlhbmdmbmVnIiwiYSI6ImNscTRzc245NTA5c3cya3BhdXA4MDY0NWQifQ._9T_J9z7Hvcd00nQL3UuSw',
+ 'accessToken': 'put-your-api-key-here',
'reload': reload,
};
@@ -81,14 +83,6 @@ function init() {
world = new THREE.Group();
scene.add( world );
- // lights
- const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
- dirLight.position.set( 1, 2, 3 );
- world.add( dirLight );
-
- const ambLight = new THREE.AmbientLight( 0xffffff, 1 );
- world.add( ambLight );
-
tilesGroup = new THREE.Group();
world.add( tilesGroup );
@@ -144,6 +138,7 @@ function init() {
const pos = projectToWorld( origin );
tilesGroup.position.copy( pos );
+ tilesGroup.position.z = 1;
// Since the 3D model is in real world meters, a scale transform needs to be
// applied since the CustomLayerInterface expects units in MercatorCoordinates.
@@ -157,7 +152,11 @@ function init() {
render: function ( gl, matrix ) {
renderer.resetState();
+
+ // update tiles
+ camera.updateMatrixWorld();
tiles.update();
+
renderer.render( scene, camera );
map.triggerRepaint();
diff --git a/example/mapboxExampleCamera.js b/example/mapboxExampleCamera.js
index 3e8b63ef..1cfa9edb 100644
--- a/example/mapboxExampleCamera.js
+++ b/example/mapboxExampleCamera.js
@@ -1,71 +1,24 @@
import * as THREE from 'three';
-const utils = {
- clamp: function ( n, min, max ) {
-
- return Math.min( max, Math.max( min, n ) );
-
- },
- makePerspectiveMatrix: function ( fovy, aspect, near, far ) {
-
- var out = new THREE.Matrix4();
- var f = 1.0 / Math.tan( fovy / 2 ),
- nf = 1 / ( near - far );
- var newMatrix = [
- f / aspect,
- 0,
- 0,
- 0,
- 0,
- f,
- 0,
- 0,
- 0,
- 0,
- ( far + near ) * nf,
- - 1,
- 0,
- 0,
- 2 * far * near * nf,
- 0,
- ];
-
- out.elements = newMatrix;
- return out;
-
- },
-};
-
// 512 * 2000
// 512: TILE_SIZE
const WORLD_SIZE = 10240000;
-const FOV_ORTHO = ( 0.1 / 180 ) * Math.PI; // Mapbox doesn't accept 0 as FOV
-const FOV = Math.atan( 3 / 4 ); // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/main/src/geo/transform.js#L93
const EARTH_RADIUS = 6371008.8; // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L11
-const EARTH_CIRCUMFERENCE_EQUATOR = 40075017; // from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L117
const MapConstants = {
WORLD_SIZE: WORLD_SIZE,
PROJECTION_WORLD_SIZE: WORLD_SIZE / ( EARTH_RADIUS * Math.PI * 2 ),
MERCATOR_A: EARTH_RADIUS,
DEG2RAD: Math.PI / 180,
- RAD2DEG: 180 / Math.PI,
- EARTH_RADIUS: EARTH_RADIUS,
EARTH_CIRCUMFERENCE: 2 * Math.PI * EARTH_RADIUS,
- EARTH_CIRCUMFERENCE_EQUATOR: EARTH_CIRCUMFERENCE_EQUATOR,
- FOV_ORTHO: FOV_ORTHO,
- FOV: FOV,
- FOV_DEGREES: ( FOV * 180 ) / Math.PI,
TILE_SIZE: 512,
};
-const Constants = MapConstants;
-
function projectToWorld( coords ) {
// Spherical mercator forward projection, re-scaling to WORLD_SIZE
var projected = [
- - Constants.MERCATOR_A * Constants.DEG2RAD * coords[ 0 ] * Constants.PROJECTION_WORLD_SIZE,
- - Constants.MERCATOR_A * Math.log( Math.tan( Math.PI * 0.25 + 0.5 * Constants.DEG2RAD * coords[ 1 ] ) ) * Constants.PROJECTION_WORLD_SIZE,
+ - MapConstants.MERCATOR_A * MapConstants.DEG2RAD * coords[ 0 ] * MapConstants.PROJECTION_WORLD_SIZE,
+ - MapConstants.MERCATOR_A * Math.log( Math.tan( Math.PI * 0.25 + 0.5 * MapConstants.DEG2RAD * coords[ 1 ] ) ) * MapConstants.PROJECTION_WORLD_SIZE,
];
// z dimension, defaulting to 0 if not provided
@@ -86,7 +39,7 @@ function projectToWorld( coords ) {
function projectedUnitsPerMeter( latitude ) {
return Math.abs(
- Constants.WORLD_SIZE / Math.cos( Constants.DEG2RAD * latitude ) / Constants.EARTH_CIRCUMFERENCE );
+ MapConstants.WORLD_SIZE / Math.cos( MapConstants.DEG2RAD * latitude ) / MapConstants.EARTH_CIRCUMFERENCE );
}
@@ -112,19 +65,18 @@ class CameraSync {
0
),
worldSizeRatio: MapConstants.TILE_SIZE / MapConstants.WORLD_SIZE,
- worldSize: MapConstants.TILE_SIZE * this.map.transform.scale,
};
// Listen for move events from the map and update the Three.js camera
- const _this = this;
+
this.map
- .on( 'move', function () {
+ .on( 'move', () =>{
- _this.updateCamera();
+ this.updateCamera();
} )
- .on( 'resize', function () {
+ .on( 'resize', () =>{
- _this.setupCamera();
+ this.setupCamera();
} );
@@ -157,42 +109,14 @@ class CameraSync {
const t = this.map.transform;
this.camera.aspect = t.width / t.height;
- let farZ = 0;
- let furthestDistance = 0;
+
this.halfFov = t._fov / 2;
- const groundAngle = Math.PI / 2 + t._pitch;
// pitch seems to influence heavily the depth calculation and cannot be more than 60 = PI/3 < v1 and 85 > v2
const pitchAngle = Math.cos( Math.PI / 2 - t._pitch );
this.cameraToCenterDistance = ( 0.5 / Math.tan( this.halfFov ) ) * t.height;
- let pixelsPerMeter = 1;
const worldSize = this.worldSize();
- pixelsPerMeter = this.mercatorZfromAltitude( 1, t.center.lat ) * worldSize;
- const fovAboveCenter = t._fov * ( 0.5 + t.centerOffset.y / t.height );
-
- // Adjust distance to MSL by the minimum possible elevation visible on screen,
- // this way the far plane is pushed further in the case of negative elevation.
- const minElevationInPixels = t.elevation
- ? t.elevation.getMinElevationBelowMSL() * pixelsPerMeter
- : 0;
-
- const cameraToSeaLevelDistance = ( t._camera.position[ 2 ] * worldSize - minElevationInPixels ) / Math.cos( t._pitch );
-
- const topHalfSurfaceDistance = ( Math.sin( fovAboveCenter ) * cameraToSeaLevelDistance ) / Math.sin(
- utils.clamp(
- Math.PI - groundAngle - fovAboveCenter,
- 0.01,
- Math.PI - 0.01
- )
- );
-
- // Calculate z distance of the farthest fragment that should be rendered.
- furthestDistance = pitchAngle * topHalfSurfaceDistance + cameraToSeaLevelDistance;
- // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
- const horizonDistance = cameraToSeaLevelDistance * ( 1 / t._horizonShift );
- farZ = Math.min( furthestDistance * 1.01, horizonDistance );
-
this.cameraTranslateZ = new THREE.Matrix4().makeTranslation(
0,
0,
@@ -205,11 +129,11 @@ class CameraSync {
const h = t.height;
const w = t.width;
- this.camera.projectionMatrix = utils.makePerspectiveMatrix(
+ this.camera.projectionMatrix.elements = t._camera.getCameraToClipPerspective(
t._fov,
w / h,
nearZ,
- farZ
+ t._farZ
);
// Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
// If this is applied directly to the projection matrix, it will work OK but break raycasting
@@ -217,7 +141,7 @@ class CameraSync {
// When terrain layers are included, height of 3D layers must be modified from t_camera.z * worldSize
if ( t.elevation )
cameraWorldMatrix.elements[ 14 ] = t._camera.position[ 2 ] * worldSize;
- //this.camera.matrixWorld.elements is equivalent to t._camera._transform
+ // this.camera.matrixWorld.elements is equivalent to t._camera._transform
this.camera.matrixWorld.copy( cameraWorldMatrix );
const zoomPow = t.scale * this.state.worldSizeRatio;
From bc0e526acea5ff805b72c384c94b7041f2cb71ee Mon Sep 17 00:00:00 2001
From: xxf <876029169@qq.com>
Date: Wed, 10 Jan 2024 16:28:47 +0800
Subject: [PATCH 5/5] Note CameraSync comes from threebox
---
example/mapboxExampleCamera.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/example/mapboxExampleCamera.js b/example/mapboxExampleCamera.js
index 1cfa9edb..c438227e 100644
--- a/example/mapboxExampleCamera.js
+++ b/example/mapboxExampleCamera.js
@@ -1,5 +1,6 @@
import * as THREE from 'three';
+// The code in this file comes from https://github.com/jscastro76/threebox/tree/master I have simplified and modified some content
// 512 * 2000
// 512: TILE_SIZE
const WORLD_SIZE = 10240000;