< Summary

Line coverage
100%
Covered lines: 685
Uncovered lines: 0
Coverable lines: 685
Total lines: 1538
Line coverage: 100%
Branch coverage
100%
Covered branches: 160
Total branches: 160
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: get_UnnormalizedNormal()100%11100%
File 1: get_Normal()100%44100%
File 1: get_Area()100%11100%
File 1: get_Bounds()100%11100%
File 1: get_Centroid()100%11100%
File 1: get_IsDegenerate()100%11100%
File 1: GetVertex(...)100%44100%
File 1: GetEdge(...)100%44100%
File 1: GetPoint(...)100%11100%
File 1: TryGetProjectedBarycentricWeights(...)100%22100%
File 1: GetClosestPointAnchor(...)100%44100%
File 1: ContainsProjection(...)100%66100%
File 1: Contains(...)100%11100%
File 1: ClosestPoint(...)100%3232100%
File 1: DistanceSquared(...)100%11100%
File 1: Deconstruct(...)100%11100%
File 1: op_Equality(...)100%11100%
File 1: op_Inequality(...)100%11100%
File 1: Equals(...)100%44100%
File 1: Equals(...)100%22100%
File 1: GetHashCode()100%11100%
File 1: ClosestPointOnEdges(...)100%11100%
File 1: TrySetCloserPoint(...)100%22100%
File 1: GetExactNormal(...)100%11100%
File 1: GetExactNormalComponents(...)100%11100%
File 1: GetDifferenceDot(...)100%11100%
File 1: ComponentMin(...)100%11100%
File 1: ComponentMax(...)100%11100%
File 2: TryGetCircleSlabContact(...)100%11100%
File 2: TryGetCenteredCapsuleSlabContact(...)100%11100%
File 2: ValidateCapsuleSlab(...)100%88100%
File 2: TryGetClosestPointsToCenteredAxis(...)100%11100%
File 2: DoesCenteredCapsuleOverlap(...)100%22100%
File 2: TryGetCenteredCapsuleContact(...)100%11100%
File 2: ValidateCenteredCapsuleContact(...)100%44100%
File 2: ValidateCenteredAxis(...)100%44100%
File 2: TryGetFiniteConeIntersectionMinimumAxialPoint(...)100%88100%
File 2: KeepEdgeCandidate(...)100%66100%
File 2: ValidateFiniteCone(...)100%66100%
File 2: TryGetFiniteSlabProjectedCircleContact(...)100%44100%
File 2: TryGetFiniteSlabProjectedCircleSweep(...)100%88100%
File 3: TryGetContact(...)100%44100%
File 4: TryGetCenteredFiniteCylinderSupportContact(...)100%11100%
File 4: TryGetCenteredFiniteConeSupportContact(...)100%11100%
File 4: TryGetCenteredCapsuleContact(...)100%44100%
File 4: TryGetSphereContact(...)100%66100%
File 4: ValidateCenteredSurfaceContact(...)100%66100%
File 4: TryGetSphereContactInChart(...)100%1212100%
File 4: TryGetCenteredCapsuleContactInChart(...)100%66100%
File 4: TryGetSupportContact(...)100%44100%
File 4: ValidateRigidTriangleFrame(...)100%22100%
File 4: ValidateRigidShapeFrame(...)100%22100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/Triangles/FixedTriangle.cs

#LineLine coverage
 1//=======================================================================
 2// FixedTriangle.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 an ordered triangle in three-dimensional fixed-point space.
 17/// </summary>
 18/// <remarks>
 19/// Triangle queries preserve complete raw-coordinate differences and exact wide
 20/// predicates until their final public <see cref="Fixed64"/> conversion.
 21/// </remarks>
 22[Serializable]
 23[MemoryPackable]
 24public partial struct FixedTriangle : IEquatable<FixedTriangle>
 25{
 26    #region Constants
 27
 28    /// <summary>
 29    /// The number of vertices in a triangle.
 30    /// </summary>
 31    public const int VertexCount = 3;
 32
 33    /// <summary>
 34    /// The number of edges in a triangle.
 35    /// </summary>
 36    public const int EdgeCount = 3;
 37
 38    #endregion
 39
 40    #region Fields
 41
 42    /// <summary>
 43    /// The first vertex.
 44    /// </summary>
 45    [JsonInclude]
 46    [MemoryPackOrder(0)]
 47    public Vector3d A;
 48
 49    /// <summary>
 50    /// The second vertex.
 51    /// </summary>
 52    [JsonInclude]
 53    [MemoryPackOrder(1)]
 54    public Vector3d B;
 55
 56    /// <summary>
 57    /// The third vertex.
 58    /// </summary>
 59    [JsonInclude]
 60    [MemoryPackOrder(2)]
 61    public Vector3d C;
 62
 63    #endregion
 64
 65    #region Constructors
 66
 67    /// <summary>
 68    /// Initializes a triangle from ordered vertices.
 69    /// </summary>
 70    [JsonConstructor]
 71    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 72    public FixedTriangle(Vector3d a, Vector3d b, Vector3d c)
 73    {
 41674        A = a;
 41675        B = b;
 41676        C = c;
 41677    }
 78
 79    #endregion
 80
 81    #region Properties
 82
 83    /// <summary>
 84    /// The unnormalized triangle normal from <c>cross(B - A, C - A)</c>.
 85    /// </summary>
 86    /// <remarks>
 87    /// Each exact cross component is rounded once, half to even, and saturates
 88    /// independently at the public Q32.32 boundary.
 89    /// </remarks>
 90    [JsonIgnore]
 91    [MemoryPackIgnore]
 92    public Vector3d UnnormalizedNormal
 93    {
 94        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 95        get
 96        {
 997            GetExactNormalComponents(out Signed192 x, out Signed192 y, out Signed192 z);
 998            return new Vector3d(
 999                Fixed64.RoundSignedToFixed(x, FixedMath.SHIFT_AMOUNT_I),
 9100                Fixed64.RoundSignedToFixed(y, FixedMath.SHIFT_AMOUNT_I),
 9101                Fixed64.RoundSignedToFixed(z, FixedMath.SHIFT_AMOUNT_I));
 102        }
 103    }
 104
 105    /// <summary>
 106    /// The normalized triangle normal derived from the exact cross product.
 107    /// </summary>
 108    /// <remarks>
 109    /// Components are rounded half to even from the exact squared magnitude.
 110    /// Triangles at or below the inclusive degeneracy threshold return
 111    /// <see cref="Vector3d.Zero"/>.
 112    /// </remarks>
 113    [JsonIgnore]
 114    [MemoryPackIgnore]
 115    public Vector3d Normal
 116    {
 117        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118        get
 119        {
 21120            GetExactNormalComponents(out Signed192 x, out Signed192 y, out Signed192 z);
 21121            Signed320 squaredMagnitude = WideGeometry.GetSquaredMagnitude(
 21122                x,
 21123                y,
 21124                z,
 21125                out Signed320 xSquare,
 21126                out Signed320 ySquare,
 21127                out Signed320 zSquare);
 21128            if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(squaredMagnitude))
 5129                return Vector3d.Zero;
 130
 16131            Signed192 magnitude = WideArithmetic.GetFloorSquareRoot(squaredMagnitude, out Signed192 remainder);
 16132            Signed192 ceilingMagnitude = remainder.IsZero
 16133                ? magnitude
 16134                : WideArithmetic.AddSigned192(magnitude, Signed192.Signed(1L));
 16135            return new Vector3d(
 16136                Fixed64.NormalizeWideComponent(x, xSquare, ceilingMagnitude, squaredMagnitude),
 16137                Fixed64.NormalizeWideComponent(y, ySquare, ceilingMagnitude, squaredMagnitude),
 16138                Fixed64.NormalizeWideComponent(z, zSquare, ceilingMagnitude, squaredMagnitude));
 139        }
 140    }
 141
 142    /// <summary>
 143    /// The non-negative surface area of the triangle.
 144    /// </summary>
 145    /// <remarks>
 146    /// The exact cross-product magnitude is halved and rounded once, half to
 147    /// even. Results beyond the positive Q32.32 range saturate to
 148    /// <see cref="Fixed64.MaxValue"/>.
 149    /// </remarks>
 150    [JsonIgnore]
 151    [MemoryPackIgnore]
 152    public Fixed64 Area
 153    {
 154        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 155        get
 156        {
 16157            GetExactNormal(out _, out _, out _, out Signed320 squaredMagnitude);
 16158            Signed192 root = WideArithmetic.GetFloorSquareRoot(squaredMagnitude, out Signed192 remainder);
 16159            return Fixed64.RoundSquareRootToFixed(root, remainder, FixedMath.SHIFT_AMOUNT_I + 1);
 160        }
 161    }
 162
 163    /// <summary>
 164    /// The normalized axis-aligned box that contains all vertices.
 165    /// </summary>
 166    [JsonIgnore]
 167    [MemoryPackIgnore]
 168    public FixedBoundBox Bounds
 169    {
 170        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2171        get => FixedBoundBox.FromMinMax(ComponentMin(ComponentMin(A, B), C), ComponentMax(ComponentMax(A, B), C));
 172    }
 173
 174    /// <summary>
 175    /// The arithmetic center of the three vertices, rounded half to even per component.
 176    /// </summary>
 177    [JsonIgnore]
 178    [MemoryPackIgnore]
 179    public Vector3d Centroid
 180    {
 181        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 82182        get => new(
 82183            FixedMath.Average(A.X, B.X, C.X),
 82184            FixedMath.Average(A.Y, B.Y, C.Y),
 82185            FixedMath.Average(A.Z, B.Z, C.Z));
 186    }
 187
 188    /// <summary>
 189    /// Returns true when the exact squared normal magnitude is at or below
 190    /// the inclusive <see cref="Fixed64.Epsilon"/> threshold.
 191    /// </summary>
 192    [JsonIgnore]
 193    [MemoryPackIgnore]
 194    public bool IsDegenerate
 195    {
 196        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 197        get
 198        {
 324199            GetExactNormal(out _, out _, out _, out Signed320 squaredMagnitude);
 324200            return WideGeometry.IsQ128MagnitudeAtMostEpsilon(squaredMagnitude);
 201        }
 202    }
 203
 204    #endregion
 205
 206    #region Geometry Access
 207
 208    /// <summary>
 209    /// Gets a vertex by stable index: 0 = A, 1 = B, 2 = C.
 210    /// </summary>
 211    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 212    public Vector3d GetVertex(int index) =>
 5213        index switch
 5214        {
 1215            0 => A,
 1216            1 => B,
 1217            2 => C,
 2218            _ => throw new ArgumentOutOfRangeException(nameof(index), $"Vertex index must be between 0 and {VertexCount 
 5219        };
 220
 221    /// <summary>
 222    /// Gets an edge by stable index: 0 = AB, 1 = BC, 2 = CA.
 223    /// </summary>
 224    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 225    public FixedSegment GetEdge(int index) =>
 145226        index switch
 145227        {
 50228            0 => new FixedSegment(A, B),
 47229            1 => new FixedSegment(B, C),
 46230            2 => new FixedSegment(C, A),
 2231            _ => throw new ArgumentOutOfRangeException(nameof(index), $"Edge index must be between 0 and {EdgeCount - 1}
 145232        };
 233
 234    /// <summary>
 235    /// Gets the point represented by barycentric weights for vertices B and C
 236    /// without saturating intermediate differences.
 237    /// </summary>
 238    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 239    public Vector3d GetPoint(Fixed64 weightB, Fixed64 weightC) =>
 111240        Vector3d.BarycentricCoordinates(A, B, C, weightB, weightC);
 241
 242    #endregion
 243
 244    #region Spatial Queries
 245
 246    /// <summary>
 247    /// Computes barycentric weights for the point projected onto this triangle's plane.
 248    /// </summary>
 249    /// <returns>
 250    /// True when the exact Gram denominator is outside the inclusive
 251    /// <see cref="Fixed64.Epsilon"/> failure threshold; otherwise false with
 252    /// all three weights set to zero.
 253    /// </returns>
 254    /// <remarks>
 255    /// Successful weights are computed from independent exact numerators, then
 256    /// rounded half to even and saturated independently at the public boundary.
 257    /// The point is projected onto the triangle plane; it need not lie on it.
 258    /// </remarks>
 259    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 260    public bool TryGetProjectedBarycentricWeights(Vector3d point, out Fixed64 weightA, out Fixed64 weightB, out Fixed64 
 261    {
 22262        Signed192 abAb = GetDifferenceDot(B, A, B, A);
 22263        Signed192 abAc = GetDifferenceDot(B, A, C, A);
 22264        Signed192 acAc = GetDifferenceDot(C, A, C, A);
 22265        Signed192 apAb = GetDifferenceDot(point, A, B, A);
 22266        Signed192 apAc = GetDifferenceDot(point, A, C, A);
 22267        Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc);
 268
 22269        if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator))
 270        {
 3271            weightA = Fixed64.Zero;
 3272            weightB = Fixed64.Zero;
 3273            weightC = Fixed64.Zero;
 3274            return false;
 275        }
 276
 19277        Signed320 numeratorB = WideArithmetic.MultiplySubtract(acAc, apAb, abAc, apAc);
 19278        Signed320 numeratorC = WideArithmetic.MultiplySubtract(abAb, apAc, abAc, apAb);
 19279        Signed320 numeratorA = WideArithmetic.SubtractSigned320(
 19280            WideArithmetic.SubtractSigned320(denominator, numeratorB),
 19281            numeratorC);
 19282        weightA = Fixed64.GetSignedRatio(numeratorA, denominator);
 19283        weightB = Fixed64.GetSignedRatio(numeratorB, denominator);
 19284        weightC = Fixed64.GetSignedRatio(numeratorC, denominator);
 19285        return true;
 286    }
 287
 288    /// <summary>
 289    /// Gets the closest point on this triangle to a point carried by another
 290    /// rigid frame without materializing either absolute world point.
 291    /// </summary>
 292    /// <remarks>
 293    /// The returned anchor remains in the triangle's rigid frame. Voronoi
 294    /// predicates retain the complete relative-frame displacement until the
 295    /// final local barycentric conversion.
 296    /// </remarks>
 297    public FixedPointAnchor GetClosestPointAnchor(
 298        Vector3d triangleOrigin,
 299        FixedQuaternion triangleRotation,
 300        in FixedPointAnchor point)
 301    {
 300302        if (!triangleRotation.IsNormalized())
 303        {
 1304            throw new ArgumentException(
 1305                "Triangle rotation must be normalized.",
 1306                nameof(triangleRotation));
 307        }
 299308        if (!point.Rotation.IsNormalized())
 309        {
 1310            throw new ArgumentException(
 1311                "Point rotation must be normalized.",
 1312                nameof(point));
 313        }
 314
 298315        return WideTriangleRelations.GetClosestPointAnchor(
 298316            this,
 298317            triangleOrigin,
 298318            triangleRotation,
 298319            point);
 320    }
 321
 322    /// <summary>
 323    /// Determines whether the supplied point projects within or onto this
 324    /// triangle without requiring the point to lie on its plane.
 325    /// </summary>
 326    /// <remarks>
 327    /// The projected barycentric signs are classified from exact wide
 328    /// numerators. Degenerate triangles have no projected face and return false.
 329    /// </remarks>
 330    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 331    public bool ContainsProjection(Vector3d point)
 332    {
 33333        Signed192 abAb = GetDifferenceDot(B, A, B, A);
 33334        Signed192 abAc = GetDifferenceDot(B, A, C, A);
 33335        Signed192 acAc = GetDifferenceDot(C, A, C, A);
 33336        Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc);
 33337        if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator))
 1338            return false;
 339
 32340        Signed192 apAb = GetDifferenceDot(point, A, B, A);
 32341        Signed192 apAc = GetDifferenceDot(point, A, C, A);
 32342        Signed320 numeratorB = WideArithmetic.MultiplySubtract(acAc, apAb, abAc, apAc);
 32343        if (numeratorB.Sign < 0)
 1344            return false;
 345
 31346        Signed320 numeratorC = WideArithmetic.MultiplySubtract(abAb, apAc, abAc, apAb);
 31347        if (numeratorC.Sign < 0)
 2348            return false;
 349
 29350        return WideArithmetic.SubtractSigned320(
 29351            WideArithmetic.SubtractSigned320(denominator, numeratorB),
 29352            numeratorC).Sign >= 0;
 353    }
 354
 355    /// <summary>
 356    /// Determines whether the point is within the inclusive squared-distance
 357    /// epsilon of this triangle.
 358    /// </summary>
 359    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 360    public bool Contains(Vector3d point)
 361    {
 20362        return DistanceSquared(point) <= Fixed64.Epsilon;
 363    }
 364
 365    /// <summary>
 366    /// Finds the closest point on or inside this triangle to the supplied point.
 367    /// </summary>
 368    /// <remarks>
 369    /// Voronoi-region predicates and degenerate edge distances use exact wide
 370    /// intermediates. Degenerate candidates are visited in AB, BC, CA order,
 371    /// and an exact distance tie retains the first candidate.
 372    /// </remarks>
 373    public Vector3d ClosestPoint(Vector3d point)
 374    {
 67375        Signed192 abAb = GetDifferenceDot(B, A, B, A);
 67376        Signed192 abAc = GetDifferenceDot(B, A, C, A);
 67377        Signed192 acAc = GetDifferenceDot(C, A, C, A);
 67378        Signed320 denominator = WideArithmetic.MultiplySubtract(abAb, acAc, abAc, abAc);
 67379        if (WideGeometry.IsQ128MagnitudeAtMostEpsilon(denominator))
 16380            return ClosestPointOnEdges(point);
 381
 51382        Signed192 d1 = GetDifferenceDot(B, A, point, A);
 51383        Signed192 d2 = GetDifferenceDot(C, A, point, A);
 51384        if (d1.Sign <= 0 && d2.Sign <= 0)
 3385            return A;
 386
 48387        Signed192 d3 = WideArithmetic.SubtractSigned192(d1, abAb);
 48388        Signed192 d4 = WideArithmetic.SubtractSigned192(d2, abAc);
 48389        if (d3.Sign >= 0 && WideArithmetic.SubtractSigned192(d4, d3).Sign <= 0)
 1390            return B;
 391
 47392        Signed320 vc = WideArithmetic.MultiplySubtract(d1, d4, d3, d2);
 47393        if (vc.Sign <= 0 && d1.Sign >= 0 && d3.Sign <= 0)
 394        {
 4395            _ = Fixed64.TryGetUnitIntervalRatio(
 4396                d1,
 4397                WideArithmetic.SubtractSigned192(d1, d3),
 4398                out Fixed64 parameter);
 4399            return Vector3d.Lerp(A, B, parameter);
 400        }
 401
 43402        Signed192 d5 = WideArithmetic.SubtractSigned192(d1, abAc);
 43403        Signed192 d6 = WideArithmetic.SubtractSigned192(d2, acAc);
 43404        if (d6.Sign >= 0 && WideArithmetic.SubtractSigned192(d5, d6).Sign <= 0)
 1405            return C;
 406
 42407        Signed320 vb = WideArithmetic.MultiplySubtract(d5, d2, d1, d6);
 42408        if (vb.Sign <= 0 && d2.Sign >= 0 && d6.Sign <= 0)
 409        {
 2410            _ = Fixed64.TryGetUnitIntervalRatio(
 2411                d2,
 2412                WideArithmetic.SubtractSigned192(d2, d6),
 2413                out Fixed64 parameter);
 2414            return Vector3d.Lerp(A, C, parameter);
 415        }
 416
 40417        Signed320 va = WideArithmetic.MultiplySubtract(d3, d6, d5, d4);
 40418        Signed192 d4MinusD3 = WideArithmetic.SubtractSigned192(d4, d3);
 40419        Signed192 d5MinusD6 = WideArithmetic.SubtractSigned192(d5, d6);
 40420        if (va.Sign <= 0 && d4MinusD3.Sign >= 0 && d5MinusD6.Sign >= 0)
 421        {
 21422            _ = Fixed64.TryGetUnitIntervalRatio(
 21423                d4MinusD3,
 21424                WideArithmetic.AddSigned192(d4MinusD3, d5MinusD6),
 21425                out Fixed64 parameter);
 21426            return Vector3d.Lerp(B, C, parameter);
 427        }
 428
 19429        _ = Fixed64.TryGetUnitIntervalRatio(vb, denominator, out Fixed64 weightB);
 19430        _ = Fixed64.TryGetUnitIntervalRatio(vc, denominator, out Fixed64 weightC);
 19431        return GetPoint(weightB, weightC);
 432    }
 433
 434    /// <summary>
 435    /// Computes the squared distance from the supplied point to this triangle,
 436    /// rounded once and positively saturated at the public boundary.
 437    /// </summary>
 438    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 439    public Fixed64 DistanceSquared(Vector3d point)
 440    {
 28441        Vector3d closest = ClosestPoint(point);
 28442        Signed192 squaredDistance = GetDifferenceDot(point, closest, point, closest);
 28443        return Fixed64.RoundSquaredDistance(squaredDistance);
 444    }
 445
 446    #endregion
 447
 448    #region Deconstruction
 449
 450    /// <summary>
 451    /// Deconstructs the triangle into ordered vertices.
 452    /// </summary>
 453    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 454    public void Deconstruct(out Vector3d a, out Vector3d b, out Vector3d c)
 455    {
 1456        a = A;
 1457        b = B;
 1458        c = C;
 1459    }
 460
 461    #endregion
 462
 463    #region Operators
 464
 465    /// <summary>
 466    /// Determines whether two triangles have the same ordered vertices.
 467    /// </summary>
 468    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2469    public static bool operator ==(FixedTriangle left, FixedTriangle right) => left.Equals(right);
 470
 471    /// <summary>
 472    /// Determines whether two triangles have different ordered vertices.
 473    /// </summary>
 474    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2475    public static bool operator !=(FixedTriangle left, FixedTriangle right) => !left.Equals(right);
 476
 477    #endregion
 478
 479    #region Equality
 480
 481    /// <inheritdoc />
 482    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 483    public bool Equals(FixedTriangle other)
 484    {
 8485        return A == other.A && B == other.B && C == other.C;
 486    }
 487
 488    /// <inheritdoc />
 489    public override bool Equals(object? obj)
 490    {
 2491        return obj is FixedTriangle other && Equals(other);
 492    }
 493
 494    /// <inheritdoc />
 495    public override int GetHashCode()
 496    {
 497        unchecked
 498        {
 2499            int hash = 17;
 2500            hash = (hash * 31) + A.StateHash;
 2501            hash = (hash * 31) + B.StateHash;
 2502            hash = (hash * 31) + C.StateHash;
 2503            return hash;
 504        }
 505    }
 506
 507    #endregion
 508
 509    #region Helpers
 510
 511    private Vector3d ClosestPointOnEdges(Vector3d point)
 512    {
 16513        Vector3d best = GetEdge(0).ClosestPoint(point);
 16514        TrySetCloserPoint(GetEdge(1), point, ref best);
 16515        TrySetCloserPoint(GetEdge(2), point, ref best);
 16516        return best;
 517    }
 518
 519    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 520    private static void TrySetCloserPoint(FixedSegment edge, Vector3d point, ref Vector3d best)
 521    {
 32522        Vector3d candidate = edge.ClosestPoint(point);
 32523        if (WideGeometry.CompareSquaredDistance3D(
 32524            point.X, candidate.X, point.Y, candidate.Y, point.Z, candidate.Z,
 32525            point.X, best.X, point.Y, best.Y, point.Z, best.Z) >= 0)
 26526            return;
 527
 6528        best = candidate;
 6529    }
 530
 531    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 532    internal void GetExactNormal(
 533        out Signed192 x,
 534        out Signed192 y,
 535        out Signed192 z,
 536        out Signed320 squaredMagnitude)
 537    {
 542538        GetExactNormalComponents(out x, out y, out z);
 542539        squaredMagnitude = WideGeometry.GetSquaredMagnitude(x, y, z, out _, out _, out _);
 542540    }
 541
 542    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 543    private void GetExactNormalComponents(
 544        out Signed192 x,
 545        out Signed192 y,
 546        out Signed192 z)
 547    {
 572548        WideGeometry.GetDifferenceCrossProduct3D(
 572549            B.X, A.X, B.Y, A.Y, B.Z, A.Z,
 572550            C.X, A.X, C.Y, A.Y, C.Z, A.Z,
 572551            out x,
 572552            out y,
 572553            out z);
 572554    }
 555
 556    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 557    private static Signed192 GetDifferenceDot(
 558        Vector3d leftEnd,
 559        Vector3d leftStart,
 560        Vector3d rightEnd,
 561        Vector3d rightStart) =>
 604562        WideGeometry.GetDifferenceDotProduct3D(
 604563            leftEnd.X, leftStart.X, leftEnd.Y, leftStart.Y, leftEnd.Z, leftStart.Z,
 604564            rightEnd.X, rightStart.X, rightEnd.Y, rightStart.Y, rightEnd.Z, rightStart.Z);
 565
 566    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 567    private static Vector3d ComponentMin(Vector3d a, Vector3d b)
 568    {
 4569        return new Vector3d(FixedMath.Min(a.X, b.X), FixedMath.Min(a.Y, b.Y), FixedMath.Min(a.Z, b.Z));
 570    }
 571
 572    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 573    private static Vector3d ComponentMax(Vector3d a, Vector3d b)
 574    {
 4575        return new Vector3d(FixedMath.Max(a.X, b.X), FixedMath.Max(a.Y, b.Y), FixedMath.Max(a.Z, b.Z));
 576    }
 577
 578    #endregion
 579}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/Triangles/FixedTriangle.FiniteShapeQueries.cs

#LineLine coverage
 1//=======================================================================
 2// FixedTriangle.FiniteShapeQueries.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 finite-axis, finite-cone, and finite-slab contact and sweep
 14/// queries for rigidly transformed triangles.
 15/// </content>
 16public partial struct FixedTriangle
 17{
 18    /// <summary>
 19    /// Attempts to construct canonical contact anchors between this rigidly
 20    /// transformed triangle and one vertical circle slab.
 21    /// </summary>
 22    public readonly bool TryGetCircleSlabContact(
 23        Vector3d triangleOrigin,
 24        FixedQuaternion triangleRotation,
 25        Vector3d slabCenter,
 26        Fixed64 circleFrameRotation,
 27        Fixed64 slabHalfThickness,
 28        Fixed64 circleRadius,
 29        out FixedContactAnchors contact)
 30    {
 4631        ValidateRigidTriangleFrame(triangleRotation);
 4632        ValidateCapsuleSlab(
 4633            Vector2d.Right,
 4634            Fixed64.Zero,
 4635            circleRadius,
 4636            slabHalfThickness);
 4437        return WideOrientedBox.TryGetTriangleCapsuleSlabContact(
 4438            triangleOrigin,
 4439            triangleRotation,
 4440            this,
 4441            slabCenter,
 4442            circleFrameRotation,
 4443            Vector2d.Right,
 4444            Fixed64.Zero,
 4445            circleRadius,
 4446            slabHalfThickness,
 4447            out contact);
 48    }
 49
 50    /// <summary>
 51    /// Attempts to construct canonical contact anchors between this rigidly
 52    /// transformed triangle and one vertical centered-capsule slab.
 53    /// </summary>
 54    public readonly bool TryGetCenteredCapsuleSlabContact(
 55        Vector3d triangleOrigin,
 56        FixedQuaternion triangleRotation,
 57        Vector3d slabCenter,
 58        Fixed64 capsuleFrameRotation,
 59        Vector2d localCapsuleAxisDirection,
 60        Fixed64 capsuleAxisLength,
 61        Fixed64 capsuleRadius,
 62        Fixed64 slabHalfThickness,
 63        out FixedContactAnchors contact)
 64    {
 4065        ValidateRigidTriangleFrame(triangleRotation);
 4066        ValidateCapsuleSlab(
 4067            localCapsuleAxisDirection,
 4068            capsuleAxisLength,
 4069            capsuleRadius,
 4070            slabHalfThickness);
 3871        return WideOrientedBox.TryGetTriangleCapsuleSlabContact(
 3872            triangleOrigin,
 3873            triangleRotation,
 3874            this,
 3875            slabCenter,
 3876            capsuleFrameRotation,
 3877            localCapsuleAxisDirection,
 3878            capsuleAxisLength,
 3879            capsuleRadius,
 3880            slabHalfThickness,
 3881            out contact);
 82    }
 83
 84    private static void ValidateCapsuleSlab(
 85        Vector2d localAxisDirection,
 86        Fixed64 axisLength,
 87        Fixed64 radius,
 88        Fixed64 halfThickness)
 89    {
 8690        if (!localAxisDirection.IsNormalized())
 91        {
 192            throw new ArgumentException(
 193                "Capsule axis direction must be normalized.",
 194                nameof(localAxisDirection));
 95        }
 8596        if (axisLength < Fixed64.Zero)
 197            throw new ArgumentOutOfRangeException(nameof(axisLength));
 8498        if (radius < Fixed64.Zero)
 199            throw new ArgumentOutOfRangeException(nameof(radius));
 83100        if (halfThickness <= Fixed64.Zero)
 1101            throw new ArgumentOutOfRangeException(nameof(halfThickness));
 82102    }
 103
 104    /// <summary>
 105    /// Attempts to return the closest points on this triangle and a conceptual
 106    /// centered finite axis without materializing its endpoints.
 107    /// </summary>
 108    public readonly bool TryGetClosestPointsToCenteredAxis(
 109        Vector3d center,
 110        Vector3d axisDirection,
 111        Fixed64 axisLength,
 112        out Vector3d pointOnTriangle,
 113        out Vector3d pointOnAxis)
 114    {
 7115        ValidateCenteredAxis(axisDirection, axisLength);
 7116        return WideFiniteAxisIntersection.TryGetClosestPointsToCenteredAxis(
 7117            this,
 7118            center,
 7119            axisDirection,
 7120            axisLength,
 7121            out pointOnTriangle,
 7122            out pointOnAxis);
 123    }
 124
 125    /// <summary>
 126    /// Returns whether this triangle inclusively overlaps a conceptual centered
 127    /// capsule without materializing either contact witness.
 128    /// </summary>
 129    public readonly bool DoesCenteredCapsuleOverlap(
 130        Vector3d center,
 131        Vector3d axisDirection,
 132        Fixed64 axisLength,
 133        Fixed64 radius)
 134    {
 4135        ValidateCenteredAxis(axisDirection, axisLength);
 2136        if (radius < Fixed64.Zero)
 1137            throw new ArgumentOutOfRangeException(nameof(radius));
 138
 1139        return WideFiniteAxisIntersection.DoesCenteredCapsuleTriangleOverlap(
 1140            this,
 1141            center,
 1142            axisDirection,
 1143            axisLength,
 1144            radius);
 145    }
 146
 147    /// <summary>
 148    /// Attempts to create an inclusive contact between this triangle and a
 149    /// conceptual centered capsule.
 150    /// </summary>
 151    /// <remarks>
 152    /// The returned normal points from the triangle toward the capsule.
 153    /// <paramref name="fallbackNormal"/> is used only when the closest axis
 154    /// and triangle points coincide. A false result means that the contact is
 155    /// separated or that at least one final output is not representable; use
 156    /// <see cref="DoesCenteredCapsuleOverlap"/> when classification must remain
 157    /// independent of witness materialization.
 158    /// </remarks>
 159    public readonly bool TryGetCenteredCapsuleContact(
 160        Vector3d center,
 161        Vector3d axisDirection,
 162        Fixed64 axisLength,
 163        Fixed64 radius,
 164        Vector3d fallbackNormal,
 165        out Vector3d pointOnTriangle,
 166        out Vector3d pointOnCapsule,
 167        out Vector3d normal,
 168        out Fixed64 depth)
 169    {
 6170        ValidateCenteredCapsuleContact(
 6171            axisDirection,
 6172            axisLength,
 6173            radius,
 6174            fallbackNormal);
 175
 4176        return WideFiniteAxisIntersection.TryGetCenteredCapsuleTriangleContact(
 4177            this,
 4178            center,
 4179            axisDirection,
 4180            axisLength,
 4181            radius,
 4182            fallbackNormal,
 4183            out pointOnTriangle,
 4184            out pointOnCapsule,
 4185            out normal,
 4186            out depth);
 187    }
 188
 189    private static void ValidateCenteredCapsuleContact(
 190        Vector3d axisDirection,
 191        Fixed64 axisLength,
 192        Fixed64 radius,
 193        Vector3d fallbackNormal)
 194    {
 11195        ValidateCenteredAxis(axisDirection, axisLength);
 11196        if (radius < Fixed64.Zero)
 1197            throw new ArgumentOutOfRangeException(nameof(radius));
 10198        if (!fallbackNormal.IsNormalized())
 1199            throw new ArgumentException("Fallback normal must be normalized.", nameof(fallbackNormal));
 9200    }
 201
 202    private static void ValidateCenteredAxis(
 203        Vector3d axisDirection,
 204        Fixed64 axisLength)
 205    {
 22206        if (!axisDirection.IsNormalized())
 1207            throw new ArgumentException("Axis direction must be normalized.", nameof(axisDirection));
 21208        if (axisLength < Fixed64.Zero)
 1209            throw new ArgumentOutOfRangeException(nameof(axisLength));
 20210    }
 211
 212    /// <summary>
 213    /// Finds the intersecting point with the smallest axial parameter in an
 214    /// apex-authored finite cone.
 215    /// </summary>
 216    /// <remarks>
 217    /// Boundary candidates are visited in AB, BC, CA, face order. Triangle-face
 218    /// roots use a <see cref="Fixed64.MaxValue"/>-scaled parameter lattice;
 219    /// exact wide predicates select the witness before one final point rounding.
 220    /// </remarks>
 221    public bool TryGetFiniteConeIntersectionMinimumAxialPoint(
 222        Vector3d apex,
 223        Vector3d apexToBaseDirection,
 224        Fixed64 height,
 225        Fixed64 baseRadius,
 226        out Vector3d point)
 227    {
 29228        ValidateFiniteCone(
 29229            apexToBaseDirection,
 29230            height,
 29231            baseRadius,
 29232            nameof(apexToBaseDirection),
 29233            nameof(height));
 234
 26235        bool found = false;
 26236        Vector3d bestPoint = default;
 26237        Fixed64 bestAxial = Fixed64.MaxValue;
 26238        KeepEdgeCandidate(GetEdge(0), apex, apexToBaseDirection, height, baseRadius, ref found, ref bestPoint, ref bestA
 26239        KeepEdgeCandidate(GetEdge(1), apex, apexToBaseDirection, height, baseRadius, ref found, ref bestPoint, ref bestA
 26240        KeepEdgeCandidate(GetEdge(2), apex, apexToBaseDirection, height, baseRadius, ref found, ref bestPoint, ref bestA
 241
 26242        GetExactNormal(out Signed192 normalX, out Signed192 normalY, out Signed192 normalZ, out Signed320 normalSquared)
 26243        if (!WideGeometry.IsQ128MagnitudeAtMostEpsilon(normalSquared)
 26244            && WideTriangleConeIntersection.TryGetFaceMinimumAxialPoint(
 26245                this,
 26246                normalX,
 26247                normalY,
 26248                normalZ,
 26249                normalSquared,
 26250                apex,
 26251                apexToBaseDirection,
 26252                height,
 26253                baseRadius,
 26254                found,
 26255                out Vector3d facePoint,
 26256                out Fixed64 faceAxial)
 26257            && (!found || faceAxial < bestAxial))
 258        {
 11259            found = true;
 11260            bestPoint = facePoint;
 261        }
 262
 26263        point = bestPoint;
 26264        return found;
 265    }
 266
 267    private static void KeepEdgeCandidate(
 268        FixedSegment edge,
 269        Vector3d apex,
 270        Vector3d axisDirection,
 271        Fixed64 height,
 272        Fixed64 baseRadius,
 273        ref bool found,
 274        ref Vector3d bestPoint,
 275        ref Fixed64 bestAxial)
 276    {
 78277        if (!edge.TryGetFiniteConeIntersectionMinimumAxialPoint(
 78278                apex,
 78279                axisDirection,
 78280                height,
 78281                baseRadius,
 78282                out Vector3d candidate))
 283        {
 63284            return;
 285        }
 286
 15287        Fixed64 axial = FixedMath.Min(
 15288            Vector3d.ProjectNonNegativeDifferenceParameter(candidate, apex, axisDirection),
 15289            height);
 15290        if (found && axial >= bestAxial)
 8291            return;
 292
 7293        found = true;
 7294        bestPoint = candidate;
 7295        bestAxial = axial;
 7296    }
 297
 298    private static void ValidateFiniteCone(
 299        Vector3d axisDirection,
 300        Fixed64 height,
 301        Fixed64 baseRadius,
 302        string axisParameterName,
 303        string heightParameterName)
 304    {
 29305        if (!axisDirection.IsNormalized())
 1306            throw new ArgumentException("Finite cone axis direction must be normalized.", axisParameterName);
 28307        if (height <= Fixed64.Zero)
 1308            throw new ArgumentOutOfRangeException(heightParameterName);
 27309        if (baseRadius < Fixed64.Zero)
 1310            throw new ArgumentOutOfRangeException(nameof(baseRadius));
 26311    }
 312
 313    /// <summary>
 314    /// Attempts to find a triangle witness where a world-X/Z circle overlaps
 315    /// the triangle portion inside one finite world-Y slab.
 316    /// </summary>
 317    public readonly bool TryGetFiniteSlabProjectedCircleContact(
 318        Vector3d triangleOrigin,
 319        FixedQuaternion triangleRotation,
 320        Vector2d circleCenter,
 321        Fixed64 circleRadius,
 322        Fixed64 slabCenterY,
 323        Fixed64 slabHalfThickness,
 324        out FixedPointAnchor triangleContact)
 325    {
 8326        ValidateRigidTriangleFrame(triangleRotation);
 7327        if (circleRadius < Fixed64.Zero)
 1328            throw new ArgumentOutOfRangeException(nameof(circleRadius));
 6329        if (slabHalfThickness < Fixed64.Zero)
 1330            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 331
 5332        return WideOrientedBox.TryGetFiniteSlabProjectedCircleSweep(
 5333            this,
 5334            triangleOrigin,
 5335            triangleRotation,
 5336            circleCenter,
 5337            Vector2d.Right,
 5338            Fixed64.Zero,
 5339            circleRadius,
 5340            slabCenterY,
 5341            slabHalfThickness,
 5342            out _,
 5343            out triangleContact);
 344    }
 345
 346    /// <summary>
 347    /// Finds the first distance where a circle swept in world X/Z reaches the
 348    /// triangle portion inside one finite world-Y slab.
 349    /// </summary>
 350    /// <remarks>
 351    /// Triangle transformation, slab clipping, and time-of-impact
 352    /// classification remain exact until the final Q32.32 distance and local
 353    /// triangle witness are rounded.
 354    /// </remarks>
 355    public readonly bool TryGetFiniteSlabProjectedCircleSweep(
 356        Vector3d triangleOrigin,
 357        FixedQuaternion triangleRotation,
 358        Vector2d circleStart,
 359        Vector2d normalizedDirection,
 360        Fixed64 maximumDistance,
 361        Fixed64 circleRadius,
 362        Fixed64 slabCenterY,
 363        Fixed64 slabHalfThickness,
 364        out Fixed64 distance,
 365        out FixedPointAnchor triangleContact)
 366    {
 65367        ValidateRigidTriangleFrame(triangleRotation);
 65368        if (!normalizedDirection.IsNormalized())
 369        {
 1370            throw new ArgumentException(
 1371                "Sweep direction must be normalized.",
 1372                nameof(normalizedDirection));
 373        }
 64374        if (maximumDistance < Fixed64.Zero)
 1375            throw new ArgumentOutOfRangeException(nameof(maximumDistance));
 63376        if (circleRadius < Fixed64.Zero)
 1377            throw new ArgumentOutOfRangeException(nameof(circleRadius));
 62378        if (slabHalfThickness < Fixed64.Zero)
 1379            throw new ArgumentOutOfRangeException(nameof(slabHalfThickness));
 380
 61381        return WideOrientedBox.TryGetFiniteSlabProjectedCircleSweep(
 61382            this,
 61383            triangleOrigin,
 61384            triangleRotation,
 61385            circleStart,
 61386            normalizedDirection,
 61387            maximumDistance,
 61388            circleRadius,
 61389            slabCenterY,
 61390            slabHalfThickness,
 61391            out distance,
 61392            out triangleContact);
 393    }
 394}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/Triangles/FixedTriangle.PairContacts.cs

#LineLine coverage
 1//=======================================================================
 2// FixedTriangle.PairContacts.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 exact full-domain contact generation between two rigidly
 14/// transformed triangles.
 15/// </content>
 16public partial struct FixedTriangle
 17{
 18    /// <summary>
 19    /// Attempts to construct an exact full-domain contact relation between
 20    /// this triangle and <paramref name="second"/> in their respective rigid
 21    /// frames.
 22    /// </summary>
 23    /// <remarks>
 24    /// Exact touching is a contact with zero depth. The normal points from the
 25    /// first triangle toward the second, and each returned anchor remains in
 26    /// its input frame. The winning depth is rounded half to even once; an
 27    /// unrepresentable positive depth is returned as <see cref="Fixed64.MaxValue"/>
 28    /// with <see cref="FixedContactAnchors.DepthIsClamped"/> set.
 29    /// </remarks>
 30    /// <returns>
 31    /// <see langword="false"/> when either triangle has an exact-zero normal or
 32    /// when a separating axis has negative overlap; otherwise
 33    /// <see langword="true"/>.
 34    /// </returns>
 35    /// <exception cref="ArgumentException">
 36    /// <paramref name="firstRotation"/> is not normalized.
 37    /// </exception>
 38    /// <exception cref="ArgumentException">
 39    /// <paramref name="secondRotation"/> is not normalized.
 40    /// </exception>
 41    public readonly bool TryGetContact(
 42        Vector3d firstOrigin,
 43        FixedQuaternion firstRotation,
 44        Vector3d secondOrigin,
 45        FixedQuaternion secondRotation,
 46        FixedTriangle second,
 47        out FixedContactAnchors contact)
 48    {
 9049        if (!firstRotation.IsNormalized())
 50        {
 151            throw new ArgumentException(
 152                "The first triangle rotation must be normalized.",
 153                nameof(firstRotation));
 54        }
 8955        if (!secondRotation.IsNormalized())
 56        {
 157            throw new ArgumentException(
 158                "The second triangle rotation must be normalized.",
 159                nameof(secondRotation));
 60        }
 61
 8862        return WideTriangleRelations.TryGetContact(
 8863            this,
 8864            firstOrigin,
 8865            firstRotation,
 8866            second,
 8867            secondOrigin,
 8868            secondRotation,
 8869            out contact);
 70    }
 71}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Primitives/Triangles/FixedTriangle.RigidContacts.cs

#LineLine coverage
 1//=======================================================================
 2// FixedTriangle.RigidContacts.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/// Rigid-frame contact generation for <see cref="FixedTriangle"/> against
 14/// centered convex shapes (cylinders, cones, etc.) that are transformed by
 15/// their own position/rotation. Validates that the supplied triangle and
 16/// shape frames are rigid (uniform, non-degenerate) before computing a
 17/// support point on the shape and projecting/clamping it onto the triangle
 18/// to produce a <see cref="FixedContactAnchors"/> result.
 19/// </content>
 20public partial struct FixedTriangle
 21{
 22    /// <summary>
 23    /// Attempts to project one centered finite-cylinder support onto this
 24    /// rigidly transformed triangle.
 25    /// </summary>
 26    public readonly bool TryGetCenteredFiniteCylinderSupportContact(
 27        Vector3d triangleOrigin,
 28        FixedQuaternion triangleRotation,
 29        Vector3d cylinderCenter,
 30        FixedQuaternion cylinderRotation,
 31        Fixed64 cylinderHeight,
 32        Fixed64 cylinderRadius,
 33        Vector3d supportDirection,
 34        Vector3d normalTriangleToCylinder,
 35        out FixedContactAnchors contact)
 36    {
 1237        ValidateRigidTriangleFrame(triangleRotation);
 1238        ValidateRigidShapeFrame(cylinderRotation, nameof(cylinderRotation));
 1239        ValidateCenteredSurfaceContact(
 1240            cylinderHeight,
 1241            cylinderRadius,
 1242            normalTriangleToCylinder,
 1243            nameof(cylinderHeight));
 944        FixedPointAnchor support =
 945            WideGeometry.GetCenteredCylinderSupportAnchor(
 946                cylinderCenter,
 947                cylinderRotation,
 948                cylinderHeight,
 949                cylinderRadius,
 950                supportDirection);
 951        return TryGetSupportContact(
 952            this,
 953            triangleOrigin,
 954            triangleRotation,
 955            support,
 956            normalTriangleToCylinder,
 957            out contact);
 58    }
 59
 60    /// <summary>
 61    /// Attempts to project one centered finite-cone support onto this rigidly
 62    /// transformed triangle.
 63    /// </summary>
 64    public readonly bool TryGetCenteredFiniteConeSupportContact(
 65        Vector3d triangleOrigin,
 66        FixedQuaternion triangleRotation,
 67        Vector3d coneCenter,
 68        FixedQuaternion coneRotation,
 69        Fixed64 coneHeight,
 70        Fixed64 coneRadius,
 71        Vector3d supportDirection,
 72        Vector3d normalTriangleToCone,
 73        out FixedContactAnchors contact)
 74    {
 275        ValidateRigidTriangleFrame(triangleRotation);
 276        ValidateRigidShapeFrame(coneRotation, nameof(coneRotation));
 277        ValidateCenteredSurfaceContact(
 278            coneHeight,
 279            coneRadius,
 280            normalTriangleToCone,
 281            nameof(coneHeight));
 282        FixedPointAnchor support =
 283            WideGeometry.GetCenteredConeSupportAnchor(
 284                coneCenter,
 285                coneRotation,
 286                coneHeight,
 287                coneRadius,
 288                supportDirection);
 289        return TryGetSupportContact(
 290            this,
 291            triangleOrigin,
 292            triangleRotation,
 293            support,
 294            normalTriangleToCone,
 295            out contact);
 96    }
 97
 98    /// <summary>
 99    /// Attempts to construct an exact contact between this rigidly transformed
 100    /// triangle and a centered capsule.
 101    /// </summary>
 102    /// <remarks>
 103    /// The returned triangle witness remains in the supplied triangle frame.
 104    /// The capsule witness remains in the supplied capsule frame so callers can
 105    /// retain stable rigid-frame feature identity independently of world pose.
 106    /// </remarks>
 107    public readonly bool TryGetCenteredCapsuleContact(
 108        Vector3d triangleOrigin,
 109        FixedQuaternion triangleRotation,
 110        Vector3d capsuleCenter,
 111        FixedQuaternion capsuleRotation,
 112        Fixed64 capsuleAxisLength,
 113        Fixed64 capsuleRadius,
 114        Vector3d fallbackNormal,
 115        out FixedContactAnchors contact)
 116    {
 6117        ValidateRigidTriangleFrame(triangleRotation);
 5118        ValidateRigidShapeFrame(capsuleRotation, nameof(capsuleRotation));
 5119        Vector3d capsuleAxisDirection =
 5120            capsuleRotation.Rotate(Vector3d.Up).Normalized;
 5121        ValidateCenteredCapsuleContact(
 5122            capsuleAxisDirection,
 5123            capsuleAxisLength,
 5124            capsuleRadius,
 5125            fallbackNormal);
 126
 5127        var capsuleCenterAnchor = new FixedPointAnchor(
 5128            capsuleCenter,
 5129            FixedQuaternion.Identity,
 5130            Vector3d.Zero);
 5131        FixedQuaternion inverseTriangleRotation =
 5132            triangleRotation.Inverse();
 5133        if (capsuleCenterAnchor.TryGetLocalPointIn(
 5134                triangleOrigin,
 5135                triangleRotation,
 5136                out Vector3d localCapsuleCenter))
 137        {
 3138            return TryGetCenteredCapsuleContactInChart(
 3139                this,
 3140                localCapsuleCenter,
 3141                inverseTriangleRotation.Rotate(capsuleAxisDirection),
 3142                capsuleAxisLength,
 3143                capsuleRadius,
 3144                inverseTriangleRotation.Rotate(fallbackNormal),
 3145                triangleOrigin,
 3146                triangleRotation,
 3147                capsuleCenter,
 3148                capsuleRotation,
 3149                capsuleAxisDirection,
 3150                chartIsTriangleLocal: true,
 3151                out contact);
 152        }
 153
 2154        FixedPointAnchor first = new(
 2155            triangleOrigin,
 2156            triangleRotation,
 2157            A);
 2158        FixedPointAnchor second = new(
 2159            triangleOrigin,
 2160            triangleRotation,
 2161            B);
 2162        FixedPointAnchor third = new(
 2163            triangleOrigin,
 2164            triangleRotation,
 2165            C);
 2166        bool triangleRepresentable =
 2167            first.TryGetPoint(out Vector3d worldFirst)
 2168            & second.TryGetPoint(out Vector3d worldSecond)
 2169            & third.TryGetPoint(out Vector3d worldThird);
 2170        if (!triangleRepresentable)
 171        {
 1172            contact = default;
 1173            return false;
 174        }
 175
 1176        return TryGetCenteredCapsuleContactInChart(
 1177            new FixedTriangle(worldFirst, worldSecond, worldThird),
 1178            capsuleCenter,
 1179            capsuleAxisDirection,
 1180            capsuleAxisLength,
 1181            capsuleRadius,
 1182            fallbackNormal,
 1183            triangleOrigin,
 1184            triangleRotation,
 1185            capsuleCenter,
 1186            capsuleRotation,
 1187            capsuleAxisDirection,
 1188            chartIsTriangleLocal: false,
 1189            out contact);
 190    }
 191
 192    /// <summary>
 193    /// Attempts to construct an exact contact between this rigidly transformed
 194    /// triangle and a sphere.
 195    /// </summary>
 196    /// <remarks>
 197    /// The relation selects whichever exact coordinate chart can represent the
 198    /// interacting features. The returned triangle witness always remains in
 199    /// the supplied triangle frame.
 200    /// </remarks>
 201    public readonly bool TryGetSphereContact(
 202        Vector3d triangleOrigin,
 203        FixedQuaternion triangleRotation,
 204        Vector3d sphereCenter,
 205        FixedQuaternion sphereRotation,
 206        Fixed64 sphereRadius,
 207        out FixedContactAnchors contact)
 208    {
 9209        ValidateRigidTriangleFrame(triangleRotation);
 8210        ValidateRigidShapeFrame(sphereRotation, nameof(sphereRotation));
 7211        if (sphereRadius < Fixed64.Zero)
 1212            throw new ArgumentOutOfRangeException(nameof(sphereRadius));
 213
 6214        var sphereCenterAnchor = new FixedPointAnchor(
 6215            sphereCenter,
 6216            FixedQuaternion.Identity,
 6217            Vector3d.Zero);
 6218        if (sphereCenterAnchor.TryGetLocalPointIn(
 6219                triangleOrigin,
 6220                triangleRotation,
 6221                out Vector3d localSphereCenter))
 222        {
 4223            return TryGetSphereContactInChart(
 4224                this,
 4225                localSphereCenter,
 4226                sphereCenter,
 4227                sphereRotation,
 4228                sphereRadius,
 4229                triangleOrigin,
 4230                triangleRotation,
 4231                chartIsTriangleLocal: true,
 4232                out contact);
 233        }
 234
 2235        FixedPointAnchor first = new(
 2236            triangleOrigin,
 2237            triangleRotation,
 2238            A);
 2239        FixedPointAnchor second = new(
 2240            triangleOrigin,
 2241            triangleRotation,
 2242            B);
 2243        FixedPointAnchor third = new(
 2244            triangleOrigin,
 2245            triangleRotation,
 2246            C);
 2247        bool triangleRepresentable =
 2248            first.TryGetPoint(out Vector3d worldFirst)
 2249            & second.TryGetPoint(out Vector3d worldSecond)
 2250            & third.TryGetPoint(out Vector3d worldThird);
 2251        if (!triangleRepresentable)
 252        {
 1253            contact = default;
 1254            return false;
 255        }
 256
 1257        return TryGetSphereContactInChart(
 1258            new FixedTriangle(worldFirst, worldSecond, worldThird),
 1259            sphereCenter,
 1260            sphereCenter,
 1261            sphereRotation,
 1262            sphereRadius,
 1263            triangleOrigin,
 1264            triangleRotation,
 1265            chartIsTriangleLocal: false,
 1266            out contact);
 267    }
 268
 269    private static void ValidateCenteredSurfaceContact(
 270        Fixed64 height,
 271        Fixed64 radius,
 272        Vector3d normal,
 273        string heightParameterName)
 274    {
 14275        if (height <= Fixed64.Zero)
 1276            throw new ArgumentOutOfRangeException(heightParameterName);
 13277        if (radius < Fixed64.Zero)
 1278            throw new ArgumentOutOfRangeException(nameof(radius));
 12279        if (!normal.IsNormalized())
 1280            throw new ArgumentException("Triangle contact normal must be normalized.", nameof(normal));
 11281    }
 282
 283    private static bool TryGetSphereContactInChart(
 284        FixedTriangle triangle,
 285        Vector3d chartSphereCenter,
 286        Vector3d worldSphereCenter,
 287        FixedQuaternion sphereRotation,
 288        Fixed64 sphereRadius,
 289        Vector3d triangleOrigin,
 290        FixedQuaternion triangleRotation,
 291        bool chartIsTriangleLocal,
 292        out FixedContactAnchors contact)
 293    {
 5294        Vector3d pointOnTriangle = triangle.ClosestPoint(chartSphereCenter);
 5295        if (!Vector3d.TryGetDistance(
 5296                chartSphereCenter,
 5297                pointOnTriangle,
 5298                out Fixed64 distance)
 5299            || distance > sphereRadius)
 300        {
 1301            contact = default;
 1302            return false;
 303        }
 304
 305        Vector3d chartNormal;
 4306        if (distance > Fixed64.Epsilon)
 307        {
 2308            _ = Vector3d.TrySubtract(
 2309                chartSphereCenter,
 2310                pointOnTriangle,
 2311                out Vector3d separation);
 2312            chartNormal = separation / distance;
 313        }
 314        else
 315        {
 2316            chartNormal = triangle.Normal;
 2317            if (Vector3d.CompareProjection(
 2318                    chartSphereCenter,
 2319                    triangle.Centroid,
 2320                    chartNormal) < 0)
 321            {
 1322                chartNormal = -chartNormal;
 323            }
 324        }
 325
 4326        Vector3d worldNormal = chartIsTriangleLocal
 4327            ? triangleRotation.Rotate(chartNormal).Normalized
 4328            : chartNormal;
 4329        Vector3d triangleLocalPoint = pointOnTriangle;
 4330        if (!chartIsTriangleLocal)
 331        {
 332            // A closest point of the materialized world triangle is a convex
 333            // combination of the representable source-local vertices.
 1334            _ = new FixedPointAnchor(
 1335                    pointOnTriangle,
 1336                    FixedQuaternion.Identity,
 1337                    Vector3d.Zero)
 1338                .TryGetLocalPointIn(
 1339                    triangleOrigin,
 1340                    triangleRotation,
 1341                    out triangleLocalPoint);
 342        }
 343
 4344        contact = new FixedContactAnchors(
 4345            new FixedPointAnchor(
 4346                triangleOrigin,
 4347                triangleRotation,
 4348                triangleLocalPoint),
 4349            new FixedPointAnchor(
 4350                worldSphereCenter,
 4351                sphereRotation,
 4352                sphereRotation.Inverse().Rotate(-worldNormal) * sphereRadius),
 4353            worldNormal,
 4354            sphereRadius - distance,
 4355            depthIsClamped: false);
 4356        return true;
 357    }
 358
 359    private static bool TryGetCenteredCapsuleContactInChart(
 360        FixedTriangle triangle,
 361        Vector3d chartCapsuleCenter,
 362        Vector3d chartCapsuleAxis,
 363        Fixed64 capsuleAxisLength,
 364        Fixed64 capsuleRadius,
 365        Vector3d chartFallbackNormal,
 366        Vector3d triangleOrigin,
 367        FixedQuaternion triangleRotation,
 368        Vector3d worldCapsuleCenter,
 369        FixedQuaternion capsuleRotation,
 370        Vector3d worldCapsuleAxis,
 371        bool chartIsTriangleLocal,
 372        out FixedContactAnchors contact)
 373    {
 4374        if (!WideFiniteAxisIntersection
 4375            .TryGetCenteredCapsuleTriangleLocalContact(
 4376                triangle,
 4377                chartCapsuleCenter,
 4378                chartCapsuleAxis,
 4379                capsuleAxisLength,
 4380                capsuleRadius,
 4381                chartFallbackNormal,
 4382                out Vector3d pointOnTriangle,
 4383                out Fixed64 axisParameter,
 4384                out Vector3d chartNormal,
 4385                out Fixed64 depth,
 4386                out bool depthIsClamped))
 387        {
 1388            contact = default;
 1389            return false;
 390        }
 391
 3392        Vector3d worldNormal = chartIsTriangleLocal
 3393            ? triangleRotation.Rotate(chartNormal).Normalized
 3394            : chartNormal;
 3395        Vector3d triangleLocalPoint = pointOnTriangle;
 3396        if (!chartIsTriangleLocal)
 397        {
 1398            _ = new FixedPointAnchor(
 1399                    pointOnTriangle,
 1400                    FixedQuaternion.Identity,
 1401                    Vector3d.Zero)
 1402                .TryGetLocalPointIn(
 1403                    triangleOrigin,
 1404                    triangleRotation,
 1405                    out triangleLocalPoint);
 406        }
 407
 3408        Vector3d capsuleLocalNormal =
 3409            capsuleRotation.Inverse().Rotate(worldNormal);
 3410        contact = new FixedContactAnchors(
 3411            new FixedPointAnchor(
 3412                triangleOrigin,
 3413                triangleRotation,
 3414                triangleLocalPoint),
 3415            new FixedPointAnchor(
 3416                worldCapsuleCenter,
 3417                capsuleRotation,
 3418                new Vector3d(
 3419                    Fixed64.Zero,
 3420                    axisParameter,
 3421                    Fixed64.Zero),
 3422                -capsuleLocalNormal * capsuleRadius),
 3423            worldNormal,
 3424            depth,
 3425            depthIsClamped);
 3426        return true;
 427    }
 428
 429    private static bool TryGetSupportContact(
 430        FixedTriangle triangle,
 431        Vector3d triangleOrigin,
 432        FixedQuaternion triangleRotation,
 433        in FixedPointAnchor support,
 434        Vector3d worldNormal,
 435        out FixedContactAnchors contact)
 436    {
 11437        if (!support.TryGetLocalPointIn(
 11438                triangleOrigin,
 11439                triangleRotation,
 11440                out Vector3d localSupport))
 441        {
 1442            contact = default;
 1443            return false;
 444        }
 445
 10446        Vector3d localNormal =
 10447            triangleRotation.Inverse().Rotate(worldNormal);
 10448        if (!WideFiniteAxisIntersection.TryGetTriangleSupportPointContact(
 10449                triangle,
 10450                localSupport,
 10451                localNormal,
 10452                out Vector3d pointOnTriangle,
 10453                out Fixed64 depth,
 10454                out bool depthIsClamped))
 455        {
 5456            contact = default;
 5457            return false;
 458        }
 459
 5460        contact = new FixedContactAnchors(
 5461            new FixedPointAnchor(
 5462                triangleOrigin,
 5463                triangleRotation,
 5464                pointOnTriangle),
 5465            support,
 5466            worldNormal,
 5467            depth,
 5468            depthIsClamped);
 5469        return true;
 470    }
 471
 472    private static void ValidateRigidTriangleFrame(
 473        FixedQuaternion triangleRotation)
 474    {
 188475        if (!triangleRotation.IsNormalized())
 476        {
 3477            throw new ArgumentException(
 3478                "The triangle rotation must be normalized.",
 3479                nameof(triangleRotation));
 480        }
 185481    }
 482
 483    private static void ValidateRigidShapeFrame(
 484        FixedQuaternion rotation,
 485        string parameterName)
 486    {
 27487        if (!rotation.IsNormalized())
 488        {
 1489            throw new ArgumentException(
 1490                "The shape rotation must be normalized.",
 1491                parameterName);
 492        }
 26493    }
 494}

Methods/Properties

.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
get_UnnormalizedNormal()
get_Normal()
get_Area()
get_Bounds()
get_Centroid()
get_IsDegenerate()
GetVertex(System.Int32)
GetEdge(System.Int32)
GetPoint(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetProjectedBarycentricWeights(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
GetClosestPointAnchor(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.FixedPointAnchor&)
ContainsProjection(FixedMathSharp.Vector3d)
Contains(FixedMathSharp.Vector3d)
ClosestPoint(FixedMathSharp.Vector3d)
DistanceSquared(FixedMathSharp.Vector3d)
Deconstruct(FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
op_Equality(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.FixedTriangle)
op_Inequality(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.FixedTriangle)
Equals(FixedMathSharp.Geometry.FixedTriangle)
Equals(System.Object)
GetHashCode()
ClosestPointOnEdges(FixedMathSharp.Vector3d)
TrySetCloserPoint(FixedMathSharp.Geometry.FixedSegment,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
GetExactNormal(FixedMathSharp.Signed192&,FixedMathSharp.Signed192&,FixedMathSharp.Signed192&,FixedMathSharp.Signed320&)
GetExactNormalComponents(FixedMathSharp.Signed192&,FixedMathSharp.Signed192&,FixedMathSharp.Signed192&)
GetDifferenceDot(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ComponentMin(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ComponentMax(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryGetCircleSlabContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCapsuleSlabContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
ValidateCapsuleSlab(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetClosestPointsToCenteredAxis(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
DoesCenteredCapsuleOverlap(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetCenteredCapsuleContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
ValidateCenteredCapsuleContact(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
ValidateCenteredAxis(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
TryGetFiniteConeIntersectionMinimumAxialPoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
KeepEdgeCandidate(FixedMathSharp.Geometry.FixedSegment,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
ValidateFiniteCone(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.String,System.String)
TryGetFiniteSlabProjectedCircleContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedPointAnchor&)
TryGetFiniteSlabProjectedCircleSweep(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Geometry.FixedPointAnchor&)
TryGetContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredFiniteCylinderSupportContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredFiniteConeSupportContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCapsuleContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetSphereContact(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
ValidateCenteredSurfaceContact(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.String)
TryGetSphereContactInChart(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,System.Boolean,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetCenteredCapsuleContactInChart(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,System.Boolean,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetSupportContact(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Vector3d,FixedMathSharp.Geometry.FixedContactAnchors&)
ValidateRigidTriangleFrame(FixedMathSharp.FixedQuaternion)
ValidateRigidShapeFrame(FixedMathSharp.FixedQuaternion,System.String)