1. 기본도형

오늘은 3d 기본 도형에 대해 알아보고 구현해보자 

알아볼 기본도형은 점, 선, 구등이 있다.

점은 말그대로 하나의 좌표이다.

 

선은 시작과 끝이 있는 선분이다. 

 

레이는 시작점이 있으면서 한쪽 방향으로 쭉 뻗어나가는 광선이다.

 

구는 중심점과 반지름을 가진 도형으로 중심에서 반지름까지의 모든 점을 합쳐주면 하나의 구가 되는 것이다.

 

AABB는 x,y,z축이 정렬되어(평행한) 만들어진 Box모양이다. 

 

OBB는 x,y,z축이 정렬되어 있지않은 채 만들어 Box모양이다. 

 

Plane은 평면으로 만들 때 점3개를 이용하여 만들거나 노멀벡터와 한점이나 노멀벡터와 평면과 원점까지의 거리를 활용 하여 만들어줄 수 있다.

 

Triangle은 3개의 점으로 이루어진 도형으로 이때 Union이라는 것을 사용해볼 것인데 이는 자료형이 3개의 각기 다른 자료형으로 표현가능하다는 것이다.

 

Primitive3D.h

#pragma once

// *************
// Point3D
// *************
using Point3D = Vec3;

// *************
// Line3D
// *************

struct Line3D
{
	Point3D start = Point3D(0.f);
	Point3D end = Point3D(0.f);

	float Length() { return Vec3::Distance(start, end); }
	//제곱급
	float LengthSq() { return Vec3::DistanceSquared(start, end); }
};

// *************
// Ray3D
// *************

struct Ray3D
{
	Point3D origin = Point3D(0.f);
	Vec3 direction = Vec3(0.f);

	void NormalizeDirection() { direction.Normalize(); }

	//Ray3D생성
	static Ray3D FromPoints(const Point3D& from, const Point3D& to) { return Ray3D{ from, to - from }; }
};

// *************
// Sphere3D
// *************

struct Sphere3D
{
	Point3D position;
	float radius;
};

// *************
// AABB
// *************

struct AABB3D
{
	Point3D position = Vec3(0.f);
	Vec3 size = Vec3(1.f, 1.f, 1.f);

	//최소점
	static Vec3 GetMin(const AABB3D& aabb)
	{
		Vec3 p1 = aabb.position + aabb.size;
		Vec3 p2 = aabb.position - aabb.size;
		return Vec3(fminf(p1.x, p2.x), fminf(p1.y, p2.y), fminf(p1.z, p2.z));
	}

	//최대점
	static Vec3 GetMax(const AABB3D& aabb)
	{
		Vec3 p1 = aabb.position + aabb.size;
		Vec3 p2 = aabb.position - aabb.size;
		return Vec3(fmaxf(p1.x, p2.x), fmaxf(p1.y, p2.y), fmaxf(p1.z, p2.z));
	}

	//최소 최대주면 AABB 생성해주는
	static AABB3D FromMinMax(const Vec3& min, const Vec3& max)
	{
		return AABB3D((min + max) / 2, (max - min) / 2);
	}
};

// *****************
// OBB
// *****************

struct OBB3D
{
	Point3D position;
	Vec3 size;
	Matrix orientation;
	// Vec4 quaternion;
	// Vec3 rotation;
};

// *****************
// Plane3D
// *****************

// 삼각형 (정점3개)
// 노멀 + 정점 1개
// 노멀 + 거리
struct Plane3D
{
	Vec3 normal;
	float distance;
};

// *****************
// Triangle3D
// *****************

struct Triangle3D
{
	//3개모두 사용할 수 있다.
	union
	{
		struct
		{
			Point3D a;
			Point3D b;
			Point3D c;
		};
		Point3D points[3];
		float values[9];
	};
};

 

 

2.Point Test

이제 만약 어떤 점이 있다면 그것이 도형에 포함이 되어있는지 확인하는 이론에 대해 배워보고 코드로 구현해보자. 

먼저 구에 대해 Point Test를 해보자. 중심점과의 거리를 통해 판별할 수 있다.

bool MathUtils::PointInSphere(const Point3D& point, const Sphere3D& sphere) {
    float magSq = (point - sphere.position).LengthSquared();
    float radSq = sphere.radius * sphere.radius;
    return magSq <= radSq;
}

Point3D MathUtils::ClossetPoint(const Sphere3D& sphere, const Point3D& point) {
    Vec3 sphereToPointDir = (point - sphere.position);
    sphereToPointDir.Normalize();
    return sphere.position + sphereToPointDir * sphere.radius;
}

AABB도 안에 포함되어있는지 표면에 있는거에서 가장 가까운 점이 어느것인지 찾아보자. 이때 위에서 만들어준 min과 max값을 활용하여 검사해줄 수 있다.

bool MathUtils::PointInAABB(const Point3D& point, const AABB3D& aabb) {
    Point3D min = AABB3D::GetMin(aabb);
    Point3D max = AABB3D::GetMax(aabb);

    if (point.x < min.x || point.y < min.y || point.z < min.z)
        return false;
    if (point.x > max.x || point.y > max.y || point.z > max.z)
        return false;

    return true;
}

Point3D MathUtils::ClossetPoint(const AABB3D& aabb, const Point3D& point) {
    Point3D result = point;
    Point3D minPt = AABB3D::GetMin(aabb);
    Point3D maxPt = AABB3D::GetMax(aabb);

    result.x = max(result.x, minPt.x);
    result.y = max(result.y, minPt.y);
    result.z = max(result.z, minPt.z);

    result.x = min(result.x, minPt.x);
    result.y = min(result.y, minPt.y);
    result.z = min(result.z, minPt.z);

    return result;
}

OBB도 동일하게 검사해주는데 이때 AABB로 변환해준 다음에 테스트를 해줄 수 도 있고 아니면 매축에 프로젝션 연산을 해주면 된다.

bool MathUtils::PointInOBB(const Point3D& point, const OBB3D& obb) {
    Vec3 dir = point - obb.position;

    vector<Vec3> axis = { obb.orientation.Right(), obb.orientation.Up(), obb.orientation.Backward() };
    vector<float> size = { obb.size.x, obb.size.y, obb.size.z };

    for (int i = 0; i < 3; i++) {
        float distance = dir.Dot(axis[i]);

        if (distance > size[i] || distance < -size[i])
            return false;
    }

    return true;
}

Point3D MathUtils::ClossetPoint(const OBB3D& obb, const Point3D& point) {
    Vec3 dir = point - obb.position;
    Point3D result;
    vector<Vec3> axis = { obb.orientation.Right(), obb.orientation.Up(), obb.orientation.Backward() };
    vector<float> size = { obb.size.x, obb.size.y, obb.size.z };

    for (int i = 0; i < 3; i++) {
        float distance = dir.Dot(axis[i]);

        distance = clamp(distance, -size[i], size[i]);
        result = result + (axis[i] * distance);
    }

    return obb.position + result;
}

Plane은 위에서 구해준 방법대로 점과 노멀벡터를 내적해주는 것으로 Distance를 구할 수 있는데 이 값을 검사해주면 된다.

bool MathUtils::PointInPlane(const Point3D& point, const Plane3D& plane) {
    float dot = point.Dot(plane.normal);
    return fabs(dot - plane.distance) < FLT_EPSILON;
}

Point3D MathUtils::ClossetPoint(const Plane3D& plane, const Point3D& point) {
    float dot = point.Dot(plane.normal);
    float distance = dot - plane.distance;
    return point - plane.normal * distance;
}

 

Line은 내적을 통해서 구해줄텐데 내적했을 때 거리가 0인지 검사하는 것으로 내부에 있는지 파악할 수 있다. 가장 가까운 점은 내적을 통한 크기와 기존 크기를 통해 비율을 구해서 활용해주면 된다.

bool MathUtils::PointInLine(const Point3D& point, const Line3D& line) {
    Point3D closest = ClossetPoint(line, point);
    return (closest - point).LengthSquared() == 0.f;
}

Point3D MathUtils::ClossetPoint(const Line3D& line, const Point3D& point) {
    Vec3 lineVec = line.end - line.start;
    float t = (point - line.start).Dot(lineVec) / lineVec.Dot(lineVec);
    t = clamp(t, 0.0f, 1.0f);
    return line.start + lineVec * t;
}

 

Ray는 내적을 통해 구해줄텐데 수직을 내렸을 때의 좌표를 구해주면 되는 것이기 때문에 방향벡터와 정점의 좌표 - 시작점을 내적해주고 이를 방향벡터에 곱해주면 위치를 구해줄 수 있다. 그리고 내부에 있는지 확인하려면 시작좌표와 비교하고 아니라면 시작좌표와의 방향벡터와 기존 레이의 방향벡터가 내적했을 때 1이면 같은것이기 때문에 내부에 있는것으로 판단해주면 된다.

bool MathUtils::PointInRay(const Point3D& point, const Ray3D& ray) {
    if (point == ray.origin)
        return true;
    Vec3 norm = point - ray.origin;
    norm.Normalize();
    return fabs(norm.Dot(ray.direction) - 1.0f) < FLT_EPSILON;
}

Point3D MathUtils::ClossetPoint(const Ray3D& ray, const Point3D& point) {
    float t = (point - ray.origin).Dot(ray.direction);
    t = fmaxf(t, 0.0f);
    return ray.origin + ray.direction * t;
}

+ Recent posts