← City3D assetfogon/colectivo-urbano
Colectivo
Drag to turn it// «Colectivo» · gpt-6-sol (medium)
// A Buenos Aires city bus (colectivo): a long boxy bus along y painted in bright fileteado-inspired colors (red and cream with a blue stripe), a big front windshield at -y with a route number sign on top, a row of side windows, a front door and a rear door, and six black wheels.
const C = {
red: "#d73539", redLight: "#ed4945", redDark: "#a92430",
cream: "#f6e7bd", creamLight: "#fff2d1", creamShade: "#dfcca1",
blue: "#2179b8", blueLight: "#50a7d2", blueDark: "#155586",
gold: "#f5bb48", glass: "#86cadd", glassDark: "#397b9c",
frame: "#253b49", tire: "#20242a", tread: "#30343a",
hub: "#bec7c6", metal: "#889599"
};
// Long, solid coach body. The front faces -y.
box(14, 4, 5, 34, 44, 20, C.cream);
box(14, 4, 5, 34, 44, 10, C.red);
box(14, 4, 19, 34, 44, 21, C.red);
box(13, 4, 6, 13, 44, 21, C.cream);
box(35, 4, 6, 35, 44, 21, C.cream);
for (const x of [13, 35]) {
box(x, 4, 6, x, 44, 10, C.red);
box(x, 4, 19, x, 44, 21, C.red);
box(x, 4, 10, x, 44, 11, C.blue);
box(x, 4, 12, x, 44, 12, C.gold);
box(x, 4, 18, x, 44, 18, C.creamShade);
box(x, 4, 21, x, 44, 21, C.redLight);
}
// Flecks in the broad painted panels.
for (let i = 0; i < 165; i++) {
const y = 5 + Math.floor(rand() * 39);
const z = 6 + Math.floor(rand() * 4);
const x = rand() < 0.5 ? 13 : 35;
if (rand() < 0.28) set(x, y, z, C.redLight);
}
box(14, 4, 22, 34, 44, 22, C.creamLight);
box(15, 5, 23, 33, 43, 23, C.creamShade);
box(17, 12, 24, 31, 17, 24, C.creamLight);
box(18, 13, 25, 30, 16, 25, C.metal);
box(18, 34, 24, 30, 38, 24, C.creamLight);
box(20, 35, 25, 28, 37, 25, C.metal);
// Side windows, with chunky posts and a light-reflecting upper edge.
function sideWindow(x, y0, y1) {
box(x, y0, 13, x, y1, 17, C.frame);
box(x + (x < 24 ? -1 : 1), y0 + 1, 14,
x + (x < 24 ? -1 : 1), y1 - 1, 17, C.glassDark);
const outer = x < 24 ? x - 1 : x + 1;
box(outer, y0 + 1, 17, outer, y1 - 1, 17, C.glass);
set(outer, y0 + 1, 16, C.blueLight);
box(outer, y0, 13, outer, y1, 13, C.creamLight);
}
for (const [a, b] of [[7, 11], [17, 21], [23, 27], [29, 33], [36, 39], [41, 43]])
sideWindow(13, a, b);
for (const [a, b] of [[16, 20], [21, 25], [31, 34], [36, 39], [41, 43]])
sideWindow(35, a, b);
// Two glazed passenger doors on the right side.
function door(y0, y1) {
box(35, y0, 6, 36, y1, 18, C.frame);
box(37, y0 + 1, 8, 37, y1 - 1, 17, C.glassDark);
const mid = Math.floor((y0 + y1) / 2);
box(38, y0 + 1, 10, 38, mid - 1, 16, C.glass);
box(38, mid + 1, 10, 38, y1 - 1, 16, C.glass);
box(38, mid, 7, 38, mid, 18, C.creamLight);
box(38, y0, 7, 38, y0, 18, C.creamLight);
box(38, y1, 7, 38, y1, 18, C.creamLight);
box(38, y0 + 1, 7, 38, y1 - 1, 7, C.gold);
set(39, mid - 1, 11, C.gold);
set(39, mid + 1, 11, C.gold);
}
door(6, 12);
door(26, 30);
// Gold, curling fileteado accents on the cream side panels.
for (const x of [12, 36]) {
for (const y of [13, 28]) {
set(x, y, 15, C.gold);
set(x, y + 1, 15, C.gold);
set(x, y + 1, 16, C.gold);
set(x, y, 17, C.blue);
set(x, y - 1, 16, C.redLight);
}
}
// Front fascia, windshield, and prominent route-number box.
box(15, 3, 6, 33, 3, 21, C.red);
box(16, 2, 12, 32, 2, 19, C.frame);
box(17, 1, 13, 23, 1, 18, C.glassDark);
box(25, 1, 13, 31, 1, 18, C.glassDark);
box(18, 1, 16, 22, 1, 18, C.glass);
box(26, 1, 16, 30, 1, 18, C.glass);
box(24, 1, 12, 24, 1, 19, C.creamLight);
box(17, 1, 12, 31, 1, 12, C.creamLight);
box(18, 0, 20, 30, 1, 24, C.frame);
box(18, 0, 24, 30, 0, 24, C.gold);
// Pixel numerals "152", five blocks tall.
const numerals = [
["010", "110", "010", "010", "111"],
["111", "100", "111", "001", "111"],
["111", "001", "111", "100", "111"]
];
for (let d = 0; d < 3; d++)
for (let row = 0; row < 5; row++)
for (let col = 0; col < 3; col++)
if (numerals[d][row][col] === "1")
set(20 + d * 4 + col, -0 + 0, 23 - row, C.creamLight);
// Recessed sign face is at y=0; lower windshield remains behind it.
box(17, 1, 10, 31, 1, 10, C.blue);
box(20, 1, 8, 28, 1, 9, C.redDark);
for (const x of [17, 29]) {
box(x, 0, 7, x + 2, 0, 8, C.creamLight);
set(x + 1, 0, 7, C.gold);
}
box(15, 0, 5, 33, 1, 5, C.metal);
box(16, 0, 6, 32, 0, 6, C.redDark);
set(15, 1, 17, C.frame);
set(33, 1, 17, C.frame);
box(14, 0, 16, 15, 2, 17, C.frame);
box(33, 0, 16, 34, 2, 17, C.frame);
// Rear window, lights, and bumper.
box(17, 45, 13, 31, 45, 18, C.frame);
box(18, 46, 14, 30, 46, 17, C.glassDark);
box(19, 46, 17, 29, 46, 17, C.glass);
box(15, 45, 6, 33, 45, 11, C.redDark);
for (const x of [16, 31]) {
box(x, 46, 7, x + 1, 46, 10, C.redLight);
box(x, 46, 8, x + 1, 46, 8, C.gold);
}
box(15, 46, 5, 33, 46, 5, C.metal);
// Open dark wheel wells, cut into both lower sides.
for (const cy of [14, 34, 41])
for (const x of [13, 14, 34, 35])
for (let y = cy - 4; y <= cy + 4; y++)
for (let z = 5; z <= 8; z++)
if ((y - cy) ** 2 + (z - 4) ** 2 <= 20)
set(x, y, z, C.frame);
// Each of the six tires is an independent spinning part.
for (const cy of [14, 34, 41]) {
for (const side of ["left", "right"]) {
const left = side === "left";
const axle = left ? 12 : 36;
const name = `wheel_${side}_${cy}`;
part(name, [axle, cy, 4]);
for (let x = axle - 2; x <= axle + 2; x++)
for (let y = cy - 4; y <= cy + 4; y++)
for (let z = 0; z <= 8; z++) {
const r2 = (y - cy) ** 2 + (z - 4) ** 2;
if (r2 <= 16)
set(x, y, z, r2 >= 11 ? C.tread : C.tire);
}
const face = left ? axle - 2 : axle + 2;
for (let y = cy - 2; y <= cy + 2; y++)
for (let z = 2; z <= 6; z++)
if ((y - cy) ** 2 + (z - 4) ** 2 <= 5)
set(face + (left ? -1 : 1), y, z, C.hub);
set(face + (left ? -2 : 2), cy, 4, C.blueDark);
for (const [dy, dz] of [[0, 2], [2, 0], [0, -2], [-2, 0]])
set(face + (left ? -1 : 1), cy + dy, 4 + dz, C.metal);
animate(name, { type: "spin", axis: "x", speed: 6 });
}
}
part("body", [24, 24, 0]);import { draw } from "./assets/voxel";
import { colectivoUrbano } from "./assets/colectivo-urbano";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, colectivoUrbano, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: its longest side, in px
// angle: turn on the floor, in radians
// t: seconds; if it moves, that animates it# In a terminal, in the game's folder
fogon assets add colectivo-urbano
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["colectivo-urbano"] })
# It lands in client/assets/colectivo-urbano.ts
# https://fogon.page/object/colectivo-urbano