@johnmorrisdotca/uchusen-rockets 0.4.0 → 0.6.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 +25 -0
- package/NOTICE.md +2 -0
- package/README.md +80 -5
- 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.0-dragon-apart.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-apart.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-apart.thick.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-capsule.glb +0 -0
- package/dist/assets/falcon-9-v1.0-dragon-capsule.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-capsule.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-capsule.thick.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-first-stage.glb +0 -0
- package/dist/assets/falcon-9-v1.0-dragon-first-stage.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-first-stage.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-first-stage.thick.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-second-stage.glb +0 -0
- package/dist/assets/falcon-9-v1.0-dragon-second-stage.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-second-stage.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-second-stage.thick.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-trunk.glb +0 -0
- package/dist/assets/falcon-9-v1.0-dragon-trunk.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-trunk.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon-trunk.thick.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon.glb +0 -0
- package/dist/assets/falcon-9-v1.0-dragon.painted.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon.svg +1 -0
- package/dist/assets/falcon-9-v1.0-dragon.thick.svg +1 -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/build/glb.js +15 -7
- package/dist/build/index.d.ts +5 -0
- package/dist/build/index.js +16 -3
- package/dist/data/falcon.js +18 -0
- package/dist/data/flights.d.ts +7 -0
- package/dist/data/flights.js +204 -13
- package/dist/data/propellants.d.ts +59 -0
- package/dist/data/propellants.js +43 -0
- package/dist/data/sources.d.ts +92 -0
- package/dist/data/sources.js +12 -0
- 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.d.ts +6 -1
- package/dist/element/scene.js +71 -7
- package/dist/element.d.ts +1 -0
- package/dist/element.js +18 -6
- package/dist/flight.d.ts +2 -0
- package/dist/flight.js +8 -1
- package/dist/flightbar.d.ts +30 -0
- package/dist/flightbar.js +46 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/model/falcon.js +2 -2
- 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.js +2 -2
- package/dist/model/starship.d.ts +6 -3
- package/dist/model/starship.js +12 -7
- package/dist/react.d.ts +3 -3
- package/package.json +18 -3
package/dist/element/plumes.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import type { FlameKind } from "../data/propellants.ts";
|
|
1
2
|
import { BufferGeometry, Group, PointLight, ShaderMaterial } from "three";
|
|
2
3
|
import { type Bell } from "../engines.ts";
|
|
3
|
-
/** What burns in each engine's flame, as a picture: a fuel's flame is drawn in its own colours. */
|
|
4
|
-
export type Fuel = "methane" | "kerosene";
|
|
5
4
|
/** One part's flames: at most as many as it has bells. Its buffers are made once and written when the engines that burn change. */
|
|
6
5
|
export declare class PartPlumes {
|
|
7
6
|
readonly group: Group<import("three").Object3DEventMap>;
|
|
@@ -9,23 +8,23 @@ export declare class PartPlumes {
|
|
|
9
8
|
private flames;
|
|
10
9
|
private glows;
|
|
11
10
|
private key;
|
|
12
|
-
constructor(kit: PlumeKit, bells: readonly Bell[]);
|
|
11
|
+
constructor(kit: PlumeKit, bells: readonly Bell[], kind: FlameKind);
|
|
13
12
|
/** The bells that burn now. Nothing is written when they are the ones that burned last. */
|
|
14
13
|
light(burning: readonly Bell[]): void;
|
|
15
14
|
dispose(): void;
|
|
16
15
|
}
|
|
17
|
-
/** One set of everything a flame needs, for one viewer. */
|
|
16
|
+
/** One set of everything a flame needs, for one viewer. The materials of each kind of flame share one program and one set of clocks; only the two colours differ. */
|
|
18
17
|
export declare class PlumeKit {
|
|
19
18
|
readonly shape: BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
20
19
|
readonly quad: BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
21
|
-
readonly flame: ShaderMaterial;
|
|
22
|
-
readonly glow: ShaderMaterial;
|
|
23
20
|
/** The hull's own glow in the painted look. Always in the scene, at no strength when no engine burns, so lighting engines never makes the materials build again. */
|
|
24
21
|
readonly light: PointLight;
|
|
25
22
|
private uniforms;
|
|
26
|
-
|
|
23
|
+
private kinds;
|
|
24
|
+
private kind;
|
|
25
|
+
flameFor(kind: FlameKind): ShaderMaterial;
|
|
26
|
+
glowFor(kind: FlameKind): ShaderMaterial;
|
|
27
27
|
setLook(painted: boolean): void;
|
|
28
|
-
setFuel(fuel: Fuel): void;
|
|
29
28
|
/** 0 near the ground, 1 high up where a flame fans out. */
|
|
30
29
|
setAir(air: number): void;
|
|
31
30
|
/** Where the ground stands, for a flame to stop at. */
|
package/dist/element/plumes.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Flames for the 3D view, made here and loaded only with it. One shape, one material and one instanced draw for every engine of a part, however many burn, so lighting and dousing engines changes numbers, never what the graphics card holds. The look of a flame (its shock diamonds, its length, its glow) is a picture, not a measurement.
|
|
2
1
|
import { AdditiveBlending, BufferGeometry, Color, DoubleSide, DynamicDrawUsage, Float32BufferAttribute, Group, InstancedMesh, Matrix4, PointLight, Quaternion, ShaderMaterial, Vector3 } from "three";
|
|
3
2
|
import { PLUME_DIAMONDS, plumeLength } from "../engines.js";
|
|
4
3
|
const STEPS = 28;
|
|
@@ -100,9 +99,14 @@ function glowShape() {
|
|
|
100
99
|
geometry.setIndex([0, 1, 2, 0, 2, 3]);
|
|
101
100
|
return geometry;
|
|
102
101
|
}
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
/** What a flame is drawn in: the kind of flame its engine's propellant gives (`src/data/propellants.ts`), and a neutral grey-white where no page read says. A picture, not a measurement. */
|
|
103
|
+
const FLAME_TONES = {
|
|
105
104
|
kerosene: { hot: "#fff4d2", cool: "#ff7a1a" },
|
|
105
|
+
methane: { hot: "#fff1e6", cool: "#6f7dff" },
|
|
106
|
+
hydrogen: { hot: "#ffffff", cool: "#6fa8ff" },
|
|
107
|
+
solid: { hot: "#fff1c4", cool: "#ff4a12" },
|
|
108
|
+
hypergolic: { hot: "#ffffff", cool: "#b8c4ee" },
|
|
109
|
+
neutral: { hot: "#ffffff", cool: "#9aa3b2" },
|
|
106
110
|
};
|
|
107
111
|
const place = new Matrix4();
|
|
108
112
|
const still = new Quaternion();
|
|
@@ -115,11 +119,11 @@ export class PartPlumes {
|
|
|
115
119
|
flames;
|
|
116
120
|
glows;
|
|
117
121
|
key = "";
|
|
118
|
-
constructor(kit, bells) {
|
|
122
|
+
constructor(kit, bells, kind) {
|
|
119
123
|
this.bells = bells;
|
|
120
124
|
this.group.name = "plumes";
|
|
121
|
-
this.flames = new InstancedMesh(kit.shape, kit.
|
|
122
|
-
this.glows = new InstancedMesh(kit.quad, kit.
|
|
125
|
+
this.flames = new InstancedMesh(kit.shape, kit.flameFor(kind), bells.length);
|
|
126
|
+
this.glows = new InstancedMesh(kit.quad, kit.glowFor(kind), bells.length);
|
|
123
127
|
for (const mesh of [this.flames, this.glows]) {
|
|
124
128
|
mesh.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
125
129
|
mesh.frustumCulled = false;
|
|
@@ -149,12 +153,10 @@ export class PartPlumes {
|
|
|
149
153
|
this.glows.dispose();
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
|
-
/** One set of everything a flame needs, for one viewer. */
|
|
156
|
+
/** One set of everything a flame needs, for one viewer. The materials of each kind of flame share one program and one set of clocks; only the two colours differ. */
|
|
153
157
|
export class PlumeKit {
|
|
154
158
|
shape = flameShape();
|
|
155
159
|
quad = glowShape();
|
|
156
|
-
flame;
|
|
157
|
-
glow;
|
|
158
160
|
/** The hull's own glow in the painted look. Always in the scene, at no strength when no engine burns, so lighting engines never makes the materials build again. */
|
|
159
161
|
light = new PointLight("#ffb56b", 0, 0, 1.6);
|
|
160
162
|
uniforms = {
|
|
@@ -164,21 +166,31 @@ export class PlumeKit {
|
|
|
164
166
|
uLine: { value: 1 },
|
|
165
167
|
uFloor: { value: -1e6 },
|
|
166
168
|
uGlow: { value: 0.75 },
|
|
167
|
-
uHot: { value: new Color(FUEL_COLOURS.kerosene.hot) },
|
|
168
|
-
uCool: { value: new Color(FUEL_COLOURS.kerosene.cool) },
|
|
169
169
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
kinds = new Map();
|
|
171
|
+
kind(kind) {
|
|
172
|
+
const known = this.kinds.get(kind);
|
|
173
|
+
if (known)
|
|
174
|
+
return known;
|
|
175
|
+
const tone = FLAME_TONES[kind];
|
|
176
|
+
const uniforms = { ...this.uniforms, uHot: { value: new Color(tone.hot) }, uCool: { value: new Color(tone.cool) } };
|
|
177
|
+
const common = { transparent: true, depthWrite: false, blending: AdditiveBlending, toneMapped: false, uniforms };
|
|
178
|
+
const made = {
|
|
179
|
+
flame: new ShaderMaterial({ ...common, vertexShader: FLAME_VERTEX, fragmentShader: FLAME_FRAGMENT, side: DoubleSide }),
|
|
180
|
+
glow: new ShaderMaterial({ ...common, vertexShader: GLOW_VERTEX, fragmentShader: GLOW_FRAGMENT }),
|
|
181
|
+
};
|
|
182
|
+
this.kinds.set(kind, made);
|
|
183
|
+
return made;
|
|
184
|
+
}
|
|
185
|
+
flameFor(kind) {
|
|
186
|
+
return this.kind(kind).flame;
|
|
187
|
+
}
|
|
188
|
+
glowFor(kind) {
|
|
189
|
+
return this.kind(kind).glow;
|
|
174
190
|
}
|
|
175
191
|
setLook(painted) {
|
|
176
192
|
this.uniforms.uLine.value = painted ? 0 : 1;
|
|
177
193
|
}
|
|
178
|
-
setFuel(fuel) {
|
|
179
|
-
this.uniforms.uHot.value.set(FUEL_COLOURS[fuel].hot);
|
|
180
|
-
this.uniforms.uCool.value.set(FUEL_COLOURS[fuel].cool);
|
|
181
|
-
}
|
|
182
194
|
/** 0 near the ground, 1 high up where a flame fans out. */
|
|
183
195
|
setAir(air) {
|
|
184
196
|
this.uniforms.uAir.value = air;
|
|
@@ -200,8 +212,11 @@ export class PlumeKit {
|
|
|
200
212
|
dispose() {
|
|
201
213
|
this.shape.dispose();
|
|
202
214
|
this.quad.dispose();
|
|
203
|
-
this.
|
|
204
|
-
|
|
215
|
+
for (const { flame, glow } of this.kinds.values()) {
|
|
216
|
+
flame.dispose();
|
|
217
|
+
glow.dispose();
|
|
218
|
+
}
|
|
219
|
+
this.kinds.clear();
|
|
205
220
|
this.light.dispose();
|
|
206
221
|
this.light.removeFromParent();
|
|
207
222
|
}
|
package/dist/element/scene.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface ViewerOptions {
|
|
|
9
9
|
onLost: () => void;
|
|
10
10
|
/** Where the licensed models are. */
|
|
11
11
|
assets: string;
|
|
12
|
-
/** Tells the page which painted parts have come in. */
|
|
12
|
+
/** Tells the page which painted parts have come in, or "" when the look is not painted. */
|
|
13
13
|
onPainted: (state: string) => void;
|
|
14
14
|
/** Tells the page a timeline moved. */
|
|
15
15
|
onFlight?: () => void;
|
|
@@ -62,6 +62,9 @@ export declare class RocketViewer {
|
|
|
62
62
|
private lastDistance;
|
|
63
63
|
private resize;
|
|
64
64
|
private disposed;
|
|
65
|
+
private compiling;
|
|
66
|
+
private compiled;
|
|
67
|
+
private settled;
|
|
65
68
|
constructor(holder: HTMLElement, options: ViewerOptions);
|
|
66
69
|
private homeAngle;
|
|
67
70
|
setLabel(label: string): void;
|
|
@@ -80,6 +83,8 @@ export declare class RocketViewer {
|
|
|
80
83
|
private repeating;
|
|
81
84
|
/** One of the three looks. The camera does not move. */
|
|
82
85
|
setLook(look: LookName): void;
|
|
86
|
+
/** Builds the shaders a look needs beside the page's own work, where the browser can build them in parallel (KHR_parallel_shader_compile), and holds the next draw until they are in, so the page keeps answering while a look is first built. The last picture stays up meanwhile. A model that comes in later builds its own. */
|
|
87
|
+
private prepare;
|
|
83
88
|
/** Which engines burn: at full thrust every one that shows, a count for each part, or none. Nothing is made or freed by this after a part's first flame: only which of its flames are drawn changes. */
|
|
84
89
|
setEngines(lit: {
|
|
85
90
|
readonly [slot: string]: number;
|
package/dist/element/scene.js
CHANGED
|
@@ -18,6 +18,8 @@ import { GroundKit } from "./ground.js";
|
|
|
18
18
|
import { PartPlumes, PlumeKit } from "./plumes.js";
|
|
19
19
|
const IDLE_STOP_MS = 90_000;
|
|
20
20
|
const MOVE_MS = 1400;
|
|
21
|
+
/** Longest a draw waits for shaders to build: a browser that never says they are ready still draws. */
|
|
22
|
+
const COMPILE_MS = 4000;
|
|
21
23
|
const VIEW_MS = 700;
|
|
22
24
|
const autoRotateOf = (spin) => (spin.seconds <= 0 ? 0 : (60 / spin.seconds) * (spin.way === "cw" ? -1 : 1));
|
|
23
25
|
const LINE_VERTEX = /* glsl */ `
|
|
@@ -77,8 +79,6 @@ void main() {
|
|
|
77
79
|
/** The materials of an engine's bell, in this package's own meshes. */
|
|
78
80
|
const NOZZLE_MATERIALS = new Set(["bell", "bell-inside", "engine-metal"]);
|
|
79
81
|
const asVec = (v) => [v.x, v.y, v.z];
|
|
80
|
-
/** A flame's colours, a picture: Raptor burns methane, Merlin kerosene. */
|
|
81
|
-
const fuelOf = (built) => (built.report.parts.some(({ part }) => part.rocket === "starship") ? "methane" : "kerosene");
|
|
82
82
|
/** The pictures a licensed model's material may hold (the shared sky it reflects is not one of them). */
|
|
83
83
|
const MODEL_PICTURES = ["map", "normalMap", "roughnessMap", "metalnessMap", "aoMap", "emissiveMap"];
|
|
84
84
|
export class RocketViewer {
|
|
@@ -129,6 +129,9 @@ export class RocketViewer {
|
|
|
129
129
|
lastDistance = 0;
|
|
130
130
|
resize;
|
|
131
131
|
disposed = false;
|
|
132
|
+
compiling = 0;
|
|
133
|
+
compiled = new Set();
|
|
134
|
+
settled = Promise.resolve();
|
|
132
135
|
constructor(holder, options) {
|
|
133
136
|
this.holder = holder;
|
|
134
137
|
this.options = options;
|
|
@@ -242,8 +245,20 @@ export class RocketViewer {
|
|
|
242
245
|
to: [0, 0, 0],
|
|
243
246
|
hideAtEnd: false,
|
|
244
247
|
plumes: null,
|
|
248
|
+
moves: [],
|
|
245
249
|
};
|
|
246
250
|
this.takeMeshes(source, code, view);
|
|
251
|
+
for (const node of source.children.slice(1)) {
|
|
252
|
+
const axis = node.userData.axis;
|
|
253
|
+
if (!axis)
|
|
254
|
+
continue;
|
|
255
|
+
const group = new Group();
|
|
256
|
+
group.name = node.name;
|
|
257
|
+
group.position.copy(node.position);
|
|
258
|
+
code.add(group);
|
|
259
|
+
this.takeMeshes(node, group, view);
|
|
260
|
+
view.moves.push({ name: String(node.userData.group), group, axis: new Vector3(axis[0], axis[1], axis[2]).normalize(), angle: 0 });
|
|
261
|
+
}
|
|
247
262
|
const place = places.find((p) => p.slot === part.slot);
|
|
248
263
|
holder.position.set(...(place?.at ?? [0, 0, 0]));
|
|
249
264
|
holder.visible = place?.shown ?? false;
|
|
@@ -252,7 +267,6 @@ export class RocketViewer {
|
|
|
252
267
|
}
|
|
253
268
|
this.addGlow();
|
|
254
269
|
this.setLook(look);
|
|
255
|
-
this.plumes.setFuel(fuelOf(built));
|
|
256
270
|
this.frameTo(angle ?? this.currentAngle(), false);
|
|
257
271
|
if (this.clockNow) {
|
|
258
272
|
this.flightKey = "";
|
|
@@ -263,6 +277,7 @@ export class RocketViewer {
|
|
|
263
277
|
this.canvas.dataset.parts = this.parts.map((part) => part.slot).join(" ");
|
|
264
278
|
this.drivenDirty = this.driven !== null;
|
|
265
279
|
this.wake();
|
|
280
|
+
await this.settled;
|
|
266
281
|
}
|
|
267
282
|
/** Moves the parts from the packed nodes into `into`, keeping each mesh's own placement. */
|
|
268
283
|
takeMeshes(source, into, view) {
|
|
@@ -415,9 +430,38 @@ export class RocketViewer {
|
|
|
415
430
|
this.canvas.dataset.look = look;
|
|
416
431
|
this.plumes.setLook(painted);
|
|
417
432
|
this.relight();
|
|
418
|
-
|
|
433
|
+
// "painted" is said when its shaders are built, so a reader who waits for it finds the next frame ready to draw
|
|
434
|
+
if (look === "painted")
|
|
435
|
+
void this.prepare(false).then(() => this.reportPainted());
|
|
436
|
+
else {
|
|
437
|
+
this.reportPainted();
|
|
438
|
+
void this.prepare(false);
|
|
439
|
+
}
|
|
419
440
|
this.wake();
|
|
420
441
|
}
|
|
442
|
+
/** Builds the shaders a look needs beside the page's own work, where the browser can build them in parallel (KHR_parallel_shader_compile), and holds the next draw until they are in, so the page keeps answering while a look is first built. The last picture stays up meanwhile. A model that comes in later builds its own. */
|
|
443
|
+
prepare(force) {
|
|
444
|
+
// without the browser's parallel compile (software drawing has none) compileAsync is no different from the first draw's own build, so nothing waits
|
|
445
|
+
if (this.disposed || !this.renderer.extensions.has("KHR_parallel_shader_compile") || (!force && this.compiled.has(this.look)))
|
|
446
|
+
return Promise.resolve();
|
|
447
|
+
this.compiled.add(this.look);
|
|
448
|
+
this.compiling++;
|
|
449
|
+
let timer = 0;
|
|
450
|
+
const late = new Promise((resolve) => {
|
|
451
|
+
timer = window.setTimeout(resolve, COMPILE_MS);
|
|
452
|
+
});
|
|
453
|
+
const built = this.renderer.compileAsync(this.scene, this.camera).then(() => undefined);
|
|
454
|
+
const wait = Promise.race([built, late])
|
|
455
|
+
.catch(() => undefined)
|
|
456
|
+
.finally(() => {
|
|
457
|
+
window.clearTimeout(timer);
|
|
458
|
+
this.compiling--;
|
|
459
|
+
if (!this.disposed)
|
|
460
|
+
this.wake();
|
|
461
|
+
});
|
|
462
|
+
this.settled = Promise.all([this.settled, wait]).then(() => undefined);
|
|
463
|
+
return wait;
|
|
464
|
+
}
|
|
421
465
|
/** Which engines burn: at full thrust every one that shows, a count for each part, or none. Nothing is made or freed by this after a part's first flame: only which of its flames are drawn changes. */
|
|
422
466
|
setEngines(lit) {
|
|
423
467
|
// a flight keeps its own, and what was asked for comes back after it
|
|
@@ -455,10 +499,11 @@ export class RocketViewer {
|
|
|
455
499
|
for (const view of this.parts) {
|
|
456
500
|
const count = counts.get(view.slot) ?? 0;
|
|
457
501
|
if (count > 0 && !view.plumes) {
|
|
458
|
-
const
|
|
502
|
+
const built = this.built.parts.find((part) => part.slot === view.slot);
|
|
503
|
+
const bells = bellsOf(built?.pieces ?? []);
|
|
459
504
|
if (bells.length === 0)
|
|
460
505
|
continue;
|
|
461
|
-
view.plumes = new PartPlumes(this.plumes, bells);
|
|
506
|
+
view.plumes = new PartPlumes(this.plumes, bells, built?.flame ?? "neutral");
|
|
462
507
|
view.holder.add(view.plumes.group);
|
|
463
508
|
}
|
|
464
509
|
if (!view.plumes)
|
|
@@ -507,7 +552,12 @@ export class RocketViewer {
|
|
|
507
552
|
part.holder.position.set(...(place?.at ?? [0, 0, 0]));
|
|
508
553
|
part.holder.rotation.set(0, 0, 0);
|
|
509
554
|
part.holder.visible = place?.shown ?? false;
|
|
555
|
+
for (const move of part.moves) {
|
|
556
|
+
move.angle = 0;
|
|
557
|
+
move.group.quaternion.identity();
|
|
558
|
+
}
|
|
510
559
|
}
|
|
560
|
+
this.canvas.dataset.turned = "";
|
|
511
561
|
this.plumes.setAir(0);
|
|
512
562
|
this.plumes.setFloor(-1e6);
|
|
513
563
|
this.reframe();
|
|
@@ -583,10 +633,20 @@ export class RocketViewer {
|
|
|
583
633
|
continue;
|
|
584
634
|
const turn = MathUtils.degToRad(pose.pitch);
|
|
585
635
|
const middle = (parts.find((one) => one.slot === part.slot)?.height ?? 0) / 2;
|
|
636
|
+
for (const move of part.moves) {
|
|
637
|
+
const angle = pose.moves[move.name] ?? 0;
|
|
638
|
+
if (angle === move.angle)
|
|
639
|
+
continue;
|
|
640
|
+
move.angle = angle;
|
|
641
|
+
move.group.quaternion.setFromAxisAngle(move.axis, MathUtils.degToRad(angle));
|
|
642
|
+
}
|
|
586
643
|
part.holder.visible = !pose.gone;
|
|
587
644
|
part.holder.rotation.set(0, 0, turn);
|
|
588
645
|
part.holder.position.set(place.at[0] + pose.dx + Math.sin(turn) * middle, place.at[1] + pose.dy + middle - Math.cos(turn) * middle, place.at[2]);
|
|
589
646
|
}
|
|
647
|
+
const turned = this.parts.flatMap((part) => part.moves.filter((move) => move.angle !== 0).map((move) => `${part.slot}:${move.group.name.split("~").slice(1).join("~")}=${Math.round(move.angle)}`)).join(" ");
|
|
648
|
+
if (turned !== this.canvas.dataset.turned)
|
|
649
|
+
this.canvas.dataset.turned = turned;
|
|
590
650
|
this.plumes.setAir(frame.air);
|
|
591
651
|
this.plumes.setFloor(frame.groundY);
|
|
592
652
|
this.ground?.set(frame.groundY, this.options.reducedMotion ? 0 : frame.smoke, performance.now() / 1000);
|
|
@@ -611,6 +671,7 @@ export class RocketViewer {
|
|
|
611
671
|
reportPainted() {
|
|
612
672
|
if (this.look !== "painted") {
|
|
613
673
|
this.canvas.dataset.painted = "";
|
|
674
|
+
this.options.onPainted("");
|
|
614
675
|
return;
|
|
615
676
|
}
|
|
616
677
|
const states = this.parts.map((part) => (part.modelFile ? part.modelState : "code"));
|
|
@@ -652,7 +713,7 @@ export class RocketViewer {
|
|
|
652
713
|
part.holder.add(gltf.scene);
|
|
653
714
|
part.modelState = "ready";
|
|
654
715
|
this.useModel(part, this.look === "painted");
|
|
655
|
-
this.reportPainted();
|
|
716
|
+
void this.prepare(true).then(() => this.reportPainted());
|
|
656
717
|
this.wake();
|
|
657
718
|
})
|
|
658
719
|
.catch(() => {
|
|
@@ -976,6 +1037,9 @@ export class RocketViewer {
|
|
|
976
1037
|
this.frame = 0;
|
|
977
1038
|
if (!this.visible || document.hidden || this.disposed)
|
|
978
1039
|
return;
|
|
1040
|
+
// shaders are being built: the next draw waits for them (prepare asks for a frame when they are in)
|
|
1041
|
+
if (this.compiling > 0)
|
|
1042
|
+
return;
|
|
979
1043
|
this.inTick = true;
|
|
980
1044
|
this.clock.update(now);
|
|
981
1045
|
const dt = Math.min(this.clock.getDelta(), 0.1);
|
package/dist/element.d.ts
CHANGED
|
@@ -185,6 +185,7 @@ export declare class UchusenLineup extends HTMLBase {
|
|
|
185
185
|
*/
|
|
186
186
|
export declare function defineUchusenRocket(): void;
|
|
187
187
|
export { ROCKETS, UchusenFlight };
|
|
188
|
+
export type { FlightWords } from "./flightbar.ts";
|
|
188
189
|
declare global {
|
|
189
190
|
interface HTMLElementTagNameMap {
|
|
190
191
|
"uchusen-rocket": UchusenRocket;
|
package/dist/element.js
CHANGED
|
@@ -503,11 +503,12 @@ export class UchusenRocket extends HTMLBase {
|
|
|
503
503
|
const from = this.driven ? turnedBy(angle, this.driven.yaw, this.driven.tilt) : (this.viewer?.currentAngle() ?? angle);
|
|
504
504
|
const label = this.getAttribute("label") ?? this.builtNow.report.label;
|
|
505
505
|
const lit = flying ? (media("(prefers-reduced-motion: reduce)") ? false : flying.burns) : this.engineLights();
|
|
506
|
-
const
|
|
506
|
+
const moves = flying ? Object.fromEntries(Object.entries(flying.phase.pose).flatMap(([slot, pose]) => (pose.moves ? [[slot, pose.moves]] : []))) : undefined;
|
|
507
|
+
const key = `${this.key}|${look}|${show}|${from.az.toFixed(2)}|${from.el.toFixed(2)}|${label}|${flying ? flying.index : ""}|${JSON.stringify(lit)}|${JSON.stringify(moves ?? {})}`;
|
|
507
508
|
let node = this.drawing.firstElementChild;
|
|
508
509
|
if (key !== this.drawn.key || !node) {
|
|
509
510
|
const template = document.createElement("template");
|
|
510
|
-
template.innerHTML = this.builtNow.svg({ look, show, view: from, engines: lit });
|
|
511
|
+
template.innerHTML = this.builtNow.svg({ look, show, view: from, engines: lit, ...(moves ? { moves } : {}) });
|
|
511
512
|
node = template.content.firstElementChild;
|
|
512
513
|
if (!node)
|
|
513
514
|
return;
|
|
@@ -548,9 +549,17 @@ export class UchusenRocket extends HTMLBase {
|
|
|
548
549
|
// a new rocket keeps the angle, unless the same change named another view
|
|
549
550
|
const from = viewName !== this.lastView || this.driven ? angle : this.viewer.currentAngle();
|
|
550
551
|
this.lastView = viewName;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
552
|
+
const viewer = this.viewer;
|
|
553
|
+
await viewer.load(this.builtNow, show, look, from);
|
|
554
|
+
if (this.viewer === viewer && this.builtNow) {
|
|
555
|
+
// a look or part chosen while the rocket loaded
|
|
556
|
+
const now = this.settings(this.builtNow);
|
|
557
|
+
if (now.look !== look)
|
|
558
|
+
viewer.setLook(now.look);
|
|
559
|
+
if (now.show !== show)
|
|
560
|
+
viewer.setShow(now.show);
|
|
561
|
+
this.lightViewer(viewer);
|
|
562
|
+
}
|
|
554
563
|
this.emit("uchusen-change", { report: this.builtNow.report });
|
|
555
564
|
}
|
|
556
565
|
}
|
|
@@ -648,7 +657,8 @@ export class UchusenRocket extends HTMLBase {
|
|
|
648
657
|
assets: assets ? `${assets.replace(/\/$/, "")}/` : defaultBase(),
|
|
649
658
|
onPainted: (state) => {
|
|
650
659
|
this.dataset.painted = state;
|
|
651
|
-
|
|
660
|
+
if (state)
|
|
661
|
+
this.emit("uchusen-painted", { state });
|
|
652
662
|
},
|
|
653
663
|
onFlight: () => this.noteFlight(!this.clock?.playing),
|
|
654
664
|
});
|
|
@@ -666,6 +676,8 @@ export class UchusenRocket extends HTMLBase {
|
|
|
666
676
|
if (generation !== this.generation || this.viewer !== made)
|
|
667
677
|
return;
|
|
668
678
|
const now = this.settings(this.builtNow ?? built);
|
|
679
|
+
if (now.look !== look)
|
|
680
|
+
made.setLook(now.look);
|
|
669
681
|
if (now.show !== show)
|
|
670
682
|
made.setShow(now.show);
|
|
671
683
|
this.lightViewer(made);
|
package/dist/flight.d.ts
CHANGED
|
@@ -93,6 +93,8 @@ export interface PoseNow {
|
|
|
93
93
|
dy: number;
|
|
94
94
|
pitch: number;
|
|
95
95
|
gone: boolean;
|
|
96
|
+
/** Degrees each moving group is turned, by name. */
|
|
97
|
+
moves: Readonly<Record<string, number>>;
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* Everything the picture needs at one place on the timeline: the phase, how each part stands, which engines burn, how far the ground has fallen, and how much smoke stands on the pad.
|
package/dist/flight.js
CHANGED
|
@@ -103,7 +103,14 @@ const poseIn = (pose, part, height) => ({
|
|
|
103
103
|
dy: pose?.rest ? -part.baseY : (pose?.dy ?? 0) * height,
|
|
104
104
|
pitch: pose?.pitch ?? 0,
|
|
105
105
|
gone: pose?.gone ?? false,
|
|
106
|
+
moves: pose?.moves ?? {},
|
|
106
107
|
});
|
|
108
|
+
const mixMoves = (from, to, t) => {
|
|
109
|
+
const out = {};
|
|
110
|
+
for (const name of new Set([...Object.keys(from), ...Object.keys(to)]))
|
|
111
|
+
out[name] = mix(from[name] ?? 0, to[name] ?? 0, t);
|
|
112
|
+
return out;
|
|
113
|
+
};
|
|
107
114
|
/**
|
|
108
115
|
* Everything the picture needs at a place on the timeline, for a vehicle `height` metres tall.
|
|
109
116
|
*
|
|
@@ -129,7 +136,7 @@ export function frameAt(flight, position, parts, height) {
|
|
|
129
136
|
for (const part of parts) {
|
|
130
137
|
const from = poseIn(phase.pose[part.slot], part, height);
|
|
131
138
|
const to = poseIn(next.pose[part.slot], part, height);
|
|
132
|
-
const now = { dx: mix(from.dx, to.dx, t), dy: mix(from.dy, to.dy, t), pitch: mix(from.pitch, to.pitch, t), gone: from.gone };
|
|
139
|
+
const now = { dx: mix(from.dx, to.dx, t), dy: mix(from.dy, to.dy, t), pitch: mix(from.pitch, to.pitch, t), gone: from.gone, moves: mixMoves(from.moves, to.moves, t) };
|
|
133
140
|
poses[part.slot] = now;
|
|
134
141
|
if (now.dx !== 0 || now.dy !== 0)
|
|
135
142
|
separated = true;
|
package/dist/flightbar.d.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
declare const HTMLBase: typeof HTMLElement;
|
|
2
|
+
/**
|
|
3
|
+
* The words the flight bar says itself, each of which a page can replace (for a language) with `flight.words = { play: "…" }`. A phase's name, what happens in it and a flight's note are the data's and stay as they are.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import type { FlightWords } from "@johnmorrisdotca/uchusen-rockets/element";
|
|
8
|
+
*
|
|
9
|
+
* const words: Partial<FlightWords> = { play: "Start", pause: "Stop" };
|
|
10
|
+
* console.log(Object.keys(words));
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export interface FlightWords {
|
|
14
|
+
play: string;
|
|
15
|
+
pause: string;
|
|
16
|
+
again: string;
|
|
17
|
+
back: string;
|
|
18
|
+
next: string;
|
|
19
|
+
leave: string;
|
|
20
|
+
range: string;
|
|
21
|
+
none: string;
|
|
22
|
+
sourced: string;
|
|
23
|
+
reduced: string;
|
|
24
|
+
from: string;
|
|
25
|
+
}
|
|
2
26
|
/**
|
|
3
27
|
* The `<uchusen-flight>` element: a play button, a slider and the phase's name for a rocket's flight. Its `for` attribute is the id of the `<uchusen-rocket>` it drives. A variant with no flight says so.
|
|
4
28
|
*
|
|
@@ -16,7 +40,13 @@ export declare class UchusenFlight extends HTMLBase {
|
|
|
16
40
|
private rocket;
|
|
17
41
|
private drawnFor;
|
|
18
42
|
private chartKey;
|
|
43
|
+
private said;
|
|
19
44
|
private parts;
|
|
45
|
+
/** The words this bar says, in the page's language. Anything left out stays English. */
|
|
46
|
+
get words(): Partial<FlightWords>;
|
|
47
|
+
set words(words: Partial<FlightWords>);
|
|
48
|
+
private word;
|
|
49
|
+
private relabel;
|
|
20
50
|
connectedCallback(): void;
|
|
21
51
|
disconnectedCallback(): void;
|
|
22
52
|
attributeChangedCallback(): void;
|
package/dist/flightbar.js
CHANGED
|
@@ -22,6 +22,19 @@ figcaption { font-size: 0.74rem; line-height: 1.25; margin-top: 0.2rem; }
|
|
|
22
22
|
a { color: inherit; }
|
|
23
23
|
`;
|
|
24
24
|
const sheet = { made: null };
|
|
25
|
+
const WORDS = {
|
|
26
|
+
play: "Play",
|
|
27
|
+
pause: "Pause",
|
|
28
|
+
again: "Play again",
|
|
29
|
+
back: "Back",
|
|
30
|
+
next: "Next",
|
|
31
|
+
leave: "Back to the rocket",
|
|
32
|
+
range: "Where along the flight",
|
|
33
|
+
none: "No flight timeline for this version yet: no page read here gives one.",
|
|
34
|
+
sourced: " The times and engine counts are sourced; how the picture moves between them is illustrative, not to scale. ",
|
|
35
|
+
reduced: "This device asks for reduced motion, so nothing plays and no flame shows: move the slider or step through, and the charts say which engines burn. ",
|
|
36
|
+
from: "From ",
|
|
37
|
+
};
|
|
25
38
|
/**
|
|
26
39
|
* The `<uchusen-flight>` element: a play button, a slider and the phase's name for a rocket's flight. Its `for` attribute is the id of the `<uchusen-rocket>` it drives. A variant with no flight says so.
|
|
27
40
|
*
|
|
@@ -39,7 +52,31 @@ export class UchusenFlight extends HTMLBase {
|
|
|
39
52
|
rocket = null;
|
|
40
53
|
drawnFor = "";
|
|
41
54
|
chartKey = "";
|
|
55
|
+
said = {};
|
|
42
56
|
parts = null;
|
|
57
|
+
/** The words this bar says, in the page's language. Anything left out stays English. */
|
|
58
|
+
get words() {
|
|
59
|
+
return this.said;
|
|
60
|
+
}
|
|
61
|
+
set words(words) {
|
|
62
|
+
this.said = { ...words };
|
|
63
|
+
this.relabel();
|
|
64
|
+
}
|
|
65
|
+
word(key) {
|
|
66
|
+
return this.said[key] ?? WORDS[key];
|
|
67
|
+
}
|
|
68
|
+
relabel() {
|
|
69
|
+
const parts = this.parts;
|
|
70
|
+
if (!parts)
|
|
71
|
+
return;
|
|
72
|
+
parts.back.textContent = this.word("back");
|
|
73
|
+
parts.next.textContent = this.word("next");
|
|
74
|
+
parts.leave.textContent = this.word("leave");
|
|
75
|
+
parts.range.setAttribute("aria-label", this.word("range"));
|
|
76
|
+
parts.none.textContent = this.word("none");
|
|
77
|
+
this.drawnFor = "";
|
|
78
|
+
this.update();
|
|
79
|
+
}
|
|
43
80
|
connectedCallback() {
|
|
44
81
|
if (!this.shadowRoot)
|
|
45
82
|
this.build();
|
|
@@ -87,22 +124,22 @@ export class UchusenFlight extends HTMLBase {
|
|
|
87
124
|
node.addEventListener("click", on);
|
|
88
125
|
return node;
|
|
89
126
|
};
|
|
90
|
-
const play = button("flight-play", "
|
|
127
|
+
const play = button("flight-play", this.word("play"), () => {
|
|
91
128
|
if (this.rocket?.flightState.playing)
|
|
92
129
|
this.rocket.pause();
|
|
93
130
|
else
|
|
94
131
|
this.rocket?.play();
|
|
95
132
|
});
|
|
96
133
|
const position = () => this.rocket?.flightState.position ?? 0;
|
|
97
|
-
const back = button("flight-back", "
|
|
98
|
-
const next = button("flight-next", "
|
|
99
|
-
const leave = button("flight-leave", "
|
|
134
|
+
const back = button("flight-back", this.word("back"), () => this.rocket?.seek(Math.ceil(position()) - 1));
|
|
135
|
+
const next = button("flight-next", this.word("next"), () => this.rocket?.seek(Math.floor(position()) + 1));
|
|
136
|
+
const leave = button("flight-leave", this.word("leave"), () => this.rocket?.endFlight());
|
|
100
137
|
const range = make("input", "flight-range");
|
|
101
138
|
range.type = "range";
|
|
102
139
|
range.min = "0";
|
|
103
140
|
range.step = "0.01";
|
|
104
141
|
range.value = "0";
|
|
105
|
-
range.setAttribute("aria-label", "
|
|
142
|
+
range.setAttribute("aria-label", this.word("range"));
|
|
106
143
|
range.addEventListener("input", () => this.rocket?.seek(Number(range.value)));
|
|
107
144
|
const row = document.createElement("div");
|
|
108
145
|
row.className = "row";
|
|
@@ -120,7 +157,7 @@ export class UchusenFlight extends HTMLBase {
|
|
|
120
157
|
const body = document.createElement("div");
|
|
121
158
|
body.setAttribute("part", "flight-body");
|
|
122
159
|
body.append(row, now, what, charts, about);
|
|
123
|
-
const none = make("p", "flight-none",
|
|
160
|
+
const none = make("p", "flight-none", this.word("none"));
|
|
124
161
|
none.className = "fine";
|
|
125
162
|
root.append(body, none);
|
|
126
163
|
this.parts = { play, back, next, leave, range, now, what, charts, about, none, body };
|
|
@@ -133,7 +170,7 @@ export class UchusenFlight extends HTMLBase {
|
|
|
133
170
|
link.href = source.url;
|
|
134
171
|
link.rel = "noopener";
|
|
135
172
|
link.textContent = `${source.publisher}: ${source.title}`;
|
|
136
|
-
this.parts.about.replaceChildren(`${flight.basis}
|
|
173
|
+
this.parts.about.replaceChildren(`${flight.basis}${this.word("sourced")}${flight.note ?? ""} ${reduced ? this.word("reduced") : ""}${this.word("from")}`, link, ".");
|
|
137
174
|
}
|
|
138
175
|
update = () => {
|
|
139
176
|
const parts = this.parts;
|
|
@@ -160,14 +197,14 @@ export class UchusenFlight extends HTMLBase {
|
|
|
160
197
|
this.dataset.flight = flight.id;
|
|
161
198
|
this.dataset.phase = state.active ? phase.id : "";
|
|
162
199
|
this.dataset.playing = String(state.playing);
|
|
163
|
-
parts.play.textContent = state.playing ? "
|
|
200
|
+
parts.play.textContent = this.word(state.playing ? "pause" : state.ended ? "again" : "play");
|
|
164
201
|
parts.play.setAttribute("aria-pressed", String(state.playing));
|
|
165
202
|
parts.back.disabled = state.position <= 0;
|
|
166
203
|
parts.next.disabled = state.position >= lastPosition(flight);
|
|
167
204
|
parts.leave.disabled = !state.active;
|
|
168
205
|
parts.range.value = String(state.position);
|
|
169
206
|
parts.range.setAttribute("aria-valuetext", `${clockWords(phase.at)}, ${phase.name}`);
|
|
170
|
-
parts.now.textContent = `${clockWords(phase.at)}, ${phase.name}${phase.altitude?.value ? `, ${phase.altitude.value}` : ""}`;
|
|
207
|
+
parts.now.textContent = `${clockWords(phase.at)}, ${phase.name}${phase.altitude?.value ? `, ${phase.altitude.value}` : ""}${phase.speed?.value ? `, ${phase.speed.value}` : ""}`;
|
|
171
208
|
parts.what.textContent = phase.what;
|
|
172
209
|
const chartKey = `${flight.id}/${index}/${state.active}`;
|
|
173
210
|
if (chartKey !== this.chartKey) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export { MAKERS } from "./data/makers.ts";
|
|
|
6
6
|
export type { MakerId } from "./data/makers.ts";
|
|
7
7
|
export { FLIGHT_LOG, flightLogOf } from "./data/flight-log.ts";
|
|
8
8
|
export type { LoggedFlight } from "./data/flight-log.ts";
|
|
9
|
+
export { moved, movingGroupsOf } from "./model/moves.ts";
|
|
10
|
+
export { PROPELLANTS, propellantOf } from "./data/propellants.ts";
|
|
11
|
+
export type { EnginePropellant, FlameKind } from "./data/propellants.ts";
|
|
9
12
|
export { SOURCES } from "./data/sources.ts";
|
|
10
13
|
export type { Source, SourceId } from "./data/sources.ts";
|
|
11
14
|
export { creditLine, MODEL_CREDITS } from "./data/credits.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export { PLANNED_ROCKETS, ROCKETS, partIdOf, SLOT_NAMES, variantOf } from "./roc
|
|
|
3
3
|
export { blank, figureWords } from "./data/types.js";
|
|
4
4
|
export { MAKERS } from "./data/makers.js";
|
|
5
5
|
export { FLIGHT_LOG, flightLogOf } from "./data/flight-log.js";
|
|
6
|
+
export { moved, movingGroupsOf } from "./model/moves.js";
|
|
7
|
+
export { PROPELLANTS, propellantOf } from "./data/propellants.js";
|
|
6
8
|
export { SOURCES } from "./data/sources.js";
|
|
7
9
|
export { creditLine, MODEL_CREDITS } from "./data/credits.js";
|
|
8
10
|
export { PART_ENTRIES, partEntry } from "./model/registry.js";
|
package/dist/model/falcon.js
CHANGED
|
@@ -56,11 +56,11 @@ function firstStage(d, withTop) {
|
|
|
56
56
|
if (d.legs)
|
|
57
57
|
for (const az of [45, 135, 225, 315]) {
|
|
58
58
|
const frame = radial(deg(az));
|
|
59
|
-
pieces.push({ kind: "plate", material: "black", frame, outline: [[r - 0.05, 0.8], [r + 0.5, 0.8], [r + 0.55, 3], [r + 0.15, 10.5], [r - 0.05, 11]], thickness: 1.2 });
|
|
59
|
+
pieces.push({ kind: "plate", material: "black", frame, outline: [[r - 0.05, 0.8], [r + 0.5, 0.8], [r + 0.55, 3], [r + 0.15, 10.5], [r - 0.05, 11]], thickness: 1.2, move: { name: "legs", pivot: pointOf(frame, r + 0.2, 0.8), axis: frame.w } });
|
|
60
60
|
}
|
|
61
61
|
if (d.fins)
|
|
62
62
|
for (const az of [45, 135, 225, 315])
|
|
63
|
-
pieces.push(...gridFin(az, tank + 0.1, r, r + 1.4, 1.5, 0.3, false));
|
|
63
|
+
pieces.push(...gridFin(az, tank + 0.1, r, r + 1.4, 1.5, 0.3, false, { name: "grid-fins", about: "tangent" }));
|
|
64
64
|
return pieces;
|
|
65
65
|
}
|
|
66
66
|
function secondStage(d) {
|