@johnmorrisdotca/uchusen-rockets 0.3.0 → 0.5.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 +31 -5
- package/NOTICE.md +2 -0
- package/README.md +316 -4
- package/dist/assets/droneship-asog-landed.glb +0 -0
- package/dist/assets/droneship-asog.glb +0 -0
- package/dist/assets/droneship-jrti-landed.glb +0 -0
- package/dist/assets/droneship-jrti.glb +0 -0
- package/dist/assets/droneship-ocisly-landed.glb +0 -0
- package/dist/assets/droneship-ocisly.glb +0 -0
- package/dist/assets/falcon-9-block-5-dragon-2-first-stage.glb +0 -0
- package/dist/assets/falcon-9-block-5-dragon-2.glb +0 -0
- package/dist/assets/falcon-9-block-5-first-stage.glb +0 -0
- package/dist/assets/falcon-9-block-5.glb +0 -0
- package/dist/assets/falcon-9-ft-first-stage.glb +0 -0
- package/dist/assets/falcon-9-ft.glb +0 -0
- package/dist/assets/falcon-9-v1.1-first-stage.glb +0 -0
- package/dist/assets/falcon-9-v1.1.glb +0 -0
- package/dist/assets/falcon-heavy-falcon-heavy-centre.glb +0 -0
- package/dist/assets/falcon-heavy-falcon-heavy-side-left.glb +0 -0
- package/dist/assets/falcon-heavy-falcon-heavy-side-right.glb +0 -0
- package/dist/assets/falcon-heavy-falcon-heavy.glb +0 -0
- package/dist/assets/landing-zone-lz-1-landed.glb +0 -0
- package/dist/assets/landing-zone-lz-1.glb +0 -0
- package/dist/assets/landing-zone-lz-4-landed.glb +0 -0
- package/dist/assets/landing-zone-lz-4.glb +0 -0
- package/dist/assets/launch-tower-catch-booster.glb +0 -0
- package/dist/assets/launch-tower-catch.glb +0 -0
- package/dist/assets/launch-tower-stack-booster.glb +0 -0
- package/dist/assets/launch-tower-stack-ship.glb +0 -0
- package/dist/assets/launch-tower-stack.glb +0 -0
- package/dist/assets/starship-2019.glb +0 -0
- package/dist/assets/starship-v1-booster.glb +0 -0
- package/dist/assets/starship-v1-flight1-booster.glb +0 -0
- package/dist/assets/starship-v1-flight1-ship.glb +0 -0
- package/dist/assets/starship-v1-flight1.glb +0 -0
- package/dist/assets/starship-v1-ship.glb +0 -0
- package/dist/assets/starship-v1.glb +0 -0
- package/dist/assets/starship-v2-booster.glb +0 -0
- package/dist/assets/starship-v2-ship.glb +0 -0
- package/dist/assets/starship-v2.glb +0 -0
- package/dist/assets/starship-v3-booster.glb +0 -0
- package/dist/assets/starship-v3-ship.glb +0 -0
- package/dist/assets/starship-v3.glb +0 -0
- package/dist/assets/starship-v4-booster.glb +0 -0
- package/dist/assets/starship-v4-ship.glb +0 -0
- package/dist/assets/starship-v4.glb +0 -0
- package/dist/attrs.d.ts +70 -4
- package/dist/attrs.js +59 -4
- package/dist/build/glb.d.ts +25 -0
- package/dist/build/glb.js +29 -7
- package/dist/build/index.d.ts +94 -4
- package/dist/build/index.js +71 -7
- package/dist/build/lineup.d.ts +56 -2
- package/dist/build/lineup.js +23 -2
- package/dist/builder.d.ts +47 -1
- package/dist/builder.js +14 -1
- package/dist/crossfade.d.ts +44 -2
- package/dist/crossfade.js +44 -2
- package/dist/data/credits.d.ts +44 -1
- package/dist/data/credits.js +22 -1
- package/dist/data/flight-log.d.ts +33 -1
- package/dist/data/flight-log.js +22 -1
- package/dist/data/flights.d.ts +61 -2
- package/dist/data/flights.js +160 -3
- package/dist/data/makers.d.ts +21 -0
- package/dist/data/makers.js +10 -0
- package/dist/data/propellants.d.ts +59 -0
- package/dist/data/propellants.js +43 -0
- package/dist/data/sources.d.ts +102 -0
- package/dist/data/sources.js +19 -0
- package/dist/data/types.d.ts +81 -1
- package/dist/data/types.js +26 -1
- package/dist/draw/plume.d.ts +10 -1
- package/dist/draw/plume.js +15 -5
- package/dist/element/plumes.d.ts +7 -8
- package/dist/element/plumes.js +36 -21
- package/dist/element/scene.js +30 -5
- package/dist/element.d.ts +61 -3
- package/dist/element.js +53 -5
- package/dist/embed.d.ts +110 -5
- package/dist/embed.js +88 -5
- package/dist/engines.d.ts +86 -4
- package/dist/engines.js +75 -4
- package/dist/fit.d.ts +68 -0
- package/dist/fit.js +13 -0
- package/dist/flight.d.ts +136 -9
- package/dist/flight.js +130 -10
- package/dist/flightbar.d.ts +12 -0
- package/dist/flightbar.js +13 -1
- package/dist/framing.d.ts +182 -8
- package/dist/framing.js +127 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/layout.d.ts +65 -1
- package/dist/layout.js +43 -1
- package/dist/looks.d.ts +64 -1
- package/dist/looks.js +42 -1
- package/dist/model/falcon.js +2 -2
- package/dist/model/materials.d.ts +32 -0
- package/dist/model/materials.js +10 -0
- package/dist/model/moves.d.ts +36 -0
- package/dist/model/moves.js +70 -0
- package/dist/model/pieces.d.ts +31 -0
- package/dist/model/pieces.js +51 -9
- package/dist/model/registry.d.ts +22 -0
- package/dist/model/registry.js +22 -0
- package/dist/model/starship.d.ts +6 -3
- package/dist/model/starship.js +12 -7
- package/dist/parts.d.ts +33 -1
- package/dist/pixels.d.ts +44 -4
- package/dist/pixels.js +44 -4
- package/dist/react.d.ts +26 -2
- package/dist/react.js +13 -0
- package/dist/rockets.d.ts +91 -3
- package/dist/rockets.js +58 -2
- package/dist/spin.d.ts +57 -2
- package/dist/spin.js +35 -2
- package/dist/tilt.d.ts +48 -4
- package/dist/tilt.js +26 -4
- package/package.json +27 -7
package/dist/attrs.d.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The attributes of `<uchusen-rocket>` that choose what is drawn, in the order the demo writes them. The `embed.html` query uses the same words.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { OPTION_ATTRIBUTES } from "@johnmorrisdotca/uchusen-rockets";
|
|
7
|
+
*
|
|
8
|
+
* console.log(OPTION_ATTRIBUTES.join(", "));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
1
11
|
export declare const OPTION_ATTRIBUTES: readonly ["rocket", "variant", "show", "look", "view", "build", "mode", "spin", "way", "label", "assets", "engines", "phase"];
|
|
12
|
+
/**
|
|
13
|
+
* Every choice that draws a rocket, as plain text: the same names and values as the element's attributes. Nothing here is trusted. A value outside its list falls back to a default when it is drawn.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import type { RocketOptions } from "@johnmorrisdotca/uchusen-rockets";
|
|
18
|
+
*
|
|
19
|
+
* const options: RocketOptions = { rocket: "falcon-9", variant: "block-5", look: "thick", view: "below" };
|
|
20
|
+
* console.log(options);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
2
23
|
export interface RocketOptions {
|
|
3
24
|
rocket?: string;
|
|
4
25
|
variant?: string;
|
|
@@ -17,11 +38,56 @@ export interface RocketOptions {
|
|
|
17
38
|
/** A phase of the rocket's flight, by its id: the engines that burn there. */
|
|
18
39
|
phase?: string;
|
|
19
40
|
}
|
|
20
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The attributes that draw these options. Empty values are left out.
|
|
43
|
+
*
|
|
44
|
+
* @param options The choices to turn into attributes.
|
|
45
|
+
* @returns The attributes by name, as text. A choice left out or empty has no entry.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { attributesFromOptions } from "@johnmorrisdotca/uchusen-rockets";
|
|
49
|
+
*
|
|
50
|
+
* console.log(attributesFromOptions({ rocket: "starship", variant: "v3", look: "painted", show: "" }));
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
21
53
|
export declare function attributesFromOptions(options: RocketOptions): Record<string, string>;
|
|
22
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* The options `read` finds. A missing attribute is left out; nothing here is trusted as a real rocket.
|
|
56
|
+
*
|
|
57
|
+
* @param read Reads one attribute by name and gives null when it is missing, as `element.getAttribute` does.
|
|
58
|
+
* @returns The choices found, as written. A missing attribute is left out of the result.
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { optionsFromAttributes } from "@johnmorrisdotca/uchusen-rockets";
|
|
62
|
+
*
|
|
63
|
+
* const attributes: Record<string, string> = { rocket: "starship", look: "thick" };
|
|
64
|
+
* console.log(optionsFromAttributes((name) => attributes[name] ?? null));
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
23
67
|
export declare function optionsFromAttributes(read: (name: string) => string | null): RocketOptions;
|
|
24
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The options as a query for `embed.html`.
|
|
70
|
+
*
|
|
71
|
+
* @param options The choices to write.
|
|
72
|
+
* @returns A query for `embed.html`, with one entry for each choice made.
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { rocketToParams } from "@johnmorrisdotca/uchusen-rockets";
|
|
76
|
+
*
|
|
77
|
+
* console.log(rocketToParams({ rocket: "falcon-9", show: "apart" }).toString());
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
25
80
|
export declare function rocketToParams(options: RocketOptions): URLSearchParams;
|
|
26
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* The options a query names.
|
|
83
|
+
*
|
|
84
|
+
* @param params The query of an `embed.html` address.
|
|
85
|
+
* @returns The choices the query names.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { rocketFromParams } from "@johnmorrisdotca/uchusen-rockets";
|
|
89
|
+
*
|
|
90
|
+
* console.log(rocketFromParams(new URLSearchParams("rocket=falcon-9&look=thick")));
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
27
93
|
export declare function rocketFromParams(params: URLSearchParams): RocketOptions;
|
package/dist/attrs.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
// A rocket's options as text: the attributes of `<uchusen-rocket>` and the query of `embed.html` are the same words, read by one function and written by another.
|
|
2
|
+
/**
|
|
3
|
+
* The attributes of `<uchusen-rocket>` that choose what is drawn, in the order the demo writes them. The `embed.html` query uses the same words.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { OPTION_ATTRIBUTES } from "@johnmorrisdotca/uchusen-rockets";
|
|
8
|
+
*
|
|
9
|
+
* console.log(OPTION_ATTRIBUTES.join(", "));
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
2
12
|
export const OPTION_ATTRIBUTES = ["rocket", "variant", "show", "look", "view", "build", "mode", "spin", "way", "label", "assets", "engines", "phase"];
|
|
3
13
|
const text = (value) => (typeof value === "string" && value !== "" ? value : undefined);
|
|
4
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The attributes that draw these options. Empty values are left out.
|
|
16
|
+
*
|
|
17
|
+
* @param options The choices to turn into attributes.
|
|
18
|
+
* @returns The attributes by name, as text. A choice left out or empty has no entry.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { attributesFromOptions } from "@johnmorrisdotca/uchusen-rockets";
|
|
22
|
+
*
|
|
23
|
+
* console.log(attributesFromOptions({ rocket: "starship", variant: "v3", look: "painted", show: "" }));
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
5
26
|
export function attributesFromOptions(options) {
|
|
6
27
|
const out = {};
|
|
7
28
|
for (const name of OPTION_ATTRIBUTES) {
|
|
@@ -11,7 +32,19 @@ export function attributesFromOptions(options) {
|
|
|
11
32
|
}
|
|
12
33
|
return out;
|
|
13
34
|
}
|
|
14
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The options `read` finds. A missing attribute is left out; nothing here is trusted as a real rocket.
|
|
37
|
+
*
|
|
38
|
+
* @param read Reads one attribute by name and gives null when it is missing, as `element.getAttribute` does.
|
|
39
|
+
* @returns The choices found, as written. A missing attribute is left out of the result.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { optionsFromAttributes } from "@johnmorrisdotca/uchusen-rockets";
|
|
43
|
+
*
|
|
44
|
+
* const attributes: Record<string, string> = { rocket: "starship", look: "thick" };
|
|
45
|
+
* console.log(optionsFromAttributes((name) => attributes[name] ?? null));
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
15
48
|
export function optionsFromAttributes(read) {
|
|
16
49
|
const options = {};
|
|
17
50
|
for (const name of OPTION_ATTRIBUTES) {
|
|
@@ -21,11 +54,33 @@ export function optionsFromAttributes(read) {
|
|
|
21
54
|
}
|
|
22
55
|
return options;
|
|
23
56
|
}
|
|
24
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* The options as a query for `embed.html`.
|
|
59
|
+
*
|
|
60
|
+
* @param options The choices to write.
|
|
61
|
+
* @returns A query for `embed.html`, with one entry for each choice made.
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { rocketToParams } from "@johnmorrisdotca/uchusen-rockets";
|
|
65
|
+
*
|
|
66
|
+
* console.log(rocketToParams({ rocket: "falcon-9", show: "apart" }).toString());
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
25
69
|
export function rocketToParams(options) {
|
|
26
70
|
return new URLSearchParams(attributesFromOptions(options));
|
|
27
71
|
}
|
|
28
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The options a query names.
|
|
74
|
+
*
|
|
75
|
+
* @param params The query of an `embed.html` address.
|
|
76
|
+
* @returns The choices the query names.
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { rocketFromParams } from "@johnmorrisdotca/uchusen-rockets";
|
|
80
|
+
*
|
|
81
|
+
* console.log(rocketFromParams(new URLSearchParams("rocket=falcon-9&look=thick")));
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
29
84
|
export function rocketFromParams(params) {
|
|
30
85
|
return optionsFromAttributes((name) => params.get(name));
|
|
31
86
|
}
|
package/dist/build/glb.d.ts
CHANGED
|
@@ -5,8 +5,33 @@ export interface GlbPart {
|
|
|
5
5
|
mesh: PartMesh;
|
|
6
6
|
at: readonly [number, number, number];
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* A binary glTF file: its bytes and how many triangles it holds. Save the bytes as a `.glb` file or hand them to any glTF viewer.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { build } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
14
|
+
*
|
|
15
|
+
* const glb = build({ rocket: "starship", variant: "v3" }).glb();
|
|
16
|
+
* console.log(glb.bytes.length > 1000, glb.triangles > 0);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
8
19
|
export interface Glb {
|
|
9
20
|
bytes: Uint8Array;
|
|
10
21
|
triangles: number;
|
|
11
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Writes parts into one small binary glTF file, in Node or a browser, with no dependency: one node for each part and one primitive for each material. The same parts always give the same bytes.
|
|
25
|
+
*
|
|
26
|
+
* @param parts The parts to write: each has a node name, its mesh and where it stands in metres.
|
|
27
|
+
* @returns The file's bytes and its triangle count.
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { build, toGlb } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
31
|
+
*
|
|
32
|
+
* const rocket = build({ rocket: "falcon-9", variant: "block-5" });
|
|
33
|
+
* const glb = toGlb(rocket.parts.map((part) => ({ name: part.slot, mesh: part.mesh, at: [0, 0, 0] as const })));
|
|
34
|
+
* console.log(glb.bytes.length > 1000, glb.triangles > 0);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
12
37
|
export declare function toGlb(parts: readonly GlbPart[]): Glb;
|
package/dist/build/glb.js
CHANGED
|
@@ -10,6 +10,20 @@ const LINES = 1;
|
|
|
10
10
|
const round6 = (v) => Math.round(v * 1e6) / 1e6;
|
|
11
11
|
const toLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
12
12
|
const linearOf = (hex) => [1, 3, 5].map((i) => round6(toLinear(Number.parseInt(hex.slice(i, i + 2), 16) / 255)));
|
|
13
|
+
/**
|
|
14
|
+
* Writes parts into one small binary glTF file, in Node or a browser, with no dependency: one node for each part and one primitive for each material. The same parts always give the same bytes.
|
|
15
|
+
*
|
|
16
|
+
* @param parts The parts to write: each has a node name, its mesh and where it stands in metres.
|
|
17
|
+
* @returns The file's bytes and its triangle count.
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { build, toGlb } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
21
|
+
*
|
|
22
|
+
* const rocket = build({ rocket: "falcon-9", variant: "block-5" });
|
|
23
|
+
* const glb = toGlb(rocket.parts.map((part) => ({ name: part.slot, mesh: part.mesh, at: [0, 0, 0] as const })));
|
|
24
|
+
* console.log(glb.bytes.length > 1000, glb.triangles > 0);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
13
27
|
export function toGlb(parts) {
|
|
14
28
|
const chunks = [];
|
|
15
29
|
let offset = 0;
|
|
@@ -47,8 +61,8 @@ export function toGlb(parts) {
|
|
|
47
61
|
const meshes = [];
|
|
48
62
|
const nodes = [];
|
|
49
63
|
const roots = [];
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
/** One mesh of a part or of one of its moving groups: its primitives and the packing's middle and half-size, returned as the nodes that hold it. */
|
|
65
|
+
const packMesh = (mesh, name) => {
|
|
52
66
|
const faces = [...mesh.faces];
|
|
53
67
|
// the packing's middle and half-size, over all of the part's points
|
|
54
68
|
const lo = [Infinity, Infinity, Infinity];
|
|
@@ -112,11 +126,19 @@ export function toGlb(parts) {
|
|
|
112
126
|
continue;
|
|
113
127
|
primitives.push({ attributes: { POSITION: packPositions(lines.positions) }, indices: indexAccessor(lines.indices, lines.positions.length / 3), material: useMaterial(name), mode: LINES });
|
|
114
128
|
}
|
|
115
|
-
meshes.push({ name
|
|
116
|
-
|
|
117
|
-
nodes.
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
meshes.push({ name, primitives });
|
|
130
|
+
nodes.push({ name: `${name}-mesh`, mesh: meshes.length - 1, translation: q.map(round6), scale: [round6(half), round6(half), round6(half)] });
|
|
131
|
+
return nodes.length - 1;
|
|
132
|
+
};
|
|
133
|
+
for (const part of parts) {
|
|
134
|
+
// a holder that the viewer moves, and the packed mesh under it; each moving group is a node of its own beside it, at its pivot, with the axis it turns about
|
|
135
|
+
const children = [packMesh(part.mesh, part.name)];
|
|
136
|
+
for (const group of part.mesh.moves) {
|
|
137
|
+
const inside = packMesh(group.mesh, `${part.name}~${group.id}`);
|
|
138
|
+
nodes.push({ name: `${part.name}~${group.id}`, children: [inside], translation: group.pivot.map(round6), extras: { axis: group.axis.map(round6), group: group.name } });
|
|
139
|
+
children.push(nodes.length - 1);
|
|
140
|
+
}
|
|
141
|
+
nodes.push({ name: part.name, children, translation: part.at.map(round6) });
|
|
120
142
|
roots.push(nodes.length - 1);
|
|
121
143
|
}
|
|
122
144
|
const json = {
|
package/dist/build/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FlameKind } from "../data/propellants.ts";
|
|
1
2
|
import { type Description, type FitReport } from "../fit.ts";
|
|
2
3
|
import { type Angle, type Vec3, type ViewName } from "../framing.ts";
|
|
3
4
|
import { type LookName } from "../looks.ts";
|
|
@@ -5,25 +6,77 @@ import { type PartMesh, type Bounds, type Piece } from "../model/pieces.ts";
|
|
|
5
6
|
import type { PartRecord } from "../parts.ts";
|
|
6
7
|
import { type LitBell } from "../engines.ts";
|
|
7
8
|
import { type Glb, toGlb } from "./glb.ts";
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Raised by `build` when a description does not fit. Its `report` says why, in the same words `check` gives.
|
|
11
|
+
*
|
|
12
|
+
* @param report The report of the fit check that failed.
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { build, FitError } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
16
|
+
*
|
|
17
|
+
* try {
|
|
18
|
+
* build({ rocket: "starship", variant: "v3", parts: { booster: "starship-v3-ship" } });
|
|
19
|
+
* } catch (error) {
|
|
20
|
+
* if (error instanceof FitError) console.log(error.report.issues[0]?.message);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
9
24
|
export declare class FitError extends Error {
|
|
10
25
|
readonly report: FitReport;
|
|
11
26
|
constructor(report: FitReport);
|
|
12
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* One part of a built rocket: its slot, its record, the pieces it is made of, its mesh and its bounds in metres.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { build, type BuiltPart } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
34
|
+
*
|
|
35
|
+
* const first: BuiltPart | undefined = build({ rocket: "falcon-9", variant: "block-5" }).parts[0];
|
|
36
|
+
* console.log(first?.slot, first?.record.name);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
13
39
|
export interface BuiltPart {
|
|
14
40
|
slot: string;
|
|
15
41
|
record: PartRecord;
|
|
16
42
|
pieces: readonly Piece[];
|
|
17
43
|
mesh: PartMesh;
|
|
18
44
|
bounds: Bounds;
|
|
45
|
+
/** The colour of this part's flame, from what a page says its engine burns; `neutral` when none read here says. */
|
|
46
|
+
flame: FlameKind;
|
|
19
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* What a drawing is asked for: the look, the view, what to show and whether the engines are lit. Anything left out takes its default.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { build, type SvgOptions } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
54
|
+
*
|
|
55
|
+
* const options: SvgOptions = { look: "thick", view: "below", show: "booster" };
|
|
56
|
+
* console.log(build({ rocket: "starship", variant: "v3" }).svg(options).startsWith("<svg"));
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
20
59
|
export interface SvgOptions {
|
|
21
60
|
look?: LookName;
|
|
22
61
|
view?: ViewName | Angle;
|
|
23
62
|
show?: string;
|
|
24
63
|
/** A flame at each engine that is showing, or as many as a count names for each part. The length is a picture, not a measured flame. Left out, the drawing is unchanged. */
|
|
25
64
|
engines?: boolean | Readonly<Record<string, number>>;
|
|
65
|
+
/** 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. */
|
|
66
|
+
moves?: Readonly<Record<string, Readonly<Record<string, number>>>>;
|
|
26
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* 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.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { build, type Built } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
74
|
+
*
|
|
75
|
+
* const rocket: Built = build({ rocket: "starship", variant: "v3" });
|
|
76
|
+
* console.log(rocket.shows.join(", "));
|
|
77
|
+
* console.log(rocket.boundsOf("together").max);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
27
80
|
export interface Built {
|
|
28
81
|
report: FitReport;
|
|
29
82
|
parts: readonly BuiltPart[];
|
|
@@ -44,14 +97,51 @@ export interface Built {
|
|
|
44
97
|
/** The drawing, in a look, from an angle. */
|
|
45
98
|
svg(options?: SvgOptions): string;
|
|
46
99
|
}
|
|
47
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* What a drawing draws of a part: in a stack, what nests inside the part below is out of sight.
|
|
102
|
+
*
|
|
103
|
+
* @param part A built part: its record and its pieces.
|
|
104
|
+
* @param together True when the part stands in a stack, false when it is shown alone.
|
|
105
|
+
* @returns The pieces to draw. In a stack, what nests inside the part below is left out.
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* import { build, drawnPieces } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
109
|
+
*
|
|
110
|
+
* const [first] = build({ rocket: "falcon-9", variant: "block-5" }).parts;
|
|
111
|
+
* if (first) console.log(drawnPieces(first, true).length, drawnPieces(first, false).length);
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
48
114
|
export declare function drawnPieces(part: {
|
|
49
115
|
record: PartRecord;
|
|
50
116
|
pieces: readonly Piece[];
|
|
51
117
|
}, together: boolean): readonly Piece[];
|
|
52
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* A rocket from a description. Throws FitError when it does not go together.
|
|
120
|
+
*
|
|
121
|
+
* @param description The rocket, its variant and any other part in a slot.
|
|
122
|
+
* @returns The built rocket. It throws a `FitError` when the description does not go together.
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { build } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
126
|
+
*
|
|
127
|
+
* const rocket = build({ rocket: "falcon-heavy" });
|
|
128
|
+
* console.log(rocket.report.label, rocket.glb().bytes.length > 1000);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
53
131
|
export declare function build(description: Description): Built;
|
|
54
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* One part on its own, by its id.
|
|
134
|
+
*
|
|
135
|
+
* @param id A part's id, as `PART_ENTRIES` lists it.
|
|
136
|
+
* @returns The part's record and bounds, a function for its `.glb` and one for its drawing. It throws for an id that is not a part.
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { buildPart } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
140
|
+
*
|
|
141
|
+
* const raptor = buildPart("raptor-vacuum");
|
|
142
|
+
* console.log(raptor.record.name, raptor.bounds.max);
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
55
145
|
export declare function buildPart(id: string): {
|
|
56
146
|
record: PartRecord;
|
|
57
147
|
bounds: Bounds;
|
package/dist/build/index.js
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
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
|
+
import { propellantOf } from "../data/propellants.js";
|
|
2
3
|
import { check } from "../fit.js";
|
|
3
4
|
import { cleanView, DEFAULT_VIEW, PICTURE_H, PICTURE_W, VIEWS } from "../framing.js";
|
|
4
5
|
import { cleanShow, placesFor, showsOf } from "../layout.js";
|
|
5
6
|
import { DEFAULT_LOOK } from "../looks.js";
|
|
6
7
|
import { boundsOf, meshOf } from "../model/pieces.js";
|
|
8
|
+
import { moved } from "../model/moves.js";
|
|
7
9
|
import { partEntry } from "../model/registry.js";
|
|
10
|
+
import { partIdOf, variantOf } from "../rockets.js";
|
|
8
11
|
import { plumeMarkup } from "../draw/plume.js";
|
|
9
12
|
import { defsOf, drawPart, perspective } from "../draw/svg.js";
|
|
10
13
|
import { bellsOf, litBells, pickBells, plumeLength } from "../engines.js";
|
|
11
14
|
import { toGlb } from "./glb.js";
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Raised by `build` when a description does not fit. Its `report` says why, in the same words `check` gives.
|
|
17
|
+
*
|
|
18
|
+
* @param report The report of the fit check that failed.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { build, FitError } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
22
|
+
*
|
|
23
|
+
* try {
|
|
24
|
+
* build({ rocket: "starship", variant: "v3", parts: { booster: "starship-v3-ship" } });
|
|
25
|
+
* } catch (error) {
|
|
26
|
+
* if (error instanceof FitError) console.log(error.report.issues[0]?.message);
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
13
30
|
export class FitError extends Error {
|
|
14
31
|
report;
|
|
15
32
|
constructor(report) {
|
|
@@ -42,10 +59,20 @@ const widthOf = (id) => {
|
|
|
42
59
|
const { min, max } = made(id).bounds;
|
|
43
60
|
return Math.max(Math.abs(min[0]), Math.abs(max[0]), Math.abs(min[2]), Math.abs(max[2]));
|
|
44
61
|
};
|
|
62
|
+
/** What colour a part's flame is: the kind its engine's page says it burns, found by the engine's name in the data of a variant the part is true for. */
|
|
63
|
+
function flameKindOf(part) {
|
|
64
|
+
for (const variantId of part.trueTo) {
|
|
65
|
+
const variant = variantOf(part.rocket, variantId);
|
|
66
|
+
for (const [slot, engines] of Object.entries(variant?.facts.engines ?? {}))
|
|
67
|
+
if (partIdOf(part.rocket, variantId, slot) === part.id)
|
|
68
|
+
return propellantOf(part.rocket, engines.value)?.flame ?? "neutral";
|
|
69
|
+
}
|
|
70
|
+
return "neutral";
|
|
71
|
+
}
|
|
45
72
|
function assembled(report) {
|
|
46
73
|
const parts = report.parts.map(({ slot, part }) => {
|
|
47
74
|
const { pieces, mesh, bounds } = made(part.id);
|
|
48
|
-
return { slot, record: part, pieces, mesh, bounds };
|
|
75
|
+
return { slot, record: part, pieces, mesh, bounds, flame: flameKindOf(part) };
|
|
49
76
|
});
|
|
50
77
|
const slots = parts.map((part) => part.slot);
|
|
51
78
|
const placesOf = (show) => placesFor(report.parts, cleanShow(show, slots), (part) => widthOf(part.id));
|
|
@@ -84,7 +111,7 @@ function assembled(report) {
|
|
|
84
111
|
placesOf,
|
|
85
112
|
flamesOf,
|
|
86
113
|
glb: () => toGlb(parts.map((part) => ({ name: part.slot, mesh: part.mesh, at: placesOf("together").find((place) => place.slot === part.slot)?.at ?? [0, 0, 0] }))),
|
|
87
|
-
svg: ({ look = DEFAULT_LOOK, view = DEFAULT_VIEW, show, engines = false } = {}) => {
|
|
114
|
+
svg: ({ look = DEFAULT_LOOK, view = DEFAULT_VIEW, show, engines = false, moves } = {}) => {
|
|
88
115
|
const angle = typeof view === "string" ? VIEWS[cleanView(view)] : view;
|
|
89
116
|
const places = placesOf(show).filter((place) => place.shown);
|
|
90
117
|
// a flame drawn at full thrust is held in the picture; a count of engines is the timeline's, which has its own room
|
|
@@ -103,26 +130,63 @@ function assembled(report) {
|
|
|
103
130
|
? flamesOf(show).filter((bell) => places.some((place) => place.slot === bell.slot))
|
|
104
131
|
: [];
|
|
105
132
|
return wrap(ordered
|
|
106
|
-
.map(({ place, part }) => drawPart(drawnPieces(part, together), place.at, cam, look) + plumeMarkup(flames.filter((bell) => bell.slot === place.slot), place.at, cam, look, part.slot))
|
|
133
|
+
.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))
|
|
107
134
|
.join(""), look, PICTURE_W, PICTURE_H, report.label);
|
|
108
135
|
},
|
|
109
136
|
};
|
|
110
137
|
}
|
|
111
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* What a drawing draws of a part: in a stack, what nests inside the part below is out of sight.
|
|
140
|
+
*
|
|
141
|
+
* @param part A built part: its record and its pieces.
|
|
142
|
+
* @param together True when the part stands in a stack, false when it is shown alone.
|
|
143
|
+
* @returns The pieces to draw. In a stack, what nests inside the part below is left out.
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* import { build, drawnPieces } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
147
|
+
*
|
|
148
|
+
* const [first] = build({ rocket: "falcon-9", variant: "block-5" }).parts;
|
|
149
|
+
* if (first) console.log(drawnPieces(first, true).length, drawnPieces(first, false).length);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
112
152
|
export function drawnPieces(part, together) {
|
|
113
153
|
const joint = part.record.joints.bottom?.y ?? 0;
|
|
114
154
|
if (!together || joint <= 0)
|
|
115
155
|
return part.pieces;
|
|
116
156
|
return part.pieces.filter((piece) => !(piece.kind === "lathe" && piece.under && Math.max(...piece.profile.map(([, y]) => y)) <= joint + 0.01));
|
|
117
157
|
}
|
|
118
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* A rocket from a description. Throws FitError when it does not go together.
|
|
160
|
+
*
|
|
161
|
+
* @param description The rocket, its variant and any other part in a slot.
|
|
162
|
+
* @returns The built rocket. It throws a `FitError` when the description does not go together.
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* import { build } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
166
|
+
*
|
|
167
|
+
* const rocket = build({ rocket: "falcon-heavy" });
|
|
168
|
+
* console.log(rocket.report.label, rocket.glb().bytes.length > 1000);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
119
171
|
export function build(description) {
|
|
120
172
|
const report = check(description);
|
|
121
173
|
if (!report.ok)
|
|
122
174
|
throw new FitError(report);
|
|
123
175
|
return assembled(report);
|
|
124
176
|
}
|
|
125
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* One part on its own, by its id.
|
|
179
|
+
*
|
|
180
|
+
* @param id A part's id, as `PART_ENTRIES` lists it.
|
|
181
|
+
* @returns The part's record and bounds, a function for its `.glb` and one for its drawing. It throws for an id that is not a part.
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { buildPart } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
185
|
+
*
|
|
186
|
+
* const raptor = buildPart("raptor-vacuum");
|
|
187
|
+
* console.log(raptor.record.name, raptor.bounds.max);
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
126
190
|
export function buildPart(id) {
|
|
127
191
|
const entry = partEntry(id);
|
|
128
192
|
if (!entry)
|
package/dist/build/lineup.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { type ViewName } from "../framing.ts";
|
|
2
2
|
import { type LookName } from "../looks.ts";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Comparison shapes: plain round sizes, for scale, not facts about anything.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { COMPARES } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
9
|
+
*
|
|
10
|
+
* console.log(COMPARES.person.height, COMPARES.building.height);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
4
13
|
export declare const COMPARES: {
|
|
5
14
|
readonly person: {
|
|
6
15
|
readonly name: "A person, 1.8 m";
|
|
@@ -13,13 +22,46 @@ export declare const COMPARES: {
|
|
|
13
22
|
readonly width: 18;
|
|
14
23
|
};
|
|
15
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* The name of a comparison shape that can stand in a lineup: `person` or `building`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import type { CompareId } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
31
|
+
*
|
|
32
|
+
* const id: CompareId = "person";
|
|
33
|
+
* console.log(id);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
16
36
|
export type CompareId = keyof typeof COMPARES;
|
|
37
|
+
/**
|
|
38
|
+
* One thing standing in a lineup: its key (`starship:v3` or a comparison's name), its label and its height in metres.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { lineupSvg, type LineupItem } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
43
|
+
*
|
|
44
|
+
* const items: LineupItem[] = lineupSvg({ items: ["starship:v3", "person"], unit: 3 }).items;
|
|
45
|
+
* console.log(items.map((item) => item.label));
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
17
48
|
export interface LineupItem {
|
|
18
49
|
/** "starship:v3" or a comparison's name. */
|
|
19
50
|
key: string;
|
|
20
51
|
label: string;
|
|
21
52
|
height: number;
|
|
22
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* What a lineup is asked for: the items by key, how many pixels make a metre, and optionally the look, the view, what to show, a ruler and labels.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { lineupSvg, type LineupOptions } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
60
|
+
*
|
|
61
|
+
* const options: LineupOptions = { items: ["starship:v2", "starship:v3", "building"], unit: 3, ruler: true, labels: true };
|
|
62
|
+
* console.log(lineupSvg(options).width);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
23
65
|
export interface LineupOptions {
|
|
24
66
|
/** "starship:v1,starship:v3,person,building". */
|
|
25
67
|
items: readonly string[];
|
|
@@ -31,7 +73,19 @@ export interface LineupOptions {
|
|
|
31
73
|
ruler?: boolean;
|
|
32
74
|
labels?: boolean;
|
|
33
75
|
}
|
|
34
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The whole picture as SVG text. Unknown items are left out.
|
|
78
|
+
*
|
|
79
|
+
* @param options The items and how they are drawn.
|
|
80
|
+
* @returns The picture as SVG text, the items it holds, and its width and height.
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* import { lineupSvg } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
84
|
+
*
|
|
85
|
+
* const picture = lineupSvg({ items: ["falcon-9:block-5", "falcon-heavy:falcon-heavy", "person"], unit: 4, ruler: true });
|
|
86
|
+
* console.log(picture.items.length, picture.svg.startsWith("<svg"));
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
35
89
|
export declare function lineupSvg(options: LineupOptions): {
|
|
36
90
|
svg: string;
|
|
37
91
|
items: LineupItem[];
|
package/dist/build/lineup.js
CHANGED
|
@@ -6,7 +6,16 @@ import { DEFAULT_LOOK, LOOKS } from "../looks.js";
|
|
|
6
6
|
import { defsOf, drawPart, orthographic } from "../draw/svg.js";
|
|
7
7
|
import { MATERIALS } from "../model/materials.js";
|
|
8
8
|
import { variantOf } from "../rockets.js";
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Comparison shapes: plain round sizes, for scale, not facts about anything.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { COMPARES } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
15
|
+
*
|
|
16
|
+
* console.log(COMPARES.person.height, COMPARES.building.height);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
10
19
|
export const COMPARES = {
|
|
11
20
|
person: { name: "A person, 1.8 m", height: 1.8, width: 0.5 },
|
|
12
21
|
building: { name: "A ten-storey building, 30 m", height: 30, width: 18 },
|
|
@@ -33,7 +42,19 @@ function comparison(id, x, base, unit, line, fill) {
|
|
|
33
42
|
floors += `M${n(x)} ${n(base - (h * i) / 10)}h${n(w)}`;
|
|
34
43
|
return `<rect x="${n(x)}" y="${n(base - h)}" width="${n(w)}" height="${n(h)}" fill="${fill}" stroke="${line}" stroke-width=".9"/><path d="${floors}" stroke="${line}" stroke-width=".35" opacity=".6"/>`;
|
|
35
44
|
}
|
|
36
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* The whole picture as SVG text. Unknown items are left out.
|
|
47
|
+
*
|
|
48
|
+
* @param options The items and how they are drawn.
|
|
49
|
+
* @returns The picture as SVG text, the items it holds, and its width and height.
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { lineupSvg } from "@johnmorrisdotca/uchusen-rockets/build";
|
|
53
|
+
*
|
|
54
|
+
* const picture = lineupSvg({ items: ["falcon-9:block-5", "falcon-heavy:falcon-heavy", "person"], unit: 4, ruler: true });
|
|
55
|
+
* console.log(picture.items.length, picture.svg.startsWith("<svg"));
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
37
58
|
export function lineupSvg(options) {
|
|
38
59
|
const { items, unit, look = DEFAULT_LOOK, view = "front", ruler = true, labels = true } = options;
|
|
39
60
|
const angle = VIEWS[cleanView(view)];
|