Codels
736 треуг.0,26 × 0,08 × 0,28 m

Использовать в коде

Ремикс в редакторе
Самодостаточный ES-модуль — зависит только от three. Вызовите фабрику и добавьте группу в сцену.
/**
 * Spell Book — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 736 · Size (W × H × D): 0.26 × 0.08 × 0.28 m
 *
 * Usage:
 *   import { createSpellBook } from './spell-book.js';
 *   
 *   const model = createSpellBook();
 *   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 createSpellBook() {
  const root = new THREE.Group();
  root.name = 'SpellBook';

  // Materials
  const coverMat = new THREE.MeshStandardMaterial({ name: 'cover', color: 0x7a2a3a, roughness: 0.8, flatShading: true });
  const darkMat = new THREE.MeshStandardMaterial({ name: 'dark', color: 0x491923, roughness: 0.85, flatShading: true });
  const pagesMat = new THREE.MeshStandardMaterial({ name: 'pages', color: 0xefe1bd, roughness: 0.95, flatShading: true });
  const edgeMat = new THREE.MeshStandardMaterial({ name: 'edge', color: 0xd8c59a, roughness: 0.95, flatShading: true });
  const inkMat = new THREE.MeshStandardMaterial({ name: 'ink', color: 0x4a3a30, roughness: 0.9, flatShading: true });
  const goldMat = new THREE.MeshStandardMaterial({ name: 'gold', color: 0xd9ad4c, roughness: 0.35, metalness: 0.75, flatShading: true });
  const runeMat = new THREE.MeshStandardMaterial({ name: 'rune', color: 0x8fe6ff, roughness: 0.4, emissive: 0x53d8ff, emissiveIntensity: 1.1, flatShading: true });

  // Shared geometries
  const boxGeo = new THREE.BoxGeometry(0.205, 0.008, 0.275);
  const boxGeo2 = new THREE.BoxGeometry(0.002, 0.0015, 0.255);
  const boxGeo3 = new THREE.BoxGeometry(0.181, 0.0015, 0.002);
  const cylinderGeo = new THREE.CylinderGeometry(0.0415, 0.0415, 0.014, 10, 1, false, Math.PI, Math.PI);
  const extrudeGeo = extrude([[0, 0], [-0.042, 0], [0, 0.042]], 0.011);
  const extrudeGeo2 = extrude([[0, 0], [-0.042, 0], [0, -0.042]], 0.011);
  const boxGeo4 = new THREE.BoxGeometry(0.155, 0.002, 0.006);
  const boxGeo5 = new THREE.BoxGeometry(0.006, 0.002, 0.225);
  const cylinderGeo2 = new THREE.CylinderGeometry(0.0045, 0.0045, 0.002, 6);

  // Parts
  const backCover = new THREE.Mesh(boxGeo, coverMat);
  backCover.name = 'backCover';
  backCover.position.set(0, 0.004, 0);
  root.add(backCover);
  const frontCover = new THREE.Mesh(boxGeo, coverMat);
  frontCover.name = 'frontCover';
  frontCover.position.set(0, 0.071, 0);
  root.add(frontCover);
  const pageBlock = new THREE.Mesh(new THREE.BoxGeometry(0.191, 0.059, 0.259), pagesMat);
  pageBlock.name = 'pageBlock';
  pageBlock.position.set(0.003, 0.0375, 0);
  root.add(pageBlock);
  const pageLineSide1 = new THREE.Mesh(boxGeo2, edgeMat);
  pageLineSide1.name = 'pageLineSide1';
  pageLineSide1.position.set(0.099, 0.0228, 0);
  root.add(pageLineSide1);
  const pageLineFront1 = new THREE.Mesh(boxGeo3, edgeMat);
  pageLineFront1.name = 'pageLineFront1';
  pageLineFront1.position.set(0.004, 0.0228, 0.13);
  root.add(pageLineFront1);
  const pageLineSide2 = new THREE.Mesh(boxGeo2, edgeMat);
  pageLineSide2.name = 'pageLineSide2';
  pageLineSide2.position.set(0.099, 0.0375, 0);
  root.add(pageLineSide2);
  const pageLineFront2 = new THREE.Mesh(boxGeo3, edgeMat);
  pageLineFront2.name = 'pageLineFront2';
  pageLineFront2.position.set(0.004, 0.0375, 0.13);
  root.add(pageLineFront2);
  const pageLineSide3 = new THREE.Mesh(boxGeo2, edgeMat);
  pageLineSide3.name = 'pageLineSide3';
  pageLineSide3.position.set(0.099, 0.0523, 0);
  root.add(pageLineSide3);
  const pageLineFront3 = new THREE.Mesh(boxGeo3, edgeMat);
  pageLineFront3.name = 'pageLineFront3';
  pageLineFront3.position.set(0.004, 0.0523, 0.13);
  root.add(pageLineFront3);
  const spine = new THREE.Mesh(new THREE.CylinderGeometry(0.0375, 0.0375, 0.275, 10, 1, false, Math.PI, Math.PI), coverMat);
  spine.name = 'spine';
  spine.position.set(-0.1025, 0.0375, 0);
  spine.rotation.set(deg(90), 0, 0);
  root.add(spine);
  const pageRound = new THREE.Mesh(new THREE.CylinderGeometry(0.0295, 0.0295, 0.259, 10, 1, false, Math.PI, Math.PI), pagesMat);
  pageRound.name = 'pageRound';
  pageRound.position.set(-0.1015, 0.0375, 0);
  pageRound.rotation.set(deg(90), 0, 0);
  root.add(pageRound);
  const spineRibBack = new THREE.Mesh(cylinderGeo, darkMat);
  spineRibBack.name = 'spineRibBack';
  spineRibBack.position.set(-0.1025, 0.0375, -0.085);
  spineRibBack.rotation.set(deg(90), 0, 0);
  root.add(spineRibBack);
  const spineRibMid = new THREE.Mesh(cylinderGeo, darkMat);
  spineRibMid.name = 'spineRibMid';
  spineRibMid.position.set(-0.1025, 0.0375, 0);
  spineRibMid.rotation.set(deg(90), 0, 0);
  root.add(spineRibMid);
  const spineRibFront = new THREE.Mesh(cylinderGeo, darkMat);
  spineRibFront.name = 'spineRibFront';
  spineRibFront.position.set(-0.1025, 0.0375, 0.085);
  spineRibFront.rotation.set(deg(90), 0, 0);
  root.add(spineRibFront);
  const cornerTopFront = new THREE.Mesh(extrudeGeo, goldMat);
  cornerTopFront.name = 'cornerTopFront';
  cornerTopFront.position.set(0.1045, 0.071, 0.1395);
  cornerTopFront.rotation.set(deg(-90), 0, 0);
  root.add(cornerTopFront);
  const cornerBottomFront = new THREE.Mesh(extrudeGeo, goldMat);
  cornerBottomFront.name = 'cornerBottomFront';
  cornerBottomFront.position.set(0.1045, 0.004, 0.1395);
  cornerBottomFront.rotation.set(deg(-90), 0, 0);
  root.add(cornerBottomFront);
  const cornerTopBack = new THREE.Mesh(extrudeGeo2, goldMat);
  cornerTopBack.name = 'cornerTopBack';
  cornerTopBack.position.set(0.1045, 0.071, -0.1395);
  cornerTopBack.rotation.set(deg(-90), 0, 0);
  root.add(cornerTopBack);
  const cornerBottomBack = new THREE.Mesh(extrudeGeo2, goldMat);
  cornerBottomBack.name = 'cornerBottomBack';
  cornerBottomBack.position.set(0.1045, 0.004, -0.1395);
  cornerBottomBack.rotation.set(deg(-90), 0, 0);
  root.add(cornerBottomBack);
  const claspStrap = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.004, 0.036), darkMat);
  claspStrap.name = 'claspStrap';
  claspStrap.position.set(0.0805, 0.077, 0);
  root.add(claspStrap);
  const claspDrop = new THREE.Mesh(new THREE.BoxGeometry(0.006, 0.0465, 0.036), darkMat);
  claspDrop.name = 'claspDrop';
  claspDrop.position.set(0.1055, 0.0548, 0);
  root.add(claspDrop);
  const lockPlate = new THREE.Mesh(new THREE.BoxGeometry(0.008, 0.03, 0.05), goldMat);
  lockPlate.name = 'lockPlate';
  lockPlate.position.set(0.1065, 0.0315, 0);
  root.add(lockPlate);
  const keyhole = new THREE.Mesh(new THREE.BoxGeometry(0.004, 0.012, 0.006), inkMat);
  keyhole.name = 'keyhole';
  keyhole.position.set(0.1105, 0.0307, 0);
  root.add(keyhole);
  const frameZFront = new THREE.Mesh(boxGeo4, goldMat);
  frameZFront.name = 'frameZFront';
  frameZFront.position.set(0.006, 0.076, 0.1155);
  root.add(frameZFront);
  const frameXRight = new THREE.Mesh(boxGeo5, goldMat);
  frameXRight.name = 'frameXRight';
  frameXRight.position.set(0.0805, 0.076, 0);
  root.add(frameXRight);
  const frameZBack = new THREE.Mesh(boxGeo4, goldMat);
  frameZBack.name = 'frameZBack';
  frameZBack.position.set(0.006, 0.076, -0.1155);
  root.add(frameZBack);
  const frameXLeft = new THREE.Mesh(boxGeo5, goldMat);
  frameXLeft.name = 'frameXLeft';
  frameXLeft.position.set(-0.0685, 0.076, 0);
  root.add(frameXLeft);
  const rune = new THREE.Group();
  rune.name = 'rune';
  rune.position.set(0.006, 0.076, 0);
  rune.rotation.set(deg(-90), 0, 0);
  root.add(rune);
  const runeRing = new THREE.Mesh(extrude([[0.046, 0], [0.0437, 0.0142], [0.0372, 0.027], [0.027, 0.0372], [0.0142, 0.0437], [0, 0.046], [-0.0142, 0.0437], [-0.027, 0.0372], [-0.0372, 0.027], [-0.0437, 0.0142], [-0.046, 0], [-0.0437, -0.0142], [-0.0372, -0.027], [-0.027, -0.0372], [-0.0142, -0.0437], [0, -0.046], [0.0142, -0.0437], [0.027, -0.0372], [0.0372, -0.027], [0.0437, -0.0142]], 0.002, [[[0.0371, -0.0121], [0.0316, -0.0229], [0.0229, -0.0316], [0.0121, -0.0371], [0, -0.039], [-0.0121, -0.0371], [-0.0229, -0.0316], [-0.0316, -0.0229], [-0.0371, -0.0121], [-0.039, 0], [-0.0371, 0.0121], [-0.0316, 0.0229], [-0.0229, 0.0316], [-0.0121, 0.0371], [0, 0.039], [0.0121, 0.0371], [0.0229, 0.0316], [0.0316, 0.0229], [0.0371, 0.0121], [0.039, 0]]]), runeMat);
  runeRing.name = 'runeRing';
  rune.add(runeRing);
  const runeDiamond = new THREE.Mesh(extrude([[0, 0.0324], [0.024, 0], [0, -0.0324], [-0.024, 0]], 0.002, [[[-0.017, 0], [0, -0.023], [0.017, 0], [0, 0.023]]]), runeMat);
  runeDiamond.name = 'runeDiamond';
  rune.add(runeDiamond);
  const runeStroke = new THREE.Mesh(new THREE.BoxGeometry(0.004, 0.05, 0.002), runeMat);
  runeStroke.name = 'runeStroke';
  rune.add(runeStroke);
  const runeDot1 = new THREE.Mesh(cylinderGeo2, runeMat);
  runeDot1.name = 'runeDot1';
  runeDot1.position.set(0.0226, 0.0226, 0);
  runeDot1.rotation.set(deg(90), 0, 0);
  rune.add(runeDot1);
  const runeDot2 = new THREE.Mesh(cylinderGeo2, runeMat);
  runeDot2.name = 'runeDot2';
  runeDot2.position.set(-0.0226, 0.0226, 0);
  runeDot2.rotation.set(deg(90), 0, 0);
  rune.add(runeDot2);
  const runeDot3 = new THREE.Mesh(cylinderGeo2, runeMat);
  runeDot3.name = 'runeDot3';
  runeDot3.position.set(-0.0226, -0.0226, 0);
  runeDot3.rotation.set(deg(90), 0, 0);
  rune.add(runeDot3);
  const runeDot4 = new THREE.Mesh(cylinderGeo2, runeMat);
  runeDot4.name = 'runeDot4';
  runeDot4.position.set(0.0226, -0.0226, 0);
  runeDot4.rotation.set(deg(90), 0, 0);
  rune.add(runeDot4);

  root.traverse((o) => {
    if (o.isMesh) {
      o.castShadow = true;
      o.receiveShadow = true;
    }
  });
  return root;
}

export default createSpellBook;

Оценки и комментарии

—
Оценок пока нет — будьте первым.
5
0
4
0
3
0
2
0
1
0

Войдите, чтобы ставить оценки и оставлять комментарии.

0 комментариев

Комментариев пока нет. Расскажите, как вы использовали эту модель или что можно улучшить.

Похожие модели

Та же категория или похожие теги