【问题标题】:How to find out if 2 triangles on a 2D plane rotated in 3D space are intersecting?如何找出在 3D 空间中旋转的 2D 平面上的 2 个三角形是否相交?
【发布时间】:2021-03-10 22:53:10
【问题描述】:

我正在尝试在 C# 和 unity 中创建一个方法,该方法接受 2 个三角形(它们的顶点的位置),如果三角形接触(不相互卡住,只是正确对齐和接触),则返回 true,如果不是,则为 false。我把它分成测试,它们必须有相反的法线,它们必须在同一个 2D 平面上(可以旋转),并且必须在那个 2D 平面上相交。最后一步该怎么做?

bool TrianglesTouching(Vector3[] triangle1, Vector3[] triangle2)
{
    bool touching = false;

    // Step 1: Success
    bool facing = false;
    Vector3 normal1 = SurfaceNormal(triangle1[0], triangle1[1], triangle1[2]);
    Vector3 normal2 = SurfaceNormal(triangle2[0], triangle2[1], triangle2[2]);
    facing = Util.RoundVector3(normal1, 0.0001f) == Util.RoundVector3(normal2 * -1f, 0.0001f);

    // Step 2: Success
    bool onSamePlane = false;
    Plane plane = new Plane();
    plane.Set3Points(triangle1[0], triangle1[1], triangle1[2]);
    onSamePlane = Util.RoundToNearest(plane.GetDistanceToPoint(triangle2[0]), 0.0001f) == 0f; // point 0 is an arbitrary vertex of the triangle, we could have found the position of the triangle by averaging the position of its vertices, but that isnt necessary in this case. Any vertex position of this triangle will do just fine.

    // Step 3: What do I do here?
    bool intersectingOnPlane = false;
    // Some logic to figure out if they're intersecting. Please fill this in for me, StackOverflow wizards.

    touching = facing && onSamePlane && intersectingOnPlane;
    return touching;
}

我的意思的图表: enter image description here

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    我会推荐Habrador - Use math to solve problems in Unity with C# - Chapter 6 Are two triangles in 2D space intersecting?。真的检查一下,因为它在那里解释了更多细节。

    基本上它通过经典的 3 个步骤来解决这个问题:

    • 第 1 步。 用矩形近似三角形并测试它们是否相交。
    • 第 2 步。 检查一个三角形的任何边是否与另一个三角形的任何边相交。
    • 第 3 步。 检查一个三角形的任何角是否在另一个三角形内,反之亦然。

    首先它使用一些辅助类型

    //To store triangle data to get cleaner code
    public struct Triangle
    {
        //Corners of the triangle
        public Vector3 p1, p2, p3;
        //The 3 line segments that make up this triangle
        public LineSegment[] lineSegments;
    
        public Triangle(Vector3 p1, Vector3 p2, Vector3 p3)
        {
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
    
            lineSegments = new LineSegment[3];
    
            lineSegments[0] = new LineSegment(p1, p2);
            lineSegments[1] = new LineSegment(p2, p3);
            lineSegments[2] = new LineSegment(p3, p1);
        }
    }
    
    //To create a line segment
    public struct LineSegment
    {
        //Start/end coordinates
        public Vector3 p1, p2;
    
        public LineSegment(Vector3 p1, Vector3 p2)
        {
            this.p1 = p1;
            this.p2 = p2;
        }
    }
    

    然后它检查连续的3个步骤

    //The triangle-triangle intersection in 2D algorithm
    bool IsTriangleTriangleIntersecting(Triangle triangle1, Triangle triangle2)
    {
        bool isIntersecting = false;
    
        //Step 1. AABB intersection
        if (IsIntersectingAABB(triangle1, triangle2))
        {
            //Step 2. Line segment - triangle intersection
            if (AreAnyLineSegmentsIntersecting(triangle1, triangle2))
            {
                isIntersecting = true;
            }
            //Step 3. Point in triangle intersection - if one of the triangles is inside the other
            else if (AreCornersIntersecting(triangle1, triangle2))
            {
                isIntersecting = true;
            }
        }
    
        return isIntersecting;
    }
    

    这3个步骤本身的实现

    //
    // STEP 1 - Intersection: AABB
    //
    
    //Approximate the triangles with rectangles and see if they intersect with the AABB intersection algorithm
    bool IsIntersectingAABB(Triangle t1, Triangle t2)
    {
      //Find the size of the bounding box
    
      //Triangle 1
      float t1_minX = Mathf.Min(t1.p1.x, Mathf.Min(t1.p2.x, t1.p3.x));
      float t1_maxX = Mathf.Max(t1.p1.x, Mathf.Max(t1.p2.x, t1.p3.x));
      float t1_minZ = Mathf.Min(t1.p1.z, Mathf.Min(t1.p2.z, t1.p3.z));
      float t1_maxZ = Mathf.Max(t1.p1.z, Mathf.Max(t1.p2.z, t1.p3.z));
    
      //Triangle 2
      float t2_minX = Mathf.Min(t2.p1.x, Mathf.Min(t2.p2.x, t2.p3.x));
      float t2_maxX = Mathf.Max(t2.p1.x, Mathf.Max(t2.p2.x, t2.p3.x));
      float t2_minZ = Mathf.Min(t2.p1.z, Mathf.Min(t2.p2.z, t2.p3.z));
      float t2_maxZ = Mathf.Max(t2.p1.z, Mathf.Max(t2.p2.z, t2.p3.z));
    
    
      //Are the rectangles intersecting?
      //If the min of one box in one dimension is greater than the max of another box then the boxes are not intersecting
      //They have to intersect in 2 dimensions. We have to test if box 1 is to the left or box 2 and vice versa
      bool isIntersecting = true;
    
      //X axis
      if (t1_minX > t2_maxX)
      {
          isIntersecting = false;
      }
      else if (t2_minX > t1_maxX)
      {
          isIntersecting = false;
      }
      //Z axis
      else if (t1_minZ > t2_maxZ)
      {
          isIntersecting = false;
      }
      else if (t2_minZ > t1_maxZ)
      {
          isIntersecting = false;
      }
    
    
      //Debugging to display the approximated rectangles
      //Box 1
      Vector3 BB_1_centerPos = new Vector3((t1_maxX - t1_minX) * 0.5f + t1_minX, -0.2f, (t1_maxZ - t1_minZ) * 0.5f + t1_minZ);
    
      box1Obj.transform.position = BB_1_centerPos;
    
      box1Obj.transform.localScale = new Vector3(t1_maxX - t1_minX, 0.1f, t1_maxZ - t1_minZ);
    
      //Box 2
      Vector3 BB_2_centerPos = new Vector3((t2_maxX - t2_minX) * 0.5f + t2_minX, -0.2f, (t2_maxZ - t2_minZ) * 0.5f + t2_minZ);
    
      box2Obj.transform.position = BB_2_centerPos;
    
      box2Obj.transform.localScale = new Vector3(t2_maxX - t2_minX, 0.1f, t2_maxZ - t2_minZ);
    
    
      return isIntersecting;
    }
    
    //
    // STEP 2 - Intersection: Line segment - triangle
    //
    
    //Check if any of the edges that make up one of the triangles is intersecting with any of
    //the edges of the other triangle
    bool AreAnyLineSegmentsIntersecting(Triangle t1, Triangle t2)
    {
      bool isIntersecting = false;
    
      //Loop through all edges
      for (int i = 0; i < t1.lineSegments.Length; i++)
      {
          for (int j = 0; j < t2.lineSegments.Length; j++)
          {
              //The start/end coordinates of the current line segments
              Vector3 t1_p1 = t1.lineSegments[i].p1;
              Vector3 t1_p2 = t1.lineSegments[i].p2;
              Vector3 t2_p1 = t2.lineSegments[j].p1;
              Vector3 t2_p2 = t2.lineSegments[j].p2;
    
              //Are they intersecting?
              if (AreLineSegmentsIntersecting(t1_p1, t1_p2, t2_p1, t2_p2))
              {
                  isIntersecting = true;
    
                  //To stop the outer for loop
                  i = int.MaxValue - 1;
    
                  break;
              }
          }
      }
    
      return isIntersecting;
    }
    
    //Check if 2 line segments are intersecting in 2d space
    //http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/
    //p1 and p2 belong to line 1, p3 and p4 belong to line 2
    bool AreLineSegmentsIntersecting(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
    {
      bool isIntersecting = false;
    
      float denominator = (p4.z - p3.z) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.z - p1.z);
    
      //Make sure the denominator is != 0, if 0 the lines are parallel
      if (denominator != 0)
      {
          float u_a = ((p4.x - p3.x) * (p1.z - p3.z) - (p4.z - p3.z) * (p1.x - p3.x)) / denominator;
          float u_b = ((p2.x - p1.x) * (p1.z - p3.z) - (p2.z - p1.z) * (p1.x - p3.x)) / denominator;
    
          //Is intersecting if u_a and u_b are between 0 and 1
          if (u_a >= 0 && u_a <= 1 && u_b >= 0 && u_b <= 1)
          {
              isIntersecting = true;
          }
      }
    
      return isIntersecting;
    }
    
    //
    // STEP 3 - Intersection: Point in triangle
    //
    
    //There's a possibility that one of the triangles is smaller than the other
    //So we have to check if any of the triangle's corners is inside the other triangle
    bool AreCornersIntersecting(Triangle t1, Triangle t2)
    {
      bool isIntersecting = false;
    
      //We only have to test one corner from each triangle
      //Triangle 1 in triangle 2
      if (IsPointInTriangle(t1.p1, t2.p1, t2.p2, t2.p3))
      {
          isIntersecting = true;
      }
      //Triangle 2 in triangle 1
      else if (IsPointInTriangle(t2.p1, t1.p1, t1.p2, t1.p3))
      {
          isIntersecting = true;
      }
    
      return isIntersecting;
    }
    
    
    
    //Is a point p inside a triangle p1-p2-p3?
    //From http://totologic.blogspot.se/2014/01/accurate-point-in-triangle-test.html
    bool IsPointInTriangle(Vector3 p, Vector3 p1, Vector3 p2, Vector3 p3)
    {
      bool isWithinTriangle = false;
    
      float denominator = ((p2.z - p3.z) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.z - p3.z));
    
      float a = ((p2.z - p3.z) * (p.x - p3.x) + (p3.x - p2.x) * (p.z - p3.z)) / denominator;
      float b = ((p3.z - p1.z) * (p.x - p3.x) + (p1.x - p3.x) * (p.z - p3.z)) / denominator;
      float c = 1 - a - b;
    
      //The point is within the triangle if 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1
      if (a >= 0f && a <= 1f && b >= 0f && b <= 1f && c >= 0f && c <= 1f)
      {
          isWithinTriangle = true;
      }
    
      return isWithinTriangle;
    }
    

    太好了,我决定重新设计并稍微重构一下

    /// <summary>
    /// To store triangle data to get cleaner code
    /// </summary>
    [Serializable]
    public struct Triangle2D
    {
        /// <summary>
        /// To create a line segment
        /// </summary>
        private struct LineSegment2D
        {
            //Start/end coordinates
            private readonly Vector2 _p1;
            private readonly Vector2 _p2;
    
            public LineSegment2D(Vector2 p1, Vector2 p2)
            {
                _p1 = p1;
                _p2 = p2;
            }
    
            /// <summary>
            /// Check if 2 line segments are intersecting in 2d space
            /// <para>http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/</para>
            /// </summary>
            public bool IsIntersecting(LineSegment2D other)
            {
                var p3 = other._p1;
                var p4 = other._p2;
    
                var denominator = (p4.y - p3.y) * (_p2.x - _p1.x) - (p4.x - p3.x) * (_p2.y - _p1.y);
    
                //Make sure the denominator is != 0, if 0 the lines are parallel
                if (denominator == 0) return false;
    
                var u_a = ((p4.x - p3.x) * (_p1.y - p3.y) - (p4.y - p3.y) * (_p1.x - p3.x)) / denominator;
                var u_b = ((_p2.x - _p1.x) * (_p1.y - p3.y) - (_p2.y - _p1.y) * (_p1.x - p3.x)) / denominator;
    
                //Is intersecting if u_a and u_b are between 0 and 1
                return 0 <= u_a && u_a <= 1 && 0 <= u_b && u_b <= 1;
            }
        }
    
        //Corners of the triangle
        // Can be edited via the Inspector
        [SerializeField] private Vector2 _a;
        [SerializeField] private Vector2 _b;
        [SerializeField] private Vector2 _c;
    
        //The 3 line segments that make up this triangle
        private LineSegment2D[] _lineSegments;
    
        public Triangle2D(Vector2 a, Vector2 b, Vector2 c)
        {
            _a = a;
            _b = b;
            _c = c;
    
            _lineSegments = new[]
            {
                new LineSegment2D(a, b),
                new LineSegment2D(b, c),
                new LineSegment2D(c, a)
            };
        }
    
        /// <summary>
        /// Is this triangle intersecting the other one?
        /// </summary>
        public bool IsIntersecting(Triangle2D other)
        {
            //Step 1. AABB intersection
            if (IsIntersectingAABB(other))
            {
                //Step 2. Line segment - triangle intersection
                if (AreAnyLineSegmentsIntersecting(other))
                {
                    return true;
                }
    
                //Step 3. Point in triangle intersection - if one of the triangles is inside the other
                if (AreCornersIntersecting(other))
                {
                    return true;
                }
            }
    
            return false;
        }
    
        /// <summary>
        /// Is a point p inside this triangle
        ///<para>From http://totologic.blogspot.se/2014/01/accurate-point-in-triangle-test.html</para>
        /// </summary>
        public bool IsPointInTriangle(Vector2 p)
        {
            var denominator = (_b.y - _c.y) * (_a.x - _c.x) + (_c.x - _b.x) * (_a.y - _c.y);
    
            var a = ((_b.y - _c.y) * (p.x - _c.x) + (_c.x - _b.x) * (p.y - _c.y)) / denominator;
            var b = ((_c.y - _a.y) * (p.x - _c.x) + (_a.x - _c.x) * (p.y - _c.y)) / denominator;
            var c = 1 - a - b;
    
            return 0f <= a && a <= 1f && 0f <= b && b <= 1f && 0f <= c && c <= 1f;
        }
    
        /// <summary>
        /// Approximate the triangles with rectangles and see if they intersect with the AABB intersection algorithm
        /// </summary>
        private bool IsIntersectingAABB(Triangle2D other)
        {
            //Find the size of the bounding boxes
    
            // Bounding box Triangle 1
            var t1_minX = Mathf.Min(_a.x, _b.x, _c.x);
            var t1_maxX = Mathf.Max(_a.x, _b.x, _c.x);
            var t1_minY = Mathf.Min(_a.y, _b.y, _c.y);
            var t1_maxY = Mathf.Max(_a.y, _b.y, _c.y);
    
            // Bounding box Triangle 2
            var t2_minX = Mathf.Min(other._a.x, other._b.x, other._c.x);
            var t2_maxX = Mathf.Max(other._a.x, other._b.x, other._c.x);
            var t2_minY = Mathf.Min(other._a.y, other._b.y, other._c.y);
            var t2_maxY = Mathf.Max(other._a.y, other._b.y, other._c.y);
    
            //Are the rectangles intersecting?
            //If the min of one box in one dimension is greater than the max of another box then the boxes are not intersecting
            //They have to intersect in 2 dimensions. We have to test if box 1 is to the left or box 2 and vice versa
    
            return t1_minX <= t2_maxX && t2_minX <= t1_maxX && t1_minY <= t2_maxY && t2_minY <= t1_maxY;
        }
    
        /// <summary>
        /// Check if any of the edges that make up one of the triangles is intersecting with any of the edges of the other triangle
        /// </summary>
        private bool AreAnyLineSegmentsIntersecting(Triangle2D triangle2)
        {
            //Loop through all edges
            foreach (var line1 in _lineSegments)
            {
                foreach (var line2 in triangle2._lineSegments)
                {
                    //Are they intersecting?
                    if (line1.IsIntersecting(line2))
                    {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        /// <summary>
        /// There's a possibility that one of the triangles is smaller than the other
        /// <para>So we have to check if any of the triangle's corners is inside the other triangle</para>
        /// </summary>
        private bool AreCornersIntersecting(Triangle2D other)
        {
            //We only have to test one corner from each triangle (otherwise the line segments would already intersect anyway)
            //Triangle 1 in triangle 2 or //Triangle 2 in triangle 1
            return other.IsPointInTriangle(_a) || IsPointInTriangle(other._a);
        }
    }
    

    最后的用法是例如

    // Create the two triangles structs
    var triangle1 = new Triangle2D(triangle1[0], triangle1[1], triangle1[2]);
    var triangle2 = new Triangle2D(triangle2[0], triangle2[1], triangle2[2]);
    
    bool intersectingOnPlane = triangle1.IsIntersecting(triangle2);
    

    【讨论】:

    • 嗯。如果它们实际上没有对齐并且只是相互卡住,我希望它返回 false。这个方法能做到吗?
    • 如果三角形在任何点相交,如问题标题所问,此方法返回 true ;)
    • 所以你是说如果三角形相交但实际上并不相互面对,该方法仍然会返回 true 吗?我不想要那个。三角形需要面对。 “面对”是指它们具有相反的表面法线。
    • 你不能简单地独立测试一下吗?
    • 哦,我猜。谢谢。不过,我仍然会考虑完成我自己的方法。这看起来很长很复杂。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 2011-01-25
    相关资源
    最近更新 更多