spacemap 0.1.9 → 0.1.11
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 +1 -1
- package/dist/spacemap.iife.js +207 -91
- package/dist/spacemap.js +207 -91
- package/index.d.ts +4 -0
- package/index.js +644 -66
- 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, 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, RawShaderMaterial, Raycaster, RedFormat, RingGeometry, SRGBColorSpace, Scene, ShaderChunk, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapeUtils, SphereGeometry, Sprite, SpriteMaterial, TOUCH, Texture, TextureLoader, Uint16BufferAttribute, UnsignedByteType, Vector2, Vector3, Vector4, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
2
|
+
import { ACESFilmicToneMapping, AdditiveBlending, AmbientLight, ArrowHelper, BackSide, Box3, BufferAttribute, BufferGeometry, CanvasTexture, 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, NearestFilter, NoBlending, NoColorSpace, NormalBlending, OneFactor, OneMinusSrcAlphaFactor, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, RGBAFormat, 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";
|
|
@@ -20,6 +20,7 @@ var DEFAULT_HOST = {
|
|
|
20
20
|
dataUrl: "https://static.spacemap.co",
|
|
21
21
|
imagesUrl: "https://images.spacemap.co",
|
|
22
22
|
liveUrl: "https://live.spacemap.co",
|
|
23
|
+
tilesUrl: "",
|
|
23
24
|
locale: () => "en",
|
|
24
25
|
messages: {
|
|
25
26
|
body_note_no_model: () => "no model available",
|
|
@@ -66,6 +67,7 @@ function configureHost(overrides) {
|
|
|
66
67
|
current$1.dataUrl = current$1.dataUrl.replace(/\/$/, "");
|
|
67
68
|
current$1.imagesUrl = current$1.imagesUrl.replace(/\/$/, "");
|
|
68
69
|
current$1.liveUrl = current$1.liveUrl.replace(/\/$/, "");
|
|
70
|
+
current$1.tilesUrl = current$1.tilesUrl.replace(/\/$/, "");
|
|
69
71
|
}
|
|
70
72
|
function host() {
|
|
71
73
|
return current$1;
|
|
@@ -2963,6 +2965,14 @@ function get_custom_elements_slots(element) {
|
|
|
2963
2965
|
function dataBase() {
|
|
2964
2966
|
return host().dataUrl;
|
|
2965
2967
|
}
|
|
2968
|
+
function tilesBase() {
|
|
2969
|
+
return host().tilesUrl;
|
|
2970
|
+
}
|
|
2971
|
+
function tileUrl(tiles, cap, level, x, y, frame) {
|
|
2972
|
+
const month = frame === void 0 ? "" : `${String(frame).padStart(2, "0")}/`;
|
|
2973
|
+
const chart = cap && `${cap}/`;
|
|
2974
|
+
return `${tilesBase()}/v1/tiles/${tiles.id}/${month}${chart}${level}/${x}/${y}.webp?v=${tiles.version}`;
|
|
2975
|
+
}
|
|
2966
2976
|
var versions = {};
|
|
2967
2977
|
function setDataVersions(v) {
|
|
2968
2978
|
versions = v ?? {};
|
|
@@ -6933,7 +6943,7 @@ var VisibilityController = class {
|
|
|
6933
6943
|
}
|
|
6934
6944
|
ratioVisibility(body, camDistThreeJS, pinned) {
|
|
6935
6945
|
if (!pinned && this.getLayers().hidesBody(body.data.objectType, body.data.parentId)) return VISIBILITY.HIDE;
|
|
6936
|
-
if (body.data.orbitalSource === OrbitalSource.SPICE_PROBE) return this.getProbeVisibility(body);
|
|
6946
|
+
if (body.data.orbitalSource === OrbitalSource.SPICE_PROBE) return this.getProbeVisibility(body, camDistThreeJS);
|
|
6937
6947
|
const earthFilter = this.getEarthSatGroupFilter();
|
|
6938
6948
|
if (earthFilter && body.data.parentId === "naif-399" && !earthFilter.has(body.data.id)) return VISIBILITY.HIDE;
|
|
6939
6949
|
const ot = body.data.objectType;
|
|
@@ -6958,7 +6968,7 @@ var VisibilityController = class {
|
|
|
6958
6968
|
const tier = computeVisibilityFromRatio(camDistThreeJS / 10 / refA, this.scaledSystem, 50, isFocused);
|
|
6959
6969
|
return tier === VISIBILITY.CLOSE ? VISIBILITY.FULL : tier;
|
|
6960
6970
|
}
|
|
6961
|
-
getProbeVisibility(body) {
|
|
6971
|
+
getProbeVisibility(body, camDistThreeJS) {
|
|
6962
6972
|
const ps = this.getProbeStore();
|
|
6963
6973
|
if ((ps ? ps.containingSystemAt(body.data.id, this.currentJd) : null) === EARTH_SYSTEM_NAIF && this.getEarthSatGroupFilter()) return VISIBILITY.HIDE;
|
|
6964
6974
|
const isFocused = body.data.id === this.focusedBodyIdPlain;
|
|
@@ -6974,7 +6984,7 @@ var VisibilityController = class {
|
|
|
6974
6984
|
if (!parent?.position || !body.position) return VISIBILITY.FULL;
|
|
6975
6985
|
const distToParent = f64dist(body.position, parent.position) / 10;
|
|
6976
6986
|
if (distToParent === 0) return VISIBILITY.FULL;
|
|
6977
|
-
return
|
|
6987
|
+
return camDistThreeJS / 10 / distToParent <= this.scaledPlanetary[VISIBILITY.FULL] * (isFocused ? 5 : 1) ? VISIBILITY.FULL : VISIBILITY.HIDE;
|
|
6978
6988
|
}
|
|
6979
6989
|
isProbeInFocusedSystem(body) {
|
|
6980
6990
|
const parentId = body.data.parentId;
|
|
@@ -8179,19 +8189,11 @@ function extendProbeTrailBuffer(buf, sample, lastJd, lastPos, headJd) {
|
|
|
8179
8189
|
//#endregion
|
|
8180
8190
|
//#region src/lib/scene/objects/trail/points.ts
|
|
8181
8191
|
var NUM_TRAIL_POINTS = 512;
|
|
8182
|
-
function
|
|
8183
|
-
if (curve.length === 0) return [];
|
|
8184
|
-
const anchor = body.trailAnchor ?? body.position;
|
|
8185
|
-
if (!anchor) return [];
|
|
8186
|
-
const bodyLocal = [
|
|
8187
|
-
anchor[0] - cx,
|
|
8188
|
-
anchor[1] - cy,
|
|
8189
|
-
anchor[2] - cz
|
|
8190
|
-
];
|
|
8192
|
+
function nearestStart(curve, at) {
|
|
8191
8193
|
let nearest = 0;
|
|
8192
8194
|
let best = Infinity;
|
|
8193
8195
|
for (let j = 0; j < curve.length; j++) {
|
|
8194
|
-
const d = (curve[j][0] -
|
|
8196
|
+
const d = (curve[j][0] - at[0]) ** 2 + (curve[j][1] - at[1]) ** 2 + (curve[j][2] - at[2]) ** 2;
|
|
8195
8197
|
if (d < best) {
|
|
8196
8198
|
best = d;
|
|
8197
8199
|
nearest = j;
|
|
@@ -8199,7 +8201,18 @@ function buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz) {
|
|
|
8199
8201
|
}
|
|
8200
8202
|
const prev = Math.max(nearest - 1, 0);
|
|
8201
8203
|
const next = Math.min(nearest + 1, curve.length - 1);
|
|
8202
|
-
|
|
8204
|
+
return (curve[prev][0] - at[0]) ** 2 + (curve[prev][1] - at[1]) ** 2 + (curve[prev][2] - at[2]) ** 2 < (curve[next][0] - at[0]) ** 2 + (curve[next][1] - at[1]) ** 2 + (curve[next][2] - at[2]) ** 2 ? prev : nearest;
|
|
8205
|
+
}
|
|
8206
|
+
function buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz, start) {
|
|
8207
|
+
if (curve.length === 0) return [];
|
|
8208
|
+
const anchor = body.trailAnchor ?? body.position;
|
|
8209
|
+
if (!anchor) return [];
|
|
8210
|
+
const bodyLocal = [
|
|
8211
|
+
anchor[0] - cx,
|
|
8212
|
+
anchor[1] - cy,
|
|
8213
|
+
anchor[2] - cz
|
|
8214
|
+
];
|
|
8215
|
+
const trailStart = start ?? nearestStart(curve, bodyLocal);
|
|
8203
8216
|
const points = [bodyLocal];
|
|
8204
8217
|
if (isOpenCurve) for (let k = 0; k < 512 - 1; k++) {
|
|
8205
8218
|
const idx = Math.max(trailStart - k, 0);
|
|
@@ -8295,12 +8308,16 @@ var ChunkLoader = class {
|
|
|
8295
8308
|
const parentKey = `naif-${body.parentId}`;
|
|
8296
8309
|
const objType = body.objectType;
|
|
8297
8310
|
const primaryId = objType === ObjectType.MOON && body.parentId >= 1 && body.parentId <= 9 ? `naif-${body.parentId * 100 + 99}` : null;
|
|
8298
|
-
const
|
|
8311
|
+
const elementsAt = (record, at) => {
|
|
8312
|
+
if (!primaryId) return chebyshevOsculatingElements(record, record.parentId, at);
|
|
8313
|
+
const primary = cheb.body(primaryId, at);
|
|
8314
|
+
return primary ? chebyshevOsculatingElements(record, record.parentId, at, primary) : null;
|
|
8315
|
+
};
|
|
8316
|
+
const elements = elementsAt(body, jd);
|
|
8299
8317
|
const ownId = body.id;
|
|
8300
8318
|
const ownRederive = (newJd) => {
|
|
8301
8319
|
const fresh = cheb.body(ownId, newJd);
|
|
8302
|
-
|
|
8303
|
-
return chebyshevOsculatingElements(fresh, fresh.parentId, newJd, primaryId ? cheb.body(primaryId, newJd) ?? void 0 : void 0);
|
|
8320
|
+
return fresh ? elementsAt(fresh, newJd) : null;
|
|
8304
8321
|
};
|
|
8305
8322
|
const fallbackA = Math.hypot(offset[0], offset[1], offset[2]) / 10;
|
|
8306
8323
|
const data = {
|
|
@@ -8950,6 +8967,20 @@ var ChunkWindow = class {
|
|
|
8950
8967
|
done
|
|
8951
8968
|
};
|
|
8952
8969
|
}
|
|
8970
|
+
request(jd) {
|
|
8971
|
+
for (const [zone, params] of this.zoneParams) {
|
|
8972
|
+
const idx = chunkIndexForJd(params, jd);
|
|
8973
|
+
if (this.isLoadable(params, idx)) this.loadChunk(zone, params, idx, "high")?.catch(() => {});
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
loadingAt(jd, zone) {
|
|
8977
|
+
if (this.inflight.size === 0) return false;
|
|
8978
|
+
for (const [name, params] of this.zoneParams) {
|
|
8979
|
+
if (zone !== void 0 && name !== zone) continue;
|
|
8980
|
+
if (this.inflight.has(chunkKey(name, chunkIndexForJd(params, jd)))) return true;
|
|
8981
|
+
}
|
|
8982
|
+
return false;
|
|
8983
|
+
}
|
|
8953
8984
|
pendingCurrent(jd) {
|
|
8954
8985
|
if (this.inflight.size === 0) return RESOLVED;
|
|
8955
8986
|
let pending = null;
|
|
@@ -9021,6 +9052,10 @@ var ChebyshevStore = class extends ChunkWindow {
|
|
|
9021
9052
|
end: params.end_jd
|
|
9022
9053
|
};
|
|
9023
9054
|
}
|
|
9055
|
+
isLoading(objectId, jd) {
|
|
9056
|
+
const zone = this.idToZone.get(objectId);
|
|
9057
|
+
return zone !== void 0 && this.loadingAt(jd, zone);
|
|
9058
|
+
}
|
|
9024
9059
|
resolve(objectId, jd) {
|
|
9025
9060
|
const zone = this.idToZone.get(objectId);
|
|
9026
9061
|
if (zone === void 0) return null;
|
|
@@ -10059,6 +10094,7 @@ var RENDER_TIER_PRESETS = {
|
|
|
10059
10094
|
bloomScale: .5,
|
|
10060
10095
|
pointBudget: 4e5,
|
|
10061
10096
|
maxTextureTier: "medium",
|
|
10097
|
+
tileWindows: 3,
|
|
10062
10098
|
maxSkyboxFace: 2048
|
|
10063
10099
|
},
|
|
10064
10100
|
medium: {
|
|
@@ -10068,6 +10104,7 @@ var RENDER_TIER_PRESETS = {
|
|
|
10068
10104
|
bloomScale: .75,
|
|
10069
10105
|
pointBudget: 9e5,
|
|
10070
10106
|
maxTextureTier: "medium",
|
|
10107
|
+
tileWindows: 4,
|
|
10071
10108
|
maxSkyboxFace: 2048
|
|
10072
10109
|
},
|
|
10073
10110
|
high: {
|
|
@@ -10077,6 +10114,7 @@ var RENDER_TIER_PRESETS = {
|
|
|
10077
10114
|
bloomScale: 1,
|
|
10078
10115
|
pointBudget: Infinity,
|
|
10079
10116
|
maxTextureTier: "high",
|
|
10117
|
+
tileWindows: 6,
|
|
10080
10118
|
maxSkyboxFace: Infinity
|
|
10081
10119
|
}
|
|
10082
10120
|
};
|
|
@@ -10084,6 +10122,10 @@ function maxTextureTier() {
|
|
|
10084
10122
|
if (isLowEndDevice()) return "low";
|
|
10085
10123
|
return currentRenderTier().maxTextureTier;
|
|
10086
10124
|
}
|
|
10125
|
+
function maxTileWindows() {
|
|
10126
|
+
if (isLowEndDevice()) return 0;
|
|
10127
|
+
return currentRenderTier().tileWindows;
|
|
10128
|
+
}
|
|
10087
10129
|
function resolveRenderTier() {
|
|
10088
10130
|
const measured = sceneSettings().atmosphereCalibration?.tier;
|
|
10089
10131
|
if (measured === "low" || measured === "medium") return measured;
|
|
@@ -10342,7 +10384,7 @@ function dedupeById(bodies) {
|
|
|
10342
10384
|
async function loadScene(ctx, date, targetId) {
|
|
10343
10385
|
performance.mark("sm-load-start");
|
|
10344
10386
|
loadProgress.reset();
|
|
10345
|
-
loadSystemsGlobal().catch((e) => console.warn("scene-load: systems-global (GMs/nutation) failed to load:", e));
|
|
10387
|
+
const systemsGlobalPromise = loadSystemsGlobal().catch((e) => console.warn("scene-load: systems-global (GMs/nutation) failed to load:", e));
|
|
10346
10388
|
const atmospheresPromise = loadAtmospheres().catch((e) => console.warn("scene-load: atmospheres failed to load — rendering without shells:", e));
|
|
10347
10389
|
const jd = dateToJD(date);
|
|
10348
10390
|
const metadataPromise = fetchMetadata();
|
|
@@ -10374,6 +10416,7 @@ async function loadScene(ctx, date, targetId) {
|
|
|
10374
10416
|
loadProgress.reach("ephemeris");
|
|
10375
10417
|
const loader = new ChunkLoader(ctx.chebStore);
|
|
10376
10418
|
if (ctx.probeStore) ctx.probeStore.fitCenterUsable = (id) => (ctx.chebStore?.has(id) ?? false) || ctx.getBody(id) !== void 0;
|
|
10419
|
+
await systemsGlobalPromise;
|
|
10377
10420
|
const major = await loadMajorBodies(ctx, loader, metadata, date, jd, await passengerPromise);
|
|
10378
10421
|
loadProgress.reach("majors");
|
|
10379
10422
|
await atmospheresPromise;
|
|
@@ -15429,6 +15472,491 @@ function detachSelfShadow(uniforms) {
|
|
|
15429
15472
|
uniforms.uSelfScale.value = 0;
|
|
15430
15473
|
}
|
|
15431
15474
|
|
|
15475
|
+
//#endregion
|
|
15476
|
+
//#region src/lib/scene/lod/tile-window.ts
|
|
15477
|
+
var WINDOW_TILES = 4;
|
|
15478
|
+
var MAP = 0;
|
|
15479
|
+
var NORTH = 1;
|
|
15480
|
+
var SOUTH = 2;
|
|
15481
|
+
var CHART_FOLDER = [
|
|
15482
|
+
"",
|
|
15483
|
+
"north",
|
|
15484
|
+
"south"
|
|
15485
|
+
];
|
|
15486
|
+
var FIRST_TILE_LEVEL = 3;
|
|
15487
|
+
var HOLD_TILES = .75;
|
|
15488
|
+
var BASE_WIDTH_PX = 2048;
|
|
15489
|
+
var CAP_EDGE = Math.SQRT2 - 1;
|
|
15490
|
+
var CAP_ENTER_DEG = 62;
|
|
15491
|
+
var CAP_LEAVE_DEG = 58;
|
|
15492
|
+
var BOTH_CHARTS_DEG = 45;
|
|
15493
|
+
function chartColumns(chart, level) {
|
|
15494
|
+
return chart === 0 ? 2 << level : 1 << level - 1;
|
|
15495
|
+
}
|
|
15496
|
+
function chartRows(chart, level) {
|
|
15497
|
+
return chart === 0 ? 1 << level : 1 << level - 1;
|
|
15498
|
+
}
|
|
15499
|
+
function neededLevel(radiusScene, dist, projScale, maxLevel) {
|
|
15500
|
+
const height = Math.max(dist - radiusScene, radiusScene * 1e-6);
|
|
15501
|
+
const widthPx = 2 * Math.PI * radiusScene * projScale / height;
|
|
15502
|
+
return Math.min(Math.ceil(Math.log2(widthPx / BASE_WIDTH_PX)), maxLevel);
|
|
15503
|
+
}
|
|
15504
|
+
function chartPoint(chart, x, y, z) {
|
|
15505
|
+
if (chart === 0) {
|
|
15506
|
+
const east = Math.atan2(z, -x) / (2 * Math.PI);
|
|
15507
|
+
return {
|
|
15508
|
+
x: east < 0 ? east + 1 : east,
|
|
15509
|
+
y: Math.acos(Math.min(Math.max(y, -1), 1)) / Math.PI
|
|
15510
|
+
};
|
|
15511
|
+
}
|
|
15512
|
+
const side = chart === 1 ? 1 : -1;
|
|
15513
|
+
const scale = .5 / (CAP_EDGE * (1 + side * y));
|
|
15514
|
+
return {
|
|
15515
|
+
x: .5 + x * scale,
|
|
15516
|
+
y: .5 + side * z * scale
|
|
15517
|
+
};
|
|
15518
|
+
}
|
|
15519
|
+
function cameraChart(y, previous) {
|
|
15520
|
+
const cap = y >= 0 ? 1 : 2;
|
|
15521
|
+
return Math.asin(Math.min(Math.abs(y), 1)) * 180 / Math.PI > (previous === cap ? CAP_LEAVE_DEG : CAP_ENTER_DEG) ? cap : 0;
|
|
15522
|
+
}
|
|
15523
|
+
function windowChain(level, count, chart, y) {
|
|
15524
|
+
const chain = [];
|
|
15525
|
+
for (let z = level; z >= 3 && chain.length < count; z--) chain.push({
|
|
15526
|
+
chart,
|
|
15527
|
+
level: z
|
|
15528
|
+
});
|
|
15529
|
+
const reached = chain.length > 0 && chain[chain.length - 1].level === 3;
|
|
15530
|
+
const both = Math.abs(y) > Math.sin(BOTH_CHARTS_DEG * Math.PI / 180);
|
|
15531
|
+
if (reached && both && chain.length < count) {
|
|
15532
|
+
const cap = y >= 0 ? 1 : 2;
|
|
15533
|
+
chain.push({
|
|
15534
|
+
chart: chart === 0 ? cap : 0,
|
|
15535
|
+
level: 3
|
|
15536
|
+
});
|
|
15537
|
+
}
|
|
15538
|
+
return chain;
|
|
15539
|
+
}
|
|
15540
|
+
function placeAxis(at, count, wraps, kept) {
|
|
15541
|
+
const last = Math.max(count - 4, 0);
|
|
15542
|
+
if (kept !== void 0) {
|
|
15543
|
+
let off = at - (kept + 4 / 2);
|
|
15544
|
+
if (wraps) off -= count * Math.round(off / count);
|
|
15545
|
+
if (!wraps && (kept === 0 && off < 0 || kept === last && off > 0) || Math.abs(off) <= HOLD_TILES) return kept;
|
|
15546
|
+
}
|
|
15547
|
+
const first = Math.round(at - 4 / 2);
|
|
15548
|
+
return wraps ? (first % count + count) % count : Math.min(Math.max(first, 0), last);
|
|
15549
|
+
}
|
|
15550
|
+
function placeWindow(chart, level, x, y, previous) {
|
|
15551
|
+
const columns = chartColumns(chart, level);
|
|
15552
|
+
const rows = chartRows(chart, level);
|
|
15553
|
+
const kept = previous?.chart === chart && previous.level === level ? previous : void 0;
|
|
15554
|
+
const tx0 = placeAxis(x * columns, columns, chart === 0, kept?.tx0);
|
|
15555
|
+
const ty0 = placeAxis(y * rows, rows, false, kept?.ty0);
|
|
15556
|
+
if (kept && kept.tx0 === tx0 && kept.ty0 === ty0) return kept;
|
|
15557
|
+
return {
|
|
15558
|
+
chart,
|
|
15559
|
+
level,
|
|
15560
|
+
tx0,
|
|
15561
|
+
ty0
|
|
15562
|
+
};
|
|
15563
|
+
}
|
|
15564
|
+
function slotTile(placement, sx, sy) {
|
|
15565
|
+
const columns = chartColumns(placement.chart, placement.level);
|
|
15566
|
+
const dx = ((sx - placement.tx0) % 4 + 4) % 4;
|
|
15567
|
+
const dy = ((sy - placement.ty0) % 4 + 4) % 4;
|
|
15568
|
+
return {
|
|
15569
|
+
x: (placement.tx0 + dx) % columns,
|
|
15570
|
+
y: placement.ty0 + dy
|
|
15571
|
+
};
|
|
15572
|
+
}
|
|
15573
|
+
|
|
15574
|
+
//#endregion
|
|
15575
|
+
//#region src/lib/scene/objects/surface/tile-overlay.ts
|
|
15576
|
+
var MAX_WINDOWS = 6;
|
|
15577
|
+
var MAX_DRAWN = MAX_WINDOWS + 1;
|
|
15578
|
+
var MAX_INFLIGHT = 6;
|
|
15579
|
+
var MAX_FAILURES = 4;
|
|
15580
|
+
var FETCH_TIMEOUT_S = 30;
|
|
15581
|
+
var IDLE_DISPOSE_S = 5;
|
|
15582
|
+
var FEATHER_TILES = .5;
|
|
15583
|
+
var MAX_ANISOTROPY = 16;
|
|
15584
|
+
var SLOTS = 4 * 4;
|
|
15585
|
+
var PARS = `
|
|
15586
|
+
varying vec3 vTileDir;
|
|
15587
|
+
uniform sampler2DArray uTileMap;
|
|
15588
|
+
uniform sampler2DArray uTileMask;
|
|
15589
|
+
uniform vec4 uTileGrid[ ${MAX_DRAWN} ];
|
|
15590
|
+
uniform vec4 uTileWindow[ ${MAX_DRAWN} ];
|
|
15591
|
+
// Most taps of the anisotropic filter, and a tile's side in pixels.
|
|
15592
|
+
uniform vec2 uTileFilter;
|
|
15593
|
+
|
|
15594
|
+
// Long and short half-axes of the footprint whose sides are fx and fy. The
|
|
15595
|
+
// two sides alone would do where the stretch runs along the screen's axes;
|
|
15596
|
+
// around a pole it runs every way, and each side then holds most of it.
|
|
15597
|
+
void tileAxes( vec2 fx, vec2 fy, out vec2 major, out vec2 minor ) {
|
|
15598
|
+
float p = fx.x * fx.x + fy.x * fy.x;
|
|
15599
|
+
float q = fx.y * fx.y + fy.y * fy.y;
|
|
15600
|
+
float r = fx.x * fx.y + fy.x * fy.y;
|
|
15601
|
+
float det = fx.x * fy.y - fx.y * fy.x;
|
|
15602
|
+
float hi = 0.5 * ( p + q ) + sqrt( 0.25 * ( p - q ) * ( p - q ) + r * r );
|
|
15603
|
+
float lo = det * det / max( hi, 1e-30 );
|
|
15604
|
+
vec2 axis = p >= q ? vec2( hi - q, r ) : vec2( r, hi - p );
|
|
15605
|
+
float len = length( axis );
|
|
15606
|
+
axis = len > 0.0 ? axis / len : vec2( 1.0, 0.0 );
|
|
15607
|
+
major = axis * sqrt( hi );
|
|
15608
|
+
minor = vec2( - axis.y, axis.x ) * sqrt( lo );
|
|
15609
|
+
}
|
|
15610
|
+
|
|
15611
|
+
vec3 tileColour( vec3 below ) {
|
|
15612
|
+
if ( uTileWindow[ 0 ].w < 0.5 ) return below;
|
|
15613
|
+
|
|
15614
|
+
// Where the fragment is on each chart, as fractions from the chart's
|
|
15615
|
+
// north-west corner, and what a screen pixel spans there. Both come from
|
|
15616
|
+
// the direction of the fragment rather than its interpolated coordinates:
|
|
15617
|
+
// those have one slope per triangle, which near a pole differs enough
|
|
15618
|
+
// between the two triangles of a grid cell to pick different levels, and
|
|
15619
|
+
// they jump at the 180° meridian.
|
|
15620
|
+
vec3 dir = normalize( vTileDir );
|
|
15621
|
+
vec3 ddx = dFdx( dir );
|
|
15622
|
+
vec3 ddy = dFdy( dir );
|
|
15623
|
+
|
|
15624
|
+
float flat2 = max( dir.x * dir.x + dir.z * dir.z, 1e-12 );
|
|
15625
|
+
float east = 1.0 / ( 2.0 * PI * flat2 );
|
|
15626
|
+
float south = - 1.0 / ( PI * sqrt( flat2 ) );
|
|
15627
|
+
vec2 mapAt = vec2( fract( atan( dir.z, - dir.x ) / ( 2.0 * PI ) ), acos( clamp( dir.y, - 1.0, 1.0 ) ) / PI );
|
|
15628
|
+
vec2 mapDx = vec2( ( dir.z * ddx.x - dir.x * ddx.z ) * east, ddx.y * south );
|
|
15629
|
+
vec2 mapDy = vec2( ( dir.z * ddy.x - dir.x * ddy.z ) * east, ddy.y * south );
|
|
15630
|
+
|
|
15631
|
+
// The cap of the fragment's own hemisphere, seen from above its pole.
|
|
15632
|
+
float side = dir.y < 0.0 ? - 1.0 : 1.0;
|
|
15633
|
+
float fold = 1.0 + side * dir.y;
|
|
15634
|
+
vec2 plane = vec2( dir.x, side * dir.z );
|
|
15635
|
+
float capScale = 0.5 / ( ${CAP_EDGE.toFixed(8)} * fold );
|
|
15636
|
+
vec2 capAt = 0.5 + plane * capScale;
|
|
15637
|
+
vec2 capDx = ( vec2( ddx.x, side * ddx.z ) - plane * ( side * ddx.y / fold ) ) * capScale;
|
|
15638
|
+
vec2 capDy = ( vec2( ddy.x, side * ddy.z ) - plane * ( side * ddy.y / fold ) ) * capScale;
|
|
15639
|
+
|
|
15640
|
+
float rows = ${4 .toFixed(1)};
|
|
15641
|
+
vec3 colour = vec3( 0.0 );
|
|
15642
|
+
float covered = 0.0;
|
|
15643
|
+
for ( int i = 0; i < ${MAX_DRAWN}; i ++ ) {
|
|
15644
|
+
vec4 window = uTileWindow[ i ];
|
|
15645
|
+
if ( window.w < 0.5 ) break;
|
|
15646
|
+
vec4 grid = uTileGrid[ i ];
|
|
15647
|
+
bool onMap = window.x < 0.5;
|
|
15648
|
+
// The other pole's cap.
|
|
15649
|
+
if ( ! onMap && ( window.x < 1.5 ) != ( side > 0.0 ) ) continue;
|
|
15650
|
+
vec2 tile = ( onMap ? mapAt : capAt ) * grid.xy;
|
|
15651
|
+
vec2 dx = ( onMap ? mapDx : capDx ) * grid.xy * uTileFilter.y;
|
|
15652
|
+
vec2 dy = ( onMap ? mapDy : capDy ) * grid.xy * uTileFilter.y;
|
|
15653
|
+
|
|
15654
|
+
// Level this fragment asks of the window, from the short axis of its
|
|
15655
|
+
// footprint in the window's pixels: the long axis is the anisotropic
|
|
15656
|
+
// filter's to cover, as far as its taps reach. The window fades in
|
|
15657
|
+
// over the level below its own, as a mipmap would.
|
|
15658
|
+
vec2 major;
|
|
15659
|
+
vec2 minor;
|
|
15660
|
+
tileAxes( dx, dy, major, minor );
|
|
15661
|
+
float foot = max( length( minor ), length( major ) / uTileFilter.x );
|
|
15662
|
+
float weight = clamp( 1.0 - log2( max( foot, 1e-6 ) ), 0.0, 1.0 );
|
|
15663
|
+
if ( weight <= 0.0 ) continue;
|
|
15664
|
+
|
|
15665
|
+
// Half a pixel in from a pole of the map: past it the filter would
|
|
15666
|
+
// read the far row.
|
|
15667
|
+
if ( onMap ) tile.y = clamp( tile.y, 0.0005, grid.y - 0.0005 );
|
|
15668
|
+
// Offset from the window's north-west corner; the map's longitude wraps.
|
|
15669
|
+
vec2 rel = tile - grid.zw;
|
|
15670
|
+
if ( onMap ) rel.x = mod( rel.x, grid.x );
|
|
15671
|
+
if ( rel.x < 0.0 || rel.x > rows || rel.y < 0.0 || rel.y > rows ) continue;
|
|
15672
|
+
// Distance to the window's edge, in tiles. A pole of the map is no
|
|
15673
|
+
// edge: nothing lies past it.
|
|
15674
|
+
vec2 near = min( rel, rows - rel );
|
|
15675
|
+
if ( onMap && grid.w < 0.5 ) near.y = rows - rel.y;
|
|
15676
|
+
if ( onMap && grid.w + rows > grid.y - 0.5 ) near.y = grid.w < 0.5 ? rows : rel.y;
|
|
15677
|
+
float edge = min( near.x, near.y );
|
|
15678
|
+
|
|
15679
|
+
ivec3 slot = ivec3( int( mod( tile.x, rows ) ), int( mod( tile.y, rows ) ), int( window.y ) );
|
|
15680
|
+
float alpha = weight * texelFetch( uTileMask, slot, 0 ).r * smoothstep( 0.0, ${FEATHER_TILES.toFixed(2)}, edge );
|
|
15681
|
+
if ( alpha <= 0.0 ) continue;
|
|
15682
|
+
|
|
15683
|
+
// The texture repeats every window: a tile sits in the slot its own
|
|
15684
|
+
// column and row pick, north up.
|
|
15685
|
+
vec3 st = vec3( tile.x / rows, - tile.y / rows, window.y );
|
|
15686
|
+
vec2 texel = vec2( 1.0, - 1.0 ) / ( rows * uTileFilter.y );
|
|
15687
|
+
colour += ( 1.0 - covered ) * alpha * textureGrad( uTileMap, st, major * texel, minor * texel ).rgb;
|
|
15688
|
+
covered += ( 1.0 - covered ) * alpha;
|
|
15689
|
+
if ( covered > 0.995 ) break;
|
|
15690
|
+
}
|
|
15691
|
+
return colour + ( 1.0 - covered ) * below;
|
|
15692
|
+
}
|
|
15693
|
+
`;
|
|
15694
|
+
var MAP_FRAGMENT = `
|
|
15695
|
+
#ifdef USE_MAP
|
|
15696
|
+
vec4 sampledDiffuseColor = texture2D( map, vMapUv );
|
|
15697
|
+
sampledDiffuseColor.rgb = tileColour( sampledDiffuseColor.rgb );
|
|
15698
|
+
diffuseColor *= sampledDiffuseColor;
|
|
15699
|
+
#endif
|
|
15700
|
+
`;
|
|
15701
|
+
function createOverlay(material) {
|
|
15702
|
+
const overlay = {
|
|
15703
|
+
material,
|
|
15704
|
+
uniforms: {
|
|
15705
|
+
uTileMap: { value: null },
|
|
15706
|
+
uTileMask: { value: null },
|
|
15707
|
+
uTileGrid: { value: Array.from({ length: MAX_DRAWN }, () => new Vector4()) },
|
|
15708
|
+
uTileWindow: { value: Array.from({ length: MAX_DRAWN }, () => new Vector4()) },
|
|
15709
|
+
uTileFilter: { value: new Vector2(1, 1) }
|
|
15710
|
+
},
|
|
15711
|
+
inflight: 0,
|
|
15712
|
+
failures: 0,
|
|
15713
|
+
failed: /* @__PURE__ */ new Set(),
|
|
15714
|
+
dead: false,
|
|
15715
|
+
lastUsed: 0
|
|
15716
|
+
};
|
|
15717
|
+
chainShaderHook(material, "surfaceTiles", (shader) => {
|
|
15718
|
+
Object.assign(shader.uniforms, overlay.uniforms);
|
|
15719
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nvarying vec3 vTileDir;").replace("#include <begin_vertex>", "#include <begin_vertex>\nvTileDir = position;");
|
|
15720
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <map_pars_fragment>", `#include <map_pars_fragment>\n${PARS}`).replace("#include <map_fragment>", MAP_FRAGMENT);
|
|
15721
|
+
});
|
|
15722
|
+
return overlay;
|
|
15723
|
+
}
|
|
15724
|
+
function createStore(tileSize, layers, renderer) {
|
|
15725
|
+
const size = tileSize * 4;
|
|
15726
|
+
const texture = new DataArrayTexture(null, size, size, layers);
|
|
15727
|
+
texture.source.dataReady = false;
|
|
15728
|
+
texture.format = RGBAFormat;
|
|
15729
|
+
texture.type = UnsignedByteType;
|
|
15730
|
+
texture.colorSpace = SRGBColorSpace;
|
|
15731
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
15732
|
+
texture.minFilter = texture.magFilter = LinearFilter;
|
|
15733
|
+
texture.generateMipmaps = false;
|
|
15734
|
+
texture.anisotropy = Math.min(MAX_ANISOTROPY, renderer.capabilities.getMaxAnisotropy());
|
|
15735
|
+
texture.needsUpdate = true;
|
|
15736
|
+
const mask = new DataArrayTexture(new Uint8Array(SLOTS * layers), 4, 4, layers);
|
|
15737
|
+
mask.format = RedFormat;
|
|
15738
|
+
mask.type = UnsignedByteType;
|
|
15739
|
+
mask.minFilter = mask.magFilter = NearestFilter;
|
|
15740
|
+
mask.unpackAlignment = 1;
|
|
15741
|
+
mask.needsUpdate = true;
|
|
15742
|
+
return {
|
|
15743
|
+
texture,
|
|
15744
|
+
mask,
|
|
15745
|
+
windows: [],
|
|
15746
|
+
free: Array.from({ length: layers }, (_, layer) => layer),
|
|
15747
|
+
tileSize
|
|
15748
|
+
};
|
|
15749
|
+
}
|
|
15750
|
+
function setLoaded(store, layer, slot, loaded) {
|
|
15751
|
+
store.mask.image.data[layer * SLOTS + slot] = loaded ? 255 : 0;
|
|
15752
|
+
store.mask.needsUpdate = true;
|
|
15753
|
+
}
|
|
15754
|
+
function isLoaded(store, layer, slot) {
|
|
15755
|
+
return store.mask.image.data[layer * SLOTS + slot] !== 0;
|
|
15756
|
+
}
|
|
15757
|
+
function retarget(store, win, placement, frame) {
|
|
15758
|
+
if (win.placement === placement && win.frame === frame) return;
|
|
15759
|
+
win.placement = placement;
|
|
15760
|
+
win.frame = frame;
|
|
15761
|
+
for (let slot = 0; slot < SLOTS; slot++) {
|
|
15762
|
+
const { x, y } = slotTile(placement, slot % 4, Math.floor(slot / 4));
|
|
15763
|
+
const key = `${frame ?? 0}/${win.chart}/${win.level}/${x}/${y}`;
|
|
15764
|
+
if (win.wanted[slot] === key) continue;
|
|
15765
|
+
win.wanted[slot] = key;
|
|
15766
|
+
setLoaded(store, win.layer, slot, false);
|
|
15767
|
+
const stale = win.pending.get(slot);
|
|
15768
|
+
if (stale) {
|
|
15769
|
+
stale.abort.abort();
|
|
15770
|
+
win.pending.delete(slot);
|
|
15771
|
+
}
|
|
15772
|
+
}
|
|
15773
|
+
}
|
|
15774
|
+
function release(store, win) {
|
|
15775
|
+
for (const { abort } of win.pending.values()) abort.abort();
|
|
15776
|
+
win.pending.clear();
|
|
15777
|
+
for (let slot = 0; slot < SLOTS; slot++) setLoaded(store, win.layer, slot, false);
|
|
15778
|
+
store.free.push(win.layer);
|
|
15779
|
+
}
|
|
15780
|
+
function settled(overlay, store, win) {
|
|
15781
|
+
for (let slot = 0; slot < SLOTS; slot++) {
|
|
15782
|
+
const key = win.wanted[slot];
|
|
15783
|
+
if (!key) return false;
|
|
15784
|
+
if (!isLoaded(store, win.layer, slot) && !overlay.failed.has(key)) return false;
|
|
15785
|
+
}
|
|
15786
|
+
return true;
|
|
15787
|
+
}
|
|
15788
|
+
async function fetchTile(url, signal) {
|
|
15789
|
+
const response = await fetch(url, { signal });
|
|
15790
|
+
if (!response.ok) throw new Error(`tile ${response.status}`);
|
|
15791
|
+
return createImageBitmap(await response.blob(), {
|
|
15792
|
+
imageOrientation: "flipY",
|
|
15793
|
+
premultiplyAlpha: "none",
|
|
15794
|
+
colorSpaceConversion: "none"
|
|
15795
|
+
});
|
|
15796
|
+
}
|
|
15797
|
+
var copyAt = new Vector3();
|
|
15798
|
+
var queue$1 = [];
|
|
15799
|
+
function pump(overlay, store, tiles, win, x, y, renderer, invalidate) {
|
|
15800
|
+
const placement = win.placement;
|
|
15801
|
+
if (!placement) return;
|
|
15802
|
+
const columns = chartColumns(win.chart, win.level);
|
|
15803
|
+
const cx = x * columns;
|
|
15804
|
+
const cy = y * chartRows(win.chart, win.level);
|
|
15805
|
+
queue$1.length = 0;
|
|
15806
|
+
for (let slot = 0; slot < SLOTS; slot++) {
|
|
15807
|
+
const key = win.wanted[slot];
|
|
15808
|
+
if (!key || win.pending.has(slot) || overlay.failed.has(key)) continue;
|
|
15809
|
+
if (isLoaded(store, win.layer, slot)) continue;
|
|
15810
|
+
const tile = slotTile(placement, slot % 4, Math.floor(slot / 4));
|
|
15811
|
+
let dx = tile.x + .5 - cx;
|
|
15812
|
+
if (win.chart === 0) dx -= columns * Math.round(dx / columns);
|
|
15813
|
+
queue$1.push({
|
|
15814
|
+
slot,
|
|
15815
|
+
far: Math.hypot(dx, tile.y + .5 - cy)
|
|
15816
|
+
});
|
|
15817
|
+
}
|
|
15818
|
+
queue$1.sort((a, b) => a.far - b.far);
|
|
15819
|
+
for (const { slot } of queue$1) {
|
|
15820
|
+
if (overlay.inflight >= MAX_INFLIGHT) return;
|
|
15821
|
+
const key = win.wanted[slot];
|
|
15822
|
+
const sx = slot % 4;
|
|
15823
|
+
const sy = Math.floor(slot / 4);
|
|
15824
|
+
const tile = slotTile(placement, sx, sy);
|
|
15825
|
+
const abort = new AbortController();
|
|
15826
|
+
let timedOut = false;
|
|
15827
|
+
const timer = setTimeout(() => {
|
|
15828
|
+
timedOut = true;
|
|
15829
|
+
abort.abort();
|
|
15830
|
+
}, FETCH_TIMEOUT_S * 1e3);
|
|
15831
|
+
win.pending.set(slot, {
|
|
15832
|
+
key,
|
|
15833
|
+
abort
|
|
15834
|
+
});
|
|
15835
|
+
overlay.inflight++;
|
|
15836
|
+
const folder = CHART_FOLDER[win.chart];
|
|
15837
|
+
fetchTile(tileUrl(tiles, folder, win.level, tile.x, tile.y, win.frame), abort.signal).then((bitmap) => {
|
|
15838
|
+
if (win.wanted[slot] === key && !abort.signal.aborted) {
|
|
15839
|
+
const source = new Texture(bitmap);
|
|
15840
|
+
source.colorSpace = SRGBColorSpace;
|
|
15841
|
+
source.flipY = false;
|
|
15842
|
+
source.generateMipmaps = false;
|
|
15843
|
+
copyAt.set(sx * store.tileSize, (4 - 1 - sy) * store.tileSize, win.layer);
|
|
15844
|
+
renderer.copyTextureToTexture(source, store.texture, null, copyAt);
|
|
15845
|
+
source.dispose();
|
|
15846
|
+
setLoaded(store, win.layer, slot, true);
|
|
15847
|
+
overlay.failures = 0;
|
|
15848
|
+
invalidate();
|
|
15849
|
+
}
|
|
15850
|
+
bitmap.close();
|
|
15851
|
+
}).catch(() => {
|
|
15852
|
+
if (abort.signal.aborted && !timedOut) return;
|
|
15853
|
+
overlay.failed.add(key);
|
|
15854
|
+
if (++overlay.failures >= MAX_FAILURES) overlay.dead = true;
|
|
15855
|
+
}).finally(() => {
|
|
15856
|
+
clearTimeout(timer);
|
|
15857
|
+
overlay.inflight--;
|
|
15858
|
+
if (win.pending.get(slot)?.key === key) win.pending.delete(slot);
|
|
15859
|
+
});
|
|
15860
|
+
}
|
|
15861
|
+
}
|
|
15862
|
+
function switchOff(overlay) {
|
|
15863
|
+
for (const link of overlay.uniforms.uTileWindow.value) link.w = 0;
|
|
15864
|
+
}
|
|
15865
|
+
function rest(bo, overlay, now) {
|
|
15866
|
+
switchOff(overlay);
|
|
15867
|
+
if (now - overlay.lastUsed > IDLE_DISPOSE_S * 1e3) disposeTileOverlay(bo);
|
|
15868
|
+
}
|
|
15869
|
+
function disposeTileOverlay(bo) {
|
|
15870
|
+
const overlay = bo.tileOverlay;
|
|
15871
|
+
if (!overlay) return;
|
|
15872
|
+
switchOff(overlay);
|
|
15873
|
+
const store = overlay.store;
|
|
15874
|
+
if (!store) return;
|
|
15875
|
+
for (const win of store.windows) for (const { abort } of win.pending.values()) abort.abort();
|
|
15876
|
+
store.texture.dispose();
|
|
15877
|
+
store.mask.dispose();
|
|
15878
|
+
overlay.store = void 0;
|
|
15879
|
+
overlay.uniforms.uTileMap.value = null;
|
|
15880
|
+
overlay.uniforms.uTileMask.value = null;
|
|
15881
|
+
}
|
|
15882
|
+
function tilesServe(bo) {
|
|
15883
|
+
const tiles = bo.surfaceTiles;
|
|
15884
|
+
return !!tiles && tiles.max_level >= 3 && tilesBase() !== "" && maxTileWindows() > 0 && !bo.tileOverlay?.dead && textureAllowed(tiles.distribution);
|
|
15885
|
+
}
|
|
15886
|
+
var inverse = new Matrix4();
|
|
15887
|
+
var over = new Vector3();
|
|
15888
|
+
function updateTileOverlay(bo, camera, renderer, projScale, drawable, invalidate) {
|
|
15889
|
+
const tiles = bo.surfaceTiles;
|
|
15890
|
+
const mesh = bo.mesh;
|
|
15891
|
+
const material = mesh?.material;
|
|
15892
|
+
const level = tiles && mesh && material?.map && drawable && tilesServe(bo) ? neededLevel(bo.radiusScene, bo.cachedDist, projScale, tiles.max_level) : -1;
|
|
15893
|
+
const now = performance.now();
|
|
15894
|
+
if (!tiles || !mesh || !material || level < 3) {
|
|
15895
|
+
if (bo.tileOverlay) rest(bo, bo.tileOverlay, now);
|
|
15896
|
+
return;
|
|
15897
|
+
}
|
|
15898
|
+
if (bo.tileOverlay && bo.tileOverlay.material !== material) {
|
|
15899
|
+
disposeTileOverlay(bo);
|
|
15900
|
+
bo.tileOverlay = void 0;
|
|
15901
|
+
}
|
|
15902
|
+
const overlay = bo.tileOverlay ??= createOverlay(material);
|
|
15903
|
+
overlay.lastUsed = now;
|
|
15904
|
+
const levels = tiles.max_level - 3 + 1;
|
|
15905
|
+
const count = Math.min(maxTileWindows(), MAX_WINDOWS, levels + 1);
|
|
15906
|
+
if (overlay.store && overlay.store.texture.image.depth !== count + 1) disposeTileOverlay(bo);
|
|
15907
|
+
const store = overlay.store ??= createStore(tiles.tile_size, count + 1, renderer);
|
|
15908
|
+
overlay.uniforms.uTileMap.value = store.texture;
|
|
15909
|
+
overlay.uniforms.uTileMask.value = store.mask;
|
|
15910
|
+
overlay.uniforms.uTileFilter.value.set(store.texture.anisotropy, tiles.tile_size);
|
|
15911
|
+
over.copy(camera.position).applyMatrix4(inverse.copy(mesh.matrixWorld).invert()).normalize();
|
|
15912
|
+
const cap = over.y >= 0 ? 1 : 2;
|
|
15913
|
+
overlay.chart = cameraChart(over.y, overlay.chart);
|
|
15914
|
+
const chain = windowChain(level, count, overlay.chart, over.y);
|
|
15915
|
+
const frame = tiles.frames ? bo.textureFrame : void 0;
|
|
15916
|
+
const held = (link) => store.windows.find((win) => win.chart === link.chart && win.level === link.level);
|
|
15917
|
+
store.windows = store.windows.filter((win) => {
|
|
15918
|
+
win.rank = chain.findIndex((link) => link.chart === win.chart && link.level === win.level);
|
|
15919
|
+
if (win.rank >= 0) return true;
|
|
15920
|
+
win.rank = chain.length;
|
|
15921
|
+
const heir = chain.find((link) => link.level === win.level);
|
|
15922
|
+
const heirWindow = heir && held(heir);
|
|
15923
|
+
const near = win.chart === 0 || win.chart === cap;
|
|
15924
|
+
if (heir && near && !(heirWindow && settled(overlay, store, heirWindow))) return true;
|
|
15925
|
+
release(store, win);
|
|
15926
|
+
return false;
|
|
15927
|
+
});
|
|
15928
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
15929
|
+
const link = chain[i];
|
|
15930
|
+
let win = held(link);
|
|
15931
|
+
if (!win) {
|
|
15932
|
+
const layer = store.free.pop();
|
|
15933
|
+
if (layer === void 0) continue;
|
|
15934
|
+
win = {
|
|
15935
|
+
layer,
|
|
15936
|
+
...link,
|
|
15937
|
+
rank: i,
|
|
15938
|
+
wanted: new Array(SLOTS),
|
|
15939
|
+
pending: /* @__PURE__ */ new Map()
|
|
15940
|
+
};
|
|
15941
|
+
store.windows.push(win);
|
|
15942
|
+
}
|
|
15943
|
+
const point = chartPoint(link.chart, over.x, over.y, over.z);
|
|
15944
|
+
retarget(store, win, placeWindow(link.chart, link.level, point.x, point.y, win.placement), frame);
|
|
15945
|
+
pump(overlay, store, tiles, win, point.x, point.y, renderer, invalidate);
|
|
15946
|
+
}
|
|
15947
|
+
store.windows.sort((a, b) => b.level - a.level || a.rank - b.rank);
|
|
15948
|
+
for (let i = 0; i < MAX_DRAWN; i++) {
|
|
15949
|
+
const win = store.windows[i];
|
|
15950
|
+
const link = overlay.uniforms.uTileWindow.value[i];
|
|
15951
|
+
if (!win?.placement) {
|
|
15952
|
+
link.w = 0;
|
|
15953
|
+
continue;
|
|
15954
|
+
}
|
|
15955
|
+
overlay.uniforms.uTileGrid.value[i].set(chartColumns(win.chart, win.level), chartRows(win.chart, win.level), win.placement.tx0, win.placement.ty0);
|
|
15956
|
+
link.set(win.chart, win.layer, win.level, 1);
|
|
15957
|
+
}
|
|
15958
|
+
}
|
|
15959
|
+
|
|
15432
15960
|
//#endregion
|
|
15433
15961
|
//#region src/lib/scene/objects/body/lifecycle.ts
|
|
15434
15962
|
function disposeMaterial(mat) {
|
|
@@ -15562,6 +16090,8 @@ function downgradeBodyMesh(bo, scene, clickables, meshToBody) {
|
|
|
15562
16090
|
detachSelfShadow(bo.selfShadow);
|
|
15563
16091
|
bo.selfShadow = null;
|
|
15564
16092
|
}
|
|
16093
|
+
disposeTileOverlay(bo);
|
|
16094
|
+
bo.tileOverlay = void 0;
|
|
15565
16095
|
bo.terrainWindow = null;
|
|
15566
16096
|
mesh.removeFromParent();
|
|
15567
16097
|
mesh.geometry.dispose();
|
|
@@ -16269,6 +16799,7 @@ async function loadBodyTexture(bo, textureLoader, currentJd, scene, maxTextureSi
|
|
|
16269
16799
|
if (!sceneSettings().showSurfaceTexture) return;
|
|
16270
16800
|
if (!surface) return;
|
|
16271
16801
|
bo.textureBundleId = surface.id === bodyId ? void 0 : surface.id;
|
|
16802
|
+
bo.surfaceTiles = surface.tiles;
|
|
16272
16803
|
bo.availableTiers ??= surface.tiers ?? [...TIER_NAMES];
|
|
16273
16804
|
bo.availableFrames = surface.frames;
|
|
16274
16805
|
await swapBodyTexture(bo, "low", textureFrameForJd(currentJd, bo.availableFrames), textureLoader);
|
|
@@ -16288,6 +16819,7 @@ function unloadBodyTexture(bo) {
|
|
|
16288
16819
|
bo.textureLoadGen = (bo.textureLoadGen ?? 0) + 1;
|
|
16289
16820
|
const material = bo.mesh.material;
|
|
16290
16821
|
unloadSiblingLayer("topography", bo);
|
|
16822
|
+
disposeTileOverlay(bo);
|
|
16291
16823
|
if (!material.map) return;
|
|
16292
16824
|
material.map.dispose();
|
|
16293
16825
|
material.map = null;
|
|
@@ -17631,9 +18163,11 @@ function makeTrail(body, color, basisPos = [
|
|
|
17631
18163
|
const { orbitElements, orbitCenter, data } = body;
|
|
17632
18164
|
let curve;
|
|
17633
18165
|
let isOpenCurve;
|
|
18166
|
+
let start;
|
|
17634
18167
|
if (data.satrec) {
|
|
17635
18168
|
curve = sgp4Curve(data.satrec, jd, data.n / 360, 512);
|
|
17636
18169
|
isOpenCurve = true;
|
|
18170
|
+
start = curve.length - 1;
|
|
17637
18171
|
} else {
|
|
17638
18172
|
if (!orbitElements) throw new Error("makeTrail called without orbitElements");
|
|
17639
18173
|
curve = null;
|
|
@@ -17643,7 +18177,7 @@ function makeTrail(body, color, basisPos = [
|
|
|
17643
18177
|
const cy = orbitCenter?.[1] ?? 0;
|
|
17644
18178
|
const cz = orbitCenter?.[2] ?? 0;
|
|
17645
18179
|
const useTrail = isOpenCurve || data.objectType === ObjectType.DWARF_PLANET || data.objectType === ObjectType.MOON || data.objectType === ObjectType.SPACECRAFT || data.objectType === ObjectType.COMET || isAsteroid(data.objectType);
|
|
17646
|
-
const validPoints = curve ? buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz) : [];
|
|
18180
|
+
const validPoints = curve ? buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz, start) : [];
|
|
17647
18181
|
if (curve && validPoints.length < 2) return makeEmptyTrail();
|
|
17648
18182
|
const bufferCapacity = curve ? Math.max(validPoints.length, curve.length) : 512 + 1;
|
|
17649
18183
|
const fullAlphas = new Float32Array(bufferCapacity);
|
|
@@ -17823,6 +18357,7 @@ async function loadSystemData(barycenterId, bodyObjects, scene, textureLoader, c
|
|
|
17823
18357
|
bo.availableTiers = surface.tiers;
|
|
17824
18358
|
bo.availableFrames = surface.frames;
|
|
17825
18359
|
bo.textureBundleId = surface.id === bodyId ? void 0 : surface.id;
|
|
18360
|
+
bo.surfaceTiles = surface.tiles;
|
|
17826
18361
|
if (!bo.textureTier) {
|
|
17827
18362
|
const frame = textureFrameForJd(currentJd, bo.availableFrames);
|
|
17828
18363
|
promises.push(loadBodyTextureTier(bo, "low", frame, textureLoader));
|
|
@@ -19650,15 +20185,17 @@ function refreshTrail(body, line, basisPos, jd, view) {
|
|
|
19650
20185
|
const useTrail = line.userData.useTrail;
|
|
19651
20186
|
const oc = line.userData.orbitCenter;
|
|
19652
20187
|
const cx = oc.x, cy = oc.y, cz = oc.z;
|
|
20188
|
+
let windowStart;
|
|
19653
20189
|
if (body.data.satrec) {
|
|
19654
20190
|
const meanMotion = body.data.n / 360;
|
|
19655
20191
|
const stepDays = meanMotion > 0 ? 1 / meanMotion / 512 : 0;
|
|
19656
|
-
|
|
20192
|
+
let curveJd = line.userData.curveJd;
|
|
19657
20193
|
if (curve.length === 0 || curveJd === void 0 || Math.abs(jd - curveJd) >= stepDays) {
|
|
19658
20194
|
curve = sgp4Curve(body.data.satrec, jd, meanMotion, 512);
|
|
19659
20195
|
line.userData.sourceCurve = curve;
|
|
19660
|
-
line.userData.curveJd = jd;
|
|
20196
|
+
line.userData.curveJd = curveJd = jd;
|
|
19661
20197
|
}
|
|
20198
|
+
windowStart = curve.length - (jd < curveJd ? 2 : 1);
|
|
19662
20199
|
} else if (body.orbitElements) {
|
|
19663
20200
|
if (body.rederiveElements) {
|
|
19664
20201
|
const elementsJd = line.userData.elementsJd ?? jd;
|
|
@@ -19697,7 +20234,7 @@ function refreshTrail(body, line, basisPos, jd, view) {
|
|
|
19697
20234
|
const headY = anchor[1] - cy;
|
|
19698
20235
|
const headZ = anchor[2] - cz;
|
|
19699
20236
|
if (withinTolerance(ud, curve, offX, offY, offZ, headX, headY, headZ, view)) return;
|
|
19700
|
-
const validPoints = buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz);
|
|
20237
|
+
const validPoints = buildTrailPoints(body, curve, isOpenCurve, cx, cy, cz, windowStart);
|
|
19701
20238
|
if (validPoints.length < 2) return;
|
|
19702
20239
|
const { posArr, trailArr, fullArr, capacity } = getTrailWorkingArrays(line);
|
|
19703
20240
|
const bx = cx - basisPos[0], by = cy - basisPos[1], bz = cz - basisPos[2];
|
|
@@ -20998,11 +21535,10 @@ function updatePositions(params) {
|
|
|
20998
21535
|
setVec(focus.focusTargetWorld, p);
|
|
20999
21536
|
const camOff = focus.camTargetOffset;
|
|
21000
21537
|
if (camOff && focus.camTargetWorld) addVec(focus.camTargetWorld, p, camOff);
|
|
21001
|
-
} else if (a.oldPos) {
|
|
21002
|
-
|
|
21003
|
-
focus.focusTruePos[
|
|
21004
|
-
focus.
|
|
21005
|
-
setVec(focus.focusTargetWorld, focus.focusTruePos);
|
|
21538
|
+
} else if (a.oldPos) for (let k = 0; k < 3; k++) {
|
|
21539
|
+
const moved = p[k] - a.oldPos[k];
|
|
21540
|
+
focus.focusTruePos[k] += moved;
|
|
21541
|
+
focus.focusTargetWorld[k] += moved;
|
|
21006
21542
|
}
|
|
21007
21543
|
break;
|
|
21008
21544
|
}
|
|
@@ -21025,8 +21561,10 @@ function updatePositions(params) {
|
|
|
21025
21561
|
refreshTrail(bo.body, line, basis, jd, trailView);
|
|
21026
21562
|
line.userData.refreshDeferred = false;
|
|
21027
21563
|
}
|
|
21564
|
+
const focusLoading = !focusPos && why === "loading";
|
|
21028
21565
|
return {
|
|
21029
|
-
focusUnplaced: focusedBody !== void 0 && !focusPos,
|
|
21566
|
+
focusUnplaced: focusedBody !== void 0 && !focusPos && !focusLoading,
|
|
21567
|
+
focusLoading,
|
|
21030
21568
|
anchorId
|
|
21031
21569
|
};
|
|
21032
21570
|
}
|
|
@@ -21364,10 +21902,11 @@ var Placer = class {
|
|
|
21364
21902
|
ride = planRideMarkers([], void 0, () => void 0);
|
|
21365
21903
|
rideJd = NaN;
|
|
21366
21904
|
rideFocus;
|
|
21367
|
-
constructor(ctx, bodyObjects, diagnostics) {
|
|
21905
|
+
constructor(ctx, bodyObjects, diagnostics, held = () => false) {
|
|
21368
21906
|
this.ctx = ctx;
|
|
21369
21907
|
this.bodyObjects = bodyObjects;
|
|
21370
21908
|
this.diagnostics = diagnostics;
|
|
21909
|
+
this.held = held;
|
|
21371
21910
|
}
|
|
21372
21911
|
begin(jd, focusedId, frame) {
|
|
21373
21912
|
this.passJd = jd;
|
|
@@ -21391,6 +21930,7 @@ var Placer = class {
|
|
|
21391
21930
|
this.place(body, this.passJd);
|
|
21392
21931
|
}
|
|
21393
21932
|
place(body, jd) {
|
|
21933
|
+
if (jd !== this.passJd && this.held()) jd = this.passJd;
|
|
21394
21934
|
const id = body.data.id;
|
|
21395
21935
|
if (jd === this.passJd && this.settled.has(id)) return body.position;
|
|
21396
21936
|
if (this.settling.has(id)) {
|
|
@@ -21403,11 +21943,14 @@ var Placer = class {
|
|
|
21403
21943
|
if (jd === this.passJd) this.settled.add(id);
|
|
21404
21944
|
return pos;
|
|
21405
21945
|
}
|
|
21946
|
+
under(aboveId) {
|
|
21947
|
+
return (this.ctx.getBody(aboveId) ?? this.bodyObjects.get(aboveId)?.body)?.unplaced === "loading" ? "loading" : "parent";
|
|
21948
|
+
}
|
|
21406
21949
|
seatFeature(body, jd) {
|
|
21407
21950
|
const hostId = body.featureAnchor.hostId;
|
|
21408
21951
|
const host = this.ctx.getBody(hostId);
|
|
21409
21952
|
const hostPos = host && this.place(host, jd);
|
|
21410
|
-
if (!host || !hostPos) return setUnplaced(body,
|
|
21953
|
+
if (!host || !hostPos) return setUnplaced(body, this.under(hostId));
|
|
21411
21954
|
return seatFeatureBody(body, host, hostPos, this.bodyObjects.get(hostId), jd);
|
|
21412
21955
|
}
|
|
21413
21956
|
rideAt(jd) {
|
|
@@ -21438,8 +21981,9 @@ var Placer = class {
|
|
|
21438
21981
|
if (d.orbitalSource === OrbitalSource.SPICE_PROBE) return this.settleProbe(body, bo, jd);
|
|
21439
21982
|
const parentPos = this.at(d.parentId, jd);
|
|
21440
21983
|
if (!parentPos) {
|
|
21441
|
-
|
|
21442
|
-
|
|
21984
|
+
const why = this.under(d.parentId);
|
|
21985
|
+
if (why === "parent") diagnostics.warnOnce("missing-parent", d.id, () => `place[${d.id}]: parent ${d.parentId} has no place`);
|
|
21986
|
+
return setUnplaced(body, why);
|
|
21443
21987
|
}
|
|
21444
21988
|
diagnostics.clear("missing-parent", d.id);
|
|
21445
21989
|
let offset;
|
|
@@ -21449,9 +21993,10 @@ var Placer = class {
|
|
|
21449
21993
|
return this.finish(body, bo, parentPos[0] + offset[0], parentPos[1] + offset[1], parentPos[2] + offset[2], parentPos, jd);
|
|
21450
21994
|
}
|
|
21451
21995
|
chebMiss(id, jd) {
|
|
21452
|
-
const
|
|
21453
|
-
|
|
21454
|
-
|
|
21996
|
+
const store = this.ctx.chebStore;
|
|
21997
|
+
if (store.isLoading(id, jd)) return "loading";
|
|
21998
|
+
const coverage = store.zoneCoverage(id);
|
|
21999
|
+
if (coverage !== null && (jd < coverage.start || jd > coverage.end) && jd === this.passJd) {
|
|
21455
22000
|
this.noData.count++;
|
|
21456
22001
|
if (coverage.start < this.noData.earliestStart) this.noData.earliestStart = coverage.start;
|
|
21457
22002
|
if (coverage.end > this.noData.latestEnd) this.noData.latestEnd = coverage.end;
|
|
@@ -21460,7 +22005,7 @@ var Placer = class {
|
|
|
21460
22005
|
const cov = coverage ? `[${coverage.start.toFixed(1)},${coverage.end.toFixed(1)}]` : "unknown";
|
|
21461
22006
|
return `chebStore.positionScene[${id}] returned null at jd=${jd.toFixed(3)} (coverage=${cov})`;
|
|
21462
22007
|
});
|
|
21463
|
-
return
|
|
22008
|
+
return "no-data";
|
|
21464
22009
|
}
|
|
21465
22010
|
finish(body, bo, x, y, z, parentPos, jd) {
|
|
21466
22011
|
const d = body.data;
|
|
@@ -21523,6 +22068,7 @@ var Placer = class {
|
|
|
21523
22068
|
const zonePref = activeSysId ? (fitCenterNaif) => ctx.bodies.isInSystem(`naif-${fitCenterNaif}`, activeSysId) : void 0;
|
|
21524
22069
|
const located = ctx.probeStore?.probeWithCenter(d.id, jd, zonePref) ?? null;
|
|
21525
22070
|
if (!located) {
|
|
22071
|
+
if (ctx.probeStore?.loadingAt(jd)) return setUnplaced(body, "loading");
|
|
21526
22072
|
setUnplaced(body, "no-data");
|
|
21527
22073
|
diagnostics.warnOnce("probe-unavailable", d.id, () => {
|
|
21528
22074
|
const reason = !ctx.probeStore ? "no ProbeStore" : "no zone has both a loaded chunk and a sub-chunk covering this jd";
|
|
@@ -21534,7 +22080,7 @@ var Placer = class {
|
|
|
21534
22080
|
if (probeLanded && isLandedAt(located.probe, jd)) {
|
|
21535
22081
|
const flyingFrameId = d.parentId;
|
|
21536
22082
|
const landedRender = renderLandedProbe(d, located.probe, probeLanded, jd, (id) => this.at(id, jd), ctx, bodyObjects);
|
|
21537
|
-
if (!landedRender) return setUnplaced(body,
|
|
22083
|
+
if (!landedRender) return setUnplaced(body, this.under(`naif-${probeLanded.bodyNaifId}`));
|
|
21538
22084
|
diagnostics.clear("probe-unavailable", d.id);
|
|
21539
22085
|
if (flyingFrameId !== d.parentId) body.trailBuffer?.clear();
|
|
21540
22086
|
if (bo) {
|
|
@@ -21559,7 +22105,8 @@ var Placer = class {
|
|
|
21559
22105
|
}
|
|
21560
22106
|
const primary = resolveProbePrimary(located.probe, jd, located.fitCenterNaifId, ctx.chebStore ?? null, (id) => this.at(id, jd) !== null);
|
|
21561
22107
|
if (!primary) {
|
|
21562
|
-
|
|
22108
|
+
const center = located.probe.fitCenter?.id;
|
|
22109
|
+
setUnplaced(body, center ? this.under(center) : "parent");
|
|
21563
22110
|
diagnostics.warnOnce("probe-unavailable", d.id, () => `probe ${d.id} (${d.name ?? "unnamed"}): hidden — stamped fit center is not placeable this frame`);
|
|
21564
22111
|
return null;
|
|
21565
22112
|
}
|
|
@@ -21606,7 +22153,7 @@ var Placer = class {
|
|
|
21606
22153
|
if (probeParentChanged) d.parentId = probeParentKey;
|
|
21607
22154
|
const probeParentPos = this.at(probeParentKey, jd);
|
|
21608
22155
|
if (!probeParentPos) {
|
|
21609
|
-
setUnplaced(body,
|
|
22156
|
+
setUnplaced(body, this.under(probeParentKey));
|
|
21610
22157
|
diagnostics.warnOnce("probe-unavailable", d.id, () => `probe ${d.id} (${d.name ?? "unnamed"}): hidden — fit center ${probeParentKey} has no position this frame`);
|
|
21611
22158
|
return null;
|
|
21612
22159
|
}
|
|
@@ -21883,11 +22430,11 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
|
|
|
21883
22430
|
spinAxis.set(0, 1, 0).applyQuaternion(bo.mesh.quaternion);
|
|
21884
22431
|
uniforms.uSpinAxis.value.copy(spinAxis);
|
|
21885
22432
|
}
|
|
21886
|
-
const
|
|
21887
|
-
const shellRadius = bo.atmosphere.geometryRadiusScene *
|
|
21888
|
-
const camDist = cameraPosition.distanceTo(
|
|
22433
|
+
const shellCenter = bo.root.position;
|
|
22434
|
+
const shellRadius = bo.atmosphere.geometryRadiusScene * bo.atmosphere.mesh.scale.x;
|
|
22435
|
+
const camDist = cameraPosition.distanceTo(shellCenter);
|
|
21889
22436
|
if (shellRadius > camDist * .1) state.shellProminent = true;
|
|
21890
|
-
if (quality.eclipseShadows) cullShellOccluders(uniforms,
|
|
22437
|
+
if (quality.eclipseShadows) cullShellOccluders(uniforms, shellCenter, sunVec, shellRadius);
|
|
21891
22438
|
const inside = quality.insideView && camDist < shellRadius;
|
|
21892
22439
|
const referenceKm = bo.atmosphere.planetRadiusKm;
|
|
21893
22440
|
const altKm = camDist * ((referenceKm + params.topAltitudeKm) / shellRadius) - referenceKm;
|
|
@@ -21901,7 +22448,7 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
|
|
|
21901
22448
|
}
|
|
21902
22449
|
if (inside) {
|
|
21903
22450
|
state.insideShell = true;
|
|
21904
|
-
const sinSunElev = camUp.copy(cameraPosition).sub(
|
|
22451
|
+
const sinSunElev = camUp.copy(cameraPosition).sub(shellCenter).divideScalar(camDist).dot(sunVec);
|
|
21905
22452
|
const dim = skyboxDimFactor(params, altKm, sinSunElev);
|
|
21906
22453
|
state.skyboxIntensity *= dim * (1 - deckBlend);
|
|
21907
22454
|
if (quality.refraction && params.refractivity && !underDeck) {
|
|
@@ -21915,7 +22462,7 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
|
|
|
21915
22462
|
const shellRatio = camDist / shellRadius;
|
|
21916
22463
|
if (quality.sunTint && shellRatio < SUN_TINT_MAX_RATIO) {
|
|
21917
22464
|
const planetRadiusScene = shellRadius / (1 + params.topAltitudeKm / referenceKm) * (bo.atmosphere.surfaceRadiusKm / referenceKm);
|
|
21918
|
-
camRel.copy(cameraPosition).sub(
|
|
22465
|
+
camRel.copy(cameraPosition).sub(shellCenter);
|
|
21919
22466
|
const shellSpinAxis = uniforms.uSpinAxis.value;
|
|
21920
22467
|
const shellStretch = uniforms.uStretch.value;
|
|
21921
22468
|
if (sunPathTransmittance(params, camRel, sunVec, planetRadiusScene, bo.atmosphere.surfaceRadiusKm, sunT, shellSpinAxis, shellStretch)) {
|
|
@@ -21924,7 +22471,7 @@ function updateAtmosphereShaders(bodyObjects, cameraPosition, visible, realistic
|
|
|
21924
22471
|
}
|
|
21925
22472
|
if (sunViewTint && shellRatio < bestViewTintRatio) {
|
|
21926
22473
|
bestViewTintRatio = shellRatio;
|
|
21927
|
-
bindViewTint(sunViewTint, params,
|
|
22474
|
+
bindViewTint(sunViewTint, params, shellCenter, planetRadiusScene, bo.atmosphere.surfaceRadiusKm, shellSpinAxis, shellStretch);
|
|
21928
22475
|
}
|
|
21929
22476
|
}
|
|
21930
22477
|
}
|
|
@@ -22251,7 +22798,7 @@ function desiredDemTier(altitudeRadii, current) {
|
|
|
22251
22798
|
if (tierRank(down) < tierRank(current)) return down;
|
|
22252
22799
|
return current;
|
|
22253
22800
|
}
|
|
22254
|
-
function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, focusedId, jd, appearances) {
|
|
22801
|
+
function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, focusedId, jd, appearances, invalidate) {
|
|
22255
22802
|
appearances.apply(bodyObjects, textureLoader);
|
|
22256
22803
|
const fovRad = camera.fov * Math.PI / 180;
|
|
22257
22804
|
const projScale = renderer.domElement.clientHeight / (2 * Math.tan(fovRad / 2));
|
|
@@ -22275,7 +22822,8 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
|
|
|
22275
22822
|
else if (screenR < 1024 && altitudeRadii > 2) desired = "medium";
|
|
22276
22823
|
else desired = "high";
|
|
22277
22824
|
const currentRank = tierRank(bo.textureTier);
|
|
22278
|
-
const
|
|
22825
|
+
const bodyCap = tilesServe(bo) ? Math.min(tierCap, tierRank("medium")) : tierCap;
|
|
22826
|
+
const desiredRank = Math.min(tierRank(desired), bodyCap);
|
|
22279
22827
|
const desiredFrame = textureFrameForJd(jd, bo.availableFrames);
|
|
22280
22828
|
const frameChanged = desiredFrame !== bo.textureFrame;
|
|
22281
22829
|
const wantsUpgrade = desiredRank > currentRank;
|
|
@@ -22283,6 +22831,7 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
|
|
|
22283
22831
|
const target = wantsUpgrade ? highestAvailableTier(desiredRank, bo.availableTiers) : bo.textureTier;
|
|
22284
22832
|
if (target) loadBodyTextureTier(bo, target, desiredFrame, textureLoader);
|
|
22285
22833
|
}
|
|
22834
|
+
updateTileOverlay(bo, camera, renderer, projScale * renderer.getPixelRatio(), settings.showSurfaceTexture && !bo.appearance?.surfaceOwned && !bo.model, invalidate);
|
|
22286
22835
|
const dispTiers = bo.displacementMeta?.tiers;
|
|
22287
22836
|
if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
|
|
22288
22837
|
const maxTex = renderer.capabilities.maxTextureSize;
|
|
@@ -23629,7 +24178,10 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23629
24178
|
disposed = false;
|
|
23630
24179
|
lastDataVersion = -1;
|
|
23631
24180
|
dateHolds = 0;
|
|
24181
|
+
seenJumps = 0;
|
|
24182
|
+
jumpJd = null;
|
|
23632
24183
|
focusWasOutOfRange = false;
|
|
24184
|
+
seekAwaitsData = false;
|
|
23633
24185
|
frameIndex = 0;
|
|
23634
24186
|
static WAKE_FRAMES = 30;
|
|
23635
24187
|
static IDLE_HEARTBEAT_FRAMES = 10;
|
|
@@ -23683,6 +24235,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23683
24235
|
this.ctx = ctx;
|
|
23684
24236
|
ctx.hasMeshBody = (id) => this.bodyObjects.has(id);
|
|
23685
24237
|
this.clock = clock;
|
|
24238
|
+
this.seenJumps = clock.jumps;
|
|
23686
24239
|
this.callbacks = callbacks;
|
|
23687
24240
|
this.outOfRange = new OutOfRangeNotifier(callbacks.notices);
|
|
23688
24241
|
this.labelContainer = labelContainer;
|
|
@@ -23732,7 +24285,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23732
24285
|
this.orbitPreview.setLayer(SceneRenderer.MAP_LAYER);
|
|
23733
24286
|
this.skyboxAdjuster.set(0, 0, 0);
|
|
23734
24287
|
loadSkybox(this.scene, this.renderer, ctx, () => this.disposed);
|
|
23735
|
-
this.placer = new Placer(ctx, this.bodyObjects, this.positionDiagnostics);
|
|
24288
|
+
this.placer = new Placer(ctx, this.bodyObjects, this.positionDiagnostics, () => this.dateHeld());
|
|
23736
24289
|
ctx.placer = this.placer;
|
|
23737
24290
|
const focusBody = [
|
|
23738
24291
|
ctx.getBody(initialView.id),
|
|
@@ -23872,7 +24425,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
23872
24425
|
return;
|
|
23873
24426
|
}
|
|
23874
24427
|
this.travelPath.setVisible(true);
|
|
23875
|
-
this.travelPath.setClock(this.
|
|
24428
|
+
this.travelPath.setClock(this.sceneJd);
|
|
23876
24429
|
this.travelPath.reposition(center, this.focus.focusTruePos, (id) => this.where(id));
|
|
23877
24430
|
this.travelPath.updateCameraOffset(this.camera.position);
|
|
23878
24431
|
this.reserveTravelLabelSpace();
|
|
@@ -24004,10 +24557,10 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24004
24557
|
return this.focus.focusDurationMs;
|
|
24005
24558
|
}
|
|
24006
24559
|
where(id) {
|
|
24007
|
-
return this.placer.at(id, this.
|
|
24560
|
+
return this.placer.at(id, this.sceneJd);
|
|
24008
24561
|
}
|
|
24009
24562
|
whereBody(body) {
|
|
24010
|
-
return this.placer.place(body, this.
|
|
24563
|
+
return this.placer.place(body, this.sceneJd);
|
|
24011
24564
|
}
|
|
24012
24565
|
repositionBodies() {
|
|
24013
24566
|
const [fx, fy, fz] = this.focus.focusTruePos;
|
|
@@ -24025,7 +24578,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24025
24578
|
if (!line) continue;
|
|
24026
24579
|
const trailBuffer = line.userData.trailBuffer;
|
|
24027
24580
|
if (trailBuffer) {
|
|
24028
|
-
refreshBufferTrail(bo.body, line, trailBuffer, basis, this.
|
|
24581
|
+
refreshBufferTrail(bo.body, line, trailBuffer, basis, this.sceneJd);
|
|
24029
24582
|
continue;
|
|
24030
24583
|
}
|
|
24031
24584
|
const localPositions = line.userData.trailLocalPositions;
|
|
@@ -24040,7 +24593,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24040
24593
|
const nowMs = performance.now();
|
|
24041
24594
|
const frameDtMs = this.lastTickMs ? nowMs - this.lastTickMs : 0;
|
|
24042
24595
|
this.lastTickMs = nowMs;
|
|
24043
|
-
if (!this.holdingCamera) this.cameraUp.update(this.
|
|
24596
|
+
if (!this.holdingCamera) this.cameraUp.update(this.sceneJd);
|
|
24044
24597
|
if (this.firstFrame) {
|
|
24045
24598
|
this.firstFrame = false;
|
|
24046
24599
|
this.controls.target.set(0, 0, 0);
|
|
@@ -24067,7 +24620,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24067
24620
|
this.lastRenderedCamQuat.copy(this.camera.quaternion);
|
|
24068
24621
|
this.applyJdUpdate(true);
|
|
24069
24622
|
this.refreshTravelFocus();
|
|
24070
|
-
this.callbacks.onFrame?.(this.
|
|
24623
|
+
this.callbacks.onFrame?.(this.sceneJd, renderedDtMs);
|
|
24071
24624
|
const controlsSettled = this.holdingCamera ? this.applyHeldCamera() : stepFocusAnimation(this.focus, this.camera, this.controls, () => this.repositionAll(), () => this.pointClouds.rebuildBasis());
|
|
24072
24625
|
this.applyReaderLimits(nowMs);
|
|
24073
24626
|
const focused = this.holdingCamera ? void 0 : this.focusController.current;
|
|
@@ -24090,13 +24643,13 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24090
24643
|
if (this.systemData.hasPendingUnloads() && performance.now() - this.focus.focusStartTime >= this.focus.focusDurationMs) this.systemData.drainPendingUnloads();
|
|
24091
24644
|
const { distance } = this.getCameraState();
|
|
24092
24645
|
this.updateTravelCraftSystem();
|
|
24093
|
-
this.ctx.visibility.updateCamera(distance, this.
|
|
24646
|
+
this.ctx.visibility.updateCamera(distance, this.sceneJd);
|
|
24094
24647
|
this.updateTightFar(distance);
|
|
24095
24648
|
this.systemData.syncToFocus();
|
|
24096
24649
|
this.refreshTravelPath();
|
|
24097
24650
|
this.refreshOrbitPreview();
|
|
24098
24651
|
this.extensions.update({
|
|
24099
|
-
jd: this.
|
|
24652
|
+
jd: this.sceneJd,
|
|
24100
24653
|
basis: this.focus.focusTruePos,
|
|
24101
24654
|
camera: this.camera,
|
|
24102
24655
|
viewportPx: this.viewportH,
|
|
@@ -24107,7 +24660,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24107
24660
|
refreshDeferredTrails(this.bodyObjects, this.focus, this.lastUpdatedJd, this.trailView());
|
|
24108
24661
|
updateRingShaders(this.bodyObjects, this.focus.focusTruePos, sceneSettings().realisticLighting, sceneSettings().overexposeRings, this.camera.position, this.pxPerRad());
|
|
24109
24662
|
updateEclipseUniforms(this.bodyObjects, this.focus.focusTruePos);
|
|
24110
|
-
const atmoState = updateAtmosphereShaders(this.bodyObjects, this.camera.position, sceneSettings().showAtmospheres, sceneSettings().realisticLighting, this.sunIntensityScale, currentAtmosphereConfig(), this.
|
|
24663
|
+
const atmoState = updateAtmosphereShaders(this.bodyObjects, this.camera.position, sceneSettings().showAtmospheres, sceneSettings().realisticLighting, this.sunIntensityScale, currentAtmosphereConfig(), this.sceneJd);
|
|
24111
24664
|
recordAtmospherePerf(renderedDtMs, atmoState.shellProminent);
|
|
24112
24665
|
this.scene.backgroundIntensity = atmoState.skyboxIntensity;
|
|
24113
24666
|
this.applyLayerChrome();
|
|
@@ -24124,7 +24677,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24124
24677
|
for (const light of this.ambientLights) light.intensity = ambient;
|
|
24125
24678
|
updateUserLocationOcclusion(this.userLocationMarker, this.bodyObjects, this.camera);
|
|
24126
24679
|
const focusedIdLod = nomenclatureBodyId(this.focusController.current, this.bodyObjects);
|
|
24127
|
-
updateTextureLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, this.textureLoader, focusedIdLod, this.
|
|
24680
|
+
updateTextureLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, this.textureLoader, focusedIdLod, this.sceneJd, this.appearances, this.invalidate);
|
|
24128
24681
|
updateSphereLOD(this.bodyObjects, this.camera, this.renderer, this.ctx, focusedIdLod);
|
|
24129
24682
|
updateSunShadowLight(this.bodyObjects, this.focus.focusTruePos, this.ctx, this.shadowLight, this.sunPointLight, distance, this._tmpV3, sceneSettings().realisticLighting, this.sunIntensityScale);
|
|
24130
24683
|
this.updateModelMounts();
|
|
@@ -24251,7 +24804,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24251
24804
|
};
|
|
24252
24805
|
if (!seated || !body.orientation) return void 0;
|
|
24253
24806
|
return {
|
|
24254
|
-
invQuat: bodyQuaternion(body.orientation, this.
|
|
24807
|
+
invQuat: bodyQuaternion(body.orientation, this.sceneJd, body.nutPrec).invert(),
|
|
24255
24808
|
seated,
|
|
24256
24809
|
radialKm: (dir) => renderedSurfaceRadialKm(bo, body.data.id, body.data.radiusKm, dir)
|
|
24257
24810
|
};
|
|
@@ -24507,7 +25060,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24507
25060
|
}
|
|
24508
25061
|
applyHeldCamera() {
|
|
24509
25062
|
const pose = this.cameraHold;
|
|
24510
|
-
const resolved = pose && resolvePose(pose, this.ctx, this.
|
|
25063
|
+
const resolved = pose && resolvePose(pose, this.ctx, this.sceneJd);
|
|
24511
25064
|
if (!resolved) return true;
|
|
24512
25065
|
const basis = this.focus.focusTruePos;
|
|
24513
25066
|
this.camera.up.set(resolved.up[0], resolved.up[1], resolved.up[2]).normalize();
|
|
@@ -24540,10 +25093,10 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24540
25093
|
maybeLoadTexture(body) {
|
|
24541
25094
|
const bo = this.bodyObjects.get(body.data.id);
|
|
24542
25095
|
if (!bo) return;
|
|
24543
|
-
loadBodyTexture(bo, this.textureLoader, this.
|
|
25096
|
+
loadBodyTexture(bo, this.textureLoader, this.sceneJd, this.scene, this.renderer.capabilities.maxTextureSize, this.ctx).then(() => {
|
|
24544
25097
|
if (this.focusController.current?.data.id !== body.data.id) return;
|
|
24545
25098
|
if (body.orientation && bo.mesh) {
|
|
24546
|
-
applyOrientation(bo.mesh, body.orientation, this.
|
|
25099
|
+
applyOrientation(bo.mesh, body.orientation, this.sceneJd, body.nutPrec);
|
|
24547
25100
|
this.focusController.reapplyInitialViewIfPending();
|
|
24548
25101
|
}
|
|
24549
25102
|
});
|
|
@@ -24630,8 +25183,32 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24630
25183
|
held = false;
|
|
24631
25184
|
};
|
|
24632
25185
|
}
|
|
25186
|
+
jumpLoads() {
|
|
25187
|
+
const { clock, ctx } = this;
|
|
25188
|
+
if (clock.jumps !== this.seenJumps) {
|
|
25189
|
+
this.seenJumps = clock.jumps;
|
|
25190
|
+
this.jumpJd = clock.jd;
|
|
25191
|
+
ctx.refreshTick(jdToDate(clock.jd));
|
|
25192
|
+
ctx.chebStore?.request(clock.jd);
|
|
25193
|
+
ctx.probeStore?.request(clock.jd);
|
|
25194
|
+
}
|
|
25195
|
+
const jd = this.jumpJd;
|
|
25196
|
+
if (jd === null) return false;
|
|
25197
|
+
if (!ctx.chebStore?.loadingAt(jd) && !ctx.probeStore?.loadingAt(jd)) {
|
|
25198
|
+
this.jumpJd = null;
|
|
25199
|
+
return false;
|
|
25200
|
+
}
|
|
25201
|
+
this.invalidate();
|
|
25202
|
+
return true;
|
|
25203
|
+
}
|
|
25204
|
+
dateHeld() {
|
|
25205
|
+
return !Number.isNaN(this.lastUpdatedJd) && (this.dateHolds > 0 || this.jumpLoads());
|
|
25206
|
+
}
|
|
25207
|
+
get sceneJd() {
|
|
25208
|
+
return this.dateHeld() ? this.lastUpdatedJd : this.clock.jd;
|
|
25209
|
+
}
|
|
24633
25210
|
applyJdUpdate(allowOorRefocus = false) {
|
|
24634
|
-
if (this.dateHolds > 0) return;
|
|
25211
|
+
if (this.dateHolds > 0 || this.jumpLoads()) return;
|
|
24635
25212
|
this.ensureFocusedProbeTarget();
|
|
24636
25213
|
const systemId = this.ctx.visibility.focusedSystemId;
|
|
24637
25214
|
const seatKey = this.focusedSeatConfigKey();
|
|
@@ -24641,7 +25218,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24641
25218
|
this.clock.seeked = false;
|
|
24642
25219
|
return;
|
|
24643
25220
|
}
|
|
24644
|
-
const seeked = this.clock.seeked;
|
|
25221
|
+
const seeked = this.clock.seeked || this.seekAwaitsData;
|
|
24645
25222
|
this.clock.seeked = false;
|
|
24646
25223
|
this.lastUpdatedJd = this.clock.jd;
|
|
24647
25224
|
this.lastUpdatedSystemId = systemId;
|
|
@@ -24668,6 +25245,7 @@ var SceneRenderer = class SceneRenderer {
|
|
|
24668
25245
|
this.pointClouds.maybeRebase();
|
|
24669
25246
|
}
|
|
24670
25247
|
this.syncLandedNomenclature();
|
|
25248
|
+
this.seekAwaitsData = seeked && result.focusLoading;
|
|
24671
25249
|
const losingPlace = result.focusUnplaced && !this.focusWasOutOfRange;
|
|
24672
25250
|
this.focusWasOutOfRange = result.focusUnplaced;
|
|
24673
25251
|
if (allowOorRefocus && seeked && losingPlace && result.anchorId) {
|