← MedievalObjeto 3Dfogon/puente-de-piedra
Puente de piedra
Arrastrá para girarlo// «Puente de piedra» · gpt-6-sol (medium)
// A medieval stone bridge spanning along x (x 0..47): a single big semicircular arch of grey stone blocks over a strip of blue water, a cobblestone deck on top with low crenellated parapets on both sides, and a little moss and ivy on the arch.
// Water is the only animated part.
part("water", [24, 24, 0]);
for (let x = 0; x < 48; x++) {
for (let y = 5; y <= 42; y++) {
set(x, y, 0, rand() < 0.15 ? "#67b9d7" : "#347eae");
if (rand() < 0.12) set(x, y, 1, "#81c9dc");
}
}
animate("water", {
type: "colors",
palette: ["#86d2e5", "#3888bd", "#55bcd2"],
pattern: "wave",
glow: 0.1
});
part("body", [24, 24, 0]);
const stone = ["#777772", "#85847c", "#96938a", "#aaa69a"];
const dark = "#555b56";
function masonry(x, y, z) {
let shade = z > 17 ? 2 : z < 7 ? 0 : 1;
if (rand() < 0.19) shade = Math.min(3, shade + 1);
if (rand() < 0.10) shade = Math.max(0, shade - 1);
return stone[shade];
}
// Solid bridge spandrels and abutments; a single open semicircular vault.
for (let x = 2; x <= 45; x++) {
for (let y = 14; y <= 34; y++) {
for (let z = 2; z <= 22; z++) {
const dx = x - 24;
const archHeight = Math.abs(dx) < 17.5
? 3 + Math.sqrt(17.5 * 17.5 - dx * dx)
: -1;
if (z <= archHeight) continue;
let color = masonry(x, y, z);
// Sparse, staggered mortar seams on the visible faces.
if ((y === 14 || y === 34) &&
(z % 4 === 2 || (x + (Math.floor(z / 4) % 2) * 3) % 7 === 0))
color = "#6c6d68";
set(x, y, z, color);
}
}
}
// Prominent individually cut voussoirs around both faces of the arch.
for (const y of [13, 35]) {
for (let x = 4; x <= 44; x++) {
for (let z = 3; z <= 23; z++) {
const dx = x - 24, dz = z - 3;
const r = Math.sqrt(dx * dx + dz * dz);
if (r < 17.55 || r > 20.15) continue;
const angle = Math.atan2(dz, dx);
const joint = (angle / (Math.PI / 16)) % 1;
set(x, y, z,
joint < 0.075 || joint > 0.94 ? dark :
rand() < 0.27 ? "#b4afa1" : "#99978d");
}
}
}
// Keystone at the crown.
for (const y of [12, 13, 35, 36]) {
box(23, y, 20, 25, y, 22, "#c1b9a4");
set(24, y, 19, "#b2aa97");
}
// Heavy buttresses projecting from each end of the bridge.
for (const x of [3, 41]) {
for (const y of [11, 35]) {
box(x, y, 2, x + 3, y + 2, 15, "#828078");
box(x - 1, y, 3, x + 4, y + 2, 5, "#696c66");
box(x - 1, y, 15, x + 4, y + 2, 16, "#aba69a");
for (let z = 7; z <= 13; z += 4)
box(x, y - (y === 11 ? 1 : 0), z, x + 3,
y - (y === 11 ? 1 : 0), z, "#666a63");
}
}
// Continuous cobbled roadway, with a pale curb along either edge.
box(1, 15, 23, 46, 33, 23, "#6c6b66");
for (let x = 1; x <= 46; x++) {
for (let y = 16; y <= 32; y++) {
const seam = (x + (y % 2) * 2) % 5 === 0 || y % 4 === 0;
set(x, y, 24, seam ? "#777771" :
rand() < 0.22 ? "#b7b0a3" : rand() < 0.25 ? "#918d82" : "#a39e92");
}
}
for (const y of [14, 15, 33, 34])
box(1, y, 24, 46, y, 24, "#beb6a6");
// Low crenellated parapets, capped by broad square merlons.
for (const [y0, y1] of [[12, 15], [33, 36]]) {
box(2, y0, 25, 45, y1, 26, "#85847c");
box(2, y0, 27, 45, y1, 27, "#b5afa1");
for (let x = 2; x <= 44; x += 6) {
box(x, y0, 28, Math.min(x + 3, 45), y1, 29, "#96938a");
box(x, y0, 30, Math.min(x + 3, 45), y1, 30, "#bdb5a5");
for (const yy of [y0, y1])
set(x + 1, yy, 28, "#797a72");
}
}
for (const x of [1, 46]) {
box(x, 12, 25, x, 36, 27, "#8c8980");
for (const y of [12, 33]) {
box(x, y, 28, x + (x === 1 ? 1 : 0), y + 3, 30, "#aaa496");
}
}
// Clinging moss and dangling ivy on the front arch, kept clear of its center.
for (const [cx, cz] of [[9, 11], [12, 15], [15, 18], [34, 17], [38, 13], [40, 9]]) {
for (let i = 0; i < 13; i++) {
const x = cx + Math.floor(rand() * 5) - 2;
const z = cz + Math.floor(rand() * 4) - 1;
if (get(x, 13, z))
set(x, 12, z, rand() < 0.5 ? "#596e48" : "#788451");
}
}
for (const [x, top, length] of [[11, 15, 4], [15, 19, 5], [36, 16, 4], [39, 12, 3]]) {
for (let i = 0; i < length; i++) {
set(x + (i > 2 ? 1 : 0), 11, top - i,
i % 2 ? "#4f6744" : "#71804e");
if (i % 2 === 1) set(x - 1, 11, top - i, "#748450");
}
}
// A few tiny mossy stones tucked into the road and parapet.
for (let i = 0; i < 55; i++) {
const x = 3 + Math.floor(rand() * 42);
const y = 16 + Math.floor(rand() * 17);
if (rand() < 0.75) set(x, y, 24, "#797f66");
}import { draw } from "./assets/voxel";
import { puenteDePiedra } from "./assets/puente-de-piedra";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, puenteDePiedra, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: lo que mide en px su lado más largo
// angle: giro sobre el piso, en radianes
// t: segundos; si se mueve, se anima solo con eso# En una terminal, en la carpeta del juego
fogon assets add puente-de-piedra
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["puente-de-piedra"] })
# Queda en client/assets/puente-de-piedra.ts
# https://fogon.page/object/puente-de-piedra