@mesh3d/cesium-vectortile-gl 0.5.1 → 0.5.2
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/Source/layers/FillRenderLayer.js +5 -0
- package/Source/layers/visualizers/FillLayerVisualizer.js +215 -7
- package/Source/style/StyleLayer.js +1 -0
- package/logo.svg +46 -46
- package/package.json +1 -1
- package/dist/THIRD-PARTY-LICENSES.txt +0 -772
- package/dist/cvt-gl-worker.js +0 -9274
- package/dist/cvt-gl-worker.js.map +0 -1
- package/dist/cvt-gl.js +0 -13871
- package/dist/cvt-gl.js.map +0 -1
- package/dist/cvt-gl.min.js +0 -135
- package/dist/cvt-gl.min.js.map +0 -1
|
@@ -4,6 +4,11 @@ import { registerRenderLayer } from './registerRenderLayer'
|
|
|
4
4
|
import { FillLayerVisualizer } from './visualizers/FillLayerVisualizer'
|
|
5
5
|
|
|
6
6
|
export class FillRenderLayer extends IRenderLayer {
|
|
7
|
+
constructor(sourceFeatures, styleLayer, tile) {
|
|
8
|
+
super(sourceFeatures, styleLayer, tile)
|
|
9
|
+
this.outlineOffsets = []
|
|
10
|
+
this.outlineCounts = []
|
|
11
|
+
}
|
|
7
12
|
/**
|
|
8
13
|
* @param {Cesium.FrameState} frameState
|
|
9
14
|
* @param {VectorTileset} tileset
|
|
@@ -22,6 +22,7 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
22
22
|
super(layers, tile)
|
|
23
23
|
|
|
24
24
|
this.geometryInstances = []
|
|
25
|
+
this.outlineGeometryInstances = []
|
|
25
26
|
this.primitive = null
|
|
26
27
|
this.commandsReady = false
|
|
27
28
|
}
|
|
@@ -67,6 +68,11 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
67
68
|
sourceFeature
|
|
68
69
|
)
|
|
69
70
|
|
|
71
|
+
const fillOutlineColor =
|
|
72
|
+
style.convertColor(
|
|
73
|
+
style.paint.getDataValue('fill-outline-color', tile.z, sourceFeature)
|
|
74
|
+
) || fillColor
|
|
75
|
+
|
|
70
76
|
//关键:对投影坐标细分,而不是使用cesium内置的细分
|
|
71
77
|
const vtCoords = loadGeometry(sourceFeature)
|
|
72
78
|
const polygons = classifyRings(vtCoords)
|
|
@@ -84,6 +90,7 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
84
90
|
featureId,
|
|
85
91
|
fillColor,
|
|
86
92
|
fillOpacity,
|
|
93
|
+
fillOutlineColor,
|
|
87
94
|
properties,
|
|
88
95
|
//保存原始数据的要素id,后续可以用来支持 featureState 表达式,这个表达式可以实现选定要素高亮显示
|
|
89
96
|
id: sourceFeatureId,
|
|
@@ -187,9 +194,12 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
187
194
|
*/
|
|
188
195
|
addFeature(feature, granularity) {
|
|
189
196
|
const geometryInstances = this.geometryInstances
|
|
190
|
-
const
|
|
197
|
+
const outlineGeometryInstances = this.outlineGeometryInstances
|
|
198
|
+
const { coordinates, fillColor, fillOpacity, fillOutlineColor } = feature
|
|
191
199
|
const colorBytes = fillColor.toBytes()
|
|
192
200
|
colorBytes[3] = Math.floor(colorBytes[3] * fillOpacity)
|
|
201
|
+
const outlineColorBytes = fillOutlineColor.toBytes()
|
|
202
|
+
outlineColorBytes[3] = Math.floor(outlineColorBytes[3] * fillOpacity)
|
|
193
203
|
|
|
194
204
|
// 使用 maplibre-gl 的 subdividePolygon 基于投影坐标进行细分,
|
|
195
205
|
// 而不是在转成世界坐标后再使用 Cesium.PolygonGeometry 构建,这样才能避免出现自相交、破面等现象。
|
|
@@ -199,7 +209,7 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
199
209
|
coordinates,
|
|
200
210
|
this.tile,
|
|
201
211
|
granularity,
|
|
202
|
-
|
|
212
|
+
true
|
|
203
213
|
)
|
|
204
214
|
const verticesFlattened = subdivisionRes.verticesFlattened
|
|
205
215
|
const coordDeg = [0, 0],
|
|
@@ -241,6 +251,14 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
241
251
|
: Uint8Array
|
|
242
252
|
)(subdivisionRes.indicesTriangles)
|
|
243
253
|
|
|
254
|
+
const outlineIndices = new (
|
|
255
|
+
vertCount > 65535
|
|
256
|
+
? Uint32Array
|
|
257
|
+
: vertCount > 255
|
|
258
|
+
? Uint16Array
|
|
259
|
+
: Uint8Array
|
|
260
|
+
)(subdivisionRes.indicesLineList.flat(3))
|
|
261
|
+
|
|
244
262
|
const geometry = new Cesium.Geometry({
|
|
245
263
|
attributes: {
|
|
246
264
|
position: {
|
|
@@ -266,6 +284,19 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
266
284
|
indices: indices,
|
|
267
285
|
boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
|
|
268
286
|
})
|
|
287
|
+
const outlineGeometry = new Cesium.Geometry({
|
|
288
|
+
attributes: {
|
|
289
|
+
position: {
|
|
290
|
+
componentDatatype: Cesium.ComponentDatatype.DOUBLE,
|
|
291
|
+
componentsPerAttribute: 3,
|
|
292
|
+
normalize: false,
|
|
293
|
+
values: positions
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
primitiveType: Cesium.PrimitiveType.LINES,
|
|
297
|
+
indices: outlineIndices,
|
|
298
|
+
boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
|
|
299
|
+
})
|
|
269
300
|
|
|
270
301
|
const cartographic = Cesium.Cartographic.fromCartesian(
|
|
271
302
|
geometry.boundingSphere.center
|
|
@@ -295,6 +326,25 @@ export class FillLayerVisualizer extends ILayerVisualizer {
|
|
|
295
326
|
})
|
|
296
327
|
})
|
|
297
328
|
geometryInstances.push(instance)
|
|
329
|
+
|
|
330
|
+
const outlineInstance = new Cesium.GeometryInstance({
|
|
331
|
+
geometry: outlineGeometry,
|
|
332
|
+
attributes: {
|
|
333
|
+
color: new Cesium.GeometryInstanceAttribute({
|
|
334
|
+
componentDatatype: Cesium.ComponentDatatype.UNSIGNED_BYTE,
|
|
335
|
+
componentsPerAttribute: 4,
|
|
336
|
+
normalize: true,
|
|
337
|
+
value: outlineColorBytes
|
|
338
|
+
})
|
|
339
|
+
},
|
|
340
|
+
//通过entity的形式暴露给Cesium pickEntity,这样点击时系统自带的inforbox可以弹出
|
|
341
|
+
id: new Cesium.Entity({
|
|
342
|
+
position: center,
|
|
343
|
+
id: feature.id,
|
|
344
|
+
properties: feature.properties
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
outlineGeometryInstances.push(outlineInstance)
|
|
298
348
|
}
|
|
299
349
|
|
|
300
350
|
createPrimitive() {
|
|
@@ -433,6 +483,140 @@ void main()
|
|
|
433
483
|
}
|
|
434
484
|
}
|
|
435
485
|
|
|
486
|
+
createOutlinePrimitive() {
|
|
487
|
+
const primitive = new Cesium.Primitive({
|
|
488
|
+
geometryInstances: this.outlineGeometryInstances,
|
|
489
|
+
asynchronous: !(
|
|
490
|
+
this.outlineGeometryInstances[0].geometry instanceof Cesium.Geometry
|
|
491
|
+
),
|
|
492
|
+
appearance: new Cesium.PerInstanceColorAppearance({
|
|
493
|
+
flat: true,
|
|
494
|
+
translucent: false,
|
|
495
|
+
renderState: {
|
|
496
|
+
//这里设置是没有用的,只要 translucent 为 false,
|
|
497
|
+
//Cesium 内部都会覆盖成 true,所以我们需要在 DrawCommand 创建完成后再设置
|
|
498
|
+
depthMask: false
|
|
499
|
+
},
|
|
500
|
+
fragmentShaderSource: /*glsl*/ `
|
|
501
|
+
in vec4 v_color;
|
|
502
|
+
|
|
503
|
+
uniform vec4 tileId;
|
|
504
|
+
uniform sampler2D tileIdTexture;
|
|
505
|
+
|
|
506
|
+
void main()
|
|
507
|
+
{
|
|
508
|
+
vec2 id_st = gl_FragCoord.xy / czm_viewport.zw;
|
|
509
|
+
vec4 bgId = texture(tileIdTexture, id_st);
|
|
510
|
+
if (!all(equal(bgId, tileId)))
|
|
511
|
+
{
|
|
512
|
+
discard;
|
|
513
|
+
}
|
|
514
|
+
out_FragColor = v_color;
|
|
515
|
+
}
|
|
516
|
+
`
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
//通过定义 Primitive 私有变量 _geometries、_batchTable 的 setter 和 getter,
|
|
521
|
+
//监听合批几何体和批次表的创建:
|
|
522
|
+
//1、几何体创建完成后,根据 batchId 和 featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
|
|
523
|
+
//2、批次表创建完成后,保存备用
|
|
524
|
+
let scope = this
|
|
525
|
+
Object.defineProperties(primitive, {
|
|
526
|
+
_geometries: {
|
|
527
|
+
get() {
|
|
528
|
+
return this._geometries_
|
|
529
|
+
},
|
|
530
|
+
set(geometries) {
|
|
531
|
+
this._geometries_ = geometries
|
|
532
|
+
if (geometries) {
|
|
533
|
+
scope.onOutlineGeometriesLoaded(geometries)
|
|
534
|
+
} else {
|
|
535
|
+
scope = null
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
_batchTable: {
|
|
540
|
+
get() {
|
|
541
|
+
return this._batchTable_
|
|
542
|
+
},
|
|
543
|
+
set(batchTable) {
|
|
544
|
+
this._batchTable_ = batchTable
|
|
545
|
+
if (batchTable) {
|
|
546
|
+
scope.onOutlineBatchTableCreated(batchTable)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
this.outlinePrimitive = primitive
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* 根据 batchId 和 featureId,计算每个图层几何体的起始索引(offset)和索引数量(count)
|
|
556
|
+
* @param {Cesium.Geometry[]} geometries
|
|
557
|
+
*/
|
|
558
|
+
onOutlineGeometriesLoaded(geometries) {
|
|
559
|
+
//Cesium 几何体合批结果可能是多个几何体,对应多个 DrawCommand
|
|
560
|
+
for (let pass = 0; pass < geometries.length; pass++) {
|
|
561
|
+
const batches = {}
|
|
562
|
+
const geometry = geometries[pass]
|
|
563
|
+
const batchIds = geometry.attributes.batchId.values
|
|
564
|
+
const indices = geometry.indices
|
|
565
|
+
|
|
566
|
+
//提取每个批次的起始和结束索引
|
|
567
|
+
let currBatchId = -1
|
|
568
|
+
let currBatch = null
|
|
569
|
+
for (let i = 0; i < indices.length; i++) {
|
|
570
|
+
const vertIndex = indices[i]
|
|
571
|
+
const batchId = batchIds[vertIndex]
|
|
572
|
+
if (currBatchId !== batchId) {
|
|
573
|
+
currBatchId = batchId
|
|
574
|
+
currBatch = batches[currBatchId] = {
|
|
575
|
+
begin: i,
|
|
576
|
+
end: i
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
currBatch.end = i
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
//根据图层批次范围,提取图层几何体索引范围,即起始索引(offset)和索引数量(count)
|
|
583
|
+
for (const layer of this.layers) {
|
|
584
|
+
const { firstBatchId, lastBatchId } = layer
|
|
585
|
+
if (firstBatchId === -1 || lastBatchId === -1) {
|
|
586
|
+
continue
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
let begin = -1,
|
|
590
|
+
end = -1
|
|
591
|
+
for (let batchId = firstBatchId; batchId <= lastBatchId; batchId++) {
|
|
592
|
+
const batch = batches[batchId]
|
|
593
|
+
if (batch) {
|
|
594
|
+
if (begin === -1) begin = batch.begin
|
|
595
|
+
end = batch.end
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (begin === -1 || end === -1) {
|
|
600
|
+
continue
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
//起始和结束索引,索引数量需要加1
|
|
604
|
+
layer.outlineOffsets[pass] = begin
|
|
605
|
+
layer.outlineCounts[pass] = end - begin + 1
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* 保存 Cesium Primitive 创建的批次表。图层样式变化时,通过更新批次表传递到GPU,同步更新渲染效果
|
|
611
|
+
* @param {Cesium.BatchTable} batchTable
|
|
612
|
+
*/
|
|
613
|
+
onOutlineBatchTableCreated(batchTable) {
|
|
614
|
+
this._outlineBatchTable = batchTable
|
|
615
|
+
for (const layer of this.layers) {
|
|
616
|
+
layer._outlineBatchTable = batchTable
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
436
620
|
/**
|
|
437
621
|
* 使用合批后的 drawCommand 创建副本,为渲染图层分配 drawCommand
|
|
438
622
|
* @param {Cesium.DrawCommand[]} batchedCommandList
|
|
@@ -456,14 +640,29 @@ void main()
|
|
|
456
640
|
for (let i = 0; i < this.layers.length; i++) {
|
|
457
641
|
const layer = this.layers[i]
|
|
458
642
|
const layerCommandList = (layer.commandList = [])
|
|
643
|
+
const passes = {
|
|
644
|
+
fill: 0,
|
|
645
|
+
outline: 0
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
for (let i = 0; i < batchedCommandList.length; i++) {
|
|
649
|
+
const command = batchedCommandList[i]
|
|
650
|
+
let pass, offsets, counts
|
|
651
|
+
if (command.primitiveType == Cesium.PrimitiveType.LINES) {
|
|
652
|
+
pass = passes.outline++
|
|
653
|
+
offsets = layer.outlineOffsets
|
|
654
|
+
counts = layer.outlineCounts
|
|
655
|
+
} else {
|
|
656
|
+
pass = passes.fill++
|
|
657
|
+
offsets = layer.offsets
|
|
658
|
+
counts = layer.counts
|
|
659
|
+
}
|
|
459
660
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
count = layer.counts[pass]
|
|
661
|
+
const offset = offsets[pass],
|
|
662
|
+
count = counts[pass]
|
|
463
663
|
if (typeof offset !== 'number' || typeof count !== 'number') {
|
|
464
664
|
continue
|
|
465
665
|
}
|
|
466
|
-
const command = batchedCommandList[pass]
|
|
467
666
|
command.uniformMap.tileIdTexture = function () {
|
|
468
667
|
return tileset.tileIdTexture
|
|
469
668
|
}
|
|
@@ -497,6 +696,7 @@ void main()
|
|
|
497
696
|
|
|
498
697
|
if (!this.primitive && this.geometryInstances.length) {
|
|
499
698
|
this.createPrimitive()
|
|
699
|
+
this.createOutlinePrimitive()
|
|
500
700
|
}
|
|
501
701
|
|
|
502
702
|
if (this.primitive && this.state !== 'done' && this.state !== 'error') {
|
|
@@ -508,9 +708,11 @@ void main()
|
|
|
508
708
|
//执行 primitive 的 update ,直到生成了合批后的drawCommand
|
|
509
709
|
try {
|
|
510
710
|
this.primitive.update(frameState)
|
|
711
|
+
this.outlinePrimitive.update(frameState)
|
|
511
712
|
} catch (err) {
|
|
512
713
|
//如果报错,下一帧就不要再执行 update 了,以免重复打印错误信息
|
|
513
714
|
this.geometryInstances = []
|
|
715
|
+
this.outlineGeometryInstances = []
|
|
514
716
|
this.setState('error')
|
|
515
717
|
if (err.stack) console.trace(err.stack)
|
|
516
718
|
else console.error(err)
|
|
@@ -525,7 +727,10 @@ void main()
|
|
|
525
727
|
this.createLayerCommands(batchedCommandList, tileset)
|
|
526
728
|
}
|
|
527
729
|
|
|
528
|
-
if (
|
|
730
|
+
if (
|
|
731
|
+
this.primitive._state === Cesium.PrimitiveState.FAILED ||
|
|
732
|
+
this.outlinePrimitive._state === Cesium.PrimitiveState.FAILED
|
|
733
|
+
) {
|
|
529
734
|
this.setState('error')
|
|
530
735
|
}
|
|
531
736
|
|
|
@@ -535,6 +740,9 @@ void main()
|
|
|
535
740
|
if (this._batchTable && this._batchTable._batchValuesDirty) {
|
|
536
741
|
this._batchTable.update(frameState)
|
|
537
742
|
}
|
|
743
|
+
if (this._outlineBatchTable && this._outlineBatchTable._batchValuesDirty) {
|
|
744
|
+
this._outlineBatchTable.update(frameState)
|
|
745
|
+
}
|
|
538
746
|
}
|
|
539
747
|
|
|
540
748
|
destroy() {
|
package/logo.svg
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
<svg width="400" height="400" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<!-- 背景透明 -->
|
|
3
|
-
<rect width="400" height="400" fill="transparent"/>
|
|
4
|
-
|
|
5
|
-
<!-- 地球:经纬线 + 瓦片网格 -->
|
|
6
|
-
<g stroke="#2196F3" stroke-width="1.5" fill="none" opacity="0.8">
|
|
7
|
-
<!-- 外圆(赤道) -->
|
|
8
|
-
<circle cx="200" cy="200" r="70" />
|
|
9
|
-
|
|
10
|
-
<!-- 经线(3条) -->
|
|
11
|
-
<path d="M200,130 A70,70 0 0,1 200,270" />
|
|
12
|
-
<path d="M200,130 A70,70 0 0,0 200,270" />
|
|
13
|
-
<line x1="130" y1="200" x2="270" y2="200" />
|
|
14
|
-
|
|
15
|
-
<!-- 瓦片网格(示意 MVT 分块) -->
|
|
16
|
-
<rect x="165" y="165" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
17
|
-
<rect x="200" y="165" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
18
|
-
<rect x="165" y="200" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
19
|
-
</g>
|
|
20
|
-
|
|
21
|
-
<!-- Mapbox 风格水滴(POI) -->
|
|
22
|
-
<path d="M200,165
|
|
23
|
-
C205,160 210,165 210,170
|
|
24
|
-
C210,190 200,200 200,200
|
|
25
|
-
C200,200 190,190 190,170
|
|
26
|
-
C190,165 195,160 200,165 Z"
|
|
27
|
-
fill="#2196F3" opacity="0.95"/>
|
|
28
|
-
|
|
29
|
-
<!-- 矢量数据流(从水滴延伸出的 MVT 折线) -->
|
|
30
|
-
<path d="M200,200
|
|
31
|
-
L210,210
|
|
32
|
-
L230,205
|
|
33
|
-
L250,220
|
|
34
|
-
L270,210"
|
|
35
|
-
fill="none"
|
|
36
|
-
stroke="#2196F3"
|
|
37
|
-
stroke-width="2.5"
|
|
38
|
-
stroke-linecap="round"
|
|
39
|
-
stroke-linejoin="round"/>
|
|
40
|
-
|
|
41
|
-
<!-- 项目名称 -->
|
|
42
|
-
<text x="200" y="330" text-anchor="middle"
|
|
43
|
-
font-family="ui-monospace, SFMono-Regular, 'JetBrains Mono', monospace"
|
|
44
|
-
font-size="24" fill="#2196F3" font-weight="600">
|
|
45
|
-
CVT-GL
|
|
46
|
-
</text>
|
|
1
|
+
<svg width="400" height="400" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- 背景透明 -->
|
|
3
|
+
<rect width="400" height="400" fill="transparent"/>
|
|
4
|
+
|
|
5
|
+
<!-- 地球:经纬线 + 瓦片网格 -->
|
|
6
|
+
<g stroke="#2196F3" stroke-width="1.5" fill="none" opacity="0.8">
|
|
7
|
+
<!-- 外圆(赤道) -->
|
|
8
|
+
<circle cx="200" cy="200" r="70" />
|
|
9
|
+
|
|
10
|
+
<!-- 经线(3条) -->
|
|
11
|
+
<path d="M200,130 A70,70 0 0,1 200,270" />
|
|
12
|
+
<path d="M200,130 A70,70 0 0,0 200,270" />
|
|
13
|
+
<line x1="130" y1="200" x2="270" y2="200" />
|
|
14
|
+
|
|
15
|
+
<!-- 瓦片网格(示意 MVT 分块) -->
|
|
16
|
+
<rect x="165" y="165" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
17
|
+
<rect x="200" y="165" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
18
|
+
<rect x="165" y="200" width="35" height="35" stroke-dasharray="2,2" opacity="0.6"/>
|
|
19
|
+
</g>
|
|
20
|
+
|
|
21
|
+
<!-- Mapbox 风格水滴(POI) -->
|
|
22
|
+
<path d="M200,165
|
|
23
|
+
C205,160 210,165 210,170
|
|
24
|
+
C210,190 200,200 200,200
|
|
25
|
+
C200,200 190,190 190,170
|
|
26
|
+
C190,165 195,160 200,165 Z"
|
|
27
|
+
fill="#2196F3" opacity="0.95"/>
|
|
28
|
+
|
|
29
|
+
<!-- 矢量数据流(从水滴延伸出的 MVT 折线) -->
|
|
30
|
+
<path d="M200,200
|
|
31
|
+
L210,210
|
|
32
|
+
L230,205
|
|
33
|
+
L250,220
|
|
34
|
+
L270,210"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="#2196F3"
|
|
37
|
+
stroke-width="2.5"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"/>
|
|
40
|
+
|
|
41
|
+
<!-- 项目名称 -->
|
|
42
|
+
<text x="200" y="330" text-anchor="middle"
|
|
43
|
+
font-family="ui-monospace, SFMono-Regular, 'JetBrains Mono', monospace"
|
|
44
|
+
font-size="24" fill="#2196F3" font-weight="600">
|
|
45
|
+
CVT-GL
|
|
46
|
+
</text>
|
|
47
47
|
</svg>
|