@deck.gl/extensions 9.4.0-beta.4 → 9.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dist.dev.js +98 -109
- package/dist/index.cjs +103 -94
- package/dist/index.cjs.map +1 -1
- package/dist/path-style/path-style-extension.d.ts.map +1 -1
- package/dist/path-style/path-style-extension.js +5 -2
- package/dist/path-style/path-style-extension.js.map +1 -1
- package/dist/path-style/shaders.glsl.d.ts +20 -9
- package/dist/path-style/shaders.glsl.d.ts.map +1 -1
- package/dist/path-style/shaders.glsl.js +76 -69
- package/dist/path-style/shaders.glsl.js.map +1 -1
- package/dist/utils/projection-utils.d.ts +28 -1
- package/dist/utils/projection-utils.d.ts.map +1 -1
- package/dist/utils/projection-utils.js +97 -1
- package/dist/utils/projection-utils.js.map +1 -1
- package/dist.min.js +108 -112
- package/package.json +7 -7
- package/src/path-style/path-style-extension.ts +5 -1
- package/src/path-style/shaders.glsl.ts +102 -115
- package/src/utils/projection-utils.ts +136 -2
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Plug-and-play functionalities for deck.gl layers",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "9.4.
|
|
6
|
+
"version": "9.4.1",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env=dev"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@luma.gl/shadertools": "^9.4.
|
|
46
|
-
"@luma.gl/webgl": "^9.4.
|
|
45
|
+
"@luma.gl/shadertools": "^9.4.2",
|
|
46
|
+
"@luma.gl/webgl": "^9.4.2",
|
|
47
47
|
"@math.gl/core": "^4.1.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@deck.gl/core": "~9.4.0
|
|
51
|
-
"@luma.gl/core": "~9.4.0
|
|
52
|
-
"@luma.gl/engine": "~9.4.0
|
|
50
|
+
"@deck.gl/core": "~9.4.0",
|
|
51
|
+
"@luma.gl/core": "~9.4.0",
|
|
52
|
+
"@luma.gl/engine": "~9.4.0"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "7de3aa41176da9d6dcc11562fb5fa8b9d0cb11a4"
|
|
55
55
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
dashShaders,
|
|
15
15
|
Defines,
|
|
16
16
|
offsetShaders,
|
|
17
|
+
pathStylePipelineShaders,
|
|
17
18
|
scatterplotDashShaders,
|
|
18
19
|
textBackgroundDashShaders
|
|
19
20
|
} from './shaders.glsl';
|
|
@@ -318,8 +319,11 @@ export default class PathStyleExtension extends LayerExtension<ResolvedPathStyle
|
|
|
318
319
|
return {modules: [pathStyle]};
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
// PathLayer:
|
|
322
|
+
// PathLayer: one ordered coordinate stage plus capability-specific declarations and setup.
|
|
322
323
|
let result = {} as {inject: Record<string, string>};
|
|
324
|
+
if (extension.opts.dash || extension.opts.offset) {
|
|
325
|
+
result = mergeShaders(result, pathStylePipelineShaders);
|
|
326
|
+
}
|
|
323
327
|
const defines: Defines = {};
|
|
324
328
|
if (extension.opts.dash) {
|
|
325
329
|
result = mergeShaders(result, dashShaders);
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* scalar `getDashOffsets` helper remains available for compatibility but does not back the
|
|
13
13
|
* managed attribute.
|
|
14
14
|
* 2. **Screen pixels** — the common basis for reconciling flat and billboard extrusion.
|
|
15
|
-
* At the dash injection point,
|
|
16
|
-
* a billboarded path, whose conversion also includes
|
|
17
|
-
* `
|
|
15
|
+
* At the dash injection point, the pre-offset stroke half-width is in common units for a
|
|
16
|
+
* flat path but pixels for a billboarded path, whose conversion also includes
|
|
17
|
+
* `project.focalDistance`.
|
|
18
18
|
* 3. **Half-widths along the path** — the units of `vPathPosition.y`, tested by the fragment
|
|
19
19
|
* shader. One unit spans `dashWidthPixels` screen pixels.
|
|
20
20
|
*
|
|
@@ -28,8 +28,8 @@ export type Defines = {
|
|
|
28
28
|
*/
|
|
29
29
|
HIGH_PRECISION_DASH?: boolean;
|
|
30
30
|
/**
|
|
31
|
-
* Set whenever the dash shaders are injected
|
|
32
|
-
* varyings
|
|
31
|
+
* Set whenever the dash shaders are injected. It guards PathLayer's additional arclength
|
|
32
|
+
* varyings and the dash stage of the shared PathStyle pipeline.
|
|
33
33
|
*/
|
|
34
34
|
DASH_ENABLED?: boolean;
|
|
35
35
|
/**
|
|
@@ -38,53 +38,47 @@ export type Defines = {
|
|
|
38
38
|
PATH_STYLE_OFFSET?: boolean;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Ordered PathStyle coordinate pipeline shared by the dash and offset capabilities.
|
|
43
|
+
*
|
|
44
|
+
* Offset rendering temporarily widens PathLayer's geometry so the shifted stroke fits inside the
|
|
45
|
+
* generated mesh. This stage first maps PathLayer's varyings back to the pre-offset stroke, then
|
|
46
|
+
* derives dash coordinates from that width. Keeping both operations in one injection
|
|
47
|
+
* avoids making their result depend on the order in which the capability shaders are merged.
|
|
48
|
+
* It also owns their deferred fragment rejection, after PathLayer has evaluated derivatives.
|
|
49
|
+
*/
|
|
50
|
+
export const pathStylePipelineShaders = {
|
|
42
51
|
inject: {
|
|
43
|
-
'vs:#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
'vs:#main-end': `
|
|
53
|
+
#ifdef PATH_STYLE_OFFSET
|
|
54
|
+
float offsetWidth = abs(instanceOffsets * 2.0) + 1.0;
|
|
55
|
+
float offsetDir = sign(instanceOffsets);
|
|
56
|
+
vPathPosition.x = (vPathPosition.x + offsetDir) * offsetWidth - offsetDir;
|
|
57
|
+
vPathPosition.y *= offsetWidth;
|
|
58
|
+
vPathLength *= offsetWidth;
|
|
59
|
+
#ifdef DASH_ENABLED
|
|
60
|
+
vPathBounds *= offsetWidth;
|
|
61
|
+
#endif
|
|
48
62
|
#endif
|
|
49
|
-
out vec2 vDashArray;
|
|
50
|
-
out float vDashOffset;
|
|
51
|
-
out float vDashPathLength;
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} pathStyle;
|
|
61
|
-
`,
|
|
64
|
+
#ifdef DASH_ENABLED
|
|
65
|
+
// DECKGL_FILTER_SIZE may have widened width for offset geometry. Dash coordinates are measured
|
|
66
|
+
// against the pre-offset stroke, so recover its half-width before converting dash units.
|
|
67
|
+
float strokeHalfWidth = width.x;
|
|
68
|
+
#ifdef PATH_STYLE_OFFSET
|
|
69
|
+
strokeHalfWidth /= offsetWidth;
|
|
70
|
+
#endif
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
// the path layer leave this in different units, and both differences have to be undone here
|
|
66
|
-
// for a billboarded path to dash like a flat one.
|
|
67
|
-
//
|
|
68
|
-
// Flat paths extrude in common space, so \`width\` is in common units and a unit of
|
|
69
|
-
// vPathPosition.y is width.x * project.scale pixels.
|
|
70
|
-
//
|
|
71
|
-
// Billboarded paths extrude in clip space, so \`width\` is already in pixels at this point -
|
|
72
|
-
// the clip-space conversion happens inside getLineJoinOffset. That conversion also folds in
|
|
73
|
-
// project.focalDistance, which scales the half-width the segment delta is divided by but not
|
|
74
|
-
// the delta itself, so a unit of vPathPosition.y ends up focalDistance pixels wider than the
|
|
75
|
-
// stroke it is supposed to be relative to.
|
|
72
|
+
// How many screen pixels one unit of vPathPosition.y covers. Flat paths extrude in common space;
|
|
73
|
+
// billboarded paths extrude in clip space, whose conversion also includes focalDistance.
|
|
76
74
|
float dashWidthPixels = path.billboard
|
|
77
|
-
?
|
|
78
|
-
:
|
|
75
|
+
? strokeHalfWidth * project.focalDistance
|
|
76
|
+
: strokeHalfWidth * project.scale;
|
|
77
|
+
float strokeHalfWidthPixels = path.billboard
|
|
78
|
+
? strokeHalfWidth
|
|
79
|
+
: strokeHalfWidth * project.scale;
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
-
// from dashWidthPixels by exactly the spurious focalDistance above, so expressing 'widths'
|
|
82
|
-
// as a ratio of the two corrects the billboard case onto the flat one - getDashArray is
|
|
83
|
-
// documented relative to the stroke, and flat paths already honor that.
|
|
84
|
-
float strokeHalfWidthPixels = path.billboard ? width.x : width.x * project.scale;
|
|
85
|
-
|
|
86
|
-
// Everything reduces to "how many screen pixels is one dash unit", divided through by the
|
|
87
|
-
// pixels per unit of vPathPosition.y, so the fragment shader needs no notion of units at all.
|
|
81
|
+
// Convert every public dash unit to the half-width coordinate used by the fragment shader.
|
|
88
82
|
// Keep the cases in sync with DASH_UNITS in path-style-extension.ts.
|
|
89
83
|
float dashUnitPixels = strokeHalfWidthPixels;
|
|
90
84
|
if (pathStyle.dashUnits == 1) {
|
|
@@ -116,6 +110,68 @@ vDashOffset = dashPeriod > 0.0 ? mod(dashOffsetAndLength.x, dashPeriod) : 0.0;
|
|
|
116
110
|
vDashOffset = 0.0;
|
|
117
111
|
vDashPathLength = 0.0;
|
|
118
112
|
#endif
|
|
113
|
+
#endif
|
|
114
|
+
`,
|
|
115
|
+
'fs:#main-end': `
|
|
116
|
+
#ifdef DASH_ENABLED
|
|
117
|
+
// PathLayer computes analytic-edge derivatives in its fragment body. A discard in
|
|
118
|
+
// #main-start can remove helper invocations and make those derivatives undefined, so all
|
|
119
|
+
// dash-related termination is deferred until the layer has completed that work.
|
|
120
|
+
#ifdef ANTIALIASING
|
|
121
|
+
if (inRoundedDashGap) {
|
|
122
|
+
// The rounded dash cap and PathLayer silhouette are two geometric constraints on the
|
|
123
|
+
// same fragment. Intersect their coverage instead of multiplying two edge ramps; at the
|
|
124
|
+
// cap/body shoulder both ramps are half covered and multiplication would create a dark
|
|
125
|
+
// quarter-covered notch. Keep the sub-pixel duty cycle separable from the path silhouette.
|
|
126
|
+
float pathCoverage = smoothedge(0.0, edgePixels);
|
|
127
|
+
float resolvedCapMultiplier =
|
|
128
|
+
min(pathCoverage, roundedDashResolvedCoverage) / max(pathCoverage, 1e-6);
|
|
129
|
+
dashCoverage = mix(
|
|
130
|
+
resolvedCapMultiplier,
|
|
131
|
+
roundedDashDutyCycle,
|
|
132
|
+
roundedDashSubPixelBlend
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
#endif
|
|
136
|
+
if (shouldDiscardDash) {
|
|
137
|
+
discard;
|
|
138
|
+
}
|
|
139
|
+
fragColor.a *= dashCoverage;
|
|
140
|
+
#endif
|
|
141
|
+
#ifdef PATH_STYLE_OFFSET
|
|
142
|
+
#ifndef ANTIALIASING
|
|
143
|
+
// With analytic antialiasing, PathLayer evaluates this boundary using the remapped
|
|
144
|
+
// vPathPosition and retains the complete centered coverage ramp. The hard clip remains for
|
|
145
|
+
// the original non-AA path.
|
|
146
|
+
if (abs(vPathPosition.x) > 1.0) {
|
|
147
|
+
discard;
|
|
148
|
+
}
|
|
149
|
+
#endif
|
|
150
|
+
#endif
|
|
151
|
+
`
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const dashShaders = {
|
|
156
|
+
inject: {
|
|
157
|
+
'vs:#decl': `
|
|
158
|
+
in vec2 instanceDashArrays;
|
|
159
|
+
#ifdef HIGH_PRECISION_DASH
|
|
160
|
+
// [distance from the start of the path, total length of the path], in common space.
|
|
161
|
+
in vec2 instanceDashOffsets;
|
|
162
|
+
#endif
|
|
163
|
+
out vec2 vDashArray;
|
|
164
|
+
out float vDashOffset;
|
|
165
|
+
out float vDashPathLength;
|
|
166
|
+
|
|
167
|
+
// Also declared in the fragment stage. The vertex stage needs dashAlignMode so that it can
|
|
168
|
+
// reduce the dash phase modulo the same period the fragment stage will test against, and
|
|
169
|
+
// dashUnits to scale the dash array into that period's units.
|
|
170
|
+
layout(std140) uniform pathStyleUniforms {
|
|
171
|
+
float dashAlignMode;
|
|
172
|
+
bool dashGapPickable;
|
|
173
|
+
highp int dashUnits;
|
|
174
|
+
} pathStyle;
|
|
119
175
|
`,
|
|
120
176
|
|
|
121
177
|
'fs:#decl': `
|
|
@@ -270,32 +326,6 @@ float dashPatternCoverage(
|
|
|
270
326
|
// Fully transparent fragments would still write depth and occlude whatever is behind.
|
|
271
327
|
shouldDiscardDash = shouldDiscardDash || dashCoverage <= 0.0;
|
|
272
328
|
}
|
|
273
|
-
`,
|
|
274
|
-
|
|
275
|
-
'fs:#main-end': `
|
|
276
|
-
// PathLayer computes analytic-edge derivatives in its fragment body. A discard in
|
|
277
|
-
// #main-start can remove helper invocations and make those derivatives undefined, so all
|
|
278
|
-
// dash-related termination is deferred until the layer has completed that work.
|
|
279
|
-
#ifdef ANTIALIASING
|
|
280
|
-
if (inRoundedDashGap) {
|
|
281
|
-
// The rounded dash cap and PathLayer silhouette are two geometric constraints on the
|
|
282
|
-
// same fragment. Intersect their coverage instead of multiplying two edge ramps; at the
|
|
283
|
-
// cap/body shoulder both ramps are half covered and multiplication would create a dark
|
|
284
|
-
// quarter-covered notch. Keep the sub-pixel duty cycle separable from the path silhouette.
|
|
285
|
-
float pathCoverage = smoothedge(0.0, edgePixels);
|
|
286
|
-
float resolvedCapMultiplier =
|
|
287
|
-
min(pathCoverage, roundedDashResolvedCoverage) / max(pathCoverage, 1e-6);
|
|
288
|
-
dashCoverage = mix(
|
|
289
|
-
resolvedCapMultiplier,
|
|
290
|
-
roundedDashDutyCycle,
|
|
291
|
-
roundedDashSubPixelBlend
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
#endif
|
|
295
|
-
if (shouldDiscardDash) {
|
|
296
|
-
discard;
|
|
297
|
-
}
|
|
298
|
-
fragColor.a *= dashCoverage;
|
|
299
329
|
`
|
|
300
330
|
}
|
|
301
331
|
};
|
|
@@ -521,49 +551,6 @@ in float instanceOffsets;
|
|
|
521
551
|
'vs:DECKGL_FILTER_SIZE': `
|
|
522
552
|
float offsetWidth = abs(instanceOffsets * 2.0) + 1.0;
|
|
523
553
|
size *= offsetWidth;
|
|
524
|
-
`,
|
|
525
|
-
'vs:#main-end': `
|
|
526
|
-
float offsetWidth = abs(instanceOffsets * 2.0) + 1.0;
|
|
527
|
-
float offsetDir = sign(instanceOffsets);
|
|
528
|
-
vPathPosition.x = (vPathPosition.x + offsetDir) * offsetWidth - offsetDir;
|
|
529
|
-
vPathPosition.y *= offsetWidth;
|
|
530
|
-
vPathLength *= offsetWidth;
|
|
531
|
-
#ifdef DASH_ENABLED
|
|
532
|
-
// DECKGL_FILTER_SIZE above widened the stroke by offsetWidth, so these rescalings restore
|
|
533
|
-
// units of the original half-width. The dash block merges ahead of this one.
|
|
534
|
-
if (pathStyle.dashUnits != 0) {
|
|
535
|
-
// Absolute dash units must not inherit the artificial width used to build offset
|
|
536
|
-
// geometry. Relative 'widths' units intentionally remain tied to the original stroke.
|
|
537
|
-
vDashArray *= offsetWidth;
|
|
538
|
-
}
|
|
539
|
-
vPathBounds *= offsetWidth;
|
|
540
|
-
#ifdef HIGH_PRECISION_DASH
|
|
541
|
-
vDashPathLength *= offsetWidth;
|
|
542
|
-
// The dash block reduced the CPU path offset modulo a period expressed in widened-width
|
|
543
|
-
// units. Multiplying that remainder cannot recover the original phase after wrapping, so
|
|
544
|
-
// redo the reduction with both the restored path offset and restored justified period.
|
|
545
|
-
float restoredDashUnitLength = vDashArray.x + vDashArray.y;
|
|
546
|
-
float restoredDashPeriod = pathStyle.dashAlignMode == 0.0
|
|
547
|
-
? restoredDashUnitLength
|
|
548
|
-
: vDashPathLength /
|
|
549
|
-
max(round(vDashPathLength / max(restoredDashUnitLength, 0.0001)), 1.0);
|
|
550
|
-
vDashOffset = restoredDashPeriod > 0.0
|
|
551
|
-
? mod(dashOffsetAndLength.x * offsetWidth, restoredDashPeriod)
|
|
552
|
-
: 0.0;
|
|
553
|
-
#else
|
|
554
|
-
vDashOffset *= offsetWidth;
|
|
555
|
-
#endif
|
|
556
|
-
#endif
|
|
557
|
-
`,
|
|
558
|
-
'fs:#main-end': `
|
|
559
|
-
#ifndef ANTIALIASING
|
|
560
|
-
// With analytic antialiasing, PathLayer evaluates this boundary using the remapped
|
|
561
|
-
// vPathPosition and retains the complete centered coverage ramp. The hard clip remains for
|
|
562
|
-
// the original non-AA path.
|
|
563
|
-
if (abs(vPathPosition.x) > 1.0) {
|
|
564
|
-
discard;
|
|
565
|
-
}
|
|
566
|
-
#endif
|
|
567
554
|
`
|
|
568
555
|
}
|
|
569
556
|
};
|
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import {WebMercatorViewport, OrthographicViewport} from '@deck.gl/core';
|
|
6
|
-
import type {Layer, Viewport} from '@deck.gl/core';
|
|
5
|
+
import {WebMercatorViewport, OrthographicViewport, _GlobeViewport} from '@deck.gl/core';
|
|
6
|
+
import type {CoordinateSystem, Layer, ProjectUniforms, Viewport} from '@deck.gl/core';
|
|
7
|
+
import {vec3, vec4} from '@math.gl/core';
|
|
8
|
+
import type {NumericArray} from '@math.gl/core';
|
|
9
|
+
|
|
10
|
+
/** TILE_SIZE in the project shader module */
|
|
11
|
+
const TILE_SIZE = 512;
|
|
12
|
+
/** GLOBE_RADIUS / EARTH_RADIUS in the project shader module */
|
|
13
|
+
const GLOBE_METERS_TO_COMMON = 256 / 6370972;
|
|
7
14
|
|
|
8
15
|
/** Bounds in CARTESIAN coordinates */
|
|
9
16
|
export type Bounds = [minX: number, minY: number, maxX: number, maxY: number];
|
|
@@ -28,6 +35,127 @@ export function getMercatorReferenceViewport(viewport: Viewport): Viewport {
|
|
|
28
35
|
return viewport.isGeospatial ? MERCATOR_REFERENCE_VIEWPORT : viewport;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
// Flat common space: Mercator (geospatial) or cartesian (OrthographicView). CPU counterparts of
|
|
39
|
+
// project_common_position_to_flat(). Adding a non-flat projection: update the flatten branch in
|
|
40
|
+
// core project.glsl.ts / project.wgsl.ts, isFlatViewport(), and test/render/view-presets.ts.
|
|
41
|
+
|
|
42
|
+
/** False only for GlobeViewport, whose common space is a sphere. The single globe check in this module. */
|
|
43
|
+
export function isFlatViewport(viewport: Viewport): boolean {
|
|
44
|
+
return !(viewport instanceof _GlobeViewport);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Per-call overrides of the layer's coordinateSystem, coordinateOrigin and modelMatrix */
|
|
48
|
+
export type FlatProjectOptions = {
|
|
49
|
+
fromCoordinateSystem?: CoordinateSystem;
|
|
50
|
+
fromCoordinateOrigin?: [number, number, number];
|
|
51
|
+
modelMatrix?: NumericArray | null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* CPU counterpart of `project_common_position_to_flat(project_position(position))`, relative to
|
|
56
|
+
* `getFlatCommonOrigin()`. Always pair with the GPU function: a raw `layer.projectPosition()`
|
|
57
|
+
* disagrees with it under globe or auto-offset.
|
|
58
|
+
*/
|
|
59
|
+
export function projectToFlatCommon(
|
|
60
|
+
layer: Layer,
|
|
61
|
+
position: number[],
|
|
62
|
+
opts: FlatProjectOptions = {}
|
|
63
|
+
): [number, number, number] {
|
|
64
|
+
// Same resolution as Layer.projectPosition so multi-view setups stay consistent
|
|
65
|
+
const viewport = getLayerViewport(layer);
|
|
66
|
+
if (isFlatViewport(viewport)) {
|
|
67
|
+
return layer.projectPosition(position, opts);
|
|
68
|
+
}
|
|
69
|
+
if (getSourceCoordinateSystem(layer, opts) === 'meter-offsets') {
|
|
70
|
+
return projectGlobeMeterOffsetsToFlatCommon(layer, viewport, position, opts);
|
|
71
|
+
}
|
|
72
|
+
return layer.projectPosition(position, {
|
|
73
|
+
...opts,
|
|
74
|
+
viewport: getMercatorReferenceViewport(viewport),
|
|
75
|
+
autoOffset: false
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mirrors the shader's GLOBE + METER_OFFSETS path: a linear offset in the tangent plane at the
|
|
81
|
+
* origin. Do not go through lng/lat; the error grows with the square of the offset (~200 m at 300 km).
|
|
82
|
+
*/
|
|
83
|
+
function projectGlobeMeterOffsetsToFlatCommon(
|
|
84
|
+
layer: Layer,
|
|
85
|
+
viewport: Viewport,
|
|
86
|
+
position: number[],
|
|
87
|
+
opts: FlatProjectOptions
|
|
88
|
+
): [number, number, number] {
|
|
89
|
+
const origin = opts.fromCoordinateOrigin ?? layer.props.coordinateOrigin;
|
|
90
|
+
const modelMatrix = opts.modelMatrix === undefined ? layer.props.modelMatrix : opts.modelMatrix;
|
|
91
|
+
let [x, y, z = 0] = position;
|
|
92
|
+
if (modelMatrix) {
|
|
93
|
+
[x, y, z] = vec4.transformMat4([] as number[], [x, y, z, 1], modelMatrix);
|
|
94
|
+
}
|
|
95
|
+
// project.commonOrigin: sphere position of the coordinate origin
|
|
96
|
+
const originCommon = viewport.projectPosition(origin);
|
|
97
|
+
// project_get_orientation_matrix(commonOrigin)
|
|
98
|
+
const uz = vec3.normalize([] as number[], originCommon);
|
|
99
|
+
const ux = Math.abs(uz[2]) === 1 ? [1, 0, 0] : vec3.normalize([] as number[], [uz[1], -uz[0], 0]);
|
|
100
|
+
const uy = vec3.cross([] as number[], uz, ux);
|
|
101
|
+
// enuMatrix * vec3(-x, -y, z) * metersToCommon
|
|
102
|
+
const displaced = [0, 1, 2].map(
|
|
103
|
+
i => originCommon[i] + (ux[i] * -x + uy[i] * -y + uz[i] * z) * GLOBE_METERS_TO_COMMON
|
|
104
|
+
);
|
|
105
|
+
// project_globe_to_mercator_
|
|
106
|
+
const lngLatZ = viewport.unprojectPosition(displaced);
|
|
107
|
+
return MERCATOR_REFERENCE_VIEWPORT.projectPosition(lngLatZ);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Projects `[minX, minY, maxX, maxY]` into flat common bounds. Lng/lat bounds with
|
|
112
|
+
* `minX - maxX > 180` take the shorter arc across the antimeridian, so `maxX` may exceed TILE_SIZE;
|
|
113
|
+
* test against them with `project_common_position_to_flat_wrapped()` about the bounds centre.
|
|
114
|
+
* Otherwise edges are re-ordered, so `[right, top, left, bottom]` input still works.
|
|
115
|
+
*/
|
|
116
|
+
export function projectBoundsToFlatCommon(
|
|
117
|
+
layer: Layer,
|
|
118
|
+
bounds: Readonly<[number, number, number, number]>,
|
|
119
|
+
opts?: FlatProjectOptions
|
|
120
|
+
): Bounds {
|
|
121
|
+
const a = projectToFlatCommon(layer, [bounds[0], bounds[1], 0], opts);
|
|
122
|
+
const b = projectToFlatCommon(layer, [bounds[2], bounds[3], 0], opts);
|
|
123
|
+
if (
|
|
124
|
+
bounds[0] - bounds[2] > 180 &&
|
|
125
|
+
getLayerViewport(layer).isGeospatial &&
|
|
126
|
+
getSourceCoordinateSystem(layer, opts) === 'lnglat'
|
|
127
|
+
) {
|
|
128
|
+
b[0] += TILE_SIZE;
|
|
129
|
+
}
|
|
130
|
+
return [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[0], b[0]), Math.max(a[1], b[1])];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getLayerViewport(layer: Layer): Viewport {
|
|
134
|
+
return layer.internalState?.viewport || layer.context.viewport;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The coordinate system `position` is expressed in, with `default` resolved */
|
|
138
|
+
function getSourceCoordinateSystem(layer: Layer, opts: FlatProjectOptions = {}): CoordinateSystem {
|
|
139
|
+
const coordinateSystem = opts.fromCoordinateSystem ?? layer.props.coordinateSystem;
|
|
140
|
+
if (coordinateSystem === 'default') {
|
|
141
|
+
return getLayerViewport(layer).isGeospatial ? 'lnglat' : 'cartesian';
|
|
142
|
+
}
|
|
143
|
+
return coordinateSystem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Origin of `project_common_position_to_flat()` results: `project.commonOrigin` on flat viewports,
|
|
148
|
+
* `[0, 0]` under globe, where commonOrigin is a sphere position and must never be re-added.
|
|
149
|
+
*/
|
|
150
|
+
export function getFlatCommonOrigin(
|
|
151
|
+
projectUniforms: ProjectUniforms,
|
|
152
|
+
viewport: Viewport
|
|
153
|
+
): [number, number] {
|
|
154
|
+
return isFlatViewport(viewport)
|
|
155
|
+
? [projectUniforms.commonOrigin[0], projectUniforms.commonOrigin[1]]
|
|
156
|
+
: [0, 0];
|
|
157
|
+
}
|
|
158
|
+
|
|
31
159
|
/*
|
|
32
160
|
* Compute the union of bounds from multiple layers
|
|
33
161
|
* Returns bounds in CARTESIAN coordinates
|
|
@@ -181,6 +309,12 @@ export function getRenderBounds(
|
|
|
181
309
|
return [0, 0, 1, 1];
|
|
182
310
|
}
|
|
183
311
|
|
|
312
|
+
// layerBounds are flat (Mercator) but getViewportBounds() would return sphere coordinates for a
|
|
313
|
+
// non-flat viewport; the two cannot be intersected. Render the full layer extent instead.
|
|
314
|
+
if (!isFlatViewport(viewport)) {
|
|
315
|
+
return layerBounds;
|
|
316
|
+
}
|
|
317
|
+
|
|
184
318
|
const viewportBounds = getViewportBounds(viewport, zRange);
|
|
185
319
|
// Expand viewport bounds by 2X. Heurestically chosen to avoid masking
|
|
186
320
|
// errors when mask is partially out of view
|