삼각형 648개2.14 × 3.06 × 3.08 m
코드에서 사용하기
에디터에서 리믹스stairs.js · 306줄
독립형 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;
평점 및 댓글
댓글 0개
아직 댓글이 없어요. 이 모델을 어떻게 사용했는지, 어떤 점이 더 나아질 수 있을지 공유해 주세요.
관련 모델
같은 카테고리 또는 비슷한 태그









