774 lines
33 KiB
C#
774 lines
33 KiB
C#
// Copyright (c) Wojciech Figat. All rights reserved.
|
||
|
||
#if USE_LARGE_WORLDS
|
||
using Real = System.Double;
|
||
#else
|
||
using Real = System.Single;
|
||
#endif
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// 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
|
||
{
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
[Serializable]
|
||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||
public struct BoundingFrustum : IEquatable<BoundingFrustum>
|
||
{
|
||
private Matrix pMatrix;
|
||
private Plane pNear;
|
||
private Plane pFar;
|
||
private Plane pLeft;
|
||
private Plane pRight;
|
||
private Plane pTop;
|
||
private Plane pBottom;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the Matrix that describes this bounding frustum.
|
||
/// </summary>
|
||
public Matrix Matrix
|
||
{
|
||
get => pMatrix;
|
||
set
|
||
{
|
||
pMatrix = value;
|
||
GetPlanesFromMatrix(in pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the near plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Near => pNear;
|
||
|
||
/// <summary>
|
||
/// Gets the far plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Far => pFar;
|
||
|
||
/// <summary>
|
||
/// Gets the left plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Left => pLeft;
|
||
|
||
/// <summary>
|
||
/// Gets the right plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Right => pRight;
|
||
|
||
/// <summary>
|
||
/// Gets the top plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Top => pTop;
|
||
|
||
/// <summary>
|
||
/// Gets the bottom plane of the BoundingFrustum.
|
||
/// </summary>
|
||
public Plane Bottom => pBottom;
|
||
|
||
/// <summary>
|
||
/// Creates a new instance of BoundingFrustum.
|
||
/// </summary>
|
||
/// <param name="matrix">Combined matrix that usually takes view × projection matrix.</param>
|
||
public BoundingFrustum(Matrix matrix)
|
||
{
|
||
pMatrix = matrix;
|
||
GetPlanesFromMatrix(in pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a new instance of BoundingFrustum.
|
||
/// </summary>
|
||
/// <param name="matrix">Combined matrix that usually takes view × projection matrix.</param>
|
||
public BoundingFrustum(in Matrix matrix)
|
||
{
|
||
pMatrix = matrix;
|
||
GetPlanesFromMatrix(in pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns a hash code for this instance.
|
||
/// </summary>
|
||
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
|
||
public override int GetHashCode()
|
||
{
|
||
return pMatrix.GetHashCode();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified <see cref="BoundingFrustum" /> is equal to this instance.
|
||
/// </summary>
|
||
/// <param name="other">The <see cref="BoundingFrustum" /> to compare with this instance.</param>
|
||
/// <returns><c>true</c> if the specified <see cref="BoundingFrustum" /> is equal to this instance; otherwise, <c>false</c>.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public bool Equals(in BoundingFrustum other)
|
||
{
|
||
return pMatrix == other.pMatrix;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified <see cref="BoundingFrustum" /> is equal to this instance.
|
||
/// </summary>
|
||
/// <param name="other">The <see cref="BoundingFrustum" /> to compare with this instance.</param>
|
||
/// <returns><c>true</c> if the specified <see cref="BoundingFrustum" /> is equal to this instance; otherwise, <c>false</c>.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public bool Equals(BoundingFrustum other)
|
||
{
|
||
return Equals(in other);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
|
||
/// </summary>
|
||
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
|
||
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
|
||
public override bool Equals(object obj)
|
||
{
|
||
return obj is BoundingFrustum other && Equals(in other);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Implements the operator ==.
|
||
/// </summary>
|
||
/// <param name="left">The left.</param>
|
||
/// <param name="right">The right.</param>
|
||
/// <returns>The result of the operator.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static bool operator ==(BoundingFrustum left, BoundingFrustum right)
|
||
{
|
||
return left.Equals(in right);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Implements the operator !=.
|
||
/// </summary>
|
||
/// <param name="left">The left.</param>
|
||
/// <param name="right">The right.</param>
|
||
/// <returns>The result of the operator.</returns>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static bool operator !=(BoundingFrustum left, BoundingFrustum right)
|
||
{
|
||
return !left.Equals(in right);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns one of the 6 planes related to this frustum.
|
||
/// </summary>
|
||
/// <param name="index">Plane index where 0 for Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far</param>
|
||
/// <returns>The frustum plane.</returns>
|
||
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(in 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(in Plane p1, in Plane p2, in Plane p3)
|
||
{
|
||
Vector3.Cross(p2.Normal, p3.Normal, out var n2Xn3);
|
||
Vector3.Cross(p3.Normal, p1.Normal, out var n3Xn1);
|
||
Vector3.Cross(p1.Normal, p2.Normal, out var n1Xn2);
|
||
var div1 = Vector3.Dot(p1.Normal, n2Xn3);
|
||
var div2 = Vector3.Dot(p2.Normal, n3Xn1);
|
||
var div3 = Vector3.Dot(p3.Normal, n1Xn2);
|
||
if (Mathf.IsZero(div1 * div2 * div3))
|
||
return Vector3.Zero;
|
||
return n2Xn3 * (-p1.D / div1) - n3Xn1 * (p2.D / div2) - n1Xn2 * (p3.D / div3);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a new frustum relaying on perspective camera parameters
|
||
/// </summary>
|
||
/// <param name="cameraPos">The camera pos.</param>
|
||
/// <param name="lookDir">The look dir.</param>
|
||
/// <param name="upDir">Up dir.</param>
|
||
/// <param name="fov">The fov.</param>
|
||
/// <param name="znear">The Z near.</param>
|
||
/// <param name="zfar">The Z far.</param>
|
||
/// <param name="aspect">The aspect.</param>
|
||
/// <returns>The bounding frustum calculated from perspective camera</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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)
|
||
/// </summary>
|
||
/// <returns>The 8 corners of the frustum</returns>
|
||
public Vector3[] GetCorners()
|
||
{
|
||
var corners = new Vector3[8];
|
||
GetCorners(corners);
|
||
return corners;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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)
|
||
/// </summary>
|
||
/// <returns>The 8 corners of the frustum</returns>
|
||
public void GetCorners(Vector3[] corners)
|
||
{
|
||
corners[0] = Get3PlanesInterPoint(in pNear, in pBottom, in pRight); //Near1
|
||
corners[1] = Get3PlanesInterPoint(in pNear, in pTop, in pRight); //Near2
|
||
corners[2] = Get3PlanesInterPoint(in pNear, in pTop, in pLeft); //Near3
|
||
corners[3] = Get3PlanesInterPoint(in pNear, in pBottom, in pLeft); //Near3
|
||
corners[4] = Get3PlanesInterPoint(in pFar, in pBottom, in pRight); //Far1
|
||
corners[5] = Get3PlanesInterPoint(in pFar, in pTop, in pRight); //Far2
|
||
corners[6] = Get3PlanesInterPoint(in pFar, in pTop, in pLeft); //Far3
|
||
corners[7] = Get3PlanesInterPoint(in pFar, in pBottom, in pLeft); //Far3
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether a point lay inside, intersects or lay outside the frustum.
|
||
/// </summary>
|
||
/// <param name="point">The point.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(in Vector3 point)
|
||
{
|
||
var result = PlaneIntersectionType.Front;
|
||
var planeResult = PlaneIntersectionType.Front;
|
||
for (var i = 0; i < 6; i++)
|
||
{
|
||
switch (i)
|
||
{
|
||
case 0:
|
||
planeResult = pNear.Intersects(point);
|
||
break;
|
||
case 1:
|
||
planeResult = pFar.Intersects(point);
|
||
break;
|
||
case 2:
|
||
planeResult = pLeft.Intersects(point);
|
||
break;
|
||
case 3:
|
||
planeResult = pRight.Intersects(point);
|
||
break;
|
||
case 4:
|
||
planeResult = pTop.Intersects(point);
|
||
break;
|
||
case 5:
|
||
planeResult = pBottom.Intersects(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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether a point lay inside, intersects or lay outside the frustum.
|
||
/// </summary>
|
||
/// <param name="point">The point.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(Vector3 point)
|
||
{
|
||
return Contains(in point);
|
||
}
|
||
|
||
private void GetBoxToPlanePVertexNVertex(in BoundingBox box, in 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding box.
|
||
/// </summary>
|
||
/// <param name="box">The box.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(in BoundingBox box)
|
||
{
|
||
var result = ContainmentType.Contains;
|
||
for (var i = 0; i < 6; i++)
|
||
{
|
||
var plane = GetPlane(i);
|
||
GetBoxToPlanePVertexNVertex(in box, in plane.Normal, out var p, out var n);
|
||
if (CollisionsHelper.PlaneIntersectsPoint(plane, p) == PlaneIntersectionType.Back)
|
||
return ContainmentType.Disjoint;
|
||
|
||
if (CollisionsHelper.PlaneIntersectsPoint(plane, n) == PlaneIntersectionType.Back)
|
||
result = ContainmentType.Intersects;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding box.
|
||
/// </summary>
|
||
/// <param name="box">The box.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(BoundingBox box)
|
||
{
|
||
return Contains(in box);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding box.
|
||
/// </summary>
|
||
/// <param name="box">The box.</param>
|
||
/// <param name="result">Type of the containment.</param>
|
||
public void Contains(in BoundingBox box, out ContainmentType result)
|
||
{
|
||
result = Contains(in box);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding sphere.
|
||
/// </summary>
|
||
/// <param name="sphere">The sphere.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(in BoundingSphere sphere)
|
||
{
|
||
var result = PlaneIntersectionType.Front;
|
||
var planeResult = PlaneIntersectionType.Front;
|
||
for (var i = 0; i < 6; i++)
|
||
{
|
||
switch (i)
|
||
{
|
||
case 0:
|
||
planeResult = pNear.Intersects(sphere);
|
||
break;
|
||
case 1:
|
||
planeResult = pFar.Intersects(sphere);
|
||
break;
|
||
case 2:
|
||
planeResult = pLeft.Intersects(sphere);
|
||
break;
|
||
case 3:
|
||
planeResult = pRight.Intersects(sphere);
|
||
break;
|
||
case 4:
|
||
planeResult = pTop.Intersects(sphere);
|
||
break;
|
||
case 5:
|
||
planeResult = pBottom.Intersects(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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding sphere.
|
||
/// </summary>
|
||
/// <param name="sphere">The sphere.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public ContainmentType Contains(BoundingSphere sphere)
|
||
{
|
||
return Contains(in sphere);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines the intersection relationship between the frustum and a bounding sphere.
|
||
/// </summary>
|
||
/// <param name="sphere">The sphere.</param>
|
||
/// <param name="result">Type of the containment.</param>
|
||
public void Contains(in BoundingSphere sphere, out ContainmentType result)
|
||
{
|
||
result = Contains(in sphere);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects a BoundingSphere.
|
||
/// </summary>
|
||
/// <param name="sphere">The sphere.</param>
|
||
/// <returns>Type of the containment</returns>
|
||
public bool Intersects(in BoundingSphere sphere)
|
||
{
|
||
return Contains(in sphere) != ContainmentType.Disjoint;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects a BoundingSphere.
|
||
/// </summary>
|
||
/// <param name="sphere">The sphere.</param>
|
||
/// <param name="result">Set to <c>true</c> if the current BoundingFrustum intersects a BoundingSphere.</param>
|
||
public void Intersects(in BoundingSphere sphere, out bool result)
|
||
{
|
||
result = Contains(in sphere) != ContainmentType.Disjoint;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects a BoundingBox.
|
||
/// </summary>
|
||
/// <param name="box">The box.</param>
|
||
/// <returns><c>true</c> if the current BoundingFrustum intersects a BoundingSphere.</returns>
|
||
public bool Intersects(in BoundingBox box)
|
||
{
|
||
return Contains(in box) != ContainmentType.Disjoint;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects a BoundingBox.
|
||
/// </summary>
|
||
/// <param name="box">The box.</param>
|
||
/// <param name="result"><c>true</c> if the current BoundingFrustum intersects a BoundingSphere.</param>
|
||
public void Intersects(in BoundingBox box, out bool result)
|
||
{
|
||
result = Contains(in box) != ContainmentType.Disjoint;
|
||
}
|
||
|
||
private PlaneIntersectionType PlaneIntersectsPoints(in Plane plane, Vector3[] points)
|
||
{
|
||
PlaneIntersectionType result = CollisionsHelper.PlaneIntersectsPoint(plane, points[0]);
|
||
for (var i = 1; i < points.Length; i++)
|
||
if (CollisionsHelper.PlaneIntersectsPoint(plane, points[i]) != result)
|
||
return PlaneIntersectionType.Intersecting;
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects the specified Plane.
|
||
/// </summary>
|
||
/// <param name="plane">The plane.</param>
|
||
/// <returns>Plane intersection type.</returns>
|
||
public PlaneIntersectionType Intersects(in Plane plane)
|
||
{
|
||
return PlaneIntersectsPoints(in plane, GetCorners());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects the specified Plane.
|
||
/// </summary>
|
||
/// <param name="plane">The plane.</param>
|
||
/// <param name="result">Plane intersection type.</param>
|
||
public void Intersects(in Plane plane, out PlaneIntersectionType result)
|
||
{
|
||
result = PlaneIntersectsPoints(in plane, GetCorners());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the width of the frustum at specified depth.
|
||
/// </summary>
|
||
/// <param name="depth">the depth at which to calculate frustum width.</param>
|
||
/// <returns>With of the frustum at the specified depth</returns>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the height of the frustum at specified depth.
|
||
/// </summary>
|
||
/// <param name="depth">the depth at which to calculate frustum height.</param>
|
||
/// <returns>Height of the frustum at the specified depth</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects the specified Ray.
|
||
/// </summary>
|
||
/// <param name="ray">The ray.</param>
|
||
/// <returns><c>true</c> if the current BoundingFrustum intersects the specified Ray.</returns>
|
||
public bool Intersects(in Ray ray)
|
||
{
|
||
return Intersects(in ray, out _, out _);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the current BoundingFrustum intersects the specified Ray.
|
||
/// </summary>
|
||
/// <param name="ray">The Ray to check for intersection with.</param>
|
||
/// <param name="inDistance">The distance at which the ray enters the frustum if there is an intersection and the ray starts outside the frustum.</param>
|
||
/// <param name="outDistance">The distance at which the ray exits the frustum if there is an intersection.</param>
|
||
/// <returns><c>true</c> if the current BoundingFrustum intersects the specified Ray.</returns>
|
||
public bool Intersects(in Ray ray, out Real? inDistance, out Real? outDistance)
|
||
{
|
||
if (Contains(ray.Position) != ContainmentType.Disjoint)
|
||
{
|
||
Real nearstPlaneDistance = Real.MaxValue;
|
||
for (var i = 0; i < 6; i++)
|
||
{
|
||
Plane plane = GetPlane(i);
|
||
if (CollisionsHelper.RayIntersectsPlane(ray, plane, out Real 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.
|
||
Real minDist = Real.MaxValue;
|
||
Real maxDist = Real.MinValue;
|
||
for (var i = 0; i < 6; i++)
|
||
{
|
||
Plane plane = GetPlane(i);
|
||
if (CollisionsHelper.RayIntersectsPlane(ray, plane, out Real distance))
|
||
{
|
||
minDist = Mathf.Min(minDist, distance);
|
||
maxDist = Mathf.Max(maxDist, distance);
|
||
}
|
||
}
|
||
|
||
Vector3 minPoint = ray.Position + ray.Direction * minDist;
|
||
Vector3 maxPoint = ray.Position + ray.Direction * maxDist;
|
||
Vector3 center = (minPoint + maxPoint) / 2f;
|
||
if (Contains(in center) != ContainmentType.Disjoint)
|
||
{
|
||
inDistance = minDist;
|
||
outDistance = maxDist;
|
||
return true;
|
||
}
|
||
inDistance = null;
|
||
outDistance = null;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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).
|
||
/// </summary>
|
||
/// <param name="points">The points.</param>
|
||
/// <returns>The zoom to fit distance</returns>
|
||
public Real 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();
|
||
|
||
var maxPointDist = Real.MinValue;
|
||
for (var i = 0; i < points.Length; i++)
|
||
{
|
||
var pointDist = CollisionsHelper.DistancePlanePoint(ioFrustrum.pTop, points[i]);
|
||
pointDist = Mathf.Max(pointDist, CollisionsHelper.DistancePlanePoint(ioFrustrum.pBottom, points[i]));
|
||
pointDist = Mathf.Max(pointDist, CollisionsHelper.DistancePlanePoint(ioFrustrum.pLeft, points[i]) * horizontalToVerticalMapping);
|
||
pointDist = Mathf.Max(pointDist, CollisionsHelper.DistancePlanePoint(ioFrustrum.pRight, points[i]) * horizontalToVerticalMapping);
|
||
maxPointDist = Mathf.Max(maxPointDist, pointDist);
|
||
}
|
||
return -maxPointDist / vSin;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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).
|
||
/// </summary>
|
||
/// <param name="boundingBox">The bounding box.</param>
|
||
/// <returns>The zoom to fit distance</returns>
|
||
public Real GetZoomToExtentsShiftDistance(in BoundingBox boundingBox)
|
||
{
|
||
return GetZoomToExtentsShiftDistance(boundingBox.GetCorners());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
/// <param name="points">The points.</param>
|
||
/// <returns>The zoom to fit vector</returns>
|
||
public Vector3 GetZoomToExtentsShiftVector(Vector3[] points)
|
||
{
|
||
return GetZoomToExtentsShiftDistance(points) * pNear.Normal;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.</summary>
|
||
/// <param name="boundingBox">The bounding box.</param>
|
||
/// <returns>The zoom to fit vector</returns>
|
||
public Vector3 GetZoomToExtentsShiftVector(in BoundingBox boundingBox)
|
||
{
|
||
return GetZoomToExtentsShiftDistance(boundingBox.GetCorners()) * pNear.Normal;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Indicate whether the current BoundingFrustum is Orthographic.
|
||
/// </summary>
|
||
public bool IsOrthographic => (pLeft.Normal == -pRight.Normal) && (pTop.Normal == -pBottom.Normal);
|
||
}
|
||
}
|