spacemap 0.1.15 → 0.1.17

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
@@ -110,7 +110,7 @@ var noop = () => {};
110
110
  function run_all(arr) {
111
111
  for (var i = 0; i < arr.length; i++) arr[i]();
112
112
  }
113
- function deferred() {
113
+ function deferred$1() {
114
114
  var resolve;
115
115
  var reject;
116
116
  return {
@@ -902,7 +902,7 @@ var Batch = class Batch {
902
902
  this.#discard_callbacks.add(fn);
903
903
  }
904
904
  settled() {
905
- return (this.#deferred ??= deferred()).promise;
905
+ return (this.#deferred ??= deferred$1()).promise;
906
906
  }
907
907
  static ensure() {
908
908
  if (current_batch === null) {
@@ -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.
@@ -12398,6 +13517,11 @@ function disposeChain(chain) {
12398
13517
  chain.uniforms.map.value = null;
12399
13518
  chain.uniforms.mask.value = null;
12400
13519
  }
13520
+ function chainSettled(chain, tiles) {
13521
+ const store = chain.store;
13522
+ if (!store || chain.dead || chain.uniforms.window.value[0].w === 0) return true;
13523
+ return store.windows.every((win) => settled(chain, store, tiles, win));
13524
+ }
12401
13525
  function restChain(chain, now) {
12402
13526
  switchOff(chain);
12403
13527
  if (now - chain.lastUsed > IDLE_DISPOSE_S * 1e3) disposeChain(chain);
@@ -12511,40 +13635,35 @@ float reliefValue( vec4 texel ) {
12511
13635
  return dot( texel, uReliefRead );
12512
13636
  }
12513
13637
 
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;
13638
+ // How the coordinates of each chart change along the sphere at a direction,
13639
+ // as a mat2x3 per chart: a step along the sphere times it is that step on the
13640
+ // chart, and it times a step on the chart is that step along the sphere.
13641
+ void reliefRates( vec3 dir, float side, out mat2x3 map, out mat2x3 cap ) {
12525
13642
  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 ) );
13643
+ map = mat2x3(
13644
+ vec3( dir.z, 0.0, - dir.x ) / ( 2.0 * PI * flat2 ),
13645
+ ( dir * dir.y - vec3( 0.0, 1.0, 0.0 ) ) / ( PI * sqrt( flat2 ) )
13646
+ );
12528
13647
  float fold = 1.0 + side * dir.y;
12529
13648
  float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
12530
13649
  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;
13650
+ vec3 capX = vec3( 1.0, - plane.x, 0.0 ) * capScale;
13651
+ vec3 capY = vec3( 0.0, - plane.y, side ) * capScale;
13652
+ cap = mat2x3( capX - dir * dot( capX, dir ), capY - dir * dot( capY, dir ) );
12536
13653
  }
12537
13654
 
12538
13655
  // Slope of the ground at a direction of the body's frame: a vector along the
12539
13656
  // unit sphere that points uphill, as long as the rise over the run. Also
12540
13657
  // gives the part of the fragment that the tiles answer for, and the finest
12541
13658
  // window that does, or -1.
12542
- vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int first ) {
13659
+ vec3 reliefTileSlope( vec3 dir, mat3x2 map, mat3x2 cap, float side, out float covered, out int first ) {
12543
13660
  covered = 0.0;
12544
13661
  first = - 1;
12545
13662
  vec3 slope = vec3( 0.0 );
12546
13663
  if ( uReliefWindow[ 0 ].w < 0.5 ) return slope;
12547
- ReliefRates rates = reliefRates( dir, charts.side );
13664
+ mat2x3 mapRates;
13665
+ mat2x3 capRates;
13666
+ reliefRates( dir, side, mapRates, capRates );
12548
13667
 
12549
13668
  float rows = ${4 .toFixed(1)};
12550
13669
  for ( int i = 0; i < ${7}; i ++ ) {
@@ -12554,7 +13673,7 @@ vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int fi
12554
13673
  vec2 spot;
12555
13674
  vec2 major;
12556
13675
  vec2 minor;
12557
- float alpha = tileWindowWeight( charts, window, grid, uReliefHome[ i ], uReliefFilter, spot, major, minor );
13676
+ float alpha = tileWindowWeight( map, cap, side, window, grid, uReliefHome[ i ], uReliefFilter, spot, major, minor );
12558
13677
  if ( alpha <= 0.0 ) continue;
12559
13678
  // Filtered, the mask falls towards a missing tile. The pixels beside
12560
13679
  // one are left out: their slope would read its blank slot.
@@ -12573,8 +13692,7 @@ vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int fi
12573
13692
  reliefValue( textureGrad( uReliefMap, st + east, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - east, gx, gy ) ),
12574
13693
  reliefValue( textureGrad( uReliefMap, st + south, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - south, gx, gy ) )
12575
13694
  ) * 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 );
13695
+ vec3 uphill = ( window.x < 0.5 ? mapRates : capRates ) * rise;
12578
13696
  slope += ( 1.0 - covered ) * alpha * uphill;
12579
13697
  covered += ( 1.0 - covered ) * alpha;
12580
13698
  if ( covered > 0.995 ) break;
@@ -12586,25 +13704,14 @@ function shadowGlsl(steps) {
12586
13704
  return `
12587
13705
  // One window as a shadow ray reads it. The ray is a line on the window's
12588
13706
  // 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 ) {
13707
+ // sample. 'sight' holds what a sample reads, as a mat2x4 and not a struct
13708
+ // (see the chain code): the fragment's place in the window's texture and how
13709
+ // far the ray goes there in a radian; then the part of the ray inside the
13710
+ // window, in radians along it, the window's layer and its tiles that are in,
13711
+ // a bit each. 'widest' is the longest step that the window's pixels still
13712
+ // hold, 'hands' the step from which the ray reads the next window, and
13713
+ // 'beyond' how far from the fragment the next window can be read at all.
13714
+ 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
13715
  vec4 window = uReliefWindow[ i ];
12609
13716
  vec4 grid = uReliefGrid[ i ];
12610
13717
  bool onMap = window.x < 0.5;
@@ -12612,7 +13719,7 @@ ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep )
12612
13719
  // In tiles from the window's north-west corner. The map is counted from
12613
13720
  // the point under the camera, so the fragment is on the side of the globe
12614
13721
  // nearest the window.
12615
- vec2 rel = onMap ? uReliefHome[ i ] + charts.mapAt * grid.xy : charts.capAt * grid.xy - grid.zw;
13722
+ vec2 rel = onMap ? uReliefHome[ i ] + map[ 0 ] * grid.xy : cap[ 0 ] * grid.xy - grid.zw;
12616
13723
  vec2 rate = ( onMap ? mapStep : capStep ) * grid.xy;
12617
13724
 
12618
13725
  // Half a pixel in from the window's sides: past it the filter reads the
@@ -12624,51 +13731,50 @@ ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep )
12624
13731
  vec2 near = min( a, b );
12625
13732
  vec2 far = max( a, b );
12626
13733
 
12627
- ReliefSight sight;
12628
13734
  // The texture repeats every window. The tile index of a patch alone takes
12629
13735
  // all the digits of a float: only its place in the window is added.
12630
13736
  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 );
13737
+ float leaves = min( far.x, far.y );
12635
13738
  // 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 ) );
13739
+ if ( ! onMap && ( window.x < 1.5 ) != ( side > 0.0 ) ) leaves = - 1.0;
13740
+ sight = mat2x4(
13741
+ slot.x / rows, - slot.y / rows, rate.x / rows, - rate.y / rows,
13742
+ max( near.x, near.y ), leaves, window.y, float( uReliefLoaded[ i ] )
13743
+ );
13744
+ widest = ${STEP_PIXELS.toFixed(2)} * PI / ( uReliefFilter.y * exp2( window.z ) );
12638
13745
  // Near a patch the next window can be some levels coarser. The ray then
12639
13746
  // stays here until its step is half of what that window holds: too wide
12640
13747
  // for these pixels, but they are the same ground as the fragment's. The
12641
13748
  // pyramid around a patch is not: it has none of the hills that only the
12642
13749
  // patch resolves, and close to the fragment it would stand in the ray.
12643
- sight.hands = sight.widest;
12644
- sight.beyond = 0.0;
13750
+ hands = widest;
13751
+ beyond = 0.0;
12645
13752
  if ( i + 1 < ${7} ) {
12646
13753
  float next = PI / ( uReliefFilter.y * exp2( uReliefWindow[ i + 1 ].z ) );
12647
- sight.hands = max( sight.hands, 0.5 * ${STEP_PIXELS.toFixed(2)} * next );
13754
+ hands = max( hands, 0.5 * ${STEP_PIXELS.toFixed(2)} * next );
12648
13755
  // Within half a pixel of a window, its height is that of the fragment's
12649
13756
  // own pixel, and the fragment stands above or below that by all the
12650
13757
  // relief that the pixel averages away.
12651
- sight.beyond = 0.5 * next;
13758
+ beyond = 0.5 * next;
12652
13759
  }
12653
- sight.loaded = uReliefLoaded[ i ];
12654
- return sight;
12655
13760
  }
12656
13761
 
12657
13762
  // Height of the ground 't' radians along the ray, as a part of the pyramid's
12658
13763
  // 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;
13764
+ float reliefSightValue( mat2x4 sight, float t ) {
13765
+ if ( t < sight[ 1 ].x || t > sight[ 1 ].y ) return - 1.0;
13766
+ vec2 st = sight[ 0 ].xy + t * sight[ 0 ].zw;
12662
13767
  // Both tiles that the filter can read must be in.
12663
- if ( sight.loaded != ${(1 << 4 * 4) - 1} ) {
13768
+ if ( sight[ 1 ].w != ${((1 << 4 * 4) - 1).toFixed(1)} ) {
13769
+ int loaded = int( sight[ 1 ].w );
12664
13770
  vec2 tile = vec2( st.x, - st.y ) * ${4 .toFixed(1)};
12665
13771
  float inset = 0.5 / uReliefFilter.y;
12666
13772
  ivec2 a = ivec2( floor( tile - inset ) ) & ${4 - 1};
12667
13773
  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 ) );
13774
+ int both = ( loaded >> ( a.x + ${4} * a.y ) ) & ( loaded >> ( b.x + ${4} * b.y ) );
12669
13775
  if ( ( both & 1 ) == 0 ) return - 1.0;
12670
13776
  }
12671
- return reliefValue( textureLod( uReliefMap, vec3( st, sight.from.z ), 0.0 ) );
13777
+ return reliefValue( textureLod( uReliefMap, vec3( st, sight[ 1 ].z ), 0.0 ) );
12672
13778
  }
12673
13779
 
12674
13780
  // Part of the Sun's disc that the ground leaves in sight of the fragment at
@@ -12681,23 +13787,28 @@ float reliefSightValue( ReliefSight sight, float t ) {
12681
13787
  // read from the window whose pixels are about as wide as the step. A shadow
12682
13788
  // is then sharp beside what casts it, at a cost that does not grow with its
12683
13789
  // 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 ) );
13790
+ float reliefTileShadow( vec3 dir, mat3x2 map, mat3x2 cap, float side, vec3 toSun, float tanElev, float sunSize, int first ) {
13791
+ mat2x3 mapRates;
13792
+ mat2x3 capRates;
13793
+ reliefRates( dir, side, mapRates, capRates );
13794
+ vec2 mapStep = toSun * mapRates;
13795
+ vec2 capStep = toSun * capRates;
12688
13796
 
12689
13797
  int drawn = 0;
12690
13798
  for ( int i = 0; i < ${7}; i ++ ) {
12691
13799
  if ( uReliefWindow[ i ].w > 0.5 ) drawn = i + 1;
12692
13800
  }
12693
13801
  int w = first;
12694
- ReliefSight sight;
13802
+ mat2x4 sight;
13803
+ float widest;
13804
+ float hands;
13805
+ float beyond;
12695
13806
  float own = - 1.0;
12696
13807
  for ( int i = 0; i < ${7}; i ++ ) {
12697
13808
  if ( i < first ) continue;
12698
13809
  if ( i >= drawn ) break;
12699
13810
  w = i;
12700
- sight = reliefSight( i, charts, mapStep, capStep );
13811
+ reliefSight( i, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12701
13812
  own = reliefSightValue( sight, 0.0 );
12702
13813
  if ( own >= 0.0 ) break;
12703
13814
  }
@@ -12714,7 +13825,7 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12714
13825
  // The ratio that takes steps from one pixel to the reach: 'rate' is its
12715
13826
  // logarithm times the count, and solves rate / ( exp( rate ) - 1 ) = part.
12716
13827
  float count = ${steps.toFixed(1)};
12717
- float pixel = sight.widest / ${STEP_PIXELS.toFixed(2)};
13828
+ float pixel = widest / ${STEP_PIXELS.toFixed(2)};
12718
13829
  float part = count * pixel / reach;
12719
13830
  float rate = 0.0;
12720
13831
  if ( part < 1.0 ) {
@@ -12738,16 +13849,16 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12738
13849
  for ( int i = 1; i <= ${steps}; i ++ ) {
12739
13850
  float before = t;
12740
13851
  t = t * ratio + head;
12741
- if ( w + 1 < drawn && t - before > sight.hands ) {
13852
+ if ( w + 1 < drawn && t - before > hands ) {
12742
13853
  w ++;
12743
- sight = reliefSight( w, charts, mapStep, capStep );
13854
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12744
13855
  }
12745
13856
  float value = reliefSightValue( sight, t );
12746
13857
  // The ray left this window, or a tile of it is missing.
12747
13858
  if ( value < 0.0 && w + 1 < drawn ) {
12748
- if ( t < sight.beyond ) continue;
13859
+ if ( t < beyond ) continue;
12749
13860
  w ++;
12750
- sight = reliefSight( w, charts, mapStep, capStep );
13861
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12751
13862
  value = reliefSightValue( sight, t );
12752
13863
  }
12753
13864
  if ( value < 0.0 ) continue;
@@ -12769,19 +13880,19 @@ float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev,
12769
13880
  float crest = ${(STEEPEST * .5).toFixed(2)} * around / max( worst, 1e-9 ) * perDisc;
12770
13881
  if ( hidden < 1.0 && hidden + crest > - 1.0 ) {
12771
13882
  w = worstWindow;
12772
- sight = reliefSight( w, charts, mapStep, capStep );
13883
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12773
13884
  for ( int k = 0; k < ${REFINE_STEPS}; k ++ ) {
12774
13885
  around *= 0.5;
12775
- if ( w > start && around < sight.widest ) {
13886
+ if ( w > start && around < widest ) {
12776
13887
  w --;
12777
- sight = reliefSight( w, charts, mapStep, capStep );
13888
+ reliefSight( w, map, cap, side, mapStep, capStep, sight, widest, hands, beyond );
12778
13889
  }
12779
13890
  float centre = worst;
12780
13891
  for ( int e = 0; e < 2; e ++ ) {
12781
13892
  float at = centre + ( e == 0 ? - around : around );
12782
13893
  // Nearer than a pixel, the window holds no crest: only the slope
12783
13894
  // of the fragment's own ground, which is the normal's to light.
12784
- if ( at < sight.widest * ${(1 / STEP_PIXELS).toFixed(4)} ) continue;
13895
+ if ( at < widest * ${(1 / STEP_PIXELS).toFixed(4)} ) continue;
12785
13896
  float value = reliefSightValue( sight, at );
12786
13897
  if ( value < 0.0 ) continue;
12787
13898
  float stands = ( ( value - own ) * uReliefScale / at - tanElev - 0.5 * at ) * perDisc;
@@ -12895,7 +14006,7 @@ var TILE_LIGHT = `
12895
14006
  float up = dot(sunward, selfDir);
12896
14007
  vec3 along = sunward - selfDir * up;
12897
14008
  float run = length(along);
12898
- if (run > 1e-4) tileLight = reliefTileShadow(selfDir, selfCharts, along / run, up / run, uSelfSunSize, selfFirst);
14009
+ if (run > 1e-4) tileLight = reliefTileShadow(selfDir, selfMap, selfCap, selfSide, along / run, up / run, uSelfSunSize, selfFirst);
12899
14010
  if (tileLight < 0.0) byTiles = 0.0;
12900
14011
  }`;
12901
14012
  function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir, tiles) {
@@ -13029,10 +14140,10 @@ function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir, tiles)
13029
14140
  vec3 selfDir = normalize(vSelfDir);
13030
14141
  // The part of the fragment that the tiles answer for.
13031
14142
  float selfCovered = 0.0;
13032
- ${tiles ? "TileCharts selfCharts = tileChartsFrom(uReliefAnchor, vReliefOff);\n int selfFirst = -1;" : ""}
14143
+ ${tiles ? "mat3x2 selfMap;\n mat3x2 selfCap;\n float selfSide;\n tileChartsFrom(uReliefAnchor, vReliefOff, selfMap, selfCap, selfSide);\n int selfFirst = -1;" : ""}
13033
14144
  {
13034
14145
  vec3 slope = vec3(0.0);
13035
- ${tiles ? "slope = reliefTileSlope(selfDir, selfCharts, selfCovered, selfFirst);" : ""}
14146
+ ${tiles ? "slope = reliefTileSlope(selfDir, selfMap, selfCap, selfSide, selfCovered, selfFirst);" : ""}
13036
14147
  if (selfCovered < 0.995 && uSelfScale > 0.0) slope += (1.0 - selfCovered) * selfMapSlope(selfDir);
13037
14148
  float steep = length(slope);
13038
14149
  if (steep > ${SLOPE_CLAMP.toFixed(3)}) slope *= ${SLOPE_CLAMP.toFixed(3)} / steep;
@@ -13125,34 +14236,23 @@ async function readHeightRows(bitmap, y0, y1) {
13125
14236
  }
13126
14237
  return out;
13127
14238
  }
14239
+ var PYRAMID_TIER = "pyramid";
14240
+ var pyramidFailed = /* @__PURE__ */ new WeakSet();
14241
+ function pyramidHeightsServe(bo, renderer) {
14242
+ const tiles = bo.displacementMeta?.tiles;
14243
+ return !!tiles && tiles.scale_km !== void 0 && tilesBase() !== "" && textureAllowed(tiles.distribution) && renderer.extensions.has("OES_texture_float_linear") && !pyramidFailed.has(bo);
14244
+ }
13128
14245
  async function swapDisplacementTier(bo, tier) {
13129
14246
  const meta = bo.displacementMeta;
13130
14247
  if (!meta || !bo.mesh || bo.displacementLoading || bo.displacementTier === tier) return;
13131
14248
  bo.displacementLoading = true;
13132
14249
  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;
14250
+ const tex = tier === "pyramid" ? await pyramidMap(bo, meta) : await tierMap(meta, tier);
14251
+ if (!tex) return;
14252
+ if (!bo.mesh || !bo.displacementMap) {
14253
+ tex.dispose();
14254
+ return;
14255
+ }
13156
14256
  const material = bo.mesh.material;
13157
14257
  const old = material.displacementMap;
13158
14258
  material.displacementMap = tex;
@@ -13164,6 +14264,96 @@ async function swapDisplacementTier(bo, tier) {
13164
14264
  bo.displacementLoading = false;
13165
14265
  }
13166
14266
  }
14267
+ async function tierMap(meta, tier) {
14268
+ const bitmap = await fetchHeightBitmap(displacementTierUrl(meta, tier));
14269
+ if (!bitmap) return null;
14270
+ const w = bitmap.width;
14271
+ const h = bitmap.height;
14272
+ const rows = await readHeightRows(bitmap, 0, h - 1);
14273
+ bitmap.close();
14274
+ if (!rows) return null;
14275
+ const tmp = new Uint8Array(w);
14276
+ for (let y = 0; y < h >> 1; y++) {
14277
+ const top = rows.subarray(y * w, (y + 1) * w);
14278
+ const bottom = rows.subarray((h - 1 - y) * w, (h - y) * w);
14279
+ tmp.set(top);
14280
+ top.set(bottom);
14281
+ bottom.set(tmp);
14282
+ }
14283
+ const tex = new DataTexture(rows, w, h, RedFormat, UnsignedByteType);
14284
+ tex.colorSpace = NoColorSpace;
14285
+ tex.magFilter = LinearFilter;
14286
+ tex.minFilter = LinearFilter;
14287
+ tex.generateMipmaps = false;
14288
+ tex.unpackAlignment = 1;
14289
+ tex.needsUpdate = true;
14290
+ return tex;
14291
+ }
14292
+ async function pyramidMap(bo, meta) {
14293
+ if (!meta.tiles) return null;
14294
+ try {
14295
+ return await pyramidHeights(meta, meta.tiles);
14296
+ } catch (err) {
14297
+ console.warn(`Failed to load the height pyramid of ${meta.id}:`, err);
14298
+ pyramidFailed.add(bo);
14299
+ return null;
14300
+ }
14301
+ }
14302
+ async function pyramidHeights(meta, tiles) {
14303
+ const signal = new AbortController().signal;
14304
+ const halves = await Promise.all([0, 1].map((x) => loadHeightTile(tileUrl(tiles, "", 0, x, 0), true, signal)));
14305
+ const size = halves[0].size;
14306
+ const width = 2 * size;
14307
+ const data = new Float32Array(width * size);
14308
+ const gain = (tiles.scale_km ?? 0) / 65535 / meta.scale_km;
14309
+ const offset = ((tiles.bias_km ?? 0) - meta.bias_km) / meta.scale_km;
14310
+ halves.forEach(({ heights }, x) => {
14311
+ for (let row = 0; row < size; row++) {
14312
+ const src = row * size;
14313
+ const dst = row * width + x * size;
14314
+ for (let i = 0; i < size; i++) data[dst + i] = heights[src + i] * gain + offset;
14315
+ }
14316
+ });
14317
+ const tex = new DataTexture(data, width, size, RedFormat, FloatType);
14318
+ tex.colorSpace = NoColorSpace;
14319
+ tex.magFilter = LinearFilter;
14320
+ tex.minFilter = LinearMipmapLinearFilter;
14321
+ tex.generateMipmaps = false;
14322
+ tex.mipmaps = mipChain(data, width, size);
14323
+ tex.needsUpdate = true;
14324
+ return tex;
14325
+ }
14326
+ function mipChain(data, width, height) {
14327
+ const chain = [{
14328
+ data,
14329
+ width,
14330
+ height
14331
+ }];
14332
+ while (width > 1 || height > 1) {
14333
+ const w = Math.max(1, width >> 1);
14334
+ const h = Math.max(1, height >> 1);
14335
+ const sx = width / w;
14336
+ const sy = height / h;
14337
+ const out = new Float32Array(w * h);
14338
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
14339
+ let sum = 0;
14340
+ for (let dy = 0; dy < sy; dy++) {
14341
+ const row = (y * sy + dy) * width + x * sx;
14342
+ for (let dx = 0; dx < sx; dx++) sum += data[row + dx];
14343
+ }
14344
+ out[y * w + x] = sum / (sx * sy);
14345
+ }
14346
+ chain.push({
14347
+ data: out,
14348
+ width: w,
14349
+ height: h
14350
+ });
14351
+ data = out;
14352
+ width = w;
14353
+ height = h;
14354
+ }
14355
+ return chain;
14356
+ }
13167
14357
  function bilinearHeightTexel(data, w, h, fx, fy) {
13168
14358
  const x0 = Math.floor(fx);
13169
14359
  const y0 = Math.floor(fy);
@@ -13177,7 +14367,8 @@ function bilinearHeightTexel(data, w, h, fx, fy) {
13177
14367
  const r1 = clampRow(y0 + 1) * w;
13178
14368
  const top = data[r0 + c0] * (1 - tx) + data[r0 + c1] * tx;
13179
14369
  const bot = data[r1 + c0] * (1 - tx) + data[r1 + c1] * tx;
13180
- return (top * (1 - ty) + bot * ty) / 255;
14370
+ const texel = top * (1 - ty) + bot * ty;
14371
+ return data instanceof Float32Array ? texel : texel / 255;
13181
14372
  }
13182
14373
  function disposeDisplacementFromMaterial(material) {
13183
14374
  if (!material.displacementMap) return;
@@ -13367,8 +14558,9 @@ function loadLowField(bodyId, meta) {
13367
14558
  function heightFieldFor(bo, bodyId) {
13368
14559
  const image = bo.displacementMap?.image;
13369
14560
  if (!image) return null;
13370
- if (image.data instanceof Uint8Array && image.width && image.height) return {
13371
- data: image.data,
14561
+ const data = image.data;
14562
+ if ((data instanceof Uint8Array || data instanceof Float32Array) && image.width && image.height) return {
14563
+ data,
13372
14564
  width: image.width,
13373
14565
  height: image.height,
13374
14566
  bottomUp: true
@@ -13864,20 +15056,24 @@ var AttitudeTrack = class {
13864
15056
  this.files = manifest.files;
13865
15057
  }
13866
15058
  covers(jd) {
13867
- return jd >= this.startJd && jd <= this.endJd && this.files.length > 0;
15059
+ return this.coversTdb(utcToTdb(jd));
15060
+ }
15061
+ coversTdb(tdb) {
15062
+ return tdb >= this.startJd && tdb <= this.endJd && this.files.length > 0;
13868
15063
  }
13869
15064
  orientationAt(jd, out) {
13870
- if (!this.covers(jd)) return false;
13871
- const ci = this.fileIndexFor(jd);
15065
+ const tdb = utcToTdb(jd);
15066
+ if (!this.coversTdb(tdb)) return false;
15067
+ const ci = this.fileIndexFor(tdb);
13872
15068
  const chunk = this.ensure(ci);
13873
15069
  if (!chunk) return false;
13874
15070
  this.ensure(ci - 1);
13875
15071
  this.ensure(ci + 1);
13876
- this.sampleChunk(jd, ci, chunk);
15072
+ this.sampleChunk(tdb, ci, chunk);
13877
15073
  const baseline = this.baselines?.[this.files[ci].baseline_index ?? 0];
13878
15074
  if (baseline) {
13879
15075
  const { axis, rate_rad_s, anchor, anchor_jd } = baseline;
13880
- const tSeconds = (jd - anchor_jd) * SECONDS_PER_DAY;
15076
+ const tSeconds = (tdb - anchor_jd) * SECONDS_PER_DAY;
13881
15077
  _baselineAxis.set(axis[0], axis[1], axis[2]);
13882
15078
  _qBaseline.setFromAxisAngle(_baselineAxis, rate_rad_s * tSeconds);
13883
15079
  _qb.set(anchor[1], anchor[2], anchor[3], anchor[0]);
@@ -14152,29 +15348,6 @@ function setSurfaceMap(material, map, tintHex) {
14152
15348
  material.needsUpdate = true;
14153
15349
  }
14154
15350
 
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
15351
  //#endregion
14179
15352
  //#region src/lib/scene/objects/surface/photometry.ts
14180
15353
  var WEIGHT_POLY = [
@@ -14184,16 +15357,24 @@ var WEIGHT_POLY = [
14184
15357
  ];
14185
15358
  var GRAZE_FLOOR = .05;
14186
15359
  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);
15360
+ float lunarLambertWeight(float phaseDeg, float scale) {
15361
+ 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
15362
  }
14190
15363
 
14191
- float lunarLambertGain(float phaseDeg, float mu0, float mu) {
14192
- float weight = lunarLambertWeight(phaseDeg);
15364
+ float lunarLambertGain(float phaseDeg, float mu0, float mu, float scale) {
15365
+ float weight = lunarLambertWeight(phaseDeg, scale);
14193
15366
  float sun = max(mu0, 0.0);
14194
15367
  return 2.0 * weight / max(sun + max(mu, sun), ${GRAZE_FLOOR.toFixed(3)}) + 1.0 - weight;
14195
15368
  }
14196
15369
  `;
15370
+ function makePhotometryUniforms(scale) {
15371
+ return { uLunarWeightScale: { value: scale ?? 1 } };
15372
+ }
15373
+ function lunarLambertWeight(phaseDeg, scale) {
15374
+ const [a, b, c] = WEIGHT_POLY;
15375
+ const moon = Math.min(1, Math.max(0, 1 + phaseDeg * (a + phaseDeg * (b + phaseDeg * c))));
15376
+ return Math.min(1, scale * moon);
15377
+ }
14197
15378
  var DRY_GROUND = `
14198
15379
  #ifdef USE_ROUGHNESSMAP
14199
15380
  reflectedLight.directSpecular *= texelRoughness.g;
@@ -14212,18 +15393,20 @@ var LUNAR_LAMBERT = `
14212
15393
  reflectedLight.directDiffuse *= lunarLambertGain(
14213
15394
  phaseDeg,
14214
15395
  dot(geometryNormal, sunView),
14215
- dot(geometryNormal, geometryViewDir)
15396
+ dot(geometryNormal, geometryViewDir),
15397
+ uLunarWeightScale
14216
15398
  );
14217
15399
  }
14218
15400
  `;
14219
- function attachSurfacePhotometry(material, bodyId, sunDir) {
15401
+ function attachSurfacePhotometry(material, bodyId, uniforms, sunDir) {
14220
15402
  const airless = !getAtmosphereParams(bodyId);
14221
15403
  chainShaderHook(material, airless ? "photometryAirless" : "photometry", (shader) => {
14222
15404
  let pars = "";
14223
15405
  let light = DRY_GROUND;
14224
15406
  if (airless) {
14225
15407
  shader.uniforms.uPhotometrySunDir = sunDir ?? getEclipseSceneUniforms().uSunDir;
14226
- pars = `uniform vec3 uPhotometrySunDir;\n${LUNAR_LAMBERT_GLSL}`;
15408
+ Object.assign(shader.uniforms, uniforms);
15409
+ pars = `uniform vec3 uPhotometrySunDir;\nuniform float uLunarWeightScale;\n${LUNAR_LAMBERT_GLSL}`;
14227
15410
  light += LUNAR_LAMBERT;
14228
15411
  }
14229
15412
  shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>\n${pars}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>\n${light}`);
@@ -14231,92 +15414,60 @@ function attachSurfacePhotometry(material, bodyId, sunDir) {
14231
15414
  }
14232
15415
 
14233
15416
  //#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;
15417
+ //#region src/lib/scene/objects/body/surface-albedo.ts
15418
+ var UNKNOWN_SURFACE_COLOR = "#6b6b6b";
15419
+ function luminance$1(c) {
15420
+ return .2126 * c.r + .7152 * c.g + .0722 * c.b;
15421
+ }
15422
+ function discAlbedoPerColour(airless, lunarWeightScale) {
15423
+ if (!airless) return 2 / 3;
15424
+ return (2 + lunarLambertWeight(0, lunarWeightScale ?? 1)) / 3;
15425
+ }
15426
+ var _hex = new Color();
15427
+ function surfaceAlbedoTarget(body) {
15428
+ if (!body.color) return null;
15429
+ return (body.geometricAlbedo ?? luminance$1(_hex.set(body.color))) / discAlbedoPerColour(!getAtmosphereParams(body.id), body.lunarWeightScale);
15430
+ }
15431
+ function untexturedSurfaceColour(body, out = new Color()) {
15432
+ const target = surfaceAlbedoTarget(body);
15433
+ if (target === null) return out.set(UNKNOWN_SURFACE_COLOR);
15434
+ out.set(body.color);
15435
+ return out.multiplyScalar(target / luminance$1(out));
15436
+ }
15437
+ function shinerAlbedo(body, map, out) {
15438
+ if (body.color) out.set(body.color);
15439
+ else if (body.geometricAlbedo === void 0) return null;
15440
+ else {
15441
+ const mean = map && meanMapColor(map);
15442
+ if (mean) out.copy(mean);
15443
+ else out.setRGB(1, 1, 1);
15444
+ }
15445
+ const albedo = body.geometricAlbedo ?? luminance$1(out);
15446
+ return out.multiplyScalar(1.5 * albedo / luminance$1(out));
14243
15447
  }
14244
- var PLANETSHINE_GLSL = `
14245
- #define PLANETSHINE_SHINERS ${2}
14246
- uniform vec4 uShineDir[PLANETSHINE_SHINERS];
14247
- uniform vec3 uShineLight[PLANETSHINE_SHINERS];
14248
15448
 
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
- };
15449
+ //#endregion
15450
+ //#region src/lib/scene/objects/body/shape-model-policy.ts
15451
+ var PREFER_MODEL_OVER_DEM = /* @__PURE__ */ new Set([]);
15452
+ function shapeModelSkipReason(global) {
15453
+ if (!global?.model_name) return "no-bundle";
15454
+ if (isLowEndDevice() && global.render_quality !== "high") return "low-end-device";
15455
+ if (global.displacement && !PREFER_MODEL_OVER_DEM.has(global.id)) return "dem-preferred";
15456
+ return null;
14268
15457
  }
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}`);
15458
+
15459
+ //#endregion
15460
+ //#region src/lib/scene/objects/body/orientation-apply.ts
15461
+ function applyBodyOrientation(body, orientation, nutPrec, ctx, systemId) {
15462
+ body.orientation = orientation;
15463
+ body.nutPrec = nutPrecFor(body.data.id, nutPrec);
15464
+ if (ctx) ctx.bodies.dataVersion++;
15465
+ ctx?.credits.registerOrientation({
15466
+ bodyId: body.data.id,
15467
+ systemId: systemId ?? body.data.id,
15468
+ source: orientation.source,
15469
+ reference: orientation.reference
14274
15470
  });
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
15471
  }
14321
15472
 
14322
15473
  //#endregion
@@ -14647,7 +15798,11 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
14647
15798
  bo.body.modelBaseFrame = baseFrame ?? void 0;
14648
15799
  const root = withBaseFrame(gltf.scene, baseFrame);
14649
15800
  const anchor = meta.model_anchor ? new Vector3(...meta.model_anchor) : null;
14650
- fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
15801
+ if (!fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor)) {
15802
+ disposeGltf(gltf.scene);
15803
+ bo.noPhysical = "model";
15804
+ return;
15805
+ }
14651
15806
  enableShadows(root);
14652
15807
  modelScene.add(root);
14653
15808
  root.visible = !bo.isCrashed;
@@ -14683,7 +15838,7 @@ async function loadNaturalBodyModel(bo, ctx) {
14683
15838
  console.info(`Skipping ${detail.global?.render_quality ?? "unrated"} shape mesh for ${bo.body.data.id}: ${skip}`);
14684
15839
  return;
14685
15840
  }
14686
- if (detail.global?.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, ctx);
15841
+ if (detail.global?.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, detail.global.nut_prec, ctx);
14687
15842
  const metaPromise = fetchBundleMeta(slug);
14688
15843
  const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/high.glb`, "models"));
14689
15844
  if ((bo.modelLoadEpoch ?? 0) !== epoch) {
@@ -14703,14 +15858,18 @@ async function loadNaturalBodyModel(bo, ctx) {
14703
15858
  return;
14704
15859
  }
14705
15860
  const root = gltf.scene;
14706
- fitToUnitRadius(root);
15861
+ if (!fitToUnitRadius(root)) {
15862
+ disposeGltf(gltf.scene);
15863
+ return;
15864
+ }
14707
15865
  root.userData.radialIndex = buildRadialIndex(root);
14708
- const material = makeShapeModelMaterial(bo.body.data.color ?? bodyMeshColor(bo.body.data));
14709
- attachSurfacePhotometry(material, bo.body.data.id);
15866
+ const material = makeShapeModelMaterial(untexturedSurfaceColour(bo.body.data));
15867
+ bo.photometry ??= makePhotometryUniforms(bo.body.data.lunarWeightScale);
15868
+ attachSurfacePhotometry(material, bo.body.data.id, bo.photometry);
14710
15869
  bo.planetshine = attachPlanetshine(material, bo.planetshine ?? void 0);
14711
15870
  applyShapeModelMaterial(root, material);
14712
15871
  const sphereMap = bo.mesh ? bo.mesh.material.map : null;
14713
- if (sphereMap) setShapeModelMap(root, sphereMap, bodyMeshColor(bo.body.data), bo.body.data.color);
15872
+ if (sphereMap) setShapeModelMap(root, sphereMap, untexturedSurfaceColour(bo.body.data), bo.body.data.color);
14714
15873
  enableShadows(root);
14715
15874
  attachModelEclipse(bo, root);
14716
15875
  if (meta.true_scale) {
@@ -14796,7 +15955,7 @@ function fitToUnitRadius(root, anchor = null) {
14796
15955
  const size = bbox.getSize(new Vector3());
14797
15956
  const center = bbox.getCenter(new Vector3());
14798
15957
  const maxDim = Math.max(size.x, size.y, size.z);
14799
- if (maxDim <= 0) return;
15958
+ if (maxDim <= 0) return false;
14800
15959
  const k = 2 / maxDim;
14801
15960
  root.userData.boxCenter = center.clone().multiplyScalar(k);
14802
15961
  if (anchor) center.addScaledVector(anchor, maxDim / 2);
@@ -14811,6 +15970,7 @@ function fitToUnitRadius(root, anchor = null) {
14811
15970
  const offset = root.userData.centerOffset.length();
14812
15971
  root.userData.minRadius = Math.max(0, fit.minRadius - offset);
14813
15972
  root.userData.maxRadius = root.userData.halfExtents.length() + offset;
15973
+ return true;
14814
15974
  }
14815
15975
  var OCCLUDER_SLICES = 8;
14816
15976
  var OCCLUDER_SAMPLE_TARGET = 4096;
@@ -14963,7 +16123,7 @@ function nomenclatureBodyId(focused, bodyObjects) {
14963
16123
  if (bodyObjects.get(focused.data.id)?.isLanded) return focused.data.parentId;
14964
16124
  return focused.data.id;
14965
16125
  }
14966
- var DEG2RAD$5 = Math.PI / 180;
16126
+ var DEG2RAD$4 = Math.PI / 180;
14967
16127
  var MIN_BODY_SCREEN_RADIUS_PX = 128;
14968
16128
  var LABEL_DISC_FRACTION = .9;
14969
16129
  var MIN_FEATURE_PX = 24;
@@ -15016,8 +16176,8 @@ async function attachNomenclatureLabels(bo, canvas, onFeatureSelect) {
15016
16176
  let dispKm = null;
15017
16177
  if (detail.global.displacement) {
15018
16178
  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
16179
+ latRad: f.lat * DEG2RAD$4,
16180
+ lonRad: f.lon * DEG2RAD$4
15021
16181
  })));
15022
16182
  if (bo.nomenclatureLabels || !bo.mesh) return;
15023
16183
  }
@@ -15041,7 +16201,7 @@ async function attachNomenclatureLabels(bo, canvas, onFeatureSelect) {
15041
16201
  el.dataset.featureId = String(feature.featureId);
15042
16202
  attachCanvasForwarders(el, canvas);
15043
16203
  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);
16204
+ const [ux, uy, uz] = bodyFixedUnit(feature.lat * DEG2RAD$4, feature.lon * DEG2RAD$4);
15045
16205
  const rf = radial[i];
15046
16206
  const obj = new CSS2DObject(el);
15047
16207
  obj.position.set(rf * ux, rf * uy, rf * uz);
@@ -15066,8 +16226,8 @@ async function sampleModelSurface(model, features) {
15066
16226
  const he = model.userData.halfExtents;
15067
16227
  for (let i = 0; i < n; i++) {
15068
16228
  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;
16229
+ const latRad = features[i].lat * DEG2RAD$4;
16230
+ const lonRad = features[i].lon * DEG2RAD$4;
15071
16231
  const r = castModelRadius(model, latRad, lonRad, _castNormal);
15072
16232
  if (r !== null) radii[i] = r;
15073
16233
  else {
@@ -16240,7 +17400,10 @@ uniform vec2 uTileFilter;
16240
17400
  ${TILE_CHAIN_GLSL}
16241
17401
  vec3 tileColour( vec3 below ) {
16242
17402
  if ( uTileWindow[ 0 ].w < 0.5 ) return below;
16243
- TileCharts charts = tileChartsFrom( uTileAnchor, vTileOff );
17403
+ mat3x2 map;
17404
+ mat3x2 cap;
17405
+ float side;
17406
+ tileChartsFrom( uTileAnchor, vTileOff, map, cap, side );
16244
17407
 
16245
17408
  float rows = ${4 .toFixed(1)};
16246
17409
  vec3 colour = vec3( 0.0 );
@@ -16251,7 +17414,7 @@ vec3 tileColour( vec3 below ) {
16251
17414
  vec2 spot;
16252
17415
  vec2 major;
16253
17416
  vec2 minor;
16254
- float alpha = tileWindowWeight( charts, window, uTileGrid[ i ], uTileHome[ i ], uTileFilter, spot, major, minor );
17417
+ float alpha = tileWindowWeight( map, cap, side, window, uTileGrid[ i ], uTileHome[ i ], uTileFilter, spot, major, minor );
16255
17418
  if ( alpha <= 0.0 ) continue;
16256
17419
  ivec3 slot = ivec3( int( mod( spot.x, rows ) ), int( mod( spot.y, rows ) ), int( window.y ) );
16257
17420
  alpha *= texelFetch( uTileMask, slot, 0 ).r;
@@ -16394,8 +17557,9 @@ async function swapBodyTexture(bo, tier, frame, textureLoader) {
16394
17557
  return;
16395
17558
  }
16396
17559
  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);
17560
+ const data = bo.body.data;
17561
+ setSurfaceMap(material, texture, data.color);
17562
+ if (bo.model) setShapeModelMap(bo.model, texture, untexturedSurfaceColour(data), data.color);
16399
17563
  bo.textureTier = tier;
16400
17564
  bo.textureFrame = frame;
16401
17565
  } catch (err) {
@@ -16404,6 +17568,13 @@ async function swapBodyTexture(bo, tier, frame, textureLoader) {
16404
17568
  bo.textureLoading = false;
16405
17569
  }
16406
17570
  }
17571
+ function applySurfaceColour(bo) {
17572
+ if (!bo.mesh || !bo.photometry || isModelBearing(bo.body) || bo.appearance?.surfaceOwned) return;
17573
+ const material = bo.mesh.material;
17574
+ if (material.map) return;
17575
+ untexturedSurfaceColour(bo.body.data, material.color);
17576
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
17577
+ }
16407
17578
  function applyRadiiToMesh(bo, radii) {
16408
17579
  if (!bo.mesh) return;
16409
17580
  const { a, b, c } = radii;
@@ -16415,6 +17586,11 @@ function applyRadiiToMesh(bo, radii) {
16415
17586
  kmToScene(c),
16416
17587
  kmToScene(b)
16417
17588
  ];
17589
+ bo.body.drawnRadii = {
17590
+ a,
17591
+ b,
17592
+ c
17593
+ };
16418
17594
  if (bo.atmosphere) {
16419
17595
  const eqKm = Math.max(a, b);
16420
17596
  syncAtmosphereEllipsoid(bo.atmosphere, eqKm, c, kmToScene(eqKm));
@@ -16427,14 +17603,15 @@ async function loadBodyTexture(bo, textureLoader, currentJd, scene, maxTextureSi
16427
17603
  const detail = await fetchObjectDetail(bo.body.data.id, false);
16428
17604
  ctx?.bodies.orientationRead.add(bo.body.data.id);
16429
17605
  if (!detail.global) return;
16430
- if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, ctx);
17606
+ if (detail.global.orientation && !bo.body.orientation) applyBodyOrientation(bo.body, detail.global.orientation, detail.global.nut_prec, ctx);
16431
17607
  if (detail.global.radii && bo.radiusScene > 0 && !bo.radiiApplied && !detail.global.displacement?.absolute_radius) applyRadiiToMesh(bo, detail.global.radii);
16432
17608
  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
- }
17609
+ const data = bo.body.data;
17610
+ data.color = detail.global.sbdb?.color ?? detail.global.color;
17611
+ data.geometricAlbedo = detail.global.sbdb?.geometric_albedo ?? detail.global.geometric_albedo;
17612
+ data.lunarWeightScale = detail.global.lunar_weight_scale;
17613
+ if (bo.photometry) bo.photometry.uLunarWeightScale.value = data.lunarWeightScale ?? 1;
17614
+ applySurfaceColour(bo);
16438
17615
  if (!detail.global.map_texture_available) return;
16439
17616
  const bodyId = bo.body.data.id;
16440
17617
  const surface = pickTexture([detail.global.texture && {
@@ -16476,9 +17653,9 @@ function unloadBodyTexture(bo) {
16476
17653
  if (!material.map) return;
16477
17654
  material.map.dispose();
16478
17655
  material.map = null;
16479
- material.color.set(bodyMeshColor(bo.body.data));
17656
+ untexturedSurfaceColour(bo.body.data, material.color);
16480
17657
  material.needsUpdate = true;
16481
- if (bo.model) setShapeModelMap(bo.model, null, bodyMeshColor(bo.body.data));
17658
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
16482
17659
  bo.textureTier = void 0;
16483
17660
  }
16484
17661
  async function loadBodyTextureTier(bo, tier, frame, textureLoader) {
@@ -16505,17 +17682,6 @@ async function downloadSystemMeta(barycenterId) {
16505
17682
  if (!resp.ok) throw new Error(`systems/${barycenterId}.json: ${resp.status}`);
16506
17683
  return resp.json();
16507
17684
  }
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
17685
  async function readSpin(body, ctx) {
16520
17686
  const id = body.data.id;
16521
17687
  const known = () => !!body.orientation || ctx.bodies.orientationRead.has(id);
@@ -16526,8 +17692,8 @@ async function readSpin(body, ctx) {
16526
17692
  const record = barycenterId ? (await fetchSystemMeta(barycenterId))?.[id] : void 0;
16527
17693
  const detail = record?.orientation ? null : await fetchObjectDetail(id, false);
16528
17694
  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);
17695
+ if (record?.orientation) applyBodyOrientation(body, record.orientation, record.nut_prec, ctx, barycenterId);
17696
+ else if (detail?.global?.orientation) applyBodyOrientation(body, detail.global.orientation, detail.global.nut_prec, ctx);
16531
17697
  ctx.bodies.orientationRead.add(id);
16532
17698
  if (body.orientation) ctx.bodies.orientationVersion++;
16533
17699
  }
@@ -16550,8 +17716,14 @@ async function loadSystemData(barycenterId, bodyObjects, scene, textureLoader, c
16550
17716
  const bo = bodyObjects.get(bodyId);
16551
17717
  if (!bo?.mesh) continue;
16552
17718
  ctx?.bodies.orientationRead.add(bodyId);
17719
+ const data = bo.body.data;
17720
+ data.color ??= bodyMeta.color;
17721
+ data.geometricAlbedo ??= bodyMeta.geometric_albedo;
17722
+ data.lunarWeightScale ??= bodyMeta.lunar_weight_scale;
17723
+ if (bo.photometry) bo.photometry.uLunarWeightScale.value = data.lunarWeightScale ?? 1;
17724
+ applySurfaceColour(bo);
16553
17725
  if (bodyMeta.orientation) {
16554
- adoptSystemSpin(bo.body, bodyMeta.orientation, bodyMeta.nut_prec, ctx, barycenterId);
17726
+ applyBodyOrientation(bo.body, bodyMeta.orientation, bodyMeta.nut_prec, ctx, barycenterId);
16555
17727
  applyOrientation(bo.mesh, bodyMeta.orientation, currentJd, bo.body.nutPrec);
16556
17728
  }
16557
17729
  if (bodyMeta.radii && bo.radiusScene > 0 && !bo.radiiApplied && !bodyMeta.displacement?.absolute_radius) applyRadiiToMesh(bo, bodyMeta.radii);
@@ -16589,6 +17761,7 @@ function unloadSystemTextures(barycenterId, bodyObjects, scene, ctx) {
16589
17761
  }
16590
17762
  for (const ring of bo.rings) {
16591
17763
  ring.planetShadow?.disable();
17764
+ ring.ringshine?.disable();
16592
17765
  ring.mesh.removeFromParent();
16593
17766
  disposeRingNode(ring);
16594
17767
  }
@@ -16609,6 +17782,7 @@ var ZoneRefresher = class {
16609
17782
  latestDate;
16610
17783
  failureStreak = 0;
16611
17784
  staleNotified = false;
17785
+ staleCheckRunning = false;
16612
17786
  retries = new RetryBackoff(() => this.wakes++);
16613
17787
  wakes = 0;
16614
17788
  wakeTimer;
@@ -16664,10 +17838,17 @@ var ZoneRefresher = class {
16664
17838
  recordFailure(key) {
16665
17839
  this.retries.fail(key);
16666
17840
  this.failureStreak++;
16667
- if (this.failureStreak >= STALE_FAILURE_STREAK && !this.staleNotified) {
17841
+ if (this.failureStreak < STALE_FAILURE_STREAK || this.staleNotified || this.staleCheckRunning) return;
17842
+ this.staleCheckRunning = true;
17843
+ dataVersionChanged().then((changed) => {
17844
+ this.staleCheckRunning = false;
17845
+ if (!changed) {
17846
+ this.failureStreak = 0;
17847
+ return;
17848
+ }
16668
17849
  this.staleNotified = true;
16669
17850
  this.ctx.onDataStale?.();
16670
- }
17851
+ });
16671
17852
  }
16672
17853
  wakeIn(ms) {
16673
17854
  this.wakeTimer ??= setTimeout(() => {
@@ -17636,7 +18817,7 @@ var SimClock = class {
17636
18817
  //#region src/lib/math/spherical.ts
17637
18818
  var { PI, sin, cos, asin, atan2, sqrt } = Math;
17638
18819
  var DEG$9 = 180 / PI;
17639
- var RAD$5 = PI / 180;
18820
+ var RAD$4 = PI / 180;
17640
18821
  function applyQuat(v, q, inverse) {
17641
18822
  const [x, y, z] = v;
17642
18823
  const [qx, qy, qz, qw] = inverse ? [
@@ -17672,8 +18853,8 @@ function cartesianToSpherical(cam, target, bodyQuat) {
17672
18853
  };
17673
18854
  }
17674
18855
  function sphericalToCartesian(target, lat, lon, distance, bodyQuat) {
17675
- const latR = lat * RAD$5;
17676
- const lonR = lon * RAD$5;
18856
+ const latR = lat * RAD$4;
18857
+ const lonR = lon * RAD$4;
17677
18858
  let ox;
17678
18859
  let oy;
17679
18860
  let oz;
@@ -17706,7 +18887,7 @@ function offsetFacing(focus, toward, elevationDeg, distance) {
17706
18887
  hx = 0;
17707
18888
  hz = 1;
17708
18889
  }
17709
- const e = elevationDeg * RAD$5;
18890
+ const e = elevationDeg * RAD$4;
17710
18891
  const c = cos(e);
17711
18892
  return [
17712
18893
  distance * c * hx,
@@ -18041,26 +19222,6 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM, dash) {
18041
19222
  });
18042
19223
  }
18043
19224
 
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
19225
  //#endregion
18065
19226
  //#region src/lib/scene/setup/bloom-high-pass.ts
18066
19227
  var MAX_SPAN = 16;
@@ -18106,6 +19267,26 @@ function installBloomHighPass(pass) {
18106
19267
  });
18107
19268
  }
18108
19269
 
19270
+ //#endregion
19271
+ //#region src/lib/scene/setup/output-dither.ts
19272
+ var DITHER_GLSL = `
19273
+ vec3 outputDither(vec3 color) {
19274
+ // ES 1.00 gives gl_FragCoord at medium precision. Copy it before the arithmetic.
19275
+ vec3 p3 = gl_FragCoord.xyx;
19276
+ p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
19277
+ p3 += dot(p3, p3.yxz + 33.33);
19278
+ vec3 u = fract((p3.xxy + p3.yzz) * p3.zyx);
19279
+ // The difference of two uniform samples has a triangular distribution on (-1, 1).
19280
+ vec3 noise = u - u.yzx;
19281
+ // The noise goes to 0 in the last step at each end. Thus black stays black and no value clips.
19282
+ vec3 amplitude = clamp(min(color, 1.0 - color) * 255.0, 0.0, 1.0);
19283
+ return color + noise * amplitude / 255.0;
19284
+ }
19285
+ `;
19286
+ function installOutputDither(pass) {
19287
+ 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}");
19288
+ }
19289
+
18109
19290
  //#endregion
18110
19291
  //#region src/lib/scene/setup/three-boot.ts
18111
19292
  var CAMERA_FAR_DEFAULT = 1e5;
@@ -18178,6 +19359,7 @@ function bootThree(canvas, labelContainer, ctx) {
18178
19359
  installBloomHighPass(bloomPass);
18179
19360
  composer.addPass(bloomPass);
18180
19361
  const outputPass = new OutputPass();
19362
+ installOutputDither(outputPass);
18181
19363
  outputPass.needsSwap = false;
18182
19364
  composer.addPass(outputPass);
18183
19365
  return {
@@ -18186,7 +19368,7 @@ function bootThree(canvas, labelContainer, ctx) {
18186
19368
  scene,
18187
19369
  camera,
18188
19370
  composer,
18189
- maxSceneSamples: sampleCounts[0] ?? 0,
19371
+ maxSceneSamples: sampleCounts?.[0] ?? 0,
18190
19372
  bloomPass,
18191
19373
  ambientLight,
18192
19374
  shadowLight
@@ -18855,7 +20037,7 @@ gl_Position = projectionMatrix * mvPosition;
18855
20037
  `;
18856
20038
  var anchors = /* @__PURE__ */ new WeakMap();
18857
20039
  var _modelView = new Matrix4();
18858
- var _inverse = new Matrix4();
20040
+ var _inverse$1 = new Matrix4();
18859
20041
  var _eye = new Vector3();
18860
20042
  function patch(material, anchor) {
18861
20043
  chainShaderHook(material, "cameraRelative", (shader) => {
@@ -18873,7 +20055,7 @@ function attachCameraRelative(mesh, material) {
18873
20055
  patch(material, anchor);
18874
20056
  mesh.onBeforeRender = (_renderer, _scene, camera) => {
18875
20057
  _modelView.multiplyMatrices(camera.matrixWorldInverse, mesh.matrixWorld);
18876
- _eye.setFromMatrixPosition(_inverse.copy(_modelView).invert());
20058
+ _eye.setFromMatrixPosition(_inverse$1.copy(_modelView).invert());
18877
20059
  anchor.local.value.set(Math.fround(_eye.x), Math.fround(_eye.y), Math.fround(_eye.z));
18878
20060
  anchor.view.value.copy(anchor.local.value).applyMatrix4(_modelView);
18879
20061
  };
@@ -18911,19 +20093,21 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, re
18911
20093
  let starExtras = null;
18912
20094
  let eclipseShadow = null;
18913
20095
  let planetshine = null;
20096
+ let photometry = null;
18914
20097
  let atmosphere = null;
18915
20098
  let sunTint;
18916
20099
  if (!isVirtual && !noPhysical) {
18917
20100
  if (isStar) starExtras = buildStarExtras(root, radius, color);
18918
20101
  const segments = isStar ? STAR_SPHERE_SEGMENTS : BODY_SPHERE_SEGMENTS;
18919
20102
  const geometry = new SphereGeometry(radius, segments, segments);
18920
- const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: bodyMeshColor(body.data) });
20103
+ const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: untexturedSurfaceColour(body.data) });
18921
20104
  mesh = new Mesh(geometry, material);
18922
20105
  if (modelBearing) mesh.visible = false;
18923
20106
  if (!isStar) {
18924
20107
  eclipseShadow = attachEclipseShadowToBody(material);
18925
20108
  attachCameraRelative(mesh, material);
18926
- attachSurfacePhotometry(material, id);
20109
+ photometry = makePhotometryUniforms(body.data.lunarWeightScale);
20110
+ attachSurfacePhotometry(material, id, photometry);
18927
20111
  if (!modelBearing) planetshine = attachPlanetshine(material);
18928
20112
  }
18929
20113
  root.add(mesh);
@@ -18974,6 +20158,7 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, re
18974
20158
  selfShadow: null,
18975
20159
  eclipseShadow,
18976
20160
  planetshine,
20161
+ photometry,
18977
20162
  nomenclatureLabels: null,
18978
20163
  model: null
18979
20164
  });
@@ -18992,10 +20177,9 @@ function upgradeBodyMesh(bo, scene, clickables, meshToBody) {
18992
20177
  bo.noPhysical = "radius";
18993
20178
  return;
18994
20179
  }
18995
- const color = bodyMeshColor(body.data);
18996
20180
  const segments = BODY_SPHERE_SEGMENTS;
18997
20181
  const geometry = new SphereGeometry(radiusScene, segments, segments);
18998
- const material = new MeshStandardMaterial({ color });
20182
+ const material = new MeshStandardMaterial({ color: untexturedSurfaceColour(body.data) });
18999
20183
  const mesh = new Mesh(geometry, material);
19000
20184
  if (modelBearing) mesh.visible = false;
19001
20185
  bo.root.add(mesh);
@@ -19004,7 +20188,8 @@ function upgradeBodyMesh(bo, scene, clickables, meshToBody) {
19004
20188
  meshToBody.set(mesh, body);
19005
20189
  bo.eclipseShadow = attachEclipseShadowToBody(material);
19006
20190
  attachCameraRelative(mesh, material);
19007
- attachSurfacePhotometry(material, body.data.id);
20191
+ bo.photometry = makePhotometryUniforms(body.data.lunarWeightScale);
20192
+ attachSurfacePhotometry(material, body.data.id, bo.photometry);
19008
20193
  if (!modelBearing) bo.planetshine = attachPlanetshine(material);
19009
20194
  bo.currentSegments = segments;
19010
20195
  const atmoParams = getAtmosphereParams(body.data.id);
@@ -19041,6 +20226,7 @@ function downgradeBodyMesh(bo, scene, clickables, meshToBody) {
19041
20226
  bo.currentSegments = void 0;
19042
20227
  bo.eclipseShadow = null;
19043
20228
  bo.planetshine = null;
20229
+ bo.photometry = null;
19044
20230
  bo.sunTint = void 0;
19045
20231
  bo.radiiApplied = false;
19046
20232
  bo.semiAxesScene = void 0;
@@ -19191,9 +20377,9 @@ function revert(bo, appearance) {
19191
20377
  if (applied.surfaceOwned && appearance?.surface === void 0) {
19192
20378
  applied.surfaceMap?.dispose();
19193
20379
  material.map = null;
19194
- material.color.set(bodyMeshColor(bo.body.data));
20380
+ untexturedSurfaceColour(bo.body.data, material.color);
19195
20381
  material.needsUpdate = true;
19196
- if (bo.model) setShapeModelMap(bo.model, null, bodyMeshColor(bo.body.data));
20382
+ if (bo.model) setShapeModelMap(bo.model, null, material.color);
19197
20383
  applied.surfaceOwned = void 0;
19198
20384
  applied.surfaceUrl = void 0;
19199
20385
  applied.surfaceMap = void 0;
@@ -19245,7 +20431,7 @@ async function applyToBody(bo, appearance, textureLoader, current) {
19245
20431
  const live = liveMaterial(bo);
19246
20432
  if (texture && live) {
19247
20433
  setSurfaceMap(live, texture);
19248
- if (bo.model) setShapeModelMap(bo.model, texture, bodyMeshColor(bo.body.data));
20434
+ if (bo.model) setShapeModelMap(bo.model, texture, untexturedSurfaceColour(bo.body.data));
19249
20435
  applied.surfaceUrl = appearance.surface;
19250
20436
  applied.surfaceMap = texture;
19251
20437
  }
@@ -19465,95 +20651,6 @@ function makePointCloudFromBuffer(positions, drawCount, texture, color, size = 4
19465
20651
  return points;
19466
20652
  }
19467
20653
 
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
20654
  //#endregion
19558
20655
  //#region src/lib/math/orbit/curves.ts
19559
20656
  function orbitalElementsToParabola(el, numPoints = 512, rMaxAU = 50) {
@@ -19797,6 +20894,10 @@ function commitTrail(line, posArr, trailArr, fullArr, n, isOpenCurve, useTrail)
19797
20894
 
19798
20895
  //#endregion
19799
20896
  //#region src/lib/scene/objects/trail/builder.ts
20897
+ var FAT_TRAIL_MIN_PIXEL_RATIO = 1.5;
20898
+ function drawsFat(lineWidth) {
20899
+ return lineWidth > 1 || renderPixelRatio() >= FAT_TRAIL_MIN_PIXEL_RATIO;
20900
+ }
19800
20901
  function writeBufferVerticesWithLiveHead(body, buffer, posArr, cx, cy, cz, basisPos, transform) {
19801
20902
  const head = body.trailAnchor ?? body.position;
19802
20903
  if (!head) return 0;
@@ -19820,7 +20921,7 @@ function makeBufferTrail(body, trailBuffer, color, basisPos, lineWidth) {
19820
20921
  const fullAlphas = new Float32Array(geomCap);
19821
20922
  const trailAlphas = new Float32Array(geomCap);
19822
20923
  if (total > 0) writeTrailAlphas(fullAlphas.subarray(0, total), trailAlphas.subarray(0, total), true, useTrail);
19823
- const isFat = lineWidth > 1;
20924
+ const isFat = drawsFat(lineWidth);
19824
20925
  const obj = isFat ? buildFatLineFromThin(geomCap, posArr, trailAlphas, fullAlphas, total, color, lineWidth) : buildThinLineFromArrays(posArr, trailAlphas, fullAlphas, total, color);
19825
20926
  obj.frustumCulled = false;
19826
20927
  obj.visible = false;
@@ -19871,7 +20972,7 @@ function makeTrail(body, color, basisPos = [
19871
20972
  posArr[k * 3 + 1] = validPoints[k][1] + by;
19872
20973
  posArr[k * 3 + 2] = validPoints[k][2] + bz;
19873
20974
  }
19874
- const isFat = lineWidth > 1;
20975
+ const isFat = drawsFat(lineWidth);
19875
20976
  const obj = isFat ? buildFatLineFromThin(bufferCapacity, posArr, trailAlphas, fullAlphas, validPoints.length, color, lineWidth) : buildThinLineFromArrays(posArr, trailAlphas, fullAlphas, validPoints.length, color);
19876
20977
  obj.frustumCulled = false;
19877
20978
  obj.visible = false;
@@ -20004,6 +21105,7 @@ var SystemDataLoader = class {
20004
21105
  lastBaryId = null;
20005
21106
  pendingUnloads = /* @__PURE__ */ new Set();
20006
21107
  prefetched = /* @__PURE__ */ new Set();
21108
+ loads = 0;
20007
21109
  constructor(scene, ctx, renderer, textureLoader, bodyObjects, clock, onLoaded) {
20008
21110
  this.scene = scene;
20009
21111
  this.ctx = ctx;
@@ -20049,10 +21151,14 @@ var SystemDataLoader = class {
20049
21151
  return isTopLevelParent(baryId) ? null : baryId;
20050
21152
  }
20051
21153
  load(baryId) {
21154
+ this.loads++;
20052
21155
  loadSystemData(baryId, this.bodyObjects, this.scene, this.textureLoader, this.clock.jd, this.renderer.capabilities.maxTextureSize, this.ctx).then(() => {
20053
21156
  this.onLoaded();
20054
21157
  this.ctx.bodies.orientationVersion++;
20055
- });
21158
+ }).finally(() => this.loads--);
21159
+ }
21160
+ isLoading() {
21161
+ return this.loads > 0;
20056
21162
  }
20057
21163
  drainPendingUnloads() {
20058
21164
  if (this.pendingUnloads.size === 0) return;
@@ -20765,7 +21871,7 @@ function collectDebugStats(params) {
20765
21871
 
20766
21872
  //#endregion
20767
21873
  //#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}};";
21874
+ 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
21875
  var blob = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent], { type: "text/javascript;charset=utf-8" });
20770
21876
  function WorkerWrapper(options) {
20771
21877
  let objURL;
@@ -22247,8 +23353,8 @@ function between(points, jds, jd, from = 0, to = points.length) {
22247
23353
  //#endregion
22248
23354
  //#region src/lib/math/travel/state.ts
22249
23355
  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);
23356
+ var COS_EPS = Math.cos(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
23357
+ var SIN_EPS = Math.sin(EARTH_OBLIQUITY_DEG * DEG2RAD$1);
22252
23358
  function planeRotation(w, i, om, equatorial) {
22253
23359
  const cosW = Math.cos(w * DEG2RAD$1);
22254
23360
  const sinW = Math.sin(w * DEG2RAD$1);
@@ -22271,8 +23377,8 @@ function perifocalToEcliptic(px, py, rot) {
22271
23377
  let y = rot.m21 * px + rot.m22 * py;
22272
23378
  let z = rot.m31 * px + rot.m32 * py;
22273
23379
  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;
23380
+ const yEcl = y * COS_EPS + z * SIN_EPS;
23381
+ const zEcl = -y * SIN_EPS + z * COS_EPS;
22276
23382
  y = yEcl;
22277
23383
  z = zEcl;
22278
23384
  }
@@ -22292,11 +23398,12 @@ function eclipticToScene(r) {
22292
23398
  }
22293
23399
  function elementsToState(el, jd, muKm3S2) {
22294
23400
  const { a, e, ma, n, epoch, q, tp } = el;
23401
+ const t = elementsJd(el, jd);
22295
23402
  const { om, w } = propagateOrbitAngles(el, jd);
22296
23403
  const equatorial = el.equatorial ?? false;
22297
23404
  if (!isFinite(a) || a === 0 || !isFinite(n) || n <= 0) {
22298
23405
  if (q == null || tp == null || !isFinite(q) || !isFinite(tp) || q <= 0) return null;
22299
- const solved = solveBarker(q, tp, jd);
23406
+ const solved = solveBarker(q, tp, t);
22300
23407
  if (!solved) return null;
22301
23408
  const mu = muKm3S2 && muKm3S2 > 0 ? muKm3S2 : GM_SUN_KM3_S2;
22302
23409
  const rKm = solved.r * AU_KM;
@@ -22308,7 +23415,7 @@ function elementsToState(el, jd, muKm3S2) {
22308
23415
  const nRadPerSec = n * DEG2RAD$1 / SEC_PER_DAY$1;
22309
23416
  const mu = muKm3S2 && muKm3S2 > 0 ? muKm3S2 : nRadPerSec * nRadPerSec * Math.abs(aKm) * Math.abs(aKm) * Math.abs(aKm);
22310
23417
  if (!isFinite(mu) || mu <= 0) return null;
22311
- const M = (ma + n * (jd - epoch)) * DEG2RAD$1;
23418
+ const M = (ma + n * (t - epoch)) * DEG2RAD$1;
22312
23419
  let nu;
22313
23420
  let rKm;
22314
23421
  if (e < 1 || a > 0) {
@@ -23328,13 +24435,17 @@ function classifyLagrange(geocentric, earthToSun, massRatio = EARTH_SUN_MASS_RAT
23328
24435
  function existsAt(d, jd) {
23329
24436
  return !(d.visibleFromDays !== void 0 && jd - 2451545 < d.visibleFromDays);
23330
24437
  }
24438
+ function withinValidity(d, jd) {
24439
+ const t = elementsJd(d, jd);
24440
+ return t >= d.validityStart && t <= d.validityEnd;
24441
+ }
23331
24442
  function sitsOnParent(d) {
23332
24443
  return d.a === 0 && (d.objectType === ObjectType.BARYCENTER || d.objectType === ObjectType.LAGRANGE_POINT || isMajorBody(d.objectType));
23333
24444
  }
23334
24445
  function elementsOffset$1(d, jd) {
23335
24446
  if (d.pageOnly) return "never";
23336
24447
  if (!existsAt(d, jd)) return "not-yet";
23337
- if (jd < d.validityStart || jd > d.validityEnd) return "no-data";
24448
+ if (!withinValidity(d, jd)) return "no-data";
23338
24449
  let offset;
23339
24450
  const satrec = d.satrec;
23340
24451
  if (satrec) offset = sgp4PositionScene(satrec, jd);
@@ -23817,11 +24928,82 @@ var PositionDiagnostics = class {
23817
24928
  }
23818
24929
  };
23819
24930
 
24931
+ //#endregion
24932
+ //#region src/lib/scene/shaders/planetshine-uniforms.ts
24933
+ var _shiners = [];
24934
+ var _albedos = /* @__PURE__ */ new WeakMap();
24935
+ function measuredShinerAlbedo(bo, map) {
24936
+ const { color, geometricAlbedo } = bo.body.data;
24937
+ let entry = _albedos.get(bo);
24938
+ if (!entry || entry.color !== color || entry.geometricAlbedo !== geometricAlbedo || entry.map !== map) {
24939
+ entry = {
24940
+ color,
24941
+ geometricAlbedo,
24942
+ map,
24943
+ albedo: shinerAlbedo(bo.body.data, map, new Color())
24944
+ };
24945
+ _albedos.set(bo, entry);
24946
+ }
24947
+ return entry.albedo;
24948
+ }
24949
+ function shineAlbedo(bo, material, out) {
24950
+ const measured = measuredShinerAlbedo(bo, material.map);
24951
+ if (measured) return out.copy(measured);
24952
+ out.copy(material.color);
24953
+ const mean = material.map && meanMapColor(material.map);
24954
+ return mean ? out.multiply(mean) : out;
24955
+ }
24956
+ function updatePlanetshineUniforms(bodyObjects, occluders, realistic, sunScale) {
24957
+ const sunPos = bodyObjects.get(SUN_ID)?.body.position;
24958
+ let n = 0;
24959
+ if (sunPos) for (const bo of occluders) {
24960
+ if (!bo.mesh) continue;
24961
+ const material = bo.mesh.material;
24962
+ const [x, y, z] = bo.body.position;
24963
+ const sx = sunPos[0] - x;
24964
+ const sy = sunPos[1] - y;
24965
+ const sz = sunPos[2] - z;
24966
+ const sunDist = Math.hypot(sx, sy, sz);
24967
+ const s = _shiners[n++] ??= {
24968
+ x: 0,
24969
+ y: 0,
24970
+ z: 0,
24971
+ radius: 0,
24972
+ sunX: 0,
24973
+ sunY: 0,
24974
+ sunZ: 0,
24975
+ sunlight: 0,
24976
+ albedo: new Color()
24977
+ };
24978
+ s.x = x;
24979
+ s.y = y;
24980
+ s.z = z;
24981
+ s.radius = bo.radiusScene;
24982
+ s.sunX = sx / sunDist;
24983
+ s.sunY = sy / sunDist;
24984
+ s.sunZ = sz / sunDist;
24985
+ s.sunlight = SUN_LIGHT_INTENSITY * sunScale * (realistic ? sunIrradianceFactor(sunDist) : 1);
24986
+ shineAlbedo(bo, material, s.albedo);
24987
+ }
24988
+ for (const bo of bodyObjects.values()) {
24989
+ const shine = bo.planetshine;
24990
+ const at = bo.body.position;
24991
+ if (!shine || !at) continue;
24992
+ if (bo.cachedDist > 0 && bo.radiusScene < bo.cachedDist * 1e-4) continue;
24993
+ selectShiners(shine, at[0], at[1], at[2], _shiners, n);
24994
+ }
24995
+ }
24996
+
23820
24997
  //#endregion
23821
24998
  //#region src/lib/scene/shaders/ring-uniforms.ts
23822
24999
  var SUN_RADIUS_SCENE = kmToScene(695700);
23823
25000
  var SHEETS_PER_PX = 2;
25001
+ var SIN_B_STEP = 1e-4;
25002
+ var RING_LIGHT_MIN_PX = 2;
23824
25003
  var tmp = new Vector3();
25004
+ var _axis = new Vector3();
25005
+ var _sunLocal = new Vector3();
25006
+ var _inverse = new Quaternion();
23825
25007
  function annulusView(camPos, center, poleDir, innerScene, outerScene) {
23826
25008
  tmp.subVectors(camPos, center);
23827
25009
  const axial = tmp.dot(poleDir);
@@ -23874,6 +25056,35 @@ function updateRingShaders(bodyObjects, focusTruePos, realistic, overexpose, cam
23874
25056
  ps.uRingShadowPoleDir.value.copy(psOnRing.uPlanetPoleDir.value);
23875
25057
  ps.uRingShadowCenter.value.copy(psOnRing.uPlanetCenter.value);
23876
25058
  }
25059
+ updateRingLight(bo, camPos, pxPerRad);
25060
+ }
25061
+ }
25062
+ function updateRingLight(bo, camPos, pxPerRad) {
25063
+ const caster = bo.rings.reduce((best, ring) => ring.intensityScale > best.intensityScale ? ring : best);
25064
+ const uniforms = caster.material.uniforms;
25065
+ const sunDir = uniforms.uSunDir.value;
25066
+ const pole = caster.planetShadowOnRing.uPlanetPoleDir.value;
25067
+ const center = caster.planetShadowOnRing.uPlanetCenter.value;
25068
+ const sinB = Math.min(Math.max(sunDir.dot(pole), -1), 1);
25069
+ const light = caster.light;
25070
+ const seen = caster.outerScene / Math.max(camPos.distanceTo(center), 1e-12) * pxPerRad >= RING_LIGHT_MIN_PX;
25071
+ if (seen && !(Math.abs(sinB - light.patchesSinB) < SIN_B_STEP)) {
25072
+ ringshinePatches(light.patches, light.profile, sinB, light.planet);
25073
+ light.patchesSinB = sinB;
25074
+ }
25075
+ _axis.copy(sunDir).addScaledVector(pole, -sinB);
25076
+ if (_axis.lengthSq() < 1e-12) _axis.set(Math.abs(pole.x) < .9 ? 1 : 0, Math.abs(pole.x) < .9 ? 0 : 1, 0).cross(pole);
25077
+ _axis.normalize();
25078
+ if (caster.ringshine) writeRingshine(caster.ringshine, light, _axis, sinB, uniforms.uLightScale.value);
25079
+ _sunLocal.copy(_axis).applyQuaternion(_inverse.copy(caster.mesh.quaternion).invert());
25080
+ for (const ring of bo.rings) {
25081
+ if (!ring.mesh.visible) continue;
25082
+ if (seen && ring.light.planetLightSinB !== light.patchesSinB) fillPlanetLight(ring.light, light.patchesSinB, light);
25083
+ const ringUniforms = ring.material.uniforms;
25084
+ ringUniforms.uSunAzimuth.value.set(_sunLocal.x, _sunLocal.z).normalize();
25085
+ const albedo = ringUniforms.uPlanetAlbedo.value;
25086
+ if (bo.mesh) shineAlbedo(bo, bo.mesh.material, albedo);
25087
+ else albedo.setRGB(0, 0, 0);
23877
25088
  }
23878
25089
  }
23879
25090
 
@@ -23919,15 +25130,15 @@ function seasonalAtmosphereParams(base, lsDeg) {
23919
25130
  seasonal: void 0
23920
25131
  };
23921
25132
  }
23922
- var cache = /* @__PURE__ */ new WeakMap();
25133
+ var cache$1 = /* @__PURE__ */ new WeakMap();
23923
25134
  var LS_QUANTUM_DEG = .25;
23924
25135
  function seasonalParamsForJd(base, jd) {
23925
25136
  if (!base.seasonal) return base;
23926
25137
  const lsQ = Math.round(marsSolarLongitudeDeg(jd) / LS_QUANTUM_DEG);
23927
- const hit = cache.get(base);
25138
+ const hit = cache$1.get(base);
23928
25139
  if (hit && hit.lsQ === lsQ) return hit.params;
23929
25140
  const params = seasonalAtmosphereParams(base, lsQ * LS_QUANTUM_DEG);
23930
- cache.set(base, {
25141
+ cache$1.set(base, {
23931
25142
  lsQ,
23932
25143
  params
23933
25144
  });
@@ -24073,6 +25284,7 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
24073
25284
 
24074
25285
  //#endregion
24075
25286
  //#region src/lib/scene/shaders/eclipse-uniforms.ts
25287
+ var FOCUS = new Vector3();
24076
25288
  function collectOccluders(bodyObjects, out) {
24077
25289
  let n = 0;
24078
25290
  for (const bo of bodyObjects.values()) {
@@ -24083,19 +25295,17 @@ function collectOccluders(bodyObjects, out) {
24083
25295
  out[n++] = bo;
24084
25296
  }
24085
25297
  out.length = n;
24086
- if (n > 32) {
24087
- out.sort((a, b) => b.radiusScene - a.radiusScene);
24088
- out.length = 32;
24089
- }
24090
25298
  return out;
24091
25299
  }
24092
- function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
25300
+ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, cameraDistance, air) {
24093
25301
  const eclipse = getEclipseSceneUniforms();
25302
+ const pool = getOccluderPool();
24094
25303
  const sunBo = bodyObjects.get(SUN_ID);
24095
25304
  const sunPos = sunBo?.body.position;
24096
25305
  if (!sunBo || !sunPos) {
24097
25306
  eclipse.uSunAngularRadius.value = 0;
24098
25307
  eclipse.uOccluderCount.value = 0;
25308
+ pool.count = 0;
24099
25309
  return;
24100
25310
  }
24101
25311
  const [fx, fy, fz] = focusTruePos;
@@ -24108,17 +25318,20 @@ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
24108
25318
  eclipse.uSunAngularRadius.value = Math.asin(Math.min(sunBo.radiusScene / sunDist, 1));
24109
25319
  } else eclipse.uSunAngularRadius.value = 0;
24110
25320
  const n = occluders.length;
24111
- const slots = eclipse.uOccluders.value;
24112
- const leaks = eclipse.uOccluderLeak.value;
25321
+ while (pool.occluders.length < n) {
25322
+ pool.occluders.push(new Vector4());
25323
+ pool.leaks.push(new Vector4());
25324
+ }
24113
25325
  for (let i = 0; i < n; i++) {
24114
25326
  const bo = occluders[i];
24115
25327
  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);
25328
+ pool.occluders[i].set(bx - fx, by - fy, bz - fz, bo.radiusScene);
25329
+ if (air && bo.atmosphere) atmosphereLeak(bo.atmosphere.params, bo.atmosphere.planetRadiusKm, pool.leaks[i]);
25330
+ else pool.leaks[i].set(0, 0, 0, 0);
24119
25331
  }
24120
- eclipse.uOccluderCount.value = n;
25332
+ pool.count = n;
24121
25333
  const sunDir = eclipse.uSunDir.value;
25334
+ eclipse.uOccluderCount.value = cullOccludersFor(eclipse.uOccluders.value, FOCUS, cameraDistance, sunDir, eclipse.uOccluderLeak.value);
24122
25335
  for (const bo of bodyObjects.values()) {
24123
25336
  const self = bo.eclipseShadow;
24124
25337
  const at = bo.body.position;
@@ -24130,52 +25343,6 @@ function updateEclipseUniforms(bodyObjects, occluders, focusTruePos, air) {
24130
25343
  }
24131
25344
  }
24132
25345
 
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
25346
  //#endregion
24180
25347
  //#region src/lib/scene/shaders/sun-shadow-light.ts
24181
25348
  var LIGHT_DIST = 10;
@@ -24414,6 +25581,10 @@ function gridGeometry(grid) {
24414
25581
  return geometry;
24415
25582
  }
24416
25583
  var tiledStates = /* @__PURE__ */ new WeakMap();
25584
+ var deferred = /* @__PURE__ */ new WeakSet();
25585
+ function terrainSettled(bo) {
25586
+ return !tiledStates.get(bo)?.abort && !deferred.has(bo);
25587
+ }
24417
25588
  var MAX_BUILD_FAILURES = 2;
24418
25589
  function heightTilesOf(bo) {
24419
25590
  const tiles = bo.displacementMeta?.tiles;
@@ -24468,7 +25639,10 @@ function updateTiledWindow(bo, mesh, tiles, altitude, centerTheta, centerPhi, in
24468
25639
  if (held) {
24469
25640
  const still = drift(centerTheta, centerPhi, held.centerTheta, held.centerPhi);
24470
25641
  if (kept === stepLevel && still < RECENTER_FRAC * held.angRadius) return true;
24471
- if (performance.now() - held.builtAt < REBUILD_MIN_MS) return true;
25642
+ if (performance.now() - held.builtAt < REBUILD_MIN_MS) {
25643
+ deferred.add(bo);
25644
+ return true;
25645
+ }
24472
25646
  }
24473
25647
  const grid = planNestedGrid(centerTheta, centerPhi, altitude, .5 * meta.scale_km / radiusKm, finest, bandCells, stepLevel);
24474
25648
  const thetas = grid.rows.at;
@@ -24530,6 +25704,7 @@ function updateTiledWindow(bo, mesh, tiles, altitude, centerTheta, centerPhi, in
24530
25704
  return !!held;
24531
25705
  }
24532
25706
  function updateTerrainWindow(bo, camera, eligible, invalidate) {
25707
+ deferred.delete(bo);
24533
25708
  const mesh = bo.mesh;
24534
25709
  const tex = bo.displacementMap;
24535
25710
  if (!eligible || !mesh || !tex) {
@@ -24568,7 +25743,10 @@ function updateTerrainWindow(bo, camera, eligible, invalidate) {
24568
25743
  if (w.stepLevel === stepLevel) {
24569
25744
  if (drift(centerTheta, centerPhi, w.centerTheta, w.centerPhi) < RECENTER_FRAC * w.angRadius) return true;
24570
25745
  }
24571
- if (performance.now() - w.builtAt < REBUILD_MIN_MS) return true;
25746
+ if (performance.now() - w.builtAt < REBUILD_MIN_MS) {
25747
+ deferred.add(bo);
25748
+ return true;
25749
+ }
24572
25750
  }
24573
25751
  const thetaLo = Math.max(0, centerTheta - angRadius);
24574
25752
  const thetaHi = Math.min(Math.PI, centerTheta + angRadius);
@@ -24665,6 +25843,15 @@ function desiredDemTier(altitudeRadii, current) {
24665
25843
  if (tierRank(down) < tierRank(current)) return down;
24666
25844
  return current;
24667
25845
  }
25846
+ function bodyLoading(bo) {
25847
+ if (bo.textureLoading || bo.displacementLoading || bo.clouds?.textureLoading) return true;
25848
+ if (bo.appearance?.loading || bo.modelLoadPromise) return true;
25849
+ const surface = bo.tileOverlay;
25850
+ if (surface && bo.surfaceTiles && !chainSettled(surface.chain, bo.surfaceTiles)) return true;
25851
+ const relief = bo.reliefTiles;
25852
+ if (relief && !chainSettled(relief.chain, relief.tiles)) return true;
25853
+ return !terrainSettled(bo);
25854
+ }
24668
25855
  function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, focusedId, jd, appearances, invalidate) {
24669
25856
  appearances.apply(bodyObjects, textureLoader);
24670
25857
  const fovRad = camera.fov * Math.PI / 180;
@@ -24702,7 +25889,9 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
24702
25889
  updateTileOverlay(bo, camera, renderer, devicePx, settings.showSurfaceTexture && !bo.appearance?.surfaceOwned, invalidate);
24703
25890
  updateReliefTiles(bo, camera, renderer, devicePx, invalidate);
24704
25891
  const dispTiers = bo.displacementMeta?.tiers;
24705
- if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
25892
+ if (bo.displacementMap && !bo.displacementLoading && pyramidHeightsServe(bo, renderer)) {
25893
+ if (bo.displacementTier !== "pyramid") swapDisplacementTier(bo, PYRAMID_TIER);
25894
+ } else if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
24706
25895
  const maxTex = renderer.capabilities.maxTextureSize;
24707
25896
  const capRank = maxTex >= 16383 && !reliefTilesServe(bo) ? 2 : maxTex >= 8192 ? 1 : 0;
24708
25897
  const current = bo.displacementTier ?? "low";
@@ -24717,6 +25906,13 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
24717
25906
  }
24718
25907
  }
24719
25908
 
25909
+ //#endregion
25910
+ //#region src/lib/scene/perf/calibration-state.svelte.ts
25911
+ var calibrationUi = proxy({
25912
+ progress: null,
25913
+ bootPending: false
25914
+ });
25915
+
24720
25916
  //#endregion
24721
25917
  //#region src/lib/scene/animation/focus.ts
24722
25918
  var FOCUS_DURATION_MS = FOCUS_ROT_PACING.refMs;
@@ -25025,7 +26221,7 @@ function computeGroupMode(count) {
25025
26221
  return "none";
25026
26222
  }
25027
26223
  function isCurrentlyValid(body, jd) {
25028
- return body.data.validityStart <= jd && jd <= body.data.validityEnd;
26224
+ return withinValidity(body.data, jd);
25029
26225
  }
25030
26226
  var PromotionRegistry = class {
25031
26227
  pendingDefaults = /* @__PURE__ */ new Set();
@@ -25204,7 +26400,8 @@ var PromotionRegistry = class {
25204
26400
  let n = 0;
25205
26401
  for (const body of bucket.values()) {
25206
26402
  if (this.groupTargets && !this.groupTargets.has(body.data.id)) continue;
25207
- const { validityStart, validityEnd } = body.data;
26403
+ const validityStart = clockJd(body.data, body.data.validityStart);
26404
+ const validityEnd = clockJd(body.data, body.data.validityEnd);
25208
26405
  if (validityStart <= jd) {
25209
26406
  if (validityStart > this.countStableLo) this.countStableLo = validityStart;
25210
26407
  } else if (validityStart < this.countStableHi) this.countStableHi = validityStart;
@@ -25744,19 +26941,45 @@ var ExtensionRegistry = class {
25744
26941
 
25745
26942
  //#endregion
25746
26943
  //#region src/lib/scene/extensions/anchor.ts
25747
- var RAD$4 = Math.PI / 180;
26944
+ var RAD$3 = Math.PI / 180;
25748
26945
  function isSurface(anchor) {
25749
26946
  return "latitude" in anchor;
25750
26947
  }
25751
- function surfaceDirection(latitude, longitude) {
25752
- const lat = latitude * RAD$4;
25753
- const lon = longitude * RAD$4;
25754
- return [
26948
+ function surfaceDirection(latitude, longitude, radii) {
26949
+ const lat = latitude * RAD$3;
26950
+ const lon = longitude * RAD$3;
26951
+ const out = [
25755
26952
  Math.cos(lat) * Math.cos(lon),
25756
26953
  Math.sin(lat),
25757
26954
  -Math.cos(lat) * Math.sin(lon)
25758
26955
  ];
26956
+ if (radii) onEllipsoid(radii, out, 0, out, 0);
26957
+ return out;
25759
26958
  }
26959
+ function onEllipsoid(radii, from, at, out, to) {
26960
+ const x = from[at] * radii.a;
26961
+ const y = from[at + 1] * radii.c;
26962
+ const z = from[at + 2] * radii.b;
26963
+ const n = Math.hypot(x, y, z);
26964
+ out[to] = x / n;
26965
+ out[to + 1] = y / n;
26966
+ out[to + 2] = z / n;
26967
+ }
26968
+ var GroundDirections = class {
26969
+ source = null;
26970
+ radii;
26971
+ out = new Float64Array(0);
26972
+ of(dirs, radii) {
26973
+ if (!radii) return dirs;
26974
+ if (dirs !== this.source || radii !== this.radii) {
26975
+ if (this.out.length !== dirs.length) this.out = new Float64Array(dirs.length);
26976
+ for (let i = 0; i < dirs.length; i += 3) onEllipsoid(radii, dirs, i, this.out, i);
26977
+ this.source = dirs;
26978
+ this.radii = radii;
26979
+ }
26980
+ return this.out;
26981
+ }
26982
+ };
25760
26983
  function rotateByQuaternion(q, x, y, z, out, at) {
25761
26984
  const tx = 2 * (q.y * z - q.z * y);
25762
26985
  const ty = 2 * (q.z * x - q.x * z);
@@ -25772,7 +26995,7 @@ function placeAnchor(anchor, body, centre, jd) {
25772
26995
  const [cx, cy, cz] = centre;
25773
26996
  if (isSurface(anchor)) {
25774
26997
  const distance = kmToScene(effectiveRadiusKm(body.data) + (anchor.altitudeKm ?? 0));
25775
- const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude);
26998
+ const [dx, dy, dz] = surfaceDirection(anchor.latitude, anchor.longitude, body.drawnRadii);
25776
26999
  const q = body.orientation ? bodyQuaternion(body.orientation, jd, body.nutPrec) : null;
25777
27000
  if (!q) return [
25778
27001
  cx + dx * distance,
@@ -25860,9 +27083,13 @@ function resolvePose(pose, ctx, jd) {
25860
27083
  //#region src/lib/scene/camera/motion-scale.ts
25861
27084
  var MIN_TRANSLATE_SCALE = .02;
25862
27085
  var MIN_ROTATE_SCALE = 1e-4;
27086
+ var DAMPING_FRAME_MS = 1e3 / 60;
25863
27087
  function orbitDamping(reducedMotion) {
25864
27088
  return reducedMotion ? .4 : .05;
25865
27089
  }
27090
+ function frameDamping(factor, frameDtMs) {
27091
+ return 1 - (1 - factor) ** (frameDtMs / DAMPING_FRAME_MS);
27092
+ }
25866
27093
  function cameraMotionScale(camera, focusTruePos, focused, parent, seated) {
25867
27094
  const orbitRadius = camera.position.length();
25868
27095
  if (!(orbitRadius > 0)) return {
@@ -25892,10 +27119,10 @@ function clamp$1(value, min) {
25892
27119
 
25893
27120
  //#endregion
25894
27121
  //#region src/lib/scene/user-location/marker.ts
25895
- var RAD$3 = Math.PI / 180;
27122
+ var RAD$2 = Math.PI / 180;
25896
27123
  function bodyLocalUnitVector(latitude, longitude) {
25897
- const latR = latitude * RAD$3;
25898
- const lonR = longitude * RAD$3;
27124
+ const latR = latitude * RAD$2;
27125
+ const lonR = longitude * RAD$2;
25899
27126
  return [
25900
27127
  Math.cos(latR) * Math.cos(lonR),
25901
27128
  Math.sin(latR),
@@ -26072,6 +27299,11 @@ var SceneRenderer = class SceneRenderer {
26072
27299
  lastRenderedFocusKey = "";
26073
27300
  lastRenderedCamPos = new Vector3(NaN, NaN, NaN);
26074
27301
  lastRenderedCamQuat = new Quaternion();
27302
+ restCamPos = new Vector3(NaN, NaN, NaN);
27303
+ restCamQuat = new Quaternion();
27304
+ restFocusKey = "";
27305
+ atRest = false;
27306
+ focusLoads = 0;
26075
27307
  unsubscribeBodiesAdded = null;
26076
27308
  _trailView = {
26077
27309
  camX: 0,
@@ -26088,6 +27320,7 @@ var SceneRenderer = class SceneRenderer {
26088
27320
  firstFrame = true;
26089
27321
  pendingUrlWrite = false;
26090
27322
  lastTickMs = 0;
27323
+ orbitDampingFactor;
26091
27324
  static FPS_SAMPLE_FRAMES = 30;
26092
27325
  fpsSamples = [];
26093
27326
  fpsSampleHead = 0;
@@ -26182,7 +27415,7 @@ var SceneRenderer = class SceneRenderer {
26182
27415
  this.pointClouds.setPointBudget(this.tier.pointBudget);
26183
27416
  this.controls = new OrbitControls(this.camera, canvas);
26184
27417
  this.controls.enableDamping = true;
26185
- this.controls.dampingFactor = orbitDamping(sceneSettings().resolvedReducedMotion);
27418
+ this.orbitDampingFactor = orbitDamping(sceneSettings().resolvedReducedMotion);
26186
27419
  this.controls.minDistance = focusBody ? minCameraDistance(focusBody) : kmToScene(.01);
26187
27420
  this.controls.maxDistance = 31620.5 * 10;
26188
27421
  this.controls.target.set(0, 0, 0);
@@ -26473,6 +27706,7 @@ var SceneRenderer = class SceneRenderer {
26473
27706
  const nowMs = performance.now();
26474
27707
  const frameDtMs = this.lastTickMs ? nowMs - this.lastTickMs : 0;
26475
27708
  this.lastTickMs = nowMs;
27709
+ this.controls.dampingFactor = frameDamping(this.orbitDampingFactor, frameDtMs || 16.666666666666668);
26476
27710
  if (!this.holdingCamera) this.cameraUp.update(this.sceneJd);
26477
27711
  if (this.firstFrame) {
26478
27712
  this.firstFrame = false;
@@ -26540,7 +27774,7 @@ var SceneRenderer = class SceneRenderer {
26540
27774
  refreshDeferredTrails(this.bodyObjects, this.focus, this.lastUpdatedJd, this.trailView());
26541
27775
  updateRingShaders(this.bodyObjects, this.focus.focusTruePos, sceneSettings().realisticLighting, sceneSettings().overexposeRings, this.camera.position, this.pxPerRad());
26542
27776
  const occluders = collectOccluders(this.bodyObjects, this._occluders);
26543
- updateEclipseUniforms(this.bodyObjects, occluders, this.focus.focusTruePos, sceneSettings().showAtmospheres);
27777
+ updateEclipseUniforms(this.bodyObjects, occluders, this.focus.focusTruePos, this.camera.position.length(), sceneSettings().showAtmospheres);
26544
27778
  updatePlanetshineUniforms(this.bodyObjects, occluders, sceneSettings().realisticLighting, this.sunIntensityScale);
26545
27779
  occluders.length = 0;
26546
27780
  const atmoState = updateAtmosphereShaders(this.bodyObjects, this.camera.position, sceneSettings().showAtmospheres, sceneSettings().realisticLighting, this.sunIntensityScale, currentAtmosphereConfig(), this.sceneJd);
@@ -26574,7 +27808,27 @@ var SceneRenderer = class SceneRenderer {
26574
27808
  this.renderModelOverlay();
26575
27809
  this.labelRenderer.render(this.scene, this.camera);
26576
27810
  if (this.haloDebug.active) this.haloDebug.draw(this.bodyObjects, this.camera, this.focus.focusTruePos, this.focusController.current?.data.id, this.renderer.domElement.clientWidth, this.renderer.domElement.clientHeight);
26577
- };
27811
+ this.checkRest(nowMs);
27812
+ };
27813
+ checkRest(nowMs) {
27814
+ const moved = this.cameraMovedFrom(this.restCamPos, this.restCamQuat) || this.focusKey() !== this.restFocusKey;
27815
+ this.restCamPos.copy(this.camera.position);
27816
+ this.restCamQuat.copy(this.camera.quaternion);
27817
+ this.restFocusKey = this.focusKey();
27818
+ if (moved || this.settling(nowMs)) this.atRest = false;
27819
+ else if (!this.atRest) {
27820
+ this.atRest = true;
27821
+ this.callbacks.onIdle?.();
27822
+ }
27823
+ }
27824
+ settling(nowMs) {
27825
+ if (this.covered || this.ctx.loading || calibrationUi.bootPending) return true;
27826
+ if (nowMs - this.focus.focusStartTime < this.focus.focusDurationMs) return true;
27827
+ if (this.jumpJd !== null || this.seekAwaitsData) return true;
27828
+ if (this.focusLoads > 0 || this.systemData.isLoading()) return true;
27829
+ for (const bo of this.bodyObjects.values()) if (bo.group.visible && bodyLoading(bo)) return true;
27830
+ return false;
27831
+ }
26578
27832
  syncSceneSamples(insideShell) {
26579
27833
  const target = this.composer.readBuffer;
26580
27834
  const samples = insideShell ? 0 : Math.min(this.tier.sceneSamples, this.maxSceneSamples);
@@ -26685,7 +27939,7 @@ var SceneRenderer = class SceneRenderer {
26685
27939
  surfaceRadialKm(bodyId, latitude, longitude) {
26686
27940
  const body = this.ctx.getBody(bodyId);
26687
27941
  if (!body) return null;
26688
- const dir = bodyFixedUnit(latitude * Math.PI / 180, longitude * Math.PI / 180);
27942
+ const dir = surfaceDirection(latitude, longitude, body.drawnRadii);
26689
27943
  return renderedSurfaceRadialKm(this.bodyObjects.get(bodyId), bodyId, effectiveRadiusKm(body.data), dir);
26690
27944
  }
26691
27945
  surfaceClamp(body, seated) {
@@ -26975,11 +28229,14 @@ var SceneRenderer = class SceneRenderer {
26975
28229
  if (nowMs - this.focus.focusStartTime < this.focus.focusDurationMs || this.focusKey() !== this.lastRenderedFocusKey || this.ctx.bodies.minorStreaming || this.pointClouds.hasPendingSceneAdds() || this.systemData.hasPendingUnloads()) return true;
26976
28230
  if (this.covered) return false;
26977
28231
  if (this.frameIndex - this.lastRenderedFrame >= SceneRenderer.IDLE_HEARTBEAT_FRAMES) return true;
26978
- const cam = this.camera;
26979
- if (cam.position.distanceToSquared(this.lastRenderedCamPos) > cam.position.lengthSq() * 1e-10 || 8 * (1 - Math.abs(cam.quaternion.dot(this.lastRenderedCamQuat))) > 1e-8 || this.frameIndex < this.wakeUntilFrame || this.haloDebug.active) return true;
28232
+ if (this.cameraMovedFrom(this.lastRenderedCamPos, this.lastRenderedCamQuat) || this.frameIndex < this.wakeUntilFrame || this.haloDebug.active) return true;
26980
28233
  if (this.clock.jd === this.lastRenderedJd) return false;
26981
28234
  return Math.abs(this.clock.timeScale) > 1 || nowMs - this.lastRenderMs >= SceneRenderer.REALTIME_FRAME_MS;
26982
28235
  }
28236
+ cameraMovedFrom(pos, quat) {
28237
+ const cam = this.camera;
28238
+ return !(cam.position.distanceToSquared(pos) <= cam.position.lengthSq() * 1e-10 && 8 * (1 - Math.abs(cam.quaternion.dot(quat))) <= 1e-8);
28239
+ }
26983
28240
  onControlsEnd = () => {
26984
28241
  this.pendingUrlWrite = true;
26985
28242
  this.focusController.clearPendingInitialView();
@@ -26995,13 +28252,15 @@ var SceneRenderer = class SceneRenderer {
26995
28252
  maybeLoadTexture(body) {
26996
28253
  const bo = this.bodyObjects.get(body.data.id);
26997
28254
  if (!bo) return;
28255
+ this.focusLoads += 2;
28256
+ const done = () => this.focusLoads--;
26998
28257
  loadBodyTexture(bo, this.textureLoader, this.sceneJd, this.scene, this.renderer.capabilities.maxTextureSize, this.ctx).then(() => {
26999
28258
  if (this.focusController.current?.data.id !== body.data.id) return;
27000
28259
  if (body.orientation && bo.mesh) {
27001
28260
  applyOrientation(bo.mesh, body.orientation, this.sceneJd, body.nutPrec);
27002
28261
  this.focusController.reapplyInitialViewIfPending();
27003
28262
  }
27004
- });
28263
+ }).finally(done);
27005
28264
  loadBodyModel(bo, this.modelScene, this.ctx).then(() => {
27006
28265
  if (this.focusController.current?.data.id === bo.body.data.id) this.controls.minDistance = minCameraDistance(bo.body, modelMinRadiusKm(bo));
27007
28266
  if (this.ctx.layers.skipped.has("nomenclature")) return;
@@ -27011,7 +28270,7 @@ var SceneRenderer = class SceneRenderer {
27011
28270
  });
27012
28271
  }).then(() => {
27013
28272
  if (this.selectedFeatureId !== null) setActiveFeatureLabel(bo, this.selectedFeatureId);
27014
- });
28273
+ }).finally(done);
27015
28274
  }
27016
28275
  reapplyBodyLayers() {
27017
28276
  const focused = this.focusController.current;
@@ -27164,7 +28423,7 @@ var SceneRenderer = class SceneRenderer {
27164
28423
  }
27165
28424
  }
27166
28425
  setReducedMotion(on) {
27167
- this.controls.dampingFactor = orbitDamping(on);
28426
+ this.orbitDampingFactor = orbitDamping(on);
27168
28427
  }
27169
28428
  focusOnBody(id, zoom, latitude, longitude) {
27170
28429
  this.disarmLimits();
@@ -27469,7 +28728,8 @@ var SceneRenderer = class SceneRenderer {
27469
28728
  setCovered(covered) {
27470
28729
  if (this.covered === covered) return;
27471
28730
  this.covered = covered;
27472
- if (!covered) this.invalidate();
28731
+ if (covered) this.atRest = false;
28732
+ else this.invalidate();
27473
28733
  }
27474
28734
  isContextLost() {
27475
28735
  return this.renderer.getContext().isContextLost();
@@ -27562,13 +28822,6 @@ function disposeTexture(value) {
27562
28822
  if (value && typeof value === "object" && value.isTexture) value.dispose();
27563
28823
  }
27564
28824
 
27565
- //#endregion
27566
- //#region src/lib/scene/perf/calibration-state.svelte.ts
27567
- var calibrationUi = proxy({
27568
- progress: null,
27569
- bootPending: false
27570
- });
27571
-
27572
28825
  //#endregion
27573
28826
  //#region src/lib/scene/perf/atmosphere-benchmark.ts
27574
28827
  var EARTH_RADIUS_KM = 6371;
@@ -28589,7 +29842,7 @@ var IconExtension = class extends MarkerExtension {
28589
29842
  //#endregion
28590
29843
  //#region src/lib/flatmap/projection.ts
28591
29844
  var DEG$6 = Math.PI / 180;
28592
- var RAD$2 = 180 / Math.PI;
29845
+ var RAD$1 = 180 / Math.PI;
28593
29846
  function wrapLon(lon) {
28594
29847
  if (lon >= -180 && lon < 180) return lon;
28595
29848
  return ((lon + 180) % 360 + 360) % 360 - 180;
@@ -28636,8 +29889,8 @@ function equirectangular(options) {
28636
29889
  const rowInverse = (y) => {
28637
29890
  if (Math.abs(y) > Math.PI / 2) return null;
28638
29891
  return {
28639
- lat: y * RAD$2,
28640
- lonPerX: RAD$2,
29892
+ lat: y * RAD$1,
29893
+ lonPerX: RAD$1,
28641
29894
  maxAbsX: Math.PI
28642
29895
  };
28643
29896
  };
@@ -28685,9 +29938,9 @@ function equalEarth(options) {
28685
29938
  const theta = thetaFor(y);
28686
29939
  const sinLat = 2 / EE_SQRT3 * Math.sin(theta);
28687
29940
  if (Math.abs(sinLat) > 1) return null;
28688
- const lonPerX = 3 * eeDy(theta) / (2 * EE_SQRT3 * Math.cos(theta)) * RAD$2;
29941
+ const lonPerX = 3 * eeDy(theta) / (2 * EE_SQRT3 * Math.cos(theta)) * RAD$1;
28689
29942
  return {
28690
- lat: Math.asin(sinLat) * RAD$2,
29943
+ lat: Math.asin(sinLat) * RAD$1,
28691
29944
  lonPerX,
28692
29945
  maxAbsX: 180 / lonPerX
28693
29946
  };
@@ -28719,9 +29972,9 @@ function mollweide(options) {
28719
29972
  lonPerX: 0,
28720
29973
  maxAbsX: 0
28721
29974
  };
28722
- const lonPerX = Math.PI / (2 * MW_SQRT2 * cosTheta) * RAD$2;
29975
+ const lonPerX = Math.PI / (2 * MW_SQRT2 * cosTheta) * RAD$1;
28723
29976
  return {
28724
- lat: Math.asin(sinLat) * RAD$2,
29977
+ lat: Math.asin(sinLat) * RAD$1,
28725
29978
  lonPerX,
28726
29979
  maxAbsX: 180 / lonPerX
28727
29980
  };
@@ -28768,8 +30021,8 @@ function sinusoidal(options) {
28768
30021
  maxAbsX: 0
28769
30022
  };
28770
30023
  return {
28771
- lat: y * RAD$2,
28772
- lonPerX: RAD$2 / cosLat,
30024
+ lat: y * RAD$1,
30025
+ lonPerX: RAD$1 / cosLat,
28773
30026
  maxAbsX: Math.PI * cosLat
28774
30027
  };
28775
30028
  };
@@ -28920,7 +30173,7 @@ function clamp1(v) {
28920
30173
  }
28921
30174
  function azimuthalInverse(x, y, cosC, k, lon0, sinPhi0, cosPhi0) {
28922
30175
  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];
30176
+ return [wrapLon(lon0 + Math.atan2(x * k, cosC * cosPhi0 - y * k * sinPhi0) * RAD$1), lat * RAD$1];
28924
30177
  }
28925
30178
  function discExtent(r) {
28926
30179
  return {
@@ -29017,7 +30270,7 @@ function createProjection(id, options = {}) {
29017
30270
  var DEG$5 = Math.PI / 180;
29018
30271
  var TAU = 2 * Math.PI;
29019
30272
  var RIM_STEP = 2 * DEG$5;
29020
- var RAD$1 = 180 / Math.PI;
30273
+ var RAD = 180 / Math.PI;
29021
30274
  var MAX_STEPS = 512;
29022
30275
  var DEFAULT_STEP_DEG = 2;
29023
30276
  function angularDistance(a, b) {
@@ -29026,7 +30279,7 @@ function angularDistance(a, b) {
29026
30279
  const dφ = φ2 - φ1;
29027
30280
  const dλ = wrapLon(b.lon - a.lon) * DEG$5;
29028
30281
  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;
30282
+ return 2 * Math.asin(Math.min(1, Math.sqrt(h))) * RAD;
29030
30283
  }
29031
30284
  function slerp(a, b, t) {
29032
30285
  const d = angularDistance(a, b) * DEG$5;
@@ -29042,8 +30295,8 @@ function slerp(a, b, t) {
29042
30295
  const y = ka * Math.cos(φ1) * Math.sin(λ1) + kb * Math.cos(φ2) * Math.sin(λ2);
29043
30296
  const z = ka * Math.sin(φ1) + kb * Math.sin(φ2);
29044
30297
  return {
29045
- lon: Math.atan2(y, x) * RAD$1,
29046
- lat: Math.atan2(z, Math.hypot(x, y)) * RAD$1
30298
+ lon: Math.atan2(y, x) * RAD,
30299
+ lat: Math.atan2(z, Math.hypot(x, y)) * RAD
29047
30300
  };
29048
30301
  }
29049
30302
  function lerp(a, b, t) {
@@ -29346,8 +30599,8 @@ function smallCircle(center, radiusDeg, steps = 128) {
29346
30599
  const y = c[1] * cosR + e[1] * a + n[1] * b;
29347
30600
  const z = c[2] * cosR + e[2] * a + n[2] * b;
29348
30601
  ring.push({
29349
- lon: wrapLon(Math.atan2(y, x) * RAD$1),
29350
- lat: Math.atan2(z, Math.hypot(x, y)) * RAD$1
30602
+ lon: wrapLon(Math.atan2(y, x) * RAD),
30603
+ lat: Math.atan2(z, Math.hypot(x, y)) * RAD
29351
30604
  });
29352
30605
  }
29353
30606
  return ring;
@@ -29400,6 +30653,8 @@ var SurfaceShapeExtension = class {
29400
30653
  alphas = new Float32Array(0);
29401
30654
  lengths = new Float32Array(0);
29402
30655
  fanDirs = new Float64Array(0);
30656
+ groundDirs = new GroundDirections();
30657
+ groundFanDirs = new GroundDirections();
29403
30658
  fill = null;
29404
30659
  fillScratch = new Float32Array(0);
29405
30660
  turned = [
@@ -29466,7 +30721,8 @@ var SurfaceShapeExtension = class {
29466
30721
  centre[2] - basis[2]
29467
30722
  ];
29468
30723
  if (this.line) {
29469
- this.place(this.dirs, this.count, q, scale, origin, this.scratch);
30724
+ const dirs = this.groundDirs.of(this.dirs, body.drawnRadii);
30725
+ this.place(dirs, this.count, q, scale, origin, this.scratch);
29470
30726
  writeFatTrailVertices(this.line.geometry, this.scratch, this.alphas, this.alphas, this.count);
29471
30727
  if (this.dash) {
29472
30728
  screenLengths(this.scratch, this.count, camera, viewportPx, this.lengths);
@@ -29476,7 +30732,8 @@ var SurfaceShapeExtension = class {
29476
30732
  }
29477
30733
  if (this.fill) {
29478
30734
  const position = this.fill.geometry.getAttribute("position");
29479
- this.place(this.fanDirs, this.fillScratch.length / 3, q, scale, origin, this.fillScratch);
30735
+ const fanDirs = this.groundFanDirs.of(this.fanDirs, body.drawnRadii);
30736
+ this.place(fanDirs, this.fillScratch.length / 3, q, scale, origin, this.fillScratch);
29480
30737
  position.needsUpdate = true;
29481
30738
  this.fill.material.uniforms.uCenterOffset.value.copy(camera.position).negate();
29482
30739
  }
@@ -30027,6 +31284,7 @@ var SpaceMap = class {
30027
31284
  clock: /* @__PURE__ */ new Set(),
30028
31285
  loading: /* @__PURE__ */ new Set(),
30029
31286
  progress: /* @__PURE__ */ new Set(),
31287
+ idle: /* @__PURE__ */ new Set(),
30030
31288
  error: /* @__PURE__ */ new Set(),
30031
31289
  datastale: /* @__PURE__ */ new Set(),
30032
31290
  notice: /* @__PURE__ */ new Set(),
@@ -30298,6 +31556,7 @@ var SpaceMap = class {
30298
31556
  dtMs
30299
31557
  });
30300
31558
  },
31559
+ onIdle: () => this.emit("idle"),
30301
31560
  onCameraPosition: (latitude, longitude, zoom) => {
30302
31561
  const body = this.focusedBody?.data.id;
30303
31562
  if (body === void 0) return;
@@ -31777,7 +33036,7 @@ async function acquireBitmap(url, decodeWidth) {
31777
33036
  };
31778
33037
  let work = pending.get(key);
31779
33038
  if (!work) {
31780
- work = decode(url, decodeWidth);
33039
+ work = decode$1(url, decodeWidth);
31781
33040
  pending.set(key, work);
31782
33041
  const done = () => pending.delete(key);
31783
33042
  work.then(done, done);
@@ -31789,7 +33048,7 @@ async function acquireBitmap(url, decodeWidth) {
31789
33048
  bitmap: bitmaps.put(key, bitmap, bitmapBytes(bitmap.width, bitmap.height))
31790
33049
  };
31791
33050
  }
31792
- async function decode(url, decodeWidth) {
33051
+ async function decode$1(url, decodeWidth) {
31793
33052
  const response = await fetch(url);
31794
33053
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
31795
33054
  const blob = await response.blob();
@@ -33083,17 +34342,25 @@ function findPanorama(entries, at) {
33083
34342
  if (!at) return null;
33084
34343
  return entries.find((e) => panoramaAt(e) === at || legacyAt(e) === at) ?? null;
33085
34344
  }
34345
+ var MIN_STEP_M = 1;
33086
34346
  function neighboursOf(entries, current, radiusKm) {
33087
34347
  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;
34348
+ const nearest = (step) => {
34349
+ for (let j = i + step; j >= 0 && j < entries.length; j += step) {
34350
+ const e = entries[j];
34351
+ if (e.mission !== current.mission) return null;
34352
+ const distanceM = groundDistanceM(current, e, radiusKm);
34353
+ if (distanceM >= MIN_STEP_M) return {
34354
+ entry: e,
34355
+ distanceM,
34356
+ bearingDeg: bearingDeg(current, e)
34357
+ };
34358
+ }
34359
+ return null;
34360
+ };
33094
34361
  return {
33095
- previous: toNeighbour(entries[i - 1]),
33096
- next: toNeighbour(entries[i + 1])
34362
+ previous: nearest(-1),
34363
+ next: nearest(1)
33097
34364
  };
33098
34365
  }
33099
34366
  var DEG$1 = Math.PI / 180;
@@ -33121,7 +34388,6 @@ var ARROW_DISTANCE = 9;
33121
34388
  var CLICK_SLOP_PX = 6;
33122
34389
  var ANGLE_STEP_DEG = 10;
33123
34390
  var ANGLE_SAMPLE_STEP_DEG = 1;
33124
- var MIN_ARROW_DISTANCE_M = 1;
33125
34391
  var GYRO_TILT = new Quaternion(-Math.SQRT1_2, 0, 0, Math.SQRT1_2);
33126
34392
  var SCREEN_AXIS = new Vector3(0, 0, 1);
33127
34393
  function wrapHeading(deg) {
@@ -33340,7 +34606,7 @@ var PanoramaView = class {
33340
34606
  this.setArrowTargets(this.neighbours && hasHeading(entry) ? ["previous", "next"].map((key) => ({
33341
34607
  key,
33342
34608
  n: this.neighbours?.[key] ?? null
33343
- })).filter((a) => a.n && a.n.distanceM >= MIN_ARROW_DISTANCE_M).map((a) => ({
34609
+ })).filter((a) => a.n).map((a) => ({
33344
34610
  key: a.key,
33345
34611
  bearingDeg: a.n.bearingDeg
33346
34612
  })) : []);
@@ -33738,6 +35004,106 @@ var fetchPlanetarySystemsMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__p
33738
35004
  //#region src/lib/fetch/groups/solar-system-map.ts
33739
35005
  var fetchSolarSystemMap = memoizedGzJson(() => `${dataBase()}/v1/groups/__solar_system_map__.json.gz`, { error: (res) => `Failed to fetch solar system map: ${res.status}` });
33740
35006
 
35007
+ //#endregion
35008
+ //#region src/lib/rings/strip.ts
35009
+ var cache = /* @__PURE__ */ new Map();
35010
+ function luminance(r, g, b) {
35011
+ return .2126 * r + .7152 * g + .0722 * b;
35012
+ }
35013
+ async function decode(bodyId, meta) {
35014
+ const rows = meta.strip_rows;
35015
+ if (rows.transparency === void 0 || rows.color === void 0) {
35016
+ console.warn(`Ring strip ${bodyId}/${meta.strip} has no transparency or color row, no profile drawn:`, rows);
35017
+ return null;
35018
+ }
35019
+ const response = await fetch(versionedUrl(`/v1/rings/${bodyId}/${meta.strip}`, "rings"));
35020
+ if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
35021
+ const bitmap = await createImageBitmap(await response.blob());
35022
+ const canvas = document.createElement("canvas");
35023
+ canvas.width = bitmap.width;
35024
+ canvas.height = bitmap.height;
35025
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
35026
+ if (!ctx) {
35027
+ console.warn(`Ring strip ${bodyId}/${meta.strip}: no 2D context, no profile drawn`);
35028
+ return null;
35029
+ }
35030
+ ctx.drawImage(bitmap, 0, 0);
35031
+ const n = bitmap.width;
35032
+ const pixels = ctx.getImageData(0, 0, n, bitmap.height).data;
35033
+ bitmap.close();
35034
+ const scale = meta.intensity_scale || 1;
35035
+ const tau = new Float32Array(n);
35036
+ const rgb = new Uint8ClampedArray(n * 3);
35037
+ const back = rows.backscattered ?? rows.color;
35038
+ const brightness = new Float32Array(n);
35039
+ let peak = 0;
35040
+ for (let i = 0; i < n; i++) {
35041
+ const t = (rows.transparency * n + i) * 4;
35042
+ const b = (back * n + i) * 4;
35043
+ const opacity = Math.min(1, (1 - pixels[t] / 255) * scale);
35044
+ tau[i] = opacity >= 1 ? Infinity : -Math.log(1 - opacity);
35045
+ brightness[i] = luminance(pixels[b], pixels[b + 1], pixels[b + 2]) / 255;
35046
+ if (brightness[i] > peak) peak = brightness[i];
35047
+ }
35048
+ const FLOOR = .35;
35049
+ for (let i = 0; i < n; i++) {
35050
+ const c = (rows.color * n + i) * 4;
35051
+ const lit = peak > 0 ? FLOOR + (1 - FLOOR) * (brightness[i] / peak) : 1;
35052
+ rgb[i * 3] = pixels[c] * lit;
35053
+ rgb[i * 3 + 1] = pixels[c + 1] * lit;
35054
+ rgb[i * 3 + 2] = pixels[c + 2] * lit;
35055
+ }
35056
+ return {
35057
+ inner: meta.inner_radius_km,
35058
+ outer: meta.outer_radius_km,
35059
+ tau,
35060
+ rgb
35061
+ };
35062
+ }
35063
+ async function load(bodyId) {
35064
+ const bundles = (await fetchObjectDetail(bodyId, false)).global?.rings ?? [];
35065
+ if (!bundles.length) console.warn(`Ring strips for ${bodyId}: no ring bundles on the object`);
35066
+ const profiles = [];
35067
+ for (const bundle of bundles) try {
35068
+ const profile = await decode(bodyId, bundle);
35069
+ if (profile) profiles.push(profile);
35070
+ } catch (err) {
35071
+ console.warn(`Ring strip ${bodyId}/${bundle.strip} unavailable:`, err);
35072
+ }
35073
+ return profiles;
35074
+ }
35075
+ var SEEN_TAU = -Math.log1p(-.02);
35076
+ function visibleSpan(profiles) {
35077
+ let inner = Infinity;
35078
+ let outer = -Infinity;
35079
+ for (const profile of profiles) {
35080
+ const n = profile.tau.length;
35081
+ const kmPerSample = (profile.outer - profile.inner) / (n - 1);
35082
+ for (let i = 0; i < n; i++) {
35083
+ if (profile.tau[i] < SEEN_TAU) continue;
35084
+ const km = profile.inner + i * kmPerSample;
35085
+ if (km < inner) inner = km;
35086
+ if (km > outer) outer = km;
35087
+ }
35088
+ }
35089
+ return outer >= inner ? {
35090
+ inner,
35091
+ outer
35092
+ } : null;
35093
+ }
35094
+ function loadRingStrips(bodyId) {
35095
+ let pending = cache.get(bodyId);
35096
+ if (!pending) {
35097
+ pending = load(bodyId).catch((err) => {
35098
+ console.warn(`Ring strips for ${bodyId} unavailable:`, err);
35099
+ cache.delete(bodyId);
35100
+ return [];
35101
+ });
35102
+ cache.set(bodyId, pending);
35103
+ }
35104
+ return pending;
35105
+ }
35106
+
33741
35107
  //#endregion
33742
35108
  //#region src/lib/charts/scrub.ts
33743
35109
  var DRAG_SLOP = 8;
@@ -34527,6 +35893,15 @@ function systemFromMapEntry(entry, name) {
34527
35893
  moonCount: entry.moon_count
34528
35894
  };
34529
35895
  }
35896
+ function visibleRings(system, profiles) {
35897
+ if (!system.rings || !profiles.length) return system.rings;
35898
+ const span = visibleSpan(profiles);
35899
+ const rKm = system.planetRadiusKm;
35900
+ return span && {
35901
+ innerRp: span.inner / rKm,
35902
+ outerRp: span.outer / rKm
35903
+ };
35904
+ }
34530
35905
  var TOP_MOON_R = 14;
34531
35906
  var AT_SURFACE = 1.001;
34532
35907
  var PX_PER_DEG = .62;
@@ -34876,11 +36251,12 @@ function buildSystemView(data, primaryId, exported, options, text, pick) {
34876
36251
  name: scope.name(moon.id, exported[moon.id])
34877
36252
  }));
34878
36253
  const rKm = system.planetRadiusKm;
34879
- const bands = system.rings ? [{
36254
+ const rings = data.ringProfiles ? visibleRings(system, data.ringProfiles) : system.rings;
36255
+ const bands = rings ? [{
34880
36256
  key: "rings",
34881
36257
  label: text.rings,
34882
- innerKm: system.rings.innerRp * rKm,
34883
- outerKm: system.rings.outerRp * rKm,
36258
+ innerKm: rings.innerRp * rKm,
36259
+ outerKm: rings.outerRp * rKm,
34884
36260
  tone: "amber"
34885
36261
  }] : [];
34886
36262
  const model = planetarySystemModel(system, {
@@ -35027,6 +36403,7 @@ var SystemMap = class {
35027
36403
  names;
35028
36404
  grouping;
35029
36405
  zones;
36406
+ rings;
35030
36407
  places;
35031
36408
  targets = [];
35032
36409
  extras = /* @__PURE__ */ new Map();
@@ -35038,6 +36415,7 @@ var SystemMap = class {
35038
36415
  this.names = options.names ?? {};
35039
36416
  this.grouping = options.grouping ?? "bodies";
35040
36417
  this.zones = options.zones ?? "belts";
36418
+ this.rings = options.rings ?? "all";
35041
36419
  this.places = (options.places ?? []).map(extraFromPlace);
35042
36420
  }
35043
36421
  mount(container) {
@@ -35104,7 +36482,14 @@ var SystemMap = class {
35104
36482
  };
35105
36483
  switch (view.kind) {
35106
36484
  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);
36485
+ case "system": {
36486
+ const ringed = this.rings === "visible" && !!systemEntry(systems, view.id)?.rings;
36487
+ const [names, ringProfiles] = await Promise.all([this.exportedNames(view.id, systems), ringed ? loadRingStrips(view.id) : void 0]);
36488
+ return buildSystemView({
36489
+ ...data,
36490
+ ringProfiles
36491
+ }, view.id, names, options, text, pick);
36492
+ }
35108
36493
  case "zone": {
35109
36494
  const ids = unplacedIds(solar, systems, this.bodies, this.places.map((place) => place.id));
35110
36495
  const read = (await Promise.all(ids.map((id) => this.extra(id)))).filter((extra) => extra !== null);
@@ -35357,9 +36742,6 @@ var PanoramaAttributionControl = class {
35357
36742
 
35358
36743
  //#endregion
35359
36744
  //#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
36745
  function fixed(place) {
35364
36746
  return place;
35365
36747
  }
@@ -35384,17 +36766,10 @@ function tle(line1, line2) {
35384
36766
  offsetKm: (jd) => {
35385
36767
  if (satrec.error !== SatRecError.None) return null;
35386
36768
  const teme = sgp4PositionTEME(satrec, jd);
35387
- return teme && temeToEcliptic(teme[0], teme[1], teme[2]);
36769
+ return teme && temeToEcliptic(teme[0], teme[1], teme[2], jd);
35388
36770
  }
35389
36771
  };
35390
36772
  }
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
36773
  function interpolateSamples(states, jd) {
35399
36774
  const n = states.length;
35400
36775
  if (n < 2 || jd < states[0].jd || jd > states[n - 1].jd) return null;
@@ -35478,7 +36853,7 @@ async function moonRow(id, { jd, metadata }) {
35478
36853
  }
35479
36854
  function elementsOffset(data, jd) {
35480
36855
  if (!discovered(data.visibleFromDays, jd)) return null;
35481
- if (jd < data.validityStart || jd > data.validityEnd) return null;
36856
+ if (!withinValidity(data, jd)) return null;
35482
36857
  if (data.satrec) return sgp4PositionScene(data.satrec, jd);
35483
36858
  if (data.q != null) return parabolicToPositionJD(data, jd);
35484
36859
  if (data.a === 0) return [
@@ -35546,16 +36921,9 @@ function neighbours(id, { parents }, at) {
35546
36921
  }
35547
36922
  async function spinOf(id) {
35548
36923
  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
36924
  return {
35554
- orientation,
35555
- nutPrec: sums && angles ? {
35556
- ...sums,
35557
- angles
35558
- } : void 0
36925
+ orientation: detail.global?.orientation,
36926
+ nutPrec: nutPrecFor(id, detail.global?.nut_prec)
35559
36927
  };
35560
36928
  }
35561
36929
  async function resolve(anchor, at) {