// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
// -----------------------------------------------------------------------------
// Original code from SharpDX project. https://github.com/sharpdx/SharpDX/
// Greetings to Alexandre Mutel. Original code published with the following license:
// -----------------------------------------------------------------------------
// Copyright (c) 2010-2014 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace FlaxEngine
{
///
/// Defines a frustum which can be used in frustum culling, zoom to Extents (zoom to fit) operations,
/// (matrix, frustum, camera) interchange, and many kind of intersection testing.
///
[Serializable]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct BoundingFrustum : IEquatable
{
private Matrix pMatrix;
private Plane pNear;
private Plane pFar;
private Plane pLeft;
private Plane pRight;
private Plane pTop;
private Plane pBottom;
///
/// Gets or sets the Matrix that describes this bounding frustum.
///
public Matrix Matrix
{
get => pMatrix;
set
{
pMatrix = value;
GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom);
}
}
///
/// Gets the near plane of the BoundingFrustum.
///
public Plane Near => pNear;
///
/// Gets the far plane of the BoundingFrustum.
///
public Plane Far => pFar;
///
/// Gets the left plane of the BoundingFrustum.
///
public Plane Left => pLeft;
///
/// Gets the right plane of the BoundingFrustum.
///
public Plane Right => pRight;
///
/// Gets the top plane of the BoundingFrustum.
///
public Plane Top => pTop;
///
/// Gets the bottom plane of the BoundingFrustum.
///
public Plane Bottom => pBottom;
///
/// Creates a new instance of BoundingFrustum.
///
/// Combined matrix that usually takes view × projection matrix.
public BoundingFrustum(Matrix matrix)
{
pMatrix = matrix;
GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom);
}
///
/// Returns a hash code for this instance.
///
///
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
///
public override int GetHashCode()
{
return pMatrix.GetHashCode();
}
///
/// Determines whether the specified is equal to this instance.
///
/// The to compare with this instance.
///
/// true if the specified is equal to this instance; otherwise, false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ref BoundingFrustum other)
{
return pMatrix == other.pMatrix;
}
///
/// Determines whether the specified is equal to this instance.
///
/// The to compare with this instance.
///
/// true if the specified is equal to this instance; otherwise, false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(BoundingFrustum other)
{
return Equals(ref other);
}
///
/// Determines whether the specified is equal to this instance.
///
/// The to compare with this instance.
///
/// true if the specified is equal to this instance; otherwise, false.
///
public override bool Equals(object obj)
{
if (!(obj is BoundingFrustum))
return false;
var strongValue = (BoundingFrustum)obj;
return Equals(ref strongValue);
}
///
/// Implements the operator ==.
///
/// The left.
/// The right.
///
/// The result of the operator.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(BoundingFrustum left, BoundingFrustum right)
{
return left.Equals(ref right);
}
///
/// Implements the operator !=.
///
/// The left.
/// The right.
///
/// The result of the operator.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(BoundingFrustum left, BoundingFrustum right)
{
return !left.Equals(ref right);
}
///
/// Returns one of the 6 planes related to this frustum.
///
/// Plane index where 0 fro Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far
///
public Plane GetPlane(int index)
{
switch (index)
{
case 0:
return pLeft;
case 1:
return pRight;
case 2:
return pTop;
case 3:
return pBottom;
case 4:
return pNear;
case 5:
return pFar;
default:
return new Plane();
}
}
private static void GetPlanesFromMatrix(ref Matrix matrix, out Plane near, out Plane far, out Plane left, out Plane right, out Plane top, out Plane bottom)
{
//http://www.chadvernon.com/blog/resources/directx9/frustum-culling/
// Left plane
left.Normal.X = matrix.M14 + matrix.M11;
left.Normal.Y = matrix.M24 + matrix.M21;
left.Normal.Z = matrix.M34 + matrix.M31;
left.D = matrix.M44 + matrix.M41;
left.Normalize();
// Right plane
right.Normal.X = matrix.M14 - matrix.M11;
right.Normal.Y = matrix.M24 - matrix.M21;
right.Normal.Z = matrix.M34 - matrix.M31;
right.D = matrix.M44 - matrix.M41;
right.Normalize();
// Top plane
top.Normal.X = matrix.M14 - matrix.M12;
top.Normal.Y = matrix.M24 - matrix.M22;
top.Normal.Z = matrix.M34 - matrix.M32;
top.D = matrix.M44 - matrix.M42;
top.Normalize();
// Bottom plane
bottom.Normal.X = matrix.M14 + matrix.M12;
bottom.Normal.Y = matrix.M24 + matrix.M22;
bottom.Normal.Z = matrix.M34 + matrix.M32;
bottom.D = matrix.M44 + matrix.M42;
bottom.Normalize();
// Near plane
near.Normal.X = matrix.M13;
near.Normal.Y = matrix.M23;
near.Normal.Z = matrix.M33;
near.D = matrix.M43;
near.Normalize();
// Far plane
far.Normal.X = matrix.M14 - matrix.M13;
far.Normal.Y = matrix.M24 - matrix.M23;
far.Normal.Z = matrix.M34 - matrix.M33;
far.D = matrix.M44 - matrix.M43;
far.Normalize();
}
private static Vector3 Get3PlanesInterPoint(ref Plane p1, ref Plane p2, ref Plane p3)
{
//P = -d1 * N2xN3 / N1.N2xN3 - d2 * N3xN1 / N2.N3xN1 - d3 * N1xN2 / N3.N1xN2
Vector3 v =
-p1.D * Vector3.Cross(p2.Normal, p3.Normal) / Vector3.Dot(p1.Normal, Vector3.Cross(p2.Normal, p3.Normal))
- p2.D * Vector3.Cross(p3.Normal, p1.Normal) / Vector3.Dot(p2.Normal, Vector3.Cross(p3.Normal, p1.Normal))
- p3.D * Vector3.Cross(p1.Normal, p2.Normal) / Vector3.Dot(p3.Normal, Vector3.Cross(p1.Normal, p2.Normal));
return v;
}
///
/// Creates a new frustum relaying on perspective camera parameters
///
/// The camera pos.
/// The look dir.
/// Up dir.
/// The fov.
/// The znear.
/// The zfar.
/// The aspect.
/// The bounding frustum calculated from perspective camera
public static BoundingFrustum FromCamera(Vector3 cameraPos, Vector3 lookDir, Vector3 upDir, float fov, float znear, float zfar, float aspect)
{
//http://knol.google.com/k/view-frustum
lookDir = Vector3.Normalize(lookDir);
upDir = Vector3.Normalize(upDir);
Vector3 nearCenter = cameraPos + lookDir * znear;
Vector3 farCenter = cameraPos + lookDir * zfar;
var nearHalfHeight = (float)(znear * Math.Tan(fov / 2f));
var farHalfHeight = (float)(zfar * Math.Tan(fov / 2f));
float nearHalfWidth = nearHalfHeight * aspect;
float farHalfWidth = farHalfHeight * aspect;
Vector3 rightDir = Vector3.Normalize(Vector3.Cross(upDir, lookDir));
Vector3 near1 = nearCenter - nearHalfHeight * upDir + nearHalfWidth * rightDir;
Vector3 near2 = nearCenter + nearHalfHeight * upDir + nearHalfWidth * rightDir;
Vector3 near3 = nearCenter + nearHalfHeight * upDir - nearHalfWidth * rightDir;
Vector3 near4 = nearCenter - nearHalfHeight * upDir - nearHalfWidth * rightDir;
Vector3 far1 = farCenter - farHalfHeight * upDir + farHalfWidth * rightDir;
Vector3 far2 = farCenter + farHalfHeight * upDir + farHalfWidth * rightDir;
Vector3 far3 = farCenter + farHalfHeight * upDir - farHalfWidth * rightDir;
Vector3 far4 = farCenter - farHalfHeight * upDir - farHalfWidth * rightDir;
var result = new BoundingFrustum
{
pNear = new Plane(near1, near2, near3),
pFar = new Plane(far3, far2, far1),
pLeft = new Plane(near4, near3, far3),
pRight = new Plane(far1, far2, near2),
pTop = new Plane(near2, far2, far3),
pBottom = new Plane(far4, far1, near1)
};
result.pNear.Normalize();
result.pFar.Normalize();
result.pLeft.Normalize();
result.pRight.Normalize();
result.pTop.Normalize();
result.pBottom.Normalize();
result.pMatrix = Matrix.LookAt(cameraPos, cameraPos + lookDir * 10, upDir) * Matrix.PerspectiveFov(fov, aspect, znear, zfar);
return result;
}
///
/// Returns the 8 corners of the frustum, element0 is Near1 (near right down corner)
/// , element1 is Near2 (near right top corner)
/// , element2 is Near3 (near Left top corner)
/// , element3 is Near4 (near Left down corner)
/// , element4 is Far1 (far right down corner)
/// , element5 is Far2 (far right top corner)
/// , element6 is Far3 (far left top corner)
/// , element7 is Far4 (far left down corner)
///
/// The 8 corners of the frustum
public Vector3[] GetCorners()
{
var corners = new Vector3[8];
GetCorners(corners);
return corners;
}
///
/// Returns the 8 corners of the frustum, element0 is Near1 (near right down corner)
/// , element1 is Near2 (near right top corner)
/// , element2 is Near3 (near Left top corner)
/// , element3 is Near4 (near Left down corner)
/// , element4 is Far1 (far right down corner)
/// , element5 is Far2 (far right top corner)
/// , element6 is Far3 (far left top corner)
/// , element7 is Far4 (far left down corner)
///
/// The 8 corners of the frustum
public void GetCorners(Vector3[] corners)
{
corners[0] = Get3PlanesInterPoint(ref pNear, ref pBottom, ref pRight); //Near1
corners[1] = Get3PlanesInterPoint(ref pNear, ref pTop, ref pRight); //Near2
corners[2] = Get3PlanesInterPoint(ref pNear, ref pTop, ref pLeft); //Near3
corners[3] = Get3PlanesInterPoint(ref pNear, ref pBottom, ref pLeft); //Near3
corners[4] = Get3PlanesInterPoint(ref pFar, ref pBottom, ref pRight); //Far1
corners[5] = Get3PlanesInterPoint(ref pFar, ref pTop, ref pRight); //Far2
corners[6] = Get3PlanesInterPoint(ref pFar, ref pTop, ref pLeft); //Far3
corners[7] = Get3PlanesInterPoint(ref pFar, ref pBottom, ref pLeft); //Far3
}
///
/// Checks whether a point lay inside, intersects or lay outside the frustum.
///
/// The point.
/// Type of the containment
public ContainmentType Contains(ref Vector3 point)
{
var result = PlaneIntersectionType.Front;
var planeResult = PlaneIntersectionType.Front;
for (var i = 0; i < 6; i++)
{
switch (i)
{
case 0:
planeResult = pNear.Intersects(ref point);
break;
case 1:
planeResult = pFar.Intersects(ref point);
break;
case 2:
planeResult = pLeft.Intersects(ref point);
break;
case 3:
planeResult = pRight.Intersects(ref point);
break;
case 4:
planeResult = pTop.Intersects(ref point);
break;
case 5:
planeResult = pBottom.Intersects(ref point);
break;
}
switch (planeResult)
{
case PlaneIntersectionType.Back:
return ContainmentType.Disjoint;
case PlaneIntersectionType.Intersecting:
result = PlaneIntersectionType.Intersecting;
break;
}
}
switch (result)
{
case PlaneIntersectionType.Intersecting:
return ContainmentType.Intersects;
default:
return ContainmentType.Contains;
}
}
///
/// Checks whether a point lay inside, intersects or lay outside the frustum.
///
/// The point.
/// Type of the containment
public ContainmentType Contains(Vector3 point)
{
return Contains(ref point);
}
///
/// Checks whether a group of points lay totally inside the frustum (Contains), or lay partially inside the frustum
/// (Intersects), or lay outside the frustum (Disjoint).
///
/// The points.
/// Type of the containment
public ContainmentType Contains(Vector3[] points)
{
throw new NotImplementedException();
/* TODO: (PMin) This method is wrong, does not calculate case where only plane from points is intersected
var containsAny = false;
var containsAll = true;
for (int i = 0; i < points.Length; i++)
{
switch (Contains(ref points[i]))
{
case ContainmentType.Contains:
case ContainmentType.Intersects:
containsAny = true;
break;
case ContainmentType.Disjoint:
containsAll = false;
break;
}
}
if (containsAny)
{
if (containsAll)
return ContainmentType.Contains;
else
return ContainmentType.Intersects;
}
else
return ContainmentType.Disjoint; */
}
///
/// Checks whether a group of points lay totally inside the frustum (Contains), or lay partially inside the frustum
/// (Intersects), or lay outside the frustum (Disjoint).
///
/// The points.
/// Type of the containment.
public void Contains(Vector3[] points, out ContainmentType result)
{
result = Contains(points);
}
private void GetBoxToPlanePVertexNVertex(ref BoundingBox box, ref Vector3 planeNormal, out Vector3 p, out Vector3 n)
{
p = box.Minimum;
if (planeNormal.X >= 0)
p.X = box.Maximum.X;
if (planeNormal.Y >= 0)
p.Y = box.Maximum.Y;
if (planeNormal.Z >= 0)
p.Z = box.Maximum.Z;
n = box.Maximum;
if (planeNormal.X >= 0)
n.X = box.Minimum.X;
if (planeNormal.Y >= 0)
n.Y = box.Minimum.Y;
if (planeNormal.Z >= 0)
n.Z = box.Minimum.Z;
}
///
/// Determines the intersection relationship between the frustum and a bounding box.
///
/// The box.
/// Type of the containment
public ContainmentType Contains(ref BoundingBox box)
{
var result = ContainmentType.Contains;
for (var i = 0; i < 6; i++)
{
var plane = GetPlane(i);
GetBoxToPlanePVertexNVertex(ref box, ref plane.Normal, out var p, out var n);
if (CollisionsHelper.PlaneIntersectsPoint(ref plane, ref p) == PlaneIntersectionType.Back)
return ContainmentType.Disjoint;
if (CollisionsHelper.PlaneIntersectsPoint(ref plane, ref n) == PlaneIntersectionType.Back)
result = ContainmentType.Intersects;
}
return result;
}
///
/// Determines the intersection relationship between the frustum and a bounding box.
///
/// The box.
/// Type of the containment
public ContainmentType Contains(BoundingBox box)
{
return Contains(ref box);
}
///
/// Determines the intersection relationship between the frustum and a bounding box.
///
/// The box.
/// Type of the containment.
public void Contains(ref BoundingBox box, out ContainmentType result)
{
result = Contains(ref box);
}
///
/// Determines the intersection relationship between the frustum and a bounding sphere.
///
/// The sphere.
/// Type of the containment
public ContainmentType Contains(ref BoundingSphere sphere)
{
var result = PlaneIntersectionType.Front;
var planeResult = PlaneIntersectionType.Front;
for (var i = 0; i < 6; i++)
{
switch (i)
{
case 0:
planeResult = pNear.Intersects(ref sphere);
break;
case 1:
planeResult = pFar.Intersects(ref sphere);
break;
case 2:
planeResult = pLeft.Intersects(ref sphere);
break;
case 3:
planeResult = pRight.Intersects(ref sphere);
break;
case 4:
planeResult = pTop.Intersects(ref sphere);
break;
case 5:
planeResult = pBottom.Intersects(ref sphere);
break;
}
switch (planeResult)
{
case PlaneIntersectionType.Back:
return ContainmentType.Disjoint;
case PlaneIntersectionType.Intersecting:
result = PlaneIntersectionType.Intersecting;
break;
}
}
switch (result)
{
case PlaneIntersectionType.Intersecting:
return ContainmentType.Intersects;
default:
return ContainmentType.Contains;
}
}
///
/// Determines the intersection relationship between the frustum and a bounding sphere.
///
/// The sphere.
/// Type of the containment
public ContainmentType Contains(BoundingSphere sphere)
{
return Contains(ref sphere);
}
///
/// Determines the intersection relationship between the frustum and a bounding sphere.
///
/// The sphere.
/// Type of the containment.
public void Contains(ref BoundingSphere sphere, out ContainmentType result)
{
result = Contains(ref sphere);
}
///
/// Determines the intersection relationship between the frustum and another bounding frustum.
///
/// The frustum.
/// Type of the containment
public bool Contains(ref BoundingFrustum frustum)
{
return Contains(frustum.GetCorners()) != ContainmentType.Disjoint;
}
///
/// Determines the intersection relationship between the frustum and another bounding frustum.
///
/// The frustum.
/// Type of the containment
public bool Contains(BoundingFrustum frustum)
{
return Contains(ref frustum);
}
///
/// Determines the intersection relationship between the frustum and another bounding frustum.
///
/// The frustum.
/// Type of the containment.
public void Contains(ref BoundingFrustum frustum, out bool result)
{
result = Contains(frustum.GetCorners()) != ContainmentType.Disjoint;
}
///
/// Checks whether the current BoundingFrustum intersects a BoundingSphere.
///
/// The sphere.
/// Type of the containment
public bool Intersects(ref BoundingSphere sphere)
{
return Contains(ref sphere) != ContainmentType.Disjoint;
}
///
/// Checks whether the current BoundingFrustum intersects a BoundingSphere.
///
/// The sphere.
/// Set to true if the current BoundingFrustum intersects a BoundingSphere.
public void Intersects(ref BoundingSphere sphere, out bool result)
{
result = Contains(ref sphere) != ContainmentType.Disjoint;
}
///
/// Checks whether the current BoundingFrustum intersects a BoundingBox.
///
/// The box.
/// true if the current BoundingFrustum intersects a BoundingSphere.
public bool Intersects(ref BoundingBox box)
{
return Contains(ref box) != ContainmentType.Disjoint;
}
///
/// Checks whether the current BoundingFrustum intersects a BoundingBox.
///
/// The box.
/// true if the current BoundingFrustum intersects a BoundingSphere.
public void Intersects(ref BoundingBox box, out bool result)
{
result = Contains(ref box) != ContainmentType.Disjoint;
}
private PlaneIntersectionType PlaneIntersectsPoints(ref Plane plane, Vector3[] points)
{
PlaneIntersectionType result = CollisionsHelper.PlaneIntersectsPoint(ref plane, ref points[0]);
for (var i = 1; i < points.Length; i++)
if (CollisionsHelper.PlaneIntersectsPoint(ref plane, ref points[i]) != result)
return PlaneIntersectionType.Intersecting;
return result;
}
///
/// Checks whether the current BoundingFrustum intersects the specified Plane.
///
/// The plane.
/// Plane intersection type.
public PlaneIntersectionType Intersects(ref Plane plane)
{
return PlaneIntersectsPoints(ref plane, GetCorners());
}
///
/// Checks whether the current BoundingFrustum intersects the specified Plane.
///
/// The plane.
/// Plane intersection type.
public void Intersects(ref Plane plane, out PlaneIntersectionType result)
{
result = PlaneIntersectsPoints(ref plane, GetCorners());
}
///
/// Get the width of the frustum at specified depth.
///
/// the depth at which to calculate frustum width.
/// With of the frustum at the specified depth
public float GetWidthAtDepth(float depth)
{
var hAngle = (float)(Math.PI / 2.0 - Math.Acos(Vector3.Dot(pNear.Normal, pLeft.Normal)));
return (float)(Math.Tan(hAngle) * depth * 2);
}
///
/// Get the height of the frustum at specified depth.
///
/// the depth at which to calculate frustum height.
/// Height of the frustum at the specified depth
public float GetHeightAtDepth(float depth)
{
var vAngle = (float)(Math.PI / 2.0 - Math.Acos(Vector3.Dot(pNear.Normal, pTop.Normal)));
return (float)(Math.Tan(vAngle) * depth * 2);
}
private BoundingFrustum GetInsideOutClone()
{
BoundingFrustum frustum = this;
frustum.pNear.Normal = -frustum.pNear.Normal;
frustum.pFar.Normal = -frustum.pFar.Normal;
frustum.pLeft.Normal = -frustum.pLeft.Normal;
frustum.pRight.Normal = -frustum.pRight.Normal;
frustum.pTop.Normal = -frustum.pTop.Normal;
frustum.pBottom.Normal = -frustum.pBottom.Normal;
return frustum;
}
///
/// Checks whether the current BoundingFrustum intersects the specified Ray.
///
/// The ray.
/// true if the current BoundingFrustum intersects the specified Ray.
public bool Intersects(ref Ray ray)
{
return Intersects(ref ray, out _, out _);
}
///
/// Checks whether the current BoundingFrustum intersects the specified Ray.
///
/// The Ray to check for intersection with.
///
/// The distance at which the ray enters the frustum if there is an intersection and the ray
/// starts outside the frustum.
///
/// The distance at which the ray exits the frustum if there is an intersection.
/// true if the current BoundingFrustum intersects the specified Ray.
public bool Intersects(ref Ray ray, out float? inDistance, out float? outDistance)
{
if (Contains(ray.Position) != ContainmentType.Disjoint)
{
float nearstPlaneDistance = float.MaxValue;
for (var i = 0; i < 6; i++)
{
Plane plane = GetPlane(i);
float distance;
if (CollisionsHelper.RayIntersectsPlane(ref ray, ref plane, out distance) && (distance < nearstPlaneDistance))
nearstPlaneDistance = distance;
}
inDistance = nearstPlaneDistance;
outDistance = null;
return true;
}
//We will find the two points at which the ray enters and exists the frustum
//These two points make a line which center inside the frustum if the ray intersects it
//Or outside the frustum if the ray intersects frustum planes outside it.
float minDist = float.MaxValue;
float maxDist = float.MinValue;
for (var i = 0; i < 6; i++)
{
Plane plane = GetPlane(i);
if (CollisionsHelper.RayIntersectsPlane(ref ray, ref plane, out float distance))
{
minDist = Math.Min(minDist, distance);
maxDist = Math.Max(maxDist, distance);
}
}
Vector3 minPoint = ray.Position + ray.Direction * minDist;
Vector3 maxPoint = ray.Position + ray.Direction * maxDist;
Vector3 center = (minPoint + maxPoint) / 2f;
if (Contains(ref center) != ContainmentType.Disjoint)
{
inDistance = minDist;
outDistance = maxDist;
return true;
}
inDistance = null;
outDistance = null;
return false;
}
///
/// Get the distance which when added to camera position along the lookat direction will do the effect of zoom to extents
/// (zoom to fit) operation,
/// so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the lookat direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the lookat direction (ZoomOut).
///
/// The points.
/// The zoom to fit distance
public float GetZoomToExtentsShiftDistance(Vector3[] points)
{
var vAngle = (float)(Math.PI / 2.0 - Math.Acos(Vector3.Dot(pNear.Normal, pTop.Normal)));
var vSin = (float)Math.Sin(vAngle);
var hAngle = (float)(Math.PI / 2.0 - Math.Acos(Vector3.Dot(pNear.Normal, pLeft.Normal)));
var hSin = (float)Math.Sin(hAngle);
float horizontalToVerticalMapping = vSin / hSin;
BoundingFrustum ioFrustrum = GetInsideOutClone();
float maxPointDist = float.MinValue;
for (var i = 0; i < points.Length; i++)
{
float pointDist = CollisionsHelper.DistancePlanePoint(ref ioFrustrum.pTop, ref points[i]);
pointDist = Math.Max(pointDist, CollisionsHelper.DistancePlanePoint(ref ioFrustrum.pBottom, ref points[i]));
pointDist = Math.Max(pointDist, CollisionsHelper.DistancePlanePoint(ref ioFrustrum.pLeft, ref points[i]) * horizontalToVerticalMapping);
pointDist = Math.Max(pointDist, CollisionsHelper.DistancePlanePoint(ref ioFrustrum.pRight, ref points[i]) * horizontalToVerticalMapping);
maxPointDist = Math.Max(maxPointDist, pointDist);
}
return -maxPointDist / vSin;
}
///
/// Get the distance which when added to camera position along the lookat direction will do the effect of zoom to extents
/// (zoom to fit) operation,
/// so all the passed points will fit in the current view.
/// if the returned value is positive, the camera will move toward the lookat direction (ZoomIn).
/// if the returned value is negative, the camera will move in the reverse direction of the lookat direction (ZoomOut).
///
/// The bounding box.
/// The zoom to fit distance
public float GetZoomToExtentsShiftDistance(ref BoundingBox boundingBox)
{
return GetZoomToExtentsShiftDistance(boundingBox.GetCorners());
}
///
/// Get the vector shift which when added to camera position will do the effect of zoom to extents (zoom to fit)
/// operation,
/// so all the passed points will fit in the current view.
///
/// The points.
/// The zoom to fit vector
public Vector3 GetZoomToExtentsShiftVector(Vector3[] points)
{
return GetZoomToExtentsShiftDistance(points) * pNear.Normal;
}
///
/// Get the vector shift which when added to camera position will do the effect of zoom to extents (zoom to fit)
/// operation,
/// so all the passed points will fit in the current view.
///
/// The bounding box.
/// The zoom to fit vector
public Vector3 GetZoomToExtentsShiftVector(ref BoundingBox boundingBox)
{
return GetZoomToExtentsShiftDistance(boundingBox.GetCorners()) * pNear.Normal;
}
///
/// Indicate whether the current BoundingFrustum is Orthographic.
///
///
/// true if the current BoundingFrustum is Orthographic; otherwise, false.
///
public bool IsOrthographic => (pLeft.Normal == -pRight.Normal) && (pTop.Normal == -pBottom.Normal);
}
}