Codels
648 треуг.2,14 × 3,06 × 3,08 m

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

Ремикс в редакторе
Самодостаточный ES-модуль — зависит только от three. Вызовите фабрику и добавьте группу в сцену.
/**
 * Staircase — Codels 3D model library
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 648 · Size (W × H × D): 2.14 × 3.06 × 3.08 m
 *
 * Usage:
 *   import { createStairs } from './stairs.js';
 *   
 *   const model = createStairs();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 */
import * as THREE from 'three';

const deg = THREE.MathUtils.degToRad;

// Deterministic vertex displacement (organic low-poly look).
function jitter(geometry, amount, seed) {
  const pos = geometry.attributes.position;
  const h = (a, b, c, s) => {
    let x = Math.imul(a, 73856093) ^ Math.imul(b, 19349663) ^ Math.imul(c, 83492791) ^ Math.imul(s, 2654435761);
    x = Math.imul(x ^ (x >>> 16), 2246822507);
    x = Math.imul(x ^ (x >>> 13), 3266489909);
    return ((x ^ (x >>> 16)) >>> 0) / 4294967295 - 0.5;
  };
  for (let i = 0; i < pos.count; i++) {
    const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
    const qx = Math.round(x * 1000), qy = Math.round(y * 1000), qz = Math.round(z * 1000);
    pos.setXYZ(i, x + h(qx, qy, qz, seed) * 2 * amount, y + h(qx, qy, qz, seed + 1) * 2 * amount, z + h(qx, qy, qz, seed + 2) * 2 * amount);
  }
  pos.needsUpdate = true;
  geometry.computeVertexNormals();
  return geometry;
}

// 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 createStairs() {
  const root = new THREE.Group();
  root.name = 'Stairs';

  // Materials
  const stoneMat = new THREE.MeshStandardMaterial({ name: 'stone', color: 0x958e81, roughness: 0.95, flatShading: true });
  const stone2Mat = new THREE.MeshStandardMaterial({ name: 'stone2', color: 0x81796d, roughness: 0.95, flatShading: true });
  const stoneDarkMat = new THREE.MeshStandardMaterial({ name: 'stoneDark', color: 0x665f56, roughness: 1, flatShading: true });

  // Shared geometries
  const extrudeGeo = extrude([[1.5, 0], [1.5, 0.85], [-1.5, 2.85], [-1.5, 0]], 0.26);
  const boxGeo = new THREE.BoxGeometry(0.03, 0.16, 3);
  const boxGeo2 = new THREE.BoxGeometry(0.4, 0.1, 0.4);

  // Parts
  const stepBlock = new THREE.Mesh(new THREE.BoxGeometry(1.46, 0.13, 0.3), stoneDarkMat);
  stepBlock.name = 'stepBlock';
  stepBlock.position.set(0, 0.065, 1.35);
  root.add(stepBlock);
  const tread = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 1), stoneMat);
  tread.name = 'tread';
  tread.position.set(-0.0061, 0.165, 1.3675);
  tread.rotation.set(0, deg(0.0377), 0);
  root.add(tread);
  const stepBlock2 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 0.33, 0.3), stone2Mat);
  stepBlock2.name = 'stepBlock';
  stepBlock2.position.set(0, 0.165, 1.05);
  root.add(stepBlock2);
  const tread2 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 2), stone2Mat);
  tread2.name = 'tread';
  tread2.position.set(-0.0082, 0.365, 1.0675);
  tread2.rotation.set(0, deg(0.313), 0);
  root.add(tread2);
  const stepBlock3 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 0.53, 0.3), stoneDarkMat);
  stepBlock3.name = 'stepBlock';
  stepBlock3.position.set(0, 0.265, 0.75);
  root.add(stepBlock3);
  const tread3 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 3), stone2Mat);
  tread3.name = 'tread';
  tread3.position.set(0.0009, 0.565, 0.7675);
  tread3.rotation.set(0, deg(-0.2919), 0);
  root.add(tread3);
  const stepBlock4 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 0.73, 0.3), stoneDarkMat);
  stepBlock4.name = 'stepBlock';
  stepBlock4.position.set(0, 0.365, 0.45);
  root.add(stepBlock4);
  const tread4 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 4), stoneMat);
  tread4.name = 'tread';
  tread4.position.set(0.0059, 0.765, 0.4675);
  tread4.rotation.set(0, deg(-0.2341), 0);
  root.add(tread4);
  const stepBlock5 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 0.93, 0.3), stone2Mat);
  stepBlock5.name = 'stepBlock';
  stepBlock5.position.set(0, 0.465, 0.15);
  root.add(stepBlock5);
  const tread5 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 5), stone2Mat);
  tread5.name = 'tread';
  tread5.position.set(-0.006, 0.965, 0.1675);
  tread5.rotation.set(0, deg(0.1567), 0);
  root.add(tread5);
  const stepBlock6 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 1.13, 0.3), stoneDarkMat);
  stepBlock6.name = 'stepBlock';
  stepBlock6.position.set(0, 0.565, -0.15);
  root.add(stepBlock6);
  const tread6 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 6), stoneMat);
  tread6.name = 'tread';
  tread6.position.set(-0.0041, 1.165, -0.1325);
  tread6.rotation.set(0, deg(-0.0196), 0);
  root.add(tread6);
  const stepBlock7 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 1.33, 0.3), stoneDarkMat);
  stepBlock7.name = 'stepBlock';
  stepBlock7.position.set(0, 0.665, -0.45);
  root.add(stepBlock7);
  const tread7 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 7), stoneMat);
  tread7.name = 'tread';
  tread7.position.set(0.0034, 1.365, -0.4325);
  tread7.rotation.set(0, deg(0.0674), 0);
  root.add(tread7);
  const stepBlock8 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 1.53, 0.3), stone2Mat);
  stepBlock8.name = 'stepBlock';
  stepBlock8.position.set(0, 0.765, -0.75);
  root.add(stepBlock8);
  const tread8 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 8), stoneMat);
  tread8.name = 'tread';
  tread8.position.set(0.0013, 1.565, -0.7325);
  tread8.rotation.set(0, deg(-0.113), 0);
  root.add(tread8);
  const stepBlock9 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 1.73, 0.3), stoneDarkMat);
  stepBlock9.name = 'stepBlock';
  stepBlock9.position.set(0, 0.865, -1.05);
  root.add(stepBlock9);
  const tread9 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 9), stone2Mat);
  tread9.name = 'tread';
  tread9.position.set(-0.0087, 1.765, -1.0325);
  tread9.rotation.set(0, deg(0.4519), 0);
  root.add(tread9);
  const stepBlock10 = new THREE.Mesh(new THREE.BoxGeometry(1.46, 1.93, 0.3), stoneDarkMat);
  stepBlock10.name = 'stepBlock';
  stepBlock10.position.set(0, 0.965, -1.35);
  root.add(stepBlock10);
  const tread10 = new THREE.Mesh(jitter(new THREE.BoxGeometry(1.48, 0.07, 0.335), 0.008, 10), stone2Mat);
  tread10.name = 'tread';
  tread10.position.set(-0.0015, 1.965, -1.3325);
  tread10.rotation.set(0, deg(0.162), 0);
  root.add(tread10);
  const parapet = new THREE.Mesh(extrudeGeo, stone2Mat);
  parapet.name = 'parapet';
  parapet.position.set(-0.87, 0, 0);
  parapet.rotation.set(0, deg(-90), 0);
  root.add(parapet);
  const plinth = new THREE.Mesh(boxGeo, stoneDarkMat);
  plinth.name = 'plinth';
  plinth.position.set(-1.008, 0.08, 0);
  root.add(plinth);
  const faceStone = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.193, 0.5409), 0.006, 11), stoneMat);
  faceStone.name = 'faceStone';
  faceStone.position.set(-1.006, 0.3691, -0.2522);
  root.add(faceStone);
  const faceStone2 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.1809, 0.5155), 0.006, 12), stoneMat);
  faceStone2.name = 'faceStone';
  faceStone2.position.set(-1.006, 0.9602, -1.0969);
  root.add(faceStone2);
  const faceStone3 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.1787, 0.5291), 0.006, 13), stoneMat);
  faceStone3.name = 'faceStone';
  faceStone3.position.set(-1.006, 1.1399, 0.262);
  root.add(faceStone3);
  const faceStone4 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2057, 0.356), 0.006, 14), stoneMat);
  faceStone4.name = 'faceStone';
  faceStone4.position.set(-1.006, 1.9866, -0.7483);
  root.add(faceStone4);
  const faceStone5 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2402, 0.3376), 0.006, 15), stoneMat);
  faceStone5.name = 'faceStone';
  faceStone5.position.set(-1.006, 1.3182, -0.7292);
  root.add(faceStone5);
  const faceStone6 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2537, 0.3249), 0.006, 16), stoneDarkMat);
  faceStone6.name = 'faceStone';
  faceStone6.position.set(-1.006, 1.4773, -1.0118);
  root.add(faceStone6);
  const faceStone7 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.1775, 0.5042), 0.006, 17), stoneMat);
  faceStone7.name = 'faceStone';
  faceStone7.position.set(-1.006, 0.8536, -1.0802);
  root.add(faceStone7);
  const faceStone8 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.236, 0.4662), 0.006, 18), stoneDarkMat);
  faceStone8.name = 'faceStone';
  faceStone8.position.set(-1.006, 1.1214, 0.3686);
  root.add(faceStone8);
  const coping = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 19), stone2Mat);
  coping.name = 'coping';
  coping.position.set(-0.87, 1.2233, 1);
  coping.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping);
  const coping2 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 20), stoneMat);
  coping2.name = 'coping';
  coping2.position.set(-0.87, 1.89, 0);
  coping2.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping2);
  const coping3 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 21), stone2Mat);
  coping3.name = 'coping';
  coping3.position.set(-0.87, 2.5567, -1);
  coping3.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping3);
  const newel = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.38, 1.1, 0.34), 0.01, 22), stoneMat);
  newel.name = 'newel';
  newel.position.set(-0.87, 0.55, 1.35);
  root.add(newel);
  const newelCap = new THREE.Mesh(boxGeo2, stone2Mat);
  newelCap.name = 'newelCap';
  newelCap.position.set(-0.87, 1.15, 1.35);
  root.add(newelCap);
  const newel2 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.38, 0.3, 0.34), 0.01, 23), stoneMat);
  newel2.name = 'newel';
  newel2.position.set(-0.87, 2.8, -1.33);
  root.add(newel2);
  const newelCap2 = new THREE.Mesh(boxGeo2, stone2Mat);
  newelCap2.name = 'newelCap';
  newelCap2.position.set(-0.87, 3, -1.33);
  root.add(newelCap2);
  const parapet2 = new THREE.Mesh(extrudeGeo, stone2Mat);
  parapet2.name = 'parapet';
  parapet2.position.set(0.87, 0, 0);
  parapet2.rotation.set(0, deg(-90), 0);
  root.add(parapet2);
  const plinth2 = new THREE.Mesh(boxGeo, stoneDarkMat);
  plinth2.name = 'plinth';
  plinth2.position.set(1.008, 0.08, 0);
  root.add(plinth2);
  const faceStone9 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2136, 0.3178), 0.006, 24), stoneDarkMat);
  faceStone9.name = 'faceStone';
  faceStone9.position.set(1.006, 1.6613, -0.756);
  root.add(faceStone9);
  const faceStone10 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.1739, 0.5837), 0.006, 25), stoneMat);
  faceStone10.name = 'faceStone';
  faceStone10.position.set(1.006, 1.1506, -0.0323);
  root.add(faceStone10);
  const faceStone11 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.202, 0.4442), 0.006, 26), stoneDarkMat);
  faceStone11.name = 'faceStone';
  faceStone11.position.set(1.006, 0.4602, -0.1246);
  root.add(faceStone11);
  const faceStone12 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.249, 0.3072), 0.006, 27), stoneDarkMat);
  faceStone12.name = 'faceStone';
  faceStone12.position.set(1.006, 0.66, 0.856);
  root.add(faceStone12);
  const faceStone13 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2605, 0.3929), 0.006, 28), stoneMat);
  faceStone13.name = 'faceStone';
  faceStone13.position.set(1.006, 1.1258, -1.029);
  root.add(faceStone13);
  const faceStone14 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.1817, 0.4808), 0.006, 29), stoneDarkMat);
  faceStone14.name = 'faceStone';
  faceStone14.position.set(1.006, 0.7464, -0.1336);
  root.add(faceStone14);
  const faceStone15 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2268, 0.4866), 0.006, 30), stoneMat);
  faceStone15.name = 'faceStone';
  faceStone15.position.set(1.006, 0.9056, 0.3044);
  root.add(faceStone15);
  const faceStone16 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.04, 0.2629, 0.4737), 0.006, 31), stoneDarkMat);
  faceStone16.name = 'faceStone';
  faceStone16.position.set(1.006, 1.1586, -1.023);
  root.add(faceStone16);
  const coping4 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 32), stone2Mat);
  coping4.name = 'coping';
  coping4.position.set(0.87, 1.2233, 1);
  coping4.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping4);
  const coping5 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 33), stoneMat);
  coping5.name = 'coping';
  coping5.position.set(0.87, 1.89, 0);
  coping5.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping5);
  const coping6 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.33, 1.1859, 0.09), 0.006, 34), stone2Mat);
  coping6.name = 'coping';
  coping6.position.set(0.87, 2.5567, -1);
  coping6.rotation.set(deg(-56.3099), 0, 0);
  root.add(coping6);
  const newel3 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.38, 1.1, 0.34), 0.01, 35), stoneMat);
  newel3.name = 'newel';
  newel3.position.set(0.87, 0.55, 1.35);
  root.add(newel3);
  const newelCap3 = new THREE.Mesh(boxGeo2, stone2Mat);
  newelCap3.name = 'newelCap';
  newelCap3.position.set(0.87, 1.15, 1.35);
  root.add(newelCap3);
  const newel4 = new THREE.Mesh(jitter(new THREE.BoxGeometry(0.38, 0.3, 0.34), 0.01, 36), stoneMat);
  newel4.name = 'newel';
  newel4.position.set(0.87, 2.8, -1.33);
  root.add(newel4);
  const newelCap4 = new THREE.Mesh(boxGeo2, stone2Mat);
  newelCap4.name = 'newelCap';
  newelCap4.position.set(0.87, 3, -1.33);
  root.add(newelCap4);

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

export default createStairs;

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

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

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

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

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

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

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