< Summary

Information
Class: FixedMathSharp.Geometry.FixedOrientedBox
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/FixedOrientedBox.cs
Line coverage
100%
Covered lines: 412
Uncovered lines: 0
Coverable lines: 412
Total lines: 1028
Line coverage: 100%
Branch coverage
100%
Covered branches: 106
Total branches: 106
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/FixedOrientedBox.cs

#LineLine coverage
 1//=======================================================================
 2// FixedOrientedBox.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;
 11
 12namespace FixedMathSharp.Geometry;
 13
 14/// <summary>
 15/// Represents an immutable oriented box through canonical center, orientation,
 16/// and positive local half-extents.
 17/// </summary>
 18/// <remarks>
 19/// World corners are intentionally not stored or exposed. Use local feature
 20/// selection and <see cref="TryMaterializeLocalPoint"/> when a representable
 21/// world-space witness is required. Geometry is evaluated against the exact
 22/// scale-invariant rational basis of the stored quaternion; rounded axes are
 23/// presentation values rather than query inputs.
 24/// </remarks>
 25public readonly struct FixedOrientedBox : IEquatable<FixedOrientedBox>
 26{
 27    /// <summary>
 28    /// The number of stable local corners exposed by <see cref="GetLocalCorner"/>.
 29    /// </summary>
 30    public const int CornerCount = 8;
 31
 32    /// <summary>
 33    /// Initializes an oriented box from canonical geometry.
 34    /// </summary>
 35    /// <exception cref="ArgumentException">
 36    /// <paramref name="orientation"/> is not normalized.
 37    /// </exception>
 38    /// <exception cref="ArgumentOutOfRangeException">
 39    /// At least one half-extent is not positive.
 40    /// </exception>
 41    [JsonConstructor]
 42    public FixedOrientedBox(
 43        Vector3d center,
 44        FixedQuaternion orientation,
 45        Vector3d halfExtents)
 46    {
 93847        if (!orientation.IsNormalized())
 348            throw new ArgumentException("The oriented-box rotation must be normalized.", nameof(orientation));
 93549        if (!HasPositiveHalfExtents(halfExtents))
 50        {
 351            throw new ArgumentOutOfRangeException(
 352                nameof(halfExtents),
 353                "Every oriented-box half-extent must be positive.");
 54        }
 55
 93256        Center = center;
 93257        Orientation = orientation;
 93258        HalfExtents = halfExtents;
 93259    }
 60
 61    /// <summary>
 62    /// The world-space center.
 63    /// </summary>
 64    [JsonInclude]
 65    public Vector3d Center { get; }
 66
 67    /// <summary>
 68    /// The normalized local-to-world orientation.
 69    /// </summary>
 70    [JsonInclude]
 71    public FixedQuaternion Orientation { get; }
 72
 73    /// <summary>
 74    /// The positive local face distances.
 75    /// </summary>
 76    [JsonInclude]
 77    public Vector3d HalfExtents { get; }
 78
 79    /// <summary>
 80    /// Gets the nearest round-half-to-even <see cref="Fixed64"/> views of all
 81    /// three exact local-to-world axes.
 82    /// </summary>
 83    public void GetAxes(
 84        out Vector3d axisX,
 85        out Vector3d axisY,
 86        out Vector3d axisZ)
 87    {
 52988        EnsureValid();
 52889        GetAxesUnchecked(out axisX, out axisY, out axisZ);
 52890    }
 91
 92    /// <summary>
 93    /// Gets a stable center-relative local corner.
 94    /// </summary>
 95    /// <remarks>
 96    /// Index bits select positive X, Y, and Z respectively. The order is
 97    /// <c>---</c>, <c>+--</c>, <c>-+-</c>, <c>++-</c>, <c>--+</c>,
 98    /// <c>+-+</c>, <c>-++</c>, <c>+++</c>.
 99    /// </remarks>
 100    public Vector3d GetLocalCorner(int index)
 101    {
 522102        EnsureValid();
 521103        if ((uint)index >= CornerCount)
 104        {
 2105            throw new ArgumentOutOfRangeException(
 2106                nameof(index),
 2107                $"Corner index must be between 0 and {CornerCount - 1}.");
 108        }
 109
 519110        return new Vector3d(
 519111            (index & 1) == 0 ? -HalfExtents.X : HalfExtents.X,
 519112            (index & 2) == 0 ? -HalfExtents.Y : HalfExtents.Y,
 519113            (index & 4) == 0 ? -HalfExtents.Z : HalfExtents.Z);
 114    }
 115
 116    /// <summary>
 117    /// Gets the center-relative local support point for a world-space direction.
 118    /// </summary>
 119    /// <remarks>
 120    /// An exact zero projection selects the negative extent so ties retain the
 121    /// lower local corner index.
 122    /// </remarks>
 123    public Vector3d GetLocalSupportPoint(Vector3d worldDirection)
 124    {
 516125        EnsureValid();
 515126        return WideOrientedBox.GetLocalSupportPoint(
 515127            worldDirection,
 515128            Orientation,
 515129            HalfExtents);
 130    }
 131
 132    /// <summary>
 133    /// Gets the least-outward analytical axis-aligned bounds, clipped only at
 134    /// the final representable scalar endpoints.
 135    /// </summary>
 136    public FixedBoundBox GetBoundsClippedToDomain()
 137    {
 517138        EnsureValid();
 516139        return WideOrientedBox.GetBoundsClippedToDomain(
 516140            Center,
 516141            Orientation,
 516142            HalfExtents);
 143    }
 144
 145    /// <summary>
 146    /// Returns whether a world-space point lies inside or on every conceptual
 147    /// box face.
 148    /// </summary>
 149    public bool Contains(Vector3d point)
 150    {
 2703151        EnsureValid();
 2702152        return WideOrientedBox.Contains(
 2702153            point,
 2702154            Center,
 2702155            Orientation,
 2702156            HalfExtents);
 157    }
 158
 159    /// <summary>
 160    /// Gets the conceptual closest surface point in this box's rigid frame.
 161    /// </summary>
 162    /// <remarks>
 163    /// Outside points clamp independently to the local extents. Inside points
 164    /// select the nearest face with stable X, then Y, then Z ties. The returned
 165    /// anchor remains valid when the selected absolute world point is outside
 166    /// the representable scalar domain.
 167    /// </remarks>
 168    public FixedPointAnchor GetClosestPointAnchor(Vector3d point)
 169    {
 524170        EnsureValid();
 523171        return WideOrientedBox.GetClosestPointAnchor(
 523172            point,
 523173            Center,
 523174            Orientation,
 523175            HalfExtents);
 176    }
 177
 178    /// <summary>
 179    /// Attempts to materialize the nearest lattice representation of the
 180    /// conceptual closest surface point.
 181    /// </summary>
 182    /// <remarks>
 183    /// Outside points clamp independently to the local extents. Inside points
 184    /// select the nearest face with stable X, then Y, then Z ties. The method
 185    /// returns <see langword="false"/> only when the selected final world point
 186    /// is not representable.
 187    /// </remarks>
 188    public bool TryGetClosestPointOnSurface(
 189        Vector3d point,
 190        out Vector3d closestPoint)
 523191        => GetClosestPointAnchor(point).TryGetPoint(out closestPoint);
 192
 193    /// <summary>
 194    /// Gets the nearest representable normal of the nearest conceptual face.
 195    /// </summary>
 196    /// <remarks>
 197    /// Outside points select the first violated axis in X, then Y, then Z
 198    /// order, matching their nearest clamped surface feature. For contained
 199    /// points, equal inward face distances use the same axis order. A zero
 200    /// local projection selects the positive face.
 201    /// </remarks>
 202    public Vector3d GetNearestFaceNormal(Vector3d point)
 203    {
 527204        EnsureValid();
 526205        return WideOrientedBox.GetNearestFaceNormal(
 526206            point,
 526207            Center,
 526208            Orientation,
 526209            HalfExtents);
 210    }
 211
 212    /// <summary>
 213    /// Attempts to materialize a center-relative local point in world space.
 214    /// </summary>
 215    /// <remarks>
 216    /// Each coordinate retains the exact rational quaternion basis, all three
 217    /// products, and the center contribution until one final round-half-to-even
 218    /// conversion. The result is the nearest lattice representation of the
 219    /// conceptual point; a conceptual boundary point need not remain exactly on
 220    /// that boundary after quantization. Failure is atomic.
 221    /// </remarks>
 222    public bool TryMaterializeLocalPoint(
 223        Vector3d localPoint,
 224        out Vector3d worldPoint)
 225    {
 528226        EnsureValid();
 527227        return WideOrientedBox.TryMaterializeLocalPoint(
 527228            Center,
 527229            Orientation,
 527230            localPoint,
 527231            out worldPoint);
 232    }
 233
 234    /// <summary>
 235    /// Attempts to rotate a center-relative local offset into world-space
 236    /// coordinates without adding <see cref="Center"/>.
 237    /// </summary>
 238    /// <remarks>
 239    /// All three quaternion-basis products are retained until one final
 240    /// round-half-to-even conversion per component. This is the canonical
 241    /// admission path for relative box features whose absolute world points
 242    /// may lie outside the scalar coordinate domain.
 243    /// </remarks>
 244    public bool TryTransformLocalOffset(
 245        Vector3d localOffset,
 246        out Vector3d worldOffset)
 247    {
 2248        EnsureValid();
 2249        return WideOrientedBox.TryTransformLocalOffset(
 2250            Orientation,
 2251            localOffset,
 2252            out worldOffset);
 253    }
 254
 255    /// <summary>
 256    /// Attempts to return the center-relative world-space support offset for a
 257    /// direction.
 258    /// </summary>
 259    /// <remarks>
 260    /// Exact zero projections select the negative local extent so ties retain
 261    /// the stable lower corner index.
 262    /// </remarks>
 263    public bool TryGetSupportOffset(
 264        Vector3d worldDirection,
 265        out Vector3d centerOffset)
 266    {
 4267        EnsureValid();
 4268        return WideOrientedBox.TryGetSupportOffset(
 4269            worldDirection,
 4270            Orientation,
 4271            HalfExtents,
 4272            out centerOffset);
 273    }
 274
 275    /// <summary>
 276    /// Attempts to construct one exact-final-narrowing Minkowski support
 277    /// difference between this box and another origin-relative support.
 278    /// </summary>
 279    /// <remarks>
 280    /// The result is
 281    /// <c>(Center + boxSupport) - (otherOrigin + otherOriginSupportOffset)</c>.
 282    /// Neither absolute support point is materialized independently.
 283    /// </remarks>
 284    public bool TryGetSupportDifference(
 285        Vector3d otherOrigin,
 286        Vector3d otherOriginSupportOffset,
 287        Vector3d worldDirection,
 288        out Vector3d difference)
 289    {
 3290        EnsureValid();
 3291        return WideOrientedBox.TryGetSupportDifference(
 3292            Center,
 3293            Orientation,
 3294            HalfExtents,
 3295            otherOrigin,
 3296            otherOriginSupportOffset,
 3297            worldDirection,
 3298            out difference);
 299    }
 300
 301    /// <summary>
 302    /// Attempts to construct the exact Minkowski support difference between
 303    /// this box and <paramref name="other"/>.
 304    /// </summary>
 305    /// <remarks>
 306    /// Both rotated support offsets and the center difference are retained as
 307    /// rational wide values until one final narrowing per component.
 308    /// </remarks>
 309    public bool TryGetSupportDifference(
 310        FixedOrientedBox other,
 311        Vector3d worldDirection,
 312        out Vector3d difference)
 313    {
 2314        EnsureValid();
 2315        other.EnsureValid();
 2316        return WideOrientedBox.TryGetSupportDifference(
 2317            Center,
 2318            Orientation,
 2319            HalfExtents,
 2320            other.Center,
 2321            other.Orientation,
 2322            other.HalfExtents,
 2323            worldDirection,
 2324            out difference);
 325    }
 326
 327    /// <summary>
 328    /// Finds the closed parameter interval where a bounded ray overlaps this
 329    /// oriented box.
 330    /// </summary>
 331    /// <remarks>
 332    /// World-to-local projections and slab clipping retain the exact rational
 333    /// quaternion basis. The direction need not be normalized.
 334    /// </remarks>
 335    public bool TryGetRayIntersectionInterval(
 336        FixedRay ray,
 337        Fixed64 maxParameter,
 338        out Fixed64 entry,
 339        out Fixed64 exit)
 340    {
 12341        EnsureValid();
 12342        if (maxParameter < Fixed64.Zero)
 1343            throw new ArgumentOutOfRangeException(nameof(maxParameter));
 344
 11345        return WideOrientedBox.TryGetRayIntersectionInterval(
 11346            Center,
 11347            Orientation,
 11348            HalfExtents,
 11349            ray.Position,
 11350            ray.Direction,
 11351            maxParameter,
 11352            out entry,
 11353            out exit);
 354    }
 355
 356    /// <summary>
 357    /// Determines whether two boxes have identical canonical state.
 358    /// </summary>
 359    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 360    public static bool operator ==(FixedOrientedBox left, FixedOrientedBox right) =>
 2361        left.Equals(right);
 362
 363    /// <summary>
 364    /// Determines whether two boxes have different canonical state.
 365    /// </summary>
 366    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 367    public static bool operator !=(FixedOrientedBox left, FixedOrientedBox right) =>
 2368        !left.Equals(right);
 369
 370    /// <inheritdoc/>
 371    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 372    public override bool Equals(object? obj) =>
 2373        obj is FixedOrientedBox other && Equals(other);
 374
 375    /// <inheritdoc/>
 376    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 377    public bool Equals(FixedOrientedBox other) =>
 7378        Center.Equals(other.Center)
 7379        && Orientation.Equals(other.Orientation)
 7380        && HalfExtents.Equals(other.HalfExtents);
 381
 382    /// <inheritdoc/>
 383    public override int GetHashCode()
 384    {
 385        unchecked
 386        {
 2387            int hash = 17;
 2388            hash = (hash * 31) + Center.GetHashCode();
 2389            hash = (hash * 31) + Orientation.GetHashCode();
 2390            hash = (hash * 31) + HalfExtents.GetHashCode();
 2391            return hash;
 392        }
 393    }
 394
 395    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 396    private void GetAxesUnchecked(
 397        out Vector3d axisX,
 398        out Vector3d axisY,
 399        out Vector3d axisZ) =>
 528400        WideOrientedBox.GetAxes(Orientation, out axisX, out axisY, out axisZ);
 401
 402    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 403    private void EnsureValid()
 404    {
 8478405        if (!HasPositiveHalfExtents(HalfExtents))
 406        {
 9407            throw new InvalidOperationException("The oriented box does not contain valid canonical geometry.");
 408        }
 8469409    }
 410
 411    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 412    private static bool HasPositiveHalfExtents(Vector3d halfExtents) =>
 9413413        halfExtents.X > Fixed64.Zero
 9413414        && halfExtents.Y > Fixed64.Zero
 9413415        && halfExtents.Z > Fixed64.Zero;
 416
 417    /// <summary>
 418    /// Attempts to construct an exact contact against another oriented box.
 419    /// </summary>
 420    public bool TryGetContact(
 421        FixedOrientedBox other,
 422        out FixedContactAnchors contact)
 423    {
 15424        EnsureValid();
 15425        other.EnsureValid();
 14426        return WideOrientedBox.TryGetContact(
 14427            Center,
 14428            Orientation,
 14429            HalfExtents,
 14430            other.Center,
 14431            other.Orientation,
 14432            other.HalfExtents,
 14433            out contact);
 434    }
 435
 436    /// <summary>
 437    /// Attempts to construct an exact contact against a nondegenerate
 438    /// triangle.
 439    /// </summary>
 440    /// <param name="triangleOrigin">
 441    /// The triangle's rigid-frame origin.
 442    /// </param>
 443    /// <param name="triangleRotation">The triangle's local-to-world rotation.</param>
 444    /// <param name="triangle">The triangle in its rigid-frame coordinates.</param>
 445    /// <param name="contact">
 446    /// The box-center-relative and triangle-origin-relative contact relation.
 447    /// </param>
 448    /// <returns>
 449    /// <see langword="true"/> when the shapes overlap and both relative
 450    /// contact anchors are representable; otherwise <see langword="false"/>.
 451    /// </returns>
 452    public bool TryGetTriangleContact(
 453        Vector3d triangleOrigin,
 454        FixedQuaternion triangleRotation,
 455        FixedTriangle triangle,
 456        out FixedContactAnchors contact)
 457    {
 142458        EnsureValid();
 142459        if (!triangleRotation.IsNormalized())
 460        {
 1461            throw new ArgumentException(
 1462                "The triangle rotation must be normalized.",
 1463                nameof(triangleRotation));
 464        }
 141465        return WideOrientedBox.TryGetTriangleContact(
 141466            Center,
 141467            Orientation,
 141468            HalfExtents,
 141469            triangleOrigin,
 141470            triangleRotation,
 141471            triangle,
 141472            out contact);
 473    }
 474
 475    /// <summary>
 476    /// Attempts to construct an exact triangle contact and, for a parallel
 477    /// box-face/triangle-face feature, clips stable box corners to the
 478    /// triangle.
 479    /// </summary>
 480    /// <remarks>
 481    /// <paramref name="faceContacts"/> must provide capacity for four
 482    /// contacts. <paramref name="contact"/> always contains the primary exact
 483    /// contact relation when the shapes overlap. <paramref name="faceContactCount"/>
 484    /// is zero for non-face features.
 485    /// </remarks>
 486    public bool TryGetTriangleContact(
 487        Vector3d triangleOrigin,
 488        FixedQuaternion triangleRotation,
 489        FixedTriangle triangle,
 490        Span<FixedContactLocalPoints> faceContacts,
 491        out FixedContactAnchors contact,
 492        out int faceContactCount)
 493    {
 11494        EnsureValid();
 11495        if (faceContacts.Length < 4)
 496        {
 1497            throw new ArgumentException(
 1498                "Triangle face-contact output requires capacity for four contacts.",
 1499                nameof(faceContacts));
 500        }
 10501        if (!triangleRotation.IsNormalized())
 502        {
 1503            throw new ArgumentException(
 1504                "The triangle rotation must be normalized.",
 1505                nameof(triangleRotation));
 506        }
 9507        if (!WideOrientedBox.TryGetTriangleContact(
 9508                Center,
 9509                Orientation,
 9510                HalfExtents,
 9511                triangleOrigin,
 9512                triangleRotation,
 9513                triangle,
 9514                out contact))
 515        {
 1516            faceContactCount = 0;
 1517            return false;
 518        }
 519
 8520        WideOrientedBox.GetTriangleFaceContacts(
 8521            Center,
 8522            Orientation,
 8523            HalfExtents,
 8524            triangleOrigin,
 8525            triangleRotation,
 8526            triangle,
 8527            contact,
 8528            faceContacts,
 8529            out faceContactCount);
 8530        return true;
 531    }
 532
 533    /// <summary>
 534    /// Attempts to construct an exact contact against a convex point span.
 535    /// </summary>
 536    /// <remarks>
 537    /// Hull points are origin-relative offsets expressed in the hull's local
 538    /// orientation. Triangle and edge indices provide the complete convex SAT
 539    /// topology. The method performs no absolute world-point materialization.
 540    /// </remarks>
 541    public bool TryGetConvexHullContact(
 542        Vector3d hullOrigin,
 543        FixedQuaternion hullOrientation,
 544        ReadOnlySpan<Vector3d> hullLocalOffsets,
 545        ReadOnlySpan<int> triangleVertexIndices,
 546        ReadOnlySpan<int> edgeVertexPairs,
 547        out FixedContactAnchors contact)
 548    {
 84549        EnsureValid();
 84550        if (!hullOrientation.IsNormalized())
 551        {
 1552            throw new ArgumentException(
 1553                "The convex-hull rotation must be normalized.",
 1554                nameof(hullOrientation));
 555        }
 83556        if (hullLocalOffsets.Length < 4)
 557        {
 1558            throw new ArgumentException(
 1559                "A convex hull requires at least four origin-relative points.",
 1560                nameof(hullLocalOffsets));
 561        }
 82562        if (triangleVertexIndices.Length == 0
 82563            || triangleVertexIndices.Length % FixedTriangle.VertexCount != 0)
 564        {
 2565            throw new ArgumentException(
 2566                "Convex-hull triangle topology must be a nonempty multiple of three.",
 2567                nameof(triangleVertexIndices));
 568        }
 80569        if (edgeVertexPairs.Length == 0
 80570            || (edgeVertexPairs.Length & 1) != 0)
 571        {
 2572            throw new ArgumentException(
 2573                "Convex-hull edge topology must contain complete vertex-index pairs.",
 2574                nameof(edgeVertexPairs));
 575        }
 78576        ValidateConvexHullIndices(
 78577            hullLocalOffsets.Length,
 78578            triangleVertexIndices,
 78579            nameof(triangleVertexIndices));
 77580        ValidateConvexHullIndices(
 77581            hullLocalOffsets.Length,
 77582            edgeVertexPairs,
 77583            nameof(edgeVertexPairs));
 76584        return WideOrientedBox.TryGetConvexHullContact(
 76585            Center,
 76586            Orientation,
 76587            HalfExtents,
 76588            hullOrigin,
 76589            hullOrientation,
 76590            hullLocalOffsets,
 76591            triangleVertexIndices,
 76592            edgeVertexPairs,
 76593            out contact);
 594    }
 595
 596    private static void ValidateConvexHullIndices(
 597        int vertexCount,
 598        ReadOnlySpan<int> indices,
 599        string parameterName)
 600    {
 9508601        for (int index = 0; index < indices.Length; index++)
 602        {
 4601603            if ((uint)indices[index] >= (uint)vertexCount)
 604            {
 2605                throw new ArgumentOutOfRangeException(
 2606                    parameterName,
 2607                    "Convex-hull topology contains an invalid vertex index.");
 608            }
 609        }
 153610    }
 611
 612    /// <summary>
 613    /// Attempts to construct an exact contact against a centered 3D capsule.
 614    /// </summary>
 615    public bool TryGetCenteredCapsuleContact(
 616        Vector3d capsuleCenter,
 617        FixedQuaternion capsuleRotation,
 618        Vector3d localCapsuleAxisDirection,
 619        Fixed64 capsuleAxisLength,
 620        Fixed64 capsuleRadius,
 621        out FixedContactAnchors contact)
 622    {
 241623        EnsureValid();
 241624        if (!capsuleRotation.IsNormalized())
 625        {
 1626            throw new ArgumentException(
 1627                "Capsule rotation must be normalized.",
 1628                nameof(capsuleRotation));
 629        }
 240630        if (!localCapsuleAxisDirection.IsNormalized())
 631        {
 1632            throw new ArgumentException(
 1633                "Local capsule axis direction must be normalized.",
 1634                nameof(localCapsuleAxisDirection));
 635        }
 239636        if (capsuleAxisLength < Fixed64.Zero)
 1637            throw new ArgumentOutOfRangeException(nameof(capsuleAxisLength));
 238638        if (capsuleRadius < Fixed64.Zero)
 1639            throw new ArgumentOutOfRangeException(nameof(capsuleRadius));
 237640        return WideOrientedBox.TryGetCenteredCapsuleContact(
 237641            Center,
 237642            Orientation,
 237643            HalfExtents,
 237644            capsuleCenter,
 237645            capsuleRotation,
 237646            localCapsuleAxisDirection,
 237647            capsuleAxisLength,
 237648            capsuleRadius,
 237649            out contact);
 650    }
 651
 652    /// <summary>
 653    /// Attempts to construct an exact contact against a centered finite
 654    /// cylinder.
 655    /// </summary>
 656    /// <remarks>
 657    /// Includes cap, side and rim constraints when selecting the minimum
 658    /// translation. Tangency is a contact with zero depth. The selected normal
 659    /// and complete depth are rounded only after exact classification and
 660    /// ordering; depth uses nearest-even rounding. A zero radius is a finite
 661    /// axis segment. Oblique rims require more work than parallel cap contacts.
 662    /// </remarks>
 663    public bool TryGetCenteredCylinderContact(
 664        Vector3d cylinderCenter,
 665        FixedQuaternion cylinderRotation,
 666        Vector3d localCylinderAxisDirection,
 667        Fixed64 cylinderAxisLength,
 668        Fixed64 cylinderRadius,
 669        out FixedContactAnchors contact)
 670    {
 151671        EnsureValid();
 151672        ValidateCenteredCylinder(
 151673            cylinderRotation,
 151674            localCylinderAxisDirection,
 151675            cylinderAxisLength,
 151676            cylinderRadius);
 147677        return WideOrientedBox.TryGetCenteredCylinderContact(
 147678            Center,
 147679            Orientation,
 147680            HalfExtents,
 147681            cylinderCenter,
 147682            cylinderRotation,
 147683            localCylinderAxisDirection,
 147684            cylinderAxisLength,
 147685            cylinderRadius,
 147686            out contact);
 687    }
 688
 689    /// <summary>
 690    /// Attempts to construct an exact contact against a centered finite
 691    /// cylinder and, for a parallel cap-to-face feature, clips a stable
 692    /// allocation-free contact set from the disk/rectangle intersection.
 693    /// </summary>
 694    /// <remarks>
 695    /// <paramref name="capFaceContacts"/> must provide capacity for four
 696    /// contacts. <paramref name="contact"/> always contains the primary exact
 697    /// contact relation when the shapes overlap. <paramref name="capFaceContactCount"/>
 698    /// is zero for non-cap-face features, so callers should use the primary
 699    /// contact in that case.
 700    /// </remarks>
 701    public bool TryGetCenteredCylinderContact(
 702        Vector3d cylinderCenter,
 703        FixedQuaternion cylinderRotation,
 704        Vector3d localCylinderAxisDirection,
 705        Fixed64 cylinderAxisLength,
 706        Fixed64 cylinderRadius,
 707        Span<FixedContactLocalPoints> capFaceContacts,
 708        out FixedContactAnchors contact,
 709        out int capFaceContactCount)
 710    {
 1036711        EnsureValid();
 1036712        ValidateCenteredCylinder(
 1036713            cylinderRotation,
 1036714            localCylinderAxisDirection,
 1036715            cylinderAxisLength,
 1036716            cylinderRadius);
 1036717        if (capFaceContacts.Length < 4)
 718        {
 1719            throw new ArgumentException(
 1720                "Cylinder cap-face contact output requires capacity for four contacts.",
 1721                nameof(capFaceContacts));
 722        }
 723
 1035724        if (!WideOrientedBox.TryGetCenteredCylinderContact(
 1035725                Center,
 1035726                Orientation,
 1035727                HalfExtents,
 1035728                cylinderCenter,
 1035729                cylinderRotation,
 1035730                localCylinderAxisDirection,
 1035731                cylinderAxisLength,
 1035732                cylinderRadius,
 1035733                out contact,
 1035734                out CenteredCylinderContactFeature feature))
 735        {
 8736            capFaceContactCount = 0;
 8737            return false;
 738        }
 739
 1027740        WideOrientedBox.GetCenteredCylinderCapFaceContacts(
 1027741            Center,
 1027742            Orientation,
 1027743            HalfExtents,
 1027744            cylinderCenter,
 1027745            cylinderRotation,
 1027746            localCylinderAxisDirection,
 1027747            cylinderAxisLength,
 1027748            cylinderRadius,
 1027749            contact,
 1027750            feature,
 1027751            capFaceContacts,
 1027752            out capFaceContactCount);
 1027753        return true;
 754    }
 755
 756    private static void ValidateCenteredCylinder(
 757        FixedQuaternion cylinderRotation,
 758        Vector3d localCylinderAxisDirection,
 759        Fixed64 cylinderAxisLength,
 760        Fixed64 cylinderRadius)
 761    {
 1187762        if (!cylinderRotation.IsNormalized())
 763        {
 1764            throw new ArgumentException(
 1765                "Cylinder rotation must be normalized.",
 1766                nameof(cylinderRotation));
 767        }
 1186768        if (!localCylinderAxisDirection.IsNormalized())
 769        {
 1770            throw new ArgumentException(
 1771                "Local cylinder axis direction must be normalized.",
 1772                nameof(localCylinderAxisDirection));
 773        }
 1185774        if (cylinderAxisLength <= Fixed64.Zero)
 1775            throw new ArgumentOutOfRangeException(nameof(cylinderAxisLength));
 1184776        if (cylinderRadius < Fixed64.Zero)
 1777            throw new ArgumentOutOfRangeException(nameof(cylinderRadius));
 1183778    }
 779
 780    /// <summary>
 781    /// Attempts to construct an exact box-to-sphere contact using only
 782    /// center-relative contact offsets.
 783    /// </summary>
 784    /// <param name="sphereCenter">The sphere center.</param>
 785    /// <param name="sphereRotation">
 786    /// The normalized rigid frame used to retain stable sphere-local contact
 787    /// feature identity.
 788    /// </param>
 789    /// <param name="sphereRadius">The nonnegative sphere radius.</param>
 790    /// <param name="contact">The relative contact relation when overlapping.</param>
 791    /// <returns>
 792    /// <see langword="true"/> when the conceptual shapes overlap and both
 793    /// required relative offsets are representable.
 794    /// </returns>
 795    public bool TryGetSphereContact(
 796        Vector3d sphereCenter,
 797        FixedQuaternion sphereRotation,
 798        Fixed64 sphereRadius,
 799        out FixedContactAnchors contact)
 800    {
 21801        EnsureValid();
 21802        if (!sphereRotation.IsNormalized())
 803        {
 1804            throw new ArgumentException(
 1805                "Sphere rotation must be normalized.",
 1806                nameof(sphereRotation));
 807        }
 20808        if (sphereRadius < Fixed64.Zero)
 1809            throw new ArgumentOutOfRangeException(nameof(sphereRadius));
 19810        return WideOrientedBox.TryGetSphereContact(
 19811            Center,
 19812            Orientation,
 19813            HalfExtents,
 19814            sphereCenter,
 19815            sphereRotation,
 19816            sphereRadius,
 19817            out contact);
 818    }
 819
 820    /// <summary>
 821    /// Attempts to construct an exact contact against a rotated convex polygon
 822    /// extruded through a closed world-Y slab.
 823    /// </summary>
 824    /// <param name="prismOrigin">The representable prism origin.</param>
 825    /// <param name="prismRotation">
 826    /// The prism's counterclockwise X/Z rotation in radians.
 827    /// </param>
 828    /// <param name="prismLocalOffsets">
 829    /// Ordered clockwise or counterclockwise X/Z boundary offsets in the
 830    /// prism's local frame.
 831    /// </param>
 832    /// <param name="prismHalfThickness">The positive world-Y half-thickness.</param>
 833    /// <param name="contact">The frame-relative contact anchors when overlapping.</param>
 834    public bool TryGetConvexPrismContact(
 835        Vector3d prismOrigin,
 836        Fixed64 prismRotation,
 837        ReadOnlySpan<Vector2d> prismLocalOffsets,
 838        Fixed64 prismHalfThickness,
 839        out FixedContactAnchors contact)
 840    {
 27841        EnsureValid();
 27842        if (prismLocalOffsets.Length < 3)
 843        {
 1844            throw new ArgumentException(
 1845                "A convex prism requires at least three ordered boundary offsets.",
 1846                nameof(prismLocalOffsets));
 847        }
 26848        if (prismHalfThickness <= Fixed64.Zero)
 2849            throw new ArgumentOutOfRangeException(nameof(prismHalfThickness));
 24850        return WideOrientedBox.TryGetConvexPrismContact(
 24851            Center,
 24852            Orientation,
 24853            HalfExtents,
 24854            prismOrigin,
 24855            prismRotation,
 24856            prismLocalOffsets,
 24857            prismHalfThickness,
 24858            out contact);
 859    }
 860
 861    /// <summary>
 862    /// Attempts to construct an exact contact against a vertical circular
 863    /// prism centered in X/Z and extruded through a closed world-Y slab.
 864    /// </summary>
 865    public bool TryGetCircleSlabContact(
 866        Vector3d slabCenter,
 867        Fixed64 circleFrameRotation,
 868        Fixed64 slabHalfThickness,
 869        Fixed64 radius,
 870        out FixedContactAnchors contact)
 871    {
 21872        EnsureValid();
 21873        if (slabHalfThickness <= Fixed64.Zero)
 1874            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 20875        if (radius < Fixed64.Zero)
 1876            throw new ArgumentOutOfRangeException(nameof(radius));
 19877        return WideOrientedBox.TryGetCircleSlabContact(
 19878            Center,
 19879            Orientation,
 19880            HalfExtents,
 19881            slabCenter,
 19882            circleFrameRotation,
 19883            slabHalfThickness,
 19884            radius,
 19885            out contact);
 886    }
 887
 888    /// <summary>
 889    /// Attempts to construct an exact contact against a centered planar
 890    /// capsule extruded through a closed world-Y slab.
 891    /// </summary>
 892    public bool TryGetCenteredCapsuleSlabContact(
 893        Vector3d slabCenter,
 894        Fixed64 capsuleFrameRotation,
 895        Vector2d localCapsuleAxisDirection,
 896        Fixed64 capsuleAxisLength,
 897        Fixed64 capsuleRadius,
 898        Fixed64 slabHalfThickness,
 899        out FixedContactAnchors contact)
 900    {
 16901        EnsureValid();
 16902        if (!localCapsuleAxisDirection.IsNormalized())
 903        {
 1904            throw new ArgumentException(
 1905                "Local capsule axis direction must be normalized.",
 1906                nameof(localCapsuleAxisDirection));
 907        }
 15908        if (capsuleAxisLength < Fixed64.Zero)
 1909            throw new ArgumentOutOfRangeException(nameof(capsuleAxisLength));
 14910        if (capsuleRadius < Fixed64.Zero)
 1911            throw new ArgumentOutOfRangeException(nameof(capsuleRadius));
 13912        if (slabHalfThickness <= Fixed64.Zero)
 1913            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 12914        return WideOrientedBox.TryGetCenteredCapsuleSlabContact(
 12915            Center,
 12916            Orientation,
 12917            HalfExtents,
 12918            slabCenter,
 12919            capsuleFrameRotation,
 12920            localCapsuleAxisDirection,
 12921            capsuleAxisLength,
 12922            capsuleRadius,
 12923            slabHalfThickness,
 12924            out contact);
 925    }
 926
 927    /// <summary>
 928    /// Finds the first X/Z travel distance at which a circle extruded through
 929    /// a fixed world-Y slab intersects this oriented box.
 930    /// </summary>
 931    /// <remarks>
 932    /// The slab-clipped box projection remains an internal wide rational
 933    /// relation. No box corner, slab intersection, or projected polygon must
 934    /// be representable. Tangency and start overlap are inclusive, and the
 935    /// returned distance uses round-half-to-even.
 936    /// </remarks>
 937    public bool TryGetCircleSlabSweepDistance(
 938        Vector3d slabStartCenter,
 939        Vector2d normalizedDirection,
 940        Fixed64 maxDistance,
 941        Fixed64 slabHalfThickness,
 942        Fixed64 radius,
 943        out Fixed64 distance)
 944    {
 146945        EnsureValid();
 146946        if (!normalizedDirection.IsNormalized())
 947        {
 1948            throw new ArgumentException(
 1949                "Sweep direction must be normalized.",
 1950                nameof(normalizedDirection));
 951        }
 145952        if (maxDistance < Fixed64.Zero)
 1953            throw new ArgumentOutOfRangeException(nameof(maxDistance));
 144954        if (slabHalfThickness <= Fixed64.Zero)
 1955            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 143956        if (radius < Fixed64.Zero)
 1957            throw new ArgumentOutOfRangeException(nameof(radius));
 142958        return WideOrientedBox.TryGetCircleSlabSweepDistance(
 142959            Center,
 142960            Orientation,
 142961            HalfExtents,
 142962            slabStartCenter,
 142963            normalizedDirection,
 142964            maxDistance,
 142965            slabHalfThickness,
 142966            radius,
 142967            out distance);
 968    }
 969
 970    /// <summary>
 971    /// Finds the first distance along an authored X/Z segment where a circle
 972    /// extruded through a fixed world-Y slab intersects this box.
 973    /// </summary>
 974    /// <remarks>
 975    /// The segment is retained exactly without normalizing its displacement.
 976    /// Its interval maps to [0, <paramref name="totalDistance"/>] with one
 977    /// final round-half-to-even distance conversion.
 978    /// </remarks>
 979    public bool TryGetCircleSlabSweepDistance(
 980        FixedSegment2d segment,
 981        Fixed64 totalDistance,
 982        Fixed64 slabCenterY,
 983        Fixed64 slabHalfThickness,
 984        Fixed64 radius,
 985        out Fixed64 distance)
 986    {
 150987        EnsureValid();
 150988        _ = segment.GetPointAtDistance(Fixed64.Zero, totalDistance);
 148989        if (slabHalfThickness <= Fixed64.Zero)
 1990            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 147991        if (radius < Fixed64.Zero)
 1992            throw new ArgumentOutOfRangeException(nameof(radius));
 146993        return WideOrientedBox.TryGetCircleSlabSweepDistance(
 146994            Center, Orientation, HalfExtents, segment, totalDistance,
 146995            slabCenterY, slabHalfThickness, radius, out distance);
 996    }
 997
 998    /// <summary>
 999    /// Returns a conservative X/Z separation lower bound between this box and
 1000    /// a circle extruded through a fixed world-Y slab.
 1001    /// </summary>
 1002    /// <remarks>
 1003    /// Projected edge witnesses are certified against every rational
 1004    /// half-plane. When the closest feature is a projected vertex, the result
 1005    /// uses the full-domain Euclidean distance floor before subtracting the
 1006    /// circle radius. The result never overestimates true projected
 1007    /// separation. When the Y intervals are disjoint, it returns the
 1008    /// conservative vertical interval gap instead.
 1009    /// </remarks>
 1010    public Fixed64 GetCircleSlabSeparationLowerBound(
 1011        Vector3d slabCenter,
 1012        Fixed64 slabHalfThickness,
 1013        Fixed64 radius)
 1014    {
 111015        EnsureValid();
 111016        if (slabHalfThickness <= Fixed64.Zero)
 11017            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 101018        if (radius < Fixed64.Zero)
 11019            throw new ArgumentOutOfRangeException(nameof(radius));
 91020        return WideOrientedBox.GetCircleSlabSeparationLowerBound(
 91021            Center,
 91022            Orientation,
 91023            HalfExtents,
 91024            slabCenter,
 91025            slabHalfThickness,
 91026            radius);
 1027    }
 1028}

Methods/Properties

.ctor(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
GetAxes(FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
GetLocalCorner(System.Int32)
GetLocalSupportPoint(FixedMathSharp.Vector3d)
GetBoundsClippedToDomain()
Contains(FixedMathSharp.Vector3d)
GetClosestPointAnchor(FixedMathSharp.Vector3d)
TryGetClosestPointOnSurface(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
GetNearestFaceNormal(FixedMathSharp.Vector3d)
TryMaterializeLocalPoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryTransformLocalOffset(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetSupportOffset(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetSupportDifference(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetSupportDifference(FixedMathSharp.Geometry.FixedOrientedBox,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetRayIntersectionInterval(FixedMathSharp.Geometry.FixedRay,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
op_Equality(FixedMathSharp.Geometry.FixedOrientedBox,FixedMathSharp.Geometry.FixedOrientedBox)
op_Inequality(FixedMathSharp.Geometry.FixedOrientedBox,FixedMathSharp.Geometry.FixedOrientedBox)
Equals(System.Object)
Equals(FixedMathSharp.Geometry.FixedOrientedBox)
GetHashCode()
GetAxesUnchecked(FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
EnsureValid()
HasPositiveHalfExtents(FixedMathSharp.Vector3d)
TryGetContact(FixedMathSharp.Geometry.FixedOrientedBox,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetTriangleContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetTriangleContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.FixedTriangle,System.Span`1<FixedMathSharp.Geometry.FixedContactLocalPoints>,FixedMathSharp.Geometry.FixedContactAnchors&,System.Int32&)
TryGetConvexHullContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,System.ReadOnlySpan`1<FixedMathSharp.Vector3d>,System.ReadOnlySpan`1<System.Int32>,System.ReadOnlySpan`1<System.Int32>,FixedMathSharp.Geometry.FixedContactAnchors&)
ValidateConvexHullIndices(System.Int32,System.ReadOnlySpan`1<System.Int32>,System.String)
TryGetCenteredCapsuleContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCylinderContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCylinderContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Span`1<FixedMathSharp.Geometry.FixedContactLocalPoints>,FixedMathSharp.Geometry.FixedContactAnchors&,System.Int32&)
ValidateCenteredCylinder(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetSphereContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetConvexPrismContact(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<FixedMathSharp.Vector2d>,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCircleSlabContact(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCapsuleSlabContact(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCircleSlabSweepDistance(FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetCircleSlabSweepDistance(FixedMathSharp.Geometry.FixedSegment2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
GetCircleSlabSeparationLowerBound(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)