using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// Plane represents a normalized plane equation.
/// "Over" or "Above" the plane is considered the side of
/// the plane towards where the normal is pointing.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Plane : IEquatable<Plane>
{
private Vector3 _normal;
private real_t _d;
/// <summary>
/// The normal of the plane, which must be a unit vector.
/// In the scalar equation of the plane <c>ax + by + cz = d</c>, this is
/// the vector <c>(a, b, c)</c>, where <c>d</c> is the <see cref="D"/> property.
/// </summary>
/// <value>Equivalent to <see cref="X"/>, <see cref="Y"/>, and <see cref="Z"/>.</value>
public Vector3 Normal
{
readonly get { return _normal; }
set { _normal = value; }
}
/// <summary>
/// The distance from the origin to the plane (in the direction of
/// <see cref="Normal"/>). This value is typically non-negative.
/// In the scalar equation of the plane <c>ax + by + cz = d</c>,
/// this is <c>d</c>, while the <c>(a, b, c)</c> coordinates are represented
/// by the <see cref="Normal"/> property.
/// </summary>
/// <value>The plane's distance from the origin.</value>
public real_t D
{
readonly get { return _d; }
set { _d = value; }
}
/// <summary>
/// The X component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s X value.</value>
public real_t X
{
readonly get
{
return _normal.X;
}
set
{
_normal.X = value;
}
}
/// <summary>
/// The Y component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s Y value.</value>
public real_t Y
{
readonly get
{
return _normal.Y;
}
set
{
_normal.Y = value;
}
}
/// <summary>
/// The Z component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s Z value.</value>
public real_t Z
{
readonly get
{
return _normal.Z;
}
set
{
_normal.Z = value;
}
}
/// <summary>
/// Returns the shortest distance from this plane to the position <paramref name="point"/>.
/// </summary>
/// <param name="point">The position to use for the calculation.</param>
/// <returns>The shortest distance.</returns>
public readonly real_t DistanceTo(Vector3 point)
{
return _normal.Dot(point) - _d;
}
/// <summary>
/// Returns the center of the plane, the point on the plane closest to the origin.
/// The point where the normal line going through the origin intersects the plane.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/> multiplied by <see cref="D"/>.</value>
public readonly Vector3 GetCenter()
{
return _normal * _d;
}
/// <summary>
/// Returns <see langword="true"/> if point is inside the plane.
/// Comparison uses a custom minimum tolerance threshold.
/// </summary>
/// <param name="point">The point to check.</param>
/// <param name="tolerance">The tolerance threshold.</param>
/// <returns>A <see langword="bool"/> for whether or not the plane has the point.</returns>
public readonly bool HasPoint(Vector3 point, real_t tolerance = Mathf.Epsilon)
{
real_t dist = _normal.Dot(point) - _d;
return Mathf.Abs(dist) <= tolerance;
}
/// <summary>
/// Returns the intersection point of the three planes: <paramref name="b"/>, <paramref name="c"/>,
/// and this plane. If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="b">One of the three planes to use in the calculation.</param>
/// <param name="c">One of the three planes to use in the calculation.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? Intersect3(Plane b, Plane c)
{
real_t denom = _normal.Cross(b._normal).Dot(c._normal);
if (Mathf.IsZeroApprox(denom))
{
return null;
}
Vector3 result = (b._normal.Cross(c._normal) * _d) +
(c._normal.Cross(_normal) * b._d) +
(_normal.Cross(b._normal) * c._d);
return result / denom;
}
/// <summary>
/// Returns the intersection point of a ray consisting of the position <paramref name="from"/>
/// and the direction normal <paramref name="dir"/> with this plane.
/// If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="from">The start of the ray.</param>
/// <param name="dir">The direction of the ray, normalized.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? IntersectsRay(Vector3 from, Vector3 dir)
{
real_t den = _normal.Dot(dir);
if (Mathf.IsZeroApprox(den))
{
return null;
}
real_t dist = (_normal.Dot(from) - _d) / den;
// This is a ray, before the emitting pos (from) does not exist
if (dist > Mathf.Epsilon)
{
return null;
}
return from - (dir * dist);
}
/// <summary>
/// Returns the intersection point of a line segment from
/// position <paramref name="begin"/> to position <paramref name="end"/> with this plane.
/// If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="begin">The start of the line segment.</param>
/// <param name="end">The end of the line segment.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? IntersectsSegment(Vector3 begin, Vector3 end)
{
Vector3 segment = begin - end;
real_t den = _normal.Dot(segment);
if (Mathf.IsZeroApprox(den))
{
return null;
}
real_t dist = (_normal.Dot(begin) - _d) / den;
// Only allow dist to be in the range of 0 to 1, with tolerance.
if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon)
{
return null;
}
return begin - (segment * dist);
}
/// <summary>
/// Returns <see langword="true"/> if this plane is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return _normal.IsFinite() && Mathf.IsFinite(D);
}
/// <summary>
/// Returns <see langword="true"/> if <paramref name="point"/> is located above the plane.
/// </summary>
/// <param name="point">The point to check.</param>
/// <returns>A <see langword="bool"/> for whether or not the point is above the plane.</returns>
public readonly bool IsPointOver(Vector3 point)
{
return _normal.Dot(point) > _d;
}
/// <summary>
/// Returns the plane scaled to unit length.
/// </summary>
/// <returns>A normalized version of the plane.</returns>
public readonly Plane Normalized()
{
real_t len = _normal.Length();
if (len == 0)
{
return new Plane(0, 0, 0, 0);
}
return new Plane(_normal / len, _d / len);
}
/// <summary>
/// Returns the orthogonal projection of <paramref name="point"/> into the plane.
/// </summary>
/// <param name="point">The point to project.</param>
/// <returns>The projected point.</returns>
public readonly Vector3 Project(Vector3 point)
{
return point - (_normal * DistanceTo(point));
}
// Constants
private static readonly Plane _planeYZ = new Plane(1, 0, 0, 0);
private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0);
private static readonly Plane _planeXY = new Plane(0, 0, 1, 0);
/// <summary>
/// A <see cref="Plane"/> that extends in the Y and Z axes (normal vector points +X).
/// </summary>
/// <value>Equivalent to <c>new Plane(1, 0, 0, 0)</c>.</value>
public static Plane PlaneYZ { get { return _planeYZ; } }
/// <summary>
/// A <see cref="Plane"/> that extends in the X and Z axes (normal vector points +Y).
/// </summary>
/// <value>Equivalent to <c>new Plane(0, 1, 0, 0)</c>.</value>
public static Plane PlaneXZ { get { return _planeXZ; } }
/// <summary>
/// A <see cref="Plane"/> that extends in the X and Y axes (normal vector points +Z).
/// </summary>
/// <value>Equivalent to <c>new Plane(0, 0, 1, 0)</c>.</value>
public static Plane PlaneXY { get { return _planeXY; } }
/// <summary>
/// Constructs a <see cref="Plane"/> from four values.
/// <paramref name="a"/>, <paramref name="b"/> and <paramref name="c"/> become the
/// components of the resulting plane's <see cref="Normal"/> vector.
/// <paramref name="d"/> becomes the plane's distance from the origin.
/// </summary>
/// <param name="a">The X component of the plane's normal vector.</param>
/// <param name="b">The Y component of the plane's normal vector.</param>
/// <param name="c">The Z component of the plane's normal vector.</param>
/// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(real_t a, real_t b, real_t c, real_t d)
{
_normal = new Vector3(a, b, c);
_d = d;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector.
/// The plane will intersect the origin.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
public Plane(Vector3 normal)
{
_normal = normal;
_d = 0;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and
/// the plane's distance to the origin <paramref name="d"/>.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
/// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(Vector3 normal, real_t d)
{
_normal = normal;
_d = d;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and
/// a <paramref name="point"/> on the plane.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
/// <param name="point">The point on the plane.</param>
public Plane(Vector3 normal, Vector3 point)
{
_normal = normal;
_d = _normal.Dot(point);
}
/// <summary>
/// Constructs a <see cref="Plane"/> from the three points, given in clockwise order.
/// </summary>
/// <param name="v1">The first point.</param>
/// <param name="v2">The second point.</param>
/// <param name="v3">The third point.</param>
public Plane(Vector3 v1, Vector3 v2, Vector3 v3)
{
_normal = (v1 - v3).Cross(v1 - v2);
_normal.Normalize();
_d = _normal.Dot(v1);
}
/// <summary>
/// Returns the negative value of the <see cref="Plane"/>.
/// This is the same as writing <c>new Plane(-p.Normal, -p.D)</c>.
/// This operation flips the direction of the normal vector and
/// also flips the distance value, resulting in a Plane that is
/// in the same place, but facing the opposite direction.
/// </summary>
/// <param name="plane">The plane to negate/flip.</param>
/// <returns>The negated/flipped plane.</returns>
public static Plane operator -(Plane plane)
{
return new Plane(-plane._normal, -plane._d);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Plane"/>s are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the planes are exactly equal.</returns>
public static bool operator ==(Plane left, Plane right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Plane"/>s are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the planes are not equal.</returns>
public static bool operator !=(Plane left, Plane right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the plane and the other object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Plane other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="other"/> are equal.
/// </summary>
/// <param name="other">The other plane to compare.</param>
/// <returns>Whether or not the planes are exactly equal.</returns>
public readonly bool Equals(Plane other)
{
return _normal == other._normal && _d == other._d;
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="other"/> are
/// approximately equal, by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
/// </summary>
/// <param name="other">The other plane to compare.</param>
/// <returns>Whether or not the planes are approximately equal.</returns>
public readonly bool IsEqualApprox(Plane other)
{
return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(_d, other._d);
}
/// <summary>
/// Serves as the hash function for <see cref="Plane"/>.
/// </summary>
/// <returns>A hash code for this plane.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_normal, _d);
}
/// <summary>
/// Converts this <see cref="Plane"/> to a string.
/// </summary>
/// <returns>A string representation of this plane.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this plane.</returns>
public readonly string ToString(string? format)
{
return $"{_normal.ToString(format)}, {_d.ToString(format, CultureInfo.InvariantCulture)}";
}
}
}