@mesh3d/cesium-vectortile-gl 0.4.4 → 0.4.6

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.
Files changed (51) hide show
  1. package/.gitattributes +11 -0
  2. package/.gitconfig +3 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +5 -0
  5. package/.vscode/settings.json +25 -0
  6. package/LICENSE.md +203 -203
  7. package/README.md +202 -167
  8. package/Source/Cesium.d.ts +2692 -2691
  9. package/Source/VectorTileLOD.js +720 -532
  10. package/Source/VectorTileRenderList.js +70 -70
  11. package/Source/VectorTileset.js +473 -447
  12. package/Source/layers/BackgroundRenderLayer.js +91 -89
  13. package/Source/layers/FillRenderLayer.js +18 -18
  14. package/Source/layers/IRenderLayer.js +160 -152
  15. package/Source/layers/LineRenderLayer.js +104 -94
  16. package/Source/layers/SymbolRenderLayer.js +30 -31
  17. package/Source/layers/index.js +23 -16
  18. package/Source/layers/registerRenderLayer.js +24 -24
  19. package/Source/layers/visualizers/FillLayerVisualizer.js +542 -426
  20. package/Source/layers/visualizers/ILayerVisualizer.js +90 -94
  21. package/Source/layers/visualizers/LineLayerVisualizer.js +702 -571
  22. package/Source/layers/visualizers/SymbolLayerVisualizer.js +514 -244
  23. package/Source/sources/GeoJSONSource.js +53 -46
  24. package/Source/sources/ISource.js +39 -39
  25. package/Source/sources/VectorSource.js +94 -52
  26. package/Source/sources/granularitySettings.js +23 -20
  27. package/Source/sources/index.js +6 -11
  28. package/Source/sources/registerSource.js +17 -19
  29. package/Source/style/StyleLayer.js +43 -43
  30. package/Source/style/StyleLayerProperties.js +44 -43
  31. package/Source/style/index.js +2 -2
  32. package/Source/symbol/SymbolPlacements.js +117 -88
  33. package/Source/workers/VectorTileWorker.js +41 -0
  34. package/Source/workers/ellipsoid.js +47 -0
  35. package/Source/workers/processTileTask.js +329 -0
  36. package/Source/workers/styleEvaluator.js +168 -0
  37. package/benchmark.html +148 -0
  38. package/dist/cvt-gl-worker.js +9274 -0
  39. package/dist/cvt-gl-worker.js.map +1 -0
  40. package/dist/cvt-gl.js +2570 -2001
  41. package/dist/cvt-gl.js.map +1 -1
  42. package/dist/cvt-gl.min.js +3 -3
  43. package/dist/cvt-gl.min.js.map +1 -1
  44. package/eslint.config.mjs +58 -0
  45. package/index.js +9 -6
  46. package/mlt.html +26 -25
  47. package/package.json +64 -41
  48. package/prettier.config.mjs +30 -0
  49. package/vite.config.mjs +43 -0
  50. package/vite.worker.config.mjs +31 -0
  51. package/worker.html +26 -0
@@ -1,426 +1,542 @@
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
- layer.state = 'done'
366
- }
367
-
368
- //标记 drawCommand 创建完成
369
- this.state = 'done'
370
- }
371
-
372
- update(frameState, tileset) {
373
- if (!this.geometryInstances) {
374
- return
375
- }
376
-
377
- super.update(frameState, tileset)
378
-
379
- if (!this.primitive && this.geometryInstances.length) {
380
- this.createPrimitive()
381
- }
382
-
383
- if (this.primitive && this.state !== 'done' && this.state !== 'error') {
384
- //先保存系统的 commandList
385
- const preCommandList = frameState.commandList
386
- //临时覆盖 frameState.commandList,用于获取合批之后的drawCommand
387
- const batchedCommandList = frameState.commandList = []
388
-
389
- //执行 primitive 的 update ,直到生成了合批后的drawCommand
390
- try {
391
- this.primitive.update(frameState)
392
- } catch (err) {//如果报错,下一帧就不要再执行 update 了,以免重复打印错误信息
393
- this.geometryInstances = []
394
- this.setState('error')
395
- if (err.stack) console.trace(err.stack)
396
- else console.error(err);
397
- return
398
- }
399
-
400
- //使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
401
- if (batchedCommandList.length > 0) {
402
- this.createLayerCommands(batchedCommandList, tileset)
403
- }
404
-
405
- if (this.primitive._state === Cesium.PrimitiveState.FAILED) {
406
- this.setState('error')
407
- }
408
-
409
- //恢复系统的 commandList
410
- frameState.commandList = preCommandList
411
- this.geometryInstances = []
412
- }
413
- }
414
-
415
- destroy() {
416
- this.primitive = this.primitive && this.primitive.destroy()
417
- this._batchTable = null
418
- this.geometryInstances = null
419
-
420
- super.destroy()
421
- }
422
-
423
- isDestroyed() {
424
- return false
425
- }
426
- }
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 =
39
+ granularitySettings.globe.line.getGranularityForZoomLevel(tile.z) / 2
40
+ const scope = this
41
+ let featureId = 0
42
+ const promoteId = tileset.sources[layer.style.source].styleSource.promoteId
43
+
44
+ for (const sourceFeature of features) {
45
+ const featureType = VectorTileFeature.types[sourceFeature.type]
46
+ const properties = sourceFeature.properties
47
+ if (featureType !== 'Polygon') continue
48
+
49
+ const fillPattern = style.paint.getDataValue(
50
+ 'fill-pattern',
51
+ tile.z,
52
+ sourceFeature
53
+ )
54
+ if (fillPattern) {
55
+ warnOnce('fill图层:不支持纹理填充(fill-pattern)')
56
+ continue
57
+ }
58
+
59
+ const sourceFeatureId = sourceFeature.id || properties[promoteId]
60
+ //读取图层样式属性
61
+ const fillColor = style.convertColor(
62
+ style.paint.getDataValue('fill-color', tile.z, sourceFeature)
63
+ )
64
+ const fillOpacity = style.paint.getDataValue(
65
+ 'fill-opacity',
66
+ tile.z,
67
+ sourceFeature
68
+ )
69
+
70
+ //关键:对投影坐标细分,而不是使用cesium内置的细分
71
+ const vtCoords = loadGeometry(sourceFeature)
72
+ const polygons = classifyRings(vtCoords)
73
+ for (const coordinates of polygons) {
74
+ if (coordinates.some(ring => ring.length < 3)) continue
75
+
76
+ const batchId = geometryInstances.length
77
+ if (featureId == 0) {
78
+ layer.firstBatchId = batchId
79
+ }
80
+ layer.lastBatchId = batchId
81
+
82
+ const fillFeature = {
83
+ coordinates,
84
+ featureId,
85
+ fillColor,
86
+ fillOpacity,
87
+ properties,
88
+ //保存原始数据的要素id,后续可以用来支持 featureState 表达式,这个表达式可以实现选定要素高亮显示
89
+ id: sourceFeatureId,
90
+ //保存batchId,将矢量要素与几何顶点关联,后续可以实时更新图层样式
91
+ batchId
92
+ }
93
+
94
+ scope.addFeature(fillFeature, granularity)
95
+
96
+ featureId++
97
+ }
98
+ }
99
+
100
+ layer.offsets = []
101
+ layer.counts = []
102
+
103
+ this.layers.push(layer)
104
+ }
105
+
106
+ /**
107
+ * Web Worker 结果构建图层几何体(positions/normals/indices 已由 Worker 算好)
108
+ * @param {object} workerLayerData - { layerId, source, sourceLayer, styleLayer, batches, firstBatchId, lastBatchId }
109
+ * @param {IRenderLayer} layer
110
+ * @param {Cesium.FrameState} frameState
111
+ * @param {VectorTileset} tileset
112
+ */
113
+ addLayerFromWorkerResult(workerLayerData, layer, frameState, tileset) {
114
+ const { batches, firstBatchId, lastBatchId } = workerLayerData
115
+ const geometryInstances = this.geometryInstances
116
+ const cartesian = new Cesium.Cartesian3()
117
+
118
+ for (const batch of batches) {
119
+ const { positions, normals, st, indices, colorBytes, id, properties } =
120
+ batch
121
+ const vertCount = positions.length / 3
122
+ const geometry = new Cesium.Geometry({
123
+ attributes: {
124
+ position: {
125
+ componentDatatype: Cesium.ComponentDatatype.DOUBLE,
126
+ componentsPerAttribute: 3,
127
+ normalize: false,
128
+ values: positions
129
+ },
130
+ normal: {
131
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
132
+ componentsPerAttribute: 3,
133
+ normalize: false,
134
+ values: normals
135
+ },
136
+ st: {
137
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
138
+ componentsPerAttribute: 2,
139
+ normalize: false,
140
+ values: st
141
+ }
142
+ },
143
+ primitiveType: Cesium.PrimitiveType.TRIANGLES,
144
+ indices,
145
+ boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
146
+ })
147
+ const cartographic = Cesium.Cartographic.fromCartesian(
148
+ geometry.boundingSphere.center
149
+ )
150
+ cartographic.height = 0
151
+ const center = Cesium.Cartographic.toCartesian(
152
+ cartographic,
153
+ null,
154
+ cartesian
155
+ )
156
+ const instance = new Cesium.GeometryInstance({
157
+ geometry,
158
+ attributes: {
159
+ color: new Cesium.GeometryInstanceAttribute({
160
+ componentDatatype: Cesium.ComponentDatatype.UNSIGNED_BYTE,
161
+ componentsPerAttribute: 4,
162
+ normalize: true,
163
+ value: Array.from(colorBytes)
164
+ })
165
+ },
166
+ id: new Cesium.Entity({
167
+ position: center,
168
+ id,
169
+ properties
170
+ })
171
+ })
172
+ geometryInstances.push(instance)
173
+ }
174
+
175
+ layer.firstBatchId = firstBatchId
176
+ layer.lastBatchId = lastBatchId
177
+ layer.offsets = []
178
+ layer.counts = []
179
+ this.layers.push(layer)
180
+ }
181
+
182
+ /**
183
+ * 创建一个多边形的几何体实例
184
+ * @param {FillFeature} feature
185
+ * @param {number} granularity
186
+ */
187
+ addFeature(feature, granularity) {
188
+ const geometryInstances = this.geometryInstances
189
+ const { coordinates, fillColor, fillOpacity } = feature
190
+ const colorBytes = fillColor.toBytes()
191
+ colorBytes[3] = Math.floor(colorBytes[3] * fillOpacity)
192
+
193
+ // 使用 maplibre-gl 的 subdividePolygon 基于投影坐标进行细分,
194
+ // 而不是在转成世界坐标后再使用 Cesium.PolygonGeometry 构建,这样才能避免出现自相交、破面等现象。
195
+ // 商业版性能优化:将此过程移到 Web Worker ,多线程加速,同时避免主线程阻塞
196
+
197
+ const subdivisionRes = subdividePolygon(
198
+ coordinates,
199
+ this.tile,
200
+ granularity,
201
+ false
202
+ )
203
+ const verticesFlattened = subdivisionRes.verticesFlattened
204
+ const coordDeg = [0, 0],
205
+ cartesian = new Cesium.Cartesian3()
206
+ const vertCount = verticesFlattened.length / 2
207
+ const positions = new Float64Array(vertCount * 3)
208
+ const normals = new Float32Array(vertCount * 3)
209
+ const sts = new Float32Array(vertCount * 2)
210
+
211
+ for (let i = 0, j = 0; i < verticesFlattened.length; i += 2, j++) {
212
+ const x = verticesFlattened[i],
213
+ y = verticesFlattened[i + 1]
214
+ const coord = this.tile.transformPoint(x, y, coordDeg)
215
+ const position = Cesium.Cartesian3.fromDegrees(
216
+ coord[0],
217
+ coord[1],
218
+ 0,
219
+ null,
220
+ cartesian
221
+ )
222
+ positions[j * 3] = position.x
223
+ positions[j * 3 + 1] = position.y
224
+ positions[j * 3 + 2] = position.z
225
+
226
+ const normal = Cesium.Cartesian3.normalize(position, position)
227
+ normals[j * 3] = normal.x
228
+ normals[j * 3 + 1] = normal.y
229
+ normals[j * 3 + 2] = normal.z
230
+
231
+ sts[j * 2] = x / EXTENT
232
+ sts[j * 2 + 1] = y / EXTENT
233
+ }
234
+
235
+ const indices = new (
236
+ vertCount > 65535
237
+ ? Uint32Array
238
+ : vertCount > 255
239
+ ? Uint16Array
240
+ : Uint8Array
241
+ )(subdivisionRes.indicesTriangles)
242
+
243
+ const geometry = new Cesium.Geometry({
244
+ attributes: {
245
+ position: {
246
+ componentDatatype: Cesium.ComponentDatatype.DOUBLE,
247
+ componentsPerAttribute: 3,
248
+ normalize: false,
249
+ values: positions
250
+ },
251
+ normal: {
252
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
253
+ componentsPerAttribute: 3,
254
+ normalize: false,
255
+ values: normals
256
+ },
257
+ st: {
258
+ componentDatatype: Cesium.ComponentDatatype.FLOAT,
259
+ componentsPerAttribute: 2,
260
+ normalize: false,
261
+ values: sts
262
+ }
263
+ },
264
+ primitiveType: Cesium.PrimitiveType.TRIANGLES,
265
+ indices: indices,
266
+ boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
267
+ })
268
+
269
+ const cartographic = Cesium.Cartographic.fromCartesian(
270
+ geometry.boundingSphere.center
271
+ )
272
+ cartographic.height = 0 //包围盒中心可能高于或者低于地面,需要避免双击锁定视角时进入地下
273
+ const center = Cesium.Cartographic.toCartesian(
274
+ cartographic,
275
+ null,
276
+ cartesian
277
+ )
278
+
279
+ const instance = new Cesium.GeometryInstance({
280
+ geometry,
281
+ attributes: {
282
+ color: new Cesium.GeometryInstanceAttribute({
283
+ componentDatatype: Cesium.ComponentDatatype.UNSIGNED_BYTE,
284
+ componentsPerAttribute: 4,
285
+ normalize: true,
286
+ value: colorBytes
287
+ })
288
+ },
289
+ //通过entity的形式暴露给Cesium pickEntity,这样点击时系统自带的inforbox可以弹出
290
+ id: new Cesium.Entity({
291
+ position: center,
292
+ id: feature.id,
293
+ properties: feature.properties
294
+ })
295
+ })
296
+ geometryInstances.push(instance)
297
+ }
298
+
299
+ createPrimitive() {
300
+ const primitive = new Cesium.Primitive({
301
+ geometryInstances: this.geometryInstances,
302
+ asynchronous: !(
303
+ this.geometryInstances[0].geometry instanceof Cesium.Geometry
304
+ ),
305
+ appearance: new Cesium.PerInstanceColorAppearance({
306
+ flat: true,
307
+ translucent: false,
308
+ renderState: {
309
+ //这里设置是没有用的,只要 translucent 为 false,
310
+ //Cesium 内部都会覆盖成 true,所以我们需要在 DrawCommand 创建完成后再设置
311
+ depthMask: false
312
+ },
313
+ fragmentShaderSource: /*glsl*/ `
314
+ in vec4 v_color;
315
+
316
+ uniform vec4 tileId;
317
+ uniform sampler2D tileIdTexture;
318
+
319
+ void main()
320
+ {
321
+ vec2 id_st = gl_FragCoord.xy / czm_viewport.zw;
322
+ vec4 bgId = texture(tileIdTexture, id_st);
323
+ if (!all(equal(bgId, tileId)))
324
+ {
325
+ discard;
326
+ }
327
+ out_FragColor = v_color;
328
+ }
329
+ `
330
+ })
331
+ })
332
+
333
+ //通过定义 Primitive 私有变量 _geometries、_batchTable 的 setter 和 getter,
334
+ //监听合批几何体和批次表的创建:
335
+ //1、几何体创建完成后,根据 batchId featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
336
+ //2、批次表创建完成后,保存备用
337
+ let scope = this
338
+ Object.defineProperties(primitive, {
339
+ _geometries: {
340
+ get() {
341
+ return this._geometries_
342
+ },
343
+ set(geometries) {
344
+ this._geometries_ = geometries
345
+ if (geometries) {
346
+ scope.onGeometriesLoaded(geometries)
347
+ } else {
348
+ scope = null
349
+ }
350
+ }
351
+ },
352
+ _batchTable: {
353
+ get() {
354
+ return this._batchTable_
355
+ },
356
+ set(batchTable) {
357
+ this._batchTable_ = batchTable
358
+ if (batchTable) {
359
+ scope.onBatchTableCreated(batchTable)
360
+ }
361
+ }
362
+ }
363
+ })
364
+
365
+ this.primitive = primitive
366
+ }
367
+
368
+ /**
369
+ * 根据 batchId 和 featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
370
+ * @param {Cesium.Geometry[]} geometries
371
+ */
372
+ onGeometriesLoaded(geometries) {
373
+ //Cesium 几何体合批结果可能是多个几何体,对应多个 DrawCommand
374
+ for (let pass = 0; pass < geometries.length; pass++) {
375
+ const batches = {}
376
+ const geometry = geometries[pass]
377
+ const batchIds = geometry.attributes.batchId.values
378
+ const indices = geometry.indices
379
+
380
+ //提取每个批次的起始和结束索引
381
+ let currBatchId = -1
382
+ let currBatch = null
383
+ for (let i = 0; i < indices.length; i++) {
384
+ const vertIndex = indices[i]
385
+ const batchId = batchIds[vertIndex]
386
+ if (currBatchId !== batchId) {
387
+ currBatchId = batchId
388
+ currBatch = batches[currBatchId] = {
389
+ begin: i,
390
+ end: i
391
+ }
392
+ }
393
+ currBatch.end = i
394
+ }
395
+
396
+ //根据图层批次范围,提取图层几何体索引范围,即起始索引(offset)和索引数量(count)
397
+ for (const layer of this.layers) {
398
+ const { firstBatchId, lastBatchId } = layer
399
+ if (firstBatchId === -1 || lastBatchId === -1) {
400
+ continue
401
+ }
402
+
403
+ let begin = -1,
404
+ end = -1
405
+ for (let batchId = firstBatchId; batchId <= lastBatchId; batchId++) {
406
+ const batch = batches[batchId]
407
+ if (batch) {
408
+ if (begin === -1) begin = batch.begin
409
+ end = batch.end
410
+ }
411
+ }
412
+
413
+ if (begin === -1 || end === -1) {
414
+ continue
415
+ }
416
+
417
+ //起始和结束索引,索引数量需要加1
418
+ layer.offsets[pass] = begin
419
+ layer.counts[pass] = end - begin + 1
420
+ }
421
+ }
422
+ }
423
+
424
+ /**
425
+ * 保存 Cesium Primitive 创建的批次表。图层样式变化时,通过更新批次表传递到GPU,同步更新渲染效果
426
+ * @param {Cesium.BatchTable} batchTable
427
+ */
428
+ onBatchTableCreated(batchTable) {
429
+ this._batchTable = batchTable
430
+ }
431
+
432
+ /**
433
+ * 使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
434
+ * @param {Cesium.DrawCommand[]} batchedCommandList
435
+ * @param {VectorTileset} tileset
436
+ */
437
+ createLayerCommands(batchedCommandList, tileset) {
438
+ const renderState = Cesium.RenderState.fromCache({
439
+ id: 'fill',
440
+ blending: Cesium.BlendingState.ALPHA_BLEND,
441
+ depthMask: false,
442
+ depthTest: {
443
+ enabled: true
444
+ },
445
+ cull: {
446
+ enabled: true
447
+ }
448
+ })
449
+ const tileId = this.tile.tileId
450
+ this.renderState = renderState
451
+
452
+ for (let i = 0; i < this.layers.length; i++) {
453
+ const layer = this.layers[i]
454
+ const layerCommandList = (layer.commandList = [])
455
+
456
+ for (let pass = 0; pass < batchedCommandList.length; pass++) {
457
+ const offset = layer.offsets[pass],
458
+ count = layer.counts[pass]
459
+ if (typeof offset !== 'number' || typeof count !== 'number') {
460
+ continue
461
+ }
462
+ const command = batchedCommandList[pass]
463
+ command.uniformMap.tileIdTexture = function () {
464
+ return tileset.tileIdTexture
465
+ }
466
+ command.uniformMap.tileId = function () {
467
+ return tileId.color
468
+ }
469
+ command.pass = Cesium.Pass.CESIUM_3D_TILE
470
+ //通过副本的 offset 和 count 指定图层的绘制范围
471
+ const layerCommand = Cesium.DrawCommand.shallowClone(command)
472
+ layerCommand.pass = Cesium.Pass.CESIUM_3D_TILE
473
+ layerCommand.renderState = renderState
474
+ layerCommand.layerType = 'fill'
475
+ layerCommand.offset = offset
476
+ layerCommand.count = count
477
+ layerCommandList.push(layerCommand)
478
+ }
479
+
480
+ layer.state = 'done'
481
+ }
482
+
483
+ //标记 drawCommand 创建完成
484
+ this.state = 'done'
485
+ }
486
+
487
+ update(frameState, tileset) {
488
+ if (!this.geometryInstances) {
489
+ return
490
+ }
491
+
492
+ super.update(frameState, tileset)
493
+
494
+ if (!this.primitive && this.geometryInstances.length) {
495
+ this.createPrimitive()
496
+ }
497
+
498
+ if (this.primitive && this.state !== 'done' && this.state !== 'error') {
499
+ //先保存系统的 commandList
500
+ const preCommandList = frameState.commandList
501
+ //临时覆盖 frameState.commandList,用于获取合批之后的drawCommand
502
+ const batchedCommandList = (frameState.commandList = [])
503
+
504
+ //执行 primitive 的 update ,直到生成了合批后的drawCommand
505
+ try {
506
+ this.primitive.update(frameState)
507
+ } catch (err) {
508
+ //如果报错,下一帧就不要再执行 update 了,以免重复打印错误信息
509
+ this.geometryInstances = []
510
+ this.setState('error')
511
+ if (err.stack) console.trace(err.stack)
512
+ else console.error(err)
513
+ return
514
+ }
515
+
516
+ //使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
517
+ if (batchedCommandList.length > 0) {
518
+ this.createLayerCommands(batchedCommandList, tileset)
519
+ }
520
+
521
+ if (this.primitive._state === Cesium.PrimitiveState.FAILED) {
522
+ this.setState('error')
523
+ }
524
+
525
+ //恢复系统的 commandList
526
+ frameState.commandList = preCommandList
527
+ this.geometryInstances = []
528
+ }
529
+ }
530
+
531
+ destroy() {
532
+ this.primitive = this.primitive && this.primitive.destroy()
533
+ this._batchTable = null
534
+ this.geometryInstances = null
535
+
536
+ super.destroy()
537
+ }
538
+
539
+ isDestroyed() {
540
+ return false
541
+ }
542
+ }