← Basics3D assetfogon/racimo-de-uvas
Racimo de uvas
Drag to turn it// «Racimo de uvas» · gpt-6-sol (medium)
// Grape bunch («Racimo de uvas»).
// Vines and leaves sit behind the grapes.
const wood = ["#59391f", "#785028", "#946734", "#ac7b3d"];
box(23, 25, 33, 25, 27, 41, wood[1]);
box(24, 24, 37, 25, 25, 43, wood[2]);
box(25, 25, 41, 27, 27, 44, wood[1]);
box(26, 24, 43, 27, 25, 45, wood[2]);
line(24, 25, 38, 18, 24, 36, 1, wood[0]);
line(25, 26, 39, 33, 27, 41, 1, wood[1]);
// A squared, curling tendril on the left.
const olive = "#607335";
line(18, 24, 37, 14, 24, 40, 1, olive);
line(14, 24, 40, 9, 24, 39, 0, "#839342");
line(9, 24, 39, 8, 24, 36, 0, olive);
line(8, 24, 36, 11, 24, 34, 0, olive);
line(11, 24, 34, 14, 24, 36, 0, "#91a04c");
line(14, 24, 36, 13, 24, 38, 0, olive);
// Angular green leaves above and to the right of the bunch.
const leafRows = [
[45, 33, 35], [44, 31, 37], [43, 29, 38],
[42, 29, 40], [41, 31, 42], [40, 30, 43],
[39, 28, 41], [38, 28, 39], [37, 29, 37],
[36, 30, 34]
];
for (const [z, x0, x1] of leafRows) {
box(x0, 26, z, x1, 28, z, "#426b32");
box(x0 + 1, 25, z, x1 - 1, 25, z,
z > 41 ? "#5e873a" : "#54813a");
}
line(28, 24, 37, 39, 24, 41, 0, "#9baa4b");
line(32, 24, 39, 34, 24, 45, 0, "#8ca747");
box(36, 25, 42, 38, 25, 43, "#759746");
box(39, 25, 40, 41, 25, 40, "#9caf50");
box(30, 24, 37, 32, 24, 39, "#789746");
box(33, 25, 44, 34, 25, 45, "#345c31");
// Each grape has a dark rounded underside and irregular lavender highlights.
function grape(cx, cy, cz, rx = 5, ry = 4.5, rz = 4.5) {
for (let z = Math.floor(cz - rz); z <= Math.ceil(cz + rz); z++) {
for (let y = Math.floor(cy - ry); y <= Math.ceil(cy + ry); y++) {
for (let x = Math.floor(cx - rx); x <= Math.ceil(cx + rx); x++) {
const dx = (x - cx) / rx, dy = (y - cy) / ry, dz = (z - cz) / rz;
if (dx * dx + dy * dy + dz * dz > 1) continue;
const light = dz * 0.40 - dx * 0.13 - dy * 0.15;
let color = light < -0.30 ? "#54285f"
: light < -0.05 ? "#693275"
: light < 0.22 ? "#824094"
: light < 0.46 ? "#a35cb0"
: "#be81c8";
if (rand() < 0.10) {
color = light > 0.12 ? "#ae6bb9" : "#73377f";
}
// Short broken glints across the upper, viewer-facing facets.
if (dz > 0.25 && dy < -0.33 && dx > -0.65 && dx < 0.56 &&
(z + Math.floor(x / 3)) % 4 === 0 && rand() < 0.76) {
color = rand() < 0.35 ? "#d2a0d7" : "#bd7cc9";
}
set(x, y, z, color);
}
}
}
}
// Rear grapes give the bunch depth; the front rows form its tapered silhouette.
const rear = [
[19, 28, 33], [27, 29, 34], [33, 28, 31],
[14, 28, 26], [23, 29, 26], [32, 29, 25],
[17, 29, 19], [26, 30, 19], [33, 28, 18],
[21, 28, 12], [29, 28, 12], [25, 27, 7]
];
for (const p of rear) grape(...p, 5, 4.5, 4.5);
const front = [
[18, 20, 33], [27, 20, 33], [35, 21, 31],
[12, 18, 26], [22, 18, 25], [33, 19, 25],
[16, 19, 19], [26, 19, 18], [35, 20, 18],
[21, 20, 12], [30, 21, 12],
[25, 21, 4] // This bottom grape reaches the floor at z = 0.
];
for (const p of front) grape(...p, 5, 4.5, 4.5);import { draw } from "./assets/voxel";
import { racimoDeUvas } from "./assets/racimo-de-uvas";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, racimoDeUvas, 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 racimo-de-uvas
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["racimo-de-uvas"] })
# It lands in client/assets/racimo-de-uvas.ts
# https://fogon.page/object/racimo-de-uvas