import type * as THREE from "three"; import type { AssetCache, Item3D } from "@johnmorrisdotca/kazoku-ui/renderer"; import { type ThreeParts } from "../realistic/textures.ts"; import { type TileSet, type TileSources } from "../realistic/tile-set.ts"; import type { Layer } from "../realistic/tiers.ts"; import type { LightMode } from "../realistic/earth.ts"; import { type Frame, type PlanetName, type Vec3 } from "./ephemeris.ts"; import { type ScaleMode } from "./scale.ts"; import type { SatelliteRef } from "../satellites/types.ts"; import { type RideFace, type RideLook, type RideModelSize, type RideViewpoint } from "./ride.ts"; import { type LightFade } from "../realistic/light-fade.ts"; import type { SkyFrame } from "../sky/frame.ts"; import type { PlaceView } from "../markers/places.ts"; import { type PlaceSpriteData } from "../markers/place-sprites.ts"; import type { CountryFrame } from "../country-names/geometry.ts"; import type { GeoPoint } from "../types.ts"; import type { PathHit } from "./paths.ts"; import { type SatelliteScreen, type SatelliteState } from "./satellite-layer.ts"; import { type BodyLabelState } from "../realistic/body-label.ts"; import { type MeteorState } from "../meteors/layer.ts"; import { type BorderState } from "../borders/layer.ts"; import { type GridState } from "../grid/layer.ts"; import { type BeamState } from "../beam/layer.ts"; import { type RouteState } from "../route/layer.ts"; type Three = ThreeParts; /** How big a satellite seen from outside by a ride from itself is drawn: its radius is this share of the height of the view (a model about a fifth of the height across; the satellite is a few metres, so this is not to scale). */ /** A number easing from one value to another (the model's share, its seat, how much of it is there): `cur` is where it is. */ export interface FrameEase { cur: number; from: number; to: number; start: number; } /** How much of the view's shorter side the Earth's disc fills at zoom 1 when the page does not say: 0.9, so the atmosphere's glow (8% beyond the disc) is inside the view and never cut. */ export declare const DEFAULT_EARTH_SIZE = 0.9; /** * The camera's distance, in radii of the body it looks at, at which the body's disc fills `size` of the view's shorter side (`aspect` is the view's width over its height; the field of view is vertical). * Pure; `size` may be above 1 for a body drawn larger than the view. * * @param size - Share of the shorter side the disc's diameter fills. * @param aspect - Width over height of the view. * @returns The distance, in the body's radii. * * @example * ```ts * import { fitDistance } from "@johnmorrisdotca/chikyu-earth/space"; * * console.log(fitDistance(0.9, 16 / 9).toFixed(2)); * ``` */ export declare function fitDistance(size: number, aspect: number): number; /** * The camera at the centre of the Earth's view: `ground` is held over a place on the ground (the Earth keeps still, the clock moves the light), `space` is held in the stars at the direction the place was in when it was set (the Earth turns beneath it), `sun` is held on the Sun's side (the day and night line keeps its place while the Earth turns). * * @example * ```ts no-run * import type { CameraMode, ChikyuSpace } from "@johnmorrisdotca/chikyu-earth/space/element"; * * const hold = (space: ChikyuSpace, mode: CameraMode): void => { * space.camera = mode; * }; * console.log(typeof hold); * ``` */ export type CameraMode = "ground" | "space" | "sun"; /** A moon that has been out of sight this long is let go (its pictures too). */ export declare const MOON_KEEP_MS = 4000; /** * A body of the Sun, the Moon and the planets is made by the painter (and so gets its mapped picture) only when it is in the view and at least this many CSS pixels across its radius; a smaller one, or one out of the view, is a plain sphere of the right colour (the Moon, the view's own Moon). * It is let go (its pictures with it) once it has been smaller than `BODY_LET_GO_PX`, or out of the view, for `BODY_KEEP_MS`. */ export declare const BODY_MAKE_PX = 6; export declare const BODY_LET_GO_PX = 3; export declare const BODY_KEEP_MS = 4000; /** For a reader who asked for reduced motion, the pictures that arrive within this time of one that was drawn ask for one draw between them (none at all while the clock steps). */ export declare const ARRIVAL_GATHER_MS = 1000; /** * The eleven bodies the space view places, names and lets a reader choose: the Earth, the Sun, the Moon, the seven other planets and Pluto. * * @example * ```ts no-run * import { BODY_NAMES } from "@johnmorrisdotca/chikyu-earth/space/element"; * * console.log(BODY_NAMES.join(", ")); * ``` */ export declare const BODY_NAMES: readonly ["Earth", "Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto"]; /** * The name of one of the bodies in `BODY_NAMES`. * * @example * ```ts no-run * import { BODY_NAMES, type BodyName } from "@johnmorrisdotca/chikyu-earth/space/element"; * * const mars: BodyName = "Mars"; * console.log(BODY_NAMES.includes(mars)); * ``` */ export type BodyName = (typeof BODY_NAMES)[number]; /** * What a camera can ride along with: the name of one of the bodies, or a satellite of a group (`{ group, index }`). * * @example * ```ts no-run * import type { RideTarget } from "@johnmorrisdotca/chikyu-earth/space/element"; * * const targets: RideTarget[] = ["Moon", { group: "stations", index: 0 }]; * console.log(targets.length); * ``` */ export type RideTarget = BodyName | SatelliteRef; export interface RideState { target: RideTarget; /** `from`: the camera is at the body, looking at the Earth. `follow`: the camera keeps a fixed distance from it, looking at it. `above` (a satellite): the camera is on the line from the Earth's centre through the satellite, looking straight down. */ view: "from" | "follow" | "above"; look: RideLook; /** The facing the yaw and tilt look round from, or null. */ face: RideFace | null; /** For `follow` a satellite: how far the camera keeps, in kilometres. */ distanceKm: number; /** `outside`: a ridden satellite is in the picture (the camera a little behind and above it in `from`, a drawing of it where the page has no model); `inside`: as a ride was before, the camera at the satellite and no drawing of it where the page has no model. */ viewpoint: RideViewpoint; /** How the model of a satellite seen from above is sized as the camera rises: `distance` shrinks it and hands over to the mark, `fixed` keeps one share of the picture. */ modelSize: RideModelSize; } /** The camera as the last draw left it, for the next one to start from when the ride changes. */ export interface Pose { p: Vec3; q: [number, number, number, number]; fov: number; valid: boolean; } export interface SpaceModel { /** Moment shown, milliseconds since the epoch. */ time: number; mode: ScaleMode; /** The shared controls' yaw, tilt and zoom. */ view: { yaw: number; tilt: number; zoom: number; }; /** The home place: the camera starts above it. */ centre: { lon: number; lat: number; }; /** Which way the camera is held (see `CameraMode`). */ camera: CameraMode; /** How many times its real thickness the air is drawn, 1 to 5 (`AIR_THICKNESS_DEFAULT` until the element says otherwise); read at every draw. */ airThickness: number; /** Degrees an hour the cloud picture is made to move eastward against the ground: 0, the default (it is one snapshot, not a forecast). */ cloudDrift: number; /** For the `space` camera: the Earth's turn against the stars (degrees) at the moment the camera was set, so it stays in the stars while the Earth turns beneath; null until the next draw sets it. */ cameraSpin: number | null; /** How much of the Earth's turn the space camera takes: 0 holds the stars, 1 turns with the Earth. */ follow: number; /** The share of the view's shorter side the Earth's disc fills at zoom 1, and where its centre is in the view (0 to 1 across and down; 0.5, 0.5 is the middle). */ earth: { size: number; x: number; y: number; }; /** The camera's distance at zoom 1 in radii of the body it looks at, as the last draw worked it out from the view's shape and `earth.size`. */ fit: number; orbits: boolean; labels: boolean; /** Milliseconds of game time one second of wall time covers (the ephemeris is looked up once per such span). */ stepMs: number; /** The body the camera looks at, and (in `zoom`) fills the view with at 1x. */ focus: BodyName; /** How the Earth is lit (see `LightMode`); the Sun, the Moon and the planets keep their true places either way. */ light: LightMode; /** The cross-fade of the Earth's night lights between the true light and `lit`, stepped at each draw (src/realistic/light-fade.ts). */ lightFade: LightFade; /** How the painter draws the Sun, the planets and their moons (one of its `looks`, or `plain` for the sphere in the right colour); empty for its first. */ bodies: string; /** Filled at each draw. */ drawn: { frame: Frame; distance: number; radiusPx: number; } | null; /** What the painter has made now: the Sun and planets with a drawing, the moons shown and the moons held (shown, or gone from the view for less than `MOON_KEEP_MS`), whether the Moon is the painter's, and the bodies with finer map tiles on them (set at each draw). */ painted: { bodies: number; moons: number; held: number; moon: boolean; tiles: number; mapped: number; pending: number; }; /** How many times astronomy-engine has been asked for a frame. */ lookups: number; /** Game seconds a second (the clock's speed). */ speed: number; /** The satellites shown, the one chosen, and what the last draw did with them. */ satellites: SatelliteState; /** Meteors seen from space, on the Earth (src/meteors/layer.ts): off unless the element switches them on. */ meteors: MeteorState; /** The name written on the Earth (src/realistic/body-label.ts): off unless the element sets it. */ bodyLabel: BodyLabelState; /** The borders of the countries and of their regions, on the Earth (src/borders/layer.ts): off unless the element switches them on. */ borders: BorderState; /** The meridians and parallels on the Earth (src/grid/layer.ts): off unless the element switches them on. */ grid: GridState; /** The beam of light from the chosen satellite to the ground below it (src/beam/layer.ts): off unless the element switches it on. */ beam: BeamState; /** A route between two places, on the Earth (src/route/layer.ts): off unless the element sets it. */ route: RouteState; /** The body or satellite the camera rides along with, or null. */ ride: RideState | null; /** Set at each draw: the target of the ride could not be placed (a satellite whose elements are too old, or that is gone), so the ordinary camera was used. */ rideLost: boolean; /** A camera move in progress from where the last draw left it: when it began (ms of the page's clock), how long it takes. */ transition: { start: number; duration: number; from: { p: Vec3; q: [number, number, number, number]; fov: number; }; } | null; /** The shared controls' view as the last draw used it, and how tall the view was (CSS pixels): `setView` skips a draw for a turn that moves nothing by half a pixel. */ drawnView: { yaw: number; tilt: number; zoom: number; } | null; /** The point of the ground the view would look at with yaw and tilt 0, as of the last draw (the middle of the view is this less the yaw and tilt), or null while the camera rides or looks at another body. */ lookBase: GeoPoint | null; heightPx: number; pose: Pose; /** Pixels a second that riding makes things move on the screen at 1x clock speed (0 when not riding): the draw rate follows it. */ rideMotion: number; /** The heading of a riding camera in degrees, added up change by change from the controls' wrapped yaw (`rideTurnDegrees`): a drag the whole way round is a turn the whole way round, and a change of zoom swings nothing. */ rideTurn: number; /** How far the riding camera is from the Earth's centre in Earth radii as of the last draw, or null when none rides: the range of tilt that does something follows it. */ rideDistance: number | null; /** * The framing of the ridden satellite's model (MODULES-1316): the radius as a share of the picture's height and where its middle sits (a share of the height from the top) as the page set them (`null`: the ride's own), the eased values the last * draw used, how long a change takes (0 for reduced motion), and the ground the picture spans below the satellite as of the last draw. */ rideFrame: { share: number | null; seat: number | null; fresh: boolean; ms: number; moving: boolean; share_: FrameEase; seat_: FrameEase; weight_: FrameEase; groundKm: { across: number; tall: number; } | null; }; /** The page's path under a point of the view (CSS pixels), within a radius (`paths`, and the chosen satellite's own orbit), if a draw has been made and one is there. */ pathAt: ((x: number, y: number, radius: number) => Omit | null) | null; /** The satellite at a point of the view (CSS pixels), within a radius, if a draw has been made and one is there. */ pick: ((x: number, y: number, radius: number) => SatelliteRef | null) | null; /** The followed satellite's model, when there is one drawn: where its middle is on the screen (CSS pixels), as of the last draw. */ followModel: { x: number; y: number; shown: boolean; parts: number; share: number; seat: number; weight: number; radiusPx: number; box: { left: number; top: number; right: number; bottom: number; }; axes: { x: Vec3; y: Vec3; z: Vec3; }; } | null; /** The map tiles laid over the Earth while the scene is built: what they are doing, for a page or a test. */ tiles: TileSet | null; /** Where a satellite of a shown group is on the screen as of the last draw (`placeSatellite`), or `null`. */ placeSatellite: ((ref: SatelliteRef) => SatelliteScreen | null) | null; ratio(): number; } export declare const newSpaceModel: () => SpaceModel; /** Where a body is on the screen: its centre and its apparent radius, in CSS pixels in the box. */ export interface BodyPlace { x: number; y: number; r: number; } /** * One body as drawn by a painter: an object of radius 1 at the origin, which the scene places, scales and (for the Sun) turns to face the camera. * * @example * ```ts no-run * import type { BodyDrawing } from "@johnmorrisdotca/chikyu-earth/space"; * * const drawing = { object: undefined as never, dispose: () => undefined } satisfies BodyDrawing; * console.log(typeof drawing.dispose); * ``` */ export interface BodyDrawing { object: THREE.Object3D; /** Told at each draw the unit direction from the body to the Sun, in scene axes (x to the vernal equinox, y north). */ light?(toSun: Vec3): void; /** * Told at each draw, after the object is placed and scaled: the direction to the Sun, the moment shown, how many CSS pixels across its radius the body is (a sharper picture is worth fetching only for a big one), and the * camera with the size of the view in CSS pixels (`view`), so a body big in the view can lay finer map tiles over the part of it that is seen, and a glow can face the camera. */ update?(info: { toSun: Vec3; ms: number; radiusPx: number; view?: BodyView; }): void; /** What the window of finer map tiles on the body is doing (`shown` is the level on it now, or `null`), for a drawing that has one. */ readonly tiles?: { shown: number | null; } | undefined; /** True to have the scene turn the object to face the camera (a disc or a glow). */ billboard?: boolean; dispose(): void; } /** * The camera that draws the bodies and the size of its view in CSS pixels, as the space view tells a drawing at each draw. The camera's matrices are current. * * @example * ```ts no-run * import type { BodyView } from "@johnmorrisdotca/chikyu-earth/space"; * * const across = (view: BodyView): number => view.width; * console.log(typeof across); * ``` */ export interface BodyView { camera: THREE.Camera; width: number; height: number; } /** * The seam for drawing a followed satellite as a 3D model: the package ships no model, the page supplies them. `has` says whether there is a model for a satellite (by NORAD number: `25544` is the ISS); `make` makes it as a drawing of radius 1 * (its middle at the origin, its long axis along +x, its top, the side that faces space, along +z, and its left along +y), at once or later (a promise); the view places it where the satellite is, points it the way it flies, sizes it to read and * gives it the Sun's direction (`light`, `update`). `null` from `make` is no model: the satellite stays a dot. * * @example * ```ts no-run * import type { SatellitePainter } from "@johnmorrisdotca/chikyu-earth/space"; * * const painter: SatellitePainter = { has: (norad) => norad === 25544, make: async () => null }; * console.log(painter.has(25544)); * ``` */ export interface SatellitePainter { has(noradId: number): boolean; make(three: Three, noradId: number, context: PaintContext): BodyDrawing | null | Promise; } /** * What the scene tells a painter when it makes a body. * * @example * ```ts no-run * import type { PaintContext } from "@johnmorrisdotca/chikyu-earth/space"; * * const context: PaintContext = { look: "painted", invalidate: () => undefined, ratio: 2, maxTexture: 2048 }; * console.log(context.look); * ``` */ export interface PaintContext { /** The look asked for: one of the painter's `looks`, or empty for its first. */ look: string; /** Ask for a new draw (a picture has arrived). */ invalidate(): void; /** Device pixels to a CSS pixel. */ ratio: number; /** The widest picture, in pixels, the GPU should be given. */ maxTexture: number; /** True to draw every body that has a surface picture from it, in any look but `plain`: the space view asks for this, so a planet or the Moon is never shown shaded where it has a real map. */ mapped?: boolean; } /** * One moon of a planet at a moment: its name, where it is from the planet's centre and how big it is, both in radii of the planet, in scene axes. * * @example * ```ts no-run * import type { BodyMoon } from "@johnmorrisdotca/chikyu-earth/space"; * * const io: BodyMoon = { name: "Io", offset: [5.9, 0, 0], radius: 0.0255 }; * console.log(io.name); * ``` */ export interface BodyMoon { name: string; offset: Readonly; radius: number; } /** * The seam for drawing the Sun, the planets and their moons. The space view places them, sizes them, lights them and labels them; a painter only makes what is drawn at * each place. The default (`plainPainter`) is a sphere in the body's colour, and a glow for the Sun. Another package can supply better looking ones: * `@johnmorrisdotca/wakusei-planets/painter` draws the real Sun, planets, Pluto and moons. * `make` may return a drawing at once or later (a promise); a drawing may update itself and ask for a redraw when a picture arrives. `moons` is optional: the moons the painter can * draw for a planet at a moment, which the view shows round the planet when it is near enough to see them. `looks` names what `context.look` may be. * * @example * ```ts no-run * import { plainPainter, type BodyPainter } from "@johnmorrisdotca/chikyu-earth/space"; * * const painter: BodyPainter = { make: (three, name) => plainPainter.make(three, name) }; * console.log(typeof painter.make); * ``` */ export interface BodyPainter { make(three: ThreeParts, name: string, context?: PaintContext): BodyDrawing | Promise; moons?(planet: PlanetName, ms: number): readonly BodyMoon[]; looks?: readonly string[]; /** Whose pictures the painter draws, in English and Japanese: the space view writes it under the picture while the painter draws the bodies. */ credit?: { en: string; ja: string; }; } export interface SpaceDeps { assets: AssetCache; textureBase: () => string; cap: () => number; invalidate: () => void; onError?: (error: unknown) => void; onTier?: (layer: Layer, width: number) => void; /** How much the air costs: `full` (12 samples along the ray and 4 toward the Sun) or `light` (6 and 2, for a small or slow device). Read once, when the scene is built. */ air?: () => "full" | "light"; /** Where each body is on the screen, in CSS pixels in the box, or null when it is behind the camera. Called at each draw. */ onLabels?: (places: Record) => void; /** Where the sky falls on the screen at this draw, and the bodies that cover it (the names of the stars and the constellation lines are laid over it). Called at each draw. */ onSky?: (frame: SkyFrame) => void; /** Where points of the Earth's surface fall on the screen at this draw, for the places drawn on it. Called at each draw. */ onPlaces?: (view: PlaceView) => void; /** The rings the places' layer wants drawn in one draw call, read right after `onPlaces`. */ placeSprites?: () => PlaceSpriteData | null; /** Told once, when the rings of the places that are not markers of their own were first wanted: whether the picture can draw them (false when the three.js parts handed over lack `Points`). */ onPlaceRings?: (drawn: boolean) => void; /** Where the Earth is on the screen (its disc) and how to find a place on it, for the names of cities and countries. Called at each draw, with `null` while no Earth is shown. */ onCities?: (frame: CountryFrame | null) => void; /** The name written on the Earth could not be made. */ onLabelError?: (error: unknown) => void; /** Where the chosen satellite is on the screen, or null. Called at each draw while a satellite is chosen (and once when it is let go). */ onSelected?: (place: { x: number; y: number; } | null) => void; /** Where the satellite under the pointer is, or null. Called at each draw while one is under the pointer (and once when it is let go). */ onHover?: (place: { x: number; y: number; visible: boolean; } | null) => void; /** Where each member of a labelled group is on the screen at this draw (the groups of `model.satellites.marked`); an empty list once when the last group is let go. */ onSatelliteMarks?: (marks: { ref: SatelliteRef; x: number; y: number; visible: boolean; heightKm: number; occluded: boolean; }[]) => void; /** Called at the end of a draw while the camera is moving from one ride to another, so the next frame is asked for (at the page's frame cap). */ onMoving?: () => void; /** The target of the ride could not be placed at this draw. */ onRideLost?: () => void; /** The satellites could not be drawn (the three.js parts handed over lack `Points`). */ onSatellitesError?: (error: unknown) => void; /** The painted satellite trail. Read at every draw; nothing is uploaded while `step` returns false. */ paint?: { data(): Uint8Array; width: number; height: number; step(ms: number): boolean; span(): number; on(): boolean; }; /** How the Sun, the planets and their moons are drawn; left out, plain spheres in the right colours (see `BodyPainter`). */ painter?: BodyPainter; /** The models of large satellites, for a followed satellite (see `SatellitePainter`); none, and a satellite is a dot. */ satellitePainter?: () => SatellitePainter | undefined; /** The painter could not make a body (the three.js parts handed over lack something it needs); the body is drawn plain instead. */ onPainterError?: (error: unknown) => void; /** A body the painter drew changed (a picture arrived, a window of its tiles changed, a body was made): the painter's credit, which names the pictures on the screen now, may have changed. Told before the redraw is asked for. */ onBodies?: () => void; /** The map source of each layer now (the folder of the `tiles` attribute), or none: the Earth then keeps the package's own pictures. Read at every draw. */ sources?: () => TileSources; /** Told once when the tile set has been read: the width in pixels of the finest level of each of its layers. */ onTiles?: (layers: Record) => void; /** Told at the end of a draw, after the map tiles are placed, so a credit line can follow which tiles are on screen. */ onTilesDrawn?: () => void; } /** * The default look: a sphere in the planet's colour, lit by the Sun, and for the Sun a glowing disc. * * @example * ```ts no-run * import { plainPainter } from "@johnmorrisdotca/chikyu-earth/space"; * * console.log(typeof plainPainter.make); * ``` */ export declare const plainPainter: { make(three: ThreeParts, name: string): BodyDrawing; } & BodyPainter; export declare function createSpaceItem(model: SpaceModel, deps: SpaceDeps): Item3D; export {};