Physics 클래스는 물리 처리에 대한 모든 것을 관리한다.
(블록과 블록이 이루고 있는 타워까지)
Physics 생성자
Physics::Physics ()
{
srand(time(NULL) * time(NULL));
gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gDefaultAllocatorCallback, gDefaultErrorCallback);
gPhysicsSDK = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale());
if(gPhysicsSDK == NULL)
{
cerr<<"Error creating PhysX3 device, Exiting..."<<endl;
exit(1);
}
PxSceneDesc sceneDesc(gPhysicsSDK->getTolerancesScale());
sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0);
sceneDesc.cpuDispatcher = PxDefaultCpuDispatcherCreate(1);
sceneDesc.filterShader = PxDefaultSimulationFilterShader;
gScene = gPhysicsSDK->createScene(sceneDesc);
// 재질 생성
// 1. staticFriction = 접촉면과 수직방향으로 발생하는 저항력의 크기, 값이 클수록 덜 미끄러진다
// 2. dynamicFriction = 접촉면과 평행방향으로 발생하는 저항력의 크기, 값이 클수록 덜 미끄러진다.
// 3. restitution = 물체가 충돌 후 튀어오르는 정도, 값이 클수록 충돌 후 더 많이 튀어오른다.
PxMaterial* stoneMat = gPhysicsSDK->createMaterial(5.0, 0.5f, 0.1f);
// PxTransform의 1번째 인자는 위치값을 나타낸다 (Vec3로 각 x, y, z)
// PxQuat는 회전 정보를 나타낸다. (PxHalfPi만큼 PxVec3 방향으로..)
// 이렇게 생성된 평면은 XY평면에 수직하게 된다.
PxTransform planePos = PxTransform(PxVec3(0.0f, 0.0f, 0.0f), PxQuat(PxHalfPi, PxVec3(0.0f, 0.0f, 1.0f)));
// 정적인 물체 plane을 생성하는 코드
// 이 함수는 PxRigidStatic을 반환하는데, 이건 정적인, 움직이지 않는 물체를 표현한다
// PxRigidStatic은 Shape를 가지며, shape에 다양한 geometry를 적용할 수 있다.
plane = gPhysicsSDK->createRigidStatic(planePos);
// PxPlaneGeometry를 전달했으므로 물체는 평면이 된다.
// stoneMat은 물체의 물성을 전달한다.
// 이렇게 생성된 도형은 이후 RigidActor에 전달된다.
PxShape* shape = gPhysicsSDK->createShape(PxPlaneGeometry(), *stoneMat);
plane->attachShape(*shape);
gScene->addActor(*plane);
buildTower();
//highestBlocks.push_back();
}
- Physics의 생성자에서 하는 일은 아래와 같다.
1. Physics API 초기화
: PxPhysics 클래스와 PxFoundation 클래스를 초기화한다.
*PxPhysics 클래스는 객체를 생성하고 초기화하는데 사용된다.
*PxFoundation 클래스는 물리 시뮬레이터 시스템의 초기화와 종료를 담당한다.
2. Scene 생성
*PxScene 클래스는 물리 시뮬레이션을 위한 공간을 정의한다.
3. Ground 생성
: 정확히는 Plane의 역할을 하는 RigidStatic 객체를 Scene에 추가한다.
*RigidStatic 객체는 정적인, 움직이지 않는 객체를 나타낸다.
4. Tower 생성
: buildTower 함수를 통해 타워를 생성한다.
: 젠가에서 타워는 여러 개의 블록으로 이루어져 있으므로,
: buildTower 내부에서는 그 블록을 생성해서 적합한 위치에 배치한다.
buildTower 함수
- 이 함수가 하는 일은 for문을 돌며 각 층마다 3개씩 블록을 생성해서 쌓아나가는 것
- 상세 구현 내용은 아래와 같다.
1. 랜덤 값을 3 세트 계산한다 (한 층에 블록이 3개니까)
2. 1에서 구한 랜덤 값을 이용해서 createMaterial 함수를 통해 Material을 생성한다.
3. 각 블록에 대한 위치값 결정 (한 층에 해당하는 3개 블록 각각의 위치)
4. 전역에 상수값으로 정의된 블록의 크기를 참조해서 박스 형태의 지오메트리 생성
5. 해당 지오메트리와 3에서 구한 위치값, 2에서 구한 Material을 이용해서 RigidDynamic 객체 생성(Actor)
6. 생성된 Actor를 Scene에 추가
// 물리엔진을 이용해 블록을 쌓는 기능
void Physics::buildTower()
{
// 이전에 쌓았던 블록 제거
if (blockArray.size() > 0)
{
for (int i = 0; i < numBlocks; i++)
{
gScene->removeActor(*blockArray[i]);
}
}
blockArray.clear();
bool yes = true;
// 랜덤한 값을 생성해서 마찰력, 크기를 결정한다
for (int i=0; i < numBlocks/3; i++)
{
float sFricRand1 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float dFricRand1 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float sFricRand2 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float dFricRand2 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float sFricRand3 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float dFricRand3 = 0.0f - ((float(rand())/float(RAND_MAX))*randFriction);
float shortRand1 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float middleRand1 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float longRand1 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float shortRand2 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float middleRand2 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float longRand2 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float shortRand3 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float middleRand3 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
float longRand3 = randSize - ((float(rand())/float(RAND_MAX))*2*randSize);
if (shortRand1 < shortRand2 && shortRand2 <= shortRand3)
{
if(yes)
shortRand3 = shortRand1;
else
shortRand2 = shortRand1;
yes = !yes;
}
else if (shortRand1 > shortRand2 && shortRand3 > shortRand2)
{
if (!yes)
shortRand1 = shortRand2;
else
shortRand3 = shortRand2;
yes = !yes;
}
else if (shortRand1 > shortRand2 && shortRand2 >= shortRand3)
{
if(yes)
shortRand3 = shortRand1;
else
shortRand2 = shortRand3;
yes = !yes;
}
// 위의 랜덤한 값을 이용해 Material을 생성한다.
woodMat1 = gPhysicsSDK->createMaterial(0.5f+sFricRand1, 0.2f+dFricRand1, 0.603f);
woodMat2 = gPhysicsSDK->createMaterial(0.5f+sFricRand2, 0.2f+dFricRand2, 0.603f);
woodMat3 = gPhysicsSDK->createMaterial(0.5f+sFricRand3, 0.2f+dFricRand3, 0.603f);
PxTransform bPos1, bPos2, bPos3;
PxRigidDynamic *temp1 = NULL, *temp2 = NULL, *temp3 = NULL;
// 홀수, 짝수층에 대해 다른 방향으로 블록을 쌓는다
if (i%2 != 0)
{
bPos1 = PxTransform(PxVec3(0.0f, (float(i) * 2*halfBoxShortSide)+0.1f, (-poseOffset - 2*halfBoxMiddleSide)), PxQuat(PxHalfPi, PxVec3(0.0f, 1.0f, 0.0f)));
bPos2 = PxTransform(PxVec3(0.0f, (float(i) * 2*halfBoxShortSide)+0.1f, 0.0f), PxQuat(PxHalfPi, PxVec3(0.0f, 1.0f, 0.0f)));
bPos3 = PxTransform(PxVec3(0.0f, (float(i) * 2*halfBoxShortSide)+0.1f, (+poseOffset + 2*halfBoxMiddleSide)), PxQuat(PxHalfPi, PxVec3(0.0f, 1.0f, 0.0f)));
}
else
{
bPos1 = PxTransform(PxVec3((-poseOffset - 2*halfBoxMiddleSide), (float(i) * 2*halfBoxShortSide)+0.1f, 0.0f)); //+halfBoxShortSide
bPos2 = PxTransform(PxVec3(0.0f, (float(i) * 2*halfBoxShortSide)+0.1f, 0.0f));
bPos3 = PxTransform(PxVec3((+poseOffset + 2*halfBoxMiddleSide), (float(i) * 2*halfBoxShortSide)+0.1f, 0.0f));
}
// 크기를 결정하고 그 크기에 해당하는 박스 지오메트리를 생성한다.
// 그 후에 그 지오메트리를 통해 Actor를 생성하고,
// 그 Actor를 Scene에 추가한다.
PxBoxGeometry bGeometry1 = PxBoxGeometry(PxVec3(halfBoxMiddleSide+middleRand1,halfBoxShortSide+shortRand1,halfBoxLongSide+longRand1));
temp1 = PxCreateDynamic(*gPhysicsSDK, bPos1, bGeometry1, *woodMat1, 1.0f);
gScene->addActor(*temp1);
blockArray.push_back(temp1);
PxBoxGeometry bGeometry2 = PxBoxGeometry(PxVec3(halfBoxMiddleSide+middleRand2,halfBoxShortSide+shortRand2,halfBoxLongSide+longRand2));
temp2 = PxCreateDynamic(*gPhysicsSDK, bPos2, bGeometry2, *woodMat2, 1.0f);
gScene->addActor(*temp2);
blockArray.push_back(temp2);
PxBoxGeometry bGeometry3 = PxBoxGeometry(PxVec3(halfBoxMiddleSide+middleRand3,halfBoxShortSide+shortRand3,halfBoxLongSide+longRand3));
temp3 = PxCreateDynamic(*gPhysicsSDK, bPos3, bGeometry3, *woodMat3, 1.0f);
gScene->addActor(*temp3);
blockArray.push_back(temp3);
}
// 가장 높은 위치의 블럭 인덱스 저장
if (!highestBlocks.empty())
highestBlocks.clear();
highestBlocks.push_back(49);
highestBlocks.push_back(50);
highestBlocks.push_back(51);
highestBlocks.push_back(52);
highestBlocks.push_back(53);
numBlocksHighest = 0;
}
- 맨 마지막 코드는 가장 위에 놓인 블록의 인덱스를 저장한다.
(이후 코드에서 이 인덱스를 검사해서 조작할 수 있는 블록인지 확인하는데, 정확한 의미는 좀 더 들여다봐야 할 듯)
getBoxPoseRender 함수
- 이 함수는 물체가 그려지는 위치를 나타내는 행렬을 뽑아내는 함수
- 2번째 인자로 넘어가는 float이 matrix가 저장될 첫 번째 주소이다.
// 물체가 그려지는 위치를 받아오는 함수
void Physics::getBoxPoseRender(const int *num, float *mat)
{
if (*num < 0 )
return;
PxTransform trans = blockArray[*num]->getGlobalPose();
PxMat44 m = PxMat44(trans);
mat[0] = m.column0.x;
mat[1] = m.column0.y;
mat[2] = m.column0.z;
mat[3] = m.column0.w;
mat[4] = m.column1.x;
mat[5] = m.column1.y;
mat[6] = m.column1.z;
mat[7] = m.column1.w;
mat[8] = m.column2.x;
mat[9] = m.column2.y;
mat[10] = m.column2.z;
mat[11] = m.column2.w;
mat[12] = m.column3.x;// + halfBoxMiddleSide;
mat[13] = m.column3.y;// + halfBoxShortSide;
mat[14] = m.column3.z;// + halfBoxLongSide;
mat[15] = m.column3.w;
}
- 아직 이 함수가 어떻게 사용되는지는 확인하지 못했다.
addSpring 함수
- 이 함수는 물리 시뮬레이션에서 스프링을 추가하는 함수
- 동작은 다음과 같이 나뉜다.
1. 높이가 가장 높은 블록들을 가져온 후 가장 높은 위치의 블록에서 2를 빼서,
가장 높은 블록의 개수가 2개 이상이 되도록 한다.
그리고 for문을 돌아서 선택된 것이 있다면 리턴 (정확한 의도는 아직 파악할 수 없음)
2. 인자로 넘겨 받은 pos값을 통해 시작 위치를 찾는다.
3. 인자로 넘겨 받은 dir값을 통해 방향을 구한 후 정규화시킨다.
4. Scene의 멤버함수를 통해 빛을 쏴서 충돌여부를 검사한 후 결과값은 PxRaycastBuffer에 담긴다.
5. 충돌했으면 true가 반환되어 그 방향으로 스프링이 발사된다. (false라면 스프링의 발사위치는 현재 블록 위치)
void Physics::addSpring(const int *num, const float *pos, const float *dir)
{
//avoid wrong blocks
if (*num < 0) return;
//높이가 가장 높은 블록들을 가져온다.
vector<int> unusableBlocks = highestBlocks;
// 가장 높은 블록의 개수가 2개 이상이 되도록 한다.
int start = 2 - numBlocksHighest;
for (int i = start; i< 5; i++)
{
// start 변수 이후의 블록 중에 선택된 것이 있다면 리턴
if (unusableBlocks[i]==*num)
return;
}
currBlock = *num;
//시작 위치를 찾는다.
//인자로 넘어온건 Vector3의 첫 번째 요소(x)여야 한다
PxVec3 startPoint = PxVec3(pos[0], pos[1], pos[2]);
//PxQuat(PxHalfPi, PxVec3(0.0f, 1.0f, 0.0f)).rotate(startPoint);
// 스프링의 방향을 구한다.
//dirTest = PxVec3(dir[0], dir[1], dir[2]);
PxVec3 goUnit = PxVec3(dir[0], dir[1], dir[2]);
goUnit.normalize();
//충돌 결과를 저장하기 위한 구조체
PxRaycastBuffer hit;
//nbHits: 레이캐스트 충돌 검사에서 검출된 충돌 개수
//block : 충돌된 물체의 정보(바운딩 박스, 면, 액터 등)
//distance : 충돌 지점까지의 거리
//position : 충돌 지점의 위치
//normal : 충돌 지점의 법선 벡터
// 충돌 여부 검사
bool status;
status = gScene->raycast(startPoint, goUnit, 100.0f, hit);
// status가 false인 경우 스프링이 발사되는 위치는 현재 블록의 위치가 된다.
if (!status)
{
PxVec3 tempBlockPos = blockArray[currBlock]->getGlobalPose().p;
globalPosMouse = tempBlockPos;
localPosBlock = tempBlockPos;
}
// status가 true인경우 충돌했다는 의미이므로 그 방향으로 스프링이 발사된다.
else
{
PxVec3 tempBlockPos = blockArray[currBlock]->getGlobalPose().p;
globalPosMouse = hit.block.position;
//globalPosMouse.y = tempBlockPos.y;
localPosBlock = (globalPosMouse - tempBlockPos);
}
springNormalLength = 0.0f;
}
updateSpringPos 함수
- 스프링 시뮬레이션을 업데이트하는 함수
- 하는 일은 아래와 같다.
1. 선택된 블록인지, 사용할 수 있는 블록인지 검사한다.
2. 입력으로 받은 각도 값을 통해 각도를 구하고,
그 각도만큼 받은 방향을 회전시킨다.
3. 그 회전시킨 방향을 나타내는 벡터 nDir을 만들고 정규화한다.
4. 매 update마다 globalPosMouse 위치를 nDir 방향으로 조금씩 이동시킨다.
5. 마우스에서 블록을 향하는 벡터를 구하고 그 거리를 구한 후에 벡터를 정규화시킨다.
6. 해당 방향으로 스프링 상수만큼의 힘을 갖는 벡터를 정의한다.
7. addForceAtLocalPos 함수를 통해 힘을 적용시킨다.
// 스프링 시뮬레이션을 업데이트하는 함수
void Physics::updateSpringPos(float angleInRadians, float length, const float *dir)
{
// 선택된 블록이 있는지 확인
if (currBlock < 0)
return;
// 선택된 사용할 수 있는 블록인지 확인
vector<int> unusableBlocks = highestBlocks;
int start = 2 - numBlocksHighest;
for (int i = start; i< 5; i++)
{
if (unusableBlocks[i]==currBlock)
return;
}
// 입력된 각도와 방향에 따라 새로운 방향을 구한다.
float angle = angleInRadians + PxPi;
float xDir = cosf(angle) * dir[0] - sinf(angle) * dir[2];
float zDir = sinf(angle) * dir[0] + cosf(angle) * dir[2];
PxVec3 nDir = PxVec3(xDir, 0.0f, zDir);
nDir.normalize();
// globalPosMouse 위치를 nDir 위치로 조금씩 이동시키는 로직
globalPosMouse = globalPosMouse + (0.1f * nDir);
// 마우스와 블록 사이의 거리 벡터 구하기
PxVec3 dirBetweenBoxAndMouse = globalPosMouse - (blockArray[currBlock]->getGlobalPose().p + localPosBlock);
// 블록을 상하 방향으로 이동을 방지한다.
dirBetweenBoxAndMouse.y = 0.0f;
// 마우스와 블록 간 거리를 저장한다.
float dirBetweenBoxAndMouseLength = dirBetweenBoxAndMouse.magnitude();
float deltaLength = dirBetweenBoxAndMouseLength - springNormalLength;
dirBetweenBoxAndMouse.normalize();
// 탄성 힘의 크기와 방향을 결정한다.
// 벡터는 방향과 크기를 가지고 있으니까.
PxVec3 forceToAdd = springConstant * dirBetweenBoxAndMouse;
// 위에서 구한 힘 벡터를 이용해서 지정한 방향으로 힘을 가한다.
PxRigidBodyExt::addForceAtLocalPos(*blockArray[currBlock], forceToAdd, localPosBlock, PxForceMode::eFORCE);
// *blockArray[currBlock] 블록에게 localPosBlock 방향에서 forceToAdd 방향으로 힘을 가한다!
}
pushBlock 함수
- 블록을 밀어내는 기능을 하는 함수
- 마찬가지로 인자로 넘어온 num 값이 유효한지 검사한다.
- 두 번째 인자로 넘어온 방향을 통해 힘과 방향을 구하고,
- 첫 번째 인자로 넘어온 인덱스값을 통해 블록에 접근해서 위치값을 꺼내온다.
- 그 위치에 힘을 적용한다. (젠가 블록이 특정 방향으로 밀리게 된다)
// 블록을 밀어내는 기능을 하는 함수
bool Physics::pushBlock(const int *num, const float *dir)
{
if (*num < 0) return false;
// 상단의 블럭인지 확인해서 밀어낼 수 있는지 확인
vector<int> unusableBlocks = highestBlocks;
int top = numBlocksHighest;
int start = 2 - top ;
for (int i = start; i< 5; i++)
{
if (unusableBlocks[i]==*num)
return false;
}
// 힘의 크기와 방향을 정한다
PxVec3 impulse = PxVec3(dir[0], 0.0f, dir[2]);
// position은 그 힘을 적용할 위치
PxVec3 position = blockArray[*num]->getGlobalPose().p;
// 힘을 적용한다
PxRigidBodyExt::addForceAtPos(*blockArray[*num], impulse, position, PxForceMode::eIMPULSE);
// eIMPULSE는 일정한 힘을 물체에 적용하는 상수값
//힘이 작용했으면 TRUE 반환
return true;
}
'PhysX > [Github] Jenga 분석' 카테고리의 다른 글
3. Physics 클래스 분석 - 2 (0) | 2023.02.28 |
---|---|
1. WinMain - Jenga 게임 메인 로직 (0) | 2023.02.26 |
댓글