← Basics3D assetfogon/anana
Ananá
Drag to turn it// «Ananá» · gpt-6-sol (medium)
// Pineapple («Ananá»).
const cx = 24, cy = 24;
// Rounded, slightly tapered pineapple body.
const profile = [
[0, 4], [2, 7], [5, 9.5], [9, 11],
[14, 12], [19, 11.5], [23, 10],
[26, 7], [28, 3.5]
];
function radiusAt(z) {
for (let i = 1; i < profile.length; i++) {
if (z <= profile[i][0]) {
const [za, ra] = profile[i - 1], [zb, rb] = profile[i];
return ra + (rb - ra) * (z - za) / (zb - za);
}
}
return 0;
}
function distanceToLine(v, period) {
const m = ((v % period) + period) % period;
return Math.min(m, period - m);
}
for (let z = 0; z <= 28; z++) {
const r = radiusAt(z);
for (let x = 11; x <= 37; x++) {
for (let y = 11; y <= 37; y++) {
const dx = x - cx, dy = y - cy;
const d = Math.hypot(dx, dy * 1.055);
if (d > r) continue;
let color = "#d49a29";
if (z < 5) color = "#b77b23";
else if (z > 23) color = "#dba52b";
else if (y < 20) color = "#e7af32";
else if (y > 28) color = "#bf8626";
if (rand() < 0.13) color = y < 25 ? "#f0b83b" : "#c48b26";
if (rand() < 0.07) color = "#a97521";
// Two crossing diagonal rows give the skin its diamond scales.
if (d > r - 1.65 && z > 1 && z < 27) {
const angle = Math.atan2(dy * 1.055, dx);
const around = angle * 11.8;
const a = distanceToLine(z * 1.18 + around, 6.4);
const b = distanceToLine(z * 1.18 - around, 6.4);
if (a < 0.85 || b < 0.85)
color = rand() < 0.3 ? "#a87320" : "#bb8422";
else if (a > 1.75 && b > 1.75)
color = rand() < 0.3 ? "#ffd052" : "#edb83b";
if (a < 0.8 && b < 0.8) color = "#99681d";
}
set(x, y, z, color);
}
}
}
// Little raised golden eyes in the centers of the individual scales.
for (let z = 3; z <= 25; z++) {
const r = radiusAt(z);
for (let x = 12; x <= 36; x++) {
for (let y = 12; y <= 36; y++) {
const dx = x - cx, dy = y - cy;
const d = Math.hypot(dx, dy * 1.055);
if (d < r - 1.4 || d > r + 0.1) continue;
const angle = Math.atan2(dy * 1.055, dx);
const around = angle * 11.8;
const a = distanceToLine(z * 1.18 + around - 3.2, 6.4);
const b = distanceToLine(z * 1.18 - around - 3.2, 6.4);
if (a < 0.75 && b < 0.75 && rand() < 0.72) {
const ox = x + Math.round(dx / d);
const oy = y + Math.round(dy / d);
set(ox, oy, z, y < 26 ? "#f5c343" : "#dba431");
if (rand() < 0.42) set(ox, oy, z - 1, "#ba8125");
}
}
}
}
// Dark green socket where the leaves meet the fruit.
ellipsoid(cx, cy, 28, 6, 5.5, 2.5, "#205f28");
for (let i = 0; i < 22; i++) {
const a = i * Math.PI * 2 / 22;
const rr = 6.2 + rand() * 2.4;
const x = Math.round(cx + Math.cos(a) * rr);
const y = Math.round(cy + Math.sin(a) * rr);
line(cx + Math.cos(a) * 3, cy + Math.sin(a) * 3, 28,
x, y, 28 + Math.floor(rand() * 3), 1, "#267532");
}
// Broad, pointed leaves, built from chunky stepped green blades.
function leaf(angle, length, baseZ, rise, width, shade) {
const dx = Math.cos(angle), dy = Math.sin(angle);
const px = -dy, py = dx;
const bx = cx + dx * 2, by = cy + dy * 2;
for (let i = 0; i <= length; i++) {
const t = i / length;
const x = bx + dx * i;
const y = by + dy * i;
const z = Math.round(baseZ + rise * t * t - Math.sin(Math.PI * t) * 1.5);
const w = t > 0.78 ? (t > 0.94 ? 0 : 1) : width;
for (let side = -w; side <= w; side++) {
const xx = Math.round(x + px * side), yy = Math.round(y + py * side);
set(xx, yy, z, side === -w ? "#164e24" : shade);
if (t < 0.78 && Math.abs(side) < w)
set(xx, yy, z - 1, "#174e25");
if (side === 0 && t > 0.15 && t < 0.9 && i % 3 !== 0)
set(xx, yy, z + 1, "#34863a");
}
}
}
// Long outer leaves create the characteristic spreading crown.
for (let i = 0; i < 16; i++) {
const a = i * Math.PI * 2 / 16 + 0.12;
leaf(a, 11 + Math.floor(rand() * 5), 30 + (i % 3), 2 + (i % 4),
i % 3 === 0 ? 2 : 1, i % 4 === 0 ? "#2a7933" : "#226b2d");
}
// Shorter leaves fill the upper middle.
for (let i = 0; i < 13; i++) {
const a = (i + 0.45) * Math.PI * 2 / 13;
leaf(a, 6 + Math.floor(rand() * 5), 32 + (i % 3), 5 + (i % 3),
1, i % 2 ? "#216b2c" : "#2a7a34");
}
// Upright, irregular central spears.
const spears = [
[-3, 0, 41], [-2, -3, 43], [0, -1, 45], [2, 1, 43],
[3, -3, 40], [-4, 3, 39], [1, 4, 41], [5, 2, 38],
[-1, 3, 44], [4, -1, 42], [-5, -2, 37]
];
for (const [ex, ey, top] of spears) {
const start = 31;
for (let z = start; z <= top; z++) {
const t = (z - start) / (top - start);
const x = Math.round(cx + ex * t);
const y = Math.round(cy + ey * t);
const w = t < 0.48 ? 1 : 0;
box(x - w, y - w, z, x + w, y + w, z,
y < cy ? "#237532" : "#195b29");
set(x, y - 1, z, z % 4 === 0 ? "#348e3d" : "#2c8036");
}
}import { draw } from "./assets/voxel";
import { anana } from "./assets/anana";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, anana, 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 anana
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["anana"] })
# It lands in client/assets/anana.ts
# https://fogon.page/object/anana