Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions examples/website/google-3d-tiles/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import React, {useState} from 'react';
import React, {useState, useMemo} from 'react';
import {scaleLinear} from 'd3-scale';
import {createRoot} from 'react-dom/client';
import {DeckGL} from '@deck.gl/react';
import {TerrainController} from '@deck.gl/core';
import {MapView, _GlobeView as GlobeView, TerrainController} from '@deck.gl/core';
import {GeoJsonLayer} from '@deck.gl/layers';
import {Tile3DLayer} from '@deck.gl/geo-layers';
import {DataFilterExtension, _TerrainExtension as TerrainExtension} from '@deck.gl/extensions';
Expand All @@ -28,10 +28,10 @@ const INITIAL_VIEW_STATE = {
latitude: 50.089,
longitude: 14.42,
zoom: 16,
minZoom: 14,
maxZoom: 18,
bearing: 90,
pitch: 60
minZoom: 0,
maxZoom: 24,
bearing: 0,
pitch: 0
};

const BUILDING_DATA =
Expand All @@ -50,6 +50,8 @@ function getTooltip({object}) {

export default function App({data = TILESET_URL, distance = 0, opacity = 0.2}) {
const [credits, setCredits] = useState('');
const [useGlobe, setUseGlobe] = useState(false);

const onTraversalComplete = selectedTiles => {
const uniqueCredits = new Set();
selectedTiles.forEach(tile => {
Expand Down Expand Up @@ -90,15 +92,42 @@ export default function App({data = TILESET_URL, distance = 0, opacity = 0.2}) {
})
];

const view = useMemo(
() =>
useGlobe
? new GlobeView({id: 'view', controller: true})
: new MapView({
id: 'view',
controller: {type: TerrainController, touchRotate: true, inertia: 500}
}),
[useGlobe]
);

return (
<div>
<DeckGL
key={useGlobe ? 'globe' : 'map'}
style={{backgroundColor: '#061714'}}
views={view}
initialViewState={INITIAL_VIEW_STATE}
controller={{type: TerrainController, touchRotate: true, inertia: 500}}
layers={layers}
getTooltip={getTooltip}
/>
<button
onClick={() => setUseGlobe(v => !v)}
style={{
position: 'absolute',
top: '8px',
left: '8px',
padding: '6px 10px',
fontFamily: 'sans-serif',
fontSize: '12px',
border: 'none',
cursor: 'pointer'
}}
>
{useGlobe ? 'Map' : 'Globe'}
</button>
<div
style={{position: 'absolute', left: '8px', bottom: '4px', color: 'white', fontSize: '10px'}}
>
Expand Down
15 changes: 13 additions & 2 deletions modules/core/src/shaderlib/project/project.glsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ float project_size() {
// Adjust by 1 / cos(latitude)
// If geometry.position (vertex in common space) is populated, use it
// Otherwise use geometry.worldPosition (anchor in world space)

if (geometry.position.w == 0.0) {
return project_size_at_latitude(geometry.worldPosition.y);
}

// latitude from common y: 2.0 * (atan(exp(y / TILE_SIZE * 2.0 * PI - PI)) - PI / 4.0)
// Taylor series of 1 / cos(latitude)
// Max error < 0.003

float y = geometry.position.y / TILE_SIZE * 2.0 - 1.0;
float y2 = y * y;
float y4 = y2 * y2;
Expand Down Expand Up @@ -208,6 +208,17 @@ vec4 project_position(vec4 position, vec3 position64Low) {
position_world.w
);
}
if (project.coordinateSystem == COORDINATE_SYSTEM_METER_OFFSETS) {
// position_world is meters in the ENU tangent frame at coordinateOrigin.
Comment thread
charlieforward9 marked this conversation as resolved.
Outdated
// commonOrigin (precomputed CPU-side) is the globe-space position of
// that origin; project_get_orientation_matrix derives the ENU frame from
// that direction so the tangent plane attaches to the sphere and tiles
// land at their geographic location oriented with the surface.
mat3 enuMatrix = project_get_orientation_matrix(project.commonOrigin);
float metersToCommon = GLOBE_RADIUS / EARTH_RADIUS;
vec3 offsetCommon = (enuMatrix * vec3(-position_world.xy, position_world.z)) * metersToCommon;
return vec4(project.commonOrigin + offsetCommon, position_world.w);
}
Comment thread
charlieforward9 marked this conversation as resolved.
}
if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET) {
if (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT) {
Expand Down
11 changes: 11 additions & 0 deletions modules/core/src/shaderlib/project/project.wgsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,17 @@ fn project_position_vec4_f64(position: vec4<f32>, position64Low: vec3<f32>) -> v
position_world.w
);
}
if (project.coordinateSystem == COORDINATE_SYSTEM_METER_OFFSETS) {
// position_world is meters in the ENU tangent frame at coordinateOrigin.
// commonOrigin (precomputed CPU-side) is the globe-space position of
// that origin; project_get_orientation_matrix derives the ENU frame from
// that direction so the tangent plane attaches to the sphere and tiles
// land at their geographic location oriented with the surface.
let enuMatrix = project_get_orientation_matrix(project.commonOrigin);
let metersToCommon = GLOBE_RADIUS / EARTH_RADIUS;
let offsetCommon = (enuMatrix * vec3<f32>(-position_world.x, -position_world.y, position_world.z)) * metersToCommon;
return vec4<f32>(project.commonOrigin + offsetCommon, position_world.w);
}
}
if (project.projectionMode == PROJECTION_MODE_WEB_MERCATOR_AUTO_OFFSET) {
if (project.coordinateSystem == COORDINATE_SYSTEM_LNGLAT) {
Expand Down
17 changes: 17 additions & 0 deletions modules/core/src/shaderlib/project/viewport-uniforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,5 +359,22 @@ function calculateViewportUniforms({
}
}

// For GLOBE + METER_OFFSETS, precompute the globe-space position of
// coordinateOrigin into commonOrigin. The shader derives the ENU frame from
// this direction via project_get_orientation_matrix, avoiding per-vertex trig.
if (viewport.projectionMode === PROJECTION_MODE.GLOBE && coordinateSystem === 'meter-offsets') {
const EARTH_RADIUS = 6370972;
const GLOBE_RADIUS = 256;
const lambda = (coordinateOrigin[0] * Math.PI) / 180;
const phi = (coordinateOrigin[1] * Math.PI) / 180;
const cosPhi = Math.cos(phi);
const D = ((coordinateOrigin[2] || 0) / EARTH_RADIUS + 1.0) * GLOBE_RADIUS;
uniforms.commonOrigin = [
Math.sin(lambda) * cosPhi * D,
-Math.cos(lambda) * cosPhi * D,
Math.sin(phi) * D
];
}

return uniforms;
}
10 changes: 9 additions & 1 deletion modules/core/src/viewports/globe-viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,15 @@ export default class GlobeViewport extends Viewport {
// https://github.com/maplibre/maplibre-gl-js/blob/f8ab4b48d59ab8fe7b068b102538793bbdd4c848/src/geo/projection/globe_transform.ts#L575-L577
const scale = Math.pow(2, zoom - zoomAdjust(latitude));
const nearZ = opts.nearZ ?? nearZMultiplier;
const farZ = opts.farZ ?? (altitude + (GLOBE_RADIUS * 2 * scale) / height) * farZMultiplier;
// Far plane = distance from camera to horizon (beyond that, earth's
// curvature occludes everything). Using the full globe-diameter formula
// would push far past anything drawable and wreck depth-buffer precision
// at close zoom, causing z-fighting on 3D features like Tile3DLayer
// meshes. Multiply by 2 to keep headroom for tall features (mountains,
// buildings) peeking above the horizon.
const globeRadiusView = (GLOBE_RADIUS * scale) / height;
const horizonDistance = Math.sqrt(altitude * altitude + 2 * altitude * globeRadiusView);
const farZ = opts.farZ ?? horizonDistance * 2 * farZMultiplier;

// Calculate view matrix
const viewMatrix = new Matrix4().lookAt({eye: [0, -altitude, 0], up: [0, 0, 1]});
Expand Down
5 changes: 2 additions & 3 deletions modules/core/src/views/globe-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import View, {CommonViewState, CommonViewProps} from './view';
import GlobeViewport from '../viewports/globe-viewport';
import WebMercatorViewport from '../viewports/web-mercator-viewport';
import GlobeController from '../controllers/globe-controller';
import type {Parameters} from '@luma.gl/core';

Expand Down Expand Up @@ -53,8 +52,8 @@ export default class GlobeView extends View<GlobeViewState, GlobeViewProps> {
});
}

getViewportType(viewState: GlobeViewState) {
Comment thread
charlieforward9 marked this conversation as resolved.
return viewState.zoom > 12 ? WebMercatorViewport : GlobeViewport;
getViewportType() {
return GlobeViewport;
}

get ControllerType() {
Expand Down
Loading