// 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); } }