繁体   English   中英

如何知道线段是否与 3d 空间中的三角形相交?

[英]How to know IF a line segment intersects a triangle in 3d space?

我有一个由 3d 空间中的 3 个点定义的三角形。 我还有一个由 3d 空间中的 2 个点定义的线段。 我想知道它们是否相交。 我真的不需要知道交点。

我不懂微积分,但我知道一些三角函数。 我知道一些关于矩阵的知识,但我很了解向量(特别是 3d 向量)。 请保持简单。

你能告诉我这个示例问题吗:

三角形:

一个:-4、3、0

b: 4, 3, 0

c: -3, -5, 4

线段:

d: 1, -2, 0

e: -2, 6, 2

编辑:

我将在 C++ 物理引擎中使用它。

一个答案涉及从 4 个顶点计算四面体体积。 请提供公式或在代码中显示。

更新:

正如 meowgoesthedog 指出的那样,我可以尝试使用Moller-Trumbore交集算法。 请参阅下面的替代解决方案的答案。

这是解决您的问题的一种方法。 计算四面体的体积 Td = (a,b,c,d) 和 Te = (a,b,c,e)。 如果 Td 或 Te 的体积为零,则线段 de 的一个端点位于包含三角形 (a,b,c) 的平面上。 如果 Td 和 Te 的体积具有相同的符号,则 de 严格位于一侧,并且没有交集。 如果 Td 和 Te 的符号相反,则 de 穿过包含 (a,b,c) 的平面。

从那里有几种策略。 一种是计算 de 穿过该平面的点 p。 然后投影到二维,解决二维中的三角点问题。

另一种方法是计算四面体 (a,b,d,e)、(b,c,d,e) 和 (c,a,d,e) 的体积。 然后只有当所有三个符号都相同时,才与三角形 (a,b,c) 相交。

如何从角坐标计算四面体的体积在整个网络上都有,在 C 中的计算几何中也有。

我实现了约瑟夫在 python 中给出的伟大答案,并认为我会分享。 该函数采用一组线段和三角形,并计算每条线段是否与任何给定三角形相交。

函数的第一个输入是一个 2xSx3 的线段数组,其中第一个索引指定线段的起点或终点,第二个索引指第 s^th 个线段,第三个索引指向 x、y、线段点的z坐标。

第二个输入是三角形顶点的 3XTX3 数组,其中第一个索引指定三个顶点之一(不必按任何特定顺序),第二个索引指的是第 t^th 个三角形,第三个索引指向三角形顶点的 x,y,z 坐标。

此函数的输出是一个大小为 S 的二进制数组,它告诉您第 s^th 条线段是否与给定的任何三角形相交。 如果您想知道线段与哪些三角形相交,则只需删除函数最后一行的总和即可。

def signedVolume(a, b, c, d):
    """Computes the signed volume of a series of tetrahedrons defined by the vertices in 
    a, b c and d. The ouput is an SxT array which gives the signed volume of the tetrahedron defined
    by the line segment 's' and two vertices of the triangle 't'."""

    return np.sum((a-d)*np.cross(b-d, c-d), axis=2)

def segmentsIntersectTriangles(s, t):
    """For each line segment in 's', this function computes whether it intersects any of the triangles
    given in 't'."""
    # compute the normals to each triangle
    normals = np.cross(t[2]-t[0], t[2]-t[1])
    normals /= np.linalg.norm(normals, axis=1)[:, np.newaxis]

    # get sign of each segment endpoint, if the sign changes then we know this segment crosses the
    # plane which contains a triangle. If the value is zero the endpoint of the segment lies on the 
    # plane.
    # s[i][:, np.newaxis] - t[j] -> S x T x 3 array
    sign1 = np.sign(np.sum(normals*(s[0][:, np.newaxis] - t[2]), axis=2)) # S x T
    sign2 = np.sign(np.sum(normals*(s[1][:, np.newaxis] - t[2]), axis=2)) # S x T

    # determine segments which cross the plane of a triangle. 1 if the sign of the end points of s is 
    # different AND one of end points of s is not a vertex of t
    cross = (sign1 != sign2)*(sign1 != 0)*(sign2 != 0) # S x T 

    # get signed volumes
    v1 = np.sign(signedVolume(t[0], t[1], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T
    v2 = np.sign(signedVolume(t[1], t[2], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T
    v3 = np.sign(signedVolume(t[2], t[0], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T

    same_volume = np.logical_and((v1 == v2), (v2 == v3)) # 1 if s and t have same sign in v1, v2 and v3

    return (np.sum(cross*same_volume, axis=1) > 0)

谢谢您的帮助。 这是一个替代解决方案。 问题是针对 c++ 的,正如meowgoesthedog指出的那样,我可以尝试使用Moller-Trumbore交集算法。 这就是我想出的:

#include <math.h>

class vec3 {
public:
    float x, y, z;

    float dot(const vec3 & b) {
        return vec3::x * b.x + vec3::y * b.y + vec3::z * b.z;
    }

    vec3 cross(const vec3 & b) {
        return vec3::vec3(
            vec3::y * b.z - vec3::z * b.y,
            vec3::z * b.x - vec3::x * b.z,
            vec3::x * b.y - vec3::y * b.x
        );
    }

    vec3 normalize() {
        const float s = 1.0f / sqrtf(vec3::x * vec3::x + vec3::y * vec3::y + vec3::z * vec3::z);
        return vec3::vec3(vec3::x * s, vec3::y * s, vec3::z * s);
    }

    vec3 operator+(const vec3 & b) {
        return vec3::vec3(
            vec3::x + b.x,
            vec3::y + b.y,
            vec3::z + b.z
        );
    }
    vec3 operator+=(const vec3 & b) {
        *this = vec3::operator+(b);
        return *this;
    }

    vec3 operator-(const vec3 & b) {
        return vec3::vec3(
            vec3::x - b.x,
            vec3::y - b.y,
            vec3::z - b.z
        );
    }
    vec3 operator-=(const vec3 & b) {
        *this = vec3::operator-(b);
        return *this;
    }

    vec3 operator*(const vec3 & b) {
        return vec3::vec3(
            vec3::x * b.x,
            vec3::y * b.y,
            vec3::z * b.z
        );
    }
    vec3 operator*=(const vec3 & b) {
        *this = vec3::operator*(b);
        return *this;
    }
    vec3 operator*(float b) {
        return vec3::vec3(
            vec3::x * b,
            vec3::y * b,
            vec3::z * b
        );
    }
    vec3 operator*=(float b) {
        *this = vec3::operator*(b);
        return *this;
    }

    vec3 operator/(const vec3 & b) {
        return vec3::vec3(
            vec3::x / b.x,
            vec3::y / b.y,
            vec3::z / b.z
        );
    }
    vec3 operator/=(const vec3 & b) {
        *this = vec3::operator/(b);
        return *this;
    }
    vec3 operator/(float b) {
        return vec3::vec3(
            vec3::x * b,
            vec3::y * b,
            vec3::z * b
        );
    }
    vec3 operator/=(float b) {
        *this = vec3::operator/(b);
        return *this;
    }

    vec3(float x, float y, float z) {
        vec3::x = x;
        vec3::y = y;
        vec3::z = z;
    }
    vec3(float x) {
        vec3::x = x;
        vec3::y = x;
        vec3::z = x;
    }
    vec3() {
        //
    }
    ~vec3() {
        //
    }
};

#define EPSILON 0.000001f
bool lineSegIntersectTri(
    vec3 line[2],
    vec3 tri[3],
    vec3 * point
) {
    vec3 e0 = tri[1] - tri[0];
    vec3 e1 = tri[2] - tri[0];

    vec3 dir = line[1] - line[0];
    vec3 dir_norm = dir.normalize();

    vec3 h = dir_norm.cross(e1);
    const float a = e0.dot(h);

    if (a > -EPSILON && a < EPSILON) {
        return false;
    }

    vec3 s = line[0] - tri[0];
    const float f = 1.0f / a;
    const float u = f * s.dot(h);

    if (u < 0.0f || u > 1.0f) {
        return false;
    }

    vec3 q = s.cross(e0);
    const float v = f * dir_norm.dot(q);

    if (v < 0.0f || u + v > 1.0f) {
        return false;
    }

    const float t = f * e1.dot(q);

    if (t > EPSILON && t < sqrtf(dir.dot(dir))) { // segment intersection
        if (point) {
            *point = line[0] + dir_norm * t;
        }

        return true;
    }

    return false;
}

运行一些测试:

#include <stdio.h>

const char * boolStr(bool b) {
    if (b) {
        return "true";
    }

    return "false";
}

int main() {
    vec3 tri[3] = {
        { -1.0f, -1.0f, 0.0f },
        { 1.0f, -1.0f, 0.0f },
        { 1.0f, 1.0f, 0.0f },
    };

    vec3 line0[2] = { // should intersect
        { 0.5f, -0.5f, -1.0f },
        { 0.5f, -0.5f, 1.0f },
    };

    vec3 line1[2] = { // should not intersect
        { -0.5f, 0.5f, -1.0f },
        { -0.5f, 0.5f, 1.0f },
    };

    printf(
        "line0 intersects? : %s\r\n"
        "line1 intersects? : %s\r\n",
        boolStr(lineSegIntersectTri(line0, tri, NULL)),
        boolStr(lineSegIntersectTri(line1, tri, NULL))
    );

    return 0;
}

C#版本:

public class AlgoritmoMollerTrumbore
  {
    private const double EPSILON = 0.0000001;

    public static bool lineIntersectTriangle(Point3D[] line,
                                             Point3D[] triangle,
                                             out Point3D outIntersectionPoint)
    {
      outIntersectionPoint = new Point3D(0, 0, 0);

      Point3D rayOrigin = line[0];
      Vector3D rayVector = Point3D.Subtract(line[1], line[0]);
      rayVector.Normalize();

      Point3D vertex0 = triangle[0];
      Point3D vertex1 = triangle[1]; 
      Point3D vertex2 = triangle[2];

      Vector3D edge1 = Point3D.Subtract(vertex1, vertex0);
      Vector3D edge2 = Point3D.Subtract(vertex2, vertex0);
      Vector3D h = Vector3D.CrossProduct(rayVector, edge2);

      double a = Vector3D.DotProduct(edge1, h);
      if (a > -EPSILON && a < EPSILON)
      {
        return false;    // This ray is parallel to this triangle.
      }

      double f = 1.0 / a;      
      Vector3D s = Point3D.Subtract(rayOrigin, vertex0);

      double u = f * (Vector3D.DotProduct(s, h));
      if (u < 0.0 || u > 1.0)
      {
        return false;
      }
      Vector3D q = Vector3D.CrossProduct(s, edge1);
      double v = f * Vector3D.DotProduct(rayVector, q);
      if (v < 0.0 || u + v > 1.0)
      {
        return false;
      }
      // At this stage we can compute t to find out where the intersection point is on the line.
      double t = f * Vector3D.DotProduct(edge2, q);
      if (t > EPSILON && t < Math.Sqrt(Vector3D.DotProduct(rayVector, rayVector))) // ray intersection
      {
        outIntersectionPoint = rayOrigin + rayVector * t;
        return true;
      }
      else // This means that there is a line intersection but not a ray intersection.
      {
        return false;
      }
    }
  }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM