@johnmorrisdotca/chikyu-earth 0.5.0 → 0.8.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.
- package/CHANGELOG.md +70 -0
- package/NOTICE.md +22 -0
- package/README.md +186 -27
- package/dist/countries.d.ts +11 -0
- package/dist/countries.js +11 -0
- package/dist/element.d.ts +18 -0
- package/dist/element.js +15 -2
- package/dist/globe.d.ts +15 -1
- package/dist/globe.js +15 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +12 -1
- package/dist/markers/cluster.d.ts +27 -0
- package/dist/markers/cluster.js +16 -0
- package/dist/markers/labels.d.ts +40 -0
- package/dist/markers/labels.js +18 -0
- package/dist/markers/layer.d.ts +80 -0
- package/dist/markers/layer.js +21 -0
- package/dist/markers/project.d.ts +63 -2
- package/dist/markers/project.js +52 -2
- package/dist/markers/types.d.ts +48 -3
- package/dist/realistic/earth.d.ts +30 -5
- package/dist/realistic/earth.js +41 -134
- package/dist/realistic/element.d.ts +47 -7
- package/dist/realistic/element.js +48 -44
- package/dist/realistic/gate.d.ts +45 -1
- package/dist/realistic/gate.js +33 -1
- package/dist/realistic/shaders.d.ts +9 -4
- package/dist/realistic/shaders.js +54 -9
- package/dist/realistic/shared.d.ts +23 -0
- package/dist/realistic/shared.js +51 -0
- package/dist/realistic/sun.d.ts +40 -3
- package/dist/realistic/sun.js +40 -3
- package/dist/realistic/textures.d.ts +43 -0
- package/dist/realistic/textures.js +141 -0
- package/dist/realistic/tiers.d.ts +80 -6
- package/dist/realistic/tiers.js +69 -5
- package/dist/rotate.d.ts +59 -4
- package/dist/rotate.js +59 -4
- package/dist/satellites/index.d.ts +3 -0
- package/dist/satellites/index.js +3 -0
- package/dist/satellites/limits.d.ts +22 -0
- package/dist/satellites/limits.js +23 -0
- package/dist/satellites/track.d.ts +50 -0
- package/dist/satellites/track.js +227 -0
- package/dist/satellites/types.d.ts +161 -0
- package/dist/satellites/types.js +3 -0
- package/dist/space/element.d.ts +179 -0
- package/dist/space/element.js +541 -0
- package/dist/space/ephemeris.d.ts +151 -0
- package/dist/space/ephemeris.js +162 -0
- package/dist/space/index.d.ts +3 -0
- package/dist/space/index.js +5 -0
- package/dist/space/satellite-layer.d.ts +53 -0
- package/dist/space/satellite-layer.js +230 -0
- package/dist/space/scale.d.ts +124 -0
- package/dist/space/scale.js +141 -0
- package/dist/space/scene.d.ts +146 -0
- package/dist/space/scene.js +320 -0
- package/dist/styles.d.ts +65 -2
- package/dist/styles.js +43 -2
- package/dist/textures/moon-1024.webp +0 -0
- package/dist/textures/moon-2048.webp +0 -0
- package/dist/types.d.ts +55 -5
- package/dist/view.d.ts +63 -4
- package/dist/view.js +52 -4
- package/package.json +71 -8
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Where the Sun, the Moon and the planets are, as seen from the centre of the Earth, for any date. astronomy-engine (MIT) does the sums; this file asks it
|
|
2
|
+
// for a handful of vectors, keeps them in the scene's axes, and lets a draw interpolate between two answers a second apart instead of asking at every frame.
|
|
3
|
+
import { Body, GeoVector, HelioVector, SiderealTime } from "astronomy-engine";
|
|
4
|
+
/**
|
|
5
|
+
* The planets drawn, nearest the Sun first.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { PLANETS } from "@johnmorrisdotca/chikyu-earth/space";
|
|
10
|
+
*
|
|
11
|
+
* console.log(PLANETS.join(", "));
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export const PLANETS = ["Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"];
|
|
15
|
+
/** Equatorial J2000 axes (x to the vernal equinox, z to the north pole) to the scene's (y up). */
|
|
16
|
+
const scene = (v) => [v.x, v.z, -v.y];
|
|
17
|
+
const AU_KM = 149_597_870.7;
|
|
18
|
+
/**
|
|
19
|
+
* The Earth's radius in kilometres, the unit of the scene's distances.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { EARTH_RADIUS_KM } from "@johnmorrisdotca/chikyu-earth/space";
|
|
24
|
+
*
|
|
25
|
+
* console.log(EARTH_RADIUS_KM);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const EARTH_RADIUS_KM = 6371;
|
|
29
|
+
/**
|
|
30
|
+
* Earth radii in one astronomical unit.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { AU_IN_EARTH_RADII } from "@johnmorrisdotca/chikyu-earth/space";
|
|
35
|
+
*
|
|
36
|
+
* console.log(Math.round(AU_IN_EARTH_RADII));
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export const AU_IN_EARTH_RADII = AU_KM / EARTH_RADIUS_KM;
|
|
40
|
+
/**
|
|
41
|
+
* The state of the sky at `ms`: nine vectors and an angle, in a few tenths of a millisecond. It needs astronomy-engine (an optional peer dependency).
|
|
42
|
+
*
|
|
43
|
+
* @param ms - A moment, in milliseconds since the Unix epoch.
|
|
44
|
+
* @returns The Sun, the Moon and the planets as seen from the Earth's centre, and how far the Earth has turned.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { frameAt } from "@johnmorrisdotca/chikyu-earth/space";
|
|
49
|
+
*
|
|
50
|
+
* const frame = frameAt(Date.UTC(2026, 10, 2, 12));
|
|
51
|
+
* console.log(frame.moon.map((n) => n.toFixed(4)));
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function frameAt(ms) {
|
|
55
|
+
const date = new Date(ms);
|
|
56
|
+
const planets = {};
|
|
57
|
+
for (const name of PLANETS)
|
|
58
|
+
planets[name] = scene(GeoVector(Body[name], date, true));
|
|
59
|
+
return {
|
|
60
|
+
ms,
|
|
61
|
+
sun: scene(GeoVector(Body.Sun, date, true)),
|
|
62
|
+
moon: scene(GeoVector(Body.Moon, date, true)),
|
|
63
|
+
planets,
|
|
64
|
+
spin: SiderealTime(date) * 15,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const lerp3 = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
|
|
68
|
+
/**
|
|
69
|
+
* A frame between two others, `t` from 0 to 1. The spin goes the short way round, so a step under half a turn is right.
|
|
70
|
+
*
|
|
71
|
+
* @param a - The earlier frame.
|
|
72
|
+
* @param b - The later frame.
|
|
73
|
+
* @param t - How far from `a` to `b`, from 0 to 1.
|
|
74
|
+
* @returns The frame at that fraction.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { blend, frameAt } from "@johnmorrisdotca/chikyu-earth/space";
|
|
79
|
+
*
|
|
80
|
+
* const start = Date.UTC(2026, 10, 2);
|
|
81
|
+
* const middle = blend(frameAt(start), frameAt(start + 1000), 0.5);
|
|
82
|
+
* console.log(middle.ms - start);
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function blend(a, b, t) {
|
|
86
|
+
const planets = {};
|
|
87
|
+
for (const name of PLANETS)
|
|
88
|
+
planets[name] = lerp3(a.planets[name], b.planets[name], t);
|
|
89
|
+
let turn = b.spin - a.spin;
|
|
90
|
+
turn -= 360 * Math.round(turn / 360);
|
|
91
|
+
return { ms: a.ms + (b.ms - a.ms) * t, sun: lerp3(a.sun, b.sun, t), moon: lerp3(a.moon, b.moon, t), planets, spin: a.spin + turn * t };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Answers for any moment from two real ones: `at(ms)` asks astronomy-engine only when `ms` leaves the span the last two answers cover, and then looks
|
|
95
|
+
* `stepMs` ahead, so playing time at a steady rate costs one pair of lookups every `stepMs` and every draw in between is arithmetic.
|
|
96
|
+
*
|
|
97
|
+
* @param initialStepMs - How far ahead the second answer looks, in milliseconds.
|
|
98
|
+
* @returns A track: `at(ms)` for a frame, `lookups` for how many times the ephemeris was asked, `reset()` and `setStep(ms)`.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { createTrack } from "@johnmorrisdotca/chikyu-earth/space";
|
|
103
|
+
*
|
|
104
|
+
* const track = createTrack(1000);
|
|
105
|
+
* const start = Date.UTC(2026, 10, 2);
|
|
106
|
+
* for (let step = 0; step < 10; step += 1) track.at(start + step * 50);
|
|
107
|
+
* console.log(track.lookups);
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export function createTrack(initialStepMs = 1000) {
|
|
111
|
+
let stepMs = initialStepMs;
|
|
112
|
+
let a;
|
|
113
|
+
let b;
|
|
114
|
+
let lookups = 0;
|
|
115
|
+
return {
|
|
116
|
+
at(ms) {
|
|
117
|
+
if (!a || !b || ms < a.ms || ms > b.ms) {
|
|
118
|
+
a = frameAt(ms);
|
|
119
|
+
b = frameAt(ms + stepMs);
|
|
120
|
+
lookups += 2;
|
|
121
|
+
}
|
|
122
|
+
return blend(a, b, (ms - a.ms) / (b.ms - a.ms));
|
|
123
|
+
},
|
|
124
|
+
/** How many times astronomy-engine has been asked for a frame. */
|
|
125
|
+
get lookups() {
|
|
126
|
+
return lookups;
|
|
127
|
+
},
|
|
128
|
+
reset() {
|
|
129
|
+
a = undefined;
|
|
130
|
+
b = undefined;
|
|
131
|
+
},
|
|
132
|
+
/** Look further ahead (a faster game clock): the next lookup uses it. */
|
|
133
|
+
setStep(ms) {
|
|
134
|
+
stepMs = Math.max(1, ms);
|
|
135
|
+
a = undefined;
|
|
136
|
+
b = undefined;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The path a planet takes round the Sun, as `count` points around the orbit that starts where the planet is at `ms`, relative to the Sun (scene axes, AU).
|
|
141
|
+
*
|
|
142
|
+
* @param name - The planet.
|
|
143
|
+
* @param ms - The moment the orbit starts at, in milliseconds since the Unix epoch.
|
|
144
|
+
* @param count - How many segments to cut the orbit into.
|
|
145
|
+
* @returns `count + 1` points, the last on the first.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { orbitPath } from "@johnmorrisdotca/chikyu-earth/space";
|
|
150
|
+
*
|
|
151
|
+
* const points = orbitPath("Jupiter", Date.UTC(2026, 10, 2), 12);
|
|
152
|
+
* console.log(points.length);
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function orbitPath(name, ms, count = 96) {
|
|
156
|
+
const periodDays = { Mercury: 87.97, Venus: 224.7, Mars: 686.98, Jupiter: 4332.6, Saturn: 10759, Uranus: 30688, Neptune: 60182 }[name];
|
|
157
|
+
const points = [];
|
|
158
|
+
for (let i = 0; i <= count; i += 1)
|
|
159
|
+
points.push(scene(HelioVector(Body[name], new Date(ms + (i / count) * periodDays * 86_400_000))));
|
|
160
|
+
return points;
|
|
161
|
+
}
|
|
162
|
+
/** The Earth's own place relative to the Sun, for placing the orbits round the Sun's place in the Earth-centred scene. */
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { AU_IN_EARTH_RADII, blend, createTrack, EARTH_RADIUS_KM, frameAt, orbitPath, PLANETS, type Frame, type PlanetName, type Vec3 } from "./ephemeris.ts";
|
|
2
|
+
export { compress, MOON_COMPRESSED, placeAround, placeMoon, placeSun, RADIUS, SUN_COMPRESSED, truePosition, type ScaleMode } from "./scale.ts";
|
|
3
|
+
export { plainPainter, type BodyDrawing, type BodyPainter } from "./scene.ts";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The space view's pure parts: where the bodies are (astronomy-engine, an optional peer) and the two scales. They import on a server. The element is
|
|
2
|
+
// `@johnmorrisdotca/chikyu-earth/space/element`.
|
|
3
|
+
export { AU_IN_EARTH_RADII, blend, createTrack, EARTH_RADIUS_KM, frameAt, orbitPath, PLANETS } from "./ephemeris.js";
|
|
4
|
+
export { compress, MOON_COMPRESSED, placeAround, placeMoon, placeSun, RADIUS, SUN_COMPRESSED, truePosition } from "./scale.js";
|
|
5
|
+
export { plainPainter } from "./scene.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type * as THREE from "three";
|
|
2
|
+
import type { SatelliteRef, Satellites } from "../satellites/types.ts";
|
|
3
|
+
import type { ThreeParts } from "../realistic/textures.ts";
|
|
4
|
+
type Three = ThreeParts;
|
|
5
|
+
/** 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). */
|
|
6
|
+
export interface SatelliteStyle {
|
|
7
|
+
color: string;
|
|
8
|
+
size: number;
|
|
9
|
+
orbits: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SatelliteGroupState extends SatelliteStyle {
|
|
12
|
+
satellites: Satellites;
|
|
13
|
+
visible: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** The part of the space view's model the satellites read and write. */
|
|
16
|
+
export interface SatelliteState {
|
|
17
|
+
groups: Map<string, SatelliteGroupState>;
|
|
18
|
+
selected: SatelliteRef | null;
|
|
19
|
+
/** Written at each draw: satellites placed, whether the clock is too fast for the groups, and the milliseconds the last update took. */
|
|
20
|
+
drawn: {
|
|
21
|
+
placed: number;
|
|
22
|
+
tooFast: boolean;
|
|
23
|
+
updateMs: number;
|
|
24
|
+
maxUpdateMs: number;
|
|
25
|
+
updates: number;
|
|
26
|
+
totalMs: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare const newSatelliteState: () => SatelliteState;
|
|
30
|
+
/** The most orbits a group may draw all at once, one line each. */
|
|
31
|
+
export declare const MAX_ORBIT_GROUP = 40;
|
|
32
|
+
export interface SatelliteLayer {
|
|
33
|
+
/** Place every shown group for `model.time` and the chosen satellite exactly. */
|
|
34
|
+
update(time: number, speed: number): void;
|
|
35
|
+
/** The satellite nearest to a point of the view (CSS pixels) within `radius`, if one is in front of the Earth. */
|
|
36
|
+
pick(x: number, y: number, camera: THREE.PerspectiveCamera, size: {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}, radius: number): SatelliteRef | null;
|
|
40
|
+
/** Where the chosen satellite is in the view, if it is. */
|
|
41
|
+
place(camera: THREE.PerspectiveCamera, size: {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
}): {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
} | null;
|
|
48
|
+
/** The chosen satellite's position and velocity now (Earth radii, km/s), or false. */
|
|
49
|
+
exact(ref: SatelliteRef, time: number, out: Float64Array): boolean;
|
|
50
|
+
dispose(): void;
|
|
51
|
+
}
|
|
52
|
+
export declare function createSatelliteLayer(three: Three, scene: THREE.Scene, state: SatelliteState, ratio: () => number): SatelliteLayer;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { MAX_SATELLITE_SPEED } from "../satellites/limits.js";
|
|
2
|
+
export const newSatelliteState = () => ({ groups: new Map(), selected: null, drawn: { placed: 0, tooFast: false, updateMs: 0, maxUpdateMs: 0, updates: 0, totalMs: 0 } });
|
|
3
|
+
/** The most orbits a group may draw all at once, one line each. */
|
|
4
|
+
export const MAX_ORBIT_GROUP = 40;
|
|
5
|
+
const ORBIT_POINTS = 120;
|
|
6
|
+
const ORBIT_REFRESH_GAME_MS = 30 * 60_000;
|
|
7
|
+
const ORBIT_REFRESH_WALL_MS = 1000;
|
|
8
|
+
const POINT_VERT = /* glsl */ `
|
|
9
|
+
uniform float size;
|
|
10
|
+
void main() {
|
|
11
|
+
gl_PointSize = size;
|
|
12
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
13
|
+
}`;
|
|
14
|
+
const POINT_FRAG = /* glsl */ `
|
|
15
|
+
uniform vec3 tint;
|
|
16
|
+
uniform float ring;
|
|
17
|
+
void main() {
|
|
18
|
+
float d = length(gl_PointCoord - vec2(0.5));
|
|
19
|
+
if (d > 0.5) discard;
|
|
20
|
+
float a = ring > 0.5 ? smoothstep(0.30, 0.38, d) * (1.0 - smoothstep(0.44, 0.5, d)) : smoothstep(0.5, 0.2, d);
|
|
21
|
+
gl_FragColor = vec4(tint, a);
|
|
22
|
+
}`;
|
|
23
|
+
export function createSatelliteLayer(three, scene, state, ratio) {
|
|
24
|
+
if (!three.Points)
|
|
25
|
+
throw new Error("Satellites need `Points` from three.js: add it to the parts the three loader hands over (see setThreeLoader)");
|
|
26
|
+
const Points = three.Points;
|
|
27
|
+
const drawn = new Map();
|
|
28
|
+
const tmp = new Float64Array(6);
|
|
29
|
+
const projected = new three.Vector3();
|
|
30
|
+
const selectedMaterial = new three.ShaderMaterial({ vertexShader: POINT_VERT, fragmentShader: POINT_FRAG, uniforms: { size: { value: 14 }, tint: { value: new three.Color("#ffffff") }, ring: { value: 1 } }, transparent: true, depthWrite: false });
|
|
31
|
+
const selectedPosition = new Float32Array(3);
|
|
32
|
+
const selectedGeometry = new three.BufferGeometry();
|
|
33
|
+
selectedGeometry.setAttribute("position", new three.BufferAttribute(selectedPosition, 3));
|
|
34
|
+
const selectedPoints = new Points(selectedGeometry, selectedMaterial);
|
|
35
|
+
selectedPoints.frustumCulled = false;
|
|
36
|
+
selectedPoints.renderOrder = 6;
|
|
37
|
+
selectedPoints.visible = false;
|
|
38
|
+
scene.add(selectedPoints);
|
|
39
|
+
const selectedLine = { positions: new Float32Array(ORBIT_POINTS * 3), geometry: new three.BufferGeometry(), material: new three.LineBasicMaterial({ color: "#ffffff", transparent: true, opacity: 0.85 }) };
|
|
40
|
+
selectedLine.geometry.setAttribute("position", new three.BufferAttribute(selectedLine.positions, 3));
|
|
41
|
+
const selectedLoop = new three.LineLoop(selectedLine.geometry, selectedLine.material);
|
|
42
|
+
selectedLoop.frustumCulled = false;
|
|
43
|
+
selectedLoop.visible = false;
|
|
44
|
+
scene.add(selectedLoop);
|
|
45
|
+
let selectedKey = "";
|
|
46
|
+
let selectedAt = 0;
|
|
47
|
+
let selectedWall = 0;
|
|
48
|
+
const drop = (entry) => {
|
|
49
|
+
scene.remove(entry.points);
|
|
50
|
+
entry.geometry.dispose();
|
|
51
|
+
entry.material.dispose();
|
|
52
|
+
for (const line of entry.lines) {
|
|
53
|
+
scene.remove(line.loop);
|
|
54
|
+
line.geometry.dispose();
|
|
55
|
+
line.loop.material.dispose();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const orbitsOf = (entry, group, time, wall) => {
|
|
59
|
+
const wanted = group.orbits && group.satellites.count <= MAX_ORBIT_GROUP;
|
|
60
|
+
if (!wanted) {
|
|
61
|
+
for (const line of entry.lines)
|
|
62
|
+
line.loop.visible = false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (entry.lines.length === group.satellites.count && Math.abs(time - entry.linesAt) < ORBIT_REFRESH_GAME_MS && wall - entry.linesWall < 60_000) {
|
|
66
|
+
for (const line of entry.lines)
|
|
67
|
+
line.loop.visible = true;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (entry.lines.length === group.satellites.count && wall - entry.linesWall < ORBIT_REFRESH_WALL_MS)
|
|
71
|
+
return;
|
|
72
|
+
while (entry.lines.length < group.satellites.count) {
|
|
73
|
+
const positions = new Float32Array(ORBIT_POINTS * 3);
|
|
74
|
+
const geometry = new three.BufferGeometry();
|
|
75
|
+
geometry.setAttribute("position", new three.BufferAttribute(positions, 3));
|
|
76
|
+
const material = new three.LineBasicMaterial({ color: group.color, transparent: true, opacity: 0.45 });
|
|
77
|
+
const loop = new three.LineLoop(geometry, material);
|
|
78
|
+
loop.frustumCulled = false;
|
|
79
|
+
scene.add(loop);
|
|
80
|
+
entry.lines.push({ loop, geometry, positions });
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i < group.satellites.count; i += 1) {
|
|
83
|
+
const line = entry.lines[i];
|
|
84
|
+
const path = group.satellites.orbit(i, time, ORBIT_POINTS);
|
|
85
|
+
line.positions.set(path.subarray(0, ORBIT_POINTS * 3));
|
|
86
|
+
line.geometry.getAttribute("position").needsUpdate = true;
|
|
87
|
+
line.loop.visible = group.satellites.visible(i) || path.some((v) => v !== 0);
|
|
88
|
+
}
|
|
89
|
+
entry.linesAt = time;
|
|
90
|
+
entry.linesWall = wall;
|
|
91
|
+
};
|
|
92
|
+
const find = (ref) => state.groups.get(ref.group)?.satellites;
|
|
93
|
+
return {
|
|
94
|
+
update(time, speed) {
|
|
95
|
+
const started = performance.now();
|
|
96
|
+
const tooFast = Math.abs(speed) > MAX_SATELLITE_SPEED;
|
|
97
|
+
const step = Math.min(60_000, Math.max(1000, Math.abs(speed) * 1000));
|
|
98
|
+
for (const [name, entry] of drawn) {
|
|
99
|
+
if (state.groups.get(name)?.satellites !== entry.source) {
|
|
100
|
+
drop(entry);
|
|
101
|
+
drawn.delete(name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let placed = 0;
|
|
105
|
+
for (const [name, group] of state.groups) {
|
|
106
|
+
let entry = drawn.get(name);
|
|
107
|
+
if (!entry) {
|
|
108
|
+
const positions = new Float32Array(group.satellites.count * 3);
|
|
109
|
+
const geometry = new three.BufferGeometry();
|
|
110
|
+
const attribute = new three.BufferAttribute(positions, 3);
|
|
111
|
+
geometry.setAttribute("position", attribute);
|
|
112
|
+
const material = new three.ShaderMaterial({ vertexShader: POINT_VERT, fragmentShader: POINT_FRAG, uniforms: { size: { value: group.size * ratio() }, tint: { value: new three.Color(group.color) }, ring: { value: 0 } }, transparent: true, depthWrite: false });
|
|
113
|
+
const points = new Points(geometry, material);
|
|
114
|
+
points.frustumCulled = false;
|
|
115
|
+
points.renderOrder = 5;
|
|
116
|
+
scene.add(points);
|
|
117
|
+
entry = { source: group.satellites, points, geometry, material, positions, attribute, lines: [], linesAt: Number.NaN, linesWall: 0, style: "" };
|
|
118
|
+
drawn.set(name, entry);
|
|
119
|
+
}
|
|
120
|
+
const style = `${group.color}|${group.size}|${ratio()}`;
|
|
121
|
+
if (style !== entry.style) {
|
|
122
|
+
entry.style = style;
|
|
123
|
+
entry.material.uniforms["size"].value = group.size * ratio();
|
|
124
|
+
entry.material.uniforms["tint"].value.set(group.color);
|
|
125
|
+
}
|
|
126
|
+
const show = group.visible && !tooFast;
|
|
127
|
+
entry.points.visible = show;
|
|
128
|
+
if (!show) {
|
|
129
|
+
for (const line of entry.lines)
|
|
130
|
+
line.loop.visible = false;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
group.satellites.setStep(step);
|
|
134
|
+
group.satellites.at(time, entry.positions);
|
|
135
|
+
entry.attribute.needsUpdate = true;
|
|
136
|
+
placed += group.satellites.placed;
|
|
137
|
+
orbitsOf(entry, group, time, started);
|
|
138
|
+
}
|
|
139
|
+
const chosen = state.selected;
|
|
140
|
+
const set = chosen ? find(chosen) : undefined;
|
|
141
|
+
if (chosen && set && set.exact(chosen.index, time, tmp)) {
|
|
142
|
+
selectedPosition[0] = tmp[0];
|
|
143
|
+
selectedPosition[1] = tmp[1];
|
|
144
|
+
selectedPosition[2] = tmp[2];
|
|
145
|
+
selectedGeometry.getAttribute("position").needsUpdate = true;
|
|
146
|
+
selectedPoints.visible = true;
|
|
147
|
+
const key = `${chosen.group}:${chosen.index}`;
|
|
148
|
+
if (key !== selectedKey || (Math.abs(time - selectedAt) > ORBIT_REFRESH_GAME_MS && started - selectedWall > ORBIT_REFRESH_WALL_MS)) {
|
|
149
|
+
selectedKey = key;
|
|
150
|
+
selectedAt = time;
|
|
151
|
+
selectedWall = started;
|
|
152
|
+
selectedLine.positions.set(set.orbit(chosen.index, time, ORBIT_POINTS).subarray(0, ORBIT_POINTS * 3));
|
|
153
|
+
selectedLine.geometry.getAttribute("position").needsUpdate = true;
|
|
154
|
+
}
|
|
155
|
+
selectedLoop.visible = true;
|
|
156
|
+
selectedMaterial.uniforms["size"].value = 14 * ratio();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
selectedPoints.visible = false;
|
|
160
|
+
selectedLoop.visible = false;
|
|
161
|
+
selectedKey = "";
|
|
162
|
+
}
|
|
163
|
+
state.drawn.placed = placed;
|
|
164
|
+
state.drawn.tooFast = tooFast;
|
|
165
|
+
state.drawn.updateMs = performance.now() - started;
|
|
166
|
+
if (state.drawn.updateMs > state.drawn.maxUpdateMs)
|
|
167
|
+
state.drawn.maxUpdateMs = state.drawn.updateMs;
|
|
168
|
+
state.drawn.updates += 1;
|
|
169
|
+
state.drawn.totalMs += state.drawn.updateMs;
|
|
170
|
+
},
|
|
171
|
+
pick(x, y, camera, size, radius) {
|
|
172
|
+
let best = null;
|
|
173
|
+
let bestD = radius * radius;
|
|
174
|
+
const c = camera.position;
|
|
175
|
+
for (const [name, entry] of drawn) {
|
|
176
|
+
const group = state.groups.get(name);
|
|
177
|
+
if (!group || !entry.points.visible)
|
|
178
|
+
continue;
|
|
179
|
+
const p = entry.positions;
|
|
180
|
+
for (let i = 0; i < group.satellites.count; i += 1) {
|
|
181
|
+
if (!group.satellites.visible(i))
|
|
182
|
+
continue;
|
|
183
|
+
projected.set(p[i * 3], p[i * 3 + 1], p[i * 3 + 2]).project(camera);
|
|
184
|
+
if (projected.z > 1 || projected.z < -1)
|
|
185
|
+
continue;
|
|
186
|
+
const dx = ((projected.x + 1) / 2) * size.width - x;
|
|
187
|
+
const dy = ((1 - projected.y) / 2) * size.height - y;
|
|
188
|
+
const d = dx * dx + dy * dy;
|
|
189
|
+
if (d > bestD)
|
|
190
|
+
continue;
|
|
191
|
+
// behind the Earth: the line from the camera to the satellite passes through the unit sphere before it gets there
|
|
192
|
+
const px = p[i * 3] - c.x;
|
|
193
|
+
const py = p[i * 3 + 1] - c.y;
|
|
194
|
+
const pz = p[i * 3 + 2] - c.z;
|
|
195
|
+
const len2 = px * px + py * py + pz * pz;
|
|
196
|
+
const t = Math.max(0, Math.min(1, -(c.x * px + c.y * py + c.z * pz) / len2));
|
|
197
|
+
const nx = c.x + px * t;
|
|
198
|
+
const ny = c.y + py * t;
|
|
199
|
+
const nz = c.z + pz * t;
|
|
200
|
+
if (nx * nx + ny * ny + nz * nz < 0.998)
|
|
201
|
+
continue;
|
|
202
|
+
best = { group: name, index: i };
|
|
203
|
+
bestD = d;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return best;
|
|
207
|
+
},
|
|
208
|
+
place(camera, size) {
|
|
209
|
+
if (!selectedPoints.visible)
|
|
210
|
+
return null;
|
|
211
|
+
projected.set(selectedPosition[0], selectedPosition[1], selectedPosition[2]).project(camera);
|
|
212
|
+
if (projected.z > 1 || projected.z < -1)
|
|
213
|
+
return null;
|
|
214
|
+
return { x: ((projected.x + 1) / 2) * size.width, y: ((1 - projected.y) / 2) * size.height };
|
|
215
|
+
},
|
|
216
|
+
exact(ref, time, out) {
|
|
217
|
+
return find(ref)?.exact(ref.index, time, out) ?? false;
|
|
218
|
+
},
|
|
219
|
+
dispose() {
|
|
220
|
+
for (const entry of drawn.values())
|
|
221
|
+
drop(entry);
|
|
222
|
+
drawn.clear();
|
|
223
|
+
scene.remove(selectedPoints, selectedLoop);
|
|
224
|
+
selectedGeometry.dispose();
|
|
225
|
+
selectedMaterial.dispose();
|
|
226
|
+
selectedLine.geometry.dispose();
|
|
227
|
+
selectedLine.material.dispose();
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type Vec3 } from "./ephemeris.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The scale the space view is drawn in: `compressed` squeezes the distances so the Moon, the Sun and Neptune fit one view, and `true` keeps the real ones.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { placeMoon, type ScaleMode } from "@johnmorrisdotca/chikyu-earth/space";
|
|
8
|
+
*
|
|
9
|
+
* const mode: ScaleMode = "compressed";
|
|
10
|
+
* console.log(placeMoon(mode, [0.00257, 0, 0]));
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export type ScaleMode = "compressed" | "true";
|
|
14
|
+
/**
|
|
15
|
+
* The Moon's distance in a compressed view, in Earth radii (the true one is about 60).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { MOON_COMPRESSED } from "@johnmorrisdotca/chikyu-earth/space";
|
|
20
|
+
*
|
|
21
|
+
* console.log(MOON_COMPRESSED);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const MOON_COMPRESSED = 10;
|
|
25
|
+
/**
|
|
26
|
+
* The Sun's distance from the Earth in a compressed view, in Earth radii (the true one is about 23,500). One astronomical unit is this long there.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { SUN_COMPRESSED } from "@johnmorrisdotca/chikyu-earth/space";
|
|
31
|
+
*
|
|
32
|
+
* console.log(SUN_COMPRESSED);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare const SUN_COMPRESSED = 44;
|
|
36
|
+
/**
|
|
37
|
+
* A place in astronomical units from the Earth's centre, at true distance, in Earth radii.
|
|
38
|
+
*
|
|
39
|
+
* @param v - The place, in astronomical units.
|
|
40
|
+
* @returns The same direction, in Earth radii.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { truePosition } from "@johnmorrisdotca/chikyu-earth/space";
|
|
45
|
+
*
|
|
46
|
+
* console.log(truePosition([0.00257, 0, 0]).map(Math.round));
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare const truePosition: (v: Vec3) => Vec3;
|
|
50
|
+
/**
|
|
51
|
+
* The same direction at a compressed distance from the Earth: the Moon is 10 Earth radii away, and a body k times further than the Moon is k^0.25 times as far here.
|
|
52
|
+
*
|
|
53
|
+
* @param v - The place, in astronomical units from the Earth's centre.
|
|
54
|
+
* @returns The place in Earth radii, compressed; the centre stays at the centre.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { compress } from "@johnmorrisdotca/chikyu-earth/space";
|
|
59
|
+
*
|
|
60
|
+
* console.log(compress([0.00257, 0, 0]), compress([0, 0, 0]));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function compress(v: Vec3): Vec3;
|
|
64
|
+
/**
|
|
65
|
+
* The Moon, from the Earth's centre.
|
|
66
|
+
*
|
|
67
|
+
* @param mode - The scale.
|
|
68
|
+
* @param geo - The Moon's place in astronomical units from the Earth's centre.
|
|
69
|
+
* @returns Where to draw it, in Earth radii.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { placeMoon } from "@johnmorrisdotca/chikyu-earth/space";
|
|
74
|
+
*
|
|
75
|
+
* console.log(placeMoon("true", [0.00257, 0, 0]).map(Math.round));
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare const placeMoon: (mode: ScaleMode, geo: Vec3) => Vec3;
|
|
79
|
+
/**
|
|
80
|
+
* The Sun, from the Earth's centre: in a compressed view always 44 Earth radii away, in its real direction.
|
|
81
|
+
*
|
|
82
|
+
* @param mode - The scale.
|
|
83
|
+
* @param geo - The Sun's place in astronomical units from the Earth's centre.
|
|
84
|
+
* @returns Where to draw it, in Earth radii.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { placeSun } from "@johnmorrisdotca/chikyu-earth/space";
|
|
89
|
+
*
|
|
90
|
+
* console.log(placeSun("compressed", [1, 0, 0]));
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function placeSun(mode: ScaleMode, geo: Vec3): Vec3;
|
|
94
|
+
/**
|
|
95
|
+
* A place `helio` astronomical units from the Sun (a planet, or a point on its orbit), round the Sun at `sun`. True: as it is. Compressed: the same direction from
|
|
96
|
+
* the Sun, at 44 Earth radii times its distance in astronomical units to the power 0.45, so the Earth's own orbit is the 44 and Neptune's is about 200 (30 times as far, really).
|
|
97
|
+
*
|
|
98
|
+
* @param mode - The scale.
|
|
99
|
+
* @param sun - Where the Sun is drawn, in Earth radii.
|
|
100
|
+
* @param helio - The place in astronomical units from the Sun.
|
|
101
|
+
* @returns Where to draw the place, in Earth radii from the Earth's centre.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { placeAround } from "@johnmorrisdotca/chikyu-earth/space";
|
|
106
|
+
*
|
|
107
|
+
* console.log(placeAround("compressed", [44, 0, 0], [1, 0, 0]));
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function placeAround(mode: ScaleMode, sun: Vec3, helio: Vec3): Vec3;
|
|
111
|
+
/**
|
|
112
|
+
* Radii in Earth radii: the real ones, and ones large enough to see in a compressed view.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { RADIUS } from "@johnmorrisdotca/chikyu-earth/space";
|
|
117
|
+
*
|
|
118
|
+
* console.log(RADIUS["Moon"]);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare const RADIUS: Record<string, {
|
|
122
|
+
true: number;
|
|
123
|
+
compressed: number;
|
|
124
|
+
}>;
|