spacemap 0.1.12 → 0.1.13
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/README.md +12 -9
- package/dist/spacemap.iife.js +167 -105
- package/dist/spacemap.js +167 -105
- package/index.d.ts +18 -15
- package/index.js +477 -240
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -3025,6 +3025,17 @@ var OrbitalSource = /* @__PURE__ */ function(OrbitalSource) {
|
|
|
3025
3025
|
OrbitalSource[OrbitalSource["UNKNOWN"] = 255] = "UNKNOWN";
|
|
3026
3026
|
return OrbitalSource;
|
|
3027
3027
|
}({});
|
|
3028
|
+
var PROBE_ARCHIVES = [
|
|
3029
|
+
void 0,
|
|
3030
|
+
"naif",
|
|
3031
|
+
"esa",
|
|
3032
|
+
"naif-pds3",
|
|
3033
|
+
"naif-pds4",
|
|
3034
|
+
"jaxa-darts",
|
|
3035
|
+
"horizons",
|
|
3036
|
+
"gcat-deepcat",
|
|
3037
|
+
"spacetrack"
|
|
3038
|
+
];
|
|
3028
3039
|
var IdType = /* @__PURE__ */ function(IdType) {
|
|
3029
3040
|
IdType[IdType["NAIF"] = 0] = "NAIF";
|
|
3030
3041
|
IdType[IdType["SPKID"] = 1] = "SPKID";
|
|
@@ -3174,40 +3185,24 @@ function isNaturalBody(type) {
|
|
|
3174
3185
|
//#endregion
|
|
3175
3186
|
//#region src/lib/scene/state/orbit-sources.ts
|
|
3176
3187
|
var ORBIT_SOURCES = {
|
|
3177
|
-
[OrbitalSource.HORIZONS]: {
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
},
|
|
3181
|
-
[OrbitalSource.SBDB]: {
|
|
3182
|
-
archive: "sbdb",
|
|
3183
|
-
label: "NASA"
|
|
3184
|
-
},
|
|
3185
|
-
[OrbitalSource.SPICE]: {
|
|
3186
|
-
archive: "naif",
|
|
3187
|
-
label: "NASA"
|
|
3188
|
-
},
|
|
3188
|
+
[OrbitalSource.HORIZONS]: { archive: "horizons" },
|
|
3189
|
+
[OrbitalSource.SBDB]: { archive: "sbdb" },
|
|
3190
|
+
[OrbitalSource.SPICE]: { archive: "naif" },
|
|
3189
3191
|
[OrbitalSource.SBDB_MOON]: {
|
|
3190
3192
|
archive: "sbdb",
|
|
3191
|
-
label: "NASA",
|
|
3192
3193
|
scoped: true
|
|
3193
3194
|
},
|
|
3194
3195
|
[OrbitalSource.ASTERSAT]: {
|
|
3195
3196
|
archive: "nsdb",
|
|
3196
|
-
label: "Natural Satellites Data Base",
|
|
3197
3197
|
scoped: true
|
|
3198
3198
|
},
|
|
3199
|
-
[OrbitalSource.SPICE_PROBE]: {
|
|
3200
|
-
archive: "naif",
|
|
3201
|
-
label: "NASA"
|
|
3202
|
-
},
|
|
3199
|
+
[OrbitalSource.SPICE_PROBE]: {},
|
|
3203
3200
|
[OrbitalSource.CELESTRAK]: {
|
|
3204
3201
|
archive: "celestrak",
|
|
3205
|
-
label: "CelesTrak",
|
|
3206
3202
|
scoped: true
|
|
3207
3203
|
},
|
|
3208
3204
|
[OrbitalSource.SPACETRACK]: {
|
|
3209
3205
|
archive: "spacetrack",
|
|
3210
|
-
label: "Space-Track.org",
|
|
3211
3206
|
scoped: true
|
|
3212
3207
|
}
|
|
3213
3208
|
};
|
|
@@ -3221,6 +3216,19 @@ var ORBIT_SOURCE_ORDER = [
|
|
|
3221
3216
|
OrbitalSource.CELESTRAK,
|
|
3222
3217
|
OrbitalSource.SPACETRACK
|
|
3223
3218
|
];
|
|
3219
|
+
var ORBIT_ARCHIVE_ORGANISATIONS = {
|
|
3220
|
+
horizons: "NASA",
|
|
3221
|
+
sbdb: "NASA",
|
|
3222
|
+
naif: "NASA",
|
|
3223
|
+
"naif-pds3": "NASA",
|
|
3224
|
+
"naif-pds4": "NASA",
|
|
3225
|
+
esa: "ESA",
|
|
3226
|
+
"jaxa-darts": "JAXA",
|
|
3227
|
+
"gcat-deepcat": "McDowell Deep Space Catalog",
|
|
3228
|
+
nsdb: "Natural Satellites Data Base",
|
|
3229
|
+
celestrak: "CelesTrak",
|
|
3230
|
+
spacetrack: "Space-Track.org"
|
|
3231
|
+
};
|
|
3224
3232
|
function orbitSourceInfo(source) {
|
|
3225
3233
|
return ORBIT_SOURCES[source];
|
|
3226
3234
|
}
|
|
@@ -3281,6 +3289,13 @@ var CreditsStore = class {
|
|
|
3281
3289
|
set orbitSourceParents(value) {
|
|
3282
3290
|
set(this.#orbitSourceParents, value, true);
|
|
3283
3291
|
}
|
|
3292
|
+
#probeArchives = state(proxy(/* @__PURE__ */ new Set()));
|
|
3293
|
+
get probeArchives() {
|
|
3294
|
+
return get(this.#probeArchives);
|
|
3295
|
+
}
|
|
3296
|
+
set probeArchives(value) {
|
|
3297
|
+
set(this.#probeArchives, value, true);
|
|
3298
|
+
}
|
|
3284
3299
|
registerImagery(layer, bodyId, systemId, meta) {
|
|
3285
3300
|
this.addImagery(layer, bodyId, systemId, meta, layer === "rings");
|
|
3286
3301
|
const tiles = layer === "surface" ? tileCredit(meta) : void 0;
|
|
@@ -3322,6 +3337,7 @@ var CreditsStore = class {
|
|
|
3322
3337
|
recordOrbitSources(bodies) {
|
|
3323
3338
|
let added = false;
|
|
3324
3339
|
let scoped = false;
|
|
3340
|
+
let archived = false;
|
|
3325
3341
|
for (const b of bodies) {
|
|
3326
3342
|
const src = b.data.orbitalSource;
|
|
3327
3343
|
if (src === OrbitalSource.UNKNOWN) continue;
|
|
@@ -3329,6 +3345,11 @@ var CreditsStore = class {
|
|
|
3329
3345
|
this.orbitSources.add(src);
|
|
3330
3346
|
added = true;
|
|
3331
3347
|
}
|
|
3348
|
+
const archive = b.data.orbitArchive;
|
|
3349
|
+
if (archive && !this.probeArchives.has(archive)) {
|
|
3350
|
+
this.probeArchives.add(archive);
|
|
3351
|
+
archived = true;
|
|
3352
|
+
}
|
|
3332
3353
|
if (!orbitSourceInfo(src)?.scoped) continue;
|
|
3333
3354
|
let parents = this.orbitSourceParents.get(src);
|
|
3334
3355
|
if (!parents) {
|
|
@@ -3341,6 +3362,7 @@ var CreditsStore = class {
|
|
|
3341
3362
|
}
|
|
3342
3363
|
if (added) this.orbitSources = new Set(this.orbitSources);
|
|
3343
3364
|
if (scoped) this.orbitSourceParents = new Map(this.orbitSourceParents);
|
|
3365
|
+
if (archived) this.probeArchives = new Set(this.probeArchives);
|
|
3344
3366
|
}
|
|
3345
3367
|
recordOrbitSource(src) {
|
|
3346
3368
|
if (src === OrbitalSource.UNKNOWN || this.orbitSources.has(src)) return;
|
|
@@ -5833,6 +5855,10 @@ var TYPE_COLOR_PROBE = "#d5cfe7";
|
|
|
5833
5855
|
var SUN_ID = "naif-10";
|
|
5834
5856
|
var EARTH_ID = "naif-399";
|
|
5835
5857
|
var SSB_ID = "naif-0";
|
|
5858
|
+
function dominantPlanetId(id) {
|
|
5859
|
+
const m = /^naif-([1-9])$/.exec(id);
|
|
5860
|
+
return m ? `naif-${m[1]}99` : null;
|
|
5861
|
+
}
|
|
5836
5862
|
var MINOR_PROMOTED_IDS = new Set([
|
|
5837
5863
|
"naif-0",
|
|
5838
5864
|
"naif-9",
|
|
@@ -6208,10 +6234,6 @@ var MinorBucket = class {
|
|
|
6208
6234
|
function isTopLevelParent(parentId) {
|
|
6209
6235
|
return parentId === "naif-0" || parentId === "naif-10";
|
|
6210
6236
|
}
|
|
6211
|
-
function dominantPlanetId(id) {
|
|
6212
|
-
const m = /^naif-([1-9])$/.exec(id);
|
|
6213
|
-
return m ? `naif-${m[1]}99` : null;
|
|
6214
|
-
}
|
|
6215
6237
|
function barycenterPrimaryId(id) {
|
|
6216
6238
|
if (id === "naif-0") return SUN_ID;
|
|
6217
6239
|
return dominantPlanetId(id);
|
|
@@ -6507,38 +6529,76 @@ var loadProgress = new LoadProgress();
|
|
|
6507
6529
|
//#endregion
|
|
6508
6530
|
//#region src/lib/fetch/fetch-timeout.ts
|
|
6509
6531
|
var BOOT_FETCH_TIMEOUT_MS = 3e4;
|
|
6510
|
-
function
|
|
6511
|
-
if (background || !loadProgress.active
|
|
6532
|
+
function bootByteCounter(res, background) {
|
|
6533
|
+
if (background || !loadProgress.active) return null;
|
|
6512
6534
|
const total = Number(res.headers.get("content-length"));
|
|
6513
|
-
if (!Number.isFinite(total) || total <= 0) return
|
|
6535
|
+
if (!Number.isFinite(total) || total <= 0) return null;
|
|
6514
6536
|
loadProgress.announce(total);
|
|
6515
|
-
|
|
6516
|
-
loadProgress.addBytes(chunk.byteLength);
|
|
6517
|
-
controller.enqueue(chunk);
|
|
6518
|
-
} });
|
|
6519
|
-
return new Response(res.body.pipeThrough(counter), {
|
|
6520
|
-
status: res.status,
|
|
6521
|
-
statusText: res.statusText,
|
|
6522
|
-
headers: res.headers
|
|
6523
|
-
});
|
|
6537
|
+
return (bytes) => loadProgress.addBytes(bytes);
|
|
6524
6538
|
}
|
|
6525
6539
|
async function fetchWithTimeout(input, init = {}, timeoutMs = BOOT_FETCH_TIMEOUT_MS) {
|
|
6526
6540
|
const controller = new AbortController();
|
|
6527
|
-
|
|
6541
|
+
let timer;
|
|
6542
|
+
let stalled = false;
|
|
6543
|
+
const arm = () => {
|
|
6544
|
+
clearTimeout(timer);
|
|
6545
|
+
timer = setTimeout(() => {
|
|
6546
|
+
stalled = true;
|
|
6547
|
+
controller.abort();
|
|
6548
|
+
}, timeoutMs);
|
|
6549
|
+
};
|
|
6528
6550
|
const onCallerAbort = () => controller.abort();
|
|
6529
6551
|
init.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
6552
|
+
const release = () => {
|
|
6553
|
+
clearTimeout(timer);
|
|
6554
|
+
init.signal?.removeEventListener("abort", onCallerAbort);
|
|
6555
|
+
};
|
|
6556
|
+
const failure = (e) => stalled ? new Error(`Request stalled for ${timeoutMs} ms: ${input}`, { cause: e }) : e;
|
|
6557
|
+
arm();
|
|
6558
|
+
let res;
|
|
6530
6559
|
try {
|
|
6531
|
-
|
|
6560
|
+
res = await fetch(input, {
|
|
6532
6561
|
...init,
|
|
6533
6562
|
signal: controller.signal
|
|
6534
|
-
})
|
|
6563
|
+
});
|
|
6535
6564
|
} catch (e) {
|
|
6536
|
-
|
|
6537
|
-
throw e;
|
|
6538
|
-
}
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6565
|
+
release();
|
|
6566
|
+
throw failure(e);
|
|
6567
|
+
}
|
|
6568
|
+
if (!res.body) {
|
|
6569
|
+
release();
|
|
6570
|
+
return res;
|
|
6571
|
+
}
|
|
6572
|
+
const count = bootByteCounter(res, init.priority === "low");
|
|
6573
|
+
const reader = res.body.getReader();
|
|
6574
|
+
const body = new ReadableStream({
|
|
6575
|
+
async pull(out) {
|
|
6576
|
+
arm();
|
|
6577
|
+
try {
|
|
6578
|
+
const { done, value } = await reader.read();
|
|
6579
|
+
clearTimeout(timer);
|
|
6580
|
+
if (done) {
|
|
6581
|
+
release();
|
|
6582
|
+
out.close();
|
|
6583
|
+
return;
|
|
6584
|
+
}
|
|
6585
|
+
count?.(value.byteLength);
|
|
6586
|
+
out.enqueue(value);
|
|
6587
|
+
} catch (e) {
|
|
6588
|
+
release();
|
|
6589
|
+
out.error(failure(e));
|
|
6590
|
+
}
|
|
6591
|
+
},
|
|
6592
|
+
cancel(reason) {
|
|
6593
|
+
release();
|
|
6594
|
+
return reader.cancel(reason);
|
|
6595
|
+
}
|
|
6596
|
+
});
|
|
6597
|
+
return new Response(body, {
|
|
6598
|
+
status: res.status,
|
|
6599
|
+
statusText: res.statusText,
|
|
6600
|
+
headers: res.headers
|
|
6601
|
+
});
|
|
6542
6602
|
}
|
|
6543
6603
|
|
|
6544
6604
|
//#endregion
|
|
@@ -7147,7 +7207,7 @@ function parseLabels(text) {
|
|
|
7147
7207
|
return out;
|
|
7148
7208
|
}
|
|
7149
7209
|
var labelsByLang = /* @__PURE__ */ new Map();
|
|
7150
|
-
async function fetchLabels
|
|
7210
|
+
async function fetchLabels(lang = getLocale()) {
|
|
7151
7211
|
let p = labelsByLang.get(lang);
|
|
7152
7212
|
if (!p) {
|
|
7153
7213
|
p = (async () => {
|
|
@@ -7372,6 +7432,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
|
|
|
7372
7432
|
const fitCenterIdValue = view.getInt32(offset + 12, true);
|
|
7373
7433
|
const fitCenterIdType = view.getUint8(offset + 16);
|
|
7374
7434
|
const nSystemIntervals = view.getUint8(offset + 17);
|
|
7435
|
+
const archive = PROBE_ARCHIVES[view.getUint8(offset + 18)];
|
|
7375
7436
|
offset += 20;
|
|
7376
7437
|
const id = buildObjectId(idTypeOrdinal, objIdValue) ?? "";
|
|
7377
7438
|
if (!id) console.warn(`probes payload: probe[${p}] (value=${objIdValue}) has unknown id-type ${idTypeOrdinal}; skipping`);
|
|
@@ -7424,6 +7485,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
|
|
|
7424
7485
|
probeId: objIdValue,
|
|
7425
7486
|
hasLocalized,
|
|
7426
7487
|
objectType,
|
|
7488
|
+
archive,
|
|
7427
7489
|
fitCenter,
|
|
7428
7490
|
subStartEt,
|
|
7429
7491
|
subEndEt,
|
|
@@ -8297,7 +8359,7 @@ async function fetchElements(zone, zoom, part, time, priority) {
|
|
|
8297
8359
|
}
|
|
8298
8360
|
var ChunkLoader = class {
|
|
8299
8361
|
static prefetch(zone, zoom, part, time = null) {
|
|
8300
|
-
fetch(elementsUrl(zone, zoom, part, time));
|
|
8362
|
+
fetch(elementsUrl(zone, zoom, part, time)).catch(() => {});
|
|
8301
8363
|
}
|
|
8302
8364
|
barycenters = /* @__PURE__ */ new Map();
|
|
8303
8365
|
probeBuffers = /* @__PURE__ */ new Map();
|
|
@@ -8436,7 +8498,8 @@ var ChunkLoader = class {
|
|
|
8436
8498
|
equatorial: false,
|
|
8437
8499
|
validityStart: startJd,
|
|
8438
8500
|
validityEnd: endJd,
|
|
8439
|
-
orbitalSource: OrbitalSource.SPICE_PROBE
|
|
8501
|
+
orbitalSource: OrbitalSource.SPICE_PROBE,
|
|
8502
|
+
orbitArchive: probe.archive
|
|
8440
8503
|
};
|
|
8441
8504
|
const elements = probeOsculatingElements(probe, jd, primaryMu);
|
|
8442
8505
|
const ownId = id;
|
|
@@ -8484,7 +8547,7 @@ var ChunkLoader = class {
|
|
|
8484
8547
|
const firstPass = this.barycenters.size === 0;
|
|
8485
8548
|
const bodies = [];
|
|
8486
8549
|
let degenerate = 0;
|
|
8487
|
-
const [cols, labels] = await Promise.all([fetchElements(zone, zoom, part, time, priority), fetchLabels
|
|
8550
|
+
const [cols, labels] = await Promise.all([fetchElements(zone, zoom, part, time, priority), fetchLabels()]);
|
|
8488
8551
|
const isParabolic = cols.kind === "parabolic";
|
|
8489
8552
|
const isSGP4 = cols.kind === "sgp4";
|
|
8490
8553
|
let sliceStart = performance.now();
|
|
@@ -8926,6 +8989,41 @@ async function fetchChebyshev(zone, zoom, chunk, priority) {
|
|
|
8926
8989
|
};
|
|
8927
8990
|
}
|
|
8928
8991
|
|
|
8992
|
+
//#endregion
|
|
8993
|
+
//#region src/lib/fetch/retry-backoff.ts
|
|
8994
|
+
var FIRST_DELAY_MS = 2e3;
|
|
8995
|
+
var MAX_DELAY_MS = 6e4;
|
|
8996
|
+
var JITTER = .25;
|
|
8997
|
+
var RetryBackoff = class {
|
|
8998
|
+
failures = /* @__PURE__ */ new Map();
|
|
8999
|
+
constructor(onDue = () => {}, random = Math.random) {
|
|
9000
|
+
this.onDue = onDue;
|
|
9001
|
+
this.random = random;
|
|
9002
|
+
}
|
|
9003
|
+
waiting(key) {
|
|
9004
|
+
return this.failures.get(key)?.timer !== void 0;
|
|
9005
|
+
}
|
|
9006
|
+
fail(key) {
|
|
9007
|
+
const last = this.failures.get(key);
|
|
9008
|
+
clearTimeout(last?.timer);
|
|
9009
|
+
const failure = {
|
|
9010
|
+
count: (last?.count ?? 0) + 1,
|
|
9011
|
+
timer: void 0
|
|
9012
|
+
};
|
|
9013
|
+
const delay = Math.min(FIRST_DELAY_MS * 2 ** (failure.count - 1), MAX_DELAY_MS);
|
|
9014
|
+
const spread = 1 + JITTER * (2 * this.random() - 1);
|
|
9015
|
+
failure.timer = setTimeout(() => {
|
|
9016
|
+
failure.timer = void 0;
|
|
9017
|
+
this.onDue(key);
|
|
9018
|
+
}, delay * spread);
|
|
9019
|
+
this.failures.set(key, failure);
|
|
9020
|
+
}
|
|
9021
|
+
clear(key) {
|
|
9022
|
+
clearTimeout(this.failures.get(key)?.timer);
|
|
9023
|
+
this.failures.delete(key);
|
|
9024
|
+
}
|
|
9025
|
+
};
|
|
9026
|
+
|
|
8929
9027
|
//#endregion
|
|
8930
9028
|
//#region src/lib/fetch/position/chunk-window.ts
|
|
8931
9029
|
var NEIGHBOR_WINDOW = 1;
|
|
@@ -8936,6 +9034,11 @@ function chunkKey(zone, chunkIdx) {
|
|
|
8936
9034
|
var ChunkWindow = class {
|
|
8937
9035
|
chunks = /* @__PURE__ */ new Map();
|
|
8938
9036
|
inflight = /* @__PURE__ */ new Map();
|
|
9037
|
+
retries = new RetryBackoff(() => {
|
|
9038
|
+
this.retryDue = true;
|
|
9039
|
+
this._version++;
|
|
9040
|
+
});
|
|
9041
|
+
retryDue = false;
|
|
8939
9042
|
lastEnsuredJd = NaN;
|
|
8940
9043
|
_version = 0;
|
|
8941
9044
|
constructor(zoneParams) {
|
|
@@ -8948,19 +9051,21 @@ var ChunkWindow = class {
|
|
|
8948
9051
|
return Array.from(this.zoneParams.keys());
|
|
8949
9052
|
}
|
|
8950
9053
|
ensure(jd) {
|
|
8951
|
-
if (jd === this.lastEnsuredJd) return
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
9054
|
+
if (jd === this.lastEnsuredJd && !this.retryDue) return;
|
|
9055
|
+
this.pass(jd, false);
|
|
9056
|
+
}
|
|
9057
|
+
load(jd) {
|
|
9058
|
+
return this.pass(jd, true);
|
|
9059
|
+
}
|
|
9060
|
+
pass(jd, now) {
|
|
8955
9061
|
this.lastEnsuredJd = jd;
|
|
9062
|
+
this.retryDue = false;
|
|
8956
9063
|
const jobs = [];
|
|
8957
9064
|
const neighbors = [];
|
|
8958
|
-
let ready = true;
|
|
8959
9065
|
for (const [zone, params] of this.zoneParams) {
|
|
8960
9066
|
const center = chunkIndexForJd(params, jd);
|
|
8961
|
-
if (this.isLoadable(params, center)
|
|
8962
|
-
|
|
8963
|
-
const job = this.loadChunk(zone, params, center, "high");
|
|
9067
|
+
if (this.isLoadable(params, center)) {
|
|
9068
|
+
const job = this.loadChunk(zone, params, center, "high", now);
|
|
8964
9069
|
if (job) jobs.push(job);
|
|
8965
9070
|
}
|
|
8966
9071
|
for (let d = -NEIGHBOR_WINDOW; d <= NEIGHBOR_WINDOW; d++) {
|
|
@@ -8978,10 +9083,7 @@ var ChunkWindow = class {
|
|
|
8978
9083
|
done.catch(() => {}).then(() => {
|
|
8979
9084
|
for (const [zone, params, idx] of neighbors) this.loadChunk(zone, params, idx, "low")?.catch(() => {});
|
|
8980
9085
|
});
|
|
8981
|
-
return
|
|
8982
|
-
ready,
|
|
8983
|
-
done
|
|
8984
|
-
};
|
|
9086
|
+
return done;
|
|
8985
9087
|
}
|
|
8986
9088
|
request(jd) {
|
|
8987
9089
|
for (const [zone, params] of this.zoneParams) {
|
|
@@ -8997,23 +9099,6 @@ var ChunkWindow = class {
|
|
|
8997
9099
|
}
|
|
8998
9100
|
return false;
|
|
8999
9101
|
}
|
|
9000
|
-
pendingCurrent(jd) {
|
|
9001
|
-
if (this.inflight.size === 0) return RESOLVED;
|
|
9002
|
-
let pending = null;
|
|
9003
|
-
for (const [zone, params] of this.zoneParams) {
|
|
9004
|
-
const job = this.inflight.get(chunkKey(zone, chunkIndexForJd(params, jd)));
|
|
9005
|
-
if (job) (pending ??= []).push(job);
|
|
9006
|
-
}
|
|
9007
|
-
return pending ? Promise.all(pending).then(() => void 0) : RESOLVED;
|
|
9008
|
-
}
|
|
9009
|
-
allCurrentChunksLoaded(jd) {
|
|
9010
|
-
for (const [zone, params] of this.zoneParams) {
|
|
9011
|
-
const center = chunkIndexForJd(params, jd);
|
|
9012
|
-
if (!this.isLoadable(params, center)) continue;
|
|
9013
|
-
if (!this.isResident(zone, center)) return false;
|
|
9014
|
-
}
|
|
9015
|
-
return true;
|
|
9016
|
-
}
|
|
9017
9102
|
isResident(zone, chunkIdx) {
|
|
9018
9103
|
return this.chunks.get(zone)?.has(chunkIdx) ?? false;
|
|
9019
9104
|
}
|
|
@@ -9022,14 +9107,22 @@ var ChunkWindow = class {
|
|
|
9022
9107
|
if (!zoneMap) return;
|
|
9023
9108
|
for (const idx of zoneMap.keys()) if (idx < center - NEIGHBOR_WINDOW || idx > center + NEIGHBOR_WINDOW) zoneMap.delete(idx);
|
|
9024
9109
|
}
|
|
9025
|
-
loadChunk(zone, params, chunkIdx, priority) {
|
|
9110
|
+
loadChunk(zone, params, chunkIdx, priority, now = false) {
|
|
9026
9111
|
if (this.isResident(zone, chunkIdx)) return null;
|
|
9027
9112
|
const key = chunkKey(zone, chunkIdx);
|
|
9028
9113
|
const existing = this.inflight.get(key);
|
|
9029
9114
|
if (existing) return existing;
|
|
9030
|
-
|
|
9115
|
+
if (!now && this.retries.waiting(key)) return null;
|
|
9116
|
+
return this.track(key, this.fetchAndStore(zone, params, chunkIdx, priority));
|
|
9117
|
+
}
|
|
9118
|
+
track(key, job) {
|
|
9031
9119
|
this.inflight.set(key, job);
|
|
9032
|
-
|
|
9120
|
+
const settle = (ok) => {
|
|
9121
|
+
if (this.inflight.get(key) === job) this.inflight.delete(key);
|
|
9122
|
+
if (ok) this.retries.clear(key);
|
|
9123
|
+
else this.retries.fail(key);
|
|
9124
|
+
};
|
|
9125
|
+
job.then(() => settle(true), () => settle(false));
|
|
9033
9126
|
return job;
|
|
9034
9127
|
}
|
|
9035
9128
|
async fetchAndStore(zone, params, chunkIdx, priority) {
|
|
@@ -9160,15 +9253,7 @@ var ProbeStore = class extends ChunkWindow {
|
|
|
9160
9253
|
if (this.isResident(zone, idx)) continue;
|
|
9161
9254
|
const key = chunkKey(zone, idx);
|
|
9162
9255
|
if (this.warmed.has(key)) continue;
|
|
9163
|
-
|
|
9164
|
-
if (existing) {
|
|
9165
|
-
jobs.push(existing);
|
|
9166
|
-
continue;
|
|
9167
|
-
}
|
|
9168
|
-
const job = this.fetchAndWarm(key, zone, idx, params);
|
|
9169
|
-
this.inflight.set(key, job);
|
|
9170
|
-
job.finally(() => this.inflight.delete(key));
|
|
9171
|
-
jobs.push(job);
|
|
9256
|
+
jobs.push(this.inflight.get(key) ?? this.track(key, this.fetchAndWarm(key, zone, idx, params)));
|
|
9172
9257
|
}
|
|
9173
9258
|
await Promise.all(jobs);
|
|
9174
9259
|
}
|
|
@@ -9390,9 +9475,11 @@ function rowsFor(chunk, id) {
|
|
|
9390
9475
|
|
|
9391
9476
|
//#endregion
|
|
9392
9477
|
//#region src/lib/fetch/gz.ts
|
|
9478
|
+
function gunzip(res) {
|
|
9479
|
+
return new Response(res.body.pipeThrough(new DecompressionStream("gzip")));
|
|
9480
|
+
}
|
|
9393
9481
|
async function gunzipJson(res) {
|
|
9394
|
-
|
|
9395
|
-
return await new Response(res.body.pipeThrough(ds)).json();
|
|
9482
|
+
return await gunzip(res).json();
|
|
9396
9483
|
}
|
|
9397
9484
|
function memoizedGzJson(url, opts = {}) {
|
|
9398
9485
|
const { onMissing, fetcher = (u) => fetch(u), error = (res, u) => `memoizedGzJson: ${u} returned ${res.status} ${res.statusText}` } = opts;
|
|
@@ -9654,13 +9741,7 @@ async function createPlaceholderBody(targetId, ctx, visited = /* @__PURE__ */ ne
|
|
|
9654
9741
|
return [];
|
|
9655
9742
|
}
|
|
9656
9743
|
visited.add(targetId);
|
|
9657
|
-
|
|
9658
|
-
try {
|
|
9659
|
-
detail = await fetchObjectDetail(targetId);
|
|
9660
|
-
} catch {
|
|
9661
|
-
console.warn(`createPlaceholderBody: failed to fetch global data for ${targetId} — hiding`);
|
|
9662
|
-
return [];
|
|
9663
|
-
}
|
|
9744
|
+
const detail = await fetchObjectDetail(targetId);
|
|
9664
9745
|
const global = detail.global;
|
|
9665
9746
|
if (!global) {
|
|
9666
9747
|
console.warn(`createPlaceholderBody: no catalogue record for ${targetId} — hiding`);
|
|
@@ -9710,7 +9791,7 @@ async function ensureTargetStreamed(ctx, targetId) {
|
|
|
9710
9791
|
if (ctx.getBody(targetId)) return;
|
|
9711
9792
|
const placeholders = await createPlaceholderBody(targetId, ctx);
|
|
9712
9793
|
if (placeholders.length === 0) return;
|
|
9713
|
-
const added = routePlaceholders(ctx, placeholders, await fetchLabels
|
|
9794
|
+
const added = routePlaceholders(ctx, placeholders, await fetchLabels());
|
|
9714
9795
|
ctx.bodies.flushMinor(added > 0);
|
|
9715
9796
|
}
|
|
9716
9797
|
|
|
@@ -9771,11 +9852,17 @@ function setSceneSettings(settings) {
|
|
|
9771
9852
|
//#region src/lib/scene/zone-refresher.ts
|
|
9772
9853
|
var MIN_LOAD_INTERVAL_MS = 2e3;
|
|
9773
9854
|
var STALE_FAILURE_STREAK = 3;
|
|
9855
|
+
function loadKey(z, target) {
|
|
9856
|
+
return `${z.zone}:${z.zoom ?? ""}:${target}`;
|
|
9857
|
+
}
|
|
9774
9858
|
var ZoneRefresher = class {
|
|
9775
9859
|
zones = [];
|
|
9776
9860
|
latestDate;
|
|
9777
9861
|
failureStreak = 0;
|
|
9778
9862
|
staleNotified = false;
|
|
9863
|
+
retries = new RetryBackoff(() => this.wakes++);
|
|
9864
|
+
wakes = 0;
|
|
9865
|
+
wakeTimer;
|
|
9779
9866
|
constructor(ctx, metadata, loader, initialDate) {
|
|
9780
9867
|
this.ctx = ctx;
|
|
9781
9868
|
this.loader = loader;
|
|
@@ -9821,16 +9908,30 @@ var ZoneRefresher = class {
|
|
|
9821
9908
|
}
|
|
9822
9909
|
}
|
|
9823
9910
|
}
|
|
9824
|
-
recordSuccess() {
|
|
9911
|
+
recordSuccess(key) {
|
|
9912
|
+
this.retries.clear(key);
|
|
9825
9913
|
this.failureStreak = 0;
|
|
9826
9914
|
}
|
|
9827
|
-
recordFailure() {
|
|
9915
|
+
recordFailure(key) {
|
|
9916
|
+
this.retries.fail(key);
|
|
9828
9917
|
this.failureStreak++;
|
|
9829
9918
|
if (this.failureStreak >= STALE_FAILURE_STREAK && !this.staleNotified) {
|
|
9830
9919
|
this.staleNotified = true;
|
|
9831
9920
|
this.ctx.onDataStale?.();
|
|
9832
9921
|
}
|
|
9833
9922
|
}
|
|
9923
|
+
wakeIn(ms) {
|
|
9924
|
+
this.wakeTimer ??= setTimeout(() => {
|
|
9925
|
+
this.wakeTimer = void 0;
|
|
9926
|
+
this.wakes++;
|
|
9927
|
+
}, ms);
|
|
9928
|
+
}
|
|
9929
|
+
start(z, load) {
|
|
9930
|
+
z.inFlight = load.catch(() => {}).finally(() => {
|
|
9931
|
+
z.inFlight = null;
|
|
9932
|
+
});
|
|
9933
|
+
return load;
|
|
9934
|
+
}
|
|
9834
9935
|
invalidateZone(zoneName) {
|
|
9835
9936
|
for (const z of this.zones) {
|
|
9836
9937
|
if (z.zone !== zoneName || z.kind !== "time") continue;
|
|
@@ -9851,12 +9952,15 @@ var ZoneRefresher = class {
|
|
|
9851
9952
|
if (state.inFlight) continue;
|
|
9852
9953
|
const target = snapshotDate(state.zoomData, date);
|
|
9853
9954
|
if (target === state.currentTime) continue;
|
|
9955
|
+
if (this.retries.waiting(loadKey(state, target))) continue;
|
|
9854
9956
|
const nowMs = performance.now();
|
|
9855
|
-
|
|
9957
|
+
const held = state.lastLoadStartMs + MIN_LOAD_INTERVAL_MS - nowMs;
|
|
9958
|
+
if (held > 0) {
|
|
9959
|
+
this.wakeIn(held);
|
|
9960
|
+
continue;
|
|
9961
|
+
}
|
|
9856
9962
|
state.lastLoadStartMs = nowMs;
|
|
9857
|
-
state
|
|
9858
|
-
state.inFlight = null;
|
|
9859
|
-
});
|
|
9963
|
+
this.start(state, this.loadTime(state, target, date));
|
|
9860
9964
|
} else {
|
|
9861
9965
|
while (true) {
|
|
9862
9966
|
const target = chunkIndexForJd(state.zoomData, jd);
|
|
@@ -9871,18 +9975,18 @@ var ZoneRefresher = class {
|
|
|
9871
9975
|
if (state.inFlight) continue;
|
|
9872
9976
|
const target = chunkIndexForJd(state.zoomData, jd);
|
|
9873
9977
|
if (target === state.currentIdx) continue;
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
});
|
|
9978
|
+
if (this.retries.waiting(loadKey(state, target))) continue;
|
|
9979
|
+
this.start(state, this.loadChunk(state, target));
|
|
9877
9980
|
}
|
|
9878
9981
|
}
|
|
9879
9982
|
async ready(date = this.latestDate) {
|
|
9880
9983
|
this.latestDate = date;
|
|
9881
9984
|
const jd = dateToJD(date);
|
|
9882
9985
|
const waits = this.zones.map((z) => this.zoneReady(z, date, jd));
|
|
9883
|
-
if (this.ctx.chebStore) waits.push(this.ctx.chebStore.
|
|
9884
|
-
if (this.ctx.probeStore) waits.push(this.ctx.probeStore.
|
|
9885
|
-
await Promise.
|
|
9986
|
+
if (this.ctx.chebStore) waits.push(this.ctx.chebStore.load(jd));
|
|
9987
|
+
if (this.ctx.probeStore) waits.push(this.ctx.probeStore.load(jd));
|
|
9988
|
+
const failure = (await Promise.allSettled(waits)).find((w) => w.status === "rejected");
|
|
9989
|
+
if (failure) throw failure.reason;
|
|
9886
9990
|
}
|
|
9887
9991
|
async zoneReady(z, date, jd) {
|
|
9888
9992
|
while (z.inFlight) await z.inFlight;
|
|
@@ -9890,17 +9994,12 @@ var ZoneRefresher = class {
|
|
|
9890
9994
|
const target = snapshotDate(z.zoomData, date);
|
|
9891
9995
|
if (target === z.currentTime) return;
|
|
9892
9996
|
z.lastLoadStartMs = performance.now();
|
|
9893
|
-
z
|
|
9894
|
-
z.inFlight = null;
|
|
9895
|
-
});
|
|
9997
|
+
await this.start(z, this.loadTime(z, target, date));
|
|
9896
9998
|
} else {
|
|
9897
9999
|
const target = chunkIndexForJd(z.zoomData, jd);
|
|
9898
10000
|
if (target === z.currentIdx) return;
|
|
9899
|
-
z
|
|
9900
|
-
z.inFlight = null;
|
|
9901
|
-
});
|
|
10001
|
+
await this.start(z, this.loadChunk(z, target));
|
|
9902
10002
|
}
|
|
9903
|
-
await z.inFlight;
|
|
9904
10003
|
}
|
|
9905
10004
|
wanted = /* @__PURE__ */ new Set();
|
|
9906
10005
|
want(id) {
|
|
@@ -9913,7 +10012,7 @@ var ZoneRefresher = class {
|
|
|
9913
10012
|
this.want(targetId);
|
|
9914
10013
|
if (this.ctx.earthSatFilter || this.ctx.earthTypeFilter) {
|
|
9915
10014
|
this.invalidateZone("earth");
|
|
9916
|
-
await this.ready(date);
|
|
10015
|
+
await this.ready(date).catch(() => {});
|
|
9917
10016
|
if (this.ctx.getBody(targetId)) return;
|
|
9918
10017
|
}
|
|
9919
10018
|
}
|
|
@@ -9924,13 +10023,13 @@ var ZoneRefresher = class {
|
|
|
9924
10023
|
if (!store) return;
|
|
9925
10024
|
const jd = dateToJD(date);
|
|
9926
10025
|
const passengerPromise = passengerFor(targetId);
|
|
9927
|
-
await store.
|
|
10026
|
+
await store.load(jd).catch(() => {});
|
|
9928
10027
|
const passenger = await passengerPromise;
|
|
9929
10028
|
if (passenger) store.registerCarried(passenger);
|
|
9930
10029
|
const fcId = store.stampedFitCenterAt(targetId, jd);
|
|
9931
10030
|
if (fcId) await ensureTargetStreamed(this.ctx, fcId);
|
|
9932
10031
|
if (this.ctx.getBody(targetId)) return;
|
|
9933
|
-
const labels = await fetchLabels
|
|
10032
|
+
const labels = await fetchLabels();
|
|
9934
10033
|
const target = this.loader.processProbes(store, date, labels).find((b) => b.data.id === targetId);
|
|
9935
10034
|
if (!target) return;
|
|
9936
10035
|
this.ctx.bodies.addBodies([target]);
|
|
@@ -9991,10 +10090,11 @@ var ZoneRefresher = class {
|
|
|
9991
10090
|
if (z.zone === "earth") this.ctx.notifyEarthSatRollover();
|
|
9992
10091
|
console.log(`zone-refresher: ${z.zone}@${fromTime} → ${time} (+${added} ~${updated} -${removed})`);
|
|
9993
10092
|
this.prefetchTimeNeighbors(z, date);
|
|
9994
|
-
this.recordSuccess();
|
|
10093
|
+
this.recordSuccess(loadKey(z, time));
|
|
9995
10094
|
} catch (e) {
|
|
9996
10095
|
console.warn(`zone-refresher: failed to refresh ${z.zone}@${time}:`, e);
|
|
9997
|
-
this.recordFailure();
|
|
10096
|
+
this.recordFailure(loadKey(z, time));
|
|
10097
|
+
throw e;
|
|
9998
10098
|
}
|
|
9999
10099
|
}
|
|
10000
10100
|
async loadChunk(z, target) {
|
|
@@ -10007,11 +10107,12 @@ var ZoneRefresher = class {
|
|
|
10007
10107
|
else chunks = await Promise.all(Array.from({ length: z.parts }, (_, part) => this.loader.process(z.zone, z.zoom, part, String(target), z.parentIdType)));
|
|
10008
10108
|
this.applyChunkSwap(z, previous, target, chunks);
|
|
10009
10109
|
this.preloadChunkNeighbors(z);
|
|
10010
|
-
this.recordSuccess();
|
|
10110
|
+
this.recordSuccess(loadKey(z, target));
|
|
10011
10111
|
} catch (e) {
|
|
10012
10112
|
console.warn(`zone-refresher: ${z.zone} chunk reload failed (${previous} → ${target}):`, e);
|
|
10013
10113
|
z.currentIdx = previous;
|
|
10014
|
-
this.recordFailure();
|
|
10114
|
+
this.recordFailure(loadKey(z, target));
|
|
10115
|
+
throw e;
|
|
10015
10116
|
}
|
|
10016
10117
|
}
|
|
10017
10118
|
applyChunkSwap(z, previous, target, chunks) {
|
|
@@ -10360,18 +10461,18 @@ async function buildProbeStore(metadata, jd, layers) {
|
|
|
10360
10461
|
const missingCenter = Array.from(params).filter(([, p]) => p.fit_center_naif_id === void 0);
|
|
10361
10462
|
if (missingCenter.length > 0) console.warn(`ProbeStore: ${missingCenter.length} zone(s) missing fit_center_naif_id in metadata — re-export to refresh: ${missingCenter.map(([z]) => z).join(", ")}`);
|
|
10362
10463
|
const store = new ProbeStore(params);
|
|
10363
|
-
await store.
|
|
10464
|
+
await store.load(jd);
|
|
10364
10465
|
return store;
|
|
10365
10466
|
}
|
|
10366
10467
|
async function loadMajorBodies(ctx, loader, metadata, date, jd, passenger) {
|
|
10367
10468
|
const major = [];
|
|
10368
10469
|
let cachedLabels = null;
|
|
10369
10470
|
if (ctx.chebStore) {
|
|
10370
|
-
cachedLabels = await fetchLabels
|
|
10471
|
+
cachedLabels = await fetchLabels();
|
|
10371
10472
|
major.push(...loader.processChebyshev(date, cachedLabels));
|
|
10372
10473
|
}
|
|
10373
10474
|
if (ctx.probeStore) {
|
|
10374
|
-
cachedLabels ??= await fetchLabels
|
|
10475
|
+
cachedLabels ??= await fetchLabels();
|
|
10375
10476
|
if (passenger) ctx.probeStore.registerCarried(passenger);
|
|
10376
10477
|
major.push(...loader.processProbes(ctx.probeStore, date, cachedLabels));
|
|
10377
10478
|
}
|
|
@@ -10404,7 +10505,7 @@ async function loadScene(ctx, date, targetId) {
|
|
|
10404
10505
|
const atmospheresPromise = loadAtmospheres().catch((e) => console.warn("scene-load: atmospheres failed to load — rendering without shells:", e));
|
|
10405
10506
|
const jd = dateToJD(date);
|
|
10406
10507
|
const metadataPromise = fetchMetadata();
|
|
10407
|
-
fetchLabels
|
|
10508
|
+
fetchLabels().catch(() => {});
|
|
10408
10509
|
metadataPromise.then((metadata) => {
|
|
10409
10510
|
const moons = metadata.position.zones.moons;
|
|
10410
10511
|
if (!moons || ctx.layers.skipsZone("moons")) return;
|
|
@@ -10420,7 +10521,7 @@ async function loadScene(ctx, date, targetId) {
|
|
|
10420
10521
|
for (const zone of params.keys()) if (ctx.layers.skipsZone(zone)) params.delete(zone);
|
|
10421
10522
|
if (params.size === 0) return null;
|
|
10422
10523
|
const store = new ChebyshevStore(params);
|
|
10423
|
-
await store.
|
|
10524
|
+
await store.load(jd);
|
|
10424
10525
|
return store;
|
|
10425
10526
|
});
|
|
10426
10527
|
const probePromise = metadataPromise.then((metadata) => buildProbeStore(metadata, jd, ctx.layers));
|
|
@@ -10438,7 +10539,7 @@ async function loadScene(ctx, date, targetId) {
|
|
|
10438
10539
|
await atmospheresPromise;
|
|
10439
10540
|
ctx.bodies.addBodies(major);
|
|
10440
10541
|
ctx.credits.recordOrbitSources(major);
|
|
10441
|
-
const labels = await fetchLabels
|
|
10542
|
+
const labels = await fetchLabels();
|
|
10442
10543
|
loadProgress.reach("labels");
|
|
10443
10544
|
let addedSinceFlush = false;
|
|
10444
10545
|
const noteAdded = () => {
|
|
@@ -10489,8 +10590,8 @@ async function loadScene(ctx, date, targetId) {
|
|
|
10489
10590
|
console.warn(`scene-load: ${zone} part ${part} failed (skipped):`, e);
|
|
10490
10591
|
};
|
|
10491
10592
|
try {
|
|
10492
|
-
await Promise.all([...asteroidOtherArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.fetchMinorColumns(zone, zoom, part, time).then((cols) => handleColumnChunk(zone, cols, parentIdType)).catch(onChunkFail(zone, part))), ctx.refresher?.ready().then(() => {
|
|
10493
|
-
|
|
10593
|
+
await Promise.all([...asteroidOtherArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.fetchMinorColumns(zone, zoom, part, time).then((cols) => handleColumnChunk(zone, cols, parentIdType)).catch(onChunkFail(zone, part))), ctx.refresher?.ready().then(noteAdded, (e) => {
|
|
10594
|
+
console.warn("scene-load: the data of the opening date is not all in:", e);
|
|
10494
10595
|
})]);
|
|
10495
10596
|
await Promise.all(moonArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.process(zone, zoom, part, time, parentIdType).then((chunk) => handleMoonChunk(zone, chunk)).catch(onChunkFail(zone, part))));
|
|
10496
10597
|
} finally {
|
|
@@ -10521,6 +10622,7 @@ var fetchEarthMembership = memoizedGzJson(async () => {
|
|
|
10521
10622
|
await fetchMetadata();
|
|
10522
10623
|
return versionedUrl("/v1/membership/earth.json.gz", "membership");
|
|
10523
10624
|
}, {
|
|
10625
|
+
fetcher: fetchWithTimeout,
|
|
10524
10626
|
onMissing: () => ({}),
|
|
10525
10627
|
error: (res) => `fetchEarthMembership: ${res.status} ${res.statusText}`
|
|
10526
10628
|
});
|
|
@@ -10573,6 +10675,7 @@ var ContextManager = class {
|
|
|
10573
10675
|
smallBodyFilter = null;
|
|
10574
10676
|
missionMemberIds = null;
|
|
10575
10677
|
currentGroupSlug = null;
|
|
10678
|
+
groupFilterJob = Promise.resolve();
|
|
10576
10679
|
groupFilterListeners = /* @__PURE__ */ new Set();
|
|
10577
10680
|
smallBodyFilterListeners = /* @__PURE__ */ new Set();
|
|
10578
10681
|
earthSatRolloverListeners = /* @__PURE__ */ new Set();
|
|
@@ -10613,12 +10716,15 @@ var ContextManager = class {
|
|
|
10613
10716
|
try {
|
|
10614
10717
|
await loadScene(this, date, targetId);
|
|
10615
10718
|
} catch (e) {
|
|
10616
|
-
this.
|
|
10617
|
-
this.error = e instanceof Error ? e.message : String(e);
|
|
10618
|
-
console.error("[scene-load]", e);
|
|
10719
|
+
this.fail(e);
|
|
10619
10720
|
throw e;
|
|
10620
10721
|
}
|
|
10621
10722
|
}
|
|
10723
|
+
fail(e) {
|
|
10724
|
+
this.loading = false;
|
|
10725
|
+
this.error = e instanceof Error ? e.message : String(e);
|
|
10726
|
+
console.error("[scene-load]", e);
|
|
10727
|
+
}
|
|
10622
10728
|
refreshTick(date) {
|
|
10623
10729
|
this.refresher?.tick(date);
|
|
10624
10730
|
}
|
|
@@ -10628,15 +10734,23 @@ var ContextManager = class {
|
|
|
10628
10734
|
async ensureBody(targetId, date) {
|
|
10629
10735
|
await this.refresher?.ensureBody(targetId, date);
|
|
10630
10736
|
}
|
|
10631
|
-
|
|
10632
|
-
if (slug === this.currentGroupSlug) return;
|
|
10737
|
+
applyGroupFilter(slug) {
|
|
10738
|
+
if (slug === this.currentGroupSlug) return this.groupFilterJob;
|
|
10633
10739
|
this.currentGroupSlug = slug;
|
|
10740
|
+
const job = this.installGroupFilter(slug);
|
|
10741
|
+
this.groupFilterJob = job;
|
|
10742
|
+
job.catch(() => {
|
|
10743
|
+
if (this.groupFilterJob === job) this.currentGroupSlug = void 0;
|
|
10744
|
+
});
|
|
10745
|
+
return job;
|
|
10746
|
+
}
|
|
10747
|
+
async installGroupFilter(slug) {
|
|
10634
10748
|
if (slug?.startsWith("mission-")) {
|
|
10635
10749
|
const detail = await fetchGroupDetail(slug);
|
|
10636
10750
|
if (slug !== this.currentGroupSlug) return;
|
|
10637
10751
|
this.missionMemberIds = new Set((detail.global?.notable_members ?? []).map((mm) => mm.id).filter((id) => !!id));
|
|
10638
10752
|
} else this.missionMemberIds = null;
|
|
10639
|
-
const entry = slug ? await
|
|
10753
|
+
const entry = slug ? (await fetchGroupIndex())[slug] ?? null : null;
|
|
10640
10754
|
if (slug !== this.currentGroupSlug) return;
|
|
10641
10755
|
const nextSmallBody = this.resolveSmallBodyFilter(slug, entry?.applies_to, entry?.n);
|
|
10642
10756
|
const nextEarthSlug = entry?.applies_to === "earth_sat" ? slug : null;
|
|
@@ -10685,13 +10799,6 @@ var ContextManager = class {
|
|
|
10685
10799
|
}
|
|
10686
10800
|
return null;
|
|
10687
10801
|
}
|
|
10688
|
-
async resolveIndexEntry(slug) {
|
|
10689
|
-
try {
|
|
10690
|
-
return (await fetchGroupIndex())[slug] ?? null;
|
|
10691
|
-
} catch {
|
|
10692
|
-
return null;
|
|
10693
|
-
}
|
|
10694
|
-
}
|
|
10695
10802
|
};
|
|
10696
10803
|
|
|
10697
10804
|
//#endregion
|
|
@@ -11307,6 +11414,51 @@ function makeEnvMap(renderer) {
|
|
|
11307
11414
|
return tex;
|
|
11308
11415
|
}
|
|
11309
11416
|
|
|
11417
|
+
//#endregion
|
|
11418
|
+
//#region src/lib/scene/setup/bloom-high-pass.ts
|
|
11419
|
+
var MAX_SPAN = 16;
|
|
11420
|
+
function installBloomHighPass(pass) {
|
|
11421
|
+
pass.materialHighPassFilter.dispose();
|
|
11422
|
+
pass.materialHighPassFilter = new ShaderMaterial({
|
|
11423
|
+
uniforms: pass.highPassUniforms,
|
|
11424
|
+
vertexShader: `
|
|
11425
|
+
varying vec2 vUv;
|
|
11426
|
+
void main() {
|
|
11427
|
+
vUv = uv;
|
|
11428
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
11429
|
+
}
|
|
11430
|
+
`,
|
|
11431
|
+
fragmentShader: `
|
|
11432
|
+
uniform sampler2D tDiffuse;
|
|
11433
|
+
uniform float luminosityThreshold;
|
|
11434
|
+
uniform float smoothWidth;
|
|
11435
|
+
varying vec2 vUv;
|
|
11436
|
+
void main() {
|
|
11437
|
+
vec2 size = vec2(textureSize(tDiffuse, 0));
|
|
11438
|
+
vec2 span = size * abs(vec2(dFdx(vUv.x), dFdy(vUv.y)));
|
|
11439
|
+
vec2 lo = vUv * size - 0.5 * span;
|
|
11440
|
+
vec2 hi = lo + span;
|
|
11441
|
+
// A sliver of a pixel is rounding, not light.
|
|
11442
|
+
ivec2 first = max(ivec2(floor(lo + 0.001)), ivec2(0));
|
|
11443
|
+
ivec2 last = min(ivec2(ceil(hi - 0.001)) - 1, ivec2(size) - 1);
|
|
11444
|
+
last = min(last, first + ${MAX_SPAN - 1});
|
|
11445
|
+
vec3 sum = vec3(0.0);
|
|
11446
|
+
for (int y = first.y; y <= last.y; y++) {
|
|
11447
|
+
float wy = min(float(y) + 1.0, hi.y) - max(float(y), lo.y);
|
|
11448
|
+
for (int x = first.x; x <= last.x; x++) {
|
|
11449
|
+
float wx = min(float(x) + 1.0, hi.x) - max(float(x), lo.x);
|
|
11450
|
+
vec3 texel = texelFetch(tDiffuse, ivec2(x, y), 0).rgb;
|
|
11451
|
+
float taken = smoothstep(
|
|
11452
|
+
luminosityThreshold, luminosityThreshold + smoothWidth, luminance(texel));
|
|
11453
|
+
sum += texel * (taken * wx * wy);
|
|
11454
|
+
}
|
|
11455
|
+
}
|
|
11456
|
+
gl_FragColor = vec4(sum / (span.x * span.y), 1.0);
|
|
11457
|
+
}
|
|
11458
|
+
`
|
|
11459
|
+
});
|
|
11460
|
+
}
|
|
11461
|
+
|
|
11310
11462
|
//#endregion
|
|
11311
11463
|
//#region src/lib/scene/setup/three-boot.ts
|
|
11312
11464
|
var CAMERA_FAR_DEFAULT = 1e5;
|
|
@@ -11373,6 +11525,7 @@ function bootThree(canvas, labelContainer, ctx) {
|
|
|
11373
11525
|
composer.setSize(canvas.clientWidth, canvas.clientHeight);
|
|
11374
11526
|
composer.addPass(new RenderPass(scene, camera));
|
|
11375
11527
|
const bloomPass = new UnrealBloomPass(new Vector2(canvas.clientWidth * tier.bloomScale, canvas.clientHeight * tier.bloomScale), .3, .5, 1);
|
|
11528
|
+
installBloomHighPass(bloomPass);
|
|
11376
11529
|
composer.addPass(bloomPass);
|
|
11377
11530
|
composer.addPass(new OutputPass());
|
|
11378
11531
|
return {
|
|
@@ -11447,7 +11600,7 @@ var AttitudeTrack = class {
|
|
|
11447
11600
|
files;
|
|
11448
11601
|
chunks = /* @__PURE__ */ new Map();
|
|
11449
11602
|
inflight = /* @__PURE__ */ new Set();
|
|
11450
|
-
|
|
11603
|
+
retries = new RetryBackoff();
|
|
11451
11604
|
constructor(probeId, manifest) {
|
|
11452
11605
|
this.probeId = probeId;
|
|
11453
11606
|
this.startJd = manifest.files.length ? manifest.files[0].start_jd : 0;
|
|
@@ -11527,7 +11680,7 @@ var AttitudeTrack = class {
|
|
|
11527
11680
|
if (i < 0 || i >= this.files.length) return void 0;
|
|
11528
11681
|
const have = this.chunks.get(i);
|
|
11529
11682
|
if (have) return have;
|
|
11530
|
-
if (this.inflight.has(i) || this.
|
|
11683
|
+
if (this.inflight.has(i) || this.retries.waiting(i)) return void 0;
|
|
11531
11684
|
this.inflight.add(i);
|
|
11532
11685
|
this.load(i);
|
|
11533
11686
|
}
|
|
@@ -11539,8 +11692,9 @@ var AttitudeTrack = class {
|
|
|
11539
11692
|
const ds = new DecompressionStream("gzip");
|
|
11540
11693
|
const buf = await new Response(res.body.pipeThrough(ds)).arrayBuffer();
|
|
11541
11694
|
this.chunks.set(i, parseAttitudeChunk(buf));
|
|
11695
|
+
this.retries.clear(i);
|
|
11542
11696
|
} catch (e) {
|
|
11543
|
-
this.
|
|
11697
|
+
this.retries.fail(i);
|
|
11544
11698
|
console.warn(`attitude: chunk ${this.probeId}/${file.name} failed to load:`, e);
|
|
11545
11699
|
} finally {
|
|
11546
11700
|
this.inflight.delete(i);
|
|
@@ -12285,12 +12439,20 @@ function fetchBundleMeta(slug) {
|
|
|
12285
12439
|
if (!p) {
|
|
12286
12440
|
p = fetch(versionedUrl(`/v1/models/${slug}/metadata.json`, "models")).then((r) => r.json());
|
|
12287
12441
|
_bundleMetaCache.set(slug, p);
|
|
12442
|
+
p.catch(() => _bundleMetaCache.delete(slug));
|
|
12288
12443
|
}
|
|
12289
12444
|
return p;
|
|
12290
12445
|
}
|
|
12446
|
+
function allowedTier(meta) {
|
|
12447
|
+
if (textureAllowed(meta.exports.high.distribution)) return "high";
|
|
12448
|
+
if (meta.exports.low && textureAllowed(meta.exports.low.distribution)) return "low";
|
|
12449
|
+
}
|
|
12291
12450
|
function shapeModelCredit(meta) {
|
|
12292
12451
|
return meta.credit ?? meta.exports.high.credit;
|
|
12293
12452
|
}
|
|
12453
|
+
function modelTierCredit(meta, tier) {
|
|
12454
|
+
return meta.exports[tier]?.credit ?? meta.exports.high.credit;
|
|
12455
|
+
}
|
|
12294
12456
|
function modelUnitScene(bo) {
|
|
12295
12457
|
return bo.modelUnitOverride ?? bo.radiusScene;
|
|
12296
12458
|
}
|
|
@@ -12345,22 +12507,21 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
|
|
|
12345
12507
|
if ((bo.modelLoadEpoch ?? 0) !== epoch) return;
|
|
12346
12508
|
if (bo.mesh) bo.mesh.visible = false;
|
|
12347
12509
|
setHaloLoading(bo, true);
|
|
12348
|
-
const
|
|
12349
|
-
const
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
} catch (e) {
|
|
12354
|
-
console.warn(`Failed to load model metadata for ${slug}:`, e);
|
|
12510
|
+
const meta = await fetchBundleMeta(slug);
|
|
12511
|
+
const tier = allowedTier(meta);
|
|
12512
|
+
if (!tier) {
|
|
12513
|
+
bo.noPhysical = "model";
|
|
12514
|
+
return;
|
|
12355
12515
|
}
|
|
12516
|
+
const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/${tier}.glb`, "models"));
|
|
12356
12517
|
if ((bo.modelLoadEpoch ?? 0) !== epoch || !bo.mesh) {
|
|
12357
12518
|
disposeGltf(gltf.scene);
|
|
12358
12519
|
return;
|
|
12359
12520
|
}
|
|
12360
|
-
const baseFrame = meta
|
|
12521
|
+
const baseFrame = meta.frame_map ? frameMapQuaternion(meta.frame_map) : null;
|
|
12361
12522
|
bo.body.modelBaseFrame = baseFrame ?? void 0;
|
|
12362
12523
|
const root = withBaseFrame(gltf.scene, baseFrame);
|
|
12363
|
-
const anchor = meta
|
|
12524
|
+
const anchor = meta.model_anchor ? new Vector3(...meta.model_anchor) : null;
|
|
12364
12525
|
fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
|
|
12365
12526
|
enableShadows(root);
|
|
12366
12527
|
modelScene.add(root);
|
|
@@ -12368,20 +12529,19 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
|
|
|
12368
12529
|
bo.model = root;
|
|
12369
12530
|
bo.modelName = slug;
|
|
12370
12531
|
setHaloLoading(bo, false);
|
|
12371
|
-
if (meta) {
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
|
|
12377
|
-
}
|
|
12378
|
-
ctx?.credits.registerModel({
|
|
12379
|
-
bodyId: bo.body.data.id,
|
|
12380
|
-
source: meta.exports.high.credit.url,
|
|
12381
|
-
organisation: meta.exports.high.credit.name,
|
|
12382
|
-
license: meta.exports.high.credit.license
|
|
12383
|
-
});
|
|
12532
|
+
if (meta.scale_meters) {
|
|
12533
|
+
const spanKm = meta.scale_meters / 2e3;
|
|
12534
|
+
bo.body.data.radiusKm = spanKm * (meta.body_span_ratio ?? 1);
|
|
12535
|
+
bo.radiusScene = kmToScene(effectiveRadiusKm(bo.body.data));
|
|
12536
|
+
bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
|
|
12384
12537
|
}
|
|
12538
|
+
const credit = modelTierCredit(meta, tier);
|
|
12539
|
+
ctx?.credits.registerModel({
|
|
12540
|
+
bodyId: bo.body.data.id,
|
|
12541
|
+
source: credit.url,
|
|
12542
|
+
organisation: credit.name,
|
|
12543
|
+
license: credit.license
|
|
12544
|
+
});
|
|
12385
12545
|
} finally {
|
|
12386
12546
|
if (!bo.model) setHaloLoading(bo, false);
|
|
12387
12547
|
}
|
|
@@ -15175,8 +15335,11 @@ function sunPathTransmittance(params, camRelPos, sunDir, planetRadiusScene, plan
|
|
|
15175
15335
|
var SUN_HDR_MULTIPLIER = 6;
|
|
15176
15336
|
var LIMB_DISC_AVG = .8;
|
|
15177
15337
|
var STAR_POINT_SIZE_PX = 4;
|
|
15178
|
-
var
|
|
15179
|
-
var
|
|
15338
|
+
var STAR_POINT_LIGHT = 6 * LIMB_DISC_AVG * Math.PI * (4 / 2) ** 2;
|
|
15339
|
+
var STAR_POINT_FLOOR = .9;
|
|
15340
|
+
var STAR_POINT_SEAT_BIAS = .125;
|
|
15341
|
+
var CORONA_OPACITY = .6;
|
|
15342
|
+
var CORONA_WHOLE = 1.5;
|
|
15180
15343
|
function makeGlowTexture(color, size = 256) {
|
|
15181
15344
|
const canvas = document.createElement("canvas");
|
|
15182
15345
|
canvas.width = size;
|
|
@@ -15257,7 +15420,7 @@ function makeStarGlow(radius, color) {
|
|
|
15257
15420
|
map: makeGlowTexture(color.startsWith("#") ? hexToRgb(color) : color),
|
|
15258
15421
|
blending: AdditiveBlending,
|
|
15259
15422
|
transparent: true,
|
|
15260
|
-
opacity:
|
|
15423
|
+
opacity: CORONA_OPACITY,
|
|
15261
15424
|
depthWrite: false,
|
|
15262
15425
|
depthTest: true
|
|
15263
15426
|
}));
|
|
@@ -15265,23 +15428,44 @@ function makeStarGlow(radius, color) {
|
|
|
15265
15428
|
corona.scale.set(glowSize, glowSize, 1);
|
|
15266
15429
|
return corona;
|
|
15267
15430
|
}
|
|
15268
|
-
function
|
|
15431
|
+
function dotPixels(size) {
|
|
15432
|
+
const reach = Math.ceil(size / 2);
|
|
15433
|
+
let count = 0;
|
|
15434
|
+
for (let y = -reach; y < reach; y++) for (let x = -reach; x < reach; x++) if ((x + .5) ** 2 + (y + .5) ** 2 <= size * size / 4) count++;
|
|
15435
|
+
return count;
|
|
15436
|
+
}
|
|
15437
|
+
function makeStarPoint(color) {
|
|
15269
15438
|
const geometry = new BufferGeometry();
|
|
15270
15439
|
geometry.setAttribute("position", new Float32BufferAttribute(new Float32Array(3), 3));
|
|
15271
15440
|
const c = new Color(color);
|
|
15441
|
+
const disc = new Color(PHOTOSPHERE_CENTRE_COLOR);
|
|
15442
|
+
const viewport = new Vector4(0, 0, 1, 1);
|
|
15443
|
+
const size = 4 .toFixed(1);
|
|
15444
|
+
const uniforms = {
|
|
15445
|
+
uColor: { value: new Vector3(c.r, c.g, c.b) },
|
|
15446
|
+
uDiscColor: { value: new Vector3(disc.r, disc.g, disc.b) },
|
|
15447
|
+
uFlux: { value: 1 },
|
|
15448
|
+
uPixelRatio: { value: 1 },
|
|
15449
|
+
uPixels: { value: dotPixels(4) },
|
|
15450
|
+
uViewport: { value: viewport },
|
|
15451
|
+
uTint: { value: new Vector3(1, 1, 1) }
|
|
15452
|
+
};
|
|
15272
15453
|
const points = new Points(geometry, new ShaderMaterial({
|
|
15273
|
-
uniforms
|
|
15274
|
-
uColor: { value: new Vector3(c.r, c.g, c.b) },
|
|
15275
|
-
uIntensity: { value: STAR_POINT_HANDOFF_INTENSITY },
|
|
15276
|
-
uTint: { value: new Vector3(1, 1, 1) },
|
|
15277
|
-
uMap: { value: circleTexture }
|
|
15278
|
-
},
|
|
15454
|
+
uniforms,
|
|
15279
15455
|
vertexShader: `
|
|
15280
15456
|
#include <common>
|
|
15281
15457
|
#include <logdepthbuf_pars_vertex>
|
|
15458
|
+
uniform float uPixelRatio;
|
|
15459
|
+
uniform vec4 uViewport;
|
|
15460
|
+
varying vec2 vSeat;
|
|
15282
15461
|
void main() {
|
|
15283
15462
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
15284
|
-
|
|
15463
|
+
vec2 halfView = 0.5 * uViewport.zw;
|
|
15464
|
+
vec2 seat = floor((gl_Position.xy / gl_Position.w + 1.0) * halfView + ${(.5 + STAR_POINT_SEAT_BIAS).toFixed(3)});
|
|
15465
|
+
gl_Position.xy = (seat / halfView - 1.0) * gl_Position.w;
|
|
15466
|
+
vSeat = uViewport.xy + seat;
|
|
15467
|
+
// A margin: the square of the point must not cut the dot.
|
|
15468
|
+
gl_PointSize = ${size} * uPixelRatio + 2.0;
|
|
15285
15469
|
#include <logdepthbuf_vertex>
|
|
15286
15470
|
}
|
|
15287
15471
|
`,
|
|
@@ -15289,14 +15473,35 @@ function makeStarPoint(color, circleTexture) {
|
|
|
15289
15473
|
#include <common>
|
|
15290
15474
|
#include <logdepthbuf_pars_fragment>
|
|
15291
15475
|
uniform vec3 uColor;
|
|
15292
|
-
uniform
|
|
15476
|
+
uniform vec3 uDiscColor;
|
|
15477
|
+
uniform float uFlux;
|
|
15478
|
+
uniform float uPixelRatio;
|
|
15479
|
+
uniform float uPixels;
|
|
15293
15480
|
uniform vec3 uTint;
|
|
15294
|
-
|
|
15481
|
+
varying vec2 vSeat;
|
|
15295
15482
|
void main() {
|
|
15296
15483
|
#include <logdepthbuf_fragment>
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15484
|
+
vec2 p = gl_FragCoord.xy - vSeat;
|
|
15485
|
+
float radius = 0.5 * ${size} * uPixelRatio;
|
|
15486
|
+
if (dot(p, p) > radius * radius) discard;
|
|
15487
|
+
// The disc's colour at the handoff, so the glow keeps it there.
|
|
15488
|
+
vec3 colour = uTint * mix(uColor, uDiscColor, uFlux);
|
|
15489
|
+
float luma = max(luminance(colour), 0.0001);
|
|
15490
|
+
// What the bloom takes for sure, and what it leaves for sure.
|
|
15491
|
+
float over = ${(1 * 1.05).toFixed(2)} / luma;
|
|
15492
|
+
float under = ${(1 * .95).toFixed(2)} / luma;
|
|
15493
|
+
// The disc's light at the handoff, on all the pixels. With too little
|
|
15494
|
+
// for all, the four at the centre have it: the glow goes on for a
|
|
15495
|
+
// fainter star.
|
|
15496
|
+
float all = ${STAR_POINT_LIGHT.toFixed(3)} * uFlux * uPixelRatio * uPixelRatio / uPixels;
|
|
15497
|
+
float four = all * uPixels / 4.0;
|
|
15498
|
+
float value = clamp(all, ${STAR_POINT_FLOOR.toFixed(1)}, under);
|
|
15499
|
+
if (all >= over) {
|
|
15500
|
+
value = all;
|
|
15501
|
+
} else if (four >= over && max(abs(p.x), abs(p.y)) < 1.0) {
|
|
15502
|
+
value = four;
|
|
15503
|
+
}
|
|
15504
|
+
gl_FragColor = vec4(colour * value, 1.0);
|
|
15300
15505
|
}
|
|
15301
15506
|
`,
|
|
15302
15507
|
transparent: true,
|
|
@@ -15304,14 +15509,22 @@ function makeStarPoint(color, circleTexture) {
|
|
|
15304
15509
|
depthWrite: false
|
|
15305
15510
|
}));
|
|
15306
15511
|
points.frustumCulled = false;
|
|
15512
|
+
points.onBeforeRender = (renderer) => {
|
|
15513
|
+
renderer.getCurrentViewport(viewport).floor();
|
|
15514
|
+
const pixelRatio = renderer.getPixelRatio();
|
|
15515
|
+
if (pixelRatio !== uniforms.uPixelRatio.value) {
|
|
15516
|
+
uniforms.uPixelRatio.value = pixelRatio;
|
|
15517
|
+
uniforms.uPixels.value = dotPixels(4 * pixelRatio);
|
|
15518
|
+
}
|
|
15519
|
+
};
|
|
15307
15520
|
return points;
|
|
15308
15521
|
}
|
|
15309
|
-
function buildStarExtras(root, radius, color
|
|
15522
|
+
function buildStarExtras(root, radius, color) {
|
|
15310
15523
|
const light = new PointLight(16777215, SUN_LIGHT_INTENSITY, 0, 0);
|
|
15311
15524
|
root.add(light);
|
|
15312
15525
|
const corona = makeStarGlow(radius, color);
|
|
15313
15526
|
root.add(corona);
|
|
15314
|
-
const starPoint = makeStarPoint(color
|
|
15527
|
+
const starPoint = makeStarPoint(color);
|
|
15315
15528
|
root.add(starPoint);
|
|
15316
15529
|
return {
|
|
15317
15530
|
light,
|
|
@@ -15323,6 +15536,12 @@ function applyStarTint(corona, starPoint, tint) {
|
|
|
15323
15536
|
if (corona) corona.material.color.setRGB(tint.x, tint.y, tint.z);
|
|
15324
15537
|
if (starPoint) starPoint.material.uniforms.uTint.value.copy(tint);
|
|
15325
15538
|
}
|
|
15539
|
+
function fadeStarCorona(corona, t) {
|
|
15540
|
+
if (!corona) return;
|
|
15541
|
+
const part = Math.min(Math.max((t - 1) / (CORONA_WHOLE - 1), 0), 1);
|
|
15542
|
+
corona.visible = part > 0;
|
|
15543
|
+
corona.material.opacity = CORONA_OPACITY * part;
|
|
15544
|
+
}
|
|
15326
15545
|
function starViewTintUniforms(material) {
|
|
15327
15546
|
const uniforms = material?.uniforms;
|
|
15328
15547
|
return uniforms?.uAtmoTEnable ? uniforms : null;
|
|
@@ -15979,7 +16198,7 @@ function disposeMaterial(mat) {
|
|
|
15979
16198
|
}
|
|
15980
16199
|
var STAR_SPHERE_SEGMENTS = 96;
|
|
15981
16200
|
var BODY_SPHERE_SEGMENTS = 24;
|
|
15982
|
-
function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects,
|
|
16201
|
+
function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, rendererElement, handleFocus, onHoverChange, halfPromoteIds) {
|
|
15983
16202
|
for (const body of bodies) {
|
|
15984
16203
|
const id = body.data.id;
|
|
15985
16204
|
const isHalfPromoted = halfPromoteIds?.has(id) ?? false;
|
|
@@ -16001,7 +16220,7 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, ci
|
|
|
16001
16220
|
let atmosphere = null;
|
|
16002
16221
|
let sunTint;
|
|
16003
16222
|
if (!isVirtual && !noPhysical) {
|
|
16004
|
-
if (isStar) starExtras = buildStarExtras(root, radius, color
|
|
16223
|
+
if (isStar) starExtras = buildStarExtras(root, radius, color);
|
|
16005
16224
|
const segments = isStar ? STAR_SPHERE_SEGMENTS : BODY_SPHERE_SEGMENTS;
|
|
16006
16225
|
const geometry = new SphereGeometry(radius, segments, segments);
|
|
16007
16226
|
const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: bodyMeshColor(body.data) });
|
|
@@ -17046,30 +17265,31 @@ async function fetchPositions(bodyId) {
|
|
|
17046
17265
|
if (res.status === 404) return [];
|
|
17047
17266
|
throw new Error(`fetchPositions: ${url} returned ${res.status} ${res.statusText}`);
|
|
17048
17267
|
}
|
|
17049
|
-
|
|
17050
|
-
return parseNomenclature(await new Response(res.body.pipeThrough(ds)).arrayBuffer());
|
|
17268
|
+
return parseNomenclature(await gunzip(res).arrayBuffer());
|
|
17051
17269
|
}
|
|
17052
|
-
async function
|
|
17270
|
+
async function fetchLabelText(bodyId, lang) {
|
|
17053
17271
|
const url = versionedUrl(`/v1/nomenclature/labels/${lang}/${bodyId}.txt.gz`, "nomenclature");
|
|
17054
17272
|
const res = await fetch(url);
|
|
17055
17273
|
if (!res.ok) {
|
|
17056
|
-
if (res.status === 404) return
|
|
17274
|
+
if (res.status === 404) return "";
|
|
17057
17275
|
throw new Error(`fetchNomenclatureLabels: ${url} returned ${res.status} ${res.statusText}`);
|
|
17058
17276
|
}
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
17277
|
+
return gunzip(res).text();
|
|
17278
|
+
}
|
|
17279
|
+
function nameFeatures(records, labelText) {
|
|
17280
|
+
const labels = labelText === "" ? [] : labelText.split("\n");
|
|
17281
|
+
return records.map((rec, i) => ({
|
|
17282
|
+
...rec,
|
|
17283
|
+
name: labels[i] ?? `Feature ${rec.featureId}`
|
|
17284
|
+
}));
|
|
17062
17285
|
}
|
|
17063
17286
|
function fetchBodyNomenclature(bodyId, lang = getLocale()) {
|
|
17064
17287
|
const key = `${bodyId}:${lang}`;
|
|
17065
17288
|
let p = cache$1.get(key);
|
|
17066
17289
|
if (!p) {
|
|
17067
17290
|
p = (async () => {
|
|
17068
|
-
const [records, labels] = await Promise.all([fetchPositions(bodyId),
|
|
17069
|
-
return records
|
|
17070
|
-
...rec,
|
|
17071
|
-
name: labels[i] ?? `Feature ${rec.featureId}`
|
|
17072
|
-
}));
|
|
17291
|
+
const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabelText(bodyId, lang)]);
|
|
17292
|
+
return nameFeatures(records, labels);
|
|
17073
17293
|
})();
|
|
17074
17294
|
cache$1.set(key, p);
|
|
17075
17295
|
p.catch(() => {
|
|
@@ -20478,18 +20698,15 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
|
|
|
20478
20698
|
isClose = false;
|
|
20479
20699
|
}
|
|
20480
20700
|
if (trail && (!drawn || body.orbitCenter === null)) trail.visible = false;
|
|
20701
|
+
let starIsPoint = false;
|
|
20481
20702
|
if (bo.starPoint && drawn) {
|
|
20482
|
-
const
|
|
20483
|
-
|
|
20484
|
-
bo.starPoint.visible =
|
|
20485
|
-
if (
|
|
20486
|
-
|
|
20487
|
-
const t = screenR / STAR_POINT_HANDOFF_R;
|
|
20488
|
-
const intensity = Math.max(STAR_POINT_HANDOFF_INTENSITY * t * t, 3);
|
|
20489
|
-
material.uniforms.uIntensity.value = intensity;
|
|
20490
|
-
}
|
|
20703
|
+
const t = bo.radiusScene / dist * projScale / STAR_POINT_HANDOFF_R;
|
|
20704
|
+
starIsPoint = t < 1;
|
|
20705
|
+
bo.starPoint.visible = starIsPoint;
|
|
20706
|
+
if (starIsPoint) bo.starPoint.material.uniforms.uFlux.value = t * t;
|
|
20707
|
+
fadeStarCorona(bo.corona, t);
|
|
20491
20708
|
group.visible = true;
|
|
20492
|
-
isClose = !
|
|
20709
|
+
isClose = !starIsPoint;
|
|
20493
20710
|
showLabel = !isClose;
|
|
20494
20711
|
}
|
|
20495
20712
|
if (bo.isMinor && !isFocused && trail && !ctx.visibility.isPinned(body.data.id)) trail.visible = false;
|
|
@@ -20505,7 +20722,7 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
|
|
|
20505
20722
|
label.element.remove();
|
|
20506
20723
|
} else if (group.visible && label && label.parent !== group) group.add(label);
|
|
20507
20724
|
if (bo.mesh && bo.radiusScene > 0) {
|
|
20508
|
-
const subPixel = !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
|
|
20725
|
+
const subPixel = starIsPoint || !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
|
|
20509
20726
|
if (subPixel !== bo.meshSubPixel) {
|
|
20510
20727
|
bo.meshSubPixel = subPixel;
|
|
20511
20728
|
if (subPixel) bo.mesh.layers.disable(0);
|
|
@@ -23198,7 +23415,7 @@ var PromotionRegistry = class {
|
|
|
23198
23415
|
this.applyGroupFilter(deps.ctx.earthSatFilter);
|
|
23199
23416
|
deps.ctx.onSmallBodyFilterChange((f) => this.applySmallBodyFilter(f));
|
|
23200
23417
|
this.applySmallBodyFilter(deps.ctx.smallBodyFilter);
|
|
23201
|
-
fetchLabels
|
|
23418
|
+
fetchLabels().then((labels) => {
|
|
23202
23419
|
const alreadyLoaded = [];
|
|
23203
23420
|
const add = (id) => {
|
|
23204
23421
|
if (!this.defaults.add(id)) return;
|
|
@@ -23264,14 +23481,14 @@ var PromotionRegistry = class {
|
|
|
23264
23481
|
this.finalizeBuilds();
|
|
23265
23482
|
}
|
|
23266
23483
|
buildBodyInstance(body, halfPromoteIds, auto = false) {
|
|
23267
|
-
const { bodyObjects, ctx, clock, scene, clickables, meshToBody,
|
|
23484
|
+
const { bodyObjects, ctx, clock, scene, clickables, meshToBody, renderer } = this.deps;
|
|
23268
23485
|
if (bodyObjects.has(body.data.id)) return false;
|
|
23269
23486
|
if (!body.orbitElements && !body.data.pageOnly && body.data.orbitalSource !== OrbitalSource.SPICE_PROBE) {
|
|
23270
23487
|
body.orbitElements = body.data;
|
|
23271
23488
|
body.orbitCenterId = body.data.parentId;
|
|
23272
23489
|
}
|
|
23273
23490
|
ctx.place(body, clock.jd);
|
|
23274
|
-
buildMajorBodies([body], scene, clickables, meshToBody, bodyObjects,
|
|
23491
|
+
buildMajorBodies([body], scene, clickables, meshToBody, bodyObjects, renderer.domElement, this.deps.onBodyClick, (id, hovered) => hovered ? this.deps.hoveredBodyIds.add(id) : this.deps.hoveredBodyIds.delete(id), halfPromoteIds);
|
|
23275
23492
|
const bo = bodyObjects.get(body.data.id);
|
|
23276
23493
|
if (bo && !body.data.name) loadBodyLabel(bo);
|
|
23277
23494
|
if (body.data.objectType === ObjectType.SPACECRAFT) ctx.bodies.dirtySpacecraftGroups.add(body.data.parentId);
|
|
@@ -23476,7 +23693,7 @@ var FocusController = class {
|
|
|
23476
23693
|
_tmpV3 = new Vector3();
|
|
23477
23694
|
pendingInitialView = null;
|
|
23478
23695
|
upgradeTargetIds = /* @__PURE__ */ new Set();
|
|
23479
|
-
constructor(deps, initialFocus, hoveredBodyIds,
|
|
23696
|
+
constructor(deps, initialFocus, hoveredBodyIds, renderer) {
|
|
23480
23697
|
this.deps = deps;
|
|
23481
23698
|
this.focusedBody = initialFocus;
|
|
23482
23699
|
this.promotion = new PromotionRegistry({
|
|
@@ -23485,7 +23702,6 @@ var FocusController = class {
|
|
|
23485
23702
|
ctx: deps.ctx,
|
|
23486
23703
|
clock: deps.clock,
|
|
23487
23704
|
renderer,
|
|
23488
|
-
circleTexture,
|
|
23489
23705
|
clickables: deps.clickables,
|
|
23490
23706
|
meshToBody: deps.meshToBody,
|
|
23491
23707
|
hoveredBodyIds,
|
|
@@ -24346,7 +24562,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24346
24562
|
loadTexture: (b) => this.maybeLoadTexture(b),
|
|
24347
24563
|
repositionAll: () => this.repositionAll(),
|
|
24348
24564
|
assignMapLayerToTrails: () => this.assignMapLayerToTrails()
|
|
24349
|
-
}, focusBody, this.hoveredBodyIds, this.
|
|
24565
|
+
}, focusBody, this.hoveredBodyIds, this.renderer);
|
|
24350
24566
|
this.coverageWatch = new ProbeCoverageWatch(clock, callbacks.notices);
|
|
24351
24567
|
const framing = focusBody?.data.id === initialView.id ? initialView : {
|
|
24352
24568
|
latitude: 45,
|
|
@@ -24380,7 +24596,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24380
24596
|
this.tick();
|
|
24381
24597
|
}
|
|
24382
24598
|
buildScene() {
|
|
24383
|
-
buildMajorBodies(this.ctx.bodies.majorBodies, this.scene, this.clickables, this.meshToBody, this.bodyObjects, this.
|
|
24599
|
+
buildMajorBodies(this.ctx.bodies.majorBodies, this.scene, this.clickables, this.meshToBody, this.bodyObjects, this.renderer.domElement, (body) => this.focusController.handleFocus(body), (id, hovered) => {
|
|
24384
24600
|
if (hovered) this.hoveredBodyIds.add(id);
|
|
24385
24601
|
else this.hoveredBodyIds.delete(id);
|
|
24386
24602
|
this.invalidate();
|
|
@@ -25227,7 +25443,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
25227
25443
|
const systemId = this.ctx.visibility.focusedSystemId;
|
|
25228
25444
|
const seatKey = this.focusedSeatConfigKey();
|
|
25229
25445
|
const probeVersion = this.ctx.probeStore?.version ?? 0;
|
|
25230
|
-
const dataVersion = this.ctx.bodies.dataVersion + (this.ctx.chebStore?.version ?? 0);
|
|
25446
|
+
const dataVersion = this.ctx.bodies.dataVersion + (this.ctx.chebStore?.version ?? 0) + (this.ctx.refresher?.wakes ?? 0);
|
|
25231
25447
|
if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion && dataVersion === this.lastDataVersion) {
|
|
25232
25448
|
this.clock.seeked = false;
|
|
25233
25449
|
return;
|
|
@@ -28473,6 +28689,7 @@ var SpaceMap = class {
|
|
|
28473
28689
|
longitude: target.lon
|
|
28474
28690
|
})
|
|
28475
28691
|
});
|
|
28692
|
+
if (arrival.outcome === "failed") throw arrival.error;
|
|
28476
28693
|
return this.settled(arrival.flightMs);
|
|
28477
28694
|
}
|
|
28478
28695
|
async goTo(id, options = {}) {
|
|
@@ -28505,11 +28722,24 @@ var SpaceMap = class {
|
|
|
28505
28722
|
}
|
|
28506
28723
|
}
|
|
28507
28724
|
let renderer;
|
|
28725
|
+
let loadError;
|
|
28726
|
+
const note = (error) => {
|
|
28727
|
+
loadError ??= error;
|
|
28728
|
+
};
|
|
28729
|
+
const failed = () => {
|
|
28730
|
+
console.warn(`[map] goTo ${id}: its data did not load:`, loadError);
|
|
28731
|
+
return {
|
|
28732
|
+
outcome: "failed",
|
|
28733
|
+
error: loadError,
|
|
28734
|
+
clockMoved,
|
|
28735
|
+
flightMs: 0
|
|
28736
|
+
};
|
|
28737
|
+
};
|
|
28508
28738
|
try {
|
|
28509
28739
|
await this.open(id).catch(() => {});
|
|
28510
28740
|
const date = jdToDate(this.clock.jd);
|
|
28511
|
-
await this.ctx.ready(date);
|
|
28512
|
-
await this.ctx.ensureBody(id, date);
|
|
28741
|
+
await this.ctx.ready(date).catch(note);
|
|
28742
|
+
await this.ctx.ensureBody(id, date).catch(note);
|
|
28513
28743
|
renderer = await this.rendering();
|
|
28514
28744
|
} finally {
|
|
28515
28745
|
release();
|
|
@@ -28517,6 +28747,7 @@ var SpaceMap = class {
|
|
|
28517
28747
|
if (superseded()) return gone;
|
|
28518
28748
|
const body = this.ctx.getBody(id);
|
|
28519
28749
|
if (!body || !renderer) {
|
|
28750
|
+
if (loadError !== void 0) return failed();
|
|
28520
28751
|
console.warn(`[map] goTo ${id}: not an object of this map`);
|
|
28521
28752
|
return {
|
|
28522
28753
|
outcome: "missing",
|
|
@@ -28524,8 +28755,14 @@ var SpaceMap = class {
|
|
|
28524
28755
|
flightMs: 0
|
|
28525
28756
|
};
|
|
28526
28757
|
}
|
|
28758
|
+
const placed = renderer.placeOf(body) !== null;
|
|
28759
|
+
if (!placed && loadError !== void 0) {
|
|
28760
|
+
windows ??= await coverage();
|
|
28761
|
+
if (superseded()) return gone;
|
|
28762
|
+
if (nearestCoveredJd(windows, this.clock.jd) === this.clock.jd) return failed();
|
|
28763
|
+
}
|
|
28527
28764
|
options.commit?.(body);
|
|
28528
|
-
if (
|
|
28765
|
+
if (placed) {
|
|
28529
28766
|
const view = options.view?.(body) ?? {};
|
|
28530
28767
|
const distance = view.distance ?? (body.data.radiusKm > 0 ? this.framing({}, id) : distanceBefore);
|
|
28531
28768
|
let flightMs = 0;
|
|
@@ -32629,7 +32866,6 @@ var SystemMap = class {
|
|
|
32629
32866
|
|
|
32630
32867
|
//#endregion
|
|
32631
32868
|
//#region src/lib/scene/state/attribution.ts
|
|
32632
|
-
var DEFAULT_ORBIT_LABELS = Object.fromEntries(ORBIT_SOURCE_ORDER.map((s) => [s, ORBIT_SOURCES[s].label]));
|
|
32633
32869
|
function feedsFocusedSystem(ctx, source) {
|
|
32634
32870
|
const parents = ctx.credits.orbitSourceParents.get(source);
|
|
32635
32871
|
if (!parents) return true;
|
|
@@ -32637,22 +32873,28 @@ function feedsFocusedSystem(ctx, source) {
|
|
|
32637
32873
|
for (const parent of parents) if (ctx.bodies.isInSystem(parent, sysId)) return true;
|
|
32638
32874
|
return false;
|
|
32639
32875
|
}
|
|
32640
|
-
function
|
|
32641
|
-
|
|
32642
|
-
|
|
32643
|
-
|
|
32644
|
-
|
|
32876
|
+
function creditedOrbitArchives(ctx) {
|
|
32877
|
+
const credits = ctx.credits;
|
|
32878
|
+
const archives = /* @__PURE__ */ new Set();
|
|
32879
|
+
for (const source of ORBIT_SOURCE_ORDER) {
|
|
32880
|
+
if (!credits.orbitSources.has(source)) continue;
|
|
32881
|
+
const { archive, scoped } = ORBIT_SOURCES[source];
|
|
32882
|
+
if (scoped && !feedsFocusedSystem(ctx, source)) continue;
|
|
32883
|
+
if (archive) archives.add(archive);
|
|
32884
|
+
else for (const a of PROBE_ARCHIVES) if (a && credits.probeArchives.has(a)) archives.add(a);
|
|
32885
|
+
}
|
|
32886
|
+
return [...archives];
|
|
32645
32887
|
}
|
|
32646
32888
|
function scopedCredits(ctx, all) {
|
|
32647
32889
|
const systemId = ctx.visibility.focusedSystemId;
|
|
32648
32890
|
const bodyId = ctx.visibility.focusedBodyId;
|
|
32649
32891
|
return [...all].filter((c) => c.bodyId === bodyId || systemId && c.systemId === systemId);
|
|
32650
32892
|
}
|
|
32651
|
-
function attributionChips(ctx, labels =
|
|
32893
|
+
function attributionChips(ctx, labels = ORBIT_ARCHIVE_ORGANISATIONS) {
|
|
32652
32894
|
const credits = ctx.credits;
|
|
32653
32895
|
credits.imageryVersion;
|
|
32654
32896
|
credits.modelVersion;
|
|
32655
|
-
const orbits = [...new Set(
|
|
32897
|
+
const orbits = [...new Set(creditedOrbitArchives(ctx).map((archive) => labels[archive]))];
|
|
32656
32898
|
const organisations = /* @__PURE__ */ new Set();
|
|
32657
32899
|
for (const credit of scopedCredits(ctx, credits.imagery.values())) organisations.add(credit.organisation);
|
|
32658
32900
|
const bodyId = ctx.visibility.focusedBodyId;
|
|
@@ -32901,12 +33143,7 @@ async function open(date) {
|
|
|
32901
33143
|
const params = chebyshevZoneParams(metadata);
|
|
32902
33144
|
if (params.size > 0) chebyshev = new ChebyshevStore(params);
|
|
32903
33145
|
}
|
|
32904
|
-
|
|
32905
|
-
await chebyshev?.ensure(jd).done;
|
|
32906
|
-
} catch (error) {
|
|
32907
|
-
chebyshev = null;
|
|
32908
|
-
throw error;
|
|
32909
|
-
}
|
|
33146
|
+
await chebyshev?.load(jd);
|
|
32910
33147
|
return {
|
|
32911
33148
|
jd,
|
|
32912
33149
|
metadata,
|