spacemap 0.1.13 → 0.1.14
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 +4 -4
- package/dist/spacemap.iife.js +607 -232
- package/dist/spacemap.js +607 -232
- package/index.d.ts +42 -13
- package/index.js +2135 -568
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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,
|
|
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";
|
|
3
3
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
4
4
|
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
5
5
|
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
|
@@ -2960,6 +2960,12 @@ function get_custom_elements_slots(element) {
|
|
|
2960
2960
|
return result;
|
|
2961
2961
|
}
|
|
2962
2962
|
|
|
2963
|
+
//#endregion
|
|
2964
|
+
//#region src/lib/fetch/tile-path.ts
|
|
2965
|
+
function tilePath(cap, level, x, y) {
|
|
2966
|
+
return `${cap && `${cap}/`}${level}/${x}/${y}.webp`;
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2963
2969
|
//#endregion
|
|
2964
2970
|
//#region src/lib/fetch/data-base.ts
|
|
2965
2971
|
function dataBase() {
|
|
@@ -2968,10 +2974,12 @@ function dataBase() {
|
|
|
2968
2974
|
function tilesBase() {
|
|
2969
2975
|
return host().tilesUrl;
|
|
2970
2976
|
}
|
|
2977
|
+
function tileRoot(tiles) {
|
|
2978
|
+
return `${tilesBase()}/v1/tiles/${tiles.id}/`;
|
|
2979
|
+
}
|
|
2971
2980
|
function tileUrl(tiles, cap, level, x, y, frame) {
|
|
2972
2981
|
const month = frame === void 0 ? "" : `${String(frame).padStart(2, "0")}/`;
|
|
2973
|
-
|
|
2974
|
-
return `${tilesBase()}/v1/tiles/${tiles.id}/${month}${chart}${level}/${x}/${y}.webp?v=${tiles.version}`;
|
|
2982
|
+
return `${tileRoot(tiles)}${month}${tilePath(cap, level, x, y)}?v=${tiles.version}`;
|
|
2975
2983
|
}
|
|
2976
2984
|
var versions = {};
|
|
2977
2985
|
function setDataVersions(v) {
|
|
@@ -3240,6 +3248,11 @@ function tileCredit(map) {
|
|
|
3240
3248
|
if (!tiles || tilesBase() === "" || !textureAllowed(tiles.distribution)) return void 0;
|
|
3241
3249
|
return tiles.source === map.source && tiles.attribution === map.attribution ? void 0 : tiles;
|
|
3242
3250
|
}
|
|
3251
|
+
function patchCredits(map) {
|
|
3252
|
+
const tiles = map.tiles;
|
|
3253
|
+
if (!tiles?.patches || tilesBase() === "" || !textureAllowed(tiles.distribution)) return [];
|
|
3254
|
+
return [...new Map(tiles.patches.map((patch) => [patch.source, patch])).values()];
|
|
3255
|
+
}
|
|
3243
3256
|
|
|
3244
3257
|
//#endregion
|
|
3245
3258
|
//#region src/lib/scene/state/credits.svelte.ts
|
|
@@ -3298,8 +3311,9 @@ var CreditsStore = class {
|
|
|
3298
3311
|
}
|
|
3299
3312
|
registerImagery(layer, bodyId, systemId, meta) {
|
|
3300
3313
|
this.addImagery(layer, bodyId, systemId, meta, layer === "rings");
|
|
3301
|
-
|
|
3302
|
-
|
|
3314
|
+
if (layer !== "surface" && layer !== "topography") return;
|
|
3315
|
+
const tiles = tileCredit(meta);
|
|
3316
|
+
for (const work of [...tiles ? [tiles] : [], ...patchCredits(meta)]) this.addImagery(layer, bodyId, systemId, work, true);
|
|
3303
3317
|
}
|
|
3304
3318
|
addImagery(layer, bodyId, systemId, meta, bySource) {
|
|
3305
3319
|
const key = [
|
|
@@ -6700,18 +6714,18 @@ function smallBodyFiltersEqual(a, b) {
|
|
|
6700
6714
|
if (a.kind === "flag" && b.kind === "flag") return a.flag === b.flag;
|
|
6701
6715
|
return false;
|
|
6702
6716
|
}
|
|
6703
|
-
var pending$
|
|
6717
|
+
var pending$3 = null;
|
|
6704
6718
|
function fetchGroupIndex() {
|
|
6705
|
-
if (pending$
|
|
6719
|
+
if (pending$3) return pending$3;
|
|
6706
6720
|
const p = fetchWithTimeout(`${dataBase()}/v1/groups/__index__.json`).then((r) => {
|
|
6707
6721
|
if (!r.ok) throw new Error(`Failed to fetch group index: ${r.status}`);
|
|
6708
6722
|
return r.json();
|
|
6709
6723
|
});
|
|
6710
|
-
pending$
|
|
6724
|
+
pending$3 = p;
|
|
6711
6725
|
p.catch(() => {
|
|
6712
|
-
if (pending$
|
|
6726
|
+
if (pending$3 === p) pending$3 = null;
|
|
6713
6727
|
});
|
|
6714
|
-
return pending$
|
|
6728
|
+
return pending$3;
|
|
6715
6729
|
}
|
|
6716
6730
|
|
|
6717
6731
|
//#endregion
|
|
@@ -8912,9 +8926,9 @@ function partsForDate(zoom, isoDate, cap = 0) {
|
|
|
8912
8926
|
const parts = zoom.parts_by_date[isoDate] ?? zoom.parts;
|
|
8913
8927
|
return cap > 0 ? Math.min(parts, cap) : parts;
|
|
8914
8928
|
}
|
|
8915
|
-
var pending$
|
|
8929
|
+
var pending$2 = null;
|
|
8916
8930
|
function fetchMetadata() {
|
|
8917
|
-
if (pending$
|
|
8931
|
+
if (pending$2) return pending$2;
|
|
8918
8932
|
const p = fetchWithTimeout(`${dataBase()}/v1/metadata.json`).then((r) => {
|
|
8919
8933
|
if (!r.ok) throw new Error(`Failed to fetch metadata: ${r.status}`);
|
|
8920
8934
|
return r.json();
|
|
@@ -8922,11 +8936,11 @@ function fetchMetadata() {
|
|
|
8922
8936
|
setDataVersions(meta.versions);
|
|
8923
8937
|
return meta;
|
|
8924
8938
|
});
|
|
8925
|
-
pending$
|
|
8939
|
+
pending$2 = p;
|
|
8926
8940
|
p.catch(() => {
|
|
8927
|
-
if (pending$
|
|
8941
|
+
if (pending$2 === p) pending$2 = null;
|
|
8928
8942
|
});
|
|
8929
|
-
return pending$
|
|
8943
|
+
return pending$2;
|
|
8930
8944
|
}
|
|
8931
8945
|
function chebyshevZoneParams(meta) {
|
|
8932
8946
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -9088,7 +9102,8 @@ var ChunkWindow = class {
|
|
|
9088
9102
|
request(jd) {
|
|
9089
9103
|
for (const [zone, params] of this.zoneParams) {
|
|
9090
9104
|
const idx = chunkIndexForJd(params, jd);
|
|
9091
|
-
if (this.isLoadable(params, idx))
|
|
9105
|
+
if (!this.isLoadable(params, idx)) continue;
|
|
9106
|
+
this.loadChunk(zone, params, idx, "high", true)?.catch(() => {});
|
|
9092
9107
|
}
|
|
9093
9108
|
}
|
|
9094
9109
|
loadingAt(jd, zone) {
|
|
@@ -9099,6 +9114,14 @@ var ChunkWindow = class {
|
|
|
9099
9114
|
}
|
|
9100
9115
|
return false;
|
|
9101
9116
|
}
|
|
9117
|
+
awaitedAt(jd, zone) {
|
|
9118
|
+
for (const [name, params] of this.zoneParams) {
|
|
9119
|
+
if (zone !== void 0 && name !== zone) continue;
|
|
9120
|
+
const key = chunkKey(name, chunkIndexForJd(params, jd));
|
|
9121
|
+
if (this.inflight.has(key) || this.retries.waiting(key)) return true;
|
|
9122
|
+
}
|
|
9123
|
+
return false;
|
|
9124
|
+
}
|
|
9102
9125
|
isResident(zone, chunkIdx) {
|
|
9103
9126
|
return this.chunks.get(zone)?.has(chunkIdx) ?? false;
|
|
9104
9127
|
}
|
|
@@ -9122,7 +9145,10 @@ var ChunkWindow = class {
|
|
|
9122
9145
|
if (ok) this.retries.clear(key);
|
|
9123
9146
|
else this.retries.fail(key);
|
|
9124
9147
|
};
|
|
9125
|
-
job.then(() => settle(true), () =>
|
|
9148
|
+
job.then(() => settle(true), (error) => {
|
|
9149
|
+
console.warn(`[position] chunk ${key} did not load:`, error);
|
|
9150
|
+
settle(false);
|
|
9151
|
+
});
|
|
9126
9152
|
return job;
|
|
9127
9153
|
}
|
|
9128
9154
|
async fetchAndStore(zone, params, chunkIdx, priority) {
|
|
@@ -9161,9 +9187,9 @@ var ChebyshevStore = class extends ChunkWindow {
|
|
|
9161
9187
|
end: params.end_jd
|
|
9162
9188
|
};
|
|
9163
9189
|
}
|
|
9164
|
-
|
|
9190
|
+
isAwaited(objectId, jd) {
|
|
9165
9191
|
const zone = this.idToZone.get(objectId);
|
|
9166
|
-
return zone !== void 0 && this.
|
|
9192
|
+
return zone !== void 0 && this.awaitedAt(jd, zone);
|
|
9167
9193
|
}
|
|
9168
9194
|
resolve(objectId, jd) {
|
|
9169
9195
|
const zone = this.idToZone.get(objectId);
|
|
@@ -9548,6 +9574,7 @@ var GROUP_BUNDLES = {
|
|
|
9548
9574
|
localizedPath: (lang, b) => `/v1/groups/${lang}/${b}.json.gz`
|
|
9549
9575
|
};
|
|
9550
9576
|
var clientFetcher = (path, versionClass) => fetchGzipBundle(versionClass ? versionedUrl(path, versionClass) : `${dataBase()}${path}`);
|
|
9577
|
+
var prefetchFetcher = (path, versionClass) => fetchGzipBundle(versionClass ? versionedUrl(path, versionClass) : `${dataBase()}${path}`, { priority: "low" });
|
|
9551
9578
|
async function loadBundlePair(desc, key, { meta, lang, fetchBundle, localized = true }) {
|
|
9552
9579
|
const counts = meta[desc.counts];
|
|
9553
9580
|
const nGlobal = counts?.global ?? 0;
|
|
@@ -9559,22 +9586,22 @@ async function loadBundlePair(desc, key, { meta, lang, fetchBundle, localized =
|
|
|
9559
9586
|
localized: localizedBundle?.[key] ?? null
|
|
9560
9587
|
};
|
|
9561
9588
|
}
|
|
9562
|
-
async function fetchBundlePair(desc, key, lang, localized = true) {
|
|
9589
|
+
async function fetchBundlePair(desc, key, lang, localized = true, background = false) {
|
|
9563
9590
|
return loadBundlePair(desc, key, {
|
|
9564
9591
|
meta: await fetchMetadata(),
|
|
9565
9592
|
lang,
|
|
9566
9593
|
localized,
|
|
9567
|
-
fetchBundle: clientFetcher
|
|
9594
|
+
fetchBundle: background ? prefetchFetcher : clientFetcher
|
|
9568
9595
|
});
|
|
9569
9596
|
}
|
|
9570
9597
|
|
|
9571
9598
|
//#endregion
|
|
9572
9599
|
//#region src/lib/fetch/objects/object-data.ts
|
|
9573
9600
|
function isViewable(entry) {
|
|
9574
|
-
return
|
|
9601
|
+
return textureAllowed(entry.distribution);
|
|
9575
9602
|
}
|
|
9576
|
-
function fetchObjectDetail(fileId, fetchLocalized = true, lang = getLocale()) {
|
|
9577
|
-
return fetchBundlePair(OBJECT_BUNDLES, fileId, lang, fetchLocalized);
|
|
9603
|
+
function fetchObjectDetail(fileId, fetchLocalized = true, lang = getLocale(), background = false) {
|
|
9604
|
+
return fetchBundlePair(OBJECT_BUNDLES, fileId, lang, fetchLocalized, background);
|
|
9578
9605
|
}
|
|
9579
9606
|
|
|
9580
9607
|
//#endregion
|
|
@@ -9937,6 +9964,7 @@ var ZoneRefresher = class {
|
|
|
9937
9964
|
if (z.zone !== zoneName || z.kind !== "time") continue;
|
|
9938
9965
|
z.currentTime = "";
|
|
9939
9966
|
z.lastLoadStartMs = -Infinity;
|
|
9967
|
+
this.retries.clear(loadKey(z, snapshotDate(z.zoomData, this.latestDate)));
|
|
9940
9968
|
}
|
|
9941
9969
|
this.tick(this.latestDate);
|
|
9942
9970
|
}
|
|
@@ -10006,7 +10034,11 @@ var ZoneRefresher = class {
|
|
|
10006
10034
|
this.wanted.add(id);
|
|
10007
10035
|
}
|
|
10008
10036
|
async ensureBody(targetId, date) {
|
|
10009
|
-
|
|
10037
|
+
const anchorFailure = targetId.startsWith("probe-") ? await this.ensureProbe(targetId, date) : null;
|
|
10038
|
+
await this.ensureListed(targetId, date);
|
|
10039
|
+
if (anchorFailure) throw anchorFailure.error;
|
|
10040
|
+
}
|
|
10041
|
+
async ensureListed(targetId, date) {
|
|
10010
10042
|
if (this.ctx.getBody(targetId)) return;
|
|
10011
10043
|
if (targetId.startsWith("norad_satcat-") && !this.wanted.has(targetId)) {
|
|
10012
10044
|
this.want(targetId);
|
|
@@ -10020,20 +10052,22 @@ var ZoneRefresher = class {
|
|
|
10020
10052
|
}
|
|
10021
10053
|
async ensureProbe(targetId, date) {
|
|
10022
10054
|
const store = this.ctx.probeStore;
|
|
10023
|
-
if (!store) return;
|
|
10055
|
+
if (!store) return null;
|
|
10024
10056
|
const jd = dateToJD(date);
|
|
10025
10057
|
const passengerPromise = passengerFor(targetId);
|
|
10026
10058
|
await store.load(jd).catch(() => {});
|
|
10027
10059
|
const passenger = await passengerPromise;
|
|
10028
10060
|
if (passenger) store.registerCarried(passenger);
|
|
10029
10061
|
const fcId = store.stampedFitCenterAt(targetId, jd);
|
|
10030
|
-
|
|
10031
|
-
if (this.ctx.getBody(targetId)) return;
|
|
10062
|
+
const anchorFailure = fcId ? await ensureTargetStreamed(this.ctx, fcId).then(() => null, (error) => ({ error })) : null;
|
|
10063
|
+
if (this.ctx.getBody(targetId)) return anchorFailure;
|
|
10032
10064
|
const labels = await fetchLabels();
|
|
10033
10065
|
const target = this.loader.processProbes(store, date, labels).find((b) => b.data.id === targetId);
|
|
10034
|
-
if (
|
|
10035
|
-
|
|
10036
|
-
|
|
10066
|
+
if (target) {
|
|
10067
|
+
this.ctx.bodies.addBodies([target]);
|
|
10068
|
+
this.ctx.credits.recordOrbitSources([target]);
|
|
10069
|
+
}
|
|
10070
|
+
return anchorFailure;
|
|
10037
10071
|
}
|
|
10038
10072
|
async loadTime(z, time, date) {
|
|
10039
10073
|
const fromTime = z.currentTime;
|
|
@@ -10212,6 +10246,9 @@ var RENDER_TIER_PRESETS = {
|
|
|
10212
10246
|
pointBudget: 4e5,
|
|
10213
10247
|
maxTextureTier: "medium",
|
|
10214
10248
|
tileWindows: 3,
|
|
10249
|
+
reliefWindows: 2,
|
|
10250
|
+
reliefShadowSteps: 12,
|
|
10251
|
+
terrainBandCells: 16,
|
|
10215
10252
|
maxSkyboxFace: 2048
|
|
10216
10253
|
},
|
|
10217
10254
|
medium: {
|
|
@@ -10222,6 +10259,9 @@ var RENDER_TIER_PRESETS = {
|
|
|
10222
10259
|
pointBudget: 9e5,
|
|
10223
10260
|
maxTextureTier: "medium",
|
|
10224
10261
|
tileWindows: 4,
|
|
10262
|
+
reliefWindows: 3,
|
|
10263
|
+
reliefShadowSteps: 16,
|
|
10264
|
+
terrainBandCells: 24,
|
|
10225
10265
|
maxSkyboxFace: 2048
|
|
10226
10266
|
},
|
|
10227
10267
|
high: {
|
|
@@ -10232,6 +10272,9 @@ var RENDER_TIER_PRESETS = {
|
|
|
10232
10272
|
pointBudget: Infinity,
|
|
10233
10273
|
maxTextureTier: "high",
|
|
10234
10274
|
tileWindows: 6,
|
|
10275
|
+
reliefWindows: 4,
|
|
10276
|
+
reliefShadowSteps: 32,
|
|
10277
|
+
terrainBandCells: 32,
|
|
10235
10278
|
maxSkyboxFace: Infinity
|
|
10236
10279
|
}
|
|
10237
10280
|
};
|
|
@@ -10243,6 +10286,13 @@ function maxTileWindows() {
|
|
|
10243
10286
|
if (isLowEndDevice()) return 0;
|
|
10244
10287
|
return currentRenderTier().tileWindows;
|
|
10245
10288
|
}
|
|
10289
|
+
function maxReliefWindows() {
|
|
10290
|
+
if (isLowEndDevice()) return 0;
|
|
10291
|
+
return currentRenderTier().reliefWindows;
|
|
10292
|
+
}
|
|
10293
|
+
function reliefShadowSteps() {
|
|
10294
|
+
return currentRenderTier().reliefShadowSteps;
|
|
10295
|
+
}
|
|
10246
10296
|
function resolveRenderTier() {
|
|
10247
10297
|
const measured = sceneSettings().atmosphereCalibration?.tier;
|
|
10248
10298
|
if (measured === "low" || measured === "medium") return measured;
|
|
@@ -10955,7 +11005,7 @@ var SimClock = class {
|
|
|
10955
11005
|
//#endregion
|
|
10956
11006
|
//#region src/lib/math/spherical.ts
|
|
10957
11007
|
var { PI, sin, cos, asin, atan2, sqrt } = Math;
|
|
10958
|
-
var DEG$
|
|
11008
|
+
var DEG$9 = 180 / PI;
|
|
10959
11009
|
var RAD$5 = PI / 180;
|
|
10960
11010
|
function applyQuat(v, q, inverse) {
|
|
10961
11011
|
const [x, y, z] = v;
|
|
@@ -10986,8 +11036,8 @@ function cartesianToSpherical(cam, target, bodyQuat) {
|
|
|
10986
11036
|
dz
|
|
10987
11037
|
], bodyQuat, true);
|
|
10988
11038
|
return {
|
|
10989
|
-
latitude: asin(dy / distance) * DEG$
|
|
10990
|
-
longitude: bodyQuat ? atan2(-dz, dx) * DEG$
|
|
11039
|
+
latitude: asin(dy / distance) * DEG$9,
|
|
11040
|
+
longitude: bodyQuat ? atan2(-dz, dx) * DEG$9 : atan2(dx, dz) * DEG$9,
|
|
10991
11041
|
distance
|
|
10992
11042
|
};
|
|
10993
11043
|
}
|
|
@@ -11945,7 +11995,7 @@ function tagShaderModifier(material, mod) {
|
|
|
11945
11995
|
const mods = tagged.userData.shaderMods ??= [];
|
|
11946
11996
|
if (mods.includes(mod)) return;
|
|
11947
11997
|
mods.push(mod);
|
|
11948
|
-
material.customProgramCacheKey = () => mods.join("|");
|
|
11998
|
+
material.customProgramCacheKey = () => mods.map((name) => typeof name === "string" ? name : name()).join("|");
|
|
11949
11999
|
}
|
|
11950
12000
|
function chainShaderHook(material, mod, patch) {
|
|
11951
12001
|
const prev = material.onBeforeCompile;
|
|
@@ -15551,159 +15601,6 @@ function setStarLimbAlpha(material, alpha) {
|
|
|
15551
15601
|
if (alpha && uniforms?.uLimbAlpha) uniforms.uLimbAlpha.value.set(alpha[0], alpha[1], alpha[2]);
|
|
15552
15602
|
}
|
|
15553
15603
|
|
|
15554
|
-
//#endregion
|
|
15555
|
-
//#region src/lib/scene/objects/surface/self-shadow.ts
|
|
15556
|
-
var STEPS = 32;
|
|
15557
|
-
var NORMAL_STRENGTH = 1;
|
|
15558
|
-
var GRADIENT_STENCIL = 2;
|
|
15559
|
-
var SLOPE_CLAMP = 1.5;
|
|
15560
|
-
var SHADOW_REACH = 4;
|
|
15561
|
-
var SHADOW_SOFT = .04;
|
|
15562
|
-
var TERMINATOR_GATE_LIT = .02;
|
|
15563
|
-
var TERMINATOR_GATE_DARK = -.08;
|
|
15564
|
-
var MARCH_GRAZE_FADE = .18;
|
|
15565
|
-
function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir) {
|
|
15566
|
-
const tex = heightMap.image;
|
|
15567
|
-
const texel = new Vector2(1 / (tex?.width ?? 4096), 1 / (tex?.height ?? 2048));
|
|
15568
|
-
const existing = material.userData.selfShadow;
|
|
15569
|
-
if (existing) {
|
|
15570
|
-
existing.uSelfHeightMap.value = heightMap;
|
|
15571
|
-
existing.uSelfScale.value = scaleScene;
|
|
15572
|
-
existing.uSelfTexel.value.copy(texel);
|
|
15573
|
-
return existing;
|
|
15574
|
-
}
|
|
15575
|
-
const uniforms = {
|
|
15576
|
-
uSelfHeightMap: { value: heightMap },
|
|
15577
|
-
uSelfScale: { value: scaleScene },
|
|
15578
|
-
uSelfTexel: { value: texel }
|
|
15579
|
-
};
|
|
15580
|
-
material.userData.selfShadow = uniforms;
|
|
15581
|
-
const sun = sunDir ?? getEclipseSceneUniforms().uSunDir;
|
|
15582
|
-
chainShaderHook(material, "selfShadow", (shader) => {
|
|
15583
|
-
Object.assign(shader.uniforms, uniforms, { uSelfSunDir: sun });
|
|
15584
|
-
shader.vertexShader = shader.vertexShader.replace("#include <common>", `#include <common>
|
|
15585
|
-
varying vec3 vSelfWorldNormal;
|
|
15586
|
-
varying vec2 vSelfUv;
|
|
15587
|
-
varying vec3 vSelfEastW;
|
|
15588
|
-
varying vec3 vSelfNorthW;
|
|
15589
|
-
varying float vSelfRadiusW;
|
|
15590
|
-
varying float vSelfCosLat;`).replace("#include <beginnormal_vertex>", `#include <beginnormal_vertex>
|
|
15591
|
-
vSelfWorldNormal = normalize(mat3(modelMatrix) * objectNormal);
|
|
15592
|
-
vSelfUv = uv;
|
|
15593
|
-
// Analytic equirect tangent basis: smooth per fragment, so the
|
|
15594
|
-
// relief normal doesn't inherit mesh facets.
|
|
15595
|
-
vec3 nObj = normalize(position);
|
|
15596
|
-
vec3 ec = cross(vec3(0.0, 1.0, 0.0), nObj);
|
|
15597
|
-
float cl = length(ec);
|
|
15598
|
-
vec3 eastObj = cl > 1e-5 ? ec / cl : vec3(1.0, 0.0, 0.0);
|
|
15599
|
-
vSelfEastW = normalize(mat3(modelMatrix) * eastObj);
|
|
15600
|
-
vSelfNorthW = normalize(mat3(modelMatrix) * cross(nObj, eastObj));
|
|
15601
|
-
vSelfRadiusW = length(mat3(modelMatrix) * position);
|
|
15602
|
-
vSelfCosLat = cl;`);
|
|
15603
|
-
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
15604
|
-
#define SELF_STEPS ${STEPS}
|
|
15605
|
-
uniform sampler2D uSelfHeightMap;
|
|
15606
|
-
uniform float uSelfScale;
|
|
15607
|
-
uniform vec2 uSelfTexel;
|
|
15608
|
-
uniform vec3 uSelfSunDir;
|
|
15609
|
-
varying vec3 vSelfWorldNormal;
|
|
15610
|
-
varying vec2 vSelfUv;
|
|
15611
|
-
varying vec3 vSelfEastW;
|
|
15612
|
-
varying vec3 vSelfNorthW;
|
|
15613
|
-
varying float vSelfRadiusW;
|
|
15614
|
-
varying float vSelfCosLat;
|
|
15615
|
-
|
|
15616
|
-
float selfHeight(vec2 uv) {
|
|
15617
|
-
return texture2D(uSelfHeightMap, uv).r * uSelfScale;
|
|
15618
|
-
}
|
|
15619
|
-
|
|
15620
|
-
// Analytic equirect frame (Tu=∂pos/∂u east, Tv=∂pos/∂v south). Screen-
|
|
15621
|
-
// derivative tangents were piecewise-constant per triangle, tiling the
|
|
15622
|
-
// relief normal with mesh facets. |Tu|=2πR·cos(lat), |Tv|=πR is the true metric.
|
|
15623
|
-
void selfTangents(out vec3 Tu, out vec3 Tv) {
|
|
15624
|
-
float R = vSelfRadiusW;
|
|
15625
|
-
Tu = vSelfEastW * (6.2831853 * R * vSelfCosLat);
|
|
15626
|
-
Tv = -vSelfNorthW * (3.14159265 * R);
|
|
15627
|
-
}`).replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
|
|
15628
|
-
{
|
|
15629
|
-
vec3 Tu, Tv;
|
|
15630
|
-
selfTangents(Tu, Tv);
|
|
15631
|
-
float lenTu = length(Tu);
|
|
15632
|
-
float lenTv = length(Tv);
|
|
15633
|
-
vec3 Nw = normalize(vSelfWorldNormal);
|
|
15634
|
-
if (uSelfScale > 0.0 && lenTu > 1e-12 && lenTv > 1e-12) {
|
|
15635
|
-
// Wider-than-one-texel stencil averages out 8-bit terracing, floored
|
|
15636
|
-
// at the low tier's texel so a higher-res map keeps the same
|
|
15637
|
-
// physical smoothing width instead of flattening the relief.
|
|
15638
|
-
vec2 d = max(uSelfTexel, vec2(1.0 / 2048.0, 1.0 / 1024.0)) * ${GRADIENT_STENCIL.toFixed(3)};
|
|
15639
|
-
float hu = selfHeight(vSelfUv + vec2(d.x, 0.0))
|
|
15640
|
-
- selfHeight(vSelfUv - vec2(d.x, 0.0));
|
|
15641
|
-
float hv = selfHeight(vSelfUv + vec2(0.0, d.y))
|
|
15642
|
-
- selfHeight(vSelfUv - vec2(0.0, d.y));
|
|
15643
|
-
// Slope = Δheight / Δworld-distance along each axis.
|
|
15644
|
-
float su = ${NORMAL_STRENGTH.toFixed(3)} * hu / (2.0 * d.x * lenTu);
|
|
15645
|
-
float sv = ${NORMAL_STRENGTH.toFixed(3)} * hv / (2.0 * d.y * lenTv);
|
|
15646
|
-
// |Tu|/|Tv|=2·cos(lat): fade relief at the poles, where |Tu|→0 blows up east-west slope into a starburst.
|
|
15647
|
-
float poleFade = smoothstep(0.0, 0.12, lenTu / (2.0 * lenTv));
|
|
15648
|
-
su = clamp(su * poleFade, -${SLOPE_CLAMP.toFixed(3)}, ${SLOPE_CLAMP.toFixed(3)});
|
|
15649
|
-
sv = clamp(sv * poleFade, -${SLOPE_CLAMP.toFixed(3)}, ${SLOPE_CLAMP.toFixed(3)});
|
|
15650
|
-
vec3 reliefN = cross(Tu / lenTu + Nw * su, Tv / lenTv + Nw * sv);
|
|
15651
|
-
float rl = length(reliefN);
|
|
15652
|
-
if (rl > 1e-12) {
|
|
15653
|
-
reliefN /= rl;
|
|
15654
|
-
if (dot(reliefN, Nw) < 0.0) reliefN = -reliefN;
|
|
15655
|
-
normal = normalize((viewMatrix * vec4(reliefN, 0.0)).xyz);
|
|
15656
|
-
}
|
|
15657
|
-
}
|
|
15658
|
-
}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>
|
|
15659
|
-
{
|
|
15660
|
-
vec3 Nw = normalize(vSelfWorldNormal);
|
|
15661
|
-
vec3 L = normalize(uSelfSunDir);
|
|
15662
|
-
float gN = dot(L, Nw);
|
|
15663
|
-
// Gate on the GEOMETRIC normal: the relief normal can tilt sunward
|
|
15664
|
-
// past the terminator and light the night side. Fading direct light
|
|
15665
|
-
// by geometric N·L keeps the night dark regardless.
|
|
15666
|
-
float shadow = smoothstep(${TERMINATOR_GATE_DARK.toFixed(3)}, ${TERMINATOR_GATE_LIT.toFixed(3)}, gN);
|
|
15667
|
-
vec3 Tu, Tv;
|
|
15668
|
-
selfTangents(Tu, Tv);
|
|
15669
|
-
float lenTu = length(Tu);
|
|
15670
|
-
float lenTv = length(Tv);
|
|
15671
|
-
// Local crater self-shadow, lit hemisphere only — the macro gate owns
|
|
15672
|
-
// the night side; the short march can't see that curvature.
|
|
15673
|
-
if (shadow > 0.0 && uSelfScale > 0.0 && lenTu > 1e-12 && lenTv > 1e-12 && gN > 0.0) {
|
|
15674
|
-
float lu = dot(L, Tu / lenTu);
|
|
15675
|
-
float lv = dot(L, Tv / lenTv);
|
|
15676
|
-
float lh = sqrt(lu * lu + lv * lv);
|
|
15677
|
-
if (lh > 1e-4) {
|
|
15678
|
-
float ds = ${SHADOW_REACH.toFixed(3)} * uSelfScale / float(SELF_STEPS);
|
|
15679
|
-
// World step → uv step via the per-axis metric (|Tu|, |Tv|).
|
|
15680
|
-
vec2 stepUv = ds * vec2((lu / lh) / lenTu, (lv / lh) / lenTv);
|
|
15681
|
-
float h0 = selfHeight(vSelfUv);
|
|
15682
|
-
float tanElev = gN / lh;
|
|
15683
|
-
float block = 0.0;
|
|
15684
|
-
for (int i = 1; i <= SELF_STEPS; i++) {
|
|
15685
|
-
vec2 uv = vSelfUv + float(i) * stepUv;
|
|
15686
|
-
float rayH = h0 + float(i) * ds * tanElev;
|
|
15687
|
-
block = max(block, selfHeight(uv) - rayH);
|
|
15688
|
-
}
|
|
15689
|
-
// Fade the march out at grazing angles: a near-horizontal ray
|
|
15690
|
-
// occludes every away-facing slope, banding the terminator.
|
|
15691
|
-
float marchFade = smoothstep(0.0, ${MARCH_GRAZE_FADE.toFixed(3)}, gN);
|
|
15692
|
-
shadow *= 1.0 - marchFade * clamp(block / (${SHADOW_SOFT.toFixed(3)} * uSelfScale), 0.0, 1.0);
|
|
15693
|
-
}
|
|
15694
|
-
}
|
|
15695
|
-
reflectedLight.directDiffuse *= shadow;
|
|
15696
|
-
reflectedLight.directSpecular *= shadow;
|
|
15697
|
-
}`);
|
|
15698
|
-
});
|
|
15699
|
-
return uniforms;
|
|
15700
|
-
}
|
|
15701
|
-
function detachSelfShadow(uniforms) {
|
|
15702
|
-
if (!uniforms) return;
|
|
15703
|
-
uniforms.uSelfHeightMap.value = null;
|
|
15704
|
-
uniforms.uSelfScale.value = 0;
|
|
15705
|
-
}
|
|
15706
|
-
|
|
15707
15604
|
//#endregion
|
|
15708
15605
|
//#region src/lib/scene/lod/tile-window.ts
|
|
15709
15606
|
var WINDOW_TILES = 4;
|
|
@@ -15717,7 +15614,7 @@ var CHART_FOLDER = [
|
|
|
15717
15614
|
];
|
|
15718
15615
|
var FIRST_TILE_LEVEL = 3;
|
|
15719
15616
|
var HOLD_TILES = .75;
|
|
15720
|
-
var BASE_WIDTH_PX = 2048;
|
|
15617
|
+
var BASE_WIDTH_PX$1 = 2048;
|
|
15721
15618
|
var CAP_EDGE = Math.SQRT2 - 1;
|
|
15722
15619
|
var CAP_ENTER_DEG = 62;
|
|
15723
15620
|
var CAP_LEAVE_DEG = 58;
|
|
@@ -15731,7 +15628,7 @@ function chartRows(chart, level) {
|
|
|
15731
15628
|
function neededLevel(radiusScene, dist, projScale, maxLevel) {
|
|
15732
15629
|
const height = Math.max(dist - radiusScene, radiusScene * 1e-6);
|
|
15733
15630
|
const widthPx = 2 * Math.PI * radiusScene * projScale / height;
|
|
15734
|
-
return Math.min(Math.ceil(Math.log2(widthPx / BASE_WIDTH_PX)), maxLevel);
|
|
15631
|
+
return Math.min(Math.ceil(Math.log2(widthPx / BASE_WIDTH_PX$1)), maxLevel);
|
|
15735
15632
|
}
|
|
15736
15633
|
function chartPoint(chart, x, y, z) {
|
|
15737
15634
|
if (chart === 0) {
|
|
@@ -15752,12 +15649,17 @@ function cameraChart(y, previous) {
|
|
|
15752
15649
|
const cap = y >= 0 ? 1 : 2;
|
|
15753
15650
|
return Math.asin(Math.min(Math.abs(y), 1)) * 180 / Math.PI > (previous === cap ? CAP_LEAVE_DEG : CAP_ENTER_DEG) ? cap : 0;
|
|
15754
15651
|
}
|
|
15755
|
-
function windowChain(level, count, chart, y) {
|
|
15652
|
+
function windowChain(level, count, chart, y, maxLevel) {
|
|
15756
15653
|
const chain = [];
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15654
|
+
const own = Math.max(1, Math.floor(count / 3));
|
|
15655
|
+
let z = level;
|
|
15656
|
+
while (z >= 3 && chain.length < count) {
|
|
15657
|
+
chain.push({
|
|
15658
|
+
chart,
|
|
15659
|
+
level: z
|
|
15660
|
+
});
|
|
15661
|
+
z = z > maxLevel && chain.length >= count - own ? Math.min(z - 1, maxLevel) : z - 1;
|
|
15662
|
+
}
|
|
15761
15663
|
const reached = chain.length > 0 && chain[chain.length - 1].level === 3;
|
|
15762
15664
|
const both = Math.abs(y) > Math.sin(BOTH_CHARTS_DEG * Math.PI / 180);
|
|
15763
15665
|
if (reached && both && chain.length < count) {
|
|
@@ -15793,6 +15695,42 @@ function placeWindow(chart, level, x, y, previous) {
|
|
|
15793
15695
|
ty0
|
|
15794
15696
|
};
|
|
15795
15697
|
}
|
|
15698
|
+
function patchOfTile(patches, level, x, y) {
|
|
15699
|
+
return patches?.find((patch) => {
|
|
15700
|
+
const up = patch.max_level - level;
|
|
15701
|
+
return up >= 0 && x >= patch.x[0] >> up && x <= patch.x[1] >> up && y >= patch.y[0] >> up && y <= patch.y[1] >> up;
|
|
15702
|
+
});
|
|
15703
|
+
}
|
|
15704
|
+
var REACH_TILES = 4 / 2 - .5;
|
|
15705
|
+
function deepestLevel(maxLevel, patches, x, y) {
|
|
15706
|
+
let deepest = maxLevel;
|
|
15707
|
+
if (!patches) return deepest;
|
|
15708
|
+
for (const patch of patches) for (let level = patch.max_level; level > deepest; level--) {
|
|
15709
|
+
const up = patch.max_level - level;
|
|
15710
|
+
const columns = chartColumns(0, level);
|
|
15711
|
+
const x0 = patch.x[0] >> up;
|
|
15712
|
+
const y0 = patch.y[0] >> up;
|
|
15713
|
+
const halfWidth = ((patch.x[1] >> up) + 1 - x0) / 2;
|
|
15714
|
+
const halfHeight = ((patch.y[1] >> up) + 1 - y0) / 2;
|
|
15715
|
+
let east = x * columns - (x0 + halfWidth);
|
|
15716
|
+
east -= columns * Math.round(east / columns);
|
|
15717
|
+
const south = y * chartRows(0, level) - (y0 + halfHeight);
|
|
15718
|
+
if (Math.abs(east) - halfWidth <= REACH_TILES && Math.abs(south) - halfHeight <= REACH_TILES) {
|
|
15719
|
+
deepest = level;
|
|
15720
|
+
break;
|
|
15721
|
+
}
|
|
15722
|
+
}
|
|
15723
|
+
return deepest;
|
|
15724
|
+
}
|
|
15725
|
+
function windowHome(placement, x, y) {
|
|
15726
|
+
const columns = chartColumns(placement.chart, placement.level);
|
|
15727
|
+
let east = x * columns - placement.tx0;
|
|
15728
|
+
east -= columns * Math.round(east / columns);
|
|
15729
|
+
return {
|
|
15730
|
+
x: east,
|
|
15731
|
+
y: y * chartRows(placement.chart, placement.level) - placement.ty0
|
|
15732
|
+
};
|
|
15733
|
+
}
|
|
15796
15734
|
function slotTile(placement, sx, sy) {
|
|
15797
15735
|
const columns = chartColumns(placement.chart, placement.level);
|
|
15798
15736
|
const dx = ((sx - placement.tx0) % 4 + 4) % 4;
|
|
@@ -15804,24 +15742,190 @@ function slotTile(placement, sx, sy) {
|
|
|
15804
15742
|
}
|
|
15805
15743
|
|
|
15806
15744
|
//#endregion
|
|
15807
|
-
//#region src/lib/scene/
|
|
15745
|
+
//#region src/lib/scene/relief/worker.ts?worker&inline
|
|
15746
|
+
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}}const s=Math.sin(60*Math.PI/180);function c(e,t,n,r){return((e*16+t)*8192+n)*8192+r}function l(e){let t=e%8192,n=Math.floor(e/8192)%8192,r=Math.floor(e/(8192*8192));return{chart:Math.floor(r/16),level:r%16,x:n,y:t}}const u={chart:0,level:0,x0:0,x1:0,y0:0,y1:0,fx:0,fy:0};function d(e,t,n,r,c){let l=e<1||Math.abs(n)<=s?0:n>0?1:2,d=i(l,e)*c,f=a(l,e)*c,p=o(l,t,n,r),m=p.x*d-.5,h=p.y*f-.5,g=Math.floor(m),_=Math.floor(h),v=e=>Math.min(Math.max(e,0),d-1),y=e=>Math.min(Math.max(e,0),f-1);return u.chart=l,u.level=e,u.fx=m-g,u.fy=h-_,u.x0=l===0?(g+d)%d:v(g),u.x1=l===0?(g+1)%d:v(g+1),u.y0=y(_),u.y1=y(_+1),u}function f(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 p(e,t,n){let r=new Set([c(0,0,0,0),c(0,0,1,0)]);return f(e,t,(e,t,i,a,o)=>{let s=d(t,i,a,o,n),l=Math.floor(s.x0/n),u=Math.floor(s.x1/n),f=Math.floor(s.y0/n),p=Math.floor(s.y1/n);r.add(c(s.chart,s.level,l,f)),u!==l&&r.add(c(s.chart,s.level,u,f)),p!==f&&(r.add(c(s.chart,s.level,l,p)),u!==l&&r.add(c(s.chart,s.level,u,p)))}),r}function m(e,t,n,r,i,a){for(let o=t;o>=0;o--){let t=d(o,n,r,i,a),s=(n,r)=>{let i=e(c(t.chart,o,Math.floor(n/a),Math.floor(r/a)));return i&&i[r%a*a+n%a]},l=s(t.x0,t.y0),u=s(t.x1,t.y0),f=s(t.x0,t.y1),p=s(t.x1,t.y1);if(l===void 0||u===void 0||f===void 0||p===void 0)continue;let m=l+(u-l)*t.fx;return m+(f+(p-f)*t.fx-m)*t.fy}}function h(e,t,n,r,i,a){let o=new Float32Array(e.at.length*t.at.length),s=!1;return f(e,t,(e,t,c,l,u)=>{let d=m(n,t,c,l,u,r);d===void 0?s=!0:o[e]=a+i*d/65535}),s?null:o}function g(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 _(e,t){let n=new Uint16Array(t*t);for(let t=0,r=0;t<n.length;t++,r+=4)n[t]=e[r]<<8|e[r+1];return n}const v=self,y=new Map;let b;async function x(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;b?.width!==i&&(b=new OffscreenCanvas(i,i));let a=b.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 S(e,t){let{rgba:n,size:r}=await x(e.url,t),i=g(n,r);v.postMessage({type:`tile`,id:e.id,size:r,bytes:i},[i.buffer])}const C=new Map;let w=``;async function T(r,i){let{rows:a,columns:o,tileSize:s}=r,c=`${r.root}?${r.version}`;c!==w&&C.clear(),w=c;let u=p(a,o,s),d=[...u].filter(e=>!C.has(e));await Promise.all(Array.from({length:6},async()=>{for(let t=d.pop();t!==void 0;t=d.pop()){let{chart:a,level:o,x:s,y:c}=l(t),u=`${r.root}${e(n[a],o,s,c)}?v=${r.version}`;try{let{rgba:e,size:n}=await x(u,i);C.set(t,_(e,n))}catch{if(i.aborted)return}}})),i.throwIfAborted();let f=h(a,o,e=>C.get(e),s,r.scaleKm,r.biasKm);for(let e of[...C.keys()])u.has(e)||C.delete(e);if(!f)throw Error(`no height tiles for the grid`);let m=t(r.radius,a.at,o.at,f,r.unitsPerKm),g=m.indices.slice();v.postMessage({type:`grid`,id:r.id,...m,indices:g,riseKm:f},[m.positions.buffer,m.normals.buffer,m.uvs.buffer,g.buffer,f.buffer])}async function E(e){let t=new AbortController;y.set(e.id,t);try{e.type===`tile`?await S(e,t.signal):await T(e,t.signal)}catch{v.postMessage({type:`failed`,id:e.id})}finally{y.delete(e.id)}}self.onmessage=e=>{let t=e.data;t.type===`abort`?y.get(t.id)?.abort():E(t)};";
|
|
15747
|
+
var blob$1 = typeof self !== "undefined" && self.Blob && new Blob(["URL.revokeObjectURL(import.meta.url);", jsContent$1], { type: "text/javascript;charset=utf-8" });
|
|
15748
|
+
function WorkerWrapper$1(options) {
|
|
15749
|
+
let objURL;
|
|
15750
|
+
try {
|
|
15751
|
+
objURL = blob$1 && (self.URL || self.webkitURL).createObjectURL(blob$1);
|
|
15752
|
+
if (!objURL) throw "";
|
|
15753
|
+
const worker = new Worker(objURL, {
|
|
15754
|
+
type: "module",
|
|
15755
|
+
name: options?.name
|
|
15756
|
+
});
|
|
15757
|
+
worker.addEventListener("error", () => {
|
|
15758
|
+
(self.URL || self.webkitURL).revokeObjectURL(objURL);
|
|
15759
|
+
});
|
|
15760
|
+
return worker;
|
|
15761
|
+
} catch (e) {
|
|
15762
|
+
return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent$1), {
|
|
15763
|
+
type: "module",
|
|
15764
|
+
name: options?.name
|
|
15765
|
+
});
|
|
15766
|
+
}
|
|
15767
|
+
}
|
|
15768
|
+
|
|
15769
|
+
//#endregion
|
|
15770
|
+
//#region src/lib/scene/relief/client.ts
|
|
15771
|
+
var worker = null;
|
|
15772
|
+
var nextId = 1;
|
|
15773
|
+
var pending$1 = /* @__PURE__ */ new Map();
|
|
15774
|
+
function drop(reason) {
|
|
15775
|
+
worker?.terminate();
|
|
15776
|
+
worker = null;
|
|
15777
|
+
const lost = [...pending$1.values()];
|
|
15778
|
+
pending$1.clear();
|
|
15779
|
+
for (const { reject } of lost) reject(new Error(reason));
|
|
15780
|
+
}
|
|
15781
|
+
function ensureWorker() {
|
|
15782
|
+
if (worker) return worker;
|
|
15783
|
+
worker = new WorkerWrapper$1();
|
|
15784
|
+
worker.onmessage = (ev) => {
|
|
15785
|
+
const msg = ev.data;
|
|
15786
|
+
const request = pending$1.get(msg.id);
|
|
15787
|
+
pending$1.delete(msg.id);
|
|
15788
|
+
if (!request) return;
|
|
15789
|
+
if (msg.type === "failed") request.reject(/* @__PURE__ */ new Error("relief worker could not answer"));
|
|
15790
|
+
else request.resolve(msg);
|
|
15791
|
+
};
|
|
15792
|
+
worker.onerror = (ev) => drop(`relief worker error: ${ev.message || "unknown"}`);
|
|
15793
|
+
worker.onmessageerror = () => drop("relief worker sent a message that could not be read");
|
|
15794
|
+
return worker;
|
|
15795
|
+
}
|
|
15796
|
+
function ask(build, signal) {
|
|
15797
|
+
return new Promise((resolve, reject) => {
|
|
15798
|
+
if (typeof Worker === "undefined") {
|
|
15799
|
+
reject(/* @__PURE__ */ new Error("no workers here"));
|
|
15800
|
+
return;
|
|
15801
|
+
}
|
|
15802
|
+
const id = nextId++;
|
|
15803
|
+
pending$1.set(id, {
|
|
15804
|
+
resolve,
|
|
15805
|
+
reject
|
|
15806
|
+
});
|
|
15807
|
+
signal.addEventListener("abort", () => {
|
|
15808
|
+
if (!pending$1.delete(id)) return;
|
|
15809
|
+
ensureWorker().postMessage({
|
|
15810
|
+
type: "abort",
|
|
15811
|
+
id
|
|
15812
|
+
});
|
|
15813
|
+
reject(new DOMException("aborted", "AbortError"));
|
|
15814
|
+
}, { once: true });
|
|
15815
|
+
ensureWorker().postMessage(build(id));
|
|
15816
|
+
});
|
|
15817
|
+
}
|
|
15818
|
+
function absolute(url) {
|
|
15819
|
+
return new URL(url, location.href).href;
|
|
15820
|
+
}
|
|
15821
|
+
function loadHeightTile(url, signal) {
|
|
15822
|
+
return ask((id) => ({
|
|
15823
|
+
type: "tile",
|
|
15824
|
+
id,
|
|
15825
|
+
url: absolute(url)
|
|
15826
|
+
}), signal);
|
|
15827
|
+
}
|
|
15828
|
+
function buildTerrainGrid(spec, signal) {
|
|
15829
|
+
return ask((id) => ({
|
|
15830
|
+
...spec,
|
|
15831
|
+
type: "grid",
|
|
15832
|
+
id,
|
|
15833
|
+
root: absolute(spec.root)
|
|
15834
|
+
}), signal);
|
|
15835
|
+
}
|
|
15836
|
+
|
|
15837
|
+
//#endregion
|
|
15838
|
+
//#region src/lib/scene/objects/surface/tile-chain.ts
|
|
15808
15839
|
var MAX_WINDOWS = 6;
|
|
15809
|
-
var MAX_DRAWN =
|
|
15840
|
+
var MAX_DRAWN = 6 + 1;
|
|
15810
15841
|
var MAX_INFLIGHT = 6;
|
|
15811
|
-
var MAX_FAILURES = 4;
|
|
15842
|
+
var MAX_FAILURES$1 = 4;
|
|
15812
15843
|
var FETCH_TIMEOUT_S = 30;
|
|
15813
15844
|
var IDLE_DISPOSE_S = 5;
|
|
15814
15845
|
var FEATHER_TILES = .5;
|
|
15815
15846
|
var MAX_ANISOTROPY = 16;
|
|
15816
15847
|
var SLOTS = 4 * 4;
|
|
15817
|
-
var
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
//
|
|
15824
|
-
|
|
15848
|
+
var TILE_CHAIN_GLSL = `
|
|
15849
|
+
#ifndef TILE_CHAIN
|
|
15850
|
+
#define TILE_CHAIN
|
|
15851
|
+
|
|
15852
|
+
// Where a fragment is on the map and on the cap of its own hemisphere, as
|
|
15853
|
+
// fractions of the chart, and what a screen pixel spans there. The place on
|
|
15854
|
+
// the cap is from the chart's north-west corner. The place on the map is from
|
|
15855
|
+
// the point that the maker of the charts counts from.
|
|
15856
|
+
struct TileCharts {
|
|
15857
|
+
vec2 mapAt;
|
|
15858
|
+
vec2 mapDx;
|
|
15859
|
+
vec2 mapDy;
|
|
15860
|
+
vec2 capAt;
|
|
15861
|
+
vec2 capDx;
|
|
15862
|
+
vec2 capDy;
|
|
15863
|
+
float side;
|
|
15864
|
+
};
|
|
15865
|
+
|
|
15866
|
+
// The cap of the hemisphere of dir, seen from above its pole. ddx and ddy are
|
|
15867
|
+
// what a screen pixel adds to dir.
|
|
15868
|
+
void tileCap( vec3 dir, vec3 ddx, vec3 ddy, out float side, out vec2 capAt, out vec2 capDx, out vec2 capDy ) {
|
|
15869
|
+
side = dir.y < 0.0 ? - 1.0 : 1.0;
|
|
15870
|
+
float fold = 1.0 + side * dir.y;
|
|
15871
|
+
vec2 plane = vec2( dir.x, side * dir.z );
|
|
15872
|
+
float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
|
|
15873
|
+
capAt = 0.5 + plane * capScale;
|
|
15874
|
+
capDx = ( vec2( ddx.x, side * ddx.z ) - plane * ( side * ddx.y / fold ) ) * capScale;
|
|
15875
|
+
capDy = ( vec2( ddy.x, side * ddy.z ) - plane * ( side * ddy.y / fold ) ) * capScale;
|
|
15876
|
+
}
|
|
15877
|
+
|
|
15878
|
+
// The charts of a direction, the map counted from its north-west corner. The
|
|
15879
|
+
// direction is the fragment's own rather than its interpolated coordinates:
|
|
15880
|
+
// those have one slope per triangle, which near a pole differs enough between
|
|
15881
|
+
// the two triangles of a grid cell to pick different levels, and they jump at
|
|
15882
|
+
// the 180° meridian.
|
|
15883
|
+
TileCharts tileCharts( vec3 dir ) {
|
|
15884
|
+
TileCharts charts;
|
|
15885
|
+
vec3 ddx = dFdx( dir );
|
|
15886
|
+
vec3 ddy = dFdy( dir );
|
|
15887
|
+
|
|
15888
|
+
float flat2 = max( dir.x * dir.x + dir.z * dir.z, 1e-12 );
|
|
15889
|
+
float east = 1.0 / ( 2.0 * PI * flat2 );
|
|
15890
|
+
float south = - 1.0 / ( PI * sqrt( flat2 ) );
|
|
15891
|
+
charts.mapAt = vec2( fract( atan( dir.z, - dir.x ) / ( 2.0 * PI ) ), acos( clamp( dir.y, - 1.0, 1.0 ) ) / PI );
|
|
15892
|
+
charts.mapDx = vec2( ( dir.z * ddx.x - dir.x * ddx.z ) * east, ddx.y * south );
|
|
15893
|
+
charts.mapDy = vec2( ( dir.z * ddy.x - dir.x * ddy.z ) * east, ddy.y * south );
|
|
15894
|
+
tileCap( dir, ddx, ddy, charts.side, charts.capAt, charts.capDx, charts.capDy );
|
|
15895
|
+
return charts;
|
|
15896
|
+
}
|
|
15897
|
+
|
|
15898
|
+
// The charts of the point at + off, the map counted from at. A position on
|
|
15899
|
+
// the sphere has seven digits, and a tile index of a patch takes them all;
|
|
15900
|
+
// the offset keeps its own.
|
|
15901
|
+
TileCharts tileChartsFrom( vec3 at, vec3 off ) {
|
|
15902
|
+
TileCharts charts;
|
|
15903
|
+
vec3 pos = at + off;
|
|
15904
|
+
vec3 ox = dFdx( off );
|
|
15905
|
+
vec3 oy = dFdy( off );
|
|
15906
|
+
|
|
15907
|
+
// The turn about the axis and the drop from the north pole, each as the
|
|
15908
|
+
// angle between at and the point.
|
|
15909
|
+
float flat2 = max( pos.x * pos.x + pos.z * pos.z, 1e-30 );
|
|
15910
|
+
float flat1 = sqrt( flat2 );
|
|
15911
|
+
float flat0 = sqrt( at.x * at.x + at.z * at.z );
|
|
15912
|
+
float turn = atan( at.z * off.x - at.x * off.z, at.x * pos.x + at.z * pos.z );
|
|
15913
|
+
float out1 = ( 2.0 * ( at.x * off.x + at.z * off.z ) + off.x * off.x + off.z * off.z ) / ( flat1 + flat0 );
|
|
15914
|
+
float drop = atan( out1 * at.y - off.y * flat0, pos.y * at.y + flat1 * flat0 );
|
|
15915
|
+
charts.mapAt = vec2( turn / ( 2.0 * PI ), drop / PI );
|
|
15916
|
+
float east = 1.0 / ( 2.0 * PI * flat2 );
|
|
15917
|
+
float south = 1.0 / ( PI * dot( pos, pos ) );
|
|
15918
|
+
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 );
|
|
15919
|
+
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 );
|
|
15920
|
+
|
|
15921
|
+
// A cap goes no deeper than the pyramid, so the direction itself will do.
|
|
15922
|
+
float range = length( pos );
|
|
15923
|
+
vec3 dir = pos / range;
|
|
15924
|
+
vec3 ddx = ( ox - dir * dot( dir, ox ) ) / range;
|
|
15925
|
+
vec3 ddy = ( oy - dir * dot( dir, oy ) ) / range;
|
|
15926
|
+
tileCap( dir, ddx, ddy, charts.side, charts.capAt, charts.capDx, charts.capDy );
|
|
15927
|
+
return charts;
|
|
15928
|
+
}
|
|
15825
15929
|
|
|
15826
15930
|
// Long and short half-axes of the footprint whose sides are fx and fy. The
|
|
15827
15931
|
// two sides alone would do where the stretch runs along the screen's axes;
|
|
@@ -15840,105 +15944,61 @@ void tileAxes( vec2 fx, vec2 fy, out vec2 major, out vec2 minor ) {
|
|
|
15840
15944
|
minor = vec2( - axis.y, axis.x ) * sqrt( lo );
|
|
15841
15945
|
}
|
|
15842
15946
|
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
//
|
|
15850
|
-
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
// The cap of the fragment's own hemisphere, seen from above its pole.
|
|
15864
|
-
float side = dir.y < 0.0 ? - 1.0 : 1.0;
|
|
15865
|
-
float fold = 1.0 + side * dir.y;
|
|
15866
|
-
vec2 plane = vec2( dir.x, side * dir.z );
|
|
15867
|
-
float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
|
|
15868
|
-
vec2 capAt = 0.5 + plane * capScale;
|
|
15869
|
-
vec2 capDx = ( vec2( ddx.x, side * ddx.z ) - plane * ( side * ddx.y / fold ) ) * capScale;
|
|
15870
|
-
vec2 capDy = ( vec2( ddy.x, side * ddy.z ) - plane * ( side * ddy.y / fold ) ) * capScale;
|
|
15947
|
+
// How much of a window shows at the fragment, before the mask of its tiles.
|
|
15948
|
+
// home is the point that the charts count the map from, in tiles from the
|
|
15949
|
+
// window's north-west corner. Gives the fragment's place in the window's
|
|
15950
|
+
// texture, in tiles, and the axes of its footprint, in the window's pixels.
|
|
15951
|
+
float tileWindowWeight( TileCharts charts, vec4 window, vec4 grid, vec2 home, vec2 tileFilter, out vec2 spot, out vec2 major, out vec2 minor ) {
|
|
15952
|
+
bool onMap = window.x < 0.5;
|
|
15953
|
+
// The other pole's cap.
|
|
15954
|
+
if ( ! onMap && ( window.x < 1.5 ) != ( charts.side > 0.0 ) ) return 0.0;
|
|
15955
|
+
vec2 dx = ( onMap ? charts.mapDx : charts.capDx ) * grid.xy * tileFilter.y;
|
|
15956
|
+
vec2 dy = ( onMap ? charts.mapDy : charts.capDy ) * grid.xy * tileFilter.y;
|
|
15957
|
+
|
|
15958
|
+
// Level this fragment asks of the window, from the short axis of its
|
|
15959
|
+
// footprint in the window's pixels: the long axis is the anisotropic
|
|
15960
|
+
// filter's to cover, as far as its taps reach. The window fades in
|
|
15961
|
+
// over the level below its own, as a mipmap would.
|
|
15962
|
+
tileAxes( dx, dy, major, minor );
|
|
15963
|
+
float foot = max( length( minor ), length( major ) / tileFilter.x );
|
|
15964
|
+
float weight = clamp( 1.0 - log2( max( foot, 1e-6 ) ), 0.0, 1.0 );
|
|
15965
|
+
if ( weight <= 0.0 ) return 0.0;
|
|
15871
15966
|
|
|
15872
15967
|
float rows = ${4 .toFixed(1)};
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
vec4 window = uTileWindow[ i ];
|
|
15877
|
-
if ( window.w < 0.5 ) break;
|
|
15878
|
-
vec4 grid = uTileGrid[ i ];
|
|
15879
|
-
bool onMap = window.x < 0.5;
|
|
15880
|
-
// The other pole's cap.
|
|
15881
|
-
if ( ! onMap && ( window.x < 1.5 ) != ( side > 0.0 ) ) continue;
|
|
15882
|
-
vec2 tile = ( onMap ? mapAt : capAt ) * grid.xy;
|
|
15883
|
-
vec2 dx = ( onMap ? mapDx : capDx ) * grid.xy * uTileFilter.y;
|
|
15884
|
-
vec2 dy = ( onMap ? mapDy : capDy ) * grid.xy * uTileFilter.y;
|
|
15885
|
-
|
|
15886
|
-
// Level this fragment asks of the window, from the short axis of its
|
|
15887
|
-
// footprint in the window's pixels: the long axis is the anisotropic
|
|
15888
|
-
// filter's to cover, as far as its taps reach. The window fades in
|
|
15889
|
-
// over the level below its own, as a mipmap would.
|
|
15890
|
-
vec2 major;
|
|
15891
|
-
vec2 minor;
|
|
15892
|
-
tileAxes( dx, dy, major, minor );
|
|
15893
|
-
float foot = max( length( minor ), length( major ) / uTileFilter.x );
|
|
15894
|
-
float weight = clamp( 1.0 - log2( max( foot, 1e-6 ) ), 0.0, 1.0 );
|
|
15895
|
-
if ( weight <= 0.0 ) continue;
|
|
15896
|
-
|
|
15968
|
+
// Offset from the window's north-west corner, in tiles.
|
|
15969
|
+
vec2 rel = onMap ? home + charts.mapAt * grid.xy : charts.capAt * grid.xy - grid.zw;
|
|
15970
|
+
if ( onMap ) {
|
|
15897
15971
|
// Half a pixel in from a pole of the map: past it the filter would
|
|
15898
|
-
// read the far row.
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
float alpha = weight * texelFetch( uTileMask, slot, 0 ).r * smoothstep( 0.0, ${FEATHER_TILES.toFixed(2)}, edge );
|
|
15913
|
-
if ( alpha <= 0.0 ) continue;
|
|
15914
|
-
|
|
15915
|
-
// The texture repeats every window: a tile sits in the slot its own
|
|
15916
|
-
// column and row pick, north up.
|
|
15917
|
-
vec3 st = vec3( tile.x / rows, - tile.y / rows, window.y );
|
|
15918
|
-
vec2 texel = vec2( 1.0, - 1.0 ) / ( rows * uTileFilter.y );
|
|
15919
|
-
colour += ( 1.0 - covered ) * alpha * textureGrad( uTileMap, st, major * texel, minor * texel ).rgb;
|
|
15920
|
-
covered += ( 1.0 - covered ) * alpha;
|
|
15921
|
-
if ( covered > 0.995 ) break;
|
|
15922
|
-
}
|
|
15923
|
-
return colour + ( 1.0 - covered ) * below;
|
|
15972
|
+
// read the far row. The map's longitude wraps.
|
|
15973
|
+
rel.y = clamp( rel.y, 0.0005 - grid.w, grid.y - 0.0005 - grid.w );
|
|
15974
|
+
rel.x = mod( rel.x, grid.x );
|
|
15975
|
+
}
|
|
15976
|
+
if ( rel.x < 0.0 || rel.x > rows || rel.y < 0.0 || rel.y > rows ) return 0.0;
|
|
15977
|
+
// Distance to the window's edge, in tiles. A pole of the map is no
|
|
15978
|
+
// edge: nothing lies past it.
|
|
15979
|
+
vec2 near = min( rel, rows - rel );
|
|
15980
|
+
if ( onMap && grid.w < 0.5 ) near.y = rows - rel.y;
|
|
15981
|
+
if ( onMap && grid.w + rows > grid.y - 0.5 ) near.y = grid.w < 0.5 ? rows : rel.y;
|
|
15982
|
+
// The texture repeats every window: a tile sits in the slot its own
|
|
15983
|
+
// column and row pick, north up.
|
|
15984
|
+
spot = rel + mod( grid.zw, rows );
|
|
15985
|
+
return weight * smoothstep( 0.0, ${FEATHER_TILES.toFixed(2)}, min( near.x, near.y ) );
|
|
15924
15986
|
}
|
|
15925
|
-
|
|
15926
|
-
var MAP_FRAGMENT = `
|
|
15927
|
-
#ifdef USE_MAP
|
|
15928
|
-
vec4 sampledDiffuseColor = texture2D( map, vMapUv );
|
|
15929
|
-
sampledDiffuseColor.rgb = tileColour( sampledDiffuseColor.rgb );
|
|
15930
|
-
diffuseColor *= sampledDiffuseColor;
|
|
15987
|
+
|
|
15931
15988
|
#endif
|
|
15932
15989
|
`;
|
|
15933
|
-
function
|
|
15934
|
-
|
|
15935
|
-
|
|
15990
|
+
function createChain(kind) {
|
|
15991
|
+
return {
|
|
15992
|
+
kind,
|
|
15936
15993
|
uniforms: {
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15994
|
+
map: { value: null },
|
|
15995
|
+
mask: { value: null },
|
|
15996
|
+
grid: { value: Array.from({ length: 7 }, () => new Vector4()) },
|
|
15997
|
+
window: { value: Array.from({ length: 7 }, () => new Vector4()) },
|
|
15998
|
+
home: { value: Array.from({ length: 7 }, () => new Vector2()) },
|
|
15999
|
+
anchor: { value: new Vector3(1, 0, 0) },
|
|
16000
|
+
loaded: { value: new Int32Array(7) },
|
|
16001
|
+
filter: { value: new Vector2(1, 1) }
|
|
15942
16002
|
},
|
|
15943
16003
|
inflight: 0,
|
|
15944
16004
|
failures: 0,
|
|
@@ -15946,20 +16006,14 @@ function createOverlay(material) {
|
|
|
15946
16006
|
dead: false,
|
|
15947
16007
|
lastUsed: 0
|
|
15948
16008
|
};
|
|
15949
|
-
chainShaderHook(material, "surfaceTiles", (shader) => {
|
|
15950
|
-
Object.assign(shader.uniforms, overlay.uniforms);
|
|
15951
|
-
shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nvarying vec3 vTileDir;").replace("#include <begin_vertex>", "#include <begin_vertex>\nvTileDir = position;");
|
|
15952
|
-
shader.fragmentShader = shader.fragmentShader.replace("#include <map_pars_fragment>", `#include <map_pars_fragment>\n${PARS}`).replace("#include <map_fragment>", MAP_FRAGMENT);
|
|
15953
|
-
});
|
|
15954
|
-
return overlay;
|
|
15955
16009
|
}
|
|
15956
|
-
function createStore(tileSize, layers, renderer) {
|
|
16010
|
+
function createStore(kind, tileSize, layers, renderer) {
|
|
15957
16011
|
const size = tileSize * 4;
|
|
15958
16012
|
const texture = new DataArrayTexture(null, size, size, layers);
|
|
15959
16013
|
texture.source.dataReady = false;
|
|
15960
|
-
texture.format =
|
|
16014
|
+
texture.format = kind.format;
|
|
15961
16015
|
texture.type = UnsignedByteType;
|
|
15962
|
-
texture.colorSpace =
|
|
16016
|
+
texture.colorSpace = kind.colorSpace;
|
|
15963
16017
|
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
15964
16018
|
texture.magFilter = LinearFilter;
|
|
15965
16019
|
texture.minFilter = LinearMipmapLinearFilter;
|
|
@@ -15969,7 +16023,8 @@ function createStore(tileSize, layers, renderer) {
|
|
|
15969
16023
|
const mask = new DataArrayTexture(new Uint8Array(SLOTS * layers), 4, 4, layers);
|
|
15970
16024
|
mask.format = RedFormat;
|
|
15971
16025
|
mask.type = UnsignedByteType;
|
|
15972
|
-
mask.minFilter = mask.magFilter =
|
|
16026
|
+
mask.minFilter = mask.magFilter = LinearFilter;
|
|
16027
|
+
mask.wrapS = mask.wrapT = RepeatWrapping;
|
|
15973
16028
|
mask.unpackAlignment = 1;
|
|
15974
16029
|
mask.needsUpdate = true;
|
|
15975
16030
|
return {
|
|
@@ -16010,26 +16065,27 @@ function release(store, win) {
|
|
|
16010
16065
|
for (let slot = 0; slot < SLOTS; slot++) setLoaded(store, win.layer, slot, false);
|
|
16011
16066
|
store.free.push(win.layer);
|
|
16012
16067
|
}
|
|
16013
|
-
function
|
|
16068
|
+
function patchOf(tiles, win, tile) {
|
|
16069
|
+
if (win.chart !== 0 || win.level <= tiles.max_level) return void 0;
|
|
16070
|
+
return patchOfTile(tiles.patches, win.level, tile.x, tile.y);
|
|
16071
|
+
}
|
|
16072
|
+
function exists(tiles, win, slot) {
|
|
16073
|
+
if (win.level <= tiles.max_level) return true;
|
|
16074
|
+
if (!win.placement) return false;
|
|
16075
|
+
return patchOf(tiles, win, slotTile(win.placement, slot % 4, Math.floor(slot / 4))) !== void 0;
|
|
16076
|
+
}
|
|
16077
|
+
function settled(chain, store, tiles, win) {
|
|
16014
16078
|
for (let slot = 0; slot < SLOTS; slot++) {
|
|
16015
16079
|
const key = win.wanted[slot];
|
|
16016
16080
|
if (!key) return false;
|
|
16017
|
-
if (
|
|
16081
|
+
if (isLoaded(store, win.layer, slot) || chain.failed.has(key)) continue;
|
|
16082
|
+
if (exists(tiles, win, slot)) return false;
|
|
16018
16083
|
}
|
|
16019
16084
|
return true;
|
|
16020
16085
|
}
|
|
16021
|
-
async function fetchTile(url, signal) {
|
|
16022
|
-
const response = await fetch(url, { signal });
|
|
16023
|
-
if (!response.ok) throw new Error(`tile ${response.status}`);
|
|
16024
|
-
return createImageBitmap(await response.blob(), {
|
|
16025
|
-
imageOrientation: "flipY",
|
|
16026
|
-
premultiplyAlpha: "none",
|
|
16027
|
-
colorSpaceConversion: "none"
|
|
16028
|
-
});
|
|
16029
|
-
}
|
|
16030
16086
|
var copyAt = new Vector3();
|
|
16031
16087
|
var queue$1 = [];
|
|
16032
|
-
function pump(
|
|
16088
|
+
function pump(chain, store, tiles, win, x, y, renderer, invalidate) {
|
|
16033
16089
|
const placement = win.placement;
|
|
16034
16090
|
if (!placement) return;
|
|
16035
16091
|
const columns = chartColumns(win.chart, win.level);
|
|
@@ -16038,8 +16094,8 @@ function pump(overlay, store, tiles, win, x, y, renderer, invalidate) {
|
|
|
16038
16094
|
queue$1.length = 0;
|
|
16039
16095
|
for (let slot = 0; slot < SLOTS; slot++) {
|
|
16040
16096
|
const key = win.wanted[slot];
|
|
16041
|
-
if (!key || win.pending.has(slot) ||
|
|
16042
|
-
if (isLoaded(store, win.layer, slot)) continue;
|
|
16097
|
+
if (!key || win.pending.has(slot) || chain.failed.has(key)) continue;
|
|
16098
|
+
if (isLoaded(store, win.layer, slot) || !exists(tiles, win, slot)) continue;
|
|
16043
16099
|
const tile = slotTile(placement, slot % 4, Math.floor(slot / 4));
|
|
16044
16100
|
let dx = tile.x + .5 - cx;
|
|
16045
16101
|
if (win.chart === 0) dx -= columns * Math.round(dx / columns);
|
|
@@ -16050,7 +16106,7 @@ function pump(overlay, store, tiles, win, x, y, renderer, invalidate) {
|
|
|
16050
16106
|
}
|
|
16051
16107
|
queue$1.sort((a, b) => a.far - b.far);
|
|
16052
16108
|
for (const { slot } of queue$1) {
|
|
16053
|
-
if (
|
|
16109
|
+
if (chain.inflight >= MAX_INFLIGHT) return;
|
|
16054
16110
|
const key = win.wanted[slot];
|
|
16055
16111
|
const sx = slot % 4;
|
|
16056
16112
|
const sy = Math.floor(slot / 4);
|
|
@@ -16065,129 +16121,785 @@ function pump(overlay, store, tiles, win, x, y, renderer, invalidate) {
|
|
|
16065
16121
|
key,
|
|
16066
16122
|
abort
|
|
16067
16123
|
});
|
|
16068
|
-
|
|
16124
|
+
chain.inflight++;
|
|
16069
16125
|
const folder = CHART_FOLDER[win.chart];
|
|
16070
|
-
|
|
16126
|
+
const version = patchOf(tiles, win, tile)?.version ?? tiles.version;
|
|
16127
|
+
const pyramid = {
|
|
16128
|
+
id: tiles.id,
|
|
16129
|
+
version
|
|
16130
|
+
};
|
|
16131
|
+
chain.kind.load(tileUrl(pyramid, folder, win.level, tile.x, tile.y, win.frame), abort.signal).then((loaded) => {
|
|
16071
16132
|
if (win.wanted[slot] === key && !abort.signal.aborted) {
|
|
16072
|
-
const source = new Texture(bitmap);
|
|
16073
|
-
source.colorSpace = SRGBColorSpace;
|
|
16074
|
-
source.flipY = false;
|
|
16075
|
-
source.generateMipmaps = false;
|
|
16076
16133
|
copyAt.set(sx * store.tileSize, (4 - 1 - sy) * store.tileSize, win.layer);
|
|
16077
|
-
renderer.copyTextureToTexture(
|
|
16078
|
-
source.dispose();
|
|
16134
|
+
renderer.copyTextureToTexture(loaded.texture, store.texture, null, copyAt);
|
|
16079
16135
|
setLoaded(store, win.layer, slot, true);
|
|
16080
|
-
|
|
16136
|
+
chain.failures = 0;
|
|
16081
16137
|
invalidate();
|
|
16082
16138
|
}
|
|
16083
|
-
|
|
16139
|
+
loaded.release();
|
|
16084
16140
|
}).catch(() => {
|
|
16085
16141
|
if (abort.signal.aborted && !timedOut) return;
|
|
16086
|
-
|
|
16087
|
-
if (++
|
|
16142
|
+
chain.failed.add(key);
|
|
16143
|
+
if (++chain.failures >= MAX_FAILURES$1) chain.dead = true;
|
|
16088
16144
|
}).finally(() => {
|
|
16089
16145
|
clearTimeout(timer);
|
|
16090
|
-
|
|
16146
|
+
chain.inflight--;
|
|
16091
16147
|
if (win.pending.get(slot)?.key === key) win.pending.delete(slot);
|
|
16092
16148
|
});
|
|
16093
16149
|
}
|
|
16094
16150
|
}
|
|
16095
|
-
function switchOff(
|
|
16096
|
-
for (const link of
|
|
16151
|
+
function switchOff(chain) {
|
|
16152
|
+
for (const link of chain.uniforms.window.value) link.w = 0;
|
|
16153
|
+
}
|
|
16154
|
+
function disposeChain(chain) {
|
|
16155
|
+
switchOff(chain);
|
|
16156
|
+
const store = chain.store;
|
|
16157
|
+
if (!store) return;
|
|
16158
|
+
for (const win of store.windows) for (const { abort } of win.pending.values()) abort.abort();
|
|
16159
|
+
store.texture.dispose();
|
|
16160
|
+
store.mask.dispose();
|
|
16161
|
+
chain.store = void 0;
|
|
16162
|
+
chain.uniforms.map.value = null;
|
|
16163
|
+
chain.uniforms.mask.value = null;
|
|
16164
|
+
}
|
|
16165
|
+
function restChain(chain, now) {
|
|
16166
|
+
switchOff(chain);
|
|
16167
|
+
if (now - chain.lastUsed > IDLE_DISPOSE_S * 1e3) disposeChain(chain);
|
|
16168
|
+
}
|
|
16169
|
+
var inverse = new Matrix4();
|
|
16170
|
+
var over = new Vector3();
|
|
16171
|
+
var anchor = new Vector3();
|
|
16172
|
+
function pointUnderCamera(mesh, camera) {
|
|
16173
|
+
return over.copy(camera.position).applyMatrix4(inverse.copy(mesh.matrixWorld).invert()).normalize();
|
|
16174
|
+
}
|
|
16175
|
+
function finestLevelAt$1(chain, tiles, over) {
|
|
16176
|
+
if (cameraChart(over.y, chain?.chart) !== 0) return tiles.max_level;
|
|
16177
|
+
const point = chartPoint(0, over.x, over.y, over.z);
|
|
16178
|
+
return deepestLevel(tiles.max_level, tiles.patches, point.x, point.y);
|
|
16179
|
+
}
|
|
16180
|
+
function chainLength(tiles, limit) {
|
|
16181
|
+
let finest = tiles.max_level;
|
|
16182
|
+
if (tiles.patches) for (const patch of tiles.patches) finest = Math.max(finest, patch.max_level);
|
|
16183
|
+
const levels = finest - 3 + 1;
|
|
16184
|
+
return Math.min(limit, 6, levels + 1);
|
|
16185
|
+
}
|
|
16186
|
+
function updateChain(chain, tiles, count, level, over, radius, frame, renderer, invalidate) {
|
|
16187
|
+
chain.lastUsed = performance.now();
|
|
16188
|
+
if (chain.store && chain.store.texture.image.depth !== count + 1) disposeChain(chain);
|
|
16189
|
+
const store = chain.store ??= createStore(chain.kind, tiles.tile_size, count + 1, renderer);
|
|
16190
|
+
chain.uniforms.map.value = store.texture;
|
|
16191
|
+
chain.uniforms.mask.value = store.mask;
|
|
16192
|
+
chain.uniforms.filter.value.set(store.texture.anisotropy, tiles.tile_size);
|
|
16193
|
+
const cap = over.y >= 0 ? 1 : 2;
|
|
16194
|
+
chain.chart = cameraChart(over.y, chain.chart);
|
|
16195
|
+
const links = windowChain(level, count, chain.chart, over.y, tiles.max_level);
|
|
16196
|
+
anchor.set(Math.fround(over.x * radius), Math.fround(over.y * radius), Math.fround(over.z * radius));
|
|
16197
|
+
chain.uniforms.anchor.value.copy(anchor);
|
|
16198
|
+
const range = anchor.length();
|
|
16199
|
+
const home = chartPoint(0, anchor.x / range, anchor.y / range, anchor.z / range);
|
|
16200
|
+
const held = (link) => store.windows.find((win) => win.chart === link.chart && win.level === link.level);
|
|
16201
|
+
store.windows = store.windows.filter((win) => {
|
|
16202
|
+
win.rank = links.findIndex((link) => link.chart === win.chart && link.level === win.level);
|
|
16203
|
+
if (win.rank >= 0) return true;
|
|
16204
|
+
win.rank = links.length;
|
|
16205
|
+
const heir = links.find((link) => link.level === win.level);
|
|
16206
|
+
const heirWindow = heir && held(heir);
|
|
16207
|
+
const near = win.chart === 0 || win.chart === cap;
|
|
16208
|
+
if (heir && near && !(heirWindow && settled(chain, store, tiles, heirWindow))) return true;
|
|
16209
|
+
release(store, win);
|
|
16210
|
+
return false;
|
|
16211
|
+
});
|
|
16212
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
16213
|
+
const link = links[i];
|
|
16214
|
+
let win = held(link);
|
|
16215
|
+
if (!win) {
|
|
16216
|
+
const layer = store.free.pop();
|
|
16217
|
+
if (layer === void 0) continue;
|
|
16218
|
+
win = {
|
|
16219
|
+
layer,
|
|
16220
|
+
...link,
|
|
16221
|
+
rank: i,
|
|
16222
|
+
wanted: new Array(SLOTS),
|
|
16223
|
+
pending: /* @__PURE__ */ new Map()
|
|
16224
|
+
};
|
|
16225
|
+
store.windows.push(win);
|
|
16226
|
+
}
|
|
16227
|
+
const point = chartPoint(link.chart, over.x, over.y, over.z);
|
|
16228
|
+
retarget(store, win, placeWindow(link.chart, link.level, point.x, point.y, win.placement), frame);
|
|
16229
|
+
pump(chain, store, tiles, win, point.x, point.y, renderer, invalidate);
|
|
16230
|
+
}
|
|
16231
|
+
store.windows.sort((a, b) => b.level - a.level || a.rank - b.rank);
|
|
16232
|
+
for (let i = 0; i < 7; i++) {
|
|
16233
|
+
const win = store.windows[i];
|
|
16234
|
+
const link = chain.uniforms.window.value[i];
|
|
16235
|
+
if (!win?.placement) {
|
|
16236
|
+
link.w = 0;
|
|
16237
|
+
continue;
|
|
16238
|
+
}
|
|
16239
|
+
chain.uniforms.grid.value[i].set(chartColumns(win.chart, win.level), chartRows(win.chart, win.level), win.placement.tx0, win.placement.ty0);
|
|
16240
|
+
if (win.chart === 0) {
|
|
16241
|
+
const from = windowHome(win.placement, home.x, home.y);
|
|
16242
|
+
chain.uniforms.home.value[i].set(from.x, from.y);
|
|
16243
|
+
}
|
|
16244
|
+
link.set(win.chart, win.layer, win.level, 1);
|
|
16245
|
+
let loaded = 0;
|
|
16246
|
+
for (let slot = 0; slot < SLOTS; slot++) if (isLoaded(store, win.layer, slot)) loaded |= 1 << slot;
|
|
16247
|
+
chain.uniforms.loaded.value[i] = loaded;
|
|
16248
|
+
}
|
|
16249
|
+
}
|
|
16250
|
+
|
|
16251
|
+
//#endregion
|
|
16252
|
+
//#region src/lib/scene/objects/surface/relief-tiles.ts
|
|
16253
|
+
var MARCH_REACH = 8;
|
|
16254
|
+
var STEP_PIXELS = 1.5;
|
|
16255
|
+
var REFINE_STEPS = 3;
|
|
16256
|
+
var STEEPEST = 1;
|
|
16257
|
+
var MIN_SUN_SIZE = .006;
|
|
16258
|
+
var SLOPE_GLSL = `
|
|
16259
|
+
uniform sampler2DArray uReliefMap;
|
|
16260
|
+
uniform sampler2DArray uReliefMask;
|
|
16261
|
+
uniform vec4 uReliefGrid[ ${7} ];
|
|
16262
|
+
uniform vec4 uReliefWindow[ ${7} ];
|
|
16263
|
+
uniform int uReliefLoaded[ ${7} ];
|
|
16264
|
+
uniform vec2 uReliefFilter;
|
|
16265
|
+
uniform float uReliefScale;
|
|
16266
|
+
${TILE_CHAIN_GLSL}
|
|
16267
|
+
// Each byte is filtered alone, and the sum of the two is the filtered height.
|
|
16268
|
+
float reliefValue( vec4 texel ) {
|
|
16269
|
+
return dot( texel.rg, vec2( 65280.0, 255.0 ) ) / 65535.0;
|
|
16270
|
+
}
|
|
16271
|
+
|
|
16272
|
+
// How the coordinates of each chart change along the sphere at a direction:
|
|
16273
|
+
// the dot product with a step along the sphere is that step on the chart.
|
|
16274
|
+
struct ReliefRates {
|
|
16275
|
+
vec3 mapX;
|
|
16276
|
+
vec3 mapY;
|
|
16277
|
+
vec3 capX;
|
|
16278
|
+
vec3 capY;
|
|
16279
|
+
};
|
|
16280
|
+
|
|
16281
|
+
ReliefRates reliefRates( vec3 dir, float side ) {
|
|
16282
|
+
ReliefRates rates;
|
|
16283
|
+
float flat2 = max( dir.x * dir.x + dir.z * dir.z, 1e-12 );
|
|
16284
|
+
rates.mapX = vec3( dir.z, 0.0, - dir.x ) / ( 2.0 * PI * flat2 );
|
|
16285
|
+
rates.mapY = ( dir * dir.y - vec3( 0.0, 1.0, 0.0 ) ) / ( PI * sqrt( flat2 ) );
|
|
16286
|
+
float fold = 1.0 + side * dir.y;
|
|
16287
|
+
float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
|
|
16288
|
+
vec2 plane = vec2( dir.x, side * dir.z ) * side / fold;
|
|
16289
|
+
rates.capX = vec3( 1.0, - plane.x, 0.0 ) * capScale;
|
|
16290
|
+
rates.capY = vec3( 0.0, - plane.y, side ) * capScale;
|
|
16291
|
+
rates.capX -= dir * dot( rates.capX, dir );
|
|
16292
|
+
rates.capY -= dir * dot( rates.capY, dir );
|
|
16293
|
+
return rates;
|
|
16294
|
+
}
|
|
16295
|
+
|
|
16296
|
+
// Slope of the ground at a direction of the body's frame: a vector along the
|
|
16297
|
+
// unit sphere that points uphill, as long as the rise over the run. Also
|
|
16298
|
+
// gives the part of the fragment that the tiles answer for, and the finest
|
|
16299
|
+
// window that does, or -1.
|
|
16300
|
+
vec3 reliefTileSlope( vec3 dir, TileCharts charts, out float covered, out int first ) {
|
|
16301
|
+
covered = 0.0;
|
|
16302
|
+
first = - 1;
|
|
16303
|
+
vec3 slope = vec3( 0.0 );
|
|
16304
|
+
if ( uReliefWindow[ 0 ].w < 0.5 ) return slope;
|
|
16305
|
+
ReliefRates rates = reliefRates( dir, charts.side );
|
|
16306
|
+
|
|
16307
|
+
float rows = ${4 .toFixed(1)};
|
|
16308
|
+
for ( int i = 0; i < ${7}; i ++ ) {
|
|
16309
|
+
vec4 window = uReliefWindow[ i ];
|
|
16310
|
+
if ( window.w < 0.5 ) break;
|
|
16311
|
+
vec4 grid = uReliefGrid[ i ];
|
|
16312
|
+
vec2 spot;
|
|
16313
|
+
vec2 major;
|
|
16314
|
+
vec2 minor;
|
|
16315
|
+
float alpha = tileWindowWeight( charts, window, grid, - grid.zw, uReliefFilter, spot, major, minor );
|
|
16316
|
+
if ( alpha <= 0.0 ) continue;
|
|
16317
|
+
// Filtered, the mask falls towards a missing tile. The pixels beside
|
|
16318
|
+
// one are left out: their slope would read its blank slot.
|
|
16319
|
+
alpha *= smoothstep( 0.51, 0.55, texture( uReliefMask, vec3( spot / rows, window.y ) ).r );
|
|
16320
|
+
if ( alpha <= 0.0 ) continue;
|
|
16321
|
+
if ( first < 0 ) first = i;
|
|
16322
|
+
|
|
16323
|
+
vec3 st = vec3( spot.x / rows, - spot.y / rows, window.y );
|
|
16324
|
+
vec2 texel = vec2( 1.0, - 1.0 ) / ( rows * uReliefFilter.y );
|
|
16325
|
+
vec2 gx = major * texel;
|
|
16326
|
+
vec2 gy = minor * texel;
|
|
16327
|
+
vec3 east = vec3( texel.x, 0.0, 0.0 );
|
|
16328
|
+
vec3 south = vec3( 0.0, texel.y, 0.0 );
|
|
16329
|
+
// Rise over two pixels of the window, then over the whole chart.
|
|
16330
|
+
vec2 rise = vec2(
|
|
16331
|
+
reliefValue( textureGrad( uReliefMap, st + east, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - east, gx, gy ) ),
|
|
16332
|
+
reliefValue( textureGrad( uReliefMap, st + south, gx, gy ) ) - reliefValue( textureGrad( uReliefMap, st - south, gx, gy ) )
|
|
16333
|
+
) * 0.5 * grid.xy * uReliefFilter.y;
|
|
16334
|
+
bool onMap = window.x < 0.5;
|
|
16335
|
+
vec3 uphill = rise.x * ( onMap ? rates.mapX : rates.capX ) + rise.y * ( onMap ? rates.mapY : rates.capY );
|
|
16336
|
+
slope += ( 1.0 - covered ) * alpha * uphill;
|
|
16337
|
+
covered += ( 1.0 - covered ) * alpha;
|
|
16338
|
+
if ( covered > 0.995 ) break;
|
|
16339
|
+
}
|
|
16340
|
+
return slope * uReliefScale;
|
|
16341
|
+
}
|
|
16342
|
+
`;
|
|
16343
|
+
function shadowGlsl(steps) {
|
|
16344
|
+
return `
|
|
16345
|
+
// One window as a shadow ray reads it. The ray is a line on the window's
|
|
16346
|
+
// chart, so where it is inside the window is worked out once, not at each
|
|
16347
|
+
// sample.
|
|
16348
|
+
struct ReliefSight {
|
|
16349
|
+
// The fragment in the window's texture, with its layer, and how far the
|
|
16350
|
+
// ray goes there in a radian.
|
|
16351
|
+
vec3 from;
|
|
16352
|
+
vec2 rate;
|
|
16353
|
+
// The part of the ray inside the window, in radians along it.
|
|
16354
|
+
float enters;
|
|
16355
|
+
float leaves;
|
|
16356
|
+
// The longest step that the window's pixels still hold.
|
|
16357
|
+
float widest;
|
|
16358
|
+
int loaded;
|
|
16359
|
+
};
|
|
16360
|
+
|
|
16361
|
+
ReliefSight reliefSight( int i, TileCharts charts, vec2 mapStep, vec2 capStep ) {
|
|
16362
|
+
vec4 window = uReliefWindow[ i ];
|
|
16363
|
+
vec4 grid = uReliefGrid[ i ];
|
|
16364
|
+
bool onMap = window.x < 0.5;
|
|
16365
|
+
float rows = ${4 .toFixed(1)};
|
|
16366
|
+
// In tiles from the window's north-west corner. The map goes round: the
|
|
16367
|
+
// fragment is taken on the side nearest the window.
|
|
16368
|
+
vec2 rel = ( onMap ? charts.mapAt : charts.capAt ) * grid.xy - grid.zw;
|
|
16369
|
+
if ( onMap ) rel.x = mod( rel.x - 0.5 * rows + 0.5 * grid.x, grid.x ) + 0.5 * rows - 0.5 * grid.x;
|
|
16370
|
+
vec2 rate = ( onMap ? mapStep : capStep ) * grid.xy;
|
|
16371
|
+
|
|
16372
|
+
// Half a pixel in from the window's sides: past it the filter reads the
|
|
16373
|
+
// tile of the far side.
|
|
16374
|
+
float inset = 0.5 / uReliefFilter.y;
|
|
16375
|
+
vec2 run = mix( vec2( 1e-20 ), rate, greaterThan( abs( rate ), vec2( 1e-20 ) ) );
|
|
16376
|
+
vec2 a = ( inset - rel ) / run;
|
|
16377
|
+
vec2 b = ( rows - inset - rel ) / run;
|
|
16378
|
+
vec2 near = min( a, b );
|
|
16379
|
+
vec2 far = max( a, b );
|
|
16380
|
+
|
|
16381
|
+
ReliefSight sight;
|
|
16382
|
+
sight.from = vec3( ( rel.x + grid.z ) / rows, - ( rel.y + grid.w ) / rows, window.y );
|
|
16383
|
+
sight.rate = vec2( rate.x, - rate.y ) / rows;
|
|
16384
|
+
sight.enters = max( near.x, near.y );
|
|
16385
|
+
sight.leaves = min( far.x, far.y );
|
|
16386
|
+
// The other pole's cap holds none of the ray.
|
|
16387
|
+
if ( ! onMap && ( window.x < 1.5 ) != ( charts.side > 0.0 ) ) sight.leaves = - 1.0;
|
|
16388
|
+
sight.widest = ${STEP_PIXELS.toFixed(2)} * PI / ( uReliefFilter.y * exp2( window.z ) );
|
|
16389
|
+
sight.loaded = uReliefLoaded[ i ];
|
|
16390
|
+
return sight;
|
|
16391
|
+
}
|
|
16392
|
+
|
|
16393
|
+
// Height of the ground 't' radians along the ray, as a part of the pyramid's
|
|
16394
|
+
// range. Negative where the window does not hold that ground.
|
|
16395
|
+
float reliefSightValue( ReliefSight sight, float t ) {
|
|
16396
|
+
if ( t < sight.enters || t > sight.leaves ) return - 1.0;
|
|
16397
|
+
vec2 st = sight.from.xy + t * sight.rate;
|
|
16398
|
+
// Both tiles that the filter can read must be in.
|
|
16399
|
+
if ( sight.loaded != ${(1 << 4 * 4) - 1} ) {
|
|
16400
|
+
vec2 tile = vec2( st.x, - st.y ) * ${4 .toFixed(1)};
|
|
16401
|
+
float inset = 0.5 / uReliefFilter.y;
|
|
16402
|
+
ivec2 a = ivec2( floor( tile - inset ) ) & ${4 - 1};
|
|
16403
|
+
ivec2 b = ivec2( floor( tile + inset ) ) & ${4 - 1};
|
|
16404
|
+
int both = ( sight.loaded >> ( a.x + ${4} * a.y ) ) & ( sight.loaded >> ( b.x + ${4} * b.y ) );
|
|
16405
|
+
if ( ( both & 1 ) == 0 ) return - 1.0;
|
|
16406
|
+
}
|
|
16407
|
+
return reliefValue( textureLod( uReliefMap, vec3( st, sight.from.z ), 0.0 ) );
|
|
16408
|
+
}
|
|
16409
|
+
|
|
16410
|
+
// Part of the Sun's disc that the ground leaves in sight of the fragment at
|
|
16411
|
+
// 'dir', or -1 where no window holds the fragment. The Sun is 'toSun' along
|
|
16412
|
+
// the sphere, 'tanElev' above it, and 'sunSize' is the radius of its disc.
|
|
16413
|
+
// 'first' is the fragment's window, as the slope gave it.
|
|
16414
|
+
//
|
|
16415
|
+
// A ray is marched towards the Sun. Each step is longer than the last by one
|
|
16416
|
+
// ratio, from a pixel of the fragment's window to the end of the ray, and is
|
|
16417
|
+
// read from the window whose pixels are about as wide as the step. A shadow
|
|
16418
|
+
// is then sharp beside what casts it, at a cost that does not grow with its
|
|
16419
|
+
// length.
|
|
16420
|
+
float reliefTileShadow( vec3 dir, TileCharts charts, vec3 toSun, float tanElev, float sunSize, int first ) {
|
|
16421
|
+
ReliefRates rates = reliefRates( dir, charts.side );
|
|
16422
|
+
vec2 mapStep = vec2( dot( rates.mapX, toSun ), dot( rates.mapY, toSun ) );
|
|
16423
|
+
vec2 capStep = vec2( dot( rates.capX, toSun ), dot( rates.capY, toSun ) );
|
|
16424
|
+
|
|
16425
|
+
int drawn = 0;
|
|
16426
|
+
for ( int i = 0; i < ${7}; i ++ ) {
|
|
16427
|
+
if ( uReliefWindow[ i ].w > 0.5 ) drawn = i + 1;
|
|
16428
|
+
}
|
|
16429
|
+
int w = first;
|
|
16430
|
+
ReliefSight sight;
|
|
16431
|
+
float own = - 1.0;
|
|
16432
|
+
for ( int i = 0; i < ${7}; i ++ ) {
|
|
16433
|
+
if ( i < first ) continue;
|
|
16434
|
+
if ( i >= drawn ) break;
|
|
16435
|
+
w = i;
|
|
16436
|
+
sight = reliefSight( i, charts, mapStep, capStep );
|
|
16437
|
+
own = reliefSightValue( sight, 0.0 );
|
|
16438
|
+
if ( own >= 0.0 ) break;
|
|
16439
|
+
}
|
|
16440
|
+
if ( own < 0.0 ) return - 1.0;
|
|
16441
|
+
int start = w;
|
|
16442
|
+
|
|
16443
|
+
// A ray rises above the sphere by its slope, and by the sphere's own
|
|
16444
|
+
// curve under it. Past where it clears the pyramid's range, nothing
|
|
16445
|
+
// can stand in it.
|
|
16446
|
+
float clear = sqrt( tanElev * tanElev + 2.0 * uReliefScale * ( 1.0 - own ) ) - tanElev;
|
|
16447
|
+
float reach = min( clear, ${MARCH_REACH.toFixed(1)} * uReliefScale );
|
|
16448
|
+
if ( reach < 1e-7 ) return 1.0;
|
|
16449
|
+
|
|
16450
|
+
// The ratio that takes steps from one pixel to the reach: 'rate' is its
|
|
16451
|
+
// logarithm times the count, and solves rate / ( exp( rate ) - 1 ) = part.
|
|
16452
|
+
float count = ${steps.toFixed(1)};
|
|
16453
|
+
float pixel = sight.widest / ${STEP_PIXELS.toFixed(2)};
|
|
16454
|
+
float part = count * pixel / reach;
|
|
16455
|
+
float rate = 0.0;
|
|
16456
|
+
if ( part < 1.0 ) {
|
|
16457
|
+
rate = - log( part );
|
|
16458
|
+
for ( int k = 0; k < 4; k ++ ) rate = log( 1.0 + rate / part );
|
|
16459
|
+
}
|
|
16460
|
+
// Each sample is the one before times the ratio, plus the first step.
|
|
16461
|
+
bool even = rate < 1e-3;
|
|
16462
|
+
float ratio = even ? 1.0 : exp( rate / count );
|
|
16463
|
+
float head = even ? reach / count : reach * ( ratio - 1.0 ) / ( exp( rate ) - 1.0 );
|
|
16464
|
+
|
|
16465
|
+
// How far the ground stands into the ray is counted in discs of the Sun.
|
|
16466
|
+
// The disc is an angle, and the ray's rise is a slope.
|
|
16467
|
+
float perDisc = 1.0 / ( max( sunSize, ${MIN_SUN_SIZE.toFixed(4)} ) * ( 1.0 + tanElev * tanElev ) );
|
|
16468
|
+
float t = 0.0;
|
|
16469
|
+
float hidden = - 1e9;
|
|
16470
|
+
// The sample that hides the most, the step around it and its window.
|
|
16471
|
+
float worst = 0.0;
|
|
16472
|
+
float around = 0.0;
|
|
16473
|
+
int worstWindow = w;
|
|
16474
|
+
for ( int i = 1; i <= ${steps}; i ++ ) {
|
|
16475
|
+
float before = t;
|
|
16476
|
+
t = t * ratio + head;
|
|
16477
|
+
if ( w + 1 < drawn && t - before > sight.widest ) {
|
|
16478
|
+
w ++;
|
|
16479
|
+
sight = reliefSight( w, charts, mapStep, capStep );
|
|
16480
|
+
}
|
|
16481
|
+
float value = reliefSightValue( sight, t );
|
|
16482
|
+
// The ray left this window, or a tile of it is missing.
|
|
16483
|
+
if ( value < 0.0 && w + 1 < drawn ) {
|
|
16484
|
+
w ++;
|
|
16485
|
+
sight = reliefSight( w, charts, mapStep, capStep );
|
|
16486
|
+
value = reliefSightValue( sight, t );
|
|
16487
|
+
}
|
|
16488
|
+
if ( value < 0.0 ) continue;
|
|
16489
|
+
float stands = ( ( value - own ) * uReliefScale / t - tanElev - 0.5 * t ) * perDisc;
|
|
16490
|
+
if ( stands > hidden ) {
|
|
16491
|
+
hidden = stands;
|
|
16492
|
+
worst = t;
|
|
16493
|
+
around = t - before;
|
|
16494
|
+
worstWindow = w;
|
|
16495
|
+
// The whole disc is hidden.
|
|
16496
|
+
if ( hidden >= 1.0 ) break;
|
|
16497
|
+
}
|
|
16498
|
+
}
|
|
16499
|
+
|
|
16500
|
+
// The crest that hides the most lies between two samples, and far out
|
|
16501
|
+
// those are far apart. Look for it around the best one, each time at half
|
|
16502
|
+
// the distance and in a finer window. Not where the best one is so clear
|
|
16503
|
+
// of the ray that no crest beside it can reach the Sun's disc.
|
|
16504
|
+
float crest = ${(STEEPEST * .5).toFixed(2)} * around / max( worst, 1e-9 ) * perDisc;
|
|
16505
|
+
if ( hidden < 1.0 && hidden + crest > - 1.0 ) {
|
|
16506
|
+
w = worstWindow;
|
|
16507
|
+
sight = reliefSight( w, charts, mapStep, capStep );
|
|
16508
|
+
for ( int k = 0; k < ${REFINE_STEPS}; k ++ ) {
|
|
16509
|
+
around *= 0.5;
|
|
16510
|
+
if ( w > start && around < sight.widest ) {
|
|
16511
|
+
w --;
|
|
16512
|
+
sight = reliefSight( w, charts, mapStep, capStep );
|
|
16513
|
+
}
|
|
16514
|
+
float centre = worst;
|
|
16515
|
+
for ( int e = 0; e < 2; e ++ ) {
|
|
16516
|
+
float at = centre + ( e == 0 ? - around : around );
|
|
16517
|
+
float value = reliefSightValue( sight, at );
|
|
16518
|
+
if ( value < 0.0 ) continue;
|
|
16519
|
+
float stands = ( ( value - own ) * uReliefScale / at - tanElev - 0.5 * at ) * perDisc;
|
|
16520
|
+
if ( stands > hidden ) {
|
|
16521
|
+
hidden = stands;
|
|
16522
|
+
worst = at;
|
|
16523
|
+
}
|
|
16524
|
+
}
|
|
16525
|
+
}
|
|
16526
|
+
}
|
|
16527
|
+
return 1.0 - smoothstep( - 1.0, 1.0, hidden );
|
|
16528
|
+
}
|
|
16529
|
+
`;
|
|
16530
|
+
}
|
|
16531
|
+
function reliefTilesGlsl(steps) {
|
|
16532
|
+
return steps > 0 ? SLOPE_GLSL + shadowGlsl(steps) : SLOPE_GLSL;
|
|
16533
|
+
}
|
|
16534
|
+
async function loadTile(url, signal) {
|
|
16535
|
+
const { bytes, size } = await loadHeightTile(url, signal);
|
|
16536
|
+
const texture = new DataTexture(bytes, size, size, RGFormat, UnsignedByteType);
|
|
16537
|
+
return {
|
|
16538
|
+
texture,
|
|
16539
|
+
release: () => texture.dispose()
|
|
16540
|
+
};
|
|
16541
|
+
}
|
|
16542
|
+
var HEIGHT_TILES = {
|
|
16543
|
+
format: RGFormat,
|
|
16544
|
+
colorSpace: NoColorSpace,
|
|
16545
|
+
load: loadTile
|
|
16546
|
+
};
|
|
16547
|
+
function reliefTilesAvailable(tiles) {
|
|
16548
|
+
return !!tiles && tiles.scale_km !== void 0 && tiles.max_level >= 3 && tilesBase() !== "" && maxReliefWindows() > 0 && textureAllowed(tiles.distribution);
|
|
16549
|
+
}
|
|
16550
|
+
function reliefTilesOf(material, tiles, radiusKm) {
|
|
16551
|
+
if (!reliefTilesAvailable(tiles)) return void 0;
|
|
16552
|
+
const held = material.userData.reliefTiles;
|
|
16553
|
+
if (held) return held;
|
|
16554
|
+
const chain = createChain(HEIGHT_TILES);
|
|
16555
|
+
const relief = {
|
|
16556
|
+
tiles,
|
|
16557
|
+
chain,
|
|
16558
|
+
shadowSteps: reliefShadowSteps(),
|
|
16559
|
+
uniforms: {
|
|
16560
|
+
uReliefMap: chain.uniforms.map,
|
|
16561
|
+
uReliefMask: chain.uniforms.mask,
|
|
16562
|
+
uReliefGrid: chain.uniforms.grid,
|
|
16563
|
+
uReliefWindow: chain.uniforms.window,
|
|
16564
|
+
uReliefLoaded: chain.uniforms.loaded,
|
|
16565
|
+
uReliefFilter: chain.uniforms.filter,
|
|
16566
|
+
uReliefScale: { value: (tiles.scale_km ?? 0) / radiusKm }
|
|
16567
|
+
}
|
|
16568
|
+
};
|
|
16569
|
+
material.userData.reliefTiles = relief;
|
|
16570
|
+
return relief;
|
|
16571
|
+
}
|
|
16572
|
+
function reliefTilesServe(bo) {
|
|
16573
|
+
return !!bo.reliefTiles && !bo.reliefTiles.chain.dead;
|
|
16574
|
+
}
|
|
16575
|
+
function disposeReliefTiles(bo) {
|
|
16576
|
+
if (bo.reliefTiles) disposeChain(bo.reliefTiles.chain);
|
|
16577
|
+
}
|
|
16578
|
+
function updateReliefTiles(bo, camera, renderer, projScale, invalidate) {
|
|
16579
|
+
const relief = bo.reliefTiles;
|
|
16580
|
+
if (!relief) return;
|
|
16581
|
+
const mesh = bo.mesh;
|
|
16582
|
+
if (mesh && relief.shadowSteps !== reliefShadowSteps()) {
|
|
16583
|
+
relief.shadowSteps = reliefShadowSteps();
|
|
16584
|
+
mesh.material.needsUpdate = true;
|
|
16585
|
+
}
|
|
16586
|
+
const level = mesh && bo.selfShadow && !relief.chain.dead ? neededLevel(bo.radiusScene, bo.cachedDist, projScale, relief.tiles.max_level) : -1;
|
|
16587
|
+
if (!mesh || level < 3) {
|
|
16588
|
+
restChain(relief.chain, performance.now());
|
|
16589
|
+
return;
|
|
16590
|
+
}
|
|
16591
|
+
const levels = relief.tiles.max_level - 3 + 1;
|
|
16592
|
+
updateChain(relief.chain, relief.tiles, Math.min(maxReliefWindows(), levels), level, pointUnderCamera(mesh, camera), bo.radiusScene, void 0, renderer, invalidate);
|
|
16593
|
+
}
|
|
16594
|
+
|
|
16595
|
+
//#endregion
|
|
16596
|
+
//#region src/lib/scene/objects/surface/self-shadow.ts
|
|
16597
|
+
var STEPS = 32;
|
|
16598
|
+
var NORMAL_STRENGTH = 1;
|
|
16599
|
+
var GRADIENT_STENCIL = 2;
|
|
16600
|
+
var SLOPE_CLAMP = 1.5;
|
|
16601
|
+
var SHADOW_REACH = 4;
|
|
16602
|
+
var SHADOW_SOFT = .04;
|
|
16603
|
+
var TERMINATOR_GATE_LIT = .02;
|
|
16604
|
+
var TERMINATOR_GATE_DARK = -.08;
|
|
16605
|
+
var MARCH_GRAZE_FADE = .18;
|
|
16606
|
+
var TILE_LIGHT = `
|
|
16607
|
+
if (byTiles > 0.005 && dot(normal, mat3(viewMatrix) * L) > 0.0) {
|
|
16608
|
+
vec3 sunward = normalize(L * mat3(modelMatrix));
|
|
16609
|
+
float up = dot(sunward, selfDir);
|
|
16610
|
+
vec3 along = sunward - selfDir * up;
|
|
16611
|
+
float run = length(along);
|
|
16612
|
+
if (run > 1e-4) tileLight = reliefTileShadow(selfDir, selfCharts, along / run, up / run, uSelfSunSize, selfFirst);
|
|
16613
|
+
if (tileLight < 0.0) byTiles = 0.0;
|
|
16614
|
+
}`;
|
|
16615
|
+
function attachSelfShadowToBody(material, heightMap, scaleScene, sunDir, tiles) {
|
|
16616
|
+
const tex = heightMap.image;
|
|
16617
|
+
const texel = new Vector2(1 / (tex?.width ?? 4096), 1 / (tex?.height ?? 2048));
|
|
16618
|
+
const existing = material.userData.selfShadow;
|
|
16619
|
+
if (existing) {
|
|
16620
|
+
existing.uSelfHeightMap.value = heightMap;
|
|
16621
|
+
existing.uSelfScale.value = scaleScene;
|
|
16622
|
+
existing.uSelfTexel.value.copy(texel);
|
|
16623
|
+
return existing;
|
|
16624
|
+
}
|
|
16625
|
+
const uniforms = {
|
|
16626
|
+
uSelfHeightMap: { value: heightMap },
|
|
16627
|
+
uSelfScale: { value: scaleScene },
|
|
16628
|
+
uSelfTexel: { value: texel }
|
|
16629
|
+
};
|
|
16630
|
+
material.userData.selfShadow = uniforms;
|
|
16631
|
+
const sun = sunDir ?? getEclipseSceneUniforms().uSunDir;
|
|
16632
|
+
chainShaderHook(material, tiles ? () => `selfShadowTiles${reliefShadowSteps()}` : "selfShadow", (shader) => {
|
|
16633
|
+
const steps = tiles ? reliefShadowSteps() : 0;
|
|
16634
|
+
Object.assign(shader.uniforms, uniforms, { uSelfSunDir: sun }, tiles?.uniforms);
|
|
16635
|
+
if (tiles) shader.uniforms.uSelfSunSize = getEclipseSceneUniforms().uSunAngularRadius;
|
|
16636
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", `#include <common>
|
|
16637
|
+
varying vec3 vSelfWorldNormal;
|
|
16638
|
+
varying vec2 vSelfUv;
|
|
16639
|
+
varying vec3 vSelfDir;
|
|
16640
|
+
varying vec3 vSelfEastW;
|
|
16641
|
+
varying vec3 vSelfNorthW;
|
|
16642
|
+
varying float vSelfRadiusW;
|
|
16643
|
+
varying float vSelfCosLat;`).replace("#include <beginnormal_vertex>", `#include <beginnormal_vertex>
|
|
16644
|
+
vSelfWorldNormal = normalize(mat3(modelMatrix) * objectNormal);
|
|
16645
|
+
vSelfUv = uv;
|
|
16646
|
+
vSelfDir = position;
|
|
16647
|
+
// Analytic equirect tangent basis: smooth per fragment, so the
|
|
16648
|
+
// relief normal doesn't inherit mesh facets.
|
|
16649
|
+
vec3 nObj = normalize(position);
|
|
16650
|
+
vec3 ec = cross(vec3(0.0, 1.0, 0.0), nObj);
|
|
16651
|
+
float cl = length(ec);
|
|
16652
|
+
vec3 eastObj = cl > 1e-5 ? ec / cl : vec3(1.0, 0.0, 0.0);
|
|
16653
|
+
vSelfEastW = normalize(mat3(modelMatrix) * eastObj);
|
|
16654
|
+
vSelfNorthW = normalize(mat3(modelMatrix) * cross(nObj, eastObj));
|
|
16655
|
+
vSelfRadiusW = length(mat3(modelMatrix) * position);
|
|
16656
|
+
vSelfCosLat = cl;`);
|
|
16657
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
16658
|
+
#define SELF_STEPS ${STEPS}
|
|
16659
|
+
uniform sampler2D uSelfHeightMap;
|
|
16660
|
+
uniform float uSelfScale;
|
|
16661
|
+
uniform vec2 uSelfTexel;
|
|
16662
|
+
uniform vec3 uSelfSunDir;
|
|
16663
|
+
uniform mat3 normalMatrix;
|
|
16664
|
+
${tiles ? "uniform mat4 modelMatrix;\n uniform float uSelfSunSize;" : ""}
|
|
16665
|
+
varying vec3 vSelfWorldNormal;
|
|
16666
|
+
varying vec2 vSelfUv;
|
|
16667
|
+
varying vec3 vSelfDir;
|
|
16668
|
+
varying vec3 vSelfEastW;
|
|
16669
|
+
varying vec3 vSelfNorthW;
|
|
16670
|
+
varying float vSelfRadiusW;
|
|
16671
|
+
varying float vSelfCosLat;
|
|
16672
|
+
${tiles ? reliefTilesGlsl(steps) : ""}
|
|
16673
|
+
|
|
16674
|
+
float selfHeight(vec2 uv) {
|
|
16675
|
+
return texture2D(uSelfHeightMap, uv).r * uSelfScale;
|
|
16676
|
+
}
|
|
16677
|
+
|
|
16678
|
+
// Slope of the whole-globe height map at a direction of the
|
|
16679
|
+
// body's frame: a vector along the unit sphere that points
|
|
16680
|
+
// uphill, as long as the rise over the run.
|
|
16681
|
+
vec3 selfMapSlope(vec3 dir) {
|
|
16682
|
+
float cosLat = sqrt(max(1.0 - dir.y * dir.y, 0.0));
|
|
16683
|
+
if (cosLat < 1e-5) return vec3(0.0);
|
|
16684
|
+
vec3 east = vec3(dir.z, 0.0, -dir.x) / cosLat;
|
|
16685
|
+
vec3 north = cross(dir, east);
|
|
16686
|
+
// Wider-than-one-texel stencil averages out 8-bit terracing, floored
|
|
16687
|
+
// at the low tier's texel so a higher-res map keeps the same
|
|
16688
|
+
// physical smoothing width instead of flattening the relief.
|
|
16689
|
+
vec2 d = max(uSelfTexel, vec2(1.0 / 2048.0, 1.0 / 1024.0)) * ${GRADIENT_STENCIL.toFixed(3)};
|
|
16690
|
+
float hu = selfHeight(vSelfUv + vec2(d.x, 0.0))
|
|
16691
|
+
- selfHeight(vSelfUv - vec2(d.x, 0.0));
|
|
16692
|
+
float hv = selfHeight(vSelfUv + vec2(0.0, d.y))
|
|
16693
|
+
- selfHeight(vSelfUv - vec2(0.0, d.y));
|
|
16694
|
+
// The map's u runs east over 2π·cos(lat) radii, its v north over π radii.
|
|
16695
|
+
float su = hu / (2.0 * d.x * 6.2831853 * cosLat * vSelfRadiusW);
|
|
16696
|
+
float sv = hv / (2.0 * d.y * 3.14159265 * vSelfRadiusW);
|
|
16697
|
+
// Fade at the poles, where the columns of the map close in and
|
|
16698
|
+
// blow the east-west slope up into a starburst.
|
|
16699
|
+
float gain = ${NORMAL_STRENGTH.toFixed(3)} * smoothstep(0.0, 0.12, cosLat);
|
|
16700
|
+
su = clamp(su * gain, -${SLOPE_CLAMP.toFixed(3)}, ${SLOPE_CLAMP.toFixed(3)});
|
|
16701
|
+
sv = clamp(sv * gain, -${SLOPE_CLAMP.toFixed(3)}, ${SLOPE_CLAMP.toFixed(3)});
|
|
16702
|
+
return east * su + north * sv;
|
|
16703
|
+
}
|
|
16704
|
+
|
|
16705
|
+
// Analytic equirect frame for the shadow march: Tu=∂pos/∂u runs east and
|
|
16706
|
+
// Tv=∂pos/∂v north, as the map's v does. |Tu|=2πR·cos(lat), |Tv|=πR.
|
|
16707
|
+
void selfTangents(out vec3 Tu, out vec3 Tv) {
|
|
16708
|
+
float R = vSelfRadiusW;
|
|
16709
|
+
Tu = vSelfEastW * (6.2831853 * R * vSelfCosLat);
|
|
16710
|
+
Tv = vSelfNorthW * (3.14159265 * R);
|
|
16711
|
+
}
|
|
16712
|
+
|
|
16713
|
+
// Part of the Sun's light that the ground of the whole-globe map
|
|
16714
|
+
// lets through, for a Sun towards L that is gN above the sphere.
|
|
16715
|
+
float selfMapLight(vec3 L, float gN) {
|
|
16716
|
+
vec3 Tu, Tv;
|
|
16717
|
+
selfTangents(Tu, Tv);
|
|
16718
|
+
float lenTu = length(Tu);
|
|
16719
|
+
float lenTv = length(Tv);
|
|
16720
|
+
if (lenTu <= 1e-12 || lenTv <= 1e-12) return 1.0;
|
|
16721
|
+
float lu = dot(L, Tu / lenTu);
|
|
16722
|
+
float lv = dot(L, Tv / lenTv);
|
|
16723
|
+
float lh = sqrt(lu * lu + lv * lv);
|
|
16724
|
+
if (lh <= 1e-4) return 1.0;
|
|
16725
|
+
float ds = ${SHADOW_REACH.toFixed(3)} * uSelfScale / float(SELF_STEPS);
|
|
16726
|
+
// World step → uv step via the per-axis metric (|Tu|, |Tv|).
|
|
16727
|
+
vec2 stepUv = ds * vec2((lu / lh) / lenTu, (lv / lh) / lenTv);
|
|
16728
|
+
float h0 = selfHeight(vSelfUv);
|
|
16729
|
+
float tanElev = gN / lh;
|
|
16730
|
+
float block = 0.0;
|
|
16731
|
+
for (int i = 1; i <= SELF_STEPS; i++) {
|
|
16732
|
+
vec2 uv = vSelfUv + float(i) * stepUv;
|
|
16733
|
+
float rayH = h0 + float(i) * ds * tanElev;
|
|
16734
|
+
block = max(block, selfHeight(uv) - rayH);
|
|
16735
|
+
}
|
|
16736
|
+
// Fade the march out at grazing angles: a near-horizontal ray
|
|
16737
|
+
// occludes every away-facing slope, banding the terminator.
|
|
16738
|
+
float marchFade = smoothstep(0.0, ${MARCH_GRAZE_FADE.toFixed(3)}, gN);
|
|
16739
|
+
return 1.0 - marchFade * clamp(block / (${SHADOW_SOFT.toFixed(3)} * uSelfScale), 0.0, 1.0);
|
|
16740
|
+
}`).replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
|
|
16741
|
+
vec3 selfDir = normalize(vSelfDir);
|
|
16742
|
+
// The part of the fragment that the tiles answer for.
|
|
16743
|
+
float selfCovered = 0.0;
|
|
16744
|
+
${tiles ? "TileCharts selfCharts = tileCharts(selfDir);\n int selfFirst = -1;" : ""}
|
|
16745
|
+
{
|
|
16746
|
+
vec3 slope = vec3(0.0);
|
|
16747
|
+
${tiles ? "slope = reliefTileSlope(selfDir, selfCharts, selfCovered, selfFirst);" : ""}
|
|
16748
|
+
if (selfCovered < 0.995 && uSelfScale > 0.0) slope += (1.0 - selfCovered) * selfMapSlope(selfDir);
|
|
16749
|
+
float steep = length(slope);
|
|
16750
|
+
if (steep > ${SLOPE_CLAMP.toFixed(3)}) slope *= ${SLOPE_CLAMP.toFixed(3)} / steep;
|
|
16751
|
+
normal = normalize(normalMatrix * (selfDir - slope));
|
|
16752
|
+
}`).replace("#include <lights_fragment_end>", `#include <lights_fragment_end>
|
|
16753
|
+
{
|
|
16754
|
+
vec3 Nw = normalize(vSelfWorldNormal);
|
|
16755
|
+
vec3 L = normalize(uSelfSunDir);
|
|
16756
|
+
float gN = dot(L, Nw);
|
|
16757
|
+
// Gate on the GEOMETRIC normal: the relief normal can tilt sunward
|
|
16758
|
+
// past the terminator and light the night side. Fading direct light
|
|
16759
|
+
// by geometric N·L keeps the night dark regardless.
|
|
16760
|
+
float shadow = smoothstep(${TERMINATOR_GATE_DARK.toFixed(3)}, ${TERMINATOR_GATE_LIT.toFixed(3)}, gN);
|
|
16761
|
+
// Local crater self-shadow, lit hemisphere only — the macro gate owns
|
|
16762
|
+
// the night side; the short march can't see that curvature.
|
|
16763
|
+
if (shadow > 0.0 && gN > 0.0) {
|
|
16764
|
+
// The part of the fragment whose shadow the tiles cast.
|
|
16765
|
+
float byTiles = ${steps > 0 ? "selfCovered" : "0.0"};
|
|
16766
|
+
float tileLight = 1.0;
|
|
16767
|
+
${steps > 0 ? TILE_LIGHT : ""}
|
|
16768
|
+
float mapLight = 1.0;
|
|
16769
|
+
if (byTiles < 0.995 && uSelfScale > 0.0) mapLight = selfMapLight(L, gN);
|
|
16770
|
+
shadow *= mix(mapLight, tileLight, byTiles);
|
|
16771
|
+
}
|
|
16772
|
+
reflectedLight.directDiffuse *= shadow;
|
|
16773
|
+
reflectedLight.directSpecular *= shadow;
|
|
16774
|
+
}`);
|
|
16775
|
+
});
|
|
16776
|
+
return uniforms;
|
|
16777
|
+
}
|
|
16778
|
+
function detachSelfShadow(uniforms) {
|
|
16779
|
+
if (!uniforms) return;
|
|
16780
|
+
uniforms.uSelfHeightMap.value = null;
|
|
16781
|
+
uniforms.uSelfScale.value = 0;
|
|
16782
|
+
}
|
|
16783
|
+
|
|
16784
|
+
//#endregion
|
|
16785
|
+
//#region src/lib/scene/objects/surface/tile-overlay.ts
|
|
16786
|
+
var PARS = `
|
|
16787
|
+
varying vec3 vTileOff;
|
|
16788
|
+
uniform sampler2DArray uTileMap;
|
|
16789
|
+
uniform sampler2DArray uTileMask;
|
|
16790
|
+
uniform vec4 uTileGrid[ ${7} ];
|
|
16791
|
+
uniform vec4 uTileWindow[ ${7} ];
|
|
16792
|
+
uniform vec2 uTileHome[ ${7} ];
|
|
16793
|
+
uniform vec3 uTileAnchor;
|
|
16794
|
+
uniform vec2 uTileFilter;
|
|
16795
|
+
${TILE_CHAIN_GLSL}
|
|
16796
|
+
vec3 tileColour( vec3 below ) {
|
|
16797
|
+
if ( uTileWindow[ 0 ].w < 0.5 ) return below;
|
|
16798
|
+
TileCharts charts = tileChartsFrom( uTileAnchor, vTileOff );
|
|
16799
|
+
|
|
16800
|
+
float rows = ${4 .toFixed(1)};
|
|
16801
|
+
vec3 colour = vec3( 0.0 );
|
|
16802
|
+
float covered = 0.0;
|
|
16803
|
+
for ( int i = 0; i < ${7}; i ++ ) {
|
|
16804
|
+
vec4 window = uTileWindow[ i ];
|
|
16805
|
+
if ( window.w < 0.5 ) break;
|
|
16806
|
+
vec2 spot;
|
|
16807
|
+
vec2 major;
|
|
16808
|
+
vec2 minor;
|
|
16809
|
+
float alpha = tileWindowWeight( charts, window, uTileGrid[ i ], uTileHome[ i ], uTileFilter, spot, major, minor );
|
|
16810
|
+
if ( alpha <= 0.0 ) continue;
|
|
16811
|
+
ivec3 slot = ivec3( int( mod( spot.x, rows ) ), int( mod( spot.y, rows ) ), int( window.y ) );
|
|
16812
|
+
alpha *= texelFetch( uTileMask, slot, 0 ).r;
|
|
16813
|
+
if ( alpha <= 0.0 ) continue;
|
|
16814
|
+
|
|
16815
|
+
vec3 st = vec3( spot.x / rows, - spot.y / rows, window.y );
|
|
16816
|
+
vec2 texel = vec2( 1.0, - 1.0 ) / ( rows * uTileFilter.y );
|
|
16817
|
+
colour += ( 1.0 - covered ) * alpha * textureGrad( uTileMap, st, major * texel, minor * texel ).rgb;
|
|
16818
|
+
covered += ( 1.0 - covered ) * alpha;
|
|
16819
|
+
if ( covered > 0.995 ) break;
|
|
16820
|
+
}
|
|
16821
|
+
return colour + ( 1.0 - covered ) * below;
|
|
16822
|
+
}
|
|
16823
|
+
`;
|
|
16824
|
+
var MAP_FRAGMENT = `
|
|
16825
|
+
#ifdef USE_MAP
|
|
16826
|
+
vec4 sampledDiffuseColor = texture2D( map, vMapUv );
|
|
16827
|
+
sampledDiffuseColor.rgb = tileColour( sampledDiffuseColor.rgb );
|
|
16828
|
+
diffuseColor *= sampledDiffuseColor;
|
|
16829
|
+
#endif
|
|
16830
|
+
`;
|
|
16831
|
+
async function loadColourTile(url, signal) {
|
|
16832
|
+
const response = await fetch(url, { signal });
|
|
16833
|
+
if (!response.ok) throw new Error(`tile ${response.status}`);
|
|
16834
|
+
const bitmap = await createImageBitmap(await response.blob(), {
|
|
16835
|
+
imageOrientation: "flipY",
|
|
16836
|
+
premultiplyAlpha: "none",
|
|
16837
|
+
colorSpaceConversion: "none"
|
|
16838
|
+
});
|
|
16839
|
+
const texture = new Texture(bitmap);
|
|
16840
|
+
texture.colorSpace = SRGBColorSpace;
|
|
16841
|
+
texture.flipY = false;
|
|
16842
|
+
texture.generateMipmaps = false;
|
|
16843
|
+
return {
|
|
16844
|
+
texture,
|
|
16845
|
+
release() {
|
|
16846
|
+
texture.dispose();
|
|
16847
|
+
bitmap.close();
|
|
16848
|
+
}
|
|
16849
|
+
};
|
|
16097
16850
|
}
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16851
|
+
var COLOUR_TILES = {
|
|
16852
|
+
format: RGBAFormat,
|
|
16853
|
+
colorSpace: SRGBColorSpace,
|
|
16854
|
+
load: loadColourTile
|
|
16855
|
+
};
|
|
16856
|
+
function createOverlay(material) {
|
|
16857
|
+
const chain = createChain(COLOUR_TILES);
|
|
16858
|
+
chainShaderHook(material, "surfaceTiles", (shader) => {
|
|
16859
|
+
Object.assign(shader.uniforms, {
|
|
16860
|
+
uTileMap: chain.uniforms.map,
|
|
16861
|
+
uTileMask: chain.uniforms.mask,
|
|
16862
|
+
uTileGrid: chain.uniforms.grid,
|
|
16863
|
+
uTileWindow: chain.uniforms.window,
|
|
16864
|
+
uTileHome: chain.uniforms.home,
|
|
16865
|
+
uTileAnchor: chain.uniforms.anchor,
|
|
16866
|
+
uTileFilter: chain.uniforms.filter
|
|
16867
|
+
});
|
|
16868
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nvarying vec3 vTileOff;\nuniform vec3 uTileAnchor;").replace("#include <begin_vertex>", "#include <begin_vertex>\nvTileOff = position - uTileAnchor;");
|
|
16869
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <map_pars_fragment>", `#include <map_pars_fragment>\n${PARS}`).replace("#include <map_fragment>", MAP_FRAGMENT);
|
|
16870
|
+
});
|
|
16871
|
+
return {
|
|
16872
|
+
material,
|
|
16873
|
+
chain
|
|
16874
|
+
};
|
|
16101
16875
|
}
|
|
16102
16876
|
function disposeTileOverlay(bo) {
|
|
16103
|
-
|
|
16104
|
-
if (!overlay) return;
|
|
16105
|
-
switchOff(overlay);
|
|
16106
|
-
const store = overlay.store;
|
|
16107
|
-
if (!store) return;
|
|
16108
|
-
for (const win of store.windows) for (const { abort } of win.pending.values()) abort.abort();
|
|
16109
|
-
store.texture.dispose();
|
|
16110
|
-
store.mask.dispose();
|
|
16111
|
-
overlay.store = void 0;
|
|
16112
|
-
overlay.uniforms.uTileMap.value = null;
|
|
16113
|
-
overlay.uniforms.uTileMask.value = null;
|
|
16877
|
+
if (bo.tileOverlay) disposeChain(bo.tileOverlay.chain);
|
|
16114
16878
|
}
|
|
16115
16879
|
function tilesServe(bo) {
|
|
16116
16880
|
const tiles = bo.surfaceTiles;
|
|
16117
|
-
return !!tiles && !bo.model && tiles.max_level >= 3 && tilesBase() !== "" && maxTileWindows() > 0 && !bo.tileOverlay?.dead && textureAllowed(tiles.distribution);
|
|
16881
|
+
return !!tiles && !bo.model && tiles.max_level >= 3 && tilesBase() !== "" && maxTileWindows() > 0 && !bo.tileOverlay?.chain.dead && textureAllowed(tiles.distribution);
|
|
16118
16882
|
}
|
|
16119
|
-
var inverse = new Matrix4();
|
|
16120
|
-
var over = new Vector3();
|
|
16121
16883
|
function updateTileOverlay(bo, camera, renderer, projScale, drawable, invalidate) {
|
|
16122
16884
|
const tiles = bo.surfaceTiles;
|
|
16123
16885
|
const mesh = bo.mesh;
|
|
16124
16886
|
const material = mesh?.material;
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
if (
|
|
16128
|
-
|
|
16887
|
+
let over;
|
|
16888
|
+
let level = -1;
|
|
16889
|
+
if (tiles && mesh && material?.map && drawable && tilesServe(bo)) {
|
|
16890
|
+
over = pointUnderCamera(mesh, camera);
|
|
16891
|
+
const finest = finestLevelAt$1(bo.tileOverlay?.chain, tiles, over);
|
|
16892
|
+
level = neededLevel(bo.radiusScene, bo.cachedDist, projScale, finest);
|
|
16893
|
+
}
|
|
16894
|
+
if (!tiles || !over || !material || level < 3) {
|
|
16895
|
+
if (bo.tileOverlay) restChain(bo.tileOverlay.chain, performance.now());
|
|
16129
16896
|
return;
|
|
16130
16897
|
}
|
|
16131
16898
|
if (bo.tileOverlay && bo.tileOverlay.material !== material) {
|
|
16132
16899
|
disposeTileOverlay(bo);
|
|
16133
16900
|
bo.tileOverlay = void 0;
|
|
16134
16901
|
}
|
|
16135
|
-
|
|
16136
|
-
overlay.lastUsed = now;
|
|
16137
|
-
const levels = tiles.max_level - 3 + 1;
|
|
16138
|
-
const count = Math.min(maxTileWindows(), MAX_WINDOWS, levels + 1);
|
|
16139
|
-
if (overlay.store && overlay.store.texture.image.depth !== count + 1) disposeTileOverlay(bo);
|
|
16140
|
-
const store = overlay.store ??= createStore(tiles.tile_size, count + 1, renderer);
|
|
16141
|
-
overlay.uniforms.uTileMap.value = store.texture;
|
|
16142
|
-
overlay.uniforms.uTileMask.value = store.mask;
|
|
16143
|
-
overlay.uniforms.uTileFilter.value.set(store.texture.anisotropy, tiles.tile_size);
|
|
16144
|
-
over.copy(camera.position).applyMatrix4(inverse.copy(mesh.matrixWorld).invert()).normalize();
|
|
16145
|
-
const cap = over.y >= 0 ? 1 : 2;
|
|
16146
|
-
overlay.chart = cameraChart(over.y, overlay.chart);
|
|
16147
|
-
const chain = windowChain(level, count, overlay.chart, over.y);
|
|
16148
|
-
const frame = tiles.frames ? bo.textureFrame : void 0;
|
|
16149
|
-
const held = (link) => store.windows.find((win) => win.chart === link.chart && win.level === link.level);
|
|
16150
|
-
store.windows = store.windows.filter((win) => {
|
|
16151
|
-
win.rank = chain.findIndex((link) => link.chart === win.chart && link.level === win.level);
|
|
16152
|
-
if (win.rank >= 0) return true;
|
|
16153
|
-
win.rank = chain.length;
|
|
16154
|
-
const heir = chain.find((link) => link.level === win.level);
|
|
16155
|
-
const heirWindow = heir && held(heir);
|
|
16156
|
-
const near = win.chart === 0 || win.chart === cap;
|
|
16157
|
-
if (heir && near && !(heirWindow && settled(overlay, store, heirWindow))) return true;
|
|
16158
|
-
release(store, win);
|
|
16159
|
-
return false;
|
|
16160
|
-
});
|
|
16161
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
16162
|
-
const link = chain[i];
|
|
16163
|
-
let win = held(link);
|
|
16164
|
-
if (!win) {
|
|
16165
|
-
const layer = store.free.pop();
|
|
16166
|
-
if (layer === void 0) continue;
|
|
16167
|
-
win = {
|
|
16168
|
-
layer,
|
|
16169
|
-
...link,
|
|
16170
|
-
rank: i,
|
|
16171
|
-
wanted: new Array(SLOTS),
|
|
16172
|
-
pending: /* @__PURE__ */ new Map()
|
|
16173
|
-
};
|
|
16174
|
-
store.windows.push(win);
|
|
16175
|
-
}
|
|
16176
|
-
const point = chartPoint(link.chart, over.x, over.y, over.z);
|
|
16177
|
-
retarget(store, win, placeWindow(link.chart, link.level, point.x, point.y, win.placement), frame);
|
|
16178
|
-
pump(overlay, store, tiles, win, point.x, point.y, renderer, invalidate);
|
|
16179
|
-
}
|
|
16180
|
-
store.windows.sort((a, b) => b.level - a.level || a.rank - b.rank);
|
|
16181
|
-
for (let i = 0; i < MAX_DRAWN; i++) {
|
|
16182
|
-
const win = store.windows[i];
|
|
16183
|
-
const link = overlay.uniforms.uTileWindow.value[i];
|
|
16184
|
-
if (!win?.placement) {
|
|
16185
|
-
link.w = 0;
|
|
16186
|
-
continue;
|
|
16187
|
-
}
|
|
16188
|
-
overlay.uniforms.uTileGrid.value[i].set(chartColumns(win.chart, win.level), chartRows(win.chart, win.level), win.placement.tx0, win.placement.ty0);
|
|
16189
|
-
link.set(win.chart, win.layer, win.level, 1);
|
|
16190
|
-
}
|
|
16902
|
+
updateChain((bo.tileOverlay ??= createOverlay(material)).chain, tiles, chainLength(tiles, maxTileWindows()), level, over, bo.radiusScene, tiles.frames ? bo.textureFrame : void 0, renderer, invalidate);
|
|
16191
16903
|
}
|
|
16192
16904
|
|
|
16193
16905
|
//#endregion
|
|
@@ -16325,6 +17037,8 @@ function downgradeBodyMesh(bo, scene, clickables, meshToBody) {
|
|
|
16325
17037
|
}
|
|
16326
17038
|
disposeTileOverlay(bo);
|
|
16327
17039
|
bo.tileOverlay = void 0;
|
|
17040
|
+
disposeReliefTiles(bo);
|
|
17041
|
+
bo.reliefTiles = void 0;
|
|
16328
17042
|
bo.terrainWindow = null;
|
|
16329
17043
|
mesh.removeFromParent();
|
|
16330
17044
|
mesh.geometry.dispose();
|
|
@@ -16613,11 +17327,14 @@ async function attachDisplacementMap(material, dispMeta, tier, textureLoader, sp
|
|
|
16613
17327
|
}
|
|
16614
17328
|
texture.colorSpace = NoColorSpace;
|
|
16615
17329
|
material.displacementMap = texture;
|
|
16616
|
-
material
|
|
16617
|
-
material.displacementBias = dispMeta.bias_km * kmToLocal - (dispMeta.absolute_radius ? sphereRadius : 0);
|
|
17330
|
+
setDisplacementScale(material, dispMeta, sphereRadius, kmToLocal);
|
|
16618
17331
|
material.needsUpdate = true;
|
|
16619
17332
|
return texture;
|
|
16620
17333
|
}
|
|
17334
|
+
function setDisplacementScale(material, dispMeta, sphereRadius, kmToLocal = kmToScene(1)) {
|
|
17335
|
+
material.displacementScale = dispMeta.scale_km * kmToLocal;
|
|
17336
|
+
material.displacementBias = dispMeta.bias_km * kmToLocal - (dispMeta.absolute_radius ? sphereRadius : 0);
|
|
17337
|
+
}
|
|
16621
17338
|
function displacementTierUrl(dispMeta, tier) {
|
|
16622
17339
|
return versionedUrl(`/v1/textures/${dispMeta.id}/${tier}.webp`, "textures");
|
|
16623
17340
|
}
|
|
@@ -16870,7 +17587,12 @@ var SIBLING_LAYERS = {
|
|
|
16870
17587
|
bo.displacementMap = tex;
|
|
16871
17588
|
bo.displacementMeta = meta;
|
|
16872
17589
|
bo.displacementTier = "low";
|
|
16873
|
-
|
|
17590
|
+
if (!sceneSettings().showSelfShadow) {
|
|
17591
|
+
bo.selfShadow = null;
|
|
17592
|
+
return;
|
|
17593
|
+
}
|
|
17594
|
+
bo.reliefTiles = reliefTilesOf(material, meta.tiles, effectiveRadiusKm(bo.body.data));
|
|
17595
|
+
bo.selfShadow = attachSelfShadowToBody(material, tex, kmToScene(meta.scale_km), void 0, bo.reliefTiles);
|
|
16874
17596
|
},
|
|
16875
17597
|
unload(bo, mesh) {
|
|
16876
17598
|
if (!mesh) return;
|
|
@@ -16879,6 +17601,8 @@ var SIBLING_LAYERS = {
|
|
|
16879
17601
|
bo.displacementTier = void 0;
|
|
16880
17602
|
detachSelfShadow(bo.selfShadow);
|
|
16881
17603
|
bo.selfShadow = null;
|
|
17604
|
+
disposeReliefTiles(bo);
|
|
17605
|
+
bo.reliefTiles = void 0;
|
|
16882
17606
|
}
|
|
16883
17607
|
},
|
|
16884
17608
|
clouds: {
|
|
@@ -17288,6 +18012,7 @@ function fetchBodyNomenclature(bodyId, lang = getLocale()) {
|
|
|
17288
18012
|
let p = cache$1.get(key);
|
|
17289
18013
|
if (!p) {
|
|
17290
18014
|
p = (async () => {
|
|
18015
|
+
await fetchMetadata();
|
|
17291
18016
|
const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabelText(bodyId, lang)]);
|
|
17292
18017
|
return nameFeatures(records, labels);
|
|
17293
18018
|
})();
|
|
@@ -17351,7 +18076,7 @@ function gridCell(grid, latRad, lonRad) {
|
|
|
17351
18076
|
ty
|
|
17352
18077
|
};
|
|
17353
18078
|
}
|
|
17354
|
-
const { thetas, phis } = grid;
|
|
18079
|
+
const { thetas, phis, riseKm } = grid;
|
|
17355
18080
|
const theta = Math.PI / 2 - latRad;
|
|
17356
18081
|
let phi = (lonRad - Math.PI) % (2 * Math.PI);
|
|
17357
18082
|
if (phi < 0) phi += 2 * Math.PI;
|
|
@@ -17367,7 +18092,8 @@ function gridCell(grid, latRad, lonRad) {
|
|
|
17367
18092
|
[iy0 + 1, ix0 + 1]
|
|
17368
18093
|
].map(([iy, ix]) => ({
|
|
17369
18094
|
latRad: Math.PI / 2 - thetas[iy],
|
|
17370
|
-
lonRad: phis[ix] + Math.PI
|
|
18095
|
+
lonRad: phis[ix] + Math.PI,
|
|
18096
|
+
riseKm: riseKm?.[iy * phis.length + ix]
|
|
17371
18097
|
})),
|
|
17372
18098
|
tx,
|
|
17373
18099
|
ty
|
|
@@ -17543,7 +18269,8 @@ function gridForBody(bo) {
|
|
|
17543
18269
|
return tw ? {
|
|
17544
18270
|
kind: "window",
|
|
17545
18271
|
thetas: tw.thetas,
|
|
17546
|
-
phis: tw.phis
|
|
18272
|
+
phis: tw.phis,
|
|
18273
|
+
riseKm: tw.riseKm
|
|
17547
18274
|
} : {
|
|
17548
18275
|
kind: "uniform",
|
|
17549
18276
|
segs: bo?.currentSegments ?? 128
|
|
@@ -17552,20 +18279,22 @@ function gridForBody(bo) {
|
|
|
17552
18279
|
function renderedCell(bo, bodyId, radiusKm, latRad, lonRad) {
|
|
17553
18280
|
const radii = radiiFor(bodyId, radiusKm);
|
|
17554
18281
|
if (!radii) return null;
|
|
18282
|
+
const grid = gridForBody(bo);
|
|
17555
18283
|
let hf = null;
|
|
17556
18284
|
let scaleKm = 0;
|
|
17557
18285
|
let biasKm = 0;
|
|
17558
18286
|
const meta = bo?.displacementMeta;
|
|
17559
|
-
|
|
18287
|
+
const ownHeights = grid.kind === "window" && !!grid.riseKm;
|
|
18288
|
+
if (meta && bo?.displacementMap && !ownHeights) {
|
|
17560
18289
|
hf = heightFieldFor(bo, bodyId);
|
|
17561
18290
|
if (!hf) return null;
|
|
17562
18291
|
scaleKm = meta.scale_km;
|
|
17563
18292
|
biasKm = meta.bias_km - (meta.absolute_radius ? radiusKm : 0);
|
|
17564
18293
|
}
|
|
17565
|
-
const { corners, tx, ty } = gridCell(
|
|
18294
|
+
const { corners, tx, ty } = gridCell(grid, latRad, lonRad);
|
|
17566
18295
|
return {
|
|
17567
18296
|
pts: corners.map((corner) => {
|
|
17568
|
-
const dispKm = hf ? sampleHeightTexel(hf, corner.latRad, corner.lonRad) * scaleKm + biasKm : 0;
|
|
18297
|
+
const dispKm = corner.riseKm ?? (hf ? sampleHeightTexel(hf, corner.latRad, corner.lonRad) * scaleKm + biasKm : 0);
|
|
17569
18298
|
return displacedPoint(corner.latRad, corner.lonRad, dispKm, radiusKm, radii.a, radii.b, radii.c);
|
|
17570
18299
|
}),
|
|
17571
18300
|
tx,
|
|
@@ -20292,14 +21021,14 @@ var PointCloudSystem = class {
|
|
|
20292
21021
|
|
|
20293
21022
|
//#endregion
|
|
20294
21023
|
//#region src/lib/scene/objects/trail/lagrange-frame.ts
|
|
20295
|
-
var DEG$
|
|
21024
|
+
var DEG$8 = Math.PI / 180;
|
|
20296
21025
|
var J2000 = 2451545;
|
|
20297
21026
|
var A = 1.00000261;
|
|
20298
21027
|
var E = .01671123;
|
|
20299
|
-
var L0 = 100.46457166 * DEG$
|
|
20300
|
-
var L_RATE = 35999.37244981 / 36525 * DEG$
|
|
20301
|
-
var PERI0 = 102.93768193 * DEG$
|
|
20302
|
-
var PERI_RATE = .32327364 / 36525 * DEG$
|
|
21028
|
+
var L0 = 100.46457166 * DEG$8;
|
|
21029
|
+
var L_RATE = 35999.37244981 / 36525 * DEG$8;
|
|
21030
|
+
var PERI0 = 102.93768193 * DEG$8;
|
|
21031
|
+
var PERI_RATE = .32327364 / 36525 * DEG$8;
|
|
20303
21032
|
function earthHeliocentric(jd) {
|
|
20304
21033
|
const t = jd - J2000;
|
|
20305
21034
|
const peri = PERI0 + PERI_RATE * t;
|
|
@@ -22225,7 +22954,7 @@ var Placer = class {
|
|
|
22225
22954
|
}
|
|
22226
22955
|
chebMiss(id, jd) {
|
|
22227
22956
|
const store = this.ctx.chebStore;
|
|
22228
|
-
if (store.
|
|
22957
|
+
if (store.isAwaited(id, jd)) return "loading";
|
|
22229
22958
|
const coverage = store.zoneCoverage(id);
|
|
22230
22959
|
if (coverage !== null && (jd < coverage.start || jd > coverage.end) && jd === this.passJd) {
|
|
22231
22960
|
this.noData.count++;
|
|
@@ -22299,7 +23028,7 @@ var Placer = class {
|
|
|
22299
23028
|
const zonePref = activeSysId ? (fitCenterNaif) => ctx.bodies.isInSystem(`naif-${fitCenterNaif}`, activeSysId) : void 0;
|
|
22300
23029
|
const located = ctx.probeStore?.probeWithCenter(d.id, jd, zonePref) ?? null;
|
|
22301
23030
|
if (!located) {
|
|
22302
|
-
if (ctx.probeStore?.
|
|
23031
|
+
if (ctx.probeStore?.awaitedAt(jd)) return setUnplaced(body, "loading");
|
|
22303
23032
|
setUnplaced(body, "no-data");
|
|
22304
23033
|
diagnostics.warnOnce("probe-unavailable", d.id, () => {
|
|
22305
23034
|
const reason = !ctx.probeStore ? "no ProbeStore" : "no zone has both a loaded chunk and a sub-chunk covering this jd";
|
|
@@ -22510,11 +23239,11 @@ function updateRingShaders(bodyObjects, focusTruePos, realistic, overexpose, cam
|
|
|
22510
23239
|
|
|
22511
23240
|
//#endregion
|
|
22512
23241
|
//#region src/lib/scene/objects/surface/atmosphere-season.ts
|
|
22513
|
-
var DEG$
|
|
23242
|
+
var DEG$7 = Math.PI / 180;
|
|
22514
23243
|
var J2000_JD = 2451545;
|
|
22515
23244
|
function marsSolarLongitudeDeg(jd) {
|
|
22516
23245
|
const d = jd - J2000_JD;
|
|
22517
|
-
const m = (19.3871 + .52402073 * d) * DEG$
|
|
23246
|
+
const m = (19.3871 + .52402073 * d) * DEG$7;
|
|
22518
23247
|
return ((270.3871 + .524038496 * d + ((10.691 + 3e-7 * d) * Math.sin(m) + .623 * Math.sin(2 * m) + .05 * Math.sin(3 * m) + .005 * Math.sin(4 * m) + 5e-4 * Math.sin(5 * m))) % 360 + 360) % 360;
|
|
22519
23248
|
}
|
|
22520
23249
|
function sampleSeason(lsGrid, values, lsDeg) {
|
|
@@ -22798,66 +23527,134 @@ function updateSunShadowLight(bodyObjects, focusTruePos, ctx, shadowLight, sunPo
|
|
|
22798
23527
|
}
|
|
22799
23528
|
|
|
22800
23529
|
//#endregion
|
|
22801
|
-
//#region src/lib/scene/lod/terrain-
|
|
23530
|
+
//#region src/lib/scene/lod/terrain-grid.ts
|
|
22802
23531
|
var COARSE_SEGS = 128;
|
|
22803
|
-
var
|
|
22804
|
-
var ENTER_ALT = .25;
|
|
22805
|
-
var EXIT_ALT = .32;
|
|
22806
|
-
var RECENTER_FRAC = .35;
|
|
22807
|
-
var REBUILD_MIN_MS = 300;
|
|
22808
|
-
var MARGIN = 1.25;
|
|
23532
|
+
var MARGIN$1 = 1.25;
|
|
22809
23533
|
var SIN_FLOOR = .05;
|
|
22810
|
-
var
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
const
|
|
22815
|
-
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
for (const [lo, hi] of intervals) {
|
|
22822
|
-
const n0 = Math.ceil(Math.max(lo, 0) / step - 1e-9);
|
|
22823
|
-
const n1 = Math.floor(Math.min(hi, max) / step + 1e-9);
|
|
22824
|
-
for (let n = n0; n <= n1; n++) pts.push(n * step);
|
|
23534
|
+
var BASE_WIDTH_PX = 2048;
|
|
23535
|
+
function mergeBands(max, bands, coarseStep) {
|
|
23536
|
+
const inside = (band, p) => !!band && band.intervals.some(([lo, hi]) => p > lo + 1e-9 && p < hi - 1e-9);
|
|
23537
|
+
const points = [];
|
|
23538
|
+
const segments = Math.round(max / coarseStep);
|
|
23539
|
+
for (let j = 0; j <= segments; j++) {
|
|
23540
|
+
const p = j === segments ? max : j * coarseStep;
|
|
23541
|
+
if (j === 0 || j === segments || !inside(bands[bands.length - 1], p)) points.push({
|
|
23542
|
+
p,
|
|
23543
|
+
level: 0
|
|
23544
|
+
});
|
|
22825
23545
|
}
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
23546
|
+
bands.forEach((band, i) => {
|
|
23547
|
+
for (const [lo, hi] of band.intervals) {
|
|
23548
|
+
const n0 = Math.ceil(Math.max(lo, 0) / band.step - 1e-9);
|
|
23549
|
+
const n1 = Math.floor(Math.min(hi, max) / band.step + 1e-9);
|
|
23550
|
+
for (let n = n0; n <= n1; n++) {
|
|
23551
|
+
const p = n * band.step;
|
|
23552
|
+
if (!inside(bands[i - 1], p)) points.push({
|
|
23553
|
+
p,
|
|
23554
|
+
level: band.level
|
|
23555
|
+
});
|
|
23556
|
+
}
|
|
23557
|
+
}
|
|
23558
|
+
});
|
|
23559
|
+
points.sort((a, b) => a.p - b.p || b.level - a.level);
|
|
23560
|
+
const tolerance = (bands[0]?.step ?? coarseStep) * .25;
|
|
23561
|
+
const kept = [];
|
|
23562
|
+
for (const point of points) {
|
|
23563
|
+
const last = kept[kept.length - 1];
|
|
23564
|
+
if (last && point.p - last.p < tolerance) last.level = Math.max(last.level, point.level);
|
|
23565
|
+
else kept.push(point);
|
|
23566
|
+
}
|
|
23567
|
+
kept[0].p = 0;
|
|
23568
|
+
kept[kept.length - 1].p = max;
|
|
23569
|
+
return {
|
|
23570
|
+
at: Float64Array.from(kept, (point) => point.p),
|
|
23571
|
+
level: Int8Array.from(kept, (point) => point.level)
|
|
23572
|
+
};
|
|
23573
|
+
}
|
|
23574
|
+
function arcIntervals(centre, half) {
|
|
23575
|
+
const full = 2 * Math.PI;
|
|
23576
|
+
if (half >= Math.PI) return [[0, full]];
|
|
23577
|
+
const lo = centre - half;
|
|
23578
|
+
const hi = centre + half;
|
|
23579
|
+
if (lo < 0) return [[0, hi], [lo + full, full]];
|
|
23580
|
+
if (hi > full) return [[0, hi - full], [lo, full]];
|
|
23581
|
+
return [[lo, hi]];
|
|
23582
|
+
}
|
|
23583
|
+
function finestPixel(maxLevel) {
|
|
23584
|
+
return 2 * Math.PI / (BASE_WIDTH_PX * 2 ** maxLevel);
|
|
23585
|
+
}
|
|
23586
|
+
function nestedStepLevel(altitude, maxLevel, bandCells, previous) {
|
|
23587
|
+
const pixel = finestPixel(maxLevel);
|
|
23588
|
+
const raw = Math.log2(Math.max(altitude / bandCells, pixel) / pixel);
|
|
23589
|
+
if (previous !== void 0 && raw > previous - .85 && raw < previous + .15) return previous;
|
|
23590
|
+
return Math.max(0, Math.ceil(raw - 1e-9));
|
|
23591
|
+
}
|
|
23592
|
+
function planNestedGrid(centreTheta, centrePhi, altitude, peak, maxLevel, bandCells, stepLevel) {
|
|
23593
|
+
const pixel = finestPixel(maxLevel);
|
|
23594
|
+
const rowStep = Math.PI / 128;
|
|
23595
|
+
const columnStep = 2 * Math.PI / 128;
|
|
23596
|
+
const horizon = (height) => Math.acos(1 / (1 + Math.max(height, 0)));
|
|
23597
|
+
const reach = MARGIN$1 * (horizon(altitude) + horizon(peak));
|
|
23598
|
+
const sinTheta = Math.max(Math.sin(centreTheta), SIN_FLOOR);
|
|
23599
|
+
const widen = Math.round(Math.log2(1 / sinTheta));
|
|
23600
|
+
const rowBands = [];
|
|
23601
|
+
const columnBands = [];
|
|
23602
|
+
let radius = 0;
|
|
23603
|
+
for (let exponent = stepLevel; radius < reach; exponent++) {
|
|
23604
|
+
const step = pixel * 2 ** exponent;
|
|
23605
|
+
if (step >= rowStep) break;
|
|
23606
|
+
radius += bandCells * step;
|
|
23607
|
+
const level = Math.max(maxLevel - exponent, 0);
|
|
23608
|
+
rowBands.push({
|
|
23609
|
+
intervals: [[Math.max(centreTheta - radius, 0), Math.min(centreTheta + radius, Math.PI)]],
|
|
23610
|
+
step,
|
|
23611
|
+
level
|
|
23612
|
+
});
|
|
23613
|
+
const half = Math.min(radius / sinTheta, Math.PI);
|
|
23614
|
+
const fit = Math.ceil(Math.log2(2 * half / (4 * bandCells) / pixel) - 1e-9);
|
|
23615
|
+
const columnExponent = Math.max(exponent + widen, fit);
|
|
23616
|
+
const columnWidth = pixel * 2 ** columnExponent;
|
|
23617
|
+
if (columnWidth >= columnStep) continue;
|
|
23618
|
+
columnBands.push({
|
|
23619
|
+
intervals: arcIntervals(centrePhi, half),
|
|
23620
|
+
step: columnWidth,
|
|
23621
|
+
level: Math.max(maxLevel - (columnExponent - widen), 0)
|
|
23622
|
+
});
|
|
22832
23623
|
}
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
23624
|
+
return {
|
|
23625
|
+
rows: mergeBands(Math.PI, rowBands, rowStep),
|
|
23626
|
+
columns: mergeBands(2 * Math.PI, columnBands, columnStep),
|
|
23627
|
+
stepLevel,
|
|
23628
|
+
innerRadius: bandCells * pixel * 2 ** stepLevel
|
|
23629
|
+
};
|
|
22836
23630
|
}
|
|
22837
|
-
function
|
|
23631
|
+
function buildGridArrays(radius, thetas, phis, riseKm, unitsPerKm = 1) {
|
|
22838
23632
|
const rows = thetas.length;
|
|
22839
23633
|
const cols = phis.length;
|
|
22840
23634
|
const positions = new Float32Array(rows * cols * 3);
|
|
22841
23635
|
const normals = new Float32Array(rows * cols * 3);
|
|
22842
23636
|
const uvs = new Float32Array(rows * cols * 2);
|
|
23637
|
+
const sinPhi = Float64Array.from(phis, Math.sin);
|
|
23638
|
+
const cosPhi = Float64Array.from(phis, Math.cos);
|
|
22843
23639
|
for (let iy = 0; iy < rows; iy++) {
|
|
22844
23640
|
const theta = thetas[iy];
|
|
22845
23641
|
const sinT = Math.sin(theta);
|
|
22846
23642
|
const cosT = Math.cos(theta);
|
|
22847
23643
|
for (let ix = 0; ix < cols; ix++) {
|
|
22848
|
-
const
|
|
22849
|
-
const nx = -
|
|
23644
|
+
const vertex = iy * cols + ix;
|
|
23645
|
+
const nx = -cosPhi[ix] * sinT;
|
|
22850
23646
|
const ny = cosT;
|
|
22851
|
-
const nz =
|
|
22852
|
-
const
|
|
22853
|
-
|
|
22854
|
-
positions[i3
|
|
22855
|
-
positions[i3 +
|
|
23647
|
+
const nz = sinPhi[ix] * sinT;
|
|
23648
|
+
const r = radius + (riseKm ? riseKm[vertex] * unitsPerKm : 0);
|
|
23649
|
+
const i3 = vertex * 3;
|
|
23650
|
+
positions[i3] = r * nx;
|
|
23651
|
+
positions[i3 + 1] = r * ny;
|
|
23652
|
+
positions[i3 + 2] = r * nz;
|
|
22856
23653
|
normals[i3] = nx;
|
|
22857
23654
|
normals[i3 + 1] = ny;
|
|
22858
23655
|
normals[i3 + 2] = nz;
|
|
22859
|
-
const i2 =
|
|
22860
|
-
uvs[i2] =
|
|
23656
|
+
const i2 = vertex * 2;
|
|
23657
|
+
uvs[i2] = phis[ix] / (2 * Math.PI);
|
|
22861
23658
|
uvs[i2 + 1] = 1 - theta / Math.PI;
|
|
22862
23659
|
}
|
|
22863
23660
|
}
|
|
@@ -22883,19 +23680,155 @@ function buildGridGeometry(radius, thetas, phis) {
|
|
|
22883
23680
|
}
|
|
22884
23681
|
}
|
|
22885
23682
|
}
|
|
23683
|
+
return {
|
|
23684
|
+
positions,
|
|
23685
|
+
normals,
|
|
23686
|
+
uvs,
|
|
23687
|
+
indices: indices.subarray(0, k)
|
|
23688
|
+
};
|
|
23689
|
+
}
|
|
23690
|
+
|
|
23691
|
+
//#endregion
|
|
23692
|
+
//#region src/lib/scene/lod/terrain-window.ts
|
|
23693
|
+
var CELL_BUDGET = 256;
|
|
23694
|
+
var ENTER_ALT = .25;
|
|
23695
|
+
var EXIT_ALT = .32;
|
|
23696
|
+
var RECENTER_FRAC = .35;
|
|
23697
|
+
var REBUILD_MIN_MS = 300;
|
|
23698
|
+
var MAX_VERTS = 4e5;
|
|
23699
|
+
var _inv = new Matrix4();
|
|
23700
|
+
var _camLocal = new Vector3();
|
|
23701
|
+
function mergeAxis(max, intervals, step) {
|
|
23702
|
+
const inFine = (p) => intervals.some(([lo, hi]) => p > lo + 1e-9 && p < hi - 1e-9);
|
|
23703
|
+
const coarseStep = max / 128;
|
|
23704
|
+
const pts = [];
|
|
23705
|
+
for (let j = 0; j <= 128; j++) {
|
|
23706
|
+
const p = j === 128 ? max : j * coarseStep;
|
|
23707
|
+
if (j === 0 || j === 128 || !inFine(p)) pts.push(p);
|
|
23708
|
+
}
|
|
23709
|
+
for (const [lo, hi] of intervals) {
|
|
23710
|
+
const n0 = Math.ceil(Math.max(lo, 0) / step - 1e-9);
|
|
23711
|
+
const n1 = Math.floor(Math.min(hi, max) / step + 1e-9);
|
|
23712
|
+
for (let n = n0; n <= n1; n++) pts.push(n * step);
|
|
23713
|
+
}
|
|
23714
|
+
pts.sort((a, b) => a - b);
|
|
23715
|
+
const tol = step * .25;
|
|
23716
|
+
const out = [];
|
|
23717
|
+
for (const p of pts) {
|
|
23718
|
+
if (out.length && p - out[out.length - 1] < tol) continue;
|
|
23719
|
+
out.push(p);
|
|
23720
|
+
}
|
|
23721
|
+
out[0] = 0;
|
|
23722
|
+
out[out.length - 1] = max;
|
|
23723
|
+
return Float64Array.from(out);
|
|
23724
|
+
}
|
|
23725
|
+
function gridGeometry(grid) {
|
|
22886
23726
|
const geometry = new BufferGeometry();
|
|
22887
|
-
geometry.setAttribute("position", new BufferAttribute(positions, 3));
|
|
22888
|
-
geometry.setAttribute("normal", new BufferAttribute(normals, 3));
|
|
22889
|
-
geometry.setAttribute("uv", new BufferAttribute(uvs, 2));
|
|
22890
|
-
geometry.setIndex(new BufferAttribute(indices
|
|
23727
|
+
geometry.setAttribute("position", new BufferAttribute(grid.positions, 3));
|
|
23728
|
+
geometry.setAttribute("normal", new BufferAttribute(grid.normals, 3));
|
|
23729
|
+
geometry.setAttribute("uv", new BufferAttribute(grid.uvs, 2));
|
|
23730
|
+
geometry.setIndex(new BufferAttribute(grid.indices, 1));
|
|
22891
23731
|
return geometry;
|
|
22892
23732
|
}
|
|
23733
|
+
var tiledStates = /* @__PURE__ */ new WeakMap();
|
|
23734
|
+
var MAX_BUILD_FAILURES = 2;
|
|
23735
|
+
function heightTilesOf(bo) {
|
|
23736
|
+
const tiles = bo.displacementMeta?.tiles;
|
|
23737
|
+
return reliefTilesAvailable(tiles) && !tiledStates.get(bo)?.dead ? tiles : void 0;
|
|
23738
|
+
}
|
|
23739
|
+
function restoreDisplacement(bo) {
|
|
23740
|
+
if (!bo.mesh || !bo.displacementMeta) return;
|
|
23741
|
+
setDisplacementScale(bo.mesh.material, bo.displacementMeta, bo.radiusScene);
|
|
23742
|
+
}
|
|
22893
23743
|
function exitWindow(bo) {
|
|
23744
|
+
const state = tiledStates.get(bo);
|
|
23745
|
+
state?.abort?.abort();
|
|
23746
|
+
if (state) state.abort = void 0;
|
|
22894
23747
|
if (!bo.terrainWindow) return;
|
|
23748
|
+
if (bo.terrainWindow.riseKm) restoreDisplacement(bo);
|
|
22895
23749
|
bo.terrainWindow = null;
|
|
22896
23750
|
bo.currentSegments = void 0;
|
|
22897
23751
|
}
|
|
22898
|
-
function
|
|
23752
|
+
function drift(thetaA, phiA, thetaB, phiB) {
|
|
23753
|
+
const cos = Math.sin(thetaA) * Math.sin(thetaB) * Math.cos(phiA - phiB) + Math.cos(thetaA) * Math.cos(thetaB);
|
|
23754
|
+
return Math.acos(Math.min(1, Math.max(-1, cos)));
|
|
23755
|
+
}
|
|
23756
|
+
function updateTiledWindow(bo, mesh, tiles, altitude, centerTheta, centerPhi, invalidate) {
|
|
23757
|
+
const held = bo.terrainWindow?.riseKm ? bo.terrainWindow : void 0;
|
|
23758
|
+
const material = mesh.material;
|
|
23759
|
+
if (held) {
|
|
23760
|
+
material.displacementScale = 0;
|
|
23761
|
+
material.displacementBias = 0;
|
|
23762
|
+
}
|
|
23763
|
+
const state = tiledStates.get(bo) ?? {
|
|
23764
|
+
failures: 0,
|
|
23765
|
+
dead: false
|
|
23766
|
+
};
|
|
23767
|
+
tiledStates.set(bo, state);
|
|
23768
|
+
if (state.abort) return !!held;
|
|
23769
|
+
const meta = bo.displacementMeta;
|
|
23770
|
+
if (!meta) return !!held;
|
|
23771
|
+
const bandCells = currentRenderTier().terrainBandCells;
|
|
23772
|
+
const stepLevel = nestedStepLevel(altitude, tiles.max_level, bandCells, held?.stepLevel);
|
|
23773
|
+
if (held) {
|
|
23774
|
+
const still = drift(centerTheta, centerPhi, held.centerTheta, held.centerPhi);
|
|
23775
|
+
if (held.stepLevel === stepLevel && still < RECENTER_FRAC * held.angRadius) return true;
|
|
23776
|
+
if (performance.now() - held.builtAt < REBUILD_MIN_MS) return true;
|
|
23777
|
+
}
|
|
23778
|
+
const radiusKm = effectiveRadiusKm(bo.body.data);
|
|
23779
|
+
const grid = planNestedGrid(centerTheta, centerPhi, altitude, .5 * meta.scale_km / radiusKm, tiles.max_level, bandCells, stepLevel);
|
|
23780
|
+
const thetas = grid.rows.at;
|
|
23781
|
+
const phis = grid.columns.at;
|
|
23782
|
+
if (thetas.length * phis.length > MAX_VERTS) {
|
|
23783
|
+
console.warn(`terrain grid for ${bo.body.data.id} would need ${thetas.length}×${phis.length} vertices: left to the shader`);
|
|
23784
|
+
state.dead = true;
|
|
23785
|
+
return !!held;
|
|
23786
|
+
}
|
|
23787
|
+
const abort = new AbortController();
|
|
23788
|
+
state.abort = abort;
|
|
23789
|
+
const asked = performance.now();
|
|
23790
|
+
buildTerrainGrid({
|
|
23791
|
+
root: tileRoot(tiles),
|
|
23792
|
+
version: tiles.version,
|
|
23793
|
+
tileSize: tiles.tile_size,
|
|
23794
|
+
scaleKm: tiles.scale_km ?? 0,
|
|
23795
|
+
biasKm: (tiles.bias_km ?? 0) - (meta.absolute_radius ? radiusKm : 0),
|
|
23796
|
+
radius: kmToScene(radiusKm),
|
|
23797
|
+
unitsPerKm: kmToScene(1),
|
|
23798
|
+
rows: grid.rows,
|
|
23799
|
+
columns: grid.columns
|
|
23800
|
+
}, abort.signal).then((built) => {
|
|
23801
|
+
if (state.abort !== abort) return;
|
|
23802
|
+
state.abort = void 0;
|
|
23803
|
+
state.failures = 0;
|
|
23804
|
+
if (bo.mesh !== mesh || !bo.displacementMap) return;
|
|
23805
|
+
const old = mesh.geometry;
|
|
23806
|
+
mesh.geometry = gridGeometry(built);
|
|
23807
|
+
old.dispose();
|
|
23808
|
+
material.displacementScale = 0;
|
|
23809
|
+
material.displacementBias = 0;
|
|
23810
|
+
bo.terrainWindow = {
|
|
23811
|
+
thetas,
|
|
23812
|
+
phis,
|
|
23813
|
+
centerTheta,
|
|
23814
|
+
centerPhi,
|
|
23815
|
+
angRadius: grid.innerRadius,
|
|
23816
|
+
stepLevel,
|
|
23817
|
+
texWidth: 2048 * 2 ** tiles.max_level,
|
|
23818
|
+
builtAt: performance.now(),
|
|
23819
|
+
riseKm: built.riseKm
|
|
23820
|
+
};
|
|
23821
|
+
bo.currentSegments = 128;
|
|
23822
|
+
console.debug(`terrain grid ${bo.body.data.id}: ${thetas.length}×${phis.length}, level ${stepLevel}, ${Math.round(performance.now() - asked)} ms`);
|
|
23823
|
+
invalidate();
|
|
23824
|
+
}).catch(() => {
|
|
23825
|
+
if (state.abort !== abort) return;
|
|
23826
|
+
state.abort = void 0;
|
|
23827
|
+
if (++state.failures >= MAX_BUILD_FAILURES) state.dead = true;
|
|
23828
|
+
});
|
|
23829
|
+
return !!held;
|
|
23830
|
+
}
|
|
23831
|
+
function updateTerrainWindow(bo, camera, eligible, invalidate) {
|
|
22899
23832
|
const mesh = bo.mesh;
|
|
22900
23833
|
const tex = bo.displacementMap;
|
|
22901
23834
|
if (!eligible || !mesh || !tex) {
|
|
@@ -22914,23 +23847,25 @@ function updateTerrainWindow(bo, camera, eligible) {
|
|
|
22914
23847
|
exitWindow(bo);
|
|
22915
23848
|
return false;
|
|
22916
23849
|
}
|
|
23850
|
+
const centerTheta = Math.acos(Math.min(1, Math.max(-1, _camLocal.y / dist)));
|
|
23851
|
+
let centerPhi = Math.atan2(_camLocal.z, -_camLocal.x);
|
|
23852
|
+
if (centerPhi < 0) centerPhi += 2 * Math.PI;
|
|
23853
|
+
const tiles = heightTilesOf(bo);
|
|
23854
|
+
if (tiles) return updateTiledWindow(bo, mesh, tiles, alt, centerTheta, centerPhi, invalidate);
|
|
23855
|
+
if (bo.terrainWindow?.riseKm) restoreDisplacement(bo);
|
|
22917
23856
|
const texWidth = tex.image?.width ?? 2048;
|
|
22918
23857
|
const texelAng = 2 * Math.PI / texWidth;
|
|
22919
23858
|
const horizon = Math.acos(1 / (1 + alt));
|
|
22920
|
-
const rawStep = Math.max(texelAng, 2 * horizon * MARGIN / CELL_BUDGET);
|
|
23859
|
+
const rawStep = Math.max(texelAng, 2 * horizon * MARGIN$1 / CELL_BUDGET);
|
|
22921
23860
|
const rawLevel = Math.log2(rawStep / texelAng);
|
|
22922
23861
|
let stepLevel = Math.max(0, Math.ceil(rawLevel - 1e-9));
|
|
22923
23862
|
const w = bo.terrainWindow;
|
|
22924
23863
|
if (w && w.texWidth === texWidth && rawLevel > w.stepLevel - .85 && rawLevel < w.stepLevel + .15) stepLevel = w.stepLevel;
|
|
22925
23864
|
const step = texelAng * 2 ** stepLevel;
|
|
22926
23865
|
const angRadius = step * CELL_BUDGET / 2;
|
|
22927
|
-
const centerTheta = Math.acos(Math.min(1, Math.max(-1, _camLocal.y / dist)));
|
|
22928
|
-
let centerPhi = Math.atan2(_camLocal.z, -_camLocal.x);
|
|
22929
|
-
if (centerPhi < 0) centerPhi += 2 * Math.PI;
|
|
22930
23866
|
if (w && w.texWidth === texWidth) {
|
|
22931
23867
|
if (w.stepLevel === stepLevel) {
|
|
22932
|
-
|
|
22933
|
-
if (Math.acos(Math.min(1, Math.max(-1, cosDrift))) < RECENTER_FRAC * w.angRadius) return true;
|
|
23868
|
+
if (drift(centerTheta, centerPhi, w.centerTheta, w.centerPhi) < RECENTER_FRAC * w.angRadius) return true;
|
|
22934
23869
|
}
|
|
22935
23870
|
if (performance.now() - w.builtAt < REBUILD_MIN_MS) return true;
|
|
22936
23871
|
}
|
|
@@ -22950,7 +23885,7 @@ function updateTerrainWindow(bo, camera, eligible) {
|
|
|
22950
23885
|
return false;
|
|
22951
23886
|
}
|
|
22952
23887
|
const old = mesh.geometry;
|
|
22953
|
-
mesh.geometry =
|
|
23888
|
+
mesh.geometry = gridGeometry(buildGridArrays(geomRadius, thetas, phis));
|
|
22954
23889
|
old.dispose();
|
|
22955
23890
|
console.debug(`terrain window ${bo.body.data.id}: ${thetas.length}×${phis.length} grid, level ${stepLevel} (tex ${texWidth})`);
|
|
22956
23891
|
bo.terrainWindow = {
|
|
@@ -22963,7 +23898,7 @@ function updateTerrainWindow(bo, camera, eligible) {
|
|
|
22963
23898
|
texWidth,
|
|
22964
23899
|
builtAt: performance.now()
|
|
22965
23900
|
};
|
|
22966
|
-
bo.currentSegments =
|
|
23901
|
+
bo.currentSegments = 128;
|
|
22967
23902
|
return true;
|
|
22968
23903
|
}
|
|
22969
23904
|
|
|
@@ -22997,7 +23932,7 @@ function desiredSphereSegments(screenR, isStar, inSystem, current) {
|
|
|
22997
23932
|
}
|
|
22998
23933
|
return target;
|
|
22999
23934
|
}
|
|
23000
|
-
function updateSphereLOD(bodyObjects, camera, renderer, ctx, focusedId) {
|
|
23935
|
+
function updateSphereLOD(bodyObjects, camera, renderer, ctx, focusedId, invalidate) {
|
|
23001
23936
|
const fovRad = camera.fov * Math.PI / 180;
|
|
23002
23937
|
const projScale = renderer.domElement.clientHeight / (2 * Math.tan(fovRad / 2));
|
|
23003
23938
|
const activeSystem = ctx.visibility.activeSystemId;
|
|
@@ -23008,7 +23943,7 @@ function updateSphereLOD(bodyObjects, camera, renderer, ctx, focusedId) {
|
|
|
23008
23943
|
const isStar = bo.body.data.objectType === ObjectType.STAR;
|
|
23009
23944
|
const id = bo.body.data.id;
|
|
23010
23945
|
if (id === terrainId || bo.terrainWindow) {
|
|
23011
|
-
if (updateTerrainWindow(bo, camera, id === terrainId)) continue;
|
|
23946
|
+
if (updateTerrainWindow(bo, camera, id === terrainId, invalidate)) continue;
|
|
23012
23947
|
}
|
|
23013
23948
|
const desired = desiredSphereSegments(bo.radiusScene / bo.cachedDist * projScale, isStar, activeSystem ? id === activeSystem || ctx.visibility.isInActiveSystem(bo.body.data.parentId) : id === focusedId, bo.currentSegments ?? 64);
|
|
23014
23949
|
if (desired === bo.currentSegments) continue;
|
|
@@ -23062,11 +23997,13 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
|
|
|
23062
23997
|
const target = wantsUpgrade ? highestAvailableTier(desiredRank, bo.availableTiers) : bo.textureTier;
|
|
23063
23998
|
if (target) loadBodyTextureTier(bo, target, desiredFrame, textureLoader);
|
|
23064
23999
|
}
|
|
23065
|
-
|
|
24000
|
+
const devicePx = projScale * renderer.getPixelRatio();
|
|
24001
|
+
updateTileOverlay(bo, camera, renderer, devicePx, settings.showSurfaceTexture && !bo.appearance?.surfaceOwned, invalidate);
|
|
24002
|
+
updateReliefTiles(bo, camera, renderer, devicePx, invalidate);
|
|
23066
24003
|
const dispTiers = bo.displacementMeta?.tiers;
|
|
23067
24004
|
if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
|
|
23068
24005
|
const maxTex = renderer.capabilities.maxTextureSize;
|
|
23069
|
-
const capRank = maxTex >= 16383 ? 2 : maxTex >= 8192 ? 1 : 0;
|
|
24006
|
+
const capRank = maxTex >= 16383 && !reliefTilesServe(bo) ? 2 : maxTex >= 8192 ? 1 : 0;
|
|
23070
24007
|
const current = bo.displacementTier ?? "low";
|
|
23071
24008
|
const target = highestAvailableTier(Math.min(tierRank(desiredDemTier(altitudeRadii, current)), capRank, tierCap), dispTiers);
|
|
23072
24009
|
if (target && target !== current) swapDisplacementTier(bo, target);
|
|
@@ -24908,7 +25845,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24908
25845
|
updateUserLocationOcclusion(this.userLocationMarker, this.bodyObjects, this.camera);
|
|
24909
25846
|
const focusedIdLod = nomenclatureBodyId(this.focusController.current, this.bodyObjects);
|
|
24910
25847
|
updateTextureLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, this.textureLoader, focusedIdLod, this.sceneJd, this.appearances, this.invalidate);
|
|
24911
|
-
updateSphereLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, focusedIdLod);
|
|
25848
|
+
updateSphereLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, focusedIdLod, this.invalidate);
|
|
24912
25849
|
updateSunShadowLight(this.bodyObjects, this.focus.focusTruePos, this.ctx, this.shadowLight, this.sunPointLight, distance, this._tmpV3, sceneSettings().realisticLighting, this.sunIntensityScale);
|
|
24913
25850
|
this.updateModelMounts();
|
|
24914
25851
|
this.pointClouds.drainOnePendingSceneAdd();
|
|
@@ -25380,6 +26317,10 @@ var SceneRenderer = class SceneRenderer {
|
|
|
25380
26317
|
}
|
|
25381
26318
|
lastProbeTargetCheck = null;
|
|
25382
26319
|
requestedProbeTargets = /* @__PURE__ */ new Set();
|
|
26320
|
+
probeTargetRetries = new RetryBackoff(() => {
|
|
26321
|
+
this.lastProbeTargetCheck = null;
|
|
26322
|
+
this.invalidate();
|
|
26323
|
+
});
|
|
25383
26324
|
askedProbeRides = /* @__PURE__ */ new Set();
|
|
25384
26325
|
ensureFocusedProbeTarget() {
|
|
25385
26326
|
const focused = this.focusController.current;
|
|
@@ -25397,9 +26338,13 @@ var SceneRenderer = class SceneRenderer {
|
|
|
25397
26338
|
if (!fcId) return;
|
|
25398
26339
|
const body = this.ctx.getBody(fcId);
|
|
25399
26340
|
if (!body) {
|
|
25400
|
-
if (this.requestedProbeTargets.has(fcId)) return;
|
|
26341
|
+
if (this.requestedProbeTargets.has(fcId) || this.probeTargetRetries.waiting(fcId)) return;
|
|
25401
26342
|
this.requestedProbeTargets.add(fcId);
|
|
25402
|
-
this.ctx.ensureBody(fcId, jdToDate(this.clock.jd))
|
|
26343
|
+
this.ctx.ensureBody(fcId, jdToDate(this.clock.jd)).then(() => this.probeTargetRetries.clear(fcId), (error) => {
|
|
26344
|
+
console.warn(`[renderer] the body ${fcId} a craft is fit to did not load:`, error);
|
|
26345
|
+
this.requestedProbeTargets.delete(fcId);
|
|
26346
|
+
this.probeTargetRetries.fail(fcId);
|
|
26347
|
+
});
|
|
25403
26348
|
return;
|
|
25404
26349
|
}
|
|
25405
26350
|
this.focusController.promotion.ensureBodyObjects(body);
|
|
@@ -25845,6 +26790,10 @@ var SceneRenderer = class SceneRenderer {
|
|
|
25845
26790
|
this.travelPath.dispose();
|
|
25846
26791
|
this.extensions.dispose();
|
|
25847
26792
|
for (const bo of this.bodyObjects.values()) if (bo.model) unloadBodyModel(bo);
|
|
26793
|
+
for (const bo of this.bodyObjects.values()) {
|
|
26794
|
+
disposeTileOverlay(bo);
|
|
26795
|
+
disposeReliefTiles(bo);
|
|
26796
|
+
}
|
|
25848
26797
|
this.pointClouds.dispose();
|
|
25849
26798
|
this.disposeScene(this.scene);
|
|
25850
26799
|
this.disposeScene(this.modelScene);
|
|
@@ -26903,7 +27852,7 @@ var IconExtension = class extends MarkerExtension {
|
|
|
26903
27852
|
|
|
26904
27853
|
//#endregion
|
|
26905
27854
|
//#region src/lib/flatmap/projection.ts
|
|
26906
|
-
var DEG$
|
|
27855
|
+
var DEG$6 = Math.PI / 180;
|
|
26907
27856
|
var RAD$2 = 180 / Math.PI;
|
|
26908
27857
|
function wrapLon(lon) {
|
|
26909
27858
|
if (lon >= -180 && lon < 180) return lon;
|
|
@@ -26968,7 +27917,7 @@ function equirectangular(options) {
|
|
|
26968
27917
|
maxX: Math.PI,
|
|
26969
27918
|
maxY: Math.PI / 2
|
|
26970
27919
|
},
|
|
26971
|
-
forward: (lon, lat) => [wrapLon(lon - lon0) * DEG$
|
|
27920
|
+
forward: (lon, lat) => [wrapLon(lon - lon0) * DEG$6, lat * DEG$6],
|
|
26972
27921
|
rowInverse,
|
|
26973
27922
|
inverse: rowToInverse(rowInverse, lon0)
|
|
26974
27923
|
};
|
|
@@ -26983,8 +27932,8 @@ var eeDy = (t) => 9 * EE_A4 * t ** 8 + 7 * EE_A3 * t ** 6 + 3 * EE_A2 * t ** 2 +
|
|
|
26983
27932
|
function equalEarth(options) {
|
|
26984
27933
|
const lon0 = options.centerLon ?? 0;
|
|
26985
27934
|
const forward = (lon, lat) => {
|
|
26986
|
-
const theta = Math.asin(clamp1(EE_SQRT3 / 2 * Math.sin(lat * DEG$
|
|
26987
|
-
const dl = wrapLon(lon - lon0) * DEG$
|
|
27935
|
+
const theta = Math.asin(clamp1(EE_SQRT3 / 2 * Math.sin(lat * DEG$6)));
|
|
27936
|
+
const dl = wrapLon(lon - lon0) * DEG$6;
|
|
26988
27937
|
return [2 * EE_SQRT3 * dl * Math.cos(theta) / (3 * eeDy(theta)), eeY(theta)];
|
|
26989
27938
|
};
|
|
26990
27939
|
const thetaFor = (y) => {
|
|
@@ -27054,7 +28003,7 @@ function mollweide(options) {
|
|
|
27054
28003
|
maxY: MW_SQRT2
|
|
27055
28004
|
},
|
|
27056
28005
|
forward: (lon, lat) => {
|
|
27057
|
-
const phi = lat * DEG$
|
|
28006
|
+
const phi = lat * DEG$6;
|
|
27058
28007
|
let theta = phi;
|
|
27059
28008
|
if (Math.abs(Math.abs(phi) - Math.PI / 2) < 1e-9) theta = Math.sign(phi) * (Math.PI / 2);
|
|
27060
28009
|
else {
|
|
@@ -27065,7 +28014,7 @@ function mollweide(options) {
|
|
|
27065
28014
|
if (Math.abs(step) < 1e-12) break;
|
|
27066
28015
|
}
|
|
27067
28016
|
}
|
|
27068
|
-
const dl = wrapLon(lon - lon0) * DEG$
|
|
28017
|
+
const dl = wrapLon(lon - lon0) * DEG$6;
|
|
27069
28018
|
return [2 * MW_SQRT2 / Math.PI * dl * Math.cos(theta), MW_SQRT2 * Math.sin(theta)];
|
|
27070
28019
|
},
|
|
27071
28020
|
rowInverse,
|
|
@@ -27101,8 +28050,8 @@ function sinusoidal(options) {
|
|
|
27101
28050
|
maxY: Math.PI / 2
|
|
27102
28051
|
},
|
|
27103
28052
|
forward: (lon, lat) => {
|
|
27104
|
-
const phi = lat * DEG$
|
|
27105
|
-
return [wrapLon(lon - lon0) * DEG$
|
|
28053
|
+
const phi = lat * DEG$6;
|
|
28054
|
+
return [wrapLon(lon - lon0) * DEG$6 * Math.cos(phi), phi];
|
|
27106
28055
|
},
|
|
27107
28056
|
rowInverse,
|
|
27108
28057
|
inverse: rowToInverse(rowInverse, lon0)
|
|
@@ -27224,7 +28173,7 @@ function robinson(options) {
|
|
|
27224
28173
|
const [i, f] = robinsonSegment(Math.abs(lat) / ROBINSON_STEP);
|
|
27225
28174
|
const length = splineAt(splineSegment(ROBINSON_LENGTH, i), f);
|
|
27226
28175
|
const height = splineAt(splineSegment(ROBINSON_HEIGHT, i), f);
|
|
27227
|
-
return [ROBINSON_KX * length * wrapLon(lon - lon0) * DEG$
|
|
28176
|
+
return [ROBINSON_KX * length * wrapLon(lon - lon0) * DEG$6, ROBINSON_KY * height * Math.sign(lat)];
|
|
27228
28177
|
},
|
|
27229
28178
|
rowInverse,
|
|
27230
28179
|
inverse: rowToInverse(rowInverse, lon0)
|
|
@@ -27246,14 +28195,14 @@ function discExtent(r) {
|
|
|
27246
28195
|
};
|
|
27247
28196
|
}
|
|
27248
28197
|
function azimuthalDirection(lon, lat, lon0, phi0) {
|
|
27249
|
-
const dl = wrapLon(lon - lon0) * DEG$
|
|
27250
|
-
const phi = lat * DEG$
|
|
28198
|
+
const dl = wrapLon(lon - lon0) * DEG$6;
|
|
28199
|
+
const phi = lat * DEG$6;
|
|
27251
28200
|
return [Math.cos(phi) * Math.sin(dl), Math.cos(phi0) * Math.sin(phi) - Math.sin(phi0) * Math.cos(phi) * Math.cos(dl)];
|
|
27252
28201
|
}
|
|
27253
28202
|
function orthographic(options) {
|
|
27254
28203
|
const lon0 = options.centerLon ?? 0;
|
|
27255
|
-
const phi0 = (options.centerLat ?? 0) * DEG$
|
|
27256
|
-
const clip = Math.min(options.clipAngle ?? 90, 90) * DEG$
|
|
28204
|
+
const phi0 = (options.centerLat ?? 0) * DEG$6;
|
|
28205
|
+
const clip = Math.min(options.clipAngle ?? 90, 90) * DEG$6;
|
|
27257
28206
|
const cosClip = Math.cos(clip);
|
|
27258
28207
|
const radius = Math.sin(clip);
|
|
27259
28208
|
const sinPhi0 = Math.sin(phi0);
|
|
@@ -27266,8 +28215,8 @@ function orthographic(options) {
|
|
|
27266
28215
|
azimuthal: true,
|
|
27267
28216
|
extent: discExtent(radius),
|
|
27268
28217
|
forward: (lon, lat) => {
|
|
27269
|
-
const dl = wrapLon(lon - lon0) * DEG$
|
|
27270
|
-
const phi = lat * DEG$
|
|
28218
|
+
const dl = wrapLon(lon - lon0) * DEG$6;
|
|
28219
|
+
const phi = lat * DEG$6;
|
|
27271
28220
|
if (Math.sin(phi0) * Math.sin(phi) + Math.cos(phi0) * Math.cos(phi) * Math.cos(dl) < cosClip) return null;
|
|
27272
28221
|
return azimuthalDirection(lon, lat, lon0, phi0);
|
|
27273
28222
|
},
|
|
@@ -27281,8 +28230,8 @@ function orthographic(options) {
|
|
|
27281
28230
|
var STEREOGRAPHIC_CLIP = 120;
|
|
27282
28231
|
function stereographic(options) {
|
|
27283
28232
|
const lon0 = options.centerLon ?? 0;
|
|
27284
|
-
const phi0 = (options.centerLat ?? 90) * DEG$
|
|
27285
|
-
const clip = Math.min(options.clipAngle ?? STEREOGRAPHIC_CLIP, 170) * DEG$
|
|
28233
|
+
const phi0 = (options.centerLat ?? 90) * DEG$6;
|
|
28234
|
+
const clip = Math.min(options.clipAngle ?? STEREOGRAPHIC_CLIP, 170) * DEG$6;
|
|
27286
28235
|
const cosClip = Math.cos(clip);
|
|
27287
28236
|
const radius = 2 * Math.tan(clip / 2);
|
|
27288
28237
|
const sinPhi0 = Math.sin(phi0);
|
|
@@ -27295,8 +28244,8 @@ function stereographic(options) {
|
|
|
27295
28244
|
azimuthal: true,
|
|
27296
28245
|
extent: discExtent(radius),
|
|
27297
28246
|
forward: (lon, lat) => {
|
|
27298
|
-
const dl = wrapLon(lon - lon0) * DEG$
|
|
27299
|
-
const phi = lat * DEG$
|
|
28247
|
+
const dl = wrapLon(lon - lon0) * DEG$6;
|
|
28248
|
+
const phi = lat * DEG$6;
|
|
27300
28249
|
const cosC = Math.sin(phi0) * Math.sin(phi) + Math.cos(phi0) * Math.cos(phi) * Math.cos(dl);
|
|
27301
28250
|
if (cosC < cosClip) return null;
|
|
27302
28251
|
const k = 2 / (1 + cosC);
|
|
@@ -27329,30 +28278,30 @@ function createProjection(id, options = {}) {
|
|
|
27329
28278
|
|
|
27330
28279
|
//#endregion
|
|
27331
28280
|
//#region src/lib/flatmap/geometry.ts
|
|
27332
|
-
var DEG$
|
|
28281
|
+
var DEG$5 = Math.PI / 180;
|
|
27333
28282
|
var TAU = 2 * Math.PI;
|
|
27334
|
-
var RIM_STEP = 2 * DEG$
|
|
28283
|
+
var RIM_STEP = 2 * DEG$5;
|
|
27335
28284
|
var RAD$1 = 180 / Math.PI;
|
|
27336
28285
|
var MAX_STEPS = 512;
|
|
27337
28286
|
var DEFAULT_STEP_DEG = 2;
|
|
27338
28287
|
function angularDistance(a, b) {
|
|
27339
|
-
const φ1 = a.lat * DEG$
|
|
27340
|
-
const φ2 = b.lat * DEG$
|
|
28288
|
+
const φ1 = a.lat * DEG$5;
|
|
28289
|
+
const φ2 = b.lat * DEG$5;
|
|
27341
28290
|
const dφ = φ2 - φ1;
|
|
27342
|
-
const dλ = wrapLon(b.lon - a.lon) * DEG$
|
|
28291
|
+
const dλ = wrapLon(b.lon - a.lon) * DEG$5;
|
|
27343
28292
|
const h = Math.sin(dφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(dλ / 2) ** 2;
|
|
27344
28293
|
return 2 * Math.asin(Math.min(1, Math.sqrt(h))) * RAD$1;
|
|
27345
28294
|
}
|
|
27346
28295
|
function slerp(a, b, t) {
|
|
27347
|
-
const d = angularDistance(a, b) * DEG$
|
|
28296
|
+
const d = angularDistance(a, b) * DEG$5;
|
|
27348
28297
|
if (d < 1e-12) return a;
|
|
27349
28298
|
const sinD = Math.sin(d);
|
|
27350
28299
|
const ka = Math.sin((1 - t) * d) / sinD;
|
|
27351
28300
|
const kb = Math.sin(t * d) / sinD;
|
|
27352
|
-
const φ1 = a.lat * DEG$
|
|
27353
|
-
const λ1 = a.lon * DEG$
|
|
27354
|
-
const φ2 = b.lat * DEG$
|
|
27355
|
-
const λ2 = (a.lon + wrapLon(b.lon - a.lon)) * DEG$
|
|
28301
|
+
const φ1 = a.lat * DEG$5;
|
|
28302
|
+
const λ1 = a.lon * DEG$5;
|
|
28303
|
+
const φ2 = b.lat * DEG$5;
|
|
28304
|
+
const λ2 = (a.lon + wrapLon(b.lon - a.lon)) * DEG$5;
|
|
27356
28305
|
const x = ka * Math.cos(φ1) * Math.cos(λ1) + kb * Math.cos(φ2) * Math.cos(λ2);
|
|
27357
28306
|
const y = ka * Math.cos(φ1) * Math.sin(λ1) + kb * Math.cos(φ2) * Math.sin(λ2);
|
|
27358
28307
|
const z = ka * Math.sin(φ1) + kb * Math.sin(φ2);
|
|
@@ -27586,7 +28535,7 @@ function turnsLeft(loop) {
|
|
|
27586
28535
|
for (let i = 0; i < loop.length; i++) {
|
|
27587
28536
|
const a = loop[i];
|
|
27588
28537
|
const b = loop[(i + 1) % loop.length];
|
|
27589
|
-
sum += wrapLon(b.lon - a.lon) * DEG$
|
|
28538
|
+
sum += wrapLon(b.lon - a.lon) * DEG$5 * (2 + Math.sin(a.lat * DEG$5) + Math.sin(b.lat * DEG$5));
|
|
27590
28539
|
}
|
|
27591
28540
|
const area = -sum / 2;
|
|
27592
28541
|
return area > 0 ? area < TAU : area < -TAU;
|
|
@@ -27622,8 +28571,8 @@ function boxRing(latMin, latMax, lonMin, lonSpan) {
|
|
|
27622
28571
|
return ring;
|
|
27623
28572
|
}
|
|
27624
28573
|
function smallCircle(center, radiusDeg, steps = 128) {
|
|
27625
|
-
const φ0 = center.lat * DEG$
|
|
27626
|
-
const λ0 = center.lon * DEG$
|
|
28574
|
+
const φ0 = center.lat * DEG$5;
|
|
28575
|
+
const λ0 = center.lon * DEG$5;
|
|
27627
28576
|
const c = [
|
|
27628
28577
|
Math.cos(φ0) * Math.cos(λ0),
|
|
27629
28578
|
Math.cos(φ0) * Math.sin(λ0),
|
|
@@ -27649,7 +28598,7 @@ function smallCircle(center, radiusDeg, steps = 128) {
|
|
|
27649
28598
|
c[2] * e[0] - c[0] * e[2],
|
|
27650
28599
|
c[0] * e[1] - c[1] * e[0]
|
|
27651
28600
|
];
|
|
27652
|
-
const r = radiusDeg * DEG$
|
|
28601
|
+
const r = radiusDeg * DEG$5;
|
|
27653
28602
|
const sinR = Math.sin(r);
|
|
27654
28603
|
const cosR = Math.cos(r);
|
|
27655
28604
|
const ring = [];
|
|
@@ -29379,8 +30328,202 @@ var Overlay = class {
|
|
|
29379
30328
|
}
|
|
29380
30329
|
};
|
|
29381
30330
|
|
|
30331
|
+
//#endregion
|
|
30332
|
+
//#region src/lib/flatmap/tile-plan.ts
|
|
30333
|
+
var DEG$4 = Math.PI / 180;
|
|
30334
|
+
var MAX_STRETCH = 1.25;
|
|
30335
|
+
var MAX_WINDOW_PIXELS = 8e6;
|
|
30336
|
+
var GRID = 17;
|
|
30337
|
+
var MARGIN = .15;
|
|
30338
|
+
var CAP_GAIN = .75;
|
|
30339
|
+
function chartFraction(chart, lon, lat) {
|
|
30340
|
+
if (chart === 0) return [(lon + 180) / 360, (90 - lat) / 180];
|
|
30341
|
+
const side = chart === 1 ? 1 : -1;
|
|
30342
|
+
const r = .5 * Math.tan((90 - side * lat) * DEG$4 / 2) / CAP_EDGE;
|
|
30343
|
+
return [.5 + r * Math.cos(lon * DEG$4), .5 - side * r * Math.sin(lon * DEG$4)];
|
|
30344
|
+
}
|
|
30345
|
+
function levelForStep(step, tileSize) {
|
|
30346
|
+
return Math.ceil(Math.log2(2 * Math.PI / (step * MAX_STRETCH * 2 * tileSize)));
|
|
30347
|
+
}
|
|
30348
|
+
function stepOfLevel(level, tileSize) {
|
|
30349
|
+
return 2 * Math.PI / (2 * tileSize * 2 ** level);
|
|
30350
|
+
}
|
|
30351
|
+
function finestLevelAt(pyramid, lon, lat) {
|
|
30352
|
+
const [across, fy] = chartFraction(0, lon, lat);
|
|
30353
|
+
const fx = across - Math.floor(across);
|
|
30354
|
+
let finest = pyramid.max_level;
|
|
30355
|
+
for (const patch of pyramid.patches ?? []) {
|
|
30356
|
+
if (patch.max_level <= finest) continue;
|
|
30357
|
+
const x = Math.floor(fx * chartColumns(0, patch.max_level));
|
|
30358
|
+
const y = Math.floor(fy * chartRows(0, patch.max_level));
|
|
30359
|
+
if (patchOfTile([patch], patch.max_level, x, y)) finest = patch.max_level;
|
|
30360
|
+
}
|
|
30361
|
+
return finest;
|
|
30362
|
+
}
|
|
30363
|
+
function extentOn(chart, frame, places, centre) {
|
|
30364
|
+
const [cx] = chartFraction(0, centre[0], centre[1]);
|
|
30365
|
+
const extent = {
|
|
30366
|
+
minX: Infinity,
|
|
30367
|
+
maxX: -Infinity,
|
|
30368
|
+
minY: Infinity,
|
|
30369
|
+
maxY: -Infinity
|
|
30370
|
+
};
|
|
30371
|
+
for (const [lon, lat] of places) {
|
|
30372
|
+
const [across, y] = chartFraction(chart, lon, lat);
|
|
30373
|
+
if (chart !== 0 && (across < 0 || across > 1 || y < 0 || y > 1)) return null;
|
|
30374
|
+
const x = chart === 0 ? across - Math.round(across - cx) : across;
|
|
30375
|
+
extent.minX = Math.min(extent.minX, x);
|
|
30376
|
+
extent.maxX = Math.max(extent.maxX, x);
|
|
30377
|
+
extent.minY = Math.min(extent.minY, y);
|
|
30378
|
+
extent.maxY = Math.max(extent.maxY, y);
|
|
30379
|
+
}
|
|
30380
|
+
const padX = (extent.maxX - extent.minX) / (GRID - 1);
|
|
30381
|
+
const padY = (extent.maxY - extent.minY) / (GRID - 1);
|
|
30382
|
+
extent.minX -= padX;
|
|
30383
|
+
extent.maxX += padX;
|
|
30384
|
+
extent.minY -= padY;
|
|
30385
|
+
extent.maxY += padY;
|
|
30386
|
+
if (chart === 0 && (frame.north || frame.south)) {
|
|
30387
|
+
extent.minX = cx - .5;
|
|
30388
|
+
extent.maxX = cx + .5;
|
|
30389
|
+
if (frame.north) extent.minY = 0;
|
|
30390
|
+
if (frame.south) extent.maxY = 1;
|
|
30391
|
+
}
|
|
30392
|
+
return extent;
|
|
30393
|
+
}
|
|
30394
|
+
function windowOver(chart, level, tileSize, extent, margin) {
|
|
30395
|
+
const chartWidth = chartColumns(chart, level) * tileSize;
|
|
30396
|
+
const chartHeight = chartRows(chart, level) * tileSize;
|
|
30397
|
+
const rect = (grow) => {
|
|
30398
|
+
const growX = (extent.maxX - extent.minX) * grow;
|
|
30399
|
+
const growY = (extent.maxY - extent.minY) * grow;
|
|
30400
|
+
let x0 = Math.floor((extent.minX - growX) * chartWidth);
|
|
30401
|
+
let x1 = Math.ceil((extent.maxX + growX) * chartWidth);
|
|
30402
|
+
if (chart !== 0) {
|
|
30403
|
+
x0 = Math.max(x0, 0);
|
|
30404
|
+
x1 = Math.min(x1, chartWidth);
|
|
30405
|
+
} else if (x1 - x0 > chartWidth) {
|
|
30406
|
+
x0 = Math.round((extent.minX + extent.maxX) / 2 * chartWidth - chartWidth / 2);
|
|
30407
|
+
x1 = x0 + chartWidth;
|
|
30408
|
+
}
|
|
30409
|
+
const y0 = Math.max(Math.floor((extent.minY - growY) * chartHeight), 0);
|
|
30410
|
+
const y1 = Math.min(Math.ceil((extent.maxY + growY) * chartHeight), chartHeight);
|
|
30411
|
+
return [
|
|
30412
|
+
x0,
|
|
30413
|
+
y0,
|
|
30414
|
+
x1,
|
|
30415
|
+
y1
|
|
30416
|
+
];
|
|
30417
|
+
};
|
|
30418
|
+
const [x0, y0, x1, y1] = rect(margin);
|
|
30419
|
+
const inner = rect(0);
|
|
30420
|
+
return {
|
|
30421
|
+
chart,
|
|
30422
|
+
level,
|
|
30423
|
+
x0,
|
|
30424
|
+
y0,
|
|
30425
|
+
width: Math.max(x1 - x0, 1),
|
|
30426
|
+
height: Math.max(y1 - y0, 1),
|
|
30427
|
+
inner: [
|
|
30428
|
+
inner[0],
|
|
30429
|
+
inner[1],
|
|
30430
|
+
inner[2] - 1,
|
|
30431
|
+
inner[3] - 1
|
|
30432
|
+
]
|
|
30433
|
+
};
|
|
30434
|
+
}
|
|
30435
|
+
function fitted(chart, level, tileSize, extent, margin) {
|
|
30436
|
+
for (const grow of [margin, 0]) {
|
|
30437
|
+
const spec = windowOver(chart, level, tileSize, extent, grow);
|
|
30438
|
+
if (spec.width * spec.height <= 8e6) return spec;
|
|
30439
|
+
}
|
|
30440
|
+
}
|
|
30441
|
+
function reaches(patch, extent) {
|
|
30442
|
+
const columns = chartColumns(0, patch.max_level);
|
|
30443
|
+
const rows = chartRows(0, patch.max_level);
|
|
30444
|
+
const south = (patch.y[1] + 1) / rows;
|
|
30445
|
+
if (patch.y[0] / rows > extent.maxY || south < extent.minY) return false;
|
|
30446
|
+
let west = patch.x[0] / columns;
|
|
30447
|
+
west -= Math.round(west - (extent.minX + extent.maxX) / 2);
|
|
30448
|
+
const east = west + (patch.x[1] + 1 - patch.x[0]) / columns;
|
|
30449
|
+
return west <= extent.maxX && east >= extent.minX;
|
|
30450
|
+
}
|
|
30451
|
+
function planWindows(pyramid, frame, firstLevel, moves = true) {
|
|
30452
|
+
const margin = moves ? MARGIN : 0;
|
|
30453
|
+
const centre = frame.place(.5, .5);
|
|
30454
|
+
if (!centre || !(frame.step > 0)) return [];
|
|
30455
|
+
const places = [];
|
|
30456
|
+
for (let j = 0; j < GRID; j++) for (let i = 0; i < GRID; i++) {
|
|
30457
|
+
const place = frame.place(i / (GRID - 1), j / (GRID - 1));
|
|
30458
|
+
if (place) places.push(place);
|
|
30459
|
+
}
|
|
30460
|
+
const onMap = extentOn(0, frame, places, centre);
|
|
30461
|
+
const wanted = levelForStep(frame.step, pyramid.tile_size);
|
|
30462
|
+
const specs = [];
|
|
30463
|
+
let finest = pyramid.max_level;
|
|
30464
|
+
for (const patch of pyramid.patches ?? []) if (patch.max_level > finest && reaches(patch, onMap)) finest = patch.max_level;
|
|
30465
|
+
for (let level = Math.min(wanted, finest); level > pyramid.max_level; level--) {
|
|
30466
|
+
const spec = fitted(0, level, pyramid.tile_size, onMap, margin);
|
|
30467
|
+
if (!spec) continue;
|
|
30468
|
+
specs.push(spec);
|
|
30469
|
+
break;
|
|
30470
|
+
}
|
|
30471
|
+
const cap = centre[1] >= 0 ? 1 : 2;
|
|
30472
|
+
const onCap = extentOn(cap, frame, places, centre);
|
|
30473
|
+
for (let level = Math.min(wanted, pyramid.max_level); level >= firstLevel; level--) {
|
|
30474
|
+
const map = fitted(0, level, pyramid.tile_size, onMap, margin);
|
|
30475
|
+
const polar = onCap && level >= 1 ? fitted(cap, level, pyramid.tile_size, onCap, margin) : void 0;
|
|
30476
|
+
const mapPixels = map ? map.width * map.height : Infinity;
|
|
30477
|
+
const spec = polar && polar.width * polar.height <= mapPixels * CAP_GAIN ? polar : map ?? polar;
|
|
30478
|
+
if (!spec) continue;
|
|
30479
|
+
specs.push(spec);
|
|
30480
|
+
break;
|
|
30481
|
+
}
|
|
30482
|
+
return specs;
|
|
30483
|
+
}
|
|
30484
|
+
function windowTiles(spec, pyramid) {
|
|
30485
|
+
const size = pyramid.tile_size;
|
|
30486
|
+
const columns = chartColumns(spec.chart, spec.level);
|
|
30487
|
+
const rows = chartRows(spec.chart, spec.level);
|
|
30488
|
+
const tiles = [];
|
|
30489
|
+
const lastY = Math.min(Math.floor((spec.y0 + spec.height - 1) / size), rows - 1);
|
|
30490
|
+
const lastX = Math.floor((spec.x0 + spec.width - 1) / size);
|
|
30491
|
+
for (let ty = Math.max(Math.floor(spec.y0 / size), 0); ty <= lastY; ty++) for (let tx = Math.floor(spec.x0 / size); tx <= lastX; tx++) {
|
|
30492
|
+
if (spec.chart !== 0 && (tx < 0 || tx >= columns)) continue;
|
|
30493
|
+
const x = (tx % columns + columns) % columns;
|
|
30494
|
+
const patch = spec.level > pyramid.max_level ? patchOfTile(pyramid.patches, spec.level, x, ty) : void 0;
|
|
30495
|
+
if (spec.level > pyramid.max_level && !patch) continue;
|
|
30496
|
+
tiles.push({
|
|
30497
|
+
cap: CHART_FOLDER[spec.chart],
|
|
30498
|
+
level: spec.level,
|
|
30499
|
+
x,
|
|
30500
|
+
y: ty,
|
|
30501
|
+
dx: tx * size - spec.x0,
|
|
30502
|
+
dy: ty * size - spec.y0,
|
|
30503
|
+
patch
|
|
30504
|
+
});
|
|
30505
|
+
}
|
|
30506
|
+
const midX = spec.width / 2 - size / 2;
|
|
30507
|
+
const midY = spec.height / 2 - size / 2;
|
|
30508
|
+
const off = (tile) => Math.hypot(tile.dx - midX, tile.dy - midY);
|
|
30509
|
+
return tiles.sort((a, b) => off(a) - off(b));
|
|
30510
|
+
}
|
|
30511
|
+
function covers(held, spec, tileSize) {
|
|
30512
|
+
if (held.chart !== spec.chart || held.level !== spec.level) return false;
|
|
30513
|
+
let turn = 0;
|
|
30514
|
+
if (spec.chart === 0) {
|
|
30515
|
+
const chartWidth = chartColumns(0, spec.level) * tileSize;
|
|
30516
|
+
turn = chartWidth * Math.round((spec.inner[0] - held.x0) / chartWidth);
|
|
30517
|
+
}
|
|
30518
|
+
return held.x0 + turn <= spec.inner[0] && held.x0 + turn + held.width > spec.inner[2] && held.y0 <= spec.inner[1] && held.y0 + held.height > spec.inner[3];
|
|
30519
|
+
}
|
|
30520
|
+
|
|
29382
30521
|
//#endregion
|
|
29383
30522
|
//#region src/lib/flatmap/raster.ts
|
|
30523
|
+
var CAP_GRID = 8;
|
|
30524
|
+
function chartWidth(win) {
|
|
30525
|
+
return chartColumns(win.chart, win.level) * win.tileSize;
|
|
30526
|
+
}
|
|
29384
30527
|
function sourceSize(source) {
|
|
29385
30528
|
const width = "width" in source ? source.width : source.codedWidth;
|
|
29386
30529
|
const height = "height" in source ? source.height : source.codedHeight;
|
|
@@ -29413,9 +30556,12 @@ function imageToRaster(source, maxWidth) {
|
|
|
29413
30556
|
var InverseLookup = class {
|
|
29414
30557
|
width;
|
|
29415
30558
|
height;
|
|
30559
|
+
refU;
|
|
30560
|
+
refV;
|
|
29416
30561
|
u;
|
|
29417
30562
|
v;
|
|
29418
30563
|
inside;
|
|
30564
|
+
caps = /* @__PURE__ */ new Map();
|
|
29419
30565
|
constructor(viewport, width, height) {
|
|
29420
30566
|
this.width = width;
|
|
29421
30567
|
this.height = height;
|
|
@@ -29424,6 +30570,8 @@ var InverseLookup = class {
|
|
|
29424
30570
|
this.v = new Float32Array(count);
|
|
29425
30571
|
this.inside = new Uint8Array(count);
|
|
29426
30572
|
const { projection } = viewport;
|
|
30573
|
+
const middle = projection.inverse(viewport.view.centerX, viewport.view.centerY);
|
|
30574
|
+
[this.refU, this.refV] = middle ? chartFraction(0, middle[0], middle[1]) : [.5, .5];
|
|
29427
30575
|
const step = 1 / viewport.scale;
|
|
29428
30576
|
const x0 = viewport.view.centerX - viewport.width / 2 * step;
|
|
29429
30577
|
const y0 = viewport.view.centerY + viewport.height / 2 * step;
|
|
@@ -29437,8 +30585,8 @@ var InverseLookup = class {
|
|
|
29437
30585
|
fillRow(projection, row, y, x0, step, viewport) {
|
|
29438
30586
|
const line = projection.rowInverse(y);
|
|
29439
30587
|
if (!line) return;
|
|
29440
|
-
const { width } = this;
|
|
29441
|
-
const v = (90 - line.lat) / 180;
|
|
30588
|
+
const { width, refU } = this;
|
|
30589
|
+
const v = (90 - line.lat) / 180 - this.refV;
|
|
29442
30590
|
const { minX, maxX } = projection.extent;
|
|
29443
30591
|
const worldWidth = maxX - minX;
|
|
29444
30592
|
const repeating = viewport.repeatsHorizontally;
|
|
@@ -29447,9 +30595,9 @@ var InverseLookup = class {
|
|
|
29447
30595
|
if (repeating) x = ((x - minX) % worldWidth + worldWidth) % worldWidth + minX;
|
|
29448
30596
|
else if (x < minX || x > maxX) continue;
|
|
29449
30597
|
if (x < -line.maxAbsX || x > line.maxAbsX) continue;
|
|
29450
|
-
const turns = (x * line.lonPerX + projection.centerLon + 180) / 360;
|
|
30598
|
+
const turns = (x * line.lonPerX + projection.centerLon + 180) / 360 - refU;
|
|
29451
30599
|
const i = row + px;
|
|
29452
|
-
this.u[i] = turns - Math.
|
|
30600
|
+
this.u[i] = turns - Math.round(turns);
|
|
29453
30601
|
this.v[i] = v;
|
|
29454
30602
|
this.inside[i] = 1;
|
|
29455
30603
|
}
|
|
@@ -29470,31 +30618,154 @@ var InverseLookup = class {
|
|
|
29470
30618
|
const place = projection.inverse(x, y);
|
|
29471
30619
|
if (!place) continue;
|
|
29472
30620
|
const i = row + px;
|
|
29473
|
-
|
|
29474
|
-
this.
|
|
30621
|
+
const turns = (place[0] + 180) / 360 - this.refU;
|
|
30622
|
+
this.u[i] = turns - Math.round(turns);
|
|
30623
|
+
this.v[i] = (90 - place[1]) / 180 - this.refV;
|
|
29475
30624
|
this.inside[i] = 1;
|
|
29476
30625
|
}
|
|
29477
30626
|
}
|
|
30627
|
+
offsets(chart) {
|
|
30628
|
+
if (chart === 0) return {
|
|
30629
|
+
refX: this.refU,
|
|
30630
|
+
refY: this.refV,
|
|
30631
|
+
x: this.u,
|
|
30632
|
+
y: this.v
|
|
30633
|
+
};
|
|
30634
|
+
let cap = this.caps.get(chart);
|
|
30635
|
+
if (!cap) {
|
|
30636
|
+
cap = this.capOffsets(chart);
|
|
30637
|
+
this.caps.set(chart, cap);
|
|
30638
|
+
}
|
|
30639
|
+
return cap;
|
|
30640
|
+
}
|
|
30641
|
+
capOffsets(chart) {
|
|
30642
|
+
const { u, v, inside, refU, refV, width, height } = this;
|
|
30643
|
+
const side = chart === 1 ? 1 : -1;
|
|
30644
|
+
const [refX, refY] = chartFraction(chart, refU * 360 - 180, 90 - refV * 180);
|
|
30645
|
+
const x = new Float32Array(u.length);
|
|
30646
|
+
const y = new Float32Array(u.length);
|
|
30647
|
+
const exact = (i) => {
|
|
30648
|
+
const fromPole = side > 0 ? refV + v[i] : 1 - refV - v[i];
|
|
30649
|
+
const radius = .5 * Math.tan(fromPole * Math.PI / 2) / CAP_EDGE;
|
|
30650
|
+
const lon = (refU + u[i]) * 2 * Math.PI - Math.PI;
|
|
30651
|
+
x[i] = .5 + radius * Math.cos(lon) - refX;
|
|
30652
|
+
y[i] = .5 - side * radius * Math.sin(lon) - refY;
|
|
30653
|
+
};
|
|
30654
|
+
const columns = gridLines(width);
|
|
30655
|
+
const rows = gridLines(height);
|
|
30656
|
+
if (columns.length < 2 || rows.length < 2) {
|
|
30657
|
+
for (let i = 0; i < u.length; i++) if (inside[i]) exact(i);
|
|
30658
|
+
return {
|
|
30659
|
+
refX,
|
|
30660
|
+
refY,
|
|
30661
|
+
x,
|
|
30662
|
+
y
|
|
30663
|
+
};
|
|
30664
|
+
}
|
|
30665
|
+
for (const py of rows) for (const px of columns) if (inside[py * width + px]) exact(py * width + px);
|
|
30666
|
+
for (let r = 0; r < rows.length - 1; r++) {
|
|
30667
|
+
const top = rows[r];
|
|
30668
|
+
const bottom = rows[r + 1];
|
|
30669
|
+
const lastRow = r === rows.length - 2 ? bottom : bottom - 1;
|
|
30670
|
+
for (let c = 0; c < columns.length - 1; c++) {
|
|
30671
|
+
const left = columns[c];
|
|
30672
|
+
const right = columns[c + 1];
|
|
30673
|
+
const lastColumn = c === columns.length - 2 ? right : right - 1;
|
|
30674
|
+
const nw = top * width + left;
|
|
30675
|
+
const ne = top * width + right;
|
|
30676
|
+
const sw = bottom * width + left;
|
|
30677
|
+
const se = bottom * width + right;
|
|
30678
|
+
if (!(inside[nw] && inside[ne] && inside[sw] && inside[se])) {
|
|
30679
|
+
for (let py = top; py <= lastRow; py++) for (let i = py * width + left; i <= py * width + lastColumn; i++) if (inside[i]) exact(i);
|
|
30680
|
+
continue;
|
|
30681
|
+
}
|
|
30682
|
+
for (let py = top; py <= lastRow; py++) {
|
|
30683
|
+
const fy = (py - top) / (bottom - top);
|
|
30684
|
+
let atX = x[nw] + fy * (x[sw] - x[nw]);
|
|
30685
|
+
let atY = y[nw] + fy * (y[sw] - y[nw]);
|
|
30686
|
+
const stepX = (x[ne] + fy * (x[se] - x[ne]) - atX) / (right - left);
|
|
30687
|
+
const stepY = (y[ne] + fy * (y[se] - y[ne]) - atY) / (right - left);
|
|
30688
|
+
const skip = py === top || py === bottom;
|
|
30689
|
+
for (let px = left; px <= lastColumn; px++) {
|
|
30690
|
+
if (!(skip && (px === left || px === right))) {
|
|
30691
|
+
x[py * width + px] = atX;
|
|
30692
|
+
y[py * width + px] = atY;
|
|
30693
|
+
}
|
|
30694
|
+
atX += stepX;
|
|
30695
|
+
atY += stepY;
|
|
30696
|
+
}
|
|
30697
|
+
}
|
|
30698
|
+
}
|
|
30699
|
+
}
|
|
30700
|
+
return {
|
|
30701
|
+
refX,
|
|
30702
|
+
refY,
|
|
30703
|
+
x,
|
|
30704
|
+
y
|
|
30705
|
+
};
|
|
30706
|
+
}
|
|
29478
30707
|
};
|
|
30708
|
+
function gridLines(length) {
|
|
30709
|
+
const lines = [];
|
|
30710
|
+
for (let at = 0; at < length - 1; at += CAP_GRID) lines.push(at);
|
|
30711
|
+
lines.push(length - 1);
|
|
30712
|
+
return lines;
|
|
30713
|
+
}
|
|
29479
30714
|
function compositeLayers(lookup, layers, target) {
|
|
29480
30715
|
const out = target.data;
|
|
29481
30716
|
out.fill(0);
|
|
29482
|
-
const { width, height, u, v, inside } = lookup;
|
|
30717
|
+
const { width, height, u, v, inside, refU, refV } = lookup;
|
|
29483
30718
|
const count = width * height;
|
|
29484
30719
|
for (const layer of layers) {
|
|
29485
30720
|
const { image, opacity, blend } = layer;
|
|
29486
30721
|
if (opacity <= 0) continue;
|
|
29487
|
-
const
|
|
30722
|
+
const base = image.data;
|
|
29488
30723
|
const sw = image.width;
|
|
29489
30724
|
const sh = image.height;
|
|
29490
30725
|
const add = blend === "add";
|
|
30726
|
+
const windows = (layer.detail ?? []).map((win) => {
|
|
30727
|
+
const offsets = lookup.offsets(win.chart);
|
|
30728
|
+
const across = chartWidth(win);
|
|
30729
|
+
const down = win.chart === 0 ? across / 2 : across;
|
|
30730
|
+
let left = offsets.refX * across - win.x0;
|
|
30731
|
+
if (win.chart === 0) left -= across * Math.round((left - win.width / 2) / across);
|
|
30732
|
+
return {
|
|
30733
|
+
xs: offsets.x,
|
|
30734
|
+
ys: offsets.y,
|
|
30735
|
+
left,
|
|
30736
|
+
top: offsets.refY * down - win.y0,
|
|
30737
|
+
across,
|
|
30738
|
+
down,
|
|
30739
|
+
width: win.width,
|
|
30740
|
+
height: win.height,
|
|
30741
|
+
data: win.data
|
|
30742
|
+
};
|
|
30743
|
+
});
|
|
29491
30744
|
for (let i = 0; i < count; i++) {
|
|
29492
30745
|
if (!inside[i]) continue;
|
|
29493
|
-
let
|
|
29494
|
-
let
|
|
29495
|
-
|
|
29496
|
-
|
|
29497
|
-
|
|
30746
|
+
let src = base;
|
|
30747
|
+
let s = -1;
|
|
30748
|
+
for (let k = 0; k < windows.length; k++) {
|
|
30749
|
+
const win = windows[k];
|
|
30750
|
+
const tx = win.left + win.xs[i] * win.across;
|
|
30751
|
+
if (!(tx >= 0 && tx < win.width)) continue;
|
|
30752
|
+
const ty = win.top + win.ys[i] * win.down;
|
|
30753
|
+
if (!(ty >= 0 && ty < win.height)) continue;
|
|
30754
|
+
const t = ((ty | 0) * win.width + (tx | 0)) * 4;
|
|
30755
|
+
if (win.data[t + 3] === 0) continue;
|
|
30756
|
+
src = win.data;
|
|
30757
|
+
s = t;
|
|
30758
|
+
break;
|
|
30759
|
+
}
|
|
30760
|
+
if (s < 0) {
|
|
30761
|
+
let across = refU + u[i];
|
|
30762
|
+
across -= Math.floor(across);
|
|
30763
|
+
let sx = Math.floor(across * sw);
|
|
30764
|
+
let sy = Math.floor((refV + v[i]) * sh);
|
|
30765
|
+
sx = sx < 0 ? sx + sw : sx >= sw ? sx - sw : sx;
|
|
30766
|
+
sy = sy < 0 ? 0 : sy >= sh ? sh - 1 : sy;
|
|
30767
|
+
s = (sy * sw + sx) * 4;
|
|
30768
|
+
}
|
|
29498
30769
|
const alpha = src[s + 3] / 255 * opacity;
|
|
29499
30770
|
if (alpha <= 0) continue;
|
|
29500
30771
|
const d = i * 4;
|
|
@@ -29528,22 +30799,56 @@ function drawEquirect(ctx, viewport, layers) {
|
|
|
29528
30799
|
const [right, bottom] = viewport.toScreen(maxX - meridian, minY);
|
|
29529
30800
|
const from = viewport.repeatsHorizontally ? 0 : viewport.toScreen(minX, maxY)[0];
|
|
29530
30801
|
const to = viewport.repeatsHorizontally ? ctx.canvas.width : viewport.toScreen(maxX, maxY)[0];
|
|
30802
|
+
const paint = (target, layer, size) => {
|
|
30803
|
+
const pattern = target.createPattern(layer.image, "repeat-x");
|
|
30804
|
+
if (!pattern) return;
|
|
30805
|
+
pattern.setTransform(new DOMMatrix().translateSelf(left, top).scaleSelf((right - left) / size[0], (bottom - top) / size[1]));
|
|
30806
|
+
target.save();
|
|
30807
|
+
target.imageSmoothingEnabled = true;
|
|
30808
|
+
target.imageSmoothingQuality = "high";
|
|
30809
|
+
target.fillStyle = pattern;
|
|
30810
|
+
target.fillRect(from, top, to - from, bottom - top);
|
|
30811
|
+
const detail = layer.detail ?? [];
|
|
30812
|
+
if (detail.length) {
|
|
30813
|
+
target.beginPath();
|
|
30814
|
+
target.rect(from, top, to - from, bottom - top);
|
|
30815
|
+
target.clip();
|
|
30816
|
+
}
|
|
30817
|
+
for (let k = detail.length - 1; k >= 0; k--) {
|
|
30818
|
+
const win = detail[k];
|
|
30819
|
+
const scale = (right - left) / chartWidth(win);
|
|
30820
|
+
let x = left + win.x0 * scale;
|
|
30821
|
+
const middle = x + win.width * scale / 2;
|
|
30822
|
+
x += (right - left) * Math.round((target.canvas.width / 2 - middle) / (right - left));
|
|
30823
|
+
target.drawImage(win.canvas, x, top + win.y0 * scale, win.width * scale, win.height * scale);
|
|
30824
|
+
}
|
|
30825
|
+
target.restore();
|
|
30826
|
+
};
|
|
29531
30827
|
ctx.save();
|
|
29532
|
-
ctx.imageSmoothingEnabled = true;
|
|
29533
|
-
ctx.imageSmoothingQuality = "high";
|
|
29534
30828
|
for (const layer of layers) {
|
|
29535
30829
|
if (layer.opacity <= 0) continue;
|
|
29536
30830
|
const size = sourceSize(layer.image);
|
|
29537
|
-
|
|
29538
|
-
if (!pattern) continue;
|
|
29539
|
-
pattern.setTransform(new DOMMatrix().translateSelf(left, top).scaleSelf((right - left) / size[0], (bottom - top) / size[1]));
|
|
30831
|
+
if (!size) continue;
|
|
29540
30832
|
ctx.globalAlpha = layer.opacity;
|
|
29541
30833
|
ctx.globalCompositeOperation = layer.blend === "add" ? "lighter" : "source-over";
|
|
29542
|
-
ctx.
|
|
29543
|
-
|
|
30834
|
+
const scratch = layer.detail?.length && (layer.opacity < 1 || layer.blend === "add") ? scratchContext(ctx.canvas.width, ctx.canvas.height) : null;
|
|
30835
|
+
if (scratch) {
|
|
30836
|
+
paint(scratch, layer, size);
|
|
30837
|
+
ctx.drawImage(scratch.canvas, 0, 0);
|
|
30838
|
+
} else paint(ctx, layer, size);
|
|
29544
30839
|
}
|
|
29545
30840
|
ctx.restore();
|
|
29546
30841
|
}
|
|
30842
|
+
var scratch$1 = null;
|
|
30843
|
+
function scratchContext(width, height) {
|
|
30844
|
+
scratch$1 ??= document.createElement("canvas").getContext("2d");
|
|
30845
|
+
if (!scratch$1) return null;
|
|
30846
|
+
if (scratch$1.canvas.width !== width || scratch$1.canvas.height !== height) {
|
|
30847
|
+
scratch$1.canvas.width = width;
|
|
30848
|
+
scratch$1.canvas.height = height;
|
|
30849
|
+
} else scratch$1.clearRect(0, 0, width, height);
|
|
30850
|
+
return scratch$1;
|
|
30851
|
+
}
|
|
29547
30852
|
|
|
29548
30853
|
//#endregion
|
|
29549
30854
|
//#region src/lib/flatmap/sources.ts
|
|
@@ -29682,10 +30987,14 @@ var Store = class {
|
|
|
29682
30987
|
this.evict();
|
|
29683
30988
|
return value;
|
|
29684
30989
|
}
|
|
29685
|
-
release(key) {
|
|
30990
|
+
release(key, keep = true) {
|
|
29686
30991
|
const entry = this.entries.get(key);
|
|
29687
30992
|
if (!entry) return;
|
|
29688
30993
|
entry.claims = Math.max(0, entry.claims - 1);
|
|
30994
|
+
if (!keep && !entry.claims) {
|
|
30995
|
+
this.entries.delete(key);
|
|
30996
|
+
this.free(entry.value);
|
|
30997
|
+
}
|
|
29689
30998
|
this.evict();
|
|
29690
30999
|
}
|
|
29691
31000
|
evict() {
|
|
@@ -29723,7 +31032,8 @@ async function acquireBitmap(url, decodeWidth) {
|
|
|
29723
31032
|
if (!work) {
|
|
29724
31033
|
work = decode(url, decodeWidth);
|
|
29725
31034
|
pending.set(key, work);
|
|
29726
|
-
|
|
31035
|
+
const done = () => pending.delete(key);
|
|
31036
|
+
work.then(done, done);
|
|
29727
31037
|
}
|
|
29728
31038
|
const bitmap = await work;
|
|
29729
31039
|
if (!bitmap) return null;
|
|
@@ -29746,8 +31056,8 @@ async function decode(url, decodeWidth) {
|
|
|
29746
31056
|
return createImageBitmap(blob);
|
|
29747
31057
|
}
|
|
29748
31058
|
}
|
|
29749
|
-
function releaseBitmap(key) {
|
|
29750
|
-
bitmaps.release(key);
|
|
31059
|
+
function releaseBitmap(key, keep = true) {
|
|
31060
|
+
bitmaps.release(key, keep);
|
|
29751
31061
|
}
|
|
29752
31062
|
function acquireRaster(url, workingWidth, source) {
|
|
29753
31063
|
const key = textureKey(url, workingWidth);
|
|
@@ -29767,6 +31077,129 @@ function releaseRaster(key) {
|
|
|
29767
31077
|
rasters.release(key);
|
|
29768
31078
|
}
|
|
29769
31079
|
|
|
31080
|
+
//#endregion
|
|
31081
|
+
//#region src/lib/flatmap/tile-windows.ts
|
|
31082
|
+
var MAX_FAILURES = 4;
|
|
31083
|
+
var MAX_STALE = 2;
|
|
31084
|
+
var scratch = null;
|
|
31085
|
+
function copyTile(win, tile, dx, dy) {
|
|
31086
|
+
const x0 = Math.max(dx, 0);
|
|
31087
|
+
const y0 = Math.max(dy, 0);
|
|
31088
|
+
const width = Math.min(dx + tile.width, win.width) - x0;
|
|
31089
|
+
const height = Math.min(dy + tile.height, win.height) - y0;
|
|
31090
|
+
if (width <= 0 || height <= 0) return;
|
|
31091
|
+
scratch ??= document.createElement("canvas").getContext("2d", { willReadFrequently: true });
|
|
31092
|
+
if (!scratch) return;
|
|
31093
|
+
if (scratch.canvas.width < tile.width || scratch.canvas.height < tile.height) {
|
|
31094
|
+
scratch.canvas.width = tile.width;
|
|
31095
|
+
scratch.canvas.height = tile.height;
|
|
31096
|
+
}
|
|
31097
|
+
scratch.globalCompositeOperation = "copy";
|
|
31098
|
+
scratch.drawImage(tile, x0 - dx, y0 - dy, width, height, 0, 0, width, height);
|
|
31099
|
+
const pixels = scratch.getImageData(0, 0, width, height).data;
|
|
31100
|
+
const row = width * 4;
|
|
31101
|
+
for (let y = 0; y < height; y++) win.data.set(pixels.subarray(y * row, (y + 1) * row), ((y0 + y) * win.width + x0) * 4);
|
|
31102
|
+
}
|
|
31103
|
+
var LayerTiles = class {
|
|
31104
|
+
windows = [];
|
|
31105
|
+
kind = "pixels";
|
|
31106
|
+
failures = 0;
|
|
31107
|
+
failed = /* @__PURE__ */ new Set();
|
|
31108
|
+
dead = false;
|
|
31109
|
+
constructor(block, changed, moves) {
|
|
31110
|
+
this.block = block;
|
|
31111
|
+
this.changed = changed;
|
|
31112
|
+
this.moves = moves;
|
|
31113
|
+
}
|
|
31114
|
+
plan(specs, kind, frame) {
|
|
31115
|
+
if (kind !== this.kind) {
|
|
31116
|
+
this.clear();
|
|
31117
|
+
this.kind = kind;
|
|
31118
|
+
}
|
|
31119
|
+
for (const win of this.windows) win.wanted = false;
|
|
31120
|
+
for (const spec of this.dead ? [] : specs) {
|
|
31121
|
+
const held = this.windows.find((win) => win.frame === frame && covers(win, spec, this.block.tile_size));
|
|
31122
|
+
if (held) held.wanted = true;
|
|
31123
|
+
else this.windows.push(this.open(spec, frame));
|
|
31124
|
+
}
|
|
31125
|
+
this.prune();
|
|
31126
|
+
}
|
|
31127
|
+
rasters() {
|
|
31128
|
+
return this.ordered().filter((win) => !!win.data);
|
|
31129
|
+
}
|
|
31130
|
+
canvases() {
|
|
31131
|
+
return this.ordered().flatMap((win) => win.ctx ? [{
|
|
31132
|
+
...win,
|
|
31133
|
+
canvas: win.ctx.canvas
|
|
31134
|
+
}] : []);
|
|
31135
|
+
}
|
|
31136
|
+
clear() {
|
|
31137
|
+
for (const win of this.windows) this.drop(win);
|
|
31138
|
+
this.windows = [];
|
|
31139
|
+
}
|
|
31140
|
+
drop(win) {
|
|
31141
|
+
win.dropped = true;
|
|
31142
|
+
win.data = void 0;
|
|
31143
|
+
if (win.ctx) win.ctx.canvas.width = 0;
|
|
31144
|
+
}
|
|
31145
|
+
ordered() {
|
|
31146
|
+
return [...this.windows].sort((a, b) => b.level - a.level || Number(b.wanted) - Number(a.wanted));
|
|
31147
|
+
}
|
|
31148
|
+
open(spec, frame) {
|
|
31149
|
+
const win = {
|
|
31150
|
+
...spec,
|
|
31151
|
+
tileSize: this.block.tile_size,
|
|
31152
|
+
frame,
|
|
31153
|
+
pending: 0,
|
|
31154
|
+
wanted: true,
|
|
31155
|
+
dropped: false
|
|
31156
|
+
};
|
|
31157
|
+
if (this.kind === "canvas") {
|
|
31158
|
+
const canvas = document.createElement("canvas");
|
|
31159
|
+
canvas.width = spec.width;
|
|
31160
|
+
canvas.height = spec.height;
|
|
31161
|
+
win.ctx = canvas.getContext("2d") ?? void 0;
|
|
31162
|
+
} else win.data = new Uint8ClampedArray(spec.width * spec.height * 4);
|
|
31163
|
+
for (const tile of windowTiles(spec, this.block)) {
|
|
31164
|
+
const url = tileUrl({
|
|
31165
|
+
id: this.block.id,
|
|
31166
|
+
version: tile.patch?.version ?? this.block.version
|
|
31167
|
+
}, tile.cap, tile.level, tile.x, tile.y, frame);
|
|
31168
|
+
if (this.failed.has(url)) continue;
|
|
31169
|
+
win.pending++;
|
|
31170
|
+
this.load(win, url, tile.dx, tile.dy);
|
|
31171
|
+
}
|
|
31172
|
+
return win;
|
|
31173
|
+
}
|
|
31174
|
+
async load(win, url, dx, dy) {
|
|
31175
|
+
try {
|
|
31176
|
+
const held = await acquireBitmap(url, null);
|
|
31177
|
+
if (held) try {
|
|
31178
|
+
if (win.ctx && !win.dropped) win.ctx.drawImage(held.bitmap, dx, dy);
|
|
31179
|
+
else if (!win.dropped) copyTile(win, held.bitmap, dx, dy);
|
|
31180
|
+
} finally {
|
|
31181
|
+
releaseBitmap(held.key, this.moves());
|
|
31182
|
+
}
|
|
31183
|
+
this.failures = 0;
|
|
31184
|
+
} catch {
|
|
31185
|
+
this.failed.add(url);
|
|
31186
|
+
if (++this.failures >= MAX_FAILURES) this.dead = true;
|
|
31187
|
+
}
|
|
31188
|
+
win.pending--;
|
|
31189
|
+
if (this.dead) this.clear();
|
|
31190
|
+
else if (win.dropped) return;
|
|
31191
|
+
else if (win.pending === 0) this.prune();
|
|
31192
|
+
this.changed();
|
|
31193
|
+
}
|
|
31194
|
+
prune() {
|
|
31195
|
+
const loading = this.windows.some((win) => win.wanted && win.pending > 0);
|
|
31196
|
+
const stale = this.windows.filter((win) => !win.wanted);
|
|
31197
|
+
const kept = loading ? stale.slice(-MAX_STALE) : [];
|
|
31198
|
+
for (const win of stale) if (!kept.includes(win)) this.drop(win);
|
|
31199
|
+
this.windows = this.windows.filter((win) => !win.dropped);
|
|
31200
|
+
}
|
|
31201
|
+
};
|
|
31202
|
+
|
|
29770
31203
|
//#endregion
|
|
29771
31204
|
//#region src/lib/flatmap/view.ts
|
|
29772
31205
|
function clampToBand(lon, lat, limits) {
|
|
@@ -29865,6 +31298,9 @@ var MOVING_BUDGET_MS = 11;
|
|
|
29865
31298
|
var MIN_INTERACTION_SCALE = .4;
|
|
29866
31299
|
var WORTH_COARSENING = .8;
|
|
29867
31300
|
var SETTLE_MS = 140;
|
|
31301
|
+
var TILED_BASE_TIER = "medium";
|
|
31302
|
+
var MOVING_PLAN_MS = 250;
|
|
31303
|
+
var MAX_TILE_STRETCH = 2;
|
|
29868
31304
|
var DEFAULT_BODY = "naif-399";
|
|
29869
31305
|
var RASTER_LAYERS = [
|
|
29870
31306
|
{
|
|
@@ -29911,6 +31347,7 @@ var FlatMap = class {
|
|
|
29911
31347
|
projectionOptions;
|
|
29912
31348
|
view;
|
|
29913
31349
|
limits;
|
|
31350
|
+
ownMaxZoom;
|
|
29914
31351
|
limitsArmed = false;
|
|
29915
31352
|
jd;
|
|
29916
31353
|
cooperative = null;
|
|
@@ -29919,6 +31356,8 @@ var FlatMap = class {
|
|
|
29919
31356
|
rasters = /* @__PURE__ */ new Map();
|
|
29920
31357
|
pending = /* @__PURE__ */ new Set();
|
|
29921
31358
|
missing = /* @__PURE__ */ new Set();
|
|
31359
|
+
tiles = /* @__PURE__ */ new Map();
|
|
31360
|
+
plannedAt = 0;
|
|
29922
31361
|
generation = 0;
|
|
29923
31362
|
controls = null;
|
|
29924
31363
|
attribution = null;
|
|
@@ -29952,6 +31391,7 @@ var FlatMap = class {
|
|
|
29952
31391
|
maxZoom: 16,
|
|
29953
31392
|
...options.limits
|
|
29954
31393
|
};
|
|
31394
|
+
this.ownMaxZoom = options.limits?.maxZoom === void 0;
|
|
29955
31395
|
this.view = {
|
|
29956
31396
|
zoom: options.zoom ?? 1,
|
|
29957
31397
|
centerX: 0,
|
|
@@ -30008,6 +31448,7 @@ var FlatMap = class {
|
|
|
30008
31448
|
for (const layer of this.layerList) if (!isRaster(layer)) layer.dispose?.();
|
|
30009
31449
|
this.overlay.clear();
|
|
30010
31450
|
this.dropTextures();
|
|
31451
|
+
this.dropTiles();
|
|
30011
31452
|
this.root.remove();
|
|
30012
31453
|
this.container = null;
|
|
30013
31454
|
}
|
|
@@ -30035,16 +31476,18 @@ var FlatMap = class {
|
|
|
30035
31476
|
release() {
|
|
30036
31477
|
this.generation++;
|
|
30037
31478
|
this.dropTextures();
|
|
31479
|
+
this.dropTiles();
|
|
30038
31480
|
this.missing.clear();
|
|
30039
31481
|
for (const layer of this.layerList) if (!isRaster(layer) && !this.customLayers.includes(layer)) layer.dispose?.();
|
|
30040
31482
|
}
|
|
30041
31483
|
buildLayers() {
|
|
30042
31484
|
const { messages } = host();
|
|
30043
31485
|
const layers = [];
|
|
31486
|
+
this.dropTiles();
|
|
30044
31487
|
for (const spec of RASTER_LAYERS) {
|
|
30045
31488
|
const bundle = this.sources[spec.kind];
|
|
30046
31489
|
if (!bundle) continue;
|
|
30047
|
-
|
|
31490
|
+
const layer = {
|
|
30048
31491
|
kind: "raster",
|
|
30049
31492
|
id: spec.kind,
|
|
30050
31493
|
label: spec.label(messages),
|
|
@@ -30054,7 +31497,17 @@ var FlatMap = class {
|
|
|
30054
31497
|
credit: creditOf(bundle),
|
|
30055
31498
|
tiers: bundle.tiers,
|
|
30056
31499
|
url: (jd, tier) => bundleUrl(bundle, tier, jd)
|
|
30057
|
-
}
|
|
31500
|
+
};
|
|
31501
|
+
layers.push(layer);
|
|
31502
|
+
if (!bundle.tiles) continue;
|
|
31503
|
+
const tiles = new LayerTiles(bundle.tiles, () => {
|
|
31504
|
+
if (tiles.dead) {
|
|
31505
|
+
this.loadVisible();
|
|
31506
|
+
this.emit("layerschange", this.layers);
|
|
31507
|
+
}
|
|
31508
|
+
this.render();
|
|
31509
|
+
}, () => this.moves());
|
|
31510
|
+
this.tiles.set(layer, tiles);
|
|
30058
31511
|
}
|
|
30059
31512
|
layers.push(graticuleLayer(messages.layer_graticule()));
|
|
30060
31513
|
layers.push(nomenclatureLayer(this.bodyId, messages.layer_nomenclature()));
|
|
@@ -30078,11 +31531,23 @@ var FlatMap = class {
|
|
|
30078
31531
|
get credits() {
|
|
30079
31532
|
const seen = /* @__PURE__ */ new Set();
|
|
30080
31533
|
const out = [];
|
|
31534
|
+
const add = (credit) => {
|
|
31535
|
+
if (seen.has(credit.organisation)) return;
|
|
31536
|
+
seen.add(credit.organisation);
|
|
31537
|
+
out.push(credit);
|
|
31538
|
+
};
|
|
30081
31539
|
for (const layer of this.layerList) {
|
|
30082
31540
|
if (!layer.visible || !layer.credit) continue;
|
|
30083
|
-
|
|
30084
|
-
|
|
30085
|
-
|
|
31541
|
+
add(layer.credit);
|
|
31542
|
+
const tiles = this.tilesOf(layer);
|
|
31543
|
+
if (!tiles) continue;
|
|
31544
|
+
const map = {
|
|
31545
|
+
...layer.credit,
|
|
31546
|
+
tiles: tiles.block
|
|
31547
|
+
};
|
|
31548
|
+
const pyramid = tileCredit(map);
|
|
31549
|
+
if (pyramid) add(pyramid);
|
|
31550
|
+
for (const patch of patchCredits(map)) add(patch);
|
|
30086
31551
|
}
|
|
30087
31552
|
return out;
|
|
30088
31553
|
}
|
|
@@ -30201,11 +31666,15 @@ var FlatMap = class {
|
|
|
30201
31666
|
maxZoom: 16,
|
|
30202
31667
|
...limits
|
|
30203
31668
|
};
|
|
31669
|
+
this.ownMaxZoom = limits.maxZoom === void 0;
|
|
30204
31670
|
this.render();
|
|
30205
31671
|
return this;
|
|
30206
31672
|
}
|
|
30207
31673
|
getLimits() {
|
|
30208
|
-
return
|
|
31674
|
+
return this.ownMaxZoom ? {
|
|
31675
|
+
...this.limits,
|
|
31676
|
+
maxZoom: this.pictureMaxZoom()
|
|
31677
|
+
} : { ...this.limits };
|
|
30209
31678
|
}
|
|
30210
31679
|
setTime(jd) {
|
|
30211
31680
|
this.jd = jd;
|
|
@@ -30285,8 +31754,10 @@ var FlatMap = class {
|
|
|
30285
31754
|
if (!set) return;
|
|
30286
31755
|
for (const listener of [...set]) listener(...args);
|
|
30287
31756
|
}
|
|
30288
|
-
tier(
|
|
30289
|
-
|
|
31757
|
+
tier(layer) {
|
|
31758
|
+
let wanted = TIER_BY_ZOOM.find(([zoom]) => this.view.zoom >= zoom)?.[1] ?? "low";
|
|
31759
|
+
if (this.tilesOf(layer)) wanted = lowerTier(wanted, TILED_BASE_TIER);
|
|
31760
|
+
return bestTier(layer.tiers, lowerTier(wanted, tierForWidth(this.usableSourceWidth())));
|
|
30290
31761
|
}
|
|
30291
31762
|
usableSourceWidth() {
|
|
30292
31763
|
return this.projectionId === "equirectangular" ? this.deviceWidth() * this.view.zoom : this.workingWidth();
|
|
@@ -30304,7 +31775,7 @@ var FlatMap = class {
|
|
|
30304
31775
|
}
|
|
30305
31776
|
picturesInUse() {
|
|
30306
31777
|
return this.layerList.filter((l) => isRaster(l) && l.visible).map((layer) => {
|
|
30307
|
-
const tier = this.tier(layer
|
|
31778
|
+
const tier = this.tier(layer);
|
|
30308
31779
|
return {
|
|
30309
31780
|
url: layer.url(this.jd, tier),
|
|
30310
31781
|
tier
|
|
@@ -30371,10 +31842,98 @@ var FlatMap = class {
|
|
|
30371
31842
|
this.bitmaps.clear();
|
|
30372
31843
|
this.rasters.clear();
|
|
30373
31844
|
}
|
|
31845
|
+
tilesOf(layer) {
|
|
31846
|
+
const tiles = this.tiles.get(layer);
|
|
31847
|
+
if (!tiles || !isRaster(layer) || tiles.dead || tilesBase() === "") return void 0;
|
|
31848
|
+
if (!textureAllowed(tiles.block.distribution)) return void 0;
|
|
31849
|
+
return Math.max(tiles.block.max_level, ...(tiles.block.patches ?? []).map((patch) => patch.max_level)) >= this.firstTileLevel(layer, tiles) ? tiles : void 0;
|
|
31850
|
+
}
|
|
31851
|
+
firstTileLevel(layer, tiles) {
|
|
31852
|
+
const whole = TIER_WIDTH[bestTier(layer.tiers, TILED_BASE_TIER)] ?? MAX_WORKING_WIDTH;
|
|
31853
|
+
const width = this.projectionId === "equirectangular" ? whole : Math.min(whole, MAX_WORKING_WIDTH);
|
|
31854
|
+
return Math.floor(Math.log2(width / (2 * tiles.block.tile_size))) + 1;
|
|
31855
|
+
}
|
|
31856
|
+
frameSample(viewport, density) {
|
|
31857
|
+
const { width, height, projection } = viewport;
|
|
31858
|
+
const at = (px, py) => viewport.unproject(px, py);
|
|
31859
|
+
const middle = at(width / 2, height / 2);
|
|
31860
|
+
const beside = at(width / 2 + 1, height / 2);
|
|
31861
|
+
const below = at(width / 2, height / 2 + 1);
|
|
31862
|
+
if (!middle || !beside || !below) return void 0;
|
|
31863
|
+
const from = {
|
|
31864
|
+
lon: middle[0],
|
|
31865
|
+
lat: middle[1]
|
|
31866
|
+
};
|
|
31867
|
+
const step = Math.min(angularDistance(from, {
|
|
31868
|
+
lon: beside[0],
|
|
31869
|
+
lat: beside[1]
|
|
31870
|
+
}), angularDistance(from, {
|
|
31871
|
+
lon: below[0],
|
|
31872
|
+
lat: below[1]
|
|
31873
|
+
}));
|
|
31874
|
+
const shows = (lat) => {
|
|
31875
|
+
const pole = projection.azimuthal ? viewport.project(0, lat) : null;
|
|
31876
|
+
return !!pole && pole[0] >= 0 && pole[0] <= width && pole[1] >= 0 && pole[1] <= height;
|
|
31877
|
+
};
|
|
31878
|
+
return {
|
|
31879
|
+
place: (fx, fy) => at(fx * width, fy * height),
|
|
31880
|
+
step: step * Math.PI / 180 / density,
|
|
31881
|
+
north: shows(90),
|
|
31882
|
+
south: shows(-90)
|
|
31883
|
+
};
|
|
31884
|
+
}
|
|
31885
|
+
planTiles(viewport, density) {
|
|
31886
|
+
const kind = this.projectionId === "equirectangular" ? "canvas" : "pixels";
|
|
31887
|
+
let frame;
|
|
31888
|
+
for (const [layer, tiles] of this.tiles) {
|
|
31889
|
+
const drawn = isRaster(layer) && layer.visible && layer.opacity > 0 && this.tilesOf(layer);
|
|
31890
|
+
frame ??= drawn ? this.frameSample(viewport, density) : void 0;
|
|
31891
|
+
const first = drawn && frame ? this.firstTileLevel(layer, tiles) : 0;
|
|
31892
|
+
const specs = drawn && frame ? planWindows(tiles.block, frame, first, this.moves()) : [];
|
|
31893
|
+
tiles.plan(specs, kind, textureFrameForJd(this.jd, tiles.block.frames));
|
|
31894
|
+
}
|
|
31895
|
+
}
|
|
31896
|
+
moves() {
|
|
31897
|
+
return this.dragPan.isEnabled() || this.scrollZoom.isEnabled();
|
|
31898
|
+
}
|
|
31899
|
+
dropTiles() {
|
|
31900
|
+
for (const tiles of this.tiles.values()) tiles.clear();
|
|
31901
|
+
this.tiles.clear();
|
|
31902
|
+
}
|
|
31903
|
+
pictureMaxZoom() {
|
|
31904
|
+
let zoom = 16;
|
|
31905
|
+
const { clientWidth, clientHeight } = this.root;
|
|
31906
|
+
if (!clientWidth || !clientHeight) return zoom;
|
|
31907
|
+
const viewport = new Viewport(this.projection, clientWidth, clientHeight, this.view);
|
|
31908
|
+
const frame = this.frameSample(viewport, 1);
|
|
31909
|
+
const middle = frame?.place(.5, .5);
|
|
31910
|
+
if (!frame || !middle) return zoom;
|
|
31911
|
+
for (const layer of this.layerList) {
|
|
31912
|
+
const tiles = layer.visible ? this.tilesOf(layer) : void 0;
|
|
31913
|
+
if (!tiles) continue;
|
|
31914
|
+
const finest = stepOfLevel(finestLevelAt(tiles.block, middle[0], middle[1]), tiles.block.tile_size) / MAX_TILE_STRETCH;
|
|
31915
|
+
zoom = Math.max(zoom, this.view.zoom * frame.step / finest);
|
|
31916
|
+
}
|
|
31917
|
+
return zoom;
|
|
31918
|
+
}
|
|
31919
|
+
heldLimits() {
|
|
31920
|
+
if (!this.ownMaxZoom) return this.limits;
|
|
31921
|
+
return this.layerList.some((layer) => layer.visible && this.tilesOf(layer)) ? {
|
|
31922
|
+
...this.limits,
|
|
31923
|
+
maxZoom: void 0
|
|
31924
|
+
} : this.limits;
|
|
31925
|
+
}
|
|
31926
|
+
tileStepDeg(lon, lat) {
|
|
31927
|
+
const surface = this.layerList.find((layer) => layer.id === "surface");
|
|
31928
|
+
const tiles = surface && this.tilesOf(surface);
|
|
31929
|
+
if (!tiles) return null;
|
|
31930
|
+
return stepOfLevel(finestLevelAt(tiles.block, lon, lat), tiles.block.tile_size) * 180 / Math.PI;
|
|
31931
|
+
}
|
|
30374
31932
|
viewport(width, height) {
|
|
30375
31933
|
const w = width ?? this.root.clientWidth ?? 1;
|
|
30376
31934
|
const h = height ?? this.root.clientHeight ?? 1;
|
|
30377
|
-
|
|
31935
|
+
const limits = this.limitsArmed ? this.heldLimits() : {};
|
|
31936
|
+
this.view = clampView(this.view, this.projection, w, h, limits);
|
|
30378
31937
|
return new Viewport(this.projection, Math.max(1, w), Math.max(1, h), this.view);
|
|
30379
31938
|
}
|
|
30380
31939
|
render() {
|
|
@@ -30415,6 +31974,11 @@ var FlatMap = class {
|
|
|
30415
31974
|
}
|
|
30416
31975
|
const cssViewport = this.viewport(cssWidth, cssHeight);
|
|
30417
31976
|
const deviceViewport = new Viewport(this.projection, width, height, this.view);
|
|
31977
|
+
const now = performance.now();
|
|
31978
|
+
if (!this.moving || now - this.plannedAt > MOVING_PLAN_MS) {
|
|
31979
|
+
this.plannedAt = now;
|
|
31980
|
+
this.planTiles(cssViewport, sharp);
|
|
31981
|
+
}
|
|
30418
31982
|
this.clipToDisc(cssViewport);
|
|
30419
31983
|
this.drawRaster(deviceViewport, width, height);
|
|
30420
31984
|
this.drawVector(cssViewport);
|
|
@@ -30451,18 +32015,20 @@ var FlatMap = class {
|
|
|
30451
32015
|
if (layers.length === 0) return;
|
|
30452
32016
|
if (this.projectionId === "equirectangular") {
|
|
30453
32017
|
const drawable = layers.map((layer) => ({
|
|
30454
|
-
image: this.bitmaps.get(layer.url(this.jd, this.tier(layer
|
|
32018
|
+
image: this.bitmaps.get(layer.url(this.jd, this.tier(layer)))?.bitmap,
|
|
30455
32019
|
opacity: layer.opacity,
|
|
30456
|
-
blend: layer.blend
|
|
32020
|
+
blend: layer.blend,
|
|
32021
|
+
detail: this.tilesOf(layer)?.canvases()
|
|
30457
32022
|
})).filter((l) => Boolean(l.image));
|
|
30458
32023
|
if (drawable.length) drawEquirect(ctx, viewport, drawable);
|
|
30459
32024
|
return;
|
|
30460
32025
|
}
|
|
30461
32026
|
const workingWidth = this.workingWidth();
|
|
30462
32027
|
const stack = layers.map((layer) => ({
|
|
30463
|
-
image: this.rasterFor(layer.url(this.jd, this.tier(layer
|
|
32028
|
+
image: this.rasterFor(layer.url(this.jd, this.tier(layer)), workingWidth),
|
|
30464
32029
|
opacity: layer.opacity,
|
|
30465
|
-
blend: layer.blend
|
|
32030
|
+
blend: layer.blend,
|
|
32031
|
+
detail: this.tilesOf(layer)?.rasters()
|
|
30466
32032
|
})).filter((l) => Boolean(l.image));
|
|
30467
32033
|
if (stack.length === 0) return;
|
|
30468
32034
|
if (!this.buffer || this.buffer.width !== width || this.buffer.height !== height) this.buffer = ctx.createImageData(width, height);
|
|
@@ -30620,7 +32186,8 @@ var FlatMap = class {
|
|
|
30620
32186
|
zoomBy(factor, px, py) {
|
|
30621
32187
|
this.limitsArmed = true;
|
|
30622
32188
|
const anchor = this.viewport().fromScreen(px, py);
|
|
30623
|
-
const
|
|
32189
|
+
const furthest = this.ownMaxZoom ? Math.max(this.pictureMaxZoom(), this.view.zoom) : this.limits.maxZoom ?? Number.POSITIVE_INFINITY;
|
|
32190
|
+
const zoom = Math.min(furthest, Math.max(1, this.limits.minZoom ?? 1, this.view.zoom * factor));
|
|
30624
32191
|
if (zoom === this.view.zoom) return;
|
|
30625
32192
|
this.view = {
|
|
30626
32193
|
...this.view,
|
|
@@ -30753,7 +32320,7 @@ function wrap(deg) {
|
|
|
30753
32320
|
//#region src/lib/panorama/minimap.ts
|
|
30754
32321
|
var DEG$2 = Math.PI / 180;
|
|
30755
32322
|
function hasHeading(entry) {
|
|
30756
|
-
return entry.orientation !== "unknown"
|
|
32323
|
+
return entry.orientation !== "unknown";
|
|
30757
32324
|
}
|
|
30758
32325
|
|
|
30759
32326
|
//#endregion
|
|
@@ -31029,7 +32596,7 @@ var PanoramaView = class {
|
|
|
31029
32596
|
key: a.key,
|
|
31030
32597
|
bearingDeg: a.n.bearingDeg
|
|
31031
32598
|
})) : []);
|
|
31032
|
-
await this.loadTexture(versionedUrl(`/v1/panoramas/${entry.id}.webp`, "panoramas"), entry.north_offset_deg
|
|
32599
|
+
await this.loadTexture(versionedUrl(`/v1/panoramas/${entry.id}.webp`, "panoramas"), entry.north_offset_deg, !opening);
|
|
31033
32600
|
if (this.current === entry) this.emit("load", entry);
|
|
31034
32601
|
}
|
|
31035
32602
|
async step(key) {
|