@johnmorrisdotca/uchusen-rockets 0.19.0 → 0.20.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 +19 -0
- package/LICENSES/Orbiter-MIT.txt +21 -0
- package/NOTICE.md +37 -1
- package/README.md +14 -7
- package/dist/assets/falcon-heavy-test-flight-apart.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-apart.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-apart.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-car.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-car.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-centre.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-centre.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-left.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-side-right.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight-starman.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight-starman.thick.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.glb +0 -0
- package/dist/assets/falcon-heavy-test-flight.painted.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.svg +1 -0
- package/dist/assets/falcon-heavy-test-flight.thick.svg +1 -0
- package/dist/assets/models/roadster-first-generation-car.glb +0 -0
- package/dist/assets/models/roadster-second-generation-2-car.glb +0 -0
- package/dist/assets/models/roadster-second-generation-car.glb +0 -0
- package/dist/assets/roadster-first-generation-apart.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-apart.svg +1 -0
- package/dist/assets/roadster-first-generation-apart.thick.svg +1 -0
- package/dist/assets/roadster-first-generation-car.glb +0 -0
- package/dist/assets/roadster-first-generation-car.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-car.svg +1 -0
- package/dist/assets/roadster-first-generation-car.thick.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.glb +0 -0
- package/dist/assets/roadster-first-generation-starman.painted.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.svg +1 -0
- package/dist/assets/roadster-first-generation-starman.thick.svg +1 -0
- package/dist/assets/roadster-first-generation.glb +0 -0
- package/dist/assets/roadster-first-generation.painted.svg +1 -0
- package/dist/assets/roadster-first-generation.svg +1 -0
- package/dist/assets/roadster-first-generation.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.svg +1 -0
- package/dist/assets/roadster-second-generation-2-apart.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.glb +0 -0
- package/dist/assets/roadster-second-generation-2-car.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.svg +1 -0
- package/dist/assets/roadster-second-generation-2-car.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.glb +0 -0
- package/dist/assets/roadster-second-generation-2-starman.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.svg +1 -0
- package/dist/assets/roadster-second-generation-2-starman.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-2.glb +0 -0
- package/dist/assets/roadster-second-generation-2.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-2.svg +1 -0
- package/dist/assets/roadster-second-generation-2.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.svg +1 -0
- package/dist/assets/roadster-second-generation-apart.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-car.glb +0 -0
- package/dist/assets/roadster-second-generation-car.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-car.svg +1 -0
- package/dist/assets/roadster-second-generation-car.thick.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.glb +0 -0
- package/dist/assets/roadster-second-generation-starman.painted.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.svg +1 -0
- package/dist/assets/roadster-second-generation-starman.thick.svg +1 -0
- package/dist/assets/roadster-second-generation.glb +0 -0
- package/dist/assets/roadster-second-generation.painted.svg +1 -0
- package/dist/assets/roadster-second-generation.svg +1 -0
- package/dist/assets/roadster-second-generation.thick.svg +1 -0
- package/dist/data/countries.d.ts +51 -0
- package/dist/data/countries.js +91 -0
- package/dist/data/credits.d.ts +36 -0
- package/dist/data/credits.js +36 -0
- package/dist/data/falcon.js +26 -14
- package/dist/data/makers.js +1 -0
- package/dist/data/roadster.d.ts +13 -0
- package/dist/data/roadster.js +73 -0
- package/dist/data/sources.d.ts +56 -0
- package/dist/data/sources.js +7 -0
- package/dist/element/flag.d.ts +30 -0
- package/dist/element/flag.js +23 -0
- package/dist/element/probe.d.ts +15 -0
- package/dist/element/probe.js +46 -0
- package/dist/element/scene.d.ts +7 -0
- package/dist/element/scene.js +38 -10
- package/dist/element.d.ts +7 -0
- package/dist/element.js +74 -20
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/inspace.js +1 -0
- package/dist/model/registry.js +6 -4
- package/dist/model/roadster.d.ts +31 -0
- package/dist/model/roadster.js +112 -0
- package/dist/parts.d.ts +1 -1
- package/dist/physics/atmosphere.d.ts +182 -0
- package/dist/physics/atmosphere.js +246 -0
- package/dist/physics/attitude.d.ts +342 -0
- package/dist/physics/attitude.js +351 -0
- package/dist/physics/burns.d.ts +194 -0
- package/dist/physics/burns.js +157 -0
- package/dist/physics/elements.d.ts +133 -0
- package/dist/physics/elements.js +239 -0
- package/dist/physics/gravity.d.ts +53 -0
- package/dist/physics/gravity.js +78 -0
- package/dist/physics/index.d.ts +17 -0
- package/dist/physics/index.js +8 -0
- package/dist/physics/propagate.d.ts +110 -0
- package/dist/physics/propagate.js +122 -0
- package/dist/physics/rocket.d.ts +105 -0
- package/dist/physics/rocket.js +102 -0
- package/dist/physics/transfers.d.ts +128 -0
- package/dist/physics/transfers.js +155 -0
- package/dist/physics/vector.d.ts +20 -0
- package/dist/physics/vector.js +9 -0
- package/dist/react.d.ts +1 -1
- package/dist/rockets.d.ts +5 -0
- package/dist/rockets.js +9 -4
- package/dist/sun.d.ts +13 -0
- package/dist/sun.js +25 -0
- package/package.json +11 -1
package/dist/element/scene.js
CHANGED
|
@@ -31,6 +31,7 @@ import { placeNameLabels } from "./namePlace.js";
|
|
|
31
31
|
import { plinthGroup } from "./plinth.js";
|
|
32
32
|
import { PartPlumes, PlumeKit } from "./plumes.js";
|
|
33
33
|
import { ThrusterLayer } from "./thrusterLayer.js";
|
|
34
|
+
import { glProbe } from "./probe.js";
|
|
34
35
|
import { craftJets, endQuads } from "../thrusters.js";
|
|
35
36
|
/** The ships whose lamps turn the sun down in the Lit look. Kit modules stay in daylight. */
|
|
36
37
|
const NIGHT_SHIPS = new Set(["fleet-ship-01", "fleet-kyotori", "fleet-drone", "fleet-ship-03", "fleet-ship-ariake", "fleet-02", "fleet-02-original", "fleet-kaminari", "fleet-emiake", "fleet-romaryu"]);
|
|
@@ -55,15 +56,8 @@ const COMPILE_MS = 4000;
|
|
|
55
56
|
/** A change of craft, section or Fit eases the camera for this long, even in scale. */
|
|
56
57
|
const SIZE_EASE_MS = 800;
|
|
57
58
|
const autoRotateOf = (spin) => (spin.seconds <= 0 ? 0 : (60 / spin.seconds) * (spin.way === "cw" ? -1 : 1));
|
|
58
|
-
/** A real graphics card keeps smooth edges. Software drawing spends the frame on them, and a heavy ship then misses a frame of the wait. */
|
|
59
|
-
const smoothEdges = () =>
|
|
60
|
-
const canvas = document.createElement("canvas");
|
|
61
|
-
const gl = canvas.getContext("webgl2");
|
|
62
|
-
const info = gl?.getExtension("WEBGL_debug_renderer_info");
|
|
63
|
-
const name = info && gl ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : "";
|
|
64
|
-
gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
65
|
-
return !/swiftshader|llvmpipe|softpipe/i.test(name);
|
|
66
|
-
};
|
|
59
|
+
/** A real graphics card keeps smooth edges. Software drawing spends the frame on them, and a heavy ship then misses a frame of the wait. The page's one WebGL probe says which this is. */
|
|
60
|
+
const smoothEdges = () => !glProbe().software;
|
|
67
61
|
const LINE_VERTEX = /* glsl */ `
|
|
68
62
|
varying vec3 vNormal;
|
|
69
63
|
varying vec2 vUv;
|
|
@@ -427,6 +421,31 @@ export class RocketViewer {
|
|
|
427
421
|
this.offScreen = next;
|
|
428
422
|
this.wake();
|
|
429
423
|
}
|
|
424
|
+
/** The direction towards the Sun, in the craft's own frame, when a page gives one; null keeps the showroom light. */
|
|
425
|
+
sunFrom = null;
|
|
426
|
+
/** One light from the Sun's real direction, as in space: the far side dark, with at most a faint fill. Null puts the showroom light back. */
|
|
427
|
+
setSun(direction) {
|
|
428
|
+
const before = this.sunFrom;
|
|
429
|
+
this.sunFrom = direction ? new Vector3(...direction) : null;
|
|
430
|
+
this.canvas.dataset.sun = this.sunFrom ? "on" : "off";
|
|
431
|
+
if (before === this.sunFrom || (before && this.sunFrom && before.equals(this.sunFrom)))
|
|
432
|
+
return;
|
|
433
|
+
this.nightSide();
|
|
434
|
+
this.aimKey();
|
|
435
|
+
this.wake();
|
|
436
|
+
}
|
|
437
|
+
/** The key light comes from the Sun when there is one, else from the showroom's upper front. */
|
|
438
|
+
keyDirection() {
|
|
439
|
+
return (this.sunFrom ?? new Vector3(3, 5, 4)).clone().normalize();
|
|
440
|
+
}
|
|
441
|
+
aimKey() {
|
|
442
|
+
if (this.shadows && this.built) {
|
|
443
|
+
this.fitShadow();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
this.sun.position.copy(this.sun.target.position).addScaledVector(this.keyDirection(), 7);
|
|
447
|
+
this.sun.target.updateMatrixWorld();
|
|
448
|
+
}
|
|
430
449
|
/** Open space lets a craft leave the frame. Wrap, the older float, brings it back on the other side. */
|
|
431
450
|
edgesOpen = false;
|
|
432
451
|
setEdges(open) {
|
|
@@ -1081,6 +1100,15 @@ export class RocketViewer {
|
|
|
1081
1100
|
/** The Lit look of a named SPX ship is the night side. Daylight stays the default, so lamps glow in the sun, and a kit module stays in daylight. */
|
|
1082
1101
|
nightSide() {
|
|
1083
1102
|
const night = this.shipNight && this.lightsMode !== "off" && this.glowOn;
|
|
1103
|
+
if (this.sunFrom && !night) {
|
|
1104
|
+
// in space there is one light: the Sun, a little stronger than the showroom's key, and next to nothing from anywhere else
|
|
1105
|
+
this.sun.intensity = 2.4;
|
|
1106
|
+
this.rim.intensity = 0;
|
|
1107
|
+
this.fill.intensity = 0;
|
|
1108
|
+
this.sky.intensity = 0.03;
|
|
1109
|
+
this.scene.environmentIntensity = 0.06;
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1084
1112
|
this.sun.intensity = night ? 0.16 : 1.6;
|
|
1085
1113
|
this.rim.intensity = night ? 0.04 : 1.1;
|
|
1086
1114
|
this.fill.intensity = night ? 0.03 : 0.35;
|
|
@@ -1607,7 +1635,7 @@ export class RocketViewer {
|
|
|
1607
1635
|
const { min, max } = this.built.boundsOf(this.show);
|
|
1608
1636
|
const centre = new Vector3((min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2);
|
|
1609
1637
|
const radius = Math.max(1, Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) / 2);
|
|
1610
|
-
const direction =
|
|
1638
|
+
const direction = this.keyDirection();
|
|
1611
1639
|
this.sun.position.copy(centre).addScaledVector(direction, radius * 2);
|
|
1612
1640
|
this.sun.target.position.copy(centre);
|
|
1613
1641
|
const shadow = this.sun.shadow;
|
package/dist/element.d.ts
CHANGED
|
@@ -63,8 +63,12 @@ export declare class UchusenRocket extends HTMLBase {
|
|
|
63
63
|
private builtNow;
|
|
64
64
|
private queued;
|
|
65
65
|
private stateNow;
|
|
66
|
+
/** What the flag in the corner is for: a country's code, "" for none, "off", or "-" before the first look. */
|
|
67
|
+
private flagKey;
|
|
66
68
|
private shownAt;
|
|
67
69
|
private starting;
|
|
70
|
+
/** Whether this element has asked the page's WebGL answer since it started watching. */
|
|
71
|
+
private glAsked;
|
|
68
72
|
private key;
|
|
69
73
|
private driven;
|
|
70
74
|
private placeFrame;
|
|
@@ -199,6 +203,8 @@ export declare class UchusenRocket extends HTMLBase {
|
|
|
199
203
|
/** The drawing for what the attributes say now. It is made from code, so it needs no file. A zoom or a move only changes which part of it the box shows (its viewBox), so the drawing is as sharp as the box and never grows past it. */
|
|
200
204
|
private redraw;
|
|
201
205
|
private mark;
|
|
206
|
+
/** `flags`: the rocket's country's flag in the picture's corner, from hata (an optional peer) when the page has it. `data-flag` is the code shown, or empty once it is settled that none is; it is absent while one is loading or the attribute is off. */
|
|
207
|
+
private syncFlag;
|
|
202
208
|
private applyRocket;
|
|
203
209
|
/** A fleet ship is built coarse first (the first picture and the far view) and fine when the camera comes near, an inset opens or one part is shown alone: once per visit, never on a turn or a look change. */
|
|
204
210
|
private fine;
|
|
@@ -255,6 +261,7 @@ export declare class UchusenLineup extends HTMLBase {
|
|
|
255
261
|
*/
|
|
256
262
|
export declare function defineUchusenRocket(): void;
|
|
257
263
|
export { ROCKETS, UchusenFlight };
|
|
264
|
+
export { setFlagLoader, type FlagSource } from "./element/flag.ts";
|
|
258
265
|
export type { FlightWords } from "./flightbar.ts";
|
|
259
266
|
declare global {
|
|
260
267
|
interface HTMLElementTagNameMap {
|
package/dist/element.js
CHANGED
|
@@ -6,6 +6,7 @@ import { buildOf } from "./buildPlay.js";
|
|
|
6
6
|
import { bellsOf, enginesWanted, pickBells } from "./engines.js";
|
|
7
7
|
import { lightMode } from "./model/fleet/lights.js";
|
|
8
8
|
import { thrusterMode } from "./thrusters.js";
|
|
9
|
+
import { parseSun } from "./sun.js";
|
|
9
10
|
import { motionsOf, motionSpeed, motionWanted } from "./model/fleet/motion.js";
|
|
10
11
|
import { engineChart } from "./draw/plume.js";
|
|
11
12
|
import { burnsOf, FlightClock, flightOf, frameAt, phaseAt, phasePosition } from "./flight.js";
|
|
@@ -21,7 +22,9 @@ import { ROCKETS, SLOT_NAMES } from "./rockets.js";
|
|
|
21
22
|
import { tiltMode, tiltTurn } from "./tilt.js";
|
|
22
23
|
import { skyQuery } from "./element/solarGroups.js";
|
|
23
24
|
import { Panel } from "./element/panel.js";
|
|
25
|
+
import { glProbe } from "./element/probe.js";
|
|
24
26
|
import { spinOf } from "./spin.js";
|
|
27
|
+
import { flagSvg } from "./element/flag.js";
|
|
25
28
|
const HTMLBase = typeof HTMLElement === "undefined" ? class {
|
|
26
29
|
} : HTMLElement;
|
|
27
30
|
/** A view out of sight keeps its WebGL canvas this long before letting it go (a browser allows only a few at a time). */
|
|
@@ -46,6 +49,15 @@ export function setAssetBase(url) {
|
|
|
46
49
|
assetBase = url.endsWith("/") ? url : `${url}/`;
|
|
47
50
|
}
|
|
48
51
|
const defaultBase = () => assetBase ?? new URL("./assets/", import.meta.url).href;
|
|
52
|
+
/** A country's name in the page's language, or its code where the browser has no name for it. */
|
|
53
|
+
function regionName(code, lang) {
|
|
54
|
+
try {
|
|
55
|
+
return new Intl.DisplayNames([lang || "en"], { type: "region" }).of(code) ?? code;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return code;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
49
61
|
const CSS = `
|
|
50
62
|
:host { display: block; position: relative; color: inherit; }
|
|
51
63
|
:host([hidden]) { display: none; }
|
|
@@ -72,6 +84,8 @@ const CSS = `
|
|
|
72
84
|
.panel table { border-collapse: collapse; width: 100%; }
|
|
73
85
|
.panel th, .panel td { text-align: left; padding: 0.15rem 0.4rem; border-bottom: 1px solid color-mix(in srgb, currentColor 25%, transparent); vertical-align: top; }
|
|
74
86
|
.panel p { margin: 0 0 0.5rem; }
|
|
87
|
+
.flag { position: absolute; top: 0.5rem; left: 0.5rem; display: block; width: 1.5rem; aspect-ratio: 4 / 3; line-height: 0; pointer-events: none; box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 35%, transparent); }
|
|
88
|
+
.flag svg { display: block; width: 100%; height: 100%; }
|
|
75
89
|
.tilt { position: absolute; right: 0.5rem; bottom: 0.5rem; min-height: 2rem; padding: 0.25rem 0.75rem; border: 1px solid currentColor; border-radius: 1rem; background: transparent; color: inherit; font: inherit; font-size: 0.8rem; cursor: pointer; }
|
|
76
90
|
@media (prefers-reduced-motion: reduce) {
|
|
77
91
|
.drawing, .canvas { filter: none !important; transition: opacity var(--uchusen-fade, 400ms) ease; }
|
|
@@ -94,29 +108,13 @@ function adopt(root, css, holder) {
|
|
|
94
108
|
const rocketSheet = { sheet: null };
|
|
95
109
|
const lineupSheet = { sheet: null };
|
|
96
110
|
const media = (query) => typeof matchMedia === "function" && matchMedia(query).matches;
|
|
97
|
-
|
|
98
|
-
function webglWorks() {
|
|
99
|
-
if (webgl !== null)
|
|
100
|
-
return webgl;
|
|
101
|
-
try {
|
|
102
|
-
const probe = document.createElement("canvas");
|
|
103
|
-
const gl = probe.getContext("webgl2") ?? probe.getContext("webgl");
|
|
104
|
-
webgl = Boolean(gl);
|
|
105
|
-
gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
webgl = false;
|
|
109
|
-
}
|
|
110
|
-
return webgl;
|
|
111
|
-
}
|
|
111
|
+
/** Why the 3D view is not wanted, from what is known without making a WebGL context. Whether WebGL works is asked once, when the first element comes on screen. */
|
|
112
112
|
function whyNot(mode) {
|
|
113
113
|
if (mode === "drawing")
|
|
114
114
|
return "off";
|
|
115
115
|
const connection = navigator.connection;
|
|
116
116
|
if (connection?.saveData || media("(prefers-reduced-data: reduce)"))
|
|
117
117
|
return "reduced-data";
|
|
118
|
-
if (!webglWorks())
|
|
119
|
-
return "no-webgl";
|
|
120
118
|
return "";
|
|
121
119
|
}
|
|
122
120
|
/**
|
|
@@ -134,7 +132,7 @@ function whyNot(mode) {
|
|
|
134
132
|
* ```
|
|
135
133
|
*/
|
|
136
134
|
export class UchusenRocket extends HTMLBase {
|
|
137
|
-
static observedAttributes = ["rocket", "variant", "show", "look", "view", "build", "mode", "assets", "spin", "way", "label", "engines", "lights", "thrusters", "phase", "motion", "labels", "inspector", "beside", "captions", "scale-note", "off-screen", "scale", "progress", "float", "names", "leave", "docked", "edges", "world", "world-turn", "form"];
|
|
135
|
+
static observedAttributes = ["rocket", "variant", "show", "look", "view", "build", "mode", "assets", "spin", "way", "label", "engines", "lights", "thrusters", "phase", "motion", "labels", "inspector", "beside", "captions", "scale-note", "off-screen", "scale", "progress", "float", "names", "leave", "docked", "edges", "world", "world-turn", "form", "sun", "flags"];
|
|
138
136
|
descriptionValue = null;
|
|
139
137
|
picture = null;
|
|
140
138
|
holder = null;
|
|
@@ -152,8 +150,12 @@ export class UchusenRocket extends HTMLBase {
|
|
|
152
150
|
builtNow = null;
|
|
153
151
|
queued = false;
|
|
154
152
|
stateNow = "picture";
|
|
153
|
+
/** What the flag in the corner is for: a country's code, "" for none, "off", or "-" before the first look. */
|
|
154
|
+
flagKey = "-";
|
|
155
155
|
shownAt = 0;
|
|
156
156
|
starting = false;
|
|
157
|
+
/** Whether this element has asked the page's WebGL answer since it started watching. */
|
|
158
|
+
glAsked = false;
|
|
157
159
|
key = "";
|
|
158
160
|
driven = null;
|
|
159
161
|
placeFrame = 0;
|
|
@@ -334,7 +336,9 @@ export class UchusenRocket extends HTMLBase {
|
|
|
334
336
|
attributeChangedCallback(name, before, after) {
|
|
335
337
|
if (before === after)
|
|
336
338
|
return;
|
|
337
|
-
if (name === "
|
|
339
|
+
if (name === "flags")
|
|
340
|
+
this.syncFlag();
|
|
341
|
+
else if (name === "scale") {
|
|
338
342
|
this.viewer?.setScaleMode(after === "true");
|
|
339
343
|
// `scale="true"` is the true-scale view. A number is a Kaminari size, which rebuilds the craft.
|
|
340
344
|
if (after !== "true" && after !== "false")
|
|
@@ -359,6 +363,8 @@ export class UchusenRocket extends HTMLBase {
|
|
|
359
363
|
this.viewer?.setFloating(after !== null);
|
|
360
364
|
else if (name === "edges")
|
|
361
365
|
this.viewer?.setEdges(after === "open");
|
|
366
|
+
else if (name === "sun")
|
|
367
|
+
this.viewer?.setSun(parseSun(after));
|
|
362
368
|
else if (name === "world")
|
|
363
369
|
this.viewer?.setOrbit(after === "earth");
|
|
364
370
|
else if (name === "world-turn")
|
|
@@ -677,6 +683,7 @@ export class UchusenRocket extends HTMLBase {
|
|
|
677
683
|
holder.className = "stage";
|
|
678
684
|
picture.append(drawing, holder);
|
|
679
685
|
root.appendChild(picture);
|
|
686
|
+
this.flagKey = "-";
|
|
680
687
|
this.picture = picture;
|
|
681
688
|
this.drawing = drawing;
|
|
682
689
|
this.holder = holder;
|
|
@@ -747,6 +754,40 @@ export class UchusenRocket extends HTMLBase {
|
|
|
747
754
|
else
|
|
748
755
|
delete this.dataset.docked;
|
|
749
756
|
this.dataset.custom = String(this.builtNow.report.custom);
|
|
757
|
+
this.syncFlag();
|
|
758
|
+
}
|
|
759
|
+
/** `flags`: the rocket's country's flag in the picture's corner, from hata (an optional peer) when the page has it. `data-flag` is the code shown, or empty once it is settled that none is; it is absent while one is loading or the attribute is off. */
|
|
760
|
+
syncFlag() {
|
|
761
|
+
const picture = this.picture;
|
|
762
|
+
if (!picture)
|
|
763
|
+
return;
|
|
764
|
+
const on = this.hasAttribute("flags") && this.getAttribute("flags") !== "off";
|
|
765
|
+
const code = on && this.builtNow ? (ROCKETS[this.descriptionNow().rocket]?.country.value ?? "") : "";
|
|
766
|
+
const key = on ? code : "off";
|
|
767
|
+
if (key === this.flagKey)
|
|
768
|
+
return;
|
|
769
|
+
this.flagKey = key;
|
|
770
|
+
picture.querySelector(".flag")?.remove();
|
|
771
|
+
if (!on)
|
|
772
|
+
return void delete this.dataset.flag;
|
|
773
|
+
if (!code)
|
|
774
|
+
return void (this.dataset.flag = "");
|
|
775
|
+
delete this.dataset.flag;
|
|
776
|
+
void flagSvg(code).then((svg) => {
|
|
777
|
+
if (this.flagKey !== key || this.picture !== picture)
|
|
778
|
+
return;
|
|
779
|
+
if (svg) {
|
|
780
|
+
const badge = document.createElement("span");
|
|
781
|
+
badge.className = "flag";
|
|
782
|
+
badge.setAttribute("part", "flag");
|
|
783
|
+
badge.setAttribute("role", "img");
|
|
784
|
+
badge.setAttribute("aria-label", regionName(code, this.closest("[lang]")?.getAttribute("lang")));
|
|
785
|
+
badge.innerHTML = svg;
|
|
786
|
+
badge.firstElementChild?.setAttribute("aria-hidden", "true");
|
|
787
|
+
picture.append(badge);
|
|
788
|
+
}
|
|
789
|
+
this.dataset.flag = svg ? code : "";
|
|
790
|
+
});
|
|
750
791
|
}
|
|
751
792
|
async applyRocket() {
|
|
752
793
|
this.mark();
|
|
@@ -900,7 +941,8 @@ export class UchusenRocket extends HTMLBase {
|
|
|
900
941
|
}
|
|
901
942
|
startWatching() {
|
|
902
943
|
this.watch?.disconnect();
|
|
903
|
-
|
|
944
|
+
let reason = whyNot(this.getAttribute("mode") ?? "auto");
|
|
945
|
+
this.glAsked = false;
|
|
904
946
|
if (reason) {
|
|
905
947
|
this.setState(reason === "off" ? "picture" : "fallback", reason);
|
|
906
948
|
if (reason !== "off")
|
|
@@ -916,6 +958,16 @@ export class UchusenRocket extends HTMLBase {
|
|
|
916
958
|
this.kickFlight();
|
|
917
959
|
if (reason)
|
|
918
960
|
return;
|
|
961
|
+
if (seen.isIntersecting && !this.glAsked) {
|
|
962
|
+
// The probe makes a context, which software drawing takes seconds over: it waits until a view is about to draw, and is made once for the whole page.
|
|
963
|
+
this.glAsked = true;
|
|
964
|
+
if (!glProbe().works) {
|
|
965
|
+
reason = "no-webgl";
|
|
966
|
+
this.setState("fallback", reason);
|
|
967
|
+
this.emit("uchusen-fallback", { reason });
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
919
971
|
if (seen.isIntersecting) {
|
|
920
972
|
this.shownAt ||= performance.now();
|
|
921
973
|
window.clearTimeout(this.releaseTimer);
|
|
@@ -1003,6 +1055,7 @@ export class UchusenRocket extends HTMLBase {
|
|
|
1003
1055
|
made.drive(this.driven);
|
|
1004
1056
|
made.setFloating(this.hasAttribute("float"));
|
|
1005
1057
|
made.setEdges(this.getAttribute("edges") === "open");
|
|
1058
|
+
made.setSun(parseSun(this.getAttribute("sun")));
|
|
1006
1059
|
made.setNameLabels(this.getAttribute("names") !== "off");
|
|
1007
1060
|
made.setVisible(true);
|
|
1008
1061
|
const { look, show, viewName, angle } = this.settings(this.builtNow ?? built);
|
|
@@ -1195,3 +1248,4 @@ export function defineUchusenRocket() {
|
|
|
1195
1248
|
}
|
|
1196
1249
|
defineUchusenRocket();
|
|
1197
1250
|
export { ROCKETS, UchusenFlight };
|
|
1251
|
+
export { setFlagLoader } from "./element/flag.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { blank, figureWords } from "./data/types.ts";
|
|
|
4
4
|
export type { Disagreement, Figure, Variant, VariantFacts, VariantStatus } from "./data/types.ts";
|
|
5
5
|
export { MAKERS } from "./data/makers.ts";
|
|
6
6
|
export type { MakerId } from "./data/makers.ts";
|
|
7
|
+
export { COUNTRY_CODES, countryOf, MAKER_COUNTRIES } from "./data/countries.ts";
|
|
8
|
+
export type { CountryCode } from "./data/countries.ts";
|
|
7
9
|
export { FLIGHT_LOG, flightLogOf } from "./data/flight-log.ts";
|
|
8
10
|
export type { LoggedFlight } from "./data/flight-log.ts";
|
|
9
11
|
export { moved, movingGroupsOf } from "./model/moves.ts";
|
|
@@ -57,6 +59,7 @@ export { beaconLamp, dutyOf, lampFactor, lightMode, lightPool, sternLamp, workLa
|
|
|
57
59
|
export type { Motion, MotionKind, MotionSpeed } from "./model/fleet/motion.ts";
|
|
58
60
|
export { bellsOf, enginesWanted, FLAME_SETTLE_MS, pickBells, plumeLength, ringsOf } from "./engines.ts";
|
|
59
61
|
export type { Bell } from "./engines.ts";
|
|
62
|
+
export { parseSun } from "./sun.ts";
|
|
60
63
|
export { OPTION_ATTRIBUTES, attributesFromOptions, optionsFromAttributes, rocketFromParams, rocketToParams } from "./attrs.ts";
|
|
61
64
|
export type { RocketOptions } from "./attrs.ts";
|
|
62
65
|
export { assetPathOf, CDN, EMBED_KINDS, SITE, THREE_VERSION, embedCode, embedNote, paintedCredit } from "./embed.ts";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { PLANNED_ROCKETS, ROCKETS, partIdOf, SLOT_NAMES, variantOf } from "./rockets.js";
|
|
3
3
|
export { blank, figureWords } from "./data/types.js";
|
|
4
4
|
export { MAKERS } from "./data/makers.js";
|
|
5
|
+
export { COUNTRY_CODES, countryOf, MAKER_COUNTRIES } from "./data/countries.js";
|
|
5
6
|
export { FLIGHT_LOG, flightLogOf } from "./data/flight-log.js";
|
|
6
7
|
export { moved, movingGroupsOf } from "./model/moves.js";
|
|
7
8
|
export { PROPELLANTS, propellantOf } from "./data/propellants.js";
|
|
@@ -34,6 +35,7 @@ export { beamLight } from "./draw/beamLight.js";
|
|
|
34
35
|
export { angleAt, MOTIONS, motionNote, motionSpeed, motionsOf, motionWanted, slideAt } from "./model/fleet/motion.js";
|
|
35
36
|
export { beaconLamp, dutyOf, lampFactor, lightMode, lightPool, sternLamp, workLamp } from "./model/fleet/lights.js";
|
|
36
37
|
export { bellsOf, enginesWanted, FLAME_SETTLE_MS, pickBells, plumeLength, ringsOf } from "./engines.js";
|
|
38
|
+
export { parseSun } from "./sun.js";
|
|
37
39
|
export { OPTION_ATTRIBUTES, attributesFromOptions, optionsFromAttributes, rocketFromParams, rocketToParams } from "./attrs.js";
|
|
38
40
|
export { assetPathOf, CDN, EMBED_KINDS, SITE, THREE_VERSION, embedCode, embedNote, paintedCredit } from "./embed.js";
|
|
39
41
|
export { COMPANIES, COMPANY_KINDS, companyOf } from "./model/fleet/company.js";
|
package/dist/inspace.js
CHANGED
|
@@ -7,6 +7,7 @@ const SPACE = {
|
|
|
7
7
|
sls: ["upper-stage", "service-module", "capsule"],
|
|
8
8
|
"falcon-9": ["second-stage", "fairing", "trunk", "capsule"],
|
|
9
9
|
dragon: ["trunk", "capsule"],
|
|
10
|
+
"falcon-heavy": ["car", "starman"],
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
13
|
* The slots to draw when the craft is in space, or null when the picture stays the whole variant.
|
package/dist/model/registry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Every part there is, with its record and how to make its pieces. A new rocket adds its rows here and nothing else changes.
|
|
2
2
|
import { DRAGON_VARIANTS } from "../data/dragon.js";
|
|
3
|
-
import { FALCON_9_VARIANTS } from "../data/falcon.js";
|
|
3
|
+
import { FALCON_9_VARIANTS, FALCON_HEAVY_VARIANTS } from "../data/falcon.js";
|
|
4
4
|
import { STARSHIP_VARIANTS } from "../data/starship.js";
|
|
5
5
|
import { capsule, centreCore, DRAGON_DRAWS, falconFairing, falconFirstStage, FALCON_DRAWS, falconSecondStage, grasshopper, merlin, sideBooster, trunk } from "./falcon.js";
|
|
6
6
|
import { FALCON_1_VARIANTS } from "../data/falcon1.js";
|
|
@@ -37,6 +37,8 @@ import { HUBBLE_VARIANTS } from "../data/hubble.js";
|
|
|
37
37
|
import { HUBBLE_BUILDS } from "./hubble.js";
|
|
38
38
|
import { TIANGONG_VARIANTS } from "../data/tiangong.js";
|
|
39
39
|
import { TIANGONG_BUILDS } from "./tiangong.js";
|
|
40
|
+
import { ROADSTER_VARIANTS } from "../data/roadster.js";
|
|
41
|
+
import { ROADSTER_BUILDS } from "./roadster.js";
|
|
40
42
|
import { DRONE_VARIANTS } from "../data/drone.js";
|
|
41
43
|
import { DRONE_BUILDS } from "./drone.js";
|
|
42
44
|
import { DRAGON_INSIDE_BUILDS } from "./dragoninside.js";
|
|
@@ -145,11 +147,11 @@ const FH_FROM = "This package's code for the two line looks, proportions taken f
|
|
|
145
147
|
const FH_SIDE_HEIGHT = 45.6;
|
|
146
148
|
const FH_OFFSET = 4.25;
|
|
147
149
|
const figure = (value, source) => ({ value, source });
|
|
148
|
-
entries.push({ record: { id: "falcon-heavy-falcon-heavy-centre", kind: "first-stage", name: "Falcon Heavy centre core, with its upper stage and fairing", rocket: "falcon-heavy", trueTo: ["falcon-heavy"], height: 70, diameter: 5.2, sourced: { height: figure(70, "wikipedia-falcon-heavy"), diameter: { ...blankFigure, note: "3.7 m the core; 5.2 m the fairing." } }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-centre"], drawnFrom: FH_FROM }, pieces: () => centreCore(70, 1.85) }, { record: { id: "falcon-heavy-falcon-heavy-side-left", kind: "first-stage", name: "Falcon Heavy side booster, left", rocket: "falcon-heavy", trueTo: ["falcon-heavy"], height: FH_SIDE_HEIGHT, diameter: 3.7, strapOn: { x: -FH_OFFSET, z: 0 }, sourced: { height: blankFigure, diameter: figure(3.7, "wikipedia-falcon-heavy") }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-side-left"], drawnFrom: FH_FROM }, pieces: () => sideBooster(FH_SIDE_HEIGHT, 1.85, -1) }, { record: { id: "falcon-heavy-falcon-heavy-side-right", kind: "first-stage", name: "Falcon Heavy side booster, right", rocket: "falcon-heavy", trueTo: ["falcon-heavy"], height: FH_SIDE_HEIGHT, diameter: 3.7, strapOn: { x: FH_OFFSET, z: 0 }, sourced: { height: blankFigure, diameter: figure(3.7, "wikipedia-falcon-heavy") }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-side-right"], drawnFrom: FH_FROM }, pieces: () => sideBooster(FH_SIDE_HEIGHT, 1.85, 1) }, { record: { id: "grasshopper-grasshopper-vehicle", kind: "test-vehicle", name: "Grasshopper, test vehicle", rocket: "grasshopper", trueTo: ["grasshopper"], height: 32, diameter: 3.2, sourced: { height: figure(32, "wikipedia-grasshopper"), diameter: blankFigure }, joints: {}, painted: PAINTED["grasshopper-grasshopper-vehicle"], drawnFrom: "This package's code for the two line looks, proportions taken from Echo_3's low-poly model at the sourced height; that model for the painted look." }, pieces: () => grasshopper(32) });
|
|
150
|
+
entries.push({ record: { id: "falcon-heavy-falcon-heavy-centre", kind: "first-stage", name: "Falcon Heavy centre core, with its upper stage and fairing", rocket: "falcon-heavy", trueTo: ["falcon-heavy"], height: 70, diameter: 5.2, sourced: { height: figure(70, "wikipedia-falcon-heavy"), diameter: { ...blankFigure, note: "3.7 m the core; 5.2 m the fairing." } }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-centre"], drawnFrom: FH_FROM }, pieces: () => centreCore(70, 1.85) }, { record: { id: "falcon-heavy-falcon-heavy-side-left", kind: "first-stage", name: "Falcon Heavy side booster, left", rocket: "falcon-heavy", trueTo: ["falcon-heavy", "test-flight"], height: FH_SIDE_HEIGHT, diameter: 3.7, strapOn: { x: -FH_OFFSET, z: 0 }, sourced: { height: blankFigure, diameter: figure(3.7, "wikipedia-falcon-heavy") }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-side-left"], drawnFrom: FH_FROM }, pieces: () => sideBooster(FH_SIDE_HEIGHT, 1.85, -1) }, { record: { id: "falcon-heavy-falcon-heavy-side-right", kind: "first-stage", name: "Falcon Heavy side booster, right", rocket: "falcon-heavy", trueTo: ["falcon-heavy", "test-flight"], height: FH_SIDE_HEIGHT, diameter: 3.7, strapOn: { x: FH_OFFSET, z: 0 }, sourced: { height: blankFigure, diameter: figure(3.7, "wikipedia-falcon-heavy") }, joints: {}, painted: PAINTED["falcon-heavy-falcon-heavy-side-right"], drawnFrom: FH_FROM }, pieces: () => sideBooster(FH_SIDE_HEIGHT, 1.85, 1) }, { record: { id: "grasshopper-grasshopper-vehicle", kind: "test-vehicle", name: "Grasshopper, test vehicle", rocket: "grasshopper", trueTo: ["grasshopper"], height: 32, diameter: 3.2, sourced: { height: figure(32, "wikipedia-grasshopper"), diameter: blankFigure }, joints: {}, painted: PAINTED["grasshopper-grasshopper-vehicle"], drawnFrom: "This package's code for the two line looks, proportions taken from Echo_3's low-poly model at the sourced height; that model for the painted look." }, pieces: () => grasshopper(32) });
|
|
149
151
|
// ---------------------------------------------------------------------------------------------------------------------------------
|
|
150
152
|
// Rockets drawn wholly by this package's code, from their tables
|
|
151
|
-
const stackFacts = [...DRONE_VARIANTS, ...HUBBLE_VARIANTS, ...ISS_VARIANTS, ...TIANGONG_VARIANTS, ...JUPITER_C_VARIANTS, ...MERCURY_VARIANTS, ...GEMINI_VARIANTS, ...APOLLO_SOYUZ_VARIANTS, ...LUNAR_MODULE_VARIANTS, ...STARBASE_VARIANTS, ...DRAGON_VARIANTS, ...TOWER_VARIANTS, ...DRONESHIP_VARIANTS, ...LANDING_ZONE_VARIANTS, ...SOYUZ_VARIANTS, ...NEW_GLENN_VARIANTS, ...ELECTRON_VARIANTS, ...SLS_VARIANTS, ...FALCON_1_VARIANTS, ...SATURN_V_VARIANTS, ...SHUTTLE_VARIANTS, ...SHIP_02_VARIANTS];
|
|
152
|
-
const stacks = [...DRONE_BUILDS, ...HUBBLE_BUILDS, ...ISS_BUILDS, ...TIANGONG_BUILDS, ...JUPITER_C_BUILDS, ...MERCURY_BUILDS, ...GEMINI_BUILDS, ...APOLLO_SOYUZ_BUILDS, ...LUNAR_MODULE_BUILDS, ...STARBASE_BUILDS, ...DRAGON_INSIDE_BUILDS, ...TOWER_BUILDS, ...DRONESHIP_BUILDS, ...LANDING_ZONE_BUILDS, ...SOYUZ_BUILDS, ...NEW_GLENN_BUILDS, ...ELECTRON_BUILDS, ...SLS_BUILDS, ...FALCON_1_BUILDS, ...SATURN_V_BUILDS, ...SHUTTLE_BUILDS, ...SHIP_02_BUILDS];
|
|
153
|
+
const stackFacts = [...ROADSTER_VARIANTS, ...FALCON_HEAVY_VARIANTS, ...DRONE_VARIANTS, ...HUBBLE_VARIANTS, ...ISS_VARIANTS, ...TIANGONG_VARIANTS, ...JUPITER_C_VARIANTS, ...MERCURY_VARIANTS, ...GEMINI_VARIANTS, ...APOLLO_SOYUZ_VARIANTS, ...LUNAR_MODULE_VARIANTS, ...STARBASE_VARIANTS, ...DRAGON_VARIANTS, ...TOWER_VARIANTS, ...DRONESHIP_VARIANTS, ...LANDING_ZONE_VARIANTS, ...SOYUZ_VARIANTS, ...NEW_GLENN_VARIANTS, ...ELECTRON_VARIANTS, ...SLS_VARIANTS, ...FALCON_1_VARIANTS, ...SATURN_V_VARIANTS, ...SHUTTLE_VARIANTS, ...SHIP_02_VARIANTS];
|
|
154
|
+
const stacks = [...ROADSTER_BUILDS, ...DRONE_BUILDS, ...HUBBLE_BUILDS, ...ISS_BUILDS, ...TIANGONG_BUILDS, ...JUPITER_C_BUILDS, ...MERCURY_BUILDS, ...GEMINI_BUILDS, ...APOLLO_SOYUZ_BUILDS, ...LUNAR_MODULE_BUILDS, ...STARBASE_BUILDS, ...DRAGON_INSIDE_BUILDS, ...TOWER_BUILDS, ...DRONESHIP_BUILDS, ...LANDING_ZONE_BUILDS, ...SOYUZ_BUILDS, ...NEW_GLENN_BUILDS, ...ELECTRON_BUILDS, ...SLS_BUILDS, ...FALCON_1_BUILDS, ...SATURN_V_BUILDS, ...SHUTTLE_BUILDS, ...SHIP_02_BUILDS];
|
|
153
155
|
for (const build of stacks)
|
|
154
156
|
for (const part of build.parts) {
|
|
155
157
|
const { height, diameter } = facts(stackFacts, build.variant, part.slot);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Piece, Vec3 } from "./pieces.ts";
|
|
2
|
+
import type { StackBuild } from "./stacks.ts";
|
|
3
|
+
/** One car as measured from its model, in metres: nose towards +Z, on the ground at y = 0. */
|
|
4
|
+
export interface CarDraw {
|
|
5
|
+
/** Nose to tail, and across. */
|
|
6
|
+
length: number;
|
|
7
|
+
width: number;
|
|
8
|
+
/** The top of the windscreen. */
|
|
9
|
+
height: number;
|
|
10
|
+
/** The wheels: their middles along the car, their radius and how far each stands from the middle. */
|
|
11
|
+
front: number;
|
|
12
|
+
rear: number;
|
|
13
|
+
wheel: number;
|
|
14
|
+
track: number;
|
|
15
|
+
/** The top of the doors. */
|
|
16
|
+
belt: number;
|
|
17
|
+
/** Where the windscreen meets the body, along the car. */
|
|
18
|
+
screen: number;
|
|
19
|
+
/** The driver's hip, in the left seat: across, up and along. */
|
|
20
|
+
seat: Vec3;
|
|
21
|
+
/** The middle of the steering wheel. */
|
|
22
|
+
wheelAt: Vec3;
|
|
23
|
+
}
|
|
24
|
+
export declare const ROADSTER_DRAWS: Readonly<Record<string, CarDraw>>;
|
|
25
|
+
/** The car: its body seen from the side and pressed to its width, its cockpit open, the windscreen and the four wheels. */
|
|
26
|
+
export declare function carPieces(d: CarDraw): Piece[];
|
|
27
|
+
/** Starman in the driver's seat: legs forward to the pedals, his back against the seat, his right hand on the wheel, his left arm along the top of the door. */
|
|
28
|
+
export declare function starmanPieces(d: CarDraw): Piece[];
|
|
29
|
+
/** Where the payload stands in Falcon Heavy: 0.8 m above where its fairing begins (the fairing's sourced 13.1 m under the 70 m top), clear of the fairing's walls. The 0.8 m is a placing, not a figure. */
|
|
30
|
+
export declare const FALCON_HEAVY_PAYLOAD_Y: number;
|
|
31
|
+
export declare const ROADSTER_BUILDS: readonly StackBuild[];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// The Roadster in plain shapes for the line looks, and Starman in every look. The car's length, wheelbase, wheels and seat are measured from each licensed model at its sourced scale; the painted look is the
|
|
2
|
+
// model. Starman is this package's own: a seated figure in a white suit with dark trim, a white helmet with a dark visor, his right hand on the wheel and his left arm on the door. He shares the car's origin
|
|
3
|
+
// (the middle of the car on the ground), so he sits in its seat in any build that places the two together.
|
|
4
|
+
import { ROADSTER_FLIGHT } from "../data/roadster.js";
|
|
5
|
+
import { blank } from "../data/types.js";
|
|
6
|
+
import { centreCore } from "./falcon.js";
|
|
7
|
+
import { strut } from "./pieces.js";
|
|
8
|
+
export const ROADSTER_DRAWS = {
|
|
9
|
+
"first-generation": { length: 3.946, width: 1.85, height: 1.03, front: 1.114, rear: -1.258, wheel: 0.3, track: 1.46, belt: 0.8, screen: 0.95, seat: [0.29, 0.34, -0.22], wheelAt: [0.29, 0.62, 0.37] },
|
|
10
|
+
"second-generation": { length: 4.47, width: 1.83, height: 1.14, front: 1.417, rear: -1.408, wheel: 0.31, track: 1.52, belt: 0.84, screen: 0.75, seat: [0.32, 0.22, -0.18], wheelAt: [0.32, 0.58, 0.3] },
|
|
11
|
+
"second-generation-2": { length: 4.28, width: 1.86, height: 1.07, front: 1.398, rear: -1.429, wheel: 0.31, track: 1.58, belt: 0.8, screen: 0.7, seat: [0.32, 0.17, -0.2], wheelAt: [0.32, 0.55, 0.28] },
|
|
12
|
+
};
|
|
13
|
+
const side = (d) => {
|
|
14
|
+
const nose = d.length / 2;
|
|
15
|
+
return [
|
|
16
|
+
[-nose, 0.22],
|
|
17
|
+
[-d.length * 0.16, 0.14],
|
|
18
|
+
[nose * 0.8, 0.14],
|
|
19
|
+
[nose, 0.32],
|
|
20
|
+
[nose * 0.92, 0.6],
|
|
21
|
+
[d.screen, d.belt - 0.02],
|
|
22
|
+
[-nose * 0.9, d.belt + 0.04],
|
|
23
|
+
[-nose, 0.62],
|
|
24
|
+
];
|
|
25
|
+
};
|
|
26
|
+
const octagon = (r) => Array.from({ length: 12 }, (_, i) => [Math.cos((i * Math.PI) / 6) * r, Math.sin((i * Math.PI) / 6) * r]);
|
|
27
|
+
/** The car: its body seen from the side and pressed to its width, its cockpit open, the windscreen and the four wheels. */
|
|
28
|
+
export function carPieces(d) {
|
|
29
|
+
const half = d.width / 2;
|
|
30
|
+
const pieces = [{ kind: "plate", material: "red", frame: { o: [0, 0, 0], u: [0, 0, 1], v: [0, 1, 0], w: [1, 0, 0] }, outline: side(d), thickness: d.width - 0.06 }];
|
|
31
|
+
// the open cockpit, dark, sunk into the top of the body
|
|
32
|
+
pieces.push({ kind: "plate", material: "dark", frame: { o: [0, d.belt + 0.005, 0], u: [1, 0, 0], v: [0, 0, 1], w: [0, 1, 0] }, outline: [[-half + 0.2, -0.75], [half - 0.2, -0.75], [half - 0.2, d.screen - 0.05], [-half + 0.2, d.screen - 0.05]], thickness: 0.01, plain: true });
|
|
33
|
+
// the windscreen, leaning back from the scuttle
|
|
34
|
+
const lean = 0.55;
|
|
35
|
+
const rise = d.height - d.belt;
|
|
36
|
+
const up = [0, Math.cos(lean), -Math.sin(lean)];
|
|
37
|
+
pieces.push({ kind: "plate", material: "glass", frame: { o: [0, d.belt, d.screen], u: [1, 0, 0], v: up, w: [0, Math.sin(lean), Math.cos(lean)] }, outline: [[-half + 0.18, 0], [half - 0.18, 0], [half - 0.26, rise / Math.cos(lean)], [-half + 0.26, rise / Math.cos(lean)]], thickness: 0.012 });
|
|
38
|
+
for (const z of [d.front, d.rear])
|
|
39
|
+
for (const s of [1, -1]) {
|
|
40
|
+
const x = (s * d.track) / 2;
|
|
41
|
+
pieces.push({ kind: "plate", material: "black", frame: { o: [x, d.wheel, z], u: [0, 0, 1], v: [0, 1, 0], w: [1, 0, 0] }, outline: octagon(d.wheel), thickness: 0.24 });
|
|
42
|
+
pieces.push({ kind: "plate", material: "fin-dark", frame: { o: [x + s * 0.121, d.wheel, z], u: [0, 0, 1], v: [0, 1, 0], w: [1, 0, 0] }, outline: octagon(d.wheel * 0.62), thickness: 0.01, plain: true });
|
|
43
|
+
}
|
|
44
|
+
return pieces;
|
|
45
|
+
}
|
|
46
|
+
const at = (p, dx, dy, dz) => [p[0] + dx, p[1] + dy, p[2] + dz];
|
|
47
|
+
/** Starman in the driver's seat: legs forward to the pedals, his back against the seat, his right hand on the wheel, his left arm along the top of the door. */
|
|
48
|
+
export function starmanPieces(d) {
|
|
49
|
+
const hip = d.seat;
|
|
50
|
+
const shoulder = at(hip, 0, 0.52, -0.1);
|
|
51
|
+
const knee = at(hip, 0, 0.12, 0.44);
|
|
52
|
+
// the feet on the floor, which is never under the ground
|
|
53
|
+
const foot = [hip[0], Math.max(0.12, hip[1] - 0.18), hip[2] + 0.86];
|
|
54
|
+
const right = at(shoulder, -0.19, 0, 0);
|
|
55
|
+
const left = at(shoulder, 0.19, 0, 0);
|
|
56
|
+
const door = [Math.min(d.width / 2 - 0.08, hip[0] + 0.42), d.belt + 0.05, hip[2] + 0.12];
|
|
57
|
+
const pieces = [
|
|
58
|
+
// the torso, leaning back with the seat
|
|
59
|
+
{ kind: "lathe", material: "white", segs: 16, at: [hip[0], hip[2]], lean: [0, -0.19], profile: [[0.14, hip[1]], [0.18, hip[1] + 0.22], [0.19, shoulder[1] - 0.06], [0.1, shoulder[1] + 0.04]] },
|
|
60
|
+
// the helmet, white, round, with its dark visor facing the road
|
|
61
|
+
{ kind: "lathe", material: "white", segs: 20, at: [shoulder[0], shoulder[2] - 0.02], profile: [[0.07, shoulder[1] + 0.04], [0.15, shoulder[1] + 0.13], [0.155, shoulder[1] + 0.22], [0.12, shoulder[1] + 0.31], [0.001, shoulder[1] + 0.35]] },
|
|
62
|
+
{ kind: "plate", material: "glass", frame: { o: [shoulder[0], shoulder[1] + 0.2, shoulder[2] + 0.12], u: [1, 0, 0], v: [0, 1, 0], w: [0, 0, 1] }, outline: [[-0.1, -0.06], [0.1, -0.06], [0.11, 0.05], [-0.11, 0.05]], thickness: 0.04, plain: true },
|
|
63
|
+
// a dark ring where the helmet meets the suit
|
|
64
|
+
{ kind: "lathe", material: "black", segs: 16, at: [shoulder[0], shoulder[2] - 0.02], profile: [[0.12, shoulder[1] + 0.01], [0.12, shoulder[1] + 0.06]], only: "mesh" },
|
|
65
|
+
];
|
|
66
|
+
for (const s of [-1, 1]) {
|
|
67
|
+
const h = at(hip, s * 0.1, 0, 0);
|
|
68
|
+
const k = at(knee, s * 0.1, 0, 0);
|
|
69
|
+
const f = at(foot, s * 0.1, 0, 0);
|
|
70
|
+
pieces.push(strut("white", h, k, 0.15, 0.15), strut("white", k, f, 0.13, 0.13), strut("black", f, at(f, 0, 0.02, 0.1), 0.12, 0.14));
|
|
71
|
+
}
|
|
72
|
+
// his right hand on the wheel's rim, his left arm on the door top
|
|
73
|
+
const hand = at(d.wheelAt, -0.12, 0.04, -0.03);
|
|
74
|
+
const elbowR = at(right, -0.02, -0.22, 0.2);
|
|
75
|
+
const elbowL = at(left, 0.12, -0.2, 0.05);
|
|
76
|
+
pieces.push(strut("white", right, elbowR, 0.11, 0.11), strut("white", elbowR, hand, 0.1, 0.1), strut("black", hand, at(hand, 0, 0, 0.06), 0.09, 0.09));
|
|
77
|
+
pieces.push(strut("white", left, elbowL, 0.11, 0.11), strut("white", elbowL, door, 0.1, 0.1), strut("black", door, at(door, 0, 0, 0.07), 0.09, 0.09));
|
|
78
|
+
return pieces;
|
|
79
|
+
}
|
|
80
|
+
const FILES = {
|
|
81
|
+
"first-generation": { file: "roadster-first-generation-car.glb", credit: "the86guy-roadster" },
|
|
82
|
+
"second-generation": { file: "roadster-second-generation-car.glb", credit: "metarex4d-roadster" },
|
|
83
|
+
"second-generation-2": { file: "roadster-second-generation-2-car.glb", credit: "gmmichaelonearth-roadster", also: ["minchou19-wheels"] },
|
|
84
|
+
};
|
|
85
|
+
const STARMAN_NAME = "Starman, a mannequin in a pressure suit";
|
|
86
|
+
const carPart = (variant, place) => {
|
|
87
|
+
const d = ROADSTER_DRAWS[variant];
|
|
88
|
+
return { slot: "car", kind: "payload", name: variant === "first-generation" ? "The Roadster, first generation" : "The Roadster, second generation", height: d.height, diameter: d.width, sourcedDiameter: blank(`Its width as drawn: the model at its sourced scale is ${d.width} m across.`), painted: FILES[variant], ...(place ? { place } : {}), pieces: () => carPieces(d) };
|
|
89
|
+
};
|
|
90
|
+
const starmanPart = (variant, place) => {
|
|
91
|
+
const d = ROADSTER_DRAWS[variant];
|
|
92
|
+
return { slot: "starman", kind: "payload", name: STARMAN_NAME, height: d.seat[1] + 0.87, diameter: 0.6, sourcedDiameter: blank("No source read here gives Starman's size."), place, pieces: () => starmanPieces(d) };
|
|
93
|
+
};
|
|
94
|
+
const FROM = (variant) => variant === "first-generation"
|
|
95
|
+
? "The 86 guy's model for the painted look, with its badges and roof left off and its bonnet made bare carbon; this package's plain shapes for the line looks, measured from it at the sourced length. Starman is this package's own in every look."
|
|
96
|
+
: "Its author's model for the painted look, with its badges left off, painted the first generation's red; this package's plain shapes for the line looks, measured from it at the sourced wheelbase. Starman is this package's own in every look.";
|
|
97
|
+
/** Where the payload stands in Falcon Heavy: 0.8 m above where its fairing begins (the fairing's sourced 13.1 m under the 70 m top), clear of the fairing's walls. The 0.8 m is a placing, not a figure. */
|
|
98
|
+
export const FALCON_HEAVY_PAYLOAD_Y = 70 - 13.1 + 0.8;
|
|
99
|
+
export const ROADSTER_BUILDS = [
|
|
100
|
+
...Object.keys(ROADSTER_DRAWS).map((variant) => ({ rocket: "roadster", variant, drawnFrom: FROM(variant), parts: [carPart(variant), starmanPart(variant, { x: 0, z: 0 })] })),
|
|
101
|
+
{
|
|
102
|
+
rocket: "falcon-heavy",
|
|
103
|
+
variant: ROADSTER_FLIGHT.id,
|
|
104
|
+
drawnFrom: "Antonio_Adams's low-poly Falcon Heavy and the 86 guy's Roadster for the painted look, this package's plain shapes for the line looks. Starman is this package's own in every look.",
|
|
105
|
+
parts: [
|
|
106
|
+
// the same vehicle as Falcon Heavy's own, on its first flight: its centre core is its own part here, so the test flight is reached from its own foot
|
|
107
|
+
{ slot: "centre", kind: "first-stage", name: "Falcon Heavy centre core, with its upper stage and fairing", height: 70, diameter: 5.2, sourcedDiameter: blank("3.7 m the core; 5.2 m the fairing."), painted: { file: "falcon-heavy-falcon-heavy-centre.glb", credit: "antonio-falcon-heavy" }, pieces: () => centreCore(70, 1.85) },
|
|
108
|
+
carPart("first-generation", { x: 0, z: 0, y: FALCON_HEAVY_PAYLOAD_Y }),
|
|
109
|
+
starmanPart("first-generation", { x: 0, z: 0, y: FALCON_HEAVY_PAYLOAD_Y }),
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
];
|
package/dist/parts.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Figure } from "./data/types.ts";
|
|
|
11
11
|
* console.log(kind);
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
export type PartKind = "booster" | "first-stage" | "ship" | "second-stage" | "fairing" | "capsule" | "trunk" | "engine" | "fin" | "flap" | "ring" | "test-vehicle" | "third-stage" | "fourth-stage" | "tail" | "adapter" | "service-module" | "tower" | "tank" | "solid-booster" | "liquid-booster" | "orbiter" | "deck" | "mount" | "arms" | "site" | "spacecraft" | "module" | "array" | "antenna";
|
|
14
|
+
export type PartKind = "booster" | "first-stage" | "ship" | "second-stage" | "fairing" | "capsule" | "trunk" | "engine" | "fin" | "flap" | "ring" | "test-vehicle" | "third-stage" | "fourth-stage" | "tail" | "adapter" | "service-module" | "tower" | "tank" | "solid-booster" | "liquid-booster" | "orbiter" | "deck" | "mount" | "arms" | "site" | "spacecraft" | "module" | "array" | "antenna" | "payload";
|
|
15
15
|
/**
|
|
16
16
|
* Where a part mates. Every part's origin is the middle of its base on its axis, at its lowest point; Y is up and the windward side faces +Z, in metres.
|
|
17
17
|
*
|