@johnmorrisdotca/uchusen-rockets 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -4
- package/README.md +3 -2
- package/dist/beside.d.ts +76 -0
- package/dist/beside.js +83 -0
- package/dist/build/index.d.ts +9 -2
- package/dist/build/index.js +49 -23
- package/dist/build/lineup.d.ts +39 -1
- package/dist/build/lineup.js +70 -3
- package/dist/data/sources.d.ts +8 -0
- package/dist/data/sources.js +1 -0
- package/dist/data/types.d.ts +4 -0
- package/dist/element/scene.d.ts +5 -0
- package/dist/element/scene.js +62 -16
- package/dist/element.d.ts +2 -0
- package/dist/element.js +14 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/journey/bodies.d.ts +47 -0
- package/dist/journey/bodies.js +2 -0
- package/dist/journey/constants.d.ts +115 -0
- package/dist/journey/constants.js +134 -0
- package/dist/journey/countdown.d.ts +49 -0
- package/dist/journey/countdown.js +56 -0
- package/dist/journey/crafts.d.ts +165 -0
- package/dist/journey/crafts.js +141 -0
- package/dist/journey/index.d.ts +11 -0
- package/dist/journey/index.js +8 -0
- package/dist/journey/physics.d.ts +114 -0
- package/dist/journey/physics.js +154 -0
- package/dist/journey/timeline.d.ts +115 -0
- package/dist/journey/timeline.js +193 -0
- package/dist/journey/trip.d.ts +213 -0
- package/dist/journey/trip.js +452 -0
- package/dist/journey/words.d.ts +96 -0
- package/dist/journey/words.js +144 -0
- package/dist/rockets.js +11 -2
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
## [0.12.0] - 2026-11-05
|
|
6
|
+
|
|
7
|
+
Tilt up to 90 degrees for craft in space, and journey mode fixes.
|
|
8
|
+
|
|
9
|
+
- **Tilt up to 90 degrees for craft in space** (MODULES-430): each variant has `view.tiltMax`, 90 for the SPX fleet, the ISS, Hubble, Dragon, Gemini, Mercury, the lunar module and Apollo-Soyuz, and 45 for rockets and sites on the ground (it stopped at about 20 before). The demo gives it to the view controls' `tilt-max`, so the slider, the drag and `?tilt=` follow the craft chosen, and a tip beyond the new limit is brought back to it when a rocket is chosen. The camera stays held at 87 degrees, so there is no flip, and Fit still frames the whole craft. Tests: `test/tilt-max.test.ts`, `e2e/tilt-max.demo.ts`.
|
|
10
|
+
- **Journey mode fixes**: after separation the side view shows the payload itself (Orion for SLS, Cargo Dragon for Falcon Heavy) instead of the fairing dropped in ascent; a payload with no braking engine of its own flies past its target (verdict "Flyby only", no braking burn, fuel n/a with the reason) instead of braking at an assumed 0.2 g; Japanese lists in the numbers panel use 「、」.
|
|
11
|
+
- Test only: the fleet walk and the GPU counts in the freeze tests wait for each ship to draw before they read, so a slow runner no longer reads a count from before the new ship is in.
|
|
12
|
+
|
|
13
|
+
## [0.11.0] - 2026-11-04
|
|
14
|
+
|
|
15
|
+
Journey mode (Earth to the Moon and the planets) and real craft for scale beside the SPX fleet.
|
|
16
|
+
|
|
17
|
+
- **Journey mode, slice 1b**: after each burn the side view shows only what is left, drawn from the rockets' own parts at the whole rocket's scale (Saturn V: S-IVB with the Apollo spacecraft, then the command and service module; SLS: upper stage with Orion, then Orion; Falcon Heavy: upper stage with its payload, then the payload in its fairing). `build().svg()` takes `only` (the slots left, closed up) and `frame` (the bounds to fit to). Falcon Heavy and SLS are drawn from the front so all their cores show. The Fuel row is the propellant used or n/a with a reason; the payload has its own row, "Payload to <target>".
|
|
18
|
+
- **Journey mode, first slice** (MODULES-394, parts 395 to 399 and 401): `journey.html` in the demo and the `@johnmorrisdotca/uchusen-rockets/journey` entry. Choose a start (the planets), an end (the Moon or a planet) and a craft (Saturn V, Space Launch System, Falcon Heavy, ISV Kaminari SPX-207), then fly it: a countdown with an engine preheat bar and an ignition ring, a side view with the craft's own line drawing, a map, a route bar, the numbers, the flip and the braking burn, and a summary. A rocket flies a Hohmann transfer on a launch window found from the planets' real positions (wakusei-planets); a fleet ship flies a relativistic flip and burn. Every craft figure names its source or is marked as an estimate; a craft that cannot go says why in one line. No WebGL: one 2D canvas, SVG and the line drawing, a frame only while a trip plays, 30 a second at most. Pinned against published values in `test/journey-physics.test.ts` and `test/journey-trip.test.ts`; guide in English and Japanese; plan in `docs/journey/plan.md`. Dev dependency: wakusei-planets 0.15.0.
|
|
19
|
+
- **Beside it** (MODULES-431): `beside="on"` on `<uchusen-rocket>` stands the real Starship stack (Super Heavy and the ship, Block 3, from the package's own model) beside the craft on screen at true scale, on the same ground line and clear of it, in the 3D view and the drawing. Fit holds both craft. The demo has a "Beside it" toggle under Scale; it is kept in the address (`?beside=on`), survives a reload, and is not offered for Starship itself. `build(description, { beside: true })` gives `Built.beside` (the parts beside, slots `beside-booster` and `beside-ship`), and `boundsOf`, `placesOf`, `glb` and `svg` take them in; `besideLayout`, `besideBounds`, `canBeside` and `BESIDE_ROCKET` are the pure layout. The stack beside is drawn in this package's own paint in every look, so no licensed model comes in uncredited; a flight hides it.
|
|
20
|
+
- **Real craft for scale** (MODULES-431): on the comparison page Starship with Super Heavy, Saturn V, SLS Block 1 and the ISS lie sideways in the SPX fleet band at the fleet ships' own scale and ruler, one row each, a rocket nose right and the station along its long axis, each labelled with its name and sourced length (124.4, 110.64, 98.15 and 109 m). A "Real craft for scale" toggle is on by default and is kept in the address (`?real=off`); it is hidden with Rockets alone, which has no fleet band. `<uchusen-lineup real="on">` and `lineupSvg({ real: true })` (which returns `real`) do it; off is today's picture. `REAL_CRAFT` lists them. The ISS line model ends its arrays 1.2 m inside the sourced 109 m, so its bar is 1.1% short.
|
|
9
21
|
|
|
10
22
|
## [0.10.0] - 2026-11-04
|
|
11
23
|
|
|
12
24
|
The SPX fleet's first ships: ISV Kaminari SPX-207, ISV Miyamoto SPX-452 and her sister ISV Ariake SPX-186, with the viewer panel and the fleet on the comparison page.
|
|
13
25
|
|
|
26
|
+
- **Fix, the chosen tab of a ship's panel under the 3D view**: it was drawn as a blank pill in every theme, because its fill was `currentColor` while its words were set to `Canvas` on the same button, so both were one colour. The chosen tab and the Labels toggle now have a tinted fill, a ring and bold words, in the page's own colours.
|
|
27
|
+
- **Fix, the demo's Rocket list**: under SPX it holds the fleet ships by their registry names (ISV Miyamoto, SPX-452; ISV Ariake, SPX-186; ISV Kaminari, SPX-207) and no longer the kit's "Fleet ..." boxes of parts, on the demo and on the code builder. A kit box still draws when the address names it (`?rocket=fleet-tanks`).
|
|
14
28
|
- **Fleet kit quality pass** (MODULES-414, MODULES-407 follow-up): the kit's look is raised. The hull is mostly white quilted thermal blanket with a few rigid framed panels, crinkled foil on the hardware that wears it and orange only on small removable access panels (`LIVERY`, `hullPanels`, `blanketPanel`, `framedPanel`, `foilBand`); plates can be quilted, framed over a sunk field, a single sheet or loose foil facets (`Plate.quilt`, `inset`, `sheet`, `facets`) and a lathe can be fluted. Pipes have real bends, bolted flange joints, glands, blind caps, valves and stand-off clamp brackets (`pipeRun`), and conduits leave the hull through glands. Nozzles are regeneratively cooled bells with a fluted tube-wall jacket, manifolds, a foil-wrapped chamber, a turbopump with its lines, a gimbal and actuators. The line look is held to 0.6 of the painted look's density. The Lit look: drive coils, windows, red and green navigation lights, strobes, floodlights and grow-lamps glow with "Engines: Lit" (`MATERIALS` `glow`). New: separation joints (`separationJoint`, `separation` on a ship's place), armoured portholes with burn shutters, an observation dome with six shutter petals, ten furnished rooms (`ROOMS`, `room`), and an observation dome lounge and a solarium module with clear glass (`opacity`). A fleet module may be up to 1.25 MB and a ship 12 MB.
|
|
15
29
|
- **The fleet module kit** (`src/model/fleet/`, `docs/fleet/kit.md`): the pieces every ship of the fictional fleet is built from, in plain geometry and metres. 38 modules (fusion engine clusters and thruster pods, cryogenic and slim tanks, a sphere tank farm, cargo containers and a bay for four, habitat rings and drums, a laboratory, a sensor mast, a reactor, radiator arrays, docking nodes, trusses, adapters and a forward shield), each with its name, mass and size, the standard docking collars (four classes, S to H, each twice the one before) and a cutaway where it has an inside. A ship is a list of modules on a spine (`assemble`, `shipPieces`, `shipParts`), checked joint by joint. A detail kit (plating, hatches, conduits, radiators, tile, antennae, stencils, lamps, greebles) covers every surface; repeated detail is stamped, one template written many times. The shared data of the ships: engine cards (thrust = mass flow x specific impulse x g0, held by a test) with the relativistic performance and the Proxima trip, hull-layer stacks by role that a cutaway draws as real concentric shells, named sections with labels and numbered decks, four system insets of five or six numbered sub-parts, swappable mission pods, an exploded assembly, a symbol legend, scale bar and views at one scale, and the fleet palette. The modules are rockets in the picker under "Fleet (fictional)", labelled fictional, in all three looks, with one sample ship. A fleet part is lit with a shadow. Every module has a coarse level and a fine one (`shipLevels`, `timeBuild`, a build budget of 1 s for the first picture and 3 s for full detail, held by a test), names the real hardware it descends from, and is built with round pipes (`pipeRun`: bends, flanges, brackets) and I-beam, channel and round-strut sections. Any registry number is a designation (`designation("207")`); `titleBlock` and `specBlock` carry the glossary (ISV, class codes, FTD and DCD, KAZE, ATC, the Japanese name with its ruby); `BUILDERS` makes a module of a ship's own size; `cutYaw` turns where a cutaway opens; the solids by range of SPX-03's adapter (`cub`, `prism`, `quad`, `discX`, `discZ`) are shared. The fleet is written in-universe (`titleBlock`: planned and in-development status, designations from `SPX-`, the maker "SPX"); the picker group is "SPX fleet" and the NASA group is just "NASA" so the maker row stays on one line. The element has `screenPoint(point)` and a `uchusen-draw` event, so a label can follow a part in 3D. Adds about 62 KB (minified) to the root entry and about 25 MB to the package's assets; the fleet's ships are allowed 6 MB of mesh where a rocket is allowed 600 KB.
|
|
16
30
|
- Test only: the freeze test walks the fleet in its own test, so the walks of every real rocket stay as long as they were.
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Uchūsen means "spaceship". Each rocket comes in every variant a source document
|
|
|
12
12
|
<img alt="TypeScript" src="https://img.shields.io/badge/types-TypeScript-3178c6">
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
<p align="center"><a href="https://johnmorrisdotca.github.io/uchusen-rockets/"><strong>Open the demo →</strong></a> · <a href="./docs/variants.md">Variants and sizes</a> · <a href="./docs/parts.md">Build from parts</a> · <a href="./docs/element.md">The element</a> · <a href="./docs/flight.md">Lit engines and flights</a> · <a href="./docs/embed.md">Use one on your site</a> · <a href="./docs/assets.md">Assets</a> · <a href="./docs/guides/getting-started.md">Guides</a> · <a href="https://johnmorrisdotca.github.io/uchusen-rockets/builder.html">Code builder</a> · <a href="https://johnmorrisdotca.github.io/uchusen-rockets/docs/">Docs site</a></p>
|
|
15
|
+
<p align="center"><a href="https://johnmorrisdotca.github.io/uchusen-rockets/"><strong>Open the demo →</strong></a> · <a href="./docs/variants.md">Variants and sizes</a> · <a href="./docs/parts.md">Build from parts</a> · <a href="./docs/element.md">The element</a> · <a href="./docs/flight.md">Lit engines and flights</a> · <a href="./docs/embed.md">Use one on your site</a> · <a href="./docs/assets.md">Assets</a> · <a href="./docs/guides/getting-started.md">Guides</a> · <a href="https://johnmorrisdotca.github.io/uchusen-rockets/builder.html">Code builder</a> · <a href="https://johnmorrisdotca.github.io/uchusen-rockets/journey.html">Journey mode</a> · <a href="https://johnmorrisdotca.github.io/uchusen-rockets/docs/">Docs site</a></p>
|
|
16
16
|
|
|
17
17
|
<p align="center"><img alt="Block 3 of the rocket as a painted 3D view, stainless steel with black tiles, beside its controls and its sizes, each with a source." src="https://raw.githubusercontent.com/johnmorrisdotca/uchusen-rockets/main/docs/images/main-painted.jpg" width="820"></p>
|
|
18
18
|
|
|
@@ -41,7 +41,7 @@ npm install @johnmorrisdotca/uchusen-rockets three
|
|
|
41
41
|
<uchusen-rocket rocket="starship" variant="v2" show="booster" look="thick" view="below"></uchusen-rocket>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
That is all a page needs with a bundler. The drawing shows at once; when the element comes on screen and the browser can draw WebGL, the 3D view takes over without a jump. Drag to turn it, use the arrow keys, or leave it to its slow spin. `engines="on"` lights each engine at full thrust, in the painted look and as a drawn flame in the line looks, and a reader who asked for reduced motion has them off. A flame's length and its shock diamonds are a picture, not a measurement. [A flight](docs/flight.md) plays each phase with the engines that burn in it, from published timelines. With no WebGL, under Save-Data, or on any failure, the drawing stays.
|
|
44
|
+
That is all a page needs with a bundler. The drawing shows at once; when the element comes on screen and the browser can draw WebGL, the 3D view takes over without a jump. Drag to turn it, use the arrow keys, or leave it to its slow spin. `engines="on"` lights each engine at full thrust, in the painted look and as a drawn flame in the line looks, and a reader who asked for reduced motion has them off. A flame's length and its shock diamonds are a picture, not a measurement. `beside="on"` stands Starship with Super Heavy next to the rocket at true scale, to see how big it is. [A flight](docs/flight.md) plays each phase with the engines that burn in it, from published timelines. With no WebGL, under Save-Data, or on any failure, the drawing stays.
|
|
45
45
|
|
|
46
46
|
## Who it is for
|
|
47
47
|
|
|
@@ -363,6 +363,7 @@ Each guide has a Japanese twin in [docs/guides/ja](docs/guides/ja).
|
|
|
363
363
|
| [Build a rocket from parts](docs/guides/build-from-parts.md) | Part records, the fit check, `build`, the builder |
|
|
364
364
|
| [Lit engines and flights](docs/guides/engines-and-flights.md) | Engines at full thrust, flights, what is sourced |
|
|
365
365
|
| [Rockets to scale](docs/guides/scale-lineups.md) | `<uchusen-lineup>` and `lineupSvg` |
|
|
366
|
+
| [Journey mode](docs/guides/journey.md) | Fly a rocket or a fleet ship to the Moon or a planet: windows, burns, the flip |
|
|
366
367
|
| [Put a rocket on your own site](docs/guides/embed-on-your-site.md) | Six ways, and the credit |
|
|
367
368
|
| [React and other frameworks](docs/guides/react-and-frameworks.md) | React, Vue, Svelte, Angular |
|
|
368
369
|
| [Bundlers and CDNs](docs/guides/bundlers-and-cdn.md) | unpkg, Vite, webpack |
|
package/dist/beside.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Description } from "./fit.ts";
|
|
2
|
+
import type { Vec3 } from "./framing.ts";
|
|
3
|
+
import type { Bounds } from "./model/pieces.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The rocket that can stand beside another for scale: the real Starship stack, Super Heavy and the ship, Block 3. Beside another craft it is drawn in this package's own paint, so no licensed model comes in unseen.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { BESIDE_ROCKET } from "@johnmorrisdotca/uchusen-rockets";
|
|
10
|
+
*
|
|
11
|
+
* console.log(BESIDE_ROCKET.rocket, BESIDE_ROCKET.variant);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare const BESIDE_ROCKET: Readonly<Description>;
|
|
15
|
+
/** What the stack beside is called. */
|
|
16
|
+
export declare const BESIDE_NAME = "Starship with Super Heavy";
|
|
17
|
+
/**
|
|
18
|
+
* The words that say what the craft beside it is: its name and its sourced length, such as "Starship with Super Heavy, 124.4 m".
|
|
19
|
+
*
|
|
20
|
+
* @returns The label the 3D view puts under the stack.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { besideLabel } from "@johnmorrisdotca/uchusen-rockets";
|
|
24
|
+
*
|
|
25
|
+
* console.log(besideLabel());
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function besideLabel(): string;
|
|
29
|
+
/** The least room between the two, in metres. */
|
|
30
|
+
export declare const BESIDE_GAP = 4;
|
|
31
|
+
/**
|
|
32
|
+
* Whether Starship can stand beside this rocket: every rocket but Starship itself.
|
|
33
|
+
*
|
|
34
|
+
* @param rocket A rocket's id.
|
|
35
|
+
* @returns False for Starship, true for any other.
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { canBeside } from "@johnmorrisdotca/uchusen-rockets";
|
|
39
|
+
*
|
|
40
|
+
* console.log(canBeside("saturn-v"), canBeside("starship"));
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const canBeside: (rocket: string) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Where the other rocket stands: its offset in metres, to add to where its own parts stand, and the box it fills there. It is never scaled. Its foot is on the ground line (y = 0) as every rocket's is, it keeps its own
|
|
46
|
+
* line (z = 0), and its nearest side is a gap clear of the craft's widest, to the right (+X).
|
|
47
|
+
*
|
|
48
|
+
* @param craft The box the craft fills, in metres.
|
|
49
|
+
* @param other The box the other rocket fills on its own, in metres.
|
|
50
|
+
* @returns The offset and the box the other rocket fills once it is there.
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { besideLayout } from "@johnmorrisdotca/uchusen-rockets";
|
|
54
|
+
*
|
|
55
|
+
* const { at, bounds } = besideLayout({ min: [-4, 0, -4], max: [4, 70, 4] }, { min: [-9, 0, -5], max: [9, 124, 9] });
|
|
56
|
+
* console.log(at, bounds.max[1]);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function besideLayout(craft: Bounds, other: Bounds): {
|
|
60
|
+
at: Vec3;
|
|
61
|
+
bounds: Bounds;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The box that holds both: what the camera must frame, and what Fit fits.
|
|
65
|
+
*
|
|
66
|
+
* @param craft The box the craft fills.
|
|
67
|
+
* @param placed The box the other rocket fills where `besideLayout` put it.
|
|
68
|
+
* @returns The smallest box that holds both.
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { besideBounds } from "@johnmorrisdotca/uchusen-rockets";
|
|
72
|
+
*
|
|
73
|
+
* console.log(besideBounds({ min: [-4, 0, -4], max: [4, 70, 4] }, { min: [8, 0, -5], max: [26, 124, 9] }).max);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function besideBounds(craft: Bounds, placed: Bounds): Bounds;
|
package/dist/beside.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { variantOf } from "./rockets.js";
|
|
2
|
+
/**
|
|
3
|
+
* The rocket that can stand beside another for scale: the real Starship stack, Super Heavy and the ship, Block 3. Beside another craft it is drawn in this package's own paint, so no licensed model comes in unseen.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { BESIDE_ROCKET } from "@johnmorrisdotca/uchusen-rockets";
|
|
8
|
+
*
|
|
9
|
+
* console.log(BESIDE_ROCKET.rocket, BESIDE_ROCKET.variant);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export const BESIDE_ROCKET = { rocket: "starship", variant: "v3" };
|
|
13
|
+
/** What the stack beside is called. */
|
|
14
|
+
export const BESIDE_NAME = "Starship with Super Heavy";
|
|
15
|
+
/**
|
|
16
|
+
* The words that say what the craft beside it is: its name and its sourced length, such as "Starship with Super Heavy, 124.4 m".
|
|
17
|
+
*
|
|
18
|
+
* @returns The label the 3D view puts under the stack.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { besideLabel } from "@johnmorrisdotca/uchusen-rockets";
|
|
22
|
+
*
|
|
23
|
+
* console.log(besideLabel());
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function besideLabel() {
|
|
27
|
+
const height = variantOf(BESIDE_ROCKET.rocket, BESIDE_ROCKET.variant)?.facts.height.value;
|
|
28
|
+
return typeof height === "number" ? `${BESIDE_NAME}, ${height.toLocaleString("en-US")} m` : BESIDE_NAME;
|
|
29
|
+
}
|
|
30
|
+
/** The least room between the two, in metres. */
|
|
31
|
+
export const BESIDE_GAP = 4;
|
|
32
|
+
/**
|
|
33
|
+
* Whether Starship can stand beside this rocket: every rocket but Starship itself.
|
|
34
|
+
*
|
|
35
|
+
* @param rocket A rocket's id.
|
|
36
|
+
* @returns False for Starship, true for any other.
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { canBeside } from "@johnmorrisdotca/uchusen-rockets";
|
|
40
|
+
*
|
|
41
|
+
* console.log(canBeside("saturn-v"), canBeside("starship"));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const canBeside = (rocket) => rocket !== BESIDE_ROCKET.rocket;
|
|
45
|
+
/**
|
|
46
|
+
* Where the other rocket stands: its offset in metres, to add to where its own parts stand, and the box it fills there. It is never scaled. Its foot is on the ground line (y = 0) as every rocket's is, it keeps its own
|
|
47
|
+
* line (z = 0), and its nearest side is a gap clear of the craft's widest, to the right (+X).
|
|
48
|
+
*
|
|
49
|
+
* @param craft The box the craft fills, in metres.
|
|
50
|
+
* @param other The box the other rocket fills on its own, in metres.
|
|
51
|
+
* @returns The offset and the box the other rocket fills once it is there.
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { besideLayout } from "@johnmorrisdotca/uchusen-rockets";
|
|
55
|
+
*
|
|
56
|
+
* const { at, bounds } = besideLayout({ min: [-4, 0, -4], max: [4, 70, 4] }, { min: [-9, 0, -5], max: [9, 124, 9] });
|
|
57
|
+
* console.log(at, bounds.max[1]);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function besideLayout(craft, other) {
|
|
61
|
+
const gap = Math.max(BESIDE_GAP, (craft.max[0] - craft.min[0]) * 0.1);
|
|
62
|
+
const at = [craft.max[0] + gap - other.min[0], 0 - other.min[1], 0];
|
|
63
|
+
return { at, bounds: { min: [other.min[0] + at[0], other.min[1] + at[1], other.min[2]], max: [other.max[0] + at[0], other.max[1] + at[1], other.max[2]] } };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The box that holds both: what the camera must frame, and what Fit fits.
|
|
67
|
+
*
|
|
68
|
+
* @param craft The box the craft fills.
|
|
69
|
+
* @param placed The box the other rocket fills where `besideLayout` put it.
|
|
70
|
+
* @returns The smallest box that holds both.
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { besideBounds } from "@johnmorrisdotca/uchusen-rockets";
|
|
74
|
+
*
|
|
75
|
+
* console.log(besideBounds({ min: [-4, 0, -4], max: [4, 70, 4] }, { min: [8, 0, -5], max: [26, 124, 9] }).max);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function besideBounds(craft, placed) {
|
|
79
|
+
return {
|
|
80
|
+
min: [Math.min(craft.min[0], placed.min[0]), Math.min(craft.min[1], placed.min[1]), Math.min(craft.min[2], placed.min[2])],
|
|
81
|
+
max: [Math.max(craft.max[0], placed.max[0]), Math.max(craft.max[1], placed.max[1]), Math.max(craft.max[2], placed.max[2])],
|
|
82
|
+
};
|
|
83
|
+
}
|
package/dist/build/index.d.ts
CHANGED
|
@@ -65,6 +65,10 @@ export interface SvgOptions {
|
|
|
65
65
|
engines?: boolean | Readonly<Record<string, number>>;
|
|
66
66
|
/** Degrees each moving group of each part is turned from where it is drawn at rest, by slot and then by name: `{ booster: { "grid-fins": 12 } }`. A flight asks for them. */
|
|
67
67
|
moves?: Readonly<Record<string, Readonly<Record<string, number>>>>;
|
|
68
|
+
/** Only these slots, closed up into one stack, centred in `frame` when it is given: a rocket after its first stages have gone. Left out, `show` decides. */
|
|
69
|
+
only?: readonly string[];
|
|
70
|
+
/** The bounds the picture is fitted to, from another build's `boundsOf`: parts drawn with it keep the scale of the whole rocket they came from. */
|
|
71
|
+
frame?: Bounds;
|
|
68
72
|
}
|
|
69
73
|
/**
|
|
70
74
|
* A rocket built from a description: the fit report, its parts, and the ways to ask for its size, where each part stands, its lit engines, a `.glb` file and a drawing.
|
|
@@ -81,6 +85,8 @@ export interface SvgOptions {
|
|
|
81
85
|
export interface Built {
|
|
82
86
|
report: FitReport;
|
|
83
87
|
parts: readonly BuiltPart[];
|
|
88
|
+
/** The parts of the rocket standing beside it, when it was built `beside`; empty otherwise. They are not in `parts`; `placesOf`, `boundsOf`, `glb` and `svg` take them in. Their slots start `beside-`. */
|
|
89
|
+
beside: readonly BuiltPart[];
|
|
84
90
|
/** What can be shown: together, apart and each part. */
|
|
85
91
|
shows: readonly string[];
|
|
86
92
|
/** The bounds of a choice of what to show, in metres, and with `lit` the flames of its engines at full thrust. */
|
|
@@ -131,7 +137,7 @@ export declare function drawnPieces(part: {
|
|
|
131
137
|
* A rocket from a description. Throws FitError when it does not go together.
|
|
132
138
|
*
|
|
133
139
|
* @param description The rocket, its variant and any other part in a slot.
|
|
134
|
-
* @param options `detail`: `"coarse"` leaves out the small 3D-only detail of a very detailed part, for a fast first view; left out, every piece is made.
|
|
140
|
+
* @param options `detail`: `"coarse"` leaves out the small 3D-only detail of a very detailed part, for a fast first view; left out, every piece is made. `beside`: the real Starship stack (Block 3) stands beside it at true scale, on the same ground, and `boundsOf`, `placesOf`, `glb` and `svg` take it in; Starship itself has nothing beside it.
|
|
135
141
|
* @returns The built rocket. It throws a `FitError` when the description does not go together.
|
|
136
142
|
* @example
|
|
137
143
|
* ```ts
|
|
@@ -143,6 +149,7 @@ export declare function drawnPieces(part: {
|
|
|
143
149
|
*/
|
|
144
150
|
export declare function build(description: Description, options?: {
|
|
145
151
|
detail?: DetailLevel;
|
|
152
|
+
beside?: boolean;
|
|
146
153
|
}): Built;
|
|
147
154
|
/**
|
|
148
155
|
* One part on its own, by its id.
|
|
@@ -165,5 +172,5 @@ export declare function buildPart(id: string): {
|
|
|
165
172
|
};
|
|
166
173
|
export { toGlb };
|
|
167
174
|
export type { Glb };
|
|
168
|
-
export { COMPARES, lineupSvg } from "./lineup.ts";
|
|
175
|
+
export { COMPARES, lineupSvg, REAL_CRAFT } from "./lineup.ts";
|
|
169
176
|
export type { CompareId, LineupItem, LineupOptions } from "./lineup.ts";
|
package/dist/build/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The builder: one description into one mesh and one drawing. Works in Node and in a browser, with no dependency: the geometry here is this package's own.
|
|
2
2
|
import { propellantOf } from "../data/propellants.js";
|
|
3
3
|
import { check } from "../fit.js";
|
|
4
|
+
import { BESIDE_ROCKET, besideBounds, besideLayout, canBeside } from "../beside.js";
|
|
4
5
|
import { cleanView, DEFAULT_VIEW, PICTURE_H, PICTURE_W, VIEWS } from "../framing.js";
|
|
5
6
|
import { cleanShow, placesFor, showsOf } from "../layout.js";
|
|
6
7
|
import { DEFAULT_LOOK } from "../looks.js";
|
|
@@ -87,25 +88,20 @@ function flameKindOf(part) {
|
|
|
87
88
|
}
|
|
88
89
|
return "neutral";
|
|
89
90
|
}
|
|
90
|
-
function assembled(report) {
|
|
91
|
-
const
|
|
91
|
+
function assembled(report, other) {
|
|
92
|
+
const own = report.parts.map(({ slot, part }) => {
|
|
92
93
|
const one = made(part.id);
|
|
93
94
|
return { slot, record: part, pieces: one.pieces, get mesh() { return one.mesh; }, bounds: one.bounds, flame: flameKindOf(part) };
|
|
94
95
|
});
|
|
95
|
-
const slots =
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const told = report.variant?.facts.engines[part.slot]?.value;
|
|
102
|
-
return { slot: part.slot, atY: place.at[1], shown: place.shown, pieces: part.pieces, beside: place.at[0] !== 0 || place.at[2] !== 0, dark: told === null || told === undefined };
|
|
103
|
-
}), !show || show === "together");
|
|
104
|
-
};
|
|
105
|
-
const boundsOfShow = (show, lit = false) => {
|
|
96
|
+
const slots = own.map((part) => part.slot);
|
|
97
|
+
const beside = (other?.parts ?? []).map((part) => ({ slot: `beside-${part.slot}`, record: part.record, pieces: part.pieces, get mesh() { return part.mesh; }, bounds: part.bounds, flame: part.flame }));
|
|
98
|
+
// what is drawn: the rocket's parts, then the ones beside it
|
|
99
|
+
const parts = [...own, ...beside];
|
|
100
|
+
const ownPlaces = (show) => placesFor(report.parts, cleanShow(show, slots), (part) => widthOf(part.id));
|
|
101
|
+
const boundsOfPlaces = (places) => {
|
|
106
102
|
const min = [Infinity, Infinity, Infinity];
|
|
107
103
|
const max = [-Infinity, -Infinity, -Infinity];
|
|
108
|
-
for (const place of
|
|
104
|
+
for (const place of places) {
|
|
109
105
|
if (!place.shown)
|
|
110
106
|
continue;
|
|
111
107
|
const { bounds } = parts.find((part) => part.slot === place.slot);
|
|
@@ -114,6 +110,34 @@ function assembled(report) {
|
|
|
114
110
|
max[c] = Math.max(max[c], bounds.max[c] + place.at[c]);
|
|
115
111
|
}
|
|
116
112
|
}
|
|
113
|
+
return { min, max };
|
|
114
|
+
};
|
|
115
|
+
// the other rocket stands whole, on the ground, clear of whatever of this one shows
|
|
116
|
+
const placesOf = (show) => {
|
|
117
|
+
const mine = ownPlaces(show);
|
|
118
|
+
if (!other)
|
|
119
|
+
return mine;
|
|
120
|
+
const { at } = besideLayout(boundsOfPlaces(mine), other.boundsOf("together"));
|
|
121
|
+
return [...mine, ...other.placesOf("together").map((place) => ({ slot: `beside-${place.slot}`, at: [place.at[0] + at[0], place.at[1] + at[1], place.at[2] + at[2]], shown: true }))];
|
|
122
|
+
};
|
|
123
|
+
const flamesAt = (places, together) => litBells(places.map((place) => {
|
|
124
|
+
const part = parts.find((p) => p.slot === place.slot);
|
|
125
|
+
const told = report.variant?.facts.engines[part.slot]?.value;
|
|
126
|
+
return { slot: part.slot, atY: place.at[1], shown: place.shown, pieces: part.pieces, beside: place.at[0] !== 0 || place.at[2] !== 0, dark: told === null || told === undefined };
|
|
127
|
+
}), together);
|
|
128
|
+
const flamesOf = (show) => flamesAt(placesOf(show), !show || show === "together");
|
|
129
|
+
// the parts that are left, closed up joint to joint, and centred in a frame when there is one
|
|
130
|
+
const stackOf = (only, frame) => {
|
|
131
|
+
const stacked = placesFor(report.parts.filter(({ slot }) => only.includes(slot)), "together", (part) => widthOf(part.id));
|
|
132
|
+
if (!frame || stacked.length === 0)
|
|
133
|
+
return stacked;
|
|
134
|
+
const mine = boundsOfPlaces(stacked);
|
|
135
|
+
const dy = (frame.min[1] + frame.max[1]) / 2 - (mine.min[1] + mine.max[1]) / 2;
|
|
136
|
+
return stacked.map((place) => ({ ...place, at: [place.at[0], place.at[1] + dy, place.at[2]] }));
|
|
137
|
+
};
|
|
138
|
+
const boundsOfShow = (show, lit = false) => {
|
|
139
|
+
const mine = boundsOfPlaces(ownPlaces(show));
|
|
140
|
+
const { min, max } = other ? besideBounds(mine, besideLayout(mine, other.boundsOf("together")).bounds) : mine;
|
|
117
141
|
if (lit)
|
|
118
142
|
for (const bell of flamesOf(show)) {
|
|
119
143
|
const place = placesOf(show).find((one) => one.slot === bell.slot);
|
|
@@ -123,17 +147,19 @@ function assembled(report) {
|
|
|
123
147
|
};
|
|
124
148
|
return {
|
|
125
149
|
report,
|
|
126
|
-
parts,
|
|
150
|
+
parts: own,
|
|
151
|
+
beside,
|
|
127
152
|
shows: showsOf(slots),
|
|
128
153
|
boundsOf: boundsOfShow,
|
|
129
154
|
placesOf,
|
|
130
155
|
flamesOf,
|
|
131
156
|
glb: () => toGlb(parts.map((part) => ({ name: part.slot, mesh: part.mesh, at: placesOf("together").find((place) => place.slot === part.slot)?.at ?? [0, 0, 0] }))),
|
|
132
|
-
svg: ({ look = DEFAULT_LOOK, view = DEFAULT_VIEW, show, engines = false, moves } = {}) => {
|
|
157
|
+
svg: ({ look = DEFAULT_LOOK, view = DEFAULT_VIEW, show, engines = false, moves, only, frame } = {}) => {
|
|
133
158
|
const angle = typeof view === "string" ? VIEWS[cleanView(view)] : view;
|
|
134
|
-
const
|
|
159
|
+
const placed = only ? stackOf(only, frame) : placesOf(show);
|
|
160
|
+
const places = placed.filter((place) => place.shown);
|
|
135
161
|
// a flame drawn at full thrust is held in the picture; a count of engines is the timeline's, which has its own room
|
|
136
|
-
const cam = perspective(boundsOfShow(show, engines === true), angle);
|
|
162
|
+
const cam = perspective(frame ?? boundsOfShow(show, engines === true), angle);
|
|
137
163
|
const ordered = places
|
|
138
164
|
.map((place) => {
|
|
139
165
|
const part = parts.find((p) => p.slot === place.slot);
|
|
@@ -141,11 +167,11 @@ function assembled(report) {
|
|
|
141
167
|
return { place, part, depth: cam.depth(middle) };
|
|
142
168
|
})
|
|
143
169
|
.sort((a, b) => b.depth - a.depth);
|
|
144
|
-
const together = !show || show === "together";
|
|
170
|
+
const together = only !== undefined || !show || show === "together";
|
|
145
171
|
const flames = typeof engines === "object"
|
|
146
172
|
? parts.flatMap((part) => pickBells(bellsOf(part.pieces), engines[part.slot] ?? 0).map((bell) => ({ ...bell, slot: part.slot })))
|
|
147
173
|
: engines
|
|
148
|
-
? flamesOf(show).filter((bell) => places.some((place) => place.slot === bell.slot))
|
|
174
|
+
? (only ? flamesAt(placed, true) : flamesOf(show)).filter((bell) => places.some((place) => place.slot === bell.slot))
|
|
149
175
|
: [];
|
|
150
176
|
return wrap(ordered
|
|
151
177
|
.map(({ place, part }) => drawPart(moved(drawnPieces(part, together), moves?.[part.slot] ?? {}), place.at, cam, look) + plumeMarkup(flames.filter((bell) => bell.slot === place.slot), place.at, cam, look, part.slot, part.flame))
|
|
@@ -177,7 +203,7 @@ export function drawnPieces(part, together) {
|
|
|
177
203
|
* A rocket from a description. Throws FitError when it does not go together.
|
|
178
204
|
*
|
|
179
205
|
* @param description The rocket, its variant and any other part in a slot.
|
|
180
|
-
* @param options `detail`: `"coarse"` leaves out the small 3D-only detail of a very detailed part, for a fast first view; left out, every piece is made.
|
|
206
|
+
* @param options `detail`: `"coarse"` leaves out the small 3D-only detail of a very detailed part, for a fast first view; left out, every piece is made. `beside`: the real Starship stack (Block 3) stands beside it at true scale, on the same ground, and `boundsOf`, `placesOf`, `glb` and `svg` take it in; Starship itself has nothing beside it.
|
|
181
207
|
* @returns The built rocket. It throws a `FitError` when the description does not go together.
|
|
182
208
|
* @example
|
|
183
209
|
* ```ts
|
|
@@ -194,7 +220,7 @@ export function build(description, options = {}) {
|
|
|
194
220
|
const before = detail.level;
|
|
195
221
|
detail.level = options.detail ?? "full";
|
|
196
222
|
try {
|
|
197
|
-
const built = assembled(report);
|
|
223
|
+
const built = assembled(report, options.beside && canBeside(description.rocket) ? build(BESIDE_ROCKET, { detail: "full" }) : undefined);
|
|
198
224
|
// a part's pieces are made when it is assembled, at this level: the lazy parts of a heavy part keep the level they were made at
|
|
199
225
|
return built;
|
|
200
226
|
}
|
|
@@ -232,4 +258,4 @@ export function buildPart(id) {
|
|
|
232
258
|
};
|
|
233
259
|
}
|
|
234
260
|
export { toGlb };
|
|
235
|
-
export { COMPARES, lineupSvg } from "./lineup.js";
|
|
261
|
+
export { COMPARES, lineupSvg, REAL_CRAFT } from "./lineup.js";
|
package/dist/build/lineup.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ViewName } from "../framing.ts";
|
|
2
2
|
import { type LookName } from "../looks.ts";
|
|
3
|
+
import { type Camera } from "../draw/svg.ts";
|
|
4
|
+
import type { Bounds } from "../model/pieces.ts";
|
|
3
5
|
/**
|
|
4
6
|
* Comparison shapes: plain round sizes, for scale, not facts about anything.
|
|
5
7
|
*
|
|
@@ -34,6 +36,34 @@ export declare const COMPARES: {
|
|
|
34
36
|
* ```
|
|
35
37
|
*/
|
|
36
38
|
export type CompareId = keyof typeof COMPARES;
|
|
39
|
+
/**
|
|
40
|
+
* The real craft that lie in the fleet band for scale: Starship with Super Heavy, Saturn V, SLS and the International Space Station. Each is a variant of the package, so its length is the sourced figure, `axis` is the way its length runs in its own model
|
|
41
|
+
* (`y` up the stack, nose up; `x` along the station's long axis), and a rocket points its nose right. They are never scaled: they are drawn at the lineup's own scale.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { REAL_CRAFT } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
46
|
+
*
|
|
47
|
+
* console.log(REAL_CRAFT.map((craft) => craft.name));
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare const REAL_CRAFT: readonly [{
|
|
51
|
+
readonly key: "starship:v3";
|
|
52
|
+
readonly name: "Starship with Super Heavy";
|
|
53
|
+
readonly axis: "y";
|
|
54
|
+
}, {
|
|
55
|
+
readonly key: "saturn-v:saturn-v";
|
|
56
|
+
readonly name: "Saturn V";
|
|
57
|
+
readonly axis: "y";
|
|
58
|
+
}, {
|
|
59
|
+
readonly key: "sls:block-1";
|
|
60
|
+
readonly name: "SLS Block 1";
|
|
61
|
+
readonly axis: "y";
|
|
62
|
+
}, {
|
|
63
|
+
readonly key: "iss:iss";
|
|
64
|
+
readonly name: "ISS";
|
|
65
|
+
readonly axis: "x";
|
|
66
|
+
}];
|
|
37
67
|
/**
|
|
38
68
|
* One thing standing in a lineup: its key (`starship:v3` or a comparison's name), its label and its height in metres.
|
|
39
69
|
*
|
|
@@ -72,6 +102,8 @@ export interface LineupOptions {
|
|
|
72
102
|
show?: string;
|
|
73
103
|
ruler?: boolean;
|
|
74
104
|
labels?: boolean;
|
|
105
|
+
/** Real craft for scale: Starship with Super Heavy, Saturn V, SLS and the ISS lie sideways in the fleet band, at the same scale as the fleet ships, nose right, each labelled with its name and length. Left out: not drawn. Only where the fleet band is. */
|
|
106
|
+
real?: boolean;
|
|
75
107
|
/** `rockets` or `fleet` leaves out the other kind; `all` (the default) draws both. A fleet ship (a rocket whose id starts with `fleet-`) lies lengthwise in its own band below the rockets. */
|
|
76
108
|
filter?: "all" | "rockets" | "fleet";
|
|
77
109
|
/** Shrinks the scale, never grows it, until the whole drawing fits this many pixels across and down (either may be left out). Without it the scale is `unit`, as it always was. */
|
|
@@ -84,7 +116,7 @@ export interface LineupOptions {
|
|
|
84
116
|
* The whole picture as SVG text. Unknown items are left out.
|
|
85
117
|
*
|
|
86
118
|
* @param options The items and how they are drawn.
|
|
87
|
-
* @returns The picture as SVG text, the items it holds, its width and height and the scale it was drawn at (pixels a metre: less than `unit` when `fit` shrank it).
|
|
119
|
+
* @returns The picture as SVG text, the items it holds, the real craft lying beside the fleet (`real`, with their lengths), its width and height and the scale it was drawn at (pixels a metre: less than `unit` when `fit` shrank it).
|
|
88
120
|
* @example
|
|
89
121
|
* ```ts
|
|
90
122
|
* import { lineupSvg } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
@@ -96,7 +128,13 @@ export interface LineupOptions {
|
|
|
96
128
|
export declare function lineupSvg(options: LineupOptions): {
|
|
97
129
|
svg: string;
|
|
98
130
|
items: LineupItem[];
|
|
131
|
+
real: LineupItem[];
|
|
99
132
|
width: number;
|
|
100
133
|
height: number;
|
|
101
134
|
unit: number;
|
|
102
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* The camera of a real craft lying in the fleet band, square on to its windward side: a rocket (`y`) points its nose right with its windward side towards us, and the station (`x`) runs along its long axis with y up. Orthographic at the lineup's scale, so
|
|
138
|
+
* a metre is the same on it as on every ship.
|
|
139
|
+
*/
|
|
140
|
+
export declare function lyingCamera(axis: "x" | "y", b: Bounds, x0: number, baseY: number, unit: number): Camera;
|
package/dist/build/lineup.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Rockets and what to compare them with, side by side on one baseline at one scale, as one drawing with a ruler. Orthographic, so a metre is the same on every one. Pure.
|
|
2
|
+
import { BESIDE_NAME } from "../beside.js";
|
|
2
3
|
import { build, drawnPieces } from "./index.js";
|
|
3
4
|
import { check } from "../fit.js";
|
|
4
5
|
import { axesOf, cleanView, directionOf, VIEWS } from "../framing.js";
|
|
@@ -20,6 +21,23 @@ export const COMPARES = {
|
|
|
20
21
|
person: { name: "A person, 1.8 m", height: 1.8, width: 0.5 },
|
|
21
22
|
building: { name: "A ten-storey building, 30 m", height: 30, width: 18 },
|
|
22
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* The real craft that lie in the fleet band for scale: Starship with Super Heavy, Saturn V, SLS and the International Space Station. Each is a variant of the package, so its length is the sourced figure, `axis` is the way its length runs in its own model
|
|
26
|
+
* (`y` up the stack, nose up; `x` along the station's long axis), and a rocket points its nose right. They are never scaled: they are drawn at the lineup's own scale.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { REAL_CRAFT } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
31
|
+
*
|
|
32
|
+
* console.log(REAL_CRAFT.map((craft) => craft.name));
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export const REAL_CRAFT = [
|
|
36
|
+
{ key: "starship:v3", name: BESIDE_NAME, axis: "y" },
|
|
37
|
+
{ key: "saturn-v:saturn-v", name: "Saturn V", axis: "y" },
|
|
38
|
+
{ key: "sls:block-1", name: "SLS Block 1", axis: "y" },
|
|
39
|
+
{ key: "iss:iss", name: "ISS", axis: "x" },
|
|
40
|
+
];
|
|
23
41
|
const xmlText = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
24
42
|
const n = (v) => (Math.round(v * 10) / 10).toString();
|
|
25
43
|
/** A rocket painted black all over vanishes on the dark ground of a lineup, so it takes the line look's colour for its outline. Only the lines change: its shape and its figures stay. */
|
|
@@ -47,7 +65,7 @@ function comparison(id, x, base, unit, line, fill) {
|
|
|
47
65
|
* The whole picture as SVG text. Unknown items are left out.
|
|
48
66
|
*
|
|
49
67
|
* @param options The items and how they are drawn.
|
|
50
|
-
* @returns The picture as SVG text, the items it holds, its width and height and the scale it was drawn at (pixels a metre: less than `unit` when `fit` shrank it).
|
|
68
|
+
* @returns The picture as SVG text, the items it holds, the real craft lying beside the fleet (`real`, with their lengths), its width and height and the scale it was drawn at (pixels a metre: less than `unit` when `fit` shrank it).
|
|
51
69
|
* @example
|
|
52
70
|
* ```ts
|
|
53
71
|
* import { lineupSvg } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
@@ -78,6 +96,19 @@ function lying(built) {
|
|
|
78
96
|
const b = built.boundsOf("together");
|
|
79
97
|
return { length: b.max[1] - b.min[1], beam: b.max[0] - b.min[0], height: b.max[2] - b.min[2], from: b.min[1], low: b.min[2] };
|
|
80
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The camera of a real craft lying in the fleet band, square on to its windward side: a rocket (`y`) points its nose right with its windward side towards us, and the station (`x`) runs along its long axis with y up. Orthographic at the lineup's scale, so
|
|
101
|
+
* a metre is the same on it as on every ship.
|
|
102
|
+
*/
|
|
103
|
+
export function lyingCamera(axis, b, x0, baseY, unit) {
|
|
104
|
+
const along = axis === "y" ? 1 : 0;
|
|
105
|
+
return {
|
|
106
|
+
project: (p) => [x0 + (p[along] - b.min[along]) * unit, axis === "y" ? baseY + (p[0] - b.max[0]) * unit : baseY - (p[1] - b.min[1]) * unit],
|
|
107
|
+
depth: (p) => -p[2],
|
|
108
|
+
toward: () => [0, 0, 1],
|
|
109
|
+
above: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
81
112
|
function draw(options, fleetKeys, unit) {
|
|
82
113
|
const { items, look = DEFAULT_LOOK, view = "front", ruler = true, labels = true } = options;
|
|
83
114
|
const angle = VIEWS[cleanView(view)];
|
|
@@ -172,8 +203,44 @@ function draw(options, fleetKeys, unit) {
|
|
|
172
203
|
longest = Math.max(longest, length);
|
|
173
204
|
fleetY = baseY + (labels ? 26 : 12);
|
|
174
205
|
}
|
|
206
|
+
// the real craft for scale lie in the same band, one row each, at the same scale: a rocket nose right, the station along its long axis
|
|
207
|
+
const real = [];
|
|
208
|
+
let realEnd = 0;
|
|
209
|
+
if (ships.length && options.real)
|
|
210
|
+
for (const craft of REAL_CRAFT) {
|
|
211
|
+
const [rocket, variant] = craft.key.split(":");
|
|
212
|
+
const named = variantOf(rocket, variant);
|
|
213
|
+
if (!named || !check({ rocket, variant: named.id }).ok)
|
|
214
|
+
continue;
|
|
215
|
+
const built = build({ rocket, variant: named.id });
|
|
216
|
+
const b = built.boundsOf("together");
|
|
217
|
+
// a rocket's long axis is y and the picture's up is -x (its windward side faces us); the station's is x, with y up
|
|
218
|
+
const along = craft.axis === "y" ? 1 : 0;
|
|
219
|
+
const length = b.max[along] - b.min[along];
|
|
220
|
+
const tall = craft.axis === "y" ? b.max[0] - b.min[0] : b.max[1] - b.min[1];
|
|
221
|
+
const baseY = fleetY + 6 + tall * unit;
|
|
222
|
+
const x0 = rulerRoom;
|
|
223
|
+
const cam = lyingCamera(craft.axis, b, x0, baseY, unit);
|
|
224
|
+
const show = built.shows[0];
|
|
225
|
+
const edge = edgeOf(built, look);
|
|
226
|
+
for (const place of built.placesOf(show).filter((one) => one.shown).sort((a, c) => a.at[2] - c.at[2])) {
|
|
227
|
+
const part = built.parts.find((p) => p.slot === place.slot);
|
|
228
|
+
if (part)
|
|
229
|
+
fleetBody.push(drawPart(drawnPieces(part, show === "together"), place.at, cam, look, 0.7, edge));
|
|
230
|
+
}
|
|
231
|
+
fleetBody.push(`<path d="M${n(x0)} ${n(baseY)}H${n(x0 + length * unit)}" stroke="currentColor" stroke-width=".6" opacity=".4"/>`);
|
|
232
|
+
// the length is the sourced one: a rocket's height, the station's overall length
|
|
233
|
+
const sourced = craft.axis === "y" ? named.facts.height.value : named.facts.diameter.value;
|
|
234
|
+
const metres = typeof sourced === "number" ? sourced : Math.round(length * 10) / 10;
|
|
235
|
+
const text = `${craft.name}, ${metres.toLocaleString("en-US")} m`;
|
|
236
|
+
if (labels)
|
|
237
|
+
fleetBody.push(`<text x="${n(x0 + length * unit + 8)}" y="${n(baseY - 2)}" font-size="11" fill="currentColor">${xmlText(text)}</text>`);
|
|
238
|
+
real.push({ key: craft.key, label: craft.name, height: metres });
|
|
239
|
+
realEnd = Math.max(realEnd, x0 + length * unit + (labels ? 8 + text.length * 6.2 : 0));
|
|
240
|
+
fleetY = baseY + 10;
|
|
241
|
+
}
|
|
175
242
|
if (ships.length) {
|
|
176
|
-
width = Math.max(width, rulerRoom + longest * unit + 8);
|
|
243
|
+
width = Math.max(width, rulerRoom + longest * unit + 8, realEnd + 8);
|
|
177
244
|
if (ruler) {
|
|
178
245
|
// the metres, along the ground of the fleet band, the same scale as the rockets' ruler
|
|
179
246
|
const steps = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000];
|
|
@@ -196,5 +263,5 @@ function draw(options, fleetKeys, unit) {
|
|
|
196
263
|
rule += `<text x="${rulerRoom - 16}" y="${n(base - tallest * unit - 10)}" text-anchor="end" font-size="10" fill="currentColor" opacity=".85">m</text><path d="M${rulerRoom - 8} ${n(base)}H${n(width)}" stroke="currentColor" stroke-width=".6" opacity=".4"/>`;
|
|
197
264
|
}
|
|
198
265
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n(width)} ${n(height)}" width="${n(width)}" height="${n(height)}" role="img" aria-label="${xmlText(`To scale: ${[...known.map((item) => item.label), ...ships.map((ship) => ship.label)].join(", ")}`)}">${defsOf(look)}${rule}${body.join("")}${fleetBody.join("")}</svg>`;
|
|
199
|
-
return { svg, width, height, items: [...known.map(({ key, label, height: h }) => ({ key, label, height: h })), ...ships.map((ship) => ({ key: ship.key, label: ship.label, height: ship.size.length }))] };
|
|
266
|
+
return { svg, width, height, real, items: [...known.map(({ key, label, height: h }) => ({ key, label, height: h })), ...ships.map((ship) => ({ key: ship.key, label: ship.label, height: ship.size.length }))] };
|
|
200
267
|
}
|
package/dist/data/sources.d.ts
CHANGED
|
@@ -652,6 +652,14 @@ export declare const SOURCES: {
|
|
|
652
652
|
readonly read: "direct";
|
|
653
653
|
readonly licence: "MIT";
|
|
654
654
|
};
|
|
655
|
+
readonly "wikipedia-voyager-1": {
|
|
656
|
+
readonly title: "Voyager 1 (its speed leaving the Solar System, 61,602 km/h in 2013)";
|
|
657
|
+
readonly publisher: "Wikipedia";
|
|
658
|
+
readonly url: "https://en.wikipedia.org/wiki/Voyager_1";
|
|
659
|
+
readonly date: "2026-11-04";
|
|
660
|
+
readonly read: "direct";
|
|
661
|
+
readonly licence: "CC BY-SA 4.0";
|
|
662
|
+
};
|
|
655
663
|
};
|
|
656
664
|
/**
|
|
657
665
|
* The id of a source in `SOURCES`.
|
package/dist/data/sources.js
CHANGED
|
@@ -91,4 +91,5 @@ export const SOURCES = {
|
|
|
91
91
|
"sfn-cots1-presskit": { title: "SpaceX COTS Demo Flight 1 press kit (its mission timeline, page 7)", publisher: "SpaceX, as hosted by Spaceflight Now", url: "https://spaceflightnow.com/falcon9/002/cots1_presskit.pdf", date: "2026-11-03", read: "direct" },
|
|
92
92
|
"sfn-flight-35": { title: "Timeline of SpaceX's 35th Falcon 9 launch", publisher: "Spaceflight Now", url: "https://spaceflightnow.com/?p=24998", date: "2017-05-31", read: "direct" },
|
|
93
93
|
"uchusen-fleet-ship-02": { title: "ISV 加美鳴 (かみなり) Kaminari, SPX-207, the Interstellar Heavy Transport: an original SPX fleet design (docs/fleet/ship-02.md)", publisher: "John Morris, uchusen-rockets", url: "https://github.com/johnmorrisdotca/uchusen-rockets/blob/main/docs/fleet/ship-02.md", date: "2026-11-04", read: "direct", licence: "MIT" },
|
|
94
|
+
"wikipedia-voyager-1": { title: "Voyager 1 (its speed leaving the Solar System, 61,602 km/h in 2013)", publisher: "Wikipedia", url: "https://en.wikipedia.org/wiki/Voyager_1", date: "2026-11-04", read: "direct", licence: "CC BY-SA 4.0" },
|
|
94
95
|
};
|