604 tris0.88 × 1.82 × 0.84 m
Use it in code
Remix in the editorvillager.js · 298 lines
Self-contained ES module — only depends on three. Call the factory and add the group to your scene.
/**
* Villager — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 604 · Size (W × H × D): 0.88 × 1.82 × 0.84 m
*
* Usage:
* import { createVillager } from './villager.js';
*
* const model = createVillager();
* model.position.set(0, 0, 0);
* scene.add(model);
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
const options = bevel
? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
: { depth, bevelEnabled: false };
return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}
export function createVillager() {
const root = new THREE.Group();
root.name = 'Villager';
// Materials
const skinMat = new THREE.MeshStandardMaterial({ name: 'skin', color: 0xf0c39c, roughness: 0.8, flatShading: true });
const hairMat = new THREE.MeshStandardMaterial({ name: 'hair', color: 0x7a4a2a, roughness: 0.9, flatShading: true });
const tunicMat = new THREE.MeshStandardMaterial({ name: 'tunic', color: 0x5e8b3f, roughness: 0.9, flatShading: true });
const tunicDarkMat = new THREE.MeshStandardMaterial({ name: 'tunicDark', color: 0x42612c, roughness: 0.9, flatShading: true });
const pantsMat = new THREE.MeshStandardMaterial({ name: 'pants', color: 0x7b6248, roughness: 0.9, flatShading: true });
const shoeMat = new THREE.MeshStandardMaterial({ name: 'shoe', color: 0x4a3326, roughness: 0.8, flatShading: true });
const clothMat = new THREE.MeshStandardMaterial({ name: 'cloth', color: 0xefe6d2, roughness: 0.95, flatShading: true });
const strawMat = new THREE.MeshStandardMaterial({ name: 'straw', color: 0xe2c06a, roughness: 0.9, flatShading: true });
const eyeMat = new THREE.MeshStandardMaterial({ name: 'eye', color: 0x231c20, roughness: 0.35, flatShading: true });
const blushMat = new THREE.MeshStandardMaterial({ name: 'blush', color: 0xe88a7e, roughness: 0.7, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(0.19, 0.52, 0.21);
const boxGeo2 = new THREE.BoxGeometry(0.202, 0.035, 0.222);
const boxGeo3 = new THREE.BoxGeometry(0.2, 0.09, 0.27);
const boxGeo4 = new THREE.BoxGeometry(0.035, 0.012, 0.282);
const boxGeo5 = new THREE.BoxGeometry(0.16, 0.2, 0.19);
const boxGeo6 = new THREE.BoxGeometry(0.17, 0.05, 0.2);
const boxGeo7 = new THREE.BoxGeometry(0.14, 0.26, 0.16);
const boxGeo8 = new THREE.BoxGeometry(0.05, 0.16, 0.264);
const boxGeo9 = new THREE.BoxGeometry(0.06, 0.08, 0.01);
const boxGeo10 = new THREE.BoxGeometry(0.0228, 0.0228, 0.006);
const boxGeo11 = new THREE.BoxGeometry(0.07, 0.035, 0.006);
const boxGeo12 = new THREE.BoxGeometry(0.04, 0.025, 0.01);
// Parts
const legL = new THREE.Group();
legL.name = 'legL';
legL.position.set(0.105, 0.6, 0);
root.add(legL);
const trouserL = new THREE.Mesh(boxGeo, pantsMat);
trouserL.name = 'trouserL';
trouserL.position.set(0, -0.26, 0);
legL.add(trouserL);
const wrapL1 = new THREE.Mesh(boxGeo2, clothMat);
wrapL1.name = 'wrapL1';
wrapL1.position.set(0, -0.36, 0);
wrapL1.rotation.set(0, 0, deg(-4));
legL.add(wrapL1);
const wrapL2 = new THREE.Mesh(boxGeo2, clothMat);
wrapL2.name = 'wrapL2';
wrapL2.position.set(0, -0.44, 0);
wrapL2.rotation.set(0, 0, deg(4));
legL.add(wrapL2);
const shoeL = new THREE.Mesh(boxGeo3, shoeMat);
shoeL.name = 'shoeL';
shoeL.position.set(0, -0.555, 0.025);
legL.add(shoeL);
const legR = new THREE.Group();
legR.name = 'legR';
legR.position.set(-0.105, 0.6, 0);
root.add(legR);
const trouserR = new THREE.Mesh(boxGeo, pantsMat);
trouserR.name = 'trouserR';
trouserR.position.set(0, -0.26, 0);
legR.add(trouserR);
const wrapR1 = new THREE.Mesh(boxGeo2, clothMat);
wrapR1.name = 'wrapR1';
wrapR1.position.set(0, -0.36, 0);
wrapR1.rotation.set(0, 0, deg(4));
legR.add(wrapR1);
const wrapR2 = new THREE.Mesh(boxGeo2, clothMat);
wrapR2.name = 'wrapR2';
wrapR2.position.set(0, -0.44, 0);
wrapR2.rotation.set(0, 0, deg(-4));
legR.add(wrapR2);
const shoeR = new THREE.Mesh(boxGeo3, shoeMat);
shoeR.name = 'shoeR';
shoeR.position.set(0, -0.555, 0.025);
legR.add(shoeR);
const body = new THREE.Group();
body.name = 'body';
root.add(body);
const tunic = new THREE.Mesh(new THREE.BoxGeometry(0.46, 0.53, 0.27), tunicMat);
tunic.name = 'tunic';
tunic.position.set(0, 0.855, 0);
body.add(tunic);
const tunicSkirt = new THREE.Mesh(new THREE.CylinderGeometry(0.3323, 0.3748, 0.2, 4, 1, false, Math.PI / 4), tunicMat);
tunicSkirt.name = 'tunicSkirt';
tunicSkirt.position.set(0, 0.5, 0);
tunicSkirt.scale.set(1, 1, 0.5957);
body.add(tunicSkirt);
const tunicHem = new THREE.Mesh(new THREE.CylinderGeometry(0.3762, 0.3818, 0.03, 4, 1, false, Math.PI / 4), tunicDarkMat);
tunicHem.name = 'tunicHem';
tunicHem.position.set(0, 0.415, 0);
tunicHem.scale.set(1, 1, 0.6042);
body.add(tunicHem);
const neckline = new THREE.Mesh(extrude([[-0.07, 0], [0.07, 0], [0, -0.11]], 0.01), skinMat);
neckline.name = 'neckline';
neckline.position.set(0, 1.115, 0.137);
body.add(neckline);
const lace = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.012, 0.01), tunicDarkMat);
lace.name = 'lace';
lace.position.set(0, 1.05, 0.141);
body.add(lace);
const ropeBelt = new THREE.Mesh(new THREE.BoxGeometry(0.472, 0.04, 0.282), strawMat);
ropeBelt.name = 'ropeBelt';
ropeBelt.position.set(0, 0.64, 0);
body.add(ropeBelt);
const ropeEnd = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.12, 0.02), strawMat);
ropeEnd.name = 'ropeEnd';
ropeEnd.position.set(-0.13, 0.57, 0.151);
ropeEnd.rotation.set(0, 0, deg(-8));
body.add(ropeEnd);
const apron = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, 0.012), clothMat);
apron.name = 'apron';
apron.position.set(0, 0.5, 0.169);
body.add(apron);
const apronPocket = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.08, 0.008), tunicDarkMat);
apronPocket.name = 'apronPocket';
apronPocket.position.set(0.05, 0.48, 0.177);
body.add(apronPocket);
const apronBib = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.24, 0.012), clothMat);
apronBib.name = 'apronBib';
apronBib.position.set(0, 0.77, 0.138);
body.add(apronBib);
const apronStrapL = new THREE.Mesh(boxGeo4, clothMat);
apronStrapL.name = 'apronStrapL';
apronStrapL.position.set(0.1, 1.121, 0);
body.add(apronStrapL);
const apronStrapR = new THREE.Mesh(boxGeo4, clothMat);
apronStrapR.name = 'apronStrapR';
apronStrapR.position.set(-0.1, 1.121, 0);
body.add(apronStrapR);
const armL = new THREE.Group();
armL.name = 'armL';
armL.position.set(0.31, 1.08, 0);
armL.rotation.set(0, 0, deg(6));
root.add(armL);
const sleeveL = new THREE.Mesh(boxGeo5, tunicMat);
sleeveL.name = 'sleeveL';
sleeveL.position.set(0, -0.085, 0);
armL.add(sleeveL);
const sleeveRollL = new THREE.Mesh(boxGeo6, tunicDarkMat);
sleeveRollL.name = 'sleeveRollL';
sleeveRollL.position.set(0, -0.18, 0);
armL.add(sleeveRollL);
const forearmL = new THREE.Mesh(boxGeo7, skinMat);
forearmL.name = 'forearmL';
forearmL.position.set(0, -0.32, 0);
armL.add(forearmL);
const armR = new THREE.Group();
armR.name = 'armR';
armR.position.set(-0.31, 1.08, 0);
armR.rotation.set(0, 0, deg(-6));
root.add(armR);
const sleeveR = new THREE.Mesh(boxGeo5, tunicMat);
sleeveR.name = 'sleeveR';
sleeveR.position.set(0, -0.085, 0);
armR.add(sleeveR);
const sleeveRollR = new THREE.Mesh(boxGeo6, tunicDarkMat);
sleeveRollR.name = 'sleeveRollR';
sleeveRollR.position.set(0, -0.18, 0);
armR.add(sleeveRollR);
const forearmR = new THREE.Mesh(boxGeo7, skinMat);
forearmR.name = 'forearmR';
forearmR.position.set(0, -0.32, 0);
armR.add(forearmR);
const head = new THREE.Group();
head.name = 'head';
head.position.set(0, 1.1, 0);
root.add(head);
const neck = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.06, 0.16), skinMat);
neck.name = 'neck';
neck.position.set(0, 0.02, 0);
head.add(neck);
const skull = new THREE.Mesh(new THREE.BoxGeometry(0.48, 0.48, 0.48), skinMat);
skull.name = 'skull';
skull.position.set(0, 0.27, 0);
head.add(skull);
const hairTop = new THREE.Mesh(new THREE.BoxGeometry(0.492, 0.12, 0.492), hairMat);
hairTop.name = 'hairTop';
hairTop.position.set(0, 0.456, 0);
head.add(hairTop);
const hairBack = new THREE.Mesh(new THREE.BoxGeometry(0.492, 0.32, 0.12), hairMat);
hairBack.name = 'hairBack';
hairBack.position.set(0, 0.31, -0.186);
head.add(hairBack);
const sideburnL = new THREE.Mesh(boxGeo8, hairMat);
sideburnL.name = 'sideburnL';
sideburnL.position.set(0.221, 0.34, -0.1056);
head.add(sideburnL);
const sideburnR = new THREE.Mesh(boxGeo8, hairMat);
sideburnR.name = 'sideburnR';
sideburnR.position.set(-0.221, 0.34, -0.1056);
head.add(sideburnR);
const fringeL = new THREE.Mesh(new THREE.BoxGeometry(0.18, 0.07, 0.03), hairMat);
fringeL.name = 'fringeL';
fringeL.position.set(0.1, 0.38, 0.248);
fringeL.rotation.set(0, 0, deg(8));
head.add(fringeL);
const fringeR = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.06, 0.03), hairMat);
fringeR.name = 'fringeR';
fringeR.position.set(-0.12, 0.385, 0.248);
fringeR.rotation.set(0, 0, deg(-6));
head.add(fringeR);
const eyeL = new THREE.Mesh(boxGeo9, eyeMat);
eyeL.name = 'eyeL';
eyeL.position.set(0.1, 0.29, 0.243);
head.add(eyeL);
const eyeShineL = new THREE.Mesh(boxGeo10, clothMat);
eyeShineL.name = 'eyeShineL';
eyeShineL.position.set(0.1084, 0.3076, 0.249);
head.add(eyeShineL);
const cheekL = new THREE.Mesh(boxGeo11, blushMat);
cheekL.name = 'cheekL';
cheekL.position.set(0.155, 0.21, 0.243);
head.add(cheekL);
const eyeR = new THREE.Mesh(boxGeo9, eyeMat);
eyeR.name = 'eyeR';
eyeR.position.set(-0.1, 0.29, 0.243);
head.add(eyeR);
const eyeShineR = new THREE.Mesh(boxGeo10, clothMat);
eyeShineR.name = 'eyeShineR';
eyeShineR.position.set(-0.1084, 0.3076, 0.249);
head.add(eyeShineR);
const cheekR = new THREE.Mesh(boxGeo11, blushMat);
cheekR.name = 'cheekR';
cheekR.position.set(-0.155, 0.21, 0.243);
head.add(cheekR);
const nose = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.05, 0.04), skinMat);
nose.name = 'nose';
nose.position.set(0, 0.23, 0.26);
head.add(nose);
const mouth = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.025, 0.01), blushMat);
mouth.name = 'mouth';
mouth.position.set(0, 0.145, 0.243);
head.add(mouth);
const smileL = new THREE.Mesh(boxGeo12, blushMat);
smileL.name = 'smileL';
smileL.position.set(0.06, 0.155, 0.243);
smileL.rotation.set(0, 0, deg(25));
head.add(smileL);
const smileR = new THREE.Mesh(boxGeo12, blushMat);
smileR.name = 'smileR';
smileR.position.set(-0.06, 0.155, 0.243);
smileR.rotation.set(0, 0, deg(-25));
head.add(smileR);
const strawHat = new THREE.Group();
strawHat.name = 'strawHat';
strawHat.position.set(0, 0.475, 0);
strawHat.rotation.set(deg(-4), 0, deg(3));
head.add(strawHat);
const hatBrim = new THREE.Mesh(new THREE.CylinderGeometry(0.44, 0.44, 0.03, 10, 1, false, 0.3142), strawMat);
hatBrim.name = 'hatBrim';
hatBrim.position.set(0, 0.015, 0);
strawHat.add(hatBrim);
const hatCrown = new THREE.Mesh(new THREE.CylinderGeometry(0.2828, 0.3606, 0.19, 4, 1, false, Math.PI / 4), strawMat);
hatCrown.name = 'hatCrown';
hatCrown.position.set(0, 0.125, 0);
strawHat.add(hatCrown);
const hatBand = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.3705, 0.05, 4, 1, false, Math.PI / 4), tunicDarkMat);
hatBand.name = 'hatBand';
hatBand.position.set(0, 0.055, 0);
strawHat.add(hatBand);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createVillager;
Ratings & comments
0 comments
No comments yet. Share how you used this model or what could be better.
Related models
Same category or similar tags









