index.js 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550
  1. var __defProp = Object.defineProperty;
  2. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  3. var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  4. // src/index.ts
  5. import {
  6. Cartesian2 as Cartesian23,
  7. Math as CesiumMath,
  8. Rectangle
  9. } from "cesium";
  10. // src/windParticlesComputing.ts
  11. import { PixelDatatype, PixelFormat, Sampler, Texture, TextureMagnificationFilter, TextureMinificationFilter, Cartesian2, FrameRateMonitor } from "cesium";
  12. // src/shaderManager.ts
  13. import { ShaderSource } from "cesium";
  14. // src/shaders/updatePosition.ts
  15. var updatePositionShader = (
  16. /*glsl*/
  17. `#version 300 es
  18. precision highp float;
  19. uniform sampler2D currentParticlesPosition;
  20. uniform sampler2D particlesSpeed;
  21. in vec2 v_textureCoordinates;
  22. out vec4 fragColor;
  23. void main() {
  24. // \u83B7\u53D6\u5F53\u524D\u7C92\u5B50\u7684\u4F4D\u7F6E
  25. vec2 currentPos = texture(currentParticlesPosition, v_textureCoordinates).rg;
  26. // \u83B7\u53D6\u7C92\u5B50\u7684\u901F\u5EA6
  27. vec2 speed = texture(particlesSpeed, v_textureCoordinates).rg;
  28. // \u8BA1\u7B97\u4E0B\u4E00\u4E2A\u4F4D\u7F6E
  29. vec2 nextPos = currentPos + speed;
  30. // \u5C06\u65B0\u7684\u4F4D\u7F6E\u5199\u5165 fragColor
  31. fragColor = vec4(nextPos, 0.0, 1.0);
  32. }
  33. `
  34. );
  35. // src/shaders/calculateSpeed.ts
  36. var calculateSpeedShader = (
  37. /*glsl*/
  38. `#version 300 es
  39. // the size of UV textures: width = lon, height = lat
  40. uniform sampler2D U; // eastward wind
  41. uniform sampler2D V; // northward wind
  42. uniform sampler2D currentParticlesPosition; // (lon, lat, lev)
  43. uniform vec2 uRange; // (min, max)
  44. uniform vec2 vRange; // (min, max)
  45. uniform vec2 speedRange; // (min, max)
  46. uniform vec2 dimension; // (lon, lat)
  47. uniform vec2 minimum; // minimum of each dimension
  48. uniform vec2 maximum; // maximum of each dimension
  49. uniform float speedScaleFactor;
  50. uniform float frameRateAdjustment;
  51. in vec2 v_textureCoordinates;
  52. vec2 getInterval(vec2 maximum, vec2 minimum, vec2 dimension) {
  53. return (maximum - minimum) / (dimension - 1.0);
  54. }
  55. vec2 mapPositionToNormalizedIndex2D(vec2 lonLat) {
  56. // ensure the range of longitude and latitude
  57. lonLat.x = clamp(lonLat.x, minimum.x, maximum.x);
  58. lonLat.y = clamp(lonLat.y, minimum.y, maximum.y);
  59. vec2 interval = getInterval(maximum, minimum, dimension);
  60. vec2 index2D = vec2(0.0);
  61. index2D.x = (lonLat.x - minimum.x) / interval.x;
  62. index2D.y = (lonLat.y - minimum.y) / interval.y;
  63. vec2 normalizedIndex2D = vec2(index2D.x / dimension.x, index2D.y / dimension.y);
  64. return normalizedIndex2D;
  65. }
  66. float getWindComponent(sampler2D componentTexture, vec2 lonLat) {
  67. vec2 normalizedIndex2D = mapPositionToNormalizedIndex2D(lonLat);
  68. float result = texture(componentTexture, normalizedIndex2D).r;
  69. return result;
  70. }
  71. vec2 getWindComponents(vec2 lonLat) {
  72. vec2 normalizedIndex2D = mapPositionToNormalizedIndex2D(lonLat);
  73. float u = texture(U, normalizedIndex2D).r;
  74. float v = texture(V, normalizedIndex2D).r;
  75. return vec2(u, v);
  76. }
  77. vec2 bilinearInterpolation(vec2 lonLat) {
  78. float lon = lonLat.x;
  79. float lat = lonLat.y;
  80. vec2 interval = getInterval(maximum, minimum, dimension);
  81. // Calculate grid cell coordinates
  82. float lon0 = floor(lon / interval.x) * interval.x;
  83. float lon1 = lon0 + interval.x;
  84. float lat0 = floor(lat / interval.y) * interval.y;
  85. float lat1 = lat0 + interval.y;
  86. // Get wind vectors at four corners
  87. vec2 v00 = getWindComponents(vec2(lon0, lat0));
  88. vec2 v10 = getWindComponents(vec2(lon1, lat0));
  89. vec2 v01 = getWindComponents(vec2(lon0, lat1));
  90. vec2 v11 = getWindComponents(vec2(lon1, lat1));
  91. // Check if all wind vectors are zero
  92. if (length(v00) == 0.0 && length(v10) == 0.0 && length(v01) == 0.0 && length(v11) == 0.0) {
  93. return vec2(0.0, 0.0);
  94. }
  95. // Calculate interpolation weights
  96. float s = (lon - lon0) / interval.x;
  97. float t = (lat - lat0) / interval.y;
  98. // Perform bilinear interpolation on vector components
  99. vec2 v0 = mix(v00, v10, s);
  100. vec2 v1 = mix(v01, v11, s);
  101. return mix(v0, v1, t);
  102. }
  103. vec2 lengthOfLonLat(vec2 lonLat) {
  104. // unit conversion: meters -> longitude latitude degrees
  105. // see https://en.wikipedia.org/wiki/Geographic_coordinate_system#Length_of_a_degree for detail
  106. // Calculate the length of a degree of latitude and longitude in meters
  107. float latitude = radians(lonLat.y);
  108. float term1 = 111132.92;
  109. float term2 = 559.82 * cos(2.0 * latitude);
  110. float term3 = 1.175 * cos(4.0 * latitude);
  111. float term4 = 0.0023 * cos(6.0 * latitude);
  112. float latLength = term1 - term2 + term3 - term4;
  113. float term5 = 111412.84 * cos(latitude);
  114. float term6 = 93.5 * cos(3.0 * latitude);
  115. float term7 = 0.118 * cos(5.0 * latitude);
  116. float longLength = term5 - term6 + term7;
  117. return vec2(longLength, latLength);
  118. }
  119. vec2 convertSpeedUnitToLonLat(vec2 lonLat, vec2 speed) {
  120. vec2 lonLatLength = lengthOfLonLat(lonLat);
  121. float u = speed.x / lonLatLength.x;
  122. float v = speed.y / lonLatLength.y;
  123. vec2 windVectorInLonLat = vec2(u, v);
  124. return windVectorInLonLat;
  125. }
  126. vec2 calculateSpeedByRungeKutta2(vec2 lonLat) {
  127. // see https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods#Second-order_methods_with_two_stages for detail
  128. const float h = 0.5;
  129. vec2 y_n = lonLat;
  130. vec2 f_n = bilinearInterpolation(lonLat);
  131. vec2 midpoint = y_n + 0.5 * h * convertSpeedUnitToLonLat(y_n, f_n) * speedScaleFactor;
  132. vec2 speed = h * bilinearInterpolation(midpoint) * speedScaleFactor;
  133. return speed;
  134. }
  135. vec2 calculateWindNorm(vec2 speed) {
  136. float speedLength = length(speed.xy);
  137. if(speedLength == 0.0){
  138. return vec2(0.0);
  139. }
  140. // Clamp speedLength to range
  141. float clampedSpeed = clamp(speedLength, speedRange.x, speedRange.y);
  142. float normalizedSpeed = (clampedSpeed - speedRange.x) / (speedRange.y - speedRange.x);
  143. return vec2(speedLength, normalizedSpeed);
  144. }
  145. out vec4 fragColor;
  146. void main() {
  147. // texture coordinate must be normalized
  148. vec2 lonLat = texture(currentParticlesPosition, v_textureCoordinates).rg;
  149. vec2 speedOrigin = bilinearInterpolation(lonLat);
  150. vec2 speed = calculateSpeedByRungeKutta2(lonLat) * frameRateAdjustment;
  151. vec2 speedInLonLat = convertSpeedUnitToLonLat(lonLat, speed);
  152. fragColor = vec4(speedInLonLat, calculateWindNorm(speedOrigin));
  153. }
  154. `
  155. );
  156. // src/shaders/postProcessingPosition.ts
  157. var postProcessingPositionFragmentShader = (
  158. /*glsl*/
  159. `#version 300 es
  160. precision highp float;
  161. uniform sampler2D nextParticlesPosition;
  162. uniform sampler2D particlesSpeed; // (u, v, norm)
  163. // range (min, max)
  164. uniform vec2 lonRange;
  165. uniform vec2 latRange;
  166. // range (min, max)
  167. uniform vec2 dataLonRange;
  168. uniform vec2 dataLatRange;
  169. uniform float randomCoefficient;
  170. uniform float dropRate;
  171. uniform float dropRateBump;
  172. // \u6DFB\u52A0\u65B0\u7684 uniform \u53D8\u91CF
  173. uniform bool useViewerBounds;
  174. in vec2 v_textureCoordinates;
  175. // pseudo-random generator
  176. const vec3 randomConstants = vec3(12.9898, 78.233, 4375.85453);
  177. const vec2 normalRange = vec2(0.0, 1.0);
  178. float rand(vec2 seed, vec2 range) {
  179. vec2 randomSeed = randomCoefficient * seed;
  180. float temp = dot(randomConstants.xy, randomSeed);
  181. temp = fract(sin(temp) * (randomConstants.z + temp));
  182. return temp * (range.y - range.x) + range.x;
  183. }
  184. vec2 generateRandomParticle(vec2 seed) {
  185. vec2 range;
  186. float randomLon, randomLat;
  187. if (useViewerBounds) {
  188. // \u5728\u5F53\u524D\u89C6\u57DF\u8303\u56F4\u5185\u751F\u6210\u7C92\u5B50
  189. randomLon = rand(seed, lonRange);
  190. randomLat = rand(-seed, latRange);
  191. } else {
  192. // \u5728\u6570\u636E\u8303\u56F4\u5185\u751F\u6210\u7C92\u5B50
  193. randomLon = rand(seed, dataLonRange);
  194. randomLat = rand(-seed, dataLatRange);
  195. }
  196. return vec2(randomLon, randomLat);
  197. }
  198. bool particleOutbound(vec2 particle) {
  199. return particle.y < dataLatRange.x || particle.y > dataLatRange.y || particle.x < dataLonRange.x || particle.x > dataLonRange.y;
  200. }
  201. out vec4 fragColor;
  202. void main() {
  203. vec2 nextParticle = texture(nextParticlesPosition, v_textureCoordinates).rg;
  204. vec4 nextSpeed = texture(particlesSpeed, v_textureCoordinates);
  205. float speedNorm = nextSpeed.a;
  206. float particleDropRate = dropRate + dropRateBump * speedNorm;
  207. vec2 seed1 = nextParticle.xy + v_textureCoordinates;
  208. vec2 seed2 = nextSpeed.rg + v_textureCoordinates;
  209. vec2 randomParticle = generateRandomParticle(seed1);
  210. float randomNumber = rand(seed2, normalRange);
  211. if (randomNumber < particleDropRate || particleOutbound(nextParticle)) {
  212. fragColor = vec4(randomParticle, 0.0, 1.0); // 1.0 means this is a random particle
  213. } else {
  214. fragColor = vec4(nextParticle, 0.0, 0.0);
  215. }
  216. }
  217. `
  218. );
  219. // src/shaders/segmentDraw.ts
  220. var renderParticlesVertexShader = (
  221. /*glsl*/
  222. `#version 300 es
  223. precision highp float;
  224. in vec2 st;
  225. in vec3 normal;
  226. uniform sampler2D previousParticlesPosition;
  227. uniform sampler2D currentParticlesPosition;
  228. uniform sampler2D postProcessingPosition;
  229. uniform sampler2D particlesSpeed;
  230. uniform float frameRateAdjustment;
  231. uniform float particleHeight;
  232. uniform float aspect;
  233. uniform float pixelSize;
  234. uniform vec2 lineWidth;
  235. uniform vec2 lineLength;
  236. uniform vec2 domain;
  237. uniform bool is3D;
  238. // \u6DFB\u52A0\u8F93\u51FA\u53D8\u91CF\u4F20\u9012\u7ED9\u7247\u5143\u7740\u8272\u5668
  239. out vec4 speed;
  240. out float v_segmentPosition;
  241. out vec2 textureCoordinate;
  242. // \u6DFB\u52A0\u7ED3\u6784\u4F53\u5B9A\u4E49
  243. struct adjacentPoints {
  244. vec4 previous;
  245. vec4 current;
  246. vec4 next;
  247. };
  248. vec3 convertCoordinate(vec2 lonLat) {
  249. // WGS84 (lon, lat, lev) -> ECEF (x, y, z)
  250. // read https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#From_geodetic_to_ECEF_coordinates for detail
  251. // WGS 84 geometric constants
  252. float a = 6378137.0; // Semi-major axis
  253. float b = 6356752.3142; // Semi-minor axis
  254. float e2 = 6.69437999014e-3; // First eccentricity squared
  255. float latitude = radians(lonLat.y);
  256. float longitude = radians(lonLat.x);
  257. float cosLat = cos(latitude);
  258. float sinLat = sin(latitude);
  259. float cosLon = cos(longitude);
  260. float sinLon = sin(longitude);
  261. float N_Phi = a / sqrt(1.0 - e2 * sinLat * sinLat);
  262. float h = particleHeight; // it should be high enough otherwise the particle may not pass the terrain depth test
  263. vec3 cartesian = vec3(0.0);
  264. cartesian.x = (N_Phi + h) * cosLat * cosLon;
  265. cartesian.y = (N_Phi + h) * cosLat * sinLon;
  266. cartesian.z = ((b * b) / (a * a) * N_Phi + h) * sinLat;
  267. return cartesian;
  268. }
  269. vec4 calculateProjectedCoordinate(vec2 lonLat) {
  270. if (is3D) {
  271. vec3 particlePosition = convertCoordinate(lonLat);
  272. // \u4F7F\u7528 modelViewProjection \u77E9\u9635\u8FDB\u884C\u6295\u5F71\u53D8\u6362
  273. vec4 projectedPosition = czm_modelViewProjection * vec4(particlePosition, 1.0);
  274. return projectedPosition;
  275. } else {
  276. vec3 position2D = vec3(radians(lonLat.x), radians(lonLat.y), 0.0);
  277. return czm_modelViewProjection * vec4(position2D, 1.0);
  278. }
  279. }
  280. vec4 calculateOffsetOnNormalDirection(vec4 pointA, vec4 pointB, float offsetSign, float widthFactor) {
  281. vec2 aspectVec2 = vec2(aspect, 1.0);
  282. vec2 pointA_XY = (pointA.xy / pointA.w) * aspectVec2;
  283. vec2 pointB_XY = (pointB.xy / pointB.w) * aspectVec2;
  284. // \u8BA1\u7B97\u65B9\u5411\u5411\u91CF
  285. vec2 direction = normalize(pointB_XY - pointA_XY);
  286. // \u8BA1\u7B97\u6CD5\u5411\u91CF
  287. vec2 normalVector = vec2(-direction.y, direction.x);
  288. normalVector.x = normalVector.x / aspect;
  289. // \u4F7F\u7528 widthFactor \u8C03\u6574\u5BBD\u5EA6
  290. float offsetLength = widthFactor * lineWidth.y;
  291. normalVector = offsetLength * normalVector;
  292. vec4 offset = vec4(offsetSign * normalVector, 0.0, 0.0);
  293. return offset;
  294. }
  295. void main() {
  296. // \u7FFB\u8F6C Y \u8F74\u5750\u6807
  297. vec2 flippedIndex = vec2(st.x, 1.0 - st.y);
  298. vec2 particleIndex = flippedIndex;
  299. speed = texture(particlesSpeed, particleIndex);
  300. vec2 previousPosition = texture(previousParticlesPosition, particleIndex).rg;
  301. vec2 currentPosition = texture(currentParticlesPosition, particleIndex).rg;
  302. vec2 nextPosition = texture(postProcessingPosition, particleIndex).rg;
  303. float isAnyRandomPointUsed = texture(postProcessingPosition, particleIndex).a +
  304. texture(currentParticlesPosition, particleIndex).a +
  305. texture(previousParticlesPosition, particleIndex).a;
  306. adjacentPoints projectedCoordinates;
  307. if (isAnyRandomPointUsed > 0.0) {
  308. projectedCoordinates.previous = calculateProjectedCoordinate(previousPosition);
  309. projectedCoordinates.current = projectedCoordinates.previous;
  310. projectedCoordinates.next = projectedCoordinates.previous;
  311. } else {
  312. projectedCoordinates.previous = calculateProjectedCoordinate(previousPosition);
  313. projectedCoordinates.current = calculateProjectedCoordinate(currentPosition);
  314. projectedCoordinates.next = calculateProjectedCoordinate(nextPosition);
  315. }
  316. int pointToUse = int(normal.x);
  317. float offsetSign = normal.y;
  318. vec4 offset = vec4(0.0);
  319. // \u8BA1\u7B97\u901F\u5EA6\u76F8\u5173\u7684\u5BBD\u5EA6\u548C\u957F\u5EA6\u56E0\u5B50
  320. float speedLength = clamp(speed.b, domain.x, domain.y);
  321. float normalizedSpeed = (speedLength - domain.x) / (domain.y - domain.x);
  322. // \u6839\u636E\u901F\u5EA6\u8BA1\u7B97\u5BBD\u5EA6
  323. float widthFactor = mix(lineWidth.x, lineWidth.y, normalizedSpeed);
  324. widthFactor *= (pointToUse < 0 ? 1.0 : 0.5); // \u5934\u90E8\u66F4\u5BBD\uFF0C\u5C3E\u90E8\u66F4\u7A84
  325. // Calculate length based on speed
  326. float lengthFactor = mix(lineLength.x, lineLength.y, normalizedSpeed) * pixelSize;
  327. if (pointToUse == 1) {
  328. // \u5934\u90E8\u4F4D\u7F6E
  329. offset = pixelSize * calculateOffsetOnNormalDirection(
  330. projectedCoordinates.previous,
  331. projectedCoordinates.current,
  332. offsetSign,
  333. widthFactor
  334. );
  335. gl_Position = projectedCoordinates.previous + offset;
  336. v_segmentPosition = 0.0; // \u5934\u90E8
  337. } else if (pointToUse == -1) {
  338. // Get direction and normalize it to length 1.0
  339. vec4 direction = normalize(projectedCoordinates.next - projectedCoordinates.current);
  340. vec4 extendedPosition = projectedCoordinates.current + direction * lengthFactor;
  341. offset = pixelSize * calculateOffsetOnNormalDirection(
  342. projectedCoordinates.current,
  343. extendedPosition,
  344. offsetSign,
  345. widthFactor
  346. );
  347. gl_Position = extendedPosition + offset;
  348. v_segmentPosition = 1.0; // \u5C3E\u90E8
  349. }
  350. textureCoordinate = st;
  351. }
  352. `
  353. );
  354. var renderParticlesFragmentShader = (
  355. /*glsl*/
  356. `#version 300 es
  357. precision highp float;
  358. in vec4 speed;
  359. in float v_segmentPosition;
  360. in vec2 textureCoordinate;
  361. uniform vec2 domain;
  362. uniform vec2 displayRange;
  363. uniform sampler2D colorTable;
  364. uniform sampler2D segmentsDepthTexture;
  365. out vec4 fragColor;
  366. void main() {
  367. const float zero = 0.0;
  368. if(speed.a > zero && speed.b > displayRange.x && speed.b < displayRange.y) {
  369. float speedLength = clamp(speed.b, domain.x, domain.y);
  370. float normalizedSpeed = (speedLength - domain.x) / (domain.y - domain.x);
  371. vec4 baseColor = texture(colorTable, vec2(normalizedSpeed, zero));
  372. // \u4F7F\u7528\u66F4\u5E73\u6ED1\u7684\u6E10\u53D8\u6548\u679C
  373. float alpha = smoothstep(0.0, 1.0, v_segmentPosition);
  374. alpha = pow(alpha, 1.5); // \u8C03\u6574\u900F\u660E\u5EA6\u6E10\u53D8\u66F2\u7EBF
  375. // \u6839\u636E\u901F\u5EA6\u8C03\u6574\u900F\u660E\u5EA6
  376. float speedAlpha = mix(0.3, 1.0, speed.a);
  377. // \u7EC4\u5408\u989C\u8272\u548C\u900F\u660E\u5EA6
  378. fragColor = vec4(baseColor.rgb, baseColor.a * alpha * speedAlpha);
  379. } else {
  380. fragColor = vec4(zero);
  381. }
  382. float segmentsDepth = texture(segmentsDepthTexture, textureCoordinate).r;
  383. float globeDepth = czm_unpackDepth(texture(czm_globeDepthTexture, textureCoordinate));
  384. if (segmentsDepth < globeDepth) {
  385. fragColor = vec4(zero);
  386. }
  387. }
  388. `
  389. );
  390. // src/shaderManager.ts
  391. var ShaderManager = class {
  392. static getCalculateSpeedShader() {
  393. return new ShaderSource({
  394. sources: [calculateSpeedShader]
  395. });
  396. }
  397. static getUpdatePositionShader() {
  398. return new ShaderSource({
  399. sources: [updatePositionShader]
  400. });
  401. }
  402. static getSegmentDrawVertexShader() {
  403. return new ShaderSource({
  404. sources: [renderParticlesVertexShader]
  405. });
  406. }
  407. static getSegmentDrawFragmentShader() {
  408. return new ShaderSource({
  409. sources: [renderParticlesFragmentShader]
  410. });
  411. }
  412. static getPostProcessingPositionShader() {
  413. return new ShaderSource({
  414. sources: [postProcessingPositionFragmentShader]
  415. });
  416. }
  417. };
  418. // src/customPrimitive.ts
  419. import {
  420. ShaderProgram,
  421. VertexArray,
  422. DrawCommand,
  423. RenderState,
  424. Pass,
  425. ClearCommand,
  426. Color,
  427. defined,
  428. ComputeCommand,
  429. Matrix4,
  430. BufferUsage,
  431. defaultValue,
  432. destroyObject
  433. } from "cesium";
  434. var CustomPrimitive = class {
  435. constructor(options) {
  436. __publicField(this, "commandType");
  437. __publicField(this, "geometry");
  438. __publicField(this, "attributeLocations");
  439. __publicField(this, "primitiveType");
  440. __publicField(this, "uniformMap");
  441. __publicField(this, "vertexShaderSource");
  442. __publicField(this, "fragmentShaderSource");
  443. __publicField(this, "rawRenderState");
  444. __publicField(this, "framebuffer");
  445. __publicField(this, "outputTexture");
  446. __publicField(this, "autoClear");
  447. __publicField(this, "preExecute");
  448. __publicField(this, "show");
  449. __publicField(this, "commandToExecute");
  450. __publicField(this, "clearCommand");
  451. __publicField(this, "isDynamic");
  452. this.commandType = options.commandType;
  453. this.geometry = options.geometry;
  454. this.attributeLocations = options.attributeLocations;
  455. this.primitiveType = options.primitiveType;
  456. this.uniformMap = options.uniformMap || {};
  457. this.vertexShaderSource = options.vertexShaderSource;
  458. this.fragmentShaderSource = options.fragmentShaderSource;
  459. this.rawRenderState = options.rawRenderState;
  460. this.framebuffer = options.framebuffer;
  461. this.outputTexture = options.outputTexture;
  462. this.autoClear = defaultValue(options.autoClear, false);
  463. this.preExecute = options.preExecute;
  464. this.show = true;
  465. this.commandToExecute = void 0;
  466. this.clearCommand = void 0;
  467. this.isDynamic = options.isDynamic ?? (() => true);
  468. if (this.autoClear) {
  469. this.clearCommand = new ClearCommand({
  470. color: new Color(0, 0, 0, 0),
  471. depth: 1,
  472. framebuffer: this.framebuffer,
  473. pass: Pass.OPAQUE
  474. });
  475. }
  476. }
  477. createCommand(context) {
  478. if (this.commandType === "Draw") {
  479. const vertexArray = VertexArray.fromGeometry({
  480. context,
  481. geometry: this.geometry,
  482. attributeLocations: this.attributeLocations,
  483. bufferUsage: BufferUsage.STATIC_DRAW
  484. });
  485. const shaderProgram = ShaderProgram.fromCache({
  486. context,
  487. vertexShaderSource: this.vertexShaderSource,
  488. fragmentShaderSource: this.fragmentShaderSource,
  489. attributeLocations: this.attributeLocations
  490. });
  491. const renderState = RenderState.fromCache(this.rawRenderState);
  492. return new DrawCommand({
  493. owner: this,
  494. vertexArray,
  495. primitiveType: this.primitiveType,
  496. modelMatrix: Matrix4.IDENTITY,
  497. renderState,
  498. shaderProgram,
  499. framebuffer: this.framebuffer,
  500. uniformMap: this.uniformMap,
  501. pass: Pass.OPAQUE
  502. });
  503. } else if (this.commandType === "Compute") {
  504. return new ComputeCommand({
  505. owner: this,
  506. fragmentShaderSource: this.fragmentShaderSource,
  507. uniformMap: this.uniformMap,
  508. outputTexture: this.outputTexture,
  509. persists: true
  510. });
  511. } else {
  512. throw new Error("Unknown command type");
  513. }
  514. }
  515. setGeometry(context, geometry) {
  516. this.geometry = geometry;
  517. if (defined(this.commandToExecute)) {
  518. this.commandToExecute.vertexArray = VertexArray.fromGeometry({
  519. context,
  520. geometry: this.geometry,
  521. attributeLocations: this.attributeLocations,
  522. bufferUsage: BufferUsage.STATIC_DRAW
  523. });
  524. }
  525. }
  526. update(frameState) {
  527. if (!this.isDynamic()) {
  528. return;
  529. }
  530. if (!this.show || !defined(frameState)) {
  531. return;
  532. }
  533. if (!defined(this.commandToExecute)) {
  534. this.commandToExecute = this.createCommand(frameState.context);
  535. }
  536. if (defined(this.preExecute)) {
  537. this.preExecute();
  538. }
  539. if (!frameState.commandList) {
  540. console.warn("frameState.commandList is undefined");
  541. return;
  542. }
  543. if (defined(this.clearCommand)) {
  544. frameState.commandList.push(this.clearCommand);
  545. }
  546. if (defined(this.commandToExecute)) {
  547. frameState.commandList.push(this.commandToExecute);
  548. }
  549. }
  550. isDestroyed() {
  551. return false;
  552. }
  553. destroy() {
  554. if (defined(this.commandToExecute)) {
  555. this.commandToExecute.shaderProgram?.destroy();
  556. this.commandToExecute.shaderProgram = void 0;
  557. }
  558. return destroyObject(this);
  559. }
  560. };
  561. // src/utils.ts
  562. function deepMerge(from, to) {
  563. if (!from) return to;
  564. if (!to) return from;
  565. const result = { ...to };
  566. for (const key in from) {
  567. if (Object.prototype.hasOwnProperty.call(from, key)) {
  568. const fromValue = from[key];
  569. const toValue = to[key];
  570. if (Array.isArray(fromValue)) {
  571. result[key] = fromValue.slice();
  572. continue;
  573. }
  574. if (fromValue && typeof fromValue === "object") {
  575. result[key] = deepMerge(fromValue, toValue || {});
  576. continue;
  577. }
  578. if (fromValue !== void 0) {
  579. result[key] = fromValue;
  580. }
  581. }
  582. }
  583. return result;
  584. }
  585. // src/windParticlesComputing.ts
  586. var WindParticlesComputing = class {
  587. constructor(context, windData, options, viewerParameters, scene) {
  588. __publicField(this, "context");
  589. __publicField(this, "options");
  590. __publicField(this, "viewerParameters");
  591. __publicField(this, "windTextures");
  592. __publicField(this, "particlesTextures");
  593. __publicField(this, "primitives");
  594. __publicField(this, "windData");
  595. __publicField(this, "frameRateMonitor");
  596. __publicField(this, "frameRate", 60);
  597. __publicField(this, "frameRateAdjustment", 1);
  598. this.context = context;
  599. this.options = options;
  600. this.viewerParameters = viewerParameters;
  601. this.windData = windData;
  602. this.frameRateMonitor = new FrameRateMonitor({
  603. scene,
  604. samplingWindow: 1,
  605. quietPeriod: 0
  606. });
  607. this.initFrameRate();
  608. this.createWindTextures();
  609. this.createParticlesTextures();
  610. this.createComputingPrimitives();
  611. }
  612. initFrameRate() {
  613. const updateFrameRate = () => {
  614. if (this.frameRateMonitor.lastFramesPerSecond > 20) {
  615. this.frameRate = this.frameRateMonitor.lastFramesPerSecond;
  616. this.frameRateAdjustment = 60 / Math.max(this.frameRate, 1);
  617. }
  618. };
  619. updateFrameRate();
  620. const intervalId = setInterval(updateFrameRate, 1e3);
  621. this.frameRateMonitor.lowFrameRate.addEventListener((scene, frameRate) => {
  622. console.warn(`Low frame rate detected: ${frameRate} FPS`);
  623. });
  624. this.frameRateMonitor.nominalFrameRate.addEventListener((scene, frameRate) => {
  625. console.log(`Frame rate returned to normal: ${frameRate} FPS`);
  626. });
  627. const originalDestroy = this.destroy.bind(this);
  628. this.destroy = () => {
  629. clearInterval(intervalId);
  630. originalDestroy();
  631. };
  632. }
  633. createWindTextures() {
  634. const options = {
  635. context: this.context,
  636. width: this.windData.width,
  637. height: this.windData.height,
  638. pixelFormat: PixelFormat.RED,
  639. pixelDatatype: PixelDatatype.FLOAT,
  640. flipY: this.options.flipY ?? false,
  641. sampler: new Sampler({
  642. minificationFilter: TextureMinificationFilter.LINEAR,
  643. magnificationFilter: TextureMagnificationFilter.LINEAR
  644. })
  645. };
  646. this.windTextures = {
  647. U: new Texture({
  648. ...options,
  649. source: {
  650. arrayBufferView: new Float32Array(this.windData.u.array)
  651. }
  652. }),
  653. V: new Texture({
  654. ...options,
  655. source: {
  656. arrayBufferView: new Float32Array(this.windData.v.array)
  657. }
  658. })
  659. };
  660. }
  661. createParticlesTextures() {
  662. const options = {
  663. context: this.context,
  664. width: this.options.particlesTextureSize,
  665. height: this.options.particlesTextureSize,
  666. pixelFormat: PixelFormat.RGBA,
  667. pixelDatatype: PixelDatatype.FLOAT,
  668. flipY: false,
  669. source: {
  670. arrayBufferView: new Float32Array(this.options.particlesTextureSize * this.options.particlesTextureSize * 4).fill(0)
  671. },
  672. sampler: new Sampler({
  673. minificationFilter: TextureMinificationFilter.NEAREST,
  674. magnificationFilter: TextureMagnificationFilter.NEAREST
  675. })
  676. };
  677. this.particlesTextures = {
  678. previousParticlesPosition: new Texture(options),
  679. currentParticlesPosition: new Texture(options),
  680. nextParticlesPosition: new Texture(options),
  681. postProcessingPosition: new Texture(options),
  682. particlesSpeed: new Texture(options)
  683. };
  684. }
  685. destroyParticlesTextures() {
  686. Object.values(this.particlesTextures).forEach((texture) => texture.destroy());
  687. }
  688. createComputingPrimitives() {
  689. this.primitives = {
  690. calculateSpeed: new CustomPrimitive({
  691. commandType: "Compute",
  692. uniformMap: {
  693. U: () => this.windTextures.U,
  694. V: () => this.windTextures.V,
  695. uRange: () => new Cartesian2(this.windData.u.min, this.windData.u.max),
  696. vRange: () => new Cartesian2(this.windData.v.min, this.windData.v.max),
  697. speedRange: () => new Cartesian2(this.windData.speed.min, this.windData.speed.max),
  698. currentParticlesPosition: () => this.particlesTextures.currentParticlesPosition,
  699. speedScaleFactor: () => {
  700. return (this.viewerParameters.pixelSize + 50) * this.options.speedFactor;
  701. },
  702. frameRateAdjustment: () => this.frameRateAdjustment,
  703. dimension: () => new Cartesian2(this.windData.width, this.windData.height),
  704. minimum: () => new Cartesian2(this.windData.bounds.west, this.windData.bounds.south),
  705. maximum: () => new Cartesian2(this.windData.bounds.east, this.windData.bounds.north)
  706. },
  707. fragmentShaderSource: ShaderManager.getCalculateSpeedShader(),
  708. outputTexture: this.particlesTextures.particlesSpeed,
  709. preExecute: () => {
  710. const temp = this.particlesTextures.previousParticlesPosition;
  711. this.particlesTextures.previousParticlesPosition = this.particlesTextures.currentParticlesPosition;
  712. this.particlesTextures.currentParticlesPosition = this.particlesTextures.postProcessingPosition;
  713. this.particlesTextures.postProcessingPosition = temp;
  714. if (this.primitives.calculateSpeed.commandToExecute) {
  715. this.primitives.calculateSpeed.commandToExecute.outputTexture = this.particlesTextures.particlesSpeed;
  716. }
  717. },
  718. isDynamic: () => this.options.dynamic
  719. }),
  720. updatePosition: new CustomPrimitive({
  721. commandType: "Compute",
  722. uniformMap: {
  723. currentParticlesPosition: () => this.particlesTextures.currentParticlesPosition,
  724. particlesSpeed: () => this.particlesTextures.particlesSpeed
  725. },
  726. fragmentShaderSource: ShaderManager.getUpdatePositionShader(),
  727. outputTexture: this.particlesTextures.nextParticlesPosition,
  728. preExecute: () => {
  729. if (this.primitives.updatePosition.commandToExecute) {
  730. this.primitives.updatePosition.commandToExecute.outputTexture = this.particlesTextures.nextParticlesPosition;
  731. }
  732. },
  733. isDynamic: () => this.options.dynamic
  734. }),
  735. postProcessingPosition: new CustomPrimitive({
  736. commandType: "Compute",
  737. uniformMap: {
  738. nextParticlesPosition: () => this.particlesTextures.nextParticlesPosition,
  739. particlesSpeed: () => this.particlesTextures.particlesSpeed,
  740. lonRange: () => this.viewerParameters.lonRange,
  741. latRange: () => this.viewerParameters.latRange,
  742. dataLonRange: () => new Cartesian2(this.windData.bounds.west, this.windData.bounds.east),
  743. dataLatRange: () => new Cartesian2(this.windData.bounds.south, this.windData.bounds.north),
  744. randomCoefficient: function () {
  745. return Math.random();
  746. },
  747. dropRate: () => this.options.dropRate,
  748. dropRateBump: () => this.options.dropRateBump,
  749. useViewerBounds: () => this.options.useViewerBounds
  750. },
  751. fragmentShaderSource: ShaderManager.getPostProcessingPositionShader(),
  752. outputTexture: this.particlesTextures.postProcessingPosition,
  753. preExecute: () => {
  754. if (this.primitives.postProcessingPosition.commandToExecute) {
  755. this.primitives.postProcessingPosition.commandToExecute.outputTexture = this.particlesTextures.postProcessingPosition;
  756. }
  757. },
  758. isDynamic: () => this.options.dynamic
  759. })
  760. };
  761. }
  762. reCreateWindTextures() {
  763. this.windTextures.U.destroy();
  764. this.windTextures.V.destroy();
  765. this.createWindTextures();
  766. }
  767. updateWindData(data) {
  768. this.windData = data;
  769. this.reCreateWindTextures();
  770. }
  771. updateOptions(options) {
  772. const needUpdateWindTextures = options.flipY !== void 0 && options.flipY !== this.options.flipY;
  773. this.options = deepMerge(options, this.options);
  774. if (needUpdateWindTextures) {
  775. this.reCreateWindTextures();
  776. }
  777. }
  778. processWindData(data) {
  779. const { array } = data;
  780. let { min, max } = data;
  781. const result = new Float32Array(array.length);
  782. if (min === void 0) {
  783. console.warn("min is undefined, calculate min");
  784. min = Math.min(...array);
  785. }
  786. if (max === void 0) {
  787. console.warn("max is undefined, calculate max");
  788. max = Math.max(...array);
  789. }
  790. const maxNum = Math.max(Math.abs(min), Math.abs(max));
  791. for (let i = 0; i < array.length; i++) {
  792. const value = array[i] / maxNum;
  793. result[i] = value;
  794. }
  795. console.log(result);
  796. return result;
  797. }
  798. destroy() {
  799. Object.values(this.windTextures).forEach((texture) => texture.destroy());
  800. Object.values(this.particlesTextures).forEach((texture) => texture.destroy());
  801. Object.values(this.primitives).forEach((primitive) => primitive.destroy());
  802. this.frameRateMonitor.destroy();
  803. }
  804. };
  805. // src/windParticlesRendering.ts
  806. import { Geometry as Geometry2, GeometryAttribute, ComponentDatatype, PrimitiveType as PrimitiveType2, GeometryAttributes, Color as Color2, Texture as Texture2, Sampler as Sampler2, TextureMinificationFilter as TextureMinificationFilter2, TextureMagnificationFilter as TextureMagnificationFilter2, PixelFormat as PixelFormat2, PixelDatatype as PixelDatatype2, Framebuffer, Appearance, SceneMode, TextureWrap, VertexArray as VertexArray2, BufferUsage as BufferUsage2, Cartesian2 as Cartesian22 } from "cesium";
  807. var WindParticlesRendering = class {
  808. constructor(context, options, viewerParameters, computing) {
  809. __publicField(this, "context");
  810. __publicField(this, "options");
  811. __publicField(this, "viewerParameters");
  812. __publicField(this, "computing");
  813. __publicField(this, "primitives");
  814. __publicField(this, "colorTable");
  815. __publicField(this, "textures");
  816. __publicField(this, "framebuffers");
  817. this.context = context;
  818. this.options = options;
  819. this.viewerParameters = viewerParameters;
  820. this.computing = computing;
  821. if (typeof this.options.particlesTextureSize !== "number" || this.options.particlesTextureSize <= 0) {
  822. console.error("Invalid particlesTextureSize. Using default value of 256.");
  823. this.options.particlesTextureSize = 256;
  824. }
  825. this.colorTable = this.createColorTableTexture();
  826. this.textures = this.createRenderingTextures();
  827. this.framebuffers = this.createRenderingFramebuffers();
  828. this.primitives = this.createPrimitives();
  829. }
  830. createRenderingTextures() {
  831. const colorTextureOptions = {
  832. context: this.context,
  833. width: this.context.drawingBufferWidth,
  834. height: this.context.drawingBufferHeight,
  835. pixelFormat: PixelFormat2.RGBA,
  836. pixelDatatype: PixelDatatype2.UNSIGNED_BYTE
  837. };
  838. const depthTextureOptions = {
  839. context: this.context,
  840. width: this.context.drawingBufferWidth,
  841. height: this.context.drawingBufferHeight,
  842. pixelFormat: PixelFormat2.DEPTH_COMPONENT,
  843. pixelDatatype: PixelDatatype2.UNSIGNED_INT
  844. };
  845. return {
  846. segmentsColor: new Texture2(colorTextureOptions),
  847. segmentsDepth: new Texture2(depthTextureOptions)
  848. };
  849. }
  850. createRenderingFramebuffers() {
  851. return {
  852. segments: new Framebuffer({
  853. context: this.context,
  854. colorTextures: [this.textures.segmentsColor],
  855. depthTexture: this.textures.segmentsDepth
  856. })
  857. };
  858. }
  859. destoryRenderingFramebuffers() {
  860. Object.values(this.framebuffers).forEach((framebuffer) => {
  861. framebuffer.destroy();
  862. });
  863. }
  864. createColorTableTexture() {
  865. const colorTableData = new Float32Array(this.options.colors.flatMap((color) => {
  866. const cesiumColor = Color2.fromCssColorString(color);
  867. return [cesiumColor.red, cesiumColor.green, cesiumColor.blue, cesiumColor.alpha];
  868. }));
  869. return new Texture2({
  870. context: this.context,
  871. width: this.options.colors.length,
  872. height: 1,
  873. pixelFormat: PixelFormat2.RGBA,
  874. pixelDatatype: PixelDatatype2.FLOAT,
  875. sampler: new Sampler2({
  876. minificationFilter: TextureMinificationFilter2.LINEAR,
  877. magnificationFilter: TextureMagnificationFilter2.LINEAR,
  878. wrapS: TextureWrap.CLAMP_TO_EDGE,
  879. wrapT: TextureWrap.CLAMP_TO_EDGE
  880. }),
  881. source: {
  882. width: this.options.colors.length,
  883. height: 1,
  884. arrayBufferView: colorTableData
  885. }
  886. });
  887. }
  888. createSegmentsGeometry() {
  889. const repeatVertex = 4, texureSize = this.options.particlesTextureSize;
  890. let st = [];
  891. for (let s = 0; s < texureSize; s++) {
  892. for (let t = 0; t < texureSize; t++) {
  893. for (let i = 0; i < repeatVertex; i++) {
  894. st.push(s / texureSize);
  895. st.push(t / texureSize);
  896. }
  897. }
  898. }
  899. st = new Float32Array(st);
  900. const particlesCount = this.options.particlesTextureSize ** 2;
  901. let normal = [];
  902. for (let i = 0; i < particlesCount; i++) {
  903. normal.push(
  904. // (point to use, offset sign, not used component)
  905. -1,
  906. -1,
  907. 0,
  908. -1,
  909. 1,
  910. 0,
  911. 1,
  912. -1,
  913. 0,
  914. 1,
  915. 1,
  916. 0
  917. );
  918. }
  919. normal = new Float32Array(normal);
  920. let vertexIndexes = [];
  921. for (let i = 0, vertex = 0; i < particlesCount; i++) {
  922. vertexIndexes.push(
  923. // 第一个三角形用的顶点
  924. vertex + 0,
  925. vertex + 1,
  926. vertex + 2,
  927. // 第二个三角形用的顶点
  928. vertex + 2,
  929. vertex + 1,
  930. vertex + 3
  931. );
  932. vertex += repeatVertex;
  933. }
  934. vertexIndexes = new Uint32Array(vertexIndexes);
  935. const geometry = new Geometry2({
  936. attributes: new GeometryAttributes({
  937. st: new GeometryAttribute({
  938. componentDatatype: ComponentDatatype.FLOAT,
  939. componentsPerAttribute: 2,
  940. values: st
  941. }),
  942. normal: new GeometryAttribute({
  943. componentDatatype: ComponentDatatype.FLOAT,
  944. componentsPerAttribute: 3,
  945. values: normal
  946. })
  947. }),
  948. indices: vertexIndexes
  949. });
  950. return geometry;
  951. }
  952. createRawRenderState(options) {
  953. return Appearance.getDefaultRenderState(true, false, {
  954. viewport: void 0,
  955. depthTest: void 0,
  956. depthMask: void 0,
  957. blending: void 0,
  958. ...options
  959. });
  960. }
  961. createPrimitives() {
  962. const segments = new CustomPrimitive({
  963. commandType: "Draw",
  964. attributeLocations: {
  965. st: 0,
  966. normal: 1
  967. },
  968. geometry: this.createSegmentsGeometry(),
  969. primitiveType: PrimitiveType2.TRIANGLES,
  970. uniformMap: {
  971. previousParticlesPosition: () => this.computing.particlesTextures.previousParticlesPosition,
  972. currentParticlesPosition: () => this.computing.particlesTextures.currentParticlesPosition,
  973. postProcessingPosition: () => this.computing.particlesTextures.postProcessingPosition,
  974. particlesSpeed: () => this.computing.particlesTextures.particlesSpeed,
  975. frameRateAdjustment: () => this.computing.frameRateAdjustment,
  976. colorTable: () => this.colorTable,
  977. domain: () => {
  978. const domain = new Cartesian22(this.options.domain?.min ?? this.computing.windData.speed.min, this.options.domain?.max ?? this.computing.windData.speed.max);
  979. return domain;
  980. },
  981. displayRange: () => {
  982. const displayRange = new Cartesian22(
  983. this.options.displayRange?.min ?? this.computing.windData.speed.min,
  984. this.options.displayRange?.max ?? this.computing.windData.speed.max
  985. );
  986. return displayRange;
  987. },
  988. particleHeight: () => this.options.particleHeight || 0,
  989. aspect: () => this.context.drawingBufferWidth / this.context.drawingBufferHeight,
  990. pixelSize: () => this.viewerParameters.pixelSize,
  991. lineWidth: () => {
  992. const width = this.options.lineWidth || DefaultOptions.lineWidth;
  993. return new Cartesian22(width.min, width.max);
  994. },
  995. lineLength: () => {
  996. const length = this.options.lineLength || DefaultOptions.lineLength;
  997. return new Cartesian22(length.min, length.max);
  998. },
  999. is3D: () => this.viewerParameters.sceneMode === SceneMode.SCENE3D,
  1000. segmentsDepthTexture: () => this.textures.segmentsDepth
  1001. },
  1002. vertexShaderSource: ShaderManager.getSegmentDrawVertexShader(),
  1003. fragmentShaderSource: ShaderManager.getSegmentDrawFragmentShader(),
  1004. rawRenderState: this.createRawRenderState({
  1005. viewport: void 0,
  1006. depthTest: {
  1007. enabled: true
  1008. },
  1009. depthMask: true,
  1010. blending: {
  1011. enabled: true,
  1012. blendEquation: WebGLRenderingContext.FUNC_ADD,
  1013. blendFuncSource: WebGLRenderingContext.SRC_ALPHA,
  1014. blendFuncDestination: WebGLRenderingContext.ONE_MINUS_SRC_ALPHA
  1015. }
  1016. })
  1017. });
  1018. return { segments };
  1019. }
  1020. onParticlesTextureSizeChange() {
  1021. const geometry = this.createSegmentsGeometry();
  1022. this.primitives.segments.geometry = geometry;
  1023. const vertexArray = VertexArray2.fromGeometry({
  1024. context: this.context,
  1025. geometry,
  1026. attributeLocations: this.primitives.segments.attributeLocations,
  1027. bufferUsage: BufferUsage2.STATIC_DRAW
  1028. });
  1029. if (this.primitives.segments.commandToExecute) {
  1030. this.primitives.segments.commandToExecute.vertexArray = vertexArray;
  1031. }
  1032. }
  1033. onColorTableChange() {
  1034. this.colorTable.destroy();
  1035. this.colorTable = this.createColorTableTexture();
  1036. }
  1037. updateOptions(options) {
  1038. const needUpdateColorTable = options.colors && JSON.stringify(options.colors) !== JSON.stringify(this.options.colors);
  1039. this.options = deepMerge(options, this.options);
  1040. if (needUpdateColorTable) {
  1041. this.onColorTableChange();
  1042. }
  1043. }
  1044. destroy() {
  1045. Object.values(this.framebuffers).forEach((framebuffer) => {
  1046. framebuffer.destroy();
  1047. });
  1048. Object.values(this.primitives).forEach((primitive) => {
  1049. primitive.destroy();
  1050. });
  1051. this.colorTable.destroy();
  1052. }
  1053. };
  1054. // src/windParticleSystem.ts
  1055. import { ClearCommand as ClearCommand2, Color as Color3, Pass as Pass2 } from "cesium";
  1056. var WindParticleSystem = class {
  1057. constructor(context, windData, options, viewerParameters, scene) {
  1058. __publicField(this, "computing");
  1059. __publicField(this, "rendering");
  1060. __publicField(this, "options");
  1061. __publicField(this, "viewerParameters");
  1062. __publicField(this, "context");
  1063. this.context = context;
  1064. this.options = options;
  1065. this.viewerParameters = viewerParameters;
  1066. this.computing = new WindParticlesComputing(context, windData, options, viewerParameters, scene);
  1067. this.rendering = new WindParticlesRendering(context, options, viewerParameters, this.computing);
  1068. this.clearFramebuffers();
  1069. }
  1070. getPrimitives() {
  1071. const primitives = [
  1072. this.computing.primitives.calculateSpeed,
  1073. this.computing.primitives.updatePosition,
  1074. this.computing.primitives.postProcessingPosition,
  1075. this.rendering.primitives.segments
  1076. ];
  1077. return primitives;
  1078. }
  1079. clearFramebuffers() {
  1080. const clearCommand = new ClearCommand2({
  1081. color: new Color3(0, 0, 0, 0),
  1082. depth: 1,
  1083. framebuffer: void 0,
  1084. pass: Pass2.OPAQUE
  1085. });
  1086. Object.keys(this.rendering.framebuffers).forEach((key) => {
  1087. clearCommand.framebuffer = this.rendering.framebuffers[key];
  1088. clearCommand.execute(this.context);
  1089. });
  1090. }
  1091. changeOptions(options) {
  1092. let maxParticlesChanged = false;
  1093. if (options.particlesTextureSize && this.options.particlesTextureSize !== options.particlesTextureSize) {
  1094. maxParticlesChanged = true;
  1095. }
  1096. const newOptions = deepMerge(options, this.options);
  1097. if (newOptions.particlesTextureSize < 1) {
  1098. throw new Error("particlesTextureSize must be greater than 0");
  1099. }
  1100. this.options = newOptions;
  1101. this.rendering.updateOptions(options);
  1102. this.computing.updateOptions(options);
  1103. if (maxParticlesChanged) {
  1104. this.computing.destroyParticlesTextures();
  1105. this.computing.createParticlesTextures();
  1106. this.rendering.onParticlesTextureSizeChange();
  1107. }
  1108. }
  1109. applyViewerParameters(viewerParameters) {
  1110. this.viewerParameters = viewerParameters;
  1111. this.computing.viewerParameters = viewerParameters;
  1112. this.rendering.viewerParameters = viewerParameters;
  1113. }
  1114. destroy() {
  1115. this.computing.destroy();
  1116. this.rendering.destroy();
  1117. }
  1118. };
  1119. // src/index.ts
  1120. var DefaultOptions = {
  1121. particlesTextureSize: 100,
  1122. dropRate: 3e-3,
  1123. particleHeight: 1e3,
  1124. dropRateBump: 0.01,
  1125. speedFactor: 1,
  1126. lineWidth: { min: 1, max: 2 },
  1127. lineLength: { min: 20, max: 100 },
  1128. colors: ["white"],
  1129. flipY: false,
  1130. useViewerBounds: false,
  1131. domain: void 0,
  1132. displayRange: void 0,
  1133. dynamic: true
  1134. };
  1135. var _WindLayer = class _WindLayer {
  1136. /**
  1137. * WindLayer class for visualizing wind field data with particle animation in Cesium.
  1138. *
  1139. * @class
  1140. * @param {Viewer} viewer - The Cesium viewer instance.
  1141. * @param {WindData} windData - The wind field data to visualize.
  1142. * @param {Partial<WindLayerOptions>} [options] - Optional configuration options for the wind layer.
  1143. * @param {number} [options.particlesTextureSize=100] - Size of the particle texture. Determines the maximum number of particles (size squared).
  1144. * @param {number} [options.particleHeight=0] - Height of particles above the ground in meters.
  1145. * @param {Object} [options.lineWidth={ min: 1, max: 2 }] - Width range of particle trails.
  1146. * @param {Object} [options.lineLength={ min: 20, max: 100 }] - Length range of particle trails.
  1147. * @param {number} [options.speedFactor=1.0] - Factor to adjust the speed of particles.
  1148. * @param {number} [options.dropRate=0.003] - Rate at which particles are dropped (reset).
  1149. * @param {number} [options.dropRateBump=0.001] - Additional drop rate for slow-moving particles.
  1150. * @param {string[]} [options.colors=['white']] - Array of colors for particles. Can be used to create color gradients.
  1151. * @param {boolean} [options.flipY=false] - Whether to flip the Y-axis of the wind data.
  1152. * @param {boolean} [options.useViewerBounds=false] - Whether to use the viewer bounds to generate particles.
  1153. * @param {boolean} [options.dynamic=true] - Whether to enable dynamic particle animation.
  1154. */
  1155. constructor(viewer, windData, options) {
  1156. __publicField(this, "_show", true);
  1157. __publicField(this, "_resized", false);
  1158. __publicField(this, "windData");
  1159. __publicField(this, "viewer");
  1160. __publicField(this, "scene");
  1161. __publicField(this, "options");
  1162. __publicField(this, "particleSystem");
  1163. __publicField(this, "viewerParameters");
  1164. __publicField(this, "_isDestroyed", false);
  1165. __publicField(this, "primitives", []);
  1166. __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
  1167. this.show = true;
  1168. this.viewer = viewer;
  1169. this.scene = viewer.scene;
  1170. this.options = { ..._WindLayer.defaultOptions, ...options };
  1171. this.windData = this.processWindData(windData);
  1172. this.viewerParameters = {
  1173. lonRange: new Cartesian23(-180, 180),
  1174. latRange: new Cartesian23(-90, 90),
  1175. pixelSize: 1e3,
  1176. sceneMode: this.scene.mode
  1177. };
  1178. this.updateViewerParameters();
  1179. this.particleSystem = new WindParticleSystem(this.scene.context, this.windData, this.options, this.viewerParameters, this.scene);
  1180. this.add();
  1181. this.setupEventListeners();
  1182. }
  1183. get show() {
  1184. return this._show;
  1185. }
  1186. set show(value) {
  1187. if (this._show !== value) {
  1188. this._show = value;
  1189. this.updatePrimitivesVisibility(value);
  1190. }
  1191. }
  1192. setupEventListeners() {
  1193. this.viewer.camera.percentageChanged = 0.01;
  1194. this.viewer.camera.changed.addEventListener(this.updateViewerParameters.bind(this));
  1195. this.scene.morphComplete.addEventListener(this.updateViewerParameters.bind(this));
  1196. window.addEventListener("resize", this.updateViewerParameters.bind(this));
  1197. }
  1198. removeEventListeners() {
  1199. this.viewer.camera.changed.removeEventListener(this.updateViewerParameters.bind(this));
  1200. this.scene.morphComplete.removeEventListener(this.updateViewerParameters.bind(this));
  1201. window.removeEventListener("resize", this.updateViewerParameters.bind(this));
  1202. }
  1203. processWindData(windData) {
  1204. if (windData.speed?.min === void 0 || windData.speed?.max === void 0 || windData.speed.array === void 0) {
  1205. const speed = {
  1206. array: new Float32Array(windData.u.array.length),
  1207. min: Number.MAX_VALUE,
  1208. max: Number.MIN_VALUE
  1209. };
  1210. for (let i = 0; i < windData.u.array.length; i++) {
  1211. speed.array[i] = Math.sqrt(windData.u.array[i] * windData.u.array[i] + windData.v.array[i] * windData.v.array[i]);
  1212. if (speed.array[i] !== 0) {
  1213. speed.min = Math.min(speed.min, speed.array[i]);
  1214. speed.max = Math.max(speed.max, speed.array[i]);
  1215. }
  1216. }
  1217. windData = { ...windData, speed };
  1218. }
  1219. return windData;
  1220. }
  1221. /**
  1222. * Get the wind data at a specific longitude and latitude.
  1223. * @param {number} lon - The longitude.
  1224. * @param {number} lat - The latitude.
  1225. * @returns {Object} - An object containing the u, v, and speed values at the specified coordinates.
  1226. */
  1227. getDataAtLonLat(lon, lat) {
  1228. const { bounds, width, height, u, v, speed } = this.windData;
  1229. const { flipY } = this.options;
  1230. if (lon < bounds.west || lon > bounds.east || lat < bounds.south || lat > bounds.north) {
  1231. return null;
  1232. }
  1233. const xNorm = (lon - bounds.west) / (bounds.east - bounds.west) * (width - 1);
  1234. let yNorm = (lat - bounds.south) / (bounds.north - bounds.south) * (height - 1);
  1235. if (flipY) {
  1236. yNorm = height - 1 - yNorm;
  1237. }
  1238. const x = Math.floor(xNorm);
  1239. const y = Math.floor(yNorm);
  1240. const x0 = Math.floor(xNorm);
  1241. const x1 = Math.min(x0 + 1, width - 1);
  1242. const y0 = Math.floor(yNorm);
  1243. const y1 = Math.min(y0 + 1, height - 1);
  1244. const wx = xNorm - x0;
  1245. const wy = yNorm - y0;
  1246. const index = y * width + x;
  1247. const i00 = y0 * width + x0;
  1248. const i10 = y0 * width + x1;
  1249. const i01 = y1 * width + x0;
  1250. const i11 = y1 * width + x1;
  1251. const u00 = u.array[i00];
  1252. const u10 = u.array[i10];
  1253. const u01 = u.array[i01];
  1254. const u11 = u.array[i11];
  1255. const uInterp = (1 - wx) * (1 - wy) * u00 + wx * (1 - wy) * u10 + (1 - wx) * wy * u01 + wx * wy * u11;
  1256. const v00 = v.array[i00];
  1257. const v10 = v.array[i10];
  1258. const v01 = v.array[i01];
  1259. const v11 = v.array[i11];
  1260. const vInterp = (1 - wx) * (1 - wy) * v00 + wx * (1 - wy) * v10 + (1 - wx) * wy * v01 + wx * wy * v11;
  1261. const interpolatedSpeed = Math.sqrt(uInterp * uInterp + vInterp * vInterp);
  1262. return {
  1263. original: {
  1264. u: u.array[index],
  1265. v: v.array[index],
  1266. speed: speed.array[index]
  1267. },
  1268. interpolated: {
  1269. u: uInterp,
  1270. v: vInterp,
  1271. speed: interpolatedSpeed
  1272. }
  1273. };
  1274. }
  1275. updateViewerParameters() {
  1276. const scene = this.viewer.scene;
  1277. const canvas = scene.canvas;
  1278. const corners = [
  1279. { x: 0, y: 0 },
  1280. { x: 0, y: canvas.clientHeight },
  1281. { x: canvas.clientWidth, y: 0 },
  1282. { x: canvas.clientWidth, y: canvas.clientHeight }
  1283. ];
  1284. let minLon = 180;
  1285. let maxLon = -180;
  1286. let minLat = 90;
  1287. let maxLat = -90;
  1288. let isOutsideGlobe = false;
  1289. for (const corner of corners) {
  1290. const cartesian = scene.camera.pickEllipsoid(
  1291. new Cartesian23(corner.x, corner.y),
  1292. scene.globe.ellipsoid
  1293. );
  1294. if (!cartesian) {
  1295. isOutsideGlobe = true;
  1296. break;
  1297. }
  1298. const cartographic = scene.globe.ellipsoid.cartesianToCartographic(cartesian);
  1299. const lon = CesiumMath.toDegrees(cartographic.longitude);
  1300. const lat = CesiumMath.toDegrees(cartographic.latitude);
  1301. minLon = Math.min(minLon, lon);
  1302. maxLon = Math.max(maxLon, lon);
  1303. minLat = Math.min(minLat, lat);
  1304. maxLat = Math.max(maxLat, lat);
  1305. }
  1306. if (!isOutsideGlobe) {
  1307. const lonRange = new Cartesian23(
  1308. Math.max(this.windData.bounds.west, minLon),
  1309. Math.min(this.windData.bounds.east, maxLon)
  1310. );
  1311. const latRange = new Cartesian23(
  1312. Math.max(this.windData.bounds.south, minLat),
  1313. Math.min(this.windData.bounds.north, maxLat)
  1314. );
  1315. const lonBuffer = (lonRange.y - lonRange.x) * 0.05;
  1316. const latBuffer = (latRange.y - latRange.x) * 0.05;
  1317. lonRange.x = Math.max(this.windData.bounds.west, lonRange.x - lonBuffer);
  1318. lonRange.y = Math.min(this.windData.bounds.east, lonRange.y + lonBuffer);
  1319. latRange.x = Math.max(this.windData.bounds.south, latRange.x - latBuffer);
  1320. latRange.y = Math.min(this.windData.bounds.north, latRange.y + latBuffer);
  1321. this.viewerParameters.lonRange = lonRange;
  1322. this.viewerParameters.latRange = latRange;
  1323. const dataLonRange = this.windData.bounds.east - this.windData.bounds.west;
  1324. const dataLatRange = this.windData.bounds.north - this.windData.bounds.south;
  1325. const visibleRatioLon = (lonRange.y - lonRange.x) / dataLonRange;
  1326. const visibleRatioLat = (latRange.y - latRange.x) / dataLatRange;
  1327. const visibleRatio = Math.min(visibleRatioLon, visibleRatioLat);
  1328. const pixelSize = 1e3 * visibleRatio;
  1329. if (pixelSize > 0) {
  1330. this.viewerParameters.pixelSize = Math.max(0, Math.min(1e3, pixelSize));
  1331. }
  1332. }
  1333. this.viewerParameters.sceneMode = this.scene.mode;
  1334. this.particleSystem?.applyViewerParameters(this.viewerParameters);
  1335. }
  1336. /**
  1337. * Update the wind data of the wind layer.
  1338. * @param {WindData} data - The new wind data to apply.
  1339. */
  1340. updateWindData(data) {
  1341. if (this._isDestroyed) return;
  1342. this.windData = this.processWindData(data);
  1343. this.particleSystem.computing.updateWindData(this.windData);
  1344. this.viewer.scene.requestRender();
  1345. this.dispatchEvent("dataChange", this.windData);
  1346. }
  1347. /**
  1348. * Update the options of the wind layer.
  1349. * @param {Partial<WindLayerOptions>} options - The new options to apply.
  1350. */
  1351. updateOptions(options) {
  1352. if (this._isDestroyed) return;
  1353. this.options = deepMerge(options, this.options);
  1354. this.particleSystem.changeOptions(options);
  1355. this.viewer.scene.requestRender();
  1356. this.dispatchEvent("optionsChange", this.options);
  1357. }
  1358. /**
  1359. * Zoom to the wind data bounds.
  1360. * @param {number} [duration=0] - The duration of the zoom animation.
  1361. */
  1362. zoomTo(duration = 0) {
  1363. if (this.windData.bounds) {
  1364. const rectangle = Rectangle.fromDegrees(
  1365. this.windData.bounds.west,
  1366. this.windData.bounds.south,
  1367. this.windData.bounds.east,
  1368. this.windData.bounds.north
  1369. );
  1370. this.viewer.camera.flyTo({
  1371. destination: rectangle,
  1372. duration
  1373. });
  1374. }
  1375. }
  1376. /**
  1377. * Add the wind layer to the scene.
  1378. */
  1379. add() {
  1380. this.primitives = this.particleSystem.getPrimitives();
  1381. this.primitives.forEach((primitive) => {
  1382. this.scene.primitives.add(primitive);
  1383. });
  1384. }
  1385. /**
  1386. * Remove the wind layer from the scene.
  1387. */
  1388. remove() {
  1389. this.primitives.forEach((primitive) => {
  1390. this.scene.primitives.remove(primitive);
  1391. });
  1392. this.primitives = [];
  1393. }
  1394. /**
  1395. * Check if the wind layer is destroyed.
  1396. * @returns {boolean} - True if the wind layer is destroyed, otherwise false.
  1397. */
  1398. isDestroyed() {
  1399. return this._isDestroyed;
  1400. }
  1401. /**
  1402. * Destroy the wind layer and release all resources.
  1403. */
  1404. destroy() {
  1405. this.remove();
  1406. this.removeEventListeners();
  1407. this.particleSystem.destroy();
  1408. this.eventListeners.clear();
  1409. this._isDestroyed = true;
  1410. }
  1411. updatePrimitivesVisibility(visibility) {
  1412. const show = visibility !== void 0 ? visibility : this._show;
  1413. this.primitives.forEach((primitive) => {
  1414. primitive.show = show;
  1415. });
  1416. }
  1417. /**
  1418. * Add an event listener for the specified event type.
  1419. * @param {WindLayerEventType} type - The type of event to listen for.
  1420. * @param {WindLayerEventCallback} callback - The callback function to execute when the event occurs.
  1421. */
  1422. addEventListener(type, callback) {
  1423. if (!this.eventListeners.has(type)) {
  1424. this.eventListeners.set(type, /* @__PURE__ */ new Set());
  1425. }
  1426. this.eventListeners.get(type)?.add(callback);
  1427. }
  1428. /**
  1429. * Remove an event listener for the specified event type.
  1430. * @param {WindLayerEventType} type - The type of event to remove.
  1431. * @param {WindLayerEventCallback} callback - The callback function to remove.
  1432. */
  1433. removeEventListener(type, callback) {
  1434. this.eventListeners.get(type)?.delete(callback);
  1435. }
  1436. dispatchEvent(type, data) {
  1437. this.eventListeners.get(type)?.forEach((callback) => callback(data));
  1438. }
  1439. };
  1440. __publicField(_WindLayer, "defaultOptions", DefaultOptions);
  1441. var WindLayer = _WindLayer;
  1442. export {
  1443. DefaultOptions,
  1444. WindLayer
  1445. };
  1446. //# sourceMappingURL=index.mjs.map