HelloWorld 프로젝트에서는 간단하게 박스로 피라미드를 쌓은 예제를 보여준다. 어떻게 PhysX 환경을 조성하고 강체 시뮬레이션을 돌릴 수 있는지 코드를 분석해 알아보자.
void initPhysics(bool interactive)
{
gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback);
gPvd = PxCreatePvd(*gFoundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10);
gPvd->connect(*transport,PxPvdInstrumentationFlag::eALL);
gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(),true,gPvd);
PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());
sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f);
gDispatcher = PxDefaultCpuDispatcherCreate(2);
sceneDesc.cpuDispatcher = gDispatcher;
sceneDesc.filterShader = PxDefaultSimulationFilterShader;
gScene = gPhysics->createScene(sceneDesc);
PxPvdSceneClient* pvdClient = gScene->getScenePvdClient();
if(pvdClient)
{
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true);
}
gMaterial = gPhysics->createMaterial(0.5f, 0.5f, 0.6f);
PxRigidStatic* groundPlane = PxCreatePlane(*gPhysics, PxPlane(0,1,0,0), *gMaterial);
gScene->addActor(*groundPlane);
for(PxU32 i=0;i<5;i++)
createStack(PxTransform(PxVec3(0,0,stackZ-=10.0f)), 10, 2.0f);
if(!interactive)
createDynamic(PxTransform(PxVec3(0,40,100)), PxSphereGeometry(10), PxVec3(0,-50,-100));
}
샘플 코드에서 호출되는 함수들을 타고 타고 들어가면 initPhysics라는 이름의 함수가 호출되는데, 이 함수에서 피직스에 필요한 모든 초기화 작업이 진행된다.
void initPhysics(bool interactive)
{
gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback);
PxFoundation은 PhysX와 관련된 모든 객체들의 기반이 되는 싱글톤 객체라고 한다. 이 파운데이션 객체에 매달린 객체들이 먼저 모두 릴리즈되어야 파운데이션 객체도 릴리즈할 수 있다.
gPvd = PxCreatePvd(*gFoundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10);
gPvd->connect(*transport,PxPvdInstrumentationFlag::eALL);
PxCreatePvd함수는 파운데이션 객체에 의존하는 PxPvd 클래스를 생성한다. PVD는 PhysX Visual Debugger라는 의미로, 이 클래스로부터 connect 함수를 호출하면 PhysX 기반으로 물리연산을 하는 어플리케이션으로부터 물리정보를 받아 PhysX Visual Debugger 프로그램에서 물리엔진의 동작 상태를 시각적으로 확인할 수 있다.
gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(),true,gPvd);
CreatePhysics 함수는 피직스 객체들을 생성할 수 있는 싱글톤 팩토리 클래스이다. 씬이나 매터리얼을 생성하거나 물리 객체의 형체 정보(Shape), 강체 인스턴스 등 거의 대부분의 PhysX 객체들을 생성할 때 사용된다.
PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());
sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f);
gDispatcher = PxDefaultCpuDispatcherCreate(2);
sceneDesc.cpuDispatcher = gDispatcher;
sceneDesc.filterShader = PxDefaultSimulationFilterShader;
gScene = gPhysics->createScene(sceneDesc);
PhysXScene은 PhysX 액터들이 배치될 수 있는 씬이다.
PxPvdSceneClient* pvdClient = gScene->getScenePvdClient();
if(pvdClient)
{
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true);
}
PvdSceneClient는 Physx Visual Debugger 프로그램에 한 씬의 정보를 전달할 때 어떤 정보를 전달해 줄지 플래그를 설정해주는 객체다.
gMaterial = gPhysics->createMaterial(0.5f, 0.5f, 0.6f);
PxRigidStatic* groundPlane = PxCreatePlane(*gPhysics, PxPlane(0,1,0,0), *gMaterial);
gScene->addActor(*groundPlane);
Material은 물리적인 표면 재질의 상태를 나타내는 객체다. 표면의 정지마찰계수, 운동마찰계수, 반발계수 등 표면 재질의 다양한 성질을 지정할 수 있다. groundPlane을 만들고 이 재질을 입히면 표면 재질의 물리적 특성이 반영되어 지표면이 만들어진다.
for(PxU32 i=0;i<5;i++)
createStack(PxTransform(PxVec3(0,0,stackZ-=10.0f)), 10, 2.0f);
if(!interactive)
createDynamic(PxTransform(PxVec3(0,40,100)), PxSphereGeometry(10), PxVec3(0,-50,-100));
}
static void createStack(const PxTransform& t, PxU32 size, PxReal halfExtent)
{
PxShape* shape = gPhysics->createShape(PxBoxGeometry(halfExtent, halfExtent, halfExtent), *gMaterial);
for(PxU32 i=0; i<size;i++)
{
for(PxU32 j=0;j<size-i;j++)
{
PxTransform localTm(PxVec3(PxReal(j*2) - PxReal(size-i), PxReal(i*2+1), 0) * halfExtent);
PxRigidDynamic* body = gPhysics->createRigidDynamic(t.transform(localTm));
body->attachShape(*shape);
PxRigidBodyExt::updateMassAndInertia(*body, 10.0f);
gScene->addActor(*body);
}
}
shape->release();
}
createStack, createDynamic은 샘플 프로젝트에서 정의된 코드다. 하나의 박스 객체를 만들기 위해서는 Physics 객체로부터 강체를 생성해낸 다음 박스모양의 Shape를 만들어 붙여 씬에 액터로 등록한다.
PX_FORCE_INLINE PxShape* createShape( const PxGeometry& geometry,
const PxMaterial& material,
bool isExclusive = false,
PxShapeFlags shapeFlags = PxShapeFlag::eVISUALIZATION | PxShapeFlag::eSCENE_QUERY_SHAPE | PxShapeFlag::eSIMULATION_SHAPE)
{
PxMaterial* materialPtr = const_cast<PxMaterial*>(&material);
return createShape(geometry, &materialPtr, 1, isExclusive, shapeFlags);
}
CreateShape 함수는 Shape의 속성을 나타내는 PxShapeFlags를 매개변수로 받는데 여기에 어떤 플래그를 활성화시키느냐에 따라 같은 Shape를 물리 시뮬레이션이 되는 덩어리 물질로 쓸수도 있고, 다른 물질이 닿았는지만 체크하는 볼륨 트리거로 쓸 수도 있다.
void renderCallback()
{
stepPhysics(true);
Snippets::startRender(sCamera);
PxScene* scene;
PxGetPhysics().getScenes(&scene,1);
PxU32 nbActors = scene->getNbActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC);
if(nbActors)
{
std::vector<PxRigidActor*> actors(nbActors);
scene->getActors(PxActorTypeFlag::eRIGID_DYNAMIC | PxActorTypeFlag::eRIGID_STATIC, reinterpret_cast<PxActor**>(&actors[0]), nbActors);
Snippets::renderActors(&actors[0], static_cast<PxU32>(actors.size()), true);
}
Snippets::finishRender();
}
void stepPhysics(bool /*interactive*/)
{
gScene->simulate(1.0f/60.0f);
gScene->fetchResults(true);
}
renderCallback은 매 렌더 업데이트마다 호출되는 함수다. 먼저 씬의 simulate 함수에 델타 타임을 매개변수로 넣어 시뮬레이션을 돌리고 그 결과를 받는다. 이 예제 코드에서는 업데이트된 액터들의 상태를 토대로 렌더링만 시키는데 내 게임엔진에서는 액터들의 상태를 이용해 컴포넌트들의 콜백 함수를 호출하거나 상태를 바꾸면 될 것 같다.
'자체엔진 Yunuty > PhysX' 카테고리의 다른 글
PhysX Snippet ContactReport 프로젝트 분석 (0) | 2024.01.05 |
---|