ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 231003 PhysX Collision Checking
    기록 2023. 10. 3. 18:45

    PhysX 컴포넌트에는 FilterGroup을 나누는 PxFilterData를 설정할 수 있다.

    현재 충돌체크와 충돌 확인 시 이벤트 처리 방식이 다음과 같다.

    void CustomSimulationEventCallback::onTrigger(physx::PxTriggerPair* pairs, physx::PxU32 count)
    {
    	//트리거에 충돌이 일어났을때 어떤 걸 할 것인지
    	while (count--)
    	{
    		physx::PxTriggerPair& current = *pairs++;
    
    		physx::PxShape* TriggerShape = current.triggerShape;
    		physx::PxShape* OtherShape = current.otherShape;
    
    		//충돌한 본인 혹은 상대의 액터가 null이면 continue
    		if (TriggerShape->userData == nullptr || OtherShape->userData == nullptr)
    		{
    			continue;
    		}
    		physx::PxFilterData TriggerFilterdata = TriggerShape->getSimulationFilterData();
    		physx::PxFilterData OtherFilterdata = OtherShape->getSimulationFilterData();
    
    		//둘 중 하나라도 충돌 필터 없으면 continue
    		if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) ||
    			OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None))
    		{
    			continue;
    		}
    
    		//C26813  : 비트플래그로 사용된 enum끼리의 비교는 == 이 아닌 bitwise and(&)로 비교하는 것이 좋음
    		//WARNING : resultFd.word0 == static_cast<physx::PxU32>(PhysXFilterGroup::Ground
    		
    		//Obstacle와 PlayerDynamic 충돌체크
    		if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::Obstacle) && // 트리거의 필터그룹이 장애물일때
    			OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::PlayerDynamic))// 충돌한놈의 필터그룹이 플레이어일때
    		{
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS) // 충돌 유지시 계속 들어옴
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    				int a = 0;
    			}
    		}
    		//LeverTrigger와 PlayerDynamic 충돌체크
    		if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::LeverTrigger) && // 트리거 필터 그룹 레버
    			OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::PlayerDynamic))    // 충돌한놈의 필터그룹이 플레이어일때
    		{
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS) // 충돌 유지시 계속 들어옴
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때
    			{
    
    				PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData);
    				PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData);
    			}
    		}
    	}
    }
    

    어떤 FilterGroup과 충돌을 체크 할 것인지 그 추가할 때마다 지정을 해줘야 하고

    충돌 시 이벤트도 여기서 지정해줘야 한다는 문제가 있다.

    충돌이 되었는지 해당하는 Actor에서 바로 확인하기에 위 작업이 thread로 실행되기 때문에 위 피직스 충돌 내용을 컨텐츠쪽으로 가져올 수 없다.

    충돌 유무 만을 확인할 수 있도록 체크할 콜리전 필터타입을 set에 보관하고

    class GlobalValue
    {
    public:
    	static std::set<std::pair<UINT, UINT>> PhysXCollision;
    };
    
    std::set<std::pair<UINT, UINT>> GlobalValue::PhysXCollision = { std::make_pair(static_cast<UINT>(PhysXFilterGroup::PlayerDynamic), static_cast<UINT>(PhysXFilterGroup::MonsterDynamic)), };
    

    vertex 충돌 시 thread가 부여되어 원하는 필터타입의 충돌인지 체크 하는 것이다.

    충돌하였을 경우 GameEngineActor의 atomic_bool맴버변수를 true로 변경하여 충돌 됨을 확인하고

    충돌이 끝난경우 false로 변경해주는 방법을 사용하였다.

    void CustomSimulationEventCallback::onTrigger(physx::PxTriggerPair* pairs, physx::PxU32 count)
    {
    	//트리거에 충돌이 일어났을때 어떤 걸 할 것인지
    
    	while (count--)
    	{
    		physx::PxTriggerPair& current = *pairs++;
    
    		physx::PxShape* TriggerShape = current.triggerShape; // 트리거의 모양
    		physx::PxShape* OtherShape = current.otherShape;
    
    		//충돌한 본인 혹은 상대의 액터가 null이면 continue
    		if (TriggerShape->userData == nullptr || OtherShape->userData == nullptr)
    		{
    			continue;
    		}
    		physx::PxFilterData TriggerFilterdata = TriggerShape->getSimulationFilterData();
    		physx::PxFilterData OtherFilterdata = OtherShape->getSimulationFilterData();
    
    		//둘 중 하나라도 충돌 필터 없으면 continue
    		if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) ||
    			OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None))
    		{
    			continue;
    		}
    		// 실제 데이터가 있는 경우
    
    		// 필터 두개를 make_pair
    		if (GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))||
    			GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0)))
    			) // 두개의 충돌을 체크한다면
    		{
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때
    			{
    
    				GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    				GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    				TestTrigger->isPhysXCollision = true;
    				TestActor->isPhysXCollision = true;
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때
    			{
    
    				GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    				GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    				TestTrigger->isPhysXCollision = false;
    				TestActor->isPhysXCollision = false;
    			}
    		}
    
    	}
    
    }
    

    해당하는 액터에서 체크를 하였을 때 정상적으로 디버깅이 되어 잘 작동한다고 생각했지만..

    여러 문제가 있었는데

    문제1)

    		//둘 중 하나라도 충돌 필터 없으면 continue
    		if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) ||
    			OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None))
    		{
    			continue;
    		}
    

    PhysXFilterGroup::None = 0 으로 위의 if문에 절대 들어가지 않는 식이었던 것이다

    //둘 중 하나라도 충돌 필터 없으면 continue
    		if (0 == TriggerFilterdata.word0  ||
    			0 == OtherFilterdata.word0)
    		{
    			continue;
    		}
    

    위와 같이 변경하여 필터가 없는 피직스 컴포넌트의 충돌은continue하도록 변경하였다.

    문제2)

    		// 필터 두개를 make_pair
    		if (GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))||
    			GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0)))
    			) // 두개의 충돌을 체크한다면
    		{
    

    체크할 필터 조합인지 확인하는 if문인데 end체크로 조합이 존재하지 않는 경우를 체크하여 제대로 된 충돌 체크를 하지 않았다.

    if (GlobalValue::PhysXCollision.end() != GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))||
    			GlobalValue::PhysXCollision.end() != GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0)))
    			) // 두개의 충돌을 체크한다면
    		{
    

    다음과 같이 변경하였다.

    문제3)

    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때
    			{
    
    				GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    				GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    				TestTrigger->isPhysXCollision = true;
    				TestActor->isPhysXCollision = true;
    			}
    
    			if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때
    			{
    
    				GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    				GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    				TestTrigger->isPhysXCollision = false;
    				TestActor->isPhysXCollision = false;
    			}
    

    위 식은 충돌여부는 확인할 수 있으나 무슨 컴포넌트와 충돌했는지 알 수 없다.

    std::atomic_uint filterbit = (static_cast<UINT>(TriggerFilterdata.word0) | static_cast<UINT>(OtherFilterdata.word0));
    if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때
    {
    
    	GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    	GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    	TestTrigger->isPhysXCollision |= filterbit;
    	TestActor->isPhysXCollision |= filterbit;
    }
    
    if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때
    {
    
    	GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData);
    	GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData);
    	TestTrigger->isPhysXCollision = ~(~TestTrigger->isPhysXCollision | filterbit);
    	TestActor->isPhysXCollision = ~(~TestActor->isPhysXCollision | filterbit);
    }
    

    atomic_bool이 아닌 atomic_uint를 이용하여 충돌을 체크하였는데,

    enum class PhysXFilterGroup //PhysX 충돌 용 그룹
    {
    	None = 0,
    	PlayerDynamic = (1 << 0),		// 플레이어 충돌체
    	Ground = (1 << 1),				// 바닥 충돌체
    	Obstacle = (1 << 2),			// 장애물 충돌체
    	GroundTrigger = (1 << 3),		// 트리거
    	LeverTrigger = (1 << 4),	    // Frog_Lever
    	PlayerSkill = (1 << 5),
    	MonsterDynamic = (1 << 6),
    };
    

    필터 그룹을 비트 값으로 가지고 있기 때문에 가능하였다.

    예를 들어 PhysXFilterGroup::MonsterDynamic 를 필터로 가지고 있는 몬스터 기준으로 isPhysXCollision가 0000 0000 0000 0000일때

    이 몬스터가 PhysXFilterGroup ::PlayerSkill 과 충돌한 경우

    filterbit = 0000 0000 0010 0000 | 0000 0000 0100 0000가 되어

    isPhysXCollision == 0000 0000 0110 0000가 된다.

    업데이트 충돌 event를 실행할 때는

    if ( 0 < (HitFromPlayer & static_cast<UINT>(PhysXFilterGroup::PlayerSkill)))
    {
    	//PlayerSkill과 충돌한 경우
    }
    

    다음과 같이 충돌 체크를 하고 업데이트 할 수 있다.

     

     

    다음 그림과 같이

    플레이어 어택범위에 있으면 FirePlant Enemy는 Hit모션으로 state를 변경하도록 하였다.

     

    댓글

Designed by Tistory.