← Parties and Christmas3D assetfogon/campanas-navidenas
Campanas navideñas
Drag to turn it// «Campanas navideñas» · gpt-6-sol (medium)
// Two big golden Christmas bells hanging side by side, slightly tilted toward each other, joined at the top by a red bow with holly leaves and red berries, hanging from a short dark green post. Each bell has a dark inside and a round clapper at the bottom.
// A green hanger and the bow stay still while the two bells swing.
part("body", [24, 24, 38]);
box(22, 22, 35, 26, 27, 43, "#17683d");
box(22, 22, 40, 26, 23, 43, "#279653");
box(15, 22, 35, 33, 27, 38, "#17683d");
box(16, 22, 38, 32, 23, 38, "#38a95c");
sphere(24, 24, 43, 2, "#279653");
for (const x of [15, 33]) {
sphere(x, 24, 36, 2, "#e9ac30");
set(x, 22, 37, "#ffe17b");
}
// Plump ribbon loops with small openings.
for (const side of [-1, 1]) {
const cx = 24 + side * 6;
for (let x = cx - 6; x <= cx + 6; x++)
for (let y = 16; y <= 20; y++)
for (let z = 32; z <= 39; z++) {
const outer = ((x - cx) / 5.5) ** 2 +
((z - 35.5) / 3.6) ** 2 +
((y - 18) / 2.5) ** 2;
const hole = ((x - cx) / 2.7) ** 2 +
((z - 35.5) / 1.45) ** 2;
if (outer <= 1 && hole > 1) {
const shade = z >= 37 && y <= 18 ? "#ff6760" :
y >= 19 ? "#af202c" : "#e93538";
set(x, y, z, shade);
}
}
line(24 + side * 2, 18, 34, 24 + side * 6, 18, 29, 1.6, "#db2b35");
line(24 + side * 3, 16, 33, 24 + side * 6, 16, 30, 0, "#ff7167");
}
ellipsoid(24, 15, 35, 3, 2, 3, "#b82431");
ellipsoid(24, 14, 36, 2, 1, 2, "#f04445");
set(23, 13, 37, "#ff8580");
// Holly above the knot: pointed leaves and three berries.
for (const side of [-1, 1]) {
line(24, 17, 39, 24 + side * 5, 17, 42, 1.3, "#197d42");
line(24 + side * 2, 16, 40, 24 + side * 5, 16, 42, 0, "#50be62");
set(24 + side * 5, 17, 42, "#329b4c");
}
for (const [x, z] of [[23, 40], [25, 40], [24, 42]]) {
sphere(x, 14, z, 1.3, "#cb2632");
set(x, 13, z + 1, "#ff8276");
}
const profile = [
8, 9, 9, 8.8, 8.6, 8.4, 8.1, 7.8, 7.5, 7.2,
6.9, 6.6, 6.3, 6, 5.7, 5.4, 5.1, 4.8,
4.5, 4.1, 3.7, 3.2, 2.7, 2.2
];
for (const side of [-1, 1]) {
const left = side === -1;
const name = left ? "leftBell" : "rightBell";
const pivotX = left ? 16 : 32;
part(name, [pivotX, 24, 33]);
// The centerlines lean inward toward their suspension points.
const centerAt = z => 24 + side * (9.5 - (z - 6) * 0.105);
// Little gold suspension loop.
line(pivotX, 24, 33, pivotX, 24, 29, 1.4, "#d58b1d");
sphere(pivotX, 24, 33, 1.7, "#ffdc68");
set(pivotX, 22, 34, "#fff0a3");
for (let z = 6; z <= 29; z++) {
const rx = profile[z - 6], ry = rx * 0.84;
const cx = centerAt(z);
for (let x = Math.floor(cx - rx); x <= Math.ceil(cx + rx); x++)
for (let y = Math.floor(24 - ry); y <= Math.ceil(24 + ry); y++) {
const nx = (x - cx) / rx, ny = (y - 24) / ry;
if (nx * nx + ny * ny > 1) continue;
let color;
if (z <= 7) {
color = y < 22 ? "#ffd258" : "#b87517";
if (z === 7 && y < 20) color = "#ffe37b";
} else if (z <= 10 && y < 24 &&
Math.abs(x - cx) < rx - 2 &&
y < 24 - ry * 0.32) {
// A recessed, shadowy mouth above the thick gold lip.
color = z === 8 ? "#4b321d" : "#30271e";
} else if (z === 28 || z === 29) {
color = y < 24 ? "#ffdd6b" : "#b97519";
} else if (y > 26) {
color = rand() < 0.1 ? "#e9a525" : "#c9871b";
} else if (y < 22 && nx > -0.6 && nx < -0.15) {
color = rand() < 0.13 ? "#fff0a4" : "#ffde70";
} else {
color = rand() < 0.06 ? "#ffdf76" :
rand() < 0.08 ? "#dda02c" : "#f4ba3a";
}
set(x, y, z, color);
}
}
// Rolled lower edge, dark clapper stem, and a round dangling clapper.
const mouthX = centerAt(6);
for (let x = Math.floor(mouthX - 7); x <= Math.ceil(mouthX + 7); x++) {
const d = Math.abs(x - mouthX);
if (d < 7) set(x, 17, 7, d < 5 ? "#ffe076" : "#c17b16");
}
line(mouthX, 21, 8, mouthX, 21, 4, 0, "#64421c");
sphere(mouthX, 20, 3, 3, "#b97519");
ellipsoid(mouthX - 0.7, 18, 4, 1.5, 0.8, 1.5, "#ffda65");
set(mouthX - 1, 17, 5, "#fff0a1");
// Explicit bottom block makes each clapper touch the floor at z = 0.
set(Math.round(mouthX), 20, 0, "#9b6219");
// A tiny red stamped star on the front of each golden bell.
const emblemX = Math.round(centerAt(18));
const emblemY = 17;
set(emblemX, emblemY, 19, "#f04b42");
set(emblemX, emblemY, 18, "#ffe7a0");
set(emblemX - 1, emblemY, 18, "#d92d36");
set(emblemX + 1, emblemY, 18, "#d92d36");
set(emblemX - 1, emblemY, 17, "#d92d36");
set(emblemX + 1, emblemY, 17, "#d92d36");
set(emblemX, emblemY, 16, "#d92d36");
animate(name, {
type: "swing",
axis: "y",
speed: left ? 3 : -3,
amplitude: 0.3
});
}import { draw } from "./assets/voxel";
import { campanasNavidenas } from "./assets/campanas-navidenas";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, campanasNavidenas, 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 campanas-navidenas
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["campanas-navidenas"] })
# It lands in client/assets/campanas-navidenas.ts
# https://fogon.page/object/campanas-navidenas