import type * as THREE from "three"; import type { SatelliteRef, Satellites } from "../satellites/types.ts"; import type { ThreeParts } from "../realistic/textures.ts"; import { type SatelliteFocus } from "./satellite-focus.ts"; import { type PathHit, type PathInfo, type PathRequest, type PathStyle } from "./paths.ts"; type Three = ThreeParts; /** How a group looks: the colour of its dots and their size in CSS pixels, and whether every orbit is drawn (only for a small group). */ export interface SatelliteStyle { color: string; size: number; orbits: boolean; } export interface SatelliteGroupState extends SatelliteStyle { satellites: Satellites; visible: boolean; /** How many of this group the last draw placed. 0 before the first draw and when the group is not drawn. */ placed?: number; } /** The part of the space view's model the satellites read and write. */ export interface SatelliteState { groups: Map; selected: SatelliteRef | null; /** The groups whose members are ringed and named (`labelSatellites`): the scene tells the page where each is, at every draw. */ marked: Set; /** Satellites ringed besides a whole group: the nearest ones a page asked to mark. Empty unless set. */ pinned: SatelliteRef[]; /** Written at each draw: satellites placed, whether the clock is too fast for the groups, and the milliseconds the last update took. */ /** Colour each dot by its orbit class. Off by default: a group keeps the one colour it was given. */ orbitTint: boolean; /** One group shown on its own. `null` leaves every group as the page set it. */ focus: SatelliteFocus | null; /** * The satellite the pointer rests on, and the faint orbit drawn for it. `fade` is 1 while the pointer rests and falls to 0 after it leaves. * `enabled` is off when the page turns the faint orbit off. The path is computed once per satellite, from the elements already loaded. */ hover: { ref: SatelliteRef | null; fade: number; enabled: boolean; color: string; opacity: number; }; /** * The extra paths a page asked for (`setPaths`), the look table (`setPathStyles`: the page's entries over the built-in ones), whether the chosen satellite's own orbit is drawn (`selected-path`), and a number that goes up at each change so the layer * rewrites its buffers only when something changed. `info` is what the last draw resolved and kept: the paths drawn, in order; `dropped` how many the cap left out and `unresolved` how many matched no satellite. */ paths: { requests: PathRequest[]; overrides: Record | null>; selectedOn: boolean; version: number; info: PathInfo[]; dropped: number; unresolved: number; }; /** Set at each draw by the view: the satellite a ride is on, whose own orbit is drawn in the `selected` look while `selected-path` is on. `null` when none rides a satellite. */ ridden: SatelliteRef | null; /** * The pixel ceiling of a streak's length on the screen above the interpolation limit, in CSS pixels at the Earth's own distance (`satellite-streak-max`). A streak is also never longer than {@link STREAK_REACH} Earth radii, so it shrinks with the Earth. * 0 draws the whole stretch one frame spans, however long that is. */ streakMax: number; /** Set at each draw by the view: the satellite a ride shows as its model while the model is big enough to read. Its ring (the chosen marker) and name are left off while this is so; its orbit line stays. Clears with the ride, or when the model has handed over to the mark. */ modelOver: SatelliteRef | null; drawn: { placed: number; segments: number; tooFast: boolean; dotScale: number; longestStreakPx: number; streakCapPx: number; weight: number; fade: number; earthPx: number; updateMs: number; maxUpdateMs: number; updates: number; totalMs: number; selectedRing: boolean; hoverPath: boolean; hoverOpacity: number; selectedOpacity: number; orbitLines: number; pathsDrawn: number; pathDraws: number; estimated: number; }; } /** * The longest a streak may be along its orbit, in Earth radii: 0.025, about 160 km, which is what a low satellite covers in a frame at 10 minutes a second (150 km). On the screen that is a share of the Earth's drawn radius, so a streak * is a few millimetres when the Earth fills the view, half as long when the Earth is half as large, and a dot when the Earth is tiny. A cap of so many pixels alone would leave the same length on a small Earth, where it looks long. */ export declare const STREAK_REACH = 0.025; /** The Earth's drawn radius, in CSS pixels, below which the satellites' dark rims and streaks are faded out (no weight at 40) and from which they have their whole weight (150). */ export declare const WEIGHT_FROM = 40; export declare const WEIGHT_FULL = 150; /** How much of its weight a mark has at an Earth `radiusPx` on the screen, from 0 to 1: a smooth step from {@link WEIGHT_FROM} to {@link WEIGHT_FULL}. Many small dark marks on a small Earth would blacken it. */ export declare function markWeight(radiusPx: number): number; /** * The share of the near side of a small Earth that the marks may cover at full strength before they are drawn fainter in proportion: a crowd of marks on a small Earth is a haze over it, not a set of satellites. Dots are lighter ink * than streaks are (a dot is opaque and a streak a pixel wide), so they get the smaller share. */ export declare const CROWD_COVER_DOTS = 0.06; export declare const CROWD_COVER_STREAKS = 0.35; /** * How strongly each mark is drawn so a crowd does not wash a small Earth, from 0 to 1: the share of the near side they would cover (`marks`, half of them on this side, each `area` square pixels, over the disc of an Earth `radiusPx` in radius), * and `limit` over that when it is more. With `byWeight` (the default, for dots) it acts only as the Earth gets small (see {@link markWeight}): none at all from 150 pixels, so a whole Earth is drawn as it was however many satellites there are. Without it (streaks) the limit holds at every size: a streak is as wide as it is long, so the same number of satellites covers a half Earth twice over, and 16,000 of them leave a small Earth no more washed than a whole one. */ export declare function crowdFade(marks: number, area: number, radiusPx: number, limit: number, byWeight?: boolean): number; /** The longest a streak is by default: the stretch a low satellite covers in one frame at 10 minutes a second, on a whole Earth with a radius of about 300 pixels: a few millimetres on the screen. */ export declare const DEFAULT_STREAK_MAX = 8; /** Read `satellite-streak-max`: a number of CSS pixels, `off` or 0 for no cap, and the default for nothing, a negative number or anything else. */ export declare function readStreakMax(value: string | null): number; /** The longest streak, in CSS pixels, among the first `count` of `segments` (6 numbers each), with the Earth's radius `earthPx` on the screen: a length in Earth radii times the radius. */ export declare function longestStreak(segments: Float32Array, count: number, earthPx: number): number; export declare const newSatelliteState: () => SatelliteState; /** The most orbits a group may draw all at once, one line each. */ export declare const MAX_ORBIT_GROUP = 40; /** How bright the chosen satellite's orbit is. The orbit under the pointer is about a third of this, so the two are told apart at a glance. */ export declare const SELECTED_PATH_OPACITY: number; /** The orbit under the pointer, before it fades. A page may set another with `--chikyu-hover-path-opacity`. */ export declare const HOVER_PATH_OPACITY: number; /** How long the faint orbit takes to go after the pointer leaves, in milliseconds. Reduced motion takes it away at once. */ export declare const HOVER_FADE_MS = 180; /** * How much of the faint orbit is left `elapsedMs` into its fade, from `from` (1 while the pointer rests) down to 0. * * @param elapsedMs - Milliseconds since the pointer left. * @param from - The opacity share when the fade began, 0 to 1. * @returns The share still drawn, 0 when the fade is over. */ export declare const fadeLeft: (elapsedMs: number, from: number) => number; /** How long, in milliseconds of wall time, a satellite's place is kept before SGP4 places it again when the clock is too fast to interpolate: about once a second. */ export declare const PIN_PERIOD_MS = 1000; /** * How many satellites of a group to pin in a frame that comes `elapsedMs` after the last one, so that each is pinned again about once every {@link PIN_PERIOD_MS} and no frame that comes at a lively pace pins them all: a share of the group in * proportion to the time since the last frame (a 60 Hz frame pins about a sixtieth, a 30 Hz one a thirtieth), at least one. A frame that comes a second or more after the last one pins the whole group, as one always did: a picture that * is drawn that seldom has no frames for a single batch to hitch. * * @param count - How many satellites the group has. * @param elapsedMs - Wall-clock milliseconds since the last frame that pinned a slice. * @returns How many to pin now, from 0 (an empty group) to `count`. Internal: not exported from the package. */ export declare const pinSliceSize: (count: number, elapsedMs: number) => number; /** * The slice of a group to pin in this frame: up to two runs of satellites as `[first, count]` (the second when the slice reaches the end and goes on from the start), and where the next slice begins. * * @param count - How many satellites the group has. * @param next - Where the last slice ended (the first satellite of this one). * @param elapsedMs - Wall-clock milliseconds since the last frame that pinned a slice. * @returns The runs to pin and the start of the next slice. Internal: not exported from the package. */ export declare function pinPlan(count: number, next: number, elapsedMs: number): { runs: [number, number][]; next: number; }; /** An edge of the picture a satellite lies beyond. `null` when its place is inside the box. */ export type ScreenEdge = "top" | "right" | "bottom" | "left"; /** * Which edge of a box a point lies beyond. A corner goes to the edge it passes by more. Inside the box, `null`. * * @param x - CSS pixels from the left. * @param y - CSS pixels from the top. * @param width - The box width. * @param height - The box height. * @returns The edge, or `null` when the point is inside. */ export declare function screenEdge(x: number, y: number, width: number, height: number): ScreenEdge | null; /** * Which edge a point lies beyond, from the camera's own axes: `x` to the right, `y` up. Used when the point is behind the camera, whose picture would otherwise mirror it. * * @param x - To the camera's right. * @param y - Up, in the camera's axes. * @returns The edge. */ export declare function edgeBeyondCamera(x: number, y: number): ScreenEdge; /** * Whether the Earth hides a satellite. The Earth is the unit sphere at the origin. True when the line from the camera to the satellite meets that sphere before it arrives. A graze at the limb is not hidden (the line only touches the sphere), and neither is a satellite in front of the Earth. This is not the test for a satellite behind the camera. * * @param camera - The camera, in the same units as the sphere (Earth radii). * @param satellite - The satellite, in those units. * @returns True when the globe is in the way. * * @example * ```ts * import { earthHides } from "@johnmorrisdotca/chikyu-earth/space"; * * const camera = { x: 0, y: 0, z: 4 }; * console.log(earthHides(camera, { x: 0, y: 0, z: 1.2 })); * console.log(earthHides(camera, { x: 0, y: 0, z: -1.2 })); * ``` */ export declare function earthHides(camera: { x: number; y: number; z: number; }, satellite: { x: number; y: number; z: number; }): boolean; /** * Where a satellite is on the screen now: in CSS pixels from the view's top left (which may lie outside the box), whether it can be seen, which edge it lies beyond, whether it is behind the camera, whether the Earth hides it, and how high it is. * * @example * ```ts * import type { SatelliteScreen } from "@johnmorrisdotca/chikyu-earth/space"; * * const seen = (place: SatelliteScreen | null): boolean => place !== null && place.visible; * console.log(typeof seen); * ``` */ export interface SatelliteScreen { x: number; y: number; /** Inside the box, in front of the camera and not hidden by the Earth. */ visible: boolean; /** Height above the mean Earth radius, in kilometres, at the moment drawn. */ heightKm: number; /** The edge the satellite lies beyond, or `null` when its place is inside the box. Behind the camera, the edge is the way it lies in the camera's own axes, not the mirrored picture. */ edge: ScreenEdge | null; /** The satellite is behind the camera. The picture would put it on the opposite side, so `edge` is taken from the camera's own axes. */ behind: boolean; /** The Earth hides the satellite: the line from the camera meets the globe before the satellite. False at the limb, in front, and when the satellite is only behind the camera. */ occluded: boolean; /** Its place is an estimate (`estimate-far`): the moment shown is beyond `maxAgeDays` from its elements. */ estimated: boolean; } export interface SatelliteLayer { /** Where one satellite of a shown group is on the screen as of the last update, or `null` when its group is not shown or it is not placed (its elements are too old). A satellite outside the picture or behind the camera is still returned, with `visible` false. */ placeOf(ref: SatelliteRef, camera: THREE.Camera, size: { width: number; height: number; }): SatelliteScreen | null; /** Place every shown group for `model.time` and the chosen satellite exactly. `earthPx` is the Earth's radius on the screen, so a dot grows as the view zooms in. */ update(time: number, speed: number, earthPx?: number, size?: { width: number; height: number; }): void; /** Set the dot size from the Earth's radius on the screen for this frame, after the camera is known. */ resize(earthPx: number): void; /** The satellite nearest to a point of the view (CSS pixels) within `radius`, if one is in front of the Earth. */ pick(x: number, y: number, camera: THREE.Camera, size: { width: number; height: number; }, radius: number): SatelliteRef | null; /** The path nearest to a point of the view (CSS pixels) within `radius`, if one is drawn there and not hidden by the Earth: the extra paths and the chosen satellite's own. */ pathAt(x: number, y: number, camera: THREE.Camera, size: { width: number; height: number; }, radius: number): Omit | null; /** Where the chosen satellite is in the view, if it is. */ place(camera: THREE.Camera, size: { width: number; height: number; }): { x: number; y: number; } | null; /** The chosen satellite's position and velocity now (Earth radii, km/s), or false. */ exact(ref: SatelliteRef, time: number, out: Float64Array): boolean; dispose(): void; } export declare function createSatelliteLayer(three: Three, scene: THREE.Object3D, state: SatelliteState, ratio: () => number): SatelliteLayer; export {};