← Parties and Christmas3D assetfogon/arbolito-de-navidad
Arbolito de Navidad
Drag to turn it// «Arbolito de Navidad» · gpt-6-sol (medium)
// A chunky Christmas tree: a cone of three stacked layers of dark and bright green branches with jagged edges, standing in a red pot wrapped with a gold ribbon. Round ornaments in red, gold and blue hang on the layers, a string of small glowing lights spirals around it, and a big five-pointed gold star sits on top.
const CX = 24, CY = 24;
const tiers = [
{ bottom: 9, top: 24, base: 16, tip: 4.5, phase: 0.2 },
{ bottom: 19, top: 32, base: 12.5, tip: 3.2, phase: 1.7 },
{ bottom: 28, top: 39, base: 8.8, tip: 0.7, phase: 3.1 }
];
function tierRadius(t, z, angle) {
const u = (z - t.bottom) / (t.top - t.bottom);
let r = t.base + (t.tip - t.base) * u;
const jag = Math.sin(angle * 11 + t.phase) * 0.55
+ Math.sin(angle * 19 - z * 0.7) * 0.24;
r += jag * (0.4 + 0.6 * (1 - u));
if (z <= t.bottom + 2) r += 0.5;
return r;
}
function treeRadius(z, angle) {
let r = 0;
for (const t of tiers)
if (z >= t.bottom && z <= t.top)
r = Math.max(r, tierRadius(t, z, angle));
return r;
}
// Chunky red pot, slightly wider at the rolled lip.
for (let z = 0; z <= 8; z++) {
const radius = z >= 7 ? 8 : z <= 1 ? 6.5 : 7.3;
for (let x = 15; x <= 33; x++)
for (let y = 15; y <= 33; y++) {
const d = Math.hypot(x - CX, y - CY);
if (d > radius) continue;
let color = z >= 7 ? "#ff4242" : "#db202d";
if (y <= 20) color = z >= 7 ? "#ff6253" : "#f33138";
if (z <= 1 || d > radius - 1.1) color = "#bb1729";
if (z === 8 && d > radius - 1.5) color = "#ff7770";
set(x, y, z, color);
}
}
// Gold ribbon encircling the pot.
for (let z = 3; z <= 4; z++)
for (let x = 15; x <= 33; x++)
for (let y = 15; y <= 33; y++) {
const d = Math.hypot(x - CX, y - CY);
if (d >= 6.35 && d <= 7.65)
set(x, y, z, z === 4 ? "#ffe16a" : "#e9a927");
}
box(23, 16, 1, 25, 17, 7, "#ffd651");
box(24, 15, 2, 24, 16, 7, "#fff0a0");
// Oversized toy-like ribbon bow on the front.
ellipsoid(20, 15, 5, 3, 1.2, 1.8, "#efad30");
ellipsoid(28, 15, 5, 3, 1.2, 1.8, "#efad30");
ellipsoid(20, 14, 5, 2, 0.8, 1, "#ffe477");
ellipsoid(28, 14, 5, 2, 0.8, 1, "#ffe477");
sphere(24, 14, 5, 1.6, "#ffdc55");
set(24, 13, 6, "#fff2ae");
// Short trunk tucked into the branches.
cylinder(CX, CY, 7, 3, 6, "#87502b");
box(22, 21, 8, 25, 22, 12, "#aa6937");
// Three overlapping, solid, scalloped tiers of evergreen branches.
for (let i = 0; i < tiers.length; i++) {
const t = tiers[i];
for (let z = t.bottom; z <= t.top; z++)
for (let x = 7; x <= 41; x++)
for (let y = 7; y <= 41; y++) {
const dx = x - CX, dy = y - CY;
const angle = Math.atan2(dy, dx);
const d = Math.hypot(dx, dy);
const r = tierRadius(t, z, angle);
if (d > r) continue;
const nearEdge = d > r - 2.3;
const groove = Math.sin(angle * 11 + t.phase) < -0.45;
let color = "#176b3b";
if (nearEdge) {
color = groove ? "#12552f" : "#21964b";
if (z > t.bottom + 2 && rand() < 0.28)
color = "#32b956";
if (z <= t.bottom + 1)
color = groove ? "#104c2e" : "#197c3c";
} else if (rand() < 0.13) {
color = "#208746";
}
if (y < CY - 5 && nearEdge && rand() < 0.13)
color = "#4bc768";
set(x, y, z, color);
}
}
// A fine golden cord spirals over the foliage.
for (let z = 11.5; z <= 37.5; z += 0.18) {
const angle = -Math.PI / 2 + (z - 11.5) * 0.79;
const r = treeRadius(Math.round(z), angle) + 0.5;
set(CX + Math.cos(angle) * r, CY + Math.sin(angle) * r,
z, "#d6b853");
}
// Glossy round ornaments, placed mostly on the viewer-facing branches.
const baubles = [
[12, -2.35, 0], [14, -1.42, 1], [15, -0.58, 2],
[17, -2.03, 2], [18, -0.95, 0], [20, -1.67, 1],
[21, -0.37, 0], [23, -2.48, 1], [24, -1.14, 2],
[26, -0.53, 1], [27, -2.04, 0], [29, -1.27, 1],
[31, -0.52, 2], [32, -2.22, 0], [34, -1.57, 2],
[36, -0.86, 0]
];
const ornamentColors = ["#ec3441", "#f5bd34", "#378bdf"];
const highlights = ["#ff9b9c", "#fff0a0", "#a5e2ff"];
for (const [z, angle, kind] of baubles) {
const r = treeRadius(z, angle) + 0.35;
const x = CX + Math.cos(angle) * r;
const y = CY + Math.sin(angle) * r;
sphere(x, y, z, 1.35, ornamentColors[kind]);
set(x - 0.5, y - 0.9, z + 0.7, highlights[kind]);
}
// Individual bulbs form a separate animated string.
part("lights", [CX, CY, 24]);
for (let z = 12; z <= 37; z += 0.78) {
const angle = -Math.PI / 2 + (z - 11.5) * 0.79;
const r = treeRadius(Math.round(z), angle) + 1.1;
const x = CX + Math.cos(angle) * r;
const y = CY + Math.sin(angle) * r;
sphere(x, y, z, 0.9, "#fff3ac");
}
animate("lights", {
type: "colors",
palette: ["#ff535e", "#ffe66e", "#60e883", "#69baff", "#fffaf0"],
pattern: "flicker",
glow: 0.8
});
// Thick, upright five-pointed star on the very top.
part("star", [CX, 21, 40]);
const vertices = [];
for (let i = 0; i < 10; i++) {
const a = Math.PI / 2 + i * Math.PI / 5;
const r = i % 2 === 0 ? 5 : 2.25;
vertices.push([CX + Math.cos(a) * r, 40 + Math.sin(a) * r]);
}
function insideStar(x, z) {
let inside = false;
for (let i = 0, j = 9; i < 10; j = i++) {
const [xi, zi] = vertices[i], [xj, zj] = vertices[j];
if ((zi > z) !== (zj > z) &&
x < (xj - xi) * (z - zi) / (zj - zi) + xi)
inside = !inside;
}
return inside;
}
for (let x = 18; x <= 30; x++)
for (let z = 34; z <= 45; z++)
if (insideStar(x, z)) {
for (let y = 19; y <= 22; y++)
set(x, y, z, y === 19 ? "#ffe779" :
y === 22 ? "#c98c23" : "#f7bd39");
}
sphere(CX, 18, 40, 1.35, "#fff0a0");
animate("star", {
type: "colors",
palette: ["#f8bb39", "#ffe47a", "#fff2ab"],
pattern: "pulse",
glow: 0.9
});import { draw } from "./assets/voxel";
import { arbolitoDeNavidad } from "./assets/arbolito-de-navidad";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, arbolitoDeNavidad, 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 arbolito-de-navidad
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["arbolito-de-navidad"] })
# It lands in client/assets/arbolito-de-navidad.ts
# https://fogon.page/object/arbolito-de-navidad