C++에서 광선과 평면의 교차점

Abdul Mateen 2023년10월12일
  1. C++에서 벡터 연산 사용
  2. C++에서 광선과 평면의 교차점
C++에서 광선과 평면의 교차점

이 자습서에서는 먼저 C++의 광선 평면 교차에 대한 완전한 지침을 얻을 것입니다.

먼저 구현과 함께 벡터 연산에 대해 논의합니다. 다음으로 레이-플레인 교차의 개념과 C++에서의 구현에 대해 논의할 것입니다.

C++에서 벡터 연산 사용

여기서는 관련 벡터 연산과 C++에서의 구현을 다룰 것입니다. 다음 기능이 필요합니다.

  1. 빼기 연산자: 두 점 사이의 거리를 계산합니다.
  2. 내적: 실수가 되는 두 벡터 간의 내적을 계산하는 함수입니다.
  3. 곱셈 연산자: 두 벡터의 외적을 계산합니다.
  4. float 매개변수가 있는 곱셈 연산자: 벡터와 스칼라 값 사이의 스칼라 곱셈을 계산합니다.
  5. 크기 함수: 벡터의 크기를 계산합니다.
  6. 정규화 기능: 벡터의 법선을 계산합니다.
  7. 마지막으로 스트림 연산자가 오버로드되어 벡터를 표시합니다.

다음은 완전한 벡터 3D 클래스입니다. main 기능은 벡터 3D 클래스의 멤버 기능을 확인/시연합니다.

#include <math.h>

#include <iostream>

using namespace std;

class Vec3 {
  float x;
  float y;
  float z;

 public:
  Vec3() { x, y, z = 0; }
  Vec3(float x, float y, float z) {
    this->x = x;
    this->y = y;
    this->z = z;
  }
  Vec3 &operator+=(const Vec3 &b) {
    x = x + b.x;
    y = y + b.y;
    z = z + b.z;
    return *this;
  }
  Vec3 &operator-=(const Vec3 &b) {
    x = x - b.x;
    y = y - b.y;
    z = z - b.z;
    return *this;
  }
  Vec3 operator+(const Vec3 &b) {
    Vec3 newV = *this;
    newV += b;
    return newV;
  }
  Vec3 operator-(const Vec3 &b) {
    Vec3 newV = *this;
    newV -= b;
    return newV;
  }
  Vec3 operator*(const Vec3 &b) {  // cross operator
    Vec3 newV;
    newV.x = y * b.z - z * b.y;
    newV.y = x * b.z - z * b.x;
    newV.z = x * b.y - y * b.x;
    return newV;
  }
  Vec3 &operator*=(const float s) {  // Dot equal operator
    x = x * s;
    y = y * s;
    z = z * s;
    return *this;
  }
  Vec3 operator*(const float s) {  // Dot equal operator
    Vec3 newV = *this;
    return newV *= s;
  }
  float mag() { return sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)); }
  Vec3 &normalize() {
    double mag = this->mag();
    x /= mag;
    y /= mag;
    z /= mag;
    return *this;
  }
  // Dot operator
  float dot(const Vec3 &b) { return x * b.x + y * b.y + z * b.z; }
  friend ostream &operator<<(ostream &out, const Vec3 &v) {
    out << "(" << v.x << ", " << v.y << ", " << v.z << ")\n";
    return out;
  }
};

int main() {
  Vec3 v1(-5, 7, 2);
  Vec3 v2(4, 12, 1);
  Vec3 v3(1, 1, 1);
  cout << "Vec1 = " << v1 << endl;
  v1 += v2;
  cout << "Result of adding Vec2 in Vec1 = " << v1 << endl;
  v1 -= v3;
  cout << "Result of subtracting Vec3 from vec1 = " << v1 << endl;
  v1 *= 5;  // dot operation
  cout << "Resultant after the scaling of Vec1 with a value of 5 = " << v1
       << endl;
  cout << "Magnitude for Vec1 = " << v1.mag() << endl;
  v1.normalize();
  cout << "Vec1 after normalization = " << v1 << endl;
  cout << "Dot product of Vec1 and Vec2 = " << v1 * v2 << endl;
  return 0;
}

다음은 벡터 3D 클래스의 정확한 구현을 보여주는 main 함수의 출력입니다.

Vec1 = (-5, 7, 2)

Result of adding Vec2 in Vec1 = (-1, 19, 3)

Result of subtracting Vec3 from vec1 = (-2, 18, 2)

Resultant after the scaling of Vec1 with a value of 5 = (-10, 90, 10)

Magnitude for Vec1 = 91.1043
Vec1 after normalization = (-0.109764, 0.987878, 0.109764)

Dot product of Vec1 and Vec2 = (-0.329293, -0.548821, -5.26868)

C++에서 광선과 평면의 교차점

여기서부터는 독자가 벡터 연산의 개념에 상당히 익숙하다고 가정합니다. 또한 독자가 평면에 대한 기본 개념을 가지고 있다고 가정합니다.

이러한 개념이 불편하시다면 이 링크를 따라 벡터 연산 및 평면에 대해 자세히 알아보세요.

평면에서 광선의 교차점을 찾기 위해 몇 가지 공식적인 수학적 사실과 세부 사항을 살펴보겠습니다.

우리는 두 직교 또는 수직 벡터의 내적이 항상 0이라는 것을 알고 있습니다. ab가 두 수직 또는 직교 벡터라고 가정하면 a.b=0입니다.

이제 원점에서 평면까지의 거리를 나타내는 평면의 점 p0과 평면에 수직인 벡터 n을 고려하십시오. 점 p0에서 평면의 임의 점을 빼서 벡터 p를 계산할 수 있습니다.

결과 벡터는 평면에 있고 평면 법선에 수직입니다.

이 사실은 다음 방정식을 제공합니다.

(p-p0) . N = 0                                            (i)

l0l을 광선의 시작점과 광선의 방향으로 각각 고려하십시오. 파라메트릭 형식을 사용하여 평면에 도달할 수 있습니다(점 p에서 교차함을 의미).

l0 + l * t = p                                            (ii)

광선이 평면과 평행하지 않으면 t는 광선 방향으로 배입니다. 광선은 비행기에 관심을 가질 것입니다. 다음으로 방정식 (ii)p 값을 방정식 (i)에 넣을 수 있으며 다음을 얻습니다.

(l0 + l * t – p0) . n = 0

파라메트릭 방정식을 사용하여 교차점의 위치를 계산하는 데 도움이 되는 t를 계산하려고 합니다. 방정식을 풀어봅시다:

l * t . n + (l0 – p0) . n = 0
l * t . n = - (l0 – p0) . n

t = - (l0 – p0) . n / l . n

값이 0이거나 0에 가까우면 결과가 무한대이므로 광선과 평면이 평행함을 의미하기 때문에 분모에 들어오는 선과 평면 법선의 내적을 결정해야 합니다. 따라서 분모를 확인하고 솔루션이 없음을 의미하는 false를 반환합니다(즉, 교차점).

다음 함수는 광선이 평면과 교차하는지 여부를 확인할 수 있습니다.

bool intersection plane(Vec3 &n, Vec3 &p0, Vec3 &lo, Vec3 &l, float &t) {
  // assuming vectors are all normalized
  float denom = n.dot(l);
  if (denom < 0.00005 && denom > -0.00005) / denom is near to 0 return false;
  Vec3 p010 = p0  l0;
  t = p010.dot(n);
  if t>=0)    return true;
  return false;
}

if 조건을 사용하여 분모가 0에 가까운지 확인합니다. 이는 무한 결과가 있음을 의미합니다. 광선 방향과 평면 법선의 내적은 광선이 평면에 평행한 경우 0을 제공합니다. 따라서 false를 반환합니다.

그렇지 않으면 t를 계산하고 true를 반환합니다. 이는 광선이 평면과 교차함을 의미합니다. t를 사용하여 교차점을 찾을 수 있습니다.

다음으로 광선과 평면의 교차점을 찾는 전체 코드가 아래에 있습니다.

#include <math.h>

#include <iostream>

using namespace std;

class Vec3 {
  float x;
  float y;
  float z;

 public:
  Vec3() { x, y, z = 0; }
  Vec3(float x, float y, float z) {
    this->x = x;
    this->y = y;
    this->z = z;
  }
  Vec3 &operator+=(const Vec3 &b) {
    x = x + b.x;
    y = y + b.y;
    z = z + b.z;
    return *this;
  }
  Vec3 &operator+=(const float s) {
    x = x + s;
    y = y + s;
    z = z + s;
    return *this;
  }
  Vec3 operator+(const float s) {
    Vec3 newV = *this;
    return newV += s;
  }
  Vec3 &operator-=(const Vec3 &b) {
    x = x - b.x;
    y = y - b.y;
    z = z - b.z;
    return *this;
  }
  Vec3 operator+(const Vec3 &b) {
    Vec3 newV = *this;
    newV += b;
    return newV;
  }
  Vec3 operator-(const Vec3 &b) {
    Vec3 newV = *this;
    newV -= b;
    return newV;
  }
  Vec3 operator*(const Vec3 &b) {  // cross operator
    Vec3 newV;
    newV.x = y * b.z - z * b.y;
    newV.y = x * b.z - z * b.x;
    newV.z = x * b.y - y * b.x;
    return newV;
  }
  Vec3 &operator*(const float s) {  // Dot equal operator
    x = x * s;
    y = y * s;
    z = z * s;
    return *this;
  }
  float mag() { return sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)); }
  Vec3 &normalize() {
    double mag = this->mag();
    x /= mag;
    y /= mag;
    z /= mag;
  }
  // Dot operator
  float dot(const Vec3 &b) { return x * b.x + y * b.y + z * b.z; }
  friend ostream &operator<<(ostream &out, const Vec3 &v) {
    out << "(" << v.x << ", " << v.y << ", " << v.z << ")\n";
    return out;
  }
};

bool intersectPlane(Vec3 &n, Vec3 &p0, Vec3 &l0, Vec3 &l, float &t) {
  // considering vectors are normalized
  float denom = n.dot(l);  // dot product n.l
  if (denom > 1e-6) {
    Vec3 p0l0 = p0 - l0;
    t = n.dot(p0l0) / denom;
    return (t >= 0);
  }
  return false;
}

int main() {
  Vec3 n1(3, -9, 1);  // Normal
  Vec3 p01(-4, 2, 2);
  Vec3 l01(1, 1, 1);
  Vec3 l1(1, 2, 1);
  float t;
  n1.normalize();
  p01.normalize();
  l01.normalize();
  l1.normalize();
  if (intersectPlane(n1, p01, l01, l1, t))
    cout << "T:" << t << '\n';
  else
    cout << "Ray is not intersecting the plane\n";
  cout << "------------------------\n";
  Vec3 n2(2, 2, -2);  // Normal
  Vec3 p02(2, 4, 1);
  Vec3 l02(1, 1, 1);
  Vec3 l2(1, 2, 1);
  n2.normalize();
  p02.normalize();
  l02.normalize();
  l2.normalize();
  if (intersectPlane(n2, p02, l02, l2, t))
    cout << "T:" << t << '\n';
  else
    cout << "Ray is not intersecting the plane\n";
  Vec3 intersectionPoint = l02 + l2 * t;
  cout << intersectionPoint;
  return 0;
}

이 코드의 출력은 다음과 같습니다.

Ray is not intersecting the plane
------------------------
T:0.629199
(0.83422, 1.09109, 0.83422)

보시다시피 첫 번째 데이터 포인트에서 광선은 평면에 평행합니다. 따라서 분모는 0입니다.

그러나 두 번째 데이터 포인트 세트에서 분모가 0보다 크므로 교차점을 찾을 수 있습니다.

관련 문장 - C++ Math