< Summary

Line coverage
100%
Covered lines: 938
Uncovered lines: 0
Coverable lines: 938
Total lines: 1900
Line coverage: 100%
Branch coverage
100%
Covered branches: 221
Total branches: 221
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .ctor(...)100%11100%
File 1: .ctor(...)100%11100%
File 1: get_Center()100%11100%
File 1: set_Center(...)100%22100%
File 1: get_Proportions()100%11100%
File 1: set_Proportions(...)100%11100%
File 1: get_Scope()100%11100%
File 1: get_State()100%11100%
File 1: set_State(...)100%11100%
File 1: FromMinMax(...)100%11100%
File 1: FromCenterAndSize(...)100%11100%
File 1: FromCenterAndScope(...)100%11100%
File 1: FromCenterAndSizeClippedToDomain(...)100%11100%
File 1: FromCenterAndScopeClippedToDomain(...)100%11100%
File 1: FromRelativeRotatedBoundsClippedToDomain(...)100%44100%
File 1: Orient(...)100%22100%
File 1: Resize(...)100%11100%
File 1: SetMinMax(...)100%11100%
File 1: SetBoundingBox(...)100%11100%
File 1: Contains(...)100%1010100%
File 1: Contains(...)100%11100%
File 1: Contains(...)100%88100%
File 1: Contains(...)100%66100%
File 1: Intersects(...)100%11100%
File 1: Intersects(...)100%11100%
File 1: IntersectsStrict(...)100%11100%
File 1: IntersectsStrict(...)100%11100%
File 1: Intersects(...)100%11100%
File 1: ProjectPoint(...)100%11100%
File 1: ClampPoint(...)100%11100%
File 1: ContainsBoxLike(...)100%66100%
File 1: IntersectsBoxLike(...)100%1010100%
File 1: IntersectsSphere(...)100%11100%
File 1: IntersectsSphereStrict(...)100%44100%
File 1: HasPositiveVolume()100%44100%
File 1: HasStrictAxisOverlap(...)100%1818100%
File 1: DistanceToSurface(...)100%22100%
File 1: GetPointOnSurfaceTowardsObject(...)100%11100%
File 1: ClosestPointOnSurface(...)100%1414100%
File 1: GetCorner(...)100%99100%
File 1: CopyCorners(...)100%22100%
File 1: GetVolumeExpansionCost(...)100%11100%
File 1: Union(...)100%11100%
File 1: FindClosestPointsBetweenBoxes(...)100%44100%
File 1: op_Equality(...)100%11100%
File 1: op_Inequality(...)100%11100%
File 1: Equals(...)100%22100%
File 1: Equals(...)100%22100%
File 1: GetHashCode()100%11100%
File 1: SetCenterAndHalfSize(...)100%44100%
File 1: SetCenterAndHalfSizeClippedToDomain(...)100%11100%
File 1: GetHalfSize(...)100%11100%
File 1: GetScopeMagnitude(...)100%11100%
File 2: FromFiniteConeClippedToDomain(...)100%44100%
File 2: FromCenteredCapsuleClippedToDomain(...)100%11100%
File 2: FromCenteredFiniteCylinderClippedToDomain(...)100%11100%
File 2: FromCenteredFiniteConeClippedToDomain(...)100%66100%
File 2: GetFiniteAxisLengthSquared(...)100%11100%
File 2: GetFiniteConeDiskExtent(...)100%22100%
File 2: GetFiniteDiskFloorExtent(...)100%88100%
File 2: GetFiniteDiskRepresentedSquare(...)100%11100%
File 2: GetFiniteDiskTarget(...)100%11100%
File 2: GetCenteredFiniteCylinderOutwardExtentRaw(...)100%22100%
File 2: GetPositiveCeilingRawRatio(...)100%22100%
File 2: IsCenteredFiniteCylinderExtentSufficient(...)100%11100%
File 2: FromCenteredFiniteCylinderExtentsClippedToDomain(...)100%11100%
File 2: GetCenteredFiniteCylinderBound(...)100%66100%
File 2: GetCenteredFiniteConeBound(...)100%1010100%
File 2: FromCenteredFiniteAxisClippedToDomain(...)100%11100%
File 2: ValidateCenteredFiniteAxis(...)100%88100%
File 3: FromCenteredCapsuleClippedToDomain(...)100%11100%
File 3: FromCenteredFiniteCylinderClippedToDomain(...)100%11100%
File 3: FromCenteredFiniteConeClippedToDomain(...)100%11100%
File 3: GetRigidAxis(...)100%11100%
File 3: GetRigidCapsuleBound(...)100%22100%
File 3: GetRigidCylinderOutwardExtentRaw(...)100%22100%
File 3: GetRigidPositiveCeilingRawRatio(...)100%22100%
File 3: GetRigidDiskFloorExtent(...)100%88100%
File 3: GetRigidDiskRepresentedSquare(...)100%11100%
File 3: IsRigidCylinderExtentSufficient(...)100%11100%
File 3: GetRigidConeBound(...)100%66100%
File 3: GetRigidConeBaseRimBound(...)100%66100%
File 3: GetRigidSignedRawRatio(...)100%88100%
File 3: IsRigidConeBaseRimBoundSufficient(...)100%22100%
File 3: GetRigidRationalBoundClippedToDomain(...)100%1212100%
File 3: GetMagnitude(...)100%22100%
File 3: ValidateRigidFiniteAxis(...)100%1010100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// FixedBoundBox.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.Runtime.CompilerServices;
 10using System.Text.Json.Serialization;
 11using MemoryPack;
 12
 13namespace FixedMathSharp.Geometry;
 14
 15/// <summary>
 16/// Represents a normalized three-dimensional axis-aligned bounding box.
 17/// </summary>
 18/// <remarks>
 19/// Use <see cref="FromMinMax"/>, <see cref="FromCenterAndSize"/>, or
 20/// <see cref="FromCenterAndScope"/> so construction intent is explicit at the call site.
 21/// </remarks>
 22[Serializable]
 23[MemoryPackable]
 24public partial struct FixedBoundBox : IEquatable<FixedBoundBox>
 25{
 26    /// <summary>
 27    /// The number of stable corners exposed by <see cref="GetCorner"/> and <see cref="CopyCorners"/>.
 28    /// </summary>
 29    public const int CornerCount = 8;
 30
 31    #region Nested Types
 32
 33    /// <summary>
 34    /// Represents the state of a three-dimensional axis-aligned bounding box using its minimum and maximum coordinates.
 35    /// </summary>
 36    /// <remarks>
 37    /// The bounding box is defined by two points: the minimum and maximum corners in 3D space. This
 38    /// structure is immutable and can be used to describe spatial boundaries for geometric computations, collision
 39    /// detection, or spatial queries.
 40    /// </remarks>
 41    [Serializable]
 42    [MemoryPackable]
 43    public readonly partial struct BoundingBoxState
 44    {
 45        /// <inheritdoc cref="FixedBoundBox.Min"/>
 46        [JsonInclude]
 47        [MemoryPackInclude]
 48        public readonly Vector3d Min;
 49
 50        /// <inheritdoc cref="FixedBoundBox.Max"/>
 51        [JsonInclude]
 52        [MemoryPackInclude]
 53        public readonly Vector3d Max;
 54
 55        /// <summary>
 56        /// Initializes a new instance of the BoundingBoxState class with the specified minimum and maximum coordinates.
 57        /// </summary>
 58        [JsonConstructor]
 59        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 60        public BoundingBoxState(Vector3d min, Vector3d max)
 61        {
 762            Min = Vector3d.Min(min, max);
 763            Max = Vector3d.Max(min, max);
 764        }
 65    }
 66
 67    #endregion
 68
 69    #region Constructors
 70
 71    /// <summary>
 72    /// Initializes a new instance of the FixedBoundBox class with the specified bounding box state.
 73    /// </summary>
 74    /// <param name="state">The state that defines the position, size, and orientation of the bounding box.</param>
 75    [JsonConstructor]
 76    public FixedBoundBox(BoundingBoxState state)
 77    {
 478        State = state;
 479    }
 80
 81    #endregion
 82
 83    #region Properties
 84
 85    /// <summary>
 86    /// The minimum corner of the bounding box.
 87    /// </summary>
 88    [JsonIgnore]
 89    [MemoryPackIgnore]
 90    public Vector3d Min { get; private set; }
 91
 92    /// <summary>
 93    /// The maximum corner of the bounding box.
 94    /// </summary>
 95    [JsonIgnore]
 96    [MemoryPackIgnore]
 97    public Vector3d Max { get; private set; }
 98
 99    /// <summary>
 100    /// The center of the bounding box, rounded to the nearest-even Q32.32 lattice point.
 101    /// </summary>
 102    /// <remarks>
 103    /// Assigning a different center preserves a conservative half-extent. An
 104    /// odd raw-unit span can therefore expand by one raw unit so the assigned
 105    /// center remains exact and the previous box is not under-represented.
 106    /// </remarks>
 107    /// <exception cref="OverflowException">
 108    /// An assigned center would place an endpoint outside the scalar domain.
 109    /// </exception>
 110    [JsonIgnore]
 111    [MemoryPackIgnore]
 112    public Vector3d Center
 113    {
 114        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 27115        get => Vector3d.Midpoint(Min, Max);
 116
 117        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118        set
 119        {
 6120            if (value != Center)
 5121                SetCenterAndHalfSize(value, Scope);
 4122        }
 123    }
 124
 125    /// <summary>
 126    /// The exact total size of the box.
 127    /// </summary>
 128    /// <remarks>
 129    /// Assigned values are normalized by absolute component value and divided
 130    /// outward. An odd raw-unit size therefore expands by one raw unit. Reading
 131    /// this property throws rather than returning a saturated value when an
 132    /// exact component span is not representable by <see cref="Fixed64"/>.
 133    /// </remarks>
 134    /// <exception cref="OverflowException">
 135    /// A component span is not representable, or an assigned size would place
 136    /// an endpoint outside the scalar domain.
 137    /// </exception>
 138    [JsonIgnore]
 139    [MemoryPackIgnore]
 140    public Vector3d Proportions
 141    {
 142        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 16143        get => new(
 16144            WideGeometry.GetIntervalSize(Min.X, Max.X),
 16145            WideGeometry.GetIntervalSize(Min.Y, Max.Y),
 16146            WideGeometry.GetIntervalSize(Min.Z, Max.Z));
 147
 148        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 149        set
 150        {
 1151            SetCenterAndHalfSize(Center, GetHalfSize(value));
 1152        }
 153    }
 154
 155    /// <summary>
 156    /// The smallest representable half-extent that conservatively contains the
 157    /// box around <see cref="Center"/>.
 158    /// </summary>
 159    /// <exception cref="OverflowException">
 160    /// A conservative half-extent is outside the representable scalar domain.
 161    /// </exception>
 162    [JsonIgnore]
 163    [MemoryPackIgnore]
 164    public Vector3d Scope
 165    {
 166        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 11167        get => new(
 11168            WideGeometry.GetIntervalScope(Min.X, Max.X),
 11169            WideGeometry.GetIntervalScope(Min.Y, Max.Y),
 11170            WideGeometry.GetIntervalScope(Min.Z, Max.Z));
 171    }
 172
 173    /// <summary>
 174    /// Gets or sets the current normalized bounding box state, including its minimum and maximum coordinates.
 175    /// </summary>
 176    [JsonInclude]
 177    [MemoryPackInclude]
 178    public BoundingBoxState State
 179    {
 3180        get => new(Min, Max);
 181
 182        internal set
 183        {
 4184            SetMinMax(value.Min, value.Max);
 4185        }
 186    }
 187
 188    #endregion
 189
 190    #region Factories
 191
 192    /// <summary>
 193    /// Creates a normalized bounding box from minimum and maximum corners.
 194    /// </summary>
 195    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 196    public static FixedBoundBox FromMinMax(Vector3d min, Vector3d max)
 197    {
 966198        var box = default(FixedBoundBox);
 966199        box.SetMinMax(min, max);
 966200        return box;
 201    }
 202
 203    /// <summary>
 204    /// Creates a bounding box from a center point and total size.
 205    /// </summary>
 206    /// <remarks>
 207    /// Negative size components are normalized by absolute value. Odd raw-unit
 208    /// sizes are divided outward and therefore expand by one raw unit.
 209    /// </remarks>
 210    /// <exception cref="OverflowException">
 211    /// The centered box would place an endpoint outside the scalar domain.
 212    /// </exception>
 213    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 214    public static FixedBoundBox FromCenterAndSize(Vector3d center, Vector3d size)
 215    {
 128216        var box = default(FixedBoundBox);
 128217        box.SetCenterAndHalfSize(center, GetHalfSize(size));
 126218        return box;
 219    }
 220
 221    /// <summary>
 222    /// Creates a bounding box from a center point and half-size scope.
 223    /// </summary>
 224    /// <remarks>
 225    /// Negative scope components are normalized by absolute value.
 226    /// </remarks>
 227    /// <exception cref="OverflowException">
 228    /// A scope magnitude is not representable, or the centered box would place
 229    /// an endpoint outside the scalar domain.
 230    /// </exception>
 231    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 232    public static FixedBoundBox FromCenterAndScope(Vector3d center, Vector3d scope)
 233    {
 8234        var box = default(FixedBoundBox);
 8235        box.SetCenterAndHalfSize(center, GetScopeMagnitude(scope));
 7236        return box;
 237    }
 238
 239    /// <summary>
 240    /// Creates the representable-domain intersection of a box described by a
 241    /// center point and total size.
 242    /// </summary>
 243    /// <remarks>
 244    /// Negative size components are normalized by absolute value and odd raw-
 245    /// unit sizes divide outward. Endpoints outside the scalar domain are
 246    /// explicitly clipped to <see cref="Fixed64.MinValue"/> or
 247    /// <see cref="Fixed64.MaxValue"/>.
 248    /// </remarks>
 249    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 250    public static FixedBoundBox FromCenterAndSizeClippedToDomain(Vector3d center, Vector3d size)
 251    {
 1252        var box = default(FixedBoundBox);
 1253        box.SetCenterAndHalfSizeClippedToDomain(center, GetHalfSize(size));
 1254        return box;
 255    }
 256
 257    /// <summary>
 258    /// Creates the representable-domain intersection of a box described by a
 259    /// center point and half-size scope.
 260    /// </summary>
 261    /// <remarks>
 262    /// Negative scope components are normalized by absolute value. Endpoints
 263    /// outside the scalar domain are explicitly clipped to
 264    /// <see cref="Fixed64.MinValue"/> or <see cref="Fixed64.MaxValue"/>.
 265    /// </remarks>
 266    /// <exception cref="OverflowException">
 267    /// A scope magnitude is not representable.
 268    /// </exception>
 269    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 270    public static FixedBoundBox FromCenterAndScopeClippedToDomain(Vector3d center, Vector3d scope)
 271    {
 1272        var box = default(FixedBoundBox);
 1273        box.SetCenterAndHalfSizeClippedToDomain(center, GetScopeMagnitude(scope));
 1274        return box;
 275    }
 276
 277    /// <summary>
 278    /// Creates conservative target-frame bounds for a rotated source-frame
 279    /// box, clipping only final target-frame endpoints to the scalar domain.
 280    /// </summary>
 281    /// <remarks>
 282    /// Conceptually transforms every source local point as
 283    /// <c>targetRotation^-1 * (sourceOrigin + sourceRotation * point - targetOrigin)</c>.
 284    /// Source endpoints are normalized component-wise before the exact
 285    /// projection. Intermediate world points are never materialized.
 286    /// </remarks>
 287    public static FixedBoundBox FromRelativeRotatedBoundsClippedToDomain(
 288        Vector3d sourceOrigin,
 289        FixedQuaternion sourceRotation,
 290        Vector3d sourceLocalMin,
 291        Vector3d sourceLocalMax,
 292        Vector3d targetOrigin,
 293        FixedQuaternion targetRotation)
 294    {
 72295        if (!sourceRotation.IsNormalized())
 296        {
 1297            throw new ArgumentException(
 1298                "Source rotation must be normalized.",
 1299                nameof(sourceRotation));
 300        }
 71301        if (!targetRotation.IsNormalized())
 302        {
 1303            throw new ArgumentException(
 1304                "Target rotation must be normalized.",
 1305                nameof(targetRotation));
 306        }
 307
 70308        return WideOrientedBox.GetRelativeRotatedBoundsClippedToDomain(
 70309            sourceOrigin,
 70310            sourceRotation,
 70311            sourceLocalMin,
 70312            sourceLocalMax,
 70313            targetOrigin,
 70314            targetRotation);
 315    }
 316
 317    #endregion
 318
 319    #region Mutators
 320
 321    /// <summary>
 322    /// Orients the bounding box with the given center and size.
 323    /// </summary>
 324    /// <exception cref="OverflowException">
 325    /// The requested bounds would place an endpoint outside the scalar domain.
 326    /// </exception>
 327    public void Orient(Vector3d center, Vector3d? size)
 328    {
 3329        if (size.HasValue)
 330        {
 1331            SetCenterAndHalfSize(center, GetHalfSize(size.Value));
 1332            return;
 333        }
 334
 2335        Center = center;
 1336    }
 337
 338    /// <summary>
 339    /// Resizes the bounding box to the specified size, keeping the same center.
 340    /// </summary>
 341    /// <exception cref="OverflowException">
 342    /// The requested size would place an endpoint outside the scalar domain.
 343    /// </exception>
 344    public void Resize(Vector3d size)
 345    {
 4346        SetCenterAndHalfSize(Center, GetHalfSize(size));
 3347    }
 348
 349    /// <summary>
 350    /// Sets the normalized bounds of the bounding box by specifying its minimum and maximum points.
 351    /// </summary>
 352    public void SetMinMax(Vector3d min, Vector3d max)
 353    {
 972354        Min = Vector3d.Min(min, max);
 972355        Max = Vector3d.Max(min, max);
 972356    }
 357
 358    /// <summary>
 359    /// Configures the bounding box with the specified center and scope (half-size).
 360    /// </summary>
 361    /// <remarks>
 362    /// Negative scope components are normalized by absolute value.
 363    /// </remarks>
 364    /// <exception cref="OverflowException">
 365    /// A scope magnitude is not representable, or the requested bounds would
 366    /// place an endpoint outside the scalar domain.
 367    /// </exception>
 368    public void SetBoundingBox(Vector3d center, Vector3d scope)
 369    {
 3370        SetCenterAndHalfSize(center, GetScopeMagnitude(scope));
 3371    }
 372
 373    /// <summary>
 374    /// Determines if a point is inside the bounding box (including boundaries).
 375    /// </summary>
 376    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 377    public bool Contains(Vector3d point)
 378    {
 58379        return point.X >= Min.X && point.X <= Max.X
 58380            && point.Y >= Min.Y && point.Y <= Max.Y
 58381            && point.Z >= Min.Z && point.Z <= Max.Z;
 382    }
 383
 384    /// <summary>
 385    /// Tests another bounding box against this bounding box.
 386    /// </summary>
 387    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 6388    public FixedEnclosureType Contains(FixedBoundBox box) => ContainsBoxLike(box.Min, box.Max);
 389
 390    /// <summary>
 391    /// Tests a bounding sphere against this bounding box.
 392    /// </summary>
 393    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 394    public FixedEnclosureType Contains(FixedBoundSphere sphere)
 395    {
 4396        if (WideGeometry.ContainsCenteredExtent(Min.X, Max.X, sphere.Center.X, sphere.Radius)
 4397            && WideGeometry.ContainsCenteredExtent(Min.Y, Max.Y, sphere.Center.Y, sphere.Radius)
 4398            && WideGeometry.ContainsCenteredExtent(Min.Z, Max.Z, sphere.Center.Z, sphere.Radius))
 1399            return FixedEnclosureType.Contains;
 400
 3401        return Intersects(sphere) ? FixedEnclosureType.Intersects : FixedEnclosureType.Disjoint;
 402    }
 403
 404    /// <summary>
 405    /// Tests a bounding frustum against this bounding box.
 406    /// </summary>
 407    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 408    public FixedEnclosureType Contains(FixedBoundFrustum frustum)
 409    {
 3410        if (Contains(frustum.Min) && Contains(frustum.Max))
 1411            return FixedEnclosureType.Contains;
 412
 2413        return Intersects(frustum) ? FixedEnclosureType.Intersects : FixedEnclosureType.Disjoint;
 414    }
 415
 416    /// <summary>
 417    /// Checks whether another bounding box intersects this bounding box, including boundary-only contact.
 418    /// </summary>
 419    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 19420    public bool Intersects(FixedBoundBox box) => IntersectsBoxLike(box.Min, box.Max);
 421
 422    /// <summary>
 423    /// Checks whether a bounding sphere intersects this bounding box, including boundary-only contact.
 424    /// </summary>
 425    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 21426    public bool Intersects(FixedBoundSphere sphere) => IntersectsSphere(sphere);
 427
 428    /// <summary>
 429    /// Checks whether another bounding box overlaps this bounding box with positive volume on every axis.
 430    /// </summary>
 431    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 12432    public bool IntersectsStrict(FixedBoundBox box) => HasStrictAxisOverlap(box.Min, box.Max);
 433
 434    /// <summary>
 435    /// Checks whether a bounding sphere overlaps this bounding box with positive volume.
 436    /// </summary>
 437    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 6438    public bool IntersectsStrict(FixedBoundSphere sphere) => IntersectsSphereStrict(sphere);
 439
 440    /// <summary>
 441    /// Checks whether a bounding frustum intersects this bounding box.
 442    /// </summary>
 443    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 444    public bool Intersects(FixedBoundFrustum frustum)
 445    {
 4446        return frustum.Intersects(this);
 447    }
 448
 449    /// <summary>
 450    /// Projects a point into the bounding box by clamping it to the box extents.
 451    /// </summary>
 452    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 453    public Vector3d ProjectPoint(Vector3d point)
 2454        => ClampPoint(point);
 455
 456    /// <summary>
 457    /// Clamps a point to this bounding box, returning the point unchanged when it is already inside.
 458    /// </summary>
 459    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 460    public Vector3d ClampPoint(Vector3d point)
 461    {
 28462        return new Vector3d(
 28463            FixedMath.Clamp(point.X, Min.X, Max.X),
 28464            FixedMath.Clamp(point.Y, Min.Y, Max.Y),
 28465            FixedMath.Clamp(point.Z, Min.Z, Max.Z));
 466    }
 467
 468    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 469    private FixedEnclosureType ContainsBoxLike(Vector3d otherMin, Vector3d otherMax)
 470    {
 6471        if (Contains(otherMin) && Contains(otherMax))
 2472            return FixedEnclosureType.Contains;
 473
 4474        return IntersectsBoxLike(otherMin, otherMax)
 4475            ? FixedEnclosureType.Intersects
 4476            : FixedEnclosureType.Disjoint;
 477    }
 478
 479    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 480    private bool IntersectsBoxLike(Vector3d otherMin, Vector3d otherMax)
 481    {
 23482        return Min.X <= otherMax.X && Max.X >= otherMin.X
 23483            && Min.Y <= otherMax.Y && Max.Y >= otherMin.Y
 23484            && Min.Z <= otherMax.Z && Max.Z >= otherMin.Z;
 485    }
 486
 487    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 488    private bool IntersectsSphere(FixedBoundSphere sphere)
 489    {
 21490        return WideGeometry.CompareDistanceToRadiusSum(
 21491            sphere.Center,
 21492            ClampPoint(sphere.Center),
 21493            sphere.Radius,
 21494            Fixed64.Zero) <= 0;
 495    }
 496
 497    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 498    private bool IntersectsSphereStrict(FixedBoundSphere sphere)
 499    {
 6500        return HasPositiveVolume()
 6501            && sphere.Radius > Fixed64.Zero
 6502            && WideGeometry.CompareDistanceToRadiusSum(
 6503                sphere.Center,
 6504                ClampPoint(sphere.Center),
 6505                sphere.Radius,
 6506                Fixed64.Zero) < 0;
 507    }
 508
 509    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 510    private bool HasPositiveVolume()
 511    {
 18512        return Min.X < Max.X && Min.Y < Max.Y && Min.Z < Max.Z;
 513    }
 514
 515    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 516    private bool HasStrictAxisOverlap(Vector3d otherMin, Vector3d otherMax)
 517    {
 12518        return HasPositiveVolume()
 12519            && otherMin.X < otherMax.X
 12520            && otherMin.Y < otherMax.Y
 12521            && otherMin.Z < otherMax.Z
 12522            && Min.X < otherMax.X && Max.X > otherMin.X
 12523            && Min.Y < otherMax.Y && Max.Y > otherMin.Y
 12524            && Min.Z < otherMax.Z && Max.Z > otherMin.Z;
 525    }
 526
 527    /// <summary>
 528    /// Calculates the shortest distance from a given point to the surface of the bounding box.
 529    /// If the point lies inside the box, the distance is zero.
 530    /// </summary>
 531    /// <param name="point">The point from which to calculate the distance.</param>
 532    /// <returns>
 533    /// The shortest distance from the point to the surface of the bounding box.
 534    /// If the point is inside the box, the method returns zero.
 535    /// </returns>
 536    /// <remarks>
 537    /// The method finds the closest point on the box's surface by clamping the given point
 538    /// to the box's bounds and returns the Euclidean distance between them.
 539    /// This ensures accurate distance calculations, even near corners or edges.
 540    /// </remarks>
 541    public Fixed64 DistanceToSurface(Vector3d point)
 542    {
 543        // Clamp the point to the nearest point on the box's surface
 2544        Vector3d clampedPoint = new(
 2545            FixedMath.Clamp(point.X, Min.X, Max.X),
 2546            FixedMath.Clamp(point.Y, Min.Y, Max.Y),
 2547            FixedMath.Clamp(point.Z, Min.Z, Max.Z)
 2548        );
 549
 550        // If the point is inside the box, return 0
 2551        if (Contains(point))
 1552            return Fixed64.Zero;
 553
 554        // Otherwise, return the Euclidean distance to the clamped point
 1555        return Vector3d.Distance(point, clampedPoint);
 556    }
 557
 558    /// <summary>
 559    /// Finds the closest point on the surface of the bounding box towards a specified object position.
 560    /// </summary>
 561    public Vector3d GetPointOnSurfaceTowardsObject(Vector3d objectPosition)
 1562        => ClosestPointOnSurface(ProjectPoint(objectPosition));
 563
 564    /// <summary>
 565    /// Finds the closest point on the surface of the bounding box to the specified point.
 566    /// </summary>
 567    public Vector3d ClosestPointOnSurface(Vector3d point)
 568    {
 16569        if (Contains(point))
 570        {
 571            // Calculate distances to each face and return the closest face.
 7572            Fixed64 distToMinX = point.X - Min.X;
 7573            Fixed64 distToMaxX = Max.X - point.X;
 7574            Fixed64 distToMinY = point.Y - Min.Y;
 7575            Fixed64 distToMaxY = Max.Y - point.Y;
 7576            Fixed64 distToMinZ = point.Z - Min.Z;
 7577            Fixed64 distToMaxZ = Max.Z - point.Z;
 578
 7579            Fixed64 minDistToFace = FixedMath.Min(distToMinX,
 7580                FixedMath.Min(distToMaxX,
 7581                FixedMath.Min(distToMinY,
 7582                FixedMath.Min(distToMaxY,
 7583                FixedMath.Min(distToMinZ, distToMaxZ)))));
 584
 585            // Adjust the closest point based on the face.
 8586            if (minDistToFace == distToMinX) point.X = Min.X;
 8587            else if (minDistToFace == distToMaxX) point.X = Max.X;
 588
 8589            if (minDistToFace == distToMinY) point.Y = Min.Y;
 7590            else if (minDistToFace == distToMaxY) point.Y = Max.Y;
 591
 8592            if (minDistToFace == distToMinZ) point.Z = Min.Z;
 7593            else if (minDistToFace == distToMaxZ) point.Z = Max.Z;
 594
 7595            return point;
 596        }
 597
 598        // If the point is outside the box, clamp to the nearest surface.
 9599        return new Vector3d(
 9600            FixedMath.Clamp(point.X, Min.X, Max.X),
 9601            FixedMath.Clamp(point.Y, Min.Y, Max.Y),
 9602            FixedMath.Clamp(point.Z, Min.Z, Max.Z)
 9603        );
 604    }
 605
 606    /// <summary>
 607    /// Gets a stable corner without allocating.
 608    /// </summary>
 609    /// <remarks>
 610    /// Corner order is: min/min/min, max/min/min, min/max/min, max/max/min,
 611    /// min/min/max, max/min/max, min/max/max, max/max/max.
 612    /// </remarks>
 613    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 614    public Vector3d GetCorner(int index)
 615    {
 173616        return index switch
 173617        {
 24618            0 => new Vector3d(Min.X, Min.Y, Min.Z),
 21619            1 => new Vector3d(Max.X, Min.Y, Min.Z),
 21620            2 => new Vector3d(Min.X, Max.Y, Min.Z),
 21621            3 => new Vector3d(Max.X, Max.Y, Min.Z),
 21622            4 => new Vector3d(Min.X, Min.Y, Max.Z),
 21623            5 => new Vector3d(Max.X, Min.Y, Max.Z),
 21624            6 => new Vector3d(Min.X, Max.Y, Max.Z),
 21625            7 => new Vector3d(Max.X, Max.Y, Max.Z),
 2626            _ => throw new ArgumentOutOfRangeException(nameof(index), $"Corner index must be between 0 and {CornerCount 
 173627        };
 628    }
 629
 630    /// <summary>
 631    /// Copies this box's corners into the destination span in <see cref="GetCorner"/> order.
 632    /// </summary>
 633    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 634    public void CopyCorners(Span<Vector3d> destination)
 635    {
 131636        if (destination.Length < CornerCount)
 1637            throw new ArgumentException($"The destination span must contain at least {CornerCount} elements.", nameof(de
 638
 130639        destination[0] = new Vector3d(Min.X, Min.Y, Min.Z);
 130640        destination[1] = new Vector3d(Max.X, Min.Y, Min.Z);
 130641        destination[2] = new Vector3d(Min.X, Max.Y, Min.Z);
 130642        destination[3] = new Vector3d(Max.X, Max.Y, Min.Z);
 130643        destination[4] = new Vector3d(Min.X, Min.Y, Max.Z);
 130644        destination[5] = new Vector3d(Max.X, Min.Y, Max.Z);
 130645        destination[6] = new Vector3d(Min.X, Max.Y, Max.Z);
 130646        destination[7] = new Vector3d(Max.X, Max.Y, Max.Z);
 130647    }
 648
 649    /// <summary>
 650    /// Gets the exact additional volume required for this box's union with
 651    /// <paramref name="other"/>, floored to integer world units and clamped to
 652    /// <see cref="long.MaxValue"/>.
 653    /// </summary>
 654    /// <remarks>
 655    /// The three endpoint spans and both volumes remain in exact unsigned
 656    /// 192-bit arithmetic. This metric is suitable for spatial-index insertion
 657    /// heuristics even when either box has an unrepresentable
 658    /// <see cref="Proportions"/> component.
 659    /// </remarks>
 660    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 661    public long GetVolumeExpansionCost(FixedBoundBox other) =>
 5662        WideGeometry.GetVolumeExpansionCost(Min, Max, other.Min, other.Max);
 663
 664    #endregion
 665
 666    #region Static Ops
 667
 668    /// <summary>
 669    /// Creates a new bounding box that is the union of two bounding boxes.
 670    /// </summary>
 671    public static FixedBoundBox Union(FixedBoundBox a, FixedBoundBox b)
 672    {
 1673        return FromMinMax(Vector3d.Min(a.Min, b.Min), Vector3d.Max(a.Max, b.Max));
 674    }
 675
 676    /// <summary>
 677    /// Finds the closest points between two bounding boxes.
 678    /// </summary>
 679    public static Vector3d FindClosestPointsBetweenBoxes(FixedBoundBox a, FixedBoundBox b)
 680    {
 1681        Vector3d closestPoint = Vector3d.Zero;
 1682        Fixed64 minDistance = Fixed64.MaxValue;
 683
 18684        for (int i = 0; i < CornerCount; i++)
 685        {
 8686            Vector3d corner = b.GetCorner(i);
 8687            Vector3d point = a.ClosestPointOnSurface(corner);
 8688            Fixed64 distance = Vector3d.Distance(point, corner);
 8689            if (distance < minDistance)
 690            {
 1691                closestPoint = point;
 1692                minDistance = distance;
 693            }
 694        }
 695
 1696        return closestPoint;
 697    }
 698
 699    #endregion
 700
 701    #region Equality
 702
 703    /// <summary>
 704    /// Determines whether two FixedBoundBox instances are equal.
 705    /// </summary>
 2706    public static bool operator ==(FixedBoundBox left, FixedBoundBox right) => left.Equals(right);
 707
 708    /// <summary>
 709    /// Determines whether two FixedBoundBox instances are not equal.
 710    /// </summary>
 1711    public static bool operator !=(FixedBoundBox left, FixedBoundBox right) => !left.Equals(right);
 712
 713    #endregion
 714
 715    #region Equality and HashCode Overrides
 716
 717    /// <inheritdoc/>
 2718    public override bool Equals(object? obj) => obj is FixedBoundBox other && Equals(other);
 719
 720    /// <inheritdoc/>
 721    public bool Equals(FixedBoundBox other)
 44722        => Min.Equals(other.Min) && Max.Equals(other.Max);
 723
 724    /// <inheritdoc/>
 725    public override int GetHashCode()
 726    {
 727        unchecked
 728        {
 513729            int hash = 17;
 513730            hash = (hash * 31) + Min.StateHash;
 513731            hash = (hash * 31) + Max.StateHash;
 513732            return hash;
 733        }
 734    }
 735
 736    #endregion
 737
 738    #region Helpers
 739
 740    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 741    private void SetCenterAndHalfSize(Vector3d center, Vector3d halfSize)
 742    {
 149743        if (!Vector3d.TrySubtract(center, halfSize, out Vector3d min)
 149744            || !Vector3d.TryAdd(center, halfSize, out Vector3d max))
 745        {
 5746            throw new OverflowException("The centered box places at least one endpoint outside the representable Fixed64
 747        }
 748
 144749        Min = min;
 144750        Max = max;
 144751    }
 752
 753    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 754    private void SetCenterAndHalfSizeClippedToDomain(Vector3d center, Vector3d halfSize)
 755    {
 2756        Vector3d min = center - halfSize;
 2757        Vector3d max = center + halfSize;
 2758        Min = min;
 2759        Max = max;
 2760    }
 761
 762    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 135763    private static Vector3d GetHalfSize(Vector3d size) => new(
 135764        WideGeometry.GetHalfSizeMagnitude(size.X),
 135765        WideGeometry.GetHalfSizeMagnitude(size.Y),
 135766        WideGeometry.GetHalfSizeMagnitude(size.Z));
 767
 768    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 12769    private static Vector3d GetScopeMagnitude(Vector3d scope) => new(
 12770        WideGeometry.GetExtentMagnitude(scope.X),
 12771        WideGeometry.GetExtentMagnitude(scope.Y),
 12772        WideGeometry.GetExtentMagnitude(scope.Z));
 773
 774    #endregion
 775}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Bounds/FixedBoundBox.FiniteAxis.cs

#LineLine coverage
 1//=======================================================================
 2// FixedBoundBox.FiniteAxis.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;
 9
 10namespace FixedMathSharp.Geometry;
 11
 12/// <content>
 13/// Contains methods for creating a representable-domain intersection of the tight axis-aligned bounds of finite
 14/// cones, cylinders, and capsules from their center-axis lengths and directions.
 15/// </content>
 16public partial struct FixedBoundBox
 17{
 18    /// <summary>
 19    /// Creates the representable-domain intersection of the tight axis-aligned
 20    /// bounds of a finite cone with a flat circular base.
 21    /// </summary>
 22    /// <param name="apex">The cone apex.</param>
 23    /// <param name="baseCenter">The center of the cone's flat base.</param>
 24    /// <param name="axisDirection">The normalized direction from apex to base.</param>
 25    /// <param name="baseRadius">The nonnegative base radius.</param>
 26    /// <remarks>
 27    /// Base-disk extents account for the exact squared length of a representably
 28    /// normalized axis. They are rounded outward so broad-phase bounds cannot
 29    /// omit a boundary point. Coordinates beyond the scalar domain are clipped.
 30    /// </remarks>
 31    /// <exception cref="ArgumentException">
 32    /// <paramref name="axisDirection"/> is zero or not normalized.
 33    /// </exception>
 34    /// <exception cref="ArgumentOutOfRangeException">
 35    /// <paramref name="baseRadius"/> is negative.
 36    /// </exception>
 37    public static FixedBoundBox FromFiniteConeClippedToDomain(
 38        Vector3d apex,
 39        Vector3d baseCenter,
 40        Vector3d axisDirection,
 41        Fixed64 baseRadius)
 42    {
 743        if (!axisDirection.IsNormalized())
 144            throw new ArgumentException("Finite cone axis direction must be normalized.", nameof(axisDirection));
 645        if (baseRadius < Fixed64.Zero)
 146            throw new ArgumentOutOfRangeException(nameof(baseRadius));
 47
 548        Signed192 axisLengthSquared = GetFiniteAxisLengthSquared(axisDirection);
 549        Vector3d baseExtents = new(
 550            GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.X, baseRadius),
 551            GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Y, baseRadius),
 552            GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Z, baseRadius));
 553        return FromMinMax(
 554            Vector3d.Min(apex, baseCenter - baseExtents),
 555            Vector3d.Max(apex, baseCenter + baseExtents));
 56    }
 57
 58    /// <summary>
 59    /// Creates the representable-domain intersection of a centered capsule's
 60    /// tight axis-aligned bounds from its full center-axis length.
 61    /// </summary>
 62    public static FixedBoundBox FromCenteredCapsuleClippedToDomain(
 63        Vector3d center,
 64        Vector3d axisDirection,
 65        Fixed64 axisLength,
 66        Fixed64 radius)
 67    {
 14268        ValidateCenteredFiniteAxis(axisDirection, axisLength, radius, requirePositiveLength: false);
 14069        return FromCenteredFiniteAxisClippedToDomain(
 14070            center,
 14071            axisDirection,
 14072            axisLength,
 14073            new Vector3d(radius, radius, radius));
 74    }
 75
 76    /// <summary>
 77    /// Creates the representable-domain intersection of a centered finite
 78    /// cylinder's tight axis-aligned bounds from its full axis length.
 79    /// </summary>
 80    public static FixedBoundBox FromCenteredFiniteCylinderClippedToDomain(
 81        Vector3d center,
 82        Vector3d axisDirection,
 83        Fixed64 axisLength,
 84        Fixed64 radius)
 85    {
 13686        ValidateCenteredFiniteAxis(axisDirection, axisLength, radius, requirePositiveLength: true);
 13487        Signed192 axisLengthSquared = GetFiniteAxisLengthSquared(axisDirection);
 13488        return FromCenteredFiniteCylinderExtentsClippedToDomain(
 13489            center,
 13490            GetCenteredFiniteCylinderOutwardExtentRaw(
 13491                axisLengthSquared, axisDirection.X, axisLength, radius),
 13492            GetCenteredFiniteCylinderOutwardExtentRaw(
 13493                axisLengthSquared, axisDirection.Y, axisLength, radius),
 13494            GetCenteredFiniteCylinderOutwardExtentRaw(
 13495                axisLengthSquared, axisDirection.Z, axisLength, radius));
 96    }
 97
 98    /// <summary>
 99    /// Creates the representable-domain intersection of a centered finite
 100    /// cone's tight axis-aligned bounds from its full height.
 101    /// </summary>
 102    /// <param name="center">The midpoint between the base center and apex.</param>
 103    /// <param name="axisDirection">
 104    /// The normalized direction from the base center toward the apex.
 105    /// </param>
 106    /// <param name="height">The positive full base-to-apex height.</param>
 107    /// <param name="radius">The nonnegative base radius.</param>
 108    public static FixedBoundBox FromCenteredFiniteConeClippedToDomain(
 109        Vector3d center,
 110        Vector3d axisDirection,
 111        Fixed64 height,
 112        Fixed64 radius)
 113    {
 7114        if (!axisDirection.IsNormalized())
 1115            throw new ArgumentException("Finite cone axis direction must be normalized.", nameof(axisDirection));
 6116        if (height <= Fixed64.Zero)
 1117            throw new ArgumentOutOfRangeException(nameof(height));
 5118        if (radius < Fixed64.Zero)
 1119            throw new ArgumentOutOfRangeException(nameof(radius));
 120
 4121        Signed192 axisLengthSquared = GetFiniteAxisLengthSquared(axisDirection);
 4122        return FromMinMax(
 4123            new Vector3d(
 4124                GetCenteredFiniteConeBound(
 4125                    center.X,
 4126                    axisDirection.X,
 4127                    height,
 4128                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.X, radius),
 4129                    minimum: true),
 4130                GetCenteredFiniteConeBound(
 4131                    center.Y,
 4132                    axisDirection.Y,
 4133                    height,
 4134                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Y, radius),
 4135                    minimum: true),
 4136                GetCenteredFiniteConeBound(
 4137                    center.Z,
 4138                    axisDirection.Z,
 4139                    height,
 4140                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Z, radius),
 4141                    minimum: true)),
 4142            new Vector3d(
 4143                GetCenteredFiniteConeBound(
 4144                    center.X,
 4145                    axisDirection.X,
 4146                    height,
 4147                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.X, radius),
 4148                    minimum: false),
 4149                GetCenteredFiniteConeBound(
 4150                    center.Y,
 4151                    axisDirection.Y,
 4152                    height,
 4153                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Y, radius),
 4154                    minimum: false),
 4155                GetCenteredFiniteConeBound(
 4156                    center.Z,
 4157                    axisDirection.Z,
 4158                    height,
 4159                    GetFiniteConeDiskExtent(axisLengthSquared, axisDirection.Z, radius),
 4160                    minimum: false)));
 161    }
 162
 163    private static Signed192 GetFiniteAxisLengthSquared(Vector3d axisDirection) =>
 143164        WideGeometry.GetDifferenceDotProduct3D(
 143165            axisDirection.X, Fixed64.Zero,
 143166            axisDirection.Y, Fixed64.Zero,
 143167            axisDirection.Z, Fixed64.Zero,
 143168            axisDirection.X, Fixed64.Zero,
 143169            axisDirection.Y, Fixed64.Zero,
 143170            axisDirection.Z, Fixed64.Zero);
 171
 172    private static Fixed64 GetFiniteConeDiskExtent(
 173        Signed192 axisLengthSquared,
 174        Fixed64 axisComponent,
 175        Fixed64 radius)
 176    {
 39177        Fixed64 floor = GetFiniteDiskFloorExtent(
 39178            axisLengthSquared,
 39179            axisComponent,
 39180            radius,
 39181            out Signed192 capacity,
 39182            out Signed320 radiusSquared);
 39183        Signed576 represented = GetFiniteDiskRepresentedSquare(
 39184            floor,
 39185            axisLengthSquared);
 39186        Signed576 target = GetFiniteDiskTarget(radiusSquared, capacity);
 39187        return WideArithmetic.CompareNonNegative(represented, target) == 0
 39188            ? floor
 39189            : Fixed64.FromRaw(floor.m_rawValue + 1L);
 190    }
 191
 192    private static Fixed64 GetFiniteDiskFloorExtent(
 193        Signed192 axisLengthSquared,
 194        Fixed64 axisComponent,
 195        Fixed64 radius,
 196        out Signed192 capacity,
 197        out Signed320 radiusSquared)
 198    {
 441199        Signed192 componentSquared = WideGeometry.GetDifferenceDotProduct3D(
 441200            axisComponent, Fixed64.Zero,
 441201            Fixed64.Zero, Fixed64.Zero,
 441202            Fixed64.Zero, Fixed64.Zero,
 441203            axisComponent, Fixed64.Zero,
 441204            Fixed64.Zero, Fixed64.Zero,
 441205            Fixed64.Zero, Fixed64.Zero);
 441206        capacity = WideArithmetic.SubtractSigned192(
 441207            axisLengthSquared,
 441208            componentSquared);
 441209        Signed192 radiusRaw = Signed192.Signed(radius.m_rawValue);
 441210        radiusSquared = WideArithmetic.MultiplySigned192(radiusRaw, radiusRaw);
 441211        if (capacity.IsZero || radius == Fixed64.Zero)
 31212            return Fixed64.Zero;
 410213        if (WideArithmetic.CompareMagnitude(capacity, axisLengthSquared) == 0)
 142214            return radius;
 215
 268216        Signed320 capacityTimesAxisLengthSquared = WideArithmetic.MultiplySigned192(
 268217            capacity,
 268218            axisLengthSquared);
 268219        Signed576 radicand = WideArithmetic.MultiplySigned320(
 268220            radiusSquared,
 268221            capacityTimesAxisLengthSquared);
 268222        Signed320 scaledRoot = WideArithmetic.GetFloorSquareRootScaledByFixed64(radicand);
 268223        Signed320 scaledAxisLengthSquared = WideArithmetic.MultiplySigned192(
 268224            axisLengthSquared,
 268225            Signed192.Signed(FixedMath.ONE_L));
 268226        _ = Fixed64.TryGetSignedRawRatio(
 268227            Signed576.ExtendValue(scaledRoot),
 268228            Signed576.ExtendValue(scaledAxisLengthSquared),
 268229            out Fixed64 candidate);
 230
 268231        Signed576 represented = GetFiniteDiskRepresentedSquare(
 268232            candidate,
 268233            axisLengthSquared);
 268234        Signed576 target = GetFiniteDiskTarget(radiusSquared, capacity);
 268235        return WideArithmetic.CompareNonNegative(represented, target) <= 0
 268236            ? candidate
 268237            : Fixed64.FromRaw(candidate.m_rawValue - 1L);
 238    }
 239
 240    private static Signed576 GetFiniteDiskRepresentedSquare(
 241        Fixed64 extent,
 242        Signed192 axisLengthSquared)
 243    {
 307244        Signed192 extentRaw = Signed192.Signed(extent.m_rawValue);
 307245        return WideArithmetic.MultiplySigned576(
 307246            Signed576.ExtendValue(
 307247                WideArithmetic.MultiplySigned192(extentRaw, extentRaw)),
 307248            axisLengthSquared);
 249    }
 250
 251    private static Signed576 GetFiniteDiskTarget(
 252        Signed320 radiusSquared,
 253        Signed192 capacity) =>
 307254        WideArithmetic.MultiplySigned576(
 307255            Signed576.ExtendValue(radiusSquared),
 307256            capacity);
 257
 258    private static Signed192 GetCenteredFiniteCylinderOutwardExtentRaw(
 259        Signed192 axisLengthSquared,
 260        Fixed64 axisComponent,
 261        Fixed64 axisLength,
 262        Fixed64 radius)
 263    {
 402264        Signed320 axialNumerator = WideArithmetic.MultiplySigned192(
 402265            Signed192.Signed(axisComponent.Abs().m_rawValue),
 402266            Signed192.Signed(axisLength.m_rawValue));
 402267        Fixed64 axialCeiling = GetPositiveCeilingRawRatio(
 402268            axialNumerator,
 402269            WideGeometry.CenteredAxisDenominator);
 402270        Fixed64 diskFloor = GetFiniteDiskFloorExtent(
 402271            axisLengthSquared,
 402272            axisComponent,
 402273            radius,
 402274            out Signed192 capacity,
 402275            out Signed320 radiusSquared);
 402276        Signed192 candidate = WideArithmetic.AddSigned192(
 402277            Signed192.Signed(axialCeiling.m_rawValue),
 402278            Signed192.Signed(diskFloor.m_rawValue));
 279
 280        // floor(disk) + ceil(axis) is at most one raw unit below the
 281        // combined ceiling. Decide that final carry from the exact radical.
 402282        if (!IsCenteredFiniteCylinderExtentSufficient(
 402283                candidate,
 402284                axialNumerator,
 402285                axisLengthSquared,
 402286                capacity,
 402287                radiusSquared))
 288        {
 2289            candidate = WideArithmetic.AddSigned192(
 2290                candidate,
 2291                Signed192.Signed(1L));
 292        }
 293
 402294        return candidate;
 295    }
 296
 297    private static Fixed64 GetPositiveCeilingRawRatio(
 298        Signed320 numerator,
 299        Signed192 denominator)
 300    {
 402301        Fixed64 candidate = Fixed64.GetSignedRawRatio(numerator, denominator);
 402302        Signed320 represented = WideArithmetic.MultiplySigned192(
 402303            Signed192.Signed(candidate.m_rawValue),
 402304            denominator);
 402305        return WideArithmetic.SubtractSigned320(represented, numerator).Sign >= 0
 402306            ? candidate
 402307            : Fixed64.FromRaw(candidate.m_rawValue + 1L);
 308    }
 309
 310    private static bool IsCenteredFiniteCylinderExtentSufficient(
 311        Signed192 candidate,
 312        Signed320 axialNumerator,
 313        Signed192 axisLengthSquared,
 314        Signed192 diskCapacity,
 315        Signed320 radiusSquared)
 316    {
 402317        Signed320 remainder = WideArithmetic.SubtractSigned320(
 402318            WideArithmetic.MultiplySigned192(
 402319                candidate,
 402320                WideGeometry.CenteredAxisDenominator),
 402321            axialNumerator);
 402322        Signed576 left = WideArithmetic.MultiplySigned576(
 402323            WideArithmetic.MultiplySigned320(remainder, remainder),
 402324            axisLengthSquared);
 402325        Signed320 denominatorSquared = WideArithmetic.MultiplySigned192(
 402326            WideGeometry.CenteredAxisDenominator,
 402327            WideGeometry.CenteredAxisDenominator);
 402328        Signed576 right = WideArithmetic.MultiplySigned576(
 402329            WideArithmetic.MultiplySigned320(denominatorSquared, radiusSquared),
 402330            diskCapacity);
 402331        return WideArithmetic.CompareNonNegative(left, right) >= 0;
 332    }
 333
 334    private static FixedBoundBox FromCenteredFiniteCylinderExtentsClippedToDomain(
 335        Vector3d center,
 336        Signed192 xExtent,
 337        Signed192 yExtent,
 338        Signed192 zExtent) =>
 137339        FromMinMax(
 137340            new Vector3d(
 137341                GetCenteredFiniteCylinderBound(center.X, xExtent, minimum: true),
 137342                GetCenteredFiniteCylinderBound(center.Y, yExtent, minimum: true),
 137343                GetCenteredFiniteCylinderBound(center.Z, zExtent, minimum: true)),
 137344            new Vector3d(
 137345                GetCenteredFiniteCylinderBound(center.X, xExtent, minimum: false),
 137346                GetCenteredFiniteCylinderBound(center.Y, yExtent, minimum: false),
 137347                GetCenteredFiniteCylinderBound(center.Z, zExtent, minimum: false)));
 348
 349    private static Fixed64 GetCenteredFiniteCylinderBound(
 350        Fixed64 center,
 351        Signed192 outwardExtent,
 352        bool minimum)
 353    {
 822354        Signed192 centerRaw = Signed192.Signed(center.m_rawValue);
 822355        Signed192 boundRaw = minimum
 822356            ? WideArithmetic.SubtractSigned192(centerRaw, outwardExtent)
 822357            : WideArithmetic.AddSigned192(centerRaw, outwardExtent);
 822358        if (Fixed64.TryGetSignedRawRatio(
 822359                Signed576.ExtendValue(
 822360                    Signed320.ExtendValue(boundRaw)),
 822361                Signed576.ExtendValue(
 822362                    Signed320.ExtendValue(
 822363                        Signed192.Signed(1L))),
 822364                out Fixed64 bound))
 365        {
 818366            return bound;
 367        }
 368
 4369        return boundRaw.Sign < 0 ? Fixed64.MinValue : Fixed64.MaxValue;
 370    }
 371
 372    private static Fixed64 GetCenteredFiniteConeBound(
 373        Fixed64 center,
 374        Fixed64 axisComponent,
 375        Fixed64 height,
 376        Fixed64 diskExtent,
 377        bool minimum)
 378    {
 24379        Signed320 centerNumerator = WideArithmetic.MultiplySigned192(
 24380            Signed192.Signed(center.m_rawValue),
 24381            WideGeometry.CenteredAxisDenominator);
 24382        Signed320 axialNumerator = WideArithmetic.MultiplySigned192(
 24383            Signed192.Signed(axisComponent.m_rawValue),
 24384            Signed192.Signed(height.m_rawValue));
 24385        Signed320 apexNumerator = WideArithmetic.AddSigned320(
 24386            centerNumerator,
 24387            axialNumerator);
 24388        Signed320 baseNumerator = WideArithmetic.SubtractSigned320(
 24389            centerNumerator,
 24390            axialNumerator);
 24391        Signed320 radialNumerator = WideArithmetic.MultiplySigned192(
 24392            Signed192.Signed(diskExtent.m_rawValue),
 24393            WideGeometry.CenteredAxisDenominator);
 24394        Signed320 baseRimNumerator = minimum
 24395            ? WideArithmetic.SubtractSigned320(baseNumerator, radialNumerator)
 24396            : WideArithmetic.AddSigned320(baseNumerator, radialNumerator);
 24397        Signed320 boundNumerator =
 24398            WideArithmetic.SubtractSigned320(apexNumerator, baseRimNumerator).Sign switch
 24399            {
 5400                < 0 when minimum => apexNumerator,
 11401                > 0 when !minimum => apexNumerator,
 20402                _ => baseRimNumerator,
 24403            };
 24404        return WideGeometry.GetRationalBoundClippedToDomain(
 24405            boundNumerator,
 24406            WideGeometry.CenteredAxisDenominator,
 24407            minimum);
 408    }
 409
 410    private static FixedBoundBox FromCenteredFiniteAxisClippedToDomain(
 411        Vector3d center,
 412        Vector3d axisDirection,
 413        Fixed64 axisLength,
 414        Vector3d radialExtents) =>
 140415        FromMinMax(
 140416            new Vector3d(
 140417                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140418                    center.X, axisDirection.X, axisLength, radialExtents.X, minimum: true),
 140419                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140420                    center.Y, axisDirection.Y, axisLength, radialExtents.Y, minimum: true),
 140421                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140422                    center.Z, axisDirection.Z, axisLength, radialExtents.Z, minimum: true)),
 140423            new Vector3d(
 140424                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140425                    center.X, axisDirection.X, axisLength, radialExtents.X, minimum: false),
 140426                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140427                    center.Y, axisDirection.Y, axisLength, radialExtents.Y, minimum: false),
 140428                WideGeometry.GetCenteredFiniteAxisBoundClippedToDomain(
 140429                    center.Z, axisDirection.Z, axisLength, radialExtents.Z, minimum: false)));
 430
 431    private static void ValidateCenteredFiniteAxis(
 432        Vector3d axisDirection,
 433        Fixed64 axisLength,
 434        Fixed64 radius,
 435        bool requirePositiveLength)
 436    {
 278437        if (!axisDirection.IsNormalized())
 1438            throw new ArgumentException("Finite-axis direction must be normalized.", nameof(axisDirection));
 277439        if (requirePositiveLength ? axisLength <= Fixed64.Zero : axisLength < Fixed64.Zero)
 2440            throw new ArgumentOutOfRangeException(nameof(axisLength));
 275441        if (radius < Fixed64.Zero)
 1442            throw new ArgumentOutOfRangeException(nameof(radius));
 274443    }
 444}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Bounds/FixedBoundBox.RigidFiniteAxis.cs

#LineLine coverage
 1//=======================================================================
 2// FixedBoundBox.RigidFiniteAxis.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;
 9
 10namespace FixedMathSharp.Geometry;
 11
 12/// <content>
 13/// Provides high-precision, allocation-free bounding box computations for
 14/// rigidly-rotated finite-axis shapes (e.g. capsules) by working directly
 15/// with extended-precision axis components instead of materializing the
 16/// rotated axis vector.
 17/// </content>
 18public partial struct FixedBoundBox
 19{
 20    /// <summary>
 21    /// Creates the representable-domain intersection of a centered capsule's
 22    /// tight axis-aligned bounds without materializing its rotated axis.
 23    /// </summary>
 24    public static FixedBoundBox FromCenteredCapsuleClippedToDomain(
 25        Vector3d center,
 26        FixedQuaternion rotation,
 27        Vector3d localAxisDirection,
 28        Fixed64 axisLength,
 29        Fixed64 radius)
 30    {
 731        ValidateRigidFiniteAxis(
 732            rotation,
 733            localAxisDirection,
 734            axisLength,
 735            radius,
 736            requirePositiveLength: false);
 337        GetRigidAxis(
 338            rotation,
 339            localAxisDirection,
 340            out Signed192 axisX,
 341            out Signed192 axisY,
 342            out Signed192 axisZ,
 343            out _,
 344            out Signed192 centeredDenominator);
 345        return FromMinMax(
 346            new Vector3d(
 347                GetRigidCapsuleBound(
 348                    center.X,
 349                    axisX,
 350                    axisLength,
 351                    radius,
 352                    centeredDenominator,
 353                    minimum: true),
 354                GetRigidCapsuleBound(
 355                    center.Y,
 356                    axisY,
 357                    axisLength,
 358                    radius,
 359                    centeredDenominator,
 360                    minimum: true),
 361                GetRigidCapsuleBound(
 362                    center.Z,
 363                    axisZ,
 364                    axisLength,
 365                    radius,
 366                    centeredDenominator,
 367                    minimum: true)),
 368            new Vector3d(
 369                GetRigidCapsuleBound(
 370                    center.X,
 371                    axisX,
 372                    axisLength,
 373                    radius,
 374                    centeredDenominator,
 375                    minimum: false),
 376                GetRigidCapsuleBound(
 377                    center.Y,
 378                    axisY,
 379                    axisLength,
 380                    radius,
 381                    centeredDenominator,
 382                    minimum: false),
 383                GetRigidCapsuleBound(
 384                    center.Z,
 385                    axisZ,
 386                    axisLength,
 387                    radius,
 388                    centeredDenominator,
 389                    minimum: false)));
 90    }
 91
 92    /// <summary>
 93    /// Creates the representable-domain intersection of a centered finite
 94    /// cylinder's tight axis-aligned bounds without materializing its rotated
 95    /// axis.
 96    /// </summary>
 97    public static FixedBoundBox FromCenteredFiniteCylinderClippedToDomain(
 98        Vector3d center,
 99        FixedQuaternion rotation,
 100        Vector3d localAxisDirection,
 101        Fixed64 axisLength,
 102        Fixed64 radius)
 103    {
 4104        ValidateRigidFiniteAxis(
 4105            rotation,
 4106            localAxisDirection,
 4107            axisLength,
 4108            radius,
 4109            requirePositiveLength: true);
 3110        GetRigidAxis(
 3111            rotation,
 3112            localAxisDirection,
 3113            out Signed192 axisX,
 3114            out Signed192 axisY,
 3115            out Signed192 axisZ,
 3116            out Signed320 axisSquared,
 3117            out Signed192 centeredDenominator);
 3118        return FromCenteredFiniteCylinderExtentsClippedToDomain(
 3119            center,
 3120            GetRigidCylinderOutwardExtentRaw(
 3121                axisSquared,
 3122                axisX,
 3123                axisLength,
 3124                radius,
 3125                centeredDenominator),
 3126            GetRigidCylinderOutwardExtentRaw(
 3127                axisSquared,
 3128                axisY,
 3129                axisLength,
 3130                radius,
 3131                centeredDenominator),
 3132            GetRigidCylinderOutwardExtentRaw(
 3133                axisSquared,
 3134                axisZ,
 3135                axisLength,
 3136                radius,
 3137                centeredDenominator));
 138    }
 139
 140    /// <summary>
 141    /// Creates the representable-domain intersection of a centered finite
 142    /// cone's axis-aligned bounds without materializing its rotated axis.
 143    /// </summary>
 144    /// <param name="center">The midpoint between the base center and apex.</param>
 145    /// <param name="rotation">The cone rigid-frame rotation.</param>
 146    /// <param name="localAxisDirection">
 147    /// The normalized local direction from the base center toward the apex.
 148    /// </param>
 149    /// <param name="height">The positive full base-to-apex height.</param>
 150    /// <param name="radius">The nonnegative base radius.</param>
 151    public static FixedBoundBox FromCenteredFiniteConeClippedToDomain(
 152        Vector3d center,
 153        FixedQuaternion rotation,
 154        Vector3d localAxisDirection,
 155        Fixed64 height,
 156        Fixed64 radius)
 157    {
 5158        ValidateRigidFiniteAxis(
 5159            rotation,
 5160            localAxisDirection,
 5161            height,
 5162            radius,
 5163            requirePositiveLength: true);
 4164        GetRigidAxis(
 4165            rotation,
 4166            localAxisDirection,
 4167            out Signed192 axisX,
 4168            out Signed192 axisY,
 4169            out Signed192 axisZ,
 4170            out Signed320 axisSquared,
 4171            out Signed192 centeredDenominator);
 4172        return FromMinMax(
 4173            new Vector3d(
 4174                GetRigidConeBound(
 4175                    center.X,
 4176                    axisSquared,
 4177                    axisX,
 4178                    height,
 4179                    radius,
 4180                    centeredDenominator,
 4181                    minimum: true),
 4182                GetRigidConeBound(
 4183                    center.Y,
 4184                    axisSquared,
 4185                    axisY,
 4186                    height,
 4187                    radius,
 4188                    centeredDenominator,
 4189                    minimum: true),
 4190                GetRigidConeBound(
 4191                    center.Z,
 4192                    axisSquared,
 4193                    axisZ,
 4194                    height,
 4195                    radius,
 4196                    centeredDenominator,
 4197                    minimum: true)),
 4198            new Vector3d(
 4199                GetRigidConeBound(
 4200                    center.X,
 4201                    axisSquared,
 4202                    axisX,
 4203                    height,
 4204                    radius,
 4205                    centeredDenominator,
 4206                    minimum: false),
 4207                GetRigidConeBound(
 4208                    center.Y,
 4209                    axisSquared,
 4210                    axisY,
 4211                    height,
 4212                    radius,
 4213                    centeredDenominator,
 4214                    minimum: false),
 4215                GetRigidConeBound(
 4216                    center.Z,
 4217                    axisSquared,
 4218                    axisZ,
 4219                    height,
 4220                    radius,
 4221                    centeredDenominator,
 4222                    minimum: false)));
 223    }
 224
 225    private static void GetRigidAxis(
 226        FixedQuaternion rotation,
 227        Vector3d localAxisDirection,
 228        out Signed192 axisX,
 229        out Signed192 axisY,
 230        out Signed192 axisZ,
 231        out Signed320 axisSquared,
 232        out Signed192 centeredDenominator)
 233    {
 10234        WideOrientedBox.GetRotatedLocalAxisNumerators(
 10235            rotation,
 10236            localAxisDirection,
 10237            out axisX,
 10238            out axisY,
 10239            out axisZ,
 10240            out Signed192 rotationDenominator);
 10241        axisSquared = WideArithmetic.AddSigned320(
 10242            WideArithmetic.AddSigned320(
 10243                WideArithmetic.MultiplySigned192(axisX, axisX),
 10244                WideArithmetic.MultiplySigned192(axisY, axisY)),
 10245            WideArithmetic.MultiplySigned192(axisZ, axisZ));
 10246        Signed320 centeredDenominatorWide = WideArithmetic.MultiplySigned192(
 10247            rotationDenominator,
 10248            WideGeometry.CenteredAxisDenominator);
 249        // A normalized quaternion's squared-component denominator needs at
 250        // most 68 signed bits. The 34-bit centered Q32.32 factor therefore
 251        // keeps this exact product within Signed192.
 10252        _ = Signed192.TryNarrowSigned(
 10253            centeredDenominatorWide,
 10254            out centeredDenominator);
 10255    }
 256
 257    private static Fixed64 GetRigidCapsuleBound(
 258        Fixed64 center,
 259        Signed192 axisComponent,
 260        Fixed64 axisLength,
 261        Fixed64 radius,
 262        Signed192 centeredDenominator,
 263        bool minimum)
 264    {
 18265        Signed320 centerNumerator = WideArithmetic.MultiplySigned192(
 18266            Signed192.Signed(center.m_rawValue),
 18267            centeredDenominator);
 18268        Signed320 axialNumerator = WideArithmetic.MultiplySigned192(
 18269            GetMagnitude(axisComponent),
 18270            Signed192.Signed(axisLength.m_rawValue));
 18271        Signed320 radialNumerator = WideArithmetic.MultiplySigned192(
 18272            Signed192.Signed(radius.m_rawValue),
 18273            centeredDenominator);
 18274        Signed320 extentNumerator = WideArithmetic.AddSigned320(
 18275            axialNumerator,
 18276            radialNumerator);
 18277        Signed320 boundNumerator = minimum
 18278            ? WideArithmetic.SubtractSigned320(
 18279                centerNumerator,
 18280                extentNumerator)
 18281            : WideArithmetic.AddSigned320(
 18282                centerNumerator,
 18283                extentNumerator);
 18284        return GetRigidRationalBoundClippedToDomain(
 18285            boundNumerator,
 18286            centeredDenominator,
 18287            minimum);
 288    }
 289
 290    private static Signed192 GetRigidCylinderOutwardExtentRaw(
 291        Signed320 axisSquared,
 292        Signed192 axisComponent,
 293        Fixed64 axisLength,
 294        Fixed64 radius,
 295        Signed192 centeredDenominator)
 296    {
 9297        Signed320 axialNumerator = WideArithmetic.MultiplySigned192(
 9298            GetMagnitude(axisComponent),
 9299            Signed192.Signed(axisLength.m_rawValue));
 9300        Fixed64 axialCeiling = GetRigidPositiveCeilingRawRatio(
 9301            axialNumerator,
 9302            centeredDenominator);
 9303        Fixed64 diskFloor = GetRigidDiskFloorExtent(
 9304            axisSquared,
 9305            axisComponent,
 9306            radius,
 9307            out Signed320 capacity,
 9308            out Signed320 radiusSquared);
 9309        Signed192 candidate = WideArithmetic.AddSigned192(
 9310            Signed192.Signed(axialCeiling.m_rawValue),
 9311            Signed192.Signed(diskFloor.m_rawValue));
 9312        if (!IsRigidCylinderExtentSufficient(
 9313                candidate,
 9314                axialNumerator,
 9315                centeredDenominator,
 9316                axisSquared,
 9317                capacity,
 9318                radiusSquared))
 319        {
 2320            candidate = WideArithmetic.AddSigned192(
 2321                candidate,
 2322                Signed192.Signed(1L));
 323        }
 324
 9325        return candidate;
 326    }
 327
 328    private static Fixed64 GetRigidPositiveCeilingRawRatio(
 329        Signed320 numerator,
 330        Signed192 denominator)
 331    {
 9332        _ = Fixed64.TryGetSignedRawRatio(
 9333            Signed576.ExtendValue(numerator),
 9334            Signed576.ExtendValue(
 9335                Signed320.ExtendValue(denominator)),
 9336            out Fixed64 candidate);
 9337        Signed320 represented = WideArithmetic.MultiplySigned192(
 9338            Signed192.Signed(candidate.m_rawValue),
 9339            denominator);
 9340        return WideArithmetic.SubtractSigned320(
 9341                represented,
 9342                numerator).Sign >= 0
 9343            ? candidate
 9344            : Fixed64.FromRaw(candidate.m_rawValue + 1L);
 345    }
 346
 347    private static Fixed64 GetRigidDiskFloorExtent(
 348        Signed320 axisSquared,
 349        Signed192 axisComponent,
 350        Fixed64 radius,
 351        out Signed320 capacity,
 352        out Signed320 radiusSquared)
 353    {
 33354        capacity = WideArithmetic.SubtractSigned320(
 33355            axisSquared,
 33356            WideArithmetic.MultiplySigned192(
 33357                axisComponent,
 33358                axisComponent));
 33359        Signed192 radiusRaw =
 33360            Signed192.Signed(radius.m_rawValue);
 33361        radiusSquared = WideArithmetic.MultiplySigned192(
 33362            radiusRaw,
 33363            radiusRaw);
 33364        if (capacity.IsZero || radius == Fixed64.Zero)
 18365            return Fixed64.Zero;
 15366        if (WideArithmetic.SubtractSigned320(
 15367                capacity,
 15368                axisSquared).IsZero)
 369        {
 4370            return radius;
 371        }
 372
 11373        Signed704 radicandWide = WideArithmetic.MultiplySigned320(
 11374            radiusSquared,
 11375            capacity,
 11376            axisSquared);
 377        // Rotation preserves the normalized local-axis squared length:
 378        // axisSquared and capacity are each below 2^193. Combined with the
 379        // full-domain radius square, this radicand remains below 2^523.
 11380        Signed576 radicand = new(
 11381            radicandWide.Word8,
 11382            radicandWide.Word7,
 11383            radicandWide.Word6,
 11384            radicandWide.Word5,
 11385            radicandWide.Word4,
 11386            radicandWide.Word3,
 11387            radicandWide.Word2,
 11388            radicandWide.Word1,
 11389            radicandWide.Word0);
 11390        Signed320 scaledRoot =
 11391            WideArithmetic.GetFloorSquareRootScaledByFixed64(radicand);
 11392        Signed576 scaledAxisSquared = WideArithmetic.MultiplySigned320(
 11393            axisSquared,
 11394            Signed320.ExtendValue(
 11395                Signed192.Signed(FixedMath.ONE_L)));
 11396        _ = Fixed64.TryGetSignedRawRatio(
 11397            Signed576.ExtendValue(scaledRoot),
 11398            scaledAxisSquared,
 11399            out Fixed64 candidate);
 11400        Signed576 represented = GetRigidDiskRepresentedSquare(
 11401            candidate,
 11402            axisSquared);
 11403        Signed576 target = WideArithmetic.MultiplySigned320(
 11404            radiusSquared,
 11405            capacity);
 11406        return WideArithmetic.CompareNonNegative(represented, target) <= 0
 11407            ? candidate
 11408            : Fixed64.FromRaw(candidate.m_rawValue - 1L);
 409    }
 410
 411    private static Signed576 GetRigidDiskRepresentedSquare(
 412        Fixed64 extent,
 413        Signed320 axisSquared) =>
 11414        WideArithmetic.MultiplySigned320(
 11415            WideArithmetic.MultiplySigned192(
 11416                Signed192.Signed(extent.m_rawValue),
 11417                Signed192.Signed(extent.m_rawValue)),
 11418            axisSquared);
 419
 420    private static bool IsRigidCylinderExtentSufficient(
 421        Signed192 candidate,
 422        Signed320 axialNumerator,
 423        Signed192 centeredDenominator,
 424        Signed320 axisSquared,
 425        Signed320 diskCapacity,
 426        Signed320 radiusSquared)
 427    {
 9428        Signed320 remainder = WideArithmetic.SubtractSigned320(
 9429            WideArithmetic.MultiplySigned192(
 9430                candidate,
 9431                centeredDenominator),
 9432            axialNumerator);
 9433        Signed704 left = WideArithmetic.MultiplySigned320(
 9434            remainder,
 9435            remainder,
 9436            axisSquared);
 9437        Signed320 denominatorSquared = WideArithmetic.MultiplySigned192(
 9438            centeredDenominator,
 9439            centeredDenominator);
 9440        Signed704 right = WideArithmetic.MultiplySigned320(
 9441            denominatorSquared,
 9442            radiusSquared,
 9443            diskCapacity);
 9444        return WideArithmetic.CompareNonNegative(left, right) >= 0;
 445    }
 446
 447    private static Fixed64 GetRigidConeBound(
 448        Fixed64 center,
 449        Signed320 axisSquared,
 450        Signed192 axisComponent,
 451        Fixed64 height,
 452        Fixed64 radius,
 453        Signed192 centeredDenominator,
 454        bool minimum)
 455    {
 24456        Signed320 centerNumerator = WideArithmetic.MultiplySigned192(
 24457            Signed192.Signed(center.m_rawValue),
 24458            centeredDenominator);
 24459        Signed320 axialNumerator = WideArithmetic.MultiplySigned192(
 24460            axisComponent,
 24461            Signed192.Signed(height.m_rawValue));
 24462        Signed320 apexNumerator = WideArithmetic.AddSigned320(
 24463            centerNumerator,
 24464            axialNumerator);
 24465        Fixed64 apexBound = GetRigidRationalBoundClippedToDomain(
 24466            apexNumerator,
 24467            centeredDenominator,
 24468            minimum);
 24469        Fixed64 baseRimBound = GetRigidConeBaseRimBound(
 24470            center,
 24471            axisSquared,
 24472            axisComponent,
 24473            height,
 24474            radius,
 24475            centeredDenominator,
 24476            minimum);
 24477        return minimum
 24478            ? apexBound <= baseRimBound ? apexBound : baseRimBound
 24479            : apexBound >= baseRimBound ? apexBound : baseRimBound;
 480    }
 481
 482    private static Fixed64 GetRigidConeBaseRimBound(
 483        Fixed64 center,
 484        Signed320 axisSquared,
 485        Signed192 axisComponent,
 486        Fixed64 height,
 487        Fixed64 radius,
 488        Signed192 centeredDenominator,
 489        bool minimum)
 490    {
 24491        Signed320 baseOffsetNumerator = WideArithmetic.SubtractSigned320(
 24492            default,
 24493            WideArithmetic.MultiplySigned192(
 24494                axisComponent,
 24495                Signed192.Signed(height.m_rawValue)));
 24496        Fixed64 baseOffset = GetRigidSignedRawRatio(
 24497            baseOffsetNumerator,
 24498            centeredDenominator,
 24499            roundDown: minimum);
 24500        Fixed64 diskFloor = GetRigidDiskFloorExtent(
 24501            axisSquared,
 24502            axisComponent,
 24503            radius,
 24504            out Signed320 capacity,
 24505            out Signed320 radiusSquared);
 24506        Signed192 candidate = WideArithmetic.AddSigned192(
 24507            Signed192.Signed(center.m_rawValue),
 24508            Signed192.Signed(baseOffset.m_rawValue));
 24509        candidate = minimum
 24510            ? WideArithmetic.SubtractSigned192(
 24511                candidate,
 24512                Signed192.Signed(diskFloor.m_rawValue))
 24513            : WideArithmetic.AddSigned192(
 24514                candidate,
 24515                Signed192.Signed(diskFloor.m_rawValue));
 24516        if (!IsRigidConeBaseRimBoundSufficient(
 24517                candidate,
 24518                center,
 24519                baseOffsetNumerator,
 24520                centeredDenominator,
 24521                axisSquared,
 24522                capacity,
 24523                radiusSquared,
 24524                minimum))
 525        {
 2526            candidate = minimum
 2527                ? WideArithmetic.SubtractSigned192(
 2528                    candidate,
 2529                    Signed192.Signed(1L))
 2530                : WideArithmetic.AddSigned192(
 2531                    candidate,
 2532                    Signed192.Signed(1L));
 533        }
 534
 24535        return GetRigidRationalBoundClippedToDomain(
 24536            WideArithmetic.MultiplySigned192(
 24537                candidate,
 24538                centeredDenominator),
 24539            centeredDenominator,
 24540            minimum);
 541    }
 542
 543    private static Fixed64 GetRigidSignedRawRatio(
 544        Signed320 numerator,
 545        Signed192 denominator,
 546        bool roundDown)
 547    {
 24548        _ = Fixed64.TryGetSignedRawRatio(
 24549            Signed576.ExtendValue(numerator),
 24550            Signed576.ExtendValue(
 24551                Signed320.ExtendValue(denominator)),
 24552            out Fixed64 candidate);
 24553        Signed320 represented = WideArithmetic.MultiplySigned192(
 24554            Signed192.Signed(candidate.m_rawValue),
 24555            denominator);
 24556        int comparison = WideArithmetic.SubtractSigned320(
 24557            represented,
 24558            numerator).Sign;
 24559        if (roundDown && comparison > 0)
 2560            return Fixed64.FromRaw(candidate.m_rawValue - 1L);
 22561        if (!roundDown && comparison < 0)
 4562            return Fixed64.FromRaw(candidate.m_rawValue + 1L);
 18563        return candidate;
 564    }
 565
 566    private static bool IsRigidConeBaseRimBoundSufficient(
 567        Signed192 candidate,
 568        Fixed64 center,
 569        Signed320 baseOffsetNumerator,
 570        Signed192 centeredDenominator,
 571        Signed320 axisSquared,
 572        Signed320 diskCapacity,
 573        Signed320 radiusSquared,
 574        bool minimum)
 575    {
 24576        Signed192 candidateFromCenter = WideArithmetic.SubtractSigned192(
 24577            candidate,
 24578            Signed192.Signed(center.m_rawValue));
 24579        Signed320 representedOffset = WideArithmetic.MultiplySigned192(
 24580            candidateFromCenter,
 24581            centeredDenominator);
 24582        Signed320 radialGap = minimum
 24583            ? WideArithmetic.SubtractSigned320(
 24584                baseOffsetNumerator,
 24585                representedOffset)
 24586            : WideArithmetic.SubtractSigned320(
 24587                representedOffset,
 24588                baseOffsetNumerator);
 589
 24590        Signed704 left = WideArithmetic.MultiplySigned320(
 24591            radialGap,
 24592            radialGap,
 24593            axisSquared);
 24594        Signed320 denominatorSquared = WideArithmetic.MultiplySigned192(
 24595            centeredDenominator,
 24596            centeredDenominator);
 24597        Signed704 right = WideArithmetic.MultiplySigned320(
 24598            denominatorSquared,
 24599            radiusSquared,
 24600            diskCapacity);
 24601        return WideArithmetic.CompareNonNegative(left, right) >= 0;
 602    }
 603
 604    private static Fixed64 GetRigidRationalBoundClippedToDomain(
 605        Signed320 numerator,
 606        Signed192 denominator,
 607        bool minimum)
 608    {
 66609        Signed320 minimumNumerator = WideArithmetic.MultiplySigned192(
 66610            Signed192.Signed(Fixed64.MinValue.m_rawValue),
 66611            denominator);
 66612        Signed320 maximumNumerator = WideArithmetic.MultiplySigned192(
 66613            Signed192.Signed(Fixed64.MaxValue.m_rawValue),
 66614            denominator);
 66615        if (WideArithmetic.SubtractSigned320(
 66616                numerator,
 66617                minimumNumerator).Sign < 0)
 618        {
 3619            return Fixed64.MinValue;
 620        }
 63621        if (WideArithmetic.SubtractSigned320(
 63622                numerator,
 63623                maximumNumerator).Sign > 0)
 624        {
 3625            return Fixed64.MaxValue;
 626        }
 627
 60628        _ = Fixed64.TryGetSignedRawRatio(
 60629            Signed576.ExtendValue(numerator),
 60630            Signed576.ExtendValue(
 60631                Signed320.ExtendValue(denominator)),
 60632            out Fixed64 bound);
 60633        Signed320 represented = WideArithmetic.MultiplySigned192(
 60634            Signed192.Signed(bound.m_rawValue),
 60635            denominator);
 60636        int comparison = WideArithmetic.SubtractSigned320(
 60637            represented,
 60638            numerator).Sign;
 60639        if (minimum && comparison > 0)
 7640            return Fixed64.FromRaw(bound.m_rawValue - 1L);
 53641        if (!minimum && comparison < 0)
 4642            return Fixed64.FromRaw(bound.m_rawValue + 1L);
 49643        return bound;
 644    }
 645
 646    private static Signed192 GetMagnitude(Signed192 value)
 647    {
 27648        if (value.Sign >= 0)
 21649            return value;
 6650        return WideArithmetic.SubtractSigned192(default, value);
 651    }
 652
 653    private static void ValidateRigidFiniteAxis(
 654        FixedQuaternion rotation,
 655        Vector3d localAxisDirection,
 656        Fixed64 axisLength,
 657        Fixed64 radius,
 658        bool requirePositiveLength)
 659    {
 16660        if (!rotation.IsNormalized())
 661        {
 1662            throw new ArgumentException(
 1663                "Finite-axis rotation must be normalized.",
 1664                nameof(rotation));
 665        }
 15666        if (!localAxisDirection.IsNormalized())
 667        {
 1668            throw new ArgumentException(
 1669                "Finite-axis local direction must be normalized.",
 1670                nameof(localAxisDirection));
 671        }
 14672        if (requirePositiveLength
 14673            ? axisLength <= Fixed64.Zero
 14674            : axisLength < Fixed64.Zero)
 675        {
 3676            throw new ArgumentOutOfRangeException(nameof(axisLength));
 677        }
 11678        if (radius < Fixed64.Zero)
 1679            throw new ArgumentOutOfRangeException(nameof(radius));
 10680    }
 681}

Methods/Properties

.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Geometry.FixedBoundBox/BoundingBoxState)
get_Center()
set_Center(FixedMathSharp.Vector3d)
get_Proportions()
set_Proportions(FixedMathSharp.Vector3d)
get_Scope()
get_State()
set_State(FixedMathSharp.Geometry.FixedBoundBox/BoundingBoxState)
FromMinMax(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FromCenterAndSize(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FromCenterAndScope(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FromCenterAndSizeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FromCenterAndScopeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FromRelativeRotatedBoundsClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
Orient(FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.Vector3d>)
Resize(FixedMathSharp.Vector3d)
SetMinMax(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
SetBoundingBox(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
Contains(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)
ProjectPoint(FixedMathSharp.Vector3d)
ClampPoint(FixedMathSharp.Vector3d)
ContainsBoxLike(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IntersectsBoxLike(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IntersectsSphere(FixedMathSharp.Geometry.FixedBoundSphere)
IntersectsSphereStrict(FixedMathSharp.Geometry.FixedBoundSphere)
HasPositiveVolume()
HasStrictAxisOverlap(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
DistanceToSurface(FixedMathSharp.Vector3d)
GetPointOnSurfaceTowardsObject(FixedMathSharp.Vector3d)
ClosestPointOnSurface(FixedMathSharp.Vector3d)
GetCorner(System.Int32)
CopyCorners(System.Span`1<FixedMathSharp.Vector3d>)
GetVolumeExpansionCost(FixedMathSharp.Geometry.FixedBoundBox)
Union(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Geometry.FixedBoundBox)
FindClosestPointsBetweenBoxes(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Geometry.FixedBoundBox)
op_Equality(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Geometry.FixedBoundBox)
op_Inequality(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Geometry.FixedBoundBox)
Equals(System.Object)
Equals(FixedMathSharp.Geometry.FixedBoundBox)
GetHashCode()
SetCenterAndHalfSize(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
SetCenterAndHalfSizeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetHalfSize(FixedMathSharp.Vector3d)
GetScopeMagnitude(FixedMathSharp.Vector3d)
FromFiniteConeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
FromCenteredCapsuleClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FromCenteredFiniteCylinderClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FromCenteredFiniteConeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetFiniteAxisLengthSquared(FixedMathSharp.Vector3d)
GetFiniteConeDiskExtent(FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetFiniteDiskFloorExtent(FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed192&,FixedMathSharp.Signed320&)
GetFiniteDiskRepresentedSquare(FixedMathSharp.Fixed64,FixedMathSharp.Signed192)
GetFiniteDiskTarget(FixedMathSharp.Signed320,FixedMathSharp.Signed192)
GetCenteredFiniteCylinderOutwardExtentRaw(FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetPositiveCeilingRawRatio(FixedMathSharp.Signed320,FixedMathSharp.Signed192)
IsCenteredFiniteCylinderExtentSufficient(FixedMathSharp.Signed192,FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Signed192,FixedMathSharp.Signed320)
FromCenteredFiniteCylinderExtentsClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Signed192,FixedMathSharp.Signed192,FixedMathSharp.Signed192)
GetCenteredFiniteCylinderBound(FixedMathSharp.Fixed64,FixedMathSharp.Signed192,System.Boolean)
GetCenteredFiniteConeBound(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
FromCenteredFiniteAxisClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
ValidateCenteredFiniteAxis(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
FromCenteredCapsuleClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FromCenteredFiniteCylinderClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FromCenteredFiniteConeClippedToDomain(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetRigidAxis(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Signed192&,FixedMathSharp.Signed192&,FixedMathSharp.Signed192&,FixedMathSharp.Signed320&,FixedMathSharp.Signed192&)
GetRigidCapsuleBound(FixedMathSharp.Fixed64,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed192,System.Boolean)
GetRigidCylinderOutwardExtentRaw(FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed192)
GetRigidPositiveCeilingRawRatio(FixedMathSharp.Signed320,FixedMathSharp.Signed192)
GetRigidDiskFloorExtent(FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Signed320&,FixedMathSharp.Signed320&)
GetRigidDiskRepresentedSquare(FixedMathSharp.Fixed64,FixedMathSharp.Signed320)
IsRigidCylinderExtentSufficient(FixedMathSharp.Signed192,FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Signed320,FixedMathSharp.Signed320,FixedMathSharp.Signed320)
GetRigidConeBound(FixedMathSharp.Fixed64,FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed192,System.Boolean)
GetRigidConeBaseRimBound(FixedMathSharp.Fixed64,FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed192,System.Boolean)
GetRigidSignedRawRatio(FixedMathSharp.Signed320,FixedMathSharp.Signed192,System.Boolean)
IsRigidConeBaseRimBoundSufficient(FixedMathSharp.Signed192,FixedMathSharp.Fixed64,FixedMathSharp.Signed320,FixedMathSharp.Signed192,FixedMathSharp.Signed320,FixedMathSharp.Signed320,FixedMathSharp.Signed320,System.Boolean)
GetRigidRationalBoundClippedToDomain(FixedMathSharp.Signed320,FixedMathSharp.Signed192,System.Boolean)
GetMagnitude(FixedMathSharp.Signed192)
ValidateRigidFiniteAxis(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)