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.
#!/usr/bin/env node
// Build the custom (original-art-free) car, props and blob shadow into
// src/game/assets-custom/.
// pnpm assets:build
// The level is not generated here: its source is src/game/assets-custom/level.blend,
// exported to level.glb next to it with `pnpm level:export`
// (docs/level-editing.md). Everything here is authored from scratch as
// procedural meshes. Sounds are not files; src/game/synth.ts renders them at
// load time.
import { mkdirSync, readdirSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { type GlbMaterialIn, type GlbNodeIn, type GlbPrimitiveIn, writeGlb } from './files.ts';
import type { Piece } from './mesh.ts';
import { ARROW_MATERIALS, buildArrow, buildCar, buildPrize, buildStar, CAR_MATERIALS, carShadow, PRIZE_MATERIALS, type PropMaterial, STAR_MATERIALS } from './props.ts';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const out = join(root, 'src/game/assets-custom');
const srgbToLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
function linear(hex: string): [number, number, number] {
const v = parseInt(hex.slice(1), 16);
return [srgbToLinear(((v >> 16) & 255) / 255), srgbToLinear(((v >> 8) & 255) / 255), srgbToLinear((v & 255) / 255)];
}
const round = (v: number) => Math.round(v * 1000) / 1000;
function primitives(p: Piece, materialIndex: (name: string) => number): GlbPrimitiveIn[] {
return [...p.prims.values()].map((pr) => ({
material: materialIndex(pr.material),
positions: new Float32Array(pr.positions.map(round)),
normals: new Float32Array(pr.normals),
indices: new Uint32Array(pr.indices),
}));
}
/** A props GLB plus its materials.json section (the W3D shader colours the game reads). */
function propGlb(nodes: (index: (name: string) => number) => GlbNodeIn[], table: Record<string, PropMaterial>) {
const names = Object.keys(table);
const index = (name: string) => {
const i = names.indexOf(name);
if (i < 0) throw new Error(`unknown material ${name}`);
return i;
};
const materials: GlbMaterialIn[] = names.map((name) => ({ name, color: [...linear(table[name].diffuse), 1] }));
const section = Object.fromEntries(
names.map((name) => {
const m = table[name];
return [name, { ambient: linear(m.ambient ?? m.diffuse), diffuse: linear(m.diffuse), emissive: linear(m.emissive ?? '#000000'), opacity: 1 }];
}),
);
return { glb: writeGlb(nodes(index), materials), section };
}
function main(): void {
const started = Date.now();
mkdirSync(join(out, 'ui'), { recursive: true });
const write = (name: string, data: Buffer | string) => writeFileSync(join(out, name), data);
// --- car and props -------------------------------------------------------------------
const car = buildCar();
const carGlb = propGlb(
(index) => [
{
name: 'Car',
mesh: { name: 'Car', primitives: primitives(car.body, index) },
children: car.wheelPositions.map((t, i) => ({ name: `Wheel${i + 1}`, translation: t, mesh: { name: `Wheel${i + 1}`, primitives: primitives(car.wheel, index) } })),
},
],
CAR_MATERIALS,
);
write('car.glb', carGlb.glb);
const star = propGlb((index) => [{ name: 'Star', mesh: { name: 'Star', primitives: primitives(buildStar(), index) } }], STAR_MATERIALS);
write('star.glb', star.glb);
const prize = propGlb((index) => [{ name: 'prizesbox', mesh: { name: 'prizesbox', primitives: primitives(buildPrize(), index) } }], PRIZE_MATERIALS);
write('prize.glb', prize.glb);
const arrow = propGlb((index) => [{ name: 'arrow', mesh: { name: 'arrow', primitives: primitives(buildArrow(), index) } }], ARROW_MATERIALS);
write('arrow2.glb', arrow.glb);
write('ui/carshadow.png', carShadow());
write('materials.json', JSON.stringify({ holypolycrap: carGlb.section, star: star.section, prize: prize.section, arrow2: arrow.section }, null, 1) + '\n');
write(
'README.md',
'# Custom asset bundle (generated)\n\n' +
'The car, props and shadow are built by `pnpm assets:build` (tools/assets/build.ts); level.glb is\n' +
'exported from level.blend by `pnpm level:export`. Do not edit by hand.\n' +
'Everything here is original procedural work for this project, under the repository licence.\n' +
'Sounds are synthesised at load time by src/game/synth.ts.\n',
);
const files = readdirSync(out, { recursive: true }).length;
console.log(`${files} files -> ${out} in ${Date.now() - started} ms`);
}
main();
