@johnmorrisdotca/wakusei-planets 0.7.0 → 0.11.1
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 +44 -0
- package/NOTICE.md +16 -1
- package/README.md +6 -6
- package/dist/bodies/all.d.ts +2 -0
- package/dist/bodies/all.js +2 -0
- package/dist/bodies/dwarfs.d.ts +1 -0
- package/dist/bodies/dwarfs.js +32 -0
- package/dist/bodies/moons-dwarf.d.ts +1 -0
- package/dist/bodies/moons-dwarf.js +6 -0
- package/dist/bodies/moons-neptune.js +1 -0
- package/dist/bodies/moons-pluto.js +1 -1
- package/dist/bodies/moons-saturn.js +3 -2
- package/dist/data/dwarf.d.ts +16 -0
- package/dist/data/dwarf.js +24 -0
- package/dist/data/orbits.d.ts +2 -2
- package/dist/data/orbits.js +4 -0
- package/dist/element.d.ts +4 -2
- package/dist/element.js +4 -0
- package/dist/embed.d.ts +1 -1
- package/dist/embed.js +5 -3
- package/dist/ephemeris.d.ts +6 -6
- package/dist/ephemeris.js +33 -12
- package/dist/facts.d.ts +44 -6
- package/dist/facts.js +32 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/line.js +7 -4
- package/dist/portrait/model.d.ts +2 -2
- package/dist/portrait/model.js +138 -5
- package/dist/portrait/overlay.js +2 -1
- package/dist/pose.d.ts +7 -5
- package/dist/pose.js +25 -11
- package/dist/scene/glsl.d.ts +2 -2
- package/dist/scene/glsl.js +10 -6
- package/dist/textures/ceres-1024.webp +0 -0
- package/dist/textures/ceres-512.webp +0 -0
- package/dist/textures/charon-1024.webp +0 -0
- package/dist/textures/charon-512.webp +0 -0
- package/dist/textures/dione-1024.webp +0 -0
- package/dist/textures/dione-512.webp +0 -0
- package/dist/textures/iapetus-1024.webp +0 -0
- package/dist/textures/iapetus-512.webp +0 -0
- package/dist/textures/tethys-1024.webp +0 -0
- package/dist/textures/tethys-512.webp +0 -0
- package/dist/textures/triton-1024.webp +0 -0
- package/dist/textures/triton-512.webp +0 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -1
package/dist/facts.d.ts
CHANGED
|
@@ -10,7 +10,18 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const PLANET_IDS: readonly ["mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
|
|
12
12
|
/**
|
|
13
|
-
* The
|
|
13
|
+
* The dwarf planets besides Pluto, nearest the Sun first (by the size of their orbits).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { DWARF_IDS } from "@johnmorrisdotca/wakusei-planets";
|
|
18
|
+
*
|
|
19
|
+
* console.log(DWARF_IDS.join(", "));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const DWARF_IDS: readonly ["ceres", "orcus", "haumea", "quaoar", "makemake", "gonggong", "eris", "sedna"];
|
|
23
|
+
/**
|
|
24
|
+
* The moons the package draws, by planet or dwarf planet.
|
|
14
25
|
*
|
|
15
26
|
* @example
|
|
16
27
|
* ```ts
|
|
@@ -27,6 +38,9 @@ export declare const MOONS_OF: {
|
|
|
27
38
|
readonly uranus: readonly ["miranda", "ariel", "umbriel", "titania", "oberon"];
|
|
28
39
|
readonly neptune: readonly ["proteus", "triton", "nereid"];
|
|
29
40
|
readonly pluto: readonly ["charon", "nix", "hydra"];
|
|
41
|
+
readonly orcus: readonly ["vanth"];
|
|
42
|
+
readonly haumea: readonly ["hiiaka", "namaka"];
|
|
43
|
+
readonly eris: readonly ["dysnomia"];
|
|
30
44
|
};
|
|
31
45
|
/**
|
|
32
46
|
* The name of a planet or of Pluto: `mercury` to `pluto`.
|
|
@@ -40,6 +54,30 @@ export declare const MOONS_OF: {
|
|
|
40
54
|
* ```
|
|
41
55
|
*/
|
|
42
56
|
export type PlanetId = (typeof PLANET_IDS)[number];
|
|
57
|
+
/**
|
|
58
|
+
* The name of a dwarf planet besides Pluto: `ceres`, `eris`, `haumea`...
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { DWARF_IDS, type DwarfId } from "@johnmorrisdotca/wakusei-planets";
|
|
63
|
+
*
|
|
64
|
+
* const id: DwarfId = DWARF_IDS[0];
|
|
65
|
+
* console.log(id);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export type DwarfId = (typeof DWARF_IDS)[number];
|
|
69
|
+
/**
|
|
70
|
+
* A body a moon can go round: a planet, Pluto or another dwarf planet.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { MOONS_OF, type HostId } from "@johnmorrisdotca/wakusei-planets";
|
|
75
|
+
*
|
|
76
|
+
* const host: HostId = "eris";
|
|
77
|
+
* console.log(MOONS_OF[host]);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export type HostId = PlanetId | DwarfId;
|
|
43
81
|
/**
|
|
44
82
|
* The name of a moon the package draws, such as `moon`, `io` or `titan`.
|
|
45
83
|
*
|
|
@@ -53,7 +91,7 @@ export type PlanetId = (typeof PLANET_IDS)[number];
|
|
|
53
91
|
*/
|
|
54
92
|
export type MoonId = (typeof MOONS_OF)[keyof typeof MOONS_OF][number];
|
|
55
93
|
/**
|
|
56
|
-
* The name of a body: `sun`, a planet or a moon.
|
|
94
|
+
* The name of a body: `sun`, a planet, a dwarf planet or a moon.
|
|
57
95
|
*
|
|
58
96
|
* @example
|
|
59
97
|
* ```ts
|
|
@@ -63,7 +101,7 @@ export type MoonId = (typeof MOONS_OF)[keyof typeof MOONS_OF][number];
|
|
|
63
101
|
* console.log(lineSvg(id, { size: 64 }).length > 100);
|
|
64
102
|
* ```
|
|
65
103
|
*/
|
|
66
|
-
export type BodyId = "sun" | PlanetId | MoonId;
|
|
104
|
+
export type BodyId = "sun" | PlanetId | DwarfId | MoonId;
|
|
67
105
|
/**
|
|
68
106
|
* How a body is drawn: `line` (an outline drawing), `realistic` (shaded, from the numbers) or `painted` (a real surface map).
|
|
69
107
|
*
|
|
@@ -139,8 +177,8 @@ export type Feature = {
|
|
|
139
177
|
export interface BodyFacts {
|
|
140
178
|
id: BodyId;
|
|
141
179
|
kind: "star" | "planet" | "dwarf-planet" | "moon";
|
|
142
|
-
/** The planet a moon goes round. */
|
|
143
|
-
parent?:
|
|
180
|
+
/** The planet or dwarf planet a moon goes round. */
|
|
181
|
+
parent?: HostId;
|
|
144
182
|
name: {
|
|
145
183
|
en: string;
|
|
146
184
|
ja: string;
|
|
@@ -186,7 +224,7 @@ export interface BodyFacts {
|
|
|
186
224
|
*/
|
|
187
225
|
export declare const BODIES: Readonly<Record<BodyId, BodyFacts>>;
|
|
188
226
|
/**
|
|
189
|
-
* Every body's name: the Sun, the planets outward, then the moons of each
|
|
227
|
+
* Every body's name: the Sun, the planets outward, the dwarf planets, then the moons of each.
|
|
190
228
|
*
|
|
191
229
|
* @example
|
|
192
230
|
* ```ts
|
package/dist/facts.js
CHANGED
|
@@ -12,7 +12,18 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const PLANET_IDS = ["mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
|
|
14
14
|
/**
|
|
15
|
-
* The
|
|
15
|
+
* The dwarf planets besides Pluto, nearest the Sun first (by the size of their orbits).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { DWARF_IDS } from "@johnmorrisdotca/wakusei-planets";
|
|
20
|
+
*
|
|
21
|
+
* console.log(DWARF_IDS.join(", "));
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const DWARF_IDS = ["ceres", "orcus", "haumea", "quaoar", "makemake", "gonggong", "eris", "sedna"];
|
|
25
|
+
/**
|
|
26
|
+
* The moons the package draws, by planet or dwarf planet.
|
|
16
27
|
*
|
|
17
28
|
* @example
|
|
18
29
|
* ```ts
|
|
@@ -29,6 +40,9 @@ export const MOONS_OF = {
|
|
|
29
40
|
uranus: ["miranda", "ariel", "umbriel", "titania", "oberon"],
|
|
30
41
|
neptune: ["proteus", "triton", "nereid"],
|
|
31
42
|
pluto: ["charon", "nix", "hydra"],
|
|
43
|
+
orcus: ["vanth"],
|
|
44
|
+
haumea: ["hiiaka", "namaka"],
|
|
45
|
+
eris: ["dysnomia"],
|
|
32
46
|
};
|
|
33
47
|
const f = (id, kind, en, ja, radiusKm, flattening, tiltDeg, ra, dec, w0, rate, rotationHours, colour, more = {}) => ({
|
|
34
48
|
id,
|
|
@@ -131,6 +145,17 @@ export const BODIES = {
|
|
|
131
145
|
...dots([[-60, 20, 6], [-100, -20, 7], [60, 40, 5]]),
|
|
132
146
|
],
|
|
133
147
|
}),
|
|
148
|
+
ceres: f("ceres", "dwarf-planet", "Ceres", "ケレス", 481.05, 0.0731, 4.04, 291.42744, 66.76033, 170.65, 952.1532, 9.07417, "#7b7873", {
|
|
149
|
+
showLon: 239,
|
|
150
|
+
features: [{ kind: "circle", lon: 239, lat: 20, radius: 6 }, { kind: "circle", lon: 239, lat: 20, radius: 1.5, fill: true }, { kind: "circle", lon: 10, lat: -42, radius: 10 }, ...dots([[-60, 30, 8], [100, 15, 9], [160, -20, 7], [-120, -35, 8], [40, 45, 6], [-20, 0, 5]])],
|
|
151
|
+
}),
|
|
152
|
+
orcus: f("orcus", "dwarf-planet", "Orcus", "オルクス", 455, 0, 73.83, 142.474, 15.004, 0, 655.15, 13.188, "#9a9a9c", { features: dots([[-60, 20, 9], [40, -15, 7], [130, 30, 8], [-130, -30, 6]]) }),
|
|
153
|
+
haumea: f("haumea", "dwarf-planet", "Haumea", "ハウメア", 798, 0, 90.15, 282.6, -13, 0, 2206.6, 3.915341, "#dfe0e3", { showLon: 90, axesKm: [1161, 852, 513], features: [{ kind: "oval", lon: 40, lat: 5, rx: 22, ry: 12, fill: true }, { kind: "band", lat: 0 }, ...dots([[-80, 25, 7]])] }),
|
|
154
|
+
quaoar: f("quaoar", "dwarf-planet", "Quaoar", "クワオアー", 557.8, 0, 0, 265.4, 74.409, 0, 488.72, 17.6788, "#a3735a", { showLon: 90, axesKm: [620.7, 546.5, 511.5], features: dots([[-40, 20, 8], [70, -15, 9], [150, 35, 7], [-110, -30, 6]]) }),
|
|
155
|
+
makemake: f("makemake", "dwarf-planet", "Makemake", "マケマケ", 715, 0.01, 0, 317.92, 50.03, 0, 378.5, 22.8266, "#b87f5d", { features: [{ kind: "oval", lon: 30, lat: 10, rx: 30, ry: 18, fill: true }, ...dots([[-70, -20, 8], [120, 25, 7]])] }),
|
|
156
|
+
gonggong: f("gonggong", "dwarf-planet", "Gonggong", "ゴンゴン", 615, 0, 0, 255.383, 36.831, 0, 385.7, 22.4, "#9c4f3b", { features: dots([[-50, 10, 9], [60, -25, 8], [140, 30, 7]]) }),
|
|
157
|
+
eris: f("eris", "dwarf-planet", "Eris", "エリス", 1163, 0, 78.22, 36.17, 44.51, 0, 22.805163, 378.8616, "#e8e4dc", { features: [{ kind: "band", lat: 0 }, ...dots([[-30, 30, 8], [80, -20, 9]])] }),
|
|
158
|
+
sedna: f("sedna", "dwarf-planet", "Sedna", "セドナ", 453, 0, 0, 297.062, 74.71, 0, 840.95, 10.273, "#9a3f2b", { features: dots([[-40, 20, 9], [70, -30, 8], [150, 10, 7]]) }),
|
|
134
159
|
moon: f("moon", "moon", "Moon", "月", 1737.4, 0.0012, 6.68, 269.9949, 66.5392, 38.3213, 13.17635815, 655.728, "#b8b5ae", {
|
|
135
160
|
parent: "earth",
|
|
136
161
|
synchronous: true,
|
|
@@ -169,9 +194,13 @@ export const BODIES = {
|
|
|
169
194
|
charon: f("charon", "moon", "Charon", "カロン", 606, 0, 0, 132.993, -6.163, 122.695, 56.3625225, 153.2935, "#9c9189", { parent: "pluto", synchronous: true, features: [{ kind: "oval", lon: 0, lat: 76, rx: 90, ry: 14, fill: true }, { kind: "circle", lon: 40, lat: -15, radius: 12 }, ...dots([[-60, 10, 8], [120, 30, 7]])] }),
|
|
170
195
|
nix: f("nix", "moon", "Nix", "ニクス", 19.3, 0.3, 0, 132.993, -6.163, 0, 14.48, 0, "#b3ada7", { parent: "pluto", axesKm: [24.9, 16.6, 15.6], features: [{ kind: "circle", lon: 0, lat: 0, radius: 18 }] }),
|
|
171
196
|
hydra: f("hydra", "moon", "Hydra", "ヒドラ", 19.5, 0.4, 0, 132.993, -6.163, 0, 9.42, 0, "#c4beb6", { parent: "pluto", axesKm: [32.5, 22.5, 12.5], features: [{ kind: "circle", lon: 30, lat: 10, radius: 20 }] }),
|
|
197
|
+
vanth: f("vanth", "moon", "Vanth", "ヴァント", 221, 0, 0, 142.474, 15.004, 0, 37.738618, 228.9488, "#6e6a66", { parent: "orcus", synchronous: true, features: dots([[-50, 15, 10], [80, -20, 9], [150, 30, 8]]) }),
|
|
198
|
+
hiiaka: f("hiiaka", "moon", "Hiʻiaka", "ヒイアカ", 185, 0, 0, 282.6, -13, 0, 0, 0, "#e0e0e0", { parent: "haumea", features: dots([[-30, 20, 12], [70, -25, 10]]) }),
|
|
199
|
+
namaka: f("namaka", "moon", "Namaka", "ナマカ", 75, 0, 0, 282.6, -13, 0, 0, 0, "#d0d0d0", { parent: "haumea", features: dots([[0, 0, 14]]) }),
|
|
200
|
+
dysnomia: f("dysnomia", "moon", "Dysnomia", "ディスノミア", 308, 0, 0, 36.17, 44.51, 0, 22.805163, 378.8616, "#5a5856", { parent: "eris", synchronous: true, features: dots([[-60, 20, 10], [50, -25, 9], [140, 30, 8]]) }),
|
|
172
201
|
};
|
|
173
202
|
/**
|
|
174
|
-
* Every body's name: the Sun, the planets outward, then the moons of each
|
|
203
|
+
* Every body's name: the Sun, the planets outward, the dwarf planets, then the moons of each.
|
|
175
204
|
*
|
|
176
205
|
* @example
|
|
177
206
|
* ```ts
|
|
@@ -180,7 +209,7 @@ export const BODIES = {
|
|
|
180
209
|
* console.log(BODY_IDS.length, BODY_IDS[0]);
|
|
181
210
|
* ```
|
|
182
211
|
*/
|
|
183
|
-
export const BODY_IDS = ["sun", ...PLANET_IDS, ...Object.values(MOONS_OF).flat()];
|
|
212
|
+
export const BODY_IDS = ["sun", ...PLANET_IDS, ...DWARF_IDS, ...Object.values(MOONS_OF).flat()];
|
|
184
213
|
/** A body's polar radius as a fraction of its equatorial radius. */
|
|
185
214
|
export const polarRatio = (facts) => 1 - facts.flattening;
|
|
186
215
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BODIES, BODY_IDS, MOONS_OF, PLANET_IDS, ringReach, type BodyFacts, type BodyId, type Feature, type Look, type MoonId, type PlanetId, type RingBand } from "./facts.ts";
|
|
1
|
+
export { BODIES, BODY_IDS, DWARF_IDS, MOONS_OF, PLANET_IDS, ringReach, type BodyFacts, type BodyId, type DwarfId, type Feature, type HostId, type Look, type MoonId, type PlanetId, type RingBand } from "./facts.ts";
|
|
2
2
|
export { apparent, moonGeocentric, heliocentric, orbitPath, system, type Apparent, type MoonPlace, type Pose, type System } from "./ephemeris.ts";
|
|
3
3
|
export { drawnTilt, portrait, type PortraitOptions } from "./pose.ts";
|
|
4
4
|
export { lineSvg, mix, type LineOptions } from "./line.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BODIES, BODY_IDS, MOONS_OF, PLANET_IDS, ringReach } from "./facts.js";
|
|
1
|
+
export { BODIES, BODY_IDS, DWARF_IDS, MOONS_OF, PLANET_IDS, ringReach } from "./facts.js";
|
|
2
2
|
export { apparent, moonGeocentric, heliocentric, orbitPath, system } from "./ephemeris.js";
|
|
3
3
|
export { drawnTilt, portrait } from "./pose.js";
|
|
4
4
|
export { lineSvg, mix } from "./line.js";
|
package/dist/line.js
CHANGED
|
@@ -54,12 +54,15 @@ function around(lon, lat, rx, ry, steps = 28) {
|
|
|
54
54
|
}
|
|
55
55
|
return out;
|
|
56
56
|
}
|
|
57
|
+
/** The semi-axes of a body that is far from round, as fractions of its radius. */
|
|
58
|
+
const axesOf = (facts) => (facts.axesKm === undefined ? undefined : [facts.axesKm[0] / facts.radiusKm, facts.axesKm[1] / facts.radiusKm, facts.axesKm[2] / facts.radiusKm]);
|
|
57
59
|
function featurePaths(facts, pose, frame, stroke, hair) {
|
|
58
60
|
const f = facts.flattening;
|
|
59
|
-
const
|
|
61
|
+
const axes = axesOf(facts);
|
|
62
|
+
const visible = (lon, lat) => facing(pose, lon, lat, f, axes) > 0.02;
|
|
60
63
|
const out = [];
|
|
61
64
|
const draw = (pts, close, fill) => {
|
|
62
|
-
const parts = runs(pts.map((p) => surfacePoint(pose, p.lon, p.lat, f)), (_, i) => visible(pts[i].lon, pts[i].lat));
|
|
65
|
+
const parts = runs(pts.map((p) => surfacePoint(pose, p.lon, p.lat, f, axes)), (_, i) => visible(pts[i].lon, pts[i].lat));
|
|
63
66
|
for (const part of parts) {
|
|
64
67
|
const whole = close && part.length === pts.length;
|
|
65
68
|
if (part.length < 2)
|
|
@@ -176,7 +179,7 @@ function cachedOrbit(id, time) {
|
|
|
176
179
|
}
|
|
177
180
|
function bodyGroup(id, pose, frame, o, hair, ringScale) {
|
|
178
181
|
const facts = BODIES[id];
|
|
179
|
-
const sil = silhouette(pose, facts.flattening);
|
|
182
|
+
const sil = silhouette(pose, facts.flattening, axesOf(facts));
|
|
180
183
|
const disc = `<ellipse cx="${n(frame.cx)}" cy="${n(frame.cy)}" rx="${n(frame.r * sil.a)}" ry="${n(frame.r * sil.b)}" transform="rotate(${n(sil.angle)} ${n(frame.cx)} ${n(frame.cy)})"`;
|
|
181
184
|
const rings = ringPaths(facts, pose, frame, o.stroke, hair, ringScale);
|
|
182
185
|
const dark = id === "sun" || facts.kind === "star" ? "" : nightPath(pose, frame);
|
|
@@ -227,7 +230,7 @@ export function lineSvg(id, options = {}) {
|
|
|
227
230
|
if (options.moons === true && facts.parent === undefined && id !== "sun")
|
|
228
231
|
return systemSvg(id, options, size, stroke, paper, hair, head);
|
|
229
232
|
const pose = poseFor(id, options);
|
|
230
|
-
const reach = Math.max(ringReach(facts), id === "sun" ? 1.08 : 1);
|
|
233
|
+
const reach = Math.max(ringReach(facts), id === "sun" ? 1.08 : 1, ...(axesOf(facts) ?? []));
|
|
231
234
|
const fit = options.fill === undefined ? 0.94 / reach : options.fill;
|
|
232
235
|
const zoom = options.zoom ?? 1;
|
|
233
236
|
const frame = { cx: (size / 2) * (1 - (options.panX ?? 0)), cy: (size / 2) * (1 + (options.panY ?? 0)), r: (size / 2) * fit * zoom };
|
package/dist/portrait/model.d.ts
CHANGED
|
@@ -108,7 +108,7 @@ export interface Resolved {
|
|
|
108
108
|
}
|
|
109
109
|
export declare const ASPECTS: readonly (readonly [string, number])[];
|
|
110
110
|
export declare const MAX_ITEMS = 40;
|
|
111
|
-
/** The Sun, then each planet followed by its moons nearest first: the order a layout and a row of buttons use. */
|
|
111
|
+
/** The Sun, then each planet and dwarf planet followed by its moons nearest first: the order a layout and a row of buttons use. */
|
|
112
112
|
export declare const ORDER: readonly BodyId[];
|
|
113
113
|
export declare const rankOf: (id: BodyId) => number;
|
|
114
114
|
/**
|
|
@@ -285,7 +285,7 @@ export interface Preset {
|
|
|
285
285
|
scene: Portrait;
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
|
-
* Ready-made scenes to start from: a family photo of the Sun and the planets, the gas giants to scale, Jupiter and the Galileans,
|
|
288
|
+
* Ready-made scenes to start from: a family photo of the Sun and the planets, the gas giants to scale, Jupiter and the Galileans, the Earth and the Moon to scale (size and distance), Mars and its moons at their true distance, and the inner planets round the Sun.
|
|
289
289
|
*
|
|
290
290
|
* @example
|
|
291
291
|
* ```ts
|
package/dist/portrait/model.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// The portrait builder's scene as plain data, and everything that can be worked out from it without a screen: where each body goes when the layout is automatic, the camera that fits them,
|
|
2
2
|
// which body is under a pointer, where a dragged body snaps, the order they are drawn in, the history for undo, and the short text that keeps a scene in the address. Pure: no DOM, no three.js.
|
|
3
3
|
import { ORBITS } from "../data/orbits.js";
|
|
4
|
-
import {
|
|
4
|
+
import { heliocentric, moonVector } from "../ephemeris.js";
|
|
5
|
+
import { BODIES, DWARF_IDS, MOONS_OF, PLANET_IDS, ringReach } from "../facts.js";
|
|
5
6
|
export const ASPECTS = [["16:9", 16 / 9], ["4:3", 4 / 3], ["1:1", 1], ["3:4", 3 / 4]];
|
|
6
7
|
export const MAX_ITEMS = 40;
|
|
7
|
-
/** The Sun, then each planet followed by its moons nearest first: the order a layout and a row of buttons use. */
|
|
8
|
-
export const ORDER = ["sun", ...PLANET_IDS.flatMap((planet) => [planet, ...(planet in MOONS_OF ? [...MOONS_OF[planet]].sort(byDistance) : [])])];
|
|
8
|
+
/** The Sun, then each planet and dwarf planet followed by its moons nearest first: the order a layout and a row of buttons use. */
|
|
9
|
+
export const ORDER = ["sun", ...[...PLANET_IDS, ...DWARF_IDS].flatMap((planet) => [planet, ...(planet in MOONS_OF ? [...MOONS_OF[planet]].sort(byDistance) : [])])];
|
|
9
10
|
function byDistance(a, b) {
|
|
10
11
|
return (ORBITS[a]?.a ?? 0) - (ORBITS[b]?.a ?? 0);
|
|
11
12
|
}
|
|
@@ -122,6 +123,130 @@ function arrangeUnits(units, how, aspect) {
|
|
|
122
123
|
}
|
|
123
124
|
return out;
|
|
124
125
|
}
|
|
126
|
+
// --- the true orbit distance layout ---------------------------------------------------------------------------------------------------------------------
|
|
127
|
+
const AU_KM = 149_597_870.7;
|
|
128
|
+
const J2000 = Date.UTC(2000, 0, 1, 12);
|
|
129
|
+
/**
|
|
130
|
+
* The orbit layout, in kilometres: the Sun (if it is in the scene) with the planets at their true distance, each planet with its moons at theirs; or each planet with its moons, side by side.
|
|
131
|
+
* With a date the bodies are where they really are, seen from above the ecliptic; without one they are in a line to the right of their centre. Sizes are true, or squeezed so the smallest can be seen.
|
|
132
|
+
*/
|
|
133
|
+
function orbitLayout(p) {
|
|
134
|
+
const items = p.items;
|
|
135
|
+
const ms = p.date === undefined ? undefined : Date.parse(p.date);
|
|
136
|
+
const when = ms !== undefined && Number.isFinite(ms) ? ms : undefined;
|
|
137
|
+
const index = new Map(items.map((i, k) => [i.body, k]));
|
|
138
|
+
const planetKind = (id) => BODIES[id].parent === undefined && id !== "sun";
|
|
139
|
+
const planetSpot = (id) => {
|
|
140
|
+
const v = heliocentric(id, when ?? J2000);
|
|
141
|
+
return when === undefined ? { x: Math.hypot(v[0], v[1], v[2]) * AU_KM, y: 0 } : { x: v[0] * AU_KM, y: v[1] * AU_KM };
|
|
142
|
+
};
|
|
143
|
+
const moonSpot = (id) => {
|
|
144
|
+
if (when === undefined)
|
|
145
|
+
return { x: ORBITS[id]?.a ?? 0, y: 0 };
|
|
146
|
+
const v = moonVector(id, when);
|
|
147
|
+
return { x: v[0], y: v[1] };
|
|
148
|
+
};
|
|
149
|
+
const systems = [];
|
|
150
|
+
const taken = new Set();
|
|
151
|
+
const sun = index.get("sun");
|
|
152
|
+
const attachMoons = (system, planet, at) => {
|
|
153
|
+
for (const [k, item] of items.entries()) {
|
|
154
|
+
if (BODIES[item.body].parent !== items[planet].body || taken.has(k))
|
|
155
|
+
continue;
|
|
156
|
+
const m = moonSpot(item.body);
|
|
157
|
+
system.members.push(k);
|
|
158
|
+
system.spot.set(k, { x: at.x + m.x, y: at.y + m.y });
|
|
159
|
+
system.centre.set(k, planet);
|
|
160
|
+
taken.add(k);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (sun !== undefined) {
|
|
164
|
+
const system = { members: [sun], spot: new Map([[sun, { x: 0, y: 0 }]]), centre: new Map() };
|
|
165
|
+
taken.add(sun);
|
|
166
|
+
for (const [k, item] of items.entries()) {
|
|
167
|
+
if (!planetKind(item.body))
|
|
168
|
+
continue;
|
|
169
|
+
const at = planetSpot(item.body);
|
|
170
|
+
system.members.push(k);
|
|
171
|
+
system.spot.set(k, at);
|
|
172
|
+
system.centre.set(k, sun);
|
|
173
|
+
taken.add(k);
|
|
174
|
+
attachMoons(system, k, at);
|
|
175
|
+
}
|
|
176
|
+
systems.push(system);
|
|
177
|
+
}
|
|
178
|
+
for (const [k, item] of items.entries()) {
|
|
179
|
+
if (taken.has(k) || !planetKind(item.body))
|
|
180
|
+
continue;
|
|
181
|
+
const system = { members: [k], spot: new Map([[k, { x: 0, y: 0 }]]), centre: new Map() };
|
|
182
|
+
taken.add(k);
|
|
183
|
+
if (p.moons === "auto")
|
|
184
|
+
attachMoons(system, k, { x: 0, y: 0 });
|
|
185
|
+
systems.push(system);
|
|
186
|
+
}
|
|
187
|
+
for (const [k] of items.entries()) {
|
|
188
|
+
if (taken.has(k))
|
|
189
|
+
continue;
|
|
190
|
+
systems.push({ members: [k], spot: new Map([[k, { x: 0, y: 0 }]]), centre: new Map() });
|
|
191
|
+
taken.add(k);
|
|
192
|
+
}
|
|
193
|
+
// sizes: true, or squeezed against the size of the picture the distances make
|
|
194
|
+
const ids = items.map((i) => i.body);
|
|
195
|
+
let span = 0;
|
|
196
|
+
for (const system of systems) {
|
|
197
|
+
for (const k of system.members) {
|
|
198
|
+
const c = system.centre.get(k);
|
|
199
|
+
if (c === undefined)
|
|
200
|
+
continue;
|
|
201
|
+
const a = system.spot.get(k);
|
|
202
|
+
const b = system.spot.get(c);
|
|
203
|
+
span = Math.max(span, Math.hypot(a.x - b.x, a.y - b.y));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const biggest = Math.max(...ids.map((id) => BODIES[id].radiusKm));
|
|
207
|
+
span = Math.max(span, biggest * 8);
|
|
208
|
+
const radius = (id) => (p.scale === "true" ? BODIES[id].radiusKm : Math.max(0.014 * span, 0.06 * span * (BODIES[id].radiusKm / biggest) ** 0.4));
|
|
209
|
+
const raw = items.map((item, index2) => ({ ...item, index: index2, x: 0, y: 0, r: radius(item.body), reach: radius(item.body) * reachOf(item.body) }));
|
|
210
|
+
// the circle each body goes round its centre on reaches this far every way
|
|
211
|
+
const extent = (system) => {
|
|
212
|
+
let minX = Infinity;
|
|
213
|
+
let maxX = -Infinity;
|
|
214
|
+
let minY = Infinity;
|
|
215
|
+
let maxY = -Infinity;
|
|
216
|
+
for (const k of system.members) {
|
|
217
|
+
const a = system.spot.get(k);
|
|
218
|
+
const reach = raw[k].reach;
|
|
219
|
+
minX = Math.min(minX, a.x - reach);
|
|
220
|
+
maxX = Math.max(maxX, a.x + reach);
|
|
221
|
+
minY = Math.min(minY, a.y - reach);
|
|
222
|
+
maxY = Math.max(maxY, a.y + reach);
|
|
223
|
+
const c = system.centre.get(k);
|
|
224
|
+
if (c !== undefined) {
|
|
225
|
+
const b = system.spot.get(c);
|
|
226
|
+
const d = Math.hypot(a.x - b.x, a.y - b.y);
|
|
227
|
+
minX = Math.min(minX, b.x - d);
|
|
228
|
+
maxX = Math.max(maxX, b.x + d);
|
|
229
|
+
minY = Math.min(minY, b.y - d);
|
|
230
|
+
maxY = Math.max(maxY, b.y + d);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { minX, maxX, minY, maxY };
|
|
234
|
+
};
|
|
235
|
+
const extents = systems.map(extent);
|
|
236
|
+
const units = systems.map((s, k) => ({ members: s.members, at: [], minX: extents[k].minX, maxX: extents[k].maxX, halfH: Math.max(extents[k].maxY, -extents[k].minY) }));
|
|
237
|
+
const spots = arrangeUnits(units, "row", p.aspect);
|
|
238
|
+
const reachPoints = [];
|
|
239
|
+
systems.forEach((s, k) => {
|
|
240
|
+
const dy = (extents[k].minY + extents[k].maxY) / 2;
|
|
241
|
+
for (const m of s.members) {
|
|
242
|
+
const a = s.spot.get(m);
|
|
243
|
+
raw[m].x = spots[k].ox + a.x;
|
|
244
|
+
raw[m].y = a.y - dy;
|
|
245
|
+
}
|
|
246
|
+
reachPoints.push({ x: spots[k].ox + extents[k].minX, y: extents[k].minY - dy }, { x: spots[k].ox + extents[k].maxX, y: extents[k].maxY - dy });
|
|
247
|
+
});
|
|
248
|
+
return { placed: raw, reachPoints };
|
|
249
|
+
}
|
|
125
250
|
/** The camera that holds every body with a margin: its centre and half-height. */
|
|
126
251
|
export function fitCamera(placed, aspect, margin = 1.1, labels = false) {
|
|
127
252
|
if (placed.length === 0)
|
|
@@ -161,6 +286,12 @@ export function resolve(scene) {
|
|
|
161
286
|
if (p.layout === "manual" || p.items.length === 0) {
|
|
162
287
|
return { placed: p.items.map((item, index) => ({ ...item, index, reach: item.r * reachOf(item.body) })), camera: p.camera };
|
|
163
288
|
}
|
|
289
|
+
if (p.arrange === "orbit") {
|
|
290
|
+
const { placed: raw, reachPoints } = orbitLayout(p);
|
|
291
|
+
const cam = fitCamera([...raw, ...reachPoints.map((q) => ({ x: q.x, y: q.y, reach: 0 }))], p.aspect, 1.1, p.labels);
|
|
292
|
+
const k = 1 / cam.h;
|
|
293
|
+
return { placed: raw.map((it) => ({ ...it, x: (it.x - cam.x) * k, y: (it.y - cam.y) * k, r: it.r * k, reach: it.reach * k })), camera: { x: 0, y: 0, h: 1 } };
|
|
294
|
+
}
|
|
164
295
|
const ids = p.items.map((item) => item.body);
|
|
165
296
|
const sizes = sizesFor(ids, p.scale);
|
|
166
297
|
const grouped = groups(p.items, p.moons);
|
|
@@ -561,7 +692,7 @@ export function decodeScene(text) {
|
|
|
561
692
|
}
|
|
562
693
|
const fromList = (ids, change = {}) => ids.reduce((p, id) => addBody(p, id), { ...defaultPortrait(), ...change });
|
|
563
694
|
/**
|
|
564
|
-
* Ready-made scenes to start from: a family photo of the Sun and the planets, the gas giants to scale, Jupiter and the Galileans,
|
|
695
|
+
* Ready-made scenes to start from: a family photo of the Sun and the planets, the gas giants to scale, Jupiter and the Galileans, the Earth and the Moon to scale (size and distance), Mars and its moons at their true distance, and the inner planets round the Sun.
|
|
565
696
|
*
|
|
566
697
|
* @example
|
|
567
698
|
* ```ts
|
|
@@ -574,5 +705,7 @@ export const PRESETS = [
|
|
|
574
705
|
{ id: "family", name: { en: "Family photo", ja: "家族写真" }, scene: fromList(["sun", ...PLANET_IDS], { arrange: "arc", scale: "readable", moons: "manual", orbits: false }) },
|
|
575
706
|
{ id: "giants", name: { en: "Gas giants", ja: "巨大ガス惑星" }, scene: fromList(["jupiter", "saturn", "uranus", "neptune"], { arrange: "row", scale: "true", moons: "manual", bg: "gradient" }) },
|
|
576
707
|
{ id: "galileans", name: { en: "Jupiter and the Galileans", ja: "木星とガリレオ衛星" }, scene: fromList(["jupiter", "io", "europa", "ganymede", "callisto"], { arrange: "row", scale: "readable", moons: "auto", orbits: true }) },
|
|
577
|
-
{ id: "earthmoon", name: { en: "Earth and Moon to scale", ja: "地球と月 (
|
|
708
|
+
{ id: "earthmoon", name: { en: "Earth and Moon to scale", ja: "地球と月 (大きさと距離の比)" }, scene: fromList(["earth", "moon"], { arrange: "orbit", scale: "true", moons: "auto", orbits: true }) },
|
|
709
|
+
{ id: "marsmoons", name: { en: "Mars and its moons at true distance", ja: "火星と衛星 (本当の距離)" }, scene: fromList(["mars", "phobos", "deimos"], { arrange: "orbit", scale: "readable", moons: "auto", orbits: true }) },
|
|
710
|
+
{ id: "orrery", name: { en: "The inner planets round the Sun", ja: "太陽と内側の惑星 (本当の距離)" }, scene: fromList(["sun", "mercury", "venus", "earth", "mars"], { arrange: "orbit", scale: "readable", moons: "auto", orbits: true, bg: "black" }) },
|
|
578
711
|
];
|
package/dist/portrait/overlay.js
CHANGED
|
@@ -64,8 +64,9 @@ export function shapesOf(p, resolved, width, height, lang = "en", edit) {
|
|
|
64
64
|
const out = { circles: [], lines: [], texts: [] };
|
|
65
65
|
const byBody = new Map(resolved.placed.map((q) => [q.body, q]));
|
|
66
66
|
if (p.orbits) {
|
|
67
|
+
const sun = p.arrange === "orbit" ? byBody.get("sun") : undefined;
|
|
67
68
|
for (const moon of resolved.placed) {
|
|
68
|
-
const host = BODIES[moon.body].parent === undefined ? undefined : byBody.get(BODIES[moon.body].parent);
|
|
69
|
+
const host = BODIES[moon.body].parent === undefined ? (moon.body === "sun" ? undefined : sun) : byBody.get(BODIES[moon.body].parent);
|
|
69
70
|
if (host === undefined)
|
|
70
71
|
continue;
|
|
71
72
|
const d = Math.hypot(moon.x - host.x, moon.y - host.y) * at.scale;
|
package/dist/pose.d.ts
CHANGED
|
@@ -50,12 +50,14 @@ export declare const drawnTilt: (facts: BodyFacts) => number;
|
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
52
|
export declare function portrait(id: BodyId, options?: PortraitOptions): Pose;
|
|
53
|
-
/**
|
|
54
|
-
export
|
|
53
|
+
/** The semi-axes of a body that is not a spheroid, as fractions of its radius: along its longitude 0, along its east and along its pole. */
|
|
54
|
+
export type Axes = readonly [number, number, number];
|
|
55
|
+
/** A longitude and latitude (degrees) as a point on the body, in the viewer's frame (unit radius): x right, y up, z toward the viewer. The flattening squeezes it along the pole; `axes` shape a triaxial body instead. */
|
|
56
|
+
export declare function surfacePoint(pose: Pose, lon: number, lat: number, flattening?: number, axes?: Axes): Vec3;
|
|
55
57
|
/** The outward normal's z at a longitude and latitude: positive when that point faces the viewer. */
|
|
56
|
-
export declare function facing(pose: Pose, lon: number, lat: number, flattening?: number): number;
|
|
57
|
-
/** The silhouette of a body: its half-width, half-height along the pole's direction and the angle of the pole from straight up, clockwise, in degrees. */
|
|
58
|
-
export declare function silhouette(pose: Pose, flattening: number): {
|
|
58
|
+
export declare function facing(pose: Pose, lon: number, lat: number, flattening?: number, axes?: Axes): number;
|
|
59
|
+
/** The silhouette of a body: its half-width, half-height along the pole's direction and the angle of the pole from straight up, clockwise, in degrees. With `axes` it is the outline of a triaxial body: its long half-width, its short one and the angle of the long one from straight right, clockwise. */
|
|
60
|
+
export declare function silhouette(pose: Pose, flattening: number, axes?: Axes): {
|
|
59
61
|
a: number;
|
|
60
62
|
b: number;
|
|
61
63
|
angle: number;
|
package/dist/pose.js
CHANGED
|
@@ -43,26 +43,40 @@ export function portrait(id, options = {}) {
|
|
|
43
43
|
const light = unit([-Math.sin(phase) * Math.cos(lift), Math.sin(lift), Math.cos(phase) * Math.cos(lift)]);
|
|
44
44
|
return { pole, meridian, light };
|
|
45
45
|
}
|
|
46
|
-
/** A longitude and latitude (degrees) as a point on the body, in the viewer's frame (unit radius): x right, y up, z toward the viewer. The flattening squeezes it along the pole. */
|
|
47
|
-
export function surfacePoint(pose, lon, lat, flattening = 0) {
|
|
46
|
+
/** A longitude and latitude (degrees) as a point on the body, in the viewer's frame (unit radius): x right, y up, z toward the viewer. The flattening squeezes it along the pole; `axes` shape a triaxial body instead. */
|
|
47
|
+
export function surfacePoint(pose, lon, lat, flattening = 0, axes) {
|
|
48
48
|
const cl = Math.cos(lat * RAD);
|
|
49
49
|
const east = cross(pose.pole, pose.meridian);
|
|
50
|
-
const x = cl * Math.cos(lon * RAD);
|
|
51
|
-
const y = cl * Math.sin(lon * RAD);
|
|
52
|
-
const z = Math.sin(lat * RAD) * (1 - flattening);
|
|
50
|
+
const x = cl * Math.cos(lon * RAD) * (axes?.[0] ?? 1);
|
|
51
|
+
const y = cl * Math.sin(lon * RAD) * (axes?.[1] ?? 1);
|
|
52
|
+
const z = Math.sin(lat * RAD) * (axes?.[2] ?? 1 - flattening);
|
|
53
53
|
return [pose.meridian[0] * x + east[0] * y + pose.pole[0] * z, pose.meridian[1] * x + east[1] * y + pose.pole[1] * z, pose.meridian[2] * x + east[2] * y + pose.pole[2] * z];
|
|
54
54
|
}
|
|
55
55
|
/** The outward normal's z at a longitude and latitude: positive when that point faces the viewer. */
|
|
56
|
-
export function facing(pose, lon, lat, flattening = 0) {
|
|
56
|
+
export function facing(pose, lon, lat, flattening = 0, axes) {
|
|
57
57
|
const cl = Math.cos(lat * RAD);
|
|
58
58
|
const east = cross(pose.pole, pose.meridian);
|
|
59
|
-
const x = cl * Math.cos(lon * RAD);
|
|
60
|
-
const y = cl * Math.sin(lon * RAD);
|
|
61
|
-
const z = Math.sin(lat * RAD) / (1 - flattening);
|
|
59
|
+
const x = (cl * Math.cos(lon * RAD)) / (axes?.[0] ?? 1);
|
|
60
|
+
const y = (cl * Math.sin(lon * RAD)) / (axes?.[1] ?? 1);
|
|
61
|
+
const z = Math.sin(lat * RAD) / (axes?.[2] ?? 1 - flattening);
|
|
62
62
|
return pose.meridian[2] * x + east[2] * y + pose.pole[2] * z;
|
|
63
63
|
}
|
|
64
|
-
/** The silhouette of a body: its half-width, half-height along the pole's direction and the angle of the pole from straight up, clockwise, in degrees. */
|
|
65
|
-
export function silhouette(pose, flattening) {
|
|
64
|
+
/** The silhouette of a body: its half-width, half-height along the pole's direction and the angle of the pole from straight up, clockwise, in degrees. With `axes` it is the outline of a triaxial body: its long half-width, its short one and the angle of the long one from straight right, clockwise. */
|
|
65
|
+
export function silhouette(pose, flattening, axes) {
|
|
66
|
+
if (axes !== undefined) {
|
|
67
|
+
const east = cross(pose.pole, pose.meridian);
|
|
68
|
+
const cols = [
|
|
69
|
+
[pose.meridian[0] * axes[0], pose.meridian[1] * axes[0]],
|
|
70
|
+
[east[0] * axes[1], east[1] * axes[1]],
|
|
71
|
+
[pose.pole[0] * axes[2], pose.pole[1] * axes[2]],
|
|
72
|
+
];
|
|
73
|
+
const sxx = cols.reduce((t, c) => t + c[0] * c[0], 0);
|
|
74
|
+
const syy = cols.reduce((t, c) => t + c[1] * c[1], 0);
|
|
75
|
+
const sxy = cols.reduce((t, c) => t + c[0] * c[1], 0);
|
|
76
|
+
const mean = (sxx + syy) / 2;
|
|
77
|
+
const spread = Math.sqrt(((sxx - syy) / 2) ** 2 + sxy * sxy);
|
|
78
|
+
return { a: Math.sqrt(mean + spread), b: Math.sqrt(Math.max(0, mean - spread)), angle: -0.5 * Math.atan2(2 * sxy, sxx - syy) * DEG };
|
|
79
|
+
}
|
|
66
80
|
const c = 1 - flattening;
|
|
67
81
|
const s = pose.pole[2];
|
|
68
82
|
return { a: 1, b: Math.sqrt(c * c * (1 - s * s) + s * s), angle: Math.atan2(pose.pole[0], pose.pole[1]) * DEG };
|
package/dist/scene/glsl.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const VERTEX = "\nvarying vec3 vObj;\nvarying vec3 vN;\nvarying vec3 vW;\nvoid main() {\n vObj = vec3(position.z, position.x, position.y);\n vN = normalize(normalMatrix * normal);\n vec4 w = modelMatrix * vec4(position, 1.0);\n vW = w.xyz;\n gl_Position = projectionMatrix * viewMatrix * w;\n}";
|
|
2
2
|
/** Noise, craters and the band palette, for the surface code of a body to call. */
|
|
3
|
-
export declare const PRELUDE = "\nfloat hash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }\nvec3 hash33(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.xxy + p.yxx) * p.zyx); }\nfloat vnoise(vec3 p) {\n vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f);\n return mix(mix(mix(hash13(i), hash13(i + vec3(1,0,0)), f.x), mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), f.x), f.y),\n mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), f.x), mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), f.x), f.y), f.z);\n}\nfloat fbm(vec3 p) { float a = 0.5; float s = 0.0; for (int i = 0; i < 5; i++) { s += a * vnoise(p); p = p * 2.03 + vec3(1.7, 9.2, 3.1); a *= 0.5; } return s; }\n// Craters: a bowl and a raised rim in each cell that has one. Returns a height, negative in a bowl.\nfloat craters(vec3 p, float freq, float amp, float seed) {\n vec3 q = p * freq + seed; vec3 ip = floor(q); float h = 0.0;\n for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) for (int z = -1; z <= 1; z++) {\n vec3 c = ip + vec3(float(x), float(y), float(z));\n float on = step(0.52, hash13(c + 3.7));\n vec3 ctr = c + hash33(c);\n float rad = mix(0.12, 0.36, hash13(c + 17.3));\n float d = length(q - ctr) / rad;\n float bowl = d < 1.0 ? (d * d - 1.0) : 0.0;\n float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 : 0.0;\n h += on * amp * rad * (bowl + rim);\n }\n return h;\n}\nfloat lon(vec3 p) { return atan(p.y, p.x); }\nfloat lat(vec3 p) { return asin(clamp(p.z, -1.0, 1.0)); }\nfloat wrapPi(float a) { return a - 6.2831853 * floor((a + 3.1415927) / 6.2831853); }\nvec3 dirOf(float lonDeg, float latDeg) { float lo = radians(lonDeg); float la = radians(latDeg); return vec3(cos(la) * cos(lo), cos(la) * sin(lo), sin(la)); }\n// One big round bowl with a rim, by longitude, latitude and angular radius in degrees. Negative inside.\nfloat bowl(vec3 p, float lonDeg, float latDeg, float radDeg, float depth) {\n float d = acos(clamp(dot(p, dirOf(lonDeg, latDeg)), -1.0, 1.0)) / radians(radDeg);\n return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3;\n}\n// A soft oval on the body, 1 inside and 0 outside, by longitude and latitude in degrees.\nfloat oval(vec3 p, float lon0, float lat0, float rx, float ry, float soft) {\n float dx = wrapPi(lon(p) - radians(lon0)) * cos(radians(lat0)) / radians(rx);\n float dy = (lat(p) - radians(lat0)) / radians(ry);\n return 1.0 - smoothstep(1.0 - soft, 1.0, sqrt(dx * dx + dy * dy));\n}";
|
|
3
|
+
export declare const PRELUDE = "\nfloat hash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }\nvec3 hash33(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.xxy + p.yxx) * p.zyx); }\nfloat vnoise(vec3 p) {\n vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f);\n return mix(mix(mix(hash13(i), hash13(i + vec3(1,0,0)), f.x), mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), f.x), f.y),\n mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), f.x), mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), f.x), f.y), f.z);\n}\n// Noise that knows how big a pixel is: an octave finer than the pixel can show is replaced by its average (0.5) instead of being sampled, so a small body is smooth and not a spray of pixel-sized specks.\nfloat fbm(vec3 p) { float fw = length(fwidth(p)); float a = 0.5; float s = 0.0; for (int i = 0; i < 5; i++) { s += a * mix(0.5, vnoise(p), 1.0 - smoothstep(0.3, 0.7, fw)); p = p * 2.03 + vec3(1.7, 9.2, 3.1); fw *= 2.03; a *= 0.5; } return s; }\n// Craters: a bowl and a raised rim in each cell that has one. Returns a height, negative in a bowl. A crater smaller than about two pixels fades out (it would only be noise).\nfloat craters(vec3 p, float freq, float amp, float seed) {\n vec3 q = p * freq + seed; vec3 ip = floor(q); float h = 0.0;\n float fw = length(fwidth(q));\n for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) for (int z = -1; z <= 1; z++) {\n vec3 c = ip + vec3(float(x), float(y), float(z));\n float on = step(0.52, hash13(c + 3.7));\n vec3 ctr = c + hash33(c);\n float rad = mix(0.12, 0.36, hash13(c + 17.3));\n float d = length(q - ctr) / rad;\n float bowl = d < 1.0 ? (d * d - 1.0) : 0.0;\n float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d) : 0.0;\n h += on * amp * rad * (bowl + rim) * (1.0 - smoothstep(0.35, 0.9, fw / rad));\n }\n return h;\n}\nfloat lon(vec3 p) { return atan(p.y, p.x); }\nfloat lat(vec3 p) { return asin(clamp(p.z, -1.0, 1.0)); }\nfloat wrapPi(float a) { return a - 6.2831853 * floor((a + 3.1415927) / 6.2831853); }\nvec3 dirOf(float lonDeg, float latDeg) { float lo = radians(lonDeg); float la = radians(latDeg); return vec3(cos(la) * cos(lo), cos(la) * sin(lo), sin(la)); }\n// One big round bowl with a rim, by longitude, latitude and angular radius in degrees. Negative inside.\nfloat bowl(vec3 p, float lonDeg, float latDeg, float radDeg, float depth) {\n float d = acos(clamp(dot(p, dirOf(lonDeg, latDeg)), -1.0, 1.0)) / radians(radDeg);\n return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d);\n}\n// A soft oval on the body, 1 inside and 0 outside, by longitude and latitude in degrees.\nfloat oval(vec3 p, float lon0, float lat0, float rx, float ry, float soft) {\n float dx = wrapPi(lon(p) - radians(lon0)) * cos(radians(lat0)) / radians(rx);\n float dy = (lat(p) - radians(lat0)) / radians(ry);\n return 1.0 - smoothstep(1.0 - soft, 1.0, sqrt(dx * dx + dy * dy));\n}";
|
|
4
4
|
/** The fragment shader of a body: the surface code goes between the prelude and `main`. */
|
|
5
5
|
export declare function bodyFragment(surface: string, painted: boolean, world?: boolean): string;
|
|
6
6
|
/** A painted body's surface code: the map by longitude and latitude, with the seam at 180 degrees left alone. */
|
|
@@ -19,4 +19,4 @@ export declare const RING_FRAGMENT_WORLD: string;
|
|
|
19
19
|
export declare const GLOW_VERTEX = "\nvarying vec2 vP;\nvoid main() { vP = position.xy; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }";
|
|
20
20
|
export declare const GLOW_FRAGMENT = "\nprecision highp float;\nvarying vec2 vP;\nuniform vec3 uColour;\nvoid main() {\n float r = length(vP);\n if (r < 0.98) discard;\n float a = exp(-(r - 1.0) * 2.4) * 0.62;\n float streak = 0.82 + 0.18 * sin(atan(vP.y, vP.x) * 9.0 + r * 3.0);\n a *= streak * smoothstep(3.0, 1.6, r);\n gl_FragColor = vec4(uColour * a, a);\n}";
|
|
21
21
|
/** A cheap lit ball for a moon seen small beside its planet. */
|
|
22
|
-
export declare const BALL_FRAGMENT = "\nprecision highp float;\nuniform vec3 uLight;\nuniform vec3 uColour;\nvarying vec3 vObj;\nvarying vec3 vN;\nvarying vec3 vW;\n\nfloat hash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }\nvec3 hash33(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.xxy + p.yxx) * p.zyx); }\nfloat vnoise(vec3 p) {\n vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f);\n return mix(mix(mix(hash13(i), hash13(i + vec3(1,0,0)), f.x), mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), f.x), f.y),\n mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), f.x), mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), f.x), f.y), f.z);\n}\nfloat fbm(vec3 p) { float a = 0.5; float s = 0.0; for (int i = 0; i < 5; i++) { s += a * vnoise(p); p = p * 2.03 + vec3(1.7, 9.2, 3.1); a *= 0.5; } return s; }\n// Craters: a bowl and a raised rim in each cell that has one. Returns a height, negative in a bowl.\nfloat craters(vec3 p, float freq, float amp, float seed) {\n vec3 q = p * freq + seed; vec3 ip = floor(q); float h = 0.0;\n for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) for (int z = -1; z <= 1; z++) {\n vec3 c = ip + vec3(float(x), float(y), float(z));\n float on = step(0.52, hash13(c + 3.7));\n vec3 ctr = c + hash33(c);\n float rad = mix(0.12, 0.36, hash13(c + 17.3));\n float d = length(q - ctr) / rad;\n float bowl = d < 1.0 ? (d * d - 1.0) : 0.0;\n float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 : 0.0;\n h += on * amp * rad * (bowl + rim);\n }\n return h;\n}\nfloat lon(vec3 p) { return atan(p.y, p.x); }\nfloat lat(vec3 p) { return asin(clamp(p.z, -1.0, 1.0)); }\nfloat wrapPi(float a) { return a - 6.2831853 * floor((a + 3.1415927) / 6.2831853); }\nvec3 dirOf(float lonDeg, float latDeg) { float lo = radians(lonDeg); float la = radians(latDeg); return vec3(cos(la) * cos(lo), cos(la) * sin(lo), sin(la)); }\n// One big round bowl with a rim, by longitude, latitude and angular radius in degrees. Negative inside.\nfloat bowl(vec3 p, float lonDeg, float latDeg, float radDeg, float depth) {\n float d = acos(clamp(dot(p, dirOf(lonDeg, latDeg)), -1.0, 1.0)) / radians(radDeg);\n return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3;\n}\n// A soft oval on the body, 1 inside and 0 outside, by longitude and latitude in degrees.\nfloat oval(vec3 p, float lon0, float lat0, float rx, float ry, float soft) {\n float dx = wrapPi(lon(p) - radians(lon0)) * cos(radians(lat0)) / radians(rx);\n float dy = (lat(p) - radians(lat0)) / radians(ry);\n return 1.0 - smoothstep(1.0 - soft, 1.0, sqrt(dx * dx + dy * dy));\n}\nvoid main() {\n vec3 n = normalize(vN);\n float shade = 0.82 + 0.3 * (fbm(normalize(vObj) * 4.0) - 0.5);\n float diff = max(dot(n, uLight), 0.0);\n vec3 c = pow(uColour * shade, vec3(2.2)) * (0.04 + 1.1 * diff);\n gl_FragColor = vec4(pow(c, vec3(1.0 / 2.2)), 1.0);\n}";
|
|
22
|
+
export declare const BALL_FRAGMENT = "\nprecision highp float;\nuniform vec3 uLight;\nuniform vec3 uColour;\nvarying vec3 vObj;\nvarying vec3 vN;\nvarying vec3 vW;\n\nfloat hash13(vec3 p) { p = fract(p * 0.1031); p += dot(p, p.zyx + 31.32); return fract((p.x + p.y) * p.z); }\nvec3 hash33(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.xxy + p.yxx) * p.zyx); }\nfloat vnoise(vec3 p) {\n vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f);\n return mix(mix(mix(hash13(i), hash13(i + vec3(1,0,0)), f.x), mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), f.x), f.y),\n mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), f.x), mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), f.x), f.y), f.z);\n}\n// Noise that knows how big a pixel is: an octave finer than the pixel can show is replaced by its average (0.5) instead of being sampled, so a small body is smooth and not a spray of pixel-sized specks.\nfloat fbm(vec3 p) { float fw = length(fwidth(p)); float a = 0.5; float s = 0.0; for (int i = 0; i < 5; i++) { s += a * mix(0.5, vnoise(p), 1.0 - smoothstep(0.3, 0.7, fw)); p = p * 2.03 + vec3(1.7, 9.2, 3.1); fw *= 2.03; a *= 0.5; } return s; }\n// Craters: a bowl and a raised rim in each cell that has one. Returns a height, negative in a bowl. A crater smaller than about two pixels fades out (it would only be noise).\nfloat craters(vec3 p, float freq, float amp, float seed) {\n vec3 q = p * freq + seed; vec3 ip = floor(q); float h = 0.0;\n float fw = length(fwidth(q));\n for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) for (int z = -1; z <= 1; z++) {\n vec3 c = ip + vec3(float(x), float(y), float(z));\n float on = step(0.52, hash13(c + 3.7));\n vec3 ctr = c + hash33(c);\n float rad = mix(0.12, 0.36, hash13(c + 17.3));\n float d = length(q - ctr) / rad;\n float bowl = d < 1.0 ? (d * d - 1.0) : 0.0;\n float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d) : 0.0;\n h += on * amp * rad * (bowl + rim) * (1.0 - smoothstep(0.35, 0.9, fw / rad));\n }\n return h;\n}\nfloat lon(vec3 p) { return atan(p.y, p.x); }\nfloat lat(vec3 p) { return asin(clamp(p.z, -1.0, 1.0)); }\nfloat wrapPi(float a) { return a - 6.2831853 * floor((a + 3.1415927) / 6.2831853); }\nvec3 dirOf(float lonDeg, float latDeg) { float lo = radians(lonDeg); float la = radians(latDeg); return vec3(cos(la) * cos(lo), cos(la) * sin(lo), sin(la)); }\n// One big round bowl with a rim, by longitude, latitude and angular radius in degrees. Negative inside.\nfloat bowl(vec3 p, float lonDeg, float latDeg, float radDeg, float depth) {\n float d = acos(clamp(dot(p, dirOf(lonDeg, latDeg)), -1.0, 1.0)) / radians(radDeg);\n return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d);\n}\n// A soft oval on the body, 1 inside and 0 outside, by longitude and latitude in degrees.\nfloat oval(vec3 p, float lon0, float lat0, float rx, float ry, float soft) {\n float dx = wrapPi(lon(p) - radians(lon0)) * cos(radians(lat0)) / radians(rx);\n float dy = (lat(p) - radians(lat0)) / radians(ry);\n return 1.0 - smoothstep(1.0 - soft, 1.0, sqrt(dx * dx + dy * dy));\n}\nvoid main() {\n vec3 n = normalize(vN);\n float shade = 0.82 + 0.3 * (fbm(normalize(vObj) * 4.0) - 0.5);\n float diff = max(dot(n, uLight), 0.0);\n vec3 c = pow(uColour * shade, vec3(2.2)) * (0.04 + 1.1 * diff);\n gl_FragColor = vec4(pow(c, vec3(1.0 / 2.2)), 1.0);\n}";
|
package/dist/scene/glsl.js
CHANGED
|
@@ -19,10 +19,12 @@ float vnoise(vec3 p) {
|
|
|
19
19
|
return mix(mix(mix(hash13(i), hash13(i + vec3(1,0,0)), f.x), mix(hash13(i + vec3(0,1,0)), hash13(i + vec3(1,1,0)), f.x), f.y),
|
|
20
20
|
mix(mix(hash13(i + vec3(0,0,1)), hash13(i + vec3(1,0,1)), f.x), mix(hash13(i + vec3(0,1,1)), hash13(i + vec3(1,1,1)), f.x), f.y), f.z);
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Noise that knows how big a pixel is: an octave finer than the pixel can show is replaced by its average (0.5) instead of being sampled, so a small body is smooth and not a spray of pixel-sized specks.
|
|
23
|
+
float fbm(vec3 p) { float fw = length(fwidth(p)); float a = 0.5; float s = 0.0; for (int i = 0; i < 5; i++) { s += a * mix(0.5, vnoise(p), 1.0 - smoothstep(0.3, 0.7, fw)); p = p * 2.03 + vec3(1.7, 9.2, 3.1); fw *= 2.03; a *= 0.5; } return s; }
|
|
24
|
+
// Craters: a bowl and a raised rim in each cell that has one. Returns a height, negative in a bowl. A crater smaller than about two pixels fades out (it would only be noise).
|
|
24
25
|
float craters(vec3 p, float freq, float amp, float seed) {
|
|
25
26
|
vec3 q = p * freq + seed; vec3 ip = floor(q); float h = 0.0;
|
|
27
|
+
float fw = length(fwidth(q));
|
|
26
28
|
for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) for (int z = -1; z <= 1; z++) {
|
|
27
29
|
vec3 c = ip + vec3(float(x), float(y), float(z));
|
|
28
30
|
float on = step(0.52, hash13(c + 3.7));
|
|
@@ -30,8 +32,8 @@ float craters(vec3 p, float freq, float amp, float seed) {
|
|
|
30
32
|
float rad = mix(0.12, 0.36, hash13(c + 17.3));
|
|
31
33
|
float d = length(q - ctr) / rad;
|
|
32
34
|
float bowl = d < 1.0 ? (d * d - 1.0) : 0.0;
|
|
33
|
-
float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 : 0.0;
|
|
34
|
-
h += on * amp * rad * (bowl + rim);
|
|
35
|
+
float rim = d >= 1.0 ? exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d) : 0.0;
|
|
36
|
+
h += on * amp * rad * (bowl + rim) * (1.0 - smoothstep(0.35, 0.9, fw / rad));
|
|
35
37
|
}
|
|
36
38
|
return h;
|
|
37
39
|
}
|
|
@@ -42,7 +44,7 @@ vec3 dirOf(float lonDeg, float latDeg) { float lo = radians(lonDeg); float la =
|
|
|
42
44
|
// One big round bowl with a rim, by longitude, latitude and angular radius in degrees. Negative inside.
|
|
43
45
|
float bowl(vec3 p, float lonDeg, float latDeg, float radDeg, float depth) {
|
|
44
46
|
float d = acos(clamp(dot(p, dirOf(lonDeg, latDeg)), -1.0, 1.0)) / radians(radDeg);
|
|
45
|
-
return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3;
|
|
47
|
+
return d < 1.0 ? depth * (d * d - 1.0) : depth * exp(-pow((d - 1.0) * 5.0, 2.0)) * 0.3 * smoothstep(1.0, 1.08, d);
|
|
46
48
|
}
|
|
47
49
|
// A soft oval on the body, 1 inside and 0 outside, by longitude and latitude in degrees.
|
|
48
50
|
float oval(vec3 p, float lon0, float lat0, float rx, float ry, float soft) {
|
|
@@ -90,7 +92,9 @@ void main() {
|
|
|
90
92
|
float hx = dFdx(s.a); float hy = dFdy(s.a);
|
|
91
93
|
vec3 grad = cross(dpy, n) * hx + cross(n, dpx) * hy;
|
|
92
94
|
float det = dot(dpx, cross(dpy, n));
|
|
93
|
-
|
|
95
|
+
// relief is worked out from differences between neighbouring pixels, which cannot show a bump only a few pixels wide: on a small disc it fades out and the colour alone carries the craters
|
|
96
|
+
float reliefLod = 1.0 - smoothstep(0.008, 0.024, length(fwidth(p)));
|
|
97
|
+
vec3 nb = normalize(abs(det) * n - sign(det) * uRelief * reliefLod * grad * 22.0);
|
|
94
98
|
float d = dot(nb, uLight);
|
|
95
99
|
float diff = clamp((d + uWrap) / (1.0 + uWrap), 0.0, 1.0) * smoothstep(-0.02 - uWrap * 0.6, 0.1 + uWrap * 0.8, dg);
|
|
96
100
|
if (uRingOn > 0.5) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|