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

Fix level of detail at high pitch #4779

Draft
wants to merge 170 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
6132078
Globe - basic infrastructure, raster layer adaptation for globe (#3783)
kubapelc Mar 15, 2024
c1886e8
Globe - fill layer (#3882)
kubapelc Apr 8, 2024
b9675ca
Merge branch 'main' into globe
HarelM Apr 10, 2024
1754899
Fix build due to bad merge.
HarelM Apr 10, 2024
db7eb72
Merge branch 'main' into globe
HarelM Apr 10, 2024
cceaebc
Globe - line layer (#3961)
kubapelc Apr 10, 2024
1d1b34e
Merge branch 'main' into globe
HarelM Apr 10, 2024
b5d7bba
Merge branch 'main' into globe
HarelM Apr 11, 2024
bf4a5b5
Globe - fill extrusion layer (#3968)
kubapelc Apr 11, 2024
66b2262
Kubapelc/globe pr hillshade (#3979)
kubapelc Apr 12, 2024
3e9f933
Merge branch 'main' into globe
HarelM Apr 12, 2024
016d5cf
Merge branch 'main' into globe
HarelM Apr 17, 2024
9fc1183
Merge branch 'main' into globe
HarelM Apr 18, 2024
1ccdb02
Globe - circle and heatmap layers (#4015)
kubapelc Apr 20, 2024
c613b60
Merge branch 'main' into globe
HarelM May 15, 2024
d2d8f75
Improve readibility of build test and fix it.
HarelM May 15, 2024
759606a
Globe - symbols & symbol bugfixes (#4067)
kubapelc May 20, 2024
9636080
Merge branch 'main' into globe
HarelM May 20, 2024
a304459
Globe - example images (#4140)
kubapelc May 20, 2024
ee6ef0d
Globe - clipping fix (#4146)
kubapelc May 21, 2024
518041f
Merge branch 'main' into globe
HarelM May 21, 2024
bd9ac45
Merge branch 'main' into globe
HarelM Jun 4, 2024
c7c789a
Fix bad merge
HarelM Jun 4, 2024
b503911
Fix lint
HarelM Jun 4, 2024
cb2a046
Fix location of old vertex count
HarelM Jun 4, 2024
a25074d
replace expected file for terrain changes
HarelM Jun 4, 2024
dbf6173
Merge branch 'main' into globe
HarelM Jun 17, 2024
881efb6
Fix rename in main merge branch
HarelM Jun 17, 2024
aa2b288
Fix build test
HarelM Jun 17, 2024
ba95d93
Merge branch 'main' into globe
HarelM Jun 17, 2024
c343c43
Move projection to style class (#4267)
HarelM Jun 18, 2024
3c5e958
Add an Atmosphere layer for Globe (#3888) (#4020)
Pheonor Jun 20, 2024
bcd4cfd
Merge branch 'main' into globe
HarelM Jun 28, 2024
91a3043
missing fix from merge
HarelM Jun 28, 2024
ec7eb68
Fix lint
HarelM Jun 28, 2024
376b213
Terrain fix (#4343)
kubapelc Jun 28, 2024
09aaf3e
Fix missing image for globe example
HarelM Jun 28, 2024
66d4402
Update atmosphere (#4345)
Pheonor Jun 29, 2024
4b35099
Merge branch 'main' into globe
HarelM Jul 1, 2024
7f23795
Merge branch 'main' into globe
HarelM Jul 4, 2024
9e72755
Globe - transform+projection changes (#4341)
kubapelc Jul 12, 2024
90d8727
Merge branch 'main' into globe
HarelM Jul 27, 2024
b83bee8
Fix painter test
HarelM Jul 27, 2024
5fea74a
Merge branch 'main' into globe
HarelM Jul 27, 2024
d5709cd
Merge branch 'main' into globe
HarelM Aug 1, 2024
6973a03
Fix lint
HarelM Aug 1, 2024
3e67533
Merge branch 'main' into globe
HarelM Aug 1, 2024
f4d7926
Update test/build/min.test.ts
HarelM Aug 1, 2024
d3e6374
Merge branch 'main' into globe
HarelM Aug 4, 2024
c181cb4
Update changelog
HarelM Aug 4, 2024
cc24dd8
Merge branch 'main' into globe
HarelM Aug 6, 2024
2f81dd7
Globe - camera controls (#4408)
kubapelc Aug 7, 2024
06c19e0
Merge branch 'main' into globe
HarelM Aug 12, 2024
7f3220f
Globe: bugfixes: raster layer & projection change (#4546)
kubapelc Aug 13, 2024
8c45af4
Merge branch 'main' into globe
HarelM Aug 22, 2024
359da6c
Merge branch 'main' into globe
kubapelc Aug 26, 2024
0ffbbba
Adapt new heatmap code for globe, update build size
kubapelc Aug 26, 2024
b2a98b7
Fix render tests
kubapelc Aug 26, 2024
ed1437e
Merge branch 'main' into globe
HarelM Aug 26, 2024
b859df9
Globe - custom layers API and examples, globe dev guide (#4577)
kubapelc Aug 27, 2024
decd85c
Merge branch 'main' into globe
HarelM Aug 29, 2024
86b9fc3
Globe - Covering tiles (#4615)
kubapelc Sep 3, 2024
38cf5a8
Merge branch 'main' into globe
HarelM Sep 4, 2024
798b7e8
Merge branch 'main' into globe
HarelM Sep 4, 2024
f3b7d32
Merge branch 'main' into globe
HarelM Sep 11, 2024
5a6d815
fix typo
HarelM Sep 11, 2024
97dcd88
Merge branch 'main' into globe
HarelM Sep 12, 2024
471c996
Remove sky disabling in examples as this is no longer needed.
HarelM Sep 12, 2024
99d58a8
Merge branch 'main' into globe
HarelM Sep 12, 2024
555ce7b
Fix spelling
HarelM Sep 12, 2024
aba2735
Fix spelling - unencode
HarelM Sep 23, 2024
ebd791f
Fix more spelling
HarelM Sep 23, 2024
3a1be48
Merge branch 'main' into globe
HarelM Sep 23, 2024
d0981a0
Fix lint
HarelM Sep 23, 2024
af682a7
add failing unit tests to demonstrate issue #
NathanMOlson Sep 23, 2024
e04efc4
fix failing unit tests
NathanMOlson Sep 23, 2024
396464e
formatting
NathanMOlson Sep 23, 2024
bbfa12a
better calculation of distanceZ
NathanMOlson Sep 23, 2024
ed09a92
update globe transform to calculate zoom level for each individual ti…
NathanMOlson Sep 23, 2024
f13b4ef
match behavior from globe to mercator
NathanMOlson Sep 23, 2024
3aa725d
update changelog
NathanMOlson Sep 23, 2024
0ee1c32
fix distance units
NathanMOlson Sep 23, 2024
8a1c9ed
code hygiene
NathanMOlson Sep 23, 2024
e3b54e7
fix source_cache unit tests (fix "overscaled" behavior)
NathanMOlson Sep 24, 2024
24ecd8c
fix degrees vs radians
NathanMOlson Sep 24, 2024
53f42b6
load lower resolution tiles for terrain, based on parameter deltaZoom
NathanMOlson Sep 24, 2024
5aa3595
fix failing unit test (caused by radians vs degrees fix)
NathanMOlson Sep 24, 2024
3efe1b2
update terrain shading results
NathanMOlson Sep 25, 2024
8173270
update test results for failing render tests
NathanMOlson Sep 25, 2024
21ae70e
update terrain/default render test
NathanMOlson Sep 25, 2024
6f9bad8
upadte terrain heatmap render test
NathanMOlson Sep 25, 2024
22305b6
fix globe raster-pole e render test by re-incorporating tileSize into…
NathanMOlson Sep 25, 2024
d125e59
fix checkerboard render test, which was failing due to a very slight …
NathanMOlson Sep 25, 2024
dfcbf2a
aded failing unit test for overscaled tiles
NathanMOlson Sep 25, 2024
04f52e2
fix unit test with z < 0
NathanMOlson Sep 25, 2024
b00ad1d
fix render test "projection/globe/collision-text-variable-anchor/pit…
NathanMOlson Sep 25, 2024
49a9c41
code hygiene
NathanMOlson Sep 25, 2024
756b26b
add test that shows zoom levels at high pitch in terrain
NathanMOlson Sep 25, 2024
8730631
actual ptich was limited to 60 degrees, so set it to 60 for clarity
NathanMOlson Sep 26, 2024
7ec03f4
code hygiene
NathanMOlson Sep 26, 2024
5830405
remove osm tiles in terrain render tests and replace with number tiles
NathanMOlson Sep 26, 2024
9e6c0be
fix hardcoded terrain tileSize
NathanMOlson Sep 27, 2024
8b01384
fix unit test
NathanMOlson Sep 27, 2024
4f58b3b
Add parameter to change tile selection behavior at high pitch
NathanMOlson Sep 27, 2024
efbaa5c
Merge branch 'main' into lodfix_globe
NathanMOlson Sep 30, 2024
ff0be21
remove console.log
NathanMOlson Oct 1, 2024
6ad7a4b
fix bad merge
NathanMOlson Oct 1, 2024
18b5973
code hygiene
NathanMOlson Oct 1, 2024
71a55e9
reduce formatting differences
NathanMOlson Oct 1, 2024
6eaa30e
rearrange mercatorCoveringTiles to be more like globe
NathanMOlson Oct 1, 2024
a1d35b0
make globe covering tiles more like mercator
NathanMOlson Oct 1, 2024
9477a2a
make globe more like mercator
NathanMOlson Oct 1, 2024
9ce7d6a
more rearranging
NathanMOlson Oct 1, 2024
a083dfb
rearranging
NathanMOlson Oct 1, 2024
8ebb6a3
rearranging
NathanMOlson Oct 1, 2024
bdaaac0
rearranging
NathanMOlson Oct 1, 2024
3473302
rearranging
NathanMOlson Oct 1, 2024
033ea68
rearranging
NathanMOlson Oct 1, 2024
c37bfc5
rearranging
NathanMOlson Oct 1, 2024
92226e8
more rearranging
NathanMOlson Oct 1, 2024
ba08e74
change globeTransform to always return for renderWorldCopies
NathanMOlson Oct 1, 2024
b79338b
rearranging
NathanMOlson Oct 1, 2024
9ce373c
fix tile ordering for globe
NathanMOlson Oct 1, 2024
b13861c
code hygiene
NathanMOlson Oct 1, 2024
56e26ab
combine globe and mercator coveringTiles() implementations
NathanMOlson Oct 1, 2024
c58f1a7
add types
NathanMOlson Oct 1, 2024
cb082da
comments
NathanMOlson Oct 1, 2024
87f747d
fix optinal param
NathanMOlson Oct 1, 2024
5495e2c
documentation update
NathanMOlson Oct 1, 2024
8cfe7a9
move coveringTiles helper functions into interface
NathanMOlson Oct 1, 2024
a35f112
add return type
NathanMOlson Oct 1, 2024
a7ceaf6
always construct TerrainSourceCache with a sourceCache containing _so…
NathanMOlson Oct 1, 2024
301c25d
update render tests
NathanMOlson Oct 1, 2024
433a532
update render test
NathanMOlson Oct 1, 2024
a1ecfac
update render test
NathanMOlson Oct 1, 2024
455cb75
update render tests
NathanMOlson Oct 1, 2024
1f243b5
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 1, 2024
261a4a2
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 2, 2024
0d6605e
improve name of pitchBehavior -> pitchTileLoadingBehavior
NathanMOlson Oct 10, 2024
ccae1df
add tileZoomDeadband property
NathanMOlson Oct 14, 2024
a58651d
lint
NathanMOlson Oct 14, 2024
9acb2f7
default tileZooomDeadband to 0
NathanMOlson Oct 14, 2024
07d309f
Merge branch 'main' into lodfix_globe and resolve conflicts
NathanMOlson Oct 16, 2024
b9e491b
update build size
NathanMOlson Oct 16, 2024
aa95dd7
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 17, 2024
a231a28
fix merge errors
NathanMOlson Oct 17, 2024
7c626cf
adjust build size
NathanMOlson Oct 17, 2024
591773a
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 22, 2024
9dc9797
fix CHANGELOG
NathanMOlson Oct 22, 2024
8b77d4b
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 25, 2024
c0679f7
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 28, 2024
94a0a57
move calculateTileZoom() out to its own function, remove top-level pa…
NathanMOlson Oct 28, 2024
386b968
allow overriding definition of calculateTileZoom
NathanMOlson Oct 28, 2024
1a2ed77
Update CHANGELOG.md
NathanMOlson Oct 28, 2024
4bb8544
Update src/geo/projection/covering_tiles.ts
NathanMOlson Oct 28, 2024
a6842b0
cameraVFOV -> cameraVerticalFOV
NathanMOlson Oct 28, 2024
8e47b41
move coveringZoomLevel() and CoveringZoomOptions to covering_tiles
NathanMOlson Oct 29, 2024
4b69155
remove unneeded export
NathanMOlson Oct 29, 2024
f5fa8aa
move coveringTilesDetails to re-used objects
NathanMOlson Oct 29, 2024
d16e54b
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 29, 2024
4925c25
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 29, 2024
4835605
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 29, 2024
1e61f82
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 31, 2024
2fa31f5
Merge branch 'main' into lodfix_globe
NathanMOlson Oct 31, 2024
1855951
fix coveringZoomLevel call
NathanMOlson Oct 31, 2024
d23cc33
remove coveringTiles() interface from ITransform
NathanMOlson Oct 31, 2024
a2c1e58
Don't allow constant zoom level when the field of view extends near t…
NathanMOlson Oct 31, 2024
8ea60fe
restore missing /2 in calculateTileZoom() FOV adjustment
NathanMOlson Oct 31, 2024
72e365b
fix unit test
NathanMOlson Oct 31, 2024
810855f
limit zoom change with FOV to a factor of 2. (only affects VFOV > 120…
NathanMOlson Oct 31, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

### 🐞 Bug fixes
- Fix line-placed map-pitch-aligned texts being too large when viewed from some latitudes on a globe ([#4786](https://github.com/maplibre/maplibre-gl-js/issues/4786))
- ⚠️ Fix level of detail at high pitch angle by changing which tiles to load ([#3983](https://github.com/maplibre/maplibre-gl-js/issues/3983))
- _...Add new stuff here..._

## 5.0.0-pre.4
Expand Down
290 changes: 290 additions & 0 deletions src/geo/projection/covering_tiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import {OverscaledTileID} from '../../source/tile_id';
import {Aabb, Frustum, IntersectionResult} from '../../util/primitives';
import {vec2, vec4} from 'gl-matrix';
import {IReadonlyTransform} from '../transform_interface';
import {MercatorCoordinate} from '../mercator_coordinate';
import {scaleZoom} from '../transform_helper';
import {clamp, degreesToRadians} from '../../util/util';
import {Terrain} from '../../render/terrain';

type CoveringTilesResult = {
tileID: OverscaledTileID;
distanceSq: number;
tileDistanceToCamera: number;
};

type CoveringTilesStackEntry = {
zoom: number;
x: number;
y: number;
wrap: number;
fullyVisible: boolean;
};

export type CoveringZoomOptions = {
/**
* Whether to round or floor the target zoom level. If true, the value will be rounded to the closest integer. Otherwise the value will be floored.
*/
roundZoom?: boolean;
/**
* Tile size, expressed in screen pixels.
*/
tileSize: number;
};

export type CoveringTilesOptions = CoveringZoomOptions & {
/**
* Smallest allowed tile zoom.
*/
minzoom?: number;
/**
* Largest allowed tile zoom.
*/
maxzoom?: number;
/**
* `true` if tiles should be sent back to the worker for each overzoomed zoom level, `false` if not.
* Fill this option when computing covering tiles for a source.
* When true, any tile at `maxzoom` level that should be overscaled to a greater zoom will have
* its zoom set to the overscaled greater zoom. When false, such tiles will have zoom set to `maxzoom`.
*/
reparseOverscaled?: boolean;
/**
* When terrain is present, tile visibility will be computed in regards to the min and max elevations for each tile.
*/
terrain?: Terrain;
/**
* Optional function to redefine how tiles are loaded at high pitch angles.
*/
calculateTileZoom?: CalculateTileZoomFunction;
};

/**
* Function to define how tiles are loaded at high pitch angles
* @param requestedCenterZoom - the requested zoom level, valid at the center point.
* @param distanceToTile2D - 2D distance from the camera to the candidate tile, in mercator units.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is mercator units?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"mercator units" are described in custom_style_layer.ts line 96. Here's the most relevant part of the description:

The spherical mercator coordinate `[0, 0]` represents the top left corner of the mercator world
and `[1, 1]` represents the bottom right corner. A box with identical x, y, and z lengths in mercator
units would be rendered as a cube. {@link MercatorCoordinate.fromLngLat} can be used to project
a `LngLat` to a mercator coordinate.

For this function, the units don't actually matter as long as distanceToTile2D, distanceToTileZ, and distanceToCenter3D are all given in the same units. I think it's best to specify units though? Or we could change the inputs to be ratios (distanceToTile2D/distanceToCenter3D and distanceToTileZ/distanceToCenter3D) but that seems less clear.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Units are very important, I just didn't know what it meant.
If there's an easy way to link to what you wrote here it will be great, or link to a class/type that is relevant.
Totally optional.

* @param distanceToTileZ - vertical distance from the camera to the candidate tile, in mercator units.
* @param distanceToCenter3D - distance from camera to center point, in mercator units
* @param cameraVerticalFOV - camera vertical field of view, in degrees
* @return the desired zoom level for this tile. May not be an integer.
*/
export type CalculateTileZoomFunction = (requestedCenterZoom: number,
distanceToTile2D: number,
distanceToTileZ: number,
distanceToCenter3D: number,
cameraVerticalFOV: number) => number;

export interface CoveringTilesDetailsProvider {
Copy link
Collaborator

@HarelM HarelM Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this interface be in a different file?

/**
* Returns the distance from the point to the tile
* @param pointX - point x.
* @param pointY - point y.
* @param tileID - Tile x, y and z for zoom.
* @param aabb - tile AABB
*/
distanceToTile2d: (pointX: number, pointY: number, tileID: {x: number; y: number; z: number}, aabb: Aabb) => number;

// Returns the wrap value for a given tile.
getWrap: (centerCoord: MercatorCoordinate, tileID: {x:number; y: number; z: number}, parentWrap: number) => number;

/**
* Returns the AABB of the specified tile.
* @param tileID - Tile x, y and z for zoom.
* @param wrap - wrap number of the tile.
* @param elevation - camera center point elevation.
* @param options - CoveringTilesOptions.
*/
getTileAABB: (tileID: {x: number; y: number; z: number}, wrap: number, elevation: number, options: CoveringTilesOptions) => Aabb;

/**
* Whether to allow variable zoom, which is used at high pitch angle to avoid loading an excessive amount of tiles.
*/
allowVariableZoom: (transform: IReadonlyTransform, options: CoveringTilesOptions) => boolean;
}

/**
* A simple/heuristic function that returns whether the tile is visible under the current transform.
* @returns an {@link IntersectionResult}.
*/
export function isTileVisible(frustum: Frustum, aabb: Aabb, plane?: vec4): IntersectionResult {

const frustumTest = aabb.intersectsFrustum(frustum);
if (!plane) {
NathanMOlson marked this conversation as resolved.
Show resolved Hide resolved
return frustumTest;
}
const planeTest = aabb.intersectsPlane(plane);

if (frustumTest === IntersectionResult.None || planeTest === IntersectionResult.None) {
return IntersectionResult.None;
}

if (frustumTest === IntersectionResult.Full && planeTest === IntersectionResult.Full) {
return IntersectionResult.Full;
}

return IntersectionResult.Partial;
}

function calculateTileZoom(requestedCenterZoom: number,
distanceToTile2D: number,
distanceToTileZ: number,
distanceToCenter3D: number,
cameraVerticalFOV: number) : number {
/**
* Controls how tiles are loaded at high pitch angles. Higher numbers cause fewer, lower resolution
* tiles to be loaded. At 0, tiles are loaded with approximately constant screen X resolution.
* At 1, tiles are loaded with approximately constant screen area.
* At 2, tiles are loaded with approximately constant screen Y resolution.
*/
const pitchTileLoadingBehavior = 1.0;
/**
* Controls how tiles are loaded at high pitch angles. Controls how different the distance to a tile must be (compared with the center point)
* before a new zoom level is requested. For example, if tileZoomDeadband = 1 and the center zoom is 14, tiles distant enough to be loaded at
* z13 will be loaded at z14, and tiles distant enough to be loaded at z14 will be loaded at z15. A higher number causes more tiles to be loaded
* at the center zoom level. This also results in more tiles being loaded overall.
*/
const tileZoomDeadband = 0.0;
let thisTileDesiredZ = requestedCenterZoom;
const thisTilePitch = Math.atan(distanceToTile2D / distanceToTileZ);
const distanceToTile3D = Math.hypot(distanceToTile2D, distanceToTileZ);
// if distance to candidate tile is a tiny bit farther than distance to center,
// use the same zoom as the center. This is achieved by the scaling distance ratio by cos(fov/2)
thisTileDesiredZ = requestedCenterZoom + scaleZoom(distanceToCenter3D / distanceToTile3D / Math.max(0.5, Math.cos(degreesToRadians(cameraVerticalFOV / 2))));
thisTileDesiredZ += pitchTileLoadingBehavior * scaleZoom(Math.cos(thisTilePitch)) / 2;
thisTileDesiredZ = thisTileDesiredZ + clamp(requestedCenterZoom - thisTileDesiredZ, -tileZoomDeadband, tileZoomDeadband);
return thisTileDesiredZ;
}

/**
* Return what zoom level of a tile source would most closely cover the tiles displayed by this transform.
* @param options - The options, most importantly the source's tile size.
* @returns An integer zoom level at which all tiles will be visible.
*/
export function coveringZoomLevel(transform: IReadonlyTransform, options: CoveringZoomOptions): number {
const z = (options.roundZoom ? Math.round : Math.floor)(
transform.zoom + scaleZoom(transform.tileSize / options.tileSize)
);
// At negative zoom levels load tiles from z0 because negative tile zoom levels don't exist.
return Math.max(0, z);
}

/**
* Returns a list of tiles that optimally covers the screen. Adapted for globe projection.
* Correctly handles LOD when moving over the antimeridian.
* @param transform - The transform instance.
* @param frustum - The covering frustum.
* @param plane - The clipping plane used by globe transform, or null.
* @param cameraCoord - The x, y, z position of the camera in MercatorCoordinates.
* @param cameraCoord - The x, y, z position of the center point in MercatorCoordinates.
* @param options - Additional coveringTiles options.
* @param details - Interface to define required helper functions.
* @returns A list of tile coordinates, ordered by ascending distance from camera.
*/
export function coveringTiles(transform: IReadonlyTransform, options: CoveringTilesOptions): OverscaledTileID[] {
const frustum = transform.getCameraFrustum();
const plane = transform.getClippingPlane();
const cameraCoord = transform.screenPointToMercatorCoordinate(transform.getCameraPoint());
const centerCoord = MercatorCoordinate.fromLngLat(transform.center, transform.elevation);
cameraCoord.z = centerCoord.z + Math.cos(transform.pitchInRadians) * transform.cameraToCenterDistance / transform.worldSize;
const detailsProvider = transform.getCoveringTilesDetailsProvider();
const allowVariableZoom = detailsProvider.allowVariableZoom(transform, options);

const desiredZ = coveringZoomLevel(transform, options);
const minZoom = options.minzoom || 0;
const maxZoom = options.maxzoom !== undefined ? options.maxzoom : transform.maxZoom;
const nominalZ = Math.min(Math.max(0, desiredZ), maxZoom);

const numTiles = Math.pow(2, nominalZ);
const cameraPoint = [numTiles * cameraCoord.x, numTiles * cameraCoord.y, 0];
const centerPoint = [numTiles * centerCoord.x, numTiles * centerCoord.y, 0];
const distanceToCenter2d = Math.hypot(centerCoord.x - cameraCoord.x, centerCoord.y - cameraCoord.y);
const distanceZ = Math.abs(centerCoord.z - cameraCoord.z);
const distanceToCenter3d = Math.hypot(distanceToCenter2d, distanceZ);

const newRootTile = (wrap: number): CoveringTilesStackEntry => {
return {
zoom: 0,
x: 0,
y: 0,
wrap,
fullyVisible: false
};
};

// Do a depth-first traversal to find visible tiles and proper levels of detail
const stack: Array<CoveringTilesStackEntry> = [];
const result: Array<CoveringTilesResult> = [];

if (transform.renderWorldCopies) {
// Render copy of the globe thrice on both sides
for (let i = 1; i <= 3; i++) {
stack.push(newRootTile(-i));
stack.push(newRootTile(i));
}
}

stack.push(newRootTile(0));

while (stack.length > 0) {
const it = stack.pop();
const x = it.x;
const y = it.y;
let fullyVisible = it.fullyVisible;
const tileID = {x, y, z: it.zoom};
const aabb = detailsProvider.getTileAABB(tileID, it.wrap, transform.elevation, options);

// Visibility of a tile is not required if any of its ancestor is fully visible
if (!fullyVisible) {
const intersectResult = isTileVisible(frustum, aabb, plane);

if (intersectResult === IntersectionResult.None)
continue;

fullyVisible = intersectResult === IntersectionResult.Full;
}

const distToTile2d = detailsProvider.distanceToTile2d(cameraCoord.x, cameraCoord.y, tileID, aabb);

let thisTileDesiredZ = desiredZ;
if (allowVariableZoom) {
const tileZoomFunc = options.calculateTileZoom || calculateTileZoom;
thisTileDesiredZ = tileZoomFunc(transform.zoom + scaleZoom(transform.tileSize / options.tileSize),
distToTile2d,
distanceZ,
distanceToCenter3d,
transform.fov);
}
thisTileDesiredZ = (options.roundZoom ? Math.round : Math.floor)(thisTileDesiredZ);
thisTileDesiredZ = Math.max(0, thisTileDesiredZ);
const z = Math.min(thisTileDesiredZ, maxZoom);

// We need to compute a valid wrap value for the tile to keep globe compatibility with mercator
it.wrap = detailsProvider.getWrap(centerCoord, tileID, it.wrap);

// Have we reached the target depth?
if (it.zoom >= z) {
if (it.zoom < minZoom) {
continue;
}
const dz = nominalZ - it.zoom;
const dx = cameraPoint[0] - 0.5 - (x << dz);
const dy = cameraPoint[1] - 0.5 - (y << dz);
const overscaledZ = options.reparseOverscaled ? thisTileDesiredZ : it.zoom;
result.push({
tileID: new OverscaledTileID(it.zoom === maxZoom ? overscaledZ : it.zoom, it.wrap, it.zoom, x, y),
distanceSq: vec2.sqrLen([centerPoint[0] - 0.5 - x, centerPoint[1] - 0.5 - y]),
// this variable is currently not used, but may be important to reduce the amount of loaded tiles
tileDistanceToCamera: Math.sqrt(dx * dx + dy * dy)
});
continue;
}

for (let i = 0; i < 4; i++) {
const childX = (x << 1) + (i % 2);
const childY = (y << 1) + (i >> 1);
const childZ = it.zoom + 1;
stack.push({zoom: childZ, x: childX, y: childY, wrap: it.wrap, fullyVisible});
}
}

return result.sort((a, b) => a.distanceSq - b.distanceSq).map(a => a.tileID);
}
22 changes: 13 additions & 9 deletions src/geo/projection/globe_covering_tiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import {Aabb} from '../../util/primitives';
import {expectToBeCloseToArray} from '../../util/test/util';
import {getTileAABB} from './globe_covering_tiles';
import {GlobeCoveringTilesDetailsProvider} from './globe_covering_tiles';

describe('aabb', () => {
test('z=0', () => {
const aabb = getTileAABB({
const detailsProvider = new GlobeCoveringTilesDetailsProvider();
const aabb = detailsProvider.getTileAABB({
x: 0,
y: 0,
z: 0,
});
}, null, null, null);
expect(aabb).toEqual(new Aabb(
[-1, -1, -1],
[1, 1, 1],
));
});

test('z=1,x=0', () => {
const aabb = getTileAABB({
const detailsProvider = new GlobeCoveringTilesDetailsProvider();
const aabb = detailsProvider.getTileAABB({
x: 0,
y: 0,
z: 1,
});
}, null, null, null);
expect(aabb).toEqual(new Aabb(
[-1, 0, -1],
[0, 1, 1],
));
});

test('z=1,x=1', () => {
const aabb = getTileAABB({
const detailsProvider = new GlobeCoveringTilesDetailsProvider();
const aabb = detailsProvider.getTileAABB({
x: 1,
y: 0,
z: 1,
});
}, null, null, null);
expect(aabb).toEqual(new Aabb(
[0, 0, -1],
[1, 1, 1],
));
});

test('z=2,x=1', () => {
const aabb = getTileAABB({
const detailsProvider = new GlobeCoveringTilesDetailsProvider();
const aabb = detailsProvider.getTileAABB({
x: 1,
y: 0,
z: 2,
});
}, null, null, null);
expectToBeCloseToArray([...aabb.min], [-0.3985368153383868, 0.9171523356672743, -7.321002528698027e-17,]);
expectToBeCloseToArray([...aabb.max], [0, 1, 0.3985368153383868]);
});
Expand Down
Loading
Loading