← Pirates and sea3D assetfogon/balas-de-canon
Pila de balas de cañón
Drag to turn it// «Pila de balas de cañón» · gpt-6-sol (medium)
// A neat pyramid of round black iron cannonballs (like a stacked pyramid of spheres, 4 layers) on a small square wooden pallet.
// A four-tier pyramid of black iron cannonballs on a wooden pallet.
const woodDark = "#704126";
const woodMid = "#a96737";
const woodLight = "#c9874b";
const seam = "#55321f";
// Three sturdy runners beneath the pallet.
for (const x of [6, 21, 36]) {
box(x, 3, 0, x + 6, 44, 2, woodDark);
box(x, 3, 1, x + 6, 4, 2, woodMid);
box(x, 43, 1, x + 6, 44, 2, woodMid);
}
// Four broad, slightly varied deck planks.
const plankColors = ["#ad6b3b", "#bd7841", "#a76336", "#b8733e"];
for (let i = 0; i < 4; i++) {
const x = 4 + i * 10;
box(x, 4, 3, x + 8, 43, 5, plankColors[i]);
box(x, 4, 5, x + 8, 43, 5, i % 2 ? "#ca8950" : woodLight);
box(x, 4, 3, x + 8, 4, 4, woodMid);
// Long, restrained wood-grain lines.
for (const offset of [2, 6]) {
for (let y = 8; y < 40; y += 9) {
line(x + offset, y, 5, x + offset, y + 4, 5, 0,
offset === 2 ? "#d39358" : "#ad6939");
}
}
if (i < 3) box(x + 9, 4, 3, x + 9, 43, 5, seam);
}
// End-grain and cheerful brass nail heads along the visible front.
for (const x of [8, 18, 28, 38]) {
set(x, 4, 4, "#e5ad52");
set(x + 1, 4, 4, "#b77a30");
}
for (const x of [6, 41]) {
for (const y of [6, 41]) set(x, y, 6, "#e8b754");
}
// Draw each ball independently so its rounded edge remains readable.
function cannonball(cx, cy, cz) {
const r = 4.7;
for (let x = Math.floor(cx - r); x <= Math.ceil(cx + r); x++) {
for (let y = Math.floor(cy - r); y <= Math.ceil(cy + r); y++) {
for (let z = Math.floor(cz - r); z <= Math.ceil(cz + r); z++) {
const dx = x - cx, dy = y - cy, dz = z - cz;
const d2 = dx * dx + dy * dy + dz * dz;
if (d2 > r * r) continue;
let color;
if (d2 > 19.4) color = "#171b20";
else if (dz < -1.8) color = "#20252b";
else if (dz > 1.2) color = "#414952";
else color = "#30363e";
// Broad reflected daylight across the upper front-left face.
if (dy < -1.6 && dz > 0 && dx < 1.5 && d2 < 19.4)
color = dz > 1.8 ? "#59636c" : "#48515b";
if (dy > 1.5 && dz < 1) color = "#242a30";
if (d2 < 19.4 && rand() < 0.045)
color = dz > 0 ? "#505961" : "#3b424a";
// A tiny, crisp glint on the foremost surface.
const frontY = Math.ceil(cy - Math.sqrt(Math.max(0, r * r - dx * dx - dz * dz)));
if (y === frontY && dx >= -2.5 && dx <= -0.5 &&
dz >= 1.4 && dz <= 2.5)
color = dx < -1 ? "#849099" : "#68747e";
set(x, y, z, color);
}
}
}
}
// Square tiers: 4×4, 3×3, 2×2, and a single crowning ball.
for (let tier = 0; tier < 4; tier++) {
const count = 4 - tier;
const first = 10.5 + tier * 4.5;
const height = 10 + tier * 6.4;
for (let ix = 0; ix < count; ix++) {
for (let iy = 0; iy < count; iy++) {
cannonball(first + ix * 9, first + iy * 9, height);
}
}
}import { draw } from "./assets/voxel";
import { balasDeCanon } from "./assets/balas-de-canon";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, balasDeCanon, 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 balas-de-canon
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["balas-de-canon"] })
# It lands in client/assets/balas-de-canon.ts
# https://fogon.page/object/balas-de-canon