← Basics3D assetfogon/arco-de-piedra
Arco de piedra
Drag to turn it// «Arco de piedra» · gpt-6-sol (medium)
// Stone arch («Arco de piedra»).
const dark = "#505a51";
const mortar = "#666d62";
const stone = ["#8e9387", "#999d90", "#a5a89a", "#858c80"];
const light = "#b9baaa";
const moss = ["#526c34", "#668042", "#77904a", "#405b32"];
function block(x0, y0, z0, x1, y1, z1, color) {
box(x0, y0, z0, x1, y1, z1, color);
if (z1 > z0) box(x0, y0, z1, x1, y1, z1, light);
if (y0 <= y1 && z1 > z0) box(x0, y0, z0, x1, y0, z1 - 1, stone[0]);
}
// Low, slightly irregular foundation.
box(5, 13, 0, 43, 34, 0, dark);
box(6, 12, 0, 41, 34, 0, dark);
box(6, 14, 1, 42, 33, 1, mortar);
box(7, 15, 2, 41, 32, 2, "#777d70");
for (let x = 7; x <= 41; x += 5) {
box(x, 14, 1, Math.min(x + 3, 42), 14, 1, stone[0]);
box(x, 33, 1, Math.min(x + 3, 42), 33, 1, stone[0]);
}
// Worn paving, with broad, dark joints between individual flagstones.
for (let y = 15; y <= 31; y += 5) {
const offset = ((y - 15) / 5) % 2 ? 2 : 0;
for (let x = 8 - offset; x <= 40; x += 6) {
const a = Math.max(7, x), b = Math.min(41, x + 4);
if (a > b) continue;
const c = stone[Math.floor(rand() * stone.length)];
box(a, y, 3, b, Math.min(y + 3, 32), 3, c);
if (rand() > 0.19)
box(a + 1, y + 1, 4, Math.min(b, a + 3), Math.min(y + 2, 32), 4, c);
if (rand() > 0.66) set(a, y, 4, light);
}
}
// Raised, layered feet beneath the two uprights.
for (const x of [11, 35]) {
box(x - 3, 19, 3, x + 7, 31, 3, dark);
block(x - 2, 20, 4, x + 6, 30, 4, stone[1]);
box(x - 1, 21, 5, x + 5, 29, 5, mortar);
}
// Thick masonry pillars. Each course has staggered vertical joints.
for (const left of [true, false]) {
const x0 = left ? 10 : 34;
const x1 = left ? 17 : 41;
box(x0, 21, 5, x1, 29, 25, mortar);
for (let z = 5, row = 0; z <= 25; z += 4, row++) {
const top = Math.min(z + 2, 25);
const split = row % 2 ? x0 + 3 : x0 + 4;
block(x0, 21, z, split - 1, 29, top, stone[(row + 1) % 4]);
block(split + 1, 21, z, x1, 29, top, stone[(row + 2) % 4]);
// The recessed joint stays visible on the front and outer faces.
box(split, 21, z, split, 21, top, dark);
if (row % 2 === 0) box(x0, 20, z + 1, x1, 20, z + 1, "#777e72");
}
// Projecting caps and narrow carved bands.
block(x0 - 1, 20, 22, x1 + 1, 30, 23, stone[2]);
box(x0 - 1, 20, 21, x1 + 1, 30, 21, dark);
box(x0, 20, 24, x1, 29, 24, "#777e71");
block(x0 - 1, 20, 5, x1 + 1, 30, 6, stone[1]);
}
// The arch is solid stone from its stepped inner curve to its outer crest.
function outer(x) {
return Math.floor(40 - 0.048 * (x - 25) * (x - 25));
}
function inner(x) {
return Math.floor(33 - 0.061 * (x - 25) * (x - 25));
}
for (let x = 11; x <= 39; x++) {
const lo = inner(x), hi = outer(x);
for (let y = 21; y <= 29; y++) {
for (let z = lo; z <= hi; z++) {
let c = stone[Math.floor(x / 4 + (z - lo) / 4) % 4];
if (y === 21) c = stone[Math.floor(x / 4 + (z - lo) / 4 + 1) % 4];
if (z === hi || y === 29) c = light;
if (z === lo) c = "#737c70";
// Deep joints divide the curved span into chunky voussoirs.
if ((x + 1) % 4 === 0 && z < hi) c = mortar;
if (z === lo + 4 && y === 21) c = mortar;
set(x, y, z, c);
}
}
}
// Raised stone ribs across the crown, and its conspicuous central keystone.
for (let x = 12; x <= 38; x += 4) {
const h = outer(x);
box(x, 20, h, Math.min(x + 2, 38), 30, h, stone[2]);
box(x, 21, h + 1, Math.min(x + 1, 38), 29, h + 1, light);
}
block(23, 19, 34, 27, 30, 39, stone[2]);
box(23, 19, 34, 27, 19, 38, "#929789");
box(22, 20, 39, 29, 30, 39, stone[1]);
box(22, 21, 40, 27, 28, 40, light);
// A chipped, uneven silhouette along the old upper edge.
box(15, 23, 35, 16, 27, 35, stone[2]);
box(19, 23, 39, 20, 28, 39, stone[1]);
box(30, 24, 38, 31, 29, 38, stone[2]);
set(36, 21, 32, null);
set(37, 21, 31, null);
// Small ivy and moss patches favor the shaded left face and mortar seams.
function mossPatch(x, y, z, radius) {
for (let dx = -radius; dx <= radius; dx++)
for (let dz = -radius; dz <= radius; dz++) {
if (Math.abs(dx) + Math.abs(dz) > radius + 1 || rand() < 0.28) continue;
const xx = x + dx, zz = z + dz;
if (get(xx, y + 1, zz) || get(xx, y, zz))
set(xx, y - 1, zz, moss[Math.floor(rand() * moss.length)]);
}
}
mossPatch(14, 20, 16, 3);
mossPatch(12, 20, 9, 2);
mossPatch(17, 20, 25, 3);
mossPatch(19, 20, 32, 2);
mossPatch(25, 20, 37, 1);
mossPatch(38, 20, 8, 1);
mossPatch(39, 20, 25, 1);
// Stray growth in cracks and at the foot of the arch.
for (let i = 0; i < 65; i++) {
const x = 8 + Math.floor(rand() * 34);
const y = 15 + Math.floor(rand() * 18);
if (rand() < 0.5 && get(x, y, 3))
set(x, y, 4, moss[Math.floor(rand() * moss.length)]);
}import { draw } from "./assets/voxel";
import { arcoDePiedra } from "./assets/arco-de-piedra";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, arcoDePiedra, 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 arco-de-piedra
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["arco-de-piedra"] })
# It lands in client/assets/arco-de-piedra.ts
# https://fogon.page/object/arco-de-piedra