< Summary

Information
Class: Gravitas.Colliders.ColliderScalePolicy
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/ColliderScalePolicy.cs
Line coverage
100%
Covered lines: 125
Uncovered lines: 0
Coverable lines: 125
Total lines: 249
Line coverage: 100%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CapturePlanar(...)100%44100%
Capture(...)100%22100%
CaptureScale(...)100%22100%
ValidateAncestry(...)100%22100%
TryExtractPlanarTransform(...)100%66100%
Validate(...)100%11100%
Validate(...)100%11100%
IsPositive(...)100%22100%
IsPositive(...)100%44100%
Scale(...)100%11100%
Scale(...)100%11100%
Scale(...)100%11100%
Scale(...)100%11100%
ScalePositive(...)100%11100%
ScalePositive(...)100%11100%
ScalePositive(...)100%11100%
ScalePositive(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/ColliderScalePolicy.cs

#LineLine coverage
 1//=======================================================================
 2// ColliderScalePolicy.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;
 9
 10namespace Gravitas.Colliders;
 11
 12/// <summary>
 13/// Owns Gravitas's physical-scale admission boundary. FixedTransform permits
 14/// signed and zero authored scale; collider geometry requires positive authored
 15/// ancestry and positive resulting world dimensions on every axis it consumes.
 16/// </summary>
 17internal static class ColliderScalePolicy
 18{
 19    internal static Vector2d CapturePlanar(
 20        FixedTransform transform,
 21        out Fixed4x4 worldMatrix,
 22        out Fixed64 worldRotation)
 23    {
 3568324        SwiftThrowHelper.ThrowIfNull(transform, nameof(transform));
 3568325        FixedTransform? current = transform;
 7139826        while (current != null)
 27        {
 3572728            Validate(current.LocalScaleXZ);
 3571529            current = current.Parent;
 30        }
 31
 3567132        Vector2d worldScale = default;
 3567133        worldRotation = default;
 3567134        bool captured = transform.TryGetLocalToWorldMatrix(out worldMatrix)
 3567135            && TryExtractPlanarTransform(
 3567136                worldMatrix,
 3567137                out worldScale,
 3567138                out worldRotation);
 3567139        SwiftThrowHelper.ThrowIfArgument(
 3567140            !captured,
 3567141            nameof(transform),
 3567142            "2D collider transform hierarchy must compose to a representable, nonsheared X/Z transform.");
 3566643        Validate(worldScale);
 3566644        return worldScale;
 45    }
 46
 47    internal static Vector3d Capture(
 48        FixedTransform transform,
 49        out Fixed4x4 worldMatrix,
 50        out FixedQuaternion worldRotation)
 51    {
 192552        SwiftThrowHelper.ThrowIfNull(transform, nameof(transform));
 192553        ValidateAncestry(transform);
 54
 191155        Vector3d worldScale = default;
 191156        worldRotation = default;
 191157        bool captured = transform.TryGetLocalToWorldMatrix(out worldMatrix)
 191158            && Fixed4x4.Decompose(
 191159                worldMatrix,
 191160                out _,
 191161                out worldRotation,
 191162                out worldScale);
 191163        SwiftThrowHelper.ThrowIfArgument(
 191164            !captured,
 191165            nameof(transform),
 191166            "Collider transform hierarchy must compose to a representable, nonsheared transform.");
 190967        Validate(worldScale);
 190968        return worldScale;
 69    }
 70
 71    internal static Vector3d CaptureScale(FixedTransform transform)
 72    {
 7290073        ValidateAncestry(transform);
 7289574        if (transform.Parent == null)
 7288475            return transform.LocalScale;
 76
 1177        Vector3d worldScale = default;
 1178        bool capturedMatrix =
 1179            transform.TryGetLocalToWorldMatrix(out Fixed4x4 worldMatrix);
 1180        bool capturedScale = Fixed4x4.Decompose(
 1181            worldMatrix,
 1182            out _,
 1183            out _,
 1184            out worldScale);
 1185        SwiftThrowHelper.ThrowIfArgument(
 1186            !(capturedMatrix & capturedScale),
 1187            nameof(transform),
 1188            "Collider transform hierarchy must compose to a representable, nonsheared transform.");
 889        Validate(worldScale);
 890        return worldScale;
 91    }
 92
 93    private static void ValidateAncestry(FixedTransform transform)
 94    {
 7482595        FixedTransform? current = transform;
 14966896        while (current != null)
 97        {
 7486298            Validate(current.LocalScale);
 7484399            current = current.Parent;
 100        }
 74806101    }
 102
 103    private static bool TryExtractPlanarTransform(
 104        Fixed4x4 matrix,
 105        out Vector2d scale,
 106        out Fixed64 rotation)
 107    {
 108        // A planar collider requires a block-diagonal X/Z and Y basis. Checking
 109        // both directions prevents scale extraction from silently discarding
 110        // hierarchy coupling that happens not to move local Y=0 points.
 35670111        if ((matrix.M12 != Fixed64.Zero)
 35670112            | (matrix.M21 != Fixed64.Zero)
 35670113            | (matrix.M23 != Fixed64.Zero)
 35670114            | (matrix.M32 != Fixed64.Zero))
 115        {
 2116            scale = Vector2d.Zero;
 2117            rotation = Fixed64.Zero;
 2118            return false;
 119        }
 120
 35668121        Fixed4x4 planarMatrix = Fixed4x4.Identity;
 35668122        planarMatrix.M11 = matrix.M11;
 35668123        planarMatrix.M13 = matrix.M13;
 35668124        planarMatrix.M31 = matrix.M31;
 35668125        planarMatrix.M33 = matrix.M33;
 35668126        if (!Fixed4x4.Decompose(
 35668127                planarMatrix,
 35668128                out _,
 35668129                out FixedQuaternion planarRotation,
 35668130                out Vector3d planarScale)
 35668131            || planarScale.X <= Fixed64.Zero)
 132        {
 2133            scale = Vector2d.Zero;
 2134            rotation = Fixed64.Zero;
 2135            return false;
 136        }
 137
 35666138        scale = planarScale.ToVector2d();
 35666139        Vector3d right = planarRotation.Rotate(Vector3d.Right);
 35666140        rotation = PlanarRotation.Canonicalize(
 35666141            FixedMath.Atan2(right.Z, right.X));
 35666142        return true;
 143    }
 144
 145    internal static void Validate(Vector2d scale)
 146    {
 71619147        SwiftThrowHelper.ThrowIfArgument(
 71619148            !IsPositive(scale),
 71619149            nameof(scale),
 71619150            "2D collider scale must be greater than zero on the X and Z axes throughout its transform ancestry.");
 71607151    }
 152
 153    internal static void Validate(Vector3d scale)
 154    {
 79209155        SwiftThrowHelper.ThrowIfArgument(
 79209156            !IsPositive(scale),
 79209157            nameof(scale),
 79209158            "3D collider scale must be greater than zero on every axis throughout its transform ancestry.");
 79188159    }
 160
 161    private static bool IsPositive(Vector2d scale) =>
 71619162        scale.X > Fixed64.Zero && scale.Y > Fixed64.Zero;
 163
 164    private static bool IsPositive(Vector3d scale) =>
 79209165        scale.X > Fixed64.Zero
 79209166        && scale.Y > Fixed64.Zero
 79209167        && scale.Z > Fixed64.Zero;
 168
 169    internal static Fixed64 Scale(
 170        Fixed64 value,
 171        Fixed64 ownerScale,
 172        Fixed64 partScale) =>
 109029173        Scale(value, ownerScale, partScale, Fixed64.One);
 174
 175    internal static Fixed64 Scale(
 176        Fixed64 value,
 177        Fixed64 ownerScale,
 178        Fixed64 partScale,
 179        Fixed64 divisor)
 180    {
 207555181        SwiftThrowHelper.ThrowIfArgument(
 207555182            !Fixed64.TryMultiplyDivide(value, ownerScale, partScale, divisor, out Fixed64 result),
 207555183            nameof(value),
 207555184            "Scaled collider geometry must be representable.");
 207555185        return result;
 186    }
 187
 188    internal static Vector2d Scale(
 189        Vector2d value,
 190        Vector2d ownerScale,
 191        Vector2d partScale) =>
 29172192        Scale(value, ownerScale, partScale, Fixed64.One);
 193
 194    internal static Vector2d Scale(
 195        Vector2d value,
 196        Vector2d ownerScale,
 197        Vector2d partScale,
 198        Fixed64 divisor) =>
 36483199        new(
 36483200            Scale(value.X, ownerScale.X, partScale.X, divisor),
 36483201            Scale(value.Y, ownerScale.Y, partScale.Y, divisor));
 202
 203    internal static Fixed64 ScalePositive(
 204        Fixed64 dimension,
 205        Fixed64 ownerScale,
 206        Fixed64 partScale)
 207    {
 84215208        Fixed64 result = Scale(dimension, ownerScale, partScale);
 84215209        SwiftThrowHelper.ThrowIfArgument(
 84215210            result <= Fixed64.Zero,
 84215211            nameof(dimension),
 84215212            "Scaled collider dimensions must remain greater than zero.");
 84210213        return result;
 214    }
 215
 216    internal static Vector2d ScalePositive(
 217        Vector2d dimensions,
 218        Vector2d ownerScale,
 219        Vector2d partScale,
 220        Fixed64 divisor) =>
 882221        new(
 882222            ScalePositive(dimensions.X, ownerScale.X, partScale.X, divisor),
 882223            ScalePositive(dimensions.Y, ownerScale.Y, partScale.Y, divisor));
 224
 225    internal static Vector3d ScalePositive(
 226        Vector3d dimensions,
 227        Vector3d ownerScale,
 228        Vector3d partScale,
 229        Fixed64 divisor) =>
 7932230        new(
 7932231            ScalePositive(dimensions.X, ownerScale.X, partScale.X, divisor),
 7932232            ScalePositive(dimensions.Y, ownerScale.Y, partScale.Y, divisor),
 7932233            ScalePositive(dimensions.Z, ownerScale.Z, partScale.Z, divisor));
 234
 235    private static Fixed64 ScalePositive(
 236        Fixed64 dimension,
 237        Fixed64 ownerScale,
 238        Fixed64 partScale,
 239        Fixed64 divisor)
 240    {
 25560241        Fixed64 result = Scale(dimension, ownerScale, partScale, divisor);
 25560242        SwiftThrowHelper.ThrowIfArgument(
 25560243            result <= Fixed64.Zero,
 25560244            nameof(dimension),
 25560245            "Scaled collider dimensions must remain greater than zero.");
 25560246        return result;
 247    }
 248
 249}