import * as Cesium from 'cesium' export const RING_STYLES = [ { inner: 0, outer: 2000, gradient: ['#FF6A57', '#A11806'], alpha: 0.42, outlineAlpha: 0.85, innerRatio: 0.12, }, ] export const MATERIAL_TYPE = 'RadialGradientMaterial' export const DRONE_TRACK_MATERIAL_TYPE = 'DroneTrackFlowMaterial' let materialRegistered = false let droneTrackMaterialRegistered = false let texturedVertexFormat = null export const getTexturedVertexFormat = () => { if (!texturedVertexFormat) { texturedVertexFormat = Cesium.MaterialAppearance?.MaterialSupport?.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_ST } return texturedVertexFormat } export const registerRadialGradientMaterial = () => { if (materialRegistered || !Cesium?.Material) return materialRegistered = true Cesium.Material._materialCache.addMaterial(MATERIAL_TYPE, { fabric: { type: MATERIAL_TYPE, uniforms: { color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0), color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0), gamma: 1.7, innerCutoff: 0.0, }, source: ` uniform vec4 color1; uniform vec4 color2; uniform float gamma; uniform float innerCutoff; czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); vec2 st = materialInput.st - vec2(0.5); float t = clamp(length(st) * 2.0, 0.0, 1.0); vec4 color = mix(color1, color2, t); float ramp = smoothstep(innerCutoff, 1.0, t); float shaped = pow(t, gamma); float alpha = color.a * ramp * shaped; material.diffuse = color.rgb; material.alpha = alpha; return material; } `, }, translucent: () => true, }) } export const registerDroneTrackMaterial = () => { if (droneTrackMaterialRegistered || !Cesium?.Material) return droneTrackMaterialRegistered = true Cesium.Material._materialCache.addMaterial(DRONE_TRACK_MATERIAL_TYPE, { fabric: { type: DRONE_TRACK_MATERIAL_TYPE, uniforms: { color: new Cesium.Color(1.0, 0.28, 0.18, 1.0), speed: 4.0, headWidth: 0.18, glowPower: 1.6, backgroundAlpha: 0.22, }, source: ` uniform vec4 color; uniform float speed; uniform float headWidth; uniform float glowPower; uniform float backgroundAlpha; czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); vec2 st = materialInput.st; float time = fract(czm_frameNumber * speed / 1000.0); float flow = fract(st.s - time); float head = smoothstep(headWidth, 0.0, flow) * smoothstep(0.0, headWidth, flow); float alpha = mix(backgroundAlpha, 1.0, head); vec3 rgb = color.rgb; material.diffuse = rgb; material.emission = rgb * alpha * glowPower; material.alpha = color.a * alpha; return material; } `, }, translucent: () => true, }) } export const createRadialGradientMaterial = (color1, color2, options = {}) => { registerRadialGradientMaterial() return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2, ...(options || {}) }) } export const createDroneTrackMaterial = options => { registerDroneTrackMaterial() return Cesium.Material.fromType(DRONE_TRACK_MATERIAL_TYPE, { color: new Cesium.Color(1.0, 0.28, 0.18, 1.0), speed: 4.0, headWidth: 0.18, glowPower: 1.6, backgroundAlpha: 0.22, ...(options || {}), }) } export const buildCirclePositions = (center, radiusMeters, steps = 64) => { const positions = [] const latRad = Cesium.Math.toRadians(center.latitude) const metersToLat = 1 / 111320 const metersToLon = 1 / (111320 * Math.cos(latRad)) for (let i = 0; i <= steps; i += 1) { const angle = Cesium.Math.toRadians((i / steps) * 360) const dx = radiusMeters * Math.cos(angle) const dy = radiusMeters * Math.sin(angle) const lon = center.longitude + dx * metersToLon const lat = center.latitude + dy * metersToLat positions.push(Cesium.Cartesian3.fromDegrees(lon, lat)) } return positions } export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters) => { if (!ringFillInstancesByStyle) return const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0) const vertexFormat = getTexturedVertexFormat() RING_STYLES.forEach((ring, index) => { if (!ringFillInstancesByStyle[index]) return const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer ringFillInstancesByStyle[index].push( new Cesium.GeometryInstance({ geometry: new Cesium.EllipseGeometry({ center: centerPosition, semiMajorAxis: outer, semiMinorAxis: outer, vertexFormat, }), }) ) }) if (!ringOutlineInstancesByStyle) return RING_STYLES.forEach((ring, index) => { if (!ringOutlineInstancesByStyle[index]) return const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer const positions = buildCirclePositions(center, outer) const outlineAlpha = typeof ring.outlineAlpha === 'number' ? ring.outlineAlpha : 0.8 ringOutlineInstancesByStyle[index].push( new Cesium.GeometryInstance({ geometry: new Cesium.GroundPolylineGeometry({ positions, width: 2, }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(outlineAlpha) ), }, }) ) }) } export class RadialGradientMaterialProperty { constructor(color1, color2) { this._definitionChanged = new Cesium.Event() this.color1 = color1 this.color2 = color2 } get isConstant () { return true } get definitionChanged () { return this._definitionChanged } getType () { return MATERIAL_TYPE } getValue (time, result) { const target = result || {} target.color1 = this.color1 target.color2 = this.color2 return target } equals (other) { return ( other instanceof RadialGradientMaterialProperty && Cesium.Color.equals(this.color1, other.color1) && Cesium.Color.equals(this.color2, other.color2) ) } }