지금까지는 물체간의 충돌만을 구현해보았다. 만약 물체가 아니라 땅을 설치하고 이것과의 충돌을 어떤식으로 구현할지가 문제이다. 

땅은 그리드 방식으로 만들어주자.

CollisionDemo.cpp

//Terrain
{
	auto obj = make_shared<GameObject>();
	obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
	obj->AddComponent(make_shared<MeshRenderer>());
	{
		auto mesh = make_shared<Mesh>();
		mesh->CreateGrid(10, 10);
		obj->GetMeshRenderer()->SetMesh(mesh);
		obj->GetMeshRenderer()->SetPass(0);
	}
	{
		obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
	}
}

 

이제 이땅에 어떻게 Collider를 넣어줄지 생각해보자. 기존에 넣어줬던대로 큰 Collider를 그냥 넣어주면 언덕과 같은 부분과 같이 높이가 반영되어있는 땅에는 제대로 적용이 되지않을 것이다. 그렇기 때문에 이 땅을 삼각형 단위로 좌표를 연산해서 이것을 이용해서 세분화된 상태로 충돌이 처리될 수 있도록 해야한다.

 

이런기능을 만들어주기 위하여 Terrain클래스 컴포넌트로 만들어서 관리할 수 있도록 하자.

Terrain.h

#pragma once
#include "Component.h"

class Terrain : public Component
{
	using Super = Component;

public:
	Terrain();
	~Terrain();

	void Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material);

	int32 GetSizeX() { return _sizeX; }
	int32 GetSizeZ() { return _sizeZ; }

	//bool Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance);

private:
	shared_ptr<Mesh> _mesh;
	int32 _sizeX = 0;
	int32 _sizeZ = 0;
};

Terrain.cpp

#include "pch.h"
#include "Terrain.h"
#include "MeshRenderer.h"
#include "Camera.h"

Terrain::Terrain() : Super(ComponentType::Terrain)
{

}

Terrain::~Terrain()
{

}

void Terrain::Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material)
{
	_sizeX = sizeX;
	_sizeZ = sizeZ;

	//Weak포인터라서 변환필요
	auto go = _gameObject.lock();

	go->GetOrAddTransform();

	if (go->GetMeshRenderer() == nullptr)
		go->AddComponent(make_shared<MeshRenderer>());

	_mesh = make_shared<Mesh>();
	_mesh->CreateGrid(sizeX, sizeZ);

	go->GetMeshRenderer()->SetMesh(_mesh);
	go->GetMeshRenderer()->SetPass(0);
	go->GetMeshRenderer()->SetMaterial(material);
}

 

사용은 다음과 같이 하면된다.

CollisionDemo.cpp

	//Terrain
	{
		auto obj = make_shared<GameObject>();
		obj->AddComponent(make_shared<Terrain>());
		obj->GetTerrain()->Create(10, 10, RESOURCES->Get<Material>(L"Veigar"));

		CUR_SCENE->Add(obj);
	}

 

이제 피킹기능을 넣어주자. 중요한점은 사각형을 구성하는 2개의 삼각형을 기준으로 피킹검사를 하고 이를 모든 사각형에 반복하면 된다.

Terrain.h

bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance)
{
	Matrix W = GetTransform()->GetWorldMatrix();
	Matrix V = Camera::S_MatView;
	Matrix P = Camera::S_MatProjection;

	Viewport& vp = GRAPHICS->GetViewport();

	//near far
	Vec3 n = vp.Unproject(Vec3(screenX, screenY, 0), W, V, P);
	Vec3 f = vp.Unproject(Vec3(screenX, screenY, 1), W, V, P);

	Vec3 start = n;
	Vec3 direction = f - n;
	direction.Normalize();

	Ray ray = Ray(start, direction);

	
	const auto& vertices = _mesh->GetGeometry()->GetVertices();

	for (int32 z = 0; z < _sizeZ; z++)
	{
		for (int32 x = 0; x < _sizeX; x++)
		{
			uint32 index[4];
			//사각형 각 점
			index[0] = (_sizeX + 1) * z + x;
			index[1] = (_sizeX + 1) * z + x + 1;
			index[2] = (_sizeX + 1) * (z + 1) + x;
			index[3] = (_sizeX + 1) * (z + 1) + x + 1;

			Vec3 p[4];
			for (int32 i = 0; i < 4; i++)
				p[i] = vertices[index[i]].position;

			//  [2]
			//   |	\
			//  [0] - [1]
			//삼각형 테스트
			if (ray.Intersects(p[0], p[1], p[2], OUT distance))
			{
				pickPos = ray.position + ray.direction * distance;
				return true;
			}

			//  [2] - [3]
			//   	\  |
			//		  [1]
			if (ray.Intersects(p[3], p[1], p[2], OUT distance))
			{
				pickPos = ray.position + ray.direction * distance;
				return true;
			}
		}
	}

	return false;
}

 

이렇게 해주고 Scene에서 Picking을 검사하는 부분에 이 Pick함수를 실행하도록 만들어주자.

Scene.cpp

shared_ptr<class GameObject> Scene::Pick(int32 screenX, int32 screenY)
{
	shared_ptr<Camera> camera = GetCamera()->GetCamera();

	float width = GRAPHICS->GetViewport().GetWidth();
	float height = GRAPHICS->GetViewport().GetHeight();
	

	Matrix projectionMatrix = camera->GetProjectionMatrix();

	//스크린 ->뷰포트공식
	float viewX = (+2.0f * screenX / width - 1.0f) / projectionMatrix(0, 0);
	float viewY = (-2.0f * screenY / height + 1.0f) / projectionMatrix(1, 1);

	Matrix viewMatrix = camera->GetViewMatrix();
	Matrix viewMatrixInv = viewMatrix.Invert();

	const auto& gameObjects = GetObjects();

	float minDistance = FLT_MAX;
	shared_ptr<GameObject> picked;

	for (auto& gameObject : gameObjects)
	{
		if (gameObject->GetCollider() == nullptr)
			continue;

		// ViewSpace에서의 Ray 정의 ViewSpace 0 0 0 -> 카메라
		Vec4 rayOrigin = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
		Vec4 rayDir = Vec4(viewX, viewY, 1.0f, 0.0f);

		// WorldSpace에서의 Ray 정의
		Vec3 worldRayOrigin = XMVector3TransformCoord(rayOrigin, viewMatrixInv);		//위치까지
		Vec3 worldRayDir = XMVector3TransformNormal(rayDir, viewMatrixInv);				//위치는 그대로 방향만
		worldRayDir.Normalize();

		// WorldSpace에서 연산
		Ray ray = Ray(worldRayOrigin, worldRayDir);

		float distance = 0.f;
		if (gameObject->GetCollider()->Intersects(ray, OUT distance) == false)
			continue;

		if (distance < minDistance)
		{
			minDistance = distance;
			picked = gameObject;
		}
	}

	for (auto& gameObjcect : gameObjects)
	{
		if (gameObjcect->GetTerrain() == nullptr)
			continue;

		Vec3 pickPos;
		float distance = 0.f;
		if (gameObjcect->GetTerrain()->Pick(screenX, screenY, OUT pickPos, OUT distance) == false)
			continue;

		if (distance < minDistance)
		{
			minDistance = distance;
			picked = gameObjcect;

		}
	}

	return picked;
}

 

이렇게 해준다음 실행해보면 땅을 클릭했을 때 잘 없어지는 것을 볼 수 있다.

 

만약 땅이 아니라 복잡한 매쉬와 충돌이나 레이케스팅을 한다면 이런 삼각형으로 나눠서 하는방법을 응용할 수 있을 것이다. 하지만 이렇게하면 부하가 엄청나다. 그렇기 때문에 간단한 도형으로 된 충돌 판정용 매쉬나 Collider 가지고 충돌을 판별해주는게 나을 수 있다.

조금 더 효율적으로 하려면 레이어를 구분해서 레이어끼리 공식을 만들어서 작동시켜주면 된다.

+ Recent posts