@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,494 @@
1
+ import { VectorTileset } from "./VectorTileset";
2
+ import * as MVT from '@mapbox/vector-tile'
3
+ import { IRenderLayer, ILayerVisualizer, RenderLayers, LayerVisualizers } from "./layers";
4
+ import { VectorTileRenderList } from "./VectorTileRenderList";
5
+ import { ISource } from "./sources/ISource";
6
+ import { EXTENT } from "maplibre-gl/src/data/extent";
7
+ import Point from "@mapbox/point-geometry";
8
+ import { subdivideVertexLine } from "maplibre-gl/src/render/subdivision";
9
+ import { granularitySettings } from "./sources/granularitySettings";
10
+
11
+ let tileDepthRenderSate = null
12
+ let nextTileKey = 0
13
+ let levelZeroMaximumGeometricError = null
14
+ /**
15
+ * 计算指定LOD层级的最大几何误差,相当于动态计算3DTiles中geometricError,只是形瓦片的几何误差是一个层级的所有瓦片都相同(rectangle大小都一样)
16
+ * @param {number} z 瓦片层级(整数,不是地图的缩放级别)
17
+ * @param {Cesium.TilingScheme} tilingScheme
18
+ * @returns
19
+ */
20
+ function getLevelMaximumGeometricError(z, tilingScheme) {
21
+ if (levelZeroMaximumGeometricError === null) {
22
+ levelZeroMaximumGeometricError = Cesium.TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(
23
+ tilingScheme.ellipsoid,
24
+ 128,
25
+ tilingScheme.getNumberOfXTilesAtLevel(0)
26
+ );
27
+ }
28
+ return levelZeroMaximumGeometricError / (1 << z);
29
+ }
30
+
31
+ /**
32
+ * LOD调度重要参数SSE的计算函数,从Cesium地形调度模块中提取出来的代码,调度逻辑和地形瓦片的一致
33
+ * @param {Cesium.FrameState} frameState
34
+ * @param {VectorTileLOD} tile
35
+ * @returns
36
+ */
37
+ function screenSpaceError(frameState, tile) {
38
+ const maxGeometricError = getLevelMaximumGeometricError(
39
+ tile.z, tile.tilingScheme
40
+ );
41
+
42
+ const distance2 = tile.distanceToCamera;
43
+ const height = frameState.context.drawingBufferHeight;
44
+ const sseDenominator = frameState.camera.frustum.sseDenominator;
45
+ let error = maxGeometricError * height / (distance2 * sseDenominator);
46
+ if (frameState.fog.enabled) {
47
+ error -= Cesium.Math.fog(distance2, frameState.fog.density) * frameState.fog.sse;
48
+ }
49
+ error /= frameState.pixelRatio;
50
+ return error;
51
+ }
52
+
53
+ function initConstants() {
54
+ if (tileDepthRenderSate !== null) return
55
+
56
+ tileDepthRenderSate = Cesium.RenderState.fromCache({
57
+ id: 'vt_tile-depth',
58
+ blending: Cesium.BlendingState.DISABLED,
59
+ depthTest: {
60
+ enabled: true
61
+ },
62
+ depthMask: true,
63
+ cull: {
64
+ enabled: true
65
+ },
66
+ stencilMask: Cesium.StencilConstants.CESIUM_3D_TILE_MASK,
67
+ stencilTest: {
68
+ backFunction: 519,
69
+ backOperation: { fail: 7680, zFail: 7680, zPass: 7681 },
70
+ enabled: true,
71
+ frontFunction: 519,
72
+ frontOperation: { fail: 7680, zFail: 7680, zPass: 7681 },
73
+ mask: 128,
74
+ reference: 128
75
+ },
76
+ colorMask: {
77
+ red: false,
78
+ green: false,
79
+ blue: false,
80
+ alpha: false
81
+ }
82
+ })
83
+ }
84
+
85
+ export class VectorTileLOD {
86
+ constructor(options) {
87
+ initConstants()
88
+
89
+ this.x = options.x
90
+ this.y = options.y
91
+ this.z = options.z
92
+ /**暂时用不到,但是还是记录父级瓦片 */
93
+ this.parent = options.parent
94
+ this.children = []
95
+
96
+ /**@type {Cesium.TilingScheme} */
97
+ this.tilingScheme = options.tilingScheme
98
+ this.rectangle = this.tilingScheme.tileXYToRectangle(this.x, this.y, this.z)
99
+ /**
100
+ * Cesium.TileBoundingRegion 不是公开的API,但是可以从Cesium地形调度相关模块源码学习,
101
+ */
102
+ this.tileBoundingRegion = new Cesium.TileBoundingRegion({
103
+ rectangle: this.rectangle,
104
+ minimumHeight: 0,
105
+ maximumHeight: 0,
106
+ ellipsoid: this.tilingScheme.ellipsoid,
107
+ computeBoundingVolumes: true
108
+ })
109
+
110
+ /**@type {IRenderLayer[]} */
111
+ this.layers = []
112
+ /**@type {ILayerVisualizer[]} */
113
+ this.visualizers = []
114
+ /**
115
+ * 保存pbf/mvt文件解析结果,准备创建图层(要素过滤)时候进一步读取要素,创建图层渲染对象时读取要素几何数据
116
+ * @type {Record<string,MVT.VectorTile>}
117
+ */
118
+ this.sources = {}
119
+
120
+ this.tileId = null
121
+ /**
122
+ * 记录最近一次访问时间(用渲染帧数表示),用于筛选过期瓦片
123
+ */
124
+ this.lastVisitTime = 0
125
+ /**
126
+ * 瓦片调度状态
127
+ * @type {'none'|'done'|'error'}
128
+ */
129
+ this.state = 'none'
130
+
131
+ this.tileId = {
132
+ x: this.x, y: this.y, z: this.z,
133
+ key: nextTileKey++,
134
+ color: Cesium.Color.fromRgba(nextTileKey - 1),
135
+ tileColor: Cesium.Color.fromRandom({
136
+ alpha: 1
137
+ })
138
+ }
139
+
140
+ const size = EXTENT * Math.pow(2, this.z),
141
+ x0 = EXTENT * this.x,
142
+ y0 = EXTENT * this.y;
143
+ this.transformPoint = function (x, y, lonlat) {
144
+ lonlat[0] = (x + x0) * 360 / size - 180
145
+ lonlat[1] = 360 / Math.PI * Math.atan(Math.exp((1 - (y + y0) * 2 / size) * Math.PI)) - 90
146
+ return lonlat //[x, y]
147
+ }
148
+ }
149
+
150
+ createChildren() {
151
+ var tiles = [{
152
+ x: this.x * 2,
153
+ y: this.y * 2 + 1,
154
+ z: this.z + 1
155
+ }, {
156
+ x: this.x * 2 + 1,
157
+ y: this.y * 2 + 1,
158
+ z: this.z + 1,
159
+ }, {
160
+ x: this.x * 2,
161
+ y: this.y * 2,
162
+ z: this.z + 1,
163
+ }, {
164
+ x: this.x * 2 + 1,
165
+ y: this.y * 2,
166
+ z: this.z + 1,
167
+ }]
168
+
169
+ for (const { x, y, z } of tiles) {
170
+ var child = new VectorTileLOD({
171
+ x, y, z,
172
+ tilingScheme: this.tilingScheme,
173
+ parent: this
174
+ })
175
+ this.children.push(child)
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @param {Cesium.FrameState} frameState
181
+ * @param {{visitChildren(child:VectorTileLOD):void;accept(tile:VectorTileLOD):void}} visitor
182
+ * @returns
183
+ */
184
+ visit(frameState, visitor) {
185
+ const tileBoundingRegion = this.tileBoundingRegion
186
+
187
+ //相机视锥剔除
188
+ this.distanceToCamera = tileBoundingRegion.distanceToCamera(frameState)
189
+ this.visibility = frameState.cullingVolume.computeVisibility(tileBoundingRegion)
190
+ if (this.visibility == Cesium.Intersect.OUTSIDE) {
191
+ return
192
+ }
193
+
194
+ //性能优化:还可以进行地平线剔除更多被地球完全遮挡的瓦片
195
+
196
+ const maxSSE = frameState.maximumScreenSpaceError
197
+ const sse = screenSpaceError(frameState, this)
198
+ if (sse >= maxSSE) {//继续遍历子级瓦片
199
+ visitor.visitChildren(this)
200
+ }
201
+ else {//使用当前瓦片渲染
202
+ visitor.accept(this)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * @param {VectorTileset} tileset
208
+ */
209
+ async getSources(tileset) {
210
+ /**@type {Record<string,ISource>} */
211
+ const sourcesToLoad = {}
212
+ const style = tileset._styleJson
213
+
214
+ for (const styleLayer of style.layers) {
215
+ const sourceId = styleLayer.source
216
+ const source = tileset.sources[sourceId]
217
+ if (source && !sourcesToLoad[sourceId]) {
218
+ sourcesToLoad[sourceId] = source
219
+ }
220
+ }
221
+
222
+ for (const sourceId in sourcesToLoad) {
223
+ const source = sourcesToLoad[sourceId]
224
+ try {
225
+ const tileData = await source.requestTile(this.x, this.y, this.z, tileset)
226
+ if (tileData) {
227
+ this.sources[sourceId] = tileData
228
+ }
229
+ } catch (err) { }
230
+ }
231
+
232
+ tileset.numLoading--
233
+ this.state = 'loaded'
234
+ }
235
+
236
+ /**
237
+ * @param {Cesium.FrameRateMonitor} frameState
238
+ * @param {VectorTileset} tileset
239
+ */
240
+ async createRenderLayers(frameState, tileset) {
241
+ const sources = this.sources
242
+ const styleLayers = tileset._styleLayers
243
+ const renderLayers = this.layers
244
+ const visualizers = this.visualizers
245
+ /**@type {Record<string,ILayerVisualizer>} */
246
+ const visualizerMap = {}
247
+
248
+ for (const styleLayer of styleLayers) {
249
+ const sourceVectorTile = sources[styleLayer.source]
250
+ /**
251
+ * 按图层类型获取对应的渲染图层类
252
+ * @type {typeof IRenderLayer}
253
+ */
254
+ const RenderLayer = RenderLayers[styleLayer.type]
255
+ const LayerVisualizer = LayerVisualizers[styleLayer.type]
256
+ const isBackgroundLayer = styleLayer.type === 'background'
257
+ if ((!isBackgroundLayer && !sourceVectorTile) || !RenderLayer) continue
258
+
259
+ const features = []
260
+ if (!isBackgroundLayer) {
261
+ const sourceType = styleLayer.source && tileset.sources[styleLayer.source].type
262
+ const sourceLayer = sourceType == 'geojson' ? '_geojsonTileLayer' : styleLayer.sourceLayer
263
+ const vectorTileLayer = sourceVectorTile.layers[sourceLayer]
264
+ if (!vectorTileLayer) continue
265
+
266
+ //读取要素,并根据图层样式filter表达式进行过滤
267
+ //性能优化:同一帧读取和过滤大量要素,耗时较长,应该将其移到Web Worker
268
+ const featureCount = vectorTileLayer.length
269
+ for (let i = 0; i < featureCount; i++) {
270
+ //读取要素
271
+ const feature = vectorTileLayer.feature(i)
272
+ //过滤要素
273
+ if (styleLayer.filter && !styleLayer.filter.filter({ zoom: this.z }, feature)) {
274
+ continue
275
+ }
276
+ features.push(feature)
277
+ }
278
+ if (!features.length) continue
279
+ }
280
+
281
+ //创建渲染图层
282
+ const renderLayer = new RenderLayer(features, styleLayer, this)
283
+ renderLayers.push(renderLayer)
284
+
285
+ //将渲染图层分配到对应类型的图层渲染器,图层渲染器应实现如下功能,以提升渲染性能:
286
+ //1、合批创建几何体、批次表和 DrawCommand;
287
+ //2、克隆合批DrwaCommand,创建副本,通过offset和count指定图层的绘制范围。
288
+ if (LayerVisualizer) {
289
+ let visualizer = visualizerMap[styleLayer.type]
290
+ if (!visualizer) {
291
+ visualizer = new LayerVisualizer(this)
292
+ visualizerMap[styleLayer.type] = visualizer
293
+ visualizers.push(visualizer)
294
+ }
295
+ visualizer.addLayer(features, renderLayer, frameState, tileset)
296
+ }
297
+ }
298
+
299
+ this.state = 'ready'
300
+ }
301
+
302
+ /**
303
+ * @param {Cesium.FrameState} frameState
304
+ * @param {VectorTileRenderList} renderList
305
+ * @param {VectorTileset} tileset
306
+ */
307
+ update(frameState, renderList, tileset) {
308
+ // 这里构建的 primitive 有如下用途:
309
+ // 1. showTileColor 设置为 true 时,展示瓦片范围,验证LOD调度效果
310
+ // 2. 生成绘制瓦片 id 的 DrawCommand,在 VectorTileset 中实时更新瓦片id纹理,实现瓦片边界精准裁剪
311
+
312
+ if (!this.primitive) {
313
+
314
+ this.primitive = new Cesium.Primitive({
315
+ geometryInstances: new Cesium.GeometryInstance({
316
+ geometry: new Cesium.RectangleGeometry({
317
+ rectangle: this.rectangle
318
+ })
319
+ }),
320
+ compressVertices: false,
321
+ asynchronous: false,
322
+ appearance: new Cesium.MaterialAppearance({
323
+ flat: true,
324
+ translucent: false,
325
+ material: Cesium.Material.fromType('Color', {
326
+ color: Cesium.Color.fromAlpha(this.tileId.tileColor, 0.25)
327
+ }),
328
+ renderState: {
329
+ blending: Cesium.BlendingState.ALPHA_BLEND,
330
+ depthMask: false,
331
+ depthTest: {
332
+ enabled: true
333
+ },
334
+ cull: {
335
+ enabled: false
336
+ }
337
+ }
338
+ })
339
+ })
340
+ this.primitive.name = '_tile-color_'
341
+ }
342
+
343
+ const tileComandList = this.commandList || []
344
+ const tileIdCommands = this.tileIdCommands || []
345
+ const tileDepthCommands = this.tileDepthCommands || []
346
+
347
+ if (!tileComandList.length) {
348
+ const preCommandList = frameState.commandList
349
+ frameState.commandList = tileComandList
350
+
351
+ this.primitive.update(frameState)
352
+
353
+ if (tileComandList.length) {
354
+
355
+ const tileIdColor = this.tileId.color
356
+ for (const tileComand of tileComandList) {
357
+ tileComand.pass = Cesium.Pass.CESIUM_3D_TILE
358
+
359
+ const tileIdCommand = Cesium.DrawCommand.shallowClone(tileComand)
360
+ tileIdCommand.renderState = Cesium.RenderState.fromCache({
361
+ id: 'tileId',
362
+ blending: {
363
+ enabled: false
364
+ },
365
+ depthTest: {
366
+ enabled: false
367
+ },
368
+ depthMask: true,
369
+ cull: {
370
+ enabled: true
371
+ }
372
+ })
373
+ tileIdCommand.layerType = 'tile-id'
374
+ tileIdCommand.uniformMap = {
375
+ ...tileComand.uniformMap
376
+ }
377
+ tileIdCommand.uniformMap.color_0 = function () {
378
+ return tileIdColor
379
+ }
380
+ tileIdCommands.push(tileIdCommand)
381
+ // 浅克隆一个副本,renderState替换成只写入深度和开启模板测试的版本,以支持Entity和GroundPrimitive等贴地对象
382
+ const tileDepthCommand = Cesium.DrawCommand.shallowClone(tileComand)
383
+ tileDepthCommand.pass = Cesium.Pass.CESIUM_3D_TILE
384
+ tileDepthCommand.renderState = tileDepthRenderSate
385
+ tileDepthCommands.layerType = 'tile-depth'
386
+ tileDepthCommands.push(tileDepthCommand)
387
+ }
388
+ this.tileIdCommands = tileIdCommands
389
+ this.tileDepthCommands = tileDepthCommands
390
+ }
391
+
392
+ this.commandList = tileComandList
393
+ frameState.commandList = preCommandList
394
+ }
395
+
396
+ //瓦片id纹理
397
+ for (const tileIdCommand of tileIdCommands) {
398
+ renderList.tileIdCommands.push(tileIdCommand)
399
+ }
400
+ //瓦片范围
401
+ if (tileset.showTileColor && tileComandList.length) {
402
+ renderList.tileCommands.push(...tileComandList)
403
+ }
404
+ //最后将瓦片深度写入主深度缓冲区,这样才能使Entity和GroundPrimitive等贴地对象能贴合瓦片内的矢量要素
405
+ if (tileDepthCommands.length) {
406
+ renderList.tileCommands.push(...tileDepthCommands)
407
+ }
408
+
409
+ //更新瓦片状态,根据状态执行不同的处理过程
410
+
411
+ //请求瓦片数据,解析pbf/mvt文件
412
+ if (this.state == 'none' && tileset.numLoading < 1) {
413
+ tileset.numLoading++
414
+ this.state = 'loading'
415
+ this.getSources(tileset)
416
+ }
417
+
418
+ //创建渲染图层实例
419
+ if (this.state === 'loaded' && tileset.numInitializing < 1) {
420
+ this.state = 'initializing'
421
+ tileset.numInitializing++
422
+ this.createRenderLayers(frameState, tileset)
423
+ }
424
+
425
+ if (this.state === 'ready') {
426
+ //更新图层渲染器
427
+ for (const visualizer of this.visualizers) {
428
+ visualizer.update(frameState, tileset)
429
+ }
430
+ //将渲染图层追加到相应的渲染队列(渲染队列内部按图层id分组,确保不同瓦片的同一个id图层都在一组内,然后按图层顺序逐组渲染)
431
+ for (const layer of this.layers) {
432
+ renderList.push(layer)
433
+ }
434
+ }
435
+ }
436
+
437
+ /**
438
+ * 卸载瓦片,当瓦片过期(不可见,且符合其他过期规则)时,释放pbf/mvt解析数据、图层渲染对象等资源,重置瓦片状态
439
+ */
440
+ unload() {
441
+ //释放用于展示瓦片范围的primitive
442
+ if (this.primitive) {
443
+ this.primitive.destroy()
444
+ this.primitive = null
445
+ }
446
+ this.tileGeometry = null
447
+
448
+ //清空瓦片克隆的 DrawCommand,不需要手动释放,相关资源在 primitive.destroy 执行时已经释放
449
+ if (this.commandList) {
450
+ this.commandList.length = 0
451
+ }
452
+ if (this.tileIdCommands) {
453
+ this.tileIdCommands.length = 0
454
+ }
455
+ if (this.tileDepthCommands) {
456
+ this.tileDepthCommands.length = 0
457
+ }
458
+
459
+ //销毁渲染图层,释放图层渲染资源
460
+ for (const visualizer of this.visualizers) {
461
+ visualizer.destroy()
462
+ }
463
+ this.visualizers.length = 0
464
+
465
+ for (const layer of this.layers) {
466
+ layer.destroy()
467
+ }
468
+ this.layers.length = 0
469
+
470
+ //释放pbf/mvt解析数据
471
+ this.sources = {}
472
+
473
+ //重置瓦片状态
474
+ this.state = 'none'
475
+ }
476
+
477
+ /**
478
+ * 销毁瓦片,释放所有资源
479
+ */
480
+ destroy() {
481
+ this.unload()
482
+
483
+ this.tileId = null
484
+ this.tilingScheme = null
485
+ this.parent = null
486
+ if (this.children) {
487
+ for (const child of this.children) {
488
+ child.destroy()
489
+ }
490
+ this.children.length = 0
491
+ this.children = null
492
+ }
493
+ }
494
+ }
@@ -0,0 +1,65 @@
1
+ import { IRenderLayer } from "./layers/IRenderLayer"
2
+
3
+ export class VectorTileRenderList {
4
+ constructor(styleLayers) {
5
+ this.styleLayers = styleLayers
6
+ this.renderLayers = []
7
+ this.layerIndexMap = {}
8
+ /**
9
+ * @type {IRenderLayer[]}
10
+ * @private
11
+ */
12
+ this.list = []
13
+ /**@type {Cesium.DrawCommand[]} */
14
+ this.tileIdCommands = []
15
+ /**@type {Cesium.DrawCommand[]} */
16
+ this.tileCommands = []
17
+ }
18
+
19
+ init() {
20
+ const { styleLayers, renderLayers, layerIndexMap } = this
21
+ for (let layerIndex = 0; layerIndex < styleLayers.length; layerIndex++) {
22
+ const styleLayer = styleLayers[layerIndex]
23
+ renderLayers[layerIndex] = []
24
+ layerIndexMap[styleLayer.id] = layerIndex
25
+ }
26
+ this.tileIdCommands.length = 0
27
+ this.tileCommands.length = 0
28
+ }
29
+
30
+ beginFrame() {
31
+ const renderLayers = this.renderLayers
32
+ for (const renderLayer of renderLayers) {
33
+ renderLayer.length = 0
34
+ }
35
+ this.tileIdCommands.length = 0
36
+ this.tileCommands.length = 0
37
+ }
38
+
39
+ push(renderLayer) {
40
+ const layerIndex = this.layerIndexMap[renderLayer.id]
41
+ this.renderLayers[layerIndex].push(renderLayer)
42
+ }
43
+
44
+ /**
45
+ * @returns {IRenderLayer[]}
46
+ */
47
+ getList() {
48
+ const list = this.list
49
+ list.length = 0
50
+ const renderLayers = this.renderLayers
51
+ for (const renderLayer of renderLayers) {
52
+ if (renderLayer) {
53
+ list.push(...renderLayer)
54
+ }
55
+ }
56
+ return list
57
+ }
58
+
59
+ destroy() {
60
+ this.styleLayers.length = 0
61
+ this.renderLayers.length = 0
62
+ this.layerIndexMap = null
63
+ this.init()
64
+ }
65
+ }