Repository navigation
logic generator
The core pure logic: takes a TrackConfig (three parts + gauge + height) and outputs the CubeSpec
sets for every TrackShape. Pure functions, Node-testable.
Assembly rules:
- Straight track is laid along Z on the xz plane by default, left/right rail centers at x = ±gauge/2, lifted by the height.
- A track is one full block (16 px) by default; a shorter rail part is tiled along Z to fill it (Create's rail segments are 8 px half-blocks, two per block).
- Other shapes are derived from the straight track by rotation (following Kuayue's
diag_template/ascending_template, expressed via
Cube.rotation+ origin rather than recomputed coordinates).
export interface GeneratorOptions {
length?: number; // total track length along the laying direction (px), default 16 (one block)
tieInterval?: number; // tie spacing (px)
}
export type TrackAxis = 'x' | 'z'; // the track laying direction// left center x=-g/2, right x=+g/2, lifted by heightPx, aligned to the part's lateral center (xMid).
// The part's axis-aligned rotation (e.g. [0,-90,0]) is baked first so rails become plain Z-aligned boxes.
export function placeRails(cfg: TrackConfig, opts: GeneratorOptions = {}): CubeSpec[];
// ensures the tie's long axis crosses X (perpendicular to the Z-laid rails); parts with their own
// rotation are trusted. Shared by placeTies and buildBaseParts.
export function orientTiePerpendicular(tie: PartModel): CubeSpec[];
// loops ties along Z every tieInterval, centered laterally; ties are not lifted (heightPx applies to the
// rails only — the "rail height above the bottom face")
export function placeTies(cfg: TrackConfig, opts: GeneratorOptions = {}): CubeSpec[];
export function centerOf(cubes: CubeSpec[]): Vec3; // bbox center// x_ortho / z_ortho straight track (axis defaults to 'z')
export function straightZ(cfg: TrackConfig, opts: GeneratorOptions = {}): CubeSpec[];
export function straightX(cfg: TrackConfig, opts: GeneratorOptions = {}): CubeSpec[];
export function straight(cfg: TrackConfig, axis: TrackAxis = 'z', opts: GeneratorOptions = {}): ShapeSpec;
// 45° diagonal: Z track rotated ±45° about the group center (Y axis). diag = +45° (PD), diag_2 = −45°
// (ND). A diagonal spans the block's diagonal: 3 rail segments / 3 ties (24 px) vs. the straight's 2/2 (16).
export function diagonal(cfg: TrackConfig, mirror: boolean, opts: GeneratorOptions = {}): ShapeSpec;
// ascending: Z track rotated −45° about X, pivot at the track center (the block center (8,8) on the xz
// plane in Java models) — NOT the front edge (8,0). yaw decides the facing (south=0/north=180/east=270/west=90).
// After the tilt the lower end dips below the xz plane; the track is lifted so the lowest point lands
// exactly on the plane AFTER the whole-model Y offset: offset ≥0 → lift to 0; offset <0 → lift extra
// −wholeY to push the sunken track back to the plane. `lift` shifts from/to and origin together.
export function ascending(
cfg: TrackConfig,
dir: 'south' | 'north' | 'east' | 'west',
opts: GeneratorOptions = {}
): ShapeSpec;
// portal track: portal_track remaps the track/tie faces (UV kept); portal_track_mip generates the two
// overlay cubes teleport_left / teleport_right wrapping each half of the ties (excluding the rails),
// sized as the tie bbox + wrap margin. With neither, it degrades to the plain straight track.
export function teleport(cfg: TrackConfig, axis: TrackAxis = 'z', opts: GeneratorOptions = {}): ShapeSpec;
// crossing: kind 'ortho' (Z + X straight), 'diag' (PD + ND diagonal), 'pd_zo' (PD diag + Z straight),
// 'nd_zo' (ND diag + Z straight). Naming matches the reference Kuayue assets: d1 = positive diagonal,
// d2 = negative diagonal; the zo variants are the base "diagonal + Z straight" crossings, the xo variants
// are the other base rotated 90° clockwise about the crossing center (flipping the diagonal sign:
// cross_d1_xo = PD + X from nd_zo, cross_d2_xo = ND + X from pd_zo; produced by crossXo / rotateCross90,
// where the −90° Y rotation is composed onto each cube's existing rotation so the diagonal survives).
export function cross(
cfg: TrackConfig,
kind: 'ortho' | 'diag' | 'pd_zo' | 'nd_zo',
opts: GeneratorOptions = {}
): ShapeSpec;export interface ShapeDef {
id: string;
name: string;
build: (cfg: TrackConfig, opts?: GeneratorOptions) => ShapeSpec;
}allShapes generates all 11 track shapes — only the models the blockstates actually reference; the rest
are expressed via rotation: no z_ortho (shape=zo = x_ortho rotated 90°), no ascending_north/east/west
(blockstate y rotations of ascending_south), no teleport_x (all four directions from teleport via y
rotations), and all four crossing models are generated. Each shape carries cubes and (for mesh parts)
meshes — mesh geometry is placed/rotated like the cubes (rotateMesh), so mesh-only parts also appear
in every directional shape. The curve base groups tie / segment_left / segment_right are created
separately by buildBaseParts. The whole-model Y offset is applied here uniformly (translating rotation
pivots too, lifting cubes and meshes).
export function allShapes(cfg: TrackConfig, opts: GeneratorOptions = {}): ShapeSpec[];