← FantasíaObjeto 3Dfogon/isla-flotante
Isla flotante
Arrastrá para girarlo// «Isla flotante» · gpt-6-sol (medium)
// A small floating island: an upside-down cone of brown earth and grey rock with dangling roots underneath, a flat top covered in bright green grass with a little tree, a few flowers and a tiny waterfall spilling off one edge.
part("island", [24, 24, 22]);
animate("island", { type: "bob", axis: "z", speed: 1, amplitude: 1.5 });
// A broad grassy cap over a tapered, uneven cone of earth and stone.
for (let z = 8; z <= 25; z++) {
const t = (z - 8) / 17;
const radius = 2 + 17 * Math.pow(t, 0.87);
for (let x = 4; x <= 44; x++) {
for (let y = 4; y <= 44; y++) {
const dx = x - 24, dy = y - 24;
const angle = Math.atan2(dy, dx);
const edge = radius + 0.65 * Math.sin(5 * angle + z * 0.38)
+ 0.45 * Math.sin(9 * angle - z * 0.27);
if (Math.hypot(dx, dy) > edge) continue;
const rocky = z < 17 || (z < 22 && Math.sin(x * 0.47 + y * 0.31) > 0.55);
const colors = rocky
? ["#565b60", "#666a69", "#777875"]
: ["#654838", "#77513b", "#896044"];
const shade = rocky ? (z < 13 ? 0 : z < 19 ? 1 : 2)
: (z < 22 ? 0 : z < 25 ? 1 : 2);
set(x, y, z, rand() < 0.13 ? colors[Math.floor(rand() * 3)] : colors[shade]);
}
}
}
// Thick, bright grass; its irregular edge hangs slightly over the dirt.
for (let x = 4; x <= 44; x++) {
for (let y = 4; y <= 44; y++) {
const a = Math.atan2(y - 24, x - 24);
const r = 18.7 + 0.55 * Math.sin(5 * a + 0.4) + 0.4 * Math.sin(9 * a);
if (Math.hypot(x - 24, y - 24) > r) continue;
box(x, y, 26, x, y, 26,
rand() < 0.12 ? "#a1e76a" : rand() < 0.25 ? "#67bd48" : "#80d653");
if (rand() < 0.08) set(x, y, 25, "#498d3c");
}
}
// Stone nubs, moss, and a few softly glowing crystals beneath the rim.
for (let i = 0; i < 25; i++) {
const a = rand() * Math.PI * 2;
const z = 12 + Math.floor(rand() * 11);
const r = 2 + 17 * Math.pow((z - 8) / 17, 0.87);
const x = Math.round(24 + Math.cos(a) * (r - 0.5));
const y = Math.round(24 + Math.sin(a) * (r - 0.5));
sphere(x, y, z, rand() < 0.7 ? 1 : 2,
rand() < 0.35 ? "#76846b" : "#787a78");
if (rand() < 0.45) set(x, y, z + 2, "#65ae68");
}
for (const [x, y, z] of [[19, 17, 17], [27, 38, 19], [37, 25, 20], [22, 29, 12]]) {
sphere(x, y, z, 1, "#9147c3");
set(x, y - 1, z, "#e16dff");
}
// Twisting roots dangle beneath the rocky point.
const roots = [
[[19, 22, 18], [17, 22, 12], [18, 20, 6], [17, 20, 2]],
[[29, 27, 18], [31, 29, 12], [30, 31, 7], [32, 32, 3]],
[[24, 19, 15], [25, 17, 10], [23, 16, 5], [24, 15, 0]],
[[26, 29, 15], [27, 31, 9], [26, 32, 5]]
];
for (const root of roots) {
for (let i = 1; i < root.length; i++) {
line(...root[i - 1], ...root[i], i === 1 ? 1 : 0, "#594638");
}
}
// Keep the lowest block exactly on z=0, even if the line's endpoint is rounded.
set(24, 15, 0, "#594638");
line(18, 20, 8, 15, 18, 5, 0, "#72513c");
line(30, 31, 7, 34, 30, 4, 0, "#72513c");
// A little tree with a crooked, sturdy trunk and a rounded leafy crown.
box(15, 23, 27, 18, 26, 34, "#684634");
box(16, 23, 29, 16, 23, 35, "#926143");
line(17, 25, 33, 15, 24, 38, 1, "#684634");
line(17, 25, 33, 21, 26, 38, 1, "#755039");
line(16, 25, 28, 12, 26, 27, 1, "#684634");
line(18, 25, 28, 21, 27, 27, 1, "#684634");
ellipsoid(16, 24, 39, 6, 5, 4, "#429c70");
ellipsoid(13, 22, 38, 4, 4, 3, "#51b875");
ellipsoid(20, 26, 39, 4, 4, 3, "#54b97c");
ellipsoid(16, 25, 41, 4, 4, 3, "#65cb80");
for (let i = 0; i < 50; i++) {
const a = rand() * Math.PI * 2;
const x = Math.round(16 + Math.cos(a) * rand() * 5);
const y = Math.round(24 + Math.sin(a) * rand() * 4);
const z = 40 + Math.floor(rand() * 4);
if (get(x, y, z)) set(x, y, z, rand() < 0.7 ? "#83df91" : "#a0eb8f");
}
for (const [x, y, z] of [[12, 21, 39], [20, 23, 40], [17, 28, 39], [14, 26, 42]]) {
sphere(x, y, z, 1, "#ec73bd");
set(x, y - 1, z, "#ffb1da");
}
// A narrow spring crosses the grass and spills over the front edge.
for (let y = 7; y <= 14; y++) {
for (let x = 29; x <= 31; x++) {
set(x, y, 27, x === 30 ? "#7be8eb" : "#39bcca");
}
}
for (let z = 13; z <= 26; z++) {
const y = z > 22 ? 5 : z > 17 ? 4 : 3;
for (let x = 29; x <= 31; x++) {
set(x, y, z, x === 30 ? "#a1f8f2" : "#31b9ce");
}
if (z % 3 === 0) set(32, y, z, "#d4ffef");
}
for (const [x, y, z] of [[29, 3, 10], [31, 4, 8], [30, 2, 6], [32, 3, 11]]) {
set(x, y, z, "#87f4ed");
}
// Flowers, mushrooms, and small stones make the grass feel inhabited.
for (const [x, y, petal] of [
[10, 21, "#ff8cc4"], [12, 32, "#f6a5d8"], [24, 12, "#ffd16c"],
[34, 17, "#ff88bb"], [37, 29, "#f8b468"], [24, 36, "#e5a2ff"],
[29, 32, "#ff8cc4"], [8, 27, "#f5bd70"]
]) {
set(x, y, 27, "#397e41");
set(x, y, 28, "#438e48");
set(x - 1, y, 29, petal);
set(x + 1, y, 29, petal);
set(x, y - 1, 29, petal);
set(x, y + 1, 29, petal);
set(x, y, 29, "#ffe998");
}
for (const [x, y] of [[21, 17], [33, 34], [13, 29]]) {
set(x, y, 27, "#efe2bd");
sphere(x, y, 29, 1, "#cf70a6");
set(x, y - 1, 29, "#ffe5ef");
}
for (const [x, y] of [[10, 24], [26, 33], [36, 23]]) {
sphere(x, y, 27, 1, "#858e80");
set(x, y, 28, "#b2bb9a");
}import { draw } from "./assets/voxel";
import { islaFlotante } from "./assets/isla-flotante";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, islaFlotante, 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 isla-flotante
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["isla-flotante"] })
# Queda en client/assets/isla-flotante.ts
# https://fogon.page/object/isla-flotante