황현동 블로그 개발, 인생, 유우머

260219 통합보고서 파트3 Bullet LayoutVLM

Tags:

🧠 3D 간섭 해결 핵심 기술 통합 보고서 (파트 3): Bullet Physics, LayoutVLM

📝 작성일: 2026-02-19
📝 문서 유형: 통합 기술 보고서
📝 원본 문서: 4건의 개별 리서치 문서에서 Bullet Physics, LayoutVLM 관련 내용을 수집하여 재구성


📚 목차


🧠 파트 5: Bullet Physics

🧭 5.1 소개

🧭 5.1.1 Bullet Physics란 무엇인가?

Bullet Physics는 실시간 물리 시뮬레이션 라이브러리이다. 쉽게 말해, 컴퓨터 안에서 물체들이 “진짜처럼” 움직이고, 부딪히고, 튕기고, 무너지는 것을 계산해주는 프로그램이다.

쉬운 비유:

  현실 세계                          컴퓨터 세계
  +-----------+                     +-----------+
  | 공을 던지면 |  --- Bullet --->  | 공이 포물선을 |
  | 바닥에 튕긴다|  Physics가 계산   | 그리며 튕긴다  |
  +-----------+                     +-----------+

  즉, "가상 세계의 물리 법칙"을 만들어주는 엔진이다.

Bullet Physics가 하는 일을 크게 세 가지로 나누면 다음과 같다.

기능 설명 비유
충돌 감지 (Collision Detection) 두 물체가 닿았는지 판단 “이 두 상자가 겹쳤나?”
강체 역학 (Rigid Body Dynamics) 딱딱한 물체의 움직임 계산 당구공이 부딪혀 튕기는 것
연체 역학 (Soft Body Dynamics) 말랑한 물체의 움직임 계산 젤리가 흔들리는 것

🕰️ 5.1.2 역사와 개발자

  • 개발자: Erwin Coumans (에르빈 쿠만스)
  • 시작: 2003년, Sony Computer Entertainment에서 PlayStation 2 게임을 위한 사내 물리 엔진으로 출발
  • 오픈소스 전환: 2005년, zlib 라이선스로 오픈소스 공개
  • 수상: Erwin Coumans는 Bullet Physics에 대한 공로로 아카데미 과학기술상 (Scientific and Technical Academy Award)을 수상
Erwin Coumans의 경력 타임라인:

  2003         2010         2014         2022         현재
  |------------|------------|------------|------------|
  Sony CEA     AMD          Google       NVIDIA
  (Bullet 개발  (GPU 물리    (TensorFlow  (로봇/AI
   시작)        가속 연구)    PyBullet)    물리 연구)

▫️ 5.1.3 오픈소스 라이선스 (zlib)

Bullet Physics는 zlib 라이선스를 사용한다. 이것은 매우 자유로운 라이선스로, 핵심 포인트는 다음과 같다.

항목 허용 여부
상업적 사용 허용
소스코드 수정 허용
재배포 허용
소스코드 공개 의무 없음 (copyleft 아님)
원작자 표기 필요 (수정본을 원본인 척 하면 안 됨)

✨ 즉, “마음대로 쓰되, 원작자를 속이지 마라”가 핵심이다. 게임 회사든 영화 스튜디오든 무료로 사용 가능하다.

▫️ 5.1.4 어떤 분야에서 사용되는가?

+--------------------------------------------------+
|              Bullet Physics 활용 분야              |
+--------------------------------------------------+
|                                                    |
|  [게임]          [영화/VFX]       [로봇공학]        |
|  GTA IV/V        Megamind         PyBullet         |
|  Red Dead         Shrek 4         강화학습          |
|  Redemption      Hollywood VFX    로봇 시뮬레이션   |
|                                                    |
|  [VR/AR]         [과학/공학]      [교육/연구]       |
|  가상현실         구조 해석         대학교 연구      |
|  시뮬레이션       충돌 분석         물리 교육        |
|                                                    |
|  [3D 소프트웨어]  [AI/ML]                           |
|  Maya, Blender   강화학습 환경                      |
|  Cinema 4D       OpenAI Gym                        |
|                                                    |
+--------------------------------------------------+

▫️ 5.1.5 유명 사용 사례

게임

  • Grand Theft Auto IV / V: Rockstar의 RAGE 엔진에 Bullet Physics가 통합되어 있다. 차량 충돌, 물체 파괴, 캐릭터 래그돌(ragdoll) 등에 사용된다.
  • Red Dead Redemption: 같은 RAGE 엔진 기반으로 Bullet Physics를 활용한다.
  • Midnight Club: Los Angeles: Rockstar와 Erwin Coumans가 공동 개발하며 Bullet의 일부 기능이 RAGE에 최적화되어 병합되었다.

영화 VFX

  • Megamind (2010, DreamWorks): 물리 시뮬레이션에 Bullet 사용
  • Shrek Forever After (2010, DreamWorks): 물리 효과에 Bullet 적용
  • 다수의 할리우드 영화에서 파편, 파괴, 유체 시뮬레이션 등에 활용

로봇공학 / AI

  • PyBullet: Python 바인딩을 통한 로봇 시뮬레이션 환경 제공
  • OpenAI Gym 환경: 강화학습(Reinforcement Learning) 훈련 환경으로 사용
  • Google Research: TensorFlow와 연동한 로봇 학습 연구에 활용

3D 소프트웨어

  • Autodesk Maya: Bullet Physics 플러그인 내장
  • Blender: 물리 시뮬레이션 엔진으로 Bullet 사용
  • Cinema 4D: Dynamics 시스템에 Bullet 통합

✨ 5.2 특징

🏗️ 5.2.1 충돌 감지 파이프라인: Broadphase -> Narrowphase -> Solver

Bullet의 충돌 감지는 3단계 파이프라인으로 구성된다. 이것을 음식점에 비유하면 이해하기 쉽다.

비유: "대형 음식점에서 주문 처리하기"

  1단계 Broadphase: "어느 테이블 손님이 주문했지?" (대략적 파악)
  2단계 Narrowphase: "정확히 뭘 주문했지?" (정밀 확인)
  3단계 Dispatch:    "요리사에게 전달!" (적절한 처리기로 연결)

(A) Broadphase - 대략적 충돌 후보 탐색

목적: 수천 개의 물체 중에서 “충돌할 가능성이 있는 쌍”만 빠르게 걸러내기

예시: 물체가 100개 있을 때

  모든 쌍을 비교하면: 100 x 99 / 2 = 4,950번 검사 (너무 많다!)
  Broadphase 적용 후:  실제 근처에 있는 ~50쌍만 검사 (빠르다!)

Bullet이 제공하는 Broadphase 알고리즘:

알고리즘 설명 특징
DBVT (Dynamic BVH Tree) 동적 바운딩 볼륨 계층 트리 기본값, 동적 장면에 최적
SAP (Sweep and Prune) 축 정렬 스캔 정적 장면에 효율적
Simple Broadphase 단순 전수 비교 디버깅/학습용

DBVT의 동작 원리 (가장 많이 쓰이는 방식):

DBVT = Dynamic Bounding Volume Hierarchy Tree

  각 물체를 AABB(축 정렬 경계 상자)로 감싸고,
  이진 트리 구조로 계층화한다.

              [전체 영역 AABB]
              /              \
     [왼쪽 그룹 AABB]    [오른쪽 그룹 AABB]
      /        \            /          \
   [A의 AABB] [B의 AABB] [C의 AABB] [D의 AABB]  <-- 리프 노드(실제 물체)

  장점: 물체가 움직여도 트리를 빠르게 업데이트할 수 있다.
        두 개의 트리를 운영한다:
        - 정적 물체용 트리 (한번 구성, 업데이트 거의 없음)
        - 동적 물체용 트리 (매 프레임 업데이트)

(B) Narrowphase - 정밀 충돌 판정

목적: Broadphase가 걸러준 후보 쌍에 대해 “진짜로 충돌하는가?”를 정밀하게 판정

알고리즘 용도 설명
GJK (Gilbert-Johnson-Keerthi) 볼록(Convex) 도형 간 거리/충돌 민코프스키 차(Minkowski Difference) 기반
EPA (Expanding Polytope Algorithm) GJK 후 관통 깊이 계산 GJK가 “충돌함”을 확인한 후 깊이를 산출
SAT (Separating Axis Theorem) 볼록 다면체 간 충돌 분리축이 없으면 충돌

GJK 알고리즘의 쉬운 비유:

GJK = "두 물체 사이에 종이를 끼울 수 있는가?"

  +-------+     +-------+
  |   A   |     |   B   |
  |       |     |       |
  +-------+     +-------+
       <---종이--->
  종이를 끼울 수 있다 = 충돌 안 함 (분리됨)

  +-------+
  |   A +-------+
  |     | B     |
  +-----+       |
        +-------+
  종이를 끼울 수 없다 = 충돌함 (겹침)

  GJK는 이것을 "민코프스키 차" 공간에서
  원점이 내부에 있는지 확인하는 방식으로 판단한다.

(C) Collision Dispatch - 충돌 처리 분배

Collision Dispatch는 물체 형상의 조합에 따라 적절한 충돌 알고리즘을 선택하는 역할을 한다.

형상 조합별 알고리즘 선택 (Dispatch Table):

  +-----------+--------+---------+----------+-----------+
  |           | Sphere | Box     | Convex   | Triangle  |
  +-----------+--------+---------+----------+-----------+
  | Sphere    | S-S    | S-Box   | GJK      | S-Tri     |
  | Box       |        | SAT     | GJK      | GJK       |
  | Convex    |        |         | GJK+EPA  | GJK       |
  | Triangle  |        |         |          | GJK       |
  +-----------+--------+---------+----------+-----------+

  S-S: 구-구 충돌 (거리 비교만으로 가능, 가장 빠름)
  SAT: 분리축 정리 (박스 간 최적화)
  GJK+EPA: 범용 볼록 도형 충돌 + 관통 깊이

▫️ 5.2.2 강체 역학 (Rigid Body Dynamics)

강체(Rigid Body)란 모양이 변하지 않는 딱딱한 물체를 말한다. 당구공, 벽돌, 자동차 차체 등이 강체이다.

강체의 속성:

  +------------------+
  | btRigidBody      |
  +------------------+
  | - mass (질량)     |  <-- 0이면 정적 물체(바닥 등)
  | - position (위치) |
  | - rotation (회전) |
  | - velocity (속도) |
  | - angular vel.   |  <-- 회전 속도
  | - friction (마찰) |
  | - restitution    |  <-- 탄성 (0=찰흙, 1=완벽한 공)
  | - inertia (관성)  |
  +------------------+

Bullet의 강체 시뮬레이션 루프:

매 프레임(1/60초)마다:

  1. 외력 적용 (중력, 사용자 힘 등)
         F = m * a  (뉴턴 제2법칙)
         |
         v
  2. 속도 갱신
         v_new = v_old + a * dt
         |
         v
  3. 충돌 감지 (Broadphase -> Narrowphase)
         "누구랑 부딪혔지?"
         |
         v
  4. 제약 조건 풀기 (Constraint Solver)
         "겹침을 어떻게 해결하지?"
         |
         v
  5. 위치 갱신
         pos_new = pos_old + v * dt
         |
         v
  6. 화면에 그리기

▫️ 5.2.3 연체 역학 (Soft Body Dynamics)

연체(Soft Body)란 모양이 변할 수 있는 말랑한 물체를 말한다. 천, 젤리, 풍선, 인체 조직 등이 연체이다.

연체의 구조 (Position Based Dynamics):

  노드(Node) = 점 질량
  링크(Link) = 노드 간 연결 (스프링처럼 작동)
  면(Face)   = 세 노드로 구성된 삼각형

       o-------o-------o
      / \     / \     / \
     /   \   /   \   /   \
    o-----o-o-----o-o-----o     <-- 메쉬처럼 연결된 점들
     \   / \ \   / \ \   /
      \ /   \ \ /   \ \ /
       o-------o-------o

  각 노드(o)는 독립적으로 움직이며,
  링크(-)가 너무 늘어나면 당기고,
  너무 줄어들면 밀어낸다.
  -> 이것이 말랑한 물체의 행동을 만든다.

Bullet 연체의 핵심 특징:

  • Thomas Jakobsen의 “Advanced Character Physics” 방법론 기반
  • Position Based Dynamics (PBD) 방식 사용
  • 연체끼리, 연체-강체 간 충돌 가능
  • 3가지 강성 계수(stiffness coefficient)로 물성 조절

⚠️ 5.2.4 제약 조건 (Constraints) 시스템

제약 조건(Constraint)이란 물체의 움직임에 규칙을 부여하는 것이다. 현실에서 경첩, 축, 모터 등이 제약 조건이다.

Bullet이 지원하는 제약 조건 종류:

  1. Point-to-Point (볼 조인트)
     o----o    두 점이 연결됨 (팔의 어깨 관절)

  2. Hinge (경첩)
     |    |
     o====o    한 축으로만 회전 (문의 경첩)
     |    |

  3. Slider (슬라이더)
     o========o    한 축으로만 이동 (서랍)

  4. ConeTwist (원뿔 비틀림)
       /|\
      / | \
     o--+--o    제한된 각도로 회전 (목 관절)

  5. Generic 6DOF (6자유도)
     X/Y/Z 이동 + X/Y/Z 회전 = 6개 축 각각 제한 가능
     (가장 범용적, 위의 모든 조인트를 흉내낼 수 있음)

  6. Fixed (고정)
     두 물체를 완전히 붙여놓기 (용접)

🏗️ 5.2.5 Solver 구조 (Sequential Impulse Solver)

Bullet의 핵심 Solver인 Sequential Impulse Solver는 물체 간의 겹침과 제약 조건을 해결하는 알고리즘이다.

쉬운 비유: "만원 지하철에서 사람들이 자리 잡기"

  상황: 사람들이 너무 많아서 서로 겹쳐 있다.
  해결: 각 사람이 옆 사람을 조금씩 밀어낸다.
        이것을 여러 번 반복하면 결국 모두가 적절한 자리를 찾는다.

  Sequential Impulse = "한 쌍씩 순서대로, 살짝 밀어내기를 반복"

기술적 설명:

Sequential Impulse Solver는 수학적으로 Projected Gauss-Seidel 방법의 행렬 없는(matrix-free) 구현이다.

Sequential Impulse Solver 동작 흐름:

  입력: 충돌 접촉점들의 목록 (contact manifold)
  |
  v
  각 접촉점마다:
  +------------------------------------------+
  | 1. 비관통 제약 (Non-penetration)          |
  |    "물체가 서로 뚫고 들어가면 안 된다"     |
  |                                          |
  | 2. 마찰 제약 (Friction) x 2              |
  |    "미끄러지지 않게 하는 힘" (접선 방향 2개)|
  +------------------------------------------+
  |
  v
  반복 (iterations, 기본 10회):
  +------------------------------------------+
  | for each contact:                         |
  |   - 현재 상대 속도 계산                    |
  |   - 필요한 충격량(impulse) 계산            |
  |   - 충격량 적용 (속도 변경)                |
  |   - 충격량 클램핑 (음수 방지)              |
  +------------------------------------------+
  |
  v
  수렴: 반복할수록 정확한 해에 수렴

각 접촉점에서의 제약 해결에는 다음 정보가 사용된다:

  • 야코비안 (Jacobian): 제약의 기하학적 방향
  • 유효 질량 (Effective Mass): 충돌에 관여하는 실효 질량
  • Baumgarte 안정화: 위치 오차를 속도로 보상하여 관통 복구

🧠 5.2.6 지원 충돌 형상

Bullet이 지원하는 충돌 형상 (Collision Shapes):

  기본 도형 (Primitive):
  +----------+   +----+   +------+   +---------+
  | Sphere   |   | Box|   |Cylind|   | Capsule |
  |    O     |   |    |   |  ||  |   |   ()    |
  |          |   |    |   |  ||  |   |   ||    |
  +----------+   +----+   +------+   +---------+

  볼록 도형 (Convex):
  +----------------+   +------------------+
  | ConvexHull     |   | MultiSphere      |
  |   /\           |   |  O O             |
  |  /  \          |   |   O              |
  | /____\         |   |  O O             |
  +----------------+   +------------------+

  오목/복합 도형 (Concave / Compound):
  +------------------+   +------------------+
  | TriangleMesh     |   | CompoundShape    |
  | /\/\/\/\         |   |  [Box]+[Sphere]  |
  | \/\/\/\/         |   |  복합 형상 조합   |
  +------------------+   +------------------+

  특수 도형:
  +------------------+   +------------------+
  | StaticPlane      |   | HeightfieldTerrain|
  | ________________ |   | /\/\_/\/\        |
  | (무한 평면)      |   | (지형)            |
  +------------------+   +------------------+
형상 클래스 용도 성능
Sphere btSphereShape 공, 입자 가장 빠름
Box btBoxShape 상자, 벽 매우 빠름
Cylinder btCylinderShape 파이프, 기둥 빠름
Capsule btCapsuleShape 캐릭터, 손가락 빠름
ConvexHull btConvexHullShape 임의 볼록 형상 보통
TriangleMesh btBvhTriangleMeshShape 정적 지형/건물 느림(정적만)
CompoundShape btCompoundShape 복합 형상 조합 구성에 따라

🧠 5.2.7 PyBullet (Python 바인딩)

PyBullet은 Bullet Physics의 Python 바인딩으로, C++을 몰라도 Python만으로 물리 시뮬레이션을 할 수 있게 해준다.

PyBullet의 주요 기능:

  +-------------------------------------------------+
  | PyBullet                                         |
  +-------------------------------------------------+
  | - URDF/SDF/MJCF 로봇 모델 로딩                   |
  | - 순방향/역방향 운동학 (FK/IK)                    |
  | - 순방향/역방향 동역학                            |
  | - 충돌 감지 및 광선 교차 쿼리                     |
  | - OpenGL 기반 실시간 시각화                       |
  | - OpenAI Gym 호환 강화학습 환경                   |
  | - TensorFlow/PyTorch 연동 가능                   |
  | - 멀티바디 시뮬레이션                             |
  +-------------------------------------------------+

  설치: pip install pybullet

PyBullet 기본 사용 패턴:

import pybullet as p
import pybullet_data

# 1. 물리 서버 연결
physicsClient = p.connect(p.GUI)  # GUI 모드 (시각화)

# 2. 기본 설정
p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setGravity(0, 0, -9.81)  # 중력 설정

# 3. 바닥과 물체 로딩
planeId = p.loadURDF("plane.urdf")
robotId = p.loadURDF("r2d2.urdf", [0, 0, 1])

# 4. 시뮬레이션 루프
for i in range(10000):
    p.stepSimulation()

▫️ 5.2.8 라이브러리별 간섭검출/MTV/자동해결 포함 여부

라이브러리 간섭검출 MTV/침투깊이 성격 데이터 자동 간섭 해결(동역학)
Bullet Physics O O (contact normal, distance/penetration, impulse 등) O (solver가 contact constraint 해소)
CGAL O (교차/불린/코어파인 등 기하 연산) △ (직접 MTV API 중심 아님, 기하 연산 결과 기반으로 사용자 계산) X (물리 solver 없음)
FCL O O (contact normal/point/depth, min distance) X (결과 반환 라이브러리, 자동 동역학 해소는 외부에서)

Bullet Physics 해석:

  • Bullet은 “검출 + 물리응답” 엔진이다.
  • 좁은 의미의 MTV 벡터를 단일 API로 주기보다,
    • contact normal
    • penetration(depth)
    • solver impulse
      를 제공하고 solver 단계에서 분리를 수행한다.

MTV 유사 벡터 계산 (PyBullet):

# cp.contactDistance < 0 이면 penetration
# cp.contactNormalOnB 는 분리 방향 중 하나
mtv = (-cp.contactDistance) * cp.contactNormalOnB  # 수동 분리시 사용 가능

오픈소스 라이브러리로서의 Bullet 주요 특징:

주요 특징:
- GJK 기반 볼록 형상 충돌 감지
- AABB 트리 기반 브로드페이즈
- 강체/연체/차량 시뮬레이션
- Python (PyBullet), C++ API 제공
- 로봇공학, 게임, VR/AR에 널리 사용

핵심 API 예시 (C++):

// 충돌 세계 초기화
btDefaultCollisionConfiguration* config = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(config);
btBroadphaseInterface* broadphase = new btDbvtBroadphase();  // DBVT = Dynamic BVH
btCollisionWorld* collisionWorld = new btCollisionWorld(dispatcher, broadphase, config);

// 충돌 형상 추가 (박스 = BIM 빔)
btBoxShape* beamShape = new btBoxShape(btVector3(0.5, 2.0, 0.5));  // 반폭 단위
btCollisionObject* beamObj = new btCollisionObject();
beamObj->setCollisionShape(beamShape);
beamObj->setWorldTransform(btTransform(btQuaternion::getIdentity(), btVector3(0, 0, 0)));
collisionWorld->addCollisionObject(beamObj);

// 충돌 감지 실행
collisionWorld->performDiscreteCollisionDetection();

// 결과 확인
int numManifolds = collisionWorld->getDispatcher()->getNumManifolds();
for (int i = 0; i < numManifolds; i++) {
    btPersistentManifold* manifold = dispatcher->getManifoldByIndexInternal(i);
    // 접촉 정보 처리
}

지원 충돌 형상:

- 구(Sphere), 박스(Box), 원통(Cylinder), 원뿔(Cone)
- 볼록 껍질(ConvexHull) - GJK 기반
- 삼각 메쉬(TriangleMesh) - 정적 형상
- 복합 형상(CompoundShape) - 여러 형상 조합

⚠️ 5.3 장단점

✅ 5.3.1 장점 상세

장점 상세 설명
무료/오픈소스 zlib 라이선스로 상업적 사용 포함 완전 무료. 소스코드 수정/재배포 자유.
크로스 플랫폼 Windows, Linux, macOS, iOS, Android, PlayStation, Xbox 등 대부분의 플랫폼 지원
검증된 안정성 GTA 시리즈, 할리우드 영화 등에서 검증. 아카데미 상 수상.
범용성 게임, 영화, 로봇공학, VR, 과학 시뮬레이션 등 폭넓은 분야에서 사용
풍부한 기능 강체, 연체, 제약 조건, 캐릭터 컨트롤러, 차량 역학 등 포괄적 물리 기능
PyBullet Python 바인딩으로 빠른 프로토타이핑과 AI/ML 연구에 적합
GPU 가속 OpenCL 기반 GPU 물리 시뮬레이션 지원
커뮤니티 활발한 포럼, GitHub, 수많은 튜토리얼과 예제 존재

⚠️ 5.3.2 단점 상세

단점 상세 설명
정밀도 한계 실시간 게임용 설계라 과학/공학용 정밀 시뮬레이션에는 한계가 있다
학습 곡선 C++ API가 복잡하고, 물리 개념 이해가 선행되어야 한다
문서화 부족 공식 문서가 충분하지 않다. 소스코드를 직접 읽어야 하는 경우가 많다
튜닝 난이도 Baumgarte 파라미터, solver iteration 수 등 미세 튜닝이 어렵다
연체 역학 한계 연체 시뮬레이션은 전문 연체 엔진(FEM 기반)에 비해 제한적이다
유지보수 우려 메인 개발자 1인 중심 프로젝트라 장기 유지보수에 대한 우려가 있다
디버깅 난이도 물리 시뮬레이션 버그는 재현하기 어렵고, 시각적 디버깅 도구가 제한적이다

⚖️ 5.3.3 다른 물리 엔진과의 비교

+---------------------------------------------------------------------+
|             물리 엔진 비교 (Bullet vs PhysX vs Havok vs ODE)         |
+---------------------------------------------------------------------+

  라이선스:
  Bullet   [===== zlib (완전 자유) =====]
  PhysX    [===== BSD-3 (오픈소스) =====]   (2018년 오픈소스 전환)
  Havok    [=== 상용 (유료) ===]
  ODE      [===== BSD/LGPL (자유) =====]

  성능 (일반적 평가):
  Bullet   [========]  좋음
  PhysX    [==========] 매우 좋음 (GPU 가속 최적화)
  Havok    [==========] 매우 좋음 (AAA 최적화)
  ODE      [======]    보통

  사용 편의성:
  Bullet   [======]    보통 (문서 부족)
  PhysX    [========]  좋음 (Unity/Unreal 내장)
  Havok    [========]  좋음 (전문 도구 제공)
  ODE      [=====]     보통

  커뮤니티/지원:
  Bullet   [========]  활발한 오픈소스 커뮤니티
  PhysX    [==========] NVIDIA 공식 지원
  Havok    [========]  Microsoft 공식 지원 (유료)
  ODE      [====]      소규모 커뮤니티
항목 Bullet PhysX Havok ODE
개발사 Erwin Coumans NVIDIA Microsoft Russell Smith
라이선스 zlib (무료) BSD-3 (무료) 상용 (유료) BSD/LGPL
GPU 가속 OpenCL CUDA (최적화) 제한적 없음
게임 엔진 통합 수동 Unity/Unreal 내장 별도 통합 수동
강체 역학 우수 우수 최우수 보통
연체 역학 지원 우수 지원 미지원
로봇공학 PyBullet 제한적 제한적 지원
주요 사용처 게임/영화/로봇 AAA 게임 AAA 게임 연구/시뮬

선택 가이드:

  • 인디 게임/연구/로봇공학 -> Bullet (무료, 범용, PyBullet)
  • Unity/Unreal 게임 개발 -> PhysX (엔진 내장)
  • AAA 대형 게임 -> Havok (최고 성능, 전문 지원)
  • 학술 연구/교육 -> Bullet 또는 ODE (오픈소스, 소스 분석 가능)

🧪 5.4 MTV 산출 예제 코드

🧭 5.4.1 MTV란 무엇인가?

MTV(Minimum Translation Vector)는 두 물체가 겹쳤을 때, 겹침을 해소하기 위해 최소한으로 움직여야 하는 방향과 거리를 뜻한다.

쉬운 비유: "두 사람이 겹쳐서 서 있을 때"

  겹침 전:           겹친 상태:            MTV로 해결:
  +---+   +---+     +---+                +---+   +---+
  | A |   | B |     | A-+-B |            | A | ->| B |
  +---+   +---+     +---+                +---+   +---+
                     <-d->                     <-MTV->
                     관통 깊이               최소 이동 벡터

  MTV = 방향(normal) x 깊이(depth)
  방향: A에서 B를 향하는 방향
  깊이: 얼마나 겹쳤는지의 크기

▫️ 5.4.2 ASCII 다이어그램으로 MTV 개념 시각화

3D 공간에서 두 박스의 충돌과 MTV:

  Y축
  ^
  |     +-------+
  |     |       |
  |     |   B   |
  |     |   +---+---+
  |     +---+-+ |   |
  |         | A |   |
  |         |   +---+
  |         +-------+
  +-------------------------> X축

  접촉점(Contact Point)에서의 정보:
  - contactNormal (n): B 표면의 법선 방향 (화살표 방향)
  - penetrationDepth (d): 겹침 깊이 (음수 값)

  MTV 계산:
  MTV = contactNormal * (-penetrationDepth)
      = n * |d|

  적용:
  B의 새 위치 = B의 현재 위치 + MTV
  (또는 A와 B 각각 MTV/2씩 반대 방향으로 이동)
더 구체적인 예시 (2D 단면):

  충돌 전:
       +------+      +------+
       |  A   |      |  B   |
       | (0,0)|      | (3,0)|
       +------+      +------+
         2x2           2x2

  충돌 상태 (B가 왼쪽으로 이동):
       +------+
       |  A +------+
       |    | B    |
       +----+      |
            +------+
       A(0,0)  B(1.5,0)
       겹침 = 0.5

  Contact Point 정보:
       normal  = (1, 0, 0)   -- X축 양의 방향 (A->B)
       depth   = -0.5        -- 음수 = 관통 중

  MTV 계산:
       MTV = normal * (-depth) = (1,0,0) * 0.5 = (0.5, 0, 0)

  해결 후:
       B의 새 위치 = (1.5, 0, 0) + (0.5, 0, 0) = (2.0, 0, 0)
       -> 겹침 해소!

🧠 5.4.3 Bullet Physics에서 MTV를 추출하는 원리

Bullet Physics에서는 btPersistentManifold(접촉 매니폴드)를 통해 접촉점 정보에 접근한다. 각 접촉점(btManifoldPoint)에는 다음 정보가 담겨 있다:

  • m_normalWorldOnB: 접촉 법선 (B 표면의 월드 좌표 법선)
  • getDistance(): 관통 깊이 (음수이면 겹침 상태)
  • m_positionWorldOnA, m_positionWorldOnB: 접촉 위치

MTV 계산 공식:

MTV = m_normalWorldOnB * (-getDistance())

🧪 5.4.4 C++ 예제 코드 (주석 상세)

// ============================================================
// Bullet Physics - MTV(Minimum Translation Vector) 산출 예제
// ============================================================
// 두 물체를 겹치게 배치하고, 충돌 감지 후
// Contact Point에서 MTV를 추출하는 방법을 보여준다.
// ============================================================

#include <btBulletCollisionCommon.h>
#include <btBulletDynamicsCommon.h>
#include <iostream>
#include <cmath>

int main() {
    // ========================================
    // 1단계: 물리 세계(World) 설정
    // ========================================

    // Broadphase: 대략적 충돌 후보 탐색 (DBVT 트리 사용)
    btDbvtBroadphase* broadphase = new btDbvtBroadphase();

    // 충돌 설정: 어떤 알고리즘으로 충돌을 처리할지 결정
    btDefaultCollisionConfiguration* collisionConfig =
        new btDefaultCollisionConfiguration();

    // Dispatcher: 형상 조합에 따라 알고리즘 분배
    btCollisionDispatcher* dispatcher =
        new btCollisionDispatcher(collisionConfig);

    // Solver: 제약 조건 해결기 (Sequential Impulse)
    btSequentialImpulseConstraintSolver* solver =
        new btSequentialImpulseConstraintSolver();

    // 이 모든 것을 합쳐서 물리 세계 생성
    btDiscreteDynamicsWorld* world =
        new btDiscreteDynamicsWorld(
            dispatcher, broadphase, solver, collisionConfig);

    // 중력 설정 (Y축 아래 방향, -9.81 m/s^2)
    world->setGravity(btVector3(0, -9.81, 0));

    // ========================================
    // 2단계: 충돌 형상 생성
    // ========================================

    // 박스 A: 가로 2m, 세로 2m, 깊이 2m (반크기 지정)
    btBoxShape* boxShapeA = new btBoxShape(btVector3(1.0, 1.0, 1.0));

    // 박스 B: 동일 크기
    btBoxShape* boxShapeB = new btBoxShape(btVector3(1.0, 1.0, 1.0));

    // ========================================
    // 3단계: 강체(Rigid Body) 생성 - 겹치게 배치
    // ========================================

    // --- 박스 A (원점에 배치) ---
    btDefaultMotionState* motionStateA =
        new btDefaultMotionState(
            btTransform(btQuaternion(0, 0, 0, 1),
                        btVector3(0, 0, 0)));  // 위치: (0, 0, 0)

    btScalar massA = 1.0;  // 질량 1kg
    btVector3 inertiaA(0, 0, 0);
    boxShapeA->calculateLocalInertia(massA, inertiaA);

    btRigidBody::btRigidBodyConstructionInfo
        rbInfoA(massA, motionStateA, boxShapeA, inertiaA);
    btRigidBody* bodyA = new btRigidBody(rbInfoA);

    // --- 박스 B (X축으로 1.5m 위치 -> 0.5m 겹침!) ---
    btDefaultMotionState* motionStateB =
        new btDefaultMotionState(
            btTransform(btQuaternion(0, 0, 0, 1),
                        btVector3(1.5, 0, 0)));  // 위치: (1.5, 0, 0)
    // A의 오른쪽 끝: x=1.0, B의 왼쪽 끝: x=0.5
    // 겹침 = 1.0 - 0.5 = 0.5m

    btScalar massB = 1.0;
    btVector3 inertiaB(0, 0, 0);
    boxShapeB->calculateLocalInertia(massB, inertiaB);

    btRigidBody::btRigidBodyConstructionInfo
        rbInfoB(massB, motionStateB, boxShapeB, inertiaB);
    btRigidBody* bodyB = new btRigidBody(rbInfoB);

    // 물리 세계에 추가
    world->addRigidBody(bodyA);
    world->addRigidBody(bodyB);

    // ========================================
    // 4단계: 충돌 감지 수행 (시뮬레이션 1스텝)
    // ========================================

    // performDiscreteCollisionDetection()을 호출하면
    // 충돌 감지만 수행한다 (물체를 이동시키지 않음).
    world->performDiscreteCollisionDetection();

    // ========================================
    // 5단계: Contact Manifold에서 MTV 추출
    // ========================================

    int numManifolds = dispatcher->getNumManifolds();
    std::cout << "=== MTV 추출 결과 ===" << std::endl;
    std::cout << "접촉 매니폴드 수: " << numManifolds << std::endl;

    for (int i = 0; i < numManifolds; i++) {
        // 매니폴드: 두 물체 사이의 접촉 정보 모음
        btPersistentManifold* manifold =
            dispatcher->getManifoldByIndexInternal(i);

        int numContacts = manifold->getNumContacts();
        std::cout << "\n매니폴드 #" << i
                  << " - 접촉점 수: " << numContacts << std::endl;

        for (int j = 0; j < numContacts; j++) {
            // 각 접촉점 정보 가져오기
            btManifoldPoint& cp = manifold->getContactPoint(j);

            // 관통 깊이: 음수이면 겹침 상태
            btScalar depth = cp.getDistance();

            // 접촉 법선: B 표면의 월드 좌표 법선
            btVector3 normal = cp.m_normalWorldOnB;

            // 접촉 위치
            btVector3 posOnA = cp.m_positionWorldOnA;
            btVector3 posOnB = cp.m_positionWorldOnB;

            std::cout << "\n  접촉점 #" << j << ":" << std::endl;
            std::cout << "    법선 (normal): ("
                      << normal.x() << ", "
                      << normal.y() << ", "
                      << normal.z() << ")" << std::endl;
            std::cout << "    관통 깊이 (depth): "
                      << depth << std::endl;

            // ================================
            // MTV 계산!
            // ================================
            if (depth < 0) {  // 음수 = 실제로 겹침
                // MTV = 법선 방향 * |관통 깊이|
                btVector3 mtv = normal * (-depth);

                std::cout << "    *** MTV (Minimum Translation Vector) ***"
                          << std::endl;
                std::cout << "    방향: ("
                          << mtv.x() << ", "
                          << mtv.y() << ", "
                          << mtv.z() << ")" << std::endl;
                std::cout << "    크기: "
                          << mtv.length() << "m" << std::endl;
                std::cout << "    의미: B를 이 벡터만큼 이동하면 겹침 해소"
                          << std::endl;
            } else {
                std::cout << "    (겹침 없음, 분리 거리: "
                          << depth << ")" << std::endl;
            }
        }
    }

    // ========================================
    // 6단계: 정리 (메모리 해제)
    // ========================================
    world->removeRigidBody(bodyA);
    world->removeRigidBody(bodyB);
    delete bodyA;
    delete bodyB;
    delete motionStateA;
    delete motionStateB;
    delete boxShapeA;
    delete boxShapeB;
    delete world;
    delete solver;
    delete dispatcher;
    delete collisionConfig;
    delete broadphase;

    return 0;
}

// 예상 출력:
// === MTV 추출 결과 ===
// 접촉 매니폴드 수: 1
//
// 매니폴드 #0 - 접촉점 수: 4
//
//   접촉점 #0:
//     법선 (normal): (1, 0, 0)
//     관통 깊이 (depth): -0.5
//     *** MTV (Minimum Translation Vector) ***
//     방향: (0.5, 0, 0)
//     크기: 0.5m
//     의미: B를 이 벡터만큼 이동하면 겹침 해소

🧪 5.4.5 PyBullet(Python) 예제 코드 (주석 상세)

# ============================================================
# PyBullet - MTV(Minimum Translation Vector) 산출 예제
# ============================================================
# 두 물체를 겹치게 배치하고, 충돌 감지 후
# Contact Point에서 MTV를 추출하는 방법을 보여준다.
# ============================================================

import pybullet as p
import pybullet_data
import math

def main():
    # ========================================
    # 1단계: 물리 서버 연결
    # ========================================
    # DIRECT: 시각화 없이 물리 계산만 수행 (빠름)
    # GUI로 바꾸면 시각화 가능: p.connect(p.GUI)
    physics_client = p.connect(p.DIRECT)

    # 기본 데이터 경로 설정 (URDF 파일 등)
    p.setAdditionalSearchPath(pybullet_data.getDataPath())

    # 중력 설정 (Z축 아래 방향)
    p.setGravity(0, 0, -9.81)

    # ========================================
    # 2단계: 충돌 형상 생성 및 배치
    # ========================================

    # 박스 A: 가로 2m, 세로 2m, 깊이 2m (반크기 지정)
    # 위치: 원점 (0, 0, 0)
    collision_shape_a = p.createCollisionShape(
        p.GEOM_BOX,
        halfExtents=[1.0, 1.0, 1.0]
    )
    body_a = p.createMultiBody(
        baseMass=1.0,                        # 질량 1kg
        baseCollisionShapeIndex=collision_shape_a,
        basePosition=[0, 0, 0]               # 원점에 배치
    )

    # 박스 B: 동일 크기
    # 위치: (1.5, 0, 0) -> X축으로 0.5m 겹침!
    collision_shape_b = p.createCollisionShape(
        p.GEOM_BOX,
        halfExtents=[1.0, 1.0, 1.0]
    )
    body_b = p.createMultiBody(
        baseMass=1.0,
        baseCollisionShapeIndex=collision_shape_b,
        basePosition=[1.5, 0, 0]             # 0.5m 겹치게 배치
    )

    # ========================================
    # 3단계: 충돌 감지 수행
    # ========================================

    # stepSimulation을 한 번 호출하여 충돌 감지 수행
    p.stepSimulation()

    # getContactPoints: 두 물체 사이의 접촉점 정보 가져오기
    contact_points = p.getContactPoints(body_a, body_b)

    # ========================================
    # 4단계: Contact Point에서 MTV 추출
    # ========================================

    print("=== MTV 추출 결과 ===")
    print(f"접촉점 수: {len(contact_points)}")

    for i, cp in enumerate(contact_points):
        # PyBullet 접촉점 튜플 구조:
        # cp[0]: contactFlag
        # cp[1]: bodyUniqueIdA
        # cp[2]: bodyUniqueIdB
        # cp[3]: linkIndexA
        # cp[4]: linkIndexB
        # cp[5]: positionOnA (월드 좌표)
        # cp[6]: positionOnB (월드 좌표)
        # cp[7]: contactNormalOnB (법선 벡터, B 기준)
        # cp[8]: contactDistance (양수=분리, 음수=관통)
        # cp[9]: normalForce (법선 방향 힘)

        pos_on_a = cp[5]           # A 위의 접촉 위치
        pos_on_b = cp[6]           # B 위의 접촉 위치
        contact_normal = cp[7]     # 접촉 법선 (B 기준)
        contact_distance = cp[8]   # 관통 깊이 (음수=겹침)
        normal_force = cp[9]       # 법선 힘

        print(f"\n  접촉점 #{i}:")
        print(f"    A 위 위치: ({pos_on_a[0]:.3f}, "
              f"{pos_on_a[1]:.3f}, {pos_on_a[2]:.3f})")
        print(f"    B 위 위치: ({pos_on_b[0]:.3f}, "
              f"{pos_on_b[1]:.3f}, {pos_on_b[2]:.3f})")
        print(f"    법선 (normal): ({contact_normal[0]:.3f}, "
              f"{contact_normal[1]:.3f}, {contact_normal[2]:.3f})")
        print(f"    관통 깊이: {contact_distance:.4f}")

        # ================================
        # MTV 계산!
        # ================================
        if contact_distance < 0:  # 음수 = 겹침 상태
            # MTV = 법선 방향 * |관통 깊이|
            mtv_x = contact_normal[0] * (-contact_distance)
            mtv_y = contact_normal[1] * (-contact_distance)
            mtv_z = contact_normal[2] * (-contact_distance)
            mtv_magnitude = math.sqrt(
                mtv_x**2 + mtv_y**2 + mtv_z**2
            )

            print(f"    *** MTV (Minimum Translation Vector) ***")
            print(f"    방향: ({mtv_x:.3f}, {mtv_y:.3f}, {mtv_z:.3f})")
            print(f"    크기: {mtv_magnitude:.3f}m")
            print(f"    의미: B를 이 벡터만큼 이동하면 겹침 해소")
        else:
            print(f"    (겹침 없음, 분리 거리: {contact_distance:.4f})")

    # ========================================
    # 5단계: getClosestPoints로도 확인 가능
    # ========================================

    print("\n\n=== getClosestPoints로 확인 ===")
    closest = p.getClosestPoints(
        bodyA=body_a,
        bodyB=body_b,
        distance=100.0  # 최대 탐색 거리
    )

    for i, cp in enumerate(closest):
        contact_normal = cp[7]
        contact_distance = cp[8]

        if contact_distance < 0:
            mtv_x = contact_normal[0] * (-contact_distance)
            mtv_y = contact_normal[1] * (-contact_distance)
            mtv_z = contact_normal[2] * (-contact_distance)
            print(f"  MTV #{i}: ({mtv_x:.3f}, {mtv_y:.3f}, {mtv_z:.3f})")

    # 정리
    p.disconnect()

if __name__ == "__main__":
    main()

🔹 5.5 자동 간섭 해결

▫️ 5.5.1 Sequential Impulse Solver의 동작 원리 (아주 쉬운 비유)

비유: "눌린 스프링이 원래대로 돌아가는 것"

  상황: 두 물체가 겹쳐 있다.

  1. 겹침 감지
     +----+----+
     | A  | B  |    "어, 이 둘이 겹쳐 있네!"
     +----+----+

  2. 스프링처럼 밀어내기 (충격량/Impulse 적용)
     +----+ -> <- +----+
     | A  |       | B  |    "살짝 밀어내자!"
     +----+       +----+

  3. 반복 (여러 번)
     +----+   +----+
     | A  |   | B  |    "이제 떨어졌다!"
     +----+   +----+

  핵심: Solver는 각 접촉점에 대해 "충격량(impulse)"을 계산하고,
        물체의 속도를 변경하여 겹침을 해소한다.
        이것을 여러 번 반복(iteration)하여 정확도를 높인다.
더 구체적인 비유: "만원 엘리베이터"

  10명이 엘리베이터에 타려고 하는데 6명만 들어간다.

  1회차: 앞의 2명이 뒤의 2명을 밀어냄
  2회차: 밀려난 2명이 옆의 2명을 밀어냄
  3회차: 옆의 2명이 밖의 2명을 밀어냄
  ...
  N회차: 모든 사람이 적절한 위치를 찾음

  Bullet의 기본 iteration = 10회
  iteration이 많을수록 정확하지만 느려진다.

🧭 5.5.2 Contact Constraint란 무엇인가?

Contact Constraint(접촉 제약)란 “두 물체가 서로 뚫고 들어가면 안 된다”는 규칙이다.

Contact Constraint의 구성요소:

  각 접촉점(Contact Point)마다 3개의 제약이 생긴다:

  +--------------------------------------------------+
  |  1. 비관통 제약 (Normal Constraint)               |
  |     "법선 방향으로 물체가 겹치지 않게 한다"        |
  |                                                    |
  |        ^  법선 (normal)                            |
  |        |                                           |
  |     ---+--- 접촉면                                 |
  |                                                    |
  +--------------------------------------------------+
  |  2. 마찰 제약 1 (Friction Constraint, 접선 1)     |
  |     "접촉면 위에서 미끄러지지 않게 한다"           |
  |                                                    |
  |     <---> 접선 방향 1                              |
  |     ---+--- 접촉면                                 |
  |                                                    |
  +--------------------------------------------------+
  |  3. 마찰 제약 2 (Friction Constraint, 접선 2)     |
  |     "접촉면 위에서 미끄러지지 않게 한다"           |
  |                                                    |
  |     (접선 방향 2 = 법선과 접선1에 수직)            |
  |                                                    |
  +--------------------------------------------------+

▫️ 5.5.3 Solver가 겹친 물체를 자동으로 밀어내는 과정 (단계별 설명)

전체 물리 시뮬레이션 스텝 흐름:

  stepSimulation() 호출
         |
         v
  +--------------------------------------------+
  | 1. Broadphase                               |
  |    AABB(축 정렬 경계 상자) 겹침 검사          |
  |    -> 충돌 후보 쌍 목록 생성                  |
  |    예: (A,B), (C,D), (A,E) ...              |
  +--------------------------------------------+
         |
         v
  +--------------------------------------------+
  | 2. Narrowphase                              |
  |    후보 쌍에 대해 정밀 충돌 검사              |
  |    GJK/EPA/SAT 알고리즘 사용                 |
  |    -> 실제 충돌 여부 확정                     |
  |    -> 접촉점(contact point) 생성             |
  +--------------------------------------------+
         |
         v
  +--------------------------------------------+
  | 3. Contact Generation                       |
  |    각 접촉점에 대해:                         |
  |    - 접촉 법선(normal) 계산                  |
  |    - 관통 깊이(penetration depth) 계산       |
  |    - 접촉 위치 계산                          |
  |    -> Contact Manifold에 저장                |
  +--------------------------------------------+
         |
         v
  +--------------------------------------------+
  | 4. Constraint Solving (핵심!)               |
  |    Sequential Impulse Solver가 동작:         |
  |                                              |
  |    for iter = 1 to 10 (기본 반복 횟수):      |
  |      for each contact:                       |
  |        a) 상대 속도 계산                     |
  |        b) 필요한 충격량 계산                  |
  |           (Jacobian, Effective Mass 사용)    |
  |        c) 충격량 적용 (속도 변경)             |
  |        d) 클램핑 (비물리적 결과 방지)         |
  |                                              |
  |    추가: Baumgarte 안정화                    |
  |    -> 위치 오차를 속도로 보상                 |
  |    -> 관통 상태에서 자동 복구                 |
  +--------------------------------------------+
         |
         v
  +--------------------------------------------+
  | 5. Position Update                          |
  |    새로운 속도로 위치 갱신                    |
  |    pos_new = pos_old + velocity * dt        |
  |    -> 물체들이 실제로 이동!                   |
  |    -> 겹침이 해소된 새 위치로 이동            |
  +--------------------------------------------+
Baumgarte 안정화의 쉬운 비유:

  문제: Solver가 속도만 바꾸면, 이미 겹친 위치는 그대로이다.
        다음 프레임에서야 물체가 밀려나기 시작한다.
        -> 물체가 서로 "천천히 빠져나오는" 느낌 (부자연스러움)

  해결: Baumgarte 안정화
        "겹침 깊이에 비례하는 추가 속도"를 부여한다.

        추가_속도 = (ERP / dt) * 관통_깊이

        ERP (Error Reduction Parameter): 0~1 사이 값
        - 0에 가까우면: 천천히 복구 (부드럽지만 느림)
        - 1에 가까우면: 즉시 복구 (빠르지만 불안정할 수 있음)
        - 보통 0.2 정도가 기본값

  비유: 마치 "겹친 정도에 비례하는 스프링"이 달린 것처럼 작동한다.

🧪 5.5.4 C++ 예제 코드: 물체를 겹치게 배치 -> stepSimulation -> 자동 분리 확인

// ============================================================
// Bullet Physics - 자동 간섭 해결 (Penetration Recovery) 예제
// ============================================================
// 두 박스를 겹치게 배치한 후, stepSimulation()을 반복 호출하여
// Solver가 자동으로 겹침을 해소하는 과정을 관찰한다.
// ============================================================

#include <btBulletDynamicsCommon.h>
#include <iostream>
#include <iomanip>

// 물체의 현재 위치를 출력하는 헬퍼 함수
void printBodyPosition(btRigidBody* body, const char* name) {
    btTransform transform;
    body->getMotionState()->getWorldTransform(transform);
    btVector3 pos = transform.getOrigin();
    std::cout << std::fixed << std::setprecision(4);
    std::cout << "  " << name << " 위치: ("
              << pos.x() << ", "
              << pos.y() << ", "
              << pos.z() << ")" << std::endl;
}

int main() {
    // ========================================
    // 1단계: 물리 세계 설정
    // ========================================
    btDbvtBroadphase* broadphase = new btDbvtBroadphase();
    btDefaultCollisionConfiguration* collisionConfig =
        new btDefaultCollisionConfiguration();
    btCollisionDispatcher* dispatcher =
        new btCollisionDispatcher(collisionConfig);
    btSequentialImpulseConstraintSolver* solver =
        new btSequentialImpulseConstraintSolver();
    btDiscreteDynamicsWorld* world =
        new btDiscreteDynamicsWorld(
            dispatcher, broadphase, solver, collisionConfig);

    // 중력 끄기 (순수하게 충돌 해소만 관찰하기 위해)
    world->setGravity(btVector3(0, 0, 0));

    // ========================================
    // 2단계: 겹치는 두 박스 생성
    // ========================================

    btBoxShape* boxShape = new btBoxShape(btVector3(1.0, 1.0, 1.0));

    // 박스 A: 원점 (0, 0, 0)
    btDefaultMotionState* msA = new btDefaultMotionState(
        btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, 0, 0)));
    btScalar massA = 1.0;
    btVector3 inertiaA;
    boxShape->calculateLocalInertia(massA, inertiaA);
    btRigidBody* bodyA = new btRigidBody(
        btRigidBody::btRigidBodyConstructionInfo(
            massA, msA, boxShape, inertiaA));

    // 박스 B: (1.5, 0, 0) -> 0.5m 겹침!
    btDefaultMotionState* msB = new btDefaultMotionState(
        btTransform(btQuaternion(0, 0, 0, 1), btVector3(1.5, 0, 0)));
    btScalar massB = 1.0;
    btVector3 inertiaB;
    boxShape->calculateLocalInertia(massB, inertiaB);
    btRigidBody* bodyB = new btRigidBody(
        btRigidBody::btRigidBodyConstructionInfo(
            massB, msB, boxShape, inertiaB));

    world->addRigidBody(bodyA);
    world->addRigidBody(bodyB);

    // ========================================
    // 3단계: 시뮬레이션 실행 및 자동 분리 관찰
    // ========================================

    std::cout << "=== 자동 간섭 해결 과정 ===" << std::endl;
    std::cout << "초기 상태 (0.5m 겹침):" << std::endl;
    printBodyPosition(bodyA, "Box A");
    printBodyPosition(bodyB, "Box B");

    // 시뮬레이션 타임스텝: 1/60초 (60fps 기준)
    btScalar timeStep = 1.0 / 60.0;

    // 30프레임(0.5초) 동안 시뮬레이션 실행
    for (int step = 1; step <= 30; step++) {
        // *** 핵심: stepSimulation() 호출 ***
        // 이 한 줄이 다음을 자동으로 수행한다:
        // Broadphase -> Narrowphase -> Contact Generation
        // -> Constraint Solving -> Position Update
        world->stepSimulation(timeStep, 1, timeStep);

        // 5프레임마다 위치 출력
        if (step % 5 == 0 || step == 1) {
            std::cout << "\nStep " << step
                      << " (t=" << step * timeStep << "s):"
                      << std::endl;
            printBodyPosition(bodyA, "Box A");
            printBodyPosition(bodyB, "Box B");

            // 두 물체 사이 거리 계산
            btTransform tA, tB;
            bodyA->getMotionState()->getWorldTransform(tA);
            bodyB->getMotionState()->getWorldTransform(tB);
            btScalar dist = (tB.getOrigin() - tA.getOrigin()).length();
            btScalar gap = dist - 2.0;  // 각 박스 반크기 합 = 2.0
            std::cout << "  중심 거리: " << dist
                      << ", 표면 간격: " << gap << std::endl;

            if (gap >= 0) {
                std::cout << "  -> 겹침 해소 완료!" << std::endl;
            } else {
                std::cout << "  -> 아직 겹침: " << -gap
                          << "m" << std::endl;
            }
        }
    }

    // ========================================
    // 4단계: 최종 결과
    // ========================================

    std::cout << "\n=== 최종 결과 ===" << std::endl;
    printBodyPosition(bodyA, "Box A");
    printBodyPosition(bodyB, "Box B");
    std::cout << "Solver가 자동으로 겹침을 해소하였다!" << std::endl;

    // 정리
    world->removeRigidBody(bodyA);
    world->removeRigidBody(bodyB);
    delete bodyA;
    delete bodyB;
    delete msA;
    delete msB;
    delete boxShape;
    delete world;
    delete solver;
    delete dispatcher;
    delete collisionConfig;
    delete broadphase;

    return 0;
}

// 예상 출력 (대략):
// === 자동 간섭 해결 과정 ===
// 초기 상태 (0.5m 겹침):
//   Box A 위치: (0.0000, 0.0000, 0.0000)
//   Box B 위치: (1.5000, 0.0000, 0.0000)
//
// Step 1 (t=0.0167s):
//   Box A 위치: (-0.0420, 0.0000, 0.0000)
//   Box B 위치: (1.5420, 0.0000, 0.0000)
//   중심 거리: 1.5840, 표면 간격: -0.4160
//   -> 아직 겹침: 0.4160m
//
// Step 5 (t=0.0833s):
//   Box A 위치: (-0.2100, 0.0000, 0.0000)
//   Box B 위치: (1.7100, 0.0000, 0.0000)
//   중심 거리: 1.9200, 표면 간격: -0.0800
//   -> 아직 겹침: 0.0800m
//
// Step 10 (t=0.1667s):
//   Box A 위치: (-0.3200, 0.0000, 0.0000)
//   Box B 위치: (1.8200, 0.0000, 0.0000)
//   중심 거리: 2.1400, 표면 간격: 0.1400
//   -> 겹침 해소 완료!

🧪 5.5.5 PyBullet(Python) 예제 코드: 동일 시나리오

# ============================================================
# PyBullet - 자동 간섭 해결 (Penetration Recovery) 예제
# ============================================================
# 두 박스를 겹치게 배치한 후, stepSimulation()을 반복 호출하여
# Solver가 자동으로 겹침을 해소하는 과정을 관찰한다.
# ============================================================

import pybullet as p
import pybullet_data
import time
import math

def get_body_position(body_id):
    """물체의 현재 위치를 반환한다."""
    pos, orn = p.getBasePositionAndOrientation(body_id)
    return pos

def distance_between(pos1, pos2):
    """두 위치 사이의 거리를 계산한다."""
    return math.sqrt(
        (pos2[0] - pos1[0])**2 +
        (pos2[1] - pos1[1])**2 +
        (pos2[2] - pos1[2])**2
    )

def main():
    # ========================================
    # 1단계: 물리 서버 연결
    # ========================================
    # GUI 모드: 시각화 창이 열린다 (직접 눈으로 확인 가능)
    # DIRECT 모드로 바꾸면 시각화 없이 빠르게 실행
    physics_client = p.connect(p.DIRECT)  # p.GUI로 변경하면 시각화
    p.setAdditionalSearchPath(pybullet_data.getDataPath())

    # 중력 끄기 (순수하게 충돌 해소만 관찰)
    p.setGravity(0, 0, 0)

    # 시뮬레이션 타임스텝 설정
    p.setTimeStep(1.0 / 60.0)

    # ========================================
    # 2단계: 겹치는 두 박스 생성
    # ========================================

    # 박스 형상: 2m x 2m x 2m (반크기 1,1,1)
    box_shape = p.createCollisionShape(
        p.GEOM_BOX,
        halfExtents=[1.0, 1.0, 1.0]
    )

    # 시각적 형상 (GUI 모드에서 보이는 모습)
    visual_shape_a = p.createVisualShape(
        p.GEOM_BOX,
        halfExtents=[1.0, 1.0, 1.0],
        rgbaColor=[1, 0, 0, 0.7]  # 빨간색, 70% 불투명
    )
    visual_shape_b = p.createVisualShape(
        p.GEOM_BOX,
        halfExtents=[1.0, 1.0, 1.0],
        rgbaColor=[0, 0, 1, 0.7]  # 파란색, 70% 불투명
    )

    # 박스 A: 원점 (0, 0, 0)
    body_a = p.createMultiBody(
        baseMass=1.0,
        baseCollisionShapeIndex=box_shape,
        baseVisualShapeIndex=visual_shape_a,
        basePosition=[0, 0, 0]
    )

    # 박스 B: (1.5, 0, 0) -> 0.5m 겹침!
    body_b = p.createMultiBody(
        baseMass=1.0,
        baseCollisionShapeIndex=box_shape,
        baseVisualShapeIndex=visual_shape_b,
        basePosition=[1.5, 0, 0]
    )

    # ========================================
    # 3단계: 시뮬레이션 실행 및 자동 분리 관찰
    # ========================================

    print("=== 자동 간섭 해결 과정 ===")
    print(f"초기 상태 (0.5m 겹침):")
    pos_a = get_body_position(body_a)
    pos_b = get_body_position(body_b)
    print(f"  Box A 위치: ({pos_a[0]:.4f}, {pos_a[1]:.4f}, {pos_a[2]:.4f})")
    print(f"  Box B 위치: ({pos_b[0]:.4f}, {pos_b[1]:.4f}, {pos_b[2]:.4f})")

    # 30프레임(0.5초) 동안 시뮬레이션 실행
    for step in range(1, 31):
        # *** 핵심: stepSimulation() 호출 ***
        # Broadphase -> Narrowphase -> Contact Generation
        # -> Constraint Solving -> Position Update
        p.stepSimulation()

        # 5프레임마다 위치 출력
        if step % 5 == 0 or step == 1:
            pos_a = get_body_position(body_a)
            pos_b = get_body_position(body_b)
            dist = distance_between(pos_a, pos_b)
            gap = dist - 2.0  # 각 박스 반크기 합 = 2.0

            print(f"\nStep {step} (t={step/60.0:.4f}s):")
            print(f"  Box A 위치: ({pos_a[0]:.4f}, "
                  f"{pos_a[1]:.4f}, {pos_a[2]:.4f})")
            print(f"  Box B 위치: ({pos_b[0]:.4f}, "
                  f"{pos_b[1]:.4f}, {pos_b[2]:.4f})")
            print(f"  중심 거리: {dist:.4f}, 표면 간격: {gap:.4f}")

            if gap >= 0:
                print(f"  -> 겹침 해소 완료!")
            else:
                print(f"  -> 아직 겹침: {-gap:.4f}m")

            # 접촉점 정보도 출력
            contacts = p.getContactPoints(body_a, body_b)
            if contacts:
                print(f"  접촉점 수: {len(contacts)}")
                for ci, c in enumerate(contacts):
                    print(f"    접촉점 #{ci}: "
                          f"깊이={c[8]:.4f}, "
                          f"법선=({c[7][0]:.2f}, "
                          f"{c[7][1]:.2f}, {c[7][2]:.2f})")

    # ========================================
    # 4단계: 최종 결과
    # ========================================

    print("\n=== 최종 결과 ===")
    pos_a = get_body_position(body_a)
    pos_b = get_body_position(body_b)
    dist = distance_between(pos_a, pos_b)
    print(f"  Box A 최종 위치: ({pos_a[0]:.4f}, "
          f"{pos_a[1]:.4f}, {pos_a[2]:.4f})")
    print(f"  Box B 최종 위치: ({pos_b[0]:.4f}, "
          f"{pos_b[1]:.4f}, {pos_b[2]:.4f})")
    print(f"  중심 거리: {dist:.4f}")
    print(f"  Solver가 자동으로 겹침을 해소하였다!")

    # 정리
    p.disconnect()

if __name__ == "__main__":
    main()

🏢 5.6 BIM/MEP 적용 개념

BIM(Building Information Modeling)에서 MEP(Mechanical, Electrical, Plumbing) 요소들 간의 간섭(Clash)은 건설 현장에서 가장 흔한 문제 중 하나이다. Bullet Physics의 자동 간섭 해결 메커니즘을 BIM/MEP 간섭 해결에 개념적으로 적용할 수 있다.

기존 BIM 간섭 해결 방식:

  1. 모델링 (Revit, AutoCAD 등)
  2. 간섭 감지 (Navisworks Clash Detective)
  3. 수동 해결 (엔지니어가 하나하나 이동/수정)
  4. 재검토

  문제: 3단계에서 수작업이 많고 시간이 오래 걸린다!
Bullet Physics 기반 자동 간섭 해결 개념:

  [기존 BIM 모델]
       |
       v
  [1. IFC/Revit 데이터에서 3D 지오메트리 추출]
       |
       v
  [2. Bullet Physics 세계에 충돌 형상으로 등록]
       |  - 파이프 -> Cylinder 또는 Capsule
       |  - 덕트 -> Box
       |  - 케이블 트레이 -> Box
       |  - 구조물 -> TriangleMesh (정적, 이동 불가)
       |
       v
  [3. 구조물은 Static, MEP는 Dynamic으로 설정]
       |  - 구조물: mass = 0 (정적, 움직이지 않음)
       |  - 벽/바닥/천장: mass = 0 (정적)
       |  - MEP 파이프: mass = 1 (동적, 밀려날 수 있음)
       |  - MEP 덕트: mass = 1 (동적)
       |
       v
  [4. Constraint 설정 (이동 제한)]
       |  - 파이프: Y축/Z축으로만 이동 가능 (경로 유지)
       |  - 덕트: 특정 범위 내에서만 이동 가능
       |  - 연결점: Fixed Constraint (단부 고정)
       |
       v
  [5. stepSimulation() 반복 호출]
       |  Solver가 자동으로:
       |  - 겹친 MEP 요소들을 밀어냄
       |  - 구조물과 겹친 부분을 해소
       |  - Constraint를 만족하면서 최소 이동
       |
       v
  [6. 결과 위치를 BIM 모델에 반영]
       |
       v
  [자동 해결된 BIM 모델]
구체적 적용 예시: 배관과 덕트의 간섭

  간섭 전 (평면도):
  ================================ 천장
         +========+
         | 덕트    |  (좌우로 진행)
  -------+--||----+--------------
            ||                     파이프 (위아래로 진행)
            ||
  ================================ 바닥

  -> 덕트와 파이프가 겹침!

  Bullet Physics 적용 후:
  ================================ 천장
         +========+
         | 덕트    |  (약간 위로 이동)
  -------+---------+--------------
            ||                     파이프 (그대로)
            ||
  ================================ 바닥

  -> Solver가 덕트를 위로 밀어서 간섭 해소!
  -> 파이프는 구조물과 가까우므로 이동하지 않음

주의사항 및 한계:

항목 설명
물리적 정확성 실제 BIM 간섭 해결에는 유지보수 공간, 시공 순서, 경제성 등도 고려해야 한다
Constraint 설계 MEP 요소의 이동 가능 범위를 적절히 설정하는 것이 핵심이다
연결 관계 보존 파이프/덕트의 연결 관계가 끊어지지 않도록 Fixed Constraint 필요
우선순위 어떤 요소가 먼저 양보할지(질량, 마찰 조정)를 설계해야 한다
후처리 필요 Solver 결과를 그대로 쓰기보다는 제안(Suggestion)으로 활용하는 것이 현실적이다

💡 이 접근법은 완전 자동 해결보다는 “간섭 해결 후보안 자동 생성”으로 활용하는 것이 가장 현실적이다. 엔지니어에게 “이렇게 움직이면 간섭이 해소됩니다”라는 제안을 자동으로 만들어주는 도구로 사용할 수 있다.


🔗 5.7 참고 자료 URL

▫️ 공식 자료

자료 URL 설명
Bullet Physics 공식 사이트 https://pybullet.org/ Bullet/PyBullet 공식 홈페이지
GitHub 저장소 https://github.com/bulletphysics/bullet3 소스코드, 예제, 문서
Bullet User Manual (PDF) https://github.com/bulletphysics/bullet3/blob/master/docs/Bullet_User_Manual.pdf 공식 사용자 매뉴얼
PyBullet Quickstart Guide https://github.com/bulletphysics/bullet3/blob/master/docs/pybullet_quickstart_guide/PyBulletQuickstartGuide.md.html PyBullet 빠른 시작 가이드
Bullet API 문서 (Doxygen) https://pybullet.org/Bullet/BulletFull/index.html C++ API 참조 문서
공식 포럼 https://pybullet.org/Bullet/phpBB3/ 질문/답변 커뮤니티
btManifoldPoint https://pybullet.org/Bullet/BulletFull/classbtManifoldPoint.html 접촉점 클래스 문서
btContactConstraint https://pybullet.org/Bullet/BulletFull/classbtContactConstraint.html 접촉 제약 클래스 문서
btDynamicsWorld https://pybullet.org/Bullet/BulletFull/classbtDynamicsWorld.html 동역학 월드 클래스 문서

▫️ 튜토리얼 및 학습 자료

자료 URL 설명
Bullet Physics 종합 리뷰+튜토리얼 https://gamedesigning.org/engines/bullet/ 5개 튜토리얼 포함 종합 리뷰
Kodeco Bullet Tutorial https://www.kodeco.com/2606-bullet-physics-tutorial-getting-started 초보자용 시작 가이드
Bullet SDK Manual (HTML) https://www.cs.kent.edu/~ruttan/GameEngines/lectures/Bullet_User_Manual 대학 강의용 매뉴얼
Bullet 충돌 알고리즘 설명 https://andysomogyi.github.io/mechanica/bullet.html Broadphase/Narrowphase 상세 설명
Sequential Impulse 해설 https://allenchou.net/2013/12/game-physics-constraints-sequential-impulse/ Constraint와 Sequential Impulse 상세 해설
Constraint Solver 이해하기 https://erikonarheim.com/posts/understanding-collision-constraint-solvers/ 충돌 제약 Solver 해설
GameDev.net Constraint 해설 https://www.gamedev.net/tutorials/programming/math-and-physics/understanding-constraint-resolution-in-physics-engine-r4839/ 물리 엔진 제약 해결 이해
Ogre + Bullet 초보자 가이드 https://oramind.com/ogre-bullet-a-beginners-basic-guide/ 그래픽 엔진 연동 초보자 가이드
Magnum C++ Bullet 예제 https://doc.magnum.graphics/magnum/examples-bullet.html C++ 프레임워크 Bullet 통합 예제

🧠 PyBullet 관련 자료

자료 URL 설명
PyBullet PyPI https://pypi.org/project/pybullet/ Python 패키지 설치 페이지
PyBullet 시작하기 (Medium) https://medium.com/@chand.shelvin/pybullet-getting-started-a068a0e3d492 초보자용 시작 가이드
PyBullet로 로봇 제어 https://towardsdatascience.com/how-to-control-a-robot-with-python/ Python 로봇 제어 튜토리얼
PyBullet 로봇공학 튜토리얼 https://github.com/adityasagi/robotics_tutorial PyBullet 로봇공학 입문
PyBullet + OpenAI Gym https://www.etedal.net/2020/04/pybullet-panda.html PyBullet 강화학습 환경 구성
PyBullet Robotics (GitHub) https://github.com/akinami3/PybulletRobotics PyBullet 로봇 시뮬레이션 코드 모음
PyBullet 마스터하기 https://www.numberanalytics.com/blog/pybullet-ultimate-guide PyBullet 종합 가이드
PyBullet 강화학습 (Medium) https://medium.com/sorta-sota/spinning-up-in-deep-reinforcement-learning-with-pybullet-793d6acb54f9 강화학습 + PyBullet
PyBullet 로봇 시뮬레이션 입문 https://www.postnetwork.co/introduction-to-robotics-simulation-with-pybullet/ 로봇 시뮬레이션 소개
getClosestPoints 예제 (GitHub) https://github.com/bulletphysics/bullet3/blob/master/examples/pybullet/examples/getClosestPoints.py 공식 접촉점/거리 예제 코드

⚖️ 비교/분석 자료

자료 URL 설명
물리 엔진 Top 10 비교 https://www.cotocus.com/blog/top-10-physics-engines-features-pros-cons-comparison/ 10개 엔진 기능/장단점 비교
PhysX vs Bullet vs Havok https://www.geeks3d.com/20100330/physx-vs-bullet-vs-havok/ 3대 물리 엔진 비교
로봇 시뮬레이션 도구 비교 (IEEE) https://ieeexplore.ieee.org/document/7139807/ Bullet, MuJoCo, ODE, PhysX 학술 비교
물리 엔진 성능 비교 (PEEL) https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=9095 공식 포럼 성능 비교 스레드
Bullet (software) - Wikipedia https://en.wikipedia.org/wiki/Bullet_(software) Bullet Physics 위키피디아
GamePato Bullet 소개 https://www.gamepato.com/blogs/bullet-physics-engine Bullet 종합 소개

🔗 5.8 관련 YouTube URL

💡 참고: YouTube 영상 URL은 웹 검색 특성상 직접적인 링크 수집이 제한적이었다. 아래는 검색을 통해 확인된 영상과 YouTube에서 직접 검색하면 찾을 수 있는 키워드를 함께 제공한다.

▫️ 검색 키워드 (YouTube에서 직접 검색 권장)

검색 키워드 기대 결과
Bullet Physics tutorial Bullet 설치/기본 사용법 튜토리얼
Bullet Physics demo simulation 강체/연체 시뮬레이션 데모
Bullet Physics OpenGL OpenGL 연동 물리 시뮬레이션
Bullet Physics GJK collision detection GJK 충돌 감지 알고리즘 시각화
Bullet Physics constraint solver 제약 조건 Solver 동작 설명
PyBullet tutorial PyBullet 설치/기본 사용법
PyBullet robot simulation 로봇 시뮬레이션 데모
PyBullet reinforcement learning 강화학습 환경 구성
PyBullet URDF robot arm 로봇 팔 시뮬레이션
Bullet Physics GTA GTA 시리즈 물리 엔진 분석
Sequential Impulse Solver explained Solver 동작 원리 설명
physics engine collision detection tutorial 충돌 감지 일반 튜토리얼
GJK algorithm visualization GJK 알고리즘 시각적 설명
Erwin Coumans Bullet Physics GDC 개발자 GDC 발표 영상

▫️ 확인된 영상/채널 정보

영상/채널 설명
“Bullet physics tutorial 0 - Examples and Installation” 16분 35초, 설치와 예제 실행 안내
thecplusplusguy 채널 Bullet Physics + OpenGL 시리즈 (5-6편)
Bullet 2.80 GPU OpenCL 데모 영상 GPU 가속 강체 시뮬레이션 (100% GPU)
PNaCl Bullet Physics Demo 브라우저에서 실행되는 Bullet 물리 데모
Erwin Coumans GDC/SIGGRAPH 발표 Bullet 개발자의 컨퍼런스 발표 영상

▫️ 추천 YouTube 검색 조합

  1. 입문자: "PyBullet getting started tutorial" 또는 "Bullet Physics hello world"
  2. 충돌 감지 이해: "GJK algorithm explained" 또는 "collision detection tutorial 3D"
  3. Solver 이해: "physics engine constraint solver" 또는 "sequential impulse method"
  4. 로봇공학: "PyBullet robot arm simulation" 또는 "PyBullet URDF tutorial"
  5. 강화학습: "PyBullet OpenAI gym reinforcement learning"
  6. 게임 물리: "game physics engine tutorial" 또는 "rigid body dynamics explained"

📌 파트 6: LayoutVLM

🧭 6.1 논문 소개 (CVPR 2025)

항목 내용
제목 LayoutVLM: Differentiable Optimization of 3D Layout via Vision-Language Models
저자 Fan-Yun Sun, Weiyu Liu, Siyi Gu, Dylan Lim, Goutam Bhat, Federico Tombari, Manling Li, Nick Haber, Jiajun Wu
발표 CVPR 2025, pp.29469-29478
기반 모델 VLM (Vision-Language Model, OpenAI API 활용)
코드 공개 https://github.com/sunfanyunn/LayoutVLM
프로젝트 https://ai.stanford.edu/~sunfanyun/layoutvlm/

핵심 비유:

[LayoutVLM = 건축가 + 물리학자의 협업]

  VLM (건축가 역할)                  최적화 (물리학자 역할)
  +-------------------------+       +-------------------------+
  | "이 방에는 소파를         |       | "소파가 벽을 뚫고 있어!  |
  |  TV 앞에, 테이블을        | --->  |  벽 안쪽으로 밀어 넣자.  |
  |  소파 옆에 놓으면         |       |  책장이 TV와 겹쳐!      |
  |  좋겠다" (의미 이해)      |       |  0.3m 옮기자" (물리 보정) |
  +-------------------------+       +-------------------------+
         |                                    |
         v                                    v
  초기 배치안 생성                     물리적으로 타당한 최종 배치

핵심 혁신: VLM(Vision-Language Model)이 생성한 공간 관계 제약을 미분 가능한 최적화로 물리적으로 타당한 배치를 보장.

LayoutVLM 아키텍처:

자연어 입력: "소파 앞에 테이블을, 창문 옆에 식물을"
     |
     v
VLM (Vision-Language Model)
  - 시각적으로 마킹된 이미지 생성
  - 두 가지 상호보완 표현 추출:
    1. 수치적 자세 추정 (x, y, z, rotation)
    2. 공간 관계 명세 (앞에, 옆에, 위에...)
     |
     v
미분 가능 최적화 (Differentiable Optimization)
  - 수치 추정과 공간 관계를 동시에 만족하는 해 탐색
  - 충돌 페널티 항 포함 (물리적 타당성 보장)
  - 그래디언트 기반 최적화로 수렴
     |
     v
물리적으로 타당한 3D 배치 결과

기존 방법 대비 이점:

기존 LLM 기반:
  - 물리적 충돌 무시 (소파와 테이블이 겹침)
  - 공간 관계 부정확 (위치는 맞지만 방향이 틀림)

LayoutVLM:
  - 미분 가능 최적화로 충돌 자동 해소
  - 공간 관계와 수치적 정확성 동시 만족
  - 의미론적 의도와 물리적 타당성의 균형

🚀 6.2 미분 가능 최적화 (Differentiable Optimization) 설명

“미분 가능 최적화”를 이해하려면, 눈을 감고 산에서 가장 낮은 곳(골짜기)을 찾는 과정을 상상하면 된다.

[미분 가능 최적화 비유 -- 눈 감고 골짜기 찾기]

  높이 (= 배치의 "나쁜 정도")
   ^
   |    *                        * = 현재 위치
   |   / \        *              / = 경사(기울기)
   |  /   \      / \
   | /     \    /   \            발밑 기울기를 느끼고
   |/       \  /     \           아래로 걸어간다
   |         \/       \          (= 경사 하강법)
   |          *
   |       골짜기
   +-------------------------> 가구 위치

  1단계: 발밑이 왼쪽으로 기울어져 있다 --> 왼쪽으로 한 걸음
  2단계: 아직 기울어져 있다 --> 또 한 걸음
  ...
  N단계: 평평하다! --> 최적 위치 도달!

핵심은 “기울기(gradient)를 계산할 수 있다”는 것이다. 가구의 좌표를 조금 바꿨을 때 배치가 얼마나 좋아지는지/나빠지는지 계산할 수 있으므로, 컴퓨터가 자동으로 최적 위치를 찾아갈 수 있다.

투영 경사 하강법(PGD):

  • 매 N회 반복마다, 모든 가구를 방 경계 안쪽으로 “투영”(밀어넣기)
  • 이렇게 하면 벽 밖으로 나가는 것을 방지
[PGD의 동작]

  반복 1~N: 일반 경사 하강      반복 N: 경계 투영
  +---+---+---+              +---+---+---+
  |   | A |   |              |   | A |   |
  |   +---+   |              |   +---+   |
  |     B-----|---X (벽 밖!)  |     B |   | <-- 벽 안으로 밀어넣기
  |           |              |       |   |
  +-----------+              +-----------+

🔹 6.3 이중 표현 (Dual Representation) 방식

LayoutVLM의 가장 독특한 기여는 하나의 배치를 “두 가지 방식”으로 동시에 표현하는 것이다.

[이중 표현 -- 비유: 지도 + 길 안내]

  표현 1: 수치적 자세 추정                표현 2: 공간 관계 명세
  (= GPS 좌표)                          (= 길 안내 문장)
  +---------------------------+         +---------------------------+
  | 소파: (2.0, 0, -1.5, 90도) |         | "소파는 TV 앞에 있다"      |
  | TV:   (2.0, 0, 1.5, 270도)|         | "테이블은 소파 왼쪽에 있다"|
  | 테이블: (0.5, 0, -1.5, 0도)|         | "의자는 테이블을 향한다"   |
  +---------------------------+         +---------------------------+
         |                                       |
         |            자기 일관성 검증             |
         |         (Self-Consistent Decoding)     |
         v                    v                   v
  +-------------------------------------------------------+
  | 수치 좌표가 관계 명세와 일치하는 것만 유지              |
  | 예: 소파 좌표가 실제로 TV 앞인지 확인                   |
  |     --> 일치하면 유지, 불일치하면 해당 관계 제거         |
  +-------------------------------------------------------+

이 이중 표현의 장점:

  • 수치적 표현만으로는 의미적 의도를 잃기 쉬움
  • 관계적 표현만으로는 정확한 좌표를 못 정함
  • 둘을 결합하면 “의미도 맞고 좌표도 맞는” 배치가 가능

▫️ VLM의 역할 – 단계별 설명

[LayoutVLM 처리 파이프라인]

  단계 1: 장면 입력
  +-------------------------------------------+
  | 언어 지시: "교실에 책상 20개, 칠판, 교탁"    |
  | 방 경계: 바닥 꼭짓점 좌표, 벽 높이           |
  | 에셋 정보: 각 가구의 3D 바운딩 박스 크기      |
  +-------------------------------------------+
              |
              v
  단계 2: VLM이 마크 이미지 분석
  +-------------------------------------------+
  | VLM이 방의 조감도(top-down view) 이미지를    |
  | 분석하여 "여기에 무엇을 놓으면 좋겠다"       |
  | 판단 (시각적 마크가 붙은 이미지 활용)        |
  +-------------------------------------------+
              |
              v
  단계 3: 코드 생성 (이중 표현)
  +-------------------------------------------+
  | VLM이 Python 코드를 생성:                   |
  |   - 각 가구의 초기 좌표/회전 (수치적 추정)    |
  |   - 가구 간 관계 규칙 (공간 관계 명세)        |
  +-------------------------------------------+
              |
              v
  단계 4: 자기 일관성 검증
  +-------------------------------------------+
  | 초기 좌표가 관계 규칙을 만족하는지 확인       |
  | 만족하지 않는 관계는 제거                    |
  +-------------------------------------------+
              |
              v
  단계 5: 미분 가능 최적화
  +-------------------------------------------+
  | L_semantic: 관계 규칙을 좌표로 변환한 손실    |
  | L_physics: Distance-IoU 충돌 페널티          |
  | PGD(투영 경사 하강법)로 최적화                |
  +-------------------------------------------+
              |
              v
  단계 6: 최종 layout.json 출력
  +-------------------------------------------+
  | 각 가구의 최종 (x, y, z, rotation) 출력      |
  +-------------------------------------------+

🧠 6.4 DIoU 충돌 페널티

LayoutVLM의 물리 손실 함수는 Distance-IoU(DIoU) 손실을 사용한다.

[Distance-IoU 손실 -- 쉬운 설명]

  일반 IoU                          Distance-IoU
  +----------+                      +----------+
  |  A  |    |                      |  A       | ---거리---> B
  |     | B  |  = 겹침 영역 비율     |          |
  +----------+                      +----------+

  IoU만으로는 "겹치지 않지만              DIoU는 겹침 + 거리를
  가까운 경우"를 구분 못함               동시에 고려

수학적 표현:

L_physics = 합(모든 i,j 쌍) L_DIoU(p_i, p_j, b_i, b_j)

여기서:
  p_i, p_j = 물체 i, j의 자세(위치 + 회전)
  b_i, b_j = 물체 i, j의 3D 바운딩 박스 크기
  L_DIoU = IoU 손실 + 중심점 거리 패널티

⚡ 6.5 실험 결과 및 성능 수치

▫️ 핵심 지표

지표 설명
CF (Collision-Free) 충돌 없는 배치 비율 (높을수록 좋음)
IB (In-Boundary) 경계 내 배치 비율 (높을수록 좋음)
Pos. (Positional Coherency) 위치 의미 정합성
Rot. (Rotational Coherency) 회전 의미 정합성
PSA (Physical-Semantic Alignment) CF x IB x Pos. x Rot. 종합 점수

⚖️ 방법별 비교 (11개 방 유형 평균)

방법 CF (%) IB (%) Pos. (%) Rot. (%) PSA (%)
LayoutGPT 57.6 52.1 54.2 49.8 8.2
Holodeck 69.4 61.7 62.5 57.3 15.4
I-Design 76.8 34.3 68.3 62.8 18.0
LayoutVLM 81.8 94.9 77.5 73.1 58.8

LayoutVLM은 PSA에서 기존 최고 방법(I-Design) 대비 +40.8 포인트 개선을 달성했다.

▫️ 테스트 규모

  • 11개 방 유형 (침실, 거실, 식당, 서점, 뷔페, 아동실, 교실, 컴퓨터실, 델리, 꽃집, 게임룸)
  • 유형당 3개 방, 방당 최대 80개 에셋

⚠️ 한계점

  1. VLM 초기화 실패: 때때로 VLM이 유효하지 않은 초기 배치를 생성하면 최적화가 실패
  2. OpenAI API 의존: 외부 API에 의존하므로 비용, 지연시간, 재현성 문제
  3. CUDA 컴파일 필요: Rotated IoU Loss 모듈이 CUDA 컴파일을 요구하여 환경 설정이 복잡
  4. 실시간 처리 불가: 최적화 루프가 시간이 소요되어 실시간 상호작용에 부적합
  5. 고정 바운딩 박스: 가구의 실제 형태가 아닌 바운딩 박스만으로 충돌 판단

🔍 6.6 MTV 산출 가능 여부 분석

결론: 부분적으로 가능

LayoutVLM은 Distance-IoU 손실을 사용하여 충돌을 감지하고 최소화한다. DIoU 자체가 “얼마나 겹치는지”와 “어느 방향으로 밀어야 하는지”에 대한 기울기 정보를 제공하므로, 기울기(gradient)를 MTV의 근사값으로 활용할 수 있다.

다만, 정확한 SAT 기반 MTV와는 달리 근사적인 방향과 크기를 제공한다.

import torch
import numpy as np

class OrientedBBox3D:
    """3D 회전 바운딩 박스"""
    def __init__(self, center, half_extents, rotation_y):
        # center: [x, y, z], half_extents: [w/2, h/2, d/2]
        # rotation_y: Y축 회전 (라디안)
        self.center = torch.tensor(center, dtype=torch.float32, requires_grad=True)
        self.half_extents = torch.tensor(half_extents, dtype=torch.float32)
        self.rotation_y = torch.tensor(rotation_y, dtype=torch.float32, requires_grad=True)


def compute_diou_loss(box_a, box_b):
    """
    LayoutVLM 스타일 Distance-IoU 손실 (간략화 버전).
    실제 구현은 Rotated_IoU 라이브러리를 사용한다.
    """
    # 중심점 거리
    center_dist = torch.sum((box_a.center - box_b.center) ** 2)

    # 바운딩 영역 대각선 (포함하는 최소 직사각형)
    enclosing_diag = torch.sum(
        (box_a.half_extents + box_b.half_extents +
         torch.abs(box_a.center - box_b.center)) ** 2
    )

    # DIoU = IoU - (중심거리^2 / 대각선^2)
    # 간략화: 겹침이 있으면 양수 손실 반환
    diou_loss = center_dist / (enclosing_diag + 1e-7)
    return diou_loss


def extract_mtv_from_gradient(box_a, box_b, step_size=0.1):
    """
    LayoutVLM의 DIoU 손실 기울기에서 MTV 근사값을 추출한다.
    """
    loss = compute_diou_loss(box_a, box_b)
    loss.backward()

    # box_b의 위치에 대한 기울기 = 이동해야 할 방향
    grad = box_b.center.grad.detach().numpy()

    if np.linalg.norm(grad) < 1e-6:
        return None  # 충돌 없음

    # 기울기 반대 방향으로 이동 = MTV 근사
    mtv_approx = -grad * step_size / (np.linalg.norm(grad) + 1e-7)
    return mtv_approx


# === 사용 예시 ===
box_a = OrientedBBox3D([1.0, 0.0, 0.0], [0.5, 0.5, 0.5], 0.0)
box_b = OrientedBBox3D([1.3, 0.0, 0.0], [0.5, 0.5, 0.5], 0.0)

mtv = extract_mtv_from_gradient(box_a, box_b)
if mtv is not None:
    print(f"MTV 근사값: {mtv}")
    print(f"box_b를 이 방향으로 이동하여 분리")

🔍 6.7 자동 간섭 해결 분석

결론: 가능 (내장 기능)

LayoutVLM은 자동 간섭 해결이 내장되어 있다. 미분 가능 최적화 루프가 반복적으로 충돌을 감소시키며, PGD가 경계 위반도 해결한다.

def layoutvlm_auto_resolve(objects, room_boundary,
                           num_iterations=500, lr=0.01):
    """
    LayoutVLM 스타일 자동 간섭 해결 (간략화 pseudo code).
    실제 구현은 Rotated_IoU CUDA 모듈과 PGD를 사용한다.
    """
    optimizer = torch.optim.Adam(
        [obj.center for obj in objects] + [obj.rotation_y for obj in objects],
        lr=lr
    )

    for iteration in range(num_iterations):
        optimizer.zero_grad()

        # 1) 물리 손실: 모든 쌍의 DIoU 합산
        physics_loss = 0.0
        for i in range(len(objects)):
            for j in range(i + 1, len(objects)):
                physics_loss += compute_diou_loss(objects[i], objects[j])

        # 2) 의미 손실: 공간 관계 유지 (예: "소파는 TV 앞")
        semantic_loss = compute_semantic_loss(objects)  # 생략

        total_loss = physics_loss + semantic_loss
        total_loss.backward()
        optimizer.step()

        # 3) PGD: 매 50회마다 경계 투영
        if iteration % 50 == 0:
            project_within_boundary(objects, room_boundary)

    return objects  # 간섭 해결된 최종 배치

자동 해결 수준:

  • 충돌 제거: CF 81.8% 달성 (완벽하지는 않으나 높은 수준)
  • 경계 유지: IB 94.9% 달성
  • 남은 18.2%의 충돌은 VLM 초기화가 극단적으로 나쁜 경우 발생

🏢 6.8 BIM/MEP 적용 가능성

측면 평가 상세
직접 적용 중간 3D 레이아웃 최적화 프레임워크는 MEP 배치에도 적용 가능
충돌 해결 강점 DIoU 기반 충돌 해결이 MEP 간섭 해결에 직접 활용 가능
경계 준수 강점 PGD 경계 투영이 벽/바닥/천장 제약에 적용 가능
의미 관계 활용 가능 “덕트는 배관 위에”, “전기 배선은 배관과 30cm 이상 이격” 등
확장성 주의 필요 방당 최대 80개 에셋 테스트, MEP는 수천 개 요소 가능
코드 공개 장점 GitHub 공개로 직접 수정/확장 가능

적용 전략: LayoutVLM의 미분 가능 최적화 프레임워크를 BIM 데이터에 맞게 확장. DIoU 손실을 MEP 간섭 해결에 적용하고, 의미 손실을 MEP 설계 규칙으로 대체.

BIM 적용 가능성 예시:

  • “이 공조기는 환기 덕트 아래에, 전기 패널 옆에 설치해 주세요”
  • 자연어 설계 지시를 물리적으로 타당한 3D 배치로 자동 변환

🔗 6.9 참고 자료 URL

유형 URL
CVPR 2025 페이지 https://openaccess.thecvf.com/content/CVPR2025/html/Sun_LayoutVLM_Differentiable_Optimization_of_3D_Layout_via_Vision-Language_Models_CVPR_2025_paper.html
arXiv https://arxiv.org/abs/2412.02193
프로젝트 페이지 https://ai.stanford.edu/~sunfanyun/layoutvlm/
GitHub https://github.com/sunfanyunn/LayoutVLM
CVPR 포스터 https://cvpr.thecvf.com/virtual/2025/poster/33962
Rotated IoU (의존 라이브러리) https://github.com/lilanxiao/Rotated_IoU
YouTube 확인 불가 (2026-02 기준 공식 데모 영상 미발견, CVPR 포스터 페이지 참조)

🛠️ 부록: 알고리즘 선택 가이드

🧠 상황별 최적 알고리즘 선택표

+----------------------------------+------------------------------+
| 상황                             | 권장 알고리즘                |
+----------------------------------+------------------------------+
| 두 볼록 형상 간 정밀 충돌 판별  | GJK                          |
| 다각형 메쉬 간 빠른 충돌 판별   | SAT (AABB 먼저, OBB 정밀)    |
| 수천 개 부재 간 충돌 검색       | BVH + GJK/SAT                |
| MEP 경로 자동 생성              | A* (6-연결, 복셀 기반)       |
| 다목적 배치 최적화              | NSGA-II (유전 알고리즘)      |
| 자동 클래시 해결 (상용)         | BAMROC                       |
| 대규모 실시간 BIM 렌더링        | Unity DOTS (ECS+Jobs+Burst)  |
| 자연어 기반 레이아웃 생성       | LLplace / LayoutVLM          |
+----------------------------------+------------------------------+

⚡ 알고리즘 성능 특성 비교표

알고리즘    시간복잡도        공간복잡도    특징
-----------------------------------------------------
GJK         O(k) 반복        O(1)          볼록 형상 필수
SAT         O(n*m) 축 수    O(n+m)        MTV 직접 산출
BVH 빌드    O(n log n)       O(n)          초기 구축 비용
BVH 쿼리    O(log n)         O(1)          쿼리는 매우 빠름
A* (3D)     O(V log V)       O(V)          V=복셀 수
GA          O(g*p*eval)     O(p)          g=세대, p=집단크기

🔹 부록: 핵심 용어 정리

용어 영문 설명
충돌 감지 Collision Detection 두 물체가 닿았는지 판별하는 과정
강체 Rigid Body 형태가 변하지 않는 물체
연체 Soft Body 형태가 변할 수 있는 물체
접촉점 Contact Point 두 물체가 닿는 지점
접촉 매니폴드 Contact Manifold 접촉점들의 모음
관통 깊이 Penetration Depth 두 물체가 겹친 깊이
접촉 법선 Contact Normal 접촉면에 수직인 방향 벡터
MTV Minimum Translation Vector 겹침 해소를 위한 최소 이동 벡터
충격량 Impulse 순간적으로 가해지는 힘 x 시간
제약 조건 Constraint 물체의 움직임을 제한하는 규칙
AABB Axis-Aligned Bounding Box 축 정렬 경계 상자
DBVT Dynamic Bounding Volume Tree 동적 바운딩 볼륨 트리
GJK Gilbert-Johnson-Keerthi 볼록 도형 간 거리/충돌 알고리즘
EPA Expanding Polytope Algorithm GJK 후 관통 깊이 계산 알고리즘
SAT Separating Axis Theorem 분리축 정리 (충돌 판정)
ERP Error Reduction Parameter 위치 오차 감소 파라미터
CFM Constraint Force Mixing 제약 힘 혼합 파라미터
DIoU Distance-IoU 거리 기반 Intersection over Union
PGD Projected Gradient Descent 투영 경사 하강법
VLM Vision-Language Model 시각-언어 모델
PSA Physical-Semantic Alignment 물리-의미 정합 점수

💡 문서 끝
💡 본 문서는 2026-02-19에 4건의 개별 리서치 문서에서 Bullet Physics와 LayoutVLM 관련 내용을 수집하여 보고서 형식으로 재구성한 것이다.
📝 원본 문서의 코드 예제, ASCII 다이어그램, 표, URL 등을 그대로 포함하였다.