260219 통합보고서 파트3 Bullet LayoutVLM
19 Feb 2026
🧠 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 검색 조합
- 입문자:
"PyBullet getting started tutorial"또는"Bullet Physics hello world" - 충돌 감지 이해:
"GJK algorithm explained"또는"collision detection tutorial 3D" - Solver 이해:
"physics engine constraint solver"또는"sequential impulse method" - 로봇공학:
"PyBullet robot arm simulation"또는"PyBullet URDF tutorial" - 강화학습:
"PyBullet OpenAI gym reinforcement learning" - 게임 물리:
"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개 에셋
⚠️ 한계점
- VLM 초기화 실패: 때때로 VLM이 유효하지 않은 초기 배치를 생성하면 최적화가 실패
- OpenAI API 의존: 외부 API에 의존하므로 비용, 지연시간, 재현성 문제
- CUDA 컴파일 필요: Rotated IoU Loss 모듈이 CUDA 컴파일을 요구하여 환경 설정이 복잡
- 실시간 처리 불가: 최적화 루프가 시간이 소요되어 실시간 상호작용에 부적합
- 고정 바운딩 박스: 가구의 실제 형태가 아닌 바운딩 박스만으로 충돌 판단
🔍 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 등을 그대로 포함하였다.