< Summary

Information
Class: FixedMathSharp.Geometry.FixedBoundSphere
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Bounds/FixedBoundSphere.cs
Line coverage
100%
Covered lines: 314
Uncovered lines: 0
Coverable lines: 314
Total lines: 878
Line coverage: 100%
Branch coverage
100%
Covered branches: 154
Total branches: 154
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
get_Radius()100%11100%
set_Radius(...)100%11100%
get_Min()100%11100%
get_Max()100%11100%
get_State()100%11100%
CreateFromBoundingBox(...)100%22100%
CreateFromFrustum(...)100%11100%
CreateFromPoints(...)100%66100%
CreateFromPoints(...)100%22100%
CreateFromPoints(...)100%11100%
CreateMerged(...)100%44100%
MergeNonContaining(...)100%88100%
CreateFromPointList(...)100%1818100%
CreateFromPointSpan(...)100%1818100%
CreateFromFrustumCorners(...)100%1616100%
CreateFromExtremePairs(...)100%88100%
ExpandToContain(...)100%22100%
ContainsSphere(...)100%22100%
TryGetMergedRadius(...)100%44100%
TryGetRequiredRadius(...)100%22100%
GetDistanceRoot(...)100%11100%
TryCreatePositiveRaw(...)100%22100%
GetFartherEndpoint(...)100%22100%
GetRawDistance(...)100%22100%
CreateUnrepresentableRadiusException()100%11100%
Contains(...)100%11100%
ContainsStrict(...)100%22100%
Contains(...)100%11100%
Contains(...)100%66100%
Contains(...)100%88100%
Intersects(...)100%11100%
Intersects(...)100%11100%
IntersectsStrict(...)100%11100%
IntersectsStrict(...)100%44100%
Intersects(...)100%11100%
Intersects(...)100%11100%
Intersects(...)100%11100%
ProjectPoint(...)100%22100%
ClampPoint(...)100%22100%
DistanceToSurface(...)100%11100%
Transform(...)100%11100%
Deconstruct(...)100%11100%
ContainsBoxLike(...)100%1818100%
GetMaxBasisScale(...)100%11100%
NormalizeRadius(...)100%11100%
op_Equality(...)100%11100%
op_Inequality(...)100%11100%
Equals(...)100%22100%
Equals(...)100%22100%
GetHashCode()100%11100%
ToString()100%11100%
ToString(...)100%11100%
TryFormat(...)100%1010100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Bounds/FixedBoundSphere.cs

#LineLine coverage
 1//=======================================================================
 2// FixedBoundSphere.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Collections.Generic;
 10using System.Globalization;
 11using System.Runtime.CompilerServices;
 12using System.Text.Json.Serialization;
 13using MemoryPack;
 14
 15namespace FixedMathSharp.Geometry;
 16
 17/// <summary>
 18/// Represents a spherical bounding volume with fixed-point precision, optimized for fast,
 19/// rotationally invariant spatial checks in 3D space.
 20/// </summary>
 21/// <remarks>
 22/// The FixedBoundSphere provides a simple yet effective way to represent the spatial extent of objects,
 23/// especially when rotational invariance is required.
 24/// Compared to FixedBoundBox, it offers faster intersection checks but is less precise in
 25/// tightly fitting non-spherical objects.
 26///
 27/// Use Cases:
 28/// - Ideal for broad-phase collision detection, proximity checks, and culling in physics engines and rendering pipeline
 29/// - Useful when fast, rotationally invariant checks are needed, such as detecting overlaps or distances between moving
 30/// - Suitable for encapsulating objects with roughly spherical shapes or objects that rotate frequently, where the boun
 31/// </remarks>
 32[Serializable]
 33[MemoryPackable]
 34public partial struct FixedBoundSphere : IEquatable<FixedBoundSphere>, IFormattable
 35#if NET8_0_OR_GREATER
 36    , ISpanFormattable
 37#endif
 38{
 39    #region Nested Types
 40
 41    /// <summary>
 42    /// Represents the normalized serializable state of a three-dimensional spherical bound.
 43    /// </summary>
 44    [Serializable]
 45    [MemoryPackable]
 46    public readonly partial struct BoundingSphereState
 47    {
 48        /// <inheritdoc cref="FixedBoundSphere.Center"/>
 49        [JsonInclude]
 50        [MemoryPackInclude]
 51        public readonly Vector3d Center;
 52
 53        /// <inheritdoc cref="FixedBoundSphere.Radius"/>
 54        [JsonInclude]
 55        [MemoryPackInclude]
 56        public readonly Fixed64 Radius;
 57
 58        /// <summary>
 59        /// Initializes a normalized state from center and radius.
 60        /// </summary>
 61        [JsonConstructor]
 62        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 63        public BoundingSphereState(Vector3d center, Fixed64 radius)
 64        {
 765            Center = center;
 766            Radius = NormalizeRadius(radius);
 767        }
 68    }
 69
 70    #endregion
 71
 72    #region Fields
 73
 74    /// <summary>
 75    /// The radius backing field.
 76    /// </summary>
 77    [JsonIgnore]
 78    [MemoryPackIgnore]
 79    private Fixed64 _radius;
 80
 81    #endregion
 82
 83    #region Constructors
 84
 85    /// <summary>
 86    /// Initializes a new instance of the FixedBoundSphere struct with the specified center and radius.
 87    /// </summary>
 88    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 89    public FixedBoundSphere(Vector3d center, Fixed64 radius)
 90    {
 25991        Center = center;
 25992        _radius = NormalizeRadius(radius);
 25993    }
 94
 95    /// <summary>
 96    /// Initializes a new instance from serialized or caller-provided state.
 97    /// </summary>
 98    [JsonConstructor]
 99    public FixedBoundSphere(BoundingSphereState state)
 100    {
 3101        Center = state.Center;
 3102        _radius = state.Radius;
 3103    }
 104
 105    #endregion
 106
 107    #region Properties
 108
 109    /// <summary>
 110    /// The center point of the sphere.
 111    /// </summary>
 112    [JsonIgnore]
 113    [MemoryPackIgnore]
 114    public Vector3d Center { get; set; }
 115
 116    /// <summary>
 117    /// The non-negative radius of the sphere. Assigned values are normalized by absolute value.
 118    /// </summary>
 119    [JsonIgnore]
 120    [MemoryPackIgnore]
 121    public Fixed64 Radius
 122    {
 123        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1171124        get => _radius;
 125
 126        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1127        set => _radius = NormalizeRadius(value);
 128    }
 129
 130    /// <summary>
 131    /// Gets the coordinates of the minimum corner of the bounding box that contains the sphere.
 132    /// </summary>
 133    /// <remarks>
 134    /// The minimum corner is calculated by subtracting the radius from each component of the sphere's center.
 135    /// This property is useful for spatial queries and bounding box calculations.
 136    /// </remarks>
 137    [JsonIgnore]
 138    [MemoryPackIgnore]
 139    public Vector3d Min
 140    {
 141        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3142        get => Center - new Vector3d(Radius, Radius, Radius);
 143    }
 144
 145    /// <summary>
 146    /// Gets the coordinates of the maximum corner of the bounding box that contains the sphere.
 147    /// </summary>
 148    /// <remarks>
 149    /// The maximum corner is calculated as the center of the sphere plus the radius in each dimension.
 150    /// This property is useful for spatial queries and bounding volume calculations.
 151    /// </remarks>
 152    [JsonIgnore]
 153    [MemoryPackIgnore]
 154    public Vector3d Max
 155    {
 156        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3157        get => Center + new Vector3d(Radius, Radius, Radius);
 158    }
 159
 160    /// <summary>
 161    /// Gets the current normalized state of the sphere.
 162    /// </summary>
 163    [JsonInclude]
 164    [MemoryPackInclude]
 3165    public BoundingSphereState State => new(Center, Radius);
 166
 167    #endregion
 168
 169    #region Methods (Static)
 170
 171    /// <summary>
 172    /// Creates a bounding sphere that contains the specified axis-aligned bounding box.
 173    /// </summary>
 174    /// <exception cref="OverflowException">
 175    /// Thrown when this construction requires an unrepresentable radius.
 176    /// </exception>
 177    public static FixedBoundSphere CreateFromBoundingBox(FixedBoundBox box)
 178    {
 4179        Vector3d center = Vector3d.Midpoint(box.Min, box.Max);
 4180        Vector3d farthestCorner = new(
 4181            GetFartherEndpoint(box.Min.X, box.Max.X, center.X),
 4182            GetFartherEndpoint(box.Min.Y, box.Max.Y, center.Y),
 4183            GetFartherEndpoint(box.Min.Z, box.Max.Z, center.Z));
 4184        if (!TryGetRequiredRadius(center, farthestCorner, Fixed64.Zero, out Fixed64 radius))
 1185            throw CreateUnrepresentableRadiusException();
 186
 3187        return new FixedBoundSphere(center, radius);
 188    }
 189
 190    /// <summary>
 191    /// Creates a bounding sphere that contains the specified frustum.
 192    /// </summary>
 193    /// <exception cref="OverflowException">
 194    /// Thrown when this construction requires an unrepresentable radius.
 195    /// </exception>
 196    public static FixedBoundSphere CreateFromFrustum(FixedBoundFrustum frustum)
 197    {
 7198        return CreateFromFrustumCorners(frustum);
 199    }
 200
 201    /// <summary>
 202    /// Creates a bounding sphere that contains the specified points.
 203    /// </summary>
 204    /// <exception cref="OverflowException">
 205    /// Thrown when this construction requires an unrepresentable radius.
 206    /// </exception>
 207    public static FixedBoundSphere CreateFromPoints(IEnumerable<Vector3d> points)
 208    {
 6209        if (points is null)
 1210            throw new ArgumentNullException(nameof(points), "Cannot create a bounding sphere from a null collection of p
 211
 5212        if (points is IReadOnlyList<Vector3d> pointList)
 4213            return CreateFromPointList(pointList);
 214
 1215        var materialized = new List<Vector3d>();
 10216        foreach (Vector3d point in points)
 4217            materialized.Add(point);
 218
 1219        return CreateFromPointList(materialized);
 220    }
 221
 222    /// <summary>
 223    /// Creates a bounding sphere that contains the specified points.
 224    /// </summary>
 225    /// <exception cref="OverflowException">
 226    /// Thrown when this construction requires an unrepresentable radius.
 227    /// </exception>
 228    public static FixedBoundSphere CreateFromPoints(Vector3d[] points)
 229    {
 10230        if (points is null)
 1231            throw new ArgumentNullException(nameof(points), "Cannot create a bounding sphere from a null collection of p
 232
 9233        return CreateFromPoints(points.AsSpan());
 234    }
 235
 236    /// <summary>
 237    /// Creates a bounding sphere that contains the specified points.
 238    /// </summary>
 239    /// <exception cref="OverflowException">
 240    /// Thrown when this construction requires an unrepresentable radius.
 241    /// </exception>
 242    public static FixedBoundSphere CreateFromPoints(ReadOnlySpan<Vector3d> points)
 243    {
 14244        return CreateFromPointSpan(points);
 245    }
 246
 247    /// <summary>
 248    /// Creates a deterministic sphere that contains the two specified spheres.
 249    /// </summary>
 250    /// <exception cref="OverflowException">
 251    /// Thrown when this construction cannot produce a containing sphere with a
 252    /// representable radius.
 253    /// </exception>
 254    public static FixedBoundSphere CreateMerged(FixedBoundSphere original, FixedBoundSphere additional)
 255    {
 13256        if (ContainsSphere(original, additional))
 2257            return original;
 258
 11259        if (ContainsSphere(additional, original))
 2260            return additional;
 261
 9262        return MergeNonContaining(original, additional);
 263    }
 264
 265    private static FixedBoundSphere MergeNonContaining(
 266        FixedBoundSphere original,
 267        FixedBoundSphere additional)
 268    {
 269
 36270        GetDistanceRoot(original.Center, additional.Center, out Signed192 distanceFloor, out Signed192 distanceRemainder
 36271        if (!TryGetMergedRadius(
 36272                distanceFloor,
 36273                distanceRemainder,
 36274                original.Radius,
 36275                additional.Radius,
 36276                out Fixed64 radius))
 277        {
 1278            throw CreateUnrepresentableRadiusException();
 279        }
 280
 35281        Signed192 firstGap = Signed192.Signed(
 35282            (radius - original.Radius).m_rawValue);
 35283        Signed192 secondGap = Signed192.Signed(
 35284            (radius - additional.Radius).m_rawValue);
 35285        Signed192 gapSum = WideArithmetic.AddSigned192(firstGap, secondGap);
 35286        Vector3d center = new(
 35287            WideGeometry.InterpolateCoordinate(
 35288                original.Center.X,
 35289                additional.Center.X,
 35290                firstGap,
 35291                gapSum),
 35292            WideGeometry.InterpolateCoordinate(
 35293                original.Center.Y,
 35294                additional.Center.Y,
 35295                firstGap,
 35296                gapSum),
 35297            WideGeometry.InterpolateCoordinate(
 35298                original.Center.Z,
 35299                additional.Center.Z,
 35300                firstGap,
 35301                gapSum));
 302
 35303        var merged = new FixedBoundSphere(center, radius);
 35304        bool containsOriginal = ContainsSphere(merged, original);
 35305        bool containsAdditional = ContainsSphere(merged, additional);
 35306        if (containsOriginal & containsAdditional)
 15307            return merged;
 308
 20309        bool originalRadiusRepresentable = TryGetRequiredRadius(
 20310            center,
 20311            original.Center,
 20312            original.Radius,
 20313            out Fixed64 originalRequired);
 20314        bool additionalRadiusRepresentable = TryGetRequiredRadius(
 20315            center,
 20316            additional.Center,
 20317            additional.Radius,
 20318            out Fixed64 additionalRequired);
 20319        if (!(originalRadiusRepresentable & additionalRadiusRepresentable))
 320        {
 1321            throw CreateUnrepresentableRadiusException();
 322        }
 323
 19324        return new FixedBoundSphere(
 19325            center,
 19326            originalRequired >= additionalRequired ? originalRequired : additionalRequired);
 327    }
 328
 329    private static FixedBoundSphere CreateFromPointList(IReadOnlyList<Vector3d> points)
 330    {
 5331        if (points.Count == 0)
 1332            throw new ArgumentException("At least one point is required to create a bounding sphere.");
 333
 4334        Vector3d minX = points[0];
 4335        Vector3d maxX = points[0];
 4336        Vector3d minY = points[0];
 4337        Vector3d maxY = points[0];
 4338        Vector3d minZ = points[0];
 4339        Vector3d maxZ = points[0];
 340
 24341        for (int i = 1; i < points.Count; i++)
 342        {
 8343            Vector3d point = points[i];
 344
 9345            if (point.X < minX.X) minX = point;
 11346            if (point.X > maxX.X) maxX = point;
 9347            if (point.Y < minY.Y) minY = point;
 10348            if (point.Y > maxY.Y) maxY = point;
 9349            if (point.Z < minZ.Z) minZ = point;
 11350            if (point.Z > maxZ.Z) maxZ = point;
 351        }
 352
 4353        FixedBoundSphere sphere = CreateFromExtremePairs(minX, maxX, minY, maxY, minZ, maxZ);
 32354        for (int i = 0; i < points.Count; i++)
 12355            ExpandToContain(ref sphere, points[i]);
 356
 4357        return sphere;
 358    }
 359
 360    private static FixedBoundSphere CreateFromPointSpan(ReadOnlySpan<Vector3d> points)
 361    {
 14362        if (points.Length == 0)
 2363            throw new ArgumentException("At least one point is required to create a bounding sphere.");
 364
 12365        Vector3d minX = points[0];
 12366        Vector3d maxX = points[0];
 12367        Vector3d minY = points[0];
 12368        Vector3d maxY = points[0];
 12369        Vector3d minZ = points[0];
 12370        Vector3d maxZ = points[0];
 371
 80372        for (int i = 1; i < points.Length; i++)
 373        {
 28374            Vector3d point = points[i];
 375
 30376            if (point.X < minX.X) minX = point;
 40377            if (point.X > maxX.X) maxX = point;
 31378            if (point.Y < minY.Y) minY = point;
 37379            if (point.Y > maxY.Y) maxY = point;
 31380            if (point.Z < minZ.Z) minZ = point;
 30381            if (point.Z > maxZ.Z) maxZ = point;
 382        }
 383
 12384        FixedBoundSphere sphere = CreateFromExtremePairs(minX, maxX, minY, maxY, minZ, maxZ);
 98385        for (int i = 0; i < points.Length; i++)
 38386            ExpandToContain(ref sphere, points[i]);
 387
 11388        return sphere;
 389    }
 390
 391    private static FixedBoundSphere CreateFromFrustumCorners(FixedBoundFrustum frustum)
 392    {
 7393        Vector3d minX = frustum.GetCorner(0);
 7394        Vector3d maxX = minX;
 7395        Vector3d minY = minX;
 7396        Vector3d maxY = minX;
 7397        Vector3d minZ = minX;
 7398        Vector3d maxZ = minX;
 399
 112400        for (int i = 1; i < FixedBoundFrustum.CornerCount; i++)
 401        {
 49402            Vector3d point = frustum.GetCorner(i);
 403
 51404            if (point.X < minX.X) minX = point;
 56405            if (point.X > maxX.X) maxX = point;
 56406            if (point.Y < minY.Y) minY = point;
 51407            if (point.Y > maxY.Y) maxY = point;
 50408            if (point.Z < minZ.Z) minZ = point;
 58409            if (point.Z > maxZ.Z) maxZ = point;
 410        }
 411
 7412        FixedBoundSphere sphere = CreateFromExtremePairs(minX, maxX, minY, maxY, minZ, maxZ);
 126413        for (int i = 0; i < FixedBoundFrustum.CornerCount; i++)
 56414            ExpandToContain(ref sphere, frustum.GetCorner(i));
 415
 7416        return sphere;
 417    }
 418
 419    private static FixedBoundSphere CreateFromExtremePairs(
 420        Vector3d minX,
 421        Vector3d maxX,
 422        Vector3d minY,
 423        Vector3d maxY,
 424        Vector3d minZ,
 425        Vector3d maxZ)
 426    {
 23427        Vector3d min = minX;
 23428        Vector3d max = maxX;
 23429        if (Vector3d.CompareDistanceSquared(minY, maxY, min, max) > 0)
 430        {
 13431            min = minY;
 13432            max = maxY;
 433        }
 23434        if (Vector3d.CompareDistanceSquared(minZ, maxZ, min, max) > 0)
 435        {
 5436            min = minZ;
 5437            max = maxZ;
 438        }
 439
 23440        Vector3d center = Vector3d.Midpoint(min, max);
 23441        Vector3d farthest = Vector3d.CompareDistanceSquared(center, min, center, max) > 0
 23442            ? min
 23443            : max;
 23444        if (!TryGetRequiredRadius(center, farthest, Fixed64.Zero, out Fixed64 radius))
 445        {
 1446            throw CreateUnrepresentableRadiusException();
 447        }
 448
 22449        return new FixedBoundSphere(center, radius);
 450    }
 451
 452    private static void ExpandToContain(ref FixedBoundSphere sphere, Vector3d point)
 453    {
 106454        if (sphere.Contains(point))
 79455            return;
 456
 27457        sphere = MergeNonContaining(sphere, new FixedBoundSphere(point, Fixed64.Zero));
 27458    }
 459
 460    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 461    private static bool ContainsSphere(FixedBoundSphere outer, FixedBoundSphere inner)
 462    {
 94463        if (outer.Radius < inner.Radius)
 5464            return false;
 465
 89466        return WideGeometry.CompareDistanceToRadiusSum(
 89467            outer.Center,
 89468            inner.Center,
 89469            outer.Radius - inner.Radius,
 89470            Fixed64.Zero) <= 0;
 471    }
 472
 473    private static bool TryGetMergedRadius(
 474        Signed192 distanceFloor,
 475        Signed192 distanceRemainder,
 476        Fixed64 firstRadius,
 477        Fixed64 secondRadius,
 478        out Fixed64 radius)
 479    {
 36480        Signed192 sum = WideArithmetic.AddSigned192(
 36481            distanceFloor,
 36482            Signed192.Signed(firstRadius.m_rawValue));
 36483        sum = WideArithmetic.AddSigned192(
 36484            sum,
 36485            Signed192.Signed(secondRadius.m_rawValue));
 36486        bool roundUp = (sum.Low & 1UL) != 0UL || !distanceRemainder.IsZero;
 36487        WideArithmetic.GetMagnitude(sum, out ulong high, out ulong middle, out ulong low);
 36488        WideArithmetic.ShiftRightOne(ref high, ref middle, ref low);
 36489        if (roundUp)
 490        {
 30491            Signed192 rounded = WideArithmetic.AddSigned192(
 30492                new Signed192(high, middle, low),
 30493                Signed192.Signed(1L));
 30494            high = rounded.High;
 30495            middle = rounded.Middle;
 30496            low = rounded.Low;
 497        }
 498
 36499        return TryCreatePositiveRaw(high, middle, low, out radius);
 500    }
 501
 502    private static bool TryGetRequiredRadius(
 503        Vector3d center,
 504        Vector3d enclosedCenter,
 505        Fixed64 enclosedRadius,
 506        out Fixed64 radius)
 507    {
 67508        GetDistanceRoot(center, enclosedCenter, out Signed192 distanceFloor, out Signed192 distanceRemainder);
 67509        if (!distanceRemainder.IsZero)
 510        {
 55511            distanceFloor = WideArithmetic.AddSigned192(
 55512                distanceFloor,
 55513                Signed192.Signed(1L));
 514        }
 515
 67516        Signed192 required = WideArithmetic.AddSigned192(
 67517            distanceFloor,
 67518            Signed192.Signed(enclosedRadius.m_rawValue));
 67519        WideArithmetic.GetMagnitude(required, out ulong high, out ulong middle, out ulong low);
 67520        return TryCreatePositiveRaw(high, middle, low, out radius);
 521    }
 522
 523    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 524    private static void GetDistanceRoot(
 525        Vector3d first,
 526        Vector3d second,
 527        out Signed192 floor,
 528        out Signed192 remainder)
 529    {
 103530        Signed192 squaredDistance = WideGeometry.GetDifferenceDotProduct3D(
 103531            first.X, second.X, first.Y, second.Y, first.Z, second.Z,
 103532            first.X, second.X, first.Y, second.Y, first.Z, second.Z);
 103533        floor = WideArithmetic.GetFloorSquareRoot(
 103534            Signed320.ExtendValue(squaredDistance),
 103535            out remainder);
 103536    }
 537
 538    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 539    private static bool TryCreatePositiveRaw(
 540        ulong high,
 541        ulong middle,
 542        ulong low,
 543        out Fixed64 value)
 544    {
 103545        if ((high | middle | (low >> 63)) != 0UL)
 546        {
 4547            value = default;
 4548            return false;
 549        }
 550
 99551        value = Fixed64.FromRaw((long)low);
 99552        return true;
 553    }
 554
 555    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 556    private static Fixed64 GetFartherEndpoint(Fixed64 first, Fixed64 second, Fixed64 center)
 557    {
 12558        ulong firstDistance = GetRawDistance(first.m_rawValue, center.m_rawValue);
 12559        ulong secondDistance = GetRawDistance(second.m_rawValue, center.m_rawValue);
 12560        return firstDistance > secondDistance ? first : second;
 561    }
 562
 563    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 564    private static ulong GetRawDistance(long first, long second) =>
 24565        first >= second
 24566            ? unchecked((ulong)first - (ulong)second)
 24567            : unchecked((ulong)second - (ulong)first);
 568
 569    private static OverflowException CreateUnrepresentableRadiusException() =>
 4570        new("A containing sphere produced for the supplied geometry requires an unrepresentable radius.");
 571
 572    #endregion
 573
 574    #region Methods (Instance)
 575
 576    /// <summary>
 577    /// Checks if a point is inside the sphere.
 578    /// </summary>
 579    /// <param name="point">The point to check.</param>
 580    /// <returns>True if the point is inside the sphere, otherwise false.</returns>
 581    public bool Contains(Vector3d point)
 582    {
 227583        return WideGeometry.CompareDistanceToRadiusSum(Center, point, Radius, Fixed64.Zero) <= 0;
 584    }
 585
 586    /// <summary>
 587    /// Returns whether the point lies strictly inside this sphere.
 588    /// </summary>
 589    /// <remarks>
 590    /// Boundary points and every point tested against a zero-radius sphere
 591    /// return <see langword="false"/>.
 592    /// </remarks>
 593    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 594    public bool ContainsStrict(Vector3d point) =>
 3595        Radius > Fixed64.Zero
 3596        && WideGeometry.CompareDistanceToRadiusSum(Center, point, Radius, Fixed64.Zero) < 0;
 597
 598    /// <summary>
 599    /// Tests a bounding box against this sphere.
 600    /// </summary>
 601    public FixedEnclosureType Contains(FixedBoundBox box)
 602    {
 13603        return ContainsBoxLike(box.Min, box.Max);
 604    }
 605
 606    /// <summary>
 607    /// Tests another sphere against this sphere.
 608    /// </summary>
 609    public FixedEnclosureType Contains(FixedBoundSphere sphere)
 610    {
 19611        if (WideGeometry.CompareDistanceToRadiusSum(Center, sphere.Center, Radius, sphere.Radius) > 0)
 5612            return FixedEnclosureType.Disjoint;
 613
 14614        Fixed64 radiusDifference = Radius - sphere.Radius;
 14615        if (radiusDifference >= Fixed64.Zero
 14616            && WideGeometry.CompareDistanceToRadiusSum(
 14617                Center,
 14618                sphere.Center,
 14619                radiusDifference,
 14620                Fixed64.Zero) <= 0)
 7621            return FixedEnclosureType.Contains;
 622
 7623        return FixedEnclosureType.Intersects;
 624    }
 625
 626    /// <summary>
 627    /// Tests a frustum against this sphere.
 628    /// </summary>
 629    public FixedEnclosureType Contains(FixedBoundFrustum frustum)
 630    {
 3631        bool containsAllCorners = true;
 632
 22633        for (int i = 0; i < FixedBoundFrustum.CornerCount; i++)
 634        {
 10635            if (!Contains(frustum.GetCorner(i)))
 636            {
 2637                containsAllCorners = false;
 2638                break;
 639            }
 640        }
 641
 3642        if (containsAllCorners)
 1643            return FixedEnclosureType.Contains;
 644
 2645        return frustum.Intersects(this)
 2646            ? FixedEnclosureType.Intersects
 2647            : FixedEnclosureType.Disjoint;
 648    }
 649
 650    /// <summary>
 651    /// Checks whether a bounding box intersects this sphere, including boundary-only contact.
 652    /// </summary>
 653    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 6654    public bool Intersects(FixedBoundBox box) => Contains(box) != FixedEnclosureType.Disjoint;
 655
 656    /// <summary>
 657    /// Checks whether another sphere intersects this sphere, including boundary-only contact.
 658    /// </summary>
 659    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 6660    public bool Intersects(FixedBoundSphere sphere) => Contains(sphere) != FixedEnclosureType.Disjoint;
 661
 662    /// <summary>
 663    /// Checks whether a bounding box overlaps this sphere with positive volume.
 664    /// </summary>
 665    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3666    public bool IntersectsStrict(FixedBoundBox box) => box.IntersectsStrict(this);
 667
 668    /// <summary>
 669    /// Checks whether another sphere overlaps this sphere with positive volume.
 670    /// </summary>
 671    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 672    public bool IntersectsStrict(FixedBoundSphere sphere)
 673    {
 4674        return Radius > Fixed64.Zero
 4675            && sphere.Radius > Fixed64.Zero
 4676            && WideGeometry.CompareDistanceToRadiusSum(Center, sphere.Center, Radius, sphere.Radius) < 0;
 677    }
 678
 679    /// <summary>
 680    /// Checks whether a frustum intersects this sphere.
 681    /// </summary>
 682    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 683    public bool Intersects(FixedBoundFrustum frustum)
 684    {
 1685        return frustum.Intersects(this);
 686    }
 687
 688    /// <summary>
 689    /// Classifies this sphere relative to a plane.
 690    /// </summary>
 691    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1692    public FixedPlaneIntersectionType Intersects(FixedPlane plane) => plane.Intersects(this);
 693
 694    /// <summary>
 695    /// Finds the first forward ray intersection with this sphere.
 696    /// </summary>
 697    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1698    public Fixed64? Intersects(FixedRay ray) => ray.Intersects(this);
 699
 700    /// <summary>
 701    /// Projects a point onto the bounding sphere. If the point is outside the sphere, it returns the closest point on t
 702    /// </summary>
 703    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 704    public Vector3d ProjectPoint(Vector3d point)
 705    {
 3706        var direction = point - Center;
 4707        if (direction.IsZero) return Center; // If the point is the center, return the center itself
 708
 2709        return Center + direction.NormalizeInPlace() * Radius;
 710    }
 711
 712    /// <summary>
 713    /// Clamps a point to this sphere, returning the point unchanged when it is already inside the sphere.
 714    /// </summary>
 715    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 716    public Vector3d ClampPoint(Vector3d point)
 717    {
 3718        if (Contains(point))
 2719            return point;
 720
 1721        return ProjectPoint(point);
 722    }
 723
 724    /// <summary>
 725    /// Calculates the distance from a point to the surface of the sphere.
 726    /// </summary>
 727    /// <param name="point">The point to calculate the distance from.</param>
 728    /// <returns>The distance from the point to the surface of the sphere.</returns>
 729    public Fixed64 DistanceToSurface(Vector3d point)
 730    {
 4731        return Vector3d.Distance(Center, point) - Radius;
 732    }
 733
 734    /// <summary>
 735    /// Creates a sphere that contains this sphere transformed by the specified matrix.
 736    /// </summary>
 737    public FixedBoundSphere Transform(Fixed4x4 matrix)
 738    {
 1739        Vector3d center = Fixed4x4.TransformPoint(matrix, Center);
 1740        Fixed64 scale = GetMaxBasisScale(matrix);
 741
 1742        return new FixedBoundSphere(center, Radius * scale);
 743    }
 744
 745    /// <summary>
 746    /// Deconstructs this sphere into its center and radius.
 747    /// </summary>
 748    public void Deconstruct(out Vector3d center, out Fixed64 radius)
 749    {
 1750        center = Center;
 1751        radius = Radius;
 1752    }
 753
 754    private FixedEnclosureType ContainsBoxLike(Vector3d min, Vector3d max)
 755    {
 13756        bool containsAllCorners =
 13757            Contains(new Vector3d(min.X, min.Y, min.Z)) &&
 13758            Contains(new Vector3d(max.X, min.Y, min.Z)) &&
 13759            Contains(new Vector3d(min.X, max.Y, min.Z)) &&
 13760            Contains(new Vector3d(max.X, max.Y, min.Z)) &&
 13761            Contains(new Vector3d(min.X, min.Y, max.Z)) &&
 13762            Contains(new Vector3d(max.X, min.Y, max.Z)) &&
 13763            Contains(new Vector3d(min.X, max.Y, max.Z)) &&
 13764            Contains(new Vector3d(max.X, max.Y, max.Z));
 765
 13766        if (containsAllCorners)
 3767            return FixedEnclosureType.Contains;
 768
 10769        Vector3d closest = new(
 10770            FixedMath.Clamp(Center.X, min.X, max.X),
 10771            FixedMath.Clamp(Center.Y, min.Y, max.Y),
 10772            FixedMath.Clamp(Center.Z, min.Z, max.Z));
 773
 10774        return WideGeometry.CompareDistanceToRadiusSum(Center, closest, Radius, Fixed64.Zero) <= 0
 10775            ? FixedEnclosureType.Intersects
 10776            : FixedEnclosureType.Disjoint;
 777    }
 778
 779    private static Fixed64 GetMaxBasisScale(Fixed4x4 matrix)
 780    {
 1781        Fixed64 row0 = matrix.M11 * matrix.M11 + matrix.M12 * matrix.M12 + matrix.M13 * matrix.M13;
 1782        Fixed64 row1 = matrix.M21 * matrix.M21 + matrix.M22 * matrix.M22 + matrix.M23 * matrix.M23;
 1783        Fixed64 row2 = matrix.M31 * matrix.M31 + matrix.M32 * matrix.M32 + matrix.M33 * matrix.M33;
 784
 1785        return FixedMath.Sqrt(FixedMath.Max(row0, FixedMath.Max(row1, row2)));
 786    }
 787
 788    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 267789    private static Fixed64 NormalizeRadius(Fixed64 radius) => FixedMath.Abs(radius);
 790
 791    #endregion
 792
 793    #region Operators
 794
 795    /// <summary>
 796    /// Determines whether two FixedBoundSphere instances are equal.
 797    /// </summary>
 798    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2799    public static bool operator ==(FixedBoundSphere left, FixedBoundSphere right) => left.Equals(right);
 800
 801    /// <summary>
 802    /// Determines whether two FixedBoundSphere instances are not equal.
 803    /// </summary>
 804    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1805    public static bool operator !=(FixedBoundSphere left, FixedBoundSphere right) => !left.Equals(right);
 806
 807    #endregion
 808
 809    #region Equality and HashCode Overrides
 810
 811    /// <inheritdoc/>
 812    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2813    public override bool Equals(object? obj) => obj is FixedBoundSphere other && Equals(other);
 814
 815    /// <inheritdoc/>
 816    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 11817    public bool Equals(FixedBoundSphere other) => Center.Equals(other.Center) && Radius.Equals(other.Radius);
 818
 819    /// <inheritdoc/>
 820    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 821    public override int GetHashCode()
 822    {
 823        unchecked
 824        {
 2825            int hash = 17;
 2826            hash = hash * 23 + Center.GetHashCode();
 2827            hash = hash * 23 + Radius.GetHashCode();
 2828            return hash;
 829        }
 830    }
 831
 832    /// <summary>
 833    /// Returns a string that represents the current FixedBoundSphere.
 834    /// </summary>
 1835    public override string ToString() => ToString(null, CultureInfo.InvariantCulture);
 836
 837    /// <summary>
 838    /// Returns a string that represents the current FixedBoundSphere.
 839    /// </summary>
 840    public string ToString(string? format, IFormatProvider? formatProvider)
 841    {
 2842        FixedBoundSphere value = this;
 2843        return FixedDiagnosticsFormatter.ToString((Span<char> destination, out int charsWritten) =>
 2844            value.TryFormat(destination, out charsWritten, format.AsSpan(), formatProvider));
 845    }
 846
 847    /// <summary>
 848    /// Formats this sphere into the provided destination buffer.
 849    /// </summary>
 850    public bool TryFormat(
 851        Span<char> destination,
 852        out int charsWritten,
 853        ReadOnlySpan<char> format,
 854        IFormatProvider? provider)
 855    {
 44856        int written = 0;
 44857        if (!FixedDiagnosticsFormatter.Append("{Center:", destination, ref written) ||
 44858            !Center.TryFormat(destination[written..], out int centerChars, format, provider))
 859        {
 27860            charsWritten = 0;
 27861            return false;
 862        }
 863
 17864        written += centerChars;
 17865        if (!FixedDiagnosticsFormatter.Append(" Radius:", destination, ref written) ||
 17866            !FixedDiagnosticsFormatter.Append(Radius, destination, ref written, format, provider) ||
 17867            !FixedDiagnosticsFormatter.Append('}', destination, ref written))
 868        {
 14869            charsWritten = 0;
 14870            return false;
 871        }
 872
 3873        charsWritten = written;
 3874        return true;
 875    }
 876
 877    #endregion
 878}

Methods/Properties

.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Geometry.FixedBoundSphere/BoundingSphereState)
get_Radius()
set_Radius(FixedMathSharp.Fixed64)
get_Min()
get_Max()
get_State()
CreateFromBoundingBox(FixedMathSharp.Geometry.FixedBoundBox)
CreateFromFrustum(FixedMathSharp.Geometry.FixedBoundFrustum)
CreateFromPoints(System.Collections.Generic.IEnumerable`1<FixedMathSharp.Vector3d>)
CreateFromPoints(FixedMathSharp.Vector3d[])
CreateFromPoints(System.ReadOnlySpan`1<FixedMathSharp.Vector3d>)
CreateMerged(FixedMathSharp.Geometry.FixedBoundSphere,FixedMathSharp.Geometry.FixedBoundSphere)
MergeNonContaining(FixedMathSharp.Geometry.FixedBoundSphere,FixedMathSharp.Geometry.FixedBoundSphere)
CreateFromPointList(System.Collections.Generic.IReadOnlyList`1<FixedMathSharp.Vector3d>)
CreateFromPointSpan(System.ReadOnlySpan`1<FixedMathSharp.Vector3d>)
CreateFromFrustumCorners(FixedMathSharp.Geometry.FixedBoundFrustum)
CreateFromExtremePairs(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ExpandToContain(FixedMathSharp.Geometry.FixedBoundSphere&,FixedMathSharp.Vector3d)
ContainsSphere(FixedMathSharp.Geometry.FixedBoundSphere,FixedMathSharp.Geometry.FixedBoundSphere)
TryGetMergedRadius(FixedMathSharp.Signed192,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetRequiredRadius(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
GetDistanceRoot(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Signed192&,FixedMathSharp.Signed192&)
TryCreatePositiveRaw(System.UInt64,System.UInt64,System.UInt64,FixedMathSharp.Fixed64&)
GetFartherEndpoint(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetRawDistance(System.Int64,System.Int64)
CreateUnrepresentableRadiusException()
Contains(FixedMathSharp.Vector3d)
ContainsStrict(FixedMathSharp.Vector3d)
Contains(FixedMathSharp.Geometry.FixedBoundBox)
Contains(FixedMathSharp.Geometry.FixedBoundSphere)
Contains(FixedMathSharp.Geometry.FixedBoundFrustum)
Intersects(FixedMathSharp.Geometry.FixedBoundBox)
Intersects(FixedMathSharp.Geometry.FixedBoundSphere)
IntersectsStrict(FixedMathSharp.Geometry.FixedBoundBox)
IntersectsStrict(FixedMathSharp.Geometry.FixedBoundSphere)
Intersects(FixedMathSharp.Geometry.FixedBoundFrustum)
Intersects(FixedMathSharp.Geometry.FixedPlane)
Intersects(FixedMathSharp.Geometry.FixedRay)
ProjectPoint(FixedMathSharp.Vector3d)
ClampPoint(FixedMathSharp.Vector3d)
DistanceToSurface(FixedMathSharp.Vector3d)
Transform(FixedMathSharp.Fixed4x4)
Deconstruct(FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
ContainsBoxLike(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetMaxBasisScale(FixedMathSharp.Fixed4x4)
NormalizeRadius(FixedMathSharp.Fixed64)
op_Equality(FixedMathSharp.Geometry.FixedBoundSphere,FixedMathSharp.Geometry.FixedBoundSphere)
op_Inequality(FixedMathSharp.Geometry.FixedBoundSphere,FixedMathSharp.Geometry.FixedBoundSphere)
Equals(System.Object)
Equals(FixedMathSharp.Geometry.FixedBoundSphere)
GetHashCode()
ToString()
ToString(System.String,System.IFormatProvider)
TryFormat(System.Span`1<System.Char>,System.Int32&,System.ReadOnlySpan`1<System.Char>,System.IFormatProvider)