Skip to content

Commit 5afb592

Browse files
wipfliprozessor13acalcuttHarelM
authored
Terrain 3D (#1022)
* A working prototype, which adds 3d-terrain to maplibre-gl. Sadly, during the development i did no intermediate commits, so in this first commit all the following functionality is included: * allow MTK terrainRGB-tiles encoding with parameters: [6553.6, 25.6, 0.03, 10000.0]. In our opinion 0.1 for height-steps is to rough. * create a TerrainSourceCache.js class which is similar to SourceCache.js and holds all the terrain-tiles used for the 3D-mesh. If a terrainRGB tile is used in both, terrain & hillshading, it is loaded twice. This makes the whole process much more easy. * create a 3d-mesh in raster_dem_tile_worker_source.js with the martini library. * rewrite the draw logic to render all layers, except symbols, into a framebuffer. This framebuffer a later used as a texture onto the 3d-mesh. * rewrite symbol rendering to use 3d-coordinates. This is done with an extra a_ele shader parameter, because the z-value of the a_pos variable is already used for other things. * add the third dimension into the collision index. * create a terrain.html test-page. * render more tiles to avoid wholes on tiled maps at canvas bottom * Proof of concept! to get mercator-coords from the 3d-mesh on screen. This is done with an extra framebuffer in which all tile coordinates are rendered in an encoded rgb value. Dont know why, but this framebuffer looks very inperformant. As advantage, grabbing coordinates from screen is very fast. It may help to render the framebuffer only every 100ms instead of every requestAnimationFrame? This logic is currently used in * map.project * map.unproject. Other usecases are: * elevate camera over terrain * use in mouse-events * use in queryRenderedFeatures * fix bug in picking correct coords from coordsFramebuffer * Add Depthbuffer to RTT Framebuffer * add z-dimension to circle layers * switch to regular grid, correct rendering below ZL14, remove visible tile-boundaries * fix raster-rendering, hacky performance improvement * add z-dimension to fill-extrusion * fix regular grid * hide symbols behind terrain * calculate elevation of symbol/circle/extrusion only once * render coords-framebuffer only on camera movement * improve performacne: render layers to texture only once * * raise camera to correct zoomlevel-distance over terrain * add exaggeration setting in TerrainSourceCache * fix farZ clipping-plane * set points: any declaration to Array<Object> * Add an elevation offset of 450m to put the dead sea into positive values * add yarn.lock file with martini entries * render tiles only if terrain is loaded * reuse framebuffer objects during rtt rendering * reuse regular-mesh in all tiles * add transform.elevation * create transform.invProjMatrix before elevation correcture * * move exaggeration calculation into shaders * new more performant encoding of the coords-framebuffer * some minor fixes, add map.addTerrain and map.removeTerrain * refactor texture rendering * decrease the number of framebuffers * decrease the number of framebuffer/texture switches * more caching and less re-rendering the old render-pipeline renders layer by layer, which is ok to render on display framebuffer, but when rendering into a textures it is more performant to render all necessary layers at once. * moved elevation-calculation to GPU This checkin is only for backup, so do not test! * changed elevation calculation from CPU to GPU This change had a big impact in performacne. To calculate the hight in CPU sometimes about 100ms per tile was needed. When loading a lot of tiles the framerate was really bad. This works now much better. NOTE: Currently only the Mesh elevation is moved to GPU, symbols are still calculated via the CPU. * refactor render to texture workflow below ZL 14 Until now, below ZL 14 (e.g. our Vector-Tile maxzoom) the tile-size increased with ZL, so ZL 1-14 had 1024px, ZL 15 2048px, ZL 16 4096px, and so on. This was very easy to program, but needed also a lot of GPU performacne and RAM. So now, the TerrainSourceCache holds tiles for each zoomlevel of the size 1024, and below ZL 14 in each is renderd a sub-region of ZL 14. * refactor the coords-framebuffer. Also the coords-framebuffer use a textures with 1024px in size. To avoid bluring the texture below ZL 14 an additional small texture is needed (u_coords_index) which holds in its RGBA values the tile quadrants of the sub-regions. NOTE: This is currently a work in progress! * fix calculate_visibility in overzoomed terrain-tiles * add very basic logic of loading tiles in respect of their elevation * correct unprojecting coordinates * correct calculation of elevation in CPU for different maxzoom settings * calcuate symbol visibility via a depth-framebuffer, because the coords-framebuffer visibility calculation gets to wired if terrain-tiles & vector-tiles has different maxzoom settings * fix symbol flickering * typo * move symbol/circle/fill_extrusion height calculation to GPU * bugfix when rerender tiles, closes #24 * * free GPU less aggressive * add deltaZoom setting to load less detailed terraintiles * fix bug in _emptyDemTexture * redraw only necesarry tiles (this still has bugs!) * bugfix with TerrainSourceCache.deltaZoom * * bugfix in u_terrain_matrix * remember texture-coords to know which tile has to rerender * disable raster fading if 3d is enabled * add linear interpolation * remove visible tile-boundaries * reimplement deltaZoom * fix marker wobbling & check visibility * some code cleanup * ... * use preloaded terraintiles when zooming in and out * ... * bugfix for empty render-to-texture stack * set LOD tile-loading in case of terrain3d * avoid double-rendering (because of fading logic) of raster tiles * calculate_visibility now checks visibility also some pixels above * ... * add code-documentation * remove mtk raster-dem tiles hack * add more minor fixes and add some comments * bugfix for collision-index calculation with disabled 3d, fixes #38 * revert old fill_extrusion_bucket centroid code, because new logic had bugs * fixes #36 * Update CHANGELOG.md * Revert "Update CHANGELOG.md" This reverts commit 0b81a41. * attribution fixes (from astridx) a1272d9 b9b0370 * Update .gitignore * fix missing new line lint complained about * first try (not finished) to leave the camera at the same height when dragging map. * Revert "attribution fixes (from astridx)" This reverts commit 2031d8e. * Create a TerrainControl to toggle terrain * fix tabs and comment * change exaggeration type to number. add options to control example * change terrain icon fill color when enabled remove second "flat/enabled" svg. used color from gps location button. * revert some merge changes * Revert package-lock.json for pull request * some improvements leave the camera in the same height during paning * set default pitch back to 65, because of too much labels & missing sky * fix "mismatched image size" errors in some of our stylesheets * add default values during tinySDF generation * fire "terrain" events * let use TerrainControl use terrain-Event * typo * may fix farZ clippingplane calculation * fix TileID-order of transform.coveringTiles result * call transform.updateElevation on every rendering * rerender rtt tiles on geojson.setData * freezeElevation while camera-easing * make tests running * speedup symbol-placement in 2D mode * ... * add first very poor TerrainSourceCache test * ... * show correct tileBoundaries & collisionBoxes in terrain * load LOD tiles in respect of terrain * rename getElevationWithExaggeration to getElevation * calculate tileDistanceToCamera form future-use in tile reduction algorithm * update terrain-test page to maxPitch = 85 * Fix some lint warnings * Fix some lint warnings * lint warning fixes * Improve typings, fix lint warnings * Add type to getElevation parameter * Update collision expected again? * calculate farZ clipping-plane in respect of transform.getHorizon * reduce number of loaded tiles * fix transform.locationPoint in 3d-mode * fix not rendered areas in high zoomlevels * fix typescript errors * fix mouse zoom & tilt logic in 2D * if last layer is a rtt layer, do not render it twice, fixes #1036 * correct calculation of queryRenderedFeatures coordinates in 3d-mode. fixes #1075 * Fix build typescript generation * remove fixme's and create issues for that * Small code fixes to avoid review comments * Fix lint * Fix draw_symbol unit test * Fix unit test * Remove console.log * Fix build * refacture TerrainSourceCache class * fix lint errors * add tests for sourceCache.usedForTerrain partent-tile-logic * minor fixes * Added some types and minor changes. * encapsulate a_centroid shader attributes to #TERRAIN3D preprocessor * remove terrain-instance from transform class, instead put as argument * add forgotten return statements * Add terrain to style spec (#1138) * Add terrain to style spec * Fix lint * Added a test in style to make sure the terrain is created. * fix lint * Added validator to terrain object * update some code-comments * do not upload fill-extrusion centroid vertextbuffer in 2d * transform.elevation setter: do not set unmodified state to let map.load event set correct map-location * fix minor bugs when enable/disable terrain * fix lint * add test for recalcuateZoom * Fixes related to console errors and some typings improvement in terrain branch (#1182) * Fixes related to console errors and some typings improvement * Fix lint * Added -1 for terrain calculation, added a test, fixed public field * Add typings * Use MapTiler terrain tiles (#1184) * Use MapTiler terrain tiles * Default location Innsbruck, add terrain control * Remove terrain_control debug page * remove freezeElevation event when disable terrain3d, fixes #1185 * Fixes #1186 - add typeof guard check * Terrain3D symbol cutoff at horizon (#1188) * implement collision-index perspective cutoff logic * getBounds calculate corners based on visible horizion * add tests * Use demotiles.maplibre.org (#1190) * Fix terrain source button (#1192) * When terrain is on, render last layer correct, fixes #1124 (#1189) * when terrain is on render last layer correct, fixes #1124 * Added test to verify the bug * Fix lint Co-authored-by: HarelM <harel.mazor@gmail.com> * Fix lint Co-authored-by: max demmelbauer <max@toursprung.com> Co-authored-by: Andrew Calcutt <acalcutt@techidiots.net> Co-authored-by: acalcutt <acalcutt@worcester.edu> Co-authored-by: HarelM <harel.mazor@gmail.com>
1 parent 52a9f54 commit 5afb592

92 files changed

Lines changed: 2782 additions & 281 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build/generate-style-spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ ${objectDeclaration('StyleSpecification', spec.$root)}
197197
198198
${objectDeclaration('LightSpecification', spec.light)}
199199
200+
${objectDeclaration('TerrainSpecification', spec.terrain)}
201+
200202
${spec.source.map(key => objectDeclaration(sourceTypeName(key), spec[key])).join('\n\n')}
201203
202204
export type SourceSpecification =

debug/terrain.html

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>MapLibre GL JS debug page for terrian</title>
6+
<meta charset='utf-8'>
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
8+
<link rel='stylesheet' href='../dist/maplibre-gl.css' />
9+
<style>
10+
body {
11+
margin: 0;
12+
padding: 0;
13+
}
14+
15+
html,
16+
body,
17+
#map {
18+
height: 100%;
19+
}
20+
</style>
21+
</head>
22+
23+
<body>
24+
<div id='map'></div>
25+
26+
<script src='../dist/maplibre-gl-dev.js'></script>
27+
<script>
28+
var map = window.map = new maplibregl.Map({
29+
container: 'map',
30+
zoom: 12,
31+
center: [11.39085, 47.27574],
32+
pitch: 52,
33+
hash: true,
34+
style: {
35+
version: 8,
36+
sources: {
37+
osm: {
38+
type: 'raster',
39+
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
40+
tileSize: 256,
41+
attribution: '&copy; OpenStreetMap Contributors',
42+
maxzoom: 19
43+
},
44+
terrainSource: {
45+
type: 'raster-dem',
46+
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
47+
tileSize: 256
48+
},
49+
hillshadeSource: {
50+
type: 'raster-dem',
51+
url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
52+
tileSize: 256
53+
},
54+
},
55+
layers: [
56+
{
57+
id: 'osm',
58+
type: 'raster',
59+
source: 'osm'
60+
},
61+
{
62+
id: 'hills',
63+
type: 'hillshade',
64+
source: 'hillshadeSource',
65+
layout: {'visibility': 'visible'},
66+
paint: {'hillshade-shadow-color': '#473B24'}
67+
}
68+
],
69+
terrain: {
70+
source: 'terrainSource',
71+
exaggeration: 1
72+
}
73+
},
74+
maxZoom: 18,
75+
maxPitch: 85
76+
});
77+
78+
map.addControl(new maplibregl.NavigationControl({
79+
visualizePitch: true,
80+
showZoom: true,
81+
showCompass: true
82+
}));
83+
84+
map.addControl(
85+
new maplibregl.TerrainControl({
86+
source: 'terrainSource',
87+
exaggeration: 1
88+
})
89+
);
90+
</script>
91+
</body>
92+
93+
</html>

src/css/maplibre-gl.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@
308308
}
309309
}
310310

311+
@svg-load ctrl-terrain url("svg/maplibregl-ctrl-terrain.svg") {
312+
fill: #333;
313+
#stroke { display: none; }
314+
}
315+
316+
@svg-load ctrl-terrain-enabled url("svg/maplibregl-ctrl-terrain.svg") {
317+
fill: #33b5e5;
318+
#stroke { display: none; }
319+
}
320+
321+
.maplibregl-ctrl button.maplibregl-ctrl-terrain .maplibregl-ctrl-icon,
322+
.mapboxgl-ctrl button.mapboxgl-ctrl-terrain .mapboxgl-ctrl-icon {
323+
background-image: svg-inline(ctrl-terrain);
324+
}
325+
326+
.maplibregl-ctrl button.maplibregl-ctrl-terrain-enabled .maplibregl-ctrl-icon,
327+
.mapboxgl-ctrl button.mapboxgl-ctrl-terrain-enabled .mapboxgl-ctrl-icon {
328+
background-image: svg-inline(ctrl-terrain-enabled);
329+
}
330+
311331
@svg-load ctrl-geolocate url("svg/maplibregl-ctrl-geolocate.svg") {
312332
fill: #333;
313333
#stroke { display: none; }
Lines changed: 7 additions & 0 deletions
Loading

src/data/bucket/fill_extrusion_attributes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ const layout = createLayout([
55
{name: 'a_normal_ed', components: 4, type: 'Int16'},
66
], 4);
77

8+
export const centroidAttributes = createLayout([
9+
{name: 'a_centroid', components: 2, type: 'Int16'}
10+
], 4);
11+
812
export default layout;
913
export const {members, size, alignment} = layout;

src/data/bucket/fill_extrusion_bucket.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {FillExtrusionLayoutArray} from '../array_types.g';
1+
import {FillExtrusionLayoutArray, PosArray} from '../array_types.g';
22

3-
import {members as layoutAttributes} from './fill_extrusion_attributes';
3+
import {members as layoutAttributes, centroidAttributes} from './fill_extrusion_attributes';
44
import SegmentVector from '../segment';
55
import {ProgramConfigurationSet} from '../program_configuration';
66
import {TriangleIndexArray} from '../index_array_type';
@@ -63,6 +63,9 @@ class FillExtrusionBucket implements Bucket {
6363
layoutVertexArray: FillExtrusionLayoutArray;
6464
layoutVertexBuffer: VertexBuffer;
6565

66+
centroidVertexArray: PosArray;
67+
centroidVertexBuffer: VertexBuffer;
68+
6669
indexArray: TriangleIndexArray;
6770
indexBuffer: IndexBuffer;
6871

@@ -81,11 +84,11 @@ class FillExtrusionBucket implements Bucket {
8184
this.hasPattern = false;
8285

8386
this.layoutVertexArray = new FillExtrusionLayoutArray();
87+
this.centroidVertexArray = new PosArray();
8488
this.indexArray = new TriangleIndexArray();
8589
this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom);
8690
this.segments = new SegmentVector();
8791
this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);
88-
8992
}
9093

9194
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID) {
@@ -131,7 +134,7 @@ class FillExtrusionBucket implements Bucket {
131134
}
132135

133136
isEmpty() {
134-
return this.layoutVertexArray.length === 0;
137+
return this.layoutVertexArray.length === 0 && this.centroidVertexArray.length === 0;
135138
}
136139

137140
uploadPending() {
@@ -141,6 +144,7 @@ class FillExtrusionBucket implements Bucket {
141144
upload(context: Context) {
142145
if (!this.uploaded) {
143146
this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, layoutAttributes);
147+
this.centroidVertexBuffer = context.createVertexBuffer(this.centroidVertexArray, centroidAttributes.members, true);
144148
this.indexBuffer = context.createIndexBuffer(this.indexArray);
145149
}
146150
this.programConfigurations.upload(context);
@@ -153,9 +157,11 @@ class FillExtrusionBucket implements Bucket {
153157
this.indexBuffer.destroy();
154158
this.programConfigurations.destroy();
155159
this.segments.destroy();
160+
this.centroidVertexBuffer.destroy();
156161
}
157162

158163
addFeature(feature: BucketFeature, geometry: Array<Array<Point>>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) {
164+
const centroid = {x: 0, y: 0, vertexCount: 0};
159165
for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) {
160166
let numVertices = 0;
161167
for (const ring of polygon) {
@@ -191,11 +197,17 @@ class FillExtrusionBucket implements Bucket {
191197

192198
addVertex(this.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 0, edgeDistance);
193199
addVertex(this.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 1, edgeDistance);
200+
centroid.x += 2 * p1.x;
201+
centroid.y += 2 * p1.y;
202+
centroid.vertexCount += 2;
194203

195204
edgeDistance += dist;
196205

197206
addVertex(this.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 0, edgeDistance);
198207
addVertex(this.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 1, edgeDistance);
208+
centroid.x += 2 * p2.x;
209+
centroid.y += 2 * p2.y;
210+
centroid.vertexCount += 2;
199211

200212
const bottomRight = segment.vertexLength;
201213

@@ -212,6 +224,7 @@ class FillExtrusionBucket implements Bucket {
212224
}
213225
}
214226
}
227+
215228
}
216229

217230
if (segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) {
@@ -240,10 +253,14 @@ class FillExtrusionBucket implements Bucket {
240253
const p = ring[i];
241254

242255
addVertex(this.layoutVertexArray, p.x, p.y, 0, 0, 1, 1, 0);
256+
centroid.x += p.x;
257+
centroid.y += p.y;
258+
centroid.vertexCount += 1;
243259

244260
flattened.push(p.x);
245261
flattened.push(p.y);
246262
}
263+
247264
}
248265

249266
const indices = earcut(flattened, holeIndices);
@@ -261,6 +278,13 @@ class FillExtrusionBucket implements Bucket {
261278
segment.vertexLength += numVertices;
262279
}
263280

281+
// remember polygon centroid to calculate elevation in GPU
282+
for (let i = 0; i < centroid.vertexCount; i++) {
283+
this.centroidVertexArray.emplaceBack(
284+
Math.floor(centroid.x / centroid.vertexCount),
285+
Math.floor(centroid.y / centroid.vertexCount)
286+
);
287+
}
264288
this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical);
265289
}
266290
}

src/data/dem_data.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,26 @@ describe('DEMData is correctly serialized and deserialized', () => {
222222
const dem0 = new DEMData('0', imageData0, 'mapbox');
223223
const serialized = serialize(dem0);
224224

225+
// calculate min/max values
226+
let min = Number.MAX_SAFE_INTEGER;
227+
let max = Number.MIN_SAFE_INTEGER;
228+
for (let x = 0; x < 4; x++) {
229+
for (let y = 0; y < 4; y++) {
230+
const ele = dem0.get(x, y);
231+
if (ele > max) max = ele;
232+
if (ele < min) min = ele;
233+
}
234+
}
235+
225236
expect(serialized).toEqual({
226237
$name: 'DEMData',
227238
uid: '0',
228239
dim: 4,
229240
stride: 6,
230241
data: dem0.data,
231-
encoding: 'mapbox'
242+
encoding: 'mapbox',
243+
max,
244+
min,
232245
});
233246

234247
const transferrables = [];

src/data/dem_data.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export default class DEMData {
1818
data: Uint32Array;
1919
stride: number;
2020
dim: number;
21+
min: number;
22+
max: number;
2123
encoding: 'mapbox' | 'terrarium';
2224

2325
// RGBAImage data has uniform 1px padding on all sides: square tile edge size defines stride
@@ -52,6 +54,17 @@ export default class DEMData {
5254
this.data[this._idx(dim, -1)] = this.data[this._idx(dim - 1, 0)];
5355
this.data[this._idx(-1, dim)] = this.data[this._idx(0, dim - 1)];
5456
this.data[this._idx(dim, dim)] = this.data[this._idx(dim - 1, dim - 1)];
57+
58+
// calculate min/max values
59+
this.min = Number.MAX_SAFE_INTEGER;
60+
this.max = Number.MIN_SAFE_INTEGER;
61+
for (let x = 0; x < dim; x++) {
62+
for (let y = 0; y < dim; y++) {
63+
const ele = this.get(x, y);
64+
if (ele > this.max) this.max = ele;
65+
if (ele < this.min) this.min = ele;
66+
}
67+
}
5568
}
5669

5770
get(x: number, y: number) {

src/geo/transform.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Transform from './transform';
33
import LngLat from './lng_lat';
44
import {OverscaledTileID, CanonicalTileID} from '../source/tile_id';
55
import {fixedLngLat, fixedCoord} from '../../test/unit/lib/fixed';
6+
import type Terrain from '../render/terrain';
67

78
describe('transform', () => {
89
test('creates a transform', () => {
@@ -340,7 +341,6 @@ describe('transform', () => {
340341
});
341342

342343
test('maintains high float precision when calculating matrices', () => {
343-
344344
const transform = new Transform(0, 22, 0, 60, true);
345345
transform.resize(200.25, 200.25);
346346
transform.zoom = 20.25;
@@ -352,4 +352,59 @@ describe('transform', () => {
352352
expect(transform.glCoordMatrix[0].toString().length).toBeGreaterThan(10);
353353
expect(transform.maxPitchScaleFactor()).toBeCloseTo(2.366025418080343, 10);
354354
});
355+
356+
test('recalcuateZoom', () => {
357+
const transform = new Transform(0, 22, 0, 60, true);
358+
transform.elevation = 200;
359+
transform.center = new LngLat(10.0, 50.0);
360+
transform.zoom = 14;
361+
transform.resize(512, 512);
362+
363+
// expect same values because of no elevation change
364+
transform.getElevation = () => 200;
365+
transform.recalculateZoom(null);
366+
expect(transform.zoom).toBe(14);
367+
368+
// expect new zoom because of elevation change
369+
transform.getElevation = () => 400;
370+
transform.recalculateZoom(null);
371+
expect(transform.zoom).toBe(14.127997275621933);
372+
expect(transform.elevation).toBe(400);
373+
expect(transform._center.lng).toBe(10.00000000000071);
374+
expect(transform._center.lat).toBe(50.00000000000017);
375+
});
376+
377+
test('pointCoordinate with terrain when returning null should fall back to 2D', () => {
378+
const transform = new Transform(0, 22, 0, 60, true);
379+
transform.resize(500, 500);
380+
const terrain = {
381+
pointCoordinate: () => null
382+
} as any as Terrain;
383+
const coordinate = transform.pointCoordinate(new Point(0, 0), terrain);
384+
385+
expect(coordinate).toBeDefined();
386+
});
387+
388+
test('horizon', () => {
389+
const transform = new Transform(0, 22, 0, 85, true);
390+
transform.resize(500, 500);
391+
transform.pitch = 75;
392+
const horizon = transform.getHorizon();
393+
394+
expect(horizon).toBeCloseTo(170.8176101748407, 10);
395+
});
396+
397+
test('getBounds with horizon', () => {
398+
const transform = new Transform(0, 22, 0, 85, true);
399+
transform.resize(500, 500);
400+
401+
transform.pitch = 60;
402+
expect(transform.getBounds().getNorthWest().toArray()).toStrictEqual(transform.pointLocation(new Point(0, 0)).toArray());
403+
404+
transform.pitch = 75;
405+
const top = Math.max(0, transform.height / 2 - transform.getHorizon());
406+
expect(top).toBeCloseTo(79.1823898251593, 10);
407+
expect(transform.getBounds().getNorthWest().toArray()).toStrictEqual(transform.pointLocation(new Point(0, top)).toArray());
408+
});
409+
355410
});

0 commit comments

Comments
 (0)