Codels
삼각형 2.2천개2.91 × 3.28 × 11.4 m

코드에서 사용하기

에디터에서 리믹스
독립형 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;

평점 및 댓글

—
아직 평점이 없어요 — 첫 평점을 남겨 보세요.
5
0
4
0
3
0
2
0
1
0

평점과 댓글을 남기려면 로그인하세요.

댓글 0개

아직 댓글이 없어요. 이 모델을 어떻게 사용했는지, 어떤 점이 더 나아질 수 있을지 공유해 주세요.

관련 모델

같은 카테고리 또는 비슷한 태그