← Ancient and prehistory3D assetfogon/cavernicola
Cavernícola
Drag to turn it// «Cavernícola» · gpt-6-sol (medium)
// A chunky cartoon caveman standing upright facing front (-y): big head with messy dark brown hair and a bushy beard, heavy brow, a one-shoulder orange tunic with dark leopard spots, bare arms and feet. He holds a big wooden club over one shoulder.
const skin = "#d99b68";
const skinLight = "#efb47b";
const skinShade = "#b97850";
const orange = "#db7435";
const orangeLight = "#f29245";
const orangeDark = "#ae4e28";
const hair = "#493024";
const hairLight = "#67412d";
const hairDark = "#30221d";
const wood = "#79502f";
const woodLight = "#a16c3b";
const woodDark = "#533823";
// Bare legs and broad feet, each swinging from its hip.
for (const [name, x, speed] of [["leftLeg", 19, 4], ["rightLeg", 29, -4]]) {
part(name, [x, 24, 16]);
ellipsoid(x, 24, 9, 3, 3, 8, skin);
ellipsoid(x, 20, 2, 4, 5, 2, skinLight);
box(x - 3, 16, 0, x + 3, 21, 1, skinLight);
for (let dx = -2; dx <= 2; dx += 2) {
box(x + dx, 15, 0, x + dx + 1, 17, 1, skinLight);
}
// Shading on the ankles.
box(x - 2, 22, 3, x + 2, 25, 4, skinShade);
animate(name, { type: "swing", axis: "x", speed, amplitude: 0.3 });
}
part("body");
// Stocky torso. One shoulder remains bare beneath the diagonal tunic.
ellipsoid(24, 23, 21, 8, 6, 9, skin);
forEach((x, y, z, color) => {
if (color !== skin || z < 13 || z > 30) return;
const covered =
z <= 25 ||
(z === 26 && x >= 21) ||
(z === 27 && x >= 23) ||
(z === 28 && x >= 26) ||
(z >= 29 && x >= 28);
if (covered) {
set(x, y, z, rand() < 0.14 ? orangeLight : orange);
} else if (rand() < 0.12) {
set(x, y, z, skinLight);
}
});
// Tunic hem and chunky leopard spots on the visible front.
for (let x = 18; x <= 30; x++) {
for (let y = 17; y <= 27; y++) {
if (get(x, y, 15) === orange || get(x, y, 15) === orangeLight) {
set(x, y, 15, orangeDark);
}
}
}
const spots = [
[20, 23], [26, 24], [29, 22], [23, 21],
[19, 19], [27, 19], [22, 17], [29, 16], [25, 26]
];
for (const [sx, sz] of spots) {
for (let y = 14; y <= 29; y++) {
const c = get(sx, y, sz);
if (c === orange || c === orangeLight) {
set(sx, y, sz, orangeDark);
if (get(sx + 1, y, sz) === orange) set(sx + 1, y, sz, orangeDark);
if (get(sx, y, sz - 1) === orange) set(sx, y, sz - 1, orangeDark);
break;
}
}
}
// Relaxed bare arm and oversized hand.
line(17, 23, 27, 13, 21, 21, 2, skin);
line(13, 21, 21, 12, 18, 17, 2, skin);
ellipsoid(12, 18, 16, 2, 2, 2, skinLight);
set(11, 16, 15, skinLight);
// Club arm, hand, and club move together about the shoulder.
part("clubArm", [31, 23, 27]);
sphere(32, 23, 27, 3, skin);
line(32, 23, 27, 35, 20, 22, 2, skin);
line(35, 20, 22, 36, 17, 26, 2, skin);
line(36, 17, 26, 39, 23, 39, 1, woodDark);
line(37, 19, 29, 39, 22, 37, 1, wood);
ellipsoid(39, 24, 40, 4, 4, 5, wood);
ellipsoid(38, 22, 41, 2, 2, 3, woodLight);
for (const [x, y, z] of [
[40, 20, 39], [41, 22, 42], [38, 20, 43],
[41, 24, 38], [39, 21, 36]
]) {
set(x, y, z, woodDark);
set(x, y, z + 1, woodLight);
}
ellipsoid(36, 17, 26, 2, 2, 2, skinLight);
box(35, 15, 25, 37, 16, 26, skinShade);
animate("clubArm", { type: "swing", axis: "x", speed: 3, amplitude: 0.4 });
part("body");
// Big, friendly head and ears.
ellipsoid(24, 22, 34, 9, 7, 8, skin);
ellipsoid(15, 21, 34, 2, 2, 3, skinShade);
ellipsoid(33, 21, 34, 2, 2, 3, skinShade);
box(15, 19, 34, 15, 20, 35, skinLight);
box(33, 19, 34, 33, 20, 35, skinLight);
// Bushy beard: cheeks, pointed chin, and a broad moustache.
ellipsoid(19, 16, 29, 4, 3, 4, hair);
ellipsoid(29, 16, 29, 4, 3, 4, hair);
ellipsoid(24, 17, 27, 6, 4, 4, hair);
for (const x of [19, 22, 25, 28]) {
ellipsoid(x, 16, 25, 2, 2, 2, x === 22 ? hairLight : hair);
}
box(20, 13, 31, 23, 14, 32, hairLight);
box(25, 13, 31, 28, 14, 32, hairLight);
box(22, 12, 29, 26, 12, 29, hairDark);
box(23, 11, 29, 25, 11, 29, "#f5dbab");
// Nose, heavy brows, and shiny black-dot eyes.
ellipsoid(24, 14, 34, 2, 2, 2, skinLight);
box(18, 13, 37, 21, 14, 38, hairDark);
box(27, 13, 37, 30, 14, 38, hairDark);
box(19, 13, 35, 20, 13, 36, "#211d1c");
box(28, 13, 35, 29, 13, 36, "#211d1c");
set(19, 12, 36, "#ffffff");
set(28, 12, 36, "#ffffff");
// Thick, uneven hair cap with a jagged fringe and loose tufts.
ellipsoid(24, 22, 40, 9, 7, 4, hair);
ellipsoid(18, 19, 41, 4, 4, 3, hairLight);
ellipsoid(28, 22, 42, 5, 4, 3, hair);
for (const [x, y, z, r] of [
[17, 15, 39, 2], [21, 14, 40, 2], [25, 15, 39, 2],
[29, 15, 40, 2], [32, 18, 39, 2],
[18, 21, 44, 2], [23, 23, 44, 2], [30, 23, 44, 2]
]) {
sphere(x, y, z, r, rand() < 0.4 ? hairLight : hair);
}
for (const [x, z] of [[17, 36], [20, 37], [24, 38], [28, 37], [31, 36]]) {
box(x, 14, z, x + 1, 15, z + 2, hair);
}import { draw } from "./assets/voxel";
import { cavernicola } from "./assets/cavernicola";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, cavernicola, 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 cavernicola
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["cavernicola"] })
# It lands in client/assets/cavernicola.ts
# https://fogon.page/object/cavernicola