← Pirates and sea3D assetfogon/isla-con-palmera
Isla con palmera
Drag to turn it// «Isla con palmera» · gpt-6-sol (medium)
// A small round sandy island surrounded by a ring of turquoise shallow water: sandy beige mound with a tall curved palm tree with big green fronds and three brown coconuts, a few small rocks and a shell.
const C = { x: 24, y: 24 };
const dist = (x, y) => Math.hypot(x - C.x, y - C.y);
// The shallow turquoise ring.
part("water", [24, 24, 0]);
for (let x = 2; x <= 46; x++) {
for (let y = 2; y <= 46; y++) {
const r = dist(x, y);
const edge = 21.1 + 0.25 * Math.sin(x * 0.8 + y * 0.4);
if (r < 14.2 || r > edge) continue;
box(x, y, 0, x, y, 1,
rand() < 0.22 ? "#39bfc4" : rand() < 0.28 ? "#62d8d2" : "#43ccc9");
if (r > 15.5 && r < 19.8 && rand() < 0.11)
set(x, y, 2, "#a1eee1");
}
}
animate("water", {
type: "colors",
palette: ["#35bdbf", "#48d0ce", "#75e3dc", "#a3f1e4"],
pattern: "wave",
glow: 0
});
part("body");
// A low, chunky beach, rising toward its middle.
for (let x = 8; x <= 40; x++) {
for (let y = 8; y <= 40; y++) {
const r = dist(x, y);
const shore = 15.15 + 0.28 * Math.sin(x * 0.9) * Math.cos(y * 0.7);
if (r > shore) continue;
const top = r < 4.5 ? 5 : r < 8.3 ? 4 : r < 11.3 ? 3 : r < 13.5 ? 2 : 1;
for (let z = 0; z <= top; z++) {
const color = z === top
? (rand() < 0.14 ? "#f9dfa0" : rand() < 0.18 ? "#e7bd78" : "#f1cf8c")
: (z < 2 ? "#c99d60" : "#ddaf6d");
set(x, y, z, color);
}
if (rand() < 0.045) set(x, y, top + 1, "#ffe6a9");
}
}
// Broken white surf, just where the water meets the beach.
for (let a = 0; a < Math.PI * 2; a += 0.075) {
if (rand() < 0.38) continue;
const r = 15.5 + 0.32 * Math.sin(a * 13);
set(24 + Math.cos(a) * r, 24 + Math.sin(a) * r, 2,
rand() < 0.5 ? "#d9fff0" : "#a8eee5");
}
// Palm roots and a gently bending, banded trunk.
const trunk = z => {
const t = (z - 5) / 27;
return {
x: 18 + 9 * t + 0.8 * Math.sin(Math.PI * t),
y: 27 - 4 * t
};
};
for (const [dx, dy] of [[-4, 1], [-2, 4], [2, 3], [4, 0], [-3, -2]]) {
line(18, 27, 6, 18 + dx, 27 + dy, 4, 1.1, "#a26839");
line(18, 27, 7, 18 + dx * 0.65, 27 + dy * 0.65, 5, 0.7, "#d49a53");
}
for (let z = 5; z <= 32; z++) {
const p = trunk(z);
const radius = 2.35 - 0.6 * (z - 5) / 27;
sphere(p.x, p.y, z, radius,
z % 5 === 0 ? "#89532e" : z % 5 === 1 ? "#b77a3f" : "#a96d39");
// Warm sunlit bark on the front-left of the trunk.
if (z % 5 > 1)
set(p.x - 1, p.y - 1, z, z % 3 === 0 ? "#dfa65b" : "#c98b49");
}
sphere(27, 23, 32, 2.2, "#80502d");
// Three distinct coconuts beneath the leaves.
for (const [x, y, z] of [[25, 21, 31], [28, 20, 30], [29, 25, 31]]) {
sphere(x, y, z, 1.7, "#754423");
set(x - 1, y - 1, z + 1, "#ae7540");
set(x, y - 2, z, "#4c301e");
}
// Little stones on the sand.
for (const [x, y, z, r] of [
[12, 22, 3, 2], [14, 20, 3, 1.3],
[32, 33, 3, 1.8], [34, 32, 2, 1.2]
]) {
ellipsoid(x, y, z, r, r * 0.72, r * 0.65, "#827e70");
set(x - 1, y - 1, z + 1, "#b9afa0");
set(x + 1, y, z, "#666c66");
}
// A small ivory fan shell on the front beach.
ellipsoid(28, 15, 4, 3.2, 2.1, 0.8, "#fff0ce");
for (let i = -3; i <= 3; i++)
line(28, 17, 4, 28 + i, 13, 4, 0, i % 2 ? "#e6bda9" : "#f8d8b6");
line(25, 13, 4, 31, 13, 4, 0, "#fff9e4");
sphere(28, 17, 4, 0.9, "#e7ac9b");
// All fronds share the joint at the crown.
part("fronds", [27, 23, 33]);
const directions = [0, 0.61, 1.28, 1.95, 2.65, 3.35, 4.02, 4.65, 5.30, 5.84];
for (let k = 0; k < directions.length; k++) {
const a = directions[k];
const dx = Math.cos(a), dy = Math.sin(a);
const px = -dy, py = dx;
const length = 12.8 + (k % 3) * 1.65;
const at = t => ({
x: 27 + dx * length * t,
y: 23 + dy * length * t,
z: 33 + 3.6 * Math.sin(Math.PI * t) - 7.5 * t * t
});
// Thick curved central rib and broad, highlighted leaf blade.
for (let j = 0; j <= 24; j++) {
const t = j / 24, p = at(t);
const width = 1.9 * Math.sin(Math.PI * t);
for (let w = -width; w <= width; w += 0.8) {
const x = p.x + px * w, y = p.y + py * w;
const z = p.z - Math.abs(w) * 0.25;
set(x, y, z - 1, "#267e42");
set(x, y, z, w < 0 ? "#44a94e" : "#54bb54");
}
set(p.x, p.y, p.z + 1, j % 4 === 0 ? "#a4dc62" : "#80cb58");
}
// Separate pointed leaflets give the palm its unmistakable silhouette.
for (let j = 2; j <= 9; j++) {
const t = j / 11;
const p = at(t);
const reach = (3.1 + 1.2 * Math.sin(Math.PI * t)) * (1 - 0.18 * t);
for (const side of [-1, 1]) {
const tipX = p.x + px * side * reach + dx * 2.3;
const tipY = p.y + py * side * reach + dy * 2.3;
const tipZ = p.z - 1.2 - 1.4 * t;
line(p.x, p.y, p.z, tipX, tipY, tipZ, 0.65,
side === -1 ? "#56bc54" : "#32984a");
line(p.x + px * side, p.y + py * side, p.z + 1,
tipX - dx, tipY - dy, tipZ + 1, 0, "#8bd35e");
}
}
}
animate("fronds", { type: "swing", axis: "y", speed: 1.5, amplitude: 0.08 });import { draw } from "./assets/voxel";
import { islaConPalmera } from "./assets/isla-con-palmera";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, islaConPalmera, 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 isla-con-palmera
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["isla-con-palmera"] })
# It lands in client/assets/isla-con-palmera.ts
# https://fogon.page/object/isla-con-palmera