← AsiáticoObjeto 3Dfogon/estanque-koi
Estanque con koi
Arrastrá para girarlo// «Estanque con koi» · gpt-6-sol (medium)
// A round garden pond with a ring of grey stones, dark blue water, lily pads with one pink flower, and two koi fish (orange-and-white and red-and-white) as the moving part, swimming in a circle.
const CX = 24, CY = 24;
const TAU = Math.PI * 2;
// Low stone foundation and deep pond basin.
for (let x = 2; x <= 46; x++) {
for (let y = 2; y <= 46; y++) {
const d = Math.hypot(x - CX, y - CY);
if (d <= 20.8) {
box(x, y, 0, x, y, 1, d > 18.2 ? "#626b68" : "#354b50");
if (d < 17.7) {
set(x, y, 2, "#17364d");
set(x, y, 3, "#17415a");
set(x, y, 4, rand() < 0.12 ? "#20516a" : "#123b54");
}
}
}
}
// A chunky ring of individually shaded, uneven grey stones.
const stoneShades = ["#aeb6ad", "#bfc4b8", "#939f9d", "#c9c9bb"];
const sectors = 44;
const sectorColors = Array.from({ length: sectors }, () =>
Math.floor(rand() * stoneShades.length)
);
const sectorHeights = Array.from({ length: sectors }, () =>
rand() < 0.28 ? 6 : 5
);
for (let x = 2; x <= 46; x++) {
for (let y = 2; y <= 46; y++) {
const dx = x - CX, dy = y - CY;
const d = Math.hypot(dx, dy);
if (d < 17.3 || d > 20.9) continue;
let a = Math.atan2(dy, dx);
if (a < 0) a += TAU;
const position = a / TAU * sectors;
const index = Math.floor(position) % sectors;
const seam = position - Math.floor(position);
const top = sectorHeights[index];
const base = stoneShades[sectorColors[index]];
for (let z = 2; z <= top; z++) {
let color = base;
if (z <= 3) color = "#737e7d";
else if (seam < 0.065 || seam > 0.955) color = "#64716f";
else if (z === top && rand() < 0.13) color = "#d6d5c5";
set(x, y, z, color);
}
}
}
// A few broad, quiet glints and broken ripples on the dark water.
for (let i = 0; i < 95; i++) {
const x = 8 + Math.floor(rand() * 33);
const y = 8 + Math.floor(rand() * 33);
const d = Math.hypot(x - CX, y - CY);
if (d < 16.5 && rand() < 0.65)
set(x, y, 4, rand() < 0.5 ? "#286179" : "#326d7d");
}
function lilyPad(px, py, radius, notchAngle) {
// Dark green edge, jade-green upper face, and a clear wedge-shaped notch.
for (let dx = -3; dx <= 3; dx++) {
for (let dy = -3; dy <= 3; dy++) {
const d = Math.hypot(dx, dy);
if (d > radius) continue;
let angle = Math.atan2(dy, dx) - notchAngle;
angle = Math.atan2(Math.sin(angle), Math.cos(angle));
if (d > 0.7 && Math.abs(angle) < 0.23) continue;
const x = px + dx, y = py + dy;
set(x, y, 5, d > radius - 0.55 ? "#204c35" : "#32744c");
if (d < radius - 0.7 && rand() < 0.28)
set(x, y, 5, "#58a46a");
}
}
set(px, py, 6, "#76ad70");
}
lilyPad(15, 16, 2.7, -0.6);
lilyPad(34, 16, 2.5, 2.4);
lilyPad(14, 30, 2.5, 0.3);
lilyPad(31, 34, 2.8, -2.1);
lilyPad(23, 12, 2.2, 1.3);
// One open cherry-pink lotus on the front-left pad.
set(15, 16, 7, "#e899ad");
for (let i = 0; i < 8; i++) {
const a = i * TAU / 8;
const x = 15 + Math.round(Math.cos(a) * 1.5);
const y = 16 + Math.round(Math.sin(a) * 1.5);
set(x, y, 7, "#dc7899");
set(x, y, 8, i % 2 ? "#f5afbf" : "#f8c0c6");
}
box(15, 16, 8, 15, 16, 9, "#f3d478");
// Both koi belong to one rotating part, centered on the pond.
part("koi", [CX, CY, 5]);
function koi(cx, cy, heading, primary, light, dark) {
const hx = Math.cos(heading), hy = Math.sin(heading);
function voxel(along, across, z, color) {
set(
Math.round(cx + along * hx - across * hy),
Math.round(cy + along * hy + across * hx),
z, color
);
}
// Soft swimming shadow immediately beneath each fish.
for (let along = -4; along <= 4; along++)
for (let across = -1; across <= 1; across++)
voxel(along, across, 4, "#285065");
// Rounded white body with bold asymmetrical koi markings.
for (let along = -3; along <= 3; along++) {
const width = Math.abs(along) <= 1 ? 2 : 1;
for (let across = -width; across <= width; across++) {
const patch = along <= -1
? (across <= 0 || along === -2)
: (along === 1 && across >= 0);
const side = patch ? primary : light;
voxel(along, across, 5, across === -width ? dark : side);
voxel(along, across, 6, side);
if (Math.abs(across) < width || along === 0)
voxel(along, across, 7, patch ? primary : "#fff4d9");
}
}
// Head, little mouth, and dark eyes.
for (let across = -1; across <= 1; across++) {
voxel(4, across, 5, light);
voxel(4, across, 6, light);
}
voxel(5, 0, 6, "#efd2a4");
voxel(3, -1, 8, "#172b31");
voxel(3, 1, 8, "#172b31");
voxel(0, 0, 8, primary); // small dorsal fin
// Broad pectoral fins and a distinct forked tail.
for (const side of [-1, 1]) {
voxel(0, side * 3, 5, light);
voxel(-1, side * 3, 5, primary);
voxel(-4, side, 5, primary);
voxel(-5, side, 5, light);
voxel(-6, side * 2, 5, primary);
voxel(-5, side * 2, 6, light);
}
voxel(-4, 0, 6, primary);
voxel(-5, 0, 6, primary);
}
koi(31, 24, Math.PI / 2, "#e77729", "#fff2d7", "#bb5425");
koi(17, 24, -Math.PI / 2, "#ba3d37", "#fff0df", "#8e3036");
animate("koi", { type: "spin", axis: "z", speed: 0.55 });
part("body");import { draw } from "./assets/voxel";
import { estanqueKoi } from "./assets/estanque-koi";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, estanqueKoi, 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 estanque-koi
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["estanque-koi"] })
# Queda en client/assets/estanque-koi.ts
# https://fogon.page/object/estanque-koi