본문으로 건너뛰기

불릿 피직스

이 섹션에서는 babylon-mmd에서 Bullet Physics 엔진 바인딩을 사용하는 방법을 설명합니다.

Bullet Physics 개요​

babylon-mmd는 MMD 모델의 물리 시뮬레이션을 위해 Bullet Physics 엔진을 사용합니다.

Bullet Physics 엔진은 충돌 감지와 강체 역학 시뮬레이션을 지원하는 C++ 로 작성된 오픈 소스 물리 엔진입니다.

일반적으로 이 엔진을 사용하려면 emscripten을 사용하여 C++ 코드를 WebAssembly(WASM)로 컴파일한 Ammo.js 라이브러리를 사용합니다.

그러나 babylon-mmd는 emscripten을 사용하지 않는 다른 접근 방식을 사용합니다. 대신 FFI를 통해 Bullet Physics 엔진을 Rust 소스 코드에 통합한 다음, wasm-bindgen을 사용하여 WASM으로 컴파일합니다.

이 과정에서 모든 Bullet Physics 바인딩 코드는 수동으로 작성되어 Ammo.js에 비해 더 나은 성능을 제공합니다.

Bullet Physics 통합 형태​

Bullet Physics 엔진은 babylon-mmd에 두 가지 주요 형태로 통합됩니다.

Bullet Physics JavaScript 바인딩​

이 바인딩은 JavaScript에서 직접 Bullet Physics 엔진을 호출할 수 있게 해줍니다. 바인딩은 babylon-mmd/esm/Runtime/Optimized/Physics/Bind 디렉토리에 위치합니다.

이 방식을 사용하여 MMD 런타임을 생성하는 코드는 다음과 같습니다:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeMPR());
const physicsRuntime = new MultiPhysicsRuntime(mmdWasmInstance);
physicsRuntime.setGravity(new Vector3(0, -9.8 * 10, 0));
physicsRuntime.register(scene);

const mmdRuntime = new MmdRuntime(scene, new MmdBulletPhysics(physicsRuntime));

이 경우 MultiPhysicsRuntime 클래스는 여러 모델의 물리 시뮬레이션을 병렬로 처리하는 객체로, 시뮬레이션을 제어할 수 있습니다.

MmdWasmPhysics 사용​

이 방식은 Rust로 작성된 MmdWasmRuntime에서 Bullet Physics 엔진을 호출합니다. 이 방법은 JavaScript에 노출된 바인딩을 사용하지 않고 Rust에서 직접 Bullet Physics 엔진을 호출하여 FFI 오버헤드를 줄입니다.

이 방식을 사용하여 MMD 런타임을 생성하는 코드는 다음과 같습니다:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeMPR());
const mmdRuntime = new MmdWasmRuntime(scene, new MmdWasmPhysics(mmdWasmInstance));

이 경우에도 MultiPhysicsRuntime과 유사한 MmdWasmPhysicsRuntimeImpl 클래스를 사용하여 물리 시뮬레이션을 제어할 수 있습니다:

const physicsRuntime = mmdRuntime.physics!.getImpl(MmdWasmPhysicsRuntimeImpl);

주요 차이점은 MultiPhysicsRuntime은 WASM 리소스를 직접 소유하는 반면, MmdWasmPhysicsRuntimeImpl은 MmdWasmRuntime이 소유한 WASM 리소스를 참조한다는 것입니다.

Bullet Physics 바인딩 객체의 메모리 관리​

Bullet Physics 엔진 바인딩은 메모리 관리를 위해 FinalizationRegistry를 사용합니다.

따라서 babylon-mmd/esm/Runtime/Optimized/Physics/Bind 디렉토리의 바인딩 코드를 직접 사용할 때 메모리가 자동으로 해제됩니다.

메모리 관리를 수동으로 제어하려면 dispose() 메서드를 호출하여 명시적으로 메모리를 해제할 수 있습니다.

const rigidBody = new RigidBody(physicsRuntime, rbInfo);
// Use rigidBody
rigidBody.dispose(); // Explicitly release memory

Bullet Physics API 사용하기​

babylon-mmd/esm/Runtime/Optimized/Physics/Bind 디렉토리의 Bullet Physics 바인딩 코드는 MMD 모델과 관련이 없는 일반적인 물리 시뮬레이션에도 사용할 수 있습니다.

다음은 Bullet Physics 바인딩을 사용하여 큐브를 땅에 떨어뜨리는 간단한 예제입니다:

const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeSPR());
const physicsRuntime = new NullPhysicsRuntime(mmdWasmInstance);
const physicsWorld = new PhysicsWorld(physicsRuntime);

// create ground mesh
const ground = CreatePlane("ground", { size: 120 }, scene);
ground.rotationQuaternion = Quaternion.RotationAxis(new Vector3(1, 0, 0), Math.PI / 2);

// create ground rigid body with static plane shape
const groundShape = new PhysicsStaticPlaneShape(runtime, new Vector3(0, 0, -1), 0);
const groundRbInfo = new RigidBodyConstructionInfo(wasmInstance);
groundRbInfo.shape = groundShape;
groundRbInfo.setInitialTransform(ground.getWorldMatrix());
groundRbInfo.motionType = MotionType.Static;

const groundRigidBody = new RigidBody(runtime, groundRbInfo);
world.addRigidBody(groundRigidBody);

// create box mesh
const baseBox = CreateBox("box", { size: 2 }, scene);
baseBox.position = new Vector3(0, 20, 0);
baseBox.rotationQuaternion = Quaternion.Identity();

// create box rigid body with box shape
const boxShape = new PhysicsBoxShape(runtime, new Vector3(1, 1, 1));
const boxRbInfo = new RigidBodyConstructionInfo(wasmInstance);
boxRbInfo.shape = boxShape;
boxRbInfo.setInitialTransform(baseBox.getWorldMatrix());
boxRbInfo.motionType = MotionType.Dynamic;

// create box rigid body
const boxRigidBody = new RigidBody(runtime, boxRbInfo);
world.addRigidBody(boxRigidBody);

const matrix = new Matrix();

// register onBeforeRenderObservable to update physics simulation
scene.onBeforeRenderObservable.add(() => {
world.stepSimulation(1 / 60, 10, 1 / 60);

boxRigidBody.getTransformMatrixToRef(matrix);
matrix.getTranslationToRef(baseBox.position);
Quaternion.FromRotationMatrixToRef(matrix, baseBox.rotationQuaternion!);
});

Bullet Physics 바인딩은 여러 컴포넌트로 구성되어 있으며, 상황에 따라 필요한 컴포넌트만 선택하여 사용할 수 있습니다.

  • PhysicsShape: 물리 시뮬레이션에서 사용되는 충돌 형태를 나타내는 클래스입니다.
    • Bullet Physics의 btCollisionShape에 해당합니다.
  • RigidBody: 물리 시뮬레이션에서 사용되는 강체를 나타내는 클래스입니다.
    • Bullet Physics의 btRigidBody에 해당합니다.
  • RigidBodyConstructionInfo: 강체 생성을 위한 정보를 포함하는 클래스입니다.
    • Bullet Physics의 btRigidBody::btRigidBodyConstructionInfo에 해당합니다.
  • Constraint: 물리 시뮬레이션에서 사용되는 제약 조건을 나타내는 클래스입니다.
    • Bullet Physics의 btTypedConstraint에 해당합니다.
  • PhysicsWorld: 물리 시뮬레이션을 관리하는 클래스입니다.
    • Bullet Physics의 btDynamicsWorld에 해당합니다.
  • PhysicsRuntime: Buffered Evaluation 처리 로직이 포함된 PhysicsWorld의 래퍼 클래스입니다.

Physics Shape​

Physics Shape는 물리 시뮬레이션에서 사용되는 충돌 형태를 나타내는 클래스입니다.

babylon-mmd는 다음과 같은 Physics Shape 클래스들을 제공합니다:

  • PhysicsBoxShape: 박스 충돌 형태를 나타내는 클래스입니다.
    • Bullet Physics의 btBoxShape에 해당합니다.
  • PhysicsSphereShape: 구체 충돌 형태를 나타내는 클래스입니다.
    • Bullet Physics의 btSphereShape에 해당합니다.
  • PhysicsCapsuleShape: 캡슐 충돌 형태를 나타내는 클래스입니다.
    • Bullet Physics의 btCapsuleShape에 해당합니다.
  • PhysicsStaticPlaneShape: 무한 평면 충돌 형태를 나타내는 클래스입니다.
    • Bullet Physics의 btStaticPlaneShape에 해당합니다.

Bullet Physics는 다른 많은 Physics Shape를 지원하지만, babylon-mmd는 MMD 모델에 필요한 충돌 형태 바인딩만 구현했습니다.

Rigid Body​

RigidBody는 물리 시뮬레이션에서 사용되는 강체를 나타냅니다.

RigidBody 클래스를 생성하려면 RigidBodyConstructionInfo 객체를 사용하여 초기화해야 합니다.

RigidBody 클래스는 두 가지 유형의 구현으로 제공됩니다:

  • RigidBody: 단일 RigidBody 객체를 나타내는 클래스입니다.
  • RigidBodyBundle: 여러 RigidBody 객체를 하나의 객체로 묶어서 처리할 수 있는 클래스입니다.

RigidBodyBundle 클래스는 여러 RigidBody 객체를 한 번에 생성할 때 RigidBody 객체 간의 Memory Locality를 개선하여 더 나은 성능을 제공합니다.

RigidBodyBundle을 효율적으로 초기화하기 위해 RigidBodyConstructionInfoList 클래스도 제공됩니다.

RigidBodyConstructionInfoList 클래스는 여러 RigidBodyConstructionInfo 객체를 하나의 객체로 묶어서 처리할 수 있는 클래스입니다.

다음은 RigidBodyBundle을 사용하는 예제입니다:

const boxShape = new PhysicsBoxShape(runtime, new Vector3(1, 1, 1));

const rbCount = 10;
const rbInfoList = new RigidBodyConstructionInfoList(wasmInstance, rbCount);
for (let k = 0; k < rbCount; ++k) {
rbInfoList.setShape(k, boxShape);
const initialTransform = Matrix.TranslationToRef(xOffset, 1 + k * 2, zOffset, matrix);
rbInfoList.setInitialTransform(k, initialTransform);
rbInfoList.setFriction(k, 1.0);
rbInfoList.setLinearDamping(k, 0.3);
rbInfoList.setAngularDamping(k, 0.3);
}
const boxRigidBodyBundle = new RigidBodyBundle(runtime, rbInfoList);
world.addRigidBodyBundle(boxRigidBodyBundle, worldId);

Constraint​

Constraint는 물리 시뮬레이션에서 사용되는 제약 조건을 나타냅니다.

babylon-mmd는 다음과 같은 Constraint 클래스들을 제공합니다:

  • Generic6DofConstraint: 6자유도 제약 조건을 나타내는 클래스입니다.
    • Bullet Physics의 btGeneric6DofConstraint에 해당합니다.
  • Generic6DofSpringConstraint: 스프링이 있는 6자유도 제약 조건을 나타내는 클래스입니다.
    • Bullet Physics의 btGeneric6DofSpringConstraint에 해당합니다.

Bullet Physics는 다른 많은 Constraint를 지원하지만, babylon-mmd는 MMD 모델에 필요한 제약 조건 바인딩만 구현했습니다.

Physics World​

PhysicsWorld는 물리 시뮬레이션을 관리하는 클래스입니다.

PhysicsWorld 클래스는 두 가지 유형의 구현으로 제공됩니다:

  • PhysicsWorld: 단일 물리 시뮬레이션 월드를 나타내는 클래스입니다.
  • MultiPhysicsWorld: 여러 물리 시뮬레이션 월드를 병렬로 처리하는 클래스입니다.
    • 각 월드 간의 상호작용을 위한 API가 제공됩니다.

RigidBody와 Constraint 객체는 물리 시뮬레이션에 참여하기 위해 PhysicsWorld 또는 MultiPhysicsWorld 객체에 반드시 추가되어야 합니다.

Physics Runtime​

PhysicsRuntime은 Buffered Evaluation 처리 로직이 포함된 PhysicsWorld의 래퍼 클래스입니다.

PhysicsRuntime 클래스는 세 가지 유형의 구현으로 제공됩니다:

  • NullPhysicsRuntime: 런타임 없이 PhysicsWorld를 사용하기 위한 클래스입니다.
  • PhysicsRuntime: PhysicsWorld를 처리하는 클래스입니다.
  • MultiPhysicsRuntime: MultiPhysicsWorld를 처리하는 클래스입니다.

PhysicsRuntime과 MultiPhysicsRuntime 클래스는 Buffered Evaluation을 지원합니다. 이는 멀티 스레딩이 가능한 환경에서 PhysicsRuntime.evaluationType 속성을 PhysicsRuntimeEvaluationType.Buffered로 설정하면, 물리 시뮬레이션이 별도의 워커 스레드에서 처리된다는 의미입니다.

physicsRuntime.evaluationType = PhysicsRuntimeEvaluationType.Buffered;
정보

PhysicsWorld 또는 MultiPhysicsWorld 객체는 락을 사용하여 동기화를 적절히 처리하는 작업을 수행하지만, 이를 직접 구현하는 것은 매우 어렵습니다.

따라서 Buffered Evaluation을 사용할 때 런타임 없이 NullPhysicsRuntime을 사용하여 물리 시뮬레이션을 제어하는 것은 매우 복잡한 작업이므로 권장하지 않습니다.

정보

MMD 런타임과 호환되는 Physics Runtime은 MultiPhysicsRuntime이며, 다른 Physics Runtime은 MMD 런타임과 호환되지 않습니다.

추가 리소스​

Bullet Physics 바인딩은 처음에 babylon-bulletphysics 리포지토리에서 개발되었으며 나중에 babylon-mmd에 통합되었습니다.

따라서 babylon-bulletphysics 리포지토리에서 Bullet Physics 바인딩에 대한 더 많은 예제와 테스트 코드를 확인할 수 있습니다.