# Book Stack

> A loose pile of two to eight hardcover books in varied sizes, thicknesses and cover colors, each with cream page blocks, colored spines and gilded spine bands, optionally topped by an open book with lined pages and a ribbon bookmark. Choose a classic, pastel or worn-leather palette and how messy the stack is. Great clutter for libraries, studies, wizard towers, desks and classrooms.

![Book Stack preview](https://codels.download/m/book-stack.png)

Multi-view sheet (3/4, front, side, top): https://codels.download/m/book-stack.sheet.png

## Facts

- Category: Props › Decor
- Size (W × H × D): 0.34 × 0.21 × 0.31 m — units are meters, +Y up, front faces +Z
- Origin: center of the base (y = 0)
- Triangles: 528 · Meshes: 44 · Materials: 10
- License: CC0-1.0 (Codels built-in)
- Tags: book, library, low-poly, stylized, fantasy, medieval, modern, office, interior, adventure, rpg, decoration, prop, leather, paper, cluster, instancing-friendly, mobile-friendly, parametric, multicolor, color-orange, size-small, poly-low
- Page: https://codels.download/models/book-stack

## Files

| Format | URL |
|---|---|
| three.js ES module (`createBookStack()`) | https://codels.download/m/book-stack.js |
| React Three Fiber component (`<BookStack />`) | https://codels.download/m/book-stack.jsx |
| Instancing module (`createBookStackInstances(count, place)`, one draw call per material) | https://codels.download/m/book-stack.instanced.js |
| Standalone HTML demo | https://codels.download/m/book-stack.html |
| glTF binary | https://codels.download/m/book-stack.glb |
| glTF binary, Meshopt-compressed (2–3× smaller; GLTFLoader needs `setMeshoptDecoder(MeshoptDecoder)`) | https://codels.download/m/book-stack.glb?compress=meshopt |
| glTF JSON | https://codels.download/m/book-stack.gltf |
| OBJ (vertex colors) + MTL | https://codels.download/m/book-stack.obj · https://codels.download/m/book-stack.mtl |
| STL | https://codels.download/m/book-stack.stl |
| CodelSpec JSON | https://codels.download/m/book-stack.json |

## Parameters

Append them as query parameters to any URL above to get a customized variant (e.g. `?count=...`).

| Param | Type | Range / options | Default | Current |
|---|---|---|---|---|
| `count` | int | 2 – 8 | 5 | 5 |
| `open` | boolean | true \| false | true | true |
| `palette` | select | classic \| pastel \| leather | classic | classic |
| `messy` | number | 0 – 1 | 0.45 | 0.45 |

## Parts

- book1 (group)
  - backCover (box · c3)
  - frontCover (box · c3)
  - spine (box · c3)
  - pages (box · pages)
  - spineBand (box · gold)
  - spineBand (box · gold)
- book2 (group)
  - backCover (box · c0)
  - frontCover (box · c0)
  - spine (box · c0)
  - pages (box · pages)
- book3 (group)
  - backCover (box · c2)
  - frontCover (box · c2)
  - spine (box · c2)
  - pages (box · pages)
  - spineBand (box · gold)
  - spineBand (box · gold)
- book4 (group)
  - backCover (box · c1)
  - frontCover (box · c1)
  - spine (box · c1)
  - pages (box · pages)
  - spineBand (box · gold)
  - spineBand (box · gold)
- book5 (group)
  - backCover (box · c4)
  - frontCover (box · c4)
  - spine (box · c4)
  - pages (box · pages)
- openBook (group)
  - openCover (box · c5)
  - openSpine (box · c5)
  - pagesRight (group)
    - pageBlock (box · pages)
    - textLine (box · ink)
    - textLine (box · ink)
    - textLine (box · ink)
    - textLine (box · ink)

## How to use

Save the code below as `book-stack.js`, then:

```js
import { createBookStack } from './book-stack.js';
const model = createBookStack();
scene.add(model);
```

## Code

```js
/**
 * Book Stack — Codels 3D model library (https://codels.download/models/book-stack)
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 528 · Size (W × H × D): 0.34 × 0.21 × 0.31 m
 *
 * Usage:
 *   import { createBookStack } from './book-stack.js';
 *   
 *   const model = createBookStack();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 */
import * as THREE from 'three';

const deg = THREE.MathUtils.degToRad;

export function createBookStack() {
  const root = new THREE.Group();
  root.name = 'BookStack';

  // Materials
  const pagesMat = new THREE.MeshStandardMaterial({ name: 'pages', color: 0xe4d6b4, roughness: 0.9, flatShading: true });
  const inkMat = new THREE.MeshStandardMaterial({ name: 'ink', color: 0x6a5e50, roughness: 0.9, flatShading: true });
  const goldMat = new THREE.MeshStandardMaterial({ name: 'gold', color: 0xc9a24a, roughness: 0.35, metalness: 0.65, flatShading: true });
  const c0Mat = new THREE.MeshStandardMaterial({ name: 'c0', color: 0x7d2f2a, roughness: 0.7, flatShading: true });
  const c1Mat = new THREE.MeshStandardMaterial({ name: 'c1', color: 0x2f4f70, roughness: 0.7, flatShading: true });
  const c2Mat = new THREE.MeshStandardMaterial({ name: 'c2', color: 0x3f5f3a, roughness: 0.7, flatShading: true });
  const c3Mat = new THREE.MeshStandardMaterial({ name: 'c3', color: 0x6e4a2a, roughness: 0.7, flatShading: true });
  const c4Mat = new THREE.MeshStandardMaterial({ name: 'c4', color: 0x4b3b60, roughness: 0.7, flatShading: true });
  const c5Mat = new THREE.MeshStandardMaterial({ name: 'c5', color: 0x2d5f5c, roughness: 0.7, flatShading: true });
  const ribbonMat = new THREE.MeshStandardMaterial({ name: 'ribbon', color: 0xb3262e, roughness: 0.8, flatShading: true });

  // Shared geometries
  const boxGeo = new THREE.BoxGeometry(0.2071, 0.004, 0.2951);
  const boxGeo2 = new THREE.BoxGeometry(0.006, 0.044, 0.008);
  const boxGeo3 = new THREE.BoxGeometry(0.2126, 0.004, 0.2934);
  const boxGeo4 = new THREE.BoxGeometry(0.1575, 0.004, 0.2138);
  const boxGeo5 = new THREE.BoxGeometry(0.006, 0.0398, 0.008);
  const boxGeo6 = new THREE.BoxGeometry(0.1939, 0.004, 0.2763);
  const boxGeo7 = new THREE.BoxGeometry(0.006, 0.0377, 0.008);
  const boxGeo8 = new THREE.BoxGeometry(0.1568, 0.004, 0.2228);
  const boxGeo9 = new THREE.BoxGeometry(0.142, 0.012, 0.2);
  const boxGeo10 = new THREE.BoxGeometry(0.0605, 0.0015, 0.005);

  // Parts
  const book1 = new THREE.Group();
  book1.name = 'book1';
  book1.position.set(-0.0096, 0, 0.0097);
  book1.rotation.set(0, deg(178.2016), 0);
  root.add(book1);
  const backCover = new THREE.Mesh(boxGeo, c3Mat);
  backCover.name = 'backCover';
  backCover.position.set(0, 0.002, 0);
  book1.add(backCover);
  const frontCover = new THREE.Mesh(boxGeo, c3Mat);
  frontCover.name = 'frontCover';
  frontCover.position.set(0, 0.041, 0);
  book1.add(frontCover);
  const spine = new THREE.Mesh(new THREE.BoxGeometry(0.2071, 0.043, 0.006), c3Mat);
  spine.name = 'spine';
  spine.position.set(0, 0.0215, 0.1446);
  book1.add(spine);
  const pages = new THREE.Mesh(new THREE.BoxGeometry(0.1971, 0.036, 0.2861), pagesMat);
  pages.name = 'pages';
  pages.position.set(0, 0.0215, -0.0015);
  book1.add(pages);
  const spineBand = new THREE.Mesh(boxGeo2, goldMat);
  spineBand.name = 'spineBand';
  spineBand.position.set(-0.0663, 0.0215, 0.1446);
  book1.add(spineBand);
  const spineBand2 = new THREE.Mesh(boxGeo2, goldMat);
  spineBand2.name = 'spineBand';
  spineBand2.position.set(0.0663, 0.0215, 0.1446);
  book1.add(spineBand2);
  const book2 = new THREE.Group();
  book2.name = 'book2';
  book2.position.set(-0.0081, 0.043, -0.0015);
  book2.rotation.set(0, deg(179.2157), 0);
  root.add(book2);
  const backCover2 = new THREE.Mesh(boxGeo3, c0Mat);
  backCover2.name = 'backCover';
  backCover2.position.set(0, 0.002, 0);
  book2.add(backCover2);
  const frontCover2 = new THREE.Mesh(boxGeo3, c0Mat);
  frontCover2.name = 'frontCover';
  frontCover2.position.set(0, 0.027, 0);
  book2.add(frontCover2);
  const spine2 = new THREE.Mesh(new THREE.BoxGeometry(0.2126, 0.029, 0.006), c0Mat);
  spine2.name = 'spine';
  spine2.position.set(0, 0.0145, 0.1437);
  book2.add(spine2);
  const pages2 = new THREE.Mesh(new THREE.BoxGeometry(0.2026, 0.022, 0.2844), pagesMat);
  pages2.name = 'pages';
  pages2.position.set(0, 0.0145, -0.0015);
  book2.add(pages2);
  const book3 = new THREE.Group();
  book3.name = 'book3';
  book3.position.set(0.0111, 0.072, 0.0079);
  book3.rotation.set(0, deg(-3.4747), 0);
  root.add(book3);
  const backCover3 = new THREE.Mesh(boxGeo4, c2Mat);
  backCover3.name = 'backCover';
  backCover3.position.set(0, 0.002, 0);
  book3.add(backCover3);
  const frontCover3 = new THREE.Mesh(boxGeo4, c2Mat);
  frontCover3.name = 'frontCover';
  frontCover3.position.set(0, 0.0368, 0);
  book3.add(frontCover3);
  const spine3 = new THREE.Mesh(new THREE.BoxGeometry(0.1575, 0.0388, 0.006), c2Mat);
  spine3.name = 'spine';
  spine3.position.set(0, 0.0194, 0.1039);
  book3.add(spine3);
  const pages3 = new THREE.Mesh(new THREE.BoxGeometry(0.1475, 0.0318, 0.2048), pagesMat);
  pages3.name = 'pages';
  pages3.position.set(0, 0.0194, -0.0015);
  book3.add(pages3);
  const spineBand3 = new THREE.Mesh(boxGeo5, goldMat);
  spineBand3.name = 'spineBand';
  spineBand3.position.set(-0.0504, 0.0194, 0.1039);
  book3.add(spineBand3);
  const spineBand4 = new THREE.Mesh(boxGeo5, goldMat);
  spineBand4.name = 'spineBand';
  spineBand4.position.set(0.0504, 0.0194, 0.1039);
  book3.add(spineBand4);
  const book4 = new THREE.Group();
  book4.name = 'book4';
  book4.position.set(0.0096, 0.1108, -0.0026);
  book4.rotation.set(0, deg(0.1058), 0);
  root.add(book4);
  const backCover4 = new THREE.Mesh(boxGeo6, c1Mat);
  backCover4.name = 'backCover';
  backCover4.position.set(0, 0.002, 0);
  book4.add(backCover4);
  const frontCover4 = new THREE.Mesh(boxGeo6, c1Mat);
  frontCover4.name = 'frontCover';
  frontCover4.position.set(0, 0.0347, 0);
  book4.add(frontCover4);
  const spine4 = new THREE.Mesh(new THREE.BoxGeometry(0.1939, 0.0367, 0.006), c1Mat);
  spine4.name = 'spine';
  spine4.position.set(0, 0.0184, 0.1351);
  book4.add(spine4);
  const pages4 = new THREE.Mesh(new THREE.BoxGeometry(0.1839, 0.0297, 0.2673), pagesMat);
  pages4.name = 'pages';
  pages4.position.set(0, 0.0184, -0.0015);
  book4.add(pages4);
  const spineBand5 = new THREE.Mesh(boxGeo7, goldMat);
  spineBand5.name = 'spineBand';
  spineBand5.position.set(-0.062, 0.0183, 0.1351);
  book4.add(spineBand5);
  const spineBand6 = new THREE.Mesh(boxGeo7, goldMat);
  spineBand6.name = 'spineBand';
  spineBand6.position.set(0.062, 0.0183, 0.1351);
  book4.add(spineBand6);
  const book5 = new THREE.Group();
  book5.name = 'book5';
  book5.position.set(-0.011, 0.1475, -0.0089);
  book5.rotation.set(0, deg(-10.7754), 0);
  root.add(book5);
  const backCover5 = new THREE.Mesh(boxGeo8, c4Mat);
  backCover5.name = 'backCover';
  backCover5.position.set(0, 0.002, 0);
  book5.add(backCover5);
  const frontCover5 = new THREE.Mesh(boxGeo8, c4Mat);
  frontCover5.name = 'frontCover';
  frontCover5.position.set(0, 0.0351, 0);
  book5.add(frontCover5);
  const spine5 = new THREE.Mesh(new THREE.BoxGeometry(0.1568, 0.0371, 0.006), c4Mat);
  spine5.name = 'spine';
  spine5.position.set(0, 0.0186, 0.1084);
  book5.add(spine5);
  const pages5 = new THREE.Mesh(new THREE.BoxGeometry(0.1468, 0.0301, 0.2138), pagesMat);
  pages5.name = 'pages';
  pages5.position.set(0, 0.0186, -0.0015);
  book5.add(pages5);
  const openBook = new THREE.Group();
  openBook.name = 'openBook';
  openBook.position.set(0.0056, 0.1847, 0.0048);
  openBook.rotation.set(0, deg(8.7674), 0);
  root.add(openBook);
  const openCover = new THREE.Mesh(new THREE.BoxGeometry(0.312, 0.004, 0.21), c5Mat);
  openCover.name = 'openCover';
  openCover.position.set(0, 0.002, 0);
  openBook.add(openCover);
  const openSpine = new THREE.Mesh(new THREE.BoxGeometry(0.016, 0.006, 0.21), c5Mat);
  openSpine.name = 'openSpine';
  openSpine.position.set(0, 0.0014, 0);
  openBook.add(openSpine);
  const pagesRight = new THREE.Group();
  pagesRight.name = 'pagesRight';
  pagesRight.position.set(0.077, 0.0095, 0);
  pagesRight.rotation.set(0, 0, deg(5));
  openBook.add(pagesRight);
  const pageBlock = new THREE.Mesh(boxGeo9, pagesMat);
  pageBlock.name = 'pageBlock';
  pageBlock.position.set(0, 0.006, 0);
  pagesRight.add(pageBlock);
  const textLine = new THREE.Mesh(new THREE.BoxGeometry(0.1032, 0.0015, 0.005), inkMat);
  textLine.name = 'textLine';
  textLine.position.set(-0.0034, 0.0128, -0.07);
  pagesRight.add(textLine);
  const textLine2 = new THREE.Mesh(new THREE.BoxGeometry(0.105, 0.0015, 0.005), inkMat);
  textLine2.name = 'textLine';
  textLine2.position.set(-0.0025, 0.0128, -0.044);
  pagesRight.add(textLine2);
  const textLine3 = new THREE.Mesh(new THREE.BoxGeometry(0.0928, 0.0015, 0.005), inkMat);
  textLine3.name = 'textLine';
  textLine3.position.set(-0.0086, 0.0128, -0.018);
  pagesRight.add(textLine3);
  const textLine4 = new THREE.Mesh(new THREE.BoxGeometry(0.1017, 0.0015, 0.005), inkMat);
  textLine4.name = 'textLine';
  textLine4.position.set(-0.0041, 0.0128, 0.008);
  pagesRight.add(textLine4);
  const textLine5 = new THREE.Mesh(new THREE.BoxGeometry(0.0899, 0.0015, 0.005), inkMat);
  textLine5.name = 'textLine';
  textLine5.position.set(-0.0101, 0.0128, 0.034);
  pagesRight.add(textLine5);
  const textLine6 = new THREE.Mesh(boxGeo10, inkMat);
  textLine6.name = 'textLine';
  textLine6.position.set(-0.0247, 0.0128, 0.06);
  pagesRight.add(textLine6);
  const pagesLeft = new THREE.Group();
  pagesLeft.name = 'pagesLeft';
  pagesLeft.position.set(-0.077, 0.0095, 0);
  pagesLeft.rotation.set(0, 0, deg(-5));
  openBook.add(pagesLeft);
  const pageBlock2 = new THREE.Mesh(boxGeo9, pagesMat);
  pageBlock2.name = 'pageBlock';
  pageBlock2.position.set(0, 0.006, 0);
  pagesLeft.add(pageBlock2);
  const textLine7 = new THREE.Mesh(new THREE.BoxGeometry(0.101, 0.0015, 0.005), inkMat);
  textLine7.name = 'textLine';
  textLine7.position.set(-0.0045, 0.0128, -0.07);
  pagesLeft.add(textLine7);
  const textLine8 = new THREE.Mesh(new THREE.BoxGeometry(0.1038, 0.0015, 0.005), inkMat);
  textLine8.name = 'textLine';
  textLine8.position.set(-0.0031, 0.0128, -0.044);
  pagesLeft.add(textLine8);
  const textLine9 = new THREE.Mesh(new THREE.BoxGeometry(0.0981, 0.0015, 0.005), inkMat);
  textLine9.name = 'textLine';
  textLine9.position.set(-0.006, 0.0128, -0.018);
  pagesLeft.add(textLine9);
  const textLine10 = new THREE.Mesh(new THREE.BoxGeometry(0.1079, 0.0015, 0.005), inkMat);
  textLine10.name = 'textLine';
  textLine10.position.set(-0.001, 0.0128, 0.008);
  pagesLeft.add(textLine10);
  const textLine11 = new THREE.Mesh(new THREE.BoxGeometry(0.093, 0.0015, 0.005), inkMat);
  textLine11.name = 'textLine';
  textLine11.position.set(-0.0085, 0.0128, 0.034);
  pagesLeft.add(textLine11);
  const textLine12 = new THREE.Mesh(boxGeo10, inkMat);
  textLine12.name = 'textLine';
  textLine12.position.set(-0.0247, 0.0128, 0.06);
  pagesLeft.add(textLine12);
  const ribbon = new THREE.Mesh(new THREE.BoxGeometry(0.008, 0.0015, 0.126), ribbonMat);
  ribbon.name = 'ribbon';
  ribbon.position.set(0.012, 0.017, 0.0462);
  ribbon.rotation.set(0, deg(8), 0);
  openBook.add(ribbon);
  const ribbonTail = new THREE.Mesh(new THREE.BoxGeometry(0.008, 0.03, 0.0015), ribbonMat);
  ribbonTail.name = 'ribbonTail';
  ribbonTail.position.set(0.032, 0.004, 0.111);
  ribbonTail.rotation.set(0, deg(8), 0);
  openBook.add(ribbonTail);

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

export default createBookStack;
```

---
Codels — 3D models as code for three.js. Search more: https://codels.download/api/v1/models?q=<query> · Guide: https://codels.download/llms.txt
