2. 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. 물리 API 초기화

: PxPhysics 클래스와 PxFoundation 클래스를 초기화합니다.

*PxPhysics 클래스는 개체를 만들고 초기화하는 데 사용됩니다.

*PxFoundation 클래스는 물리 시뮬레이터 시스템을 초기화하고 닫는 역할을 합니다.

2. 장면 생성

*PxScene 클래스는 물리 시뮬레이션을 위한 공간을 정의합니다.

3. 그라운드 만들기

: 평면 역할을 하는 RigidStatic 개체를 장면에 추가합니다.

*RigidStatic 객체는 고정된 움직이지 않는 객체를 나타냅니다.

4. 타워 만들기

: buildTower 기능으로 타워를 구축합니다.

: 젠가에서는 타워가 여러 블록으로 구성되어 있기 때문에

: buildTower에서 블록이 생성되어 적당한 위치에 배치됩니다.


buildTower 함수

– 이 함수는 for 문을 거치고 쌓아서 각 레벨에 대해 3개의 블록을 생성합니다.

– 세부 시행 내용은 다음과 같습니다.

1. 임의의 값 3세트 계산(레벨당 3개의 블록이 있기 때문에)

2. 1단계에서 얻은 임의의 값을 사용하여 createMaterial 함수를 사용하여 재질을 생성합니다.

3. 각 블록의 위치 값 결정(3개의 블록 각각의 위치는 층에 해당)

4. 전역 상수 값으로 정의된 블록 크기를 참조하여 상자 모양의 지오메트리를 생성합니다.

5. 3단계에서 얻은 지오메트리, 위치 값 및 2단계에서 얻은 재료(액터)로 RigidDynamic 객체를 만듭니다.

6. 생성된 액터를 씬에 추가

// 물리엔진을 이용해 블록을 쌓는 기능
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 함수

– 이 함수는 객체가 그려지는 위치를 나타내는 행렬을 추출하는 함수입니다.

– 두 번째 인수로 전달된 float는 행렬이 저장될 첫 번째 주소입니다.

// 물체가 그려지는 위치를 받아오는 함수
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 Member 함수를 통해 빛을 쏘고 충돌 여부를 검사한 후 그 결과를 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. 업데이트할 때마다 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 방향으로 힘을 가한다!
}


푸시블록 기능

– 블록을 밀어주는 역할을 하는 기능

– 인자로 전달된 수치가 유효한지 여부도 확인한다.

– 두 번째 인수로 전달된 방향으로 힘과 방향을 찾고,

– 첫 번째 인자로 전달된 인덱스 값으로 블록에 접근하여 위치 값을 가져옵니다.

– 이 위치에 전원을 적용합니다.

(젠가 블록은 특정 방향으로 밀림)

// 블록을 밀어내는 기능을 하는 함수
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;
}