@johnmorrisdotca/chikyu-earth 0.5.0 → 0.7.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +172 -27
  4. package/dist/countries.d.ts +11 -0
  5. package/dist/countries.js +11 -0
  6. package/dist/element.d.ts +18 -0
  7. package/dist/element.js +15 -2
  8. package/dist/globe.d.ts +15 -1
  9. package/dist/globe.js +15 -1
  10. package/dist/index.d.ts +12 -1
  11. package/dist/index.js +12 -1
  12. package/dist/markers/cluster.d.ts +27 -0
  13. package/dist/markers/cluster.js +16 -0
  14. package/dist/markers/labels.d.ts +40 -0
  15. package/dist/markers/labels.js +18 -0
  16. package/dist/markers/layer.d.ts +80 -0
  17. package/dist/markers/layer.js +21 -0
  18. package/dist/markers/project.d.ts +63 -2
  19. package/dist/markers/project.js +52 -2
  20. package/dist/markers/types.d.ts +48 -3
  21. package/dist/realistic/earth.d.ts +30 -5
  22. package/dist/realistic/earth.js +41 -134
  23. package/dist/realistic/element.d.ts +47 -7
  24. package/dist/realistic/element.js +48 -44
  25. package/dist/realistic/gate.d.ts +45 -1
  26. package/dist/realistic/gate.js +33 -1
  27. package/dist/realistic/shaders.d.ts +9 -4
  28. package/dist/realistic/shaders.js +54 -9
  29. package/dist/realistic/shared.d.ts +23 -0
  30. package/dist/realistic/shared.js +51 -0
  31. package/dist/realistic/sun.d.ts +40 -3
  32. package/dist/realistic/sun.js +40 -3
  33. package/dist/realistic/textures.d.ts +40 -0
  34. package/dist/realistic/textures.js +141 -0
  35. package/dist/realistic/tiers.d.ts +80 -6
  36. package/dist/realistic/tiers.js +69 -5
  37. package/dist/rotate.d.ts +59 -4
  38. package/dist/rotate.js +59 -4
  39. package/dist/space/element.d.ts +134 -0
  40. package/dist/space/element.js +394 -0
  41. package/dist/space/ephemeris.d.ts +151 -0
  42. package/dist/space/ephemeris.js +162 -0
  43. package/dist/space/index.d.ts +3 -0
  44. package/dist/space/index.js +5 -0
  45. package/dist/space/scale.d.ts +124 -0
  46. package/dist/space/scale.js +141 -0
  47. package/dist/space/scene.d.ts +131 -0
  48. package/dist/space/scene.js +295 -0
  49. package/dist/styles.d.ts +65 -2
  50. package/dist/styles.js +43 -2
  51. package/dist/textures/moon-1024.webp +0 -0
  52. package/dist/textures/moon-2048.webp +0 -0
  53. package/dist/types.d.ts +55 -5
  54. package/dist/view.d.ts +63 -4
  55. package/dist/view.js +52 -4
  56. package/package.json +55 -8
@@ -0,0 +1,131 @@
1
+ import type * as THREE from "three";
2
+ import type { AssetCache, Item3D } from "@johnmorrisdotca/kazoku-ui/renderer";
3
+ import { type ThreeParts } from "../realistic/textures.ts";
4
+ import type { Layer } from "../realistic/tiers.ts";
5
+ import { type Frame, type PlanetName, type Vec3 } from "./ephemeris.ts";
6
+ import { type ScaleMode } from "./scale.ts";
7
+ type Three = ThreeParts;
8
+ /** The camera's distance, in Earth radii, at which the Earth fills the view at zoom 1. */
9
+ export declare const FIT_DISTANCE = 3.4;
10
+ /**
11
+ * The ten bodies the space view places, names and lets a reader choose: the Earth, the Sun, the Moon and the seven other planets.
12
+ *
13
+ * @example
14
+ * ```ts no-run
15
+ * import { BODY_NAMES } from "@johnmorrisdotca/chikyu-earth/space/element";
16
+ *
17
+ * console.log(BODY_NAMES.join(", "));
18
+ * ```
19
+ */
20
+ export declare const BODY_NAMES: readonly ["Earth", "Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"];
21
+ /**
22
+ * The name of one of the bodies in `BODY_NAMES`.
23
+ *
24
+ * @example
25
+ * ```ts no-run
26
+ * import { BODY_NAMES, type BodyName } from "@johnmorrisdotca/chikyu-earth/space/element";
27
+ *
28
+ * const mars: BodyName = "Mars";
29
+ * console.log(BODY_NAMES.includes(mars));
30
+ * ```
31
+ */
32
+ export type BodyName = (typeof BODY_NAMES)[number];
33
+ export interface SpaceModel {
34
+ /** Moment shown, milliseconds since the epoch. */
35
+ time: number;
36
+ mode: ScaleMode;
37
+ /** The shared controls' yaw, tilt and zoom. */
38
+ view: {
39
+ yaw: number;
40
+ tilt: number;
41
+ zoom: number;
42
+ };
43
+ /** The home place: the camera starts above it. */
44
+ centre: {
45
+ lon: number;
46
+ lat: number;
47
+ };
48
+ /** Earth's turn against the stars (degrees) at the moment the camera was last moved: the camera is fixed to the stars, not to the ground. */
49
+ cameraSpin: number | null;
50
+ orbits: boolean;
51
+ labels: boolean;
52
+ /** Milliseconds of game time one second of wall time covers (the ephemeris is looked up once per such span). */
53
+ stepMs: number;
54
+ /** The body the camera looks at, and (in `zoom`) fills the view with at 1x. */
55
+ focus: BodyName;
56
+ /** Filled at each draw. */
57
+ drawn: {
58
+ frame: Frame;
59
+ distance: number;
60
+ radiusPx: number;
61
+ } | null;
62
+ /** How many times astronomy-engine has been asked for a frame. */
63
+ lookups: number;
64
+ ratio(): number;
65
+ }
66
+ export declare const newSpaceModel: () => SpaceModel;
67
+ /** Where a body is on the screen: its centre and its apparent radius, in CSS pixels in the box. */
68
+ export interface BodyPlace {
69
+ x: number;
70
+ y: number;
71
+ r: number;
72
+ }
73
+ /**
74
+ * 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.
75
+ *
76
+ * @example
77
+ * ```ts no-run
78
+ * import type { BodyDrawing } from "@johnmorrisdotca/chikyu-earth/space";
79
+ *
80
+ * const drawing = { object: undefined as never, dispose: () => undefined } satisfies BodyDrawing;
81
+ * console.log(typeof drawing.dispose);
82
+ * ```
83
+ */
84
+ export interface BodyDrawing {
85
+ object: THREE.Object3D;
86
+ /** Told at each draw the unit direction from the body to the Sun, in scene axes (x to the vernal equinox, y north). */
87
+ light?(toSun: Vec3): void;
88
+ /** True to have the scene turn the object to face the camera (a disc or a glow). */
89
+ billboard?: boolean;
90
+ dispose(): void;
91
+ }
92
+ /**
93
+ * The seam for drawing the Sun and the planets. The space view places them, sizes them, lights them and labels them; a painter only makes what is drawn at
94
+ * 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.
95
+ *
96
+ * @example
97
+ * ```ts no-run
98
+ * import { plainPainter, type BodyPainter } from "@johnmorrisdotca/chikyu-earth/space";
99
+ *
100
+ * const painter: BodyPainter = { make: (three, name) => plainPainter.make(three, name) };
101
+ * console.log(typeof painter.make);
102
+ * ```
103
+ */
104
+ export interface BodyPainter {
105
+ make(three: ThreeParts, name: PlanetName | "Sun"): BodyDrawing;
106
+ }
107
+ export interface SpaceDeps {
108
+ assets: AssetCache;
109
+ textureBase: () => string;
110
+ cap: () => number;
111
+ invalidate: () => void;
112
+ onError?: (error: unknown) => void;
113
+ onTier?: (layer: Layer, width: number) => void;
114
+ /** 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. */
115
+ onLabels?: (places: Record<BodyName, BodyPlace | null>) => void;
116
+ /** How the Sun and the planets are drawn; left out, plain spheres in the right colours (see `BodyPainter`). */
117
+ painter?: BodyPainter;
118
+ }
119
+ /**
120
+ * The default look: a sphere in the planet's colour, lit by the Sun, and for the Sun a glowing disc.
121
+ *
122
+ * @example
123
+ * ```ts no-run
124
+ * import { plainPainter } from "@johnmorrisdotca/chikyu-earth/space";
125
+ *
126
+ * console.log(typeof plainPainter.make);
127
+ * ```
128
+ */
129
+ export declare const plainPainter: BodyPainter;
130
+ export declare function createSpaceItem(model: SpaceModel, deps: SpaceDeps): Item3D<Three>;
131
+ export {};
@@ -0,0 +1,295 @@
1
+ import { ATMOSPHERE_OUTER, sphere } from "../realistic/earth.js";
2
+ import { ATMOSPHERE_FRAG, ATMOSPHERE_VERT, BODY_FRAG, CLOUD_FRAG, GLOW_FRAG, GLOW_VERT, SURFACE_FRAG, SURFACE_VERT } from "../realistic/shaders.js";
3
+ import { createTextureSet } from "../realistic/textures.js";
4
+ import { createTrack, orbitPath, PLANETS } from "./ephemeris.js";
5
+ import { placeAround, placeMoon, placeSun, RADIUS } from "./scale.js";
6
+ const RAD = Math.PI / 180;
7
+ /** The camera's distance, in Earth radii, at which the Earth fills the view at zoom 1. */
8
+ export const FIT_DISTANCE = 3.4;
9
+ const FOV = 35;
10
+ const LAYERS = ["day", "night", "clouds", "water", "moon"];
11
+ const PLAIN_COLOR = (name) => (name === "Sun" ? "#ffb347" : COLORS[name]);
12
+ /**
13
+ * The ten bodies the space view places, names and lets a reader choose: the Earth, the Sun, the Moon and the seven other planets.
14
+ *
15
+ * @example
16
+ * ```ts no-run
17
+ * import { BODY_NAMES } from "@johnmorrisdotca/chikyu-earth/space/element";
18
+ *
19
+ * console.log(BODY_NAMES.join(", "));
20
+ * ```
21
+ */
22
+ export const BODY_NAMES = ["Earth", "Sun", "Moon", ...PLANETS];
23
+ const COLORS = { Mercury: "#a79f96", Venus: "#e3c98a", Mars: "#c1593a", Jupiter: "#d2b48c", Saturn: "#e4d4a0", Uranus: "#9fd6dc", Neptune: "#4a6bd6" };
24
+ const ORBIT_COLORS = { Mercury: "#6b655f", Venus: "#8c7c55", Mars: "#7a4034", Jupiter: "#85735a", Saturn: "#8d8567", Uranus: "#5f8a8e", Neptune: "#34499a" };
25
+ export const newSpaceModel = () => ({
26
+ time: Date.now(),
27
+ mode: "compressed",
28
+ view: { yaw: 0, tilt: 0, zoom: 1 },
29
+ centre: { lon: 0, lat: 0 },
30
+ cameraSpin: null,
31
+ orbits: true,
32
+ labels: true,
33
+ stepMs: 1000,
34
+ focus: "Earth",
35
+ drawn: null,
36
+ lookups: 0,
37
+ ratio: () => Math.min(globalThis.devicePixelRatio || 1, 2),
38
+ });
39
+ /** An inertial direction from the Earth's centre, in scene axes, for right ascension and declination in degrees. */
40
+ const direction = (ra, dec) => [Math.cos(dec * RAD) * Math.cos(ra * RAD), Math.sin(dec * RAD), -Math.cos(dec * RAD) * Math.sin(ra * RAD)];
41
+ const unit = (v) => {
42
+ const l = Math.hypot(v[0], v[1], v[2]) || 1;
43
+ return [v[0] / l, v[1] / l, v[2] / l];
44
+ };
45
+ /** A square facing +z, 2 x 2, with uvs 0 to 1. */
46
+ function quad(three) {
47
+ const g = new three.BufferGeometry();
48
+ g.setAttribute("position", new three.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3));
49
+ g.setAttribute("uv", new three.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2));
50
+ g.setIndex([0, 1, 2, 0, 2, 3]);
51
+ return g;
52
+ }
53
+ /**
54
+ * The default look: a sphere in the planet's colour, lit by the Sun, and for the Sun a glowing disc.
55
+ *
56
+ * @example
57
+ * ```ts no-run
58
+ * import { plainPainter } from "@johnmorrisdotca/chikyu-earth/space";
59
+ *
60
+ * console.log(typeof plainPainter.make);
61
+ * ```
62
+ */
63
+ export const plainPainter = {
64
+ make(three, name) {
65
+ if (name === "Sun") {
66
+ const geometry = quad(three);
67
+ const material = new three.ShaderMaterial({ vertexShader: GLOW_VERT, fragmentShader: GLOW_FRAG, uniforms: { tint: { value: new three.Color(PLAIN_COLOR(name)) } }, transparent: true, depthWrite: false });
68
+ const mesh = new three.Mesh(geometry, material);
69
+ mesh.renderOrder = 4;
70
+ mesh.frustumCulled = false;
71
+ mesh.scale.setScalar(3.2);
72
+ const group = new three.Group();
73
+ group.add(mesh);
74
+ return {
75
+ object: group,
76
+ billboard: true,
77
+ dispose() {
78
+ geometry.dispose();
79
+ material.dispose();
80
+ },
81
+ };
82
+ }
83
+ const geometry = sphere(three, 48, 24);
84
+ const sun = { value: new three.Vector3(1, 0, 0) };
85
+ const material = new three.ShaderMaterial({ vertexShader: SURFACE_VERT, fragmentShader: BODY_FRAG, uniforms: { map: { value: null }, tint: { value: new three.Color(PLAIN_COLOR(name)) }, textured: { value: 0 }, sunDir: sun, ambient: { value: 0.2 } } });
86
+ return {
87
+ object: new three.Mesh(geometry, material),
88
+ light(toSun) {
89
+ sun.value.set(toSun[0], toSun[1], toSun[2]);
90
+ },
91
+ dispose() {
92
+ geometry.dispose();
93
+ material.dispose();
94
+ },
95
+ };
96
+ },
97
+ };
98
+ export function createSpaceItem(model, deps) {
99
+ return {
100
+ key: "chikyu-earth:space",
101
+ async build({ three, width, height }) {
102
+ let size = { width, height };
103
+ const textures = await createTextureSet(three, LAYERS, {
104
+ assets: deps.assets,
105
+ textureBase: deps.textureBase,
106
+ cap: deps.cap,
107
+ invalidate: deps.invalidate,
108
+ ...(deps.onError ? { onError: deps.onError } : {}),
109
+ ...(deps.onTier ? { onTier: deps.onTier } : {}),
110
+ });
111
+ const uniform = textures.uniforms;
112
+ const track = createTrack(model.stepMs);
113
+ let trackStep = model.stepMs;
114
+ const sunEarth = { value: new three.Vector3(1, 0, 0) };
115
+ const sunView = { value: new three.Vector3(1, 0, 0) };
116
+ const grid = sphere(three, 96, 48);
117
+ const solid = sphere(three, 48, 24);
118
+ const earthMaterial = new three.ShaderMaterial({
119
+ vertexShader: SURFACE_VERT,
120
+ fragmentShader: SURFACE_FRAG,
121
+ uniforms: { dayMap: uniform.day, nightMap: uniform.night, waterMap: uniform.water, sunDir: sunEarth, lights: { value: 1 }, ortho: { value: 0 } },
122
+ });
123
+ const cloudShift = { value: 0 };
124
+ const cloudMaterial = new three.ShaderMaterial({
125
+ vertexShader: SURFACE_VERT,
126
+ fragmentShader: CLOUD_FRAG,
127
+ uniforms: { cloudMap: uniform.clouds, sunDir: sunEarth, shift: cloudShift, opacity: { value: 0.9 } },
128
+ transparent: true,
129
+ depthWrite: false,
130
+ });
131
+ const atmosphereMaterial = new three.ShaderMaterial({
132
+ vertexShader: ATMOSPHERE_VERT,
133
+ fragmentShader: ATMOSPHERE_FRAG,
134
+ uniforms: { sunView, outer: { value: ATMOSPHERE_OUTER }, ortho: { value: 0 } },
135
+ transparent: true,
136
+ depthWrite: false,
137
+ side: three.BackSide,
138
+ });
139
+ const earth = new three.Mesh(grid, earthMaterial);
140
+ const clouds = new three.Mesh(grid, cloudMaterial);
141
+ clouds.scale.setScalar(1.006);
142
+ clouds.renderOrder = 3;
143
+ const glow = new three.Mesh(grid, atmosphereMaterial);
144
+ glow.scale.setScalar(ATMOSPHERE_OUTER);
145
+ glow.renderOrder = 2;
146
+ glow.frustumCulled = false;
147
+ const moonSun = { value: new three.Vector3(1, 0, 0) };
148
+ const moon = new three.Mesh(solid, new three.ShaderMaterial({ vertexShader: SURFACE_VERT, fragmentShader: BODY_FRAG, uniforms: { map: uniform.moon, tint: { value: new three.Color("#bbbbbb") }, textured: { value: 1 }, sunDir: moonSun, ambient: { value: 0.03 } } }));
149
+ const painter = deps.painter ?? plainPainter;
150
+ const scene = new three.Scene();
151
+ scene.add(earth, clouds, glow, moon);
152
+ const sunDrawing = painter.make(three, "Sun");
153
+ scene.add(sunDrawing.object);
154
+ const planets = new Map();
155
+ for (const name of PLANETS) {
156
+ const drawing = painter.make(three, name);
157
+ const positions = new Float32Array(96 * 3);
158
+ const lineGeometry = new three.BufferGeometry();
159
+ lineGeometry.setAttribute("position", new three.BufferAttribute(positions, 3));
160
+ const orbit = new three.LineLoop(lineGeometry, new three.LineBasicMaterial({ color: ORBIT_COLORS[name], transparent: true, opacity: 0.7 }));
161
+ orbit.frustumCulled = false;
162
+ scene.add(drawing.object, orbit);
163
+ planets.set(name, { drawing, orbit, positions, samples: [], epoch: Number.NaN });
164
+ }
165
+ const camera = new three.PerspectiveCamera(FOV, width / Math.max(1, height), 0.05, 1000);
166
+ const target = new three.Vector3();
167
+ const projected = new three.Vector3();
168
+ const labelFor = (v) => {
169
+ projected.set(v[0], v[1], v[2]).project(camera);
170
+ if (projected.z > 1 || projected.z < -1)
171
+ return null;
172
+ return { x: ((projected.x + 1) / 2) * size.width, y: ((1 - projected.y) / 2) * size.height };
173
+ };
174
+ return {
175
+ scene,
176
+ camera,
177
+ update() {
178
+ const frame = track.at(model.time);
179
+ model.lookups = track.lookups;
180
+ if (model.stepMs !== trackStep) {
181
+ trackStep = model.stepMs;
182
+ track.setStep(model.stepMs);
183
+ }
184
+ const mode = model.mode;
185
+ const sunPos = placeSun(mode, frame.sun);
186
+ const moonPos = placeMoon(mode, frame.moon);
187
+ const helioOf = (planet) => [planet[0] - frame.sun[0], planet[1] - frame.sun[1], planet[2] - frame.sun[2]];
188
+ // the Earth, turned by the sidereal time; light from the Sun
189
+ earth.rotation.set(0, (frame.spin + 90) * RAD, 0);
190
+ clouds.rotation.copy(earth.rotation);
191
+ const toSun = unit(frame.sun);
192
+ sunEarth.value.set(toSun[0], toSun[1], toSun[2]);
193
+ cloudShift.value = -(((model.time / 3_600_000) * 1.5) % 360) / 360;
194
+ earthMaterial.uniforms["lights"].value = 1;
195
+ // the Moon, with its near side to the Earth
196
+ moon.position.set(moonPos[0], moonPos[1], moonPos[2]);
197
+ moon.scale.setScalar(RADIUS["Moon"][mode]);
198
+ moon.up.set(0, 1, 0);
199
+ moon.lookAt(0, 0, 0);
200
+ const moonToSun = unit([frame.sun[0] - frame.moon[0], frame.sun[1] - frame.moon[1], frame.sun[2] - frame.moon[2]]);
201
+ moonSun.value.set(moonToSun[0], moonToSun[1], moonToSun[2]);
202
+ // where everything is, and how big
203
+ const pos = { Earth: [0, 0, 0], Moon: moonPos, Sun: sunPos };
204
+ for (const name of PLANETS)
205
+ pos[name] = placeAround(mode, sunPos, helioOf(frame.planets[name]));
206
+ const radiusOf = (name) => (name === "Earth" ? 1 : RADIUS[name][mode]);
207
+ // the Sun and the planets, drawn by the painter, and the planets' orbits round the Sun
208
+ sunDrawing.object.position.set(sunPos[0], sunPos[1], sunPos[2]);
209
+ sunDrawing.object.scale.setScalar(radiusOf("Sun"));
210
+ if (sunDrawing.billboard)
211
+ sunDrawing.object.quaternion.copy(camera.quaternion);
212
+ for (const name of PLANETS) {
213
+ const p = planets.get(name);
214
+ p.drawing.object.position.set(pos[name][0], pos[name][1], pos[name][2]);
215
+ p.drawing.object.scale.setScalar(radiusOf(name));
216
+ p.drawing.light?.(unit([frame.sun[0] - frame.planets[name][0], frame.sun[1] - frame.planets[name][1], frame.sun[2] - frame.planets[name][2]]));
217
+ p.orbit.visible = model.orbits;
218
+ if (model.orbits) {
219
+ if (!Number.isFinite(p.epoch) || Math.abs(model.time - p.epoch) > 365 * 86_400_000) {
220
+ p.samples = orbitPath(name, model.time, 96).slice(0, 96);
221
+ p.epoch = model.time;
222
+ }
223
+ for (let i = 0; i < 96; i += 1) {
224
+ const o = placeAround(mode, sunPos, p.samples[i]);
225
+ p.positions[i * 3] = o[0];
226
+ p.positions[i * 3 + 1] = o[1];
227
+ p.positions[i * 3 + 2] = o[2];
228
+ }
229
+ p.orbit.geometry.getAttribute("position").needsUpdate = true;
230
+ }
231
+ }
232
+ // the camera: looks at the focus body from the direction the sky was in when it was last moved, turned by yaw and tilt, as far as the zoom says
233
+ const focus = model.focus;
234
+ const focusRadius = radiusOf(focus);
235
+ const spin = model.cameraSpin ?? frame.spin;
236
+ const lon = model.centre.lon - model.view.yaw;
237
+ const lat = Math.max(-90, Math.min(90, model.centre.lat + model.view.tilt));
238
+ const aim = direction(spin + lon, lat);
239
+ const distance = Math.max(1.05 * focusRadius, (FIT_DISTANCE * focusRadius) / Math.max(1e-9, model.view.zoom));
240
+ target.set(pos[focus][0], pos[focus][1], pos[focus][2]);
241
+ camera.position.set(target.x + aim[0] * distance, target.y + aim[1] * distance, target.z + aim[2] * distance);
242
+ camera.up.set(0, 1, 0);
243
+ camera.lookAt(target);
244
+ camera.near = Math.max(0.0005, distance * 0.02 - focusRadius * 0.5, focusRadius * 0.01);
245
+ camera.far = Math.max(2000, distance * 4 + (mode === "true" ? 40 * 23_481 : 400));
246
+ camera.aspect = size.width / Math.max(1, size.height);
247
+ camera.updateProjectionMatrix();
248
+ camera.updateMatrixWorld();
249
+ sunView.value.copy(sunEarth.value).transformDirection(camera.matrixWorldInverse);
250
+ // how many pixels across each body is: for the pictures the Earth and the Moon need, and for what is chosen
251
+ const half = Math.tan((FOV / 2) * RAD);
252
+ const radiusPxOf = (name) => {
253
+ const d = Math.hypot(pos[name][0] - camera.position.x, pos[name][1] - camera.position.y, pos[name][2] - camera.position.z);
254
+ const r = radiusOf(name);
255
+ return d <= r * 1.001 ? 1e5 : ((size.height / 2) * (r / Math.sqrt(d * d - r * r))) / half;
256
+ };
257
+ const px = {};
258
+ for (const name of BODY_NAMES)
259
+ px[name] = radiusPxOf(name);
260
+ model.drawn = { frame, distance, radiusPx: px.Earth };
261
+ const ratio = model.ratio();
262
+ const earthPx = Math.min(px.Earth, 1e5) * ratio;
263
+ textures.sync({ day: earthPx, night: earthPx, clouds: earthPx, water: earthPx, moon: Math.min(px.Moon, 1e5) * ratio });
264
+ if (deps.onLabels && model.labels) {
265
+ const places = {};
266
+ for (const name of BODY_NAMES) {
267
+ const at = labelFor(pos[name]);
268
+ places[name] = at ? { x: at.x, y: at.y, r: px[name] } : null;
269
+ }
270
+ deps.onLabels(places);
271
+ }
272
+ return false;
273
+ },
274
+ resize(w, h) {
275
+ size = { width: w, height: h };
276
+ },
277
+ dispose() {
278
+ textures.dispose();
279
+ sunDrawing.dispose();
280
+ for (const p of planets.values()) {
281
+ p.drawing.dispose();
282
+ p.orbit.geometry.dispose();
283
+ p.orbit.material.dispose();
284
+ }
285
+ grid.dispose();
286
+ solid.dispose();
287
+ earthMaterial.dispose();
288
+ cloudMaterial.dispose();
289
+ atmosphereMaterial.dispose();
290
+ moon.material.dispose();
291
+ },
292
+ };
293
+ },
294
+ };
295
+ }
package/dist/styles.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ /**
2
+ * The lines and fills of one look: colours and widths only, never the geometry.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { STYLES, type Style } from "@johnmorrisdotca/chikyu-earth";
7
+ *
8
+ * const thick: Style = STYLES.thick;
9
+ * console.log(thick.outline, thick.line);
10
+ * ```
11
+ */
1
12
  export interface Style {
2
13
  /** The colour of the outlines and the rim. */
3
14
  line: string;
@@ -12,6 +23,16 @@ export interface Style {
12
23
  /** Graticule width, in SVG units. */
13
24
  graticule: number;
14
25
  }
26
+ /**
27
+ * The looks the globe is drawn in, by name: `line` (thin outlines) and `thick`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { STYLES } from "@johnmorrisdotca/chikyu-earth";
32
+ *
33
+ * console.log(Object.keys(STYLES), STYLES.line.ocean);
34
+ * ```
35
+ */
15
36
  export declare const STYLES: {
16
37
  readonly line: {
17
38
  readonly line: "#8fa1b8";
@@ -30,9 +51,51 @@ export declare const STYLES: {
30
51
  readonly graticule: 0.5;
31
52
  };
32
53
  };
54
+ /**
55
+ * The name of a look: `"line"` or `"thick"`.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { globe, type StyleName } from "@johnmorrisdotca/chikyu-earth";
60
+ *
61
+ * const look: StyleName = "thick";
62
+ * console.log(globe({ style: look, size: 100 }).includes("stroke-width"));
63
+ * ```
64
+ */
33
65
  export type StyleName = keyof typeof STYLES;
34
- /** The style a drawing is in when none is named. */
66
+ /**
67
+ * The style a drawing is in when none is named.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { DEFAULT_STYLE } from "@johnmorrisdotca/chikyu-earth";
72
+ *
73
+ * console.log(DEFAULT_STYLE);
74
+ * ```
75
+ */
35
76
  export declare const DEFAULT_STYLE: StyleName;
77
+ /**
78
+ * Every style's name, in the order they are listed.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * import { STYLE_NAMES } from "@johnmorrisdotca/chikyu-earth";
83
+ *
84
+ * console.log(STYLE_NAMES.join(", "));
85
+ * ```
86
+ */
36
87
  export declare const STYLE_NAMES: StyleName[];
37
- /** A value from outside as a style's name: the default for anything else. */
88
+ /**
89
+ * A value from outside (an attribute, a query parameter) as a style's name: the default for anything else.
90
+ *
91
+ * @param value - Anything.
92
+ * @returns A valid style name; {@link DEFAULT_STYLE} when `value` names none.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { cleanStyle } from "@johnmorrisdotca/chikyu-earth";
97
+ *
98
+ * console.log(cleanStyle("thick"), cleanStyle("neon"), cleanStyle(null));
99
+ * ```
100
+ */
38
101
  export declare const cleanStyle: (value: unknown) => StyleName;
package/dist/styles.js CHANGED
@@ -1,11 +1,52 @@
1
1
  // The two looks the globe is drawn in, the same line colours as hako-devices and uchusen-rockets: one table entry each,
2
2
  // a look changes lines and fills, never the geometry. Pure.
3
+ /**
4
+ * The looks the globe is drawn in, by name: `line` (thin outlines) and `thick`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { STYLES } from "@johnmorrisdotca/chikyu-earth";
9
+ *
10
+ * console.log(Object.keys(STYLES), STYLES.line.ocean);
11
+ * ```
12
+ */
3
13
  export const STYLES = {
4
14
  line: { line: "#8fa1b8", ocean: "#0b1424", land: "#13203a", outline: 0.5, horizon: 1, graticule: 0.3 },
5
15
  thick: { line: "#b4c2d4", ocean: "#0b1424", land: "#1a2b47", outline: 1.2, horizon: 2, graticule: 0.5 },
6
16
  };
7
- /** The style a drawing is in when none is named. */
17
+ /**
18
+ * The style a drawing is in when none is named.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { DEFAULT_STYLE } from "@johnmorrisdotca/chikyu-earth";
23
+ *
24
+ * console.log(DEFAULT_STYLE);
25
+ * ```
26
+ */
8
27
  export const DEFAULT_STYLE = "line";
28
+ /**
29
+ * Every style's name, in the order they are listed.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { STYLE_NAMES } from "@johnmorrisdotca/chikyu-earth";
34
+ *
35
+ * console.log(STYLE_NAMES.join(", "));
36
+ * ```
37
+ */
9
38
  export const STYLE_NAMES = Object.keys(STYLES);
10
- /** A value from outside as a style's name: the default for anything else. */
39
+ /**
40
+ * A value from outside (an attribute, a query parameter) as a style's name: the default for anything else.
41
+ *
42
+ * @param value - Anything.
43
+ * @returns A valid style name; {@link DEFAULT_STYLE} when `value` names none.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { cleanStyle } from "@johnmorrisdotca/chikyu-earth";
48
+ *
49
+ * console.log(cleanStyle("thick"), cleanStyle("neon"), cleanStyle(null));
50
+ * ```
51
+ */
11
52
  export const cleanStyle = (value) => typeof value === "string" && Object.hasOwn(STYLES, value) ? value : DEFAULT_STYLE;
Binary file
Binary file
package/dist/types.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { StyleName } from "./styles.ts";
2
- /** What one country's outline carries: its English and Japanese name and its ISO alpha-2 code, all from kuni. */
2
+ /**
3
+ * What one country's outline carries: its English and Japanese name and its ISO alpha-2 code, all from kuni.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { countryList, type CountryProps } from "@johnmorrisdotca/chikyu-earth";
8
+ *
9
+ * const first: CountryProps | undefined = countryList()[0];
10
+ * console.log(first?.name, first?.alpha2);
11
+ * ```
12
+ */
3
13
  export interface CountryProps {
4
14
  /** English name, from kuni (Natural Earth's own name where kuni has no entry). */
5
15
  name: string;
@@ -8,7 +18,17 @@ export interface CountryProps {
8
18
  /** ISO 3166-1 alpha-2 code, from kuni, or null where there is no code. */
9
19
  alpha2: string | null;
10
20
  }
11
- /** What a shared view controller tells the globe: its turn, tip, zoom and pan. */
21
+ /**
22
+ * What a shared view controller tells the globe: its turn (`yaw`, degrees), tip (`tilt`, degrees), zoom (1 is Fit) and pan (`panX` and `panY`, CSS pixels).
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { HOME_VIEW, type GlobeView } from "@johnmorrisdotca/chikyu-earth";
27
+ *
28
+ * const turned: GlobeView = { ...HOME_VIEW, yaw: 30 };
29
+ * console.log(turned);
30
+ * ```
31
+ */
12
32
  export interface GlobeView {
13
33
  yaw: number;
14
34
  tilt: number;
@@ -16,14 +36,44 @@ export interface GlobeView {
16
36
  panX: number;
17
37
  panY: number;
18
38
  }
19
- /** A point on the globe, in degrees. */
39
+ /**
40
+ * A point on the globe, in degrees: `lon` from -180 to 180 (east is positive), `lat` from -90 to 90 (north is positive).
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import type { GeoPoint } from "@johnmorrisdotca/chikyu-earth";
45
+ *
46
+ * const tokyo: GeoPoint = { lon: 139.7, lat: 35.7 };
47
+ * console.log(tokyo);
48
+ * ```
49
+ */
20
50
  export interface GeoPoint {
21
51
  lon: number;
22
52
  lat: number;
23
53
  }
24
- /** How the globe is turned: [lambda, phi] in degrees, the pair d3-geo's `rotate` takes. */
54
+ /**
55
+ * How the globe is turned: [lambda, phi] in degrees, the pair d3-geo's `rotate` takes.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { globe, type Rotation } from "@johnmorrisdotca/chikyu-earth";
60
+ *
61
+ * const rotation: Rotation = [-10, -30];
62
+ * console.log(globe({ rotation, size: 100 }).length > 1000);
63
+ * ```
64
+ */
25
65
  export type Rotation = [lambda: number, phi: number];
26
- /** What `globe()` may be asked. */
66
+ /**
67
+ * What `globe()` may be asked.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { globe, type GlobeOptions } from "@johnmorrisdotca/chikyu-earth";
72
+ *
73
+ * const options: GlobeOptions = { size: 200, style: "thick", graticule: true, centre: { lon: 10, lat: 30 } };
74
+ * console.log(globe(options).startsWith("<svg"));
75
+ * ```
76
+ */
27
77
  export interface GlobeOptions {
28
78
  /** Width and height of the square SVG, in pixels. Default 320. */
29
79
  size?: number;