지금까지는 물체간의 충돌만을 구현해보았다. 만약 물체가 아니라 땅을 설치하고 이것과의 충돌을 어떤식으로 구현할지가 문제이다.
땅은 그리드 방식으로 만들어주자.
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 가지고 충돌을 판별해주는게 나을 수 있다.
조금 더 효율적으로 하려면 레이어를 구분해서 레이어끼리 공식을 만들어서 작동시켜주면 된다.
'게임공부 > Directx11' 카테고리의 다른 글
[Directx11][C++][3D]31. 수학2(Intersection & RayCasting) (1) | 2024.10.09 |
---|---|
[Directx11][C++][3D]30. 도형 (0) | 2024.10.07 |
[Directx11][C++][3D]28. Collision(Box) & Picking (0) | 2024.10.06 |
[Directx11][C++][3D]27. Collision(Sphere) (3) | 2024.10.06 |
[Directx11][C++][3D]26. ViewPort 이론 (0) | 2024.10.03 |