一、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的外包围矩形是否相交,得到一定不相交的情况
两个矩形如果不相交,那么矩形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;
}
得到的结果可以正确判断四边形是否在相机可是范围内。