forked from maplibre/maplibre-gl-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcircle_bucket.ts
237 lines (196 loc) · 9.49 KB
/
circle_bucket.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import {CircleLayoutArray} from '../array_types.g';
import {members as layoutAttributes} from './circle_attributes';
import {SegmentVector} from '../segment';
import {ProgramConfigurationSet} from '../program_configuration';
import {TriangleIndexArray} from '../index_array_type';
import {loadGeometry} from '../load_geometry';
import {toEvaluationFeature} from '../evaluation_feature';
import {EXTENT} from '../extent';
import {register} from '../../util/web_worker_transfer';
import {EvaluationParameters} from '../../style/evaluation_parameters';
import type {CanonicalTileID} from '../../source/tile_id';
import type {
Bucket,
BucketParameters,
BucketFeature,
IndexedFeature,
PopulateParameters
} from '../bucket';
import type {CircleStyleLayer} from '../../style/style_layer/circle_style_layer';
import type {HeatmapStyleLayer} from '../../style/style_layer/heatmap_style_layer';
import type {Context} from '../../gl/context';
import type {IndexBuffer} from '../../gl/index_buffer';
import type {VertexBuffer} from '../../gl/vertex_buffer';
import type Point from '@mapbox/point-geometry';
import type {FeatureStates} from '../../source/source_state';
import type {ImagePosition} from '../../render/image_atlas';
import type {VectorTileLayer} from '@mapbox/vector-tile';
import {CircleGranularity} from '../../render/subdivision_granularity_settings';
const VERTEX_MIN_VALUE = -32768; // -(2^15)
// Extrude is in range 0..7, which will be mapped to -1..1 in the shader.
function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) {
// We pack circle position and extrude into range 0..65535, but vertices are stored as *signed* 16-bit integers, so we need to offset the number by 2^15.
layoutVertexArray.emplaceBack(
VERTEX_MIN_VALUE + (x * 8) + extrudeX,
VERTEX_MIN_VALUE + (y * 8) + extrudeY);
}
/**
* @internal
* Circles are represented by two triangles.
*
* Each corner has a pos that is the center of the circle and an extrusion
* vector that is where it points.
*/
export class CircleBucket<Layer extends CircleStyleLayer | HeatmapStyleLayer> implements Bucket {
index: number;
zoom: number;
overscaling: number;
layerIds: Array<string>;
layers: Array<Layer>;
stateDependentLayers: Array<Layer>;
stateDependentLayerIds: Array<string>;
layoutVertexArray: CircleLayoutArray;
layoutVertexBuffer: VertexBuffer;
indexArray: TriangleIndexArray;
indexBuffer: IndexBuffer;
hasPattern: boolean;
programConfigurations: ProgramConfigurationSet<Layer>;
segments: SegmentVector;
uploaded: boolean;
constructor(options: BucketParameters<Layer>) {
this.zoom = options.zoom;
this.overscaling = options.overscaling;
this.layers = options.layers;
this.layerIds = this.layers.map(layer => layer.id);
this.index = options.index;
this.hasPattern = false;
this.layoutVertexArray = new CircleLayoutArray();
this.indexArray = new TriangleIndexArray();
this.segments = new SegmentVector();
this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom);
this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);
}
populate(features: Array<IndexedFeature>, options: PopulateParameters, canonical: CanonicalTileID) {
const styleLayer = this.layers[0];
const bucketFeatures: BucketFeature[] = [];
let circleSortKey = null;
let sortFeaturesByKey = false;
// Heatmap circles are usually large (and map-pitch-aligned), tessellate them to allow curvature along the globe.
let subdivide = styleLayer.type === 'heatmap';
// Heatmap layers are handled in this bucket and have no evaluated properties, so we check our access
if (styleLayer.type === 'circle') {
const circleStyle = (styleLayer as CircleStyleLayer);
circleSortKey = circleStyle.layout.get('circle-sort-key');
sortFeaturesByKey = !circleSortKey.isConstant();
// Circles that are "printed" onto the map surface should be tessellated to follow the globe's curvature.
subdivide = subdivide || circleStyle.paint.get('circle-pitch-alignment') === 'map';
}
const granularity = subdivide ? options.subdivisionGranularity.circle : 1;
for (const {feature, id, index, sourceLayerIndex} of features) {
const needGeometry = this.layers[0]._featureFilter.needGeometry;
const evaluationFeature = toEvaluationFeature(feature, needGeometry);
if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue;
const sortKey = sortFeaturesByKey ?
circleSortKey.evaluate(evaluationFeature, {}, canonical) :
undefined;
const bucketFeature: BucketFeature = {
id,
properties: feature.properties,
type: feature.type,
sourceLayerIndex,
index,
geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature),
patterns: {},
sortKey
};
bucketFeatures.push(bucketFeature);
}
if (sortFeaturesByKey) {
bucketFeatures.sort((a, b) => a.sortKey - b.sortKey);
}
for (const bucketFeature of bucketFeatures) {
const {geometry, index, sourceLayerIndex} = bucketFeature;
const feature = features[index].feature;
this.addFeature(bucketFeature, geometry, index, canonical, granularity);
options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index);
}
}
update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) {
if (!this.stateDependentLayers.length) return;
this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions);
}
isEmpty() {
return this.layoutVertexArray.length === 0;
}
uploadPending() {
return !this.uploaded || this.programConfigurations.needsUpload;
}
upload(context: Context) {
if (!this.uploaded) {
this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray, layoutAttributes);
this.indexBuffer = context.createIndexBuffer(this.indexArray);
}
this.programConfigurations.upload(context);
this.uploaded = true;
}
destroy() {
if (!this.layoutVertexBuffer) return;
this.layoutVertexBuffer.destroy();
this.indexBuffer.destroy();
this.programConfigurations.destroy();
this.segments.destroy();
}
addFeature(feature: BucketFeature, geometry: Array<Array<Point>>, index: number, canonical: CanonicalTileID, granularity: CircleGranularity = 1) {
// Since we store the circle's center in each vertex, we only have 3 bits for actual vertex position in each axis.
// Thus the valid range of positions is 0..7.
// This gives us 4 possible granularity settings that are symmetrical.
// This array stores vertex positions that should by used by the tessellated quad.
let extrudes: Array<number>;
switch (granularity) {
case 1:
extrudes = [0, 7];
break;
case 3:
extrudes = [0, 2, 5, 7];
break;
case 5:
extrudes = [0, 1, 3, 4, 6, 7];
break;
case 7:
extrudes = [0, 1, 2, 3, 4, 5, 6, 7];
break;
default:
throw new Error(`Invalid circle bucket granularity: ${granularity}; valid values are 1, 3, 5, 7.`);
}
const verticesPerAxis = extrudes.length;
for (const ring of geometry) {
for (const point of ring) {
const vx = point.x;
const vy = point.y;
// Do not include points that are outside the tile boundaries.
if (vx < 0 || vx >= EXTENT || vy < 0 || vy >= EXTENT) {
continue;
}
const segment = this.segments.prepareSegment(verticesPerAxis * verticesPerAxis, this.layoutVertexArray, this.indexArray, feature.sortKey);
const index = segment.vertexLength;
for (let y = 0; y < verticesPerAxis; y++) {
for (let x = 0; x < verticesPerAxis; x++) {
addCircleVertex(this.layoutVertexArray, vx, vy, extrudes[x], extrudes[y]);
}
}
for (let y = 0; y < verticesPerAxis - 1; y++) {
for (let x = 0; x < verticesPerAxis - 1; x++) {
const lowerIndex = index + y * verticesPerAxis + x;
const upperIndex = index + (y + 1) * verticesPerAxis + x;
this.indexArray.emplaceBack(lowerIndex, upperIndex + 1, lowerIndex + 1);
this.indexArray.emplaceBack(lowerIndex, upperIndex, upperIndex + 1);
}
}
segment.vertexLength += verticesPerAxis * verticesPerAxis;
segment.primitiveLength += (verticesPerAxis - 1) * (verticesPerAxis - 1) * 2;
}
}
this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}, canonical);
}
}
register('CircleBucket', CircleBucket, {omit: ['layers']});