@sakuzu/maplibre-gl-draw 2.1.0 → 2.1.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/CHANGELOG.md +25 -0
- package/dist/dataset/retained.js +19 -18
- package/dist/dataset/selection.d.ts +3 -3
- package/dist/dataset/selection.js +3 -7
- package/dist/dataset/table-source.js +13 -11
- package/dist/view/layer/custom-layer.js +2 -1
- package/dist/view/layer/drape-planner.d.ts +3 -0
- package/dist/view/layer/drape-planner.js +9 -7
- package/dist/view/layer/frame-render.js +2 -3
- package/dist/view/layer/frame-state.d.ts +3 -0
- package/dist/view/layer/frame-state.js +2 -1
- package/dist/view/layer/gl-state.d.ts +2 -3
- package/dist/view/layer/gl-state.js +2 -3
- package/dist/view/renderers/line/dash.d.ts +27 -0
- package/dist/view/renderers/line/dash.js +23 -12
- package/dist/view/terrain/drape/bin-store.d.ts +11 -2
- package/dist/view/terrain/drape/bin-store.js +26 -2
- package/dist/view/terrain/drape/binning.d.ts +32 -0
- package/dist/view/terrain/drape/binning.js +15 -7
- package/dist/view/terrain/drape/geometry.d.ts +27 -0
- package/dist/view/terrain/drape/geometry.js +57 -6
- package/dist/view/terrain/drape/pass.d.ts +8 -3
- package/dist/view/terrain/drape/pass.js +39 -28
- package/dist/view/terrain/drape/renderer.d.ts +13 -0
- package/dist/view/terrain/drape/renderer.js +157 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ the project follows semantic versioning.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.1.1] - 2026-10-02
|
|
10
|
+
|
|
11
|
+
Three fixes to the lines drawn on the terrain: their thickness on a
|
|
12
|
+
high-DPI display, their thickness between the vertices, and dashed lines
|
|
13
|
+
that sank into the ground. Nothing else changes.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- With the terrain on, a solid line or the outline of a polygon was drawn
|
|
18
|
+
half as thick on a high-DPI display (a device pixel ratio of 2) as
|
|
19
|
+
without the terrain, when its width follows the zoom. It is now as
|
|
20
|
+
thick with the terrain as without it.
|
|
21
|
+
- With the terrain on and the camera pitched, a solid line looked like a
|
|
22
|
+
string of beads: it was thinner between its vertices than at them, and
|
|
23
|
+
the outline of a polygon was thinner along its edges. Its thickness is
|
|
24
|
+
now the same all along.
|
|
25
|
+
- With the terrain on and the camera pitched, a dashed or dotted line,
|
|
26
|
+
and the dashed outline of a polygon, sank into the ground on the side
|
|
27
|
+
towards the camera and showed a jagged edge. They are now painted on
|
|
28
|
+
the ground like solid lines, with the same dashes as without the
|
|
29
|
+
terrain.
|
|
30
|
+
|
|
9
31
|
## [2.1.0] - 2026-10-01
|
|
10
32
|
|
|
11
33
|
The standard UI package, the documentation site with twenty examples and
|
|
@@ -1146,5 +1168,8 @@ available from Kasika, Inc.
|
|
|
1146
1168
|
- A read-only mode and an interaction lock.
|
|
1147
1169
|
- Plugins, custom modes and custom feature types.
|
|
1148
1170
|
|
|
1171
|
+
[Unreleased]: https://github.com/sakuzu/maplibre-gl-draw/compare/v2.1.1...HEAD
|
|
1172
|
+
[2.1.1]: https://github.com/sakuzu/maplibre-gl-draw/compare/v2.1.0...v2.1.1
|
|
1173
|
+
[2.1.0]: https://github.com/sakuzu/maplibre-gl-draw/compare/v2.0.0...v2.1.0
|
|
1149
1174
|
[2.0.0]: https://github.com/sakuzu/maplibre-gl-draw/compare/v1.0.0...v2.0.0
|
|
1150
1175
|
[1.0.0]: https://github.com/sakuzu/maplibre-gl-draw/releases/tag/v1.0.0
|
package/dist/dataset/retained.js
CHANGED
|
@@ -324,8 +324,8 @@ export function isEmptyChunkBatches(batches) {
|
|
|
324
324
|
* @param isExternallyRenderedPoint Predicate that returns whether a point is drawn by an external
|
|
325
325
|
* renderer (every point is pushed when omitted). A point for which it is true goes neither onto
|
|
326
326
|
* a batch nor into fallback
|
|
327
|
-
* @param skipDrapedFills Do not push the
|
|
328
|
-
* drape draws them as ground pixels)
|
|
327
|
+
* @param skipDrapedFills Do not push the polygons and lines, solid or dashed (a frame where the
|
|
328
|
+
* analytic drape draws them as ground pixels)
|
|
329
329
|
* @returns The batches that were built. When the GPU resources could not be created (the shaders
|
|
330
330
|
* are not initialized) null is returned and the caller rebuilds them on the next frame
|
|
331
331
|
*
|
|
@@ -739,7 +739,7 @@ export function isDrapedGeometry(feature) {
|
|
|
739
739
|
* @internal
|
|
740
740
|
*/
|
|
741
741
|
export function collectFeature(feature, draft, styles, isExternallyRenderedPoint, skipDrapedFills = false) {
|
|
742
|
-
const
|
|
742
|
+
const skipDraped = skipDrapedFills && isDrapedGeometry(feature);
|
|
743
743
|
switch (feature.type) {
|
|
744
744
|
case 'Point':
|
|
745
745
|
collectPoint(feature, coordinatesOf(feature), draft, styles, isExternallyRenderedPoint);
|
|
@@ -753,20 +753,20 @@ export function collectFeature(feature, draft, styles, isExternallyRenderedPoint
|
|
|
753
753
|
return;
|
|
754
754
|
case 'LineString':
|
|
755
755
|
case 'Freehand':
|
|
756
|
-
collectLine(feature, coordinatesOf(feature), draft, styles,
|
|
756
|
+
collectLine(feature, coordinatesOf(feature), draft, styles, skipDraped);
|
|
757
757
|
return;
|
|
758
758
|
case 'MultiLineString':
|
|
759
759
|
for (const coords of coordinatesOf(feature)) {
|
|
760
|
-
collectLine(feature, coords, draft, styles,
|
|
760
|
+
collectLine(feature, coords, draft, styles, skipDraped);
|
|
761
761
|
}
|
|
762
762
|
return;
|
|
763
763
|
case 'Polygon':
|
|
764
|
-
collectPolygon(feature, coordinatesOf(feature), draft, styles, 0,
|
|
764
|
+
collectPolygon(feature, coordinatesOf(feature), draft, styles, 0, skipDraped);
|
|
765
765
|
return;
|
|
766
766
|
case 'MultiPolygon': {
|
|
767
767
|
const parts = coordinatesOf(feature);
|
|
768
768
|
for (let i = 0; i < parts.length; i++) {
|
|
769
|
-
collectPolygon(feature, parts[i], draft, styles, i,
|
|
769
|
+
collectPolygon(feature, parts[i], draft, styles, i, skipDraped);
|
|
770
770
|
}
|
|
771
771
|
return;
|
|
772
772
|
}
|
|
@@ -827,18 +827,19 @@ function collectPoint(feature, coord, draft, styles, isExternallyRenderedPoint)
|
|
|
827
827
|
* Pushes a line (a dashed line is sent to immediate mode because its CPU-side splitting depends
|
|
828
828
|
* on the zoom)
|
|
829
829
|
*/
|
|
830
|
-
function collectLine(feature, coords, draft, styles,
|
|
830
|
+
function collectLine(feature, coords, draft, styles, skipDraped = false) {
|
|
831
831
|
if (coords.length < 2)
|
|
832
832
|
return;
|
|
833
833
|
const strokeStyle = styles.getLineStringStrokeStyle(feature);
|
|
834
|
+
// A line, solid or dashed, is drawn by the analytic drape as ground pixels (in this frame
|
|
835
|
+
// only)
|
|
836
|
+
if (skipDraped && strokeStyle.opacity > 0 && strokeStyle.width > 0)
|
|
837
|
+
return;
|
|
834
838
|
if (strokeStyle.lineStyle !== 'solid') {
|
|
835
839
|
// The path that goes to immediate mode is not multiplied by the factors of zoomScale
|
|
836
840
|
pushFallback(draft, feature);
|
|
837
841
|
return;
|
|
838
842
|
}
|
|
839
|
-
// A solid line is drawn by the analytic drape as ground pixels (in this frame only)
|
|
840
|
-
if (skipSolid && strokeStyle.opacity > 0 && strokeStyle.width > 0)
|
|
841
|
-
return;
|
|
842
843
|
const createdZoom = getCreatedZoom(feature);
|
|
843
844
|
const target = createdZoom === undefined ? draft.fixedLines : draft.scaledLines;
|
|
844
845
|
const item = toLineBatchItem(coords, feature.id, strokeStyle, createdZoom ?? FIXED_WIDTH_ZOOM);
|
|
@@ -856,23 +857,23 @@ function collectLine(feature, coords, draft, styles, skipSolid = false) {
|
|
|
856
857
|
* A fill-only polygon is also pushed as an SDFPolygon without an outline (immediate mode uses
|
|
857
858
|
* PolygonBatchRenderer, but the appearance of the fill is the same).
|
|
858
859
|
*/
|
|
859
|
-
function collectPolygon(feature, rings, draft, styles, partIndex = 0,
|
|
860
|
+
function collectPolygon(feature, rings, draft, styles, partIndex = 0, skipDraped = false) {
|
|
860
861
|
const outerRing = rings[0];
|
|
861
862
|
if (!outerRing || outerRing.length < 3)
|
|
862
863
|
return;
|
|
863
864
|
const { fillColor, strokeStyle } = styles.getPolygonStyles(feature);
|
|
864
865
|
const hasStroke = strokeStyle.opacity > 0 && strokeStyle.width > 0;
|
|
866
|
+
if (!hasStroke && fillColor[3] <= 0)
|
|
867
|
+
return;
|
|
868
|
+
// The fill and the outline, solid or dashed, are drawn by the analytic drape as ground pixels
|
|
869
|
+
// (in this frame only)
|
|
870
|
+
if (skipDraped)
|
|
871
|
+
return;
|
|
865
872
|
if (hasStroke && strokeStyle.lineStyle !== 'solid') {
|
|
866
873
|
// The path that goes to immediate mode is not multiplied by the factors of zoomScale
|
|
867
874
|
pushFallback(draft, feature);
|
|
868
875
|
return;
|
|
869
876
|
}
|
|
870
|
-
if (!hasStroke && fillColor[3] <= 0)
|
|
871
|
-
return;
|
|
872
|
-
// The solid fill and outline are drawn by the analytic drape as ground pixels (in this frame
|
|
873
|
-
// only)
|
|
874
|
-
if (skipSolid)
|
|
875
|
-
return;
|
|
876
877
|
const createdZoom = getCreatedZoom(feature);
|
|
877
878
|
const target = createdZoom === undefined ? draft.fixedPolygons : draft.scaledPolygons;
|
|
878
879
|
// A fixed line width is declared by the negative convention (the same reason as collectLine).
|
|
@@ -6,8 +6,8 @@ import type { RetainedStyleResolver } from '../view/renderers/retained.js';
|
|
|
6
6
|
* It is paired with `collectDataset` in `view/terrain/drape/pass.ts`. Changing only one of
|
|
7
7
|
* them would show the selection highlight twice or in neither of them.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Dashed lines and dashed outlines go onto the drape like solid ones (the drape lays the pattern
|
|
10
|
+
* out along the path). For what does not go onto it, the immediate-mode path draws the highlight
|
|
11
|
+
* as before.
|
|
12
12
|
*/
|
|
13
13
|
export declare function isHandedToDrape(feature: Feature, styles: RetainedStyleResolver): boolean;
|
|
@@ -150,24 +150,20 @@ export function sameFeatureIds(a, b) {
|
|
|
150
150
|
* It is paired with `collectDataset` in `view/terrain/drape/pass.ts`. Changing only one of
|
|
151
151
|
* them would show the selection highlight twice or in neither of them.
|
|
152
152
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
153
|
+
* Dashed lines and dashed outlines go onto the drape like solid ones (the drape lays the pattern
|
|
154
|
+
* out along the path). For what does not go onto it, the immediate-mode path draws the highlight
|
|
155
|
+
* as before.
|
|
156
156
|
*/
|
|
157
157
|
export function isHandedToDrape(feature, styles) {
|
|
158
158
|
if (feature.type === 'Polygon' || feature.type === 'MultiPolygon') {
|
|
159
159
|
const { fillColor, strokeStyle } = styles.getPolygonStyles(feature);
|
|
160
160
|
const hasStroke = strokeStyle.opacity > 0 && strokeStyle.width > 0;
|
|
161
|
-
if (hasStroke && strokeStyle.lineStyle !== 'solid')
|
|
162
|
-
return false;
|
|
163
161
|
return hasStroke || fillColor[3] > 0;
|
|
164
162
|
}
|
|
165
163
|
if (feature.type === 'LineString' ||
|
|
166
164
|
feature.type === 'MultiLineString' ||
|
|
167
165
|
feature.type === 'Freehand') {
|
|
168
166
|
const strokeStyle = styles.getLineStringStrokeStyle(feature);
|
|
169
|
-
if (strokeStyle.lineStyle !== 'solid')
|
|
170
|
-
return false;
|
|
171
167
|
return strokeStyle.opacity > 0 && strokeStyle.width > 0;
|
|
172
168
|
}
|
|
173
169
|
return false;
|
|
@@ -329,7 +329,8 @@ export class TableSource {
|
|
|
329
329
|
}
|
|
330
330
|
collector(rows, context) {
|
|
331
331
|
const table = this.table;
|
|
332
|
-
// Whether the
|
|
332
|
+
// Whether the lines and polygons (solid or dashed) of each child are left to the analytic
|
|
333
|
+
// drape
|
|
333
334
|
const skipDraped = table.children.map((column) => context.skipDrapedFills === true && isDraped(column.type));
|
|
334
335
|
const createdZoom = table.columns[CREATED_ZOOM_COLUMN];
|
|
335
336
|
let next = 0;
|
|
@@ -497,18 +498,19 @@ class RowCollector {
|
|
|
497
498
|
* A line over the coordinates `[start, end)` of the geometry column of the row (a dashed line
|
|
498
499
|
* goes to immediate mode)
|
|
499
500
|
*/
|
|
500
|
-
line(row, column, start, end, createdZoom,
|
|
501
|
+
line(row, column, start, end, createdZoom, skipDraped) {
|
|
501
502
|
if (end - start < 2)
|
|
502
503
|
return;
|
|
503
504
|
const slot = this.slots.line(column.type, this.slots.ruleColorOf(row));
|
|
504
505
|
const stroke = slot.stroke;
|
|
506
|
+
// A line, solid or dashed, is drawn by the analytic drape as ground pixels (in this frame
|
|
507
|
+
// only)
|
|
508
|
+
if (skipDraped && stroke.opacity > 0 && stroke.width > 0)
|
|
509
|
+
return;
|
|
505
510
|
if (stroke.lineStyle !== 'solid') {
|
|
506
511
|
this.fallback(row);
|
|
507
512
|
return;
|
|
508
513
|
}
|
|
509
|
-
// A solid line is drawn by the analytic drape as ground pixels (in this frame only)
|
|
510
|
-
if (skipSolid && stroke.opacity > 0 && stroke.width > 0)
|
|
511
|
-
return;
|
|
512
514
|
const draft = this.draft;
|
|
513
515
|
const fixed = createdZoom === undefined;
|
|
514
516
|
let builder = fixed ? draft.packedFixedLines : draft.packedScaledLines;
|
|
@@ -528,21 +530,21 @@ class RowCollector {
|
|
|
528
530
|
*
|
|
529
531
|
* @param ringOffsets The offsets from a ring to its coordinates
|
|
530
532
|
*/
|
|
531
|
-
polygon(row, column, ringStart, ringEnd, ringOffsets, partIndex, createdZoom,
|
|
533
|
+
polygon(row, column, ringStart, ringEnd, ringOffsets, partIndex, createdZoom, skipDraped) {
|
|
532
534
|
if (ringEnd <= ringStart)
|
|
533
535
|
return;
|
|
534
536
|
if (ringOffsets[ringStart + 1] - ringOffsets[ringStart] < 3)
|
|
535
537
|
return;
|
|
536
538
|
const slot = this.slots.polygon(column.type, this.slots.ruleColorOf(row));
|
|
539
|
+
if (!slot.hasStroke && slot.fillColor[3] <= 0)
|
|
540
|
+
return;
|
|
541
|
+
// The fill and the outline, solid or dashed, are drawn by the analytic drape as ground pixels
|
|
542
|
+
if (skipDraped)
|
|
543
|
+
return;
|
|
537
544
|
if (slot.hasStroke && slot.strokeStyle.lineStyle !== 'solid') {
|
|
538
545
|
this.fallback(row);
|
|
539
546
|
return;
|
|
540
547
|
}
|
|
541
|
-
if (!slot.hasStroke && slot.fillColor[3] <= 0)
|
|
542
|
-
return;
|
|
543
|
-
// The solid fill and outline are drawn by the analytic drape as ground pixels
|
|
544
|
-
if (skipSolid)
|
|
545
|
-
return;
|
|
546
548
|
const rings = new Array(ringEnd - ringStart);
|
|
547
549
|
for (let r = ringStart; r < ringEnd; r++) {
|
|
548
550
|
rings[r - ringStart] = column.positions(ringOffsets[r], ringOffsets[r + 1]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 SAKAIDA Atsushi
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
import { resolvePixelRatio } from '../../shared/utils/pixel-ratio.js';
|
|
3
|
+
import { resolveContentPixelRatio, resolvePixelRatio } from '../../shared/utils/pixel-ratio.js';
|
|
4
4
|
import { updateGlobeSubdivision } from '../globe-subdivision.js';
|
|
5
5
|
import { beginRenderFrame } from '../shaders/frame.js';
|
|
6
6
|
import { installAnchorProjector, uninstallAnchorProjector } from '../terrain/anchor.js';
|
|
@@ -350,6 +350,7 @@ export function createCustomLayer(deps) {
|
|
|
350
350
|
zoom,
|
|
351
351
|
rawZoom,
|
|
352
352
|
dpr,
|
|
353
|
+
contentDpr: resolveContentPixelRatio(pixelRatio),
|
|
353
354
|
terrainState,
|
|
354
355
|
retainedAvailable: engine.storeRetainedCache !== null,
|
|
355
356
|
overlayRenderers: dynamicOverlayRenderers,
|
|
@@ -119,7 +119,10 @@ export interface DrapeFrameInput {
|
|
|
119
119
|
resolveStyles: () => RetainedStyleResolver;
|
|
120
120
|
/** The terrain of the map (null when the terrain state of the frame is inactive) */
|
|
121
121
|
mapTerrain: MapTerrain;
|
|
122
|
+
/** The rendering pixel ratio, for widths fixed in screen pixels (the rendering scale included) */
|
|
122
123
|
dpr: number;
|
|
124
|
+
/** The rendering pixel ratio for widths that follow the zoom (the rendering scale not applied) */
|
|
125
|
+
contentDpr: number;
|
|
123
126
|
/** The style zoom (what the rendering reads) */
|
|
124
127
|
zoom: number;
|
|
125
128
|
/** The real zoom of the camera (the band is decided with it) */
|
|
@@ -281,7 +281,7 @@ export class DrapePlanner {
|
|
|
281
281
|
*/
|
|
282
282
|
planFrame(input) {
|
|
283
283
|
const { terrain } = this.deps;
|
|
284
|
-
const { mapTerrain, dpr, zoom, rawZoom } = input;
|
|
284
|
+
const { mapTerrain, dpr, contentDpr, zoom, rawZoom } = input;
|
|
285
285
|
let drapeUsable = false;
|
|
286
286
|
this.pendingWork = false;
|
|
287
287
|
// The analytic drape is used even in a wide band when building the index is light. Cutting
|
|
@@ -297,10 +297,10 @@ export class DrapePlanner {
|
|
|
297
297
|
// cost has to be known first). The collected result is folded behind a key, so there is no
|
|
298
298
|
// cost as long as the contents do not change
|
|
299
299
|
if (mapTerrain !== null)
|
|
300
|
-
this.collect(input.resolveStyles, dpr, zoom);
|
|
300
|
+
this.collect(input.resolveStyles, dpr, contentDpr, zoom);
|
|
301
301
|
const analyticBand = rawZoom > TERRAIN_ANALYTIC_MIN_ZOOM || this.lightEnough;
|
|
302
302
|
if (mapTerrain !== null && analyticBand) {
|
|
303
|
-
drapeUsable = this.prepare(input.gl, mapTerrain, input.resolveStyles, dpr, zoom);
|
|
303
|
+
drapeUsable = this.prepare(input.gl, mapTerrain, input.resolveStyles, dpr, contentDpr, zoom);
|
|
304
304
|
}
|
|
305
305
|
else if (mapTerrain !== null) {
|
|
306
306
|
// A wide band is handled by the vertex displacement path (no index is built either)
|
|
@@ -447,6 +447,7 @@ export class DrapePlanner {
|
|
|
447
447
|
cellOffset: item.plan.cellOffset,
|
|
448
448
|
grid: item.plan.grid,
|
|
449
449
|
groundMeters: item.plan.groundMeters,
|
|
450
|
+
tileZ: item.plan.tile.z,
|
|
450
451
|
edges,
|
|
451
452
|
},
|
|
452
453
|
projection: item.projection,
|
|
@@ -500,12 +501,13 @@ export class DrapePlanner {
|
|
|
500
501
|
* "polygons are not drawn at low zooms"). The collected result is folded behind a key, so from
|
|
501
502
|
* the second time on nothing happens as long as the contents do not change.
|
|
502
503
|
*/
|
|
503
|
-
collect(resolveStyles, dpr, zoom) {
|
|
504
|
+
collect(resolveStyles, dpr, contentDpr, zoom) {
|
|
504
505
|
const { store, datasets: manager } = this.deps;
|
|
505
506
|
const datasets = manager?.listInternal() ?? [];
|
|
506
507
|
const key = [
|
|
507
508
|
this.revision,
|
|
508
509
|
dpr,
|
|
510
|
+
contentDpr,
|
|
509
511
|
marginZoom(zoom),
|
|
510
512
|
store.getLayerOrder().join('/'),
|
|
511
513
|
datasets.map((c) => `${c.id}:${c.drapeRevision}:${c.visible ? 1 : 0}:${c.order}`).join(','),
|
|
@@ -513,7 +515,7 @@ export class DrapePlanner {
|
|
|
513
515
|
].join('|');
|
|
514
516
|
let collected = this.collected;
|
|
515
517
|
if (key !== this.collectKey || !collected) {
|
|
516
|
-
collected = collectDrapeElements(store, resolveStyles(), dpr, datasets);
|
|
518
|
+
collected = collectDrapeElements(store, resolveStyles(), dpr, contentDpr, datasets);
|
|
517
519
|
this.collectKey = key;
|
|
518
520
|
this.collected = collected;
|
|
519
521
|
this.lightEnough = collected.edgeCount <= DRAPE_WIDE_EDGE_BUDGET;
|
|
@@ -529,14 +531,14 @@ export class DrapePlanner {
|
|
|
529
531
|
* not collected again just because the view moved (collecting them again would mean rebuilding
|
|
530
532
|
* the whole index).
|
|
531
533
|
*/
|
|
532
|
-
prepare(gl, terrain, resolveStyles, dpr, zoom) {
|
|
534
|
+
prepare(gl, terrain, resolveStyles, dpr, contentDpr, zoom) {
|
|
533
535
|
const { map, datasets: manager } = this.deps;
|
|
534
536
|
const context = this.deps.terrain;
|
|
535
537
|
const tiles = getRenderableTerrainTiles(terrain);
|
|
536
538
|
if (tiles.length === 0)
|
|
537
539
|
return false;
|
|
538
540
|
const datasets = manager?.listInternal() ?? [];
|
|
539
|
-
const collected = this.collect(resolveStyles, dpr, zoom);
|
|
541
|
+
const collected = this.collect(resolveStyles, dpr, contentDpr, zoom);
|
|
540
542
|
// The factors per source (which follow the zoom) are rewritten every frame, because baking
|
|
541
543
|
// them into the elements would mean rebuilding the index on every zoom movement
|
|
542
544
|
const drapeSourceFactors = this.sourceFactors;
|
|
@@ -107,9 +107,8 @@ function drawDrapeSegment(deps, f, index) {
|
|
|
107
107
|
drawSegment(paintCursor, to, last);
|
|
108
108
|
}
|
|
109
109
|
f.restoreBlendState();
|
|
110
|
-
// Back to the depth state of the layer rendering. What the drape could not paint (
|
|
111
|
-
//
|
|
112
|
-
// extensions) is drawn next, by the vertex displacement path, and the terrain must hide it
|
|
110
|
+
// Back to the depth state of the layer rendering. What the drape could not paint (the
|
|
111
|
+
// geometry being drawn, the features of extensions) is drawn next, by the vertex displacement path, and the terrain must hide it
|
|
113
112
|
// as it does in a frame without the drape. Symbols switch the depth test off themselves.
|
|
114
113
|
applySegmentDepthState(gl, deps.terrainContext, f);
|
|
115
114
|
}
|
|
@@ -111,7 +111,10 @@ export interface FrameBuildInput {
|
|
|
111
111
|
zoom: number;
|
|
112
112
|
/** The real zoom of the camera */
|
|
113
113
|
rawZoom: number;
|
|
114
|
+
/** The rendering pixel ratio, for sizes fixed in screen pixels (the rendering scale included) */
|
|
114
115
|
dpr: number;
|
|
116
|
+
/** The rendering pixel ratio for sizes that follow the zoom (the rendering scale not applied) */
|
|
117
|
+
contentDpr: number;
|
|
115
118
|
/** The terrain state of the frame (already set on the terrain context) */
|
|
116
119
|
terrainState: TerrainRenderState;
|
|
117
120
|
/** Whether the retained batches of the Store rendering exist */
|
|
@@ -139,7 +139,7 @@ function sameCameraPosition(a, b) {
|
|
|
139
139
|
*/
|
|
140
140
|
export function buildFrameState(input) {
|
|
141
141
|
const { gl, renderers: r, map, store, terrainContext, drape, renderOptions } = input;
|
|
142
|
-
const { zoom, rawZoom, dpr, terrainState } = input;
|
|
142
|
+
const { zoom, rawZoom, dpr, contentDpr, terrainState } = input;
|
|
143
143
|
const { shaderData, defaultProjectionData } = renderOptions;
|
|
144
144
|
const mapTerrain = terrainState.active ? getMapTerrain(map) : null;
|
|
145
145
|
// Shader initialization
|
|
@@ -193,6 +193,7 @@ export function buildFrameState(input) {
|
|
|
193
193
|
resolveStyles: () => r.batchManager.getRetainedRenderers()?.styles ?? r.featureDrawer,
|
|
194
194
|
mapTerrain,
|
|
195
195
|
dpr,
|
|
196
|
+
contentDpr,
|
|
196
197
|
zoom,
|
|
197
198
|
rawZoom,
|
|
198
199
|
});
|
|
@@ -34,9 +34,8 @@ export declare function applySegmentGlState(gl: WebGL2RenderingContext, terrainC
|
|
|
34
34
|
* `applySegmentGlState` sets it at the start of a slot, and the drape of the segment, which
|
|
35
35
|
* changes the depth function and the offset, hands it back with this before the layers are
|
|
36
36
|
* drawn. The layers are therefore drawn in one depth state whether or not the drape was used.
|
|
37
|
-
* What the drape cannot paint (
|
|
38
|
-
* drawn
|
|
39
|
-
* drawn after it by the vertex displacement path and must be hidden by the terrain in the same
|
|
37
|
+
* What the drape cannot paint (the geometry being drawn, the features of extensions, the datasets
|
|
38
|
+
* not handed over to it) is drawn after it by the vertex displacement path and must be hidden by the terrain in the same
|
|
40
39
|
* way. Symbols switch the depth test off themselves (`drawBillboardsWithoutDepth`).
|
|
41
40
|
*/
|
|
42
41
|
export declare function applySegmentDepthState(gl: WebGL2RenderingContext, terrainContext: TerrainContext, f: Pick<SegmentGlFrame, 'terrainState' | 'wideFallback'>): void;
|
|
@@ -33,9 +33,8 @@ export function applySegmentGlState(gl, terrainContext, f) {
|
|
|
33
33
|
* `applySegmentGlState` sets it at the start of a slot, and the drape of the segment, which
|
|
34
34
|
* changes the depth function and the offset, hands it back with this before the layers are
|
|
35
35
|
* drawn. The layers are therefore drawn in one depth state whether or not the drape was used.
|
|
36
|
-
* What the drape cannot paint (
|
|
37
|
-
* drawn
|
|
38
|
-
* drawn after it by the vertex displacement path and must be hidden by the terrain in the same
|
|
36
|
+
* What the drape cannot paint (the geometry being drawn, the features of extensions, the datasets
|
|
37
|
+
* not handed over to it) is drawn after it by the vertex displacement path and must be hidden by the terrain in the same
|
|
39
38
|
* way. Symbols switch the depth test off themselves (`drawBillboardsWithoutDepth`).
|
|
40
39
|
*/
|
|
41
40
|
export function applySegmentDepthState(gl, terrainContext, f) {
|
|
@@ -18,6 +18,33 @@ export interface DashSegment {
|
|
|
18
18
|
* @returns The dashes, in order along the line. An empty array for fewer than 2 positions
|
|
19
19
|
*/
|
|
20
20
|
export declare function splitIntoDashes(coords: Coordinate[], dashLength: number, gapLength: number, zoom: number): DashSegment[];
|
|
21
|
+
/**
|
|
22
|
+
* The coefficients of a dash pattern
|
|
23
|
+
*
|
|
24
|
+
* The dash is `max(a, b * w)` and the gap `max(w + 1, c * w)` for a line `w` pixels wide (the
|
|
25
|
+
* smallest gap, `w + 1`, keeps the round caps of two dashes apart). This table is the one
|
|
26
|
+
* source of the patterns: the subdividing path computes them here on the CPU, and the analytic
|
|
27
|
+
* drape hands the same coefficients to its shader, which evaluates the same two formulas.
|
|
28
|
+
*/
|
|
29
|
+
export interface DashCoefficients {
|
|
30
|
+
/** The shortest dash (px) */
|
|
31
|
+
readonly a: number;
|
|
32
|
+
/** The dash per pixel of width */
|
|
33
|
+
readonly b: number;
|
|
34
|
+
/** The gap per pixel of width */
|
|
35
|
+
readonly c: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The coefficients of each line style that is not solid
|
|
39
|
+
*
|
|
40
|
+
* Dashed: dashes 4 times the width (12 px at least) and gaps twice the width. Dotted: short,
|
|
41
|
+
* nearly round dots and gaps about the width.
|
|
42
|
+
*/
|
|
43
|
+
export declare const DASH_COEFFICIENTS: Readonly<Record<'dashed' | 'dotted', DashCoefficients>>;
|
|
44
|
+
/**
|
|
45
|
+
* The coefficients of a line style, or null for a solid line
|
|
46
|
+
*/
|
|
47
|
+
export declare function dashCoefficientsOf(lineStyle: 'solid' | 'dashed' | 'dotted'): DashCoefficients | null;
|
|
21
48
|
/**
|
|
22
49
|
* Returns the dash and gap lengths in px that suit a line style at a line width.
|
|
23
50
|
*
|
|
@@ -126,6 +126,22 @@ export function splitIntoDashes(coords, dashLength, gapLength, zoom) {
|
|
|
126
126
|
}
|
|
127
127
|
return segments;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* The coefficients of each line style that is not solid
|
|
131
|
+
*
|
|
132
|
+
* Dashed: dashes 4 times the width (12 px at least) and gaps twice the width. Dotted: short,
|
|
133
|
+
* nearly round dots and gaps about the width.
|
|
134
|
+
*/
|
|
135
|
+
export const DASH_COEFFICIENTS = {
|
|
136
|
+
dashed: { a: 12, b: 4, c: 2 },
|
|
137
|
+
dotted: { a: 1, b: 0.5, c: 1.2 },
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* The coefficients of a line style, or null for a solid line
|
|
141
|
+
*/
|
|
142
|
+
export function dashCoefficientsOf(lineStyle) {
|
|
143
|
+
return lineStyle === 'dashed' || lineStyle === 'dotted' ? DASH_COEFFICIENTS[lineStyle] : null;
|
|
144
|
+
}
|
|
129
145
|
/**
|
|
130
146
|
* Returns the dash and gap lengths in px that suit a line style at a line width.
|
|
131
147
|
*
|
|
@@ -137,16 +153,11 @@ export function splitIntoDashes(coords, dashLength, gapLength, zoom) {
|
|
|
137
153
|
* @returns [dashLength, gapLength], or null (for a solid line)
|
|
138
154
|
*/
|
|
139
155
|
export function getStrokeDashPattern(lineStyle, strokeWidth = 2) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Dotted: short dots (nearly round), short gaps (about the line width)
|
|
148
|
-
return [Math.max(1, strokeWidth * 0.5), Math.max(minGap, strokeWidth * 1.2)];
|
|
149
|
-
default:
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
156
|
+
const coefficients = dashCoefficientsOf(lineStyle);
|
|
157
|
+
if (!coefficients)
|
|
158
|
+
return null;
|
|
159
|
+
return [
|
|
160
|
+
Math.max(coefficients.a, coefficients.b * strokeWidth),
|
|
161
|
+
Math.max(strokeWidth + 1, coefficients.c * strokeWidth),
|
|
162
|
+
];
|
|
152
163
|
}
|
|
@@ -19,16 +19,23 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { DrapeElement, DrapeQuadBreak, DrapeTile, TileBins } from './binning.js';
|
|
21
21
|
/** The number of style texels per element */
|
|
22
|
-
export declare const DRAPE_STYLE_TEXELS =
|
|
22
|
+
export declare const DRAPE_STYLE_TEXELS = 5;
|
|
23
23
|
/**
|
|
24
24
|
* The index of the texel the selection highlight uses within the style table
|
|
25
25
|
*
|
|
26
26
|
* 0 = fill, 1 = stroke, 2 = specifications (width, kind, source, reference zoom), 3 =
|
|
27
|
-
* selection. The selection alone is put in a separate texel so that the index (the binning)
|
|
27
|
+
* selection, 4 = the dash pattern (`DRAPE_DASH_TEXEL`). The selection alone is put in a separate texel so that the index (the binning)
|
|
28
28
|
* need not be touched at all when the selection changes. Mixing it into the same texel as the
|
|
29
29
|
* color or the width would mean rebuilding the other values too on every rewrite.
|
|
30
30
|
*/
|
|
31
31
|
export declare const DRAPE_SELECTION_TEXEL = 3;
|
|
32
|
+
/**
|
|
33
|
+
* The index of the texel of the dash pattern within the style table
|
|
34
|
+
*
|
|
35
|
+
* (a, b, c, pixel ratio): the coefficients of the pattern (`DrapeDash` in `binning.ts`) and the
|
|
36
|
+
* physical pixels of the width per CSS pixel. A pixel ratio of 0 marks a solid outline.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DRAPE_DASH_TEXEL = 4;
|
|
32
39
|
/** The upper bound on the number of edge texels (16 bytes per texel, so 1M is 16MB) */
|
|
33
40
|
export declare const DRAPE_MAX_EDGE_TEXELS = 1048576;
|
|
34
41
|
/** The key of a tile */
|
|
@@ -57,6 +64,8 @@ export declare class DrapePackBuilder {
|
|
|
57
64
|
* transferred) */
|
|
58
65
|
readonly id: number;
|
|
59
66
|
edges: Float32Array<ArrayBuffer>;
|
|
67
|
+
/** Where each edge starts along its path (one number per edge, in step with `edges`) */
|
|
68
|
+
edgeStarts: Float32Array<ArrayBuffer>;
|
|
60
69
|
edgeCount: number;
|
|
61
70
|
runs: Float32Array<ArrayBuffer>;
|
|
62
71
|
runCount: number;
|
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
import { binTile, drapeCellWorkBudget } from './binning.js';
|
|
4
4
|
/** The number of style texels per element */
|
|
5
|
-
export const DRAPE_STYLE_TEXELS =
|
|
5
|
+
export const DRAPE_STYLE_TEXELS = 5;
|
|
6
6
|
/**
|
|
7
7
|
* The index of the texel the selection highlight uses within the style table
|
|
8
8
|
*
|
|
9
9
|
* 0 = fill, 1 = stroke, 2 = specifications (width, kind, source, reference zoom), 3 =
|
|
10
|
-
* selection. The selection alone is put in a separate texel so that the index (the binning)
|
|
10
|
+
* selection, 4 = the dash pattern (`DRAPE_DASH_TEXEL`). The selection alone is put in a separate texel so that the index (the binning)
|
|
11
11
|
* need not be touched at all when the selection changes. Mixing it into the same texel as the
|
|
12
12
|
* color or the width would mean rebuilding the other values too on every rewrite.
|
|
13
13
|
*/
|
|
14
14
|
export const DRAPE_SELECTION_TEXEL = 3;
|
|
15
|
+
/**
|
|
16
|
+
* The index of the texel of the dash pattern within the style table
|
|
17
|
+
*
|
|
18
|
+
* (a, b, c, pixel ratio): the coefficients of the pattern (`DrapeDash` in `binning.ts`) and the
|
|
19
|
+
* physical pixels of the width per CSS pixel. A pixel ratio of 0 marks a solid outline.
|
|
20
|
+
*/
|
|
21
|
+
export const DRAPE_DASH_TEXEL = 4;
|
|
15
22
|
/** The upper bound on the tiles retained (beyond it, those outside the view are dropped and
|
|
16
23
|
* the arrays are repacked) */
|
|
17
24
|
const MAX_CACHED_TILES = 96;
|
|
@@ -43,6 +50,13 @@ export class DrapePackBuilder {
|
|
|
43
50
|
writable: true,
|
|
44
51
|
value: new Float32Array(4 * 4096)
|
|
45
52
|
});
|
|
53
|
+
/** Where each edge starts along its path (one number per edge, in step with `edges`) */
|
|
54
|
+
Object.defineProperty(this, "edgeStarts", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
configurable: true,
|
|
57
|
+
writable: true,
|
|
58
|
+
value: new Float32Array(4096)
|
|
59
|
+
});
|
|
46
60
|
Object.defineProperty(this, "edgeCount", {
|
|
47
61
|
enumerable: true,
|
|
48
62
|
configurable: true,
|
|
@@ -125,6 +139,14 @@ export class DrapePackBuilder {
|
|
|
125
139
|
styles[at + 9] = element.kind;
|
|
126
140
|
styles[at + 10] = element.source;
|
|
127
141
|
styles[at + 11] = element.widthZoom;
|
|
142
|
+
const dash = element.dash;
|
|
143
|
+
if (dash) {
|
|
144
|
+
const dashAt = at + DRAPE_DASH_TEXEL * 4;
|
|
145
|
+
styles[dashAt] = dash.a;
|
|
146
|
+
styles[dashAt + 1] = dash.b;
|
|
147
|
+
styles[dashAt + 2] = dash.c;
|
|
148
|
+
styles[dashAt + 3] = dash.pixelRatio;
|
|
149
|
+
}
|
|
128
150
|
}
|
|
129
151
|
this.styles = styles;
|
|
130
152
|
this.elementCount = elements.length;
|
|
@@ -167,6 +189,8 @@ export class DrapePackBuilder {
|
|
|
167
189
|
const edgeTexels = bins.edges.length / 4;
|
|
168
190
|
this.edges = ensureCapacity(this.edges, (this.edgeCount + edgeTexels) * 4);
|
|
169
191
|
this.edges.set(bins.edges, edgeBase * 4);
|
|
192
|
+
this.edgeStarts = ensureCapacity(this.edgeStarts, this.edgeCount + edgeTexels);
|
|
193
|
+
this.edgeStarts.set(bins.edgeStarts, edgeBase);
|
|
170
194
|
this.edgeCount += edgeTexels;
|
|
171
195
|
const runTexels = bins.runs.length / 4;
|
|
172
196
|
this.runs = ensureCapacity(this.runs, (this.runCount + runTexels) * 4);
|
|
@@ -109,6 +109,26 @@ export declare function drapeCellWorkBudget(tileCount: number): number;
|
|
|
109
109
|
export declare const DRAPE_LOD_TRIGGER = 16000;
|
|
110
110
|
/** The color (0..1, with the opacity already folded in) */
|
|
111
111
|
export type DrapeColor = readonly [number, number, number, number];
|
|
112
|
+
/**
|
|
113
|
+
* The dash pattern of an outline that is not solid
|
|
114
|
+
*
|
|
115
|
+
* The coefficients are those of `DASH_COEFFICIENTS` in `view/renderers/line/dash.ts`: the shader
|
|
116
|
+
* works the dash and the gap out of the width the line has in the frame, with the same formulas
|
|
117
|
+
* as the subdividing path.
|
|
118
|
+
*/
|
|
119
|
+
export interface DrapeDash {
|
|
120
|
+
/** The shortest dash (CSS px) */
|
|
121
|
+
readonly a: number;
|
|
122
|
+
/** The dash per pixel of width */
|
|
123
|
+
readonly b: number;
|
|
124
|
+
/** The gap per pixel of width */
|
|
125
|
+
readonly c: number;
|
|
126
|
+
/**
|
|
127
|
+
* The physical pixels of `strokeWidthPx` per CSS pixel (the pixel ratio the width was
|
|
128
|
+
* resolved with). The pattern is measured in CSS pixels, so the shader divides it back out
|
|
129
|
+
*/
|
|
130
|
+
readonly pixelRatio: number;
|
|
131
|
+
}
|
|
112
132
|
/**
|
|
113
133
|
* An element handed to the analytic drape (a feature of the Store and a feature of a
|
|
114
134
|
* dataset have the same shape)
|
|
@@ -131,6 +151,13 @@ export interface DrapeElement {
|
|
|
131
151
|
readonly stroke: DrapeColor;
|
|
132
152
|
/** The width of the outline (physical pixels). 0 means the outline is not drawn */
|
|
133
153
|
readonly strokeWidthPx: number;
|
|
154
|
+
/**
|
|
155
|
+
* The dash pattern of the outline (absent or null for a solid outline)
|
|
156
|
+
*
|
|
157
|
+
* Only an element with a pattern has the lengths along its paths worked out and binned next
|
|
158
|
+
* to its edges (`TileBins.edgeStarts`).
|
|
159
|
+
*/
|
|
160
|
+
readonly dash?: DrapeDash | null;
|
|
134
161
|
/**
|
|
135
162
|
* The origin of the coefficients (0 = the Store, 1 and above = datasets)
|
|
136
163
|
*
|
|
@@ -201,6 +228,11 @@ export interface TileBins {
|
|
|
201
228
|
readonly runs: Float32Array;
|
|
202
229
|
/** (x0, y0, x1, y1) per edge. In-tile 0..1 coordinates */
|
|
203
230
|
readonly edges: Float32Array;
|
|
231
|
+
/**
|
|
232
|
+
* Where each edge starts along its path, one per edge (Mercator units, the 0..1 world; not
|
|
233
|
+
* in-tile). 0 for an edge of an element without a dash pattern
|
|
234
|
+
*/
|
|
235
|
+
readonly edgeStarts: Float32Array;
|
|
204
236
|
/** Whether a limit was exceeded (if it was, the caller degrades) */
|
|
205
237
|
readonly overflow: boolean;
|
|
206
238
|
/** The number of cells truncated for exceeding the budget (a local degradation. Diagnostic) */
|