< Summary

Information
Class: Gravitas.Queries.GjkSimplexScale
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Queries/GjkSimplexScale.cs
Line coverage
100%
Covered lines: 99
Uncovered lines: 0
Coverable lines: 99
Total lines: 254
Line coverage: 100%
Branch coverage
100%
Covered branches: 48
Total branches: 48
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Queries/GjkSimplexScale.cs

#LineLine coverage
 1//=======================================================================
 2// GjkSimplexScale.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using System;
 10using System.Runtime.CompilerServices;
 11
 12namespace Gravitas.Queries;
 13
 14/// <summary>
 15/// Uniformly scales GJK simplex coordinates before evaluating products whose
 16/// ratios and signs are invariant under a common scale.
 17/// </summary>
 18internal static class GjkSimplexScale
 19{
 120    private static readonly Fixed64 ProductSafeComponentLimit = (Fixed64)8;
 21
 22    /// <summary>
 23    /// Creates a two-term Minkowski difference in the shared GJK working
 24    /// coordinate. An exact arithmetic raw shift avoids round-to-even endpoint
 25    /// overshoot, so halving before subtraction covers every Fixed64 pair.
 26    /// </summary>
 27    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 28    public static Vector3d CreateWorkingDifference(Vector3d first, Vector3d second) =>
 129        CreateWorkingDifference(first, second, 1);
 30
 31    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 32    public static Vector3d CreateWorkingDifference(Vector3d first, Vector3d second, int shift)
 33    {
 434        ValidateShift(shift);
 235        if (Vector3d.TrySubtract(
 236            ScaleByPowerOfTwo(first, shift),
 237            ScaleByPowerOfTwo(second, shift),
 238            out Vector3d difference))
 39        {
 140            return difference;
 41        }
 42
 143        throw new InvalidOperationException("The selected GJK working shift does not preserve an exact difference.");
 44    }
 45
 46    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 47    public static Vector2d CreateWorkingDifference(Vector2d first, Vector2d second) =>
 148        CreateWorkingDifference(first, second, 2);
 49
 50    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 51    public static Vector2d CreateWorkingDifference(Vector2d first, Vector2d second, int shift)
 52    {
 31953        ValidateShift(shift);
 31754        if (Vector2d.TrySubtract(
 31755            ScaleByPowerOfTwo(first, shift),
 31756            ScaleByPowerOfTwo(second, shift),
 31757            out Vector2d difference))
 58        {
 31659            return difference;
 60        }
 61
 162        throw new InvalidOperationException("The selected GJK working shift does not preserve an exact difference.");
 63    }
 64
 65    /// <summary>
 66    /// Creates a three-term Minkowski difference in the shared GJK working
 67    /// coordinate without first forming a potentially saturated sum.
 68    /// </summary>
 69    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 70    public static Vector2d CreateWorkingDifference(Vector2d first, Vector2d second, Vector2d third) =>
 171        CreateWorkingDifference(first, second, third, 2);
 72
 73    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 74    public static Vector2d CreateWorkingDifference(
 75        Vector2d first,
 76        Vector2d second,
 77        Vector2d third,
 78        int shift)
 79    {
 96180        ValidateShift(shift);
 95981        if (Vector2d.TrySubtract(
 95982                ScaleByPowerOfTwo(first, shift),
 95983                ScaleByPowerOfTwo(second, shift),
 95984                out Vector2d difference)
 95985            && Vector2d.TrySubtract(
 95986                difference,
 95987                ScaleByPowerOfTwo(third, shift),
 95988                out Vector2d result))
 89        {
 95790            return result;
 91        }
 92
 293        throw new InvalidOperationException("The selected GJK working shift does not preserve an exact difference.");
 94    }
 95
 96    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 97    public static Fixed64 RestoreTwoTermDistance(Fixed64 workingDistance) =>
 198        RestoreDistance(workingDistance, 1);
 99
 100    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 101    public static Fixed64 RestoreThreeTermDistance(Fixed64 workingDistance) =>
 1102        RestoreDistance(workingDistance, 2);
 103
 104    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 105    public static Fixed64 RestoreDistance(Fixed64 workingDistance, int shift)
 106    {
 373107        ValidateShift(shift);
 371108        return shift == 0 ? workingDistance : workingDistance * (1 << shift);
 109    }
 110
 111    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 112    public static Fixed64 GetCoordinateScale(int shift)
 113    {
 649114        ValidateShift(shift);
 647115        return shift switch
 647116        {
 316117            0 => Fixed64.One,
 1118            1 => Fixed64.Half,
 330119            _ => Fixed64.Quarter
 647120        };
 121    }
 122
 123    public static int SelectTwoTermShift(
 124        Vector3d firstMin,
 125        Vector3d firstMax,
 126        Vector3d secondMin,
 127        Vector3d secondMax) =>
 2128        CanSubtractBounds(firstMin, firstMax, secondMin, secondMax) ? 0 : 1;
 129
 130    public static int SelectThreeTermShift(
 131        Vector2d point,
 132        Vector2d targetMin,
 133        Vector2d targetMax,
 134        Fixed64 expansionRadius)
 135    {
 321136        if (CanSubtractExpandedBounds(point, targetMin, targetMax, expansionRadius, 0))
 316137            return 0;
 138
 5139        return CanSubtractExpandedBounds(point, targetMin, targetMax, expansionRadius, 1) ? 1 : 2;
 140    }
 141
 142    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 143    private static Vector3d ScaleByPowerOfTwo(Vector3d value, int shift) =>
 4144        new(
 4145            value.X >> shift,
 4146            value.Y >> shift,
 4147            value.Z >> shift);
 148
 149    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 150    private static Vector2d ScaleByPowerOfTwo(Vector2d value, int shift) =>
 4488151        new(
 4488152            value.X >> shift,
 4488153            value.Y >> shift);
 154
 155    private static bool CanSubtractBounds(
 156        Vector3d firstMin,
 157        Vector3d firstMax,
 158        Vector3d secondMin,
 159        Vector3d secondMax) =>
 2160        Vector3d.TrySubtract(firstMax, secondMin, out _)
 2161            && Vector3d.TrySubtract(firstMin, secondMax, out _);
 162
 163    private static bool CanSubtractExpandedBounds(
 164        Vector2d point,
 165        Vector2d targetMin,
 166        Vector2d targetMax,
 167        Fixed64 expansionRadius,
 168        int shift)
 169    {
 326170        Vector2d scaledPoint = ScaleByPowerOfTwo(point, shift);
 326171        Vector2d scaledMin = ScaleByPowerOfTwo(targetMin, shift);
 326172        Vector2d scaledMax = ScaleByPowerOfTwo(targetMax, shift);
 326173        Fixed64 scaledRadius = ScaleRadiusCeiling(expansionRadius, shift);
 326174        Vector2d radius = new(scaledRadius, scaledRadius);
 175
 326176        return Vector2d.TrySubtract(scaledPoint, scaledMin, out Vector2d positiveDifference)
 326177            && Vector2d.TryAdd(positiveDifference, radius, out _)
 326178            && Vector2d.TrySubtract(scaledPoint, scaledMax, out Vector2d negativeDifference)
 326179            && Vector2d.TrySubtract(negativeDifference, radius, out _);
 180    }
 181
 182    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 183    private static Fixed64 ScaleRadiusCeiling(Fixed64 radius, int shift)
 184    {
 326185        Fixed64 scaled = radius >> shift;
 326186        if (shift == 0 || (scaled << shift) == radius)
 323187            return scaled;
 188
 3189        return scaled + Fixed64.MinIncrement;
 190    }
 191
 192    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 193    private static void ValidateShift(int shift)
 194    {
 2306195        if ((uint)shift > 2U)
 10196            throw new ArgumentOutOfRangeException(nameof(shift), "GJK coordinate shifts must be between zero and two.");
 2296197    }
 198
 199    public static Fixed64 ScaleForProducts(Span<Vector3d> points)
 200    {
 1359201        Fixed64 largestComponent = Fixed64.Zero;
 10426202        for (int i = 0; i < points.Length; i++)
 203        {
 3854204            Vector3d point = points[i];
 3854205            largestComponent = FixedMath.Max(
 3854206                largestComponent,
 3854207                FixedMath.Max(point.X.Abs(), FixedMath.Max(point.Y.Abs(), point.Z.Abs())));
 208        }
 209
 1359210        Fixed64 scale = Fixed64.One;
 2063211        while (largestComponent > ProductSafeComponentLimit)
 212        {
 704213            largestComponent *= Fixed64.Half;
 704214            scale *= Fixed64.Half;
 215        }
 216
 1359217        if (scale == Fixed64.One)
 1332218            return scale;
 219
 220        // With components <= 8, 3D simplex differences are <= 16 and the
 221        // largest tetrahedron face-side product remains inside Fixed64.
 202222        for (int i = 0; i < points.Length; i++)
 74223            points[i] *= scale;
 224
 27225        return scale;
 226    }
 227
 228    public static Fixed64 ScaleForProducts(Span<Vector2d> points)
 229    {
 1695230        Fixed64 largestComponent = Fixed64.Zero;
 10968231        for (int i = 0; i < points.Length; i++)
 232        {
 3789233            Vector2d point = points[i];
 3789234            largestComponent = FixedMath.Max(
 3789235                largestComponent,
 3789236                FixedMath.Max(point.X.Abs(), point.Y.Abs()));
 237        }
 238
 1695239        Fixed64 scale = Fixed64.One;
 2366240        while (largestComponent > ProductSafeComponentLimit)
 241        {
 671242            largestComponent *= Fixed64.Half;
 671243            scale *= Fixed64.Half;
 244        }
 245
 1695246        if (scale == Fixed64.One)
 1613247            return scale;
 248
 530249        for (int i = 0; i < points.Length; i++)
 183250            points[i] *= scale;
 251
 82252        return scale;
 253    }
 254}

Methods/Properties

.cctor()
CreateWorkingDifference(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CreateWorkingDifference(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32)
CreateWorkingDifference(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
CreateWorkingDifference(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,System.Int32)
CreateWorkingDifference(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
CreateWorkingDifference(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,System.Int32)
RestoreTwoTermDistance(FixedMathSharp.Fixed64)
RestoreThreeTermDistance(FixedMathSharp.Fixed64)
RestoreDistance(FixedMathSharp.Fixed64,System.Int32)
GetCoordinateScale(System.Int32)
SelectTwoTermShift(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
SelectThreeTermShift(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ScaleByPowerOfTwo(FixedMathSharp.Vector3d,System.Int32)
ScaleByPowerOfTwo(FixedMathSharp.Vector2d,System.Int32)
CanSubtractBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CanSubtractExpandedBounds(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Int32)
ScaleRadiusCeiling(FixedMathSharp.Fixed64,System.Int32)
ValidateShift(System.Int32)
ScaleForProducts(System.Span`1<FixedMathSharp.Vector3d>)
ScaleForProducts(System.Span`1<FixedMathSharp.Vector2d>)