Codels
676 tris0,85 × 1,55 × 0,77 m

Úsalo en código

Hacer remix en el editor
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

—
Aún no hay valoraciones — sé el primero.
5
0
4
0
3
0
2
0
1
0

Inicia sesión para valorar y comentar.

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