@mesh3d/cesium-vectortile-gl 0.1.0

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.
@@ -0,0 +1,24 @@
1
+ import { ILayerVisualizer } from "./visualizers/ILayerVisualizer"
2
+ import { IRenderLayer } from "./IRenderLayer"
3
+
4
+ /**
5
+ * @type {Record<string,typeof IRenderLayer>}
6
+ */
7
+ const RenderLayers = {}
8
+ /**
9
+ * @type {Record<string,typeof ILayerVisualizer>}
10
+ */
11
+ const LayerVisualizers = {}
12
+
13
+ /**
14
+ * 注册图层类型,设置渲染图层类和图层渲染器类(非必须)
15
+ * @param {"symbol" | "fill" | "line" | "circle" | "heatmap" | "fill-extrusion" | "raster" | "hillshade" | "color-relief" | "background"} type 图层类型
16
+ * @param {typeof IRenderLayer} renderLayerCls 渲染图层类,必须继承 IRenderLayer
17
+ * @param {typeof ILayerVisualizer} [layerVisualizerCls] 图层渲染器类,必须继承 ILayerVisualizer
18
+ */
19
+ function registerRenderLayer(type, renderLayerCls, layerVisualizerCls) {
20
+ RenderLayers[type] = renderLayerCls
21
+ LayerVisualizers[type] = layerVisualizerCls
22
+ }
23
+
24
+ export { RenderLayers, LayerVisualizers, registerRenderLayer }
@@ -0,0 +1,420 @@
1
+ import { VectorTileFeature, classifyRings } from "@mapbox/vector-tile"
2
+ import { IRenderLayer } from "../IRenderLayer"
3
+ import { ILayerVisualizer } from "./ILayerVisualizer"
4
+ import { loadGeometry } from "maplibre-gl/src/data/load_geometry"
5
+ import { EXTENT } from 'maplibre-gl/src/data/extent';
6
+ import { subdividePolygon } from "maplibre-gl/src/render/subdivision"
7
+ import { granularitySettings } from "../../sources/granularitySettings";
8
+ import { warnOnce } from "maplibre-gl/src/util/util";
9
+ import { VectorTileset } from "../../VectorTileset";
10
+
11
+ export class FillFeature {
12
+ constructor() {
13
+ this.featureId = 0
14
+ this.fillColor = Cesium.Color.BLACK.clone()
15
+ this.fillOpacity = 1
16
+ this.coordinates = []
17
+ }
18
+ }
19
+
20
+ export class FillLayerVisualizer extends ILayerVisualizer {
21
+ constructor(layers, tile) {
22
+ super(layers, tile)
23
+
24
+ this.geometryInstances = []
25
+ this.primitive = null
26
+ this.commandsReady = false
27
+ }
28
+
29
+ /**
30
+ * @param {VectorTileFeature[]} features
31
+ * @param {IRenderLayer} layer
32
+ * @param {Cesium.frameState} frameState
33
+ * @param {VectorTileset} tileset
34
+ */
35
+ addLayer(features, layer, frameState, tileset) {
36
+ const style = layer.style
37
+ const { tile, geometryInstances } = this
38
+ const granularity = granularitySettings.globe.line.getGranularityForZoomLevel(tile.z) / 2
39
+ const scope = this
40
+ let featureId = 0
41
+ const promoteId = tileset.sources[layer.style.source].styleSource.promoteId
42
+
43
+ for (const sourceFeature of features) {
44
+ const featureType = VectorTileFeature.types[sourceFeature.type]
45
+ const properties = sourceFeature.properties
46
+ if (featureType !== 'Polygon') continue
47
+
48
+ const fillPattern = style.paint.getDataValue('fill-pattern', tile.z, sourceFeature)
49
+ if (fillPattern) {
50
+ warnOnce('fill图层:不支持纹理填充(fill-pattern)')
51
+ continue
52
+ }
53
+
54
+ const sourceFeatureId = sourceFeature.id || properties[promoteId]
55
+ //读取图层样式属性
56
+ const fillColor = style.convertColor(style.paint.getDataValue('fill-color', tile.z, sourceFeature))
57
+ const fillOpacity = style.paint.getDataValue('fill-opacity', tile.z, sourceFeature)
58
+
59
+ //关键:对投影坐标细分,而不是使用cesium内置的细分
60
+ const vtCoords = loadGeometry(sourceFeature)
61
+ const polygons = classifyRings(vtCoords)
62
+ for (const coordinates of polygons) {
63
+ if (coordinates.some(ring => ring.length < 3)) continue
64
+
65
+ const batchId = geometryInstances.length
66
+ if (featureId == 0) {
67
+ layer.firstBatchId = batchId
68
+ }
69
+ layer.lastBatchId = batchId
70
+
71
+ const fillFeature = {
72
+ coordinates,
73
+ featureId,
74
+ fillColor,
75
+ fillOpacity,
76
+ properties,
77
+ //保存原始数据的要素id,后续可以用来支持 featureState 表达式,这个表达式可以实现选定要素高亮显示
78
+ id: sourceFeatureId,
79
+ //保存batchId,将矢量要素与几何顶点关联,后续可以实时更新图层样式
80
+ batchId
81
+ }
82
+
83
+ scope.addFeature(fillFeature, granularity)
84
+
85
+ featureId++
86
+ }
87
+ }
88
+
89
+ layer.offsets = []
90
+ layer.counts = []
91
+
92
+ this.layers.push(layer)
93
+ }
94
+
95
+ /**
96
+ * 创建一个多边形的几何体实例
97
+ * @param {FillFeature} feature
98
+ * @param {number} granularity
99
+ */
100
+ addFeature(feature, granularity) {
101
+ const geometryInstances = this.geometryInstances
102
+ const { coordinates, fillColor, fillOpacity } = feature
103
+ const colorBytes = fillColor.toBytes()
104
+ colorBytes[3] = Math.floor(colorBytes[3] * fillOpacity)
105
+
106
+ // 使用 maplibre-gl 的 subdividePolygon 基于投影坐标进行细分,
107
+ // 而不是在转成世界坐标后再使用 Cesium.PolygonGeometry 构建,这样才能避免出现自相交、破面等现象。
108
+ // 商业版性能优化:将此过程移到 Web Worker ,多线程加速,同时避免主线程阻塞
109
+
110
+ const subdivisionRes = subdividePolygon(coordinates, this.tile, granularity, false)
111
+ const verticesFlattened = subdivisionRes.verticesFlattened
112
+ const coordDeg = [0, 0], cartesian = new Cesium.Cartesian3()
113
+ const vertCount = verticesFlattened.length / 2
114
+ const positions = new Float64Array(vertCount * 3)
115
+ const normals = new Float32Array(vertCount * 3)
116
+ const sts = new Float32Array(vertCount * 2)
117
+
118
+ for (let i = 0, j = 0; i < verticesFlattened.length; i += 2, j++) {
119
+ const x = verticesFlattened[i], y = verticesFlattened[i + 1]
120
+ const coord = this.tile.transformPoint(x, y, coordDeg)
121
+ const position = Cesium.Cartesian3.fromDegrees(coord[0], coord[1], 0, null, cartesian)
122
+ positions[j * 3] = position.x
123
+ positions[j * 3 + 1] = position.y
124
+ positions[j * 3 + 2] = position.z
125
+
126
+ const normal = Cesium.Cartesian3.normalize(position, position)
127
+ normals[j * 3] = normal.x
128
+ normals[j * 3 + 1] = normal.y
129
+ normals[j * 3 + 2] = normal.z
130
+
131
+ sts[j * 2] = x / EXTENT
132
+ sts[j * 2 + 1] = y / EXTENT
133
+ }
134
+
135
+ const indices = new (vertCount > 65535 ? Uint32Array : vertCount > 255 ? Uint16Array : Uint8Array)(subdivisionRes.indicesTriangles)
136
+
137
+ const geometry = new Cesium.Geometry({
138
+ attributes: {
139
+ position: {
140
+ componentDatatype: Cesium.ComponentDatatype.DOUBLE,
141
+ componentsPerAttribute: 3,
142
+ normalize: false,
143
+ values: positions
144
+ },
145
+ normal: {
146
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
147
+ componentsPerAttribute: 3,
148
+ normalize: false,
149
+ values: normals
150
+ },
151
+ st: {
152
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
153
+ componentsPerAttribute: 2,
154
+ normalize: false,
155
+ values: sts
156
+ }
157
+ },
158
+ primitiveType: Cesium.PrimitiveType.TRIANGLES,
159
+ indices: indices,
160
+ boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
161
+ })
162
+
163
+ const cartographic = Cesium.Cartographic.fromCartesian(geometry.boundingSphere.center)
164
+ cartographic.height = 0//包围盒中心可能高于或者低于地面,需要避免双击锁定视角时进入地下
165
+ const center = Cesium.Cartographic.toCartesian(cartographic, null, cartesian)
166
+
167
+ const instance = new Cesium.GeometryInstance({
168
+ geometry,
169
+ attributes: {
170
+ color: new Cesium.GeometryInstanceAttribute({
171
+ componentDatatype: Cesium.ComponentDatatype.UNSIGNED_BYTE,
172
+ componentsPerAttribute: 4,
173
+ normalize: true,
174
+ value: colorBytes
175
+ }),
176
+ },
177
+ //通过entity的形式暴露给Cesium pickEntity,这样点击时系统自带的inforbox可以弹出
178
+ id: new Cesium.Entity({
179
+ position: center,
180
+ id: feature.id,
181
+ properties: feature.properties
182
+ })
183
+ })
184
+ geometryInstances.push(instance)
185
+ }
186
+
187
+ createPrimitive() {
188
+ const primitive = new Cesium.Primitive({
189
+ geometryInstances: this.geometryInstances,
190
+ asynchronous: !(this.geometryInstances[0].geometry instanceof Cesium.Geometry),
191
+ appearance: new Cesium.PerInstanceColorAppearance({
192
+ flat: true,
193
+ translucent: false,
194
+ renderState: {
195
+ //这里设置是没有用的,只要 translucent 为 false,
196
+ //Cesium 内部都会覆盖成 true,所以我们需要在 DrawCommand 创建完成后再设置
197
+ depthMask: false,
198
+ },
199
+ fragmentShaderSource:/*glsl*/`
200
+ in vec4 v_color;
201
+
202
+ uniform vec4 tileId;
203
+ uniform sampler2D tileIdTexture;
204
+
205
+ void main()
206
+ {
207
+ vec2 id_st = gl_FragCoord.xy / czm_viewport.zw;
208
+ vec4 bgId = texture(tileIdTexture, id_st);
209
+ if (!all(equal(bgId, tileId)))
210
+ {
211
+ discard;
212
+ }
213
+ out_FragColor = v_color;
214
+ }
215
+ `
216
+ }),
217
+ })
218
+
219
+ //通过定义 Primitive 私有变量 _geometries、_batchTable 的 setter 和 getter,
220
+ //监听合批几何体和批次表的创建:
221
+ //1、几何体创建完成后,根据 batchId 和 featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
222
+ //2、批次表创建完成后,保存备用
223
+ let scope = this
224
+ Object.defineProperties(primitive, {
225
+ _geometries: {
226
+ get() {
227
+ return this._geometries_
228
+ },
229
+ set(geometries) {
230
+ this._geometries_ = geometries
231
+ if (geometries) {
232
+ scope.onGeometriesLoaded(geometries)
233
+ }
234
+ else {
235
+ scope = null
236
+ }
237
+ }
238
+ },
239
+ _batchTable: {
240
+ get() {
241
+ return this._batchTable_
242
+ },
243
+ set(batchTable) {
244
+ this._batchTable_ = batchTable
245
+ if (batchTable) {
246
+ scope.onBatchTableCreated(batchTable)
247
+ }
248
+ }
249
+ }
250
+ })
251
+
252
+ this.primitive = primitive
253
+ }
254
+
255
+ /**
256
+ * 根据 batchId 和 featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
257
+ * @param {Cesium.Geometry[]} geometries
258
+ */
259
+ onGeometriesLoaded(geometries) {
260
+ //Cesium 几何体合批结果可能是多个几何体,对应多个 DrawCommand
261
+ for (let pass = 0; pass < geometries.length; pass++) {
262
+ const batches = {}
263
+ const geometry = geometries[pass];
264
+ const batchIds = geometry.attributes.batchId.values
265
+ const indices = geometry.indices
266
+
267
+ //提取每个批次的起始和结束索引
268
+ let currBatchId = -1
269
+ let currBatch = null
270
+ for (let i = 0; i < indices.length; i++) {
271
+ const vertIndex = indices[i]
272
+ const batchId = batchIds[vertIndex]
273
+ if (currBatchId !== batchId) {
274
+ currBatchId = batchId
275
+ currBatch = batches[currBatchId] = {
276
+ begin: i,
277
+ end: i
278
+ }
279
+ }
280
+ currBatch.end = i
281
+ }
282
+
283
+ //根据图层批次范围,提取图层几何体索引范围,即起始索引(offset)和索引数量(count)
284
+ for (const layer of this.layers) {
285
+ const { firstBatchId, lastBatchId } = layer
286
+ if (firstBatchId === -1 || lastBatchId === -1) {
287
+ continue
288
+ }
289
+
290
+ let begin = -1, end = -1
291
+ for (let batchId = firstBatchId; batchId <= lastBatchId; batchId++) {
292
+ const batch = batches[batchId]
293
+ if (batch) {
294
+ if (begin === -1) begin = batch.begin
295
+ end = batch.end
296
+ }
297
+ }
298
+
299
+ if (begin === -1 || end === -1) {
300
+ continue
301
+ }
302
+
303
+ //起始和结束索引,索引数量需要加1
304
+ layer.offsets[pass] = begin
305
+ layer.counts[pass] = end - begin + 1
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * 保存 Cesium Primitive 创建的批次表。图层样式变化时,通过更新批次表传递到GPU,同步更新渲染效果
312
+ * @param {Cesium.BatchTable} batchTable
313
+ */
314
+ onBatchTableCreated(batchTable) {
315
+ this._batchTable = batchTable
316
+ }
317
+
318
+ /**
319
+ * 使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
320
+ * @param {Cesium.DrawCommand[]} batchedCommandList
321
+ * @param {VectorTileset} tileset
322
+ */
323
+ createLayerCommands(batchedCommandList, tileset) {
324
+ const renderState = Cesium.RenderState.fromCache({
325
+ id: 'fill',
326
+ blending: Cesium.BlendingState.ALPHA_BLEND,
327
+ depthMask: false,
328
+ depthTest: {
329
+ enabled: true
330
+ },
331
+ cull: {
332
+ enabled: true
333
+ }
334
+ })
335
+ const tileId = this.tile.tileId
336
+ this.renderState = renderState
337
+
338
+ for (let i = 0; i < this.layers.length; i++) {
339
+ const layer = this.layers[i]
340
+ const layerCommandList = layer.commandList = []
341
+
342
+ for (let pass = 0; pass < batchedCommandList.length; pass++) {
343
+ const offset = layer.offsets[pass], count = layer.counts[pass]
344
+ if (typeof offset !== 'number' || typeof count !== 'number') {
345
+ continue
346
+ }
347
+ const command = batchedCommandList[pass]
348
+ command.uniformMap.tileIdTexture = function () {
349
+ return tileset.tileIdTexture
350
+ }
351
+ command.uniformMap.tileId = function () {
352
+ return tileId.color
353
+ }
354
+ command.pass = Cesium.Pass.CESIUM_3D_TILE
355
+ //通过副本的 offset 和 count 指定图层的绘制范围
356
+ const layerCommand = Cesium.DrawCommand.shallowClone(command)
357
+ layerCommand.pass = Cesium.Pass.CESIUM_3D_TILE
358
+ layerCommand.renderState = renderState
359
+ layerCommand.layerType = 'fill'
360
+ layerCommand.offset = offset
361
+ layerCommand.count = count
362
+ layerCommandList.push(layerCommand)
363
+ }
364
+ }
365
+
366
+ //标记 drawCommand 创建完成
367
+ this.state = 'done'
368
+ }
369
+
370
+ update(frameState, tileset) {
371
+ if (!this.geometryInstances) {
372
+ return
373
+ }
374
+
375
+ super.update(frameState, tileset)
376
+
377
+ if (!this.primitive && this.geometryInstances.length) {
378
+ this.createPrimitive()
379
+ }
380
+
381
+ if (this.primitive && this.state !== 'done' && this.state !== 'error') {
382
+ //先保存系统的 commandList
383
+ const preCommandList = frameState.commandList
384
+ //临时覆盖 frameState.commandList,用于获取合批之后的drawCommand
385
+ const batchedCommandList = frameState.commandList = []
386
+
387
+ //执行 primitive 的 update ,直到生成了合批后的drawCommand
388
+ try {
389
+ this.primitive.update(frameState)
390
+ } catch (err) {//如果报错,下一帧就不要再执行 update 了,以免重复打印错误信息
391
+ this.geometryInstances = []
392
+ this.state = 'error'
393
+ if (err.stack) console.trace(err.stack)
394
+ else console.error(err);
395
+ return
396
+ }
397
+
398
+ //使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
399
+ if (batchedCommandList.length > 0) {
400
+ this.createLayerCommands(batchedCommandList, tileset)
401
+ }
402
+
403
+ //恢复系统的 commandList
404
+ frameState.commandList = preCommandList
405
+ this.geometryInstances = []
406
+ }
407
+ }
408
+
409
+ destroy() {
410
+ this.primitive = this.primitive && this.primitive.destroy()
411
+ this._batchTable = null
412
+ this.geometryInstances = null
413
+
414
+ super.destroy()
415
+ }
416
+
417
+ isDestroyed() {
418
+ return false
419
+ }
420
+ }
@@ -0,0 +1,73 @@
1
+ import { VectorTileFeature } from "@mapbox/vector-tile";
2
+ import { IRenderLayer } from "../IRenderLayer";
3
+ import { VectorTileset } from "../../VectorTileset";
4
+ import { VectorTileLOD } from "../../VectorTileLOD";
5
+
6
+ /**
7
+ * 图层渲染器基类,负责瓦片内指定类型图层的合批几何体、批次表、绘图命令(DrawCommand)的构建,以及图层DrawCommand浅拷贝副本(shallow clone)的分配
8
+ * @see FillLayerVisualizer
9
+ * @see LineLayerVisualizer
10
+ * @see SymbolLayerVisualizer
11
+ */
12
+ export class ILayerVisualizer {
13
+ /**
14
+ * 构造图层渲染器实例。图层渲染器负责瓦片内指定类型图层的合批几何体、批次表、绘图命令(DrawCommand)的构建,以及图层DrawCommand浅拷贝副本(shallow clone)的分配。
15
+ * 注意:构造函数仅供VectorTileLOD调用,请勿在其他模块直接调用
16
+ * @param {VectorTileLOD} tile
17
+ * @param {IRenderLayer[]} [layers]
18
+ * @inner
19
+ * @see FillLayerVisualizer
20
+ * @see LineLayerVisualizer
21
+ * @see SymbolLayerVisualizer
22
+ */
23
+ constructor(tile, layers = []) {
24
+ /**
25
+ * @type {VectorTileLOD}
26
+ */
27
+ this.tile = tile
28
+ /**
29
+ * @type {IRenderLayer[]}
30
+ */
31
+ this.layers = layers
32
+ /**
33
+ * 图层渲染器状态
34
+ * @type {'none'|'done'|'error'}
35
+ */
36
+ this.state = 'none'
37
+ }
38
+
39
+ /**
40
+ * 添加图层:将图层及其过滤后的要素添加到图层渲染器,子类实现该方法,完成图层存储、要素转换等合批构建需要的准备工作
41
+ * @param {VectorTileFeature[]} features
42
+ * @param {IRenderLayer} renderLayer
43
+ * @param {Cesium.frameState} frameState
44
+ * @param {VectorTileset} tileset
45
+ */
46
+ addLayer(features, renderLayer, frameState, tileset) {
47
+
48
+ }
49
+
50
+ /**
51
+ * 更新渲染器:子类实现该方法,完成合批几何体、批次表、绘图命令(DrawCommand)的构建,以及图层DrawCommand浅拷贝副本(shallow clone)的分配等工作
52
+ * @param {*} frameState
53
+ * @param {*} tileset
54
+ */
55
+ update(frameState, tileset) {
56
+
57
+ }
58
+
59
+ /**
60
+ * 销毁图层渲染器对象,释放资源
61
+ */
62
+ destroy() {
63
+ this.tile = null
64
+ this.layers.length = 0
65
+ this.isDestroyed = function returnTrue() {
66
+ return true
67
+ }
68
+ }
69
+
70
+ isDestroyed() {
71
+ return false
72
+ }
73
+ }