Skip to the content

Uchusen Rockets宇宙船

Rockets drawn from sourced sizes: line drawings and painted 3D views you drop into a page, take apart and turn round, and a typed model that checks whether a rocket really goes together.

Uchusen is the Japanese word 宇宙船 for spaceship. About the name

Contents

Explanation

Architecture

How the data, the part records, the drawing, the mesh and the element fit together, and why none of them can disagree.

The package is built so that a number is typed once. A mesh, a drawing and the table beside them are made from the same sourced figures, so they cannot disagree.

The layers

LayerWhereDepends on
Datasrc/data/Nothing. Figures with sources, variants, flights, credits.
Part recordssrc/parts.ts, src/model/registry.tsData. One record for every part.
Piecessrc/model/Part records. Plain geometry from sourced sizes: lathes, boxes, rings.
Meshessrc/build/glb.tsPieces. Triangles and a .glb file.
Drawingssrc/draw/Pieces. SVG from the same pieces.
Rulessrc/fit.ts, src/layout.ts, src/framing.ts, src/looks.ts, src/flight.tsPart records. Pure functions.
The elementsrc/element.ts, src/element/All of the above, and three.js for a 3D view only.

The root and build entries import neither three.js nor the DOM. The element loads three.js only for a 3D view, so a page of drawings never fetches it.

A part is a table of numbers

A change of block is a change of numbers, for example in src/model/starship.ts. The same pieces become a mesh and a drawing, which is why they cannot disagree. A part's record holds its real size in metres, the joints it mates at, and where its shape came from.

import { PART_ENTRIES } from "@johnmorrisdotca/uchusen-rockets";

const kinds = new Map<string, number>();
for (const { record } of PART_ENTRIES) kinds.set(record.kind, (kinds.get(record.kind) ?? 0) + 1);
console.log(PART_ENTRIES.length, "parts;", Object.fromEntries(kinds));
556 parts; {
  booster: 7,
  ship: 7,
  'first-stage': 20,
  'second-stage': 10,
  fairing: 16,
  trunk: 3,
  capsule: 8,
  'test-vehicle': 1,
  spacecraft: 18,
  site: 18,
  mount: 26,
  tower: 5,
  arms: 13,
  deck: 23,
  'liquid-booster': 4,
  'third-stage': 2,
  adapter: 7,
  'solid-booster': 4,
  'service-module': 14,
  orbiter: 2,
  tank: 55,
  engine: 36,
  ring: 6,
  fin: 2,
  flap: 2,
  module: 247
}

A mesh is a pure function of its description

No clock, no unseeded random number. The same description gives the same bytes, and a test holds it. The same goes for a drawing.

import { build } from "@johnmorrisdotca/uchusen-rockets/build";

const a = build({ rocket: "falcon-9", variant: "block-5" }).glb().bytes;
const b = build({ rocket: "falcon-9", variant: "block-5" }).glb().bytes;
console.log(a.length === b.length && a.every((byte, i) => byte === b[i]));
true

A look is one table entry

LOOKS changes lines, colours and fills, never a shape. So every look works for every variant, together, apart and each part alone, with no special cases.

The element

The drawing shows first, made by code. When the element is on screen and the browser can draw WebGL, it loads three.js and takes over with a cross-fade (CROSSFADE_MS). If WebGL is missing, or the context is lost, the drawing takes over again. The element then draws only while something moves, and gives its WebGL context back after 30 seconds out of sight.

The canvas is the element's size times the screen's pixel ratio, held by pixelRatioFor to a ratio of 2, to 4096 pixels a side and to 6 million pixels in all.

import { MAX_PIXELS, MAX_RATIO, MAX_SIDE, pixelRatioFor } from "@johnmorrisdotca/uchusen-rockets";

console.log(MAX_RATIO, MAX_SIDE, MAX_PIXELS, pixelRatioFor(2560, 1440, 2));
2 4096 6000000 1.275775907699572

Nothing from outside is trusted

Every attribute is read as text and checked against a table. An unknown rocket, variant or part is refused by name, and an unknown look, view or show falls back to the default (cleanLook, cleanView, cleanShow). A new option that reaches markup has a test with a hostile value.

Adding a rocket

Add its variants to src/data/, its parts to src/model/registry.ts with pieces from plain geometry, and its row to ROCKETS. Nothing else changes: the element, the drawing, the fit check and the demo read those. See CONTRIBUTING.md for the rules a figure and a model must meet.

View sourceEdit on GitHubMade from the source of @johnmorrisdotca/uchusen-rockets 0.14.0.