676 tris0,85 × 1,55 × 0,77 m
Úsalo en código
Hacer remix en el editorninja.js · 342 líneas
Módulo ES autónomo — solo depende de three. Llama a la función factory y añade el grupo a tu escena.
/**
* Ninja — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 676 · Size (W × H × D): 0.85 × 1.55 × 0.77 m
*
* Usage:
* import { createNinja } from './ninja.js';
*
* const model = createNinja();
* 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 createNinja() {
const root = new THREE.Group();
root.name = 'Ninja';
// Materials
const outfitMat = new THREE.MeshStandardMaterial({ name: 'outfit', color: 0x2b2e3d, roughness: 0.85, flatShading: true });
const wrapMat = new THREE.MeshStandardMaterial({ name: 'wrap', color: 0x727581, roughness: 0.9, flatShading: true });
const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0xd23a3f, roughness: 0.8, flatShading: true });
const skinMat = new THREE.MeshStandardMaterial({ name: 'skin', color: 0xf0c29b, roughness: 0.8, flatShading: true });
const eyeMat = new THREE.MeshStandardMaterial({ name: 'eye', color: 0x18151a, roughness: 0.3, flatShading: true });
const shineMat = new THREE.MeshStandardMaterial({ name: 'shine', color: 0xffffff, roughness: 0.3, flatShading: true });
const steelMat = new THREE.MeshStandardMaterial({ name: 'steel', color: 0xc6ccd4, roughness: 0.25, metalness: 0.8, flatShading: true });
const sheathMat = new THREE.MeshStandardMaterial({ name: 'sheath', color: 0x141216, roughness: 0.35, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(0.2, 0.3, 0.22);
const boxGeo2 = new THREE.BoxGeometry(0.18, 0.2, 0.19);
const boxGeo3 = new THREE.BoxGeometry(0.184, 0.02, 0.194);
const boxGeo4 = new THREE.BoxGeometry(0.19, 0.08, 0.26);
const boxGeo5 = new THREE.BoxGeometry(0.06, 0.06, 0.05);
const boxGeo6 = new THREE.BoxGeometry(0.08, 0.08, 0.05);
const boxGeo7 = new THREE.BoxGeometry(0.054, 0.016, 0.049);
const boxGeo8 = new THREE.BoxGeometry(0.16, 0.22, 0.18);
const boxGeo9 = new THREE.BoxGeometry(0.14, 0.18, 0.16);
const boxGeo10 = new THREE.BoxGeometry(0.144, 0.018, 0.164);
const boxGeo11 = new THREE.BoxGeometry(0.13, 0.11, 0.15);
const boxGeo12 = new THREE.BoxGeometry(0.06, 0.07, 0.01);
const boxGeo13 = new THREE.BoxGeometry(0.0228, 0.0228, 0.006);
const boxGeo14 = new THREE.BoxGeometry(0.11, 0.03, 0.02);
// Parts
const legL = new THREE.Group();
legL.name = 'legL';
legL.position.set(0.105, 0.58, 0);
root.add(legL);
const pantsL = new THREE.Mesh(boxGeo, outfitMat);
pantsL.name = 'pantsL';
pantsL.position.set(0, -0.15, 0);
legL.add(pantsL);
const shinWrapL = new THREE.Mesh(boxGeo2, wrapMat);
shinWrapL.name = 'shinWrapL';
shinWrapL.position.set(0, -0.4, 0);
legL.add(shinWrapL);
const wrapLineL = new THREE.Mesh(boxGeo3, outfitMat);
wrapLineL.name = 'wrapLineL';
wrapLineL.position.set(0, -0.34, 0);
wrapLineL.rotation.set(0, 0, deg(-6));
legL.add(wrapLineL);
const wrapLineL2 = new THREE.Mesh(boxGeo3, outfitMat);
wrapLineL2.name = 'wrapLineL';
wrapLineL2.position.set(0, -0.41, 0);
wrapLineL2.rotation.set(0, 0, deg(6));
legL.add(wrapLineL2);
const tabiL = new THREE.Mesh(boxGeo4, outfitMat);
tabiL.name = 'tabiL';
tabiL.position.set(0, -0.54, 0.03);
legL.add(tabiL);
const bigToeL = new THREE.Mesh(boxGeo5, outfitMat);
bigToeL.name = 'bigToeL';
bigToeL.position.set(0.055, -0.55, 0.175);
legL.add(bigToeL);
const legR = new THREE.Group();
legR.name = 'legR';
legR.position.set(-0.105, 0.58, 0);
root.add(legR);
const pantsR = new THREE.Mesh(boxGeo, outfitMat);
pantsR.name = 'pantsR';
pantsR.position.set(0, -0.15, 0);
legR.add(pantsR);
const shinWrapR = new THREE.Mesh(boxGeo2, wrapMat);
shinWrapR.name = 'shinWrapR';
shinWrapR.position.set(0, -0.4, 0);
legR.add(shinWrapR);
const wrapLineR = new THREE.Mesh(boxGeo3, outfitMat);
wrapLineR.name = 'wrapLineR';
wrapLineR.position.set(0, -0.34, 0);
wrapLineR.rotation.set(0, 0, deg(6));
legR.add(wrapLineR);
const wrapLineR2 = new THREE.Mesh(boxGeo3, outfitMat);
wrapLineR2.name = 'wrapLineR';
wrapLineR2.position.set(0, -0.41, 0);
wrapLineR2.rotation.set(0, 0, deg(-6));
legR.add(wrapLineR2);
const tabiR = new THREE.Mesh(boxGeo4, outfitMat);
tabiR.name = 'tabiR';
tabiR.position.set(0, -0.54, 0.03);
legR.add(tabiR);
const bigToeR = new THREE.Mesh(boxGeo5, outfitMat);
bigToeR.name = 'bigToeR';
bigToeR.position.set(-0.055, -0.55, 0.175);
legR.add(bigToeR);
const body = new THREE.Group();
body.name = 'body';
root.add(body);
const torso = new THREE.Mesh(new THREE.BoxGeometry(0.44, 0.56, 0.26), outfitMat);
torso.name = 'torso';
torso.position.set(0, 0.8, 0);
body.add(torso);
const lapelL = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.4428, 0.01), wrapMat);
lapelL.name = 'lapelL';
lapelL.position.set(0.025, 0.88, 0.133);
lapelL.rotation.set(deg(-90), deg(-64.5923), deg(90));
body.add(lapelL);
const lapelR = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.236, 0.01), wrapMat);
lapelR.name = 'lapelR';
lapelR.position.set(-0.05, 0.985, 0.131);
lapelR.rotation.set(deg(-90), deg(53.6156), deg(-90));
body.add(lapelR);
const sash = new THREE.Mesh(new THREE.BoxGeometry(0.452, 0.11, 0.272), accentMat);
sash.name = 'sash';
sash.position.set(0, 0.61, 0);
body.add(sash);
const sashKnot = new THREE.Mesh(boxGeo6, accentMat);
sashKnot.name = 'sashKnot';
sashKnot.position.set(0.15, 0.61, 0.15);
body.add(sashKnot);
const sashTail1 = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.16, 0.02), accentMat);
sashTail1.name = 'sashTail1';
sashTail1.position.set(0.17, 0.5, 0.16);
sashTail1.rotation.set(0, 0, deg(12));
body.add(sashTail1);
const sashTail2 = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.12, 0.02), accentMat);
sashTail2.name = 'sashTail2';
sashTail2.position.set(0.12, 0.52, 0.165);
sashTail2.rotation.set(0, 0, deg(-8));
body.add(sashTail2);
const strapFront = new THREE.Mesh(new THREE.BoxGeometry(0.045, 0.5304, 0.01), sheathMat);
strapFront.name = 'strapFront';
strapFront.position.set(-0.015, 0.89, 0.137);
strapFront.rotation.set(deg(-90), deg(-45.7639), deg(-90));
body.add(strapFront);
const strapShoulder = new THREE.Mesh(new THREE.BoxGeometry(0.045, 0.07, 0.28), sheathMat);
strapShoulder.name = 'strapShoulder';
strapShoulder.position.set(0.17, 1.1, 0);
body.add(strapShoulder);
const katana = new THREE.Group();
katana.name = 'katana';
katana.position.set(-0.03, 0.97, -0.165);
katana.rotation.set(0, 0, deg(-44));
body.add(katana);
const saya = new THREE.Mesh(new THREE.BoxGeometry(0.07, 0.7, 0.045), sheathMat);
saya.name = 'saya';
saya.position.set(0, -0.1, 0);
katana.add(saya);
const sayaBand = new THREE.Mesh(new THREE.BoxGeometry(0.074, 0.03, 0.05), accentMat);
sayaBand.name = 'sayaBand';
sayaBand.position.set(0, 0.14, 0);
katana.add(sayaBand);
const sayaTip = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.04, 0.05), steelMat);
sayaTip.name = 'sayaTip';
sayaTip.position.set(0, -0.44, 0);
katana.add(sayaTip);
const tsuba = new THREE.Mesh(new THREE.CylinderGeometry(0.065, 0.065, 0.025, 8), steelMat);
tsuba.name = 'tsuba';
tsuba.position.set(0, 0.265, 0);
katana.add(tsuba);
const tsuka = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.26, 0.045), wrapMat);
tsuka.name = 'tsuka';
tsuka.position.set(0, 0.405, 0);
katana.add(tsuka);
const tsukaWrap = new THREE.Mesh(boxGeo7, sheathMat);
tsukaWrap.name = 'tsukaWrap';
tsukaWrap.position.set(0, 0.31, 0);
tsukaWrap.rotation.set(0, 0, deg(-20));
katana.add(tsukaWrap);
const tsukaWrap2 = new THREE.Mesh(boxGeo7, sheathMat);
tsukaWrap2.name = 'tsukaWrap';
tsukaWrap2.position.set(0, 0.37, 0);
tsukaWrap2.rotation.set(0, 0, deg(20));
katana.add(tsukaWrap2);
const tsukaWrap3 = new THREE.Mesh(boxGeo7, sheathMat);
tsukaWrap3.name = 'tsukaWrap';
tsukaWrap3.position.set(0, 0.43, 0);
tsukaWrap3.rotation.set(0, 0, deg(-20));
katana.add(tsukaWrap3);
const tsukaWrap4 = new THREE.Mesh(boxGeo7, sheathMat);
tsukaWrap4.name = 'tsukaWrap';
tsukaWrap4.position.set(0, 0.49, 0);
tsukaWrap4.rotation.set(0, 0, deg(20));
katana.add(tsukaWrap4);
const kashira = new THREE.Mesh(new THREE.BoxGeometry(0.058, 0.03, 0.052), steelMat);
kashira.name = 'kashira';
kashira.position.set(0, 0.545, 0);
katana.add(kashira);
const armL = new THREE.Group();
armL.name = 'armL';
armL.position.set(0.3, 1.04, 0);
armL.rotation.set(0, 0, deg(7));
root.add(armL);
const sleeveL = new THREE.Mesh(boxGeo8, outfitMat);
sleeveL.name = 'sleeveL';
sleeveL.position.set(0, -0.09, 0);
armL.add(sleeveL);
const forearmWrapL = new THREE.Mesh(boxGeo9, wrapMat);
forearmWrapL.name = 'forearmWrapL';
forearmWrapL.position.set(0, -0.29, 0);
armL.add(forearmWrapL);
const armWrapLineL = new THREE.Mesh(boxGeo10, outfitMat);
armWrapLineL.name = 'armWrapLineL';
armWrapLineL.position.set(0, -0.27, 0);
armWrapLineL.rotation.set(0, 0, deg(8));
armL.add(armWrapLineL);
const gloveL = new THREE.Mesh(boxGeo11, outfitMat);
gloveL.name = 'gloveL';
gloveL.position.set(0, -0.43, 0);
armL.add(gloveL);
const armR = new THREE.Group();
armR.name = 'armR';
armR.position.set(-0.3, 1.04, 0);
armR.rotation.set(0, 0, deg(-7));
root.add(armR);
const sleeveR = new THREE.Mesh(boxGeo8, outfitMat);
sleeveR.name = 'sleeveR';
sleeveR.position.set(0, -0.09, 0);
armR.add(sleeveR);
const forearmWrapR = new THREE.Mesh(boxGeo9, wrapMat);
forearmWrapR.name = 'forearmWrapR';
forearmWrapR.position.set(0, -0.29, 0);
armR.add(forearmWrapR);
const armWrapLineR = new THREE.Mesh(boxGeo10, outfitMat);
armWrapLineR.name = 'armWrapLineR';
armWrapLineR.position.set(0, -0.27, 0);
armWrapLineR.rotation.set(0, 0, deg(-8));
armR.add(armWrapLineR);
const gloveR = new THREE.Mesh(boxGeo11, outfitMat);
gloveR.name = 'gloveR';
gloveR.position.set(0, -0.43, 0);
armR.add(gloveR);
const head = new THREE.Group();
head.name = 'head';
head.position.set(0, 1.06, 0);
root.add(head);
const neck = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.06, 0.16), outfitMat);
neck.name = 'neck';
neck.position.set(0, 0.02, 0);
head.add(neck);
const hood = new THREE.Mesh(new THREE.BoxGeometry(0.46, 0.46, 0.46), outfitMat);
hood.name = 'hood';
hood.position.set(0, 0.26, 0);
head.add(hood);
const eyeSlit = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.13, 0.012), skinMat);
eyeSlit.name = 'eyeSlit';
eyeSlit.position.set(0, 0.28, 0.232);
head.add(eyeSlit);
const eyeL = new THREE.Mesh(boxGeo12, eyeMat);
eyeL.name = 'eyeL';
eyeL.position.set(0.09, 0.275, 0.239);
head.add(eyeL);
const eyeShineL = new THREE.Mesh(boxGeo13, shineMat);
eyeShineL.name = 'eyeShineL';
eyeShineL.position.set(0.0984, 0.2904, 0.245);
head.add(eyeShineL);
const browL = new THREE.Mesh(boxGeo14, outfitMat);
browL.name = 'browL';
browL.position.set(0.09, 0.335, 0.238);
browL.rotation.set(0, 0, deg(14));
head.add(browL);
const eyeR = new THREE.Mesh(boxGeo12, eyeMat);
eyeR.name = 'eyeR';
eyeR.position.set(-0.09, 0.275, 0.239);
head.add(eyeR);
const eyeShineR = new THREE.Mesh(boxGeo13, shineMat);
eyeShineR.name = 'eyeShineR';
eyeShineR.position.set(-0.0984, 0.2904, 0.245);
head.add(eyeShineR);
const browR = new THREE.Mesh(boxGeo14, outfitMat);
browR.name = 'browR';
browR.position.set(-0.09, 0.335, 0.238);
browR.rotation.set(0, 0, deg(-14));
head.add(browR);
const maskNose = new THREE.Mesh(extrude([[-0.05, 0], [0.05, 0], [0, 0.05]], 0.03), outfitMat);
maskNose.name = 'maskNose';
maskNose.position.set(0, 0.16, 0.24);
head.add(maskNose);
const headband = new THREE.Mesh(new THREE.BoxGeometry(0.472, 0.065, 0.472), accentMat);
headband.name = 'headband';
headband.position.set(0, 0.39, 0);
head.add(headband);
const headbandPlate = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.06, 0.012), steelMat);
headbandPlate.name = 'headbandPlate';
headbandPlate.position.set(0, 0.39, 0.242);
head.add(headbandPlate);
const plateMark = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.012, 0.004), outfitMat);
plateMark.name = 'plateMark';
plateMark.position.set(0, 0.39, 0.249);
head.add(plateMark);
const headbandKnot = new THREE.Mesh(boxGeo6, accentMat);
headbandKnot.name = 'headbandKnot';
headbandKnot.position.set(0.04, 0.39, -0.255);
head.add(headbandKnot);
const headbandTails = new THREE.Group();
headbandTails.name = 'headbandTails';
headbandTails.position.set(0.04, 0.39, -0.27);
head.add(headbandTails);
const tail1 = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.03, 0.26), accentMat);
tail1.name = 'tail1';
tail1.position.set(0.03, -0.02, -0.12);
tail1.rotation.set(deg(-25), deg(18), 0);
headbandTails.add(tail1);
const tail2 = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.03, 0.22), accentMat);
tail2.name = 'tail2';
tail2.position.set(-0.03, -0.05, -0.1);
tail2.rotation.set(deg(-40), deg(-14), 0);
headbandTails.add(tail2);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createNinja;
Valoraciones y comentarios
0 comentarios
Aún no hay comentarios. Cuenta cómo usaste este modelo o qué se podría mejorar.
Modelos relacionados
Misma categoría o etiquetas similares









