736 tris0,26 × 0,08 × 0,28 m
Utiliser dans le code
Remixer dans l’éditeurspell-book.js · 210 lignes
Module ES autonome — ne dépend que de three. Appelez la fonction factory et ajoutez le groupe à votre scène.
/**
* 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;
Notes et commentaires
0 commentaire
Aucun commentaire pour l’instant. Dites comment vous avez utilisé ce modèle ou ce qui pourrait être amélioré.
Modèles similaires
Même catégorie ou étiquettes proches









