← FantasíaObjeto 3Dfogon/libro-volador
Libro volador
Arrastrá para girarlo// «Libro volador» · gpt-6-sol (medium)
// A magic flying book: a thick open old book with a brown leather cover and gold corners, pages fluttering, floating over a small pile of scrolls, with a few glowing letters drifting above the pages.
// Scrolls on the floor beneath the floating book.
const parchment = ["#d8bb83", "#e7cf9a", "#f1dda9"];
const rolls = [
{ x: 10, y: 12, z: 3, length: 25, r: 3 },
{ x: 18, y: 34, z: 3, length: 20, r: 3 },
{ x: 8, y: 26, z: 4, length: 18, r: 3 }
];
for (const s of rolls) {
cylinder(s.x, s.y, s.z, s.r, s.length, "#bc9869", "x");
cylinder(s.x + 1, s.y, s.z, s.r, s.length - 2, parchment[Math.floor(rand() * 3)], "x");
// Rolled parchment visible at each end.
for (const end of [s.x, s.x + s.length - 1]) {
for (let t = 0; t < 19; t++) {
const a = t * 0.73;
const r = 0.3 + t * 0.125;
set(end, s.y + Math.round(Math.cos(a) * r),
s.z + Math.round(Math.sin(a) * r), "#94704e");
}
}
for (const bx of [s.x + 4, s.x + s.length - 5]) {
for (let y = s.y - 3; y <= s.y + 3; y++)
for (let z = Math.max(0, s.z - 3); z <= s.z + 3; z++)
if ((y - s.y) ** 2 + (z - s.z) ** 2 <= 9 &&
(y - s.y) ** 2 + (z - s.z) ** 2 >= 5)
set(bx, y, z, "#a57343");
set(bx, s.y - 3, s.z, "#e6b959");
}
}
// Little loose seal and curling ribbon.
sphere(32, 23, 1, 2, "#ad644d");
sphere(32, 22, 2, 1, "#e5ac53");
line(31, 21, 1, 28, 18, 0, 0, "#a34865");
line(33, 21, 1, 36, 18, 0, 0, "#a34865");
// The entire book floats together.
part("book", [24, 23, 16]);
animate("book", { type: "bob", axis: "z", speed: 2, amplitude: 1.5 });
function slope(x) {
return 12 + Math.round(Math.abs(x - 24) * 0.32);
}
// Thick, upward-tilted leather covers and layered page edges.
for (let x = 5; x <= 43; x++) {
if (x === 24) continue;
const outer = x <= 7 || x >= 41;
for (let y = 11; y <= 35; y++) {
if (outer && (y <= 12 || y >= 34)) continue; // softened corners
const z = slope(x);
const rim = outer || y <= 12 || y >= 34;
set(x, y, z, (x + y) % 7 === 0 ? "#815439" : "#69432f");
set(x, y, z + 1, rim ? "#9b693d" : "#b18a5b");
if (rim) {
for (let h = 2; h <= 5; h++)
set(x, y, z + h, h === 5 ? "#bc8950" : "#795037");
} else {
for (let h = 2; h <= 4; h++)
set(x, y, z + h, (h === 3 && y % 3 === 0) ? "#b8a077" : "#e0c799");
set(x, y, z + 5, rand() < 0.1 ? "#f8e7bd" : "#ecdbaf");
}
}
}
// Raised spine and its warm brass tooling.
box(23, 11, 12, 25, 35, 17, "#60402f");
box(24, 12, 16, 24, 34, 18, "#ae7946");
for (const y of [14, 19, 25, 31]) box(23, y, 17, 25, y + 1, 18, "#d8aa5b");
for (const y of [16, 29]) set(24, y, 19, "#f6d578");
// Gold corner guards on all four outer corners.
for (const x of [6, 42]) {
for (const y of [14, 32]) {
const z = slope(x) + 5;
box(x - 1, y - 2, z, x + 1, y - 2, z + 1, "#b88642");
box(x - 1, y - 2, z, x - 1, y + 1, z + 1, "#e3b65d");
set(x, y - 1, z + 2, "#ffdc7e");
}
}
// Printed lines on the two open pages.
for (const side of [-1, 1]) {
for (const y of [16, 19, 22, 25, 28, 31]) {
for (let d = 5; d <= 15; d++) {
if ((d + y) % 9 === 0 || (y === 31 && d > 10)) continue;
const x = 24 + side * d;
set(x, y, slope(x) + 6, rand() < 0.13 ? "#a26b80" : "#75605b");
}
}
}
// An illuminated symbol painted across the right-hand page.
for (let i = 0; i < 12; i++) {
const a = i * Math.PI / 6;
const x = Math.round(34 + 3 * Math.cos(a));
const y = Math.round(23 + 3 * Math.sin(a));
set(x, y, slope(x) + 6, i % 2 ? "#aa65bc" : "#64d4c7");
}
set(34, 23, slope(34) + 6, "#ffdf85");
for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]])
set(34 + dx, 23 + dy, slope(34 + dx) + 6, "#e69bd2");
// A few pages lift and flutter above the back of the book.
for (const [x0, x1, y0, rise] of [
[9, 20, 29, 3], [28, 39, 31, 4], [13, 21, 33, 5]
]) {
for (let x = x0; x <= x1; x++) {
for (let y = y0; y <= y0 + 2; y++) {
const z = slope(x) + rise + Math.round((y - y0) * 0.6);
set(x, y, z, y === y0 + 2 ? "#c3a77d" : "#f4e2b6");
}
}
}
// Teal and violet motes rising from the pages.
for (const [x, y, z, c] of [
[11, 14, 25, "#8bf8ed"], [19, 29, 27, "#cf82f4"],
[27, 13, 25, "#9a6af3"], [40, 24, 26, "#71eee2"],
[9, 26, 23, "#c078e9"], [35, 33, 28, "#8cf2dc"]
]) {
set(x, y, z, c);
set(x, y, z + 1, "#f2cfff");
}
// Floating, flickering golden letters, separate from the bobbing book.
part("letters", [24, 21, 29]);
animate("letters", {
type: "colors",
palette: ["#e8a84d", "#ffcf70", "#fff4ce"],
pattern: "flicker",
glow: 0.8
});
function glyph(rows, x0, y, z0) {
for (let row = 0; row < rows.length; row++)
for (let col = 0; col < rows[row].length; col++)
if (rows[row][col] === "#") {
set(x0 + col, y, z0 + rows.length - 1 - row, "#ffda7c");
if ((row + col) % 3 === 0)
set(x0 + col, y + 1, z0 + rows.length - 1 - row, "#ffedb3");
}
}
glyph([
".###.",
"#...#",
"#...#",
"#####",
"#...#",
"#...#",
"#...#"
], 12, 17, 27);
glyph([
"..#..",
"..#..",
"#.#.#",
".###.",
"#####",
"..#..",
"..#.."
], 23, 15, 29);
glyph([
".#####.",
"#.....#",
"#.....#",
"#.....#",
".#...#.",
".#...#.",
"###.###"
], 32, 21, 27);import { draw } from "./assets/voxel";
import { libroVolador } from "./assets/libro-volador";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, libroVolador, 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 libro-volador
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["libro-volador"] })
# Queda en client/assets/libro-volador.ts
# https://fogon.page/object/libro-volador