← Basics3D assetfogon/taza-de-cafe
Taza de café
Drag to turn it// «Taza de café» · gpt-6-sol (medium)
// Coffee mug («Taza de café»).
const cx = 21, cy = 23;
// The handle sits behind the right side of the mug.
const handlePath = [
[34, 24], [38, 25], [42, 23], [44, 20],
[44, 15], [42, 12], [39, 10], [34, 10]
];
for (let i = 0; i < handlePath.length - 1; i++) {
const a = handlePath[i], b = handlePath[i + 1];
line(a[0], 28, a[1], b[0], 28, b[1], 2.5, "#176669");
line(a[0], 27, a[1], b[0], 27, b[1], 1.2, "#287b7a");
}
for (const [x, z] of handlePath) sphere(x, 28, z, 2.4, "#176a6d");
line(39, 26, 25, 42, 26, 23, 0, "#49908b");
line(43, 26, 21, 44, 26, 17, 0, "#34817e");
// Solid, slightly tapered ceramic body with fine vertical glaze streaks.
for (let z = 0; z <= 28; z++) {
const radius = z < 3 ? 13.2 + z * 0.32
: z < 25 ? 14.25 + z * 0.025 : 15.1;
for (let x = 5; x <= 37; x++) {
for (let y = 7; y <= 39; y++) {
const dx = x - cx, dy = y - cy;
const d = Math.sqrt(dx * dx + dy * dy);
if (d > radius) continue;
let color = "#207579";
if (dy > 5) color = "#14666b";
if (dx < -8) color = "#155f65";
if (dx > 8 && dy < 4) color = "#287d7d";
if (z < 3) color = "#155d62";
if (d > radius - 1.35 && z > 2 && z < 27) {
const rib = Math.sin(Math.atan2(dy, dx) * 49);
if (rib > 0.56) color = dy > 5 ? "#237579" : "#348c89";
if (rib < -0.63) color = "#155f65";
if (rand() < 0.055) color = "#398b88";
}
set(x, y, z, color);
}
}
}
// Brown coffee, just below the thick rolled rim.
for (let x = 7; x <= 35; x++) {
for (let y = 9; y <= 37; y++) {
const dx = x - cx, dy = y - cy;
const d = Math.sqrt(dx * dx + dy * dy);
if (d > 12.7) continue;
let color = d > 11.5 ? "#61351f" : "#844021";
if (rand() < 0.16) color = "#a45529";
if (rand() < 0.08) color = "#69331e";
set(x, y, 29, color);
}
}
// Irregular pale crema patch floating near the middle.
for (let x = 15; x <= 27; x++) {
for (let y = 18; y <= 27; y++) {
const q = ((x - 21) / 5.4) ** 2 + ((y - 22.5) / 3.7) ** 2;
if (q > 1 + (rand() - 0.5) * 0.22) continue;
set(x, y, 30, q > 0.7 ? "#e5c77e"
: rand() < 0.22 ? "#fff8cf" : "#fff0a8");
}
}
// Thick ivory lip. Its front edge is especially bright.
for (let x = 4; x <= 38; x++) {
for (let y = 6; y <= 40; y++) {
const d = Math.hypot(x - cx, y - cy);
if (d < 12.9 || d > 16.1) continue;
set(x, y, 29, y < cy ? "#d6cfb9" : "#b8b5a5");
set(x, y, 30, y < cy ? "#f4f0df" : "#e3deca");
if (d > 14.7 && y < cy + 2)
set(x, y, 28, "#e9e5d5");
if (rand() < 0.055)
set(x, y, 30, "#fff9e9");
}
}
// Small cream-colored ceramic badge on the front-right face.
for (let x = 26; x <= 33; x++) {
const front = Math.round(cy - Math.sqrt(14.8 ** 2 - (x - cx) ** 2));
for (let z = 10; z <= 20; z++) {
if ((x === 26 || x === 33) && (z === 10 || z === 20)) continue;
const edge = x === 26 || x === 33 || z === 10 || z === 20;
set(x, front - 1, z, edge ? "#b8a982" : "#eee3bd");
if (z === 20) set(x, front - 1, z, "#fff4d5");
}
}
// Dark coffee-bean mark in the inset.
for (let x = 28; x <= 31; x++) {
const front = Math.round(cy - Math.sqrt(14.8 ** 2 - (x - cx) ** 2));
for (let z = 13; z <= 18; z++) {
if ((x === 28 || x === 31) && (z === 13 || z === 18)) continue;
set(x, front - 2, z, "#57331e");
}
}
set(29, 9, 17, "#986844");
set(30, 10, 14, "#241f1b");
// Three wisps of thin, broken, warm-gray steam.
const wisps = [
[[15, 27, 31], [14, 27, 34], [16, 28, 36], [14, 28, 39], [14, 27, 43]],
[[21, 28, 32], [22, 28, 35], [20, 29, 37], [21, 29, 40]],
[[27, 27, 32], [26, 28, 35], [28, 28, 37], [27, 28, 40], [28, 29, 43]]
];
for (let w = 0; w < wisps.length; w++) {
const points = wisps[w];
for (let i = 0; i < points.length - 1; i++) {
const a = points[i], b = points[i + 1];
line(...a, ...b, 0, i % 2 ? "#d6d3c9" : "#bcbab2");
}
for (const [x, y, z] of points)
set(x, y, z, w === 1 ? "#e5e1d7" : "#c6c3b9");
}import { draw } from "./assets/voxel";
import { tazaDeCafe } from "./assets/taza-de-cafe";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, tazaDeCafe, 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 taza-de-cafe
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["taza-de-cafe"] })
# It lands in client/assets/taza-de-cafe.ts
# https://fogon.page/object/taza-de-cafe