4.5 KiB
4.5 KiB
Phase 5-2: Rigid Body Simulation — Design Spec
Overview
voltex_physics에 리지드바디 시뮬레이션을 추가한다.
Semi-implicit Euler 적분 + 임펄스 기반 충돌 응답으로, 물체가 떨어지고 충돌하여 튕기는 기본 물리를 구현한다.
Scope
- RigidBody 컴포넌트 (질량, 속도, 반발 계수)
- 중력 + Semi-implicit Euler 적분
- 임펄스 기반 충돌 응답 (선형만)
- 위치 보정 (penetration resolution)
- physics_step 통합 함수
Out of Scope
- 각속도/회전 물리 (관성 텐서 필요, 추후 추가)
- 마찰 (Coulomb friction)
- Sequential Impulse 솔버
- 연속 충돌 감지 (CCD)
- Sleep/Island 시스템
Module Structure
기존 voltex_physics에 추가:
crates/voltex_physics/src/
├── (기존) collider.rs, contact.rs, narrow.rs, bvh.rs, collision.rs, lib.rs
├── rigid_body.rs — RigidBody 컴포넌트, PhysicsConfig
├── integrator.rs — Semi-implicit Euler 적분
└── solver.rs — 임펄스 기반 충돌 응답 + 위치 보정 + physics_step
Types
RigidBody (ECS 컴포넌트)
#[derive(Debug, Clone, Copy)]
pub struct RigidBody {
pub velocity: Vec3,
pub angular_velocity: Vec3,
pub mass: f32,
pub restitution: f32,
pub gravity_scale: f32,
}
mass == 0.0→ 정적 물체 (무한 질량, 중력 영향 없음, 움직이지 않음)mass > 0.0→ 동적 물체restitution— 반발 계수 (0.0 = 완전 비탄성, 1.0 = 완전 탄성)gravity_scale— 중력 배율 (기본 1.0)angular_velocity— 필드만 존재, 이번 Phase에서는 적분하지 않음
Methods:
dynamic(mass: f32) -> Self— 동적 물체 생성 (velocity=0, restitution=0.3, gravity_scale=1.0)statik() -> Self— 정적 물체 생성 (mass=0, velocity=0, restitution=0.3)inv_mass(&self) -> f32— mass가 0이면 0.0, 아니면 1.0/massis_static(&self) -> bool— mass == 0.0
PhysicsConfig
pub struct PhysicsConfig {
pub gravity: Vec3,
pub fixed_dt: f32,
}
default()→ gravity = (0, -9.81, 0), fixed_dt = 1.0/60.0
Functions
integrate (integrator.rs)
pub fn integrate(world: &mut World, config: &PhysicsConfig)
Transform + RigidBody를 가진 동적 entity 순회:
velocity += gravity * gravity_scale * dtposition += velocity * dt
정적 물체(mass == 0.0)는 건너뜀.
resolve_collisions (solver.rs)
pub fn resolve_collisions(world: &mut World, contacts: &[ContactPoint])
각 ContactPoint에 대해:
- 두 entity의 RigidBody 조회 (없으면 스킵)
- 상대 속도 계산:
v_rel = velocity_a - velocity_b - 법선 방향 성분:
v_rel_n = v_rel · normal - 분리 중이면 스킵:
v_rel_n > 0 - 반발 계수:
e = min(restitution_a, restitution_b) - 임펄스 크기:
j = -(1 + e) * v_rel_n / (inv_mass_a + inv_mass_b) - 속도 업데이트:
v_a += j * normal * inv_mass_a,v_b -= j * normal * inv_mass_b
위치 보정 (Positional Correction):
- 침투 깊이(depth)에 비례하여 물체를 법선 방향으로 분리
- Baumgarte stabilization:
correction = max(depth - slop, 0) * percent / (inv_mass_a + inv_mass_b) - slop = 0.01 (작은 침투 허용), percent = 0.4 (보정 비율)
pos_a -= correction * inv_mass_a * normal,pos_b += correction * inv_mass_b * normal
physics_step (solver.rs)
pub fn physics_step(world: &mut World, config: &PhysicsConfig)
integrate(world, config)let contacts = detect_collisions(world)resolve_collisions(world, &contacts)
ECS Integration Notes
- RigidBody는 Transform + Collider와 함께 entity에 추가
- 물리 스텝은 게임 루프에서 fixed_update 타이밍에 호출
- detect_collisions는 Phase 5-1의 기존 함수 재사용
Conventions
- 단위: SI (미터, 초, 킬로그램)
- 중력: (0, -9.81, 0) — Y-up 좌표계
- 법선: Phase 5-1과 동일 (entity_a → entity_b 방향)
Test Plan
rigid_body.rs
dynamic(): 기본값 확인, inv_mass 정확도statik(): mass=0, inv_mass=0, is_static=truewith_restitution: 설정 반영
integrator.rs
- 중력 낙하: 1프레임 후 위치 = (0, -9.81/60/60 ≈ position 변화)
- 정적 물체: integrate 후 위치 불변
- 초기 속도: velocity가 있는 경우 position 변화 확인
solver.rs
- 두 동적 구체 정면 충돌: 속도 반전 확인
- 동적 구체 vs 정적 바닥: 구체만 튕김, 바닥 불변
- 위치 보정: 침투 깊이만큼 분리
- physics_step E2E: 구체가 바닥 위에서 낙하 → 충돌 → 반발