SOURCE / PINNED RELEASE
Made of little things.
Supersonic RC Revive
- Release
- 1ba42f1ca1d6…
- Author-recorded commit
- baecad10b1cd…
- License
- LICENSE
- Author’s source reference
- nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f
Archive hash verified: 90d22b206672eba4…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// The custom car and props, built in the frames src/game/render/world.ts
// expects of car.glb, star.glb, prize.glb and arrow2.glb:
// - car: node "Car" (body mesh) with children Wheel1..Wheel4. The body is drawn
// at the rigid body × Car.visualOffset() (lift 2, turned 180° about Z,
// scale 0.17), so the mesh's front is −Y and 1 game unit is 1/0.17 mesh units.
// Wheels are placed by the physics; each mesh is centred, axle along X.
// - star: in the XZ plane (stars spin about world Z), drawn at scale 3.
// - prize: centred on its marker.
// - arrow: flat in XY, pointing +Y, drawn at scale 0.05 with no scene lights,
// so only its emissive colour shows.
// - ui/carshadow.png: the blob shadow under the car.
import { writePng } from './files.ts';
import { box, cylinder, piece, type Piece, quad, tri, transformPiece, type Vec3 } from './mesh.ts';
export interface PropMaterial {
/** sRGB hex. */
diffuse: string;
ambient?: string;
emissive?: string;
}
export const CAR_MATERIALS: Record<string, PropMaterial> = {
chassis: { diffuse: '#2a2c33' },
body: { diffuse: '#1fa3ff' },
bodyDark: { diffuse: '#0f5fa8' },
stripe: { diffuse: '#ffffff' },
canopy: { diffuse: '#14171f', emissive: '#10141c' },
wing: { diffuse: '#ffcc1a' },
bumper: { diffuse: '#3a3d46' },
light: { diffuse: '#fff6c8', emissive: '#d8cf98' },
tire: { diffuse: '#1c1c20' },
hub: { diffuse: '#ffcc1a' },
};
export const STAR_MATERIALS: Record<string, PropMaterial> = {
star: { diffuse: '#ffd23a', emissive: '#6a4a00' },
starEdge: { diffuse: '#ff9a1a', emissive: '#4a2400' },
};
export const PRIZE_MATERIALS: Record<string, PropMaterial> = {
giftBox: { diffuse: '#e8407a', emissive: '#300818' },
ribbon: { diffuse: '#ffe14a', emissive: '#403000' },
};
export const ARROW_MATERIALS: Record<string, PropMaterial> = {
arrowTop: { diffuse: '#000000', emissive: '#ffd02a' },
arrowSide: { diffuse: '#000000', emissive: '#c25a00' },
};
const S = 1 / 0.17;
export function buildCar(): { body: Piece; wheel: Piece; wheelPositions: Vec3[] } {
const body = piece('Car', 'none');
// Game-unit sketch (front = +Y), converted to mesh space at the end.
box(body, [-6.6, -21, -9], [6.6, 21, -7], 'chassis');
box(body, [-5.6, -15, -7], [5.6, 12, -2.2], 'body');
// Nose: sloping wedge from the body to the bumper.
const nose = (x: number): Vec3[] => [[x, 12, -2.2], [x, 20, -6], [x, 20, -7], [x, 12, -7]];
const L = nose(-5.6);
const R = nose(5.6);
quad(body, 'body', L[0], R[0], R[1], L[1]);
quad(body, 'bodyDark', L[1], R[1], R[2], L[2]);
quad(body, 'bodyDark', R[3], R[0], R[1], R[2]);
quad(body, 'bodyDark', L[0], L[3], L[2], L[1]);
box(body, [-1.2, -15, -2.2], [1.2, 18, -2.0], 'stripe');
// Side pods over the wheels' inner edge.
box(body, [-6.8, -6, -7], [-5.6, 7, -4], 'bodyDark');
box(body, [5.6, -6, -7], [6.8, 7, -4], 'bodyDark');
// Canopy and roll hoop.
box(body, [-3.4, -6, -2.2], [3.4, 5, 1.6], 'canopy');
box(body, [-3.6, -8, -2.2], [3.6, -6, 2.6], 'bumper');
// Rear wing on two posts.
box(body, [-3.5, -18.5, -2.2], [-2.5, -17.5, 3.5], 'bumper');
box(body, [2.5, -18.5, -2.2], [3.5, -17.5, 3.5], 'bumper');
box(body, [-8, -21, 3.5], [8, -16.5, 4.5], 'wing');
box(body, [-8.2, -21.2, 1.5], [-7.8, -16.3, 5.2], 'wing');
box(body, [7.8, -21.2, 1.5], [8.2, -16.3, 5.2], 'wing');
// Bumpers and headlights.
box(body, [-6.8, 20, -8.5], [6.8, 21.5, -5.5], 'bumper');
box(body, [-6.8, -21.5, -8.5], [6.8, -20, -5.5], 'bumper');
box(body, [-4.8, 19.9, -5.8], [-2.8, 20.2, -4.2], 'light');
box(body, [2.8, 19.9, -5.8], [4.8, 20.2, -4.2], 'light');
// To mesh space: turn 180° about Z, then scale up.
transformPiece(body, ([x, y, z]) => [-x * S, -y * S, z * S], ([x, y, z]) => [-x, -y, z]);
const wheel = piece('Wheel', 'none');
const R0 = 5 * S;
const W = 2.4 * S;
cylinder(wheel, 0, 0, R0, -W, W, { sides: 'tire', top: 'hub' }, 18);
cylinder(wheel, 0, 0, R0 * 0.55, -W - 0.4 * S, W + 0.4 * S, { sides: 'hub', top: 'hub' }, 10);
// The other cap of each cylinder (cylinder() has no bottom).
for (const [r, z] of [[R0, -W], [R0 * 0.55, -W - 0.4 * S]] as const) {
for (let i = 0; i < 18; i++) {
const a = (i / 18) * Math.PI * 2;
const b = ((i + 1) / 18) * Math.PI * 2;
tri(wheel, 'hub', [0, 0, z], [Math.cos(b) * r, Math.sin(b) * r, z], [Math.cos(a) * r, Math.sin(a) * r, z]);
}
}
// Axle along X: (x, y, z) → (z, x, y).
transformPiece(wheel, ([x, y, z]) => [z, x, y], ([x, y, z]) => [z, x, y]);
const wheelPositions: Vec3[] = [
[56, -100, -53],
[-56, -100, -53],
[-56, 100, -53],
[56, 100, -53],
];
return { body, wheel, wheelPositions };
}
export function buildStar(): Piece {
const star = piece('Star', 'none');
const outer = 13;
const inner = 5.5;
const t = 2.5;
const pts: Array<[number, number]> = [];
for (let i = 0; i < 10; i++) {
const a = Math.PI / 2 + (i / 10) * Math.PI * 2;
const r = i % 2 ? inner : outer;
pts.push([Math.cos(a) * r, Math.sin(a) * r]);
}
// Faces in the XZ plane: front (−Y) and back (+Y) fans, then the rim.
for (let i = 0; i < 10; i++) {
const [ax, az] = pts[i];
const [bx, bz] = pts[(i + 1) % 10];
tri(star, 'star', [0, -t, 0], [bx, -t * 0.4, bz], [ax, -t * 0.4, az]);
tri(star, 'star', [0, t, 0], [ax, t * 0.4, az], [bx, t * 0.4, bz]);
quad(star, 'starEdge', [ax, -t * 0.4, az], [bx, -t * 0.4, bz], [bx, t * 0.4, bz], [ax, t * 0.4, az]);
}
return star;
}
export function buildPrize(): Piece {
const p = piece('prizesbox', 'none');
const s = 24;
box(p, [-s, -s, -s], [s, s, s], 'giftBox');
box(p, [-s - 1, -5, -s - 1], [s + 1, 5, s + 1], 'ribbon');
box(p, [-5, -s - 1, -s - 1], [5, s + 1, s + 1], 'ribbon');
// A bow on top.
box(p, [-12, -4, s + 1], [-2, 4, s + 8], 'ribbon');
box(p, [2, -4, s + 1], [12, 4, s + 8], 'ribbon');
return p;
}
export function buildArrow(): Piece {
const p = piece('arrow', 'none');
const z = 6.75;
const outline: Array<[number, number]> = [[-24, -80], [24, -80], [24, 10], [60, 10], [0, 80.6], [-60, 10], [-24, 10]];
// Top face: the shaft quad and the head triangle.
quad(p, 'arrowTop', [-24, -80, z], [24, -80, z], [24, 10, z], [-24, 10, z]);
tri(p, 'arrowTop', [-60, 10, z], [60, 10, z], [0, 80.6, z]);
quad(p, 'arrowTop', [-24, 10, -z], [24, 10, -z], [24, -80, -z], [-24, -80, -z]);
tri(p, 'arrowTop', [60, 10, -z], [-60, 10, -z], [0, 80.6, -z]);
for (let i = 0; i < outline.length; i++) {
const [ax, ay] = outline[i];
const [bx, by] = outline[(i + 1) % outline.length];
quad(p, 'arrowSide', [ax, ay, -z], [bx, by, -z], [bx, by, z], [ax, ay, z]);
}
return p;
}
/** The blob shadow: a soft rounded rectangle in the alpha channel. */
export function carShadow(): Buffer {
const W = 32;
const H = 64;
const out = new Uint8Array(W * H * 4);
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
// Distance outside a rounded rectangle, in normalised units.
const dx = Math.max(0, Math.abs((x + 0.5) / W - 0.5) * 2 - 0.45) / 0.55;
const dy = Math.max(0, Math.abs((y + 0.5) / H - 0.5) * 2 - 0.7) / 0.3;
const d = Math.min(1, Math.hypot(dx, dy));
out[(y * W + x) * 4 + 3] = Math.round(255 * (1 - d * d * (3 - 2 * d)));
}
}
return writePng(W, H, out);
}
