← Medieval3D assetfogon/campesina
Campesina
Drag to turn it// «Campesina» · gpt-6-sol (medium)
// A chunky cartoon medieval peasant woman: a long green dress with a white apron, a white head scarf, a round head with rosy cheeks and black dot eyes, carrying a wicker basket of red apples on one arm.
const green = ["#456d39", "#527d42", "#608c4c"];
const greenDark = "#355b32";
const cream = "#f2ead5";
const creamShade = "#d9d2bf";
const skin = "#e6ae87";
const skinLight = "#f1c09a";
const wicker = ["#987044", "#ae8550", "#c49a61"];
const darkWicker = "#765534";
// Walking legs and sturdy brown shoes.
part("leftLeg", [20, 24, 10]);
box(18, 21, 3, 22, 26, 10, "#846147");
box(17, 18, 0, 23, 27, 3, "#513b30");
box(17, 18, 3, 23, 20, 4, "#72513a");
animate("leftLeg", { type: "swing", axis: "x", speed: 5, amplitude: 0.3 });
part("rightLeg", [28, 24, 10]);
box(26, 21, 3, 30, 26, 10, "#846147");
box(25, 18, 0, 31, 27, 3, "#513b30");
box(25, 18, 3, 31, 20, 4, "#72513a");
animate("rightLeg", { type: "swing", axis: "x", speed: -5, amplitude: 0.3 });
part("body", [24, 24, 24]);
// Full, bell-shaped green skirt.
function skirtR(z) {
if (z <= 9) return 13.5;
if (z <= 14) return 13.5 - (z - 9) * 0.35;
if (z <= 20) return 11.75 - (z - 14) * 0.55;
return 8.45 - (z - 20) * 0.3;
}
for (let z = 5; z <= 24; z++) {
const rx = skirtR(z), ry = rx * 0.92;
for (let x = 9; x <= 39; x++) for (let y = 10; y <= 38; y++) {
const d = ((x - 24) / rx) ** 2 + ((y - 24) / ry) ** 2;
if (d > 1) continue;
let color = y < 22 ? green[1] : green[0];
if (z >= 19 || (d > 0.72 && z > 10 && rand() < 0.13)) color = green[2];
if (z <= 7) color = y < 23 ? "#3e6938" : greenDark;
set(x, y, z, color);
}
}
// Pale apron follows the curved front of the skirt rather than lying flat.
for (let z = 8; z <= 24; z++) {
const rx = skirtR(z), ry = rx * 0.92;
const width = z < 13 ? 7 : z < 19 ? 6 : 5;
for (let x = 24 - width; x <= 24 + width; x++) {
const front = Math.round(24 - ry * Math.sqrt(Math.max(0, 1 - ((x - 24) / rx) ** 2)));
const edge = Math.abs(x - 24) === width;
set(x, front - 1, z, edge || z === 8 ? creamShade : cream);
if (z === 9 && x % 2 === 0) set(x, front - 2, z, "#fff6e5");
}
}
// Apron strings and a little tied bow at the waist.
box(17, 16, 24, 31, 17, 25, creamShade);
box(21, 15, 24, 27, 15, 25, cream);
box(22, 14, 24, 26, 14, 26, "#fff5df");
set(24, 13, 25, creamShade);
// Bodice and darker seams beneath the arms.
ellipsoid(24, 24, 27, 8, 6, 6, "#547b41");
box(17, 18, 25, 18, 23, 30, greenDark);
box(30, 18, 25, 31, 23, 30, greenDark);
box(20, 18, 29, 28, 18, 31, "#638c4d");
box(22, 17, 27, 26, 17, 29, "#638c4d");
for (let z = 27; z <= 29; z++) set(24, 16, z, "#dac18a");
// Head, rounded face, and a bit of dark hair beneath the scarf.
ellipsoid(24, 22, 36, 6, 5, 6, skin);
ellipsoid(24, 18, 37, 4, 2, 3, skinLight);
for (let x = 20; x <= 28; x++) {
set(x, 17, 40, "#573f32");
if (x === 20 || x === 28) set(x, 17, 39, "#573f32");
}
// White scarf: a broad crown, side flaps, and a pointed fold at the back.
ellipsoid(24, 23, 41, 7, 6, 3, cream);
for (let z = 32; z <= 40; z++) {
const flap = z < 35 ? 1 : 2;
box(17, 17, z, 17 + flap, 24, z, z % 3 === 0 ? creamShade : cream);
box(31 - flap, 17, z, 31, 24, z, z % 3 === 0 ? creamShade : cream);
}
box(20, 27, 32, 28, 28, 37, creamShade);
box(22, 28, 29, 26, 28, 34, cream);
box(19, 16, 39, 29, 16, 40, "#fff6e6");
// Features placed in front so they stay legible at small size.
box(21, 15, 37, 22, 15, 38, "#302a28");
box(26, 15, 37, 27, 15, 38, "#302a28");
set(22, 14, 38, "#ffffff");
set(27, 14, 38, "#ffffff");
box(23, 14, 34, 25, 15, 36, skinLight);
box(19, 16, 34, 21, 16, 35, "#d98580");
box(27, 16, 34, 29, 16, 35, "#d98580");
set(23, 15, 32, "#995c59");
set(24, 15, 32, "#995c59");
set(25, 15, 32, "#995c59");
// Free arm, hanging beside the dress.
part("leftArm", [15, 23, 29]);
ellipsoid(14, 23, 27, 4, 4, 4, green[1]);
line(13, 22, 26, 13, 19, 21, 2, "#496f3b");
box(11, 17, 21, 15, 19, 22, creamShade);
ellipsoid(13, 18, 19, 2, 2, 3, skin);
set(12, 16, 19, skinLight);
animate("leftArm", { type: "swing", axis: "x", speed: -5, amplitude: 0.25 });
// Basket-bearing arm; basket moves with the hand.
part("rightArm", [33, 23, 29]);
ellipsoid(34, 23, 27, 4, 4, 4, green[1]);
line(35, 22, 26, 37, 20, 23, 2, "#496f3b");
line(37, 20, 23, 38, 18, 22, 2, "#496f3b");
box(36, 17, 21, 39, 19, 22, creamShade);
ellipsoid(38, 17, 21, 2, 2, 2, skinLight);
// Curved wicker basket with a solid bottom and an open, apple-filled top.
for (let z = 12; z <= 19; z++) {
const rx = z < 14 ? 4 : 5;
const ry = z < 14 ? 4 : 5;
for (let x = 34; x <= 44; x++) for (let y = 14; y <= 26; y++) {
const d = ((x - 39) / rx) ** 2 + ((y - 20) / ry) ** 2;
if (d > 1) continue;
if (z <= 13 || d >= 0.52) {
const woven = (x + y + z) % 4;
set(x, y, z, wicker[woven === 0 ? 2 : woven === 2 ? 0 : 1]);
}
}
}
// Heavy woven rim and visible horizontal wicker courses.
for (let x = 34; x <= 44; x++) for (let y = 15; y <= 25; y++) {
const d = ((x - 39) / 5) ** 2 + ((y - 20) / 5) ** 2;
if (d <= 1 && d >= 0.48) {
set(x, y, 19, "#d3a66a");
if (d >= 0.69) {
set(x, y, 15, darkWicker);
set(x, y, 17, "#c39659");
}
}
}
// Arched handle, gripped near its left side.
line(35, 20, 19, 35, 20, 23, 0, darkWicker);
line(35, 20, 23, 37, 20, 25, 0, "#c39659");
line(37, 20, 25, 41, 20, 25, 0, "#c39659");
line(41, 20, 25, 43, 20, 23, 0, "#c39659");
line(43, 20, 23, 43, 20, 19, 0, darkWicker);
box(35, 17, 21, 37, 19, 22, skinLight);
// A heap of bright red apples, with highlights, stems, and leaves.
const apples = [
[37, 17, 20], [40, 17, 20], [43, 18, 20],
[36, 21, 20], [39, 21, 21], [42, 22, 20],
[38, 24, 20]
];
for (const [x, y, z] of apples) {
sphere(x, y, z, 1.5, "#aa3030");
set(x, y - 1, z + 1, "#da5143");
set(x, y, z + 2, "#62402c");
set(x + 1, y, z + 2, "#618447");
}
animate("rightArm", { type: "swing", axis: "x", speed: 5, amplitude: 0.25 });
part("body", [24, 24, 24]);import { draw } from "./assets/voxel";
import { campesina } from "./assets/campesina";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, campesina, 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 campesina
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["campesina"] })
# It lands in client/assets/campesina.ts
# https://fogon.page/object/campesina