@johnmorrisdotca/wakusei-planets 0.7.0 → 0.11.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 (47) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/NOTICE.md +16 -1
  3. package/README.md +6 -6
  4. package/dist/bodies/all.d.ts +2 -0
  5. package/dist/bodies/all.js +2 -0
  6. package/dist/bodies/dwarfs.d.ts +1 -0
  7. package/dist/bodies/dwarfs.js +31 -0
  8. package/dist/bodies/moons-dwarf.d.ts +1 -0
  9. package/dist/bodies/moons-dwarf.js +6 -0
  10. package/dist/bodies/moons-neptune.js +1 -0
  11. package/dist/bodies/moons-pluto.js +1 -1
  12. package/dist/bodies/moons-saturn.js +3 -2
  13. package/dist/data/dwarf.d.ts +16 -0
  14. package/dist/data/dwarf.js +24 -0
  15. package/dist/data/orbits.d.ts +2 -2
  16. package/dist/data/orbits.js +4 -0
  17. package/dist/element.d.ts +4 -2
  18. package/dist/element.js +4 -0
  19. package/dist/embed.d.ts +1 -1
  20. package/dist/embed.js +5 -3
  21. package/dist/ephemeris.d.ts +6 -6
  22. package/dist/ephemeris.js +33 -12
  23. package/dist/facts.d.ts +44 -6
  24. package/dist/facts.js +32 -3
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/line.js +7 -4
  28. package/dist/portrait/model.d.ts +2 -2
  29. package/dist/portrait/model.js +138 -5
  30. package/dist/portrait/overlay.js +2 -1
  31. package/dist/pose.d.ts +7 -5
  32. package/dist/pose.js +25 -11
  33. package/dist/textures/ceres-1024.webp +0 -0
  34. package/dist/textures/ceres-512.webp +0 -0
  35. package/dist/textures/charon-1024.webp +0 -0
  36. package/dist/textures/charon-512.webp +0 -0
  37. package/dist/textures/dione-1024.webp +0 -0
  38. package/dist/textures/dione-512.webp +0 -0
  39. package/dist/textures/iapetus-1024.webp +0 -0
  40. package/dist/textures/iapetus-512.webp +0 -0
  41. package/dist/textures/tethys-1024.webp +0 -0
  42. package/dist/textures/tethys-512.webp +0 -0
  43. package/dist/textures/triton-1024.webp +0 -0
  44. package/dist/textures/triton-512.webp +0 -0
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +11 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 - 2026-11-03
4
+
5
+ The drag rule, checked with the family's own check.
6
+
7
+ - **The surface follows the pointer, exactly** (MODULES-224): the demo tells the shared view controls the radius of the body's disc (`grab-radius`, kept up to date as the view is resized or scaled), so a drag of a few pixels moves the ground at the middle of the body by as many pixels, and the touched point stays under the pointer. New `discRadius` on `<wakusei-body>` (the disc's radius on the screen, in CSS pixels).
8
+ - **The check** is kazoku-ui's shared `e2e/ground.mjs` (copied unchanged from its `templates/e2e-ground.mjs`): in Mars (line, realistic and painted), Venus and the Moon a drag right moves the ground right, a drag up moves it up, the arrow keys agree and Auto-turn goes the right way; exact grab is checked in painted Mars and the Moon.
9
+ - **The documentation site is the family's of 2026-11-03** (MODULES-331): a seeded sample of its pages is read in the browser, and the guides show their pictures as real images (`![..](../images/..)`) instead of links, in English and Japanese.
10
+ - The freeze test that counts what the page keeps now counts after two rounds of changes, not one: on a slow runner the first round was still making the rows' pictures, so the count rose once and then stayed flat (24, 24, 24 with the processor slowed four times); a real leak would keep rising.
11
+ - **The Turn button goes the body's real way** (MODULES-203): Mars turns to the right and Venus, which turns backwards, to the left (it turned every body the same way before); a test per direction.
12
+
13
+ ## 0.10.0 - 2026-11-03
14
+
15
+ Painted maps for five more moons.
16
+
17
+ - **Tethys, Dione, Iapetus, Triton and Charon are painted** (MODULES-220), from public-domain maps: Cassini's global colour maps of Tethys, Dione and Iapetus (Paul Schenk, PIA18439, PIA18434, PIA18436), Voyager 2's global colour mosaic of Triton (PIA18668) and New Horizons' global map of Charon. Every source file is public domain by its Wikimedia Commons file page, and `NOTICE.md` has each one's credit and what was done to it, including where the map was centred (turned to match known features: Tethys's crater Odysseus, Iapetus's dark Cassini Regio; Triton's centre is from the USGS label of the same mosaic) and the unmapped north of Triton and south of Charon, which are continued smoothly from the real rows.
18
+ - The pictures add 0.45 MB to the package (the 512 and 1024 pixel widths; the 2048 ones are fetched when a view is zoomed in), which makes the pictures 1.94 MB; the test that holds their size now allows 2 MB.
19
+
20
+ ## 0.9.0 - 2026-11-03
21
+
22
+ The other dwarf planets.
23
+
24
+ - **Ceres, Orcus, Haumea, Quaoar, Makemake, Gonggong, Eris and Sedna** (MODULES-232), in the line, realistic and painted looks (`DWARF_IDS`, `DwarfId`, `BODIES.eris`...), and the moons whose orbits are published with a date: **Vanth** (Orcus), **Hiʻiaka and Namaka** (Haumea) and **Dysnomia** (Eris). Weywot, Xiangliu and S/2015 (136472) 1 are left out: no orbit with a published phase exists.
25
+ - **Where they are**: `heliocentric()` takes a dwarf planet too, from JPL's Small-Body Database osculating elements of June 2026, run as a plain ellipse. Against JPL Horizons on 2026-11-03 all five checked are within 0.0002 au (a test holds it). The moons' orbits are from Holler et al. 2021 (Dysnomia), Ragozzine & Brown 2009 (Hiʻiaka, Namaka) and Brown et al. 2010 (Vanth); `NOTICE.md` has the sources and says which numbers are estimates (poles that have not been measured are taken along the orbit's pole; the shaded surfaces other than Ceres's are drawn from colours alone).
26
+ - **Haumea and Quaoar are long shapes** (1161 x 852 x 513 km, turning every 3.9 hours; 1241 x 1093 x 1023 km): the line look now draws the outline of a body with `axesKm` as the ellipse it really shows, and its features on that shape (Phobos, Deimos, Proteus, Nix and Hydra too).
27
+ - **Ceres is painted** from Dawn's colourised global map (public domain; the incomplete colour at high latitudes is made the surface's own grey).
28
+ - **The picker has three groups** (MODULES-204): the Sun and the planets, the dwarf planets and the well-known moons (the Moon, Phobos, Deimos, Io, Europa, Ganymede, Callisto, Enceladus, Titan, Triton, Charon), each a click away; the group follows the body chosen and the address keeps the choice. The portrait builder and the code builder have a dwarf planets group too.
29
+ - **Tests that every body turns about its real axis the right way** (MODULES-203): the rotation rate matches the period, and the spin axis of each planet and dwarf planet makes the published axial tilt with its orbit's pole, backwards for Venus, Uranus and Pluto.
30
+ - New entry points `@johnmorrisdotca/wakusei-planets/dwarfs` and `/moons-dwarf`; `/all` has both. `DWARF_IDS`, `DwarfId` and `HostId` (a planet or dwarf planet a moon can go round) are exported; `BODIES` and `BODY_IDS` have 12 more bodies, and the Portrait builder's order puts the dwarf planets after Pluto.
31
+ - The painted pictures of Ceres add 0.13 MB to the package (512 and 1024 pixels wide).
32
+
33
+ ## 0.8.0 - 2026-11-03
34
+
35
+ The portrait builder at true orbit distance.
36
+
37
+ - **True orbit distance** (MODULES-283): a fourth arrangement beside row, arc and grid. Each moon is as far from its planet, and each planet from the Sun, as it really is, with the orbits drawn as circles, and the view backed off to hold them all. With a date set, every body is where it is then, seen from above the ecliptic; with none, they are lined up from the Sun along one side. `arrange: "orbit"` in the scene, `?scene=arrange:orbit~...` in the address.
38
+ - **A link from the demo** ("At true orbit distance", under the buttons that show bodies together) opens the same bodies in the portrait builder at true orbit distance, in the look that is chosen.
39
+
3
40
  ## 0.7.0 - 2026-11-03
4
41
 
5
42
  The portrait builder: any bodies, one picture.
package/NOTICE.md CHANGED
@@ -11,6 +11,15 @@ No picture is used by the line and shaded looks: they are drawn from numbers.
11
11
  - **Moons' orbits**: JPL Solar System Dynamics, "Planetary Satellite Mean Elements" (https://ssd.jpl.nasa.gov/sats/elem/, read 2026-11-02), made into `src/data/orbits.ts` by `scripts/make-moon-data.py`. Credit: NASA/JPL-Caltech; the elements are from R. A. Jacobson and colleagues' ephemerides cited on that page.
12
12
  - **The Moon's position**: the largest terms of the series in Jean Meeus, "Astronomical Algorithms" (2nd edition), chapter 47, written out here as numbers; the mean arguments are the standard ones.
13
13
 
14
+ ## The dwarf planets
15
+
16
+ Ceres, Orcus, Haumea, Quaoar, Makemake, Gonggong, Eris and Sedna (Pluto is with the planets) and the moons Vanth, Hiʻiaka, Namaka and Dysnomia. Values read on 2026-11-03.
17
+
18
+ - **Where they are**: NASA/JPL Small-Body Database, osculating elements at JD 2461200.5 (https://ssd.jpl.nasa.gov/api/sbdb.api), run as a plain ellipse (`src/data/dwarf.ts`): good for a few years either side of June 2026, and Ceres, pulled by Jupiter, drifts most.
19
+ - **Sizes and spin**: Ceres (966.2 x 962.0 x 891.8 km) and its pole: Ermakov et al. 2017 (as listed in Wikipedia's Ceres article); its rotation: JPL SBDB (9.074170 h). Haumea: Ortiz et al. 2017, Nature 550:219 (semi-axes 1161 x 852 x 513 km), rotation 3.915341 h from Santos-Sanz et al. 2017, A&A 604:A95, pole from Kondratyev & Kornoukhov 2018, MNRAS 478:3159 (RA 282.6, Dec -13.0). Eris: radius 1163 km, Sicardy et al. 2011; it keeps one face to Dysnomia, so its rotation is Dysnomia's orbital period, Szakats et al. 2023, A&A 669:L3. Makemake: radius 715 km, Brown 2013; rotation 22.8266 h, Hromakina et al. 2019. Gonggong: radius 615 km and rotation 22.4 h, Kiss et al. 2019, Icarus. Quaoar: 1241.4 x 1093.0 x 1023.0 km and 17.6788 h, Margoti et al. 2026 and Pereira et al. 2023. Sedna: radius 453 km (Lellouch et al. 2013, TNOs are Cool IX, which allows far more) and 10.273 h, Gaudi et al. 2005. Orcus: radius 455 km, Brown & Butler 2018, rotation 13.188 h (JPL SBDB; 7 to 21 h in the literature). Wikipedia's articles on each body were the finding aid; the papers are the sources.
20
+ - **Moons' orbits**: Dysnomia, Holler et al. 2021, Icarus 355:114130, Table 2 (arXiv 2009.13733). Hiʻiaka and Namaka, Ragozzine & Brown 2009, AJ 137:4766, Table 2 (osculating elements of 2008; the two moons pull on each other, so the position years later is rough). Vanth, Brown et al. 2010, AJ 139:2700, Table 2, solution 1 of two mirror solutions. Radii: Dysnomia 308 km (Brown & Butler 2023), Vanth 221 km (Sickafoose et al. 2018, occultation), Hiʻiaka and Namaka from the Haumea article's listed diameters (370 and 150 km). Weywot, Xiangliu and Makemake's moon S/2015 (136472) 1 are left out: no orbit with a published phase.
21
+ - **Estimated, not measured** (the pictures say so only here): these bodies' poles are not measured, so each is drawn with its spin axis along its orbit's pole (Quaoar, Makemake, Gonggong, Sedna), along Dysnomia's orbit pole (Eris) or along the far end of Vanth's orbit pole, so that it spins forwards (Orcus). Hiʻiaka's and Namaka's spin is unknown and they are drawn still. Dysnomia and Vanth are assumed to keep one face to their planet. Every drawn feature on the line look of a dwarf planet except Ceres's Occator crater and Haumea's long axis is a generic crater, and so is each shaded (realistic) look: only the colour (from the published colour indices and albedos) and the sizes are from numbers. Ceres, Haumea and Dysnomia's shapes are from the sources above. Only Ceres has a painted look.
22
+
14
23
  The tests compare the Galilean moons and four planets with astronomy-engine (MIT, https://github.com/cosinekitty/astronomy), a development dependency that is never shipped.
15
24
 
16
25
  ## The Earth
@@ -44,6 +53,12 @@ NASA says its content "generally are not subject to copyright in the United Stat
44
53
  | `rhea-*.webp` | Rhea | [Rhea Color Map.jpg](https://commons.wikimedia.org/wiki/File:Rhea_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18438), turned half way round so longitude 0 is in the middle (the source does not say where it is centred; the dark trailing side is put at 90 degrees east); the colours are false-colour (infrared, green, ultraviolet) and are turned about half way to grey |
45
54
  | `enceladus-*.webp` | Enceladus | [Enceladus Color Map.jpg](https://commons.wikimedia.org/wiki/File:Enceladus_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18435), turned half way round the same way as Rhea's (the source does not say where it is centred; the fractured trailing terrain is put at 90 degrees east); the colours are false-colour (the blue of the tiger stripes is ultraviolet) and are turned most of the way to grey |
46
55
  | `mimas-*.webp` | Mimas | [Map of Mimas 2010-02 PIA12780.jpg](https://commons.wikimedia.org/wiki/File:Map_of_Mimas_2010-02_PIA12780.jpg) | Public domain | NASA/JPL/Space Science Institute | Cassini ISS map of February 2010 (PIA12780), cropped inside its frame and labels; Herschel is at 110 degrees west as its own scale says |
56
+ | `ceres-*.webp` | Ceres | [PIA20354-Ceres-DwarfPlanet-MercatorMap-HAMO-20160322-0DegreeLongitude.jpg](https://commons.wikimedia.org/wiki/File:PIA20354-Ceres-DwarfPlanet-MercatorMap-HAMO-20160322-0DegreeLongitude.jpg) | Public domain | NASA/JPL-Caltech/UCLA/MPS/DLR/IDA | Dawn's colourised global map of Ceres (PIA20354, 140 m a pixel), longitude 0 in the middle as the file says (Occator's bright spots fall at 239 degrees east); the green and yellow high latitudes, where Dawn's colour coverage is incomplete (the map says so), are made the surface's own grey keeping their detail, and the last rows with no data repeat the nearest real ones; the colour is turned most of the way to grey |
57
+ | `tethys-*.webp` | Tethys | [Tethys Color Map.jpg](https://commons.wikimedia.org/wiki/File:Tethys_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18439, Paul Schenk), turned half way round so longitude 0 is in the middle (the source does not say where it is centred; Odysseus, at 129 degrees west, falls at 131 degrees west and the dark trailing side at 90 degrees east); the colours are enhanced (ultraviolet to infrared) and are turned about half way to grey |
58
+ | `dione-*.webp` | Dione | [Dione Color Map.jpg](https://commons.wikimedia.org/wiki/File:Dione_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18434), turned half way round the same way as Tethys's (the source does not say where it is centred; the dark trailing side with its wispy fractures is put at 90 degrees east); the colours are enhanced and are turned about half way to grey |
59
+ | `iapetus-*.webp` | Iapetus | [Iapetus Color Map.jpg](https://commons.wikimedia.org/wiki/File:Iapetus_Color_Map.jpg) | Public domain | NASA/JPL-Caltech/Space Science Institute/Lunar and Planetary Institute | Cassini global colour map (PIA18436), turned half way round the same way (the source does not say where it is centred; the dark leading side, Cassini Regio, is put at 270 degrees east, 90 degrees west); the colours are enhanced and are turned a third of the way to grey |
60
+ | `triton-*.webp` | Triton | [Triton map no grid.jpg](https://commons.wikimedia.org/wiki/File:Triton_map_no_grid.jpg) | Public domain | NASA/JPL-Caltech/Lunar & Planetary Institute (Paul Schenk; Voyager 2 images) | Voyager 2 global colour mosaic (PIA18668, 600 m a pixel, enhanced colour), as it is: USGS Astrogeology's label of the same mosaic (14138 samples wide) says the map is centred on longitude 0, east positive. Voyager never saw the north (it was in winter's dark), so the black there is continued smoothly from the colour just inside it and fades to the mean colour; the colours are turned a little under half way to grey |
61
+ | `charon-*.webp` | Charon | [Charon map iau1803c.jpg](https://commons.wikimedia.org/wiki/File:Charon_map_iau1803c.jpg) | Public domain | NASA/Johns Hopkins University Applied Physics Laboratory/Southwest Research Institute (via the IAU) | New Horizons global map of Charon (iau1803c, no labels or grid), as it is, with longitude 0 in the middle (the source does not say; the sharp hemisphere New Horizons saw best is the one that faces Pluto, which is longitude 0); the south, in the polar night New Horizons never saw, is continued from the last real rows and fades to the mean grey |
47
62
  | `saturn-ring-2048.webp` | Saturn's rings | [Saturn's rings in visible light and radio.jpg](https://commons.wikimedia.org/wiki/File:Saturn's_rings_in_visible_light_and_radio.jpg) | Public domain | NASA / JPL / Space Science Institute | Cassini's natural-colour mosaic of the lit rings (PIA07874, from PIA06175), read along one radius into a strip of 2048 colours and opacities from 74,794 to 137,035 km. The radii are fitted to five edges the picture shows (the B ring's inner edge, the two edges of the Cassini Division, the Encke gap and the A ring's outer edge), each within 300 km. The opacity is taken from the brightness, and the colour balance is moved to match the Saturn portrait above; the F ring and the D ring are not in the picture |
48
63
 
49
- The package does not ship the 2048 pixel widths: `TEXTURE_FAR_BASE` fetches them from this repository by jsDelivr's GitHub address when a view is zoomed in. The 512 and 1024 pixel widths of every picture, and the ring strip, are 1.31 MB in the package (the nine of 0.2.0 were 0.53 MB). Tethys, Dione, Iapetus, Triton and Charon have no picture: Cassini's maps of the first three are not made yet, the source of Triton's map does not give its longitudes, and the only clean public-domain map of Charon is under a grid of labels, so those five keep their drawn look.
64
+ The package does not ship the 2048 pixel widths: `TEXTURE_FAR_BASE` fetches them from this repository by jsDelivr's GitHub address when a view is zoomed in. The 512 and 1024 pixel widths of every picture, and the ring strip, are 1.94 MB in the package (the nine of 0.2.0 were 0.53 MB; Ceres adds 0.13 MB and Tethys, Dione, Iapetus, Triton and Charon 0.45 MB). Where a source does not say where its map is centred, its row says what the picture was matched to.
package/README.md CHANGED
@@ -13,9 +13,9 @@ A clean line drawing, a shaded realistic drawing, or a painted surface map, with
13
13
 
14
14
  <p align="center"><a href="https://johnmorrisdotca.github.io/wakusei-planets/"><strong>Open the demo →</strong></a> · <a href="https://johnmorrisdotca.github.io/wakusei-planets/embed.html">Embed page</a> · <a href="https://johnmorrisdotca.github.io/wakusei-planets/docs/">Docs</a></p>
15
15
 
16
- <p align="center"><img alt="Jupiter and Saturn drawn in the realistic look, and Saturn as a line drawing" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.7.0/docs/images/hero-desk.png" width="640"></p>
16
+ <p align="center"><img alt="Jupiter and Saturn drawn in the realistic look, and Saturn as a line drawing" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.11.0/docs/images/hero-desk.png" width="640"></p>
17
17
 
18
- <p align="center"><img alt="Every body at one scale, the Earth among them: each small body has a magnified picture joined by a line, and the Sun shows only its edge" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.7.0/docs/images/lineup-desk.png" width="640"></p>
18
+ <p align="center"><img alt="Every body at one scale, the Earth among them: each small body has a magnified picture joined by a line, and the Sun shows only its edge" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.11.0/docs/images/lineup-desk.png" width="640"></p>
19
19
 
20
20
  ## One minute
21
21
 
@@ -79,7 +79,7 @@ A bundled app imports it: `import "@johnmorrisdotca/wakusei-planets/element"` (a
79
79
 
80
80
  ## Features
81
81
 
82
- - The Sun, the eight planets, Pluto and 25 moons, each with its real size, flattening, axial tilt, pole and rotation as data.
82
+ - The Sun, the eight planets, Pluto, eight more dwarf planets and 29 moons, each with its real size, flattening, axial tilt, pole and rotation as data.
83
83
  - Three looks: a clean `line` drawing (an SVG string), a shaded `realistic` sphere worked out in a shader, and a `painted` surface map from public-domain imagery.
84
84
  - The sky for any date and time: a body's pole, rotation, phase and ring opening as the Earth sees it, and a planet's moons where they are then.
85
85
  - One WebGL context for the page, nothing drawn at rest, at most thirty frames a second, and everything freed when an element leaves.
@@ -109,7 +109,7 @@ Import the element once in client code, and the entry point of each body you dra
109
109
  | `realistic` | A shaded sphere drawn from the body's real colours and features, lit from the Sun's side, with the rings casting shadows. No pictures: the surface is worked out in a shader. | `three`, the body's entry point |
110
110
  | `painted` | A real surface map from public-domain NASA, USGS and JPL imagery. Small pictures first (1.31 MB for all of them in the package), a sharper one fetched when you zoom in. | `three`, the body's entry point |
111
111
 
112
- Every body has its real size relative to the others (`scale-to="jupiter"`), its axial tilt and its rotation. The Sun, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune and Pluto are drawn, and so are 25 moons (the Moon, Phobos and Deimos, the four Galilean moons, Saturn's seven larger moons, Uranus's five, Proteus, Triton and Nereid, Charon, Nix and Hydra). The Earth is drawn like the rest in the realistic and painted looks (NASA's Blue Marble), and its line look is [chikyu-earth](https://github.com/johnmorrisdotca/chikyu-earth)'s own globe: `import "@johnmorrisdotca/wakusei-planets/earth"` (it needs `@johnmorrisdotca/chikyu-earth` 0.10.0 or later installed). The painted looks cover the Sun, the planets from Mercury to Earth and Pluto, the Moon, the four Galilean moons, Titan, Rhea, Enceladus and Mimas, and Saturn's rings are Cassini's own picture of them.
112
+ Every body has its real size relative to the others (`scale-to="jupiter"`), its axial tilt and its rotation. The Sun, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune and Pluto are drawn, and so are the eight other dwarf planets (Ceres, Orcus, Haumea, Quaoar, Makemake, Gonggong, Eris and Sedna; Haumea and Quaoar as the long shapes they are, and Ceres with a painted map from Dawn) and 29 moons (the Moon, Phobos and Deimos, the four Galilean moons, Saturn's seven larger moons, Uranus's five, Proteus, Triton and Nereid, Charon, Nix and Hydra, and Vanth, Hiʻiaka, Namaka and Dysnomia, the moons of the dwarf planets whose orbits are published with a date). The Earth is drawn like the rest in the realistic and painted looks (NASA's Blue Marble), and its line look is [chikyu-earth](https://github.com/johnmorrisdotca/chikyu-earth)'s own globe: `import "@johnmorrisdotca/wakusei-planets/earth"` (it needs `@johnmorrisdotca/chikyu-earth` 0.10.0 or later installed). The painted looks cover the Sun, the planets from Mercury to Earth and Pluto, Ceres, the Moon, the four Galilean moons, Saturn's Mimas, Enceladus, Tethys, Dione, Rhea, Titan and Iapetus, Triton and Charon, and Saturn's rings are Cassini's own picture of them.
113
113
 
114
114
  ## As the Earth sees it
115
115
 
@@ -273,9 +273,9 @@ A body that turns by itself and a moving sky, in HTML:
273
273
 
274
274
  ## The portrait builder
275
275
 
276
- <p align="center"><img alt="The Sun and the planets in an arc, painted, with their names, in the portrait builder" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.7.0/docs/images/portrait-family.png" width="640"></p>
276
+ <p align="center"><img alt="The Sun and the planets in an arc, painted, with their names, in the portrait builder" src="https://raw.githubusercontent.com/johnmorrisdotca/wakusei-planets/v0.11.0/docs/images/portrait-family.png" width="640"></p>
277
277
 
278
- [The portrait builder](https://johnmorrisdotca.github.io/wakusei-planets/portrait.html) puts any bodies in one picture: drag them, size them, turn them, order them front to back, arrange them in a row, an arc or a grid (true or readable sizes, a planet's moons round it in orbit order), light them from one direction, and take the code, a PNG or an SVG. The scene is kept in the address, and every body is drawn in one WebGL scene, so the page holds one WebGL context. [The portrait builder](docs/guides/portrait-builder.md) is its guide.
278
+ [The portrait builder](https://johnmorrisdotca.github.io/wakusei-planets/portrait.html) puts any bodies in one picture: drag them, size them, turn them, order them front to back, arrange them in a row, an arc, a grid or at true orbit distance (true or readable sizes, a planet's moons round it in orbit order), light them from one direction, and take the code, a PNG or an SVG. The scene is kept in the address, and every body is drawn in one WebGL scene, so the page holds one WebGL context. [The portrait builder](docs/guides/portrait-builder.md) is its guide.
279
279
 
280
280
  ```html
281
281
  <script type="module" src="https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets/portrait/+esm"></script>
@@ -7,6 +7,7 @@ import "./saturn.ts";
7
7
  import "./uranus.ts";
8
8
  import "./neptune.ts";
9
9
  import "./pluto.ts";
10
+ import "./dwarfs.ts";
10
11
  import "./earth-look.ts";
11
12
  import "./moons-earth.ts";
12
13
  import "./moons-mars.ts";
@@ -15,3 +16,4 @@ import "./moons-saturn.ts";
15
16
  import "./moons-uranus.ts";
16
17
  import "./moons-neptune.ts";
17
18
  import "./moons-pluto.ts";
19
+ import "./moons-dwarf.ts";
@@ -7,6 +7,7 @@ import "./saturn.js";
7
7
  import "./uranus.js";
8
8
  import "./neptune.js";
9
9
  import "./pluto.js";
10
+ import "./dwarfs.js";
10
11
  import "./earth-look.js";
11
12
  import "./moons-earth.js";
12
13
  import "./moons-mars.js";
@@ -15,3 +16,4 @@ import "./moons-saturn.js";
15
16
  import "./moons-uranus.js";
16
17
  import "./moons-neptune.js";
17
18
  import "./moons-pluto.js";
19
+ import "./moons-dwarf.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { registerLook } from "../scene/look.js";
2
+ import { ice, rock } from "../scene/recipes.js";
3
+ registerLook({
4
+ id: "ceres",
5
+ surface: rock({
6
+ dark: "#4d4a47",
7
+ light: "#7e7a75",
8
+ craters: 1.4,
9
+ contrast: 0.8,
10
+ seed: 61,
11
+ patches: 3,
12
+ extra: `
13
+ float occ = oval(p, 239.0, 19.8, 3.4, 3.4, 0.5);
14
+ col = mix(col, vec3(0.93, 0.93, 0.92), occ * 0.95);`,
15
+ }),
16
+ relief: 0.7,
17
+ ambient: 0.02,
18
+ painted: { map: "ceres", widths: [512, 1024, 2048], far: 2048 },
19
+ });
20
+ registerLook({ id: "orcus", surface: rock({ dark: "#6f7074", light: "#a9a9ab", craters: 0.5, contrast: 0.5, seed: 63, patches: 2.2 }), relief: 0.5, ambient: 0.02 });
21
+ registerLook({
22
+ id: "haumea",
23
+ surface: ice({ base: "#e4e5e8", stain: "#bfc0c4", craters: 0.08, extra: "col = mix(col, vec3(0.50, 0.17, 0.12), oval(p, 40.0, 5.0, 24.0, 13.0, 0.5) * 0.85);" }),
24
+ relief: 0.3,
25
+ ambient: 0.02,
26
+ });
27
+ registerLook({ id: "quaoar", surface: rock({ dark: "#7d5844", light: "#b58b72", craters: 0.35, contrast: 0.4, seed: 65, patches: 2.4 }), relief: 0.5, ambient: 0.02 });
28
+ registerLook({ id: "makemake", surface: rock({ dark: "#8e5a3c", light: "#cf9a76", craters: 0.12, contrast: 0.3, seed: 67, patches: 2.8, extra: "col = mix(col, vec3(0.45, 0.25, 0.17), oval(p, 30.0, 10.0, 34.0, 20.0, 0.45) * 0.5);" }), relief: 0.35, ambient: 0.02 });
29
+ registerLook({ id: "gonggong", surface: rock({ dark: "#6e3524", light: "#a9604a", craters: 0.3, contrast: 0.4, seed: 69, patches: 2.5 }), relief: 0.5, ambient: 0.02 });
30
+ registerLook({ id: "eris", surface: ice({ base: "#ece8e0", stain: "#cdc6ba", craters: 0.06 }), atmosphere: { colour: [0.9, 0.9, 0.95], strength: 0.03, power: 5 }, relief: 0.2, ambient: 0.02 });
31
+ registerLook({ id: "sedna", surface: rock({ dark: "#6a2a1b", light: "#a24b34", craters: 0.2, contrast: 0.3, seed: 71, patches: 2.6 }), relief: 0.4, ambient: 0.02 });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { registerLook } from "../scene/look.js";
2
+ import { ice, rock } from "../scene/recipes.js";
3
+ registerLook({ id: "dysnomia", surface: rock({ dark: "#3f3d3b", light: "#6c6967", craters: 1.2, contrast: 0.8, seed: 73 }), relief: 0.8, ambient: 0.02 });
4
+ registerLook({ id: "hiiaka", surface: ice({ base: "#e6e6e6", stain: "#c4c4c4", craters: 0.25 }), relief: 0.6, ambient: 0.02 });
5
+ registerLook({ id: "namaka", surface: ice({ base: "#d8d8d8", stain: "#b4b4b4", craters: 0.25 }), relief: 0.6, ambient: 0.02 });
6
+ registerLook({ id: "vanth", surface: rock({ dark: "#55514d", light: "#827d78", craters: 1.2, contrast: 0.8, seed: 75 }), relief: 0.8, ambient: 0.02 });
@@ -7,5 +7,6 @@ registerLook({
7
7
  atmosphere: { colour: [0.8, 0.8, 0.9], strength: 0.05, power: 5 },
8
8
  ambient: 0.025,
9
9
  relief: 0.4,
10
+ painted: { map: "triton", widths: [512, 1024, 2048], far: 2048 },
10
11
  });
11
12
  registerLook({ id: "nereid", surface: rock({ dark: "#6e6e6e", light: "#9b9b9b", craters: 1, seed: 40 }), relief: 0.8, ambient: 0.02 });
@@ -1,5 +1,5 @@
1
1
  import { registerLook } from "../scene/look.js";
2
2
  import { rock } from "../scene/recipes.js";
3
- registerLook({ id: "charon", surface: rock({ dark: "#756f6a", light: "#aaa29a", craters: 0.7, contrast: 0.7, seed: 41, extra: "col = mix(col, vec3(0.42, 0.20, 0.13), smoothstep(radians(52.0), radians(78.0), lat(p)) * 0.85);" }), relief: 0.6, ambient: 0.02 });
3
+ registerLook({ id: "charon", surface: rock({ dark: "#756f6a", light: "#aaa29a", craters: 0.7, contrast: 0.7, seed: 41, extra: "col = mix(col, vec3(0.42, 0.20, 0.13), smoothstep(radians(52.0), radians(78.0), lat(p)) * 0.85);" }), relief: 0.6, ambient: 0.02, painted: { map: "charon", widths: [512, 1024, 2048], far: 2048 } });
4
4
  registerLook({ id: "nix", surface: rock({ dark: "#8d8983", light: "#c9c3b9", craters: 1, contrast: 0.7, seed: 43 }), relief: 0.7, ambient: 0.02 });
5
5
  registerLook({ id: "hydra", surface: rock({ dark: "#9e9a92", light: "#d4cec4", craters: 1, contrast: 0.7, seed: 47 }), relief: 0.7, ambient: 0.02 });
@@ -8,8 +8,8 @@ registerLook({
8
8
  relief: 0.3,
9
9
  painted: { map: "enceladus", widths: [512, 1024, 2048], far: 2048 },
10
10
  });
11
- registerLook({ id: "tethys", surface: rock({ dark: "#8f8d88", light: "#d0cec9", craters: 1.2, contrast: 0.9, seed: 14, patches: 2.2, extra: "float od = bowl(p, 232.0, 5.0, 24.0, 0.6); h += od; col *= 1.0 + 0.7 * od;" }), relief: 0.8, ambient: 0.02 });
12
- registerLook({ id: "dione", surface: rock({ dark: "#8c8a85", light: "#cdcbc5", craters: 1, contrast: 0.9, seed: 18, patches: 2.6 }), relief: 0.8, ambient: 0.02 });
11
+ registerLook({ id: "tethys", surface: rock({ dark: "#8f8d88", light: "#d0cec9", craters: 1.2, contrast: 0.9, seed: 14, patches: 2.2, extra: "float od = bowl(p, 232.0, 5.0, 24.0, 0.6); h += od; col *= 1.0 + 0.7 * od;" }), relief: 0.8, ambient: 0.02, painted: { map: "tethys", widths: [512, 1024, 2048], far: 2048 } });
12
+ registerLook({ id: "dione", surface: rock({ dark: "#8c8a85", light: "#cdcbc5", craters: 1, contrast: 0.9, seed: 18, patches: 2.6 }), relief: 0.8, ambient: 0.02, painted: { map: "dione", widths: [512, 1024, 2048], far: 2048 } });
13
13
  registerLook({ id: "rhea", surface: rock({ dark: "#8a8883", light: "#cac8c2", craters: 1.5, contrast: 0.9, seed: 24, patches: 2 }), relief: 0.85, ambient: 0.02, painted: { map: "rhea", widths: [512, 1024, 2048], far: 2048 } });
14
14
  registerLook({
15
15
  id: "titan",
@@ -31,4 +31,5 @@ registerLook({
31
31
  }),
32
32
  relief: 0.7,
33
33
  ambient: 0.02,
34
+ painted: { map: "iapetus", widths: [512, 1024, 2048], far: 2048 },
34
35
  });
@@ -0,0 +1,16 @@
1
+ import type { DwarfId } from "../facts.ts";
2
+ import type { OrbitElements } from "./orbits.ts";
3
+ /** The osculating heliocentric elements of one epoch. */
4
+ export interface Osculating {
5
+ /** Milliseconds since 1970. */
6
+ epoch: number;
7
+ a: number;
8
+ e: number;
9
+ i: number;
10
+ node: number;
11
+ w: number;
12
+ m: number;
13
+ n: number;
14
+ }
15
+ export declare const DWARF_ELEMENTS: Readonly<Record<DwarfId, Osculating>>;
16
+ export declare const DWARF_MOON_ORBITS: Record<string, OrbitElements>;
@@ -0,0 +1,24 @@
1
+ const epoch = 1780963200000;
2
+ export const DWARF_ELEMENTS = {
3
+ ceres: { epoch, a: 2.765552595034094, e: 0.07969229514816586, i: 10.58802780183462, node: 80.24862682043221, w: 73.29421453021587, m: 274.4193463761342, n: 0.21430445064843 },
4
+ orcus: { epoch, a: 39.37686537914865, e: 0.2205240628250881, i: 20.55681019612, node: 268.4053519455824, w: 73.56848606705262, m: 189.0972913554275, n: 0.003988800931399829 },
5
+ haumea: { epoch, a: 43.06029023650952, e: 0.1944430148898797, i: 28.20847393040364, node: 121.7860561329425, w: 240.6905472508661, m: 223.2104118812299, n: 0.003488097731816818 },
6
+ quaoar: { epoch, a: 43.15617622670811, e: 0.03520019261837198, i: 7.991575800732639, node: 188.9191248092036, w: 163.209046106457, m: 292.8487574842713, n: 0.003476479214379914 },
7
+ makemake: { epoch, a: 45.57093317300052, e: 0.1588889953992523, i: 29.02785603743067, node: 79.2948338209406, w: 297.0922733397207, m: 169.9379962048232, n: 0.003203850120050116 },
8
+ gonggong: { epoch, a: 66.86666567773766, e: 0.5042510000302973, i: 30.89906721170288, node: 336.8383156185827, w: 206.6232839773693, m: 111.664541568459, n: 0.001802559348482343 },
9
+ eris: { epoch, a: 67.93394687853566, e: 0.4382385347971672, i: 43.9258279471791, node: 36.00477044417249, w: 150.7949235840312, m: 211.774434275007, n: 0.001760247770619088 },
10
+ sedna: { epoch, a: 543.7195289104732, e: 0.8598824585187618, i: 11.92527582847476, node: 144.5061662673739, w: 311.0987725939751, m: 358.5956944005428, n: 7.773948799642578e-05 },
11
+ };
12
+ // Moons of the dwarf planets whose orbit is published with an epoch, so they can be put where they are on a date. Moons with no published phase are left out (Weywot, Xiangliu, S/2015 (136472) 1).
13
+ // Dysnomia: Holler, Grundy, Buie & Noll (2021), "The Eris/Dysnomia system I: The orbit of Dysnomia", Icarus 355:114130, Table 2 (combined fit; epoch JD 2453979.0; the angles are to the J2000 equator;
14
+ // the table gives the longitude of periapsis 307 and the mean longitude 125.78, so the argument of periapsis is 307 - 126.17 and the mean anomaly 125.78 - 307). Period 15.785899 days.
15
+ // Hiʻiaka and Namaka: Ragozzine & Brown (2009), "Orbits and Masses of the Satellites of the Dwarf Planet Haumea (2003 EL61)", AJ 137:4766, Table 2: osculating elements at HJD 2454615.0, J2000 ecliptic,
16
+ // the periods from their section III.1. The two moons pull on each other, so a plain ellipse from 2008 is only roughly right years later.
17
+ // Vanth: Brown, Ragozzine, Stansberry & Fraser (2010), "The Size, Density, and Formation of the Orcus-Vanth System in the Kuiper Belt", AJ 139:2700, Table 2, solution 1 of the two mirror solutions
18
+ // (epoch JD 2454439.280, J2000 ecliptic, a circle). Period 9.5393 days.
19
+ export const DWARF_MOON_ORBITS = {
20
+ dysnomia: { parent: "eris", frame: "j2000", epoch: 1157025600000, a: 37273, e: 0.0062, w: 180.83, m: 178.78, i: 45.49, node: 126.17, sid: 22.8051630, dw: 0, dn: 0 },
21
+ hiiaka: { parent: "haumea", frame: "ecliptic", epoch: 1211976000000, a: 49880, e: 0.0513, w: 154.1, m: 152.8, i: 126.356, node: 206.766, sid: 7.2783147, dw: 0, dn: 0 },
22
+ namaka: { parent: "haumea", frame: "ecliptic", epoch: 1211976000000, a: 25657, e: 0.249, w: 178.9, m: 178.5, i: 113.013, node: 205.016, sid: 19.6954859, dw: 0, dn: 0 },
23
+ vanth: { parent: "orcus", frame: "ecliptic", epoch: 1196793792000, a: 8980, e: 0, w: 0, m: 143.1, i: 90.2, node: 50.0, sid: 37.7386181, dw: 0, dn: 0 },
24
+ };
@@ -1,8 +1,8 @@
1
1
  export interface OrbitElements {
2
2
  /** The planet it goes round. */
3
3
  parent: string;
4
- /** The reference plane: the ecliptic, the planet's equator, or the Laplace plane whose pole is `pole` (right ascension, declination, degrees). */
5
- frame: "ecliptic" | "equatorial" | "laplace";
4
+ /** The reference plane: the ecliptic, the Earth's equator of J2000, the planet's equator, or the Laplace plane whose pole is `pole` (right ascension, declination, degrees). */
5
+ frame: "ecliptic" | "j2000" | "equatorial" | "laplace";
6
6
  pole?: [number, number];
7
7
  /** The epoch of the elements, in milliseconds since 1970. */
8
8
  epoch: number;
@@ -1,3 +1,6 @@
1
+ // Made by scripts/make-moon-data.py from JPL's Planetary Satellite Mean Elements (https://ssd.jpl.nasa.gov/sats/elem/); do not edit by hand.
2
+ // The moons of the dwarf planets are not on that page: their orbits are in dwarf.ts, from the papers it cites, and are added at the end.
3
+ import { DWARF_MOON_ORBITS } from "./dwarf.js";
1
4
  export const ORBITS = {
2
5
  moon: { parent: "earth", frame: "ecliptic", epoch: 946728000000, a: 384400, e: 0.0554, w: 318.15, m: 135.27, i: 5.16, node: 125.08, sid: 13.1763585, dw: 0.16435, dn: -0.05299 },
3
6
  phobos: { parent: "mars", frame: "laplace", pole: [317.7, 52.9], epoch: 946728000000, a: 9375, e: 0.015, w: 216.3, m: 189.7, i: 1.1, node: 169.2, sid: 1128.8443146, dw: -0.74464, dn: -0.42853 },
@@ -24,4 +27,5 @@ export const ORBITS = {
24
27
  charon: { parent: "pluto", frame: "equatorial", epoch: 946728000000, a: 19600, e: 0, w: 0, m: 304.1, i: 0, node: 0, sid: 56.3624607, dw: 0.00000, dn: 0.00000 },
25
28
  nix: { parent: "pluto", frame: "equatorial", epoch: 946728000000, a: 49300, e: 0.015, w: 31.4, m: 338.2, i: 0, node: 0, sid: 14.4842403, dw: 0.00000, dn: 0.00000 },
26
29
  hydra: { parent: "pluto", frame: "equatorial", epoch: 946728000000, a: 65200, e: 0.009, w: 139.3, m: 335, i: 0.3, node: 114.3, sid: 9.4236471, dw: 0.00000, dn: -0.07040 },
30
+ ...DWARF_MOON_ORBITS,
27
31
  };
package/dist/element.d.ts CHANGED
@@ -25,7 +25,7 @@ export type WakuseiState = "line" | "loading" | "still" | "live" | "lost" | "una
25
25
  * console.log(TEXTURE_BASE);
26
26
  * ```
27
27
  */
28
- export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@0.7.0/dist/textures/";
28
+ export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@0.11.0/dist/textures/";
29
29
  /**
30
30
  * Where the sharpest surface pictures are: the files of this exact version in the repository, on jsDelivr's GitHub address. The npm package holds only the two smaller widths, so a page that
31
31
  * never zooms in never fetches them. Set `texture-far-base` on an element to serve them yourself.
@@ -37,7 +37,7 @@ export declare const TEXTURE_BASE = "https://cdn.jsdelivr.net/npm/@johnmorrisdot
37
37
  * console.log(TEXTURE_FAR_BASE);
38
38
  * ```
39
39
  */
40
- export declare const TEXTURE_FAR_BASE = "https://cdn.jsdelivr.net/gh/johnmorrisdotca/wakusei-planets@v0.7.0/textures/";
40
+ export declare const TEXTURE_FAR_BASE = "https://cdn.jsdelivr.net/gh/johnmorrisdotca/wakusei-planets@v0.11.0/textures/";
41
41
  /**
42
42
  * The `<wakusei-body>` custom element: a body of the solar system, drawn in a look.
43
43
  *
@@ -59,6 +59,8 @@ export declare const TEXTURE_FAR_BASE = "https://cdn.jsdelivr.net/gh/johnmorrisd
59
59
  export declare class WakuseiBody extends HTMLElement {
60
60
  #private;
61
61
  static observedAttributes: string[];
62
+ /** The radius of the body's disc on the screen in CSS pixels, at the picture's own size: what `grab-radius` on the shared view controls wants for a drag that keeps the touched point under the pointer. */
63
+ get discRadius(): number;
62
64
  get body(): BodyId;
63
65
  get look(): Look;
64
66
  /** What the element is doing now. */
package/dist/element.js CHANGED
@@ -145,6 +145,10 @@ export class WakuseiBody extends HTMLElement {
145
145
  else
146
146
  this.#resume();
147
147
  };
148
+ /** The radius of the body's disc on the screen in CSS pixels, at the picture's own size: what `grab-radius` on the shared view controls wants for a drag that keeps the touched point under the pointer. */
149
+ get discRadius() {
150
+ return (this.getBoundingClientRect().width / 2) * this.#fill();
151
+ }
148
152
  get body() {
149
153
  const id = this.getAttribute("body");
150
154
  return id !== null && id in BODIES ? id : "earth";
package/dist/embed.d.ts CHANGED
@@ -61,7 +61,7 @@ export interface EmbedSettings {
61
61
  /** The package's address on the CDN, with its version. */
62
62
  cdn?: string;
63
63
  }
64
- /** The entry point that holds a body's shaded and painted look. */
64
+ /** The entry point that holds a body's shaded and painted look: the body's own, `dwarfs` for the dwarf planets besides Pluto, `moons-<planet>` for a moon and `moons-dwarf` for a moon of a dwarf planet. */
65
65
  export declare function entryOf(id: BodyId): string;
66
66
  /**
67
67
  * The text that puts a body in a page, for one way of making the page. Each way is built, parsed or run by the package's tests: the plain HTML one with a script tag from jsDelivr, an ES module, a React component,
package/dist/embed.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // The text to put a body in a page, for each way a page is made: a script tag from a CDN, an ES module, React, Vue, Angular, Svelte, or an iframe of `embed.html`. The demo's "Put it in your page"
2
2
  // section shows these as the reader changes a choice, and the tests build or parse every one. Pure: no DOM.
3
- import { BODIES } from "./facts.js";
3
+ import { BODIES, DWARF_IDS } from "./facts.js";
4
4
  import { decodeScene } from "./portrait/model.js";
5
5
  import { VERSION } from "./version.js";
6
6
  /**
@@ -17,10 +17,12 @@ export const EMBED_KINDS = ["html", "module", "react", "vue", "angular", "svelte
17
17
  const SITE = "https://johnmorrisdotca.github.io/wakusei-planets/";
18
18
  const CDN = `https://cdn.jsdelivr.net/npm/@johnmorrisdotca/wakusei-planets@${VERSION}`;
19
19
  const PACKAGE = "@johnmorrisdotca/wakusei-planets";
20
- /** The entry point that holds a body's shaded and painted look. */
20
+ /** The entry point that holds a body's shaded and painted look: the body's own, `dwarfs` for the dwarf planets besides Pluto, `moons-<planet>` for a moon and `moons-dwarf` for a moon of a dwarf planet. */
21
21
  export function entryOf(id) {
22
22
  const parent = BODIES[id].parent;
23
- return parent === undefined ? id : `moons-${parent}`;
23
+ if (parent === undefined)
24
+ return DWARF_IDS.includes(id) ? "dwarfs" : id;
25
+ return DWARF_IDS.includes(parent) ? "moons-dwarf" : `moons-${parent}`;
24
26
  }
25
27
  /** The attributes of the element for these choices, in the order they are written. */
26
28
  function attributesOf(o) {
@@ -1,10 +1,10 @@
1
- import { type BodyId, type MoonId, type PlanetId } from "./facts.ts";
1
+ import { type BodyId, type HostId, type MoonId } from "./facts.ts";
2
2
  import { type Vec3 } from "./vec.ts";
3
3
  /** Solve Kepler's equation for the eccentric anomaly, all in radians. */
4
4
  export declare function kepler(meanAnomaly: number, e: number): number;
5
5
  /**
6
- * A planet's position in the Sun's frame (J2000 ecliptic, astronomical units) at a time, from JPL's approximate elements.
7
- * @param planet - The planet's name.
6
+ * A planet's or dwarf planet's position in the Sun's frame (J2000 ecliptic, astronomical units) at a time: a planet from JPL's approximate elements, a dwarf planet from the osculating elements of one epoch (JPL's Small-Body Database).
7
+ * @param planet - The planet's or dwarf planet's name.
8
8
  * @param ms - The instant, in milliseconds since 1970.
9
9
  * @returns The position as x, y, z in au.
10
10
  *
@@ -16,7 +16,7 @@ export declare function kepler(meanAnomaly: number, e: number): number;
16
16
  * console.log(Math.hypot(x, y, z).toFixed(3));
17
17
  * ```
18
18
  */
19
- export declare function heliocentric(planet: PlanetId, ms: number): Vec3;
19
+ export declare function heliocentric(planet: HostId, ms: number): Vec3;
20
20
  /**
21
21
  * The Moon's position from the Earth's centre (J2000 ecliptic, kilometres), from the largest terms of Meeus's lunar series.
22
22
  * @param ms - The instant, in milliseconds since 1970.
@@ -164,7 +164,7 @@ export interface System {
164
164
  * console.log(moons.map((moon) => `${moon.id} ${moon.x.toFixed(1)}`).join(", "));
165
165
  * ```
166
166
  */
167
- export declare function system(planet: PlanetId, time: number): System;
167
+ export declare function system(planet: HostId, time: number): System;
168
168
  /**
169
169
  * The path one moon follows round its planet over one trip, as the Earth sees it at `time`: x, y, z in planet radii, in the viewer's frame at that moment.
170
170
  *
@@ -183,7 +183,7 @@ export declare function system(planet: PlanetId, time: number): System;
183
183
  */
184
184
  export declare function orbitPath(id: MoonId, time: number, points?: number): Float32Array;
185
185
  /** The planets' moons that are near enough to draw in a system view, nearest first, with how far each goes in planet radii. */
186
- export declare function nearMoons(planet: PlanetId, within?: number): {
186
+ export declare function nearMoons(planet: HostId, within?: number): {
187
187
  id: MoonId;
188
188
  reach: number;
189
189
  }[];
package/dist/ephemeris.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // good to about an arc minute. The Moon: the largest terms of Meeus, "Astronomical Algorithms", chapter 47. Other moons: JPL's mean elements (data/orbits.ts).
4
4
  // Light travel time is allowed for, so a turning planet shows the face it really showed when the light left. Pure: no DOM, no three.js.
5
5
  import { ORBITS } from "./data/orbits.js";
6
+ import { DWARF_ELEMENTS } from "./data/dwarf.js";
6
7
  import { BODIES, MOONS_OF } from "./facts.js";
7
8
  import { add, cross, DEG, dot, fromRaDec, length, OBLIQUITY, RAD, scale, sub, toEcliptic, unit, wrap360 } from "./vec.js";
8
9
  /** Milliseconds since 1970 at J2000.0 (2000-01-01 12:00 TT, taken as UTC). */
@@ -42,8 +43,8 @@ export function kepler(meanAnomaly, e) {
42
43
  return E;
43
44
  }
44
45
  /**
45
- * A planet's position in the Sun's frame (J2000 ecliptic, astronomical units) at a time, from JPL's approximate elements.
46
- * @param planet - The planet's name.
46
+ * A planet's or dwarf planet's position in the Sun's frame (J2000 ecliptic, astronomical units) at a time: a planet from JPL's approximate elements, a dwarf planet from the osculating elements of one epoch (JPL's Small-Body Database).
47
+ * @param planet - The planet's or dwarf planet's name.
47
48
  * @param ms - The instant, in milliseconds since 1970.
48
49
  * @returns The position as x, y, z in au.
49
50
  *
@@ -56,16 +57,34 @@ export function kepler(meanAnomaly, e) {
56
57
  * ```
57
58
  */
58
59
  export function heliocentric(planet, ms) {
59
- const el = ELEMENTS[planet];
60
- const T = centuries(ms);
61
- const a = el[0] + el[6] * T;
62
- const e = el[1] + el[7] * T;
63
- const I = (el[2] + el[8] * T) * RAD;
64
- const L = el[3] + el[9] * T;
65
- const peri = el[4] + el[10] * T;
66
- const node = (el[5] + el[11] * T) * RAD;
67
- const w = peri * RAD - node;
68
- const E = kepler((L - peri) * RAD, e);
60
+ let a;
61
+ let e;
62
+ let I;
63
+ let node;
64
+ let w;
65
+ let E;
66
+ const dwarf = DWARF_ELEMENTS[planet];
67
+ if (dwarf !== undefined) {
68
+ // a dwarf planet: the osculating elements of one epoch, run as a plain ellipse (good for years either side of it)
69
+ a = dwarf.a;
70
+ e = dwarf.e;
71
+ I = dwarf.i * RAD;
72
+ node = dwarf.node * RAD;
73
+ w = dwarf.w * RAD;
74
+ E = kepler((dwarf.m + (dwarf.n * (ms - dwarf.epoch)) / DAY) * RAD, e);
75
+ }
76
+ else {
77
+ const el = ELEMENTS[planet];
78
+ const T = centuries(ms);
79
+ a = el[0] + el[6] * T;
80
+ e = el[1] + el[7] * T;
81
+ I = (el[2] + el[8] * T) * RAD;
82
+ const L = el[3] + el[9] * T;
83
+ const peri = el[4] + el[10] * T;
84
+ node = (el[5] + el[11] * T) * RAD;
85
+ w = peri * RAD - node;
86
+ E = kepler((L - peri) * RAD, e);
87
+ }
69
88
  const x = a * (Math.cos(E) - e);
70
89
  const y = a * Math.sqrt(1 - e * e) * Math.sin(E);
71
90
  const cw = Math.cos(w);
@@ -231,6 +250,8 @@ export function moonVector(id, ms) {
231
250
  function referenceBasis(orbit) {
232
251
  if (orbit.frame === "ecliptic")
233
252
  return { x: [1, 0, 0], y: [0, 1, 0], z: [0, 0, 1] };
253
+ if (orbit.frame === "j2000")
254
+ return { x: toEcliptic([1, 0, 0]), y: toEcliptic([0, 1, 0]), z: toEcliptic([0, 0, 1]) };
234
255
  const pole = orbit.frame === "laplace" && orbit.pole !== undefined ? orbit.pole : [BODIES[orbit.parent].pole.ra, BODIES[orbit.parent].pole.dec];
235
256
  const z = fromRaDec(pole[0], pole[1]);
236
257
  const x = fromRaDec(pole[0] + 90, 0);
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 moons the package draws, by planet.
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?: PlanetId;
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 planet.
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 moons the package draws, by planet.
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 planet.
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 visible = (lon, lat) => facing(pose, lon, lat, f) > 0.02;
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 };
@@ -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, and the Earth and the Moon to scale.
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
@@ -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 { BODIES, MOONS_OF, PLANET_IDS, ringReach } from "../facts.js";
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, and the Earth and the Moon to scale.
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: "地球と月 (大きさの比)" }, scene: fromList(["earth", "moon"], { arrange: "row", scale: "true", moons: "auto", orbits: true }) },
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
  ];
@@ -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
- /** 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. */
54
- export declare function surfacePoint(pose: Pose, lon: number, lat: number, flattening?: number): Vec3;
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 };
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.7.0";
1
+ export declare const VERSION = "0.11.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.7.0";
1
+ export const VERSION = "0.11.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/wakusei-planets",
3
- "version": "0.7.0",
3
+ "version": "0.11.0",
4
4
  "description": "Wakusei Planets 惑星 — The Sun, planets and moons for the web: each as a clean line drawing, a shaded realistic drawing or a painted surface map, with real sizes, axial tilt and rotation as data, and positions of the moons for any date and time. SVG for the lines, three.js (optional) for the rest.",
5
5
  "keywords": [
6
6
  "planets",
@@ -119,6 +119,11 @@
119
119
  "import": "./dist/bodies/pluto.js",
120
120
  "default": "./dist/bodies/pluto.js"
121
121
  },
122
+ "./dwarfs": {
123
+ "types": "./dist/bodies/dwarfs.d.ts",
124
+ "import": "./dist/bodies/dwarfs.js",
125
+ "default": "./dist/bodies/dwarfs.js"
126
+ },
122
127
  "./moons-earth": {
123
128
  "types": "./dist/bodies/moons-earth.d.ts",
124
129
  "import": "./dist/bodies/moons-earth.js",
@@ -154,6 +159,11 @@
154
159
  "import": "./dist/bodies/moons-pluto.js",
155
160
  "default": "./dist/bodies/moons-pluto.js"
156
161
  },
162
+ "./moons-dwarf": {
163
+ "types": "./dist/bodies/moons-dwarf.d.ts",
164
+ "import": "./dist/bodies/moons-dwarf.js",
165
+ "default": "./dist/bodies/moons-dwarf.js"
166
+ },
157
167
  "./package.json": "./package.json",
158
168
  "./earth": {
159
169
  "types": "./dist/bodies/earth.d.ts",