← Ancient and prehistory3D assetfogon/cueva
Cueva
Drag to turn it// «Cueva» · gpt-6-sol (medium)
// A rocky cave entrance: a big rounded mound of grey-brown stone blocks in several shades with patches of moss and a few small bushes on top, and a dark arched opening on the front (-y) going deep inside. A couple of stalactites hang at the mouth and some rocks and bones lie by the entrance.
const stone = ["#756f62", "#817b6c", "#8d8675", "#999180"];
const darkStone = "#514d45";
const moss = ["#576947", "#687b4b", "#81905a"];
const cx = 24, cy = 24;
function moundHeight(x, y) {
const dx = (x - cx) / 21;
const dy = (y - cy) / 20;
const d = dx * dx + dy * dy;
if (d >= 1) return -1;
const rough = Math.sin(x * 1.7 + y * 0.6) * 0.65
+ Math.sin(y * 1.3 - x * 0.4) * 0.55;
return Math.floor(5 + 27 * Math.sqrt(1 - d) + rough);
}
// Solid, irregular stone hill.
for (let x = 3; x <= 45; x++) {
for (let y = 4; y <= 44; y++) {
const h = moundHeight(x, y);
for (let z = 0; z <= h; z++) {
const shade = Math.min(3, Math.max(0,
Math.floor(1.4 + z / 24 + (rand() - 0.5) * 1.5)));
set(x, y, z, stone[shade]);
}
}
}
// A long, genuinely hollow tunnel. Its rounded roof narrows into the hill.
for (let y = 3; y <= 31; y++) {
const halfWidth = y < 13 ? 8.3 : Math.max(6.5, 8.3 - (y - 13) * 0.12);
for (let x = 13; x <= 35; x++) {
const dx = Math.abs(x - cx);
if (dx > halfWidth) continue;
const roof = Math.floor(9 + (y < 16 ? 12 : 10) *
Math.sqrt(Math.max(0, 1 - dx * dx / (halfWidth * halfWidth))));
for (let z = 1; z <= roof; z++) set(x, y, z, null);
}
}
// Shadowed interior: a dark back wall, shaded sides, and an earthen floor.
for (let y = 8; y <= 31; y++) {
const halfWidth = y < 13 ? 8.3 : Math.max(6.5, 8.3 - (y - 13) * 0.12);
for (let x = 15; x <= 33; x++) {
if (Math.abs(x - cx) > halfWidth - 1) continue;
if (get(x, y, 0)) set(x, y, 0, y < 15 ? "#554c3d" : "#33332e");
}
}
for (let x = 17; x <= 31; x++) {
for (let z = 1; z <= 18; z++) {
if (!get(x, 32, z)) set(x, 32, z, "#191c1a");
}
}
for (let y = 12; y <= 29; y++) {
for (let z = 3; z <= 16; z++) {
if (!get(16, y, z)) set(16, y, z, "#30302b");
if (!get(32, y, z)) set(32, y, z, "#30302b");
}
}
// Chunky stones frame the arched mouth, including its raised crown.
for (const side of [-1, 1]) {
for (let z = 2; z <= 11; z += 3) {
box(cx + side * 9 - 1, 7, z, cx + side * 9 + 1, 10, z + 2,
z % 2 ? "#948a76" : "#a19883");
}
}
for (let x = 15; x <= 33; x += 3) {
const dx = (x + 1 - cx) / 10;
const top = Math.round(10 + 12 * Math.sqrt(Math.max(0, 1 - dx * dx)));
box(x, 9, top, x + 2, 12, top + 2,
x % 2 ? "#a39a84" : "#928a78");
// A few lighter upper faces make the individual arch stones legible.
box(x, 9, top + 2, x + 2, 11, top + 2, "#ada48d");
}
// Lichen and moss follow the top surface, collecting in broad patches.
for (let x = 5; x <= 43; x++) {
for (let y = 6; y <= 42; y++) {
const z = moundHeight(x, y);
if (z < 9 || !get(x, y, z)) continue;
const patch = Math.sin(x * 0.48 + y * 0.16) *
Math.cos(y * 0.57 - x * 0.12);
if (patch > 0.19 && rand() < 0.75) {
set(x, y, z, moss[Math.floor(rand() * moss.length)]);
if (rand() < 0.25 && get(x, y, z - 1))
set(x, y, z - 1, moss[0]);
}
}
}
// Small, rounded bushes growing from the crown and shoulders.
function bush(x, y, size) {
const z = moundHeight(x, y);
if (z < 0) return;
box(x, y, z + 1, x, y, z + 2, "#594d36");
ellipsoid(x, y, z + 3, size, size, size * 0.8, "#4b603b");
ellipsoid(x - 1, y - 1, z + 4, size * 0.65, size * 0.65, 1.1, "#718449");
for (let i = 0; i < 7; i++) {
const bx = x + Math.round((rand() - 0.5) * size * 2);
const by = y + Math.round((rand() - 0.5) * size * 2);
const bz = z + 3 + Math.round(rand() * 2);
if (get(bx, by, bz)) set(bx, by, bz, "#8d9c60");
}
}
bush(12, 21, 2);
bush(17, 30, 2);
bush(34, 20, 2);
bush(30, 34, 2);
bush(23, 28, 1);
// Two uneven stalactites just inside the entrance.
for (const [x, y, length] of [[20, 11, 5], [29, 12, 4], [25, 16, 3]]) {
let roof = 20;
while (roof < 32 && !get(x, y, roof)) roof++;
if (roof >= 32) continue;
for (let i = 1; i <= length; i++) {
const z = roof - i;
if (z < 3) break;
box(x, y, z, x + (i < 2 ? 1 : 0), y + (i < 2 ? 1 : 0), z,
i === length ? "#827b6a" : "#625d52");
}
}
// Dusty approach and scattered loose boulders.
for (let y = 0; y <= 8; y++) {
for (let x = 18; x <= 30; x++) {
if (Math.abs(x - 24) < 5 + y * 0.3 && !get(x, y, 0))
set(x, y, 0, rand() < 0.3 ? "#827461" : "#716650");
}
}
ellipsoid(14, 5, 2, 3, 2, 2, "#777363");
ellipsoid(12, 5, 3, 2, 2, 2, "#948b77");
ellipsoid(35, 6, 2, 3, 2, 2, "#69665c");
ellipsoid(37, 7, 2, 2, 2, 2, "#898373");
ellipsoid(31, 3, 1, 2, 1, 1, "#99917e");
// A few pale bones by the opening.
line(18, 2, 1, 21, 3, 1, 0, "#e5d7ad");
sphere(18, 2, 1, 1, "#eee0bb");
sphere(21, 3, 1, 1, "#e5d7ad");
line(27, 4, 1, 30, 3, 1, 0, "#e9dab3");
sphere(27, 4, 1, 1, "#e9dab3");
sphere(30, 3, 1, 1, "#dfcda4");import { draw } from "./assets/voxel";
import { cueva } from "./assets/cueva";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, cueva, 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 cueva
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["cueva"] })
# It lands in client/assets/cueva.ts
# https://fogon.page/object/cueva