티스토리 뷰

반응형

three.js ExtrudeGeometry I형 거더 단면 3D 모델링 교량 BIM 바이브코딩 5편 대표 이미지

1~3편의 교량은 거더를 직사각형 박스(BoxGeometry)로 단순화했다. 형상을 확인하는 데는 충분하지만, 구조 엔지니어 눈에는 아무래도 어색하다. 실제 강거더는 상부플랜지, 복부, 하부플랜지로 이루어진 I형 단면이기 때문이다.

5편에서는 three.js의 ExtrudeGeometry로 I형 거더를 만든다. 단면을 점으로 그린 뒤 길이 방향으로 밀어내는 방식이라, 구조해석 프로그램에서 단면을 정의하고 부재 길이를 주는 것과 같은 흐름이다. 단면 치수를 바꾸면 단면적, 도심, 단면2차모멘트가 바로 계산되는 데모까지 만든다.

 

이번 편에서 만들 것
 

▲ 실제로 동작하는 데모. 슬라이더로 단면 치수를 바꾸면 3D 모델, 단면 성질, 그래프가 함께 바뀐다.

 

○ 복부 높이를 키워보면 단면2차모멘트(주황 실선)는 빠르게 커지는데 강재 중량(파랑 점선)은 완만하게 늘어난다. 거더를 깊게 만드는 것이 휨 강성 면에서 효율적인 이유다.

○ 다만 복부가 얇고 높아지면 좌굴 검토가 필요하다. 실제 설계에서는 판폭두께비 기준과 수직보강재를 함께 확인해야 한다.

 

ExtrudeGeometry란

○ 2D 단면(Shape)을 한 방향으로 밀어내서 3D 형상을 만드는 three.js 형상이다. 쿠키 틀로 반죽을 찍어내는 것과 비슷하다.

○ I형, 박스형, T형처럼 단면이 일정한 부재는 모두 이 방식으로 만들 수 있다.

 

three.js ExtrudeGeometry Shape 압출 회전 3단계

 

단계 코드 하는 일
① 단면 그리기 new THREE.Shape() XY 평면에 단면 윤곽을 점으로 그림
② 밀어내기 new THREE.ExtrudeGeometry(shape, { depth: L }) 단면을 Z축 방향으로 L만큼 밀어냄
③ 방향 맞추기 geo.rotateY(Math.PI / 2) Z축 방향 부재를 X축(교축) 방향으로 돌림

 

I형 단면을 점으로 그리기

○ 단면 원점을 하부플랜지 하단 중앙에 두고, 12개 꼭짓점을 반시계 방향으로 차례로 잇는다.

○ 치수는 도면 그대로 mm로 입력하고, 그릴 때 0.001을 곱해 m로 바꾼다. 이렇게 하면 도면 치수를 옮겨 적을 때 실수가 줄어든다.

 

I형 거더 단면 12개 꼭짓점 번호와 좌표

 

// I형 단면 (입력 mm → 모델 m)
function iSection({ bt, tt, hw, tw, bb, tb }) {
  const m = 0.001;                       // mm → m 변환
  const H = tb + hw + tt;                // 전체 높이
  const s = new THREE.Shape();
  s.moveTo(-bb / 2 * m, 0);               // ① 하부플랜지 좌하단
  s.lineTo( bb / 2 * m, 0);               // ②
  s.lineTo( bb / 2 * m, tb * m);          // ③
  s.lineTo( tw / 2 * m, tb * m);          // ④ 복부 시작
  s.lineTo( tw / 2 * m, (tb + hw) * m);   // ⑤
  s.lineTo( bt / 2 * m, (tb + hw) * m);   // ⑥ 상부플랜지
  s.lineTo( bt / 2 * m, H * m);           // ⑦
  s.lineTo(-bt / 2 * m, H * m);           // ⑧
  s.lineTo(-bt / 2 * m, (tb + hw) * m);   // ⑨
  s.lineTo(-tw / 2 * m, (tb + hw) * m);   // ⑩
  s.lineTo(-tw / 2 * m, tb * m);          // ⑪
  s.lineTo(-bb / 2 * m, tb * m);          // ⑫
  s.closePath();                          // ①로 닫기
  return s;
}

 

Shape를 그릴 때 주의할 점
항목 주의할 점
점 순서 반시계 방향으로 그린다. 시계 방향이면 면의 앞뒤가 뒤집혀 조명이 이상하게 보일 수 있다.
닫기 마지막에 closePath()로 첫 점과 이어준다.
중복 점 같은 좌표를 두 번 넣지 않는다. 길이 0인 변이 생겨 모서리 선이 지저분해진다.
단위 Shape 좌표가 곧 모델 좌표다. mm 그대로 넣으면 1000배 크기가 된다.

 

압출 옵션
옵션 기본값 거더 설정 의미
depth 1 거더 길이 (예: 12) 밀어내는 길이
bevelEnabled true false 모서리 둥글리기. 강재 단면은 직각이므로 끈다
steps 1 1 길이 방향 분할 수. 직선 부재는 1이면 충분
curveSegments 12 그대로 단면에 곡선이 있을 때 분할 수
extrudePath 없음 8편에서 사용 직선 대신 곡선을 따라 밀어내기 (곡선교)

 

○ bevelEnabled의 기본값이 true라는 점을 놓치기 쉽다. 끄지 않으면 플랜지 모서리가 둥글게 부풀어서 단면 치수가 달라진다.

const geo = new THREE.ExtrudeGeometry(iSection(sec), {
  depth: L,             // 거더 길이
  bevelEnabled: false,  // 모서리 둥글리기 끔
});

 

방향과 위치 맞추기

○ ExtrudeGeometry는 항상 +Z 방향으로 밀어낸다. 교량은 X축이 교축 방향이므로 돌려줘야 한다.

순서 코드 결과
1 geo.translate(0, 0, -L / 2) 길이 방향 중앙이 원점에 오도록 이동
2 geo.rotateY(Math.PI / 2) Y축 기준 90° 회전 → +Z 방향 부재가 +X 방향으로
3 mesh.position.set(0, pierH, z) 교각 높이, 거더 위치로 배치

 

○ 순서가 중요하다. 회전을 먼저 하고 이동하면 이동 방향도 함께 돌아가서 엉뚱한 위치로 간다. 중앙 맞추기 → 회전 → 배치 순서로 한다.

 

단면 성질 계산

○ three.js는 형상만 그려줄 뿐 단면 성질을 계산하지 않는다. 데모의 단면 성질은 판 3개(하부플랜지, 복부, 상부플랜지)로 나눠서 직접 계산한 값이다.

예제 단면(상부플랜지 400 × 25, 복부 1,750 × 12, 하부플랜지 500 × 30, 단위 mm)으로 계산하면 다음과 같다.

판 b × t A (mm²) y (mm) A·y (mm³) I₀ = bt³/12 A·d²
하부플랜지 500 × 30 15,000 15.0 225,000 0.001 × 10⁹ 9.426 × 10⁹
복부 12 × 1,750 21,000 905.0 19,005,000 5.359 × 10⁹ 0.199 × 10⁹
상부플랜지 400 × 25 10,000 1,792.5 17,925,000 0.001 × 10⁹ 9.698 × 10⁹
합계   46,000   37,155,000 5.361 × 10⁹ 19.323 × 10⁹

 

     도심 ȳ = ΣA·y / ΣA = 37,155,000 / 46,000 = 807.7 mm (하단 기준)

     단면2차모멘트 I = ΣI₀ + ΣA·d² = 5.361 + 19.323 = 24.684 × 10⁹ mm⁴

     단면계수 St = I / (1,805 − 807.7) = 24.75 × 10⁶ mm³, Sb = I / 807.7 = 30.56 × 10⁶ mm³

 

○ d는 각 판의 도심에서 전체 도심까지 거리다. 플랜지는 자체 I₀는 거의 0이지만 도심에서 멀리 떨어져 있어서 A·d² 항이 전체 강성의 대부분을 차지한다. I형 단면이 휨에 효율적인 이유다.

// 단면 성질 (mm 단위) - 판 3개로 나눠 계산
function props({ bt, tt, hw, tw, bb, tb }) {
  const parts = [            // [폭, 두께, 판 도심의 높이]
    [bb, tb, tb / 2],
    [tw, hw, tb + hw / 2],
    [bt, tt, tb + hw + tt / 2],
  ];
  const A  = parts.reduce((s, [b, h]) => s + b * h, 0);
  const yb = parts.reduce((s, [b, h, y]) => s + b * h * y, 0) / A;
  const I  = parts.reduce((s, [b, h, y]) => s + b * h ** 3 / 12 + b * h * (y - yb) ** 2, 0);
  return { A, yb, I };
}

 

전체 코드

○ 메모장에 붙여넣고 girder5.html로 저장한 뒤 더블클릭하면 실행된다. 맨 위 sec 값만 바꾸면 다른 단면이 된다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>three.js I형 거더 - ExtrudeGeometry</title>
  <style>
    body { margin: 0; overflow: hidden; }
  </style>
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/"
      }
    }
  </script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 1. 장면, 카메라, 렌더러
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xdfe8f0);
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 500);
camera.position.set(11.5, 2.6, 3.2);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

scene.add(new THREE.HemisphereLight(0xffffff, 0x8899aa, 1.0));
const sun = new THREE.DirectionalLight(0xffffff, 2.0);
sun.position.set(14, 12, 6);
scene.add(sun);

// 2. 단면 치수 (단위: mm) - 구조 도면 단위 그대로 입력
const sec = {
  bt: 400, tt: 25,    // 상부플랜지 폭, 두께
  hw: 1750, tw: 12,   // 복부 높이, 두께
  bb: 500, tb: 30,    // 하부플랜지 폭, 두께
};
const L = 12;         // 거더 길이 (m)

// 3. I형 단면 그리기 (Shape) - 하부플랜지 하단 중앙이 원점, 반시계 방향 12개 점
function iSection({ bt, tt, hw, tw, bb, tb }) {
  const m = 0.001;                       // mm → m 변환
  const H = tb + hw + tt;
  const s = new THREE.Shape();
  s.moveTo(-bb / 2 * m, 0);               // ①
  s.lineTo( bb / 2 * m, 0);               // ②
  s.lineTo( bb / 2 * m, tb * m);          // ③
  s.lineTo( tw / 2 * m, tb * m);          // ④
  s.lineTo( tw / 2 * m, (tb + hw) * m);   // ⑤
  s.lineTo( bt / 2 * m, (tb + hw) * m);   // ⑥
  s.lineTo( bt / 2 * m, H * m);           // ⑦
  s.lineTo(-bt / 2 * m, H * m);           // ⑧
  s.lineTo(-bt / 2 * m, (tb + hw) * m);   // ⑨
  s.lineTo(-tw / 2 * m, (tb + hw) * m);   // ⑩
  s.lineTo(-tw / 2 * m, tb * m);          // ⑪
  s.lineTo(-bb / 2 * m, tb * m);          // ⑫
  s.closePath();
  return s;
}

// 4. 단면을 길이 방향으로 밀어내기 (Extrude)
const geo = new THREE.ExtrudeGeometry(iSection(sec), {
  depth: L,            // 압출 길이 = 거더 길이
  bevelEnabled: false, // 모서리 둥글리기 끔 (강재 단면은 직각)
});
geo.translate(0, 0, -L / 2);   // 길이 방향 중앙을 원점으로
geo.rotateY(Math.PI / 2);      // Z축 방향 압출 → X축(교축 방향)으로 돌리기

const steel = new THREE.MeshStandardMaterial({ color: 0x3f6e9e, metalness: 0.3, roughness: 0.5 });
const girder = new THREE.Mesh(geo, steel);
scene.add(girder);

// 모서리 선 (도면 느낌)
const edges = new THREE.LineSegments(new THREE.EdgesGeometry(geo), new THREE.LineBasicMaterial({ color: 0x1d3d5e }));
scene.add(edges);

scene.add(new THREE.GridHelper(20, 20, 0x99a3ad, 0xc9d1d9));

// 5. 마우스 조작
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(3, 0.9, 0);
controls.enableDamping = true;
controls.update();

renderer.setAnimationLoop(() => { controls.update(); renderer.render(scene, camera); });
addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>

 

교량에 적용하면

○ 1편 교량의 박스 거더를 I형 거더로 바꾸면 이렇게 달라진다. 아래쪽에서 보면 하부플랜지와 복부가 드러나 실제 강거더교에 훨씬 가까워진다.

 

BoxGeometry 거더 교량과 ExtrudeGeometry I형 거더 교량 비교

 

○ 거더 3개를 배치하고 바닥판, 교각, 받침까지 조립하는 과정은 7편에서 다룬다.

 

Claude에게 이렇게 요청했다

three.js로 I형 강거더 하나를 만들어줘.

- HTML 파일 하나, three.js 0.186.1 CDN importmap

- 단면 치수는 mm로 입력: 상부플랜지 400×25, 복부 1750×12, 하부플랜지 500×30

- THREE.Shape로 단면을 그리고 ExtrudeGeometry로 길이 12 m 압출, bevel 끔

- 단면 원점은 하부플랜지 하단 중앙, 점은 반시계 방향, 점마다 번호 주석

- 모델 단위는 m (mm × 0.001), 거더 길이 방향은 X축

- 모서리 선 표시, OrbitControls

- 단면적, 도심, 단면2차모멘트를 계산하는 함수도 따로

 

○ "점마다 번호 주석"을 요청하면 단면 그림과 코드를 대조하기 쉬워진다. 모양이 이상할 때 몇 번 점이 틀렸는지 바로 찾을 수 있다.

 

자주 겪는 문제
증상 원인 해결
거더가 세로로 서 있거나 옆으로 누움 회전 누락 또는 축 착각 geo.rotateY(Math.PI / 2) 확인
거더가 1000배 크기 mm 값을 그대로 Shape에 넣음 0.001을 곱해 m로 변환
모서리가 둥글고 치수가 커짐 bevelEnabled 기본값 true bevelEnabled: false
면이 어둡거나 안쪽이 보임 점을 시계 방향으로 그림 반시계 방향으로 다시 그리기
거더가 한쪽으로 치우침 중앙 맞추기 전에 회전 translate → rotate 순서로
단면 모양이 이상함 점 좌표 오타 번호 주석과 단면 그림 대조

 

정리

○ ExtrudeGeometry는 Shape(단면) → 압출(길이) → 회전(방향) 3단계로 부재를 만든다.

○ 단면은 원점을 정하고 반시계 방향으로 점을 잇는다. 입력은 mm, 모델은 m.

○ bevelEnabled: false를 잊지 말 것.

○ three.js는 형상만 그린다. 단면 성질은 판으로 나눠 직접 계산한다.

 

다음 편 예고

6편에서는 지금 데모처럼 슬라이더로 치수를 바꾸면 교량 전체가 바뀌는 파라메트릭 모델을 만든다. 경간, 폭, 거더 개수, 교각 높이를 입력값으로 묶고, 바뀐 값에 맞춰 모델을 다시 그리는 구조를 다룬다.

 

관련 글

     → [교량 BIM 바이브코딩 1편] three.js 입문: Claude 바이브코딩으로 3D 교량 만들기

     → [교량 BIM 바이브코딩 2편] three.js 기초: 장면·카메라·렌더러

     → [교량 BIM 바이브코딩 3편] OrbitControls·조명·그림자

     → [교량 BIM 바이브코딩 4편] AI 코딩 프롬프트 작성법: Claude에게 3D 모델 정확히 요청하기

     → [교량 BIM 바이브코딩 6편] three.js 파라메트릭 모델링: 슬라이더로 바뀌는 교량

     → [교량 BIM 바이브코딩 7편] three.js 교량 BIM 모델: 클릭하면 속성이 나오는 부재 조립

     → [교량 BIM 바이브코딩 8편] three.js 곡선교 모델링: 곡선 반경 R과 편경사로 휘는 교량

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/10   »
일 월 화 수 목 금 토
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31