@ztgkzhaohao/geo-effect-kit 0.1.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BoundingSphere, BillboardGraphics, CallbackPositionProperty, CallbackProperty, Cartesian2, Cartesian3, Cartesian4, CircleGeometry, Color, ColorMaterialProperty, ConstantPositionProperty, ConstantProperty, CustomDataSource, EllipsoidSurfaceAppearance, Event, GeometryInstance, GroundPrimitive, HeadingPitchRoll, HeadingPitchRange, HeightReference, HorizontalOrigin, Material, Math as CesiumMath, PointGraphics, PolylineGlowMaterialProperty, PolygonGeometry, PolygonHierarchy, PostProcessStage, Primitive, Rectangle, Transforms, VerticalOrigin, } from 'cesium';
1
+ import { BoundingSphere, BillboardGraphics, CallbackPositionProperty, CallbackProperty, Cartesian2, Cartesian3, Cartesian4, CircleGeometry, Color, ColorMaterialProperty, ConstantPositionProperty, ConstantProperty, CustomDataSource, EllipsoidSurfaceAppearance, Event, GeometryInstance, GroundPrimitive, HeadingPitchRoll, HeadingPitchRange, HeightReference, HorizontalOrigin, Material, Math as CesiumMath, PointGraphics, PolylineArrowMaterialProperty, PolylineDashMaterialProperty, PolylineGlowMaterialProperty, PolylineOutlineMaterialProperty, PolygonGeometry, PolygonHierarchy, PostProcessStage, Primitive, Rectangle, Transforms, VerticalOrigin, } from 'cesium';
2
2
  import { decompressFrames, parseGIF } from 'gifuct-js';
3
3
  export const GEO_RADAR_SCAN_MATERIAL_TYPE = 'GeoRadarScanMaterial';
4
4
  export const GEO_RIPPLE_SPREAD_MATERIAL_TYPE = 'GeoRippleSpreadMaterial';
@@ -7,16 +7,42 @@ export const GEO_SCAN_CONE_MATERIAL_TYPE = 'GeoScanConeMaterial';
7
7
  export const GEO_SHIELD_DOME_MATERIAL_TYPE = 'GeoShieldDomeMaterial';
8
8
  export const GEO_TEMPERATURE_FIELD_MATERIAL_TYPE = 'GeoTemperatureFieldMaterial';
9
9
  export const GEO_WATER_SURFACE_MATERIAL_TYPE = 'GeoWaterSurfaceMaterial';
10
+ export const GEO_MATERIAL_POLYLINE_MATERIAL_TYPE = 'GeoMaterialPolylineMaterial';
10
11
  export const RADAR_SCAN_TYPE_VALUES = ['classic', 'sector', 'pulse', 'grid'];
11
12
  export const RIPPLE_SPREAD_TYPE_VALUES = ['water', 'energy', 'soft'];
12
13
  export const SCENE_WEATHER_TYPE_VALUES = ['rain', 'snow', 'fog', 'lightning'];
13
14
  export const POST_PROCESS_TYPE_VALUES = ['bloom', 'night-vision', 'black-white', 'brightness', 'mosaic', 'depth-of-field'];
14
15
  export const POLYLINE_FLOW_TYPE_VALUES = ['dispatch', 'migration', 'attack', 'comet', 'electric'];
16
+ export const MATERIAL_POLYLINE_STYLE_VALUES = [
17
+ 'solid',
18
+ 'outline',
19
+ 'arrow',
20
+ 'dash',
21
+ 'dual-dash',
22
+ 'flow',
23
+ 'flow-color',
24
+ 'three-dash',
25
+ 'cross',
26
+ 'navigation',
27
+ ];
28
+ export const MATERIAL_POLYLINE_IMAGE_PRESET_VALUES = [
29
+ 'pulse',
30
+ 'gradual',
31
+ 'arrow-blue',
32
+ 'rainbow',
33
+ 'arrow-repeat',
34
+ 'dovetail',
35
+ 'yellow-flow',
36
+ 'transparent-flow',
37
+ 'interval',
38
+ 'small-arrow',
39
+ 'gradient',
40
+ ];
15
41
  export const FLY_LINE_MODE_VALUES = ['single-arc', 'hub-spoke', 'bidirectional'];
16
42
  export const LIGHT_WALL_TYPE_VALUES = ['security', 'warning', 'data', 'fence', 'pulse'];
17
43
  export const SCAN_CONE_TYPE_VALUES = ['searchlight', 'radar', 'camera', 'drone', 'alarm'];
18
44
  export const SHIELD_DOME_TYPE_VALUES = ['hex', 'plasma', 'matrix', 'aegis', 'storm'];
19
- export const WATER_SURFACE_TYPE_VALUES = ['river', 'lake', 'flood'];
45
+ export const WATER_SURFACE_TYPE_VALUES = ['river', 'lake', 'flood', 'flow'];
20
46
  export function createRadarScanEffect(viewer, options) {
21
47
  return new RadarScanEffect(viewer, options);
22
48
  }
@@ -32,6 +58,9 @@ export function createPostProcessEffect(viewer, options = {}) {
32
58
  export function createPolylineFlowEffect(viewer, options) {
33
59
  return new PolylineFlowEffect(viewer, options);
34
60
  }
61
+ export function createMaterialPolylineEffect(viewer, options) {
62
+ return new MaterialPolylineEffect(viewer, options);
63
+ }
35
64
  export function createFlyLineEffect(viewer, options) {
36
65
  return new FlyLineEffect(viewer, options);
37
66
  }
@@ -125,6 +154,27 @@ export function normalizePolylineFlowOptions(options) {
125
154
  visible: options.visible ?? true,
126
155
  };
127
156
  }
157
+ export function normalizeMaterialPolylineOptions(options) {
158
+ const imagePreset = normalizeMaterialPolylineImagePreset(options.imagePreset);
159
+ return {
160
+ positions: normalizePositions(options.positions, false),
161
+ style: normalizeMaterialPolylineStyle(options.style),
162
+ color: options.color ?? '#33f7ff',
163
+ secondaryColor: options.secondaryColor ?? '#ffffff',
164
+ backgroundColor: options.backgroundColor ?? 'rgba(0, 64, 255, 0.35)',
165
+ width: Math.max(1, finiteOr(options.width ?? 8, 8)),
166
+ outlineWidth: Math.max(0, finiteOr(options.outlineWidth ?? 2, 2)),
167
+ speed: clamp(finiteOr(options.speed ?? 1, 1), 0.05, 8),
168
+ repeat: normalizeMaterialPolylineRepeat(options.repeat),
169
+ imagePreset,
170
+ image: options.image ?? getMaterialPolylinePresetImage(imagePreset),
171
+ arcHeight: Math.max(0, finiteOr(options.arcHeight ?? 0, 0)),
172
+ arcSamples: clampInteger(finiteOr(options.arcSamples ?? 48, 48), 2, 128),
173
+ cornerRadius: clamp(finiteOr(options.cornerRadius ?? 0, 0), 0, 0.45),
174
+ clampToGround: options.clampToGround ?? true,
175
+ visible: options.visible ?? true,
176
+ };
177
+ }
128
178
  export function normalizeFlyLineOptions(options) {
129
179
  return {
130
180
  lines: normalizeFlyLineRoutes(options.lines),
@@ -264,6 +314,14 @@ export function shouldRebuildPolylineFlow(previous, next) {
264
314
  previous.positions.length !== next.positions.length ||
265
315
  !positionsEqual(previous.positions, next.positions));
266
316
  }
317
+ export function shouldRebuildMaterialPolyline(previous, next) {
318
+ return (previous.clampToGround !== next.clampToGround ||
319
+ previous.arcHeight !== next.arcHeight ||
320
+ previous.arcSamples !== next.arcSamples ||
321
+ previous.cornerRadius !== next.cornerRadius ||
322
+ previous.positions.length !== next.positions.length ||
323
+ !positionsEqual(previous.positions, next.positions));
324
+ }
267
325
  export function shouldRebuildFlyLine(previous, next) {
268
326
  return (previous.mode !== next.mode ||
269
327
  previous.arcHeight !== next.arcHeight ||
@@ -285,8 +343,7 @@ export function shouldRebuildLightWall(previous, next) {
285
343
  export function shouldRebuildScanCone(previous, next) {
286
344
  return (previous.radiusMeters !== next.radiusMeters ||
287
345
  previous.lengthMeters !== next.lengthMeters ||
288
- previous.aperture !== next.aperture ||
289
- !positionsEqual([previous.center], [next.center]));
346
+ previous.aperture !== next.aperture);
290
347
  }
291
348
  export function shouldRebuildShieldDome(previous, next) {
292
349
  return previous.radiusMeters !== next.radiusMeters || !positionsEqual([previous.center], [next.center]);
@@ -416,6 +473,53 @@ export function buildRippleSpreadMaterialSource() {
416
473
  }
417
474
  `;
418
475
  }
476
+ export function buildMaterialPolylineMaterialSource() {
477
+ return `
478
+ // ${GEO_MATERIAL_POLYLINE_MATERIAL_TYPE}
479
+ czm_material czm_getMaterial(czm_materialInput materialInput)
480
+ {
481
+ czm_material material = czm_getDefaultMaterial(materialInput);
482
+ vec2 st = materialInput.st;
483
+ float time = fract(czm_frameNumber * 0.016667 * speed);
484
+ vec2 uv = vec2(fract(st.s * repeatX - time), fract(st.t * repeatY));
485
+ vec4 textureColor = texture(image, uv);
486
+ float flowEnabled = step(5.5, styleType) * (1.0 - step(6.5, styleType));
487
+ float flowColorEnabled = step(6.5, styleType) * (1.0 - step(7.5, styleType));
488
+ float threeDashEnabled = step(7.5, styleType) * (1.0 - step(8.5, styleType));
489
+ float crossEnabled = step(8.5, styleType) * (1.0 - step(9.5, styleType));
490
+ float navigationEnabled = step(9.5, styleType);
491
+ float dynamicEnabled = max(max(flowEnabled, flowColorEnabled), max(max(threeDashEnabled, crossEnabled), navigationEnabled));
492
+ float centerDistance = abs(st.t - 0.5);
493
+ float centerCore = smoothstep(0.18, 0.0, centerDistance);
494
+ float sideCore = smoothstep(0.08, 0.0, abs(centerDistance - 0.32));
495
+ float dash = smoothstep(0.68, 0.72, fract(st.s * repeatX - time));
496
+ float cross = max(
497
+ smoothstep(0.08, 0.0, centerDistance) * dash,
498
+ smoothstep(0.022, 0.0, abs(fract(st.s * repeatX - time) - 0.5))
499
+ );
500
+ float navCycle = fract(st.s * repeatX - time);
501
+ float navWhite = smoothstep(0.0, 0.08, navCycle) * (1.0 - smoothstep(0.22, 0.32, navCycle));
502
+ float navGreen = smoothstep(0.24, 0.34, navCycle) * (1.0 - smoothstep(0.86, 0.96, navCycle));
503
+ float textureAlpha = textureColor.a;
504
+ vec3 imageColor = textureColor.rgb;
505
+ float imageBrightness = max(max(imageColor.r, imageColor.g), imageColor.b);
506
+ float tintWeight = (1.0 - imageBrightness) * 0.45;
507
+ vec3 flowImageColor = mix(imageColor, imageColor * color.rgb, tintWeight);
508
+ vec3 flowColor = flowImageColor * flowEnabled + mix(color.rgb, secondaryColor.rgb, smoothstep(0.0, 1.0, uv.s)) * flowColorEnabled;
509
+ vec3 threeColor = (color.rgb * centerCore + secondaryColor.rgb * sideCore) * max(centerCore, sideCore) * threeDashEnabled;
510
+ vec3 crossColor = color.rgb * cross * crossEnabled;
511
+ vec3 navColor = (secondaryColor.rgb * navWhite + color.rgb * navGreen) * navigationEnabled;
512
+ vec3 mixed = flowColor + threeColor + crossColor + navColor;
513
+ float flowColorAlpha = smoothstep(0.02, 0.18, fract(uv.s)) * (1.0 - smoothstep(0.58, 0.98, fract(uv.s)));
514
+ float patternAlpha = max(max(textureAlpha * flowEnabled + flowColorAlpha * flowColorEnabled, max(centerCore, sideCore) * threeDashEnabled), max(cross * crossEnabled, max(navWhite, navGreen) * navigationEnabled));
515
+ float backgroundAlpha = backgroundColor.a * dynamicEnabled * (1.0 - patternAlpha) * 0.58;
516
+ material.diffuse = mix(backgroundColor.rgb, mixed, clamp(patternAlpha + 0.1, 0.0, 1.0));
517
+ material.emission = mixed * (0.32 + patternAlpha * 0.84);
518
+ material.alpha = clamp(backgroundAlpha + patternAlpha * color.a, 0.0, 1.0);
519
+ return material;
520
+ }
521
+ `;
522
+ }
419
523
  export function buildSceneWeatherPostProcessSource() {
420
524
  return `
421
525
  uniform sampler2D colorTexture;
@@ -725,6 +829,13 @@ export function buildWaterSurfaceMaterialSource() {
725
829
  float riverWave = mix(waveA, max(riverFlow, waveB * 0.72), 0.45);
726
830
  float wave = mix(waveA, max(riverWave, lakeShimmer), waveStrength);
727
831
  wave = max(wave, floodPulse * floodEnabled);
832
+ // flow mirrors the yunzhou-onemap WaterPrimitive water surface style without changing river/lake/flood.
833
+ float flowEnabled = step(3.5, waterType);
834
+ float yunzhouFlow = smoothstep(0.46, 1.0, waterNoise(moving * 18.0 + flow * time * 2.2)) * (0.52 + waveA * 0.48);
835
+ float flowWash = smoothstep(0.42, 1.0, waterNoise(moving * 7.0 + crossFlow * time * 0.28));
836
+ float flowHighlight = max(yunzhouFlow, flowWash * waveB * 0.74);
837
+ float flowWave = mix(waveA, flowHighlight, 0.62);
838
+ wave = mix(wave, flowWave, flowEnabled);
728
839
  vec3 viewDirection = normalize(materialInput.positionToEyeEC);
729
840
  float viewFacing = clamp(1.0 - abs(dot(normalize(materialInput.normalEC), -viewDirection)), 0.0, 1.0);
730
841
  float fresnel = pow(viewFacing, fresnelPower) * reflectivity;
@@ -733,7 +844,10 @@ export function buildWaterSurfaceMaterialSource() {
733
844
  vec3 lakeTint = mix(color.rgb, vec3(0.36, 0.92, 0.86), 0.22);
734
845
  vec3 floodTint = mix(color.rgb, vec3(0.42, 0.68, 1.0), 0.36);
735
846
  vec3 waterColor = riverTint * riverEnabled + lakeTint * lakeEnabled + floodTint * floodEnabled;
847
+ vec3 flowTint = mix(color.rgb, vec3(0.0, 0.47, 0.50), 0.18);
848
+ waterColor = mix(waterColor, flowTint, flowEnabled);
736
849
  vec3 deepColor = mix(waterColor, vec3(0.015, 0.11, 0.18), 0.46);
850
+ deepColor = mix(deepColor, mix(flowTint, vec3(0.015, 0.11, 0.18), 0.54), flowEnabled);
737
851
  vec3 refractionColor = mix(deepColor, waterColor, 0.42 + wave * 0.34);
738
852
  refractionColor += vec3(0.02, 0.12, 0.16) * refractionStrength * (0.5 + waveB * 0.5);
739
853
  vec3 skyReflection = mix(vec3(0.58, 0.86, 1.0), vec3(1.0), pow(smallWave, 5.0));
@@ -744,6 +858,7 @@ export function buildWaterSurfaceMaterialSource() {
744
858
  material.normal = surfaceNormal;
745
859
  material.diffuse = finalColor * (0.56 + wave * 0.18);
746
860
  material.emission = reflectionColor * (0.14 + fresnel * 0.42) + waterColor * ((riverFlow * 0.08 + waveB * 0.06) * riverEnabled + floodPulse * floodEnabled * 0.22);
861
+ material.emission = mix(material.emission, reflectionColor * (0.14 + fresnel * 0.42) + waterColor * (flowHighlight * 0.11), flowEnabled);
747
862
  material.alpha = opacity * (0.62 + wave * 0.12 + fresnel * 0.2 + refractionStrength * 0.06);
748
863
  return material;
749
864
  }
@@ -797,7 +912,8 @@ export function buildScanConeMaterialSource() {
797
912
  vec2 centered = st - vec2(0.5);
798
913
  float radius = length(centered) * 2.0;
799
914
  float angle = atan(centered.y, centered.x) / 6.28318530718 + 0.5;
800
- float time = fract((czm_frameNumber * 0.016667 * speed));
915
+ float scanTimeSeconds = max(timeSeconds, czm_frameNumber * 0.016667);
916
+ float time = fract(scanTimeSeconds * speed);
801
917
  float verticalFade = smoothstep(0.0, 0.12, st.t) * (1.0 - smoothstep(0.92, 1.0, st.t));
802
918
  float sweepBand = smoothstep(0.08, 0.0, abs(fract(angle - time) - 0.5));
803
919
  float radialGrid = smoothstep(0.012, 0.0, abs(fract(radius * 7.0 - time * 2.0) - 0.5));
@@ -2370,6 +2486,211 @@ export class FlyLineEffect {
2370
2486
  this.renderFrame = 0;
2371
2487
  }
2372
2488
  }
2489
+ export class MaterialPolylineEffect {
2490
+ viewer;
2491
+ dataSource;
2492
+ options;
2493
+ routeEntity = null;
2494
+ material = null;
2495
+ renderFrame = 0;
2496
+ destroyed = false;
2497
+ constructor(viewer, options) {
2498
+ this.viewer = viewer;
2499
+ this.options = normalizeMaterialPolylineOptions(options);
2500
+ this.dataSource = new CustomDataSource('geo-effect-kit-material-polyline');
2501
+ this.dataSource.show = this.options.visible;
2502
+ this.viewer.dataSources.add(this.dataSource);
2503
+ this.renderEntity();
2504
+ this.startRenderLoop();
2505
+ this.viewer.scene.requestRender();
2506
+ }
2507
+ update(options) {
2508
+ if (this.destroyed)
2509
+ return;
2510
+ const nextImage = options.image !== undefined
2511
+ ? options.image
2512
+ : options.imagePreset !== undefined
2513
+ ? normalizeMaterialPolylineOptions({ positions: this.options.positions, imagePreset: options.imagePreset }).image
2514
+ : this.options.image;
2515
+ const next = normalizeMaterialPolylineOptions({
2516
+ ...this.options,
2517
+ ...options,
2518
+ positions: options.positions ?? this.options.positions,
2519
+ repeat: options.repeat ?? this.options.repeat,
2520
+ image: nextImage,
2521
+ clampToGround: options.clampToGround ?? this.options.clampToGround,
2522
+ visible: options.visible ?? this.options.visible,
2523
+ });
2524
+ const rebuildEntity = shouldRebuildMaterialPolyline(this.options, next);
2525
+ const materialClassChanged = isNativeMaterialPolylineStyle(this.options.style) !== isNativeMaterialPolylineStyle(next.style);
2526
+ this.options = next;
2527
+ if (rebuildEntity || materialClassChanged) {
2528
+ this.renderEntity();
2529
+ }
2530
+ else {
2531
+ this.applyEntityOptions();
2532
+ }
2533
+ this.dataSource.show = this.options.visible;
2534
+ if (this.options.visible)
2535
+ this.startRenderLoop();
2536
+ else
2537
+ this.stopRenderLoop();
2538
+ this.viewer.scene.requestRender();
2539
+ }
2540
+ show() {
2541
+ if (this.destroyed)
2542
+ return;
2543
+ this.options = { ...this.options, visible: true };
2544
+ this.dataSource.show = true;
2545
+ this.startRenderLoop();
2546
+ this.viewer.scene.requestRender();
2547
+ }
2548
+ hide() {
2549
+ if (this.destroyed)
2550
+ return;
2551
+ this.options = { ...this.options, visible: false };
2552
+ this.dataSource.show = false;
2553
+ this.stopRenderLoop();
2554
+ this.viewer.scene.requestRender();
2555
+ }
2556
+ flyTo(options = {}) {
2557
+ if (this.destroyed)
2558
+ return;
2559
+ const { center, radius } = getPositionBounds(this.getRenderPositions(), Math.max(2000, this.options.width * 900));
2560
+ this.viewer.camera.flyToBoundingSphere(new BoundingSphere(center, radius), {
2561
+ duration: options.duration ?? 1,
2562
+ offset: new HeadingPitchRange(0, options.pitch ?? -0.62, radius * (options.rangeMultiplier ?? 2.4)),
2563
+ });
2564
+ }
2565
+ destroy() {
2566
+ if (this.destroyed)
2567
+ return;
2568
+ this.destroyed = true;
2569
+ this.stopRenderLoop();
2570
+ this.dataSource.entities.removeAll();
2571
+ this.routeEntity = null;
2572
+ this.material = null;
2573
+ this.viewer.dataSources.remove(this.dataSource, true);
2574
+ this.viewer.scene.requestRender();
2575
+ }
2576
+ isVisible() {
2577
+ return this.options.visible;
2578
+ }
2579
+ isDestroyed() {
2580
+ return this.destroyed;
2581
+ }
2582
+ getOptions() {
2583
+ return {
2584
+ ...this.options,
2585
+ positions: clonePositions(this.options.positions),
2586
+ repeat: { ...this.options.repeat },
2587
+ };
2588
+ }
2589
+ renderEntity() {
2590
+ clearEntities(this.dataSource);
2591
+ this.material = null;
2592
+ const positions = positionsToCartesians(this.getRenderPositions());
2593
+ const material = this.createMaterial();
2594
+ this.routeEntity = this.dataSource.entities.add({
2595
+ id: 'geo-effect-kit-material-polyline-route',
2596
+ polyline: {
2597
+ positions,
2598
+ width: this.options.width,
2599
+ clampToGround: this.options.clampToGround && this.options.arcHeight <= 0,
2600
+ material,
2601
+ },
2602
+ });
2603
+ }
2604
+ applyEntityOptions() {
2605
+ if (!this.routeEntity?.polyline)
2606
+ return;
2607
+ this.routeEntity.polyline.width = new ConstantProperty(this.options.width);
2608
+ if (this.material) {
2609
+ this.applyDynamicMaterialUniforms(this.material);
2610
+ }
2611
+ else {
2612
+ this.routeEntity.polyline.material = this.createMaterial();
2613
+ }
2614
+ }
2615
+ createMaterial() {
2616
+ if (this.options.style === 'solid') {
2617
+ return new ColorMaterialProperty(Color.fromCssColorString(this.options.color));
2618
+ }
2619
+ if (this.options.style === 'outline') {
2620
+ return new PolylineOutlineMaterialProperty({
2621
+ color: Color.fromCssColorString(this.options.color),
2622
+ outlineColor: Color.fromCssColorString(this.options.secondaryColor),
2623
+ outlineWidth: this.options.outlineWidth,
2624
+ });
2625
+ }
2626
+ if (this.options.style === 'arrow') {
2627
+ return new PolylineArrowMaterialProperty(Color.fromCssColorString(this.options.color));
2628
+ }
2629
+ if (this.options.style === 'dash' || this.options.style === 'dual-dash') {
2630
+ return new PolylineDashMaterialProperty({
2631
+ color: Color.fromCssColorString(this.options.color),
2632
+ gapColor: Color.fromCssColorString(this.options.style === 'dual-dash' ? this.options.secondaryColor : 'rgba(0, 0, 0, 0)'),
2633
+ dashLength: Math.max(4, this.options.repeat.x * 4),
2634
+ dashPattern: this.options.style === 'dual-dash' ? 0b1111000000 : 255,
2635
+ });
2636
+ }
2637
+ registerMaterialPolylineMaterial();
2638
+ const material = new DynamicCesiumMaterialProperty(GEO_MATERIAL_POLYLINE_MATERIAL_TYPE, {});
2639
+ this.material = material;
2640
+ this.applyDynamicMaterialUniforms(material);
2641
+ return material;
2642
+ }
2643
+ applyDynamicMaterialUniforms(material) {
2644
+ material.uniforms.color = Color.fromCssColorString(this.options.color).withAlpha(1);
2645
+ material.uniforms.secondaryColor = Color.fromCssColorString(this.options.secondaryColor).withAlpha(1);
2646
+ material.uniforms.backgroundColor = Color.fromCssColorString(this.options.backgroundColor);
2647
+ material.uniforms.image = this.options.image;
2648
+ material.uniforms.speed = this.options.speed;
2649
+ material.uniforms.repeatX = this.options.repeat.x;
2650
+ material.uniforms.repeatY = this.options.repeat.y;
2651
+ material.uniforms.styleType = getMaterialPolylineStyleUniform(this.options.style);
2652
+ }
2653
+ getRenderPositions() {
2654
+ const rounded = roundPolylineCorners(this.options.positions, this.options.cornerRadius);
2655
+ if (this.options.arcHeight <= 0)
2656
+ return rounded;
2657
+ const result = [];
2658
+ for (let index = 1; index < rounded.length; index += 1) {
2659
+ const from = rounded[index - 1];
2660
+ const to = rounded[index];
2661
+ if (!from || !to)
2662
+ continue;
2663
+ const segment = sampleFlyLineArc({ from, to }, this.options.arcHeight, this.options.arcSamples);
2664
+ segment.forEach((position, segmentIndex) => {
2665
+ if (index > 1 && segmentIndex === 0)
2666
+ return;
2667
+ result.push(position);
2668
+ });
2669
+ }
2670
+ return result.length >= 2 ? result : rounded;
2671
+ }
2672
+ startRenderLoop() {
2673
+ if (this.renderFrame || !this.options.visible || typeof window === 'undefined')
2674
+ return;
2675
+ const tick = () => {
2676
+ if (this.destroyed || !this.options.visible) {
2677
+ this.renderFrame = 0;
2678
+ return;
2679
+ }
2680
+ this.viewer.scene.requestRender();
2681
+ this.renderFrame = window.requestAnimationFrame(tick);
2682
+ };
2683
+ this.renderFrame = window.requestAnimationFrame(tick);
2684
+ }
2685
+ stopRenderLoop() {
2686
+ if (!this.renderFrame || typeof window === 'undefined') {
2687
+ this.renderFrame = 0;
2688
+ return;
2689
+ }
2690
+ window.cancelAnimationFrame(this.renderFrame);
2691
+ this.renderFrame = 0;
2692
+ }
2693
+ }
2373
2694
  export class PipeFlowEffect {
2374
2695
  viewer;
2375
2696
  dataSource;
@@ -3186,6 +3507,17 @@ function createRadarScanMaterial(options) {
3186
3507
  });
3187
3508
  return material;
3188
3509
  }
3510
+ export function createRadarScanMaterialProperty(options) {
3511
+ const normalized = normalizeRadarScanOptions(options);
3512
+ registerRadarScanMaterial(normalized);
3513
+ return new DynamicCesiumMaterialProperty(GEO_RADAR_SCAN_MATERIAL_TYPE, {
3514
+ color: Color.fromCssColorString(normalized.color).withAlpha(1),
3515
+ opacity: normalized.opacity,
3516
+ ringsEnabled: normalized.rings ? 1 : 0,
3517
+ radarType: getRadarScanTypeUniform(normalized.type),
3518
+ scanDurationMs: normalized.scanDurationMs,
3519
+ });
3520
+ }
3189
3521
  function registerRippleSpreadMaterial(options) {
3190
3522
  const cache = Material._materialCache;
3191
3523
  if (!cache || cache.getMaterial(GEO_RIPPLE_SPREAD_MATERIAL_TYPE))
@@ -3270,7 +3602,7 @@ function createTemperatureFieldUniforms(options) {
3270
3602
  ...createTemperatureFieldSampleUniforms(options),
3271
3603
  };
3272
3604
  }
3273
- class DynamicCesiumMaterialProperty {
3605
+ export class DynamicCesiumMaterialProperty {
3274
3606
  definitionChanged = new Event();
3275
3607
  isConstant = false;
3276
3608
  uniforms;
@@ -3300,14 +3632,16 @@ function createLightWallMaterialProperty(options) {
3300
3632
  breathing: options.breathing ? 1 : 0,
3301
3633
  });
3302
3634
  }
3303
- function createScanConeMaterialProperty(options) {
3635
+ export function createScanConeMaterialProperty(options) {
3636
+ const normalized = normalizeScanConeOptions(options);
3304
3637
  registerScanConeMaterial();
3305
3638
  return new DynamicCesiumMaterialProperty(GEO_SCAN_CONE_MATERIAL_TYPE, {
3306
- color: Color.fromCssColorString(options.color).withAlpha(1),
3307
- opacity: options.opacity,
3308
- speed: options.speed,
3309
- coneType: getScanConeTypeUniform(options.type),
3310
- aperture: options.aperture,
3639
+ color: Color.fromCssColorString(normalized.color).withAlpha(1),
3640
+ opacity: normalized.opacity,
3641
+ speed: normalized.speed,
3642
+ timeSeconds: -1,
3643
+ coneType: getScanConeTypeUniform(normalized.type),
3644
+ aperture: normalized.aperture,
3311
3645
  });
3312
3646
  }
3313
3647
  function createShieldDomeMaterialProperty(options) {
@@ -3355,6 +3689,7 @@ function registerScanConeMaterial() {
3355
3689
  color: Color.CYAN,
3356
3690
  opacity: 0.62,
3357
3691
  speed: 1,
3692
+ timeSeconds: -1,
3358
3693
  coneType: 1,
3359
3694
  aperture: 34,
3360
3695
  }, buildScanConeMaterialSource());
@@ -3384,6 +3719,18 @@ function registerWaterSurfaceMaterial() {
3384
3719
  flowDirection: 90,
3385
3720
  }, buildWaterSurfaceMaterialSource());
3386
3721
  }
3722
+ function registerMaterialPolylineMaterial() {
3723
+ registerMaterial(GEO_MATERIAL_POLYLINE_MATERIAL_TYPE, {
3724
+ color: Color.CYAN,
3725
+ secondaryColor: Color.WHITE,
3726
+ backgroundColor: Color.BLUE.withAlpha(0.35),
3727
+ image: getMaterialPolylinePresetImage('pulse'),
3728
+ speed: 1,
3729
+ repeatX: 4,
3730
+ repeatY: 1,
3731
+ styleType: 6,
3732
+ }, buildMaterialPolylineMaterialSource());
3733
+ }
3387
3734
  function registerMaterial(type, uniforms, source) {
3388
3735
  const cache = Material._materialCache;
3389
3736
  if (!cache || cache.getMaterial(type))
@@ -3414,6 +3761,20 @@ function normalizePostProcessType(type) {
3414
3761
  function normalizePolylineFlowType(type) {
3415
3762
  return POLYLINE_FLOW_TYPE_VALUES.includes(type) ? type : 'dispatch';
3416
3763
  }
3764
+ function normalizeMaterialPolylineStyle(style) {
3765
+ return MATERIAL_POLYLINE_STYLE_VALUES.includes(style) ? style : 'flow';
3766
+ }
3767
+ function normalizeMaterialPolylineImagePreset(imagePreset) {
3768
+ return MATERIAL_POLYLINE_IMAGE_PRESET_VALUES.includes(imagePreset)
3769
+ ? imagePreset
3770
+ : 'pulse';
3771
+ }
3772
+ function normalizeMaterialPolylineRepeat(repeat) {
3773
+ return {
3774
+ x: clamp(finiteOr(repeat?.x ?? 4, 4), 1, 64),
3775
+ y: clamp(finiteOr(repeat?.y ?? 1, 1), 1, 16),
3776
+ };
3777
+ }
3417
3778
  function normalizeFlyLineMode(mode) {
3418
3779
  return FLY_LINE_MODE_VALUES.includes(mode) ? mode : 'single-arc';
3419
3780
  }
@@ -3467,6 +3828,9 @@ function getPostProcessTypeUniform(type) {
3467
3828
  return 5;
3468
3829
  return 6;
3469
3830
  }
3831
+ function getMaterialPolylineStyleUniform(style) {
3832
+ return MATERIAL_POLYLINE_STYLE_VALUES.indexOf(style) + 1;
3833
+ }
3470
3834
  function getLightWallTypeUniform(type) {
3471
3835
  if (type === 'security')
3472
3836
  return 1;
@@ -3505,7 +3869,57 @@ function getWaterSurfaceTypeUniform(type) {
3505
3869
  return 1;
3506
3870
  if (type === 'lake')
3507
3871
  return 2;
3508
- return 3;
3872
+ if (type === 'flood')
3873
+ return 3;
3874
+ return 4;
3875
+ }
3876
+ function isNativeMaterialPolylineStyle(style) {
3877
+ return style === 'solid' || style === 'outline' || style === 'arrow' || style === 'dash' || style === 'dual-dash';
3878
+ }
3879
+ function getMaterialPolylinePresetImage(imagePreset) {
3880
+ const presets = {
3881
+ pulse: createMaterialPolylineTextureDataUrl('pulse', '#ffffff', '#00ff66'),
3882
+ gradual: createMaterialPolylineTextureDataUrl('gradual', '#e8fff5', '#66bd63'),
3883
+ 'arrow-blue': createMaterialPolylineTextureDataUrl('arrow-blue', '#ffffff', '#1f78ff'),
3884
+ rainbow: createMaterialPolylineTextureDataUrl('rainbow', '#ff4d4d', '#42e8ff'),
3885
+ 'arrow-repeat': createMaterialPolylineTextureDataUrl('arrow-repeat', '#d9ffff', '#00d4ff'),
3886
+ dovetail: createMaterialPolylineTextureDataUrl('dovetail', '#e9ffd8', '#a6d96a'),
3887
+ 'yellow-flow': createMaterialPolylineTextureDataUrl('yellow-flow', '#f2ff6a', '#95ff2f'),
3888
+ 'transparent-flow': createMaterialPolylineTextureDataUrl('transparent-flow', '#59f9ff', '#0bfbff'),
3889
+ interval: createMaterialPolylineTextureDataUrl('interval', '#ffffff', '#59f9ff'),
3890
+ 'small-arrow': createMaterialPolylineTextureDataUrl('small-arrow', '#ffffff', '#33f7ff'),
3891
+ gradient: createMaterialPolylineTextureDataUrl('gradient', '#ffffff', '#33f7ff'),
3892
+ };
3893
+ return presets[imagePreset];
3894
+ }
3895
+ function createMaterialPolylineTextureDataUrl(kind, first, second) {
3896
+ const svg = getMaterialPolylineTextureSvg(kind, first, second);
3897
+ return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
3898
+ }
3899
+ function getMaterialPolylineTextureSvg(kind, first, second) {
3900
+ if (kind === 'rainbow') {
3901
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><linearGradient id="g" x1="0" x2="1"><stop stop-color="#ff2347"/><stop offset=".25" stop-color="#ffec3d"/><stop offset=".5" stop-color="#4dff73"/><stop offset=".75" stop-color="#42e8ff"/><stop offset="1" stop-color="#b34dff"/></linearGradient><rect width="128" height="16" fill="none"/><path d="M0 8H128" stroke="url(#g)" stroke-width="11" stroke-linecap="round"/></svg>`;
3902
+ }
3903
+ if (kind === 'arrow-blue' || kind === 'arrow-repeat' || kind === 'small-arrow') {
3904
+ const repeat = kind === 'small-arrow' ? 'M16 3l10 5-10 5V9H0V7h16z M48 3l10 5-10 5V9H32V7h16z M80 3l10 5-10 5V9H64V7h16z' : 'M42 2l22 6-22 6V9H0V7h42z M106 2l22 6-22 6V9H64V7h42z';
3905
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><rect width="128" height="16" fill="none"/><path d="${repeat}" fill="${second}" opacity=".95"/><path d="M0 8H128" stroke="${first}" stroke-width="2" opacity=".55"/></svg>`;
3906
+ }
3907
+ if (kind === 'dovetail') {
3908
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><rect width="128" height="16" fill="none"/><path d="M0 2h38l10 6-10 6H0l10-6z M64 2h38l10 6-10 6H64l10-6z" fill="${second}" opacity=".88"/><path d="M0 8H128" stroke="${first}" stroke-width="2" opacity=".45"/></svg>`;
3909
+ }
3910
+ if (kind === 'interval') {
3911
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><rect width="128" height="16" fill="none"/><path d="M0 8H18M32 8H50M64 8H82M96 8H114" stroke="${first}" stroke-width="8" stroke-linecap="round"/><path d="M18 8H32M50 8H64M82 8H96M114 8H128" stroke="${second}" stroke-width="4" stroke-linecap="round" opacity=".82"/></svg>`;
3912
+ }
3913
+ if (kind === 'gradient') {
3914
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><linearGradient id="g" x1="0" x2="1"><stop stop-color="${second}" stop-opacity="0"/><stop offset=".55" stop-color="${second}" stop-opacity=".86"/><stop offset="1" stop-color="${first}"/></linearGradient><rect width="128" height="16" fill="none"/><path d="M0 8H128" stroke="url(#g)" stroke-width="10" stroke-linecap="round"/></svg>`;
3915
+ }
3916
+ if (kind === 'transparent-flow') {
3917
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><linearGradient id="g" x1="0" x2="1"><stop stop-color="${second}" stop-opacity="0"/><stop offset=".45" stop-color="${second}" stop-opacity=".35"/><stop offset="1" stop-color="${first}" stop-opacity=".95"/></linearGradient><rect width="128" height="16" fill="none"/><path d="M0 8H128" stroke="url(#g)" stroke-width="9" stroke-linecap="round"/></svg>`;
3918
+ }
3919
+ if (kind === 'yellow-flow') {
3920
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><rect width="128" height="16" fill="none"/><path d="M0 8H128" stroke="${second}" stroke-width="9" stroke-linecap="round" opacity=".72"/><path d="M64 8H128" stroke="${first}" stroke-width="5" stroke-linecap="round"/></svg>`;
3921
+ }
3922
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="16" viewBox="0 0 128 16"><linearGradient id="g" x1="0" x2="1"><stop stop-color="${second}" stop-opacity="0"/><stop offset=".62" stop-color="${second}" stop-opacity=".78"/><stop offset="1" stop-color="${first}"/></linearGradient><rect width="128" height="16" fill="none"/><path d="M0 8H128" stroke="url(#g)" stroke-width="10" stroke-linecap="round"/><circle cx="108" cy="8" r="5" fill="${first}"/></svg>`;
3509
3923
  }
3510
3924
  function getPolylineFlowColor(type, color) {
3511
3925
  const base = Color.fromCssColorString(color);