← Basics3D assetfogon/tetera
Tetera
Drag to turn it// «Tetera» · gpt-6-sol (medium)
// Teapot («Tetera»).
// Porcelain teapot with cobalt-blue trim.
const ivory = "#eee9dc";
const light = "#faf7ec";
const cream = "#d9d2c2";
const shadow = "#bdb6a8";
const blue = "#204d7e";
const brightBlue = "#35699b";
const darkBlue = "#17395f";
function disk(cx, cy, z, rx, ry, color) {
for (let x = Math.ceil(cx - rx); x <= cx + rx; x++)
for (let y = Math.ceil(cy - ry); y <= cy + ry; y++)
if (((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2 <= 1)
set(x, y, z, color);
}
// The handle sits behind the rounded body. Its open center is important
// to the teapot's silhouette.
const handle = [
[12, 23, 25], [9, 23, 25], [6, 23, 23],
[4, 23, 20], [3, 23, 16], [3, 23, 12],
[5, 23, 8], [8, 23, 6], [12, 23, 6]
];
for (let i = 0; i < handle.length - 1; i++) {
line(...handle[i], ...handle[i + 1], 2.1, darkBlue);
line(handle[i][0], 21, handle[i][2],
handle[i + 1][0], 21, handle[i + 1][2], 1.25, blue);
}
line(5, 20, 19, 5, 20, 13, 0, brightBlue);
line(7, 20, 22, 11, 20, 24, 0, brightBlue);
line(6, 20, 8, 11, 20, 6, 0, brightBlue);
// Small raised foot, with a dark-blue edge below the pot.
disk(23, 24, 0, 8.5, 6.5, darkBlue);
disk(23, 24, 1, 9.2, 7.0, blue);
disk(23, 24, 2, 9.4, 7.2, brightBlue);
disk(23, 24, 3, 9.1, 6.9, cream);
disk(23, 24, 4, 9.7, 7.3, ivory);
// Full, slightly flattened porcelain belly.
const cx = 23, cy = 24, cz = 15;
for (let z = 5; z <= 25; z++) {
const taper = Math.sqrt(Math.max(0, 1 - ((z - cz) / 11.3) ** 2));
const rx = 13.5 * taper + 0.8;
const ry = 10.3 * taper + 0.5;
for (let x = Math.ceil(cx - rx); x <= cx + rx; x++) {
for (let y = Math.ceil(cy - ry); y <= cy + ry; y++) {
const d = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
if (d > 1) continue;
const angle = Math.atan2((y - cy) / ry, (x - cx) / rx);
const ribs = Math.cos(angle * 23);
let color = z >= 19 ? light : ivory;
if (y > cy + 3 || z < 9) color = cream;
if (y < cy - 3 && ribs > 0.68 && z >= 9 && z <= 23)
color = light;
if (y < cy - 3 && ribs < -0.72 && d > 0.76 && z >= 9 && z <= 23)
color = cream;
if (d > 0.89 && y > cy && rand() < 0.16) color = shadow;
set(x, y, z, color);
}
}
}
// Pronounced vertical flutes across the visible front of the porcelain.
for (let a = 3.35; a < 6.12; a += 0.235) {
for (let z = 9; z <= 23; z++) {
const taper = Math.sqrt(Math.max(0, 1 - ((z - cz) / 11.3) ** 2));
const x = Math.round(cx + (13.5 * taper + 0.6) * Math.cos(a));
const y = Math.round(cy + (10.3 * taper + 0.6) * Math.sin(a));
set(x, y, z, z > 19 ? light : ivory);
if (z > 10 && z < 22 && Math.cos(a * 23) < 0)
set(x, y + 1, z, cream);
}
}
// Layered lid: cobalt rim, broad pale top, and patterned inner ring.
disk(23, 24, 25, 12.5, 9.1, darkBlue);
disk(23, 24, 26, 12.8, 9.5, blue);
disk(23, 24, 27, 11.6, 8.4, brightBlue);
disk(23, 24, 28, 10.5, 7.4, ivory);
disk(23, 24, 29, 8.5, 5.9, light);
for (let a = 0; a < Math.PI * 2; a += 0.13) {
const r = 7.1 + (Math.sin(a * 12) > 0 ? 0.8 : 0);
set(23 + r * Math.cos(a), 24 + r * 0.7 * Math.sin(a), 30,
Math.sin(a * 12) > 0 ? cream : blue);
}
disk(23, 24, 31, 4.2, 3.2, cream);
disk(23, 24, 32, 3.1, 2.5, blue);
disk(23, 24, 33, 2.6, 2.4, darkBlue);
disk(23, 24, 34, 2.4, 2.1, blue);
disk(23, 24, 35, 2.0, 1.8, brightBlue);
set(23, 22, 35, light);
// Narrow porcelain spout curves upward to a squared blue lip.
line(34, 24, 17, 37, 24, 19, 2.6, cream);
line(37, 24, 19, 39, 24, 24, 2.3, ivory);
line(39, 24, 24, 40, 24, 31, 1.8, ivory);
line(40, 24, 31, 41, 24, 34, 1.5, cream);
line(38, 21, 21, 40, 21, 29, 0, light);
box(40, 21, 33, 44, 27, 35, blue);
box(41, 22, 35, 45, 26, 36, brightBlue);
box(42, 22, 34, 44, 24, 35, darkBlue);
box(44, 23, 33, 45, 26, 34, darkBlue);
// A hand-painted cobalt scroll on the front-right of the belly.
function front(x, z) {
const taper = Math.sqrt(Math.max(0, 1 - ((z - cz) / 11.3) ** 2));
const rx = 13.5 * taper + 0.8;
const ry = 10.3 * taper + 0.5;
const f = 1 - ((x - cx) / rx) ** 2;
return f > 0 ? Math.round(cy - ry * Math.sqrt(f)) - 1 : null;
}
function paint(x, z, color = blue) {
const y = front(x, z);
if (y !== null) {
set(x, y, z, color);
set(x, y + 1, z, color);
}
}
for (let a = 0; a < Math.PI * 2; a += 0.16) {
paint(Math.round(30 + 3.1 * Math.cos(a)),
Math.round(14 + 4.7 * Math.sin(a)), blue);
}
for (let z = 12; z <= 16; z++) paint(30, z, darkBlue);
paint(29, 17, brightBlue);
paint(31, 11, brightBlue);
paint(33, 19, blue);
paint(34, 18, blue);
paint(27, 19, blue);
paint(28, 20, brightBlue);
paint(33, 9, darkBlue);
paint(32, 8, blue);import { draw } from "./assets/voxel";
import { tetera } from "./assets/tetera";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, tetera, 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 tetera
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["tetera"] })
# It lands in client/assets/tetera.ts
# https://fogon.page/object/tetera