< Summary

Information
Class: FixedMathSharp.Geometry.TriangleConeContact
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Wide/Triangles/TriangleConeContact.cs
Line coverage
100%
Covered lines: 156
Uncovered lines: 0
Coverable lines: 156
Total lines: 271
Line coverage: 100%
Branch coverage
100%
Covered branches: 98
Total branches: 98
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
TryGetContact(...)100%44100%
TryGetContact(...)100%66100%
TryGetContact(...)100%1010100%
GetAnalyticWitnesses(...)100%1212100%
TransformAnalyticNormal(...)100%11100%
KeepAnalytic(...)100%1818100%
HasFaceMinimumCertificate(...)100%1212100%
KeepAxis(...)100%1414100%
KeepDirection(...)100%2222100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Geometry/Wide/Triangles/TriangleConeContact.cs

#LineLine coverage
 1//=======================================================================
 2// TriangleConeContact.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 static FixedMathSharp.Geometry.CylinderContactAlgebra;
 10
 11namespace FixedMathSharp.Geometry;
 12
 13/// <summary>Complete finite-cone/triangle support-fan contact ownership.</summary>
 14/// <content>
 15/// Provides complete finite-triangle contacts through exact support-fan admission and matched witnesses.
 16/// </content>
 17internal static partial class TriangleConeContact
 18{
 19    internal static bool TryGetContact(FixedTriangle triangle, Vector3d origin, FixedQuaternion rotation,
 20        Vector3d center, FixedQuaternion coneRotation, Fixed64 height, Fixed64 radius,
 21        out FixedContactAnchors contact)
 22    {
 14023        contact = default;
 14024        if (triangle.IsDegenerate)
 325            return false;
 13726        if (radius == Fixed64.Zero)
 427            return TriangleCylinderContact.TryGetContact(triangle, origin, rotation, center, coneRotation,
 428                Signed192.Raw(height), radius, out contact, out _);
 13329        var geometry = new TriangleCircularGeometry(triangle, origin, rotation, center, coneRotation,
 13330            Signed192.Raw(height), radius);
 13331        return TryGetContact(triangle, geometry, origin, rotation, center, coneRotation, height, radius, out contact);
 32    }
 33
 34    internal static bool TryGetContact(FixedTriangle triangle, in ScaledRigidFrame3d sourceFrame,
 35        Vector3d center, FixedQuaternion coneRotation, Fixed64 height, Fixed64 radius,
 36        out FixedContactAnchors contact)
 37    {
 1138        if (sourceFrame.IsRigid)
 139            return TryGetContact(triangle, sourceFrame.RigidOrigin, sourceFrame.Rotation,
 140                center, coneRotation, height, radius, out contact);
 1041        contact = default;
 1042        if (radius == Fixed64.Zero)
 143            return TriangleCylinderContact.TryGetContact(triangle, sourceFrame, center, coneRotation,
 144                Signed192.Raw(height), radius, out contact, out _);
 945        var geometry = new TriangleCircularGeometry(triangle, sourceFrame, center, coneRotation, Signed192.Raw(height), 
 46        // Physical scale, rather than authored epsilon area, owns this fan.
 647        if (geometry.FaceNormal.IsZero) return false;
 448        return TryGetContact(triangle, geometry, center, coneRotation, center, coneRotation, height, radius, out contact
 49    }
 50
 51    private static bool TryGetContact(FixedTriangle triangle, in TriangleCircularGeometry geometry,
 52        Vector3d origin, FixedQuaternion rotation, Vector3d center, FixedQuaternion coneRotation,
 53        Fixed64 height, Fixed64 radius, out FixedContactAnchors contact)
 54    {
 13755        contact = default;
 13756        Span<ulong> bestValues = stackalloc ulong[ConvexContactCandidate.Slots * Words];
 13757        Span<int> bestSigns = stackalloc int[ConvexContactCandidate.Slots];
 13758        var selection = new TriangleConeContactSelection(bestValues, bestSigns);
 13759        bool faceMinimumCertified = KeepAnalytic(geometry, height, radius, ref selection);
 13760        if (selection.Separated)
 2361            return false;
 11462        var best = selection.Candidate;
 11463        bool rootWinner = false;
 34264        Vector3d normal = default, radialPoint = default, point = default;
 65        // Generator normals can mix rational and radical components;
 66        // only rational local normals use the principal-axis cancellation.
 67        Fixed64 depth;
 68        bool clamped;
 11469        if (selection.Feature == TriangleConeContactSelection.Generator)
 4070            WideConvexPrismRelations.GetRoundedConvexContactCandidateDepth(best, Fixed64.Zero, out depth, out clamped);
 71        else
 7472            geometry.GetAnalyticDepth(best, bestSigns, selection.Mask, out depth, out clamped);
 73        // Both exact face orientations bound this winner by half the cone's
 74        // width. Diameter=max(2R,sqrt(R^2+H^2))<=2*MaxValue, so it cannot clamp.
 75        // Exact orthogonal frames and the generator seam preserve this bound.
 76        System.Diagnostics.Debug.Assert(!clamped);
 11477        if (!faceMinimumCertified && !TriangleConeRimContacts.TryGetContact(geometry, triangle, height, radius, best, de
 11478                out rootWinner, out normal, out radialPoint, out point, out depth, out clamped))
 179            return false;
 80        FixedPointAnchor coneAnchor;
 11381        if (rootWinner)
 1582            coneAnchor = TriangleCircularGeometry.GetSupport(center, coneRotation, Signed192.Raw(height), radialPoint, 1
 83        else
 84        {
 9885            GetAnalyticWitnesses(geometry, triangle, height, radius, center, coneRotation,
 9886                ref selection, out normal, out point, out coneAnchor);
 87        }
 11388        contact = new FixedContactAnchors(new FixedPointAnchor(origin, rotation, point), coneAnchor,
 11389            normal, depth, clamped);
 11390        return true;
 91    }
 92
 93    private static void GetAnalyticWitnesses(in TriangleCircularGeometry geometry, FixedTriangle triangle,
 94        Fixed64 height, Fixed64 radius, Vector3d center, FixedQuaternion coneRotation,
 95        ref TriangleConeContactSelection selection, out Vector3d normal, out Vector3d point,
 96        out FixedPointAnchor coneAnchor)
 97    {
 9898        Vector3d radialPoint = default;
 99        // Ranking and rim admission use only exact gap fields. Transform
 100        // the retained analytic normal once; witnesses keep its local form.
 98101        Span<ulong> localNormal = stackalloc ulong[7 * Words];
 98102        Span<int> localSigns = stackalloc int[7];
 196103        selection.Values[..(7 * Words)].CopyTo(localNormal); selection.Signs[..7].CopyTo(localSigns);
 98104        normal = TransformAnalyticNormal(geometry, localNormal, localSigns, ref selection);
 98105        if (selection.Feature == TriangleConeContactSelection.Generator)
 40106            TriangleConeWitnesses.GetGenerator(geometry, triangle, height, radius, selection.Mask,
 40107                localNormal, localSigns, center, coneRotation, out point, out coneAnchor);
 58108        else if (selection.Feature == TriangleConeContactSelection.BasePole)
 109        {
 24110            point = TriangleCylinderWitnesses.GetCapPoint(geometry, triangle, selection.Mask, out radialPoint);
 24111            coneAnchor = TriangleCircularGeometry.GetSupport(center, coneRotation, Signed192.Raw(height), radialPoint, 1
 112        }
 113        else
 114        {
 34115            bool apex = selection.Feature == TriangleConeContactSelection.Apex;
 34116            point = TriangleCylinderRimWitnesses.GetAnalyticPoint(geometry, triangle, selection.Mask,
 34117                localNormal[..(3 * Words)], localSigns[..3], apex ? -1 : 1, apex ? default : geometry.Radius);
 34118            if (!apex)
 119            {
 120                // Scale the exact radial direction before normalization;
 121                // a sub-raw full-normal component must not erase a rim.
 42122                Slot(localNormal, 1).Clear(); localSigns[1] = 0;
 21123                var radialCandidate = new ConvexContactCandidate(localNormal, localSigns, 0);
 21124                radialPoint = -WideConvexPrismRelations.GetConvexContactCandidateScaledNormal(radialCandidate, radius);
 125            }
 34126            coneAnchor = TriangleCircularGeometry.GetSupport(center, coneRotation, Signed192.Raw(height), radialPoint, a
 127        }
 34128    }
 129
 130    private static Vector3d TransformAnalyticNormal(in TriangleCircularGeometry geometry,
 131        scoped ReadOnlySpan<ulong> localNormal, scoped ReadOnlySpan<int> localSigns, ref TriangleConeContactSelection se
 132    {
 98133        WriteWorldDirection(geometry.WorldBasis, localNormal[..(3 * Words)], localSigns[..3],
 98134            selection.Values[..(3 * Words)], selection.Signs[..3]);
 98135        WriteWorldDirection(geometry.WorldBasis, localNormal.Slice(3 * Words, 3 * Words), localSigns.Slice(3, 3),
 98136            selection.Values.Slice(3 * Words, 3 * Words), selection.Signs.Slice(3, 3));
 98137        return WideConvexPrismRelations.GetConvexContactCandidateNormal(selection.Candidate);
 138    }
 139
 140    private static bool KeepAnalytic(in TriangleCircularGeometry geometry, Fixed64 height, Fixed64 radius,
 141        ref TriangleConeContactSelection selection)
 142    {
 137143        Span<ulong> direction = stackalloc ulong[3 * Words];
 137144        Span<int> signs = stackalloc int[3];
 137145        WriteDirection(new WideDirection3d(default, Signed576.One, default), direction, signs);
 137146        KeepAxis(geometry, direction, signs, true, ref selection);
 147        // Horizontal faces duplicate the already tested +/-Up support masks
 148        // and gaps. Their equal candidates retain the earlier axial winner.
 137149        if (!geometry.FaceNormal.X.IsZero || !geometry.FaceNormal.Z.IsZero)
 150        {
 102151            WriteDirection(geometry.FaceNormal, direction, signs);
 102152            KeepAxis(geometry, direction, signs, true, ref selection);
 153        }
 152154        if (selection.Separated) return false;
 156155        if (HasFaceMinimumCertificate(geometry, selection.Candidate)) return true;
 88156        TriangleConeGeneratorFeatures.KeepAll(geometry, height, radius, ref selection);
 664157        for (int vertex = 0; vertex < 3 && !selection.Separated; vertex++)
 158        {
 488159            WideDirection3d p = geometry.Vertex(vertex), edge = geometry.Edge(vertex);
 244160            WriteDirection(new WideDirection3d(p.X, default, p.Z), direction, signs);
 244161            KeepAxis(geometry, direction, signs, false, ref selection);
 244162            WriteDirection(new WideDirection3d(WideArithmetic.Negate(edge.Z), default, edge.X), direction, signs);
 244163            KeepAxis(geometry, direction, signs, false, ref selection);
 244164            if (edge.Y.IsZero || edge.X.IsZero && edge.Z.IsZero)
 165                continue;
 212166            CircularRimContactAlgebra.GetBasis(edge, out WideDirection3d first, out WideDirection3d second);
 424167            WriteDirection(first, direction, signs); KeepAxis(geometry, direction, signs, false, ref selection);
 424168            WriteDirection(second, direction, signs); KeepAxis(geometry, direction, signs, false, ref selection);
 212169            ProjectPerpendicular(edge, new WideDirection3d(default, Signed576.One, default), direction, signs);
 212170            KeepAxis(geometry, direction, signs, false, ref selection);
 212171            ProjectPerpendicular(edge, geometry.CapOffset(vertex, 1), direction, signs);
 212172            KeepAxis(geometry, direction, signs, false, ref selection);
 173        }
 88174        return false;
 175    }
 176
 177    private static bool HasFaceMinimumCertificate(in TriangleCircularGeometry geometry, ConvexContactCandidate best)
 178    {
 179        // A cone contains its full axial segment and each base-disk diameter.
 180        // After the nonnegative +/-face tests, a face-plane radius-d disk
 181        // about the segment's projection minus that segment contains ball(d).
 182        // The retained support attains d, proving the global minimum.
 122183        if (geometry.FaceNormal.X.IsZero && geometry.FaceNormal.Z.IsZero)
 33184            return geometry.HasFaceDiskMinimumCertificate(new WideDirection3d(default, Signed576.One, default), best);
 89185        if (!geometry.FaceNormal.Y.IsZero)
 78186            return false;
 11187        WideDirection3d axis = geometry.FaceNormal.Z.IsZero
 11188            ? new WideDirection3d(Signed576.One, default, default)
 11189            : geometry.FaceNormal.X.IsZero ? new WideDirection3d(default, default, Signed576.One) : default;
 11190        if (axis.IsZero)
 3191            return false;
 192        // Translate only the certificate to the base center. Original
 193        // geometry and selection remain authoritative for paired witnesses.
 194        // Canonical axes keep tangents <231 and shifted dots <431 bits,
 195        // within the shared certificate's established fixed-carrier bounds.
 8196        TriangleCircularGeometry baseCentered = geometry.AtCoreRegion(new WideDirection3d(default, geometry.HalfHeight, 
 8197        return baseCentered.HasFaceDiskMinimumCertificate(axis, best);
 198    }
 199
 200    private static void KeepAxis(in TriangleCircularGeometry geometry, scoped Span<ulong> direction,
 201        scoped Span<int> directionSigns, bool compactGenerator, ref TriangleConeContactSelection selection)
 202    {
 1575203        if (selection.Separated || directionSigns[0] == 0 && directionSigns[1] == 0 && directionSigns[2] == 0)
 20204            return;
 9314205        for (int orientation = 0; orientation < 2 && !selection.Separated; orientation++)
 206        {
 3102207            KeepDirection(geometry, direction, directionSigns, compactGenerator, ref selection);
 34122208            for (int component = 0; component < 3; component++) directionSigns[component] = -directionSigns[component];
 209        }
 1555210    }
 211
 212    private static void KeepDirection(in TriangleCircularGeometry geometry, scoped ReadOnlySpan<ulong> direction,
 213        scoped ReadOnlySpan<int> directionSigns, bool compactGenerator, ref TriangleConeContactSelection selection)
 214    {
 3102215        Span<ulong> values = stackalloc ulong[ConvexContactCandidate.Slots * Words];
 3102216        Span<int> signs = stackalloc int[ConvexContactCandidate.Slots];
 3102217        Span<ulong> work = stackalloc ulong[10 * Words];
 9306218        Span<ulong> rational = Slot(work, 0), current = Slot(work, 1), difference = Slot(work, 2);
 9306219        Span<ulong> temporary = Slot(work, 3), radial = Slot(work, 4), radiusSquared = Slot(work, 5);
 12408220        Span<ulong> metric = Slot(work, 6), denominator = Slot(work, 7), axial = Slot(work, 8), scale = Slot(work, 9);
 6204221        values.Clear(); signs.Clear();
 6204222        direction.CopyTo(values); directionSigns.CopyTo(signs);
 3102223        Dot(geometry.A, direction, directionSigns, rational, out int rationalSign);
 3102224        int mask = 1;
 18612225        for (int vertex = 1; vertex < 3; vertex++)
 226        {
 6204227            Dot(geometry.Vertex(vertex), direction, directionSigns, current, out int currentSign);
 12408228            current.CopyTo(difference); int comparison = currentSign;
 6204229            Add(rational, -rationalSign, difference, ref comparison);
 10908230            if (comparison > 0) { current.CopyTo(rational); rationalSign = currentSign; mask = 1 << vertex; }
 6828231            else if (comparison == 0) mask |= 1 << vertex;
 232        }
 3102233        WideArithmetic.MultiplyMagnitudes(Slot(direction, 0), Slot(direction, 0), radial);
 3102234        WideArithmetic.MultiplyMagnitudes(Slot(direction, 2), Slot(direction, 2), temporary);
 3102235        WideArithmetic.AddMagnitudeInto(temporary, radial);
 3102236        Import(geometry.Radius, current);
 3102237        WideArithmetic.MultiplyMagnitudes(current, current, radiusSquared);
 3102238        WideArithmetic.MultiplyMagnitudes(radial, radiusSquared, current);
 3102239        current.CopyTo(radiusSquared);
 3102240        Import(geometry.HalfHeight, current);
 3102241        WideArithmetic.MultiplyMagnitudes(current, Slot(direction, 1), axial);
 3102242        int side = 1;
 3102243        if (directionSigns[1] < 0)
 244        {
 851245            WideArithmetic.AddEqualMagnitudes(axial, axial, current);
 851246            WideArithmetic.MultiplyMagnitudes(current, current, temporary);
 851247            side = WideArithmetic.CompareMagnitudeEqualLength(radiusSquared, temporary);
 248        }
 3102249        SumSquares(direction, metric);
 250        // Larger K=0/projected chart directions defer generator equality to
 251        // the complete fan, without building an unused normalized denominator.
 3102252        if (side == 0 && !compactGenerator)
 16253            return;
 3086254        Import(Signed576.ExtendValue(geometry.RawScale), scale);
 3086255        WideArithmetic.MultiplyMagnitudes(scale, scale, temporary);
 3086256        WideArithmetic.MultiplyMagnitudes(metric, temporary, denominator);
 3086257        if (side == 0)
 258        {
 5259            TriangleConeGeneratorFeatures.Keep(geometry, values, signs, denominator, ref selection);
 5260            return;
 261        }
 3081262        bool apex = side < 0;
 3081263        Add(axial, directionSigns[1] * (apex ? -1 : 1), rational, ref rationalSign);
 3639264        if (apex) radiusSquared.Clear();
 3081265        int gapSign = BuildRadialCandidate(rational, rationalSign, radiusSquared, denominator, values, signs);
 3081266        int feature = apex ? TriangleConeContactSelection.Apex
 3081267            : IsZero(radial) ? TriangleConeContactSelection.BasePole : TriangleConeContactSelection.BaseRim;
 3081268        selection.Keep(values, signs, gapSign, mask, feature);
 3081269    }
 270
 271}

Methods/Properties

TryGetContact(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetContact(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.ScaledRigidFrame3d&,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
TryGetContact(FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Geometry.TriangleCircularGeometry&,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedContactAnchors&)
GetAnalyticWitnesses(FixedMathSharp.Geometry.TriangleCircularGeometry&,FixedMathSharp.Geometry.FixedTriangle,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Geometry.TriangleConeContactSelection&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Geometry.FixedPointAnchor&)
TransformAnalyticNormal(FixedMathSharp.Geometry.TriangleCircularGeometry&,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.Int32>,FixedMathSharp.Geometry.TriangleConeContactSelection&)
KeepAnalytic(FixedMathSharp.Geometry.TriangleCircularGeometry&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.TriangleConeContactSelection&)
HasFaceMinimumCertificate(FixedMathSharp.Geometry.TriangleCircularGeometry&,FixedMathSharp.Geometry.ConvexContactCandidate)
KeepAxis(FixedMathSharp.Geometry.TriangleCircularGeometry&,System.Span`1<System.UInt64>,System.Span`1<System.Int32>,System.Boolean,FixedMathSharp.Geometry.TriangleConeContactSelection&)
KeepDirection(FixedMathSharp.Geometry.TriangleCircularGeometry&,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.Int32>,System.Boolean,FixedMathSharp.Geometry.TriangleConeContactSelection&)