Refactor and enhance math, memory, and utilities
Refactored `sincos` usage in `quaternion` to use tuple-based returns for improved readability. Introduced a `random` struct with methods for generating random values of various types and dimensions, including ranges and directions. Added a `DynamicArray` class for dynamic resizing and manipulation of collections. Enhanced `SlotMap` with new methods for safe access and updates. Updated `uint` vector types with `NumericConvertable` attributes for better type interoperability. Removed the `MathUtilities` class and refactored `adj` and `adjInverse` methods for encapsulation. Improved memory management with `StackAllocator` and `UnsafeArray` enhancements. Added geometry utilities like `AABB`, `OBB`, `Plane`, and `SphereBounds` for 3D operations. Updated project configuration for versioning and NuGet packaging. Performed general code cleanup, improved validation, and aligned with modern C# practices.
This commit is contained in:
245
Misaki.HighPerformance.Mathematics/Geometry/AABB.cs
Normal file
245
Misaki.HighPerformance.Mathematics/Geometry/AABB.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an axis-aligned bounding box (AABB) defined by minimum and maximum points in 3D space.
|
||||
/// </summary>
|
||||
public struct AABB : IEquatable<AABB>
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum point contained by the AABB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If any component of <see cref="Min"/> is greater than <see cref="Max"/> then this AABB is invalid.
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsValid"/>
|
||||
public float3 Min
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum point contained by the AABB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If any component of <see cref="Max"/> is less than <see cref="Min"/> then this AABB is invalid.
|
||||
/// </remarks>
|
||||
/// <seealso cref="IsValid"/>
|
||||
public float3 Max
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the AABB with the given minimum and maximum.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you have a center and extents, you can call <see cref="CreateFromCenterAndExtents"/> or <see cref="CreateFromCenterAndHalfExtents"/>
|
||||
/// to create the AABB.
|
||||
/// </remarks>
|
||||
/// <param name="min">Minimum point inside AABB.</param>
|
||||
/// <param name="max">Maximum point inside AABB.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public AABB(float3 min, float3 max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the AABB from a center and extents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function takes full extents. It is the distance between <see cref="Min"/> and <see cref="Max"/>.
|
||||
/// If you have half extents, you can call <see cref="CreateFromCenterAndHalfExtents"/>.
|
||||
/// </remarks>
|
||||
/// <param name="center">Center of AABB.</param>
|
||||
/// <param name="extents">Full extents of AABB.</param>
|
||||
/// <returns>AABB created from inputs.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AABB CreateFromCenterAndExtents(float3 center, float3 extents)
|
||||
{
|
||||
return CreateFromCenterAndHalfExtents(center, extents * 0.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the AABB from a center and half extents.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function takes half extents. It is half the distance between <see cref="Min"/> and <see cref="Max"/>.
|
||||
/// If you have full extents, you can call <see cref="CreateFromCenterAndExtents"/>.
|
||||
/// </remarks>
|
||||
/// <param name="center">Center of AABB.</param>
|
||||
/// <param name="halfExtents">Half extents of AABB.</param>
|
||||
/// <returns>AABB created from inputs.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AABB CreateFromCenterAndHalfExtents(float3 center, float3 halfExtents)
|
||||
{
|
||||
return new AABB(center - halfExtents, center + halfExtents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new AABB with zero extents, centered at the origin.
|
||||
/// </summary>
|
||||
public static AABB Zero
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return new AABB(float3.zero, float3.zero);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the extents of the AABB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Extents is the componentwise distance between min and max.
|
||||
/// </remarks>
|
||||
public readonly float3 Extents => Max - Min;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the half extents of the AABB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// HalfExtents is half of the componentwise distance between min and max. Subtracting HalfExtents from Center
|
||||
/// gives Min and adding HalfExtents to Center gives Max.
|
||||
/// </remarks>
|
||||
public readonly float3 HalfExtents => (Max - Min) * 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the center of the AABB.
|
||||
/// </summary>
|
||||
public readonly float3 Center => (Max + Min) * 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the AABB is valid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An AABB is considered valid if <see cref="Min"/> is componentwise less than or equal to <see cref="Max"/>.
|
||||
/// </remarks>
|
||||
/// <returns>True if <see cref="Min"/> is componentwise less than or equal to <see cref="Max"/>.</returns>
|
||||
public readonly bool IsValid => math.dot(Min, Min) <= math.dot(Max, Max);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the surface area for this axis aligned bounding box.
|
||||
/// </summary>
|
||||
public readonly float SurfaceArea
|
||||
{
|
||||
get
|
||||
{
|
||||
var diff = Max - Min;
|
||||
return 2 * math.dot(diff, diff.yzx);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the input point is contained by the AABB.
|
||||
/// </summary>
|
||||
/// <param name="point">Point to test.</param>
|
||||
/// <returns>True if AABB contains the input point.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Contains(float3 point) => math.dot(point, point) >= math.dot(Min, Min) && math.dot(point, point) <= math.dot(Max, Max);
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the input AABB is contained entirely by this AABB.
|
||||
/// </summary>
|
||||
/// <param name="aabb">AABB to test.</param>
|
||||
/// <returns>True if input AABB is contained entirely by this AABB.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Contains(AABB aabb) => math.dot(Min, Min) <= math.dot(aabb.Min, aabb.Min) && math.dot(Max, Max) >= math.dot(aabb.Max, aabb.Max);
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the input AABB overlaps this AABB.
|
||||
/// </summary>
|
||||
/// <param name="aabb">AABB to test.</param>
|
||||
/// <returns>True if input AABB overlaps with this AABB.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Overlaps(AABB aabb)
|
||||
{
|
||||
return math.dot(Max, Max) >= math.dot(aabb.Min, aabb.Min) && math.dot(Min, Min) <= math.dot(aabb.Max, aabb.Max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands the AABB by the given signed distance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Positive distance expands the AABB while negative distance shrinks the AABB.
|
||||
/// </remarks>
|
||||
/// <param name="signedDistance">Signed distance to expand the AABB with.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Expand(float signedDistance)
|
||||
{
|
||||
Min -= new float3(signedDistance);
|
||||
Max += new float3(signedDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates the given AABB.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Modifies this AABB so that it contains the given AABB. If the given AABB is already contained by this AABB,
|
||||
/// then this AABB doesn't change.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Contains(Unity.Mathematics.Geometry.MinMaxAABB)"/>
|
||||
/// <param name="aabb">AABB to encapsulate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Encapsulate(AABB aabb)
|
||||
{
|
||||
Min = math.min(Min, aabb.Min);
|
||||
Max = math.max(Max, aabb.Max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulate the given point.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Modifies this AABB so that it contains the given point. If the given point is already contained by this AABB,
|
||||
/// then this AABB doesn't change.
|
||||
/// </remarks>
|
||||
/// <seealso cref="Contains(Unity.Mathematics.float3)"/>
|
||||
/// <param name="point">Point to encapsulate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Encapsulate(float3 point)
|
||||
{
|
||||
Min = math.min(Min, point);
|
||||
Max = math.max(Max, point);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Equals(AABB other)
|
||||
{
|
||||
return Min.Equals(other.Min) && Max.Equals(other.Max);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is AABB AABB)
|
||||
{
|
||||
return Equals(AABB);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static bool operator ==(AABB left, AABB right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(AABB left, AABB right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Min, Max);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return string.Format("AABB({0}, {1})", Min, Max);
|
||||
}
|
||||
}
|
||||
126
Misaki.HighPerformance.Mathematics/Geometry/OBB.cs
Normal file
126
Misaki.HighPerformance.Mathematics/Geometry/OBB.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
namespace Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
public struct OBB : IEquatable<OBB>
|
||||
{
|
||||
public quaternion Rotation
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public float3 Center
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public float3 Extents
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public OBB(quaternion rotation, float3 extents)
|
||||
{
|
||||
Rotation = rotation;
|
||||
Extents = extents;
|
||||
}
|
||||
|
||||
public readonly bool Contains(float3 point)
|
||||
{
|
||||
var localPoint = math.mul(math.conjugate(Rotation), point - Center);
|
||||
return math.all(math.abs(localPoint) <= Extents);
|
||||
}
|
||||
|
||||
private readonly void ProjectOntoAxis(float3 axis, out float min, out float max)
|
||||
{
|
||||
var localAxis = math.mul(math.conjugate(Rotation), axis);
|
||||
var r = math.abs(localAxis.x) * Extents.x + math.abs(localAxis.y) * Extents.y + math.abs(localAxis.z) * Extents.z;
|
||||
var centerProjection = math.dot(Center, axis);
|
||||
min = centerProjection - r;
|
||||
max = centerProjection + r;
|
||||
}
|
||||
|
||||
public unsafe readonly bool Overlaps(OBB other)
|
||||
{
|
||||
// Using the Separating Axis Theorem (SAT) for OBB-OBB intersection test
|
||||
var axes = stackalloc float3[15];
|
||||
var thisRotation = new float3x3(Rotation);
|
||||
var otherRotation = new float3x3(other.Rotation);
|
||||
|
||||
// 3 axes from this OBB
|
||||
axes[0] = thisRotation.c0;
|
||||
axes[1] = thisRotation.c1;
|
||||
axes[2] = thisRotation.c2;
|
||||
|
||||
// 3 axes from other OBB
|
||||
axes[3] = otherRotation.c0;
|
||||
axes[4] = otherRotation.c1;
|
||||
axes[5] = otherRotation.c2;
|
||||
|
||||
// 9 cross products of edges
|
||||
var index = 6;
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
axes[index++] = math.cross(thisRotation[i], otherRotation[j]);
|
||||
}
|
||||
}
|
||||
|
||||
var toOther = other.Center - Center;
|
||||
for (var i = 0; i < 15; i++)
|
||||
{
|
||||
var axis = axes[i];
|
||||
if (math.lengthsq(axis) < 1e-6f)
|
||||
{
|
||||
continue; // Skip near-zero axes
|
||||
}
|
||||
|
||||
axis = math.normalize(axis);
|
||||
|
||||
// Project both OBBs onto the axis
|
||||
ProjectOntoAxis(axis, out var thisMin, out var thisMax);
|
||||
other.ProjectOntoAxis(axis, out var otherMin, out var otherMax);
|
||||
|
||||
// Project the vector between centers onto the axis
|
||||
var distance = math.dot(toOther, axis);
|
||||
|
||||
// Check for overlap
|
||||
if (thisMax + distance < otherMin || otherMax + distance < thisMin)
|
||||
{
|
||||
return false; // Found a separating axis
|
||||
}
|
||||
}
|
||||
return true; // No separating axis found, OBBs overlap
|
||||
}
|
||||
|
||||
public readonly AABB ToAABB()
|
||||
{
|
||||
var absRotation = math.abs(new float3x3(Rotation));
|
||||
var worldExtents = absRotation.c0 * Extents.x + absRotation.c1 * Extents.y + absRotation.c2 * Extents.z;
|
||||
return new AABB(Center - worldExtents, Center + worldExtents);
|
||||
}
|
||||
|
||||
public readonly bool Equals(OBB other)
|
||||
{
|
||||
return Rotation.Equals(other.Rotation) && Center.Equals(other.Center) && Extents.Equals(other.Extents);
|
||||
}
|
||||
|
||||
public readonly override bool Equals(object? obj)
|
||||
{
|
||||
return obj is OBB obb && Equals(obb);
|
||||
}
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Rotation, Center, Extents);
|
||||
}
|
||||
|
||||
public static bool operator ==(OBB left, OBB right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(OBB left, OBB right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
228
Misaki.HighPerformance.Mathematics/Geometry/Plane.cs
Normal file
228
Misaki.HighPerformance.Mathematics/Geometry/Plane.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// A plane represented by a normal vector and a distance along the normal from the origin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A plane splits the 3D space in half. The normal vector points to the positive half and the other half is
|
||||
/// considered negative.
|
||||
/// </remarks>
|
||||
public struct Plane
|
||||
{
|
||||
/// <summary>
|
||||
/// A plane in the form Ax + By + Cz + Dw = 0.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Stores the plane coefficients A, B, C, D where (A, B, C) is a unit normal vector and D is the distance
|
||||
/// from the origin. A plane stored with a unit normal vector is called a normalized plane.
|
||||
/// </remarks>
|
||||
public float4 normalAndDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a Plane from arbitrary coefficients A, B, C, D of the plane equation Ax + By + Cz + Dw = 0.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The constructed plane will be the normalized form of the plane specified by the given coefficients.
|
||||
/// </remarks>
|
||||
/// <param name="coefficientA">Coefficient A from plane equation.</param>
|
||||
/// <param name="coefficientB">Coefficient B from plane equation.</param>
|
||||
/// <param name="coefficientC">Coefficient C from plane equation.</param>
|
||||
/// <param name="coefficientD">Coefficient D from plane equation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Plane(float coefficientA, float coefficientB, float coefficientC, float coefficientD)
|
||||
{
|
||||
normalAndDistance = Normalize(new float4(coefficientA, coefficientB, coefficientC, coefficientD));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a plane with a normal vector and distance from the origin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The constructed plane will be the normalized form of the plane specified by the inputs.
|
||||
/// </remarks>
|
||||
/// <param name="normal">A non-zero vector that is perpendicular to the plane. It may be any length.</param>
|
||||
/// <param name="distance">Distance from the origin along the normal. A negative value moves the plane in the
|
||||
/// same direction as the normal while a positive value moves it in the opposite direction.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Plane(float3 normal, float distance)
|
||||
{
|
||||
normalAndDistance = Normalize(new float4(normal, distance));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a plane with a normal vector and a point that lies in the plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The constructed plane will be the normalized form of the plane specified by the inputs.
|
||||
/// </remarks>
|
||||
/// <param name="normal">A non-zero vector that is perpendicular to the plane. It may be any length.</param>
|
||||
/// <param name="pointInPlane">A point that lies in the plane.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Plane(float3 normal, float3 pointInPlane)
|
||||
: this(normal, -math.dot(normal, pointInPlane))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a plane with two vectors and a point that all lie in the plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The constructed plane will be the normalized form of the plane specified by the inputs.
|
||||
/// </remarks>
|
||||
/// <param name="vector1InPlane">A non-zero vector that lies in the plane. It may be any length.</param>
|
||||
/// <param name="vector2InPlane">A non-zero vector that lies in the plane. It may be any length and must not be a scalar multiple of <paramref name="vector1InPlane"/>.</param>
|
||||
/// <param name="pointInPlane">A point that lies in the plane.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Plane(float3 vector1InPlane, float3 vector2InPlane, float3 pointInPlane)
|
||||
: this(math.cross(vector1InPlane, vector2InPlane), pointInPlane)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a normalized Plane directly without normalization cost.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you have a unit length normal vector, you can create a Plane faster than using one of its constructors
|
||||
/// by calling this function.
|
||||
/// </remarks>
|
||||
/// <param name="unitNormal">A non-zero vector that is perpendicular to the plane. It must be unit length.</param>
|
||||
/// <param name="distance">Distance from the origin along the normal. A negative value moves the plane in the
|
||||
/// same direction as the normal while a positive value moves it in the opposite direction.</param>
|
||||
/// <returns>Normalized Plane constructed from given inputs.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Plane CreateFromUnitNormalAndDistance(float3 unitNormal, float distance)
|
||||
{
|
||||
return new Plane { normalAndDistance = new float4(unitNormal, distance) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a normalized Plane without normalization cost.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you have a unit length normal vector, you can create a Plane faster than using one of its constructors
|
||||
/// by calling this function.
|
||||
/// </remarks>
|
||||
/// <param name="unitNormal">A non-zero vector that is perpendicular to the plane. It must be unit length.</param>
|
||||
/// <param name="pointInPlane">A point that lies in the plane.</param>
|
||||
/// <returns>Normalized Plane constructed from given inputs.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Plane CreateFromUnitNormalAndPointInPlane(float3 unitNormal, float3 pointInPlane)
|
||||
{
|
||||
return new Plane { normalAndDistance = new float4(unitNormal, -math.dot(unitNormal, pointInPlane)) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set the normal vector of the plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is assumed that the normal is unit length. If you set a new plane such that Ax + By + Cz + Dw = 0 but
|
||||
/// (A, B, C) is not unit length, then you must normalize the plane by calling <see cref="Normalize(Plane)"/>.
|
||||
/// </remarks>
|
||||
public float3 Normal
|
||||
{
|
||||
get => normalAndDistance.xyz;
|
||||
set => normalAndDistance.xyz = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set the distance of the plane from the origin. May be a negative value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is assumed that the normal is unit length. If you set a new plane such that Ax + By + Cz + Dw = 0 but
|
||||
/// (A, B, C) is not unit length, then you must normalize the plane by calling <see cref="Normalize(Plane)"/>.
|
||||
/// </remarks>
|
||||
public float Distance
|
||||
{
|
||||
get => normalAndDistance.w;
|
||||
set => normalAndDistance.w = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the given Plane.
|
||||
/// </summary>
|
||||
/// <param name="plane">Plane to normalize.</param>
|
||||
/// <returns>Normalized Plane.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Plane Normalize(Plane plane)
|
||||
{
|
||||
return new Plane { normalAndDistance = Normalize(plane.normalAndDistance) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the plane represented by the given plane coefficients.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The plane coefficients are A, B, C, D and stored in that order in the <see cref="float4"/>.
|
||||
/// </remarks>
|
||||
/// <param name="planeCoefficients">Plane coefficients A, B, C, D stored in x, y, z, w (respectively).</param>
|
||||
/// <returns>Normalized plane coefficients.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float4 Normalize(float4 planeCoefficients)
|
||||
{
|
||||
float recipLength = math.rsqrt(math.lengthsq(planeCoefficients.xyz));
|
||||
return new Plane { normalAndDistance = planeCoefficients * recipLength };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the signed distance from the point to the plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plane must be normalized prior to calling this function. Distance is positive if point is on side of the
|
||||
/// plane the normal points to, negative if on the opposite side and zero if the point lies in the plane.
|
||||
/// Avoid comparing equality with 0.0f when testing if a point lies exactly in the plane and use an approximate
|
||||
/// comparison instead.
|
||||
/// </remarks>
|
||||
/// <param name="point">Point to find the signed distance with.</param>
|
||||
/// <returns>Signed distance of the point from the plane.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public float SignedDistanceToPoint(float3 point)
|
||||
{
|
||||
CheckPlaneIsNormalized();
|
||||
return math.dot(normalAndDistance, new float4(point, 1.0f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects the given point onto the plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Plane must be normalized prior to calling this function. The result is the position closest to the point
|
||||
/// that still lies in the plane.
|
||||
/// </remarks>
|
||||
/// <param name="point">Point to project onto the plane.</param>
|
||||
/// <returns>Projected point that's inside the plane.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public float3 Projection(float3 point)
|
||||
{
|
||||
CheckPlaneIsNormalized();
|
||||
return point - Normal * SignedDistanceToPoint(point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the plane so the normal points in the opposite direction.
|
||||
/// </summary>
|
||||
public Plane Flipped => new Plane { normalAndDistance = -normalAndDistance };
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="Plane"/> to <see cref="float4"/>.
|
||||
/// </summary>
|
||||
/// <param name="plane">Plane to convert.</param>
|
||||
/// <returns>A <see cref="float4"/> representing the plane.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator float4(Plane plane) => plane.normalAndDistance;
|
||||
|
||||
[Conditional("ENABLE_COLLECTIONS_CHECKS")]
|
||||
void CheckPlaneIsNormalized()
|
||||
{
|
||||
float ll = math.lengthsq(Normal.xyz);
|
||||
const float lowerBound = 0.999f * 0.999f;
|
||||
const float upperBound = 1.001f * 1.001f;
|
||||
|
||||
if (ll < lowerBound || ll > upperBound)
|
||||
{
|
||||
throw new System.ArgumentException("Plane must be normalized. Call Plane.Normalize() to normalize plane.");
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Misaki.HighPerformance.Mathematics/Geometry/SphereBounds.cs
Normal file
130
Misaki.HighPerformance.Mathematics/Geometry/SphereBounds.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a three-dimensional sphere defined by a center point and a radius, typically used for spatial bounding or
|
||||
/// intersection tests.
|
||||
/// </summary>
|
||||
public struct SphereBounds : IEquatable<SphereBounds>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the center point in three-dimensional space.
|
||||
/// </summary>
|
||||
public float3 Center
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the radius of the shape.
|
||||
/// </summary>
|
||||
public float Radius
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SphereBounds(float3 center, float radius)
|
||||
{
|
||||
Center = center;
|
||||
Radius = radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified point is contained within the sphere.
|
||||
/// </summary>
|
||||
/// <param name="point">The point to test for inclusion within the sphere.</param>
|
||||
/// <returns>true if the point lies inside or on the boundary of the sphere; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Contains(float3 point)
|
||||
{
|
||||
var toPoint = point - Center;
|
||||
return math.lengthsq(toPoint) <= Radius * Radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this sphere intersects with the specified sphere.
|
||||
/// </summary>
|
||||
/// <param name="other">The sphere to test for intersection with this sphere.</param>
|
||||
/// <returns>true if the spheres intersect or touch; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Overlaps(SphereBounds other)
|
||||
{
|
||||
var toOther = other.Center - Center;
|
||||
var radiusSum = Radius + other.Radius;
|
||||
return math.lengthsq(toOther) <= radiusSum * radiusSum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly float3 ClosestPoint(float3 point)
|
||||
{
|
||||
var toPoint = point - Center;
|
||||
var toPointLength = math.length(toPoint);
|
||||
if (toPointLength <= Radius || toPointLength == 0.0f)
|
||||
{
|
||||
return point;
|
||||
}
|
||||
|
||||
return Center + toPoint / toPointLength * Radius;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Encapsulate(SphereBounds other)
|
||||
{
|
||||
var toOther = other.Center - Center;
|
||||
var toOtherLength = math.length(toOther);
|
||||
|
||||
if (toOtherLength + other.Radius > Radius)
|
||||
{
|
||||
var newRadius = (Radius + toOtherLength + other.Radius) * 0.5f;
|
||||
var k = (newRadius - Radius) / toOtherLength;
|
||||
Center += toOther * k;
|
||||
Radius = newRadius;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Encapsulate(float3 point)
|
||||
{
|
||||
var toPoint = point - Center;
|
||||
var toPointLength = math.length(toPoint);
|
||||
|
||||
if (toPointLength > Radius)
|
||||
{
|
||||
var newRadius = (Radius + toPointLength) * 0.5f;
|
||||
var k = (newRadius - Radius) / toPointLength;
|
||||
Center += toPoint * k;
|
||||
Radius = newRadius;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return $"Center: {Center}, Radius: {Radius}";
|
||||
}
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Center, Radius);
|
||||
}
|
||||
|
||||
public override readonly bool Equals(object? obj)
|
||||
{
|
||||
return obj is SphereBounds bound && Equals(bound);
|
||||
}
|
||||
|
||||
public readonly bool Equals(SphereBounds other)
|
||||
{
|
||||
return Center.Equals(other.Center) && Radius.Equals(other.Radius);
|
||||
}
|
||||
|
||||
public static bool operator ==(SphereBounds left, SphereBounds right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(SphereBounds left, SphereBounds right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user