← FantasíaObjeto 3Dfogon/bola-de-cristal
Bola de cristal
Arrastrá para girarlo// «Bola de cristal» · gpt-6-sol (medium)
// A fortune teller's crystal ball: a big round glassy ball of pale lavender and white with a swirl of mist inside, sitting on an ornate dark bronze stand with three claw feet, on top of a small round table with a purple cloth with gold tassels.
const C = {
wood: "#382a32", woodLight: "#59404a",
bronze: "#72503d", bronzeDark: "#47382f", bronzeLight: "#b78b55",
gold: "#e8bd69", goldLight: "#ffe1a0",
purple: "#663478", purpleLight: "#9453a5", purpleDark: "#49275c",
lavender: "#cbb8ec", lilac: "#e5d8fa", white: "#f8f4ff",
cyan: "#91f3ee", violet: "#a56af1", magenta: "#ee92df"
};
const cx = 24, cy = 24;
const TAU = Math.PI * 2;
// Three splayed wooden feet and the turned central column.
for (let i = 0; i < 3; i++) {
const a = -Math.PI / 2 + i * TAU / 3;
const x = Math.cos(a), y = Math.sin(a);
line(cx, cy, 2, cx + x * 11, cy + y * 11, 1, 2, C.wood);
line(cx + x * 6, cy + y * 6, 1, cx + x * 12, cy + y * 12, 1, 1, C.woodLight);
sphere(cx + x * 11, cy + y * 11, 1, 2, C.wood);
}
cylinder(cx, cy, 1, 3, 11, C.wood);
cylinder(cx, cy, 3, 4, 2, C.bronzeDark);
cylinder(cx, cy, 9, 3, 2, C.bronzeLight);
cylinder(cx, cy, 11, 16, 3, C.wood);
cylinder(cx, cy, 13, 17, 1, C.woodLight);
// Rounded, scalloped purple tablecloth.
for (let x = -17; x <= 17; x++) for (let y = -17; y <= 17; y++) {
const d = Math.hypot(x, y);
if (d > 17.25) continue;
const a = Math.atan2(y, x);
const fold = Math.sin(a * 12);
if (d <= 16.5) {
set(cx + x, cy + y, 14,
fold > 0.55 ? C.purpleLight : rand() < 0.035 ? C.purpleDark : C.purple);
}
if (d >= 15.1) {
const hem = 8 + Math.round(1.3 * (1 + Math.cos(a * 12)));
for (let z = hem; z <= 13; z++) {
set(cx + x, cy + y, z,
fold > 0.65 ? C.purpleLight : fold < -0.6 ? C.purpleDark : C.purple);
}
if (d >= 16.1) set(cx + x, cy + y, hem, C.gold);
}
}
// Gold piping, hanging tassels, and embroidered dots.
for (let i = 0; i < 56; i++) {
const a = i * TAU / 56;
const x = cx + 16.7 * Math.cos(a), y = cy + 16.7 * Math.sin(a);
set(x, y, 13, i % 4 ? C.gold : C.goldLight);
if (i % 2 === 0) {
const bottom = 6 + (i % 6 === 0 ? 0 : 1);
line(x, y, 9, x, y, bottom, 0, C.gold);
sphere(x, y, bottom, 1, i % 6 === 0 ? C.goldLight : C.gold);
}
}
for (let i = 0; i < 24; i++) {
const a = i * TAU / 24;
set(cx + 13 * Math.cos(a), cy + 13 * Math.sin(a), 15, C.gold);
}
// Ornate bronze socket beneath the crystal.
cylinder(cx, cy, 15, 4, 3, C.bronzeDark);
cylinder(cx, cy, 16, 3, 2, C.bronzeLight);
for (let z = 17; z <= 20; z++) {
const r = z === 20 ? 8 : z === 19 ? 7 : 5;
for (let x = -r; x <= r; x++) for (let y = -r; y <= r; y++) {
const d = Math.hypot(x, y);
if (d <= r && d >= r - 2) {
set(cx + x, cy + y, z, y < 0 ? C.bronzeLight : C.bronze);
}
}
}
// The ball is one magical, color-cycling part.
part("crystal ball", [cx, cy, 32]);
const R = 13, bz = 32;
for (let x = -R; x <= R; x++) for (let y = -R; y <= R; y++) {
for (let z = -R; z <= R; z++) {
const d2 = x*x + y*y + z*z;
if (d2 > R*R) continue;
const rim = d2 > 138;
let color = z > 5 ? C.white : z < -7 ? "#a995d3" : C.lavender;
if (y < -5 && z > -5 && rand() < 0.18) color = C.lilac;
if (rim && y < -2 && (x < -5 || z > 5)) color = C.lilac;
set(cx + x, cy + y, bz + z, color);
}
}
// Paint luminous mist on the front hemisphere, following its curved surface.
function glassPixel(x, z, color) {
const dx = x - cx, dz = z - bz;
const q = dx*dx + dz*dz;
if (q > 157) return;
const front = cy - Math.sqrt(R*R - q);
set(x, front, z, color);
}
function glassDot(x, z, radius, color) {
for (let dx = -radius; dx <= radius; dx++)
for (let dz = -radius; dz <= radius; dz++)
if (dx*dx + dz*dz <= radius*radius)
glassPixel(Math.round(x + dx), Math.round(z + dz), color);
}
// Wispy, interlaced curls with bright centers.
for (let i = 0; i <= 240; i++) {
const t = i / 240;
const a = -1.2 + t * Math.PI * 4.1;
const r = 1.1 + t * 8.6;
const x = cx + r * Math.cos(a);
const z = bz + r * Math.sin(a) - 0.6;
glassDot(x, z, t < 0.7 ? 1 : 0, i % 7 < 3 ? C.white : C.cyan);
}
for (let i = 0; i <= 125; i++) {
const t = i / 125, a = 1.5 + t * Math.PI * 2.6;
const r = 2 + t * 7;
glassPixel(Math.round(cx + 2 + r * Math.cos(a)),
Math.round(bz - 2 + r * Math.sin(a)),
i % 5 ? C.violet : C.magenta);
}
for (let i = 0; i < 33; i++) {
const x = cx + Math.round((rand() - 0.5) * 18);
const z = bz + Math.round((rand() - 0.5) * 17);
glassPixel(x, z, i % 3 ? C.white : C.cyan);
}
// A bright curved edge and a storybook star-shaped reflection.
for (let a = 0; a < TAU; a += 0.045) {
glassPixel(Math.round(cx + 12.2 * Math.cos(a)),
Math.round(bz + 12.2 * Math.sin(a)),
Math.sin(a) > 0 ? C.white : C.lilac);
}
glassDot(cx - 6, bz + 7, 1, C.white);
for (let k = -3; k <= 3; k++) {
glassPixel(cx - 6 + k, bz + 7, C.white);
glassPixel(cx - 6, bz + 7 + k, C.white);
}
glassPixel(cx - 8, bz + 9, C.white);
glassPixel(cx - 4, bz + 5, C.white);
animate("crystal ball", {
type: "colors", palette: [C.lavender, C.white, C.cyan, C.violet],
pattern: "wave", glow: 0.7
});
// Bronze claws sit in front of the glass, gripping its lower edge.
part("body");
for (let i = 0; i < 3; i++) {
const a = -Math.PI / 2 + i * TAU / 3;
const ux = Math.cos(a), uy = Math.sin(a);
line(cx + ux * 3, cy + uy * 3, 17,
cx + ux * 7, cy + uy * 7, 19, 2, C.bronze);
line(cx + ux * 7, cy + uy * 7, 19,
cx + ux * 9, cy + uy * 9, 23, 1, C.bronzeLight);
sphere(cx + ux * 9, cy + uy * 9, 23, 1, C.gold);
set(cx + ux * 9, cy + uy * 9, 24, C.goldLight);
}import { draw } from "./assets/voxel";
import { bolaDeCristal } from "./assets/bola-de-cristal";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, bolaDeCristal, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: lo que mide en px su lado más largo
// angle: giro sobre el piso, en radianes
// t: segundos; si se mueve, se anima solo con eso# En una terminal, en la carpeta del juego
fogon assets add bola-de-cristal
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["bola-de-cristal"] })
# Queda en client/assets/bola-de-cristal.ts
# https://fogon.page/object/bola-de-cristal