← Medieval3D assetfogon/pozo-de-los-deseos
Pozo de los deseos
Drag to turn it// «Pozo de los deseos» · gpt-6-sol (medium)
// A round medieval wishing well: a ring of grey stone blocks with a dark water surface inside with a few gold coins glinting, two wooden posts holding a small red tile roof, a wooden crank with rope and a wooden bucket hanging down.
const stone = ["#908b80", "#a39d90", "#b5ad9e", "#77756e"];
const darkStone = "#66645e";
const wood = "#493024";
const woodLight = "#715038";
const iron = "#292a2b";
const gold = "#e8bc57";
// Broad, uneven stone footing.
for (let x = 9; x <= 39; x++) for (let y = 9; y <= 39; y++) {
const r = Math.hypot(x - 24, y - 24);
if (r <= 14.2 && r > 13.6 && rand() < 0.18) continue;
if (r <= 14.2) for (let z = 0; z <= 1; z++)
set(x, y, z, rand() < 0.16 ? "#77756e" : "#88867d");
}
// Coursed, individually shaded stonework around the hollow well.
for (let z = 2; z <= 11; z++) {
for (let x = 11; x <= 37; x++) for (let y = 11; y <= 37; y++) {
const r = Math.hypot(x - 24, y - 24);
if (r < 8.5 || r > 12.7) continue;
const angle = (Math.atan2(y - 24, x - 24) + Math.PI) / (2 * Math.PI);
const course = Math.floor((z - 2) / 3);
const joint = (angle * 18 + (course % 2) * 0.5) % 1;
const seam = z === 2 || z === 5 || z === 8 || joint < 0.055;
let color = seam ? darkStone : stone[Math.floor(rand() * stone.length)];
if (!seam && rand() < 0.027 && y > 22) color = "#727b60";
set(x, y, z, color);
}
}
// Thick, pale coping stones, with darker joints between them.
for (let x = 10; x <= 38; x++) for (let y = 10; y <= 38; y++) {
const r = Math.hypot(x - 24, y - 24);
if (r < 8.3 || r > 13.25) continue;
const angle = (Math.atan2(y - 24, x - 24) + Math.PI) / (2 * Math.PI);
const joint = (angle * 16) % 1 < 0.045;
set(x, y, 12, joint ? "#77736a" : rand() < 0.4 ? "#c3b9a8" : "#aaa397");
if (r > 12.4) set(x, y, 11, "#817e75");
}
// Still, deep water and a handful of glinting coins.
for (let x = 16; x <= 32; x++) for (let y = 16; y <= 32; y++) {
if (Math.hypot(x - 24, y - 24) > 8.3) continue;
set(x, y, 9, rand() < 0.16 ? "#354c53" : "#263b43");
if (rand() < 0.07) set(x, y, 10, "#4c666a");
}
for (const [x, y] of [[19,19],[21,17],[27,18],[30,22],[17,25],[29,28],[20,29]]) {
set(x, y, 10, "#a77a32");
set(x + 1, y, 10, gold);
if (rand() < 0.5) set(x, y + 1, 10, "#f4d780");
}
// Two hefty posts, standing just outside the stone ring.
for (const x of [11, 37]) {
box(x - 1, 23, 2, x + 1, 26, 31, wood);
box(x - 1, 23, 4, x - 1, 23, 30, woodLight);
box(x - 1, 23, 14, x + 1, 26, 15, iron);
box(x - 1, 23, 25, x + 1, 26, 26, iron);
for (const z of [14, 25]) {
set(x - 1, 22, z, "#aea69a");
set(x + 1, 22, z, "#aea69a");
}
box(x - 2, 22, 29, x + 2, 27, 30, woodLight);
}
// Crossbeam, with little projecting timber ends.
box(10, 23, 30, 38, 26, 32, wood);
box(10, 23, 32, 38, 23, 32, woodLight);
for (const x of [11, 37]) {
line(x, 23, 25, x < 24 ? x + 5 : x - 5, 23, 30, 1, woodLight);
}
// Cream-plaster front gable, framed in dark timber.
for (let x = 14; x <= 34; x++) {
const roofHeight = 39 - Math.ceil(Math.abs(x - 24) * 0.48);
for (let z = 32; z < roofHeight - 1; z++) set(x, 10, z, "#e5d6b7");
}
line(14, 10, 32, 24, 10, 38, 1, wood);
line(24, 10, 38, 34, 10, 32, 1, wood);
box(14, 9, 31, 34, 10, 32, wood);
// A tiny royal-blue heraldic shield on the front gable.
box(22, 8, 33, 26, 8, 35, gold);
box(23, 7, 33, 25, 7, 35, "#244673");
set(24, 6, 35, gold);
set(24, 6, 34, "#a43c38");
set(24, 7, 32, gold);
// Solid pitched red-clay roof; staggered tile joints on both slopes.
for (let x = 6; x <= 42; x++) for (let y = 8; y <= 40; y++) {
const top = 39 - Math.ceil(Math.abs(x - 24) * 0.48);
const row = Math.floor(Math.abs(x - 24) / 3);
set(x, y, top - 2, "#542d27");
set(x, y, top - 1, "#74372c");
const seam = (y + (row % 2) * 2) % 5 === 0;
const shade = rand();
const tile = seam ? "#79372d"
: shade < 0.18 ? "#a44334"
: shade < 0.7 ? "#bb5940" : "#ce7050";
set(x, y, top, tile);
if (Math.abs(x - 24) % 3 === 2)
set(x, y, top - 1, "#d27854");
}
// Ridge tiles and dark carved fascia at both roof ends.
box(23, 7, 39, 25, 41, 39, "#8e4032");
for (const y of [8, 40]) {
line(6, y, 30, 24, y, 39, 0, wood);
line(24, y, 39, 42, y, 30, 0, wood);
}
// Wooden winding drum beneath the roof, rope descending to the bucket.
cylinder(20, 24, 29, 2, 9, woodLight, "x");
box(19, 21, 28, 20, 27, 30, iron);
box(28, 21, 28, 29, 27, 30, iron);
for (let z = 17; z <= 28; z++) {
set(24, 23, z, "#be9866");
if (z % 3 === 0) set(25, 23, z, "#e0bf85");
}
// Tapered wooden bucket: its dark, open mouth remains visible.
for (let z = 10; z <= 16; z++) {
const radius = z < 13 ? 3.2 : 4.1;
for (let x = 19; x <= 29; x++) for (let y = 19; y <= 29; y++) {
const d = Math.hypot(x - 24, y - 24);
if (d > radius) continue;
const wall = d >= radius - 1.25;
if (z === 10 || wall) {
let c = y <= 23 ? "#906644" : "#68452f";
if (x % 2 === 0 && wall) c = y <= 23 ? "#a7794f" : "#795137";
set(x, y, z, c);
}
}
}
for (let x = 19; x <= 29; x++) for (let y = 19; y <= 29; y++) {
const d = Math.hypot(x - 24, y - 24);
if (d >= 3 && d <= 4.4) {
for (const z of [11, 15]) set(x, y, z, iron);
set(x, y, 17, "#b68554");
} else if (d < 3) {
set(x, y, 16, "#302920");
}
}
for (const x of [20, 28]) {
set(x, 24, 16, "#bcb2a0");
set(x, 24, 17, iron);
}
line(20, 24, 17, 22, 24, 19, 0, iron);
line(22, 24, 19, 26, 24, 19, 0, iron);
line(26, 24, 19, 28, 24, 17, 0, iron);
set(24, 24, 19, "#c8a878");
// The exterior crank is the only moving piece: axle, bent arm and grip.
part("crank", [37, 24, 26]);
cylinder(36, 24, 26, 1, 8, woodLight, "x");
box(39, 23, 25, 40, 25, 27, iron);
line(43, 24, 26, 43, 24, 31, 1, iron);
cylinder(43, 24, 31, 1, 3, woodLight, "x");
set(45, 24, 31, "#c99a60");
animate("crank", { type: "spin", axis: "x", speed: 1 });
part("body", [24, 24, 0]);import { draw } from "./assets/voxel";
import { pozoDeLosDeseos } from "./assets/pozo-de-los-deseos";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, pozoDeLosDeseos, 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 pozo-de-los-deseos
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["pozo-de-los-deseos"] })
# It lands in client/assets/pozo-de-los-deseos.ts
# https://fogon.page/object/pozo-de-los-deseos