spacemap 0.1.15 → 0.1.16

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /*! spacemap SDK — https://spacemap.co — Mozilla Public License 2.0. Bundles MIT and ISC libraries: THIRD_PARTY_NOTICES.txt in the spacemap npm package. */
2
- import { ACESFilmicToneMapping, AdditiveBlending, AmbientLight, ArrowHelper, BackSide, Box3, BufferAttribute, BufferGeometry, CanvasTexture, Color, CubeTexture, CustomBlending, DataArrayTexture, DataTexture, DepthTexture, DirectionalLight, DoubleSide, Euler, Float32BufferAttribute, FloatType, FrontSide, Group, HalfFloatType, InstancedBufferAttribute, InstancedMesh, Line, LineBasicMaterial, LineLoop, LinearFilter, LinearMipmapLinearFilter, LinearSRGBColorSpace, MathUtils, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, NoBlending, NoColorSpace, NormalBlending, OneFactor, OneMinusSrcAlphaFactor, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, RGBAFormat, RGFormat, RawShaderMaterial, Raycaster, RedFormat, RepeatWrapping, RingGeometry, SRGBColorSpace, Scene, ShaderChunk, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapeUtils, SphereGeometry, Sprite, SpriteMaterial, TOUCH, Texture, TextureLoader, Uint16BufferAttribute, UnsignedByteType, Vector2, Vector3, Vector4, WebGLRenderTarget, WebGLRenderer } from "three";
2
+ import { ACESFilmicToneMapping, AdditiveBlending, AmbientLight, ArrowHelper, BackSide, Box3, BufferAttribute, BufferGeometry, CanvasTexture, ClampToEdgeWrapping, Color, CubeTexture, CustomBlending, DataArrayTexture, DataTexture, DataUtils, DepthTexture, DirectionalLight, DoubleSide, Euler, Float32BufferAttribute, FloatType, FrontSide, Group, HalfFloatType, InstancedBufferAttribute, InstancedMesh, Line, LineBasicMaterial, LineLoop, LinearFilter, LinearMipmapLinearFilter, LinearSRGBColorSpace, MathUtils, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, NoBlending, NoColorSpace, NormalBlending, OneFactor, OneMinusSrcAlphaFactor, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, RGBAFormat, RGFormat, RawShaderMaterial, Raycaster, RedFormat, RepeatWrapping, RingGeometry, SRGBColorSpace, Scene, ShaderChunk, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapeUtils, SphereGeometry, Sprite, SpriteMaterial, TOUCH, Texture, TextureLoader, Uint16BufferAttribute, UnsignedByteType, Vector2, Vector3, Vector4, WebGLRenderTarget, WebGLRenderer } from "three";
3
+ import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
3
4
  import { CSS2DObject } from "three/addons/renderers/CSS2DRenderer.js";
4
5
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
5
6
  import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";
@@ -8,7 +9,6 @@ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
8
9
  import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
9
10
  import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
10
11
  import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
11
- import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
12
12
  import { FullScreenQuad } from "three/addons/postprocessing/Pass.js";
13
13
 
14
14
  //#region src/lib/host.ts
@@ -2986,6 +2986,9 @@ var versions = {};
2986
2986
  function setDataVersions(v) {
2987
2987
  versions = v ?? {};
2988
2988
  }
2989
+ function getDataVersions() {
2990
+ return versions;
2991
+ }
2989
2992
  function versionedUrl(path, cls) {
2990
2993
  return buildVersionedUrl(dataBase(), path, cls);
2991
2994
  }
@@ -5314,13 +5317,392 @@ function equatorialToScene(xEq, yEq, zEq, out) {
5314
5317
  }
5315
5318
 
5316
5319
  //#endregion
5317
- //#region src/lib/math/orbit/sgp4.ts
5318
- var DEG2RAD$9 = Math.PI / 180;
5319
- var COS_EPS$4 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$9);
5320
- var SIN_EPS$4 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$9);
5320
+ //#region src/lib/time/jd.ts
5321
+ var J2000_JD$1 = 2451545;
5322
+ var SECONDS_PER_DAY$2 = 86400;
5321
5323
  var JD_UNIX_EPOCH$1 = 2440587.5;
5324
+ var MS_PER_DAY = 864e5;
5325
+ function unixMsToJD(ms) {
5326
+ return ms / MS_PER_DAY + JD_UNIX_EPOCH$1;
5327
+ }
5328
+ function dateToJD(date) {
5329
+ return unixMsToJD(date.getTime());
5330
+ }
5331
+ function jdToDate(jd) {
5332
+ return new Date((jd - JD_UNIX_EPOCH$1) * MS_PER_DAY);
5333
+ }
5334
+ var TT_MINUS_TAI = 32.184;
5335
+ var LEAP_SECONDS = [
5336
+ [2441317.5, 10],
5337
+ [2441499.5, 11],
5338
+ [2441683.5, 12],
5339
+ [2442048.5, 13],
5340
+ [2442413.5, 14],
5341
+ [2442778.5, 15],
5342
+ [2443144.5, 16],
5343
+ [2443509.5, 17],
5344
+ [2443874.5, 18],
5345
+ [2444239.5, 19],
5346
+ [2444786.5, 20],
5347
+ [2445151.5, 21],
5348
+ [2445516.5, 22],
5349
+ [2446247.5, 23],
5350
+ [2447161.5, 24],
5351
+ [2447892.5, 25],
5352
+ [2448257.5, 26],
5353
+ [2448804.5, 27],
5354
+ [2449169.5, 28],
5355
+ [2449534.5, 29],
5356
+ [2450083.5, 30],
5357
+ [2450630.5, 31],
5358
+ [2451179.5, 32],
5359
+ [2453736.5, 33],
5360
+ [2454832.5, 34],
5361
+ [2456109.5, 35],
5362
+ [2457204.5, 36],
5363
+ [2457754.5, 37]
5364
+ ];
5365
+ function taiMinusUtc(jdUtc) {
5366
+ for (let k = LEAP_SECONDS.length - 1; k >= 0; k--) if (jdUtc >= LEAP_SECONDS[k][0]) return LEAP_SECONDS[k][1];
5367
+ return LEAP_SECONDS[0][1] - 1;
5368
+ }
5369
+ function tdbMinusTt(jd) {
5370
+ const m = 6.239996 + 1.99096871e-7 * (jd - J2000_JD$1) * SECONDS_PER_DAY$2;
5371
+ return .001657 * Math.sin(m + .01671 * Math.sin(m));
5372
+ }
5373
+ function utcToTdb(jd) {
5374
+ if (!Number.isFinite(jd)) return jd;
5375
+ const tt = jd + (taiMinusUtc(jd) + TT_MINUS_TAI) / SECONDS_PER_DAY$2;
5376
+ return tt + tdbMinusTt(tt) / SECONDS_PER_DAY$2;
5377
+ }
5378
+ function tdbToUtc(jd) {
5379
+ if (!Number.isFinite(jd)) return jd;
5380
+ const tt = jd - tdbMinusTt(jd) / SECONDS_PER_DAY$2;
5381
+ return tt - (taiMinusUtc(tt - (taiMinusUtc(tt) + TT_MINUS_TAI) / SECONDS_PER_DAY$2) + TT_MINUS_TAI) / SECONDS_PER_DAY$2;
5382
+ }
5383
+ function jdToEt(jd) {
5384
+ return (utcToTdb(jd) - J2000_JD$1) * SECONDS_PER_DAY$2;
5385
+ }
5386
+ function etToTdbJd(et) {
5387
+ return J2000_JD$1 + et / SECONDS_PER_DAY$2;
5388
+ }
5389
+ function tdbJdToEt(jd) {
5390
+ return (jd - J2000_JD$1) * SECONDS_PER_DAY$2;
5391
+ }
5392
+
5393
+ //#endregion
5394
+ //#region src/lib/math/orbit/teme.ts
5395
+ var DEG$10 = Math.PI / 180;
5396
+ var ARCSEC = DEG$10 / 3600;
5397
+ var NUT_UNIT = 1e-4 * ARCSEC;
5398
+ var REUSE_DAYS = .01;
5399
+ var NUTATION = [
5400
+ [
5401
+ 0,
5402
+ 0,
5403
+ 0,
5404
+ 0,
5405
+ 1,
5406
+ -171996,
5407
+ -174.2,
5408
+ 92025,
5409
+ 8.9
5410
+ ],
5411
+ [
5412
+ 0,
5413
+ 0,
5414
+ 2,
5415
+ -2,
5416
+ 2,
5417
+ -13187,
5418
+ -1.6,
5419
+ 5736,
5420
+ -3.1
5421
+ ],
5422
+ [
5423
+ 0,
5424
+ 0,
5425
+ 2,
5426
+ 0,
5427
+ 2,
5428
+ -2274,
5429
+ -.2,
5430
+ 977,
5431
+ -.5
5432
+ ],
5433
+ [
5434
+ 0,
5435
+ 0,
5436
+ 0,
5437
+ 0,
5438
+ 2,
5439
+ 2062,
5440
+ .2,
5441
+ -895,
5442
+ .5
5443
+ ],
5444
+ [
5445
+ 0,
5446
+ 1,
5447
+ 0,
5448
+ 0,
5449
+ 0,
5450
+ 1426,
5451
+ -3.4,
5452
+ 54,
5453
+ -.1
5454
+ ],
5455
+ [
5456
+ 1,
5457
+ 0,
5458
+ 0,
5459
+ 0,
5460
+ 0,
5461
+ 712,
5462
+ .1,
5463
+ -7,
5464
+ 0
5465
+ ],
5466
+ [
5467
+ 0,
5468
+ 1,
5469
+ 2,
5470
+ -2,
5471
+ 2,
5472
+ -517,
5473
+ 1.2,
5474
+ 224,
5475
+ -.6
5476
+ ],
5477
+ [
5478
+ 0,
5479
+ 0,
5480
+ 2,
5481
+ 0,
5482
+ 1,
5483
+ -386,
5484
+ -.4,
5485
+ 200,
5486
+ 0
5487
+ ],
5488
+ [
5489
+ 1,
5490
+ 0,
5491
+ 2,
5492
+ 0,
5493
+ 2,
5494
+ -301,
5495
+ 0,
5496
+ 129,
5497
+ -.1
5498
+ ],
5499
+ [
5500
+ 0,
5501
+ -1,
5502
+ 2,
5503
+ -2,
5504
+ 2,
5505
+ 217,
5506
+ -.5,
5507
+ -95,
5508
+ .3
5509
+ ],
5510
+ [
5511
+ 1,
5512
+ 0,
5513
+ 0,
5514
+ -2,
5515
+ 0,
5516
+ -158,
5517
+ 0,
5518
+ -1,
5519
+ 0
5520
+ ],
5521
+ [
5522
+ 0,
5523
+ 0,
5524
+ 2,
5525
+ -2,
5526
+ 1,
5527
+ 129,
5528
+ .1,
5529
+ -70,
5530
+ 0
5531
+ ],
5532
+ [
5533
+ -1,
5534
+ 0,
5535
+ 2,
5536
+ 0,
5537
+ 2,
5538
+ 123,
5539
+ 0,
5540
+ -53,
5541
+ 0
5542
+ ],
5543
+ [
5544
+ 1,
5545
+ 0,
5546
+ 0,
5547
+ 0,
5548
+ 1,
5549
+ 63,
5550
+ .1,
5551
+ -33,
5552
+ 0
5553
+ ],
5554
+ [
5555
+ 0,
5556
+ 0,
5557
+ 0,
5558
+ 2,
5559
+ 0,
5560
+ 63,
5561
+ 0,
5562
+ -2,
5563
+ 0
5564
+ ],
5565
+ [
5566
+ -1,
5567
+ 0,
5568
+ 2,
5569
+ 2,
5570
+ 2,
5571
+ -59,
5572
+ 0,
5573
+ 26,
5574
+ 0
5575
+ ],
5576
+ [
5577
+ -1,
5578
+ 0,
5579
+ 0,
5580
+ 0,
5581
+ 1,
5582
+ -58,
5583
+ -.1,
5584
+ 32,
5585
+ 0
5586
+ ],
5587
+ [
5588
+ 1,
5589
+ 0,
5590
+ 2,
5591
+ 0,
5592
+ 1,
5593
+ -51,
5594
+ 0,
5595
+ 27,
5596
+ 0
5597
+ ]
5598
+ ];
5599
+ function mul(a, b) {
5600
+ const out = new Array(9);
5601
+ for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) out[3 * r + c] = a[3 * r] * b[c] + a[3 * r + 1] * b[3 + c] + a[3 * r + 2] * b[6 + c];
5602
+ return out;
5603
+ }
5604
+ function rx(a) {
5605
+ const c = Math.cos(a);
5606
+ const s = Math.sin(a);
5607
+ return [
5608
+ 1,
5609
+ 0,
5610
+ 0,
5611
+ 0,
5612
+ c,
5613
+ s,
5614
+ 0,
5615
+ -s,
5616
+ c
5617
+ ];
5618
+ }
5619
+ function ry(a) {
5620
+ const c = Math.cos(a);
5621
+ const s = Math.sin(a);
5622
+ return [
5623
+ c,
5624
+ 0,
5625
+ -s,
5626
+ 0,
5627
+ 1,
5628
+ 0,
5629
+ s,
5630
+ 0,
5631
+ c
5632
+ ];
5633
+ }
5634
+ function rz(a) {
5635
+ const c = Math.cos(a);
5636
+ const s = Math.sin(a);
5637
+ return [
5638
+ c,
5639
+ s,
5640
+ 0,
5641
+ -s,
5642
+ c,
5643
+ 0,
5644
+ 0,
5645
+ 0,
5646
+ 1
5647
+ ];
5648
+ }
5649
+ function temeToEclipticAt(jd) {
5650
+ const T = (jd - J2000_JD$1) / 36525;
5651
+ const zeta = (2306.2181 * T + .30188 * T * T + .017998 * T ** 3) * ARCSEC;
5652
+ const theta = (2004.3109 * T - .42665 * T * T - .041833 * T ** 3) * ARCSEC;
5653
+ const z = (2306.2181 * T + 1.09468 * T * T + .018203 * T ** 3) * ARCSEC;
5654
+ const l = 134.96298139 + 477198.8673981 * T + .0086972 * T * T;
5655
+ const lp = 357.52772333 + 35999.05034 * T - 1603e-7 * T * T;
5656
+ const F = 93.27191028 + 483202.0175381 * T - .0036825 * T * T;
5657
+ const D = 297.85036306 + 445267.11148 * T - .0019142 * T * T;
5658
+ const om = 125.04452222 - 1934.1362608 * T + .0020708 * T * T;
5659
+ let dpsi = 0;
5660
+ let deps = 0;
5661
+ for (const [kl, klp, kF, kD, kOm, a, b, c, d] of NUTATION) {
5662
+ const arg = (kl * l + klp * lp + kF * F + kD * D + kOm * om) * DEG$10;
5663
+ dpsi += (a + b * T) * Math.sin(arg);
5664
+ deps += (c + d * T) * Math.cos(arg);
5665
+ }
5666
+ dpsi *= NUT_UNIT;
5667
+ deps *= NUT_UNIT;
5668
+ const epsMean = (84381.448 - 46.815 * T - 59e-5 * T * T + .001813 * T ** 3) * ARCSEC;
5669
+ const eqEquinoxes = dpsi * Math.cos(epsMean);
5670
+ const toJ2000 = mul(mul(mul(mul(rz(zeta), ry(-theta)), rz(z)), mul(mul(rx(-epsMean), rz(dpsi)), rx(epsMean + deps))), rz(-eqEquinoxes));
5671
+ return mul(rx(EARTH_OBLIQUITY_DEG * DEG$10), toJ2000);
5672
+ }
5673
+ var cachedJd = NaN;
5674
+ var cached = [
5675
+ 1,
5676
+ 0,
5677
+ 0,
5678
+ 0,
5679
+ 1,
5680
+ 0,
5681
+ 0,
5682
+ 0,
5683
+ 1
5684
+ ];
5685
+ function temeToEclipticMatrix(jd) {
5686
+ if (!(Math.abs(jd - cachedJd) <= REUSE_DAYS)) {
5687
+ cached = temeToEclipticAt(jd);
5688
+ cachedJd = jd;
5689
+ }
5690
+ return cached;
5691
+ }
5692
+ function temeToEcliptic(x, y, z, jd) {
5693
+ const m = temeToEclipticMatrix(jd);
5694
+ return [
5695
+ m[0] * x + m[1] * y + m[2] * z,
5696
+ m[3] * x + m[4] * y + m[5] * z,
5697
+ m[6] * x + m[7] * y + m[8] * z
5698
+ ];
5699
+ }
5700
+
5701
+ //#endregion
5702
+ //#region src/lib/math/orbit/sgp4.ts
5703
+ var JD_UNIX_EPOCH = 2440587.5;
5322
5704
  function jdToIso(jd) {
5323
- return (/* @__PURE__ */ new Date((jd - JD_UNIX_EPOCH$1) * 864e5)).toISOString();
5705
+ return (/* @__PURE__ */ new Date((jd - JD_UNIX_EPOCH) * 864e5)).toISOString();
5324
5706
  }
5325
5707
  function buildSatrec(inputs, name) {
5326
5708
  const satrec = json2satrec({
@@ -5358,25 +5740,19 @@ function sgp4PositionTEME(satrec, jd) {
5358
5740
  z
5359
5741
  ];
5360
5742
  }
5361
- function temeKmToThreeJS(x, y, z) {
5743
+ function temeKmToThreeJS(x, y, z, jd) {
5362
5744
  const scale = 10 / AU_KM;
5363
- const xs = x * scale;
5364
- let ys = y * scale;
5365
- let zs = z * scale;
5366
- const yEcl = ys * COS_EPS$4 + zs * SIN_EPS$4;
5367
- const zEcl = -ys * SIN_EPS$4 + zs * COS_EPS$4;
5368
- ys = yEcl;
5369
- zs = zEcl;
5745
+ const [xe, ye, ze] = temeToEcliptic(x, y, z, jd);
5370
5746
  return [
5371
- xs,
5372
- zs,
5373
- -ys
5747
+ xe * scale,
5748
+ ze * scale,
5749
+ -ye * scale
5374
5750
  ];
5375
5751
  }
5376
5752
  function sgp4PositionScene(satrec, jd) {
5377
5753
  const teme = sgp4PositionTEME(satrec, jd);
5378
5754
  if (!teme) return null;
5379
- return temeKmToThreeJS(teme[0], teme[1], teme[2]);
5755
+ return temeKmToThreeJS(teme[0], teme[1], teme[2], jd);
5380
5756
  }
5381
5757
 
5382
5758
  //#endregion
@@ -5445,32 +5821,109 @@ function yieldToMain() {
5445
5821
  }
5446
5822
 
5447
5823
  //#endregion
5448
- //#region src/lib/time/jd.ts
5449
- var J2000_JD$2 = 2451545;
5450
- var SECONDS_PER_DAY$2 = 86400;
5451
- var JD_UNIX_EPOCH = 2440587.5;
5452
- var MS_PER_DAY = 864e5;
5453
- function unixMsToJD(ms) {
5454
- return ms / MS_PER_DAY + JD_UNIX_EPOCH;
5824
+ //#region src/lib/math/orbit/position.ts
5825
+ var DEG2RAD$8 = Math.PI / 180;
5826
+ var COS_EPS$2 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$8);
5827
+ var SIN_EPS$2 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$8);
5828
+ function orbitalToThreeJS(xOrb, yOrb, w, i, om, equatorial = false) {
5829
+ const cosW = Math.cos(w * DEG2RAD$8);
5830
+ const sinW = Math.sin(w * DEG2RAD$8);
5831
+ const cosI = Math.cos(i * DEG2RAD$8);
5832
+ const sinI = Math.sin(i * DEG2RAD$8);
5833
+ const cosOm = Math.cos(om * DEG2RAD$8);
5834
+ const sinOm = Math.sin(om * DEG2RAD$8);
5835
+ const x = (cosOm * cosW - sinOm * sinW * cosI) * xOrb + (-cosOm * sinW - sinOm * cosW * cosI) * yOrb;
5836
+ let y = (sinOm * cosW + cosOm * sinW * cosI) * xOrb + (-sinOm * sinW + cosOm * cosW * cosI) * yOrb;
5837
+ let z = sinW * sinI * xOrb + cosW * sinI * yOrb;
5838
+ if (equatorial) {
5839
+ const yEcl = y * COS_EPS$2 + z * SIN_EPS$2;
5840
+ const zEcl = -y * SIN_EPS$2 + z * COS_EPS$2;
5841
+ y = yEcl;
5842
+ z = zEcl;
5843
+ }
5844
+ return [
5845
+ x * 10,
5846
+ z * 10,
5847
+ -y * 10
5848
+ ];
5455
5849
  }
5456
- function dateToJD(date) {
5457
- return unixMsToJD(date.getTime());
5850
+ function elementsJd(el, jd) {
5851
+ return el.equatorial ? jd : utcToTdb(jd);
5458
5852
  }
5459
- function jdToDate(jd) {
5460
- return new Date((jd - JD_UNIX_EPOCH) * MS_PER_DAY);
5853
+ function clockJd(el, t) {
5854
+ return el.equatorial ? t : tdbToUtc(t);
5461
5855
  }
5462
- function jdToEt(jd) {
5463
- return (jd - J2000_JD$2) * SECONDS_PER_DAY$2;
5856
+ function propagateOrbitAngles(el, jd) {
5857
+ return anglesAt(el, elementsJd(el, jd));
5464
5858
  }
5465
- function etToJd(et) {
5466
- return J2000_JD$2 + et / SECONDS_PER_DAY$2;
5859
+ function anglesAt(el, t) {
5860
+ const { omDot, wDot, om, w, epoch } = el;
5861
+ if (!omDot && !wDot) return el;
5862
+ const dt = t - epoch;
5863
+ return {
5864
+ ...el,
5865
+ om: omDot ? om + omDot * dt : om,
5866
+ w: wDot ? w + wDot * dt : w
5867
+ };
5868
+ }
5869
+ function orbitalElementsToPositionJD(el, jd) {
5870
+ const { a, e, i, ma, n, epoch } = el;
5871
+ if (!isFinite(a) || !isFinite(e) || !isFinite(ma) || !isFinite(n)) {
5872
+ console.warn(`NaN in orbital elements: a=${a} e=${e} ma=${ma} n=${n}`);
5873
+ return null;
5874
+ }
5875
+ const t = elementsJd(el, jd);
5876
+ const M = (ma + n * (t - epoch)) * DEG2RAD$8;
5877
+ const { om: omPropagated, w: wPropagated } = anglesAt(el, t);
5878
+ let nu;
5879
+ let r;
5880
+ if (e < 1 || a > 0) {
5881
+ const eClamped = Math.min(e, .9999999);
5882
+ const E = solveKepler(M, eClamped);
5883
+ const sinNu = Math.sqrt(1 - eClamped * eClamped) * Math.sin(E) / (1 - eClamped * Math.cos(E));
5884
+ const cosNu = (Math.cos(E) - eClamped) / (1 - eClamped * Math.cos(E));
5885
+ nu = Math.atan2(sinNu, cosNu);
5886
+ r = a * (1 - eClamped * Math.cos(E));
5887
+ } else {
5888
+ const H = solveKeplerHyperbolic(M, e);
5889
+ if (!isFinite(H)) {
5890
+ console.warn(`solveKeplerHyperbolic overflow: M=${M} e=${e}`);
5891
+ return null;
5892
+ }
5893
+ const denom = e * Math.cosh(H) - 1;
5894
+ if (Math.abs(denom) < 1e-15) {
5895
+ console.warn(`Hyperbolic denom near zero: H=${H} e=${e} denom=${denom}`);
5896
+ return null;
5897
+ }
5898
+ const sinNu = Math.sqrt(e * e - 1) * Math.sinh(H) / denom;
5899
+ const cosNu = (e - Math.cosh(H)) / denom;
5900
+ nu = Math.atan2(sinNu, cosNu);
5901
+ r = a * (1 - e * Math.cosh(H));
5902
+ }
5903
+ const xOrb = r * Math.cos(nu);
5904
+ const yOrb = r * Math.sin(nu);
5905
+ if (!isFinite(xOrb) || !isFinite(yOrb)) {
5906
+ console.warn(`Non-finite orbital position: r=${r} nu=${nu} xOrb=${xOrb} yOrb=${yOrb} a=${a} e=${e}`);
5907
+ return null;
5908
+ }
5909
+ return orbitalToThreeJS(xOrb, yOrb, wPropagated, i, omPropagated, el.equatorial);
5910
+ }
5911
+ function parabolicToPositionJD(el, jd) {
5912
+ const { q, tp, i, om, w } = el;
5913
+ if (q == null || tp == null || !isFinite(q) || !isFinite(tp)) return null;
5914
+ const result = solveBarker(q, tp, elementsJd(el, jd));
5915
+ if (!result) return null;
5916
+ const xOrb = result.r * Math.cos(result.nu);
5917
+ const yOrb = result.r * Math.sin(result.nu);
5918
+ if (!isFinite(xOrb) || !isFinite(yOrb)) return null;
5919
+ return orbitalToThreeJS(xOrb, yOrb, w, i, om, el.equatorial);
5467
5920
  }
5468
5921
 
5469
5922
  //#endregion
5470
5923
  //#region src/lib/math/orbit/soa.ts
5471
- var DEG2RAD$8 = Math.PI / 180;
5472
- var COS_EPS$3 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$8);
5473
- var SIN_EPS$3 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$8);
5924
+ var DEG2RAD$7 = Math.PI / 180;
5925
+ var COS_EPS$1 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$7);
5926
+ var SIN_EPS$1 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$7);
5474
5927
  var KM_TO_SCENE = 10 / AU_KM;
5475
5928
  var KIND_SKIP = 0;
5476
5929
  var KIND_KEPLER = 1;
@@ -5529,8 +5982,10 @@ async function packBodiesSliced(bodies, skip, applyFlagFilter = false) {
5529
5982
  }
5530
5983
  if (!packRowInto(cols, idx, bodies[idx], skip)) continue;
5531
5984
  const d = bodies[idx].data;
5532
- if (d.validityStart < start) start = d.validityStart;
5533
- if (d.validityEnd > end) end = d.validityEnd;
5985
+ const s = clockJd(d, d.validityStart);
5986
+ const e = clockJd(d, d.validityEnd);
5987
+ if (s < start) start = s;
5988
+ if (e > end) end = e;
5534
5989
  }
5535
5990
  cols.validityStart = start === Infinity ? -Infinity : start;
5536
5991
  cols.validityEnd = end === -Infinity ? Infinity : end;
@@ -5870,6 +6325,11 @@ var TYPE_COLOR_PROBE = "#d5cfe7";
5870
6325
  var SUN_ID = "naif-10";
5871
6326
  var EARTH_ID = "naif-399";
5872
6327
  var SSB_ID = "naif-0";
6328
+ function naifId(objectId) {
6329
+ if (!objectId.startsWith("naif-")) return null;
6330
+ const value = Number.parseInt(objectId.slice(5), 10);
6331
+ return Number.isFinite(value) ? value : null;
6332
+ }
5873
6333
  function dominantPlanetId(id) {
5874
6334
  const m = /^naif-([1-9])$/.exec(id);
5875
6335
  return m ? `naif-${m[1]}99` : null;
@@ -5937,10 +6397,6 @@ function resolveBodyColor(data) {
5937
6397
  }
5938
6398
  return DEFAULT_BODY_COLOR;
5939
6399
  }
5940
- var UNKNOWN_SURFACE_COLOR = "#6b6b6b";
5941
- function bodyMeshColor(data) {
5942
- return data.color ?? UNKNOWN_SURFACE_COLOR;
5943
- }
5944
6400
 
5945
6401
  //#endregion
5946
6402
  //#region src/lib/math/orbit/partition.ts
@@ -6202,6 +6658,8 @@ var MinorBucket = class {
6202
6658
  keys: new Float64Array(counts[b]).fill(NO_KEY)
6203
6659
  });
6204
6660
  }
6661
+ const clockStarts = this.chunks.map((chunk) => tdbToUtc(chunk.validityStart));
6662
+ const clockEnds = this.chunks.map((chunk) => tdbToUtc(chunk.validityEnd));
6205
6663
  const starts = new Array(k).fill(Infinity);
6206
6664
  const ends = new Array(k).fill(-Infinity);
6207
6665
  const writeIdx = new Array(k).fill(0);
@@ -6222,8 +6680,11 @@ var MinorBucket = class {
6222
6680
  const key = rowKey(cols, i);
6223
6681
  g.keys[outIdx] = key;
6224
6682
  if (fillOrbitColumnRow(cols, i, g.cols, outIdx, key, skip)) {
6225
- if (cols.validityStart < starts[b]) starts[b] = cols.validityStart;
6226
- if (cols.validityEnd > ends[b]) ends[b] = cols.validityEnd;
6683
+ const tle = g.cols.equatorial[outIdx] === 1;
6684
+ const start = tle ? cols.validityStart : clockStarts[c];
6685
+ const end = tle ? cols.validityEnd : clockEnds[c];
6686
+ if (start < starts[b]) starts[b] = start;
6687
+ if (end > ends[b]) ends[b] = end;
6227
6688
  }
6228
6689
  if (g.colors && tmp) {
6229
6690
  const data = materializeBodyData(cols, i, this.labels, this.parentIdType);
@@ -6618,17 +7079,23 @@ async function fetchWithTimeout(input, init = {}, timeoutMs = BOOT_FETCH_TIMEOUT
6618
7079
 
6619
7080
  //#endregion
6620
7081
  //#region src/lib/fetch/systems-global.ts
6621
- var angles = /* @__PURE__ */ new Map();
7082
+ var phases = /* @__PURE__ */ new Map();
6622
7083
  var gmKm3s2 = /* @__PURE__ */ new Map();
6623
7084
  var loadPromise$1 = null;
6624
7085
  function loadSystemsGlobal() {
6625
7086
  if (loadPromise$1) return loadPromise$1;
6626
7087
  const p = (async () => {
6627
7088
  const r = await fetchWithTimeout(`${dataBase()}/v1/systems/global.json`);
6628
- if (!r.ok) return;
7089
+ if (!r.ok) throw new Error(`systems/global.json: ${r.status}`);
6629
7090
  const raw = await r.json();
6630
7091
  if (raw.gm) for (const [k, v] of Object.entries(raw.gm)) gmKm3s2.set(parseInt(k, 10), v);
6631
- if (raw.nut_prec_angles) for (const [k, v] of Object.entries(raw.nut_prec_angles)) angles.set(parseInt(k, 10), v);
7092
+ if (raw.nut_prec_angles) for (const [k, v] of Object.entries(raw.nut_prec_angles)) {
7093
+ const phaseDegree = raw.nut_prec_phase_degree?.[k] ?? 1;
7094
+ phases.set(parseInt(k, 10), {
7095
+ angles: v,
7096
+ phaseDegree
7097
+ });
7098
+ }
6632
7099
  })();
6633
7100
  p.catch(() => {
6634
7101
  if (loadPromise$1 === p) loadPromise$1 = null;
@@ -6636,11 +7103,14 @@ function loadSystemsGlobal() {
6636
7103
  loadPromise$1 = p;
6637
7104
  return p;
6638
7105
  }
6639
- function ownerIdFor(naifId) {
6640
- return naifId >= 100 ? Math.trunc(naifId / 100) : naifId;
6641
- }
6642
- function getNutPrecAngles(ownerId) {
6643
- return angles.get(ownerId);
7106
+ function nutPrecFor(id, sums) {
7107
+ const naif = naifId(id);
7108
+ if (naif === null || !sums) return void 0;
7109
+ const owned = phases.get(naif >= 100 ? Math.trunc(naif / 100) : naif);
7110
+ return owned && {
7111
+ ...sums,
7112
+ ...owned
7113
+ };
6644
7114
  }
6645
7115
  function getGmKm3s2(naifId) {
6646
7116
  return gmKm3s2.get(naifId);
@@ -7433,7 +7903,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
7433
7903
  const probeCount = view.getUint32(24, true);
7434
7904
  const subchunkDays = view.getFloat32(28, true);
7435
7905
  const subchunkS = subchunkDays * SECONDS_PER_DAY$2;
7436
- const chunkStartEt = jdToEt(startJd);
7906
+ const chunkStartEt = tdbJdToEt(startJd);
7437
7907
  const probes = [];
7438
7908
  let offset = HEADER_SIZE$2;
7439
7909
  for (let p = 0; p < probeCount; p++) {
@@ -7558,11 +8028,12 @@ function findSegmentIndex(starts, ends, jd) {
7558
8028
  return jd < ends[lo] ? lo : -1;
7559
8029
  }
7560
8030
  function chebyshevPositionKm(body, jd) {
7561
- const idx = findSegmentIndex(body.startJds, body.endJds, jd);
8031
+ const tdb = utcToTdb(jd);
8032
+ const idx = findSegmentIndex(body.startJds, body.endJds, tdb);
7562
8033
  if (idx < 0) return null;
7563
8034
  const start = body.startJds[idx];
7564
8035
  const end = body.endJds[idx];
7565
- const tau = 2 * (jd - start) / (end - start) - 1;
8036
+ const tau = 2 * (tdb - start) / (end - start) - 1;
7566
8037
  const N = body.coeffsPerAxis;
7567
8038
  const coeffs = body.coeffs;
7568
8039
  const base = idx * 3 * N;
@@ -7599,11 +8070,12 @@ function chebyshevPositionScene(body, jd) {
7599
8070
  ];
7600
8071
  }
7601
8072
  function chebyshevStateKm(body, jd) {
7602
- const idx = findSegmentIndex(body.startJds, body.endJds, jd);
8073
+ const tdb = utcToTdb(jd);
8074
+ const idx = findSegmentIndex(body.startJds, body.endJds, tdb);
7603
8075
  if (idx < 0) return null;
7604
8076
  const start = body.startJds[idx];
7605
8077
  const end = body.endJds[idx];
7606
- const tau = 2 * (jd - start) / (end - start) - 1;
8078
+ const tau = 2 * (tdb - start) / (end - start) - 1;
7607
8079
  const dTauDt = 2 / (end - start);
7608
8080
  const N = body.coeffsPerAxis;
7609
8081
  const coeffs = body.coeffs;
@@ -7651,7 +8123,7 @@ function chebyshevStateKm(body, jd) {
7651
8123
 
7652
8124
  //#endregion
7653
8125
  //#region src/lib/math/orbit/state.ts
7654
- var DEG2RAD$7 = Math.PI / 180;
8126
+ var DEG2RAD$6 = Math.PI / 180;
7655
8127
  var RAD2DEG$1 = 180 / Math.PI;
7656
8128
  var SEC_PER_DAY = 86400;
7657
8129
  var AU_PER_DAY_TO_KM_PER_SEC = AU_KM / SEC_PER_DAY;
@@ -7920,7 +8392,7 @@ var RAD2DEG = 180 / Math.PI;
7920
8392
  function keplerPureElements(sub, subStartEt, muKm3S2) {
7921
8393
  if (muKm3S2 <= 0) return null;
7922
8394
  const nRadS = Math.sqrt(muKm3S2 / (sub.aKm * sub.aKm * sub.aKm));
7923
- const epoch = etToJd(subStartEt + sub.tAnchorOffsetS);
8395
+ const epoch = etToTdbJd(subStartEt + sub.tAnchorOffsetS);
7924
8396
  return {
7925
8397
  a: sub.aKm / AU_KM,
7926
8398
  e: sub.e,
@@ -7934,7 +8406,7 @@ function keplerPureElements(sub, subStartEt, muKm3S2) {
7934
8406
  };
7935
8407
  }
7936
8408
  function keplerDriftElements(sub, subStartEt) {
7937
- const epoch = etToJd(subStartEt + sub.tAnchorOffsetS);
8409
+ const epoch = etToTdbJd(subStartEt + sub.tAnchorOffsetS);
7938
8410
  return {
7939
8411
  a: sub.aKm / AU_KM,
7940
8412
  e: sub.e,
@@ -7962,7 +8434,7 @@ function chebyshevElements(probe, jd, muKm3S2) {
7962
8434
  state.velocity[0] * KM_DAY_TO_AU_DAY,
7963
8435
  state.velocity[1] * KM_DAY_TO_AU_DAY,
7964
8436
  state.velocity[2] * KM_DAY_TO_AU_DAY
7965
- ], muAuDay2, jd);
8437
+ ], muAuDay2, utcToTdb(jd));
7966
8438
  }
7967
8439
  function probeOsculatingElements(probe, jd, muKm3S2) {
7968
8440
  const idx = findSubChunkIndex(probe, jdToEt(jd));
@@ -8351,7 +8823,7 @@ function chebyshevOsculatingElements(body, parentId, jd, origin) {
8351
8823
  (state.velocity[0] - (from?.velocity[0] ?? 0)) * KM_DAY_TO_AU_DAY,
8352
8824
  (state.velocity[1] - (from?.velocity[1] ?? 0)) * KM_DAY_TO_AU_DAY,
8353
8825
  (state.velocity[2] - (from?.velocity[2] ?? 0)) * KM_DAY_TO_AU_DAY
8354
- ], muAuDay2, jd);
8826
+ ], muAuDay2, utcToTdb(jd));
8355
8827
  }
8356
8828
  function chebyshevHelioElements(body, jd, sun) {
8357
8829
  if (!sun || body.parentId !== SSB_NAIF_ID || body.naifId === SUN_NAIF_ID) return null;
@@ -8845,7 +9317,7 @@ function isChebyshev(zoom) {
8845
9317
  return zoom.shape === "chunked";
8846
9318
  }
8847
9319
  function chunkIndexForJd(zone, jd) {
8848
- const idx = Math.floor((jd - zone.start_jd) / zone.chunk_days);
9320
+ const idx = Math.floor((utcToTdb(jd) - zone.start_jd) / zone.chunk_days);
8849
9321
  return Math.max(0, Math.min(zone.chunks - 1, idx));
8850
9322
  }
8851
9323
  function isProbeZone(entry) {
@@ -9184,8 +9656,8 @@ var ChebyshevStore = class extends ChunkWindow {
9184
9656
  const params = this.zoneParams.get(zone);
9185
9657
  if (!params) return null;
9186
9658
  return {
9187
- start: params.start_jd,
9188
- end: params.end_jd
9659
+ start: tdbToUtc(params.start_jd),
9660
+ end: tdbToUtc(params.end_jd)
9189
9661
  };
9190
9662
  }
9191
9663
  isAwaited(objectId, jd) {
@@ -9504,6 +9976,28 @@ function rowsFor(chunk, id) {
9504
9976
  return index.get(id) ?? NO_ROWS;
9505
9977
  }
9506
9978
 
9979
+ //#endregion
9980
+ //#region src/lib/fetch/version-check.ts
9981
+ var CHECK_TIMEOUT_MS = 15e3;
9982
+ async function dataVersionChanged() {
9983
+ try {
9984
+ const res = await fetch(`${dataBase()}/v1/metadata.json`, {
9985
+ cache: "no-store",
9986
+ signal: AbortSignal.timeout(CHECK_TIMEOUT_MS)
9987
+ });
9988
+ if (!res.ok) return false;
9989
+ const meta = await res.json();
9990
+ const live = getDataVersions();
9991
+ if (Object.keys(live).length === 0) return false;
9992
+ const next = meta.versions ?? {};
9993
+ const keys = new Set([...Object.keys(live), ...Object.keys(next)]);
9994
+ for (const k of keys) if (live[k] !== next[k]) return true;
9995
+ return false;
9996
+ } catch {
9997
+ return false;
9998
+ }
9999
+ }
10000
+
9507
10001
  //#endregion
9508
10002
  //#region src/lib/fetch/gz.ts
9509
10003
  function gunzip(res) {
@@ -9537,12 +10031,12 @@ function memoizedGzJson(url, opts = {}) {
9537
10031
  //#endregion
9538
10032
  //#region src/lib/fetch/bundle-cache.ts
9539
10033
  var MAX_BUNDLES = 24;
9540
- var cache$2 = /* @__PURE__ */ new Map();
10034
+ var cache$3 = /* @__PURE__ */ new Map();
9541
10035
  function fetchGzipBundle(url, init) {
9542
- let p = cache$2.get(url);
10036
+ let p = cache$3.get(url);
9543
10037
  if (p) {
9544
- cache$2.delete(url);
9545
- cache$2.set(url, p);
10038
+ cache$3.delete(url);
10039
+ cache$3.set(url, p);
9546
10040
  }
9547
10041
  if (!p) {
9548
10042
  p = (async () => {
@@ -9553,13 +10047,13 @@ function fetchGzipBundle(url, init) {
9553
10047
  }
9554
10048
  return gunzipJson(res);
9555
10049
  })();
9556
- cache$2.set(url, p);
10050
+ cache$3.set(url, p);
9557
10051
  p.catch(() => {
9558
- if (cache$2.get(url) === p) cache$2.delete(url);
10052
+ if (cache$3.get(url) === p) cache$3.delete(url);
9559
10053
  });
9560
- for (const key of cache$2.keys()) {
9561
- if (cache$2.size <= MAX_BUNDLES) break;
9562
- cache$2.delete(key);
10054
+ for (const key of cache$3.keys()) {
10055
+ if (cache$3.size <= MAX_BUNDLES) break;
10056
+ cache$3.delete(key);
9563
10057
  }
9564
10058
  }
9565
10059
  return p;
@@ -9653,9 +10147,11 @@ async function earthZoneEndJd() {
9653
10147
  return zoom && isDateSegmented(zoom) ? unixMsToJD(lastCoveredMs(zoom)) : Infinity;
9654
10148
  }
9655
10149
  async function coverageOf(id) {
9656
- const windows = (await fetchObjectDetail(id, false)).global?.coverage?.windows ?? [];
9657
- const openEnd = id.startsWith("norad_satcat-") ? await earthZoneEndJd() : Infinity;
9658
- return windows.map(([start, end]) => [start ?? -Infinity, end ?? openEnd]).filter(([start, end]) => end > start);
10150
+ const detail = await fetchObjectDetail(id, false);
10151
+ const windows = detail.global?.coverage?.windows ?? [];
10152
+ const tle = detail.global?.orbit?.scale === "planet";
10153
+ const openEnd = tle ? await earthZoneEndJd() : Infinity;
10154
+ return windows.map(([start, end]) => [start ?? -Infinity, end == null ? openEnd : tle ? end : tdbToUtc(end)]).filter(([start, end]) => end > start);
9659
10155
  }
9660
10156
  function nearestCoveredJd(windows, jd) {
9661
10157
  let best = null;
@@ -9731,7 +10227,7 @@ function bodyDataFromGlobal(id, detail) {
9731
10227
  validityStart: omm ? orbit.epoch_jd - SGP4_VALIDITY_SLACK_DAYS : -Infinity,
9732
10228
  validityEnd: omm ? orbit.epoch_jd + SGP4_VALIDITY_SLACK_DAYS : Infinity,
9733
10229
  orbitalSource: parseOrbitalSource(orbit.source),
9734
- ...coverageStart != null ? { visibleFromDays: coverageStart - J2000_JD$2 } : {},
10230
+ ...coverageStart != null ? { visibleFromDays: coverageStart - J2000_JD$1 } : {},
9735
10231
  ...isParabolic ? {
9736
10232
  q: orbit.q,
9737
10233
  tp: orbit.tp
@@ -9829,34 +10325,44 @@ async function ensureTargetStreamed(ctx, targetId) {
9829
10325
 
9830
10326
  //#endregion
9831
10327
  //#region src/lib/math/orientation.ts
9832
- var DEG2RAD$6 = Math.PI / 180;
9833
- var J2000_JD$1 = 2451545;
10328
+ var DEG2RAD$5 = Math.PI / 180;
9834
10329
  var DAYS_PER_CENTURY = 36525;
9835
- function bodyQuaternion(orientation, currentJd, nutPrec) {
9836
- const dt = currentJd - J2000_JD$1;
10330
+ function iauAngles(orientation, currentJd, nutPrec) {
10331
+ const dt = utcToTdb(currentJd) - J2000_JD$1;
9837
10332
  const T = dt / DAYS_PER_CENTURY;
9838
10333
  let raDeg = orientation.pole_ra_0 + orientation.pole_ra_1 * T;
9839
10334
  let decDeg = orientation.pole_dec_0 + orientation.pole_dec_1 * T;
9840
10335
  let wDeg = orientation.w0 + orientation.w1 * dt + orientation.w2 * dt * dt;
9841
10336
  if (nutPrec) {
9842
10337
  const { ra, dec, pm, angles } = nutPrec;
9843
- const n = Math.min(angles.length >> 1, Math.max(ra.length, dec.length, pm.length));
10338
+ const k = nutPrec.phaseDegree + 1;
10339
+ const n = Math.min(Math.floor(angles.length / k), Math.max(ra.length, dec.length, pm.length));
9844
10340
  for (let i = 0; i < n; i++) {
9845
- const theta = (angles[2 * i] + angles[2 * i + 1] * T) * DEG2RAD$6;
10341
+ let thetaDeg = 0;
10342
+ for (let j = k - 1; j >= 0; j--) thetaDeg = thetaDeg * T + angles[k * i + j];
10343
+ const theta = thetaDeg * DEG2RAD$5;
9846
10344
  const s = Math.sin(theta);
9847
10345
  if (i < ra.length) raDeg += ra[i] * s;
9848
10346
  if (i < dec.length) decDeg += dec[i] * Math.cos(theta);
9849
10347
  if (i < pm.length) wDeg += pm[i] * s;
9850
10348
  }
9851
10349
  }
9852
- const ra = raDeg * DEG2RAD$6;
9853
- const dec = decDeg * DEG2RAD$6;
10350
+ return {
10351
+ ra: raDeg,
10352
+ dec: decDeg,
10353
+ w: wDeg
10354
+ };
10355
+ }
10356
+ function bodyQuaternion(orientation, currentJd, nutPrec) {
10357
+ const { ra: raDeg, dec: decDeg, w: wDeg } = iauAngles(orientation, currentJd, nutPrec);
10358
+ const ra = raDeg * DEG2RAD$5;
10359
+ const dec = decDeg * DEG2RAD$5;
9854
10360
  const cosDec = Math.cos(dec);
9855
10361
  const pole = equatorialToScene(cosDec * Math.cos(ra), cosDec * Math.sin(ra), Math.sin(dec), new Vector3()).normalize();
9856
10362
  const node = equatorialToScene(-Math.sin(ra), Math.cos(ra), 0, new Vector3()).normalize();
9857
10363
  const third = new Vector3().crossVectors(node, pole).normalize();
9858
10364
  const tiltQuat = new Quaternion().setFromRotationMatrix(new Matrix4().makeBasis(node, pole, third));
9859
- return new Quaternion().setFromAxisAngle(pole, wDeg * DEG2RAD$6).multiply(tiltQuat);
10365
+ return new Quaternion().setFromAxisAngle(pole, wDeg * DEG2RAD$5).multiply(tiltQuat);
9860
10366
  }
9861
10367
  function applyOrientation(obj, orientation, currentJd, nutPrec) {
9862
10368
  obj.quaternion.copy(bodyQuaternion(orientation, currentJd, nutPrec));
@@ -9975,6 +10481,434 @@ function chainShaderHook(material, mod, patch) {
9975
10481
  material.needsUpdate = true;
9976
10482
  }
9977
10483
 
10484
+ //#endregion
10485
+ //#region src/lib/scene/lighting.ts
10486
+ var AMBIENT_INTENSITY = .01;
10487
+ var SUN_LIGHT_INTENSITY = 3.5;
10488
+ var ENV_BASE_INTENSITY = .04;
10489
+ function sunIrradianceFactor(sunDistScene) {
10490
+ const au = Math.max(sunDistScene / 10, .01);
10491
+ return 1 / (au * au);
10492
+ }
10493
+ function applySunPointLightMode(light, realistic, scale = 1) {
10494
+ light.decay = realistic ? 2 : 0;
10495
+ light.intensity = (realistic ? SUN_LIGHT_INTENSITY * 10 * 10 : SUN_LIGHT_INTENSITY) * scale;
10496
+ }
10497
+ function makeEnvMap(renderer) {
10498
+ const pmrem = new PMREMGenerator(renderer);
10499
+ const tex = pmrem.fromScene(new RoomEnvironment()).texture;
10500
+ pmrem.dispose();
10501
+ return tex;
10502
+ }
10503
+
10504
+ //#endregion
10505
+ //#region src/lib/scene/objects/surface/ring-light.ts
10506
+ var UNLIT_TINT = [
10507
+ 1,
10508
+ .97075,
10509
+ .952
10510
+ ];
10511
+ var FORWARD_TINT_BIAS = [
10512
+ 1.02,
10513
+ .99,
10514
+ .97
10515
+ ];
10516
+ var PROFILE_MU = Math.sin(8 * Math.PI / 180);
10517
+ function oneMinusExp(x) {
10518
+ return x < .001 ? x * (1 - .5 * x) : 1 - Math.exp(-x);
10519
+ }
10520
+ function reflectionGain(mu0, mu, tau) {
10521
+ const a = Math.max(mu0, 1e-4);
10522
+ const b = Math.max(mu, 1e-4);
10523
+ const t = Math.max(tau, 1e-6);
10524
+ return mu0 / (a + b) * oneMinusExp(t * (1 / a + 1 / b)) / (.5 * oneMinusExp(2 * t / PROFILE_MU));
10525
+ }
10526
+ var RINGSHINE_BANDS = 3;
10527
+ var SECTORS = 12;
10528
+ var RINGSHINE_PATCHES = RINGSHINE_BANDS * (SECTORS / 2 + 1);
10529
+ var RINGSHINE_MIN_MU0 = 1e-4;
10530
+ var PROFILE_BINS = 256;
10531
+ var _litGain = new Float64Array(PROFILE_BINS);
10532
+ var SECTOR_STEPS = 6;
10533
+ var PLANET_LIGHT_AZIMUTHS = 19;
10534
+ var PLANET_LIGHT_RADII = 8;
10535
+ var PLANET_ROWS = 24;
10536
+ var PLANET_COLUMNS = 48;
10537
+ function luminance$2(r, g, b) {
10538
+ return .2126 * r + .7152 * g + .0722 * b;
10539
+ }
10540
+ var SRGB_TO_LINEAR = Float64Array.from({ length: 256 }, (_, i) => {
10541
+ const c = i / 255;
10542
+ return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
10543
+ });
10544
+ function ringProfile(pixels, width, rows, inner, outer, intensity) {
10545
+ const back = new Float64Array(PROFILE_BINS);
10546
+ const forward = new Float64Array(PROFILE_BINS);
10547
+ const unlit = new Float64Array(PROFILE_BINS);
10548
+ const transmission = new Float64Array(PROFILE_BINS);
10549
+ const unlitLuminance = luminance$2(...UNLIT_TINT);
10550
+ const [br, bg, bb] = FORWARD_TINT_BIAS;
10551
+ const tint = [
10552
+ 0,
10553
+ 0,
10554
+ 0
10555
+ ];
10556
+ const at = (row, i) => (row * width + i) * 4;
10557
+ for (let k = 0; k < PROFILE_BINS; k++) {
10558
+ const from = Math.floor(k * width / PROFILE_BINS);
10559
+ const to = Math.max(from + 1, Math.floor((k + 1) * width / PROFILE_BINS));
10560
+ const r = inner + (k + .5) / PROFILE_BINS * (outer - inner);
10561
+ for (let i = from; i < to; i++) {
10562
+ const c = at(rows.color, i);
10563
+ const red = SRGB_TO_LINEAR[pixels[c]];
10564
+ const green = SRGB_TO_LINEAR[pixels[c + 1]];
10565
+ const blue = SRGB_TO_LINEAR[pixels[c + 2]];
10566
+ const opacity = Math.min(1, (1 - pixels[at(rows.transparency, i)] / 255) * intensity);
10567
+ const backscatter = pixels[at(rows.backscattered, i)] / 255 * intensity;
10568
+ back[k] += backscatter * luminance$2(red, green, blue);
10569
+ forward[k] += pixels[at(rows.forwardscattered, i)] / 255 * opacity * intensity * luminance$2(red * br, green * bg, blue * bb);
10570
+ unlit[k] += pixels[at(rows.unlitside, i)] / 255 * opacity * intensity * unlitLuminance;
10571
+ transmission[k] += 1 - opacity;
10572
+ tint[0] += backscatter * red * r;
10573
+ tint[1] += backscatter * green * r;
10574
+ tint[2] += backscatter * blue * r;
10575
+ }
10576
+ const n = to - from;
10577
+ back[k] /= n;
10578
+ forward[k] /= n;
10579
+ unlit[k] /= n;
10580
+ transmission[k] /= n;
10581
+ }
10582
+ const tintLuminance = luminance$2(tint[0], tint[1], tint[2]);
10583
+ return {
10584
+ inner,
10585
+ outer,
10586
+ back,
10587
+ forward,
10588
+ unlit,
10589
+ transmission,
10590
+ litTint: tintLuminance > 0 ? new Color(tint[0], tint[1], tint[2]).multiplyScalar(1 / tintLuminance) : new Color(1, 1, 1),
10591
+ bands: equalLightBands(back, unlit, inner, outer)
10592
+ };
10593
+ }
10594
+ function equalLightBands(back, unlit, inner, outer) {
10595
+ const cumulative = new Float64Array(PROFILE_BINS + 1);
10596
+ for (let k = 0; k < PROFILE_BINS; k++) {
10597
+ const r = inner + (k + .5) / PROFILE_BINS * (outer - inner);
10598
+ cumulative[k + 1] = cumulative[k] + (back[k] + unlit[k]) / r;
10599
+ }
10600
+ const total = cumulative[PROFILE_BINS];
10601
+ const edges = new Int32Array(RINGSHINE_BANDS + 1);
10602
+ edges[RINGSHINE_BANDS] = PROFILE_BINS;
10603
+ for (let b = 1; b < RINGSHINE_BANDS; b++) {
10604
+ let k = edges[b - 1] + 1;
10605
+ while (k < PROFILE_BINS - (RINGSHINE_BANDS - b) && cumulative[k] < total * b / RINGSHINE_BANDS) k++;
10606
+ edges[b] = k;
10607
+ }
10608
+ return edges;
10609
+ }
10610
+ function forwardWeight(cosPhase) {
10611
+ const t = Math.min(Math.max((cosPhase + 1) / 2, 0), 1);
10612
+ return 1 - t * t * (3 - 2 * t);
10613
+ }
10614
+ var PARTICLE_PHASE_EXPONENT = 3.09;
10615
+ function particlePhase(cosPhase) {
10616
+ return (1 - Math.acos(Math.min(Math.max(cosPhase, -1), 1)) / Math.PI) ** PARTICLE_PHASE_EXPONENT;
10617
+ }
10618
+ function inPlanetShadow(x, y, cosB, sinB, planet) {
10619
+ const ox = x / planet.equatorial;
10620
+ const oy = y / planet.equatorial;
10621
+ const dx = cosB / planet.equatorial;
10622
+ const dz = sinB / planet.polar;
10623
+ const b = ox * dx;
10624
+ if (b >= 0) return false;
10625
+ return ox * ox + oy * oy - b * b / (dx * dx + dz * dz) < 1;
10626
+ }
10627
+ function ringshinePatches(out, profile, sinB, planet) {
10628
+ const cosB = Math.sqrt(Math.max(0, 1 - sinB * sinB));
10629
+ const mu0 = Math.abs(sinB);
10630
+ const sunAbove = sinB >= 0;
10631
+ const { inner, outer, back, forward, unlit, transmission, bands } = profile;
10632
+ for (let i = 0; i < PROFILE_BINS; i++) _litGain[i] = reflectionGain(mu0, mu0, -Math.log(Math.max(transmission[i], 1e-6)));
10633
+ const dr = (outer - inner) / PROFILE_BINS;
10634
+ const sector = 2 * Math.PI / SECTORS;
10635
+ const dPhi = sector / SECTOR_STEPS;
10636
+ let p = 0;
10637
+ for (let band = 0; band < RINGSHINE_BANDS; band++) for (let k = 0; k <= SECTORS / 2; k++, p++) {
10638
+ let above = 0;
10639
+ let below = 0;
10640
+ let weight = 0;
10641
+ let wx = 0;
10642
+ let wy = 0;
10643
+ for (let s = 0; s < SECTOR_STEPS; s++) {
10644
+ const phi = (k - .5) * sector + (s + .5) * dPhi;
10645
+ const c = Math.cos(phi);
10646
+ const sn = Math.sin(phi);
10647
+ const w = forwardWeight(-cosB * c);
10648
+ const phase = particlePhase(-cosB * c);
10649
+ for (let i = bands[band]; i < bands[band + 1]; i++) {
10650
+ const r = inner + (i + .5) * dr;
10651
+ if (inPlanetShadow(r * c, r * sn, cosB, sinB, planet)) continue;
10652
+ const area = r * dr * dPhi;
10653
+ const lit = (back[i] * phase + (forward[i] - back[i] * phase) * w) * _litGain[i] * area;
10654
+ const through = unlit[i] * area;
10655
+ above += sunAbove ? lit : through;
10656
+ below += sunAbove ? through : lit;
10657
+ const light = lit + through;
10658
+ weight += light;
10659
+ wx += light * r * c;
10660
+ wy += light * r * Math.abs(sn);
10661
+ }
10662
+ }
10663
+ const share = k === 0 || k === SECTORS / 2 ? .5 : 1;
10664
+ if (weight > 0) {
10665
+ out[4 * p] = wx / weight;
10666
+ out[4 * p + 1] = wy / weight;
10667
+ } else {
10668
+ const r = inner + (bands[band] + bands[band + 1]) / 2 * dr;
10669
+ out[4 * p] = r * Math.cos(k * sector);
10670
+ out[4 * p + 1] = r * Math.abs(Math.sin(k * sector));
10671
+ }
10672
+ out[4 * p + 2] = above * share;
10673
+ out[4 * p + 3] = below * share;
10674
+ }
10675
+ }
10676
+ function ringshineAt(patches, mu0, qx, qy, qz, nx, ny, nz) {
10677
+ const above = qz >= 0;
10678
+ const height = Math.abs(qz);
10679
+ const qq = qx * qx + qy * qy + qz * qz;
10680
+ const nq = nx * qx + ny * qy + nz * qz;
10681
+ let sum = 0;
10682
+ for (let i = 0; i < 21; i++) {
10683
+ const px = patches[4 * i];
10684
+ const py = patches[4 * i + 1];
10685
+ const light = above ? patches[4 * i + 2] : patches[4 * i + 3];
10686
+ const base = qq + px * px + py * py - 2 * px * qx;
10687
+ const across = 2 * py * qy;
10688
+ const facingBase = px * nx - nq;
10689
+ const facingAcross = py * ny;
10690
+ for (const sign of [1, -1]) {
10691
+ const d2 = base - sign * across;
10692
+ const d = Math.sqrt(d2);
10693
+ const facing = Math.max(facingBase + sign * facingAcross, 0);
10694
+ sum += light * facing / (d2 * d * (mu0 * d + height));
10695
+ }
10696
+ }
10697
+ return 2 * mu0 * height * sum;
10698
+ }
10699
+ function ringTransmission(profile, x, y, z, cosB, sinB) {
10700
+ if (Math.abs(sinB) < 1e-6) return 1;
10701
+ const t = -z / sinB;
10702
+ if (t < 0) return 1;
10703
+ const u = (Math.hypot(x + t * cosB, y) - profile.inner) / (profile.outer - profile.inner);
10704
+ if (u < 0 || u > 1) return 1;
10705
+ const bin = Math.min(Math.floor(u * PROFILE_BINS), PROFILE_BINS - 1);
10706
+ return Math.max(profile.transmission[bin], 1e-4) ** (1 / Math.max(Math.abs(sinB), .02));
10707
+ }
10708
+ var _cells = {
10709
+ x: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10710
+ y: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10711
+ z: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10712
+ nx: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10713
+ ny: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10714
+ nz: new Float64Array(PLANET_ROWS * PLANET_COLUMNS),
10715
+ light: new Float64Array(PLANET_ROWS * PLANET_COLUMNS)
10716
+ };
10717
+ function planetLightOnRing(out, inner, outer, sinB, planet, caster, patches) {
10718
+ const cosB = Math.sqrt(Math.max(0, 1 - sinB * sinB));
10719
+ const mu0 = Math.abs(sinB);
10720
+ const { equatorial: a, polar: c } = planet;
10721
+ const cells = _cells;
10722
+ const dBeta = Math.PI / PLANET_ROWS;
10723
+ const dLambda = 2 * Math.PI / PLANET_COLUMNS;
10724
+ for (let row = 0; row < PLANET_ROWS; row++) {
10725
+ const beta = -Math.PI / 2 + (row + .5) * dBeta;
10726
+ const cb = Math.cos(beta);
10727
+ const sb = Math.sin(beta);
10728
+ const area = a * cb * Math.sqrt(c * c * cb * cb + a * a * sb * sb) * dBeta * dLambda;
10729
+ for (let col = 0; col < PLANET_COLUMNS; col++) {
10730
+ const lambda = (col + .5) * dLambda;
10731
+ const cl = Math.cos(lambda);
10732
+ const sl = Math.sin(lambda);
10733
+ const i = row * PLANET_COLUMNS + col;
10734
+ const x = a * cb * cl;
10735
+ const y = a * cb * sl;
10736
+ const z = c * sb;
10737
+ let nx = cb * cl / a;
10738
+ let ny = cb * sl / a;
10739
+ let nz = sb / c;
10740
+ const len = Math.hypot(nx, ny, nz);
10741
+ nx /= len;
10742
+ ny /= len;
10743
+ nz /= len;
10744
+ const cosSun = nx * cosB + nz * sinB;
10745
+ const day = cosSun > 0 ? cosSun * ringTransmission(caster, x, y, z, cosB, sinB) : 0;
10746
+ const night = mu0 < RINGSHINE_MIN_MU0 ? 0 : ringshineAt(patches, mu0, x, y, z, nx, ny, nz) / SUN_LIGHT_INTENSITY;
10747
+ cells.x[i] = x;
10748
+ cells.y[i] = y;
10749
+ cells.z[i] = z;
10750
+ cells.nx[i] = nx;
10751
+ cells.ny[i] = ny;
10752
+ cells.nz[i] = nz;
10753
+ cells.light[i] = (day + night) / Math.PI * area;
10754
+ }
10755
+ }
10756
+ for (let j = 0; j < 8; j++) {
10757
+ const r = inner + j / (8 - 1) * (outer - inner);
10758
+ for (let k = 0; k < 19; k++) {
10759
+ const cosPhi = 1 - 2 * k / (19 - 1);
10760
+ const px = r * cosPhi;
10761
+ const py = r * Math.sqrt(1 - cosPhi * cosPhi);
10762
+ let above = 0;
10763
+ let below = 0;
10764
+ let aboveMu = 0;
10765
+ let belowMu = 0;
10766
+ for (let i = 0; i < PLANET_ROWS * PLANET_COLUMNS; i++) {
10767
+ const vx = px - cells.x[i];
10768
+ const vy = py - cells.y[i];
10769
+ const vz = -cells.z[i];
10770
+ const d2 = vx * vx + vy * vy + vz * vz;
10771
+ const facing = cells.nx[i] * vx + cells.ny[i] * vy + cells.nz[i] * vz;
10772
+ if (facing <= 0) continue;
10773
+ const d = Math.sqrt(d2);
10774
+ const e = cells.light[i] * facing / (d2 * d);
10775
+ const mu = Math.abs(vz) / d;
10776
+ if (cells.z[i] > 0) {
10777
+ above += e;
10778
+ aboveMu += e * mu;
10779
+ } else {
10780
+ below += e;
10781
+ belowMu += e * mu;
10782
+ }
10783
+ }
10784
+ const node = 4 * (j * 19 + k);
10785
+ out[node] = above;
10786
+ out[node + 1] = below;
10787
+ out[node + 2] = above > 0 ? aboveMu / above : 0;
10788
+ out[node + 3] = below > 0 ? belowMu / below : 0;
10789
+ }
10790
+ }
10791
+ }
10792
+ var _grid = new Float32Array(4 * 19 * 8);
10793
+ function makeRingLight(profile) {
10794
+ const texture = new DataTexture(new Uint16Array(4 * 19 * 8), 19, 8, RGBAFormat, HalfFloatType);
10795
+ texture.minFilter = LinearFilter;
10796
+ texture.magFilter = LinearFilter;
10797
+ texture.wrapS = ClampToEdgeWrapping;
10798
+ texture.wrapT = ClampToEdgeWrapping;
10799
+ texture.needsUpdate = true;
10800
+ return {
10801
+ profile,
10802
+ planet: {
10803
+ equatorial: 0,
10804
+ polar: 0
10805
+ },
10806
+ planetLight: texture,
10807
+ planetLightSinB: NaN,
10808
+ patches: new Float64Array(4 * 21),
10809
+ patchesSinB: NaN
10810
+ };
10811
+ }
10812
+ function fillPlanetLight(light, sinB, caster) {
10813
+ planetLightOnRing(_grid, light.profile.inner, light.profile.outer, sinB, light.planet, caster.profile, caster.patches);
10814
+ const data = light.planetLight.image.data;
10815
+ for (let i = 0; i < _grid.length; i++) data[i] = DataUtils.toHalfFloat(_grid[i]);
10816
+ light.planetLight.needsUpdate = true;
10817
+ light.planetLightSinB = sinB;
10818
+ }
10819
+ function peakRingshine(profile, planet) {
10820
+ const patches = new Float64Array(4 * 21);
10821
+ ringshinePatches(patches, profile, 1, planet);
10822
+ let peak = 0;
10823
+ for (let deg = 2; deg < 90; deg += 2) {
10824
+ const beta = deg * Math.PI / 180;
10825
+ const cb = Math.cos(beta);
10826
+ const sb = Math.sin(beta);
10827
+ const nx = cb / planet.equatorial;
10828
+ const nz = sb / planet.polar;
10829
+ const len = Math.hypot(nx, nz);
10830
+ const e = ringshineAt(patches, 1, planet.equatorial * cb, 0, planet.polar * sb, nx / len, 0, nz / len);
10831
+ peak = Math.max(peak, e / SUN_LIGHT_INTENSITY);
10832
+ }
10833
+ return peak;
10834
+ }
10835
+ var RINGSHINE_PARS = `
10836
+ #define RINGSHINE_PATCHES ${21}
10837
+ uniform vec4 uRingshinePatch[RINGSHINE_PATCHES];
10838
+ uniform vec3 uRingshineAxis;
10839
+ uniform vec3 uRingshineTint[2];
10840
+ `;
10841
+ var RINGSHINE_LIGHT = `
10842
+ if (uRingshineTint[0].g + uRingshineTint[1].g > 0.0) {
10843
+ vec3 q = vRingShadowWorldPos - uRingShadowCenter;
10844
+ vec3 axisY = cross(uRingShadowPoleDir, uRingshineAxis);
10845
+ float height = dot(q, uRingShadowPoleDir);
10846
+ float mu0 = abs(dot(uRingShadowSunDir, uRingShadowPoleDir));
10847
+ vec3 n = inverseTransformDirection(geometryNormal, viewMatrix);
10848
+ float qx = dot(q, uRingshineAxis);
10849
+ float qy = dot(q, axisY);
10850
+ float qq = dot(q, q);
10851
+ float nx = dot(n, uRingshineAxis);
10852
+ float ny = dot(n, axisY);
10853
+ float nq = dot(n, q);
10854
+ float above = step(0.0, height);
10855
+ float absHeight = abs(height);
10856
+ float sum = 0.0;
10857
+ for (int i = 0; i < RINGSHINE_PATCHES; i++) {
10858
+ vec4 p = uRingshinePatch[i];
10859
+ float light = mix(p.w, p.z, above);
10860
+ float base = qq + dot(p.xy, p.xy) - 2.0 * p.x * qx;
10861
+ float across = 2.0 * p.y * qy;
10862
+ float facingBase = p.x * nx - nq;
10863
+ float facingAcross = p.y * ny;
10864
+ // The patch and its mirror at -y.
10865
+ vec2 d2 = vec2(base - across, base + across);
10866
+ vec2 d = sqrt(d2);
10867
+ vec2 facing = max(vec2(facingBase + facingAcross, facingBase - facingAcross), 0.0);
10868
+ sum += light * dot(facing, 1.0 / (d2 * d * (mu0 * d + absHeight)));
10869
+ }
10870
+ vec3 tint = mix(uRingshineTint[1], uRingshineTint[0], above);
10871
+ reflectedLight.indirectDiffuse +=
10872
+ tint * (2.0 * mu0 * absHeight * sum) * BRDF_Lambert(material.diffuseContribution);
10873
+ }
10874
+ `;
10875
+ function attachRingshine(material) {
10876
+ const carrier = material.userData;
10877
+ if (carrier.ringshine) return carrier.ringshine;
10878
+ const uniforms = {
10879
+ uRingshinePatch: { value: Array.from({ length: 21 }, () => new Vector4()) },
10880
+ uRingshineAxis: { value: new Vector3(1, 0, 0) },
10881
+ uRingshineTint: { value: [new Vector3(), new Vector3()] },
10882
+ disable: () => {
10883
+ for (const tint of uniforms.uRingshineTint.value) tint.set(0, 0, 0);
10884
+ }
10885
+ };
10886
+ chainShaderHook(material, "ringshine", (shader) => {
10887
+ shader.uniforms.uRingshinePatch = uniforms.uRingshinePatch;
10888
+ shader.uniforms.uRingshineAxis = uniforms.uRingshineAxis;
10889
+ shader.uniforms.uRingshineTint = uniforms.uRingshineTint;
10890
+ shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${RINGSHINE_PARS}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>\n${RINGSHINE_LIGHT}`);
10891
+ });
10892
+ carrier.ringshine = uniforms;
10893
+ return uniforms;
10894
+ }
10895
+ function writeRingshine(uniforms, light, axis, sinB, lightScale) {
10896
+ const patches = uniforms.uRingshinePatch.value;
10897
+ for (let i = 0; i < 21; i++) patches[i].fromArray(light.patches, 4 * i);
10898
+ uniforms.uRingshineAxis.value.copy(axis);
10899
+ const [above, below] = uniforms.uRingshineTint.value;
10900
+ if (Math.abs(sinB) < RINGSHINE_MIN_MU0) {
10901
+ above.set(0, 0, 0);
10902
+ below.set(0, 0, 0);
10903
+ return;
10904
+ }
10905
+ const lit = light.profile.litTint;
10906
+ const unlitLuminance = luminance$2(...UNLIT_TINT);
10907
+ const [litFace, unlitFace] = sinB >= 0 ? [above, below] : [below, above];
10908
+ litFace.set(lit.r, lit.g, lit.b).multiplyScalar(lightScale);
10909
+ unlitFace.set(...UNLIT_TINT).multiplyScalar(lightScale / unlitLuminance);
10910
+ }
10911
+
9978
10912
  //#endregion
9979
10913
  //#region src/lib/scene/objects/surface/rings.ts
9980
10914
  var RING_ANGULAR_SEGMENTS = 256;
@@ -9986,30 +10920,47 @@ async function loadStripTextures(url, rows, maxTextureSize) {
9986
10920
  if (!response.ok) throw new Error(`Failed to load ${url}: ${response.status} ${response.statusText}`);
9987
10921
  const blob = await response.blob();
9988
10922
  const bitmap = await createImageBitmap(blob);
9989
- const targetWidth = Math.min(bitmap.width, maxTextureSize);
9990
- if (targetWidth < bitmap.width) console.info(`Ring strip ${url}: downscaling ${bitmap.width}px → ${targetWidth}px to fit GL MAX_TEXTURE_SIZE.`);
9991
- const strip = readStripPixels(bitmap, url, maxTextureSize);
9992
- const textures = {};
9993
- for (const [channel, row] of Object.entries(rows)) {
10923
+ const width = bitmap.width;
10924
+ const targetWidth = Math.min(width, maxTextureSize);
10925
+ if (targetWidth < width) console.info(`Ring strip ${url}: downscaling ${width}px → ${targetWidth}px to fit GL MAX_TEXTURE_SIZE.`);
10926
+ const pixels = readStripPixels(bitmap, url, maxTextureSize);
10927
+ bitmap.close();
10928
+ const line = (row) => boxFilterRow(pixels, width, row, targetWidth);
10929
+ const texture = (data, srgb = false) => {
9994
10930
  const canvas = document.createElement("canvas");
9995
10931
  canvas.width = targetWidth;
9996
10932
  canvas.height = 2;
9997
10933
  const ctx = canvas.getContext("2d");
9998
10934
  if (!ctx) throw new Error(`Failed to acquire 2D context for ring strip ${url}`);
9999
- const line = boxFilterRow(strip, bitmap.width, row, targetWidth);
10000
- ctx.putImageData(line, 0, 0);
10001
- ctx.putImageData(line, 0, 1);
10935
+ ctx.putImageData(data, 0, 0);
10936
+ ctx.putImageData(data, 0, 1);
10002
10937
  const tex = new CanvasTexture(canvas);
10003
- if (channel === "color") tex.colorSpace = SRGBColorSpace;
10938
+ if (srgb) tex.colorSpace = SRGBColorSpace;
10004
10939
  tex.minFilter = LinearMipmapLinearFilter;
10005
10940
  tex.magFilter = LinearFilter;
10006
10941
  tex.generateMipmaps = true;
10007
10942
  tex.anisotropy = 16;
10008
10943
  tex.needsUpdate = true;
10009
- textures[channel] = tex;
10944
+ return tex;
10945
+ };
10946
+ const scatter = line(rows.backscattered);
10947
+ const forward = line(rows.forwardscattered);
10948
+ const unlit = line(rows.unlitside);
10949
+ for (let d = 0; d < scatter.data.length; d += 4) {
10950
+ scatter.data[d + 1] = forward.data[d];
10951
+ scatter.data[d + 2] = unlit.data[d];
10010
10952
  }
10011
- bitmap.close();
10012
- return textures;
10953
+ const textures = {
10954
+ color: texture(line(rows.color), true),
10955
+ scatter: texture(scatter),
10956
+ transparency: texture(line(rows.transparency))
10957
+ };
10958
+ if (rows.thickness !== void 0) textures.thickness = texture(line(rows.thickness));
10959
+ return {
10960
+ textures,
10961
+ pixels,
10962
+ width
10963
+ };
10013
10964
  }
10014
10965
  function readStripPixels(bitmap, url, maxTextureSize) {
10015
10966
  const out = new Uint8ClampedArray(bitmap.width * bitmap.height * 4);
@@ -10090,9 +11041,7 @@ var FRAGMENT_SHADER$2 = `
10090
11041
  #include <common>
10091
11042
  #include <logdepthbuf_pars_fragment>
10092
11043
 
10093
- uniform sampler2D uBackscattered;
10094
- uniform sampler2D uForwardscattered;
10095
- uniform sampler2D uUnlitside;
11044
+ uniform sampler2D uScatter;
10096
11045
  uniform sampler2D uTransparency;
10097
11046
  uniform sampler2D uColor;
10098
11047
  uniform float uInnerScene;
@@ -10114,18 +11063,56 @@ var FRAGMENT_SHADER$2 = `
10114
11063
  // 1/layerCount: opacity is split across the vertical layer instances so
10115
11064
  // the composited stack reproduces the strip's alpha.
10116
11065
  uniform float uLayerAlphaExp;
11066
+ // Light of the planet on the +Y and the -Y face over the Sun's, per unit of the
11067
+ // planet's albedo, and the mean sine of its incidence on each, by the cosine
11068
+ // of the azimuth from the Sun (x) and radius (y).
11069
+ uniform sampler2D uPlanetLight;
11070
+ // Lambert albedo of the planet, linear RGB.
11071
+ uniform vec3 uPlanetAlbedo;
11072
+ // Unit direction of the Sun in the local XZ plane.
11073
+ uniform vec2 uSunAzimuth;
10117
11074
 
10118
11075
  varying vec3 vLocalPos;
10119
11076
  varying vec3 vWorldPos;
10120
11077
  varying vec3 vWorldNormal;
10121
11078
 
10122
- // Per BJJ: the Cassini-derived color profile only fits backscattered
10123
- // light. Unlit/forward branches get a fixed near-white tint per BJJ.
10124
- const vec3 UNLIT_TINT = vec3(1.0, 0.97075, 0.952);
11079
+ const vec3 UNLIT_TINT = vec3(${UNLIT_TINT.join(", ")});
11080
+ const vec3 FORWARD_TINT_BIAS = vec3(${FORWARD_TINT_BIAS.join(", ")});
11081
+ const float PROFILE_MU = ${PROFILE_MU};
11082
+
11083
+ float oneMinusExp(float x) {
11084
+ return x < 1e-3 ? x * (1.0 - 0.5 * x) : 1.0 - exp(-x);
11085
+ }
11086
+
11087
+ float reflectionGain(float mu0, float mu, float tau) {
11088
+ float a = max(mu0, 1e-4);
11089
+ float b = max(mu, 1e-4);
11090
+ return mu0 / (a + b) * oneMinusExp(tau * (1.0 / a + 1.0 / b))
11091
+ / (0.5 * oneMinusExp(2.0 * tau / PROFILE_MU));
11092
+ }
11093
+
11094
+ float unlitGain(float mu0, float mu) {
11095
+ return 2.0 * mu0 / max(mu0 + mu, 1e-4);
11096
+ }
10125
11097
 
10126
- // Red bias on the forward-scatter branch, per BJJ's "becoming slightly
10127
- // redder" observation as phase angle climbs.
10128
- const vec3 FORWARD_TINT_BIAS = vec3(1.02, 0.99, 0.97);
11098
+ // Weight of forward scatter: 0 at phase 0, 1 at phase 180 deg, 0.5 edge-on.
11099
+ float forwardWeight(float cosPhase) {
11100
+ return 1.0 - smoothstep(-1.0, 1.0, cosPhase);
11101
+ }
11102
+
11103
+ float particlePhase(float cosPhase) {
11104
+ return pow(1.0 - acos(clamp(cosPhase, -1.0, 1.0)) / PI, ${PARTICLE_PHASE_EXPONENT});
11105
+ }
11106
+
11107
+ // The lit face at a phase and a gain: the backscatter profile under the
11108
+ // phase function of the particles, blended into the forward-scatter profile.
11109
+ vec3 litLight(vec3 back, vec3 forward, float cosPhase, float gain) {
11110
+ return mix(
11111
+ back * particlePhase(cosPhase) * gain,
11112
+ forward * min(gain, 1.0),
11113
+ forwardWeight(cosPhase)
11114
+ );
11115
+ }
10129
11116
 
10130
11117
  // Ray-march from the fragment toward the sun against the planet's oblate
10131
11118
  // spheroid: warp into the pole-aligned frame so the spheroid becomes a
@@ -10166,40 +11153,49 @@ var FRAGMENT_SHADER$2 = `
10166
11153
  // the outward direction of the face actually being viewed.
10167
11154
  vec3 N = gl_FrontFacing ? vWorldNormal : -vWorldNormal;
10168
11155
 
10169
- bool lit = dot(uSunDir, N) > 0.0;
11156
+ float sunMu = dot(uSunDir, N);
11157
+ bool lit = sunMu > 0.0;
11158
+ float mu0 = abs(sunMu);
10170
11159
 
10171
- // cosAlpha = cos(phase angle): +1 low phase, -1 high phase.
10172
11160
  vec3 viewDir = normalize(cameraPosition - vWorldPos);
10173
- float cosAlpha = dot(uSunDir, viewDir);
10174
-
10175
- vec3 finalAlbedo;
10176
- if (lit) {
10177
- // Blend backscatter → forward scatter with phase angle; smoothstep
10178
- // over [-1, 1] centers the transition on edge-on viewing (α = 90°).
10179
- // Both branches share the Cassini color profile (calibrated for
10180
- // backscatter only) plus a small red bias on the forward branch,
10181
- // since dropping color entirely visibly loses the ring's tan/gold
10182
- // hue seen in forward-scattered Cassini imagery.
10183
- vec3 colorTint = texture2D(uColor, uv).rgb;
10184
- vec3 forwardTint = colorTint * FORWARD_TINT_BIAS;
10185
- vec3 back = texture2D(uBackscattered, uv).rgb * colorTint;
10186
- vec3 forward = texture2D(uForwardscattered, uv).rgb * forwardTint;
10187
- float wForward = 1.0 - smoothstep(-1.0, 1.0, cosAlpha);
10188
- finalAlbedo = mix(back, forward, wForward);
10189
- } else {
10190
- // unlitside already is the transmitted-light appearance; no
10191
- // phase-angle blend needed.
10192
- finalAlbedo = texture2D(uUnlitside, uv).rgb * UNLIT_TINT;
10193
- }
11161
+ float mu = abs(dot(viewDir, N));
10194
11162
  // Transparency convention: 1.0 = empty space, 0.0 = opaque.
10195
- float alpha = clamp((1.0 - texture2D(uTransparency, uv).r) * uIntensityScale, 0.0, 1.0);
10196
- // Split opacity across layer instances so the composited stack
10197
- // reproduces the strip's alpha.
10198
- alpha = 1.0 - pow(1.0 - alpha, uLayerAlphaExp);
11163
+ float opacity = clamp((1.0 - texture2D(uTransparency, uv).r) * uIntensityScale, 0.0, 1.0);
11164
+ float tau = max(-log(max(1.0 - opacity, 1e-6)), 1e-6);
11165
+
11166
+ vec3 colorTint = texture2D(uColor, uv).rgb;
11167
+ vec3 scatter = texture2D(uScatter, uv).rgb;
11168
+ vec3 back = scatter.r * colorTint;
11169
+ vec3 forward = scatter.g * opacity * colorTint * FORWARD_TINT_BIAS;
11170
+ vec3 unlit = scatter.b * opacity * UNLIT_TINT;
11171
+ vec3 sunLit = lit
11172
+ ? litLight(back, forward, dot(uSunDir, viewDir), reflectionGain(mu0, mu, tau))
11173
+ : unlit * min(unlitGain(mu0, mu), 1.0);
11174
+
11175
+ // The light is the same at plus and minus an azimuth, so the texture holds its cosine.
11176
+ float azimuth = 0.5 - 0.5 * dot(vLocalPos.xz, uSunAzimuth) / radius;
11177
+ const vec2 NODES = vec2(${19}.0, ${8}.0);
11178
+ vec4 planet = texture2D(uPlanetLight, (vec2(azimuth, t) * (NODES - 1.0) + 0.5) / NODES);
11179
+ vec2 near = gl_FrontFacing ? planet.xz : planet.yw;
11180
+ vec2 far = gl_FrontFacing ? planet.yw : planet.xz;
11181
+ float planetPhase = dot(normalize(uPlanetCenter - vWorldPos), viewDir);
11182
+ vec3 planetLit = uPlanetAlbedo * (
11183
+ near.x * litLight(back, forward, planetPhase, reflectionGain(near.y, mu, tau))
11184
+ + far.x * min(unlitGain(far.y, mu), 1.0) * unlit);
11185
+
11186
+ // The slant path hides more of the background.
11187
+ float alpha = oneMinusExp(tau / max(mu, 0.02));
11188
+ // Split the opacity across the sheets of the stack so that it composites
11189
+ // to the layer's. Each sheet adds its share of the light.
11190
+ float layerAlpha = 1.0 - pow(1.0 - alpha, uLayerAlphaExp);
11191
+ float share = alpha > 1e-4 ? layerAlpha / alpha : uLayerAlphaExp;
10199
11192
 
10200
11193
  float shadow = planetShadow();
10201
11194
 
10202
- gl_FragColor = vec4(finalAlbedo * uIntensityScale * shadow * uLightScale, alpha);
11195
+ gl_FragColor = vec4(
11196
+ (sunLit * shadow + planetLit) * uIntensityScale * uLightScale * share,
11197
+ layerAlpha
11198
+ );
10203
11199
  #include <logdepthbuf_fragment>
10204
11200
  }
10205
11201
  `;
@@ -10306,8 +11302,11 @@ async function loadRingNode(bodyId, meta, maxTextureSize) {
10306
11302
  const outerScene = kmToScene(meta.outer_radius_km);
10307
11303
  const intensityScale = meta.intensity_scale ?? 1;
10308
11304
  let textures;
11305
+ let light;
10309
11306
  try {
10310
- textures = await loadStripTextures(versionedUrl(`/v1/rings/${bodyId}/${meta.strip}`, "rings"), meta.strip_rows, maxTextureSize);
11307
+ const strip = await loadStripTextures(versionedUrl(`/v1/rings/${bodyId}/${meta.strip}`, "rings"), meta.strip_rows, maxTextureSize);
11308
+ textures = strip.textures;
11309
+ light = makeRingLight(ringProfile(strip.pixels, strip.width, meta.strip_rows, innerScene, outerScene, intensityScale));
10311
11310
  } catch (err) {
10312
11311
  console.warn(`Failed to load ring strip for ${bodyId}:`, err);
10313
11312
  return null;
@@ -10316,9 +11315,7 @@ async function loadRingNode(bodyId, meta, maxTextureSize) {
10316
11315
  const layers = thicknessScene > 0 ? 48 : 1;
10317
11316
  const material = new ShaderMaterial({
10318
11317
  uniforms: {
10319
- uBackscattered: { value: textures.backscattered },
10320
- uForwardscattered: { value: textures.forwardscattered },
10321
- uUnlitside: { value: textures.unlitside },
11318
+ uScatter: { value: textures.scatter },
10322
11319
  uTransparency: { value: textures.transparency },
10323
11320
  uColor: { value: textures.color },
10324
11321
  uThickness: { value: textures.thickness ?? textures.transparency },
@@ -10334,13 +11331,17 @@ async function loadRingNode(bodyId, meta, maxTextureSize) {
10334
11331
  uPlanetEquatorialScene: { value: 0 },
10335
11332
  uPlanetPolarScene: { value: 0 },
10336
11333
  uLightScale: { value: 1 },
10337
- uIntensityScale: { value: intensityScale }
11334
+ uIntensityScale: { value: intensityScale },
11335
+ uPlanetLight: { value: light.planetLight },
11336
+ uPlanetAlbedo: { value: new Color(0, 0, 0) },
11337
+ uSunAzimuth: { value: new Vector2(1, 0) }
10338
11338
  },
10339
11339
  vertexShader: VERTEX_SHADER$2,
10340
11340
  fragmentShader: FRAGMENT_SHADER$2,
10341
11341
  transparent: true,
10342
11342
  depthWrite: false,
10343
11343
  blending: NormalBlending,
11344
+ premultipliedAlpha: true,
10344
11345
  side: DoubleSide
10345
11346
  });
10346
11347
  const geometry = new RingGeometry(innerScene, outerScene * (1 / Math.cos(Math.PI / RING_ANGULAR_SEGMENTS)), RING_ANGULAR_SEGMENTS, layers > 1 ? RING_RADIAL_SEGMENTS : 1);
@@ -10371,19 +11372,20 @@ async function loadRingNode(bodyId, meta, maxTextureSize) {
10371
11372
  innerScene,
10372
11373
  outerScene,
10373
11374
  planetShadow: null,
10374
- planetShadowOnRing
11375
+ planetShadowOnRing,
11376
+ light,
11377
+ ringshine: null
10375
11378
  };
10376
11379
  }
10377
11380
  function disposeRingNode(ring) {
10378
11381
  ring.mesh.geometry.dispose();
10379
11382
  const uniforms = ring.material.uniforms;
10380
11383
  for (const key of [
10381
- "uBackscattered",
10382
- "uForwardscattered",
10383
- "uUnlitside",
11384
+ "uScatter",
10384
11385
  "uTransparency",
10385
11386
  "uColor",
10386
- "uThickness"
11387
+ "uThickness",
11388
+ "uPlanetLight"
10387
11389
  ]) (uniforms[key]?.value)?.dispose();
10388
11390
  ring.mesh.material.dispose();
10389
11391
  }
@@ -10627,6 +11629,14 @@ var SHARED = {
10627
11629
  uOccluders: { value: Array.from({ length: 32 }, () => new Vector4()) },
10628
11630
  uOccluderLeak: { value: Array.from({ length: 32 }, () => new Vector4()) }
10629
11631
  };
11632
+ var POOL = {
11633
+ occluders: [],
11634
+ leaks: [],
11635
+ count: 0
11636
+ };
11637
+ function getOccluderPool() {
11638
+ return POOL;
11639
+ }
10630
11640
  var ECLIPSE_FACTOR_GLSL = `
10631
11641
  #define ECLIPSE_MAX_OCCLUDERS ${32}
10632
11642
  uniform float uSunAngularRadius;
@@ -10712,13 +11722,18 @@ function makeEclipseSelfUniforms() {
10712
11722
  uOccluderCount: { value: 0 }
10713
11723
  };
10714
11724
  }
11725
+ var _kept = new Int32Array(0);
11726
+ var _size = new Float64Array(0);
11727
+ var largestFirst = (a, b) => _size[b] - _size[a];
10715
11728
  function cullOccludersFor(dst, center, radius, sunDir, leakDst) {
10716
11729
  const aSun = SHARED.uSunAngularRadius.value;
10717
- const src = SHARED.uOccluders.value;
10718
- const leak = SHARED.uOccluderLeak.value;
10719
- const srcCount = SHARED.uOccluderCount.value;
11730
+ const src = POOL.occluders;
11731
+ if (_kept.length < POOL.count) {
11732
+ _kept = new Int32Array(POOL.count);
11733
+ _size = new Float64Array(POOL.count);
11734
+ }
10720
11735
  let n = 0;
10721
- for (let i = 0; i < srcCount; i++) {
11736
+ for (let i = 0; i < POOL.count; i++) {
10722
11737
  const oc = src[i];
10723
11738
  const ox = oc.x - center.x;
10724
11739
  const oy = oc.y - center.y;
@@ -10729,8 +11744,17 @@ function cullOccludersFor(dst, center, radius, sunDir, leakDst) {
10729
11744
  if (t < -(radius + oc.w)) continue;
10730
11745
  const reach = radius + oc.w + aSun * (t + radius) * 1.5;
10731
11746
  if (d2 - t * t > reach * reach) continue;
10732
- leakDst?.[n].copy(leak[i]);
10733
- dst[n++].copy(oc);
11747
+ _size[i] = oc.w * oc.w / d2;
11748
+ _kept[n++] = i;
11749
+ }
11750
+ if (n > 32) {
11751
+ _kept.subarray(0, n).sort(largestFirst);
11752
+ n = 32;
11753
+ }
11754
+ for (let k = 0; k < n; k++) {
11755
+ const i = _kept[k];
11756
+ leakDst?.[k].copy(POOL.leaks[i]);
11757
+ dst[k].copy(src[i]);
10734
11758
  }
10735
11759
  return n;
10736
11760
  }
@@ -11616,6 +12640,96 @@ function disposeAtmosphereNode(node) {
11616
12640
  node.mesh.material.dispose();
11617
12641
  }
11618
12642
 
12643
+ //#endregion
12644
+ //#region src/lib/scene/objects/surface/planetshine.ts
12645
+ var MAX_SHINERS = 2;
12646
+ var SHINE_FLOOR = 1e-4;
12647
+ function lambertSpherePhase(a) {
12648
+ return (Math.sin(a) + (Math.PI - a) * Math.cos(a)) / Math.PI;
12649
+ }
12650
+ function planetshineFraction(radius, distance, a) {
12651
+ const k = radius / distance;
12652
+ return 2 / 3 * lambertSpherePhase(a) * k * k;
12653
+ }
12654
+ var PLANETSHINE_GLSL = `
12655
+ #define PLANETSHINE_SHINERS ${2}
12656
+ uniform vec4 uShineDir[PLANETSHINE_SHINERS];
12657
+ uniform vec3 uShineLight[PLANETSHINE_SHINERS];
12658
+
12659
+ float sphereLightCosine(float cosTheta, float sinAlpha) {
12660
+ float t = max(cosTheta + sinAlpha, 0.0);
12661
+ return cosTheta >= sinAlpha ? cosTheta : t * t / (4.0 * sinAlpha);
12662
+ }
12663
+ `;
12664
+ var PLANETSHINE_LIGHT = `
12665
+ for (int i = 0; i < PLANETSHINE_SHINERS; i++) {
12666
+ vec4 shiner = uShineDir[i];
12667
+ // The slots fill in order, so the first empty slot ends the list.
12668
+ if (shiner.w <= 0.0) break;
12669
+ float cosine = sphereLightCosine(dot(geometryNormal, mat3(viewMatrix) * shiner.xyz), shiner.w);
12670
+ reflectedLight.indirectDiffuse += uShineLight[i] * cosine * BRDF_Lambert(material.diffuseContribution);
12671
+ }
12672
+ `;
12673
+ function makePlanetshineUniforms() {
12674
+ return {
12675
+ uShineDir: { value: Array.from({ length: 2 }, () => new Vector4(0, 0, 0, 0)) },
12676
+ uShineLight: { value: Array.from({ length: 2 }, () => new Vector3()) }
12677
+ };
12678
+ }
12679
+ function attachPlanetshine(material, uniforms) {
12680
+ const self = uniforms ?? makePlanetshineUniforms();
12681
+ chainShaderHook(material, "planetshine", (shader) => {
12682
+ Object.assign(shader.uniforms, self);
12683
+ shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${PLANETSHINE_GLSL}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>\n${PLANETSHINE_LIGHT}`);
12684
+ });
12685
+ return self;
12686
+ }
12687
+ var _strength = new Float64Array(2);
12688
+ var _swapDir = new Vector4();
12689
+ var _swapLight = new Vector3();
12690
+ function selectShiners(out, x, y, z, shiners, count) {
12691
+ const dirs = out.uShineDir.value;
12692
+ const lights = out.uShineLight.value;
12693
+ let kept = 0;
12694
+ for (let i = 0; i < count; i++) {
12695
+ const s = shiners[i];
12696
+ const dx = s.x - x;
12697
+ const dy = s.y - y;
12698
+ const dz = s.z - z;
12699
+ const d2 = dx * dx + dy * dy + dz * dz;
12700
+ if (2 / 3 * s.radius * s.radius < 1e-4 * d2) continue;
12701
+ const d = Math.sqrt(d2);
12702
+ if (d <= s.radius) continue;
12703
+ const k = s.radius / d;
12704
+ const cosPhase = -(dx * s.sunX + dy * s.sunY + dz * s.sunZ) / d;
12705
+ const fraction = planetshineFraction(s.radius, d, Math.acos(Math.max(-1, Math.min(1, cosPhase))));
12706
+ const strength = fraction * (.2126 * s.albedo.r + .7152 * s.albedo.g + .0722 * s.albedo.b);
12707
+ if (strength < 1e-4) continue;
12708
+ let slot = kept;
12709
+ if (kept < 2) kept++;
12710
+ else if (strength > _strength[2 - 1]) slot = 2 - 1;
12711
+ else continue;
12712
+ _strength[slot] = strength;
12713
+ dirs[slot].set(dx / d, dy / d, dz / d, k);
12714
+ lights[slot].set(s.albedo.r, s.albedo.g, s.albedo.b).multiplyScalar(s.sunlight * fraction);
12715
+ for (; slot > 0 && _strength[slot - 1] < strength; slot--) {
12716
+ _strength[slot] = _strength[slot - 1];
12717
+ _strength[slot - 1] = strength;
12718
+ _swapDir.copy(dirs[slot - 1]);
12719
+ dirs[slot - 1].copy(dirs[slot]);
12720
+ dirs[slot].copy(_swapDir);
12721
+ _swapLight.copy(lights[slot - 1]);
12722
+ lights[slot - 1].copy(lights[slot]);
12723
+ lights[slot].copy(_swapLight);
12724
+ }
12725
+ }
12726
+ for (let i = kept; i < 2; i++) {
12727
+ dirs[i].set(0, 0, 0, 0);
12728
+ lights[i].set(0, 0, 0);
12729
+ }
12730
+ return kept;
12731
+ }
12732
+
11619
12733
  //#endregion
11620
12734
  //#region src/lib/scene/objects/surface/ring-attach.ts
11621
12735
  function attachRingBundles(bodyId, systemId, rings, radii, bo, scene, maxTextureSize, ctx) {
@@ -11635,12 +12749,15 @@ function attachRingBundles(bodyId, systemId, rings, radii, bo, scene, maxTexture
11635
12749
  return;
11636
12750
  }
11637
12751
  node.mesh.userData.ringBundle = ringMeta.bundle;
12752
+ node.light.planet = ringPlanet(radii, bo.radiusScene);
11638
12753
  const host = bo.mesh ?? bo.model;
11639
12754
  if (host) node.mesh.quaternion.copy(host.quaternion);
11640
12755
  bo.rings.push(node);
11641
12756
  bo.root.add(node.mesh);
11642
12757
  if (bo.mesh && ringMeta === shadowCaster) {
11643
- node.planetShadow = attachRingShadowToPlanet(bo.mesh.material, node.innerScene, node.outerScene, node.transparency, node.intensityScale);
12758
+ const material = bo.mesh.material;
12759
+ node.planetShadow = attachRingShadowToPlanet(material, node.innerScene, node.outerScene, node.transparency, node.intensityScale);
12760
+ if (peakRingshine(node.light.profile, node.light.planet) >= 1e-4) node.ringshine = attachRingshine(material);
11644
12761
  if (bo.atmosphere) attachRingShadowToAtmosphere(bo.atmosphere, node.planetShadow, node.transparency, node.innerScene, node.outerScene);
11645
12762
  }
11646
12763
  if (radii) {
@@ -11651,6 +12768,15 @@ function attachRingBundles(bodyId, systemId, rings, radii, bo, scene, maxTexture
11651
12768
  }
11652
12769
  return promises;
11653
12770
  }
12771
+ function ringPlanet(radii, radiusScene) {
12772
+ return radii ? {
12773
+ equatorial: kmToScene((radii.a + radii.b) / 2),
12774
+ polar: kmToScene(radii.c)
12775
+ } : {
12776
+ equatorial: radiusScene,
12777
+ polar: radiusScene
12778
+ };
12779
+ }
11654
12780
 
11655
12781
  //#endregion
11656
12782
  //#region src/lib/fetch/nomenclature/parse.ts
@@ -11685,7 +12811,7 @@ function parseNomenclature(buffer) {
11685
12811
 
11686
12812
  //#endregion
11687
12813
  //#region src/lib/fetch/nomenclature/fetch.ts
11688
- var cache$1 = /* @__PURE__ */ new Map();
12814
+ var cache$2 = /* @__PURE__ */ new Map();
11689
12815
  async function fetchPositions(bodyId) {
11690
12816
  const url = versionedUrl(`/v1/nomenclature/positions/${bodyId}.bin.gz`, "nomenclature");
11691
12817
  const res = await fetch(url);
@@ -11713,16 +12839,16 @@ function nameFeatures(records, labelText) {
11713
12839
  }
11714
12840
  function fetchBodyNomenclature(bodyId, lang = getLocale()) {
11715
12841
  const key = `${bodyId}:${lang}`;
11716
- let p = cache$1.get(key);
12842
+ let p = cache$2.get(key);
11717
12843
  if (!p) {
11718
12844
  p = (async () => {
11719
12845
  await fetchMetadata();
11720
12846
  const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabelText(bodyId, lang)]);
11721
12847
  return nameFeatures(records, labels);
11722
12848
  })();
11723
- cache$1.set(key, p);
12849
+ cache$2.set(key, p);
11724
12850
  p.catch(() => {
11725
- if (cache$1.get(key) === p) cache$1.delete(key);
12851
+ if (cache$2.get(key) === p) cache$2.delete(key);
11726
12852
  });
11727
12853
  }
11728
12854
  return p;
@@ -11765,6 +12891,100 @@ function attachCanvasForwarders(el, canvas) {
11765
12891
  });
11766
12892
  }
11767
12893
 
12894
+ //#endregion
12895
+ //#region src/lib/scene/relief/worker.ts?worker&inline
12896
+ var jsContent$1 = "function e(e,t,n,r){return`${e&&`${e}/`}${t}/${n}/${r}.webp`}function t(e,t,n,r,i=1){let a=t.length,o=n.length,s=new Float32Array(a*o*3),c=new Float32Array(a*o*3),l=new Float32Array(a*o*2),u=Float64Array.from(n,Math.sin),d=Float64Array.from(n,Math.cos);for(let f=0;f<a;f++){let a=t[f],p=Math.sin(a),m=Math.cos(a);for(let t=0;t<o;t++){let h=f*o+t,g=-d[t]*p,_=m,v=u[t]*p,y=e+(r?r[h]*i:0),b=h*3;s[b]=y*g,s[b+1]=y*_,s[b+2]=y*v,c[b]=g,c[b+1]=_,c[b+2]=v;let x=h*2;l[x]=n[t]/(2*Math.PI),l[x+1]=1-a/Math.PI}}let f=new Uint32Array((a-1)*(o-1)*6),p=0;for(let e=0;e<a-1;e++){let n=t[e]<=0,r=t[e+1]>=Math.PI;for(let t=0;t<o-1;t++){let i=e*o+t+1,a=e*o+t,s=(e+1)*o+t,c=(e+1)*o+t+1;n||(f[p++]=i,f[p++]=a,f[p++]=c),r||(f[p++]=a,f[p++]=s,f[p++]=c)}}return{positions:s,normals:c,uvs:l,indices:f.subarray(0,p)}}const n=[``,`north`,`south`],r=Math.SQRT2-1;function i(e,t){return e===0?2<<t:1<<t-1}function a(e,t){return e===0?1<<t:1<<t-1}function o(e,t,n,i){if(e===0){let e=Math.atan2(i,-t)/(2*Math.PI);return{x:e<0?e+1:e,y:Math.acos(Math.min(Math.max(n,-1),1))/Math.PI}}let a=e===1?1:-1,o=.5/(r*(1+a*n));return{x:.5+t*o,y:.5+a*i*o}}function s(e,t,n,r){let i=e.max_level-t;return i>=0&&n>=e.x[0]>>i&&n<=e.x[1]>>i&&r>=e.y[0]>>i&&r<=e.y[1]>>i}function c(e,t,n,r){return e?.find(e=>s(e,t,n,r))}const l=Math.sin(60*Math.PI/180),u=2**18,d=2**17;function f(e,t,n,r){return((e*32+t)*u+n)*d+r}function p(e){let t=e%d,n=Math.floor(e/d)%u,r=Math.floor(e/(d*u));return{chart:Math.floor(r/32),level:r%32,x:n,y:t}}const m={chart:0,level:0,x0:0,x1:0,y0:0,y1:0,fx:0,fy:0};function h(e,t,n,r,s){let c=e<1||Math.abs(n)<=l?0:n>0?1:2,u=i(c,e)*s,d=a(c,e)*s,f=o(c,t,n,r),p=f.x*u-.5,h=f.y*d-.5,g=Math.floor(p),_=Math.floor(h),v=e=>Math.min(Math.max(e,0),u-1),y=e=>Math.min(Math.max(e,0),d-1);return m.chart=c,m.level=e,m.fx=p-g,m.fy=h-_,m.x0=c===0?(g+u)%u:v(g),m.x1=c===0?(g+1)%u:v(g+1),m.y0=y(_),m.y1=y(_+1),m}function g(e,t,n){let r=Float64Array.from(t.at,Math.sin),i=Float64Array.from(t.at,Math.cos),a=t.at.length;for(let o=0;o<e.at.length;o++){let s=Math.sin(e.at[o]),c=Math.cos(e.at[o]);for(let l=0;l<a;l++){let u=Math.min(e.level[o],t.level[l]);n(o*a+l,u,-i[l]*s,c,r[l]*s)}}}function _(e,t,n,r=1/0,i=()=>!0){let a=new Set([f(0,0,0,0),f(0,0,1,0)]);return g(e,t,(e,t,o,s,c)=>{for(let e=t;;e--){let t=h(e,o,s,c,n),l=Math.floor(t.x0/n),u=Math.floor(t.x1/n),d=Math.floor(t.y0/n),p=Math.floor(t.y1/n);if(!(e>r&&!(i(t.chart,e,l,d)&&i(t.chart,e,u,d)&&i(t.chart,e,l,p)&&i(t.chart,e,u,p)))){a.add(f(t.chart,e,l,d)),u!==l&&a.add(f(t.chart,e,u,d)),p!==d&&(a.add(f(t.chart,e,l,p)),u!==l&&a.add(f(t.chart,e,u,p)));return}}}),a}function v(e,t,n,r,i,a){for(let o=t;o>=0;o--){let t=h(o,n,r,i,a),s=(n,r)=>{let i=e(f(t.chart,o,Math.floor(n/a),Math.floor(r/a)));return i&&i[r%a*a+n%a]},c=s(t.x0,t.y0),l=s(t.x1,t.y0),u=s(t.x0,t.y1),d=s(t.x1,t.y1);if(c===void 0||l===void 0||u===void 0||d===void 0)continue;let p=c+(l-c)*t.fx;return p+(u+(d-u)*t.fx-p)*t.fy}}function y(e,t,n,r,i,a){let o=new Float32Array(e.at.length*t.at.length),s=!1;return g(e,t,(e,t,c,l,u)=>{let d=v(n,t,c,l,u,r);d===void 0?s=!0:o[e]=a+i*d/65535}),s?null:o}function b(e,t){let n=new Uint8Array(t*t*2);for(let r=0;r<t;r++){let i=(t-1-r)*t*4,a=r*t*2;for(let r=0;r<t;r++,i+=4,a+=2)n[a]=e[i],n[a+1]=e[i+1]}return n}function x(e,t){let n=new Float32Array(t*t);for(let r=0;r<t;r++){let i=(t-1-r)*t*4,a=(r+1)*t;for(let o=r*t;o<a;o++,i+=4)n[o]=(e[i]<<8|e[i+1])+e[i+2]/256}return n}function S(e,t,n){let r=n?new Float32Array(t*t):new Uint16Array(t*t);for(let t=0,i=0;t<r.length;t++,i+=4){let a=e[i]<<8|e[i+1];r[t]=n?a+e[i+2]/256:a}return r}const C=self,w=new Map;let T;async function E(e,t){let n=await fetch(e,{signal:t});if(!n.ok)throw Error(`tile ${n.status}`);let r=await createImageBitmap(await n.blob(),{premultiplyAlpha:`none`,colorSpaceConversion:`none`}),i=r.width;T?.width!==i&&(T=new OffscreenCanvas(i,i));let a=T.getContext(`2d`,{willReadFrequently:!0});if(!a)throw Error(`no 2d canvas in this worker`);return a.globalCompositeOperation=`copy`,a.drawImage(r,0,0),r.close(),{rgba:a.getImageData(0,0,i,i).data,size:i}}async function D(e,t){let{rgba:n,size:r}=await E(e.url,t),i=e.fine?x(n,r):b(n,r);C.postMessage({type:`tile`,id:e.id,size:r,heights:i},[i.buffer])}const O=new Map;let k=``;async function A(r,i){let{rows:a,columns:o,tileSize:s,maxLevel:l,patches:u}=r,d=[r.version,...u.map(e=>e.version)],f=`${r.root}?${d.join()}`;f!==k&&O.clear(),k=f;let m=(e,t,n,r)=>e===0?c(u,t,n,r):void 0,h=_(a,o,s,l,(e,t,n,r)=>m(e,t,n,r)!==void 0),g=[...h].filter(e=>!O.has(e));await Promise.all(Array.from({length:6},async()=>{for(let t=g.pop();t!==void 0;t=g.pop()){let{chart:a,level:o,x:s,y:c}=p(t),u=o>l,d=u?m(a,o,s,c)?.version:r.version,f=`${r.root}${e(n[a],o,s,c)}?v=${d}`;try{let{rgba:e,size:n}=await E(f,i);O.set(t,S(e,n,u))}catch{if(i.aborted)return}}})),i.throwIfAborted();let v=y(a,o,e=>O.get(e),s,r.scaleKm,r.biasKm);for(let e of[...O.keys()])h.has(e)||O.delete(e);if(!v)throw Error(`no height tiles for the grid`);let b=t(r.radius,a.at,o.at,v,r.unitsPerKm),x=b.indices.slice();C.postMessage({type:`grid`,id:r.id,...b,indices:x,riseKm:v},[b.positions.buffer,b.normals.buffer,b.uvs.buffer,x.buffer,v.buffer])}async function j(e){let t=new AbortController;w.set(e.id,t);try{e.type===`tile`?await D(e,t.signal):await A(e,t.signal)}catch{C.postMessage({type:`failed`,id:e.id})}finally{w.delete(e.id)}}self.onmessage=e=>{let t=e.data;t.type===`abort`?w.get(t.id)?.abort():j(t)};";
12897
+ var blob$1 = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent$1], { type: "text/javascript;charset=utf-8" });
12898
+ function WorkerWrapper$1(options) {
12899
+ let objURL;
12900
+ try {
12901
+ objURL = blob$1 && (self.URL || self.webkitURL).createObjectURL(blob$1);
12902
+ if (!objURL) throw "";
12903
+ const worker = new Worker(objURL, {
12904
+ type: "module",
12905
+ name: options?.name
12906
+ });
12907
+ worker.addEventListener("error", () => {
12908
+ (self.URL || self.webkitURL).revokeObjectURL(objURL);
12909
+ });
12910
+ return worker;
12911
+ } catch (e) {
12912
+ return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent$1), {
12913
+ type: "module",
12914
+ name: options?.name
12915
+ });
12916
+ }
12917
+ }
12918
+
12919
+ //#endregion
12920
+ //#region src/lib/scene/relief/client.ts
12921
+ var worker = null;
12922
+ var nextId = 1;
12923
+ var pending$1 = /* @__PURE__ */ new Map();
12924
+ function drop(reason) {
12925
+ worker?.terminate();
12926
+ worker = null;
12927
+ const lost = [...pending$1.values()];
12928
+ pending$1.clear();
12929
+ for (const { reject } of lost) reject(new Error(reason));
12930
+ }
12931
+ function ensureWorker() {
12932
+ if (worker) return worker;
12933
+ worker = new WorkerWrapper$1();
12934
+ worker.onmessage = (ev) => {
12935
+ const msg = ev.data;
12936
+ const request = pending$1.get(msg.id);
12937
+ pending$1.delete(msg.id);
12938
+ if (!request) return;
12939
+ if (msg.type === "failed") request.reject(/* @__PURE__ */ new Error("relief worker could not answer"));
12940
+ else request.resolve(msg);
12941
+ };
12942
+ worker.onerror = (ev) => drop(`relief worker error: ${ev.message || "unknown"}`);
12943
+ worker.onmessageerror = () => drop("relief worker sent a message that could not be read");
12944
+ return worker;
12945
+ }
12946
+ function ask(build, signal) {
12947
+ return new Promise((resolve, reject) => {
12948
+ if (typeof Worker === "undefined") {
12949
+ reject(/* @__PURE__ */ new Error("no workers here"));
12950
+ return;
12951
+ }
12952
+ const id = nextId++;
12953
+ pending$1.set(id, {
12954
+ resolve,
12955
+ reject
12956
+ });
12957
+ signal.addEventListener("abort", () => {
12958
+ if (!pending$1.delete(id)) return;
12959
+ ensureWorker().postMessage({
12960
+ type: "abort",
12961
+ id
12962
+ });
12963
+ reject(new DOMException("aborted", "AbortError"));
12964
+ }, { once: true });
12965
+ ensureWorker().postMessage(build(id));
12966
+ });
12967
+ }
12968
+ function absolute(url) {
12969
+ return new URL(url, location.href).href;
12970
+ }
12971
+ function loadHeightTile(url, fine, signal) {
12972
+ return ask((id) => ({
12973
+ type: "tile",
12974
+ id,
12975
+ url: absolute(url),
12976
+ fine
12977
+ }), signal);
12978
+ }
12979
+ function buildTerrainGrid(spec, signal) {
12980
+ return ask((id) => ({
12981
+ ...spec,
12982
+ type: "grid",
12983
+ id,
12984
+ root: absolute(spec.root)
12985
+ }), signal);
12986
+ }
12987
+
11768
12988
  //#endregion
11769
12989
  //#region src/lib/scene/lod/tile-window.ts
11770
12990
  var WINDOW_TILES = 4;
@@ -11919,100 +13139,6 @@ function slotTile(placement, sx, sy) {
11919
13139
  };
11920
13140
  }
11921
13141
 
11922
- //#endregion
11923
- //#region src/lib/scene/relief/worker.ts?worker&inline
11924
- var jsContent$1 = "function e(e,t,n,r){return`${e&&`${e}/`}${t}/${n}/${r}.webp`}function t(e,t,n,r,i=1){let a=t.length,o=n.length,s=new Float32Array(a*o*3),c=new Float32Array(a*o*3),l=new Float32Array(a*o*2),u=Float64Array.from(n,Math.sin),d=Float64Array.from(n,Math.cos);for(let f=0;f<a;f++){let a=t[f],p=Math.sin(a),m=Math.cos(a);for(let t=0;t<o;t++){let h=f*o+t,g=-d[t]*p,_=m,v=u[t]*p,y=e+(r?r[h]*i:0),b=h*3;s[b]=y*g,s[b+1]=y*_,s[b+2]=y*v,c[b]=g,c[b+1]=_,c[b+2]=v;let x=h*2;l[x]=n[t]/(2*Math.PI),l[x+1]=1-a/Math.PI}}let f=new Uint32Array((a-1)*(o-1)*6),p=0;for(let e=0;e<a-1;e++){let n=t[e]<=0,r=t[e+1]>=Math.PI;for(let t=0;t<o-1;t++){let i=e*o+t+1,a=e*o+t,s=(e+1)*o+t,c=(e+1)*o+t+1;n||(f[p++]=i,f[p++]=a,f[p++]=c),r||(f[p++]=a,f[p++]=s,f[p++]=c)}}return{positions:s,normals:c,uvs:l,indices:f.subarray(0,p)}}const n=[``,`north`,`south`],r=Math.SQRT2-1;function i(e,t){return e===0?2<<t:1<<t-1}function a(e,t){return e===0?1<<t:1<<t-1}function o(e,t,n,i){if(e===0){let e=Math.atan2(i,-t)/(2*Math.PI);return{x:e<0?e+1:e,y:Math.acos(Math.min(Math.max(n,-1),1))/Math.PI}}let a=e===1?1:-1,o=.5/(r*(1+a*n));return{x:.5+t*o,y:.5+a*i*o}}function s(e,t,n,r){let i=e.max_level-t;return i>=0&&n>=e.x[0]>>i&&n<=e.x[1]>>i&&r>=e.y[0]>>i&&r<=e.y[1]>>i}function c(e,t,n,r){return e?.find(e=>s(e,t,n,r))}const l=Math.sin(60*Math.PI/180),u=2**18,d=2**17;function f(e,t,n,r){return((e*32+t)*u+n)*d+r}function p(e){let t=e%d,n=Math.floor(e/d)%u,r=Math.floor(e/(d*u));return{chart:Math.floor(r/32),level:r%32,x:n,y:t}}const m={chart:0,level:0,x0:0,x1:0,y0:0,y1:0,fx:0,fy:0};function h(e,t,n,r,s){let c=e<1||Math.abs(n)<=l?0:n>0?1:2,u=i(c,e)*s,d=a(c,e)*s,f=o(c,t,n,r),p=f.x*u-.5,h=f.y*d-.5,g=Math.floor(p),_=Math.floor(h),v=e=>Math.min(Math.max(e,0),u-1),y=e=>Math.min(Math.max(e,0),d-1);return m.chart=c,m.level=e,m.fx=p-g,m.fy=h-_,m.x0=c===0?(g+u)%u:v(g),m.x1=c===0?(g+1)%u:v(g+1),m.y0=y(_),m.y1=y(_+1),m}function g(e,t,n){let r=Float64Array.from(t.at,Math.sin),i=Float64Array.from(t.at,Math.cos),a=t.at.length;for(let o=0;o<e.at.length;o++){let s=Math.sin(e.at[o]),c=Math.cos(e.at[o]);for(let l=0;l<a;l++){let u=Math.min(e.level[o],t.level[l]);n(o*a+l,u,-i[l]*s,c,r[l]*s)}}}function _(e,t,n,r=1/0,i=()=>!0){let a=new Set([f(0,0,0,0),f(0,0,1,0)]);return g(e,t,(e,t,o,s,c)=>{for(let e=t;;e--){let t=h(e,o,s,c,n),l=Math.floor(t.x0/n),u=Math.floor(t.x1/n),d=Math.floor(t.y0/n),p=Math.floor(t.y1/n);if(!(e>r&&!(i(t.chart,e,l,d)&&i(t.chart,e,u,d)&&i(t.chart,e,l,p)&&i(t.chart,e,u,p)))){a.add(f(t.chart,e,l,d)),u!==l&&a.add(f(t.chart,e,u,d)),p!==d&&(a.add(f(t.chart,e,l,p)),u!==l&&a.add(f(t.chart,e,u,p)));return}}}),a}function v(e,t,n,r,i,a){for(let o=t;o>=0;o--){let t=h(o,n,r,i,a),s=(n,r)=>{let i=e(f(t.chart,o,Math.floor(n/a),Math.floor(r/a)));return i&&i[r%a*a+n%a]},c=s(t.x0,t.y0),l=s(t.x1,t.y0),u=s(t.x0,t.y1),d=s(t.x1,t.y1);if(c===void 0||l===void 0||u===void 0||d===void 0)continue;let p=c+(l-c)*t.fx;return p+(u+(d-u)*t.fx-p)*t.fy}}function y(e,t,n,r,i,a){let o=new Float32Array(e.at.length*t.at.length),s=!1;return g(e,t,(e,t,c,l,u)=>{let d=v(n,t,c,l,u,r);d===void 0?s=!0:o[e]=a+i*d/65535}),s?null:o}function b(e,t){let n=new Uint8Array(t*t*2);for(let r=0;r<t;r++){let i=(t-1-r)*t*4,a=r*t*2;for(let r=0;r<t;r++,i+=4,a+=2)n[a]=e[i],n[a+1]=e[i+1]}return n}function x(e,t){let n=new Float32Array(t*t);for(let r=0;r<t;r++){let i=(t-1-r)*t*4,a=(r+1)*t;for(let o=r*t;o<a;o++,i+=4)n[o]=(e[i]<<8|e[i+1])+e[i+2]/256}return n}function S(e,t,n){let r=n?new Float32Array(t*t):new Uint16Array(t*t);for(let t=0,i=0;t<r.length;t++,i+=4){let a=e[i]<<8|e[i+1];r[t]=n?a+e[i+2]/256:a}return r}const C=self,w=new Map;let T;async function E(e,t){let n=await fetch(e,{signal:t});if(!n.ok)throw Error(`tile ${n.status}`);let r=await createImageBitmap(await n.blob(),{premultiplyAlpha:`none`,colorSpaceConversion:`none`}),i=r.width;T?.width!==i&&(T=new OffscreenCanvas(i,i));let a=T.getContext(`2d`,{willReadFrequently:!0});if(!a)throw Error(`no 2d canvas in this worker`);return a.globalCompositeOperation=`copy`,a.drawImage(r,0,0),r.close(),{rgba:a.getImageData(0,0,i,i).data,size:i}}async function D(e,t){let{rgba:n,size:r}=await E(e.url,t),i=e.fine?x(n,r):b(n,r);C.postMessage({type:`tile`,id:e.id,size:r,heights:i},[i.buffer])}const O=new Map;let k=``;async function A(r,i){let{rows:a,columns:o,tileSize:s,maxLevel:l,patches:u}=r,d=[r.version,...u.map(e=>e.version)],f=`${r.root}?${d.join()}`;f!==k&&O.clear(),k=f;let m=(e,t,n,r)=>e===0?c(u,t,n,r):void 0,h=_(a,o,s,l,(e,t,n,r)=>m(e,t,n,r)!==void 0),g=[...h].filter(e=>!O.has(e));await Promise.all(Array.from({length:6},async()=>{for(let t=g.pop();t!==void 0;t=g.pop()){let{chart:a,level:o,x:s,y:c}=p(t),u=o>l,d=u?m(a,o,s,c)?.version:r.version,f=`${r.root}${e(n[a],o,s,c)}?v=${d}`;try{let{rgba:e,size:n}=await E(f,i);O.set(t,S(e,n,u))}catch{if(i.aborted)return}}})),i.throwIfAborted();let v=y(a,o,e=>O.get(e),s,r.scaleKm,r.biasKm);for(let e of[...O.keys()])h.has(e)||O.delete(e);if(!v)throw Error(`no height tiles for the grid`);let b=t(r.radius,a.at,o.at,v,r.unitsPerKm),x=b.indices.slice();C.postMessage({type:`grid`,id:r.id,...b,indices:x,riseKm:v},[b.positions.buffer,b.normals.buffer,b.uvs.buffer,x.buffer,v.buffer])}async function j(e){let t=new AbortController;w.set(e.id,t);try{e.type===`tile`?await D(e,t.signal):await A(e,t.signal)}catch{C.postMessage({type:`failed`,id:e.id})}finally{w.delete(e.id)}}self.onmessage=e=>{let t=e.data;t.type===`abort`?w.get(t.id)?.abort():j(t)};";
11925
- var blob$1 = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent$1], { type: "text/javascript;charset=utf-8" });
11926
- function WorkerWrapper$1(options) {
11927
- let objURL;
11928
- try {
11929
- objURL = blob$1 && (self.URL || self.webkitURL).createObjectURL(blob$1);
11930
- if (!objURL) throw "";
11931
- const worker = new Worker(objURL, {
11932
- type: "module",
11933
- name: options?.name
11934
- });
11935
- worker.addEventListener("error", () => {
11936
- (self.URL || self.webkitURL).revokeObjectURL(objURL);
11937
- });
11938
- return worker;
11939
- } catch (e) {
11940
- return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent$1), {
11941
- type: "module",
11942
- name: options?.name
11943
- });
11944
- }
11945
- }
11946
-
11947
- //#endregion
11948
- //#region src/lib/scene/relief/client.ts
11949
- var worker = null;
11950
- var nextId = 1;
11951
- var pending$1 = /* @__PURE__ */ new Map();
11952
- function drop(reason) {
11953
- worker?.terminate();
11954
- worker = null;
11955
- const lost = [...pending$1.values()];
11956
- pending$1.clear();
11957
- for (const { reject } of lost) reject(new Error(reason));
11958
- }
11959
- function ensureWorker() {
11960
- if (worker) return worker;
11961
- worker = new WorkerWrapper$1();
11962
- worker.onmessage = (ev) => {
11963
- const msg = ev.data;
11964
- const request = pending$1.get(msg.id);
11965
- pending$1.delete(msg.id);
11966
- if (!request) return;
11967
- if (msg.type === "failed") request.reject(/* @__PURE__ */ new Error("relief worker could not answer"));
11968
- else request.resolve(msg);
11969
- };
11970
- worker.onerror = (ev) => drop(`relief worker error: ${ev.message || "unknown"}`);
11971
- worker.onmessageerror = () => drop("relief worker sent a message that could not be read");
11972
- return worker;
11973
- }
11974
- function ask(build, signal) {
11975
- return new Promise((resolve, reject) => {
11976
- if (typeof Worker === "undefined") {
11977
- reject(/* @__PURE__ */ new Error("no workers here"));
11978
- return;
11979
- }
11980
- const id = nextId++;
11981
- pending$1.set(id, {
11982
- resolve,
11983
- reject
11984
- });
11985
- signal.addEventListener("abort", () => {
11986
- if (!pending$1.delete(id)) return;
11987
- ensureWorker().postMessage({
11988
- type: "abort",
11989
- id
11990
- });
11991
- reject(new DOMException("aborted", "AbortError"));
11992
- }, { once: true });
11993
- ensureWorker().postMessage(build(id));
11994
- });
11995
- }
11996
- function absolute(url) {
11997
- return new URL(url, location.href).href;
11998
- }
11999
- function loadHeightTile(url, fine, signal) {
12000
- return ask((id) => ({
12001
- type: "tile",
12002
- id,
12003
- url: absolute(url),
12004
- fine
12005
- }), signal);
12006
- }
12007
- function buildTerrainGrid(spec, signal) {
12008
- return ask((id) => ({
12009
- ...spec,
12010
- type: "grid",
12011
- id,
12012
- root: absolute(spec.root)
12013
- }), signal);
12014
- }
12015
-
12016
13142
  //#endregion
12017
13143
  //#region src/lib/scene/render-tier.ts
12018
13144
  var RENDER_TIER_PRESETS = {
@@ -12103,37 +13229,28 @@ var TILE_CHAIN_GLSL = `
12103
13229
  #ifndef TILE_CHAIN
12104
13230
  #define TILE_CHAIN
12105
13231
 
12106
- // Where a fragment is on the map and on the cap of its own hemisphere, as
12107
- // fractions of the chart, and what a screen pixel spans there. The place on
12108
- // the cap is from the chart's north-west corner. The place on the map is from
12109
- // the point that the maker of the charts counts from.
12110
- struct TileCharts {
12111
- vec2 mapAt;
12112
- vec2 mapDx;
12113
- vec2 mapDy;
12114
- vec2 capAt;
12115
- vec2 capDx;
12116
- vec2 capDy;
12117
- float side;
12118
- };
13232
+ // A fragment on one chart is a mat3x2: its place, as a fraction of the
13233
+ // chart, then what a screen pixel adds to that along x and along y.
12119
13234
 
12120
- // The cap of the hemisphere of dir, seen from above its pole. ddx and ddy are
12121
- // what a screen pixel adds to dir.
12122
- void tileCap( vec3 dir, vec3 ddx, vec3 ddy, out float side, out vec2 capAt, out vec2 capDx, out vec2 capDy ) {
13235
+ // The cap of the hemisphere of dir, seen from above its pole: the place is
13236
+ // from the chart's north-west corner. ddx and ddy are what a screen pixel
13237
+ // adds to dir.
13238
+ void tileCap( vec3 dir, vec3 ddx, vec3 ddy, out float side, out mat3x2 cap ) {
12123
13239
  side = dir.y < 0.0 ? - 1.0 : 1.0;
12124
13240
  float fold = 1.0 + side * dir.y;
12125
13241
  vec2 plane = vec2( dir.x, side * dir.z );
12126
13242
  float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
12127
- capAt = 0.5 + plane * capScale;
12128
- capDx = ( vec2( ddx.x, side * ddx.z ) - plane * ( side * ddx.y / fold ) ) * capScale;
12129
- capDy = ( vec2( ddy.x, side * ddy.z ) - plane * ( side * ddy.y / fold ) ) * capScale;
13243
+ cap = mat3x2(
13244
+ 0.5 + plane * capScale,
13245
+ ( vec2( ddx.x, side * ddx.z ) - plane * ( side * ddx.y / fold ) ) * capScale,
13246
+ ( vec2( ddy.x, side * ddy.z ) - plane * ( side * ddy.y / fold ) ) * capScale
13247
+ );
12130
13248
  }
12131
13249
 
12132
- // The charts of the point at + off, the map counted from at. A position on
12133
- // the sphere has seven digits, and a tile index of a patch takes them all;
12134
- // the offset keeps its own.
12135
- TileCharts tileChartsFrom( vec3 at, vec3 off ) {
12136
- TileCharts charts;
13250
+ // The point at + off on the map, counted from at, and on the cap of its own
13251
+ // hemisphere, which side gives. A position on the sphere has seven digits,
13252
+ // and a tile index of a patch takes them all; the offset keeps its own.
13253
+ void tileChartsFrom( vec3 at, vec3 off, out mat3x2 map, out mat3x2 cap, out float side ) {
12137
13254
  vec3 pos = at + off;
12138
13255
  vec3 ox = dFdx( off );
12139
13256
  vec3 oy = dFdy( off );
@@ -12146,19 +13263,20 @@ TileCharts tileChartsFrom( vec3 at, vec3 off ) {
12146
13263
  float turn = atan( at.z * off.x - at.x * off.z, at.x * pos.x + at.z * pos.z );
12147
13264
  float out1 = ( 2.0 * ( at.x * off.x + at.z * off.z ) + off.x * off.x + off.z * off.z ) / ( flat1 + flat0 );
12148
13265
  float drop = atan( out1 * at.y - off.y * flat0, pos.y * at.y + flat1 * flat0 );
12149
- charts.mapAt = vec2( turn / ( 2.0 * PI ), drop / PI );
12150
13266
  float east = 1.0 / ( 2.0 * PI * flat2 );
12151
13267
  float south = 1.0 / ( PI * dot( pos, pos ) );
12152
- charts.mapDx = vec2( ( pos.z * ox.x - pos.x * ox.z ) * east, ( pos.y * ( pos.x * ox.x + pos.z * ox.z ) / flat1 - flat1 * ox.y ) * south );
12153
- charts.mapDy = vec2( ( pos.z * oy.x - pos.x * oy.z ) * east, ( pos.y * ( pos.x * oy.x + pos.z * oy.z ) / flat1 - flat1 * oy.y ) * south );
13268
+ map = mat3x2(
13269
+ turn / ( 2.0 * PI ), drop / PI,
13270
+ ( pos.z * ox.x - pos.x * ox.z ) * east, ( pos.y * ( pos.x * ox.x + pos.z * ox.z ) / flat1 - flat1 * ox.y ) * south,
13271
+ ( pos.z * oy.x - pos.x * oy.z ) * east, ( pos.y * ( pos.x * oy.x + pos.z * oy.z ) / flat1 - flat1 * oy.y ) * south
13272
+ );
12154
13273
 
12155
13274
  // A cap goes no deeper than the pyramid, so the direction itself will do.
12156
13275
  float range = length( pos );
12157
13276
  vec3 dir = pos / range;
12158
13277
  vec3 ddx = ( ox - dir * dot( dir, ox ) ) / range;
12159
13278
  vec3 ddy = ( oy - dir * dot( dir, oy ) ) / range;
12160
- tileCap( dir, ddx, ddy, charts.side, charts.capAt, charts.capDx, charts.capDy );
12161
- return charts;
13279
+ tileCap( dir, ddx, ddy, side, cap );
12162
13280
  }
12163
13281
 
12164
13282
  // Long and short half-axes of the footprint whose sides are fx and fy. The
@@ -12182,12 +13300,13 @@ void tileAxes( vec2 fx, vec2 fy, out vec2 major, out vec2 minor ) {
12182
13300
  // home is the point that the charts count the map from, in tiles from the
12183
13301
  // window's north-west corner. Gives the fragment's place in the window's
12184
13302
  // texture, in tiles, and the axes of its footprint, in the window's pixels.
12185
- float tileWindowWeight( TileCharts charts, vec4 window, vec4 grid, vec2 home, vec2 tileFilter, out vec2 spot, out vec2 major, out vec2 minor ) {
13303
+ float tileWindowWeight( mat3x2 map, mat3x2 cap, float side, vec4 window, vec4 grid, vec2 home, vec2 tileFilter, out vec2 spot, out vec2 major, out vec2 minor ) {
12186
13304
  bool onMap = window.x < 0.5;
12187
13305
  // The other pole's cap.
12188
- if ( ! onMap && ( window.x < 1.5 ) != ( charts.side > 0.0 ) ) return 0.0;
12189
- vec2 dx = ( onMap ? charts.mapDx : charts.capDx ) * grid.xy * tileFilter.y;
12190
- vec2 dy = ( onMap ? charts.mapDy : charts.capDy ) * grid.xy * tileFilter.y;
13306
+ if ( ! onMap && ( window.x < 1.5 ) != ( side > 0.0 ) ) return 0.0;
13307
+ mat3x2 chart = onMap ? map : cap;
13308
+ vec2 dx = chart[ 1 ] * grid.xy * tileFilter.y;
13309
+ vec2 dy = chart[ 2 ] * grid.xy * tileFilter.y;
12191
13310
 
12192
13311
  // Level this fragment asks of the window, from the short axis of its
12193
13312
  // footprint in the window's pixels: the long axis is the anisotropic
@@ -12200,7 +13319,7 @@ float tileWindowWeight( TileCharts charts, vec4 window, vec4 grid, vec2 home, ve
12200
13319
 
12201
13320
  float rows = ${4 .toFixed(1)};
12202
13321
  // Offset from the window's north-west corner, in tiles.
12203
- vec2 rel = onMap ? home + charts.mapAt * grid.xy : charts.capAt * grid.xy - grid.zw;
13322
+ vec2 rel = onMap ? home + chart[ 0 ] * grid.xy : chart[ 0 ] * grid.xy - grid.zw;
12204
13323
  if ( onMap ) {
12205
13324
  // Half a pixel in from a pole of the map: past it the filter would
12206
13325
  // read the far row. The map's longitude wraps.
@@ -12511,40 +13630,35 @@ float reliefValue( vec4 texel ) {
12511
13630
  return dot( texel, uReliefRead );
12512
13631
  }
12513
13632
 
12514
- // How the coordinates of each chart change along the sphere at a direction:
12515
- // the dot product with a step along the sphere is that step on the chart.
12516
- struct ReliefRates {
12517
- vec3 mapX;
12518
- vec3 mapY;
12519
- vec3 capX;
12520
- vec3 capY;
12521
- };
12522
-
12523
- ReliefRates reliefRates( vec3 dir, float side ) {
12524
- ReliefRates rates;
13633
+ // How the coordinates of each chart change along the sphere at a direction,
13634
+ // as a mat2x3 per chart: a step along the sphere times it is that step on the
13635
+ // chart, and it times a step on the chart is that step along the sphere.
13636
+ void reliefRates( vec3 dir, float side, out mat2x3 map, out mat2x3 cap ) {
12525
13637
  float flat2 = max( dir.x * dir.x + dir.z * dir.z, 1e-12 );
12526
- rates.mapX = vec3( dir.z, 0.0, - dir.x ) / ( 2.0 * PI * flat2 );
12527
- rates.mapY = ( dir * dir.y - vec3( 0.0, 1.0, 0.0 ) ) / ( PI * sqrt( flat2 ) );
13638
+ map = mat2x3(
13639
+ vec3( dir.z, 0.0, - dir.x ) / ( 2.0 * PI * flat2 ),
13640
+ ( dir * dir.y - vec3( 0.0, 1.0, 0.0 ) ) / ( PI * sqrt( flat2 ) )
13641
+ );
12528
13642
  float fold = 1.0 + side * dir.y;
12529
13643
  float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
12530
13644
  vec2 plane = vec2( dir.x, side * dir.z ) * side / fold;
12531
- rates.capX = vec3( 1.0, - plane.x, 0.0 ) * capScale;
12532
- rates.capY = vec3( 0.0, - plane.y, side ) * capScale;
12533
- rates.capX -= dir * dot( rates.capX, dir );
12534
- rates.capY -= dir * dot( rates.capY, dir );
12535
- return rates;
13645
+ vec3 capX = vec3( 1.0, - plane.x, 0.0 ) * capScale;
13646
+ vec3 capY = vec3( 0.0, - plane.y, side ) * capScale;
13647
+ cap = mat2x3( capX - dir * dot( capX, dir ), capY - dir * dot( capY, dir ) );
12536
13648
  }
12537
13649
 
12538
13650
  // Slope of the ground at a direction of the body's frame: a vector along the
12539
13651
  // unit sphere that points uphill, as long as the rise over the run. Also
12540
13652
  // gives the part of the fragment that the tiles answer for, and the finest
12541
13653
  // window that does, or -1.
12542
- vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int first ) {
13654
+ vec3 reliefTileSlope( vec3 dir, mat3x2 map, mat3x2 cap, float side, out float covered, out int first ) {
12543
13655
  covered = 0.0;
12544
13656
  first = - 1;
12545
13657
  vec3 slope = vec3( 0.0 );
12546
13658
  if ( uReliefWindow[ 0 ].w < 0.5 ) return slope;
12547
- ReliefRates rates = reliefRates( dir, charts.side );
13659
+ mat2x3 mapRates;
13660
+ mat2x3 capRates;
13661
+ reliefRates( dir, side, mapRates, capRates );
12548
13662
 
12549
13663
  float rows = ${4 .toFixed(1)};
12550
13664
  for ( int i = 0; i < ${7}; i ++ ) {
@@ -12554,7 +13668,7 @@ vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int fi
12554
13668
  vec2 spot;
12555
13669
  vec2 major;
12556
13670
  vec2 minor;
12557
- float alpha = tileWindowWeight( charts, window, grid, uReliefHome[ i ], uReliefFilter, spot, major, minor );
13671
+ float alpha = tileWindowWeight( map, cap, side, window, grid, uReliefHome[ i ], uReliefFilter, spot, major, minor );
12558
13672
  if ( alpha <= 0.0 ) continue;
12559
13673
  // Filtered, the mask falls towards a missing tile. The pixels beside
12560
13674
  // one are left out: their slope would read its blank slot.
@@ -12573,8 +13687,7 @@ vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int fi
12573
13687
  reliefValue( textureGrad( uReliefMap, st + east, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - east, gx, gy ) ),
12574
13688
  reliefValue( textureGrad( uReliefMap, st + south, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - south, gx, gy ) )
12575
13689
  ) * 0.5 * grid.xy * uReliefFilter.y;
12576
- bool onMap = window.x < 0.5;
12577
- vec3 uphill = rise.x * ( onMap ? rates.mapX : rates.capX ) + rise.y * ( onMap ? rates.mapY : rates.capY );
13690
+ vec3 uphill = ( window.x < 0.5 ? mapRates : capRates ) * rise;
12578
13691
  slope += ( 1.0 - covered ) * alpha * uphill;
12579
13692
  covered += ( 1.0 - covered ) * alpha;
12580
13693
  if ( covered > 0.995 ) break;
@@ -12586,25 +13699,14 @@ function shadowGlsl(steps) {
12586
13699
  return `
12587
13700
  // One window as a shadow ray reads it. The ray is a line on the window's
12588
13701
  // chart, so where it is inside the window is worked out once, not at each
12589
- // sample.
12590
- struct ReliefSight {
12591
- // The fragment in the window's texture, with its layer, and how far the
12592
- // ray goes there in a radian.
12593
- vec3 from;
12594
- vec2 rate;
12595
- // The part of the ray inside the window, in radians along it.
12596
- float enters;
12597
- float leaves;
12598
- // The longest step that the window's pixels still hold, and the step from
12599
- // which the ray reads the next window.
12600
- float widest;
12601
- float hands;
12602
- // How far from the fragment the next window can be read at all.
12603
- float beyond;
12604
- int loaded;
12605
- };
12606
-
12607
- ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep ) {
13702
+ // sample. 'sight' holds what a sample reads, as a mat2x4 and not a struct
13703
+ // (see the chain code): the fragment's place in the window's texture and how
13704
+ // far the ray goes there in a radian; then the part of the ray inside the
13705
+ // window, in radians along it, the window's layer and its tiles that are in,
13706
+ // a bit each. 'widest' is the longest step that the window's pixels still
13707
+ // hold, 'hands' the step from which the ray reads the next window, and
13708
+ // 'beyond' how far from the fragment the next window can be read at all.
13709
+ void reliefSight( int i, mat3x2 map, mat3x2 cap, float side, vec2 mapStep, vec2 capStep, out mat2x4 sight, out float widest, out float hands, out float beyond ) {
12608
13710
  vec4 window = uReliefWindow[ i ];
12609
13711
  vec4 grid = uReliefGrid[ i ];
12610
13712
  bool onMap = window.x < 0.5;
@@ -12612,7 +13714,7 @@ ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep )
12612
13714
  // In tiles from the window's north-west corner. The map is counted from
12613
13715
  // the point under the camera, so the fragment is on the side of the globe
12614
13716
  // nearest the window.
12615
- vec2 rel = onMap ? uReliefHome[ i ] + charts.mapAt * grid.xy : charts.capAt * grid.xy - grid.zw;
13717
+ vec2 rel = onMap ? uReliefHome[ i ] + map[ 0 ] * grid.xy : cap[ 0 ] * grid.xy - grid.zw;
12616
13718
  vec2 rate = ( onMap ? mapStep : capStep ) * grid.xy;
12617
13719
 
12618
13720
  // Half a pixel in from the window's sides: past it the filter reads the
@@ -12624,51 +13726,50 @@ ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep )
12624
13726
  vec2 near = min( a, b );
12625
13727
  vec2 far = max( a, b );
12626
13728
 
12627
- ReliefSight sight;
12628
13729
  // The texture repeats every window. The tile index of a patch alone takes
12629
13730
  // all the digits of a float: only its place in the window is added.
12630
13731
  vec2 slot = rel + mod( grid.zw, rows );
12631
- sight.from = vec3( slot.x / rows, - slot.y / rows, window.y );
12632
- sight.rate = vec2( rate.x, - rate.y ) / rows;
12633
- sight.enters = max( near.x, near.y );
12634
- sight.leaves = min( far.x, far.y );
13732
+ float leaves = min( far.x, far.y );
12635
13733
  // The other pole's cap holds none of the ray.
12636
- if ( ! onMap && ( window.x < 1.5 ) != ( charts.side > 0.0 ) ) sight.leaves = - 1.0;
12637
- sight.widest = ${STEP_PIXELS.toFixed(2)} * PI / ( uReliefFilter.y * exp2( window.z ) );
13734
+ if ( ! onMap && ( window.x < 1.5 ) != ( side > 0.0 ) ) leaves = - 1.0;
13735
+ sight = mat2x4(
13736
+ slot.x / rows, - slot.y / rows, rate.x / rows, - rate.y / rows,
13737
+ max( near.x, near.y ), leaves, window.y, float( uReliefLoaded[ i ] )
13738
+ );
13739
+ widest = ${STEP_PIXELS.toFixed(2)} * PI / ( uReliefFilter.y * exp2( window.z ) );
12638
13740
  // Near a patch the next window can be some levels coarser. The ray then
12639
13741
  // stays here until its step is half of what that window holds: too wide
12640
13742
  // for these pixels, but they are the same ground as the fragment's. The
12641
13743
  // pyramid around a patch is not: it has none of the hills that only the
12642
13744
  // patch resolves, and close to the fragment it would stand in the ray.
12643
- sight.hands = sight.widest;
12644
- sight.beyond = 0.0;
13745
+ hands = widest;
13746
+ beyond = 0.0;
12645
13747
  if ( i + 1 < ${7} ) {
12646
13748
  float next = PI / ( uReliefFilter.y * exp2( uReliefWindow[ i + 1 ].z ) );
12647
- sight.hands = max( sight.hands, 0.5 * ${STEP_PIXELS.toFixed(2)} * next );
13749
+ hands = max( hands, 0.5 * ${STEP_PIXELS.toFixed(2)} * next );
12648
13750
  // Within half a pixel of a window, its height is that of the fragment's
12649
13751
  // own pixel, and the fragment stands above or below that by all the
12650
13752
  // relief that the pixel averages away.
12651
- sight.beyond = 0.5 * next;
13753
+ beyond = 0.5 * next;
12652
13754
  }
12653
- sight.loaded = uReliefLoaded[ i ];
12654
- return sight;
12655
13755
  }
12656
13756
 
12657
13757
  // Height of the ground 't' radians along the ray, as a part of the pyramid's
12658
13758
  // range. Negative where the window does not hold that ground.
12659
- float reliefSightValue( ReliefSight sight, float t ) {
12660
- if ( t < sight.enters || t > sight.leaves ) return - 1.0;
12661
- vec2 st = sight.from.xy + t * sight.rate;
13759
+ float reliefSightValue( mat2x4 sight, float t ) {
13760
+ if ( t < sight[ 1 ].x || t > sight[ 1 ].y ) return - 1.0;
13761
+ vec2 st = sight[ 0 ].xy + t * sight[ 0 ].zw;
12662
13762
  // Both tiles that the filter can read must be in.
12663
- if ( sight.loaded != ${(1 << 4 * 4) - 1} ) {
13763
+ if ( sight[ 1 ].w != ${((1 << 4 * 4) - 1).toFixed(1)} ) {
13764
+ int loaded = int( sight[ 1 ].w );
12664
13765
  vec2 tile = vec2( st.x, - st.y ) * ${4 .toFixed(1)};
12665
13766
  float inset = 0.5 / uReliefFilter.y;
12666
13767
  ivec2 a = ivec2( floor( tile - inset ) ) & ${4 - 1};
12667
13768
  ivec2 b = ivec2( floor( tile + inset ) ) & ${4 - 1};
12668
- int both = ( sight.loaded >> ( a.x + ${4} * a.y ) ) & ( sight.loaded >> ( b.x + ${4} * b.y ) );
13769
+ int both = ( loaded >> ( a.x + ${4} * a.y ) ) & ( loaded >> ( b.x + ${4} * b.y ) );
12669
13770
  if ( ( both & 1 ) == 0 ) return - 1.0;
12670
13771
  }
12671
- return reliefValue( textureLod( uReliefMap, vec3( st, sight.from.z ), 0.0 ) );
13772
+ return reliefValue( textureLod( uReliefMap, vec3( st, sight[ 1 ].z ), 0.0 ) );
12672
13773
  }
12673
13774
 
12674
13775
  // Part of the Sun's disc that the ground leaves in sight of the fragment at
@@ -12681,23 +13782,28 @@ float reliefSightValue( ReliefSight sight, float t ) {
12681
13782
  // read from the window whose pixels are about as wide as the step. A shadow
12682
13783
  // is then sharp beside what casts it, at a cost that does not grow with its
12683
13784
  // length.
12684
- float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev, float sunSize, int first ) {
12685
- ReliefRates rates = reliefRates( dir, charts.side );
12686
- vec2 mapStep = vec2( dot( rates.mapX, toSun ), dot( rates.mapY, toSun ) );
12687
- vec2 capStep = vec2( dot( rates.capX, toSun ), dot( rates.capY, toSun ) );
13785
+ float reliefTileShadow( vec3 dir, mat3x2 map, mat3x2 cap, float side, vec3 toSun, float tanElev, float sunSize, int first ) {
13786
+ mat2x3 mapRates;
13787
+ mat2x3 capRates;
13788
+ reliefRates( dir, side, mapRates, capRates );
13789
+ vec2 mapStep = toSun * mapRates;
13790
+ vec2 capStep = toSun * capRates;
12688
13791
 
12689
13792
  int drawn = 0;
12690
13793
  for ( int i = 0; i < ${7}; i ++ ) {
12691
13794
  if ( uReliefWindow[ i ].w > 0.5 ) drawn = i + 1;
12692
13795
  }
12693
13796
  int w = first;
12694
- ReliefSight sight;
13797
+ mat2x4 sight;
13798
+ float widest;
13799
+ float hands;
13800
+ float beyond;
12695
13801
  float own = - 1.0;
12696
13802
  for ( int i = 0; i < ${7}; i ++ ) {
12697
13803
  if ( i < first ) continue;
12698
13804
  if ( i >= drawn ) break;
12699
13805
  w = i;
12700
- sight = reliefSight( i, charts, mapStep, capStep );
13806
+ reliefSight( i, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12701
13807
  own = reliefSightValue( sight, 0.0 );
12702
13808
  if ( own >= 0.0 ) break;
12703
13809
  }
@@ -12714,7 +13820,7 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12714
13820
  // The ratio that takes steps from one pixel to the reach: 'rate' is its
12715
13821
  // logarithm times the count, and solves rate / ( exp( rate ) - 1 ) = part.
12716
13822
  float count = ${steps.toFixed(1)};
12717
- float pixel = sight.widest / ${STEP_PIXELS.toFixed(2)};
13823
+ float pixel = widest / ${STEP_PIXELS.toFixed(2)};
12718
13824
  float part = count * pixel / reach;
12719
13825
  float rate = 0.0;
12720
13826
  if ( part < 1.0 ) {
@@ -12738,16 +13844,16 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12738
13844
  for ( int i = 1; i <= ${steps}; i ++ ) {
12739
13845
  float before = t;
12740
13846
  t = t * ratio + head;
12741
- if ( w + 1 < drawn && t - before > sight.hands ) {
13847
+ if ( w + 1 < drawn && t - before > hands ) {
12742
13848
  w ++;
12743
- sight = reliefSight( w, charts, mapStep, capStep );
13849
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12744
13850
  }
12745
13851
  float value = reliefSightValue( sight, t );
12746
13852
  // The ray left this window, or a tile of it is missing.
12747
13853
  if ( value < 0.0 && w + 1 < drawn ) {
12748
- if ( t < sight.beyond ) continue;
13854
+ if ( t < beyond ) continue;
12749
13855
  w ++;
12750
- sight = reliefSight( w, charts, mapStep, capStep );
13856
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12751
13857
  value = reliefSightValue( sight, t );
12752
13858
  }
12753
13859
  if ( value < 0.0 ) continue;
@@ -12769,19 +13875,19 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12769
13875
  float crest = ${(STEEPEST * .5).toFixed(2)} * around / max( worst, 1e-9 ) * perDisc;
12770
13876
  if ( hidden < 1.0 && hidden + crest > - 1.0 ) {
12771
13877
  w = worstWindow;
12772
- sight = reliefSight( w, charts, mapStep, capStep );
13878
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12773
13879
  for ( int k = 0; k < ${REFINE_STEPS}; k ++ ) {
12774
13880
  around *= 0.5;
12775
- if ( w > start && around < sight.widest ) {
13881
+ if ( w > start && around < widest ) {
12776
13882
  w --;
12777
- sight = reliefSight( w, charts, mapStep, capStep );
13883
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12778
13884
  }
12779
13885
  float centre = worst;
12780
13886
  for ( int e = 0; e < 2; e ++ ) {
12781
13887
  float at = centre + ( e == 0 ? - around : around );
12782
13888
  // Nearer than a pixel, the window holds no crest: only the slope
12783
13889
  // of the fragment's own ground, which is the normal's to light.
12784
- if ( at < sight.widest * ${(1 / STEP_PIXELS).toFixed(4)} ) continue;
13890
+ if ( at < widest * ${(1 / STEP_PIXELS).toFixed(4)} ) continue;
12785
13891
  float value = reliefSightValue( sight, at );
12786
13892
  if ( value < 0.0 ) continue;
12787
13893
  float stands = ( ( value - own ) * uReliefScale / at - tanElev - 0.5 * at ) * perDisc;
@@ -12895,7 +14001,7 @@ var TILE_LIGHT = `
12895
14001
  float up = dot(sunward, selfDir);
12896
14002
  vec3 along = sunward - selfDir * up;
12897
14003
  float run = length(along);
12898
- if (run > 1e-4) tileLight = reliefTileShadow(selfDir, selfCharts, along / run, up / run, uSelfSunSize, selfFirst);
14004
+ if (run > 1e-4) tileLight = reliefTileShadow(selfDir, selfMap, selfCap, selfSide, along / run, up / run, uSelfSunSize, selfFirst);
12899
14005
  if (tileLight < 0.0) byTiles = 0.0;
12900
14006
  }`;
12901
14007
  function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir, tiles) {
@@ -13029,10 +14135,10 @@ function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir, tiles)
13029
14135
  vec3 selfDir = normalize(vSelfDir);
13030
14136
  // The part of the fragment that the tiles answer for.
13031
14137
  float selfCovered = 0.0;
13032
- ${tiles ? "TileCharts selfCharts = tileChartsFrom(uReliefAnchor, vReliefOff);\n int selfFirst = -1;" : ""}
14138
+ ${tiles ? "mat3x2 selfMap;\n mat3x2 selfCap;\n float selfSide;\n tileChartsFrom(uReliefAnchor, vReliefOff, selfMap, selfCap, selfSide);\n int selfFirst = -1;" : ""}
13033
14139
  {
13034
14140
  vec3 slope = vec3(0.0);
13035
- ${tiles ? "slope = reliefTileSlope(selfDir, selfCharts, selfCovered, selfFirst);" : ""}
14141
+ ${tiles ? "slope = reliefTileSlope(selfDir, selfMap, selfCap, selfSide, selfCovered, selfFirst);" : ""}
13036
14142
  if (selfCovered < 0.995 && uSelfScale > 0.0) slope += (1.0 - selfCovered) * selfMapSlope(selfDir);
13037
14143
  float steep = length(slope);
13038
14144
  if (steep > ${SLOPE_CLAMP.toFixed(3)}) slope *= ${SLOPE_CLAMP.toFixed(3)} / steep;
@@ -13125,34 +14231,23 @@ async function readHeightRows(bitmap, y0, y1) {
13125
14231
  }
13126
14232
  return out;
13127
14233
  }
14234
+ var PYRAMID_TIER = "pyramid";
14235
+ var pyramidFailed = /* @__PURE__ */ new WeakSet();
14236
+ function pyramidHeightsServe(bo, renderer) {
14237
+ const tiles = bo.displacementMeta?.tiles;
14238
+ return !!tiles && tiles.scale_km !== void 0 && tilesBase() !== "" && textureAllowed(tiles.distribution) && renderer.extensions.has("OES_texture_float_linear") && !pyramidFailed.has(bo);
14239
+ }
13128
14240
  async function swapDisplacementTier(bo, tier) {
13129
14241
  const meta = bo.displacementMeta;
13130
14242
  if (!meta || !bo.mesh || bo.displacementLoading || bo.displacementTier === tier) return;
13131
14243
  bo.displacementLoading = true;
13132
14244
  try {
13133
- const bitmap = await fetchHeightBitmap(displacementTierUrl(meta, tier));
13134
- if (!bitmap) return;
13135
- const w = bitmap.width;
13136
- const h = bitmap.height;
13137
- const rows = await readHeightRows(bitmap, 0, h - 1);
13138
- bitmap.close();
13139
- if (!rows) return;
13140
- if (!bo.mesh || !bo.displacementMap) return;
13141
- const tmp = new Uint8Array(w);
13142
- for (let y = 0; y < h >> 1; y++) {
13143
- const top = rows.subarray(y * w, (y + 1) * w);
13144
- const bottom = rows.subarray((h - 1 - y) * w, (h - y) * w);
13145
- tmp.set(top);
13146
- top.set(bottom);
13147
- bottom.set(tmp);
13148
- }
13149
- const tex = new DataTexture(rows, w, h, RedFormat, UnsignedByteType);
13150
- tex.colorSpace = NoColorSpace;
13151
- tex.magFilter = LinearFilter;
13152
- tex.minFilter = LinearFilter;
13153
- tex.generateMipmaps = false;
13154
- tex.unpackAlignment = 1;
13155
- tex.needsUpdate = true;
14245
+ const tex = tier === "pyramid" ? await pyramidMap(bo, meta) : await tierMap(meta, tier);
14246
+ if (!tex) return;
14247
+ if (!bo.mesh || !bo.displacementMap) {
14248
+ tex.dispose();
14249
+ return;
14250
+ }
13156
14251
  const material = bo.mesh.material;
13157
14252
  const old = material.displacementMap;
13158
14253
  material.displacementMap = tex;
@@ -13164,6 +14259,96 @@ async function swapDisplacementTier(bo, tier) {
13164
14259
  bo.displacementLoading = false;
13165
14260
  }
13166
14261
  }
14262
+ async function tierMap(meta, tier) {
14263
+ const bitmap = await fetchHeightBitmap(displacementTierUrl(meta, tier));
14264
+ if (!bitmap) return null;
14265
+ const w = bitmap.width;
14266
+ const h = bitmap.height;
14267
+ const rows = await readHeightRows(bitmap, 0, h - 1);
14268
+ bitmap.close();
14269
+ if (!rows) return null;
14270
+ const tmp = new Uint8Array(w);
14271
+ for (let y = 0; y < h >> 1; y++) {
14272
+ const top = rows.subarray(y * w, (y + 1) * w);
14273
+ const bottom = rows.subarray((h - 1 - y) * w, (h - y) * w);
14274
+ tmp.set(top);
14275
+ top.set(bottom);
14276
+ bottom.set(tmp);
14277
+ }
14278
+ const tex = new DataTexture(rows, w, h, RedFormat, UnsignedByteType);
14279
+ tex.colorSpace = NoColorSpace;
14280
+ tex.magFilter = LinearFilter;
14281
+ tex.minFilter = LinearFilter;
14282
+ tex.generateMipmaps = false;
14283
+ tex.unpackAlignment = 1;
14284
+ tex.needsUpdate = true;
14285
+ return tex;
14286
+ }
14287
+ async function pyramidMap(bo, meta) {
14288
+ if (!meta.tiles) return null;
14289
+ try {
14290
+ return await pyramidHeights(meta, meta.tiles);
14291
+ } catch (err) {
14292
+ console.warn(`Failed to load the height pyramid of ${meta.id}:`, err);
14293
+ pyramidFailed.add(bo);
14294
+ return null;
14295
+ }
14296
+ }
14297
+ async function pyramidHeights(meta, tiles) {
14298
+ const signal = new AbortController().signal;
14299
+ const halves = await Promise.all([0, 1].map((x) => loadHeightTile(tileUrl(tiles, "", 0, x, 0), true, signal)));
14300
+ const size = halves[0].size;
14301
+ const width = 2 * size;
14302
+ const data = new Float32Array(width * size);
14303
+ const gain = (tiles.scale_km ?? 0) / 65535 / meta.scale_km;
14304
+ const offset = ((tiles.bias_km ?? 0) - meta.bias_km) / meta.scale_km;
14305
+ halves.forEach(({ heights }, x) => {
14306
+ for (let row = 0; row < size; row++) {
14307
+ const src = row * size;
14308
+ const dst = row * width + x * size;
14309
+ for (let i = 0; i < size; i++) data[dst + i] = heights[src + i] * gain + offset;
14310
+ }
14311
+ });
14312
+ const tex = new DataTexture(data, width, size, RedFormat, FloatType);
14313
+ tex.colorSpace = NoColorSpace;
14314
+ tex.magFilter = LinearFilter;
14315
+ tex.minFilter = LinearMipmapLinearFilter;
14316
+ tex.generateMipmaps = false;
14317
+ tex.mipmaps = mipChain(data, width, size);
14318
+ tex.needsUpdate = true;
14319
+ return tex;
14320
+ }
14321
+ function mipChain(data, width, height) {
14322
+ const chain = [{
14323
+ data,
14324
+ width,
14325
+ height
14326
+ }];
14327
+ while (width > 1 || height > 1) {
14328
+ const w = Math.max(1, width >> 1);
14329
+ const h = Math.max(1, height >> 1);
14330
+ const sx = width / w;
14331
+ const sy = height / h;
14332
+ const out = new Float32Array(w * h);
14333
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
14334
+ let sum = 0;
14335
+ for (let dy = 0; dy < sy; dy++) {
14336
+ const row = (y * sy + dy) * width + x * sx;
14337
+ for (let dx = 0; dx < sx; dx++) sum += data[row + dx];
14338
+ }
14339
+ out[y * w + x] = sum / (sx * sy);
14340
+ }
14341
+ chain.push({
14342
+ data: out,
14343
+ width: w,
14344
+ height: h
14345
+ });
14346
+ data = out;
14347
+ width = w;
14348
+ height = h;
14349
+ }
14350
+ return chain;
14351
+ }
13167
14352
  function bilinearHeightTexel(data, w, h, fx, fy) {
13168
14353
  const x0 = Math.floor(fx);
13169
14354
  const y0 = Math.floor(fy);
@@ -13177,7 +14362,8 @@ function bilinearHeightTexel(data, w, h, fx, fy) {
13177
14362
  const r1 = clampRow(y0 + 1) * w;
13178
14363
  const top = data[r0 + c0] * (1 - tx) + data[r0 + c1] * tx;
13179
14364
  const bot = data[r1 + c0] * (1 - tx) + data[r1 + c1] * tx;
13180
- return (top * (1 - ty) + bot * ty) / 255;
14365
+ const texel = top * (1 - ty) + bot * ty;
14366
+ return data instanceof Float32Array ? texel : texel / 255;
13181
14367
  }
13182
14368
  function disposeDisplacementFromMaterial(material) {
13183
14369
  if (!material.displacementMap) return;
@@ -13367,8 +14553,9 @@ function loadLowField(bodyId, meta) {
13367
14553
  function heightFieldFor(bo, bodyId) {
13368
14554
  const image = bo.displacementMap?.image;
13369
14555
  if (!image) return null;
13370
- if (image.data instanceof Uint8Array && image.width && image.height) return {
13371
- data: image.data,
14556
+ const data = image.data;
14557
+ if ((data instanceof Uint8Array || data instanceof Float32Array) && image.width && image.height) return {
14558
+ data,
13372
14559
  width: image.width,
13373
14560
  height: image.height,
13374
14561
  bottomUp: true
@@ -13864,20 +15051,24 @@ var AttitudeTrack = class {
13864
15051
  this.files = manifest.files;
13865
15052
  }
13866
15053
  covers(jd) {
13867
- return jd >= this.startJd && jd <= this.endJd && this.files.length > 0;
15054
+ return this.coversTdb(utcToTdb(jd));
15055
+ }
15056
+ coversTdb(tdb) {
15057
+ return tdb >= this.startJd && tdb <= this.endJd && this.files.length > 0;
13868
15058
  }
13869
15059
  orientationAt(jd, out) {
13870
- if (!this.covers(jd)) return false;
13871
- const ci = this.fileIndexFor(jd);
15060
+ const tdb = utcToTdb(jd);
15061
+ if (!this.coversTdb(tdb)) return false;
15062
+ const ci = this.fileIndexFor(tdb);
13872
15063
  const chunk = this.ensure(ci);
13873
15064
  if (!chunk) return false;
13874
15065
  this.ensure(ci - 1);
13875
15066
  this.ensure(ci + 1);
13876
- this.sampleChunk(jd, ci, chunk);
15067
+ this.sampleChunk(tdb, ci, chunk);
13877
15068
  const baseline = this.baselines?.[this.files[ci].baseline_index ?? 0];
13878
15069
  if (baseline) {
13879
15070
  const { axis, rate_rad_s, anchor, anchor_jd } = baseline;
13880
- const tSeconds = (jd - anchor_jd) * SECONDS_PER_DAY;
15071
+ const tSeconds = (tdb - anchor_jd) * SECONDS_PER_DAY;
13881
15072
  _baselineAxis.set(axis[0], axis[1], axis[2]);
13882
15073
  _qBaseline.setFromAxisAngle(_baselineAxis, rate_rad_s * tSeconds);
13883
15074
  _qb.set(anchor[1], anchor[2], anchor[3], anchor[0]);
@@ -14152,29 +15343,6 @@ function setSurfaceMap(material, map, tintHex) {
14152
15343
  material.needsUpdate = true;
14153
15344
  }
14154
15345
 
14155
- //#endregion
14156
- //#region src/lib/scene/objects/body/shape-model-policy.ts
14157
- var PREFER_MODEL_OVER_DEM = /* @__PURE__ */ new Set([]);
14158
- function shapeModelSkipReason(global) {
14159
- if (!global?.model_name) return "no-bundle";
14160
- if (isLowEndDevice() && global.render_quality !== "high") return "low-end-device";
14161
- if (global.displacement && !PREFER_MODEL_OVER_DEM.has(global.id)) return "dem-preferred";
14162
- return null;
14163
- }
14164
-
14165
- //#endregion
14166
- //#region src/lib/scene/objects/body/orientation-apply.ts
14167
- function applyBodyOrientation(body, orientation, ctx, systemId) {
14168
- body.orientation = orientation;
14169
- if (ctx) ctx.bodies.dataVersion++;
14170
- ctx?.credits.registerOrientation({
14171
- bodyId: body.data.id,
14172
- systemId: systemId ?? body.data.id,
14173
- source: orientation.source,
14174
- reference: orientation.reference
14175
- });
14176
- }
14177
-
14178
15346
  //#endregion
14179
15347
  //#region src/lib/scene/objects/surface/photometry.ts
14180
15348
  var WEIGHT_POLY = [
@@ -14184,16 +15352,24 @@ var WEIGHT_POLY = [
14184
15352
  ];
14185
15353
  var GRAZE_FLOOR = .05;
14186
15354
  var LUNAR_LAMBERT_GLSL = `
14187
- float lunarLambertWeight(float phaseDeg) {
14188
- return clamp(1.0 + phaseDeg * (${WEIGHT_POLY[0].toFixed(8)} + phaseDeg * (${WEIGHT_POLY[1].toFixed(8)} + phaseDeg * ${WEIGHT_POLY[2].toFixed(8)})), 0.0, 1.0);
15355
+ float lunarLambertWeight(float phaseDeg, float scale) {
15356
+ return min(scale * clamp(1.0 + phaseDeg * (${WEIGHT_POLY[0].toFixed(8)} + phaseDeg * (${WEIGHT_POLY[1].toFixed(8)} + phaseDeg * ${WEIGHT_POLY[2].toFixed(8)})), 0.0, 1.0), 1.0);
14189
15357
  }
14190
15358
 
14191
- float lunarLambertGain(float phaseDeg, float mu0, float mu) {
14192
- float weight = lunarLambertWeight(phaseDeg);
15359
+ float lunarLambertGain(float phaseDeg, float mu0, float mu, float scale) {
15360
+ float weight = lunarLambertWeight(phaseDeg, scale);
14193
15361
  float sun = max(mu0, 0.0);
14194
15362
  return 2.0 * weight / max(sun + max(mu, sun), ${GRAZE_FLOOR.toFixed(3)}) + 1.0 - weight;
14195
15363
  }
14196
15364
  `;
15365
+ function makePhotometryUniforms(scale) {
15366
+ return { uLunarWeightScale: { value: scale ?? 1 } };
15367
+ }
15368
+ function lunarLambertWeight(phaseDeg, scale) {
15369
+ const [a, b, c] = WEIGHT_POLY;
15370
+ const moon = Math.min(1, Math.max(0, 1 + phaseDeg * (a + phaseDeg * (b + phaseDeg * c))));
15371
+ return Math.min(1, scale * moon);
15372
+ }
14197
15373
  var DRY_GROUND = `
14198
15374
  #ifdef USE_ROUGHNESSMAP
14199
15375
  reflectedLight.directSpecular *= texelRoughness.g;
@@ -14212,18 +15388,20 @@ var LUNAR_LAMBERT = `
14212
15388
  reflectedLight.directDiffuse *= lunarLambertGain(
14213
15389
  phaseDeg,
14214
15390
  dot(geometryNormal, sunView),
14215
- dot(geometryNormal, geometryViewDir)
15391
+ dot(geometryNormal, geometryViewDir),
15392
+ uLunarWeightScale
14216
15393
  );
14217
15394
  }
14218
15395
  `;
14219
- function attachSurfacePhotometry(material, bodyId, sunDir) {
15396
+ function attachSurfacePhotometry(material, bodyId, uniforms, sunDir) {
14220
15397
  const airless = !getAtmosphereParams(bodyId);
14221
15398
  chainShaderHook(material, airless ? "photometryAirless" : "photometry", (shader) => {
14222
15399
  let pars = "";
14223
15400
  let light = DRY_GROUND;
14224
15401
  if (airless) {
14225
15402
  shader.uniforms.uPhotometrySunDir = sunDir ?? getEclipseSceneUniforms().uSunDir;
14226
- pars = `uniform vec3 uPhotometrySunDir;\n${LUNAR_LAMBERT_GLSL}`;
15403
+ Object.assign(shader.uniforms, uniforms);
15404
+ pars = `uniform vec3 uPhotometrySunDir;\nuniform float uLunarWeightScale;\n${LUNAR_LAMBERT_GLSL}`;
14227
15405
  light += LUNAR_LAMBERT;
14228
15406
  }
14229
15407
  shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${pars}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>\n${light}`);
@@ -14231,92 +15409,60 @@ function attachSurfacePhotometry(material, bodyId, sunDir) {
14231
15409
  }
14232
15410
 
14233
15411
  //#endregion
14234
- //#region src/lib/scene/objects/surface/planetshine.ts
14235
- var MAX_SHINERS = 2;
14236
- var SHINE_FLOOR = 1e-4;
14237
- function lambertSpherePhase(a) {
14238
- return (Math.sin(a) + (Math.PI - a) * Math.cos(a)) / Math.PI;
14239
- }
14240
- function planetshineFraction(radius, distance, a) {
14241
- const k = radius / distance;
14242
- return 2 / 3 * lambertSpherePhase(a) * k * k;
15412
+ //#region src/lib/scene/objects/body/surface-albedo.ts
15413
+ var UNKNOWN_SURFACE_COLOR = "#6b6b6b";
15414
+ function luminance$1(c) {
15415
+ return .2126 * c.r + .7152 * c.g + .0722 * c.b;
15416
+ }
15417
+ function discAlbedoPerColour(airless, lunarWeightScale) {
15418
+ if (!airless) return 2 / 3;
15419
+ return (2 + lunarLambertWeight(0, lunarWeightScale ?? 1)) / 3;
15420
+ }
15421
+ var _hex = new Color();
15422
+ function surfaceAlbedoTarget(body) {
15423
+ if (!body.color) return null;
15424
+ return (body.geometricAlbedo ?? luminance$1(_hex.set(body.color))) / discAlbedoPerColour(!getAtmosphereParams(body.id), body.lunarWeightScale);
15425
+ }
15426
+ function untexturedSurfaceColour(body, out = new Color()) {
15427
+ const target = surfaceAlbedoTarget(body);
15428
+ if (target === null) return out.set(UNKNOWN_SURFACE_COLOR);
15429
+ out.set(body.color);
15430
+ return out.multiplyScalar(target / luminance$1(out));
15431
+ }
15432
+ function shinerAlbedo(body, map, out) {
15433
+ if (body.color) out.set(body.color);
15434
+ else if (body.geometricAlbedo === void 0) return null;
15435
+ else {
15436
+ const mean = map && meanMapColor(map);
15437
+ if (mean) out.copy(mean);
15438
+ else out.setRGB(1, 1, 1);
15439
+ }
15440
+ const albedo = body.geometricAlbedo ?? luminance$1(out);
15441
+ return out.multiplyScalar(1.5 * albedo / luminance$1(out));
14243
15442
  }
14244
- var PLANETSHINE_GLSL = `
14245
- #define PLANETSHINE_SHINERS ${2}
14246
- uniform vec4 uShineDir[PLANETSHINE_SHINERS];
14247
- uniform vec3 uShineLight[PLANETSHINE_SHINERS];
14248
15443
 
14249
- float sphereLightCosine(float cosTheta, float sinAlpha) {
14250
- float t = max(cosTheta + sinAlpha, 0.0);
14251
- return cosTheta >= sinAlpha ? cosTheta : t * t / (4.0 * sinAlpha);
14252
- }
14253
- `;
14254
- var PLANETSHINE_LIGHT = `
14255
- for (int i = 0; i < PLANETSHINE_SHINERS; i++) {
14256
- vec4 shiner = uShineDir[i];
14257
- // The slots fill in order, so the first empty slot ends the list.
14258
- if (shiner.w <= 0.0) break;
14259
- float cosine = sphereLightCosine(dot(geometryNormal, mat3(viewMatrix) * shiner.xyz), shiner.w);
14260
- reflectedLight.indirectDiffuse += uShineLight[i] * cosine * BRDF_Lambert(material.diffuseContribution);
14261
- }
14262
- `;
14263
- function makePlanetshineUniforms() {
14264
- return {
14265
- uShineDir: { value: Array.from({ length: 2 }, () => new Vector4(0, 0, 0, 0)) },
14266
- uShineLight: { value: Array.from({ length: 2 }, () => new Vector3()) }
14267
- };
15444
+ //#endregion
15445
+ //#region src/lib/scene/objects/body/shape-model-policy.ts
15446
+ var PREFER_MODEL_OVER_DEM = /* @__PURE__ */ new Set([]);
15447
+ function shapeModelSkipReason(global) {
15448
+ if (!global?.model_name) return "no-bundle";
15449
+ if (isLowEndDevice() && global.render_quality !== "high") return "low-end-device";
15450
+ if (global.displacement && !PREFER_MODEL_OVER_DEM.has(global.id)) return "dem-preferred";
15451
+ return null;
14268
15452
  }
14269
- function attachPlanetshine(material, uniforms) {
14270
- const self = uniforms ?? makePlanetshineUniforms();
14271
- chainShaderHook(material, "planetshine", (shader) => {
14272
- Object.assign(shader.uniforms, self);
14273
- shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${PLANETSHINE_GLSL}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>\n${PLANETSHINE_LIGHT}`);
15453
+
15454
+ //#endregion
15455
+ //#region src/lib/scene/objects/body/orientation-apply.ts
15456
+ function applyBodyOrientation(body, orientation, nutPrec, ctx, systemId) {
15457
+ body.orientation = orientation;
15458
+ body.nutPrec = nutPrecFor(body.data.id, nutPrec);
15459
+ if (ctx) ctx.bodies.dataVersion++;
15460
+ ctx?.credits.registerOrientation({
15461
+ bodyId: body.data.id,
15462
+ systemId: systemId ?? body.data.id,
15463
+ source: orientation.source,
15464
+ reference: orientation.reference
14274
15465
  });
14275
- return self;
14276
- }
14277
- var _strength = new Float64Array(2);
14278
- var _swapDir = new Vector4();
14279
- var _swapLight = new Vector3();
14280
- function selectShiners(out, x, y, z, shiners, count) {
14281
- const dirs = out.uShineDir.value;
14282
- const lights = out.uShineLight.value;
14283
- let kept = 0;
14284
- for (let i = 0; i < count; i++) {
14285
- const s = shiners[i];
14286
- const dx = s.x - x;
14287
- const dy = s.y - y;
14288
- const dz = s.z - z;
14289
- const d = Math.hypot(dx, dy, dz);
14290
- if (d <= s.radius) continue;
14291
- const k = s.radius / d;
14292
- if (2 / 3 * k * k < 1e-4) continue;
14293
- const cosPhase = -(dx * s.sunX + dy * s.sunY + dz * s.sunZ) / d;
14294
- const fraction = planetshineFraction(s.radius, d, Math.acos(Math.max(-1, Math.min(1, cosPhase))));
14295
- const strength = fraction * (.2126 * s.albedo.r + .7152 * s.albedo.g + .0722 * s.albedo.b);
14296
- if (strength < 1e-4) continue;
14297
- let slot = kept;
14298
- if (kept < 2) kept++;
14299
- else if (strength > _strength[2 - 1]) slot = 2 - 1;
14300
- else continue;
14301
- _strength[slot] = strength;
14302
- dirs[slot].set(dx / d, dy / d, dz / d, k);
14303
- lights[slot].set(s.albedo.r, s.albedo.g, s.albedo.b).multiplyScalar(s.sunlight * fraction);
14304
- for (; slot > 0 && _strength[slot - 1] < strength; slot--) {
14305
- _strength[slot] = _strength[slot - 1];
14306
- _strength[slot - 1] = strength;
14307
- _swapDir.copy(dirs[slot - 1]);
14308
- dirs[slot - 1].copy(dirs[slot]);
14309
- dirs[slot].copy(_swapDir);
14310
- _swapLight.copy(lights[slot - 1]);
14311
- lights[slot - 1].copy(lights[slot]);
14312
- lights[slot].copy(_swapLight);
14313
- }
14314
- }
14315
- for (let i = kept; i < 2; i++) {
14316
- dirs[i].set(0, 0, 0, 0);
14317
- lights[i].set(0, 0, 0);
14318
- }
14319
- return kept;
14320
15466
  }
14321
15467
 
14322
15468
  //#endregion
@@ -14647,7 +15793,11 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
14647
15793
  bo.body.modelBaseFrame = baseFrame ?? void 0;
14648
15794
  const root = withBaseFrame(gltf.scene, baseFrame);
14649
15795
  const anchor = meta.model_anchor ? new Vector3(...meta.model_anchor) : null;
14650
- fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
15796
+ if (!fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor)) {
15797
+ disposeGltf(gltf.scene);
15798
+ bo.noPhysical = "model";
15799
+ return;
15800
+ }
14651
15801
  enableShadows(root);
14652
15802
  modelScene.add(root);
14653
15803
  root.visible = !bo.isCrashed;
@@ -14683,7 +15833,7 @@ async function loadNaturalBodyModel(bo, ctx) {
14683
15833
  console.info(`Skipping ${detail.global?.render_quality ?? "unrated"} shape mesh for ${bo.body.data.id}: ${skip}`);
14684
15834
  return;
14685
15835
  }
14686
- if (detail.global?.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, ctx);
15836
+ if (detail.global?.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, detail.global.nut_prec, ctx);
14687
15837
  const metaPromise = fetchBundleMeta(slug);
14688
15838
  const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/high.glb`, "models"));
14689
15839
  if ((bo.modelLoadEpoch ?? 0) !== epoch) {
@@ -14703,14 +15853,18 @@ async function loadNaturalBodyModel(bo, ctx) {
14703
15853
  return;
14704
15854
  }
14705
15855
  const root = gltf.scene;
14706
- fitToUnitRadius(root);
15856
+ if (!fitToUnitRadius(root)) {
15857
+ disposeGltf(gltf.scene);
15858
+ return;
15859
+ }
14707
15860
  root.userData.radialIndex = buildRadialIndex(root);
14708
- const material = makeShapeModelMaterial(bo.body.data.color ?? bodyMeshColor(bo.body.data));
14709
- attachSurfacePhotometry(material, bo.body.data.id);
15861
+ const material = makeShapeModelMaterial(untexturedSurfaceColour(bo.body.data));
15862
+ bo.photometry ??= makePhotometryUniforms(bo.body.data.lunarWeightScale);
15863
+ attachSurfacePhotometry(material, bo.body.data.id, bo.photometry);
14710
15864
  bo.planetshine = attachPlanetshine(material, bo.planetshine ?? void 0);
14711
15865
  applyShapeModelMaterial(root, material);
14712
15866
  const sphereMap = bo.mesh ? bo.mesh.material.map : null;
14713
- if (sphereMap) setShapeModelMap(root, sphereMap, bodyMeshColor(bo.body.data), bo.body.data.color);
15867
+ if (sphereMap) setShapeModelMap(root, sphereMap, untexturedSurfaceColour(bo.body.data), bo.body.data.color);
14714
15868
  enableShadows(root);
14715
15869
  attachModelEclipse(bo, root);
14716
15870
  if (meta.true_scale) {
@@ -14796,7 +15950,7 @@ function fitToUnitRadius(root, anchor = null) {
14796
15950
  const size = bbox.getSize(new Vector3());
14797
15951
  const center = bbox.getCenter(new Vector3());
14798
15952
  const maxDim = Math.max(size.x, size.y, size.z);
14799
- if (maxDim <= 0) return;
15953
+ if (maxDim <= 0) return false;
14800
15954
  const k = 2 / maxDim;
14801
15955
  root.userData.boxCenter = center.clone().multiplyScalar(k);
14802
15956
  if (anchor) center.addScaledVector(anchor, maxDim / 2);
@@ -14811,6 +15965,7 @@ function fitToUnitRadius(root, anchor = null) {
14811
15965
  const offset = root.userData.centerOffset.length();
14812
15966
  root.userData.minRadius = Math.max(0, fit.minRadius - offset);
14813
15967
  root.userData.maxRadius = root.userData.halfExtents.length() + offset;
15968
+ return true;
14814
15969
  }
14815
15970
  var OCCLUDER_SLICES = 8;
14816
15971
  var OCCLUDER_SAMPLE_TARGET = 4096;
@@ -14963,7 +16118,7 @@ function nomenclatureBodyId(focused, bodyObjects) {
14963
16118
  if (bodyObjects.get(focused.data.id)?.isLanded) return focused.data.parentId;
14964
16119
  return focused.data.id;
14965
16120
  }
14966
- var DEG2RAD$5 = Math.PI / 180;
16121
+ var DEG2RAD$4 = Math.PI / 180;
14967
16122
  var MIN_BODY_SCREEN_RADIUS_PX = 128;
14968
16123
  var LABEL_DISC_FRACTION = .9;
14969
16124
  var MIN_FEATURE_PX = 24;
@@ -15016,8 +16171,8 @@ async function attachNomenclatureLabels(bo, canvas, onFeatureSelect) {
15016
16171
  let dispKm = null;
15017
16172
  if (detail.global.displacement) {
15018
16173
  dispKm = await displacementsKmAt(bo, bo.body.data.id, detail.global.displacement, effectiveRadiusKm(bo.body.data), renderable.map((f) => ({
15019
- latRad: f.lat * DEG2RAD$5,
15020
- lonRad: f.lon * DEG2RAD$5
16174
+ latRad: f.lat * DEG2RAD$4,
16175
+ lonRad: f.lon * DEG2RAD$4
15021
16176
  })));
15022
16177
  if (bo.nomenclatureLabels || !bo.mesh) return;
15023
16178
  }
@@ -15041,7 +16196,7 @@ async function attachNomenclatureLabels(bo, canvas, onFeatureSelect) {
15041
16196
  el.dataset.featureId = String(feature.featureId);
15042
16197
  attachCanvasForwarders(el, canvas);
15043
16198
  if (onFeatureSelect) onTapNotDrag(el, () => onFeatureSelect(feature.featureId, feature.lat, feature.lon, effDiam));
15044
- const [ux, uy, uz] = bodyFixedUnit(feature.lat * DEG2RAD$5, feature.lon * DEG2RAD$5);
16199
+ const [ux, uy, uz] = bodyFixedUnit(feature.lat * DEG2RAD$4, feature.lon * DEG2RAD$4);
15045
16200
  const rf = radial[i];
15046
16201
  const obj = new CSS2DObject(el);
15047
16202
  obj.position.set(rf * ux, rf * uy, rf * uz);
@@ -15066,8 +16221,8 @@ async function sampleModelSurface(model, features) {
15066
16221
  const he = model.userData.halfExtents;
15067
16222
  for (let i = 0; i < n; i++) {
15068
16223
  if (i > 0 && i % MODEL_CAST_CHUNK === 0) await new Promise((r) => setTimeout(r));
15069
- const latRad = features[i].lat * DEG2RAD$5;
15070
- const lonRad = features[i].lon * DEG2RAD$5;
16224
+ const latRad = features[i].lat * DEG2RAD$4;
16225
+ const lonRad = features[i].lon * DEG2RAD$4;
15071
16226
  const r = castModelRadius(model, latRad, lonRad, _castNormal);
15072
16227
  if (r !== null) radii[i] = r;
15073
16228
  else {
@@ -16240,7 +17395,10 @@ uniform vec2 uTileFilter;
16240
17395
  ${TILE_CHAIN_GLSL}
16241
17396
  vec3 tileColour( vec3 below ) {
16242
17397
  if ( uTileWindow[ 0 ].w < 0.5 ) return below;
16243
- TileCharts charts = tileChartsFrom( uTileAnchor, vTileOff );
17398
+ mat3x2 map;
17399
+ mat3x2 cap;
17400
+ float side;
17401
+ tileChartsFrom( uTileAnchor, vTileOff, map, cap, side );
16244
17402
 
16245
17403
  float rows = ${4 .toFixed(1)};
16246
17404
  vec3 colour = vec3( 0.0 );
@@ -16251,7 +17409,7 @@ vec3 tileColour( vec3 below ) {
16251
17409
  vec2 spot;
16252
17410
  vec2 major;
16253
17411
  vec2 minor;
16254
- float alpha = tileWindowWeight( charts, window, uTileGrid[ i ], uTileHome[ i ], uTileFilter, spot, major, minor );
17412
+ float alpha = tileWindowWeight( map, cap, side, window, uTileGrid[ i ], uTileHome[ i ], uTileFilter, spot, major, minor );
16255
17413
  if ( alpha <= 0.0 ) continue;
16256
17414
  ivec3 slot = ivec3( int( mod( spot.x, rows ) ), int( mod( spot.y, rows ) ), int( window.y ) );
16257
17415
  alpha *= texelFetch( uTileMask, slot, 0 ).r;
@@ -16394,8 +17552,9 @@ async function swapBodyTexture(bo, tier, frame, textureLoader) {
16394
17552
  return;
16395
17553
  }
16396
17554
  const material = bo.mesh.material;
16397
- setSurfaceMap(material, texture, bo.body.data.color);
16398
- if (bo.model) setShapeModelMap(bo.model, texture, bodyMeshColor(bo.body.data), bo.body.data.color);
17555
+ const data = bo.body.data;
17556
+ setSurfaceMap(material, texture, data.color);
17557
+ if (bo.model) setShapeModelMap(bo.model, texture, untexturedSurfaceColour(data), data.color);
16399
17558
  bo.textureTier = tier;
16400
17559
  bo.textureFrame = frame;
16401
17560
  } catch (err) {
@@ -16404,6 +17563,13 @@ async function swapBodyTexture(bo, tier, frame, textureLoader) {
16404
17563
  bo.textureLoading = false;
16405
17564
  }
16406
17565
  }
17566
+ function applySurfaceColour(bo) {
17567
+ if (!bo.mesh || !bo.photometry || isModelBearing(bo.body) || bo.appearance?.surfaceOwned) return;
17568
+ const material = bo.mesh.material;
17569
+ if (material.map) return;
17570
+ untexturedSurfaceColour(bo.body.data, material.color);
17571
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
17572
+ }
16407
17573
  function applyRadiiToMesh(bo, radii) {
16408
17574
  if (!bo.mesh) return;
16409
17575
  const { a, b, c } = radii;
@@ -16415,6 +17581,11 @@ function applyRadiiToMesh(bo, radii) {
16415
17581
  kmToScene(c),
16416
17582
  kmToScene(b)
16417
17583
  ];
17584
+ bo.body.drawnRadii = {
17585
+ a,
17586
+ b,
17587
+ c
17588
+ };
16418
17589
  if (bo.atmosphere) {
16419
17590
  const eqKm = Math.max(a, b);
16420
17591
  syncAtmosphereEllipsoid(bo.atmosphere, eqKm, c, kmToScene(eqKm));
@@ -16427,14 +17598,15 @@ async function loadBodyTexture(bo, textureLoader, currentJd, scene, maxTextureSi
16427
17598
  const detail = await fetchObjectDetail(bo.body.data.id, false);
16428
17599
  ctx?.bodies.orientationRead.add(bo.body.data.id);
16429
17600
  if (!detail.global) return;
16430
- if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, ctx);
17601
+ if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, detail.global.nut_prec, ctx);
16431
17602
  if (detail.global.radii && bo.radiusScene > 0 && !bo.radiiApplied && !detail.global.displacement?.absolute_radius) applyRadiiToMesh(bo, detail.global.radii);
16432
17603
  if (detail.global.rings?.length) await Promise.allSettled(attachRingBundles(bo.body.data.id, bo.body.data.id, detail.global.rings, detail.global.radii, bo, scene, maxTextureSize, ctx));
16433
- const surfaceColor = detail.global.sbdb?.color ?? detail.global.color;
16434
- if (surfaceColor) {
16435
- bo.body.data.color = surfaceColor;
16436
- if (bo.mesh && !bo.textureTier) bo.mesh.material.color.set(surfaceColor);
16437
- }
17604
+ const data = bo.body.data;
17605
+ data.color = detail.global.sbdb?.color ?? detail.global.color;
17606
+ data.geometricAlbedo = detail.global.sbdb?.geometric_albedo ?? detail.global.geometric_albedo;
17607
+ data.lunarWeightScale = detail.global.lunar_weight_scale;
17608
+ if (bo.photometry) bo.photometry.uLunarWeightScale.value = data.lunarWeightScale ?? 1;
17609
+ applySurfaceColour(bo);
16438
17610
  if (!detail.global.map_texture_available) return;
16439
17611
  const bodyId = bo.body.data.id;
16440
17612
  const surface = pickTexture([detail.global.texture && {
@@ -16476,9 +17648,9 @@ function unloadBodyTexture(bo) {
16476
17648
  if (!material.map) return;
16477
17649
  material.map.dispose();
16478
17650
  material.map = null;
16479
- material.color.set(bodyMeshColor(bo.body.data));
17651
+ untexturedSurfaceColour(bo.body.data, material.color);
16480
17652
  material.needsUpdate = true;
16481
- if (bo.model) setShapeModelMap(bo.model, null, bodyMeshColor(bo.body.data));
17653
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
16482
17654
  bo.textureTier = void 0;
16483
17655
  }
16484
17656
  async function loadBodyTextureTier(bo, tier, frame, textureLoader) {
@@ -16505,17 +17677,6 @@ async function downloadSystemMeta(barycenterId) {
16505
17677
  if (!resp.ok) throw new Error(`systems/${barycenterId}.json: ${resp.status}`);
16506
17678
  return resp.json();
16507
17679
  }
16508
- function adoptSystemSpin(body, orientation, nutPrec, ctx, barycenterId) {
16509
- applyBodyOrientation(body, orientation, ctx, barycenterId);
16510
- if (!nutPrec) return;
16511
- const naifMatch = body.data.id.match(/^naif-(-?\d+)$/);
16512
- const naifId = naifMatch ? parseInt(naifMatch[1], 10) : null;
16513
- const angles = naifId !== null ? getNutPrecAngles(ownerIdFor(naifId)) : void 0;
16514
- if (angles) body.nutPrec = {
16515
- ...nutPrec,
16516
- angles
16517
- };
16518
- }
16519
17680
  async function readSpin(body, ctx) {
16520
17681
  const id = body.data.id;
16521
17682
  const known = () => !!body.orientation || ctx.bodies.orientationRead.has(id);
@@ -16526,8 +17687,8 @@ async function readSpin(body, ctx) {
16526
17687
  const record = barycenterId ? (await fetchSystemMeta(barycenterId))?.[id] : void 0;
16527
17688
  const detail = record?.orientation ? null : await fetchObjectDetail(id, false);
16528
17689
  if (known()) return;
16529
- if (record?.orientation) adoptSystemSpin(body, record.orientation, record.nut_prec, ctx, barycenterId);
16530
- else if (detail?.global?.orientation) applyBodyOrientation(body, detail.global.orientation, ctx);
17690
+ if (record?.orientation) applyBodyOrientation(body, record.orientation, record.nut_prec, ctx, barycenterId);
17691
+ else if (detail?.global?.orientation) applyBodyOrientation(body, detail.global.orientation, detail.global.nut_prec, ctx);
16531
17692
  ctx.bodies.orientationRead.add(id);
16532
17693
  if (body.orientation) ctx.bodies.orientationVersion++;
16533
17694
  }
@@ -16550,8 +17711,14 @@ async function loadSystemData(barycenterId, bodyObjects, scene, textureLoader, c
16550
17711
  const bo = bodyObjects.get(bodyId);
16551
17712
  if (!bo?.mesh) continue;
16552
17713
  ctx?.bodies.orientationRead.add(bodyId);
17714
+ const data = bo.body.data;
17715
+ data.color ??= bodyMeta.color;
17716
+ data.geometricAlbedo ??= bodyMeta.geometric_albedo;
17717
+ data.lunarWeightScale ??= bodyMeta.lunar_weight_scale;
17718
+ if (bo.photometry) bo.photometry.uLunarWeightScale.value = data.lunarWeightScale ?? 1;
17719
+ applySurfaceColour(bo);
16553
17720
  if (bodyMeta.orientation) {
16554
- adoptSystemSpin(bo.body, bodyMeta.orientation, bodyMeta.nut_prec, ctx, barycenterId);
17721
+ applyBodyOrientation(bo.body, bodyMeta.orientation, bodyMeta.nut_prec, ctx, barycenterId);
16555
17722
  applyOrientation(bo.mesh, bodyMeta.orientation, currentJd, bo.body.nutPrec);
16556
17723
  }
16557
17724
  if (bodyMeta.radii && bo.radiusScene > 0 && !bo.radiiApplied && !bodyMeta.displacement?.absolute_radius) applyRadiiToMesh(bo, bodyMeta.radii);
@@ -16589,6 +17756,7 @@ function unloadSystemTextures(barycenterId, bodyObjects, scene, ctx) {
16589
17756
  }
16590
17757
  for (const ring of bo.rings) {
16591
17758
  ring.planetShadow?.disable();
17759
+ ring.ringshine?.disable();
16592
17760
  ring.mesh.removeFromParent();
16593
17761
  disposeRingNode(ring);
16594
17762
  }
@@ -16609,6 +17777,7 @@ var ZoneRefresher = class {
16609
17777
  latestDate;
16610
17778
  failureStreak = 0;
16611
17779
  staleNotified = false;
17780
+ staleCheckRunning = false;
16612
17781
  retries = new RetryBackoff(() => this.wakes++);
16613
17782
  wakes = 0;
16614
17783
  wakeTimer;
@@ -16664,10 +17833,17 @@ var ZoneRefresher = class {
16664
17833
  recordFailure(key) {
16665
17834
  this.retries.fail(key);
16666
17835
  this.failureStreak++;
16667
- if (this.failureStreak >= STALE_FAILURE_STREAK && !this.staleNotified) {
17836
+ if (this.failureStreak < STALE_FAILURE_STREAK || this.staleNotified || this.staleCheckRunning) return;
17837
+ this.staleCheckRunning = true;
17838
+ dataVersionChanged().then((changed) => {
17839
+ this.staleCheckRunning = false;
17840
+ if (!changed) {
17841
+ this.failureStreak = 0;
17842
+ return;
17843
+ }
16668
17844
  this.staleNotified = true;
16669
17845
  this.ctx.onDataStale?.();
16670
- }
17846
+ });
16671
17847
  }
16672
17848
  wakeIn(ms) {
16673
17849
  this.wakeTimer ??= setTimeout(() => {
@@ -17636,7 +18812,7 @@ var SimClock = class {
17636
18812
  //#region src/lib/math/spherical.ts
17637
18813
  var { PI, sin, cos, asin, atan2, sqrt } = Math;
17638
18814
  var DEG$9 = 180 / PI;
17639
- var RAD$5 = PI / 180;
18815
+ var RAD$4 = PI / 180;
17640
18816
  function applyQuat(v, q, inverse) {
17641
18817
  const [x, y, z] = v;
17642
18818
  const [qx, qy, qz, qw] = inverse ? [
@@ -17672,8 +18848,8 @@ function cartesianToSpherical(cam, target, bodyQuat) {
17672
18848
  };
17673
18849
  }
17674
18850
  function sphericalToCartesian(target, lat, lon, distance, bodyQuat) {
17675
- const latR = lat * RAD$5;
17676
- const lonR = lon * RAD$5;
18851
+ const latR = lat * RAD$4;
18852
+ const lonR = lon * RAD$4;
17677
18853
  let ox;
17678
18854
  let oy;
17679
18855
  let oz;
@@ -17706,7 +18882,7 @@ function offsetFacing(focus, toward, elevationDeg, distance) {
17706
18882
  hx = 0;
17707
18883
  hz = 1;
17708
18884
  }
17709
- const e = elevationDeg * RAD$5;
18885
+ const e = elevationDeg * RAD$4;
17710
18886
  const c = cos(e);
17711
18887
  return [
17712
18888
  distance * c * hx,
@@ -18041,26 +19217,6 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM, dash) {
18041
19217
  });
18042
19218
  }
18043
19219
 
18044
- //#endregion
18045
- //#region src/lib/scene/lighting.ts
18046
- var AMBIENT_INTENSITY = .01;
18047
- var SUN_LIGHT_INTENSITY = 3.5;
18048
- var ENV_BASE_INTENSITY = .04;
18049
- function sunIrradianceFactor(sunDistScene) {
18050
- const au = Math.max(sunDistScene / 10, .01);
18051
- return 1 / (au * au);
18052
- }
18053
- function applySunPointLightMode(light, realistic, scale = 1) {
18054
- light.decay = realistic ? 2 : 0;
18055
- light.intensity = (realistic ? SUN_LIGHT_INTENSITY * 10 * 10 : SUN_LIGHT_INTENSITY) * scale;
18056
- }
18057
- function makeEnvMap(renderer) {
18058
- const pmrem = new PMREMGenerator(renderer);
18059
- const tex = pmrem.fromScene(new RoomEnvironment()).texture;
18060
- pmrem.dispose();
18061
- return tex;
18062
- }
18063
-
18064
19220
  //#endregion
18065
19221
  //#region src/lib/scene/setup/bloom-high-pass.ts
18066
19222
  var MAX_SPAN = 16;
@@ -18106,6 +19262,26 @@ function installBloomHighPass(pass) {
18106
19262
  });
18107
19263
  }
18108
19264
 
19265
+ //#endregion
19266
+ //#region src/lib/scene/setup/output-dither.ts
19267
+ var DITHER_GLSL = `
19268
+ vec3 outputDither(vec3 color) {
19269
+ // ES 1.00 gives gl_FragCoord at medium precision. Copy it before the arithmetic.
19270
+ vec3 p3 = gl_FragCoord.xyx;
19271
+ p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
19272
+ p3 += dot(p3, p3.yxz + 33.33);
19273
+ vec3 u = fract((p3.xxy + p3.yzz) * p3.zyx);
19274
+ // The difference of two uniform samples has a triangular distribution on (-1, 1).
19275
+ vec3 noise = u - u.yzx;
19276
+ // The noise goes to 0 in the last step at each end. Thus black stays black and no value clips.
19277
+ vec3 amplitude = clamp(min(color, 1.0 - color) * 255.0, 0.0, 1.0);
19278
+ return color + noise * amplitude / 255.0;
19279
+ }
19280
+ `;
19281
+ function installOutputDither(pass) {
19282
+ pass.material.fragmentShader = pass.material.fragmentShader.replace("#include <colorspace_pars_fragment>", `#include <colorspace_pars_fragment>\n${DITHER_GLSL}`).replace(/}\s*$/, "gl_FragColor.rgb = outputDither(gl_FragColor.rgb);\n}");
19283
+ }
19284
+
18109
19285
  //#endregion
18110
19286
  //#region src/lib/scene/setup/three-boot.ts
18111
19287
  var CAMERA_FAR_DEFAULT = 1e5;
@@ -18178,6 +19354,7 @@ function bootThree(canvas, labelContainer, ctx) {
18178
19354
  installBloomHighPass(bloomPass);
18179
19355
  composer.addPass(bloomPass);
18180
19356
  const outputPass = new OutputPass();
19357
+ installOutputDither(outputPass);
18181
19358
  outputPass.needsSwap = false;
18182
19359
  composer.addPass(outputPass);
18183
19360
  return {
@@ -18186,7 +19363,7 @@ function bootThree(canvas, labelContainer, ctx) {
18186
19363
  scene,
18187
19364
  camera,
18188
19365
  composer,
18189
- maxSceneSamples: sampleCounts[0] ?? 0,
19366
+ maxSceneSamples: sampleCounts?.[0] ?? 0,
18190
19367
  bloomPass,
18191
19368
  ambientLight,
18192
19369
  shadowLight
@@ -18855,7 +20032,7 @@ gl_Position = projectionMatrix * mvPosition;
18855
20032
  `;
18856
20033
  var anchors = /* @__PURE__ */ new WeakMap();
18857
20034
  var _modelView = new Matrix4();
18858
- var _inverse = new Matrix4();
20035
+ var _inverse$1 = new Matrix4();
18859
20036
  var _eye = new Vector3();
18860
20037
  function patch(material, anchor) {
18861
20038
  chainShaderHook(material, "cameraRelative", (shader) => {
@@ -18873,7 +20050,7 @@ function attachCameraRelative(mesh, material) {
18873
20050
  patch(material, anchor);
18874
20051
  mesh.onBeforeRender = (_renderer, _scene, camera) => {
18875
20052
  _modelView.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
18876
- _eye.setFromMatrixPosition(_inverse.copy(_modelView).invert());
20053
+ _eye.setFromMatrixPosition(_inverse$1.copy(_modelView).invert());
18877
20054
  anchor.local.value.set(Math.fround(_eye.x), Math.fround(_eye.y), Math.fround(_eye.z));
18878
20055
  anchor.view.value.copy(anchor.local.value).applyMatrix4(_modelView);
18879
20056
  };
@@ -18911,19 +20088,21 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, re
18911
20088
  let starExtras = null;
18912
20089
  let eclipseShadow = null;
18913
20090
  let planetshine = null;
20091
+ let photometry = null;
18914
20092
  let atmosphere = null;
18915
20093
  let sunTint;
18916
20094
  if (!isVirtual && !noPhysical) {
18917
20095
  if (isStar) starExtras = buildStarExtras(root, radius, color);
18918
20096
  const segments = isStar ? STAR_SPHERE_SEGMENTS : BODY_SPHERE_SEGMENTS;
18919
20097
  const geometry = new SphereGeometry(radius, segments, segments);
18920
- const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: bodyMeshColor(body.data) });
20098
+ const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: untexturedSurfaceColour(body.data) });
18921
20099
  mesh = new Mesh(geometry, material);
18922
20100
  if (modelBearing) mesh.visible = false;
18923
20101
  if (!isStar) {
18924
20102
  eclipseShadow = attachEclipseShadowToBody(material);
18925
20103
  attachCameraRelative(mesh, material);
18926
- attachSurfacePhotometry(material, id);
20104
+ photometry = makePhotometryUniforms(body.data.lunarWeightScale);
20105
+ attachSurfacePhotometry(material, id, photometry);
18927
20106
  if (!modelBearing) planetshine = attachPlanetshine(material);
18928
20107
  }
18929
20108
  root.add(mesh);
@@ -18974,6 +20153,7 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, re
18974
20153
  selfShadow: null,
18975
20154
  eclipseShadow,
18976
20155
  planetshine,
20156
+ photometry,
18977
20157
  nomenclatureLabels: null,
18978
20158
  model: null
18979
20159
  });
@@ -18992,10 +20172,9 @@ function upgradeBodyMesh(bo, scene, clickables, meshToBody) {
18992
20172
  bo.noPhysical = "radius";
18993
20173
  return;
18994
20174
  }
18995
- const color = bodyMeshColor(body.data);
18996
20175
  const segments = BODY_SPHERE_SEGMENTS;
18997
20176
  const geometry = new SphereGeometry(radiusScene, segments, segments);
18998
- const material = new MeshStandardMaterial({ color });
20177
+ const material = new MeshStandardMaterial({ color: untexturedSurfaceColour(body.data) });
18999
20178
  const mesh = new Mesh(geometry, material);
19000
20179
  if (modelBearing) mesh.visible = false;
19001
20180
  bo.root.add(mesh);
@@ -19004,7 +20183,8 @@ function upgradeBodyMesh(bo, scene, clickables, meshToBody) {
19004
20183
  meshToBody.set(mesh, body);
19005
20184
  bo.eclipseShadow = attachEclipseShadowToBody(material);
19006
20185
  attachCameraRelative(mesh, material);
19007
- attachSurfacePhotometry(material, body.data.id);
20186
+ bo.photometry = makePhotometryUniforms(body.data.lunarWeightScale);
20187
+ attachSurfacePhotometry(material, body.data.id, bo.photometry);
19008
20188
  if (!modelBearing) bo.planetshine = attachPlanetshine(material);
19009
20189
  bo.currentSegments = segments;
19010
20190
  const atmoParams = getAtmosphereParams(body.data.id);
@@ -19041,6 +20221,7 @@ function downgradeBodyMesh(bo, scene, clickables, meshToBody) {
19041
20221
  bo.currentSegments = void 0;
19042
20222
  bo.eclipseShadow = null;
19043
20223
  bo.planetshine = null;
20224
+ bo.photometry = null;
19044
20225
  bo.sunTint = void 0;
19045
20226
  bo.radiiApplied = false;
19046
20227
  bo.semiAxesScene = void 0;
@@ -19191,9 +20372,9 @@ function revert(bo, appearance) {
19191
20372
  if (applied.surfaceOwned && appearance?.surface === void 0) {
19192
20373
  applied.surfaceMap?.dispose();
19193
20374
  material.map = null;
19194
- material.color.set(bodyMeshColor(bo.body.data));
20375
+ untexturedSurfaceColour(bo.body.data, material.color);
19195
20376
  material.needsUpdate = true;
19196
- if (bo.model) setShapeModelMap(bo.model, null, bodyMeshColor(bo.body.data));
20377
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
19197
20378
  applied.surfaceOwned = void 0;
19198
20379
  applied.surfaceUrl = void 0;
19199
20380
  applied.surfaceMap = void 0;
@@ -19245,7 +20426,7 @@ async function applyToBody(bo, appearance, textureLoader, current) {
19245
20426
  const live = liveMaterial(bo);
19246
20427
  if (texture && live) {
19247
20428
  setSurfaceMap(live, texture);
19248
- if (bo.model) setShapeModelMap(bo.model, texture, bodyMeshColor(bo.body.data));
20429
+ if (bo.model) setShapeModelMap(bo.model, texture, untexturedSurfaceColour(bo.body.data));
19249
20430
  applied.surfaceUrl = appearance.surface;
19250
20431
  applied.surfaceMap = texture;
19251
20432
  }
@@ -19465,95 +20646,6 @@ function makePointCloudFromBuffer(positions, drawCount, texture, color, size = 4
19465
20646
  return points;
19466
20647
  }
19467
20648
 
19468
- //#endregion
19469
- //#region src/lib/math/orbit/position.ts
19470
- var DEG2RAD$4 = Math.PI / 180;
19471
- var COS_EPS$2 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$4);
19472
- var SIN_EPS$2 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$4);
19473
- function orbitalToThreeJS(xOrb, yOrb, w, i, om, equatorial = false) {
19474
- const cosW = Math.cos(w * DEG2RAD$4);
19475
- const sinW = Math.sin(w * DEG2RAD$4);
19476
- const cosI = Math.cos(i * DEG2RAD$4);
19477
- const sinI = Math.sin(i * DEG2RAD$4);
19478
- const cosOm = Math.cos(om * DEG2RAD$4);
19479
- const sinOm = Math.sin(om * DEG2RAD$4);
19480
- const x = (cosOm * cosW - sinOm * sinW * cosI) * xOrb + (-cosOm * sinW - sinOm * cosW * cosI) * yOrb;
19481
- let y = (sinOm * cosW + cosOm * sinW * cosI) * xOrb + (-sinOm * sinW + cosOm * cosW * cosI) * yOrb;
19482
- let z = sinW * sinI * xOrb + cosW * sinI * yOrb;
19483
- if (equatorial) {
19484
- const yEcl = y * COS_EPS$2 + z * SIN_EPS$2;
19485
- const zEcl = -y * SIN_EPS$2 + z * COS_EPS$2;
19486
- y = yEcl;
19487
- z = zEcl;
19488
- }
19489
- return [
19490
- x * 10,
19491
- z * 10,
19492
- -y * 10
19493
- ];
19494
- }
19495
- function propagateOrbitAngles(el, jd) {
19496
- const { omDot, wDot, om, w, epoch } = el;
19497
- if (!omDot && !wDot) return el;
19498
- const dt = jd - epoch;
19499
- return {
19500
- ...el,
19501
- om: omDot ? om + omDot * dt : om,
19502
- w: wDot ? w + wDot * dt : w
19503
- };
19504
- }
19505
- function orbitalElementsToPositionJD(el, jd) {
19506
- const { a, e, i, ma, n, epoch } = el;
19507
- if (!isFinite(a) || !isFinite(e) || !isFinite(ma) || !isFinite(n)) {
19508
- console.warn(`NaN in orbital elements: a=${a} e=${e} ma=${ma} n=${n}`);
19509
- return null;
19510
- }
19511
- const M = (ma + n * (jd - epoch)) * DEG2RAD$4;
19512
- const { om: omPropagated, w: wPropagated } = propagateOrbitAngles(el, jd);
19513
- let nu;
19514
- let r;
19515
- if (e < 1 || a > 0) {
19516
- const eClamped = Math.min(e, .9999999);
19517
- const E = solveKepler(M, eClamped);
19518
- const sinNu = Math.sqrt(1 - eClamped * eClamped) * Math.sin(E) / (1 - eClamped * Math.cos(E));
19519
- const cosNu = (Math.cos(E) - eClamped) / (1 - eClamped * Math.cos(E));
19520
- nu = Math.atan2(sinNu, cosNu);
19521
- r = a * (1 - eClamped * Math.cos(E));
19522
- } else {
19523
- const H = solveKeplerHyperbolic(M, e);
19524
- if (!isFinite(H)) {
19525
- console.warn(`solveKeplerHyperbolic overflow: M=${M} e=${e}`);
19526
- return null;
19527
- }
19528
- const denom = e * Math.cosh(H) - 1;
19529
- if (Math.abs(denom) < 1e-15) {
19530
- console.warn(`Hyperbolic denom near zero: H=${H} e=${e} denom=${denom}`);
19531
- return null;
19532
- }
19533
- const sinNu = Math.sqrt(e * e - 1) * Math.sinh(H) / denom;
19534
- const cosNu = (e - Math.cosh(H)) / denom;
19535
- nu = Math.atan2(sinNu, cosNu);
19536
- r = a * (1 - e * Math.cosh(H));
19537
- }
19538
- const xOrb = r * Math.cos(nu);
19539
- const yOrb = r * Math.sin(nu);
19540
- if (!isFinite(xOrb) || !isFinite(yOrb)) {
19541
- console.warn(`Non-finite orbital position: r=${r} nu=${nu} xOrb=${xOrb} yOrb=${yOrb} a=${a} e=${e}`);
19542
- return null;
19543
- }
19544
- return orbitalToThreeJS(xOrb, yOrb, wPropagated, i, omPropagated, el.equatorial);
19545
- }
19546
- function parabolicToPositionJD(el, jd) {
19547
- const { q, tp, i, om, w } = el;
19548
- if (q == null || tp == null || !isFinite(q) || !isFinite(tp)) return null;
19549
- const result = solveBarker(q, tp, jd);
19550
- if (!result) return null;
19551
- const xOrb = result.r * Math.cos(result.nu);
19552
- const yOrb = result.r * Math.sin(result.nu);
19553
- if (!isFinite(xOrb) || !isFinite(yOrb)) return null;
19554
- return orbitalToThreeJS(xOrb, yOrb, w, i, om, el.equatorial);
19555
- }
19556
-
19557
20649
  //#endregion
19558
20650
  //#region src/lib/math/orbit/curves.ts
19559
20651
  function orbitalElementsToParabola(el, numPoints = 512, rMaxAU = 50) {
@@ -19797,6 +20889,10 @@ function commitTrail(line, posArr, trailArr, fullArr, n, isOpenCurve, useTrail)
19797
20889
 
19798
20890
  //#endregion
19799
20891
  //#region src/lib/scene/objects/trail/builder.ts
20892
+ var FAT_TRAIL_MIN_PIXEL_RATIO = 1.5;
20893
+ function drawsFat(lineWidth) {
20894
+ return lineWidth > 1 || renderPixelRatio() >= FAT_TRAIL_MIN_PIXEL_RATIO;
20895
+ }
19800
20896
  function writeBufferVerticesWithLiveHead(body, buffer, posArr, cx, cy, cz, basisPos, transform) {
19801
20897
  const head = body.trailAnchor ?? body.position;
19802
20898
  if (!head) return 0;
@@ -19820,7 +20916,7 @@ function makeBufferTrail(body, trailBuffer, color, basisPos, lineWidth) {
19820
20916
  const fullAlphas = new Float32Array(geomCap);
19821
20917
  const trailAlphas = new Float32Array(geomCap);
19822
20918
  if (total > 0) writeTrailAlphas(fullAlphas.subarray(0, total), trailAlphas.subarray(0, total), true, useTrail);
19823
- const isFat = lineWidth > 1;
20919
+ const isFat = drawsFat(lineWidth);
19824
20920
  const obj = isFat ? buildFatLineFromThin(geomCap, posArr, trailAlphas, fullAlphas, total, color, lineWidth) : buildThinLineFromArrays(posArr, trailAlphas, fullAlphas, total, color);
19825
20921
  obj.frustumCulled = false;
19826
20922
  obj.visible = false;
@@ -19871,7 +20967,7 @@ function makeTrail(body, color, basisPos = [
19871
20967
  posArr[k * 3 + 1] = validPoints[k][1] + by;
19872
20968
  posArr[k * 3 + 2] = validPoints[k][2] + bz;
19873
20969
  }
19874
- const isFat = lineWidth > 1;
20970
+ const isFat = drawsFat(lineWidth);
19875
20971
  const obj = isFat ? buildFatLineFromThin(bufferCapacity, posArr, trailAlphas, fullAlphas, validPoints.length, color, lineWidth) : buildThinLineFromArrays(posArr, trailAlphas, fullAlphas, validPoints.length, color);
19876
20972
  obj.frustumCulled = false;
19877
20973
  obj.visible = false;
@@ -20765,7 +21861,7 @@ function collectDebugStats(params) {
20765
21861
 
20766
21862
  //#endregion
20767
21863
  //#region src/lib/math/orbit/worker.ts?worker&inline
20768
- var jsContent = "function e(e,t,n=1e-10,r=50){let i=e;for(let a=0;a<r;a++){let r=(i-t*Math.sin(i)-e)/(1-t*Math.cos(i));if(i-=Math.max(-1,Math.min(1,r)),Math.abs(r)<n)break}return i}function t(e,t,n=1e-10,r=50){if(e===0)return 0;let i=e<0?-1:1,a=Math.abs(e),o=0,s=1;for(;t*Math.sinh(s)-s<a;)if(s*=2,s>700)return NaN;let c=.5*(o+s);for(let e=0;e<r;e++){let e=t*Math.sinh(c)-c-a;e>0?s=c:o=c;let r=c-e/(t*Math.cosh(c)-1);r>o&&r<s||(r=.5*(o+s));let i=Math.abs(r-c);if(c=r,i<n)break}return i*c}function n(e,t,n){let r=.01720209895*(n-t)/(Math.sqrt(2)*e**1.5),i=Math.cbrt(3*r+Math.sqrt(1+9*r*r)),a=i-1/i,o=2*Math.atan(a),s=e*(1+a*a);return!isFinite(o)||!isFinite(s)||s<=0?null:{nu:o,r:s}}const r=149597870.7,i=23.4392911;86400**2/r**3,1/r,Math.cos(Math.PI/180*i),Math.sin(Math.PI/180*i);const a=Math.PI,o=a*2,s=a/180;180/a;const c=6378.135,l=60/Math.sqrt(c*c*c/398600.8),u=c*l/60,d=1/l,f=.001082616,p=-253881e-11/f,m=2/3,ee=1440/(2*a);function te(e,t){let n=[31,e%4==0?29:28,31,30,31,30,31,31,30,31,30,31],r=Math.floor(t),i=1,a=0;for(;r>a+n[i-1]&&i<12;)a+=n[i-1],i+=1;let o=i,s=r-a,c=(t-r)*24,l=Math.floor(c);c=(c-l)*60;let u=Math.floor(c);return{mon:o,day:s,hr:l,minute:u,sec:(c-u)*60}}function h(e,t,n,r,i,a,o=0){return 367*e-Math.floor(7*(e+Math.floor((t+9)/12))*.25)+Math.floor(275*t/9)+n+1721013.5+((o/6e4+a/60+i)/60+r)/24}function g(e,t,n,r,i,a,o=0){if(e instanceof Date){let t=e;return h(t.getUTCFullYear(),t.getUTCMonth()+1,t.getUTCDate(),t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds(),t.getUTCMilliseconds())}return h(e,t,n,r,i,a,o)}function ne(e,t){let{e3:n,ee2:r,peo:i,pgho:s,pho:c,pinco:l,plo:u,se2:d,se3:f,sgh2:p,sgh3:m,sgh4:ee,sh2:te,sh3:h,si2:g,si3:ne,sl2:_,sl3:v,sl4:y,t:b,xgh2:re,xgh3:ie,xgh4:x,xh2:ae,xh3:S,xi2:C,xi3:w,xl2:T,xl3:E,xl4:D,zmol:O,zmos:k}=e,{init:A,opsmode:j}=t,{ep:oe,inclp:M,nodep:N,argpp:se,mp:P}=t,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,le,Y,ue,de,X,fe;fe=k+119459e-10*b,A===`y`&&(fe=k),X=fe+2*.01675*Math.sin(fe),Y=Math.sin(X),U=.5*Y*Y-.25,W=-.5*Y*Math.cos(X);let pe=d*U+f*W,me=g*U+ne*W,he=_*U+v*W+y*Y,ge=p*U+m*W+ee*Y,Z=te*U+h*W;fe=O+.00015835218*b,A===`y`&&(fe=O),X=fe+2*.0549*Math.sin(fe),Y=Math.sin(X),U=.5*Y*Y-.25,W=-.5*Y*Math.cos(X);let _e=r*U+n*W,ve=C*U+w*W,ye=T*U+E*W+D*Y,be=re*U+ie*W+x*Y,xe=ae*U+S*W;return G=pe+_e,J=me+ve,le=he+ye,K=ge+be,q=Z+xe,A===`n`&&(G-=i,J-=l,le-=u,K-=s,q-=c,M+=J,oe+=G,R=Math.sin(M),L=Math.cos(M),M>=.2?(q/=R,K-=L*q,se+=K,N+=q,P+=le):(z=Math.sin(N),ce=Math.cos(N),F=R*z,I=R*ce,B=q*ce+J*L*z,V=-q*z+J*L*ce,F+=B,I+=V,N%=o,N<0&&j===`a`&&(N+=o),ue=P+se+L*N,H=le+K-J*N*R,ue+=H,de=N,N=Math.atan2(F,I),N<0&&j===`a`&&(N+=o),Math.abs(de-N)>a&&(N<de?N+=o:N-=o),P+=le,se=ue-P-L*N)),{ep:oe,inclp:M,nodep:N,argpp:se,mp:P}}function _(e){let{epoch:t,ep:n,argpp:r,tc:i,inclp:a,nodep:s,np:c}=e,l,u,d,f,p,m,ee,te,h,g,ne,_,v,y,b,re,ie,x,ae,S,C,w,T,E,D,O,k,A,j,oe,M,N,se,P,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,le,Y,ue,de,X,fe,pe,me,he,ge,Z,_e,ve,ye,be=.01675,xe=.0549,Se=c,Ce=n,we=Math.sin(s),Te=Math.cos(s),Ee=Math.sin(r),Q=Math.cos(r),De=Math.sin(a),Oe=Math.cos(a),$=Ce*Ce,ke=1-$,Ae=Math.sqrt(ke),je=t+18261.5+i/1440,Me=(4.523602-.00092422029*je)%o,Ne=Math.sin(Me),Pe=Math.cos(Me),Fe=.91375164-.03568096*Pe,Ie=Math.sqrt(1-Fe*Fe),Le=.089683511*Ne/Ie,Re=Math.sqrt(1-Le*Le),ze=5.8351514+.001944368*je,Be=.39785416*Ne/Ie,Ve=Re*Pe+.91744867*Le*Ne;Be=Math.atan2(Be,Ve),Be+=ze-Me;let He=Math.cos(Be),Ue=Math.sin(Be);S=.1945905,C=-.98088458,E=.91744867,D=.39785416,w=Te,T=we,ne=29864797e-13;let We=1/Se,Ge=0;for(;Ge<2;)Ge+=1,l=S*w+C*E*T,d=-C*w+S*E*T,ee=-S*T+C*E*w,te=C*D,h=C*T+S*E*w,g=S*D,u=Oe*ee+De*te,f=Oe*h+De*g,p=-De*ee+Oe*te,m=-De*h+Oe*g,_=l*Q+u*Ee,v=d*Q+f*Ee,y=-l*Ee+u*Q,b=-d*Ee+f*Q,re=p*Ee,ie=m*Ee,x=p*Q,ae=m*Q,_e=12*_*_-3*y*y,ve=24*_*v-6*y*b,ye=12*v*v-3*b*b,ue=3*(l*l+u*u)+_e*$,de=6*(l*d+u*f)+ve*$,X=3*(d*d+f*f)+ye*$,fe=-6*l*p+$*(-24*_*x-6*y*re),pe=-6*(l*m+d*p)+$*(-24*(v*x+_*ae)+-6*(y*ie+b*re)),me=-6*d*m+$*(-24*v*ae-6*b*ie),he=6*u*p+$*(24*_*re-6*y*x),ge=6*(f*p+u*m)+$*(24*(v*re+_*ie)-6*(b*x+y*ae)),Z=6*f*m+$*(24*v*ie-6*b*ae),ue=ue+ue+ke*_e,de=de+de+ke*ve,X=X+X+ke*ye,K=ne*We,G=-.5*K/Ae,q=K*Ae,W=-15*Ce*q,J=_*y+v*b,le=v*y+_*b,Y=v*b-_*y,Ge===1&&(O=W,k=G,A=K,j=q,oe=J,M=le,N=Y,se=ue,P=de,F=X,I=fe,L=pe,R=me,ce=he,z=ge,B=Z,V=_e,H=ve,U=ye,S=He,C=Ue,E=Fe,D=Ie,w=Re*Te+Le*we,T=we*Re-Te*Le,ne=4.7968065e-7);let Ke=(4.7199672+(.2299715*je-ze))%o,qe=(6.2565837+.017201977*je)%o,Je=2*O*M,Ye=2*O*N,Xe=2*k*L,Ze=2*k*(R-I),Qe=-2*A*P,$e=-2*A*(F-se),et=-2*A*(-21-9*$)*be,tt=2*j*H,nt=2*j*(U-V),rt=-18*j*be,it=-2*k*z,at=-2*k*(B-ce),ot=2*W*le,st=2*W*Y,ct=2*G*pe,lt=2*G*(me-fe),ut=-2*K*de,dt=-2*K*(X-ue),ft=-2*K*(-21-9*$)*xe,pt=2*q*ve,mt=2*q*(ye-_e),ht=-18*q*xe,gt=-2*G*ge,_t=-2*G*(Z-he);return{snodm:we,cnodm:Te,sinim:De,cosim:Oe,sinomm:Ee,cosomm:Q,day:je,e3:st,ee2:ot,em:Ce,emsq:$,gam:ze,peo:0,pgho:0,pho:0,pinco:0,plo:0,rtemsq:Ae,se2:Je,se3:Ye,sgh2:tt,sgh3:nt,sgh4:rt,sh2:it,sh3:at,si2:Xe,si3:Ze,sl2:Qe,sl3:$e,sl4:et,s1:W,s2:G,s3:K,s4:q,s5:J,s6:le,s7:Y,ss1:O,ss2:k,ss3:A,ss4:j,ss5:oe,ss6:M,ss7:N,sz1:se,sz2:P,sz3:F,sz11:I,sz12:L,sz13:R,sz21:ce,sz22:z,sz23:B,sz31:V,sz32:H,sz33:U,xgh2:pt,xgh3:mt,xgh4:ht,xh2:gt,xh3:_t,xi2:ct,xi3:lt,xl2:ut,xl3:dt,xl4:ft,nm:Se,z1:ue,z2:de,z3:X,z11:fe,z12:pe,z13:me,z21:he,z22:ge,z23:Z,z31:_e,z32:ve,z33:ye,zmol:Ke,zmos:qe}}function v(e){let{cosim:t,argpo:n,s1:r,s2:i,s3:s,s4:c,s5:u,sinim:d,ss1:f,ss2:p,ss3:ee,ss4:te,ss5:h,sz1:g,sz3:ne,sz11:_,sz13:v,sz21:y,sz23:b,sz31:re,sz33:ie,t:x,tc:ae,gsto:S,mo:C,mdot:w,no:T,nodeo:E,nodedot:D,xpidot:O,z1:k,z3:A,z11:j,z13:oe,z21:M,z23:N,z31:se,z33:P,ecco:F,eccsq:I}=e,{emsq:L,em:R,argpm:ce,inclm:z,mm:B,nm:V,nodem:H,irez:U,atime:W,d2201:G,d2211:K,d3210:q,d3222:J,d4410:le,d4422:Y,d5220:ue,d5232:de,d5421:X,d5433:fe,dedt:pe,didt:me,dmdt:he,dnodt:ge,domdt:Z,del1:_e,del2:ve,del3:ye,xfact:be,xlamo:xe,xli:Se,xni:Ce}=e,we,Te,Ee,Q,De,Oe,$,ke,Ae,je,Me,Ne,Pe,Fe,Ie,Le,Re,ze,Be,Ve,He,Ue,We,Ge,Ke,qe,Je,Ye,Xe,Ze,Qe,$e,et=.0043752690880113,tt=.00015835218,nt=119459e-10;U=0,V<.0052359877&&V>.0034906585&&(U=1),V>=.00826&&V<=.00924&&R>=.5&&(U=2);let rt=f*nt*h,it=p*nt*(_+v),at=-nt*ee*(g+ne-14-6*L),ot=te*nt*(re+ie-6),st=-nt*p*(y+b);(z<.052359877||z>a-.052359877)&&(st=0),d!==0&&(st/=d);let ct=ot-t*st;pe=rt+r*tt*u,me=it+i*tt*(j+oe),he=at-tt*s*(k+A-14-6*L);let lt=c*tt*(se+P-6),ut=-tt*i*(M+N);(z<.052359877||z>a-.052359877)&&(ut=0),Z=ct+lt,ge=st,d!==0&&(Z-=t/d*ut,ge+=ut/d);let dt=(S+ae*et)%o;if(R+=pe*x,z+=me*x,ce+=Z*x,H+=ge*x,B+=he*x,U!==0){if(Ze=(V/l)**m,U===2){Qe=t*t;let e=R;R=F;let n=L;L=I,$e=R*L,Fe=-.306-(R-.64)*.44,R<=.65?(Ie=3.616-13.247*R+16.29*L,Re=-19.302+117.39*R-228.419*L+156.591*$e,ze=-18.9068+109.7927*R-214.6334*L+146.5816*$e,Be=-41.122+242.694*R-471.094*L+313.953*$e,Ve=-146.407+841.88*R-1629.014*L+1083.435*$e,He=-532.114+3017.977*R-5740.032*L+3708.276*$e):(Ie=-72.099+331.819*R-508.738*L+266.724*$e,Re=-346.844+1582.851*R-2415.925*L+1246.113*$e,ze=-342.585+1554.908*R-2366.899*L+1215.972*$e,Be=-1052.797+4758.686*R-7193.992*L+3651.957*$e,Ve=-3581.69+16178.11*R-24462.77*L+12422.52*$e,He=R>.715?-5149.66+29936.92*R-54087.36*L+31324.56*$e:1464.74-4664.75*R+3763.64*L),R<.7?(Ge=-919.2277+4988.61*R-9064.77*L+5542.21*$e,Ue=-822.71072+4568.6173*R-8491.4146*L+5337.524*$e,We=-853.666+4690.25*R-8624.77*L+5341.4*$e):(Ge=-37995.78+161616.52*R-229838.2*L+109377.94*$e,Ue=-51752.104+218913.95*R-309468.16*L+146349.42*$e,We=-40023.88+170470.89*R-242699.48*L+115605.82*$e),Ke=d*d,we=.75*(1+2*t+Qe),Te=1.5*Ke,Q=1.875*d*(1-2*t-3*Qe),De=-1.875*d*(1+2*t-3*Qe),$=35*Ke*we,ke=39.375*Ke*Ke,Ae=9.84375*d*(Ke*(1-2*t-5*Qe)+.33333333*(-2+4*t+6*Qe)),je=d*(4.92187512*Ke*(-2-4*t+10*Qe)+6.56250012*(1+2*t-3*Qe)),Me=29.53125*d*(2-8*t+Qe*(-12+8*t+10*Qe)),Ne=29.53125*d*(-2-8*t+Qe*(12+8*t-10*Qe)),Ye=V*V,Xe=Ze*Ze,Je=3*Ye*Xe,qe=Je*17891679e-13,G=qe*we*Fe,K=qe*Te*Ie,Je*=Ze,qe=Je*3.7393792e-7,q=qe*Q*Re,J=qe*De*ze,Je*=Ze,qe=2*Je*7.3636953e-9,le=qe*$*Be,Y=qe*ke*Ve,Je*=Ze,qe=Je*1.1428639e-7,ue=qe*Ae*He,de=qe*je*We,qe=2*Je*2.1765803e-9,X=qe*Me*Ue,fe=qe*Ne*Ge,xe=(C+E+E-(dt+dt))%o,be=w+he+2*(D+ge-et)-T,R=e,L=n}U===1&&(Pe=1+L*(-2.5+.8125*L),Re=1+2*L,Le=1+L*(-6+6.60937*L),we=.75*(1+t)*(1+t),Ee=.9375*d*d*(1+3*t)-.75*(1+t),Oe=1+t,Oe*=1.875*Oe*Oe,_e=3*V*V*Ze*Ze,ve=2*_e*we*Pe*17891679e-13,ye=3*_e*Oe*Le*2.2123015e-7*Ze,_e=_e*Ee*Re*21460748e-13*Ze,xe=(C+E+n-dt)%o,be=w+O+he+Z+ge-(T+et)),Se=xe,Ce=T,W=0,V=T+0}return{em:R,argpm:ce,inclm:z,mm:B,nm:V,nodem:H,irez:U,atime:W,d2201:G,d2211:K,d3210:q,d3222:J,d4410:le,d4422:Y,d5220:ue,d5232:de,d5421:X,d5433:fe,dedt:pe,didt:me,dmdt:he,dndt:0,dnodt:ge,domdt:Z,del1:_e,del2:ve,del3:ye,xfact:be,xlamo:xe,xli:Se,xni:Ce}}function y(e){let t=(e-2451545)/36525,n=-62e-7*t*t*t+.093104*t*t+3164400184.812866*t+67310.54841;return n=n*s/240%o,n<0&&(n+=o),n}function b(e,t,n,r,i,a,o){return e instanceof Date?y(g(e)):y(t===void 0?e:g(e,t,n,r,i,a,o))}function re(e){let{ecco:t,epoch:n,inclo:r,opsmode:i}=e,{no:a}=e,s=t*t,c=1-s,u=Math.sqrt(c),d=Math.cos(r),p=d*d,ee=(l/a)**m,te=.75*f*(3*p-1)/(u*c),h=te/(ee*ee),g=ee*(1-h*h-h*(1/3+134*h*h/81));h=te/(g*g),a/=1+h;let ne=(l/a)**m,_=Math.sin(r),v=ne*c,y=1-5*p,re=-y-p-p,ie=1/ne,x=v*v,ae=ne*(1-t),S;if(i===`a`){let e=n-7305,t=Math.floor(e+1e-8),r=e-t,i=.017202791694070362,a=i+o;S=(1.7321343856509375+i*t+a*r+e*e*5075514194322695e-30)%o,S<0&&(S+=o)}else S=b(n+2433281.5);return{no:a,method:`n`,ainv:ie,ao:ne,con41:re,con42:y,cosio:d,cosio2:p,eccsq:s,omeosq:c,posq:x,rp:ae,rteosq:u,sinio:_,gsto:S}}function ie(e){let{irez:t,d2201:n,d2211:r,d3210:i,d3222:a,d4410:s,d4422:c,d5220:l,d5232:u,d5421:d,d5433:f,dedt:p,del1:m,del2:ee,del3:te,didt:h,dmdt:g,dnodt:ne,domdt:_,argpo:v,argpdot:y,t:b,tc:re,gsto:ie,xfact:x,xlamo:ae,no:S}=e,{atime:C,em:w,argpm:T,inclm:E,xli:D,mm:O,xni:k,nodem:A,nm:j}=e,oe=.13130908,M=2.8843198,N=.37448087,se=5.7686396,P=.95240898,F=1.8014998,I=1.050833,L=4.4108898,R=259200,ce,z,B,V,H,U,W,G,K=0,q=0,J=(ie+re*.0043752690880113)%o;if(w+=p*b,E+=h*b,T+=_*b,A+=ne*b,O+=g*b,t!==0){(C===0||b*C<=0||Math.abs(b)<Math.abs(C))&&(C=0,k=S,D=ae),ce=b>0?720:-720;let e=381;for(;e===381;)t===2?(G=v+y*C,B=G+G,z=D+D,W=n*Math.sin(B+D-se)+r*Math.sin(D-se)+i*Math.sin(G+D-P)+a*Math.sin(-G+D-P)+s*Math.sin(B+z-F)+c*Math.sin(z-F)+l*Math.sin(G+D-I)+u*Math.sin(-G+D-I)+d*Math.sin(G+z-L)+f*Math.sin(-G+z-L),H=k+x,U=n*Math.cos(B+D-se)+r*Math.cos(D-se)+i*Math.cos(G+D-P)+a*Math.cos(-G+D-P)+l*Math.cos(G+D-I)+u*Math.cos(-G+D-I)+2*(s*Math.cos(B+z-F)+c*Math.cos(z-F)+d*Math.cos(G+z-L)+f*Math.cos(-G+z-L)),U*=H):(W=m*Math.sin(D-oe)+ee*Math.sin(2*(D-M))+te*Math.sin(3*(D-N)),H=k+x,U=m*Math.cos(D-oe)+2*ee*Math.cos(2*(D-M))+3*te*Math.cos(3*(D-N)),U*=H),Math.abs(b-C)>=720?e=381:(q=b-C,e=0),e===381&&(D+=H*ce+W*R,k+=W*ce+U*R,C+=ce);j=k+W*q+U*q*q*.5,V=D+H*q+W*q*q*.5,t===1?(O=V-A-T+J,K=j-S):(O=V-2*A+2*J,K=j-S),j=S+K}return{atime:C,em:w,argpm:T,inclm:E,xli:D,mm:O,xni:k,nodem:A,dndt:K,nm:j}}var x;(function(e){e[e.None=0]=`None`,e[e.MeanEccentricityOutOfRange=1]=`MeanEccentricityOutOfRange`,e[e.MeanMotionBelowZero=2]=`MeanMotionBelowZero`,e[e.PerturbedEccentricityOutOfRange=3]=`PerturbedEccentricityOutOfRange`,e[e.SemiLatusRectumBelowZero=4]=`SemiLatusRectumBelowZero`,e[e.Decayed=6]=`Decayed`})(x||={});function ae(e,t){let n,r,i,s,d,ee,te,h,g,_,v,y,b,re,ae,S,C,w,T,E,D,O,k,A,j,oe,M;e.t=t,e.error=x.None;let N=e.mo+e.mdot*e.t,se=e.argpo+e.argpdot*e.t,P=e.nodeo+e.nodedot*e.t;g=se,D=N;let F=e.t*e.t;if(k=P+e.nodecf*F,C=1-e.cc1*e.t,w=e.bstar*e.cc4*e.t,T=e.t2cof*F,e.isimp!==1){te=e.omgcof*e.t;let t=1+e.eta*Math.cos(N);ee=e.xmcof*(t*t*t-e.delmo),S=te+ee,D=N+S,g=se-S,y=F*e.t,b=y*e.t,C=C-e.d2*F-e.d3*y-e.d4*b,w+=e.bstar*e.cc5*(Math.sin(D)-e.sinmao),T=T+e.t3cof*y+b*(e.t4cof+e.t*e.t5cof)}O=e.no;let I=e.ecco;if(E=e.inclo,e.method===`d`){re=e.t;let t=ie({irez:e.irez,d2201:e.d2201,d2211:e.d2211,d3210:e.d3210,d3222:e.d3222,d4410:e.d4410,d4422:e.d4422,d5220:e.d5220,d5232:e.d5232,d5421:e.d5421,d5433:e.d5433,dedt:e.dedt,del1:e.del1,del2:e.del2,del3:e.del3,didt:e.didt,dmdt:e.dmdt,dnodt:e.dnodt,domdt:e.domdt,argpo:e.argpo,argpdot:e.argpdot,t:e.t,tc:re,gsto:e.gsto,xfact:e.xfact,xlamo:e.xlamo,no:e.no,atime:e.atime,em:I,argpm:g,inclm:E,xli:e.xli,mm:D,xni:e.xni,nodem:k,nm:O});({em:I,argpm:g,inclm:E,mm:D,nodem:k,nm:O}=t)}if(O<=0)return e.error=x.MeanMotionBelowZero,null;let L=(l/O)**m*C*C;if(O=l/L**1.5,I-=w,I>=1||I<-.001)return e.error=x.MeanEccentricityOutOfRange,null;I<1e-6&&(I=1e-6),D+=e.no*T,j=D+g+k,k%=o,g%=o,j%=o,D=(j-g-k)%o;let R={am:L,em:I,im:E,Om:k,om:g,mm:D,nm:O},ce=Math.sin(E),z=Math.cos(E),B=I;if(A=E,_=g,M=k,oe=D,s=ce,i=z,e.method===`d`){let t=ne(e,{inclo:e.inclo,init:`n`,ep:B,inclp:A,nodep:M,argpp:_,mp:oe,opsmode:e.operationmode});if({ep:B,nodep:M,argpp:_,mp:oe}=t,A=t.inclp,A<0&&(A=-A,M+=a,_-=a),B<0||B>1)return e.error=x.PerturbedEccentricityOutOfRange,null}e.method===`d`&&(s=Math.sin(A),i=Math.cos(A),e.aycof=-.5*p*s,Math.abs(i+1)>15e-13?e.xlcof=-.25*p*s*(3+5*i)/(1+i):e.xlcof=-.25*p*s*(3+5*i)/15e-13);let V=B*Math.cos(_);S=1/(L*(1-B*B));let H=B*Math.sin(_)+S*e.aycof,U=(oe+_+M+S*e.xlcof*V-M)%o;h=U,ae=9999.9;let W=1;for(;Math.abs(ae)>=1e-12&&W<=10;)r=Math.sin(h),n=Math.cos(h),ae=1-n*V-r*H,ae=(U-H*n+V*r-h)/ae,Math.abs(ae)>=.95&&(ae=ae>0?.95:-.95),h+=ae,W+=1;let G=V*n+H*r,K=V*r-H*n,q=V*V+H*H,J=L*(1-q);if(J<0)return e.error=x.SemiLatusRectumBelowZero,null;let le=L*(1-G),Y=Math.sqrt(L)*K/le,ue=Math.sqrt(J)/le,de=Math.sqrt(1-q);S=K/(1+de);let X=L/le*(r-H-V*S),fe=L/le*(n-V+H*S);v=Math.atan2(X,fe);let pe=(fe+fe)*X,me=1-2*X*X;S=1/J;let he=.5*f*S,ge=he*S;e.method===`d`&&(d=i*i,e.con41=3*d-1,e.x1mth2=1-d,e.x7thm1=7*d-1);let Z=le*(1-1.5*ge*de*e.con41)+.5*he*e.x1mth2*me;if(Z<1)return e.error=x.Decayed,null;v-=.25*ge*e.x7thm1*pe;let _e=M+1.5*ge*i*pe,ve=A+1.5*ge*i*s*me,ye=Y-O*he*e.x1mth2*pe/l,be=ue+O*he*(e.x1mth2*me+1.5*e.con41)/l,xe=Math.sin(v),Se=Math.cos(v),Ce=Math.sin(_e),we=Math.cos(_e),Te=Math.sin(ve),Ee=Math.cos(ve),Q=-Ce*Ee,De=we*Ee,Oe=Q*xe+we*Se,$=De*xe+Ce*Se,ke=Te*xe,Ae=Q*Se-we*xe,je=De*Se-Ce*xe,Me=Te*Se;return{position:{x:Z*Oe*c,y:Z*$*c,z:Z*ke*c},velocity:{x:(ye*Oe+be*Ae)*u,y:(ye*$+be*je)*u,z:(ye*ke+be*Me)*u},meanElements:R}}function S(e,t){let{opsmode:n,satn:r,epoch:i,xbstar:o,xecco:s,xargpo:l,xinclo:u,xmo:ee,xno:te,xnodeo:h}=t,g,y,b,ie,x,S,C,w,T,E,D,O,k,A,j,oe,M,N,se,P,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,le,Y,ue,de,X,fe,pe,me,he,ge,Z,_e,ve,ye,be,xe,Se,Ce,we,Te,Ee,Q=e;Q.isimp=0,Q.method=`n`,Q.aycof=0,Q.con41=0,Q.cc1=0,Q.cc4=0,Q.cc5=0,Q.d2=0,Q.d3=0,Q.d4=0,Q.delmo=0,Q.eta=0,Q.argpdot=0,Q.omgcof=0,Q.sinmao=0,Q.t=0,Q.t2cof=0,Q.t3cof=0,Q.t4cof=0,Q.t5cof=0,Q.x1mth2=0,Q.x7thm1=0,Q.mdot=0,Q.nodedot=0,Q.xlcof=0,Q.xmcof=0,Q.nodecf=0,Q.irez=0,Q.d2201=0,Q.d2211=0,Q.d3210=0,Q.d3222=0,Q.d4410=0,Q.d4422=0,Q.d5220=0,Q.d5232=0,Q.d5421=0,Q.d5433=0,Q.dedt=0,Q.del1=0,Q.del2=0,Q.del3=0,Q.didt=0,Q.dmdt=0,Q.dnodt=0,Q.domdt=0,Q.e3=0,Q.ee2=0,Q.peo=0,Q.pgho=0,Q.pho=0,Q.pinco=0,Q.plo=0,Q.se2=0,Q.se3=0,Q.sgh2=0,Q.sgh3=0,Q.sgh4=0,Q.sh2=0,Q.sh3=0,Q.si2=0,Q.si3=0,Q.sl2=0,Q.sl3=0,Q.sl4=0,Q.gsto=0,Q.xfact=0,Q.xgh2=0,Q.xgh3=0,Q.xgh4=0,Q.xh2=0,Q.xh3=0,Q.xi2=0,Q.xi3=0,Q.xl2=0,Q.xl3=0,Q.xl4=0,Q.xlamo=0,Q.zmol=0,Q.zmos=0,Q.atime=0,Q.xli=0,Q.xni=0,Q.bstar=o,Q.ecco=s,Q.argpo=l,Q.inclo=u,Q.mo=ee,Q.no=te,Q.nodeo=h,Q.operationmode=n,78/c+1;let De=42/c;De*De*De*De,Q.init=`y`,Q.t=0;let Oe=re({satn:r,ecco:Q.ecco,epoch:i,inclo:Q.inclo,no:Q.no,method:Q.method,opsmode:Q.operationmode}),{ao:$,con42:ke,cosio:Ae,cosio2:je,eccsq:Me,omeosq:Ne,posq:Pe,rp:Fe,rteosq:Ie,sinio:Le}=Oe;if(Q.no=Oe.no,Q.con41=Oe.con41,Q.gsto=Oe.gsto,Q.a=(Q.no*d)**(-2/3),Q.alta=Q.a*(1+Q.ecco)-1,Q.altp=Q.a*(1-Q.ecco)-1,Q.error=0,Ne>=0||Q.no>=0){if(Q.isimp=0,Fe<1.034492841559484&&(Q.isimp=1),B=1.0122292801892716,F=1.880279159015271e-9,N=(Fe-1)*c,N<156){B=N-78,N<98&&(B=20);let e=(120-B)/c;F=e*e*e*e,B=B/c+1}se=1/Pe,Z=1/($-B),Q.eta=$*Q.ecco*Z,O=Q.eta*Q.eta,D=Q.ecco*Q.eta,P=Math.abs(1-O),S=F*Z**4,C=S/P**3.5,ie=C*Q.no*($*(1+1.5*O+D*(4+O))+.375*f*Z/P*Q.con41*(8+3*O*(8+O))),Q.cc1=Q.bstar*ie,x=0,Q.ecco>1e-4&&(x=-2*S*Z*p*Q.no*Le/Q.ecco),Q.x1mth2=1-je,Q.cc4=2*Q.no*C*$*Ne*(Q.eta*(2+.5*O)+Q.ecco*(.5+2*O)-f*Z/($*P)*(-3*Q.con41*(1-2*D+O*(1.5-.5*D))+.75*Q.x1mth2*(2*O-D*(1+O))*Math.cos(2*Q.argpo))),Q.cc5=2*C*$*Ne*(1+2.75*(O+D)+D*O),w=je*je,me=1.5*f*se*Q.no,he=.5*me*f*se,ge=-.46875*-165597e-11*se*se*Q.no,Q.mdot=Q.no+.5*me*Ie*Q.con41+.0625*he*Ie*(13-78*je+137*w),Q.argpdot=-.5*me*ke+.0625*he*(7-114*je+395*w)+ge*(3-36*je+49*w),ve=-me*Ae,Q.nodedot=ve+(.5*he*(4-19*je)+2*ge*(3-7*je))*Ae,_e=Q.argpdot+Q.nodedot,Q.omgcof=Q.bstar*x*Math.cos(Q.argpo),Q.xmcof=0,Q.ecco>1e-4&&(Q.xmcof=-m*S*Q.bstar/D),Q.nodecf=3.5*Ne*ve*Q.cc1,Q.t2cof=1.5*Q.cc1,Math.abs(Ae+1)>15e-13?Q.xlcof=-.25*p*Le*(3+5*Ae)/(1+Ae):Q.xlcof=-.25*p*Le*(3+5*Ae)/15e-13,Q.aycof=-.5*p*Le;let e=1+Q.eta*Math.cos(Q.mo);if(Q.delmo=e*e*e,Q.sinmao=Math.sin(Q.mo),Q.x7thm1=7*je-1,2*a/Q.no>=225){Q.method=`d`,Q.isimp=1,fe=0,j=Q.inclo;let e=_({epoch:i,ep:Q.ecco,argpp:Q.argpo,tc:fe,inclp:Q.inclo,nodep:Q.nodeo,np:Q.no,e3:Q.e3,ee2:Q.ee2,peo:Q.peo,pgho:Q.pgho,pho:Q.pho,pinco:Q.pinco,plo:Q.plo,se2:Q.se2,se3:Q.se3,sgh2:Q.sgh2,sgh3:Q.sgh3,sgh4:Q.sgh4,sh2:Q.sh2,sh3:Q.sh3,si2:Q.si2,si3:Q.si3,sl2:Q.sl2,sl3:Q.sl3,sl4:Q.sl4,xgh2:Q.xgh2,xgh3:Q.xgh3,xgh4:Q.xgh4,xh2:Q.xh2,xh3:Q.xh3,xi2:Q.xi2,xi3:Q.xi3,xl2:Q.xl2,xl3:Q.xl3,xl4:Q.xl4,zmol:Q.zmol,zmos:Q.zmos});Q.e3=e.e3,Q.ee2=e.ee2,Q.peo=e.peo,Q.pgho=e.pgho,Q.pho=e.pho,Q.pinco=e.pinco,Q.plo=e.plo,Q.se2=e.se2,Q.se3=e.se3,Q.sgh2=e.sgh2,Q.sgh3=e.sgh3,Q.sgh4=e.sgh4,Q.sh2=e.sh2,Q.sh3=e.sh3,Q.si2=e.si2,Q.si3=e.si3,Q.sl2=e.sl2,Q.sl3=e.sl3,Q.sl4=e.sl4,{sinim:y,cosim:g,em:T,emsq:E,s1:I,s2:L,s3:R,s4:ce,s5:z,ss1:V,ss2:H,ss3:U,ss4:W,ss5:G,sz1:K,sz3:q,sz11:J,sz13:le,sz21:Y,sz23:ue,sz31:de,sz33:X}=e,Q.xgh2=e.xgh2,Q.xgh3=e.xgh3,Q.xgh4=e.xgh4,Q.xh2=e.xh2,Q.xh3=e.xh3,Q.xi2=e.xi2,Q.xi3=e.xi3,Q.xl2=e.xl2,Q.xl3=e.xl3,Q.xl4=e.xl4,Q.zmol=e.zmol,Q.zmos=e.zmos,{nm:M,z1:ye,z3:be,z11:xe,z13:Se,z21:Ce,z23:we,z31:Te,z33:Ee}=e;let t=ne(Q,{inclo:j,init:Q.init,ep:Q.ecco,inclp:Q.inclo,nodep:Q.nodeo,argpp:Q.argpo,mp:Q.mo,opsmode:Q.operationmode});Q.ecco=t.ep,Q.inclo=t.inclp,Q.nodeo=t.nodep,Q.argpo=t.argpp,Q.mo=t.mp,k=0,A=0,oe=0;let n=v({cosim:g,emsq:E,argpo:Q.argpo,s1:I,s2:L,s3:R,s4:ce,s5:z,sinim:y,ss1:V,ss2:H,ss3:U,ss4:W,ss5:G,sz1:K,sz3:q,sz11:J,sz13:le,sz21:Y,sz23:ue,sz31:de,sz33:X,t:Q.t,tc:fe,gsto:Q.gsto,mo:Q.mo,mdot:Q.mdot,no:Q.no,nodeo:Q.nodeo,nodedot:Q.nodedot,xpidot:_e,z1:ye,z3:be,z11:xe,z13:Se,z21:Ce,z23:we,z31:Te,z33:Ee,ecco:Q.ecco,eccsq:Me,em:T,argpm:k,inclm:j,mm:oe,nm:M,nodem:A,irez:Q.irez,atime:Q.atime,d2201:Q.d2201,d2211:Q.d2211,d3210:Q.d3210,d3222:Q.d3222,d4410:Q.d4410,d4422:Q.d4422,d5220:Q.d5220,d5232:Q.d5232,d5421:Q.d5421,d5433:Q.d5433,dedt:Q.dedt,didt:Q.didt,dmdt:Q.dmdt,dnodt:Q.dnodt,domdt:Q.domdt,del1:Q.del1,del2:Q.del2,del3:Q.del3,xfact:Q.xfact,xlamo:Q.xlamo,xli:Q.xli,xni:Q.xni});Q.irez=n.irez,Q.atime=n.atime,Q.d2201=n.d2201,Q.d2211=n.d2211,Q.d3210=n.d3210,Q.d3222=n.d3222,Q.d4410=n.d4410,Q.d4422=n.d4422,Q.d5220=n.d5220,Q.d5232=n.d5232,Q.d5421=n.d5421,Q.d5433=n.d5433,Q.dedt=n.dedt,Q.didt=n.didt,Q.dmdt=n.dmdt,Q.dnodt=n.dnodt,Q.domdt=n.domdt,Q.del1=n.del1,Q.del2=n.del2,Q.del3=n.del3,Q.xfact=n.xfact,Q.xlamo=n.xlamo,Q.xli=n.xli,Q.xni=n.xni}Q.isimp!==1&&(b=Q.cc1*Q.cc1,Q.d2=4*$*Z*b,pe=Q.d2*Z*Q.cc1/3,Q.d3=(17*$+B)*pe,Q.d4=.5*pe*$*Z*(221*$+31*B)*Q.cc1,Q.t3cof=Q.d2+2*b,Q.t4cof=.25*(3*Q.d3+Q.cc1*(12*Q.d2+10*b)),Q.t5cof=.2*(3*Q.d4+12*Q.cc1*Q.d3+6*Q.d2*Q.d2+15*b*(2*Q.d2+b)))}ae(Q,0),Q.init=`n`}function C(e,t=`i`){let n=e.NORAD_CAT_ID.toString(),r=new Date(e.EPOCH.endsWith(`Z`)?e.EPOCH:`${e.EPOCH}Z`),i=r.getUTCFullYear(),a=Number(i.toString().slice(-2)),o=(r.valueOf()-new Date(Date.UTC(i,0,1,0,0,0)).valueOf())/(86400*1e3)+1,c=Number(e.MEAN_MOTION_DOT),l=Number(e.MEAN_MOTION_DDOT);c/=ee*1440,l/=ee*1440*1440;let u=Number(e.BSTAR),d=Number(e.INCLINATION)*s,f=Number(e.RA_OF_ASC_NODE)*s,p=Number(e.ECCENTRICITY),m=Number(e.ARG_OF_PERICENTER)*s,h=Number(e.MEAN_ANOMALY)*s,ne=Number(e.MEAN_MOTION)/ee,{mon:_,day:v,hr:y,minute:b,sec:re}=te(i,o),ie=g(i,_,v,y,b,re),x={error:0,satnum:n,epochyr:a,epochdays:o,ndot:c,nddot:l,bstar:u,inclo:d,nodeo:f,ecco:p,argpo:m,mo:h,no:ne,jdsatepoch:ie};return S(x,{opsmode:t,satn:x.satnum,epoch:x.jdsatepoch-2433281.5,xbstar:x.bstar,xecco:x.ecco,xargpo:x.argpo,xinclo:x.inclo,xmo:x.mo,xno:x.no,xnodeo:x.nodeo}),x}3*Float64Array.BYTES_PER_ELEMENT;const w=Math.PI/180;Math.cos(i*w),Math.sin(i*w);function T(e){return new Date((e-2440587.5)*864e5).toISOString()}function E(e,t){let n=C({OBJECT_NAME:t??String(e.noradCatId),OBJECT_ID:String(e.noradCatId),EPOCH:T(e.epochJd),MEAN_MOTION:e.meanMotion,ECCENTRICITY:e.eccentricity,INCLINATION:e.inclination,RA_OF_ASC_NODE:e.raOfAscNode,ARG_OF_PERICENTER:e.argOfPericenter,MEAN_ANOMALY:e.meanAnomaly,EPHEMERIS_TYPE:0,CLASSIFICATION_TYPE:`U`,NORAD_CAT_ID:e.noradCatId,ELEMENT_SET_NO:e.elementSetNo,REV_AT_EPOCH:e.revAtEpoch,BSTAR:e.bstar,MEAN_MOTION_DOT:e.meanMotionDot,MEAN_MOTION_DDOT:e.meanMotionDdot});return n.error===x.None?n:(console.warn(`buildSatrec: SGP4 init failed for NORAD ${e.noradCatId} (error=${n.error})`),null)}const D=Math.PI/180,O=Math.cos(i*D),k=Math.sin(i*D),A=10/r;function j(e,t,n,r,i){return!isFinite(Math.fround(n))||!isFinite(Math.fround(r))||!isFinite(Math.fround(i))?!1:(e[t*3]=n,e[t*3+1]=r,e[t*3+2]=i,!0)}function oe(r,i,a,o,s,c,l,u,d,f=null,p=0){if(i<r.validityStart||i>r.validityEnd)return 0;let{count:m,kind:ee,equatorial:te,a:h,e:g,i:ne,om:_,w:v,ma:y,n:b,epoch:re,q:ie,tp:S,satrec:C,flags:w,visibleFromDays:T}=r,oe=p!==0&&r.applyFlagFilter,M=r.pickBase,N=d.length/3|0,se=i-2451545,P=0;for(let F=0;F<m&&!(P>=N);F++){let m=ee[F];if(m===0||T[F]>se||oe&&(w[F]&p)!==p)continue;if(m===3){let e=C[F];if(!e){if(e=E({noradCatId:r.satnum[F],epochJd:re[F],meanMotion:b[F]/360,eccentricity:g[F],inclination:ne[F],raOfAscNode:_[F],argOfPericenter:v[F],meanAnomaly:y[F],bstar:r.bstar[F],meanMotionDot:r.ndot[F],meanMotionDdot:r.nddot[F],elementSetNo:0,revAtEpoch:0}),!e){ee[F]=0;continue}C[F]=e}let t=(i-e.jdsatepoch)*1440,n=ae(e,t);if(!n||e.error!==x.None)continue;let p=n.position.x,m=n.position.y,te=n.position.z,h=p*A,ie=m*A,S=te*A,w=ie*O+S*k,T=-ie*k+S*O,D=h,oe=T,N=-w;if(!j(d,P,a+D-c,o+oe-l,s+N-u))continue;if(f){let e=M+F,t=P*4;f[t]=e&255,f[t+1]=e>>>8&255,f[t+2]=e>>>16&255,f[t+3]=e>>>24&255}P++;continue}let N,I,L=g[F],R=ne[F],ce=_[F],z=v[F];if(m===2){let e=n(ie[F],S[F],i);if(!e)continue;N=e.r*Math.cos(e.nu),I=e.r*Math.sin(e.nu)}else{let n=h[F];if(!isFinite(n)||!isFinite(L)||!isFinite(y[F])||!isFinite(b[F]))continue;let r=i-re[F],a=(y[F]+b[F]*r)*D,o,s;if(L<1||n>0){let t=Math.min(L,.9999999),r=e(a,t),i=Math.sqrt(1-t*t)*Math.sin(r)/(1-t*Math.cos(r)),c=(Math.cos(r)-t)/(1-t*Math.cos(r));o=Math.atan2(i,c),s=n*(1-t*Math.cos(r))}else{let e=t(a,L);if(!isFinite(e))continue;let r=L*Math.cosh(e)-1;if(Math.abs(r)<1e-15)continue;let i=Math.sqrt(L*L-1)*Math.sinh(e)/r,c=(L-Math.cosh(e))/r;o=Math.atan2(i,c),s=n*(1-L*Math.cosh(e))}N=s*Math.cos(o),I=s*Math.sin(o)}if(!isFinite(N)||!isFinite(I))continue;let B=Math.cos(z*D),V=Math.sin(z*D),H=Math.cos(R*D),U=Math.sin(R*D),W=Math.cos(ce*D),G=Math.sin(ce*D),K=(W*B-G*V*H)*N+(-W*V-G*B*H)*I,q=(G*B+W*V*H)*N+(-G*V+W*B*H)*I,J=V*U*N+B*U*I;if(te[F]){let e=q*O+J*k,t=-q*k+J*O;q=e,J=t}let le=K*10,Y=J*10,ue=-q*10;if(j(d,P,a+le-c,o+Y-l,s+ue-u)){if(f){let e=M+F,t=P*4;f[t]=e&255,f[t+1]=e>>>8&255,f[t+2]=e>>>16&255,f[t+3]=e>>>24&255}P++}}return P}const M=new Map;self.onmessage=e=>{let t=e.data;if(t.type===`ping`){self.postMessage({type:`pong`});return}if(t.type===`rewireDelta`){if(t.remove)for(let e of t.remove)M.delete(e);for(let e of t.set)M.set(e.id,e.cols);return}if(t.type===`tick`){let e=[],n=[],[r,i,a]=t.basis;for(let o of t.groups){let s=M.get(o.id);if(!s){e.push({id:o.id,count:0,buf:o.out.buffer,idbuf:o.outIds.buffer}),n.push(o.out.buffer,o.outIds.buffer);continue}let c=oe(s,t.jd,o.parent[0],o.parent[1],o.parent[2],r,i,a,o.out,o.outIds,t.requiredFlags??0);e.push({id:o.id,count:c,buf:o.out.buffer,idbuf:o.outIds.buffer}),n.push(o.out.buffer,o.outIds.buffer)}self.postMessage({type:`tickResult`,groups:e},n);return}};";
21864
+ var jsContent = "function e(e,t,n=1e-10,r=50){let i=e;for(let a=0;a<r;a++){let r=(i-t*Math.sin(i)-e)/(1-t*Math.cos(i));if(i-=Math.max(-1,Math.min(1,r)),Math.abs(r)<n)break}return i}function t(e,t,n=1e-10,r=50){if(e===0)return 0;let i=e<0?-1:1,a=Math.abs(e),o=0,s=1;for(;t*Math.sinh(s)-s<a;)if(s*=2,s>700)return NaN;let c=.5*(o+s);for(let e=0;e<r;e++){let e=t*Math.sinh(c)-c-a;e>0?s=c:o=c;let r=c-e/(t*Math.cosh(c)-1);r>o&&r<s||(r=.5*(o+s));let i=Math.abs(r-c);if(c=r,i<n)break}return i*c}function n(e,t,n){let r=.01720209895*(n-t)/(Math.sqrt(2)*e**1.5),i=Math.cbrt(3*r+Math.sqrt(1+9*r*r)),a=i-1/i,o=2*Math.atan(a),s=e*(1+a*a);return!isFinite(o)||!isFinite(s)||s<=0?null:{nu:o,r:s}}const r=149597870.7,i=23.4392911;86400**2/r**3,1/r,Math.cos(Math.PI/180*i),Math.sin(Math.PI/180*i);const a=Math.PI,o=a*2,s=a/180;180/a;const c=6378.135,l=60/Math.sqrt(c*c*c/398600.8),u=c*l/60,d=1/l,f=.001082616,p=-253881e-11/f,m=2/3,ee=1440/(2*a);function te(e,t){let n=[31,e%4==0?29:28,31,30,31,30,31,31,30,31,30,31],r=Math.floor(t),i=1,a=0;for(;r>a+n[i-1]&&i<12;)a+=n[i-1],i+=1;let o=i,s=r-a,c=(t-r)*24,l=Math.floor(c);c=(c-l)*60;let u=Math.floor(c);return{mon:o,day:s,hr:l,minute:u,sec:(c-u)*60}}function h(e,t,n,r,i,a,o=0){return 367*e-Math.floor(7*(e+Math.floor((t+9)/12))*.25)+Math.floor(275*t/9)+n+1721013.5+((o/6e4+a/60+i)/60+r)/24}function g(e,t,n,r,i,a,o=0){if(e instanceof Date){let t=e;return h(t.getUTCFullYear(),t.getUTCMonth()+1,t.getUTCDate(),t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds(),t.getUTCMilliseconds())}return h(e,t,n,r,i,a,o)}function ne(e,t){let{e3:n,ee2:r,peo:i,pgho:s,pho:c,pinco:l,plo:u,se2:d,se3:f,sgh2:p,sgh3:m,sgh4:ee,sh2:te,sh3:h,si2:g,si3:ne,sl2:_,sl3:v,sl4:y,t:b,xgh2:re,xgh3:ie,xgh4:x,xh2:ae,xh3:S,xi2:C,xi3:w,xl2:T,xl3:E,xl4:D,zmol:O,zmos:k}=e,{init:A,opsmode:j}=t,{ep:oe,inclp:M,nodep:N,argpp:P,mp:se}=t,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,Y,X,le,ue,de,fe;fe=k+119459e-10*b,A===`y`&&(fe=k),de=fe+2*.01675*Math.sin(fe),X=Math.sin(de),U=.5*X*X-.25,W=-.5*X*Math.cos(de);let pe=d*U+f*W,me=g*U+ne*W,he=_*U+v*W+y*X,ge=p*U+m*W+ee*X,Z=te*U+h*W;fe=O+.00015835218*b,A===`y`&&(fe=O),de=fe+2*.0549*Math.sin(fe),X=Math.sin(de),U=.5*X*X-.25,W=-.5*X*Math.cos(de);let _e=r*U+n*W,ve=C*U+w*W,ye=T*U+E*W+D*X,be=re*U+ie*W+x*X,xe=ae*U+S*W;return G=pe+_e,J=me+ve,Y=he+ye,K=ge+be,q=Z+xe,A===`n`&&(G-=i,J-=l,Y-=u,K-=s,q-=c,M+=J,oe+=G,R=Math.sin(M),L=Math.cos(M),M>=.2?(q/=R,K-=L*q,P+=K,N+=q,se+=Y):(z=Math.sin(N),ce=Math.cos(N),F=R*z,I=R*ce,B=q*ce+J*L*z,V=-q*z+J*L*ce,F+=B,I+=V,N%=o,N<0&&j===`a`&&(N+=o),le=se+P+L*N,H=Y+K-J*N*R,le+=H,ue=N,N=Math.atan2(F,I),N<0&&j===`a`&&(N+=o),Math.abs(ue-N)>a&&(N<ue?N+=o:N-=o),se+=Y,P=le-se-L*N)),{ep:oe,inclp:M,nodep:N,argpp:P,mp:se}}function _(e){let{epoch:t,ep:n,argpp:r,tc:i,inclp:a,nodep:s,np:c}=e,l,u,d,f,p,m,ee,te,h,g,ne,_,v,y,b,re,ie,x,ae,S,C,w,T,E,D,O,k,A,j,oe,M,N,P,se,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,Y,X,le,ue,de,fe,pe,me,he,ge,Z,_e,ve,ye,be=.01675,xe=.0549,Se=c,Ce=n,we=Math.sin(s),Te=Math.cos(s),Ee=Math.sin(r),Q=Math.cos(r),De=Math.sin(a),Oe=Math.cos(a),$=Ce*Ce,ke=1-$,Ae=Math.sqrt(ke),je=t+18261.5+i/1440,Me=(4.523602-.00092422029*je)%o,Ne=Math.sin(Me),Pe=Math.cos(Me),Fe=.91375164-.03568096*Pe,Ie=Math.sqrt(1-Fe*Fe),Le=.089683511*Ne/Ie,Re=Math.sqrt(1-Le*Le),ze=5.8351514+.001944368*je,Be=.39785416*Ne/Ie,Ve=Re*Pe+.91744867*Le*Ne;Be=Math.atan2(Be,Ve),Be+=ze-Me;let He=Math.cos(Be),Ue=Math.sin(Be);S=.1945905,C=-.98088458,E=.91744867,D=.39785416,w=Te,T=we,ne=29864797e-13;let We=1/Se,Ge=0;for(;Ge<2;)Ge+=1,l=S*w+C*E*T,d=-C*w+S*E*T,ee=-S*T+C*E*w,te=C*D,h=C*T+S*E*w,g=S*D,u=Oe*ee+De*te,f=Oe*h+De*g,p=-De*ee+Oe*te,m=-De*h+Oe*g,_=l*Q+u*Ee,v=d*Q+f*Ee,y=-l*Ee+u*Q,b=-d*Ee+f*Q,re=p*Ee,ie=m*Ee,x=p*Q,ae=m*Q,_e=12*_*_-3*y*y,ve=24*_*v-6*y*b,ye=12*v*v-3*b*b,le=3*(l*l+u*u)+_e*$,ue=6*(l*d+u*f)+ve*$,de=3*(d*d+f*f)+ye*$,fe=-6*l*p+$*(-24*_*x-6*y*re),pe=-6*(l*m+d*p)+$*(-24*(v*x+_*ae)+-6*(y*ie+b*re)),me=-6*d*m+$*(-24*v*ae-6*b*ie),he=6*u*p+$*(24*_*re-6*y*x),ge=6*(f*p+u*m)+$*(24*(v*re+_*ie)-6*(b*x+y*ae)),Z=6*f*m+$*(24*v*ie-6*b*ae),le=le+le+ke*_e,ue=ue+ue+ke*ve,de=de+de+ke*ye,K=ne*We,G=-.5*K/Ae,q=K*Ae,W=-15*Ce*q,J=_*y+v*b,Y=v*y+_*b,X=v*b-_*y,Ge===1&&(O=W,k=G,A=K,j=q,oe=J,M=Y,N=X,P=le,se=ue,F=de,I=fe,L=pe,R=me,ce=he,z=ge,B=Z,V=_e,H=ve,U=ye,S=He,C=Ue,E=Fe,D=Ie,w=Re*Te+Le*we,T=we*Re-Te*Le,ne=4.7968065e-7);let Ke=(4.7199672+(.2299715*je-ze))%o,qe=(6.2565837+.017201977*je)%o,Je=2*O*M,Ye=2*O*N,Xe=2*k*L,Ze=2*k*(R-I),Qe=-2*A*se,$e=-2*A*(F-P),et=-2*A*(-21-9*$)*be,tt=2*j*H,nt=2*j*(U-V),rt=-18*j*be,it=-2*k*z,at=-2*k*(B-ce),ot=2*W*Y,st=2*W*X,ct=2*G*pe,lt=2*G*(me-fe),ut=-2*K*ue,dt=-2*K*(de-le),ft=-2*K*(-21-9*$)*xe,pt=2*q*ve,mt=2*q*(ye-_e),ht=-18*q*xe,gt=-2*G*ge,_t=-2*G*(Z-he);return{snodm:we,cnodm:Te,sinim:De,cosim:Oe,sinomm:Ee,cosomm:Q,day:je,e3:st,ee2:ot,em:Ce,emsq:$,gam:ze,peo:0,pgho:0,pho:0,pinco:0,plo:0,rtemsq:Ae,se2:Je,se3:Ye,sgh2:tt,sgh3:nt,sgh4:rt,sh2:it,sh3:at,si2:Xe,si3:Ze,sl2:Qe,sl3:$e,sl4:et,s1:W,s2:G,s3:K,s4:q,s5:J,s6:Y,s7:X,ss1:O,ss2:k,ss3:A,ss4:j,ss5:oe,ss6:M,ss7:N,sz1:P,sz2:se,sz3:F,sz11:I,sz12:L,sz13:R,sz21:ce,sz22:z,sz23:B,sz31:V,sz32:H,sz33:U,xgh2:pt,xgh3:mt,xgh4:ht,xh2:gt,xh3:_t,xi2:ct,xi3:lt,xl2:ut,xl3:dt,xl4:ft,nm:Se,z1:le,z2:ue,z3:de,z11:fe,z12:pe,z13:me,z21:he,z22:ge,z23:Z,z31:_e,z32:ve,z33:ye,zmol:Ke,zmos:qe}}function v(e){let{cosim:t,argpo:n,s1:r,s2:i,s3:s,s4:c,s5:u,sinim:d,ss1:f,ss2:p,ss3:ee,ss4:te,ss5:h,sz1:g,sz3:ne,sz11:_,sz13:v,sz21:y,sz23:b,sz31:re,sz33:ie,t:x,tc:ae,gsto:S,mo:C,mdot:w,no:T,nodeo:E,nodedot:D,xpidot:O,z1:k,z3:A,z11:j,z13:oe,z21:M,z23:N,z31:P,z33:se,ecco:F,eccsq:I}=e,{emsq:L,em:R,argpm:ce,inclm:z,mm:B,nm:V,nodem:H,irez:U,atime:W,d2201:G,d2211:K,d3210:q,d3222:J,d4410:Y,d4422:X,d5220:le,d5232:ue,d5421:de,d5433:fe,dedt:pe,didt:me,dmdt:he,dnodt:ge,domdt:Z,del1:_e,del2:ve,del3:ye,xfact:be,xlamo:xe,xli:Se,xni:Ce}=e,we,Te,Ee,Q,De,Oe,$,ke,Ae,je,Me,Ne,Pe,Fe,Ie,Le,Re,ze,Be,Ve,He,Ue,We,Ge,Ke,qe,Je,Ye,Xe,Ze,Qe,$e,et=.0043752690880113,tt=.00015835218,nt=119459e-10;U=0,V<.0052359877&&V>.0034906585&&(U=1),V>=.00826&&V<=.00924&&R>=.5&&(U=2);let rt=f*nt*h,it=p*nt*(_+v),at=-nt*ee*(g+ne-14-6*L),ot=te*nt*(re+ie-6),st=-nt*p*(y+b);(z<.052359877||z>a-.052359877)&&(st=0),d!==0&&(st/=d);let ct=ot-t*st;pe=rt+r*tt*u,me=it+i*tt*(j+oe),he=at-tt*s*(k+A-14-6*L);let lt=c*tt*(P+se-6),ut=-tt*i*(M+N);(z<.052359877||z>a-.052359877)&&(ut=0),Z=ct+lt,ge=st,d!==0&&(Z-=t/d*ut,ge+=ut/d);let dt=(S+ae*et)%o;if(R+=pe*x,z+=me*x,ce+=Z*x,H+=ge*x,B+=he*x,U!==0){if(Ze=(V/l)**m,U===2){Qe=t*t;let e=R;R=F;let n=L;L=I,$e=R*L,Fe=-.306-(R-.64)*.44,R<=.65?(Ie=3.616-13.247*R+16.29*L,Re=-19.302+117.39*R-228.419*L+156.591*$e,ze=-18.9068+109.7927*R-214.6334*L+146.5816*$e,Be=-41.122+242.694*R-471.094*L+313.953*$e,Ve=-146.407+841.88*R-1629.014*L+1083.435*$e,He=-532.114+3017.977*R-5740.032*L+3708.276*$e):(Ie=-72.099+331.819*R-508.738*L+266.724*$e,Re=-346.844+1582.851*R-2415.925*L+1246.113*$e,ze=-342.585+1554.908*R-2366.899*L+1215.972*$e,Be=-1052.797+4758.686*R-7193.992*L+3651.957*$e,Ve=-3581.69+16178.11*R-24462.77*L+12422.52*$e,He=R>.715?-5149.66+29936.92*R-54087.36*L+31324.56*$e:1464.74-4664.75*R+3763.64*L),R<.7?(Ge=-919.2277+4988.61*R-9064.77*L+5542.21*$e,Ue=-822.71072+4568.6173*R-8491.4146*L+5337.524*$e,We=-853.666+4690.25*R-8624.77*L+5341.4*$e):(Ge=-37995.78+161616.52*R-229838.2*L+109377.94*$e,Ue=-51752.104+218913.95*R-309468.16*L+146349.42*$e,We=-40023.88+170470.89*R-242699.48*L+115605.82*$e),Ke=d*d,we=.75*(1+2*t+Qe),Te=1.5*Ke,Q=1.875*d*(1-2*t-3*Qe),De=-1.875*d*(1+2*t-3*Qe),$=35*Ke*we,ke=39.375*Ke*Ke,Ae=9.84375*d*(Ke*(1-2*t-5*Qe)+.33333333*(-2+4*t+6*Qe)),je=d*(4.92187512*Ke*(-2-4*t+10*Qe)+6.56250012*(1+2*t-3*Qe)),Me=29.53125*d*(2-8*t+Qe*(-12+8*t+10*Qe)),Ne=29.53125*d*(-2-8*t+Qe*(12+8*t-10*Qe)),Ye=V*V,Xe=Ze*Ze,Je=3*Ye*Xe,qe=Je*17891679e-13,G=qe*we*Fe,K=qe*Te*Ie,Je*=Ze,qe=Je*3.7393792e-7,q=qe*Q*Re,J=qe*De*ze,Je*=Ze,qe=2*Je*7.3636953e-9,Y=qe*$*Be,X=qe*ke*Ve,Je*=Ze,qe=Je*1.1428639e-7,le=qe*Ae*He,ue=qe*je*We,qe=2*Je*2.1765803e-9,de=qe*Me*Ue,fe=qe*Ne*Ge,xe=(C+E+E-(dt+dt))%o,be=w+he+2*(D+ge-et)-T,R=e,L=n}U===1&&(Pe=1+L*(-2.5+.8125*L),Re=1+2*L,Le=1+L*(-6+6.60937*L),we=.75*(1+t)*(1+t),Ee=.9375*d*d*(1+3*t)-.75*(1+t),Oe=1+t,Oe*=1.875*Oe*Oe,_e=3*V*V*Ze*Ze,ve=2*_e*we*Pe*17891679e-13,ye=3*_e*Oe*Le*2.2123015e-7*Ze,_e=_e*Ee*Re*21460748e-13*Ze,xe=(C+E+n-dt)%o,be=w+O+he+Z+ge-(T+et)),Se=xe,Ce=T,W=0,V=T+0}return{em:R,argpm:ce,inclm:z,mm:B,nm:V,nodem:H,irez:U,atime:W,d2201:G,d2211:K,d3210:q,d3222:J,d4410:Y,d4422:X,d5220:le,d5232:ue,d5421:de,d5433:fe,dedt:pe,didt:me,dmdt:he,dndt:0,dnodt:ge,domdt:Z,del1:_e,del2:ve,del3:ye,xfact:be,xlamo:xe,xli:Se,xni:Ce}}function y(e){let t=(e-2451545)/36525,n=-62e-7*t*t*t+.093104*t*t+3164400184.812866*t+67310.54841;return n=n*s/240%o,n<0&&(n+=o),n}function b(e,t,n,r,i,a,o){return e instanceof Date?y(g(e)):y(t===void 0?e:g(e,t,n,r,i,a,o))}function re(e){let{ecco:t,epoch:n,inclo:r,opsmode:i}=e,{no:a}=e,s=t*t,c=1-s,u=Math.sqrt(c),d=Math.cos(r),p=d*d,ee=(l/a)**m,te=.75*f*(3*p-1)/(u*c),h=te/(ee*ee),g=ee*(1-h*h-h*(1/3+134*h*h/81));h=te/(g*g),a/=1+h;let ne=(l/a)**m,_=Math.sin(r),v=ne*c,y=1-5*p,re=-y-p-p,ie=1/ne,x=v*v,ae=ne*(1-t),S;if(i===`a`){let e=n-7305,t=Math.floor(e+1e-8),r=e-t,i=.017202791694070362,a=i+o;S=(1.7321343856509375+i*t+a*r+e*e*5075514194322695e-30)%o,S<0&&(S+=o)}else S=b(n+2433281.5);return{no:a,method:`n`,ainv:ie,ao:ne,con41:re,con42:y,cosio:d,cosio2:p,eccsq:s,omeosq:c,posq:x,rp:ae,rteosq:u,sinio:_,gsto:S}}function ie(e){let{irez:t,d2201:n,d2211:r,d3210:i,d3222:a,d4410:s,d4422:c,d5220:l,d5232:u,d5421:d,d5433:f,dedt:p,del1:m,del2:ee,del3:te,didt:h,dmdt:g,dnodt:ne,domdt:_,argpo:v,argpdot:y,t:b,tc:re,gsto:ie,xfact:x,xlamo:ae,no:S}=e,{atime:C,em:w,argpm:T,inclm:E,xli:D,mm:O,xni:k,nodem:A,nm:j}=e,oe=.13130908,M=2.8843198,N=.37448087,P=5.7686396,se=.95240898,F=1.8014998,I=1.050833,L=4.4108898,R=259200,ce,z,B,V,H,U,W,G,K=0,q=0,J=(ie+re*.0043752690880113)%o;if(w+=p*b,E+=h*b,T+=_*b,A+=ne*b,O+=g*b,t!==0){(C===0||b*C<=0||Math.abs(b)<Math.abs(C))&&(C=0,k=S,D=ae),ce=b>0?720:-720;let e=381;for(;e===381;)t===2?(G=v+y*C,B=G+G,z=D+D,W=n*Math.sin(B+D-P)+r*Math.sin(D-P)+i*Math.sin(G+D-se)+a*Math.sin(-G+D-se)+s*Math.sin(B+z-F)+c*Math.sin(z-F)+l*Math.sin(G+D-I)+u*Math.sin(-G+D-I)+d*Math.sin(G+z-L)+f*Math.sin(-G+z-L),H=k+x,U=n*Math.cos(B+D-P)+r*Math.cos(D-P)+i*Math.cos(G+D-se)+a*Math.cos(-G+D-se)+l*Math.cos(G+D-I)+u*Math.cos(-G+D-I)+2*(s*Math.cos(B+z-F)+c*Math.cos(z-F)+d*Math.cos(G+z-L)+f*Math.cos(-G+z-L)),U*=H):(W=m*Math.sin(D-oe)+ee*Math.sin(2*(D-M))+te*Math.sin(3*(D-N)),H=k+x,U=m*Math.cos(D-oe)+2*ee*Math.cos(2*(D-M))+3*te*Math.cos(3*(D-N)),U*=H),Math.abs(b-C)>=720?e=381:(q=b-C,e=0),e===381&&(D+=H*ce+W*R,k+=W*ce+U*R,C+=ce);j=k+W*q+U*q*q*.5,V=D+H*q+W*q*q*.5,t===1?(O=V-A-T+J,K=j-S):(O=V-2*A+2*J,K=j-S),j=S+K}return{atime:C,em:w,argpm:T,inclm:E,xli:D,mm:O,xni:k,nodem:A,dndt:K,nm:j}}var x;(function(e){e[e.None=0]=`None`,e[e.MeanEccentricityOutOfRange=1]=`MeanEccentricityOutOfRange`,e[e.MeanMotionBelowZero=2]=`MeanMotionBelowZero`,e[e.PerturbedEccentricityOutOfRange=3]=`PerturbedEccentricityOutOfRange`,e[e.SemiLatusRectumBelowZero=4]=`SemiLatusRectumBelowZero`,e[e.Decayed=6]=`Decayed`})(x||={});function ae(e,t){let n,r,i,s,d,ee,te,h,g,_,v,y,b,re,ae,S,C,w,T,E,D,O,k,A,j,oe,M;e.t=t,e.error=x.None;let N=e.mo+e.mdot*e.t,P=e.argpo+e.argpdot*e.t,se=e.nodeo+e.nodedot*e.t;g=P,D=N;let F=e.t*e.t;if(k=se+e.nodecf*F,C=1-e.cc1*e.t,w=e.bstar*e.cc4*e.t,T=e.t2cof*F,e.isimp!==1){te=e.omgcof*e.t;let t=1+e.eta*Math.cos(N);ee=e.xmcof*(t*t*t-e.delmo),S=te+ee,D=N+S,g=P-S,y=F*e.t,b=y*e.t,C=C-e.d2*F-e.d3*y-e.d4*b,w+=e.bstar*e.cc5*(Math.sin(D)-e.sinmao),T=T+e.t3cof*y+b*(e.t4cof+e.t*e.t5cof)}O=e.no;let I=e.ecco;if(E=e.inclo,e.method===`d`){re=e.t;let t=ie({irez:e.irez,d2201:e.d2201,d2211:e.d2211,d3210:e.d3210,d3222:e.d3222,d4410:e.d4410,d4422:e.d4422,d5220:e.d5220,d5232:e.d5232,d5421:e.d5421,d5433:e.d5433,dedt:e.dedt,del1:e.del1,del2:e.del2,del3:e.del3,didt:e.didt,dmdt:e.dmdt,dnodt:e.dnodt,domdt:e.domdt,argpo:e.argpo,argpdot:e.argpdot,t:e.t,tc:re,gsto:e.gsto,xfact:e.xfact,xlamo:e.xlamo,no:e.no,atime:e.atime,em:I,argpm:g,inclm:E,xli:e.xli,mm:D,xni:e.xni,nodem:k,nm:O});({em:I,argpm:g,inclm:E,mm:D,nodem:k,nm:O}=t)}if(O<=0)return e.error=x.MeanMotionBelowZero,null;let L=(l/O)**m*C*C;if(O=l/L**1.5,I-=w,I>=1||I<-.001)return e.error=x.MeanEccentricityOutOfRange,null;I<1e-6&&(I=1e-6),D+=e.no*T,j=D+g+k,k%=o,g%=o,j%=o,D=(j-g-k)%o;let R={am:L,em:I,im:E,Om:k,om:g,mm:D,nm:O},ce=Math.sin(E),z=Math.cos(E),B=I;if(A=E,_=g,M=k,oe=D,s=ce,i=z,e.method===`d`){let t=ne(e,{inclo:e.inclo,init:`n`,ep:B,inclp:A,nodep:M,argpp:_,mp:oe,opsmode:e.operationmode});if({ep:B,nodep:M,argpp:_,mp:oe}=t,A=t.inclp,A<0&&(A=-A,M+=a,_-=a),B<0||B>1)return e.error=x.PerturbedEccentricityOutOfRange,null}e.method===`d`&&(s=Math.sin(A),i=Math.cos(A),e.aycof=-.5*p*s,Math.abs(i+1)>15e-13?e.xlcof=-.25*p*s*(3+5*i)/(1+i):e.xlcof=-.25*p*s*(3+5*i)/15e-13);let V=B*Math.cos(_);S=1/(L*(1-B*B));let H=B*Math.sin(_)+S*e.aycof,U=(oe+_+M+S*e.xlcof*V-M)%o;h=U,ae=9999.9;let W=1;for(;Math.abs(ae)>=1e-12&&W<=10;)r=Math.sin(h),n=Math.cos(h),ae=1-n*V-r*H,ae=(U-H*n+V*r-h)/ae,Math.abs(ae)>=.95&&(ae=ae>0?.95:-.95),h+=ae,W+=1;let G=V*n+H*r,K=V*r-H*n,q=V*V+H*H,J=L*(1-q);if(J<0)return e.error=x.SemiLatusRectumBelowZero,null;let Y=L*(1-G),X=Math.sqrt(L)*K/Y,le=Math.sqrt(J)/Y,ue=Math.sqrt(1-q);S=K/(1+ue);let de=L/Y*(r-H-V*S),fe=L/Y*(n-V+H*S);v=Math.atan2(de,fe);let pe=(fe+fe)*de,me=1-2*de*de;S=1/J;let he=.5*f*S,ge=he*S;e.method===`d`&&(d=i*i,e.con41=3*d-1,e.x1mth2=1-d,e.x7thm1=7*d-1);let Z=Y*(1-1.5*ge*ue*e.con41)+.5*he*e.x1mth2*me;if(Z<1)return e.error=x.Decayed,null;v-=.25*ge*e.x7thm1*pe;let _e=M+1.5*ge*i*pe,ve=A+1.5*ge*i*s*me,ye=X-O*he*e.x1mth2*pe/l,be=le+O*he*(e.x1mth2*me+1.5*e.con41)/l,xe=Math.sin(v),Se=Math.cos(v),Ce=Math.sin(_e),we=Math.cos(_e),Te=Math.sin(ve),Ee=Math.cos(ve),Q=-Ce*Ee,De=we*Ee,Oe=Q*xe+we*Se,$=De*xe+Ce*Se,ke=Te*xe,Ae=Q*Se-we*xe,je=De*Se-Ce*xe,Me=Te*Se;return{position:{x:Z*Oe*c,y:Z*$*c,z:Z*ke*c},velocity:{x:(ye*Oe+be*Ae)*u,y:(ye*$+be*je)*u,z:(ye*ke+be*Me)*u},meanElements:R}}function S(e,t){let{opsmode:n,satn:r,epoch:i,xbstar:o,xecco:s,xargpo:l,xinclo:u,xmo:ee,xno:te,xnodeo:h}=t,g,y,b,ie,x,S,C,w,T,E,D,O,k,A,j,oe,M,N,P,se,F,I,L,R,ce,z,B,V,H,U,W,G,K,q,J,Y,X,le,ue,de,fe,pe,me,he,ge,Z,_e,ve,ye,be,xe,Se,Ce,we,Te,Ee,Q=e;Q.isimp=0,Q.method=`n`,Q.aycof=0,Q.con41=0,Q.cc1=0,Q.cc4=0,Q.cc5=0,Q.d2=0,Q.d3=0,Q.d4=0,Q.delmo=0,Q.eta=0,Q.argpdot=0,Q.omgcof=0,Q.sinmao=0,Q.t=0,Q.t2cof=0,Q.t3cof=0,Q.t4cof=0,Q.t5cof=0,Q.x1mth2=0,Q.x7thm1=0,Q.mdot=0,Q.nodedot=0,Q.xlcof=0,Q.xmcof=0,Q.nodecf=0,Q.irez=0,Q.d2201=0,Q.d2211=0,Q.d3210=0,Q.d3222=0,Q.d4410=0,Q.d4422=0,Q.d5220=0,Q.d5232=0,Q.d5421=0,Q.d5433=0,Q.dedt=0,Q.del1=0,Q.del2=0,Q.del3=0,Q.didt=0,Q.dmdt=0,Q.dnodt=0,Q.domdt=0,Q.e3=0,Q.ee2=0,Q.peo=0,Q.pgho=0,Q.pho=0,Q.pinco=0,Q.plo=0,Q.se2=0,Q.se3=0,Q.sgh2=0,Q.sgh3=0,Q.sgh4=0,Q.sh2=0,Q.sh3=0,Q.si2=0,Q.si3=0,Q.sl2=0,Q.sl3=0,Q.sl4=0,Q.gsto=0,Q.xfact=0,Q.xgh2=0,Q.xgh3=0,Q.xgh4=0,Q.xh2=0,Q.xh3=0,Q.xi2=0,Q.xi3=0,Q.xl2=0,Q.xl3=0,Q.xl4=0,Q.xlamo=0,Q.zmol=0,Q.zmos=0,Q.atime=0,Q.xli=0,Q.xni=0,Q.bstar=o,Q.ecco=s,Q.argpo=l,Q.inclo=u,Q.mo=ee,Q.no=te,Q.nodeo=h,Q.operationmode=n,78/c+1;let De=42/c;De*De*De*De,Q.init=`y`,Q.t=0;let Oe=re({satn:r,ecco:Q.ecco,epoch:i,inclo:Q.inclo,no:Q.no,method:Q.method,opsmode:Q.operationmode}),{ao:$,con42:ke,cosio:Ae,cosio2:je,eccsq:Me,omeosq:Ne,posq:Pe,rp:Fe,rteosq:Ie,sinio:Le}=Oe;if(Q.no=Oe.no,Q.con41=Oe.con41,Q.gsto=Oe.gsto,Q.a=(Q.no*d)**(-2/3),Q.alta=Q.a*(1+Q.ecco)-1,Q.altp=Q.a*(1-Q.ecco)-1,Q.error=0,Ne>=0||Q.no>=0){if(Q.isimp=0,Fe<1.034492841559484&&(Q.isimp=1),B=1.0122292801892716,F=1.880279159015271e-9,N=(Fe-1)*c,N<156){B=N-78,N<98&&(B=20);let e=(120-B)/c;F=e*e*e*e,B=B/c+1}P=1/Pe,Z=1/($-B),Q.eta=$*Q.ecco*Z,O=Q.eta*Q.eta,D=Q.ecco*Q.eta,se=Math.abs(1-O),S=F*Z**4,C=S/se**3.5,ie=C*Q.no*($*(1+1.5*O+D*(4+O))+.375*f*Z/se*Q.con41*(8+3*O*(8+O))),Q.cc1=Q.bstar*ie,x=0,Q.ecco>1e-4&&(x=-2*S*Z*p*Q.no*Le/Q.ecco),Q.x1mth2=1-je,Q.cc4=2*Q.no*C*$*Ne*(Q.eta*(2+.5*O)+Q.ecco*(.5+2*O)-f*Z/($*se)*(-3*Q.con41*(1-2*D+O*(1.5-.5*D))+.75*Q.x1mth2*(2*O-D*(1+O))*Math.cos(2*Q.argpo))),Q.cc5=2*C*$*Ne*(1+2.75*(O+D)+D*O),w=je*je,me=1.5*f*P*Q.no,he=.5*me*f*P,ge=-.46875*-165597e-11*P*P*Q.no,Q.mdot=Q.no+.5*me*Ie*Q.con41+.0625*he*Ie*(13-78*je+137*w),Q.argpdot=-.5*me*ke+.0625*he*(7-114*je+395*w)+ge*(3-36*je+49*w),ve=-me*Ae,Q.nodedot=ve+(.5*he*(4-19*je)+2*ge*(3-7*je))*Ae,_e=Q.argpdot+Q.nodedot,Q.omgcof=Q.bstar*x*Math.cos(Q.argpo),Q.xmcof=0,Q.ecco>1e-4&&(Q.xmcof=-m*S*Q.bstar/D),Q.nodecf=3.5*Ne*ve*Q.cc1,Q.t2cof=1.5*Q.cc1,Math.abs(Ae+1)>15e-13?Q.xlcof=-.25*p*Le*(3+5*Ae)/(1+Ae):Q.xlcof=-.25*p*Le*(3+5*Ae)/15e-13,Q.aycof=-.5*p*Le;let e=1+Q.eta*Math.cos(Q.mo);if(Q.delmo=e*e*e,Q.sinmao=Math.sin(Q.mo),Q.x7thm1=7*je-1,2*a/Q.no>=225){Q.method=`d`,Q.isimp=1,fe=0,j=Q.inclo;let e=_({epoch:i,ep:Q.ecco,argpp:Q.argpo,tc:fe,inclp:Q.inclo,nodep:Q.nodeo,np:Q.no,e3:Q.e3,ee2:Q.ee2,peo:Q.peo,pgho:Q.pgho,pho:Q.pho,pinco:Q.pinco,plo:Q.plo,se2:Q.se2,se3:Q.se3,sgh2:Q.sgh2,sgh3:Q.sgh3,sgh4:Q.sgh4,sh2:Q.sh2,sh3:Q.sh3,si2:Q.si2,si3:Q.si3,sl2:Q.sl2,sl3:Q.sl3,sl4:Q.sl4,xgh2:Q.xgh2,xgh3:Q.xgh3,xgh4:Q.xgh4,xh2:Q.xh2,xh3:Q.xh3,xi2:Q.xi2,xi3:Q.xi3,xl2:Q.xl2,xl3:Q.xl3,xl4:Q.xl4,zmol:Q.zmol,zmos:Q.zmos});Q.e3=e.e3,Q.ee2=e.ee2,Q.peo=e.peo,Q.pgho=e.pgho,Q.pho=e.pho,Q.pinco=e.pinco,Q.plo=e.plo,Q.se2=e.se2,Q.se3=e.se3,Q.sgh2=e.sgh2,Q.sgh3=e.sgh3,Q.sgh4=e.sgh4,Q.sh2=e.sh2,Q.sh3=e.sh3,Q.si2=e.si2,Q.si3=e.si3,Q.sl2=e.sl2,Q.sl3=e.sl3,Q.sl4=e.sl4,{sinim:y,cosim:g,em:T,emsq:E,s1:I,s2:L,s3:R,s4:ce,s5:z,ss1:V,ss2:H,ss3:U,ss4:W,ss5:G,sz1:K,sz3:q,sz11:J,sz13:Y,sz21:X,sz23:le,sz31:ue,sz33:de}=e,Q.xgh2=e.xgh2,Q.xgh3=e.xgh3,Q.xgh4=e.xgh4,Q.xh2=e.xh2,Q.xh3=e.xh3,Q.xi2=e.xi2,Q.xi3=e.xi3,Q.xl2=e.xl2,Q.xl3=e.xl3,Q.xl4=e.xl4,Q.zmol=e.zmol,Q.zmos=e.zmos,{nm:M,z1:ye,z3:be,z11:xe,z13:Se,z21:Ce,z23:we,z31:Te,z33:Ee}=e;let t=ne(Q,{inclo:j,init:Q.init,ep:Q.ecco,inclp:Q.inclo,nodep:Q.nodeo,argpp:Q.argpo,mp:Q.mo,opsmode:Q.operationmode});Q.ecco=t.ep,Q.inclo=t.inclp,Q.nodeo=t.nodep,Q.argpo=t.argpp,Q.mo=t.mp,k=0,A=0,oe=0;let n=v({cosim:g,emsq:E,argpo:Q.argpo,s1:I,s2:L,s3:R,s4:ce,s5:z,sinim:y,ss1:V,ss2:H,ss3:U,ss4:W,ss5:G,sz1:K,sz3:q,sz11:J,sz13:Y,sz21:X,sz23:le,sz31:ue,sz33:de,t:Q.t,tc:fe,gsto:Q.gsto,mo:Q.mo,mdot:Q.mdot,no:Q.no,nodeo:Q.nodeo,nodedot:Q.nodedot,xpidot:_e,z1:ye,z3:be,z11:xe,z13:Se,z21:Ce,z23:we,z31:Te,z33:Ee,ecco:Q.ecco,eccsq:Me,em:T,argpm:k,inclm:j,mm:oe,nm:M,nodem:A,irez:Q.irez,atime:Q.atime,d2201:Q.d2201,d2211:Q.d2211,d3210:Q.d3210,d3222:Q.d3222,d4410:Q.d4410,d4422:Q.d4422,d5220:Q.d5220,d5232:Q.d5232,d5421:Q.d5421,d5433:Q.d5433,dedt:Q.dedt,didt:Q.didt,dmdt:Q.dmdt,dnodt:Q.dnodt,domdt:Q.domdt,del1:Q.del1,del2:Q.del2,del3:Q.del3,xfact:Q.xfact,xlamo:Q.xlamo,xli:Q.xli,xni:Q.xni});Q.irez=n.irez,Q.atime=n.atime,Q.d2201=n.d2201,Q.d2211=n.d2211,Q.d3210=n.d3210,Q.d3222=n.d3222,Q.d4410=n.d4410,Q.d4422=n.d4422,Q.d5220=n.d5220,Q.d5232=n.d5232,Q.d5421=n.d5421,Q.d5433=n.d5433,Q.dedt=n.dedt,Q.didt=n.didt,Q.dmdt=n.dmdt,Q.dnodt=n.dnodt,Q.domdt=n.domdt,Q.del1=n.del1,Q.del2=n.del2,Q.del3=n.del3,Q.xfact=n.xfact,Q.xlamo=n.xlamo,Q.xli=n.xli,Q.xni=n.xni}Q.isimp!==1&&(b=Q.cc1*Q.cc1,Q.d2=4*$*Z*b,pe=Q.d2*Z*Q.cc1/3,Q.d3=(17*$+B)*pe,Q.d4=.5*pe*$*Z*(221*$+31*B)*Q.cc1,Q.t3cof=Q.d2+2*b,Q.t4cof=.25*(3*Q.d3+Q.cc1*(12*Q.d2+10*b)),Q.t5cof=.2*(3*Q.d4+12*Q.cc1*Q.d3+6*Q.d2*Q.d2+15*b*(2*Q.d2+b)))}ae(Q,0),Q.init=`n`}function C(e,t=`i`){let n=e.NORAD_CAT_ID.toString(),r=new Date(e.EPOCH.endsWith(`Z`)?e.EPOCH:`${e.EPOCH}Z`),i=r.getUTCFullYear(),a=Number(i.toString().slice(-2)),o=(r.valueOf()-new Date(Date.UTC(i,0,1,0,0,0)).valueOf())/(86400*1e3)+1,c=Number(e.MEAN_MOTION_DOT),l=Number(e.MEAN_MOTION_DDOT);c/=ee*1440,l/=ee*1440*1440;let u=Number(e.BSTAR),d=Number(e.INCLINATION)*s,f=Number(e.RA_OF_ASC_NODE)*s,p=Number(e.ECCENTRICITY),m=Number(e.ARG_OF_PERICENTER)*s,h=Number(e.MEAN_ANOMALY)*s,ne=Number(e.MEAN_MOTION)/ee,{mon:_,day:v,hr:y,minute:b,sec:re}=te(i,o),ie=g(i,_,v,y,b,re),x={error:0,satnum:n,epochyr:a,epochdays:o,ndot:c,nddot:l,bstar:u,inclo:d,nodeo:f,ecco:p,argpo:m,mo:h,no:ne,jdsatepoch:ie};return S(x,{opsmode:t,satn:x.satnum,epoch:x.jdsatepoch-2433281.5,xbstar:x.bstar,xecco:x.ecco,xargpo:x.argpo,xinclo:x.inclo,xmo:x.mo,xno:x.no,xnodeo:x.nodeo}),x}3*Float64Array.BYTES_PER_ELEMENT;const w=2451545,T=86400,E=[[2441317.5,10],[2441499.5,11],[2441683.5,12],[2442048.5,13],[2442413.5,14],[2442778.5,15],[2443144.5,16],[2443509.5,17],[2443874.5,18],[2444239.5,19],[2444786.5,20],[2445151.5,21],[2445516.5,22],[2446247.5,23],[2447161.5,24],[2447892.5,25],[2448257.5,26],[2448804.5,27],[2449169.5,28],[2449534.5,29],[2450083.5,30],[2450630.5,31],[2451179.5,32],[2453736.5,33],[2454832.5,34],[2456109.5,35],[2457204.5,36],[2457754.5,37]];function D(e){for(let t=E.length-1;t>=0;t--)if(e>=E[t][0])return E[t][1];return E[0][1]-1}function O(e){let t=6.239996+1.99096871e-7*(e-w)*T;return .001657*Math.sin(t+.01671*Math.sin(t))}function k(e){if(!Number.isFinite(e))return e;let t=e+(D(e)+32.184)/T;return t+O(t)/T}const A=Math.PI/180,j=A/3600,oe=1e-4*j,M=[[0,0,0,0,1,-171996,-174.2,92025,8.9],[0,0,2,-2,2,-13187,-1.6,5736,-3.1],[0,0,2,0,2,-2274,-.2,977,-.5],[0,0,0,0,2,2062,.2,-895,.5],[0,1,0,0,0,1426,-3.4,54,-.1],[1,0,0,0,0,712,.1,-7,0],[0,1,2,-2,2,-517,1.2,224,-.6],[0,0,2,0,1,-386,-.4,200,0],[1,0,2,0,2,-301,0,129,-.1],[0,-1,2,-2,2,217,-.5,-95,.3],[1,0,0,-2,0,-158,0,-1,0],[0,0,2,-2,1,129,.1,-70,0],[-1,0,2,0,2,123,0,-53,0],[1,0,0,0,1,63,.1,-33,0],[0,0,0,2,0,63,0,-2,0],[-1,0,2,2,2,-59,0,26,0],[-1,0,0,0,1,-58,-.1,32,0],[1,0,2,0,1,-51,0,27,0]];function N(e,t){let n=Array(9);for(let r=0;r<3;r++)for(let i=0;i<3;i++)n[3*r+i]=e[3*r]*t[i]+e[3*r+1]*t[3+i]+e[3*r+2]*t[6+i];return n}function P(e){let t=Math.cos(e),n=Math.sin(e);return[1,0,0,0,t,n,0,-n,t]}function se(e){let t=Math.cos(e),n=Math.sin(e);return[t,0,-n,0,1,0,n,0,t]}function F(e){let t=Math.cos(e),n=Math.sin(e);return[t,n,0,-n,t,0,0,0,1]}function I(e){let t=(e-w)/36525,n=(2306.2181*t+.30188*t*t+.017998*t**3)*j,r=(2004.3109*t-.42665*t*t-.041833*t**3)*j,a=(2306.2181*t+1.09468*t*t+.018203*t**3)*j,o=134.96298139+477198.8673981*t+.0086972*t*t,s=357.52772333+35999.05034*t-1603e-7*t*t,c=93.27191028+483202.0175381*t-.0036825*t*t,l=297.85036306+445267.11148*t-.0019142*t*t,u=125.04452222-1934.1362608*t+.0020708*t*t,d=0,f=0;for(let[e,n,r,i,a,p,m,ee,te]of M){let h=(e*o+n*s+r*c+i*l+a*u)*A;d+=(p+m*t)*Math.sin(h),f+=(ee+te*t)*Math.cos(h)}d*=oe,f*=oe;let p=(84381.448-46.815*t-59e-5*t*t+.001813*t**3)*j,m=d*Math.cos(p),ee=N(N(N(N(F(n),se(-r)),F(a)),N(N(P(-p),F(d)),P(p+f))),F(-m));return N(P(i*A),ee)}let L=NaN,R=[1,0,0,0,1,0,0,0,1];function ce(e){return Math.abs(e-L)<=.01||(R=I(e),L=e),R}function z(e){return new Date((e-2440587.5)*864e5).toISOString()}function B(e,t){let n=C({OBJECT_NAME:t??String(e.noradCatId),OBJECT_ID:String(e.noradCatId),EPOCH:z(e.epochJd),MEAN_MOTION:e.meanMotion,ECCENTRICITY:e.eccentricity,INCLINATION:e.inclination,RA_OF_ASC_NODE:e.raOfAscNode,ARG_OF_PERICENTER:e.argOfPericenter,MEAN_ANOMALY:e.meanAnomaly,EPHEMERIS_TYPE:0,CLASSIFICATION_TYPE:`U`,NORAD_CAT_ID:e.noradCatId,ELEMENT_SET_NO:e.elementSetNo,REV_AT_EPOCH:e.revAtEpoch,BSTAR:e.bstar,MEAN_MOTION_DOT:e.meanMotionDot,MEAN_MOTION_DDOT:e.meanMotionDdot});return n.error===x.None?n:(console.warn(`buildSatrec: SGP4 init failed for NORAD ${e.noradCatId} (error=${n.error})`),null)}const V=Math.PI/180;Math.cos(i*V),Math.sin(i*V);const H=Math.PI/180,U=Math.cos(i*H),W=Math.sin(i*H),G=10/r;function K(e,t,n,r,i){return!isFinite(Math.fround(n))||!isFinite(Math.fround(r))||!isFinite(Math.fround(i))?!1:(e[t*3]=n,e[t*3+1]=r,e[t*3+2]=i,!0)}function q(r,i,a,o,s,c,l,u,d,f=null,p=0){if(i<r.validityStart||i>r.validityEnd)return 0;let{count:m,kind:ee,equatorial:te,a:h,e:g,i:ne,om:_,w:v,ma:y,n:b,epoch:re,q:ie,tp:S,satrec:C,flags:T,visibleFromDays:E}=r,D=p!==0&&r.applyFlagFilter,O=r.pickBase,A=d.length/3|0,j=i-w,oe=k(i),M=ce(i),N=0;for(let w=0;w<m&&!(N>=A);w++){let m=ee[w];if(m===0||E[w]>j||D&&(T[w]&p)!==p)continue;if(m===3){let e=C[w];if(!e){if(e=B({noradCatId:r.satnum[w],epochJd:re[w],meanMotion:b[w]/360,eccentricity:g[w],inclination:ne[w],raOfAscNode:_[w],argOfPericenter:v[w],meanAnomaly:y[w],bstar:r.bstar[w],meanMotionDot:r.ndot[w],meanMotionDdot:r.nddot[w],elementSetNo:0,revAtEpoch:0}),!e){ee[w]=0;continue}C[w]=e}let t=(i-e.jdsatepoch)*1440,n=ae(e,t);if(!n||e.error!==x.None)continue;let p=n.position.x*G,m=n.position.y*G,te=n.position.z*G,h=M[0]*p+M[1]*m+M[2]*te,ie=M[3]*p+M[4]*m+M[5]*te,S=M[6]*p+M[7]*m+M[8]*te,T=-ie;if(!K(d,N,a+h-c,o+S-l,s+T-u))continue;if(f){let e=O+w,t=N*4;f[t]=e&255,f[t+1]=e>>>8&255,f[t+2]=e>>>16&255,f[t+3]=e>>>24&255}N++;continue}let k,A,P=g[w],se=ne[w],F=_[w],I=v[w];if(m===2){let e=n(ie[w],S[w],te[w]?i:oe);if(!e)continue;k=e.r*Math.cos(e.nu),A=e.r*Math.sin(e.nu)}else{let n=h[w];if(!isFinite(n)||!isFinite(P)||!isFinite(y[w])||!isFinite(b[w]))continue;let r=(te[w]?i:oe)-re[w],a=(y[w]+b[w]*r)*H,o,s;if(P<1||n>0){let t=Math.min(P,.9999999),r=e(a,t),i=Math.sqrt(1-t*t)*Math.sin(r)/(1-t*Math.cos(r)),c=(Math.cos(r)-t)/(1-t*Math.cos(r));o=Math.atan2(i,c),s=n*(1-t*Math.cos(r))}else{let e=t(a,P);if(!isFinite(e))continue;let r=P*Math.cosh(e)-1;if(Math.abs(r)<1e-15)continue;let i=Math.sqrt(P*P-1)*Math.sinh(e)/r,c=(P-Math.cosh(e))/r;o=Math.atan2(i,c),s=n*(1-P*Math.cosh(e))}k=s*Math.cos(o),A=s*Math.sin(o)}if(!isFinite(k)||!isFinite(A))continue;let L=Math.cos(I*H),R=Math.sin(I*H),ce=Math.cos(se*H),z=Math.sin(se*H),V=Math.cos(F*H),q=Math.sin(F*H),J=(V*L-q*R*ce)*k+(-V*R-q*L*ce)*A,Y=(q*L+V*R*ce)*k+(-q*R+V*L*ce)*A,X=R*z*k+L*z*A;if(te[w]){let e=Y*U+X*W,t=-Y*W+X*U;Y=e,X=t}let le=J*10,ue=X*10,de=-Y*10;if(K(d,N,a+le-c,o+ue-l,s+de-u)){if(f){let e=O+w,t=N*4;f[t]=e&255,f[t+1]=e>>>8&255,f[t+2]=e>>>16&255,f[t+3]=e>>>24&255}N++}}return N}const J=new Map;self.onmessage=e=>{let t=e.data;if(t.type===`ping`){self.postMessage({type:`pong`});return}if(t.type===`rewireDelta`){if(t.remove)for(let e of t.remove)J.delete(e);for(let e of t.set)J.set(e.id,e.cols);return}if(t.type===`tick`){let e=[],n=[],[r,i,a]=t.basis;for(let o of t.groups){let s=J.get(o.id);if(!s){e.push({id:o.id,count:0,buf:o.out.buffer,idbuf:o.outIds.buffer}),n.push(o.out.buffer,o.outIds.buffer);continue}let c=q(s,t.jd,o.parent[0],o.parent[1],o.parent[2],r,i,a,o.out,o.outIds,t.requiredFlags??0);e.push({id:o.id,count:c,buf:o.out.buffer,idbuf:o.outIds.buffer}),n.push(o.out.buffer,o.outIds.buffer)}self.postMessage({type:`tickResult`,groups:e},n);return}};";
20769
21865
  var blob = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent], { type: "text/javascript;charset=utf-8" });
20770
21866
  function WorkerWrapper(options) {
20771
21867
  let objURL;
@@ -22247,8 +23343,8 @@ function between(points, jds, jd, from = 0, to = points.length) {
22247
23343
  //#endregion
22248
23344
  //#region src/lib/math/travel/state.ts
22249
23345
  var DEG2RAD$1 = Math.PI / 180;
22250
- var COS_EPS$1 = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
22251
- var SIN_EPS$1 = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
23346
+ var COS_EPS = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
23347
+ var SIN_EPS = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
22252
23348
  function planeRotation(w, i, om, equatorial) {
22253
23349
  const cosW = Math.cos(w * DEG2RAD$1);
22254
23350
  const sinW = Math.sin(w * DEG2RAD$1);
@@ -22271,8 +23367,8 @@ function perifocalToEcliptic(px, py, rot) {
22271
23367
  let y = rot.m21 * px + rot.m22 * py;
22272
23368
  let z = rot.m31 * px + rot.m32 * py;
22273
23369
  if (rot.equatorial) {
22274
- const yEcl = y * COS_EPS$1 + z * SIN_EPS$1;
22275
- const zEcl = -y * SIN_EPS$1 + z * COS_EPS$1;
23370
+ const yEcl = y * COS_EPS + z * SIN_EPS;
23371
+ const zEcl = -y * SIN_EPS + z * COS_EPS;
22276
23372
  y = yEcl;
22277
23373
  z = zEcl;
22278
23374
  }
@@ -22292,11 +23388,12 @@ function eclipticToScene(r) {
22292
23388
  }
22293
23389
  function elementsToState(el, jd, muKm3S2) {
22294
23390
  const { a, e, ma, n, epoch, q, tp } = el;
23391
+ const t = elementsJd(el, jd);
22295
23392
  const { om, w } = propagateOrbitAngles(el, jd);
22296
23393
  const equatorial = el.equatorial ?? false;
22297
23394
  if (!isFinite(a) || a === 0 || !isFinite(n) || n <= 0) {
22298
23395
  if (q == null || tp == null || !isFinite(q) || !isFinite(tp) || q <= 0) return null;
22299
- const solved = solveBarker(q, tp, jd);
23396
+ const solved = solveBarker(q, tp, t);
22300
23397
  if (!solved) return null;
22301
23398
  const mu = muKm3S2 && muKm3S2 > 0 ? muKm3S2 : GM_SUN_KM3_S2;
22302
23399
  const rKm = solved.r * AU_KM;
@@ -22308,7 +23405,7 @@ function elementsToState(el, jd, muKm3S2) {
22308
23405
  const nRadPerSec = n * DEG2RAD$1 / SEC_PER_DAY$1;
22309
23406
  const mu = muKm3S2 && muKm3S2 > 0 ? muKm3S2 : nRadPerSec * nRadPerSec * Math.abs(aKm) * Math.abs(aKm) * Math.abs(aKm);
22310
23407
  if (!isFinite(mu) || mu <= 0) return null;
22311
- const M = (ma + n * (jd - epoch)) * DEG2RAD$1;
23408
+ const M = (ma + n * (t - epoch)) * DEG2RAD$1;
22312
23409
  let nu;
22313
23410
  let rKm;
22314
23411
  if (e < 1 || a > 0) {
@@ -23328,13 +24425,17 @@ function classifyLagrange(geocentric, earthToSun, massRatio = EARTH_SUN_MASS_RAT
23328
24425
  function existsAt(d, jd) {
23329
24426
  return !(d.visibleFromDays !== void 0 && jd - 2451545 < d.visibleFromDays);
23330
24427
  }
24428
+ function withinValidity(d, jd) {
24429
+ const t = elementsJd(d, jd);
24430
+ return t >= d.validityStart && t <= d.validityEnd;
24431
+ }
23331
24432
  function sitsOnParent(d) {
23332
24433
  return d.a === 0 && (d.objectType === ObjectType.BARYCENTER || d.objectType === ObjectType.LAGRANGE_POINT || isMajorBody(d.objectType));
23333
24434
  }
23334
24435
  function elementsOffset$1(d, jd) {
23335
24436
  if (d.pageOnly) return "never";
23336
24437
  if (!existsAt(d, jd)) return "not-yet";
23337
- if (jd < d.validityStart || jd > d.validityEnd) return "no-data";
24438
+ if (!withinValidity(d, jd)) return "no-data";
23338
24439
  let offset;
23339
24440
  const satrec = d.satrec;
23340
24441
  if (satrec) offset = sgp4PositionScene(satrec, jd);
@@ -23817,11 +24918,82 @@ var PositionDiagnostics = class {
23817
24918
  }
23818
24919
  };
23819
24920
 
24921
+ //#endregion
24922
+ //#region src/lib/scene/shaders/planetshine-uniforms.ts
24923
+ var _shiners = [];
24924
+ var _albedos = /* @__PURE__ */ new WeakMap();
24925
+ function measuredShinerAlbedo(bo, map) {
24926
+ const { color, geometricAlbedo } = bo.body.data;
24927
+ let entry = _albedos.get(bo);
24928
+ if (!entry || entry.color !== color || entry.geometricAlbedo !== geometricAlbedo || entry.map !== map) {
24929
+ entry = {
24930
+ color,
24931
+ geometricAlbedo,
24932
+ map,
24933
+ albedo: shinerAlbedo(bo.body.data, map, new Color())
24934
+ };
24935
+ _albedos.set(bo, entry);
24936
+ }
24937
+ return entry.albedo;
24938
+ }
24939
+ function shineAlbedo(bo, material, out) {
24940
+ const measured = measuredShinerAlbedo(bo, material.map);
24941
+ if (measured) return out.copy(measured);
24942
+ out.copy(material.color);
24943
+ const mean = material.map && meanMapColor(material.map);
24944
+ return mean ? out.multiply(mean) : out;
24945
+ }
24946
+ function updatePlanetshineUniforms(bodyObjects, occluders, realistic, sunScale) {
24947
+ const sunPos = bodyObjects.get(SUN_ID)?.body.position;
24948
+ let n = 0;
24949
+ if (sunPos) for (const bo of occluders) {
24950
+ if (!bo.mesh) continue;
24951
+ const material = bo.mesh.material;
24952
+ const [x, y, z] = bo.body.position;
24953
+ const sx = sunPos[0] - x;
24954
+ const sy = sunPos[1] - y;
24955
+ const sz = sunPos[2] - z;
24956
+ const sunDist = Math.hypot(sx, sy, sz);
24957
+ const s = _shiners[n++] ??= {
24958
+ x: 0,
24959
+ y: 0,
24960
+ z: 0,
24961
+ radius: 0,
24962
+ sunX: 0,
24963
+ sunY: 0,
24964
+ sunZ: 0,
24965
+ sunlight: 0,
24966
+ albedo: new Color()
24967
+ };
24968
+ s.x = x;
24969
+ s.y = y;
24970
+ s.z = z;
24971
+ s.radius = bo.radiusScene;
24972
+ s.sunX = sx / sunDist;
24973
+ s.sunY = sy / sunDist;
24974
+ s.sunZ = sz / sunDist;
24975
+ s.sunlight = SUN_LIGHT_INTENSITY * sunScale * (realistic ? sunIrradianceFactor(sunDist) : 1);
24976
+ shineAlbedo(bo, material, s.albedo);
24977
+ }
24978
+ for (const bo of bodyObjects.values()) {
24979
+ const shine = bo.planetshine;
24980
+ const at = bo.body.position;
24981
+ if (!shine || !at) continue;
24982
+ if (bo.cachedDist > 0 && bo.radiusScene < bo.cachedDist * 1e-4) continue;
24983
+ selectShiners(shine, at[0], at[1], at[2], _shiners, n);
24984
+ }
24985
+ }
24986
+
23820
24987
  //#endregion
23821
24988
  //#region src/lib/scene/shaders/ring-uniforms.ts
23822
24989
  var SUN_RADIUS_SCENE = kmToScene(695700);
23823
24990
  var SHEETS_PER_PX = 2;
24991
+ var SIN_B_STEP = 1e-4;
24992
+ var RING_LIGHT_MIN_PX = 2;
23824
24993
  var tmp = new Vector3();
24994
+ var _axis = new Vector3();
24995
+ var _sunLocal = new Vector3();
24996
+ var _inverse = new Quaternion();
23825
24997
  function annulusView(camPos, center, poleDir, innerScene, outerScene) {
23826
24998
  tmp.subVectors(camPos, center);
23827
24999
  const axial = tmp.dot(poleDir);
@@ -23874,6 +25046,35 @@ function updateRingShaders(bodyObjects, focusTruePos, realistic, overexpose, cam
23874
25046
  ps.uRingShadowPoleDir.value.copy(psOnRing.uPlanetPoleDir.value);
23875
25047
  ps.uRingShadowCenter.value.copy(psOnRing.uPlanetCenter.value);
23876
25048
  }
25049
+ updateRingLight(bo, camPos, pxPerRad);
25050
+ }
25051
+ }
25052
+ function updateRingLight(bo, camPos, pxPerRad) {
25053
+ const caster = bo.rings.reduce((best, ring) => ring.intensityScale > best.intensityScale ? ring : best);
25054
+ const uniforms = caster.material.uniforms;
25055
+ const sunDir = uniforms.uSunDir.value;
25056
+ const pole = caster.planetShadowOnRing.uPlanetPoleDir.value;
25057
+ const center = caster.planetShadowOnRing.uPlanetCenter.value;
25058
+ const sinB = Math.min(Math.max(sunDir.dot(pole), -1), 1);
25059
+ const light = caster.light;
25060
+ const seen = caster.outerScene / Math.max(camPos.distanceTo(center), 1e-12) * pxPerRad >= RING_LIGHT_MIN_PX;
25061
+ if (seen && !(Math.abs(sinB - light.patchesSinB) < SIN_B_STEP)) {
25062
+ ringshinePatches(light.patches, light.profile, sinB, light.planet);
25063
+ light.patchesSinB = sinB;
25064
+ }
25065
+ _axis.copy(sunDir).addScaledVector(pole, -sinB);
25066
+ if (_axis.lengthSq() < 1e-12) _axis.set(Math.abs(pole.x) < .9 ? 1 : 0, Math.abs(pole.x) < .9 ? 0 : 1, 0).cross(pole);
25067
+ _axis.normalize();
25068
+ if (caster.ringshine) writeRingshine(caster.ringshine, light, _axis, sinB, uniforms.uLightScale.value);
25069
+ _sunLocal.copy(_axis).applyQuaternion(_inverse.copy(caster.mesh.quaternion).invert());
25070
+ for (const ring of bo.rings) {
25071
+ if (!ring.mesh.visible) continue;
25072
+ if (seen && ring.light.planetLightSinB !== light.patchesSinB) fillPlanetLight(ring.light, light.patchesSinB, light);
25073
+ const ringUniforms = ring.material.uniforms;
25074
+ ringUniforms.uSunAzimuth.value.set(_sunLocal.x, _sunLocal.z).normalize();
25075
+ const albedo = ringUniforms.uPlanetAlbedo.value;
25076
+ if (bo.mesh) shineAlbedo(bo, bo.mesh.material, albedo);
25077
+ else albedo.setRGB(0, 0, 0);
23877
25078
  }
23878
25079
  }
23879
25080
 
@@ -23919,15 +25120,15 @@ function seasonalAtmosphereParams(base, lsDeg) {
23919
25120
  seasonal: void 0
23920
25121
  };
23921
25122
  }
23922
- var cache = /* @__PURE__ */ new WeakMap();
25123
+ var cache$1 = /* @__PURE__ */ new WeakMap();
23923
25124
  var LS_QUANTUM_DEG = .25;
23924
25125
  function seasonalParamsForJd(base, jd) {
23925
25126
  if (!base.seasonal) return base;
23926
25127
  const lsQ = Math.round(marsSolarLongitudeDeg(jd) / LS_QUANTUM_DEG);
23927
- const hit = cache.get(base);
25128
+ const hit = cache$1.get(base);
23928
25129
  if (hit && hit.lsQ === lsQ) return hit.params;
23929
25130
  const params = seasonalAtmosphereParams(base, lsQ * LS_QUANTUM_DEG);
23930
- cache.set(base, {
25131
+ cache$1.set(base, {
23931
25132
  lsQ,
23932
25133
  params
23933
25134
  });
@@ -24073,6 +25274,7 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
24073
25274
 
24074
25275
  //#endregion
24075
25276
  //#region src/lib/scene/shaders/eclipse-uniforms.ts
25277
+ var FOCUS = new Vector3();
24076
25278
  function collectOccluders(bodyObjects, out) {
24077
25279
  let n = 0;
24078
25280
  for (const bo of bodyObjects.values()) {
@@ -24083,19 +25285,17 @@ function collectOccluders(bodyObjects, out) {
24083
25285
  out[n++] = bo;
24084
25286
  }
24085
25287
  out.length = n;
24086
- if (n > 32) {
24087
- out.sort((a, b) => b.radiusScene - a.radiusScene);
24088
- out.length = 32;
24089
- }
24090
25288
  return out;
24091
25289
  }
24092
- function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
25290
+ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, cameraDistance, air) {
24093
25291
  const eclipse = getEclipseSceneUniforms();
25292
+ const pool = getOccluderPool();
24094
25293
  const sunBo = bodyObjects.get(SUN_ID);
24095
25294
  const sunPos = sunBo?.body.position;
24096
25295
  if (!sunBo || !sunPos) {
24097
25296
  eclipse.uSunAngularRadius.value = 0;
24098
25297
  eclipse.uOccluderCount.value = 0;
25298
+ pool.count = 0;
24099
25299
  return;
24100
25300
  }
24101
25301
  const [fx, fy, fz] = focusTruePos;
@@ -24108,17 +25308,20 @@ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
24108
25308
  eclipse.uSunAngularRadius.value = Math.asin(Math.min(sunBo.radiusScene / sunDist, 1));
24109
25309
  } else eclipse.uSunAngularRadius.value = 0;
24110
25310
  const n = occluders.length;
24111
- const slots = eclipse.uOccluders.value;
24112
- const leaks = eclipse.uOccluderLeak.value;
25311
+ while (pool.occluders.length < n) {
25312
+ pool.occluders.push(new Vector4());
25313
+ pool.leaks.push(new Vector4());
25314
+ }
24113
25315
  for (let i = 0; i < n; i++) {
24114
25316
  const bo = occluders[i];
24115
25317
  const [bx, by, bz] = bo.body.position;
24116
- slots[i].set(bx - fx, by - fy, bz - fz, bo.radiusScene);
24117
- if (air && bo.atmosphere) atmosphereLeak(bo.atmosphere.params, bo.atmosphere.planetRadiusKm, leaks[i]);
24118
- else leaks[i].set(0, 0, 0, 0);
25318
+ pool.occluders[i].set(bx - fx, by - fy, bz - fz, bo.radiusScene);
25319
+ if (air && bo.atmosphere) atmosphereLeak(bo.atmosphere.params, bo.atmosphere.planetRadiusKm, pool.leaks[i]);
25320
+ else pool.leaks[i].set(0, 0, 0, 0);
24119
25321
  }
24120
- eclipse.uOccluderCount.value = n;
25322
+ pool.count = n;
24121
25323
  const sunDir = eclipse.uSunDir.value;
25324
+ eclipse.uOccluderCount.value = cullOccludersFor(eclipse.uOccluders.value, FOCUS, cameraDistance, sunDir, eclipse.uOccluderLeak.value);
24122
25325
  for (const bo of bodyObjects.values()) {
24123
25326
  const self = bo.eclipseShadow;
24124
25327
  const at = bo.body.position;
@@ -24130,52 +25333,6 @@ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
24130
25333
  }
24131
25334
  }
24132
25335
 
24133
- //#endregion
24134
- //#region src/lib/scene/shaders/planetshine-uniforms.ts
24135
- var _shiners = Array.from({ length: 32 }, () => ({
24136
- x: 0,
24137
- y: 0,
24138
- z: 0,
24139
- radius: 0,
24140
- sunX: 0,
24141
- sunY: 0,
24142
- sunZ: 0,
24143
- sunlight: 0,
24144
- albedo: new Color()
24145
- }));
24146
- function updatePlanetshineUniforms(bodyObjects, occluders, realistic, sunScale) {
24147
- const sunPos = bodyObjects.get(SUN_ID)?.body.position;
24148
- let n = 0;
24149
- if (sunPos) for (const bo of occluders) {
24150
- if (!bo.mesh) continue;
24151
- const material = bo.mesh.material;
24152
- const [x, y, z] = bo.body.position;
24153
- const sx = sunPos[0] - x;
24154
- const sy = sunPos[1] - y;
24155
- const sz = sunPos[2] - z;
24156
- const sunDist = Math.hypot(sx, sy, sz);
24157
- const s = _shiners[n++];
24158
- s.x = x;
24159
- s.y = y;
24160
- s.z = z;
24161
- s.radius = bo.radiusScene;
24162
- s.sunX = sx / sunDist;
24163
- s.sunY = sy / sunDist;
24164
- s.sunZ = sz / sunDist;
24165
- s.sunlight = SUN_LIGHT_INTENSITY * sunScale * (realistic ? sunIrradianceFactor(sunDist) : 1);
24166
- s.albedo.copy(material.color);
24167
- const mean = material.map && meanMapColor(material.map);
24168
- if (mean) s.albedo.multiply(mean);
24169
- }
24170
- for (const bo of bodyObjects.values()) {
24171
- const shine = bo.planetshine;
24172
- const at = bo.body.position;
24173
- if (!shine || !at) continue;
24174
- if (bo.cachedDist > 0 && bo.radiusScene < bo.cachedDist * 1e-4) continue;
24175
- selectShiners(shine, at[0], at[1], at[2], _shiners, n);
24176
- }
24177
- }
24178
-
24179
25336
  //#endregion
24180
25337
  //#region src/lib/scene/shaders/sun-shadow-light.ts
24181
25338
  var LIGHT_DIST = 10;
@@ -24702,7 +25859,9 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
24702
25859
  updateTileOverlay(bo, camera, renderer, devicePx, settings.showSurfaceTexture && !bo.appearance?.surfaceOwned, invalidate);
24703
25860
  updateReliefTiles(bo, camera, renderer, devicePx, invalidate);
24704
25861
  const dispTiers = bo.displacementMeta?.tiers;
24705
- if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
25862
+ if (bo.displacementMap && !bo.displacementLoading && pyramidHeightsServe(bo, renderer)) {
25863
+ if (bo.displacementTier !== "pyramid") swapDisplacementTier(bo, PYRAMID_TIER);
25864
+ } else if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
24706
25865
  const maxTex = renderer.capabilities.maxTextureSize;
24707
25866
  const capRank = maxTex >= 16383 && !reliefTilesServe(bo) ? 2 : maxTex >= 8192 ? 1 : 0;
24708
25867
  const current = bo.displacementTier ?? "low";
@@ -25025,7 +26184,7 @@ function computeGroupMode(count) {
25025
26184
  return "none";
25026
26185
  }
25027
26186
  function isCurrentlyValid(body, jd) {
25028
- return body.data.validityStart <= jd && jd <= body.data.validityEnd;
26187
+ return withinValidity(body.data, jd);
25029
26188
  }
25030
26189
  var PromotionRegistry = class {
25031
26190
  pendingDefaults = /* @__PURE__ */ new Set();
@@ -25204,7 +26363,8 @@ var PromotionRegistry = class {
25204
26363
  let n = 0;
25205
26364
  for (const body of bucket.values()) {
25206
26365
  if (this.groupTargets && !this.groupTargets.has(body.data.id)) continue;
25207
- const { validityStart, validityEnd } = body.data;
26366
+ const validityStart = clockJd(body.data, body.data.validityStart);
26367
+ const validityEnd = clockJd(body.data, body.data.validityEnd);
25208
26368
  if (validityStart <= jd) {
25209
26369
  if (validityStart > this.countStableLo) this.countStableLo = validityStart;
25210
26370
  } else if (validityStart < this.countStableHi) this.countStableHi = validityStart;
@@ -25744,19 +26904,45 @@ var ExtensionRegistry = class {
25744
26904
 
25745
26905
  //#endregion
25746
26906
  //#region src/lib/scene/extensions/anchor.ts
25747
- var RAD$4 = Math.PI / 180;
26907
+ var RAD$3 = Math.PI / 180;
25748
26908
  function isSurface(anchor) {
25749
26909
  return "latitude" in anchor;
25750
26910
  }
25751
- function surfaceDirection(latitude, longitude) {
25752
- const lat = latitude * RAD$4;
25753
- const lon = longitude * RAD$4;
25754
- return [
26911
+ function surfaceDirection(latitude, longitude, radii) {
26912
+ const lat = latitude * RAD$3;
26913
+ const lon = longitude * RAD$3;
26914
+ const out = [
25755
26915
  Math.cos(lat) * Math.cos(lon),
25756
26916
  Math.sin(lat),
25757
26917
  -Math.cos(lat) * Math.sin(lon)
25758
26918
  ];
26919
+ if (radii) onEllipsoid(radii, out, 0, out, 0);
26920
+ return out;
25759
26921
  }
26922
+ function onEllipsoid(radii, from, at, out, to) {
26923
+ const x = from[at] * radii.a;
26924
+ const y = from[at + 1] * radii.c;
26925
+ const z = from[at + 2] * radii.b;
26926
+ const n = Math.hypot(x, y, z);
26927
+ out[to] = x / n;
26928
+ out[to + 1] = y / n;
26929
+ out[to + 2] = z / n;
26930
+ }
26931
+ var GroundDirections = class {
26932
+ source = null;
26933
+ radii;
26934
+ out = new Float64Array(0);
26935
+ of(dirs, radii) {
26936
+ if (!radii) return dirs;
26937
+ if (dirs !== this.source || radii !== this.radii) {
26938
+ if (this.out.length !== dirs.length) this.out = new Float64Array(dirs.length);
26939
+ for (let i = 0; i < dirs.length; i += 3) onEllipsoid(radii, dirs, i, this.out, i);
26940
+ this.source = dirs;
26941
+ this.radii = radii;
26942
+ }
26943
+ return this.out;
26944
+ }
26945
+ };
25760
26946
  function rotateByQuaternion(q, x, y, z, out, at) {
25761
26947
  const tx = 2 * (q.y * z - q.z * y);
25762
26948
  const ty = 2 * (q.z * x - q.x * z);
@@ -25772,7 +26958,7 @@ function placeAnchor(anchor, body, centre, jd) {
25772
26958
  const [cx, cy, cz] = centre;
25773
26959
  if (isSurface(anchor)) {
25774
26960
  const distance = kmToScene(effectiveRadiusKm(body.data) + (anchor.altitudeKm ?? 0));
25775
- const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude);
26961
+ const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude, body.drawnRadii);
25776
26962
  const q = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec) : null;
25777
26963
  if (!q) return [
25778
26964
  cx + dx * distance,
@@ -25860,9 +27046,13 @@ function resolvePose(pose, ctx, jd) {
25860
27046
  //#region src/lib/scene/camera/motion-scale.ts
25861
27047
  var MIN_TRANSLATE_SCALE = .02;
25862
27048
  var MIN_ROTATE_SCALE = 1e-4;
27049
+ var DAMPING_FRAME_MS = 1e3 / 60;
25863
27050
  function orbitDamping(reducedMotion) {
25864
27051
  return reducedMotion ? .4 : .05;
25865
27052
  }
27053
+ function frameDamping(factor, frameDtMs) {
27054
+ return 1 - (1 - factor) ** (frameDtMs / DAMPING_FRAME_MS);
27055
+ }
25866
27056
  function cameraMotionScale(camera, focusTruePos, focused, parent, seated) {
25867
27057
  const orbitRadius = camera.position.length();
25868
27058
  if (!(orbitRadius > 0)) return {
@@ -25892,10 +27082,10 @@ function clamp$1(value, min) {
25892
27082
 
25893
27083
  //#endregion
25894
27084
  //#region src/lib/scene/user-location/marker.ts
25895
- var RAD$3 = Math.PI / 180;
27085
+ var RAD$2 = Math.PI / 180;
25896
27086
  function bodyLocalUnitVector(latitude, longitude) {
25897
- const latR = latitude * RAD$3;
25898
- const lonR = longitude * RAD$3;
27087
+ const latR = latitude * RAD$2;
27088
+ const lonR = longitude * RAD$2;
25899
27089
  return [
25900
27090
  Math.cos(latR) * Math.cos(lonR),
25901
27091
  Math.sin(latR),
@@ -26088,6 +27278,7 @@ var SceneRenderer = class SceneRenderer {
26088
27278
  firstFrame = true;
26089
27279
  pendingUrlWrite = false;
26090
27280
  lastTickMs = 0;
27281
+ orbitDampingFactor;
26091
27282
  static FPS_SAMPLE_FRAMES = 30;
26092
27283
  fpsSamples = [];
26093
27284
  fpsSampleHead = 0;
@@ -26182,7 +27373,7 @@ var SceneRenderer = class SceneRenderer {
26182
27373
  this.pointClouds.setPointBudget(this.tier.pointBudget);
26183
27374
  this.controls = new OrbitControls(this.camera, canvas);
26184
27375
  this.controls.enableDamping = true;
26185
- this.controls.dampingFactor = orbitDamping(sceneSettings().resolvedReducedMotion);
27376
+ this.orbitDampingFactor = orbitDamping(sceneSettings().resolvedReducedMotion);
26186
27377
  this.controls.minDistance = focusBody ? minCameraDistance(focusBody) : kmToScene(.01);
26187
27378
  this.controls.maxDistance = 31620.5 * 10;
26188
27379
  this.controls.target.set(0, 0, 0);
@@ -26473,6 +27664,7 @@ var SceneRenderer = class SceneRenderer {
26473
27664
  const nowMs = performance.now();
26474
27665
  const frameDtMs = this.lastTickMs ? nowMs - this.lastTickMs : 0;
26475
27666
  this.lastTickMs = nowMs;
27667
+ this.controls.dampingFactor = frameDamping(this.orbitDampingFactor, frameDtMs || 16.666666666666668);
26476
27668
  if (!this.holdingCamera) this.cameraUp.update(this.sceneJd);
26477
27669
  if (this.firstFrame) {
26478
27670
  this.firstFrame = false;
@@ -26540,7 +27732,7 @@ var SceneRenderer = class SceneRenderer {
26540
27732
  refreshDeferredTrails(this.bodyObjects, this.focus, this.lastUpdatedJd, this.trailView());
26541
27733
  updateRingShaders(this.bodyObjects, this.focus.focusTruePos, sceneSettings().realisticLighting, sceneSettings().overexposeRings, this.camera.position, this.pxPerRad());
26542
27734
  const occluders = collectOccluders(this.bodyObjects, this._occluders);
26543
- updateEclipseUniforms(this.bodyObjects, occluders, this.focus.focusTruePos, sceneSettings().showAtmospheres);
27735
+ updateEclipseUniforms(this.bodyObjects, occluders, this.focus.focusTruePos, this.camera.position.length(), sceneSettings().showAtmospheres);
26544
27736
  updatePlanetshineUniforms(this.bodyObjects, occluders, sceneSettings().realisticLighting, this.sunIntensityScale);
26545
27737
  occluders.length = 0;
26546
27738
  const atmoState = updateAtmosphereShaders(this.bodyObjects, this.camera.position, sceneSettings().showAtmospheres, sceneSettings().realisticLighting, this.sunIntensityScale, currentAtmosphereConfig(), this.sceneJd);
@@ -26685,7 +27877,7 @@ var SceneRenderer = class SceneRenderer {
26685
27877
  surfaceRadialKm(bodyId, latitude, longitude) {
26686
27878
  const body = this.ctx.getBody(bodyId);
26687
27879
  if (!body) return null;
26688
- const dir = bodyFixedUnit(latitude * Math.PI / 180, longitude * Math.PI / 180);
27880
+ const dir = surfaceDirection(latitude, longitude, body.drawnRadii);
26689
27881
  return renderedSurfaceRadialKm(this.bodyObjects.get(bodyId), bodyId, effectiveRadiusKm(body.data), dir);
26690
27882
  }
26691
27883
  surfaceClamp(body, seated) {
@@ -27164,7 +28356,7 @@ var SceneRenderer = class SceneRenderer {
27164
28356
  }
27165
28357
  }
27166
28358
  setReducedMotion(on) {
27167
- this.controls.dampingFactor = orbitDamping(on);
28359
+ this.orbitDampingFactor = orbitDamping(on);
27168
28360
  }
27169
28361
  focusOnBody(id, zoom, latitude, longitude) {
27170
28362
  this.disarmLimits();
@@ -28589,7 +29781,7 @@ var IconExtension = class extends MarkerExtension {
28589
29781
  //#endregion
28590
29782
  //#region src/lib/flatmap/projection.ts
28591
29783
  var DEG$6 = Math.PI / 180;
28592
- var RAD$2 = 180 / Math.PI;
29784
+ var RAD$1 = 180 / Math.PI;
28593
29785
  function wrapLon(lon) {
28594
29786
  if (lon >= -180 && lon < 180) return lon;
28595
29787
  return ((lon + 180) % 360 + 360) % 360 - 180;
@@ -28636,8 +29828,8 @@ function equirectangular(options) {
28636
29828
  const rowInverse = (y) => {
28637
29829
  if (Math.abs(y) > Math.PI / 2) return null;
28638
29830
  return {
28639
- lat: y * RAD$2,
28640
- lonPerX: RAD$2,
29831
+ lat: y * RAD$1,
29832
+ lonPerX: RAD$1,
28641
29833
  maxAbsX: Math.PI
28642
29834
  };
28643
29835
  };
@@ -28685,9 +29877,9 @@ function equalEarth(options) {
28685
29877
  const theta = thetaFor(y);
28686
29878
  const sinLat = 2 / EE_SQRT3 * Math.sin(theta);
28687
29879
  if (Math.abs(sinLat) > 1) return null;
28688
- const lonPerX = 3 * eeDy(theta) / (2 * EE_SQRT3 * Math.cos(theta)) * RAD$2;
29880
+ const lonPerX = 3 * eeDy(theta) / (2 * EE_SQRT3 * Math.cos(theta)) * RAD$1;
28689
29881
  return {
28690
- lat: Math.asin(sinLat) * RAD$2,
29882
+ lat: Math.asin(sinLat) * RAD$1,
28691
29883
  lonPerX,
28692
29884
  maxAbsX: 180 / lonPerX
28693
29885
  };
@@ -28719,9 +29911,9 @@ function mollweide(options) {
28719
29911
  lonPerX: 0,
28720
29912
  maxAbsX: 0
28721
29913
  };
28722
- const lonPerX = Math.PI / (2 * MW_SQRT2 * cosTheta) * RAD$2;
29914
+ const lonPerX = Math.PI / (2 * MW_SQRT2 * cosTheta) * RAD$1;
28723
29915
  return {
28724
- lat: Math.asin(sinLat) * RAD$2,
29916
+ lat: Math.asin(sinLat) * RAD$1,
28725
29917
  lonPerX,
28726
29918
  maxAbsX: 180 / lonPerX
28727
29919
  };
@@ -28768,8 +29960,8 @@ function sinusoidal(options) {
28768
29960
  maxAbsX: 0
28769
29961
  };
28770
29962
  return {
28771
- lat: y * RAD$2,
28772
- lonPerX: RAD$2 / cosLat,
29963
+ lat: y * RAD$1,
29964
+ lonPerX: RAD$1 / cosLat,
28773
29965
  maxAbsX: Math.PI * cosLat
28774
29966
  };
28775
29967
  };
@@ -28920,7 +30112,7 @@ function clamp1(v) {
28920
30112
  }
28921
30113
  function azimuthalInverse(x, y, cosC, k, lon0, sinPhi0, cosPhi0) {
28922
30114
  const lat = Math.asin(clamp1(cosC * sinPhi0 + y * k * cosPhi0));
28923
- return [wrapLon(lon0 + Math.atan2(x * k, cosC * cosPhi0 - y * k * sinPhi0) * RAD$2), lat * RAD$2];
30115
+ return [wrapLon(lon0 + Math.atan2(x * k, cosC * cosPhi0 - y * k * sinPhi0) * RAD$1), lat * RAD$1];
28924
30116
  }
28925
30117
  function discExtent(r) {
28926
30118
  return {
@@ -29017,7 +30209,7 @@ function createProjection(id, options = {}) {
29017
30209
  var DEG$5 = Math.PI / 180;
29018
30210
  var TAU = 2 * Math.PI;
29019
30211
  var RIM_STEP = 2 * DEG$5;
29020
- var RAD$1 = 180 / Math.PI;
30212
+ var RAD = 180 / Math.PI;
29021
30213
  var MAX_STEPS = 512;
29022
30214
  var DEFAULT_STEP_DEG = 2;
29023
30215
  function angularDistance(a, b) {
@@ -29026,7 +30218,7 @@ function angularDistance(a, b) {
29026
30218
  const dφ = φ2 - φ1;
29027
30219
  const dλ = wrapLon(b.lon - a.lon) * DEG$5;
29028
30220
  const h = Math.sin(dφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(dλ / 2) ** 2;
29029
- return 2 * Math.asin(Math.min(1, Math.sqrt(h))) * RAD$1;
30221
+ return 2 * Math.asin(Math.min(1, Math.sqrt(h))) * RAD;
29030
30222
  }
29031
30223
  function slerp(a, b, t) {
29032
30224
  const d = angularDistance(a, b) * DEG$5;
@@ -29042,8 +30234,8 @@ function slerp(a, b, t) {
29042
30234
  const y = ka * Math.cos(φ1) * Math.sin(λ1) + kb * Math.cos(φ2) * Math.sin(λ2);
29043
30235
  const z = ka * Math.sin(φ1) + kb * Math.sin(φ2);
29044
30236
  return {
29045
- lon: Math.atan2(y, x) * RAD$1,
29046
- lat: Math.atan2(z, Math.hypot(x, y)) * RAD$1
30237
+ lon: Math.atan2(y, x) * RAD,
30238
+ lat: Math.atan2(z, Math.hypot(x, y)) * RAD
29047
30239
  };
29048
30240
  }
29049
30241
  function lerp(a, b, t) {
@@ -29346,8 +30538,8 @@ function smallCircle(center, radiusDeg, steps = 128) {
29346
30538
  const y = c[1] * cosR + e[1] * a + n[1] * b;
29347
30539
  const z = c[2] * cosR + e[2] * a + n[2] * b;
29348
30540
  ring.push({
29349
- lon: wrapLon(Math.atan2(y, x) * RAD$1),
29350
- lat: Math.atan2(z, Math.hypot(x, y)) * RAD$1
30541
+ lon: wrapLon(Math.atan2(y, x) * RAD),
30542
+ lat: Math.atan2(z, Math.hypot(x, y)) * RAD
29351
30543
  });
29352
30544
  }
29353
30545
  return ring;
@@ -29400,6 +30592,8 @@ var SurfaceShapeExtension = class {
29400
30592
  alphas = new Float32Array(0);
29401
30593
  lengths = new Float32Array(0);
29402
30594
  fanDirs = new Float64Array(0);
30595
+ groundDirs = new GroundDirections();
30596
+ groundFanDirs = new GroundDirections();
29403
30597
  fill = null;
29404
30598
  fillScratch = new Float32Array(0);
29405
30599
  turned = [
@@ -29466,7 +30660,8 @@ var SurfaceShapeExtension = class {
29466
30660
  centre[2] - basis[2]
29467
30661
  ];
29468
30662
  if (this.line) {
29469
- this.place(this.dirs, this.count, q, scale, origin, this.scratch);
30663
+ const dirs = this.groundDirs.of(this.dirs, body.drawnRadii);
30664
+ this.place(dirs, this.count, q, scale, origin, this.scratch);
29470
30665
  writeFatTrailVertices(this.line.geometry, this.scratch, this.alphas, this.alphas, this.count);
29471
30666
  if (this.dash) {
29472
30667
  screenLengths(this.scratch, this.count, camera, viewportPx, this.lengths);
@@ -29476,7 +30671,8 @@ var SurfaceShapeExtension = class {
29476
30671
  }
29477
30672
  if (this.fill) {
29478
30673
  const position = this.fill.geometry.getAttribute("position");
29479
- this.place(this.fanDirs, this.fillScratch.length / 3, q, scale, origin, this.fillScratch);
30674
+ const fanDirs = this.groundFanDirs.of(this.fanDirs, body.drawnRadii);
30675
+ this.place(fanDirs, this.fillScratch.length / 3, q, scale, origin, this.fillScratch);
29480
30676
  position.needsUpdate = true;
29481
30677
  this.fill.material.uniforms.uCenterOffset.value.copy(camera.position).negate();
29482
30678
  }
@@ -31777,7 +32973,7 @@ async function acquireBitmap(url, decodeWidth) {
31777
32973
  };
31778
32974
  let work = pending.get(key);
31779
32975
  if (!work) {
31780
- work = decode(url, decodeWidth);
32976
+ work = decode$1(url, decodeWidth);
31781
32977
  pending.set(key, work);
31782
32978
  const done = () => pending.delete(key);
31783
32979
  work.then(done, done);
@@ -31789,7 +32985,7 @@ async function acquireBitmap(url, decodeWidth) {
31789
32985
  bitmap: bitmaps.put(key, bitmap, bitmapBytes(bitmap.width, bitmap.height))
31790
32986
  };
31791
32987
  }
31792
- async function decode(url, decodeWidth) {
32988
+ async function decode$1(url, decodeWidth) {
31793
32989
  const response = await fetch(url);
31794
32990
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
31795
32991
  const blob = await response.blob();
@@ -33083,17 +34279,25 @@ function findPanorama(entries, at) {
33083
34279
  if (!at) return null;
33084
34280
  return entries.find((e) => panoramaAt(e) === at || legacyAt(e) === at) ?? null;
33085
34281
  }
34282
+ var MIN_STEP_M = 1;
33086
34283
  function neighboursOf(entries, current, radiusKm) {
33087
34284
  const i = entries.findIndex((e) => e.id === current.id);
33088
- const sameMission = (e) => e && e.mission === current.mission;
33089
- const toNeighbour = (e) => sameMission(e) ? {
33090
- entry: e,
33091
- distanceM: groundDistanceM(current, e, radiusKm),
33092
- bearingDeg: bearingDeg(current, e)
33093
- } : null;
34285
+ const nearest = (step) => {
34286
+ for (let j = i + step; j >= 0 && j < entries.length; j += step) {
34287
+ const e = entries[j];
34288
+ if (e.mission !== current.mission) return null;
34289
+ const distanceM = groundDistanceM(current, e, radiusKm);
34290
+ if (distanceM >= MIN_STEP_M) return {
34291
+ entry: e,
34292
+ distanceM,
34293
+ bearingDeg: bearingDeg(current, e)
34294
+ };
34295
+ }
34296
+ return null;
34297
+ };
33094
34298
  return {
33095
- previous: toNeighbour(entries[i - 1]),
33096
- next: toNeighbour(entries[i + 1])
34299
+ previous: nearest(-1),
34300
+ next: nearest(1)
33097
34301
  };
33098
34302
  }
33099
34303
  var DEG$1 = Math.PI / 180;
@@ -33121,7 +34325,6 @@ var ARROW_DISTANCE = 9;
33121
34325
  var CLICK_SLOP_PX = 6;
33122
34326
  var ANGLE_STEP_DEG = 10;
33123
34327
  var ANGLE_SAMPLE_STEP_DEG = 1;
33124
- var MIN_ARROW_DISTANCE_M = 1;
33125
34328
  var GYRO_TILT = new Quaternion(-Math.SQRT1_2, 0, 0, Math.SQRT1_2);
33126
34329
  var SCREEN_AXIS = new Vector3(0, 0, 1);
33127
34330
  function wrapHeading(deg) {
@@ -33340,7 +34543,7 @@ var PanoramaView = class {
33340
34543
  this.setArrowTargets(this.neighbours && hasHeading(entry) ? ["previous", "next"].map((key) => ({
33341
34544
  key,
33342
34545
  n: this.neighbours?.[key] ?? null
33343
- })).filter((a) => a.n && a.n.distanceM >= MIN_ARROW_DISTANCE_M).map((a) => ({
34546
+ })).filter((a) => a.n).map((a) => ({
33344
34547
  key: a.key,
33345
34548
  bearingDeg: a.n.bearingDeg
33346
34549
  })) : []);
@@ -33738,6 +34941,106 @@ var fetchPlanetarySystemsMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__p
33738
34941
  //#region src/lib/fetch/groups/solar-system-map.ts
33739
34942
  var fetchSolarSystemMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__solar_system_map__.json.gz`, { error: (res) => `Failed to fetch solar system map: ${res.status}` });
33740
34943
 
34944
+ //#endregion
34945
+ //#region src/lib/rings/strip.ts
34946
+ var cache = /* @__PURE__ */ new Map();
34947
+ function luminance(r, g, b) {
34948
+ return .2126 * r + .7152 * g + .0722 * b;
34949
+ }
34950
+ async function decode(bodyId, meta) {
34951
+ const rows = meta.strip_rows;
34952
+ if (rows.transparency === void 0 || rows.color === void 0) {
34953
+ console.warn(`Ring strip ${bodyId}/${meta.strip} has no transparency or color row, no profile drawn:`, rows);
34954
+ return null;
34955
+ }
34956
+ const response = await fetch(versionedUrl(`/v1/rings/${bodyId}/${meta.strip}`, "rings"));
34957
+ if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
34958
+ const bitmap = await createImageBitmap(await response.blob());
34959
+ const canvas = document.createElement("canvas");
34960
+ canvas.width = bitmap.width;
34961
+ canvas.height = bitmap.height;
34962
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
34963
+ if (!ctx) {
34964
+ console.warn(`Ring strip ${bodyId}/${meta.strip}: no 2D context, no profile drawn`);
34965
+ return null;
34966
+ }
34967
+ ctx.drawImage(bitmap, 0, 0);
34968
+ const n = bitmap.width;
34969
+ const pixels = ctx.getImageData(0, 0, n, bitmap.height).data;
34970
+ bitmap.close();
34971
+ const scale = meta.intensity_scale || 1;
34972
+ const tau = new Float32Array(n);
34973
+ const rgb = new Uint8ClampedArray(n * 3);
34974
+ const back = rows.backscattered ?? rows.color;
34975
+ const brightness = new Float32Array(n);
34976
+ let peak = 0;
34977
+ for (let i = 0; i < n; i++) {
34978
+ const t = (rows.transparency * n + i) * 4;
34979
+ const b = (back * n + i) * 4;
34980
+ const opacity = Math.min(1, (1 - pixels[t] / 255) * scale);
34981
+ tau[i] = opacity >= 1 ? Infinity : -Math.log(1 - opacity);
34982
+ brightness[i] = luminance(pixels[b], pixels[b + 1], pixels[b + 2]) / 255;
34983
+ if (brightness[i] > peak) peak = brightness[i];
34984
+ }
34985
+ const FLOOR = .35;
34986
+ for (let i = 0; i < n; i++) {
34987
+ const c = (rows.color * n + i) * 4;
34988
+ const lit = peak > 0 ? FLOOR + (1 - FLOOR) * (brightness[i] / peak) : 1;
34989
+ rgb[i * 3] = pixels[c] * lit;
34990
+ rgb[i * 3 + 1] = pixels[c + 1] * lit;
34991
+ rgb[i * 3 + 2] = pixels[c + 2] * lit;
34992
+ }
34993
+ return {
34994
+ inner: meta.inner_radius_km,
34995
+ outer: meta.outer_radius_km,
34996
+ tau,
34997
+ rgb
34998
+ };
34999
+ }
35000
+ async function load(bodyId) {
35001
+ const bundles = (await fetchObjectDetail(bodyId, false)).global?.rings ?? [];
35002
+ if (!bundles.length) console.warn(`Ring strips for ${bodyId}: no ring bundles on the object`);
35003
+ const profiles = [];
35004
+ for (const bundle of bundles) try {
35005
+ const profile = await decode(bodyId, bundle);
35006
+ if (profile) profiles.push(profile);
35007
+ } catch (err) {
35008
+ console.warn(`Ring strip ${bodyId}/${bundle.strip} unavailable:`, err);
35009
+ }
35010
+ return profiles;
35011
+ }
35012
+ var SEEN_TAU = -Math.log1p(-.02);
35013
+ function visibleSpan(profiles) {
35014
+ let inner = Infinity;
35015
+ let outer = -Infinity;
35016
+ for (const profile of profiles) {
35017
+ const n = profile.tau.length;
35018
+ const kmPerSample = (profile.outer - profile.inner) / (n - 1);
35019
+ for (let i = 0; i < n; i++) {
35020
+ if (profile.tau[i] < SEEN_TAU) continue;
35021
+ const km = profile.inner + i * kmPerSample;
35022
+ if (km < inner) inner = km;
35023
+ if (km > outer) outer = km;
35024
+ }
35025
+ }
35026
+ return outer >= inner ? {
35027
+ inner,
35028
+ outer
35029
+ } : null;
35030
+ }
35031
+ function loadRingStrips(bodyId) {
35032
+ let pending = cache.get(bodyId);
35033
+ if (!pending) {
35034
+ pending = load(bodyId).catch((err) => {
35035
+ console.warn(`Ring strips for ${bodyId} unavailable:`, err);
35036
+ cache.delete(bodyId);
35037
+ return [];
35038
+ });
35039
+ cache.set(bodyId, pending);
35040
+ }
35041
+ return pending;
35042
+ }
35043
+
33741
35044
  //#endregion
33742
35045
  //#region src/lib/charts/scrub.ts
33743
35046
  var DRAG_SLOP = 8;
@@ -34527,6 +35830,15 @@ function systemFromMapEntry(entry, name) {
34527
35830
  moonCount: entry.moon_count
34528
35831
  };
34529
35832
  }
35833
+ function visibleRings(system, profiles) {
35834
+ if (!system.rings || !profiles.length) return system.rings;
35835
+ const span = visibleSpan(profiles);
35836
+ const rKm = system.planetRadiusKm;
35837
+ return span && {
35838
+ innerRp: span.inner / rKm,
35839
+ outerRp: span.outer / rKm
35840
+ };
35841
+ }
34530
35842
  var TOP_MOON_R = 14;
34531
35843
  var AT_SURFACE = 1.001;
34532
35844
  var PX_PER_DEG = .62;
@@ -34876,11 +36188,12 @@ function buildSystemView(data, primaryId, exported, options, text, pick) {
34876
36188
  name: scope.name(moon.id, exported[moon.id])
34877
36189
  }));
34878
36190
  const rKm = system.planetRadiusKm;
34879
- const bands = system.rings ? [{
36191
+ const rings = data.ringProfiles ? visibleRings(system, data.ringProfiles) : system.rings;
36192
+ const bands = rings ? [{
34880
36193
  key: "rings",
34881
36194
  label: text.rings,
34882
- innerKm: system.rings.innerRp * rKm,
34883
- outerKm: system.rings.outerRp * rKm,
36195
+ innerKm: rings.innerRp * rKm,
36196
+ outerKm: rings.outerRp * rKm,
34884
36197
  tone: "amber"
34885
36198
  }] : [];
34886
36199
  const model = planetarySystemModel(system, {
@@ -35027,6 +36340,7 @@ var SystemMap = class {
35027
36340
  names;
35028
36341
  grouping;
35029
36342
  zones;
36343
+ rings;
35030
36344
  places;
35031
36345
  targets = [];
35032
36346
  extras = /* @__PURE__ */ new Map();
@@ -35038,6 +36352,7 @@ var SystemMap = class {
35038
36352
  this.names = options.names ?? {};
35039
36353
  this.grouping = options.grouping ?? "bodies";
35040
36354
  this.zones = options.zones ?? "belts";
36355
+ this.rings = options.rings ?? "all";
35041
36356
  this.places = (options.places ?? []).map(extraFromPlace);
35042
36357
  }
35043
36358
  mount(container) {
@@ -35104,7 +36419,14 @@ var SystemMap = class {
35104
36419
  };
35105
36420
  switch (view.kind) {
35106
36421
  case "solar-system": return buildSolarView(data, options, text, pick);
35107
- case "system": return buildSystemView(data, view.id, await this.exportedNames(view.id, systems), options, text, pick);
36422
+ case "system": {
36423
+ const ringed = this.rings === "visible" && !!systemEntry(systems, view.id)?.rings;
36424
+ const [names, ringProfiles] = await Promise.all([this.exportedNames(view.id, systems), ringed ? loadRingStrips(view.id) : void 0]);
36425
+ return buildSystemView({
36426
+ ...data,
36427
+ ringProfiles
36428
+ }, view.id, names, options, text, pick);
36429
+ }
35108
36430
  case "zone": {
35109
36431
  const ids = unplacedIds(solar, systems, this.bodies, this.places.map((place) => place.id));
35110
36432
  const read = (await Promise.all(ids.map((id) => this.extra(id)))).filter((extra) => extra !== null);
@@ -35357,9 +36679,6 @@ var PanoramaAttributionControl = class {
35357
36679
 
35358
36680
  //#endregion
35359
36681
  //#region src/lib/scene/extensions/trajectory.ts
35360
- var RAD = Math.PI / 180;
35361
- var COS_EPS = Math.cos(EARTH_OBLIQUITY_DEG * RAD);
35362
- var SIN_EPS = Math.sin(EARTH_OBLIQUITY_DEG * RAD);
35363
36682
  function fixed(place) {
35364
36683
  return place;
35365
36684
  }
@@ -35384,17 +36703,10 @@ function tle(line1, line2) {
35384
36703
  offsetKm: (jd) => {
35385
36704
  if (satrec.error !== SatRecError.None) return null;
35386
36705
  const teme = sgp4PositionTEME(satrec, jd);
35387
- return teme && temeToEcliptic(teme[0], teme[1], teme[2]);
36706
+ return teme && temeToEcliptic(teme[0], teme[1], teme[2], jd);
35388
36707
  }
35389
36708
  };
35390
36709
  }
35391
- function temeToEcliptic(x, y, z) {
35392
- return [
35393
- x,
35394
- y * COS_EPS + z * SIN_EPS,
35395
- -y * SIN_EPS + z * COS_EPS
35396
- ];
35397
- }
35398
36710
  function interpolateSamples(states, jd) {
35399
36711
  const n = states.length;
35400
36712
  if (n < 2 || jd < states[0].jd || jd > states[n - 1].jd) return null;
@@ -35478,7 +36790,7 @@ async function moonRow(id, { jd, metadata }) {
35478
36790
  }
35479
36791
  function elementsOffset(data, jd) {
35480
36792
  if (!discovered(data.visibleFromDays, jd)) return null;
35481
- if (jd < data.validityStart || jd > data.validityEnd) return null;
36793
+ if (!withinValidity(data, jd)) return null;
35482
36794
  if (data.satrec) return sgp4PositionScene(data.satrec, jd);
35483
36795
  if (data.q != null) return parabolicToPositionJD(data, jd);
35484
36796
  if (data.a === 0) return [
@@ -35546,16 +36858,9 @@ function neighbours(id, { parents }, at) {
35546
36858
  }
35547
36859
  async function spinOf(id) {
35548
36860
  const [detail] = await Promise.all([fetchObjectDetail(id, false), loadSystemsGlobal()]);
35549
- const orientation = detail.global?.orientation;
35550
- const sums = detail.global?.nut_prec;
35551
- const naif = /^naif-(-?\d+)$/.exec(id);
35552
- const angles = naif ? getNutPrecAngles(ownerIdFor(parseInt(naif[1], 10))) : void 0;
35553
36861
  return {
35554
- orientation,
35555
- nutPrec: sums && angles ? {
35556
- ...sums,
35557
- angles
35558
- } : void 0
36862
+ orientation: detail.global?.orientation,
36863
+ nutPrec: nutPrecFor(id, detail.global?.nut_prec)
35559
36864
  };
35560
36865
  }
35561
36866
  async function resolve(anchor, at) {