< Summary

Information
Class: FixedMathSharp.BoundingSphere
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Bounds/BoundingSphere.cs
Line coverage
96%
Covered lines: 208
Uncovered lines: 8
Coverable lines: 216
Total lines: 510
Line coverage: 96.2%
Branch coverage
100%
Covered branches: 86
Total branches: 86
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%
get_Min()100%11100%
get_Max()100%11100%
get_SqrRadius()100%11100%
CreateFromBoundingBox(...)100%11100%
CreateFromFrustum(...)100%22100%
CreateFromPoints(...)100%66100%
CreateMerged(...)100%44100%
CreateFromPointList(...)100%262698.18%
EnsureRadiusContainsPoint(...)100%44100%
Contains(...)100%11100%
Contains(...)100%11100%
Contains(...)100%11100%
Contains(...)100%66100%
Contains(...)100%1010100%
Intersects(...)100%11100%
Intersects(...)100%11100%
Intersects(...)100%11100%
Intersects(...)100%22100%
Intersects(...)100%11100%
Intersects(...)100%11100%
ProjectPoint(...)100%22100%
ClampPoint(...)100%22100%
DistanceToSurface(...)100%11100%
Transform(...)100%11100%
Deconstruct(...)100%210%
ContainsBoxLike(...)100%1818100%
GetMaxBasisScale(...)100%11100%
op_Equality(...)100%11100%
op_Inequality(...)100%11100%
Equals(...)100%22100%
Equals(...)100%22100%
GetHashCode()100%11100%
ToString()100%210%

File(s)

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

#LineLine coverage
 1using MemoryPack;
 2using System;
 3using System.Collections.Generic;
 4using System.Runtime.CompilerServices;
 5using System.Text.Json.Serialization;
 6
 7namespace FixedMathSharp
 8{
 9    /// <summary>
 10    /// Represents a spherical bounding volume with fixed-point precision, optimized for fast, rotationally invariant sp
 11    /// </summary>
 12    /// <remarks>
 13    /// The BoundingSphere provides a simple yet effective way to represent the spatial extent of objects, especially wh
 14    /// Compared to BoundingBox, it offers faster intersection checks but is less precise in tightly fitting non-spheric
 15    ///
 16    /// Use Cases:
 17    /// - Ideal for broad-phase collision detection, proximity checks, and culling in physics engines and rendering pipe
 18    /// - Useful when fast, rotationally invariant checks are needed, such as detecting overlaps or distances between mo
 19    /// - Suitable for encapsulating objects with roughly spherical shapes or objects that rotate frequently, where the 
 20    /// </remarks>
 21    [Serializable]
 22    [MemoryPackable]
 23    public partial struct BoundingSphere : IEquatable<BoundingSphere>
 24    {
 25        #region Fields
 26
 27        /// <summary>
 28        /// The center point of the sphere.
 29        /// </summary>
 30        [JsonInclude]
 31        [MemoryPackOrder(0)]
 32        public Vector3d Center;
 33
 34        /// <summary>
 35        /// The radius of the sphere.
 36        /// </summary>
 37        [JsonInclude]
 38        [MemoryPackOrder(1)]
 39        public Fixed64 Radius;
 40
 41        #endregion
 42
 43        #region Constructors
 44
 45        /// <summary>
 46        /// Initializes a new instance of the BoundingSphere struct with the specified center and radius.
 47        /// </summary>
 48        [JsonConstructor]
 49        public BoundingSphere(Vector3d center, Fixed64 radius)
 7750        {
 7751            Center = center;
 7752            Radius = radius;
 7753        }
 54
 55        #endregion
 56
 57        #region Properties
 58
 59        /// <summary>
 60        /// Gets the coordinates of the minimum corner of the bounding box that contains the sphere.
 61        /// </summary>
 62        /// <remarks>
 63        /// The minimum corner is calculated by subtracting the radius from each component of the sphere's center.
 64        /// This property is useful for spatial queries and bounding box calculations.
 65        /// </remarks>
 66        [JsonIgnore]
 67        [MemoryPackIgnore]
 68        public Vector3d Min
 69        {
 70            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 771            get => Center - new Vector3d(Radius, Radius, Radius);
 72        }
 73
 74        /// <summary>
 75        /// Gets the coordinates of the maximum corner of the bounding box that contains the sphere.
 76        /// </summary>
 77        /// <remarks>
 78        /// The maximum corner is calculated as the center of the sphere plus the radius in each dimension.
 79        /// This property is useful for spatial queries and bounding volume calculations.
 80        /// </remarks>
 81        [JsonIgnore]
 82        [MemoryPackIgnore]
 83        public Vector3d Max
 84        {
 85            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 586            get => Center + new Vector3d(Radius, Radius, Radius);
 87        }
 88
 89        /// <summary>
 90        /// The squared radius of the sphere.
 91        /// </summary>
 92        [JsonIgnore]
 93        [MemoryPackIgnore]
 94        public Fixed64 SqrRadius
 95        {
 96            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 8197            get => Radius * Radius;
 98        }
 99
 100        #endregion
 101
 102        #region Methods (Static)
 103
 104        /// <summary>
 105        /// Creates a bounding sphere that contains the specified axis-aligned bounding box.
 106        /// </summary>
 107        public static BoundingSphere CreateFromBoundingBox(BoundingBox box)
 1108        {
 1109            Vector3d center = (box.Min + box.Max) * Fixed64.Half;
 1110            Fixed64 radius = Vector3d.Distance(center, box.Max);
 111
 1112            return new BoundingSphere(center, radius);
 1113        }
 114
 115        /// <summary>
 116        /// Creates a bounding sphere that contains the specified frustum.
 117        /// </summary>
 118        public static BoundingSphere CreateFromFrustum(BoundingFrustum frustum)
 2119        {
 2120            if (frustum == null)
 1121                throw new ArgumentNullException(nameof(frustum));
 122
 1123            return CreateFromPoints(frustum.GetCorners());
 1124        }
 125
 126        /// <summary>
 127        /// Creates a bounding sphere that contains the specified points.
 128        /// </summary>
 129        public static BoundingSphere CreateFromPoints(IEnumerable<Vector3d> points)
 6130        {
 6131            if (points == null)
 1132                throw new ArgumentNullException(nameof(points));
 133
 5134            if (points is IReadOnlyList<Vector3d> pointList)
 4135                return CreateFromPointList(pointList);
 136
 1137            var materialized = new List<Vector3d>();
 11138            foreach (Vector3d point in points)
 4139                materialized.Add(point);
 140
 1141            return CreateFromPointList(materialized);
 4142        }
 143
 144        /// <summary>
 145        /// Creates the smallest sphere that contains the two specified spheres.
 146        /// </summary>
 147        public static BoundingSphere CreateMerged(BoundingSphere original, BoundingSphere additional)
 5148        {
 5149            Vector3d centerOffset = additional.Center - original.Center;
 5150            Fixed64 distance = centerOffset.Magnitude;
 151
 5152            if (distance + additional.Radius <= original.Radius)
 2153                return original;
 154
 3155            if (distance + original.Radius <= additional.Radius)
 2156                return additional;
 157
 1158            Fixed64 radius = (distance + original.Radius + additional.Radius) * Fixed64.Half;
 1159            Vector3d center = original.Center + centerOffset * ((radius - original.Radius) / distance);
 160
 1161            return new BoundingSphere(center, radius);
 5162        }
 163
 164        private static BoundingSphere CreateFromPointList(IReadOnlyList<Vector3d> points)
 5165        {
 5166            if (points.Count == 0)
 1167                throw new ArgumentException("At least one point is required.", nameof(points));
 168
 4169            Vector3d minX = points[0];
 4170            Vector3d maxX = points[0];
 4171            Vector3d minY = points[0];
 4172            Vector3d maxY = points[0];
 4173            Vector3d minZ = points[0];
 4174            Vector3d maxZ = points[0];
 175
 40176            for (int i = 1; i < points.Count; i++)
 16177            {
 16178                Vector3d point = points[i];
 179
 17180                if (point.x < minX.x) minX = point;
 20181                if (point.x > maxX.x) maxX = point;
 17182                if (point.y < minY.y) minY = point;
 19183                if (point.y > maxY.y) maxY = point;
 17184                if (point.z < minZ.z) minZ = point;
 18185                if (point.z > maxZ.z) maxZ = point;
 16186            }
 187
 4188            Fixed64 sqDistX = Vector3d.SqrDistance(maxX, minX);
 4189            Fixed64 sqDistY = Vector3d.SqrDistance(maxY, minY);
 4190            Fixed64 sqDistZ = Vector3d.SqrDistance(maxZ, minZ);
 191
 4192            Vector3d min = minX;
 4193            Vector3d max = maxX;
 4194            Fixed64 largestDistance = sqDistX;
 195
 4196            if (sqDistY > largestDistance)
 3197            {
 3198                min = minY;
 3199                max = maxY;
 3200                largestDistance = sqDistY;
 3201            }
 202
 4203            if (sqDistZ > largestDistance)
 1204            {
 1205                min = minZ;
 1206                max = maxZ;
 1207            }
 208
 4209            Vector3d center = (min + max) * Fixed64.Half;
 4210            Fixed64 radius = Vector3d.Distance(max, center);
 4211            Fixed64 sqRadius = radius * radius;
 212
 48213            for (int i = 0; i < points.Count; i++)
 20214            {
 20215                Vector3d diff = points[i] - center;
 20216                Fixed64 sqDistance = diff.SqrMagnitude;
 20217                if (sqDistance <= sqRadius)
 14218                    continue;
 219
 6220                Fixed64 distance = FixedMath.Sqrt(sqDistance);
 6221                if (distance == Fixed64.Zero)
 0222                    continue;
 223
 6224                Fixed64 newRadius = (radius + distance) * Fixed64.Half;
 6225                center += diff * ((distance - radius) / (Fixed64.Two * distance));
 6226                radius = newRadius;
 6227                sqRadius = EnsureRadiusContainsPoint(points[i], center, ref radius);
 6228            }
 229
 4230            return new BoundingSphere(center, radius);
 4231        }
 232
 233        private static Fixed64 EnsureRadiusContainsPoint(Vector3d point, Vector3d center, ref Fixed64 radius)
 6234        {
 6235            Fixed64 sqRadius = radius * radius;
 6236            Fixed64 sqDistance = Vector3d.SqrDistance(point, center);
 237
 6238            if (sqDistance <= sqRadius)
 1239                return sqRadius;
 240
 5241            radius = FixedMath.Sqrt(sqDistance);
 5242            sqRadius = radius * radius;
 243
 5244            if (sqRadius < sqDistance)
 3245            {
 3246                radius += Fixed64.MinIncrement;
 3247                sqRadius = radius * radius;
 3248            }
 249
 5250            return sqRadius;
 6251        }
 252
 253        #endregion
 254
 255        #region Methods (Instance)
 256
 257        /// <summary>
 258        /// Checks if a point is inside the sphere.
 259        /// </summary>
 260        /// <param name="point">The point to check.</param>
 261        /// <returns>True if the point is inside the sphere, otherwise false.</returns>
 262        public bool Contains(Vector3d point)
 63263        {
 63264            return Vector3d.SqrDistance(Center, point) <= SqrRadius;
 63265        }
 266
 267        /// <summary>
 268        /// Tests a bounding box against this sphere.
 269        /// </summary>
 270        public ContainmentType Contains(BoundingBox box)
 5271        {
 5272            return ContainsBoxLike(box.Min, box.Max);
 5273        }
 274
 275        /// <summary>
 276        /// Tests a bounding area against this sphere.
 277        /// </summary>
 278        public ContainmentType Contains(BoundingArea area)
 5279        {
 5280            return ContainsBoxLike(area.Min, area.Max);
 5281        }
 282
 283        /// <summary>
 284        /// Tests another sphere against this sphere.
 285        /// </summary>
 286        public ContainmentType Contains(BoundingSphere sphere)
 8287        {
 8288            Fixed64 sqDistance = Vector3d.SqrDistance(Center, sphere.Center);
 8289            Fixed64 combinedRadius = Radius + sphere.Radius;
 290
 8291            if (sqDistance > combinedRadius * combinedRadius)
 2292                return ContainmentType.Disjoint;
 293
 6294            Fixed64 radiusDifference = Radius - sphere.Radius;
 6295            if (radiusDifference >= Fixed64.Zero && sqDistance <= radiusDifference * radiusDifference)
 1296                return ContainmentType.Contains;
 297
 5298            return ContainmentType.Intersects;
 8299        }
 300
 301        /// <summary>
 302        /// Tests a frustum against this sphere.
 303        /// </summary>
 304        public ContainmentType Contains(BoundingFrustum frustum)
 4305        {
 4306            if (frustum == null)
 1307                throw new ArgumentNullException(nameof(frustum));
 308
 3309            Vector3d[] corners = frustum.GetCorners();
 3310            bool containsAllCorners = true;
 311
 22312            for (int i = 0; i < corners.Length; i++)
 10313            {
 10314                if (!Contains(corners[i]))
 2315                {
 2316                    containsAllCorners = false;
 2317                    break;
 318                }
 8319            }
 320
 3321            if (containsAllCorners)
 1322                return ContainmentType.Contains;
 323
 2324            return frustum.Intersects(this)
 2325                ? ContainmentType.Intersects
 2326                : ContainmentType.Disjoint;
 3327        }
 328
 329        /// <summary>
 330        /// Checks whether a bounding box intersects this sphere.
 331        /// </summary>
 332        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2333        public bool Intersects(BoundingBox box) => Contains(box) != ContainmentType.Disjoint;
 334
 335        /// <summary>
 336        /// Checks whether a bounding area intersects this sphere.
 337        /// </summary>
 338        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2339        public bool Intersects(BoundingArea area) => Contains(area) != ContainmentType.Disjoint;
 340
 341        /// <summary>
 342        /// Checks whether another sphere intersects this sphere.
 343        /// </summary>
 344        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 4345        public bool Intersects(BoundingSphere sphere) => Contains(sphere) != ContainmentType.Disjoint;
 346
 347        /// <summary>
 348        /// Checks whether a frustum intersects this sphere.
 349        /// </summary>
 350        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 351        public bool Intersects(BoundingFrustum frustum)
 2352        {
 2353            if (frustum == null)
 1354                throw new ArgumentNullException(nameof(frustum));
 355
 1356            return frustum.Intersects(this);
 1357        }
 358
 359        /// <summary>
 360        /// Classifies this sphere relative to a plane.
 361        /// </summary>
 362        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1363        public FixedPlaneIntersectionType Intersects(FixedPlane plane) => plane.Intersects(this);
 364
 365        /// <summary>
 366        /// Finds the first forward ray intersection with this sphere.
 367        /// </summary>
 368        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1369        public Fixed64? Intersects(FixedRay ray) => ray.Intersects(this);
 370
 371        /// <summary>
 372        /// Projects a point onto the bounding sphere. If the point is outside the sphere, it returns the closest point 
 373        /// </summary>
 374        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 375        public Vector3d ProjectPoint(Vector3d point)
 3376        {
 3377            var direction = point - Center;
 4378            if (direction.IsZero) return Center; // If the point is the center, return the center itself
 379
 2380            return Center + direction.Normalize() * Radius;
 3381        }
 382
 383        /// <summary>
 384        /// Clamps a point to this sphere, returning the point unchanged when it is already inside the sphere.
 385        /// </summary>
 386        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 387        public Vector3d ClampPoint(Vector3d point)
 3388        {
 3389            if (Contains(point))
 2390                return point;
 391
 1392            return ProjectPoint(point);
 3393        }
 394
 395        /// <summary>
 396        /// Calculates the distance from a point to the surface of the sphere.
 397        /// </summary>
 398        /// <param name="point">The point to calculate the distance from.</param>
 399        /// <returns>The distance from the point to the surface of the sphere.</returns>
 400        public Fixed64 DistanceToSurface(Vector3d point)
 4401        {
 4402            return Vector3d.Distance(Center, point) - Radius;
 4403        }
 404
 405        /// <summary>
 406        /// Creates a sphere that contains this sphere transformed by the specified matrix.
 407        /// </summary>
 408        public BoundingSphere Transform(Fixed4x4 matrix)
 1409        {
 1410            Vector3d center = Fixed4x4.TransformPoint(matrix, Center);
 1411            Fixed64 scale = GetMaxBasisScale(matrix);
 412
 1413            return new BoundingSphere(center, Radius * scale);
 1414        }
 415
 416        /// <summary>
 417        /// Deconstructs this sphere into its center and radius.
 418        /// </summary>
 419        public void Deconstruct(out Vector3d center, out Fixed64 radius)
 0420        {
 0421            center = Center;
 0422            radius = Radius;
 0423        }
 424
 425        private ContainmentType ContainsBoxLike(Vector3d min, Vector3d max)
 10426        {
 10427            bool containsAllCorners =
 10428                Contains(new Vector3d(min.x, min.y, min.z)) &&
 10429                Contains(new Vector3d(max.x, min.y, min.z)) &&
 10430                Contains(new Vector3d(min.x, max.y, min.z)) &&
 10431                Contains(new Vector3d(max.x, max.y, min.z)) &&
 10432                Contains(new Vector3d(min.x, min.y, max.z)) &&
 10433                Contains(new Vector3d(max.x, min.y, max.z)) &&
 10434                Contains(new Vector3d(min.x, max.y, max.z)) &&
 10435                Contains(new Vector3d(max.x, max.y, max.z));
 436
 10437            if (containsAllCorners)
 3438                return ContainmentType.Contains;
 439
 7440            Vector3d closest = new(
 7441                FixedMath.Clamp(Center.x, min.x, max.x),
 7442                FixedMath.Clamp(Center.y, min.y, max.y),
 7443                FixedMath.Clamp(Center.z, min.z, max.z));
 444
 7445            return Vector3d.SqrDistance(Center, closest) <= SqrRadius
 7446                ? ContainmentType.Intersects
 7447                : ContainmentType.Disjoint;
 10448        }
 449
 450        private static Fixed64 GetMaxBasisScale(Fixed4x4 matrix)
 1451        {
 1452            Fixed64 row0 = matrix.m00 * matrix.m00 + matrix.m01 * matrix.m01 + matrix.m02 * matrix.m02;
 1453            Fixed64 row1 = matrix.m10 * matrix.m10 + matrix.m11 * matrix.m11 + matrix.m12 * matrix.m12;
 1454            Fixed64 row2 = matrix.m20 * matrix.m20 + matrix.m21 * matrix.m21 + matrix.m22 * matrix.m22;
 455
 1456            return FixedMath.Sqrt(FixedMath.Max(row0, FixedMath.Max(row1, row2)));
 1457        }
 458
 459        #endregion
 460
 461        #region Operators
 462
 463        /// <summary>
 464        /// Determines whether two BoundingSphere instances are equal.
 465        /// </summary>
 466        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2467        public static bool operator ==(BoundingSphere left, BoundingSphere right) => left.Equals(right);
 468
 469        /// <summary>
 470        /// Determines whether two BoundingSphere instances are not equal.
 471        /// </summary>
 472        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1473        public static bool operator !=(BoundingSphere left, BoundingSphere right) => !left.Equals(right);
 474
 475        #endregion
 476
 477        #region Equality and HashCode Overrides
 478
 479        /// <inheritdoc/>
 480        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2481        public override bool Equals(object? obj) => obj is BoundingSphere other && Equals(other);
 482
 483        /// <inheritdoc/>
 484        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 10485        public bool Equals(BoundingSphere other) => Center.Equals(other.Center) && Radius.Equals(other.Radius);
 486
 487        /// <inheritdoc/>
 488        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 489        public override int GetHashCode()
 2490        {
 491            unchecked
 2492            {
 2493                int hash = 17;
 2494                hash = hash * 23 + Center.GetHashCode();
 2495                hash = hash * 23 + Radius.GetHashCode();
 2496                return hash;
 497            }
 2498        }
 499
 500        /// <summary>
 501        /// Returns a string that represents the current BoundingSphere.
 502        /// </summary>
 503        public override string ToString()
 0504        {
 0505            return $"{{Center:{Center} Radius:{Radius}}}";
 0506        }
 507
 508        #endregion
 509    }
 510}

Methods/Properties

.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
get_Min()
get_Max()
get_SqrRadius()
CreateFromBoundingBox(FixedMathSharp.BoundingBox)
CreateFromFrustum(FixedMathSharp.BoundingFrustum)
CreateFromPoints(System.Collections.Generic.IEnumerable`1<FixedMathSharp.Vector3d>)
CreateMerged(FixedMathSharp.BoundingSphere,FixedMathSharp.BoundingSphere)
CreateFromPointList(System.Collections.Generic.IReadOnlyList`1<FixedMathSharp.Vector3d>)
EnsureRadiusContainsPoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
Contains(FixedMathSharp.Vector3d)
Contains(FixedMathSharp.BoundingBox)
Contains(FixedMathSharp.BoundingArea)
Contains(FixedMathSharp.BoundingSphere)
Contains(FixedMathSharp.BoundingFrustum)
Intersects(FixedMathSharp.BoundingBox)
Intersects(FixedMathSharp.BoundingArea)
Intersects(FixedMathSharp.BoundingSphere)
Intersects(FixedMathSharp.BoundingFrustum)
Intersects(FixedMathSharp.FixedPlane)
Intersects(FixedMathSharp.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)
op_Equality(FixedMathSharp.BoundingSphere,FixedMathSharp.BoundingSphere)
op_Inequality(FixedMathSharp.BoundingSphere,FixedMathSharp.BoundingSphere)
Equals(System.Object)
Equals(FixedMathSharp.BoundingSphere)
GetHashCode()
ToString()