← Parties and Christmas3D assetfogon/baston-de-caramelo
Bastón de caramelo
Drag to turn it// «Bastón de caramelo» · gpt-6-sol (medium)
// A giant candy cane standing upright on its straight end: a thick round stick with a hook curving at the top, striped diagonally in bright red and white, with a slight shine on one side. A small green ribbon bow tied below the hook.
const cx = 29, cy = 24, radius = 4.25;
const path = [];
let distance = 0;
function point(x, z, tx, tz) {
if (path.length) {
const p = path[path.length - 1];
distance += Math.hypot(x - p.x, z - p.z);
}
path.push({ x, z, tx, tz, s: distance });
}
// Straight stick, rounded hook, and short downward-pointing tip.
for (let z = 4; z <= 31; z += 0.4) point(cx, z, 0, 1);
for (let a = 0.045; a <= Math.PI; a += 0.045) {
point(20 + 9 * Math.cos(a), 31 + 9 * Math.sin(a),
-Math.sin(a), Math.cos(a));
}
for (let z = 30.6; z >= 27; z -= 0.4) point(11, z, 0, -1);
for (let x = 6; x <= 34; x++) {
for (let y = 19; y <= 29; y++) {
for (let z = 0; z <= 45; z++) {
let nearest, d2 = Infinity;
for (const p of path) {
const v = (x - p.x) ** 2 + (z - p.z) ** 2;
if (v < d2) { d2 = v; nearest = p; }
}
const dy = y - cy;
if (d2 + dy * dy > radius * radius) continue;
const cross = (x - nearest.x) * nearest.tz -
(z - nearest.z) * nearest.tx;
const angle = Math.atan2(dy, cross);
let stripe = (nearest.s / 8.3 + angle / (2 * Math.PI)) % 1;
if (stripe < 0) stripe++;
const red = stripe < 0.46;
let color;
if (red) {
color = dy > 1.5 ? "#d91930" : "#f3293c";
if (dy < -2 && cross < -0.8) color = "#ff6470";
} else {
color = dy > 1.5 ? "#e5e7e2" : "#fff9ed";
if (dy < -2 && cross < -0.8) color = "#ffffff";
}
set(x, y, z, color);
}
}
}
// The two dangling ribbon ends sit in front of the cane.
for (let z = 15; z <= 23; z++) {
for (const side of [-1, 1]) {
const center = 29 + side * (23 - z) * 0.76;
for (let x = Math.floor(center - 2); x <= Math.ceil(center + 2); x++) {
for (let y = 17; y <= 19; y++) {
if (Math.abs(x - center) > 1.85) continue;
// Small V-cut at each ribbon tip.
if (z === 15 && Math.abs(x - center) < 0.7) continue;
set(x, y, z, y === 17 ? "#55df72" :
x < center ? "#28b84f" : "#168c3c");
}
}
}
}
// Puffy, open bow loops.
for (const loop of [
{ x: 23.7, z: 24.7, lean: -0.12 },
{ x: 34.1, z: 24.7, lean: 0.12 }
]) {
for (let x = 18; x <= 40; x++) {
for (let z = 20; z <= 29; z++) {
const u = x - loop.x - loop.lean * (z - loop.z);
const v = z - loop.z;
const outer = (u / 5.35) ** 2 + (v / 3.45) ** 2;
const inner = (u / 2.75) ** 2 + (v / 1.55) ** 2;
if (outer > 1 || inner < 1) continue;
for (let y = 15; y <= 18; y++) {
set(x, y, z, z >= loop.z && y <= 16 ? "#64e77c" :
y === 18 || z < 23 ? "#168e3d" : "#2bc354");
}
}
}
}
// Rounded knot and a small glossy glint.
ellipsoid(29, 15, 24, 2.5, 2.1, 2.7, "#149c43");
ellipsoid(28.6, 13.7, 24.5, 1.8, 0.9, 2.0, "#43d865");
set(28, 13, 25, "#a5f5a3");import { draw } from "./assets/voxel";
import { bastonDeCaramelo } from "./assets/baston-de-caramelo";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, bastonDeCaramelo, 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 baston-de-caramelo
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["baston-de-caramelo"] })
# It lands in client/assets/baston-de-caramelo.ts
# https://fogon.page/object/baston-de-caramelo