삼각형 2.2천개2.91 × 3.28 × 11.4 m
코드에서 사용하기
에디터에서 리믹스city-bus.js · 328줄
독립형 ES 모듈이에요 — three에만 의존해요. 팩토리 함수를 호출하고 그룹을 씬에 추가하세요.
/**
* City Bus — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 2,152 · Size (W × H × D): 2.91 × 3.28 × 11.36 m
*
* Usage:
* import { createCityBus } from './city-bus.js';
*
* const model = createCityBus();
* model.position.set(0, 0, 0);
* scene.add(model);
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Revolve a 2D profile [[radius, y], ...] around the Y axis.
function lathe(points, segments, phiStart = 0, phiLength = Math.PI * 2) {
return new THREE.LatheGeometry(points.map(([x, y]) => new THREE.Vector2(x, y)), segments, phiStart, phiLength);
}
// 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);
}
// Tube along a smooth curve through the given points.
function tube(path, radius, tubularSegments, radialSegments, closed = false) {
const curve = new THREE.CatmullRomCurve3(path.map(([x, y, z]) => new THREE.Vector3(x, y, z)), closed);
return new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
}
export function createCityBus() {
const root = new THREE.Group();
root.name = 'CityBus';
// Materials
const paintMat = new THREE.MeshStandardMaterial({ name: 'paint', color: 0x3a9a6c, roughness: 0.5, metalness: 0.1, flatShading: true });
const roofMat = new THREE.MeshStandardMaterial({ name: 'roof', color: 0xeeeee8, roughness: 0.55, flatShading: true });
const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0xf2c14e, roughness: 0.5, flatShading: true });
const glassMat = new THREE.MeshStandardMaterial({ name: 'glass', color: 0x27323e, roughness: 0.2, metalness: 0.3, flatShading: true });
const trimMat = new THREE.MeshStandardMaterial({ name: 'trim', color: 0x2a2b30, roughness: 0.85, flatShading: true });
const headlightMat = new THREE.MeshStandardMaterial({ name: 'headlight', color: 0xffe3a1, roughness: 0.3, emissive: 0xffb84a, flatShading: true });
const taillightMat = new THREE.MeshStandardMaterial({ name: 'taillight', color: 0xd81f26, roughness: 0.3, emissive: 0xff2424, emissiveIntensity: 0.7, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(0.36, 0.16, 0.04);
const boxGeo2 = new THREE.BoxGeometry(0.14, 0.55, 0.04);
const boxGeo3 = new THREE.BoxGeometry(0.07, 0.4, 0.18);
const boxGeo4 = new THREE.BoxGeometry(2.6, 0.32, 0.26);
const boxGeo5 = new THREE.BoxGeometry(0.02, 1, 1.1086);
const boxGeo6 = new THREE.BoxGeometry(0.02, 1, 1.1);
const boxGeo7 = new THREE.BoxGeometry(0.02, 1, 1.01);
const boxGeo8 = new THREE.BoxGeometry(0.02, 2.06, 0.52);
const boxGeo9 = new THREE.BoxGeometry(0.02, 2.06, 0.57);
const latheGeo = lathe([[0.3, -0.12], [0.3, -0.16], [0.45, -0.16], [0.5, -0.1], [0.5, 0.1], [0.45, 0.16], [0.3, 0.16], [0.3, 0.12]], 14);
const cylinderGeo = new THREE.CylinderGeometry(0.3, 0.3, 0.24, 14);
const cylinderGeo2 = new THREE.CylinderGeometry(0.12, 0.15, 0.06, 8);
const latheGeo2 = lathe([[0.3, -0.16], [0.3, -0.2], [0.45, -0.2], [0.5, -0.14], [0.5, 0.14], [0.45, 0.2], [0.3, 0.2], [0.3, 0.16]], 14);
const cylinderGeo3 = new THREE.CylinderGeometry(0.3, 0.3, 0.32, 14);
// Parts
const body = new THREE.Group();
body.name = 'body';
root.add(body);
const lowerBody = new THREE.Mesh(extrude([[5.34, 0.32], [5.42, 0.62], [5.42, 1.3], [5.36, 2.62], [5.3, 2.78], [-5.32, 2.78], [-5.42, 2.62], [-5.42, 0.62], [-5.34, 0.32], [-3.1724, 0.32], [-3.1919, 0.5981], [-3.0837, 0.855], [-2.8711, 1.0352], [-2.6, 1.1], [-2.3289, 1.0352], [-2.1163, 0.855], [-2.0081, 0.5981], [-2.0276, 0.32], [2.4276, 0.32], [2.4081, 0.5981], [2.5163, 0.855], [2.7289, 1.0352], [3, 1.1], [3.2711, 1.0352], [3.4837, 0.855], [3.5919, 0.5981], [3.5724, 0.32]], 2.38, [], [0.08, 0.08, 2]), paintMat);
lowerBody.name = 'lowerBody';
lowerBody.rotation.set(0, deg(-90), 0);
body.add(lowerBody);
const chassis = new THREE.Mesh(new THREE.BoxGeometry(1.56, 0.85, 9.8), trimMat);
chassis.name = 'chassis';
chassis.position.set(0, 0.82, 0);
body.add(chassis);
const roofCap = new THREE.Mesh(extrude([[5.32, 2.68], [5.18, 2.98], [-5.22, 2.98], [-5.34, 2.68]], 2.4, [], [0.08, 0.06, 2]), roofMat);
roofCap.name = 'roofCap';
roofCap.rotation.set(0, deg(-90), 0);
body.add(roofCap);
const acUnit = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.24, 2.6), roofMat);
acUnit.name = 'acUnit';
acUnit.position.set(0, 3.14, -1.6);
body.add(acUnit);
const acGrille = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.03, 2.2), trimMat);
acGrille.name = 'acGrille';
acGrille.position.set(0, 3.275, -1.6);
body.add(acGrille);
const windshield = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.02, 1.201), glassMat);
windshield.name = 'windshield';
windshield.position.set(0, 1.9639, 5.4839);
windshield.rotation.set(deg(-92.3859), 0, 0);
body.add(windshield);
const destinationSign = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.02, 0.2377), trimMat);
destinationSign.name = 'destinationSign';
destinationSign.position.set(0, 2.858, 5.3135);
destinationSign.rotation.set(deg(-112.249), 0, 0);
body.add(destinationSign);
const destinationText = new THREE.Mesh(new THREE.BoxGeometry(1.3, 0.02, 0.13), headlightMat);
destinationText.name = 'destinationText';
destinationText.position.set(0, 2.8646, 5.3281);
destinationText.rotation.set(deg(-112.6199), 0, 0);
body.add(destinationText);
const headlightL = new THREE.Mesh(boxGeo, headlightMat);
headlightL.name = 'headlightL';
headlightL.position.set(0.92, 0.84, 5.51);
body.add(headlightL);
const taillightL = new THREE.Mesh(boxGeo2, taillightMat);
taillightL.name = 'taillightL';
taillightL.position.set(1.06, 1.05, -5.51);
body.add(taillightL);
const tube12 = new THREE.Mesh(tube([[1.15, 2.55, 5.3], [1.38, 2.62, 5.5], [1.42, 2.45, 5.72]], 0.03, 8, 5), trimMat);
body.add(tube12);
const mirrorL = new THREE.Mesh(boxGeo3, trimMat);
mirrorL.name = 'mirrorL';
mirrorL.position.set(1.42, 2.22, 5.72);
body.add(mirrorL);
const headlightR = new THREE.Mesh(boxGeo, headlightMat);
headlightR.name = 'headlightR';
headlightR.position.set(-0.92, 0.84, 5.51);
body.add(headlightR);
const taillightR = new THREE.Mesh(boxGeo2, taillightMat);
taillightR.name = 'taillightR';
taillightR.position.set(-1.06, 1.05, -5.51);
body.add(taillightR);
const tube16 = new THREE.Mesh(tube([[-1.15, 2.55, 5.3], [-1.38, 2.62, 5.5], [-1.42, 2.45, 5.72]], 0.03, 8, 5), trimMat);
body.add(tube16);
const mirrorR = new THREE.Mesh(boxGeo3, trimMat);
mirrorR.name = 'mirrorR';
mirrorR.position.set(-1.42, 2.22, 5.72);
body.add(mirrorR);
const stripeFront = new THREE.Mesh(new THREE.BoxGeometry(2.42, 0.12, 0.02), accentMat);
stripeFront.name = 'stripeFront';
stripeFront.position.set(0, 1.22, 5.505);
body.add(stripeFront);
const bumperFront = new THREE.Mesh(boxGeo4, trimMat);
bumperFront.name = 'bumperFront';
bumperFront.position.set(0, 0.46, 5.42);
body.add(bumperFront);
const bumperRear = new THREE.Mesh(boxGeo4, trimMat);
bumperRear.name = 'bumperRear';
bumperRear.position.set(0, 0.46, -5.42);
body.add(bumperRear);
const rearWindow = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.8, 0.02), glassMat);
rearWindow.name = 'rearWindow';
rearWindow.position.set(0, 2.05, -5.506);
body.add(rearWindow);
const engineGrille = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.36, 0.02), trimMat);
engineGrille.name = 'engineGrille';
engineGrille.position.set(0, 1.05, -5.506);
body.add(engineGrille);
const windowL1 = new THREE.Mesh(boxGeo5, glassMat);
windowL1.name = 'windowL1';
windowL1.position.set(1.276, 2.06, -4.4357);
body.add(windowL1);
const windowL2 = new THREE.Mesh(boxGeo5, glassMat);
windowL2.name = 'windowL2';
windowL2.position.set(1.276, 2.06, -3.2071);
body.add(windowL2);
const windowL3 = new THREE.Mesh(boxGeo5, glassMat);
windowL3.name = 'windowL3';
windowL3.position.set(1.276, 2.06, -1.9786);
body.add(windowL3);
const windowL4 = new THREE.Mesh(boxGeo5, glassMat);
windowL4.name = 'windowL4';
windowL4.position.set(1.276, 2.06, -0.75);
body.add(windowL4);
const windowL5 = new THREE.Mesh(boxGeo5, glassMat);
windowL5.name = 'windowL5';
windowL5.position.set(1.276, 2.06, 0.4786);
body.add(windowL5);
const windowL6 = new THREE.Mesh(boxGeo5, glassMat);
windowL6.name = 'windowL6';
windowL6.position.set(1.276, 2.06, 1.7071);
body.add(windowL6);
const windowL7 = new THREE.Mesh(boxGeo5, glassMat);
windowL7.name = 'windowL7';
windowL7.position.set(1.276, 2.06, 2.9357);
body.add(windowL7);
const windowL8 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 1, 1.23), glassMat);
windowL8.name = 'windowL8';
windowL8.position.set(1.276, 2.06, 4.375);
body.add(windowL8);
const box31 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.12, 10.62), accentMat);
box31.position.set(1.276, 1.22, 0.01);
body.add(box31);
const windowR1 = new THREE.Mesh(boxGeo6, glassMat);
windowR1.name = 'windowR1';
windowR1.position.set(-1.276, 2.06, -4.44);
body.add(windowR1);
const windowR2 = new THREE.Mesh(boxGeo6, glassMat);
windowR2.name = 'windowR2';
windowR2.position.set(-1.276, 2.06, -3.22);
body.add(windowR2);
const windowR3 = new THREE.Mesh(boxGeo6, glassMat);
windowR3.name = 'windowR3';
windowR3.position.set(-1.276, 2.06, -2);
body.add(windowR3);
const windowR4 = new THREE.Mesh(boxGeo6, glassMat);
windowR4.name = 'windowR4';
windowR4.position.set(-1.276, 2.06, -0.78);
body.add(windowR4);
const windowR5 = new THREE.Mesh(boxGeo7, glassMat);
windowR5.name = 'windowR5';
windowR5.position.set(-1.276, 2.06, 1.935);
body.add(windowR5);
const windowR6 = new THREE.Mesh(boxGeo7, glassMat);
windowR6.name = 'windowR6';
windowR6.position.set(-1.276, 2.06, 3.065);
body.add(windowR6);
const box38 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.12, 5.19), accentMat);
box38.position.set(-1.276, 1.22, -2.705);
body.add(box38);
const box39 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.12, 2.38), accentMat);
box39.position.set(-1.276, 1.22, 2.5);
body.add(box39);
const box40 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.12, 0.31), accentMat);
box40.position.set(-1.276, 1.22, 5.165);
body.add(box40);
const doorFrame1 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 2.22, 1.2), trimMat);
doorFrame1.name = 'doorFrame1';
doorFrame1.position.set(-1.276, 1.53, 4.35);
body.add(doorFrame1);
const door1a = new THREE.Mesh(boxGeo8, glassMat);
door1a.name = 'door1a';
door1a.position.set(-1.288, 1.55, 4.05);
body.add(door1a);
const door1b = new THREE.Mesh(boxGeo8, glassMat);
door1b.name = 'door1b';
door1b.position.set(-1.288, 1.55, 4.65);
body.add(door1b);
const doorFrame2 = new THREE.Mesh(new THREE.BoxGeometry(0.02, 2.22, 1.3), trimMat);
doorFrame2.name = 'doorFrame2';
doorFrame2.position.set(-1.276, 1.53, 0.6);
body.add(doorFrame2);
const door2a = new THREE.Mesh(boxGeo9, glassMat);
door2a.name = 'door2a';
door2a.position.set(-1.288, 1.55, 0.275);
body.add(door2a);
const door2b = new THREE.Mesh(boxGeo9, glassMat);
door2b.name = 'door2b';
door2b.position.set(-1.288, 1.55, 0.925);
body.add(door2b);
const wheels = new THREE.Group();
wheels.name = 'wheels';
root.add(wheels);
const wheelFL = new THREE.Group();
wheelFL.name = 'wheelFL';
wheelFL.position.set(0.98, 0.5, 3);
wheels.add(wheelFL);
const tire = new THREE.Mesh(latheGeo, trimMat);
tire.name = 'tire';
tire.rotation.set(0, 0, deg(90));
wheelFL.add(tire);
const rim = new THREE.Mesh(cylinderGeo, roofMat);
rim.name = 'rim';
rim.rotation.set(0, 0, deg(90));
wheelFL.add(rim);
const hub = new THREE.Mesh(cylinderGeo2, trimMat);
hub.name = 'hub';
hub.position.set(0.14, 0, 0);
hub.rotation.set(0, 0, deg(-90));
wheelFL.add(hub);
const wheelFR = new THREE.Group();
wheelFR.name = 'wheelFR';
wheelFR.position.set(-0.98, 0.5, 3);
wheels.add(wheelFR);
const tire2 = new THREE.Mesh(latheGeo, trimMat);
tire2.name = 'tire';
tire2.rotation.set(0, 0, deg(90));
wheelFR.add(tire2);
const rim2 = new THREE.Mesh(cylinderGeo, roofMat);
rim2.name = 'rim';
rim2.rotation.set(0, 0, deg(90));
wheelFR.add(rim2);
const hub2 = new THREE.Mesh(cylinderGeo2, trimMat);
hub2.name = 'hub';
hub2.position.set(-0.14, 0, 0);
hub2.rotation.set(0, 0, deg(90));
wheelFR.add(hub2);
const wheelRL = new THREE.Group();
wheelRL.name = 'wheelRL';
wheelRL.position.set(0.98, 0.5, -2.6);
wheels.add(wheelRL);
const tire3 = new THREE.Mesh(latheGeo2, trimMat);
tire3.name = 'tire';
tire3.rotation.set(0, 0, deg(90));
wheelRL.add(tire3);
const rim3 = new THREE.Mesh(cylinderGeo3, roofMat);
rim3.name = 'rim';
rim3.rotation.set(0, 0, deg(90));
wheelRL.add(rim3);
const hub3 = new THREE.Mesh(cylinderGeo2, trimMat);
hub3.name = 'hub';
hub3.position.set(0.18, 0, 0);
hub3.rotation.set(0, 0, deg(-90));
wheelRL.add(hub3);
const wheelRR = new THREE.Group();
wheelRR.name = 'wheelRR';
wheelRR.position.set(-0.98, 0.5, -2.6);
wheels.add(wheelRR);
const tire4 = new THREE.Mesh(latheGeo2, trimMat);
tire4.name = 'tire';
tire4.rotation.set(0, 0, deg(90));
wheelRR.add(tire4);
const rim4 = new THREE.Mesh(cylinderGeo3, roofMat);
rim4.name = 'rim';
rim4.rotation.set(0, 0, deg(90));
wheelRR.add(rim4);
const hub4 = new THREE.Mesh(cylinderGeo2, trimMat);
hub4.name = 'hub';
hub4.position.set(-0.18, 0, 0);
hub4.rotation.set(0, 0, deg(90));
wheelRR.add(hub4);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createCityBus;
평점 및 댓글
댓글 0개
아직 댓글이 없어요. 이 모델을 어떻게 사용했는지, 어떤 점이 더 나아질 수 있을지 공유해 주세요.
관련 모델
같은 카테고리 또는 비슷한 태그









