Files
game_engine/docs/superpowers/specs/2026-03-25-phase5-2-rigidbody.md
2026-03-25 11:37:16 +09:00

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/mass
  • is_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 순회:

  1. velocity += gravity * gravity_scale * dt
  2. position += velocity * dt

정적 물체(mass == 0.0)는 건너뜀.

resolve_collisions (solver.rs)

pub fn resolve_collisions(world: &mut World, contacts: &[ContactPoint])

각 ContactPoint에 대해:

  1. 두 entity의 RigidBody 조회 (없으면 스킵)
  2. 상대 속도 계산: v_rel = velocity_a - velocity_b
  3. 법선 방향 성분: v_rel_n = v_rel · normal
  4. 분리 중이면 스킵: v_rel_n > 0
  5. 반발 계수: e = min(restitution_a, restitution_b)
  6. 임펄스 크기: j = -(1 + e) * v_rel_n / (inv_mass_a + inv_mass_b)
  7. 속도 업데이트: 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)
  1. integrate(world, config)
  2. let contacts = detect_collisions(world)
  3. 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=true
  • with_restitution: 설정 반영

integrator.rs

  • 중력 낙하: 1프레임 후 위치 = (0, -9.81/60/60 ≈ position 변화)
  • 정적 물체: integrate 후 위치 불변
  • 초기 속도: velocity가 있는 경우 position 변화 확인

solver.rs

  • 두 동적 구체 정면 충돌: 속도 반전 확인
  • 동적 구체 vs 정적 바닥: 구체만 튕김, 바닥 불변
  • 위치 보정: 침투 깊이만큼 분리
  • physics_step E2E: 구체가 바닥 위에서 낙하 → 충돌 → 반발