그냥 하나의 커다란 Collider를 Terrain에 붙이면 안 될까?
❌ 불가능합니다.
왜냐하면 Terrain은 단순 평면이 아니라 높낮이(산, 언덕 등)를 표현할 수 있어야 하기 때문에 단일 Collider로는 세밀한 충돌 처리가 어렵습니다.
그래서 우리는 Terrain을 삼각형 단위로 나눠서 충돌을 체크해야 해요!
Component를 상속받는 Terrain 전용 컴포넌트class Terrain : public Component {
public:
Terrain();
~Terrain();
void Create(int32 sizeX, int32 sizeZ, shared_ptr<Material> material);
bool Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance);
private:
shared_ptr<Mesh> _mesh;
int32 _sizeX = 0;
int32 _sizeZ = 0;
};
Create(): Grid Mesh 생성Pick(): 마우스 좌표 → Ray → 삼각형 교차 검사GameObject.h에 다음을 추가해야 합니다:
shared_ptr<Terrain> GetTerrain();
그리고 구현도:
shared_ptr<Terrain> GameObject::GetTerrain() {
auto component = GetFixedComponent(ComponentType::Terrain);
return static_pointer_cast<Terrain>(component);
}
auto obj = make_shared<GameObject>();
obj->AddComponent(make_shared<Terrain>());
obj->GetTerrain()->Create(10, 10, RESOURCES->Get<Material>(L"Veigar"));
CUR_SCENE->Add(obj);
이걸 실행하면 큼직한 땅이 깔리게 됩니다.
이제 마우스로 Terrain을 클릭해서, 그 위치의 실제 좌표를 얻는 기능을 만들어보자!
Terrain::Pick()pickPos에 저장bool Terrain::Pick(int32 screenX, int32 screenY, Vec3& pickPos, float& distance) {
// 1. Unproject → Ray 생성
Matrix W = GetTransform()->GetWorldMatrix();
Matrix V = Camera::S_MatView;
Matrix P = Camera::S_MatProjection;
Viewport& vp = GRAPHICS->GetViewport();
Vec3 n = vp.Unproject(Vec3(screenX, screenY, 0), W, V, P); // near
Vec3 f = vp.Unproject(Vec3(screenX, screenY, 1), W, V, P); // far
Vec3 direction = f - n; direction.Normalize();
Ray ray(n, direction);
// 2. 삼각형마다 교차 검사
auto& vertices = _mesh->GetGeometry()->GetVertices();
for (int z = 0; z < _sizeZ; z++) {
for (int x = 0; x < _sizeX; x++) {
// 정점 인덱스
uint32 i0 = (_sizeX + 1) * z + x;
uint32 i1 = i0 + 1;
uint32 i2 = i0 + (_sizeX + 1);
uint32 i3 = i2 + 1;
Vec3 p0 = vertices[i0].position;
Vec3 p1 = vertices[i1].position;
Vec3 p2 = vertices[i2].position;
Vec3 p3 = vertices[i3].position;
// 삼각형 1
if (ray.Intersects(p0, p1, p2, OUT distance)) {
pickPos = ray.position + ray.direction * distance;
return true;
}
// 삼각형 2
if (ray.Intersects(p3, p1, p2, OUT distance)) {
pickPos = ray.position + ray.direction * distance;
return true;
}
}
}
return false;
}
for (auto& gameObject : gameObjects) {
if (gameObject->GetTerrain() == nullptr)
continue;
Vec3 pickPos;
float distance = 0.f;
if (gameObject->GetTerrain()->Pick(screenX, screenY, OUT pickPos, OUT distance) == false)
continue;
if (distance < minDistance) {
minDistance = distance;
picked = gameObject;
}
}
기존 오브젝트 Pick 로직 뒤에 추가해주면 됩니다.
pickPos 위치를 찍으면 정확하게 클릭 지점을 알 수 있음