【Unity】判断物体(四边形)是否在相机的可视范围内

在这里插入图片描述

一、Unity的MonoBehaviour方法

MonoBehaviour.OnBecameVisible() 在渲染器变为对任意摄像机可见时调用。
MonoBehaviour.OnBecameInvisible()在渲染器对任何摄像机都不可见时调用。

public class ExampleClass : MonoBehaviour
{
    void OnBecameInvisible()
    {
        enabled = false;
    }
    void OnBecameVisible()
    {
        enabled = true;
    }
}

优点:可以很方便的得到物体对于相机可见和不可见的调用。

缺点:这个调用是不可控的,无法主动触发结果,而且OnBecameVisible只在物体进入相机可视范围内时触发一次,同样的OnBecameVisible只在物体退出相机可视范围内时触发一次。

自定义方法

下面的运算,都将坐标转换为屏幕坐标来运算。
相机可视范围的矩形为(0,0)(0,1)(1,0)(1,1)
为了方便说明,将相机可视范围的矩形称为矩形A,将需要计算的面称为平面B。

条件1:如果则平面B的某一个顶点在矩形A中,则平面B和矩形A一定相交。
条件2:如果则矩形A的某一个顶点在平面B中,则平面B和矩形A一定相交。

在这里插入图片描述

1.使用条件1, 得到一定相交的情况。

因为判断点是否在矩形内的算法耗时很少,所以我们直接使用。

 private bool IsPosListInRectangle(Vector2[] posList, Vector2 leftDown, Vector2 rightTop)
        {
            foreach (var pos in posList)
            {
                if (pos.x >= leftDown.x & pos.x <= rightTop.x & pos.y >= leftDown.y & pos.y < rightTop.y)
                {
                    return true;
                }
            }
            return false;
        }

传入平面B的四点数组,和矩形A的左下角点,和右上角点。
如果结果为true,平面B和矩形A一定相交。
如果结果为false,平面b的所有顶点一定不再矩形A内。

2.修改条件2,得到一定不相交的情况。

因为判断点在矩形的算法很简单,耗时很少,所以在1中我们直接使用了。
但条件2是要判断点在四边形中。 所以我们计算四边形的外包围矩形,使用四顶点中最小的x,y和最大的x,y即可。
改为判断矩形B的四个顶点,是否在平面A的外包围矩形中。
在这里插入图片描述

但如果是这种情况,矩形B右下角顶点,在平面B外包围矩形内,两平面不相交。
在这里插入图片描述

所以我们反过来,判断矩形B和平面A的外包围矩形是否相交,得到一定不相交的情况

扫描二维码关注公众号,回复: 15110152 查看本文章

两个矩形如果不相交,那么矩形B和平面A也一定不相交。

参考:两矩形相交的判定https://blog.csdn.net/qq_41220023/article/details/79748493
直接判定两个矩形的中心距离,然后判断其是否皆小于两矩形长的和的一半 和 宽的和的一半.
如果中心距离的x,小于量矩形长度和的一半,并且中心距离的y,小于量矩形长度和的一半,则两矩形一定相交。
在这里插入图片描述

 private  bool IsRectangleIntersection(Vector2 rectLeftDown0, Vector2 rectRightTop0, Vector2 rectLeftDown1, Vector2 rectRightTop1)
        {
            float rectCenterX0 = (rectRightTop0.x + rectLeftDown0.x) / 2;
            float rectCenterY0 = (rectRightTop0.y + rectLeftDown0.y) / 2;
            float rectCenterX1 = (rectRightTop1.x + rectLeftDown1.x) / 2;
            float rectCenterY1 = (rectRightTop1.y + rectLeftDown1.y) / 2;

            float centerDistanceX = Math.Abs(rectCenterX1 - rectCenterX0);
            float centerDistanceY = Math.Abs(rectCenterY1 - rectCenterY0);

            if (centerDistanceX * 2 <= Math.Abs(rectRightTop0.x - rectLeftDown0.x) +
                Math.Abs(rectRightTop1.x - rectLeftDown1.x) &
                centerDistanceY * 2 <= Math.Abs(rectRightTop0.y - rectLeftDown0.y) +
                Math.Abs(rectRightTop1.y - rectLeftDown1.y)
               )
            {
                return true;
            }

            return false;
        }

收集false结果,排除一定不在矩形A中显示的平面。

3.判断剩余的特殊情况

不满足条件1,但是相交的情况。需要逐一判断平面B的每条边,是否与矩形A相交。
在这里插入图片描述

 private bool IsPlaneInRectangle(Vector2 view0, Vector2 view1, Vector2 view2, Vector2 view3, Vector2 cameraView0, Vector2 cameraView1, Vector2 cameraView2, Vector2 cameraView3)
        {
            if (IsLineInRectangle(view0, view1, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            
            if (IsLineInRectangle(view1, view2, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            if (IsLineInRectangle(view2, view3, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            if (IsLineInRectangle(view3, view0, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            
            return false;
        }

        private bool IsLineInRectangle(Vector3 linePoint1, Vector3 linePoint2, Vector3 rectA, Vector3 rectB, Vector3 rectC, Vector3 rectD)
        {

            if (Math3D.AreLineSegmentsCrossing(linePoint1, linePoint2, rectA, rectB))
                return true;
            if (Math3D.AreLineSegmentsCrossing(linePoint1, linePoint2, rectB, rectC))
                return true;
            if (Math3D.AreLineSegmentsCrossing(linePoint1, linePoint2, rectC, rectD))
                return true;
            if (Math3D.AreLineSegmentsCrossing(linePoint1, linePoint2, rectD, rectA))
                return true;

            return false;

        }
        
        public bool AreLineSegmentsCrossing(Vector3 pointA1, Vector3 pointA2, Vector3 pointB1, Vector3 pointB2)
        {

            Vector3 closestPointA;
            Vector3 closestPointB;
            int sideA;
            int sideB;

            Vector3 lineVecA = pointA2 - pointA1;
            Vector3 lineVecB = pointB2 - pointB1;

            bool valid = ClosestPointsOnTwoLines(out closestPointA, out closestPointB, pointA1, lineVecA.normalized, pointB1, lineVecB.normalized);

            //lines are not parallel
            if (valid)
            {

                sideA = PointOnWhichSideOfLineSegment(pointA1, pointA2, closestPointA);
                sideB = PointOnWhichSideOfLineSegment(pointB1, pointB2, closestPointB);

                if ((sideA == 0) && (sideB == 0))
                {

                    return true;
                }

                else
                {

                    return false;
                }
            }

            //lines are parallel
            else
            {

                return false;
            }
        }
        public static bool ClosestPointsOnTwoLines(out Vector3 closestPointLine1, out Vector3 closestPointLine2, Vector3 linePoint1, Vector3 lineVec1, Vector3 linePoint2, Vector3 lineVec2)
        {

            closestPointLine1 = Vector3.zero;
            closestPointLine2 = Vector3.zero;

            float a = Vector3.Dot(lineVec1, lineVec1);
            float b = Vector3.Dot(lineVec1, lineVec2);
            float e = Vector3.Dot(lineVec2, lineVec2);

            float d = a * e - b * b;

            //lines are not parallel
            if (d != 0.0f)
            {

                Vector3 r = linePoint1 - linePoint2;
                float c = Vector3.Dot(lineVec1, r);
                float f = Vector3.Dot(lineVec2, r);

                float s = (b * f - c * e) / d;
                float t = (a * f - c * b) / d;

                closestPointLine1 = linePoint1 + lineVec1 * s;
                closestPointLine2 = linePoint2 + lineVec2 * t;

                return true;
            }

            else
            {
                return false;
            }
        }
        public int PointOnWhichSideOfLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point)
        {

            Vector3 lineVec = linePoint2 - linePoint1;
            Vector3 pointVec = point - linePoint1;

            float dot = Vector3.Dot(pointVec, lineVec);

            //point is on side of linePoint2, compared to linePoint1
            if (dot > 0)
            {

                //point is on the line segment
                if (pointVec.magnitude <= lineVec.magnitude)
                {

                    return 0;
                }

                //point is not on the line segment and it is on the side of linePoint2
                else
                {

                    return 2;
                }
            }

            //Point is not on side of linePoint2, compared to linePoint1.
            //Point is not on the linsegment and it is on the side of linePoint1.
            else
            {

                return 1;
            }
        }

到此为止,所有情况都已经判断完毕。

4.完整的调用代码

具体方法在上方1,2,3步骤中

  private bool IsPlaneInRectangle(Vector2 view0, Vector2 view1, Vector2 view2, Vector2 view3, Vector2 cameraView0, Vector2 cameraView1, Vector2 cameraView2, Vector2 cameraView3)
        {
            //1.判断瓦片顶点是否在相机视野矩形内
            if (IsPosListInRectangle(new Vector2[]{view0, view1, view2, view3},cameraView0, cameraView2))
                return true;

            //2.判断相机视野矩形顶点,不在瓦片外包围矩形内部
            float viewMinX = Math.Min(Math.Min(Math.Min(view0.x, view1.x), view2.x), view3.x);
            float viewMaxX = Math.Max(Math.Max(Math.Max(view0.x, view1.x), view2.x), view3.x);
            float viewMinY = Math.Min(Math.Min(Math.Min(view0.y, view1.y), view2.y), view3.y);
            float viewMaxY = Math.Max(Math.Max(Math.Max(view0.y, view1.y), view2.y), view3.y);
            if (!IsRectangleInRectangle(cameraView0, cameraView2, new Vector2(viewMinX, viewMinY),
                    new Vector2(viewMaxX, viewMaxY)))
            {
                return false;
            }
            
            //3.其他特殊情况
            if (IsLineInRectangle_Map(view0, view1, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            if (IsLineInRectangle_Map(view1, view2, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            if (IsLineInRectangle_Map(view2, view3, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            if (IsLineInRectangle_Map(view3, view0, cameraView0, cameraView1, cameraView2, cameraView3))
                return true;
            
            return false;
        }

得到的结果可以正确判断四边形是否在相机可是范围内。

猜你喜欢

转载自blog.csdn.net/boyZhenGui/article/details/127317077