< Summary

Information
Class: Gravitas.Queries.RaycastSegmentWorker
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Queries/3D/RaycastSegmentWorker.cs
Line coverage
100%
Covered lines: 256
Uncovered lines: 0
Coverable lines: 256
Total lines: 496
Line coverage: 100%
Branch coverage
100%
Covered branches: 120
Total branches: 120
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/3D/RaycastSegmentWorker.cs

#LineLine coverage
 1//=======================================================================
 2// RaycastSegmentWorker.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 FixedMathSharp.Geometry;
 10using Gravitas.Colliders;
 11using SwiftCollections;
 12using SwiftCollections.Query;
 13
 14namespace Gravitas.Queries;
 15
 16/// <summary>
 17/// Stores segment data used by one context-owned raycast service while checking collider overlaps.
 18/// </summary>
 19public sealed class RaycastSegmentWorker
 20{
 21    private Vector3d _cachedOrigin;
 22    private Vector3d _cachedEnd;
 23    private Vector3d _cachedSegment;
 24    private Vector3d _segmentDirection;
 25    private Fixed64 _segmentLength;
 26    private bool _segmentIsValid;
 27    private bool _calculateIntersections;
 519328    private readonly SwiftList<int> _meshTriangleBuffer = new();
 29
 130    internal Vector3d SegmentDirection => _segmentDirection;
 31
 32    /// <summary>
 33    /// Prepares this worker for overlap checks against the line segment between two points.
 34    /// </summary>
 35    public void PrepareSegmentCheck(Vector3d p1, Vector3d p2, bool calculateIntersectionPoints = true)
 36    {
 260737        _cachedOrigin = p1;
 260738        _cachedEnd = p2;
 39
 260740        if (!Vector3d.TrySubtract(p2, p1, out Vector3d segment)
 260741            || !Vector3d.TryGetMagnitude(segment, out _segmentLength))
 42        {
 243            _segmentLength = Fixed64.Zero;
 244            _cachedSegment = Vector3d.Zero;
 245            _segmentDirection = Vector3d.Zero;
 246            _segmentIsValid = false;
 247            _calculateIntersections = calculateIntersectionPoints;
 248            return;
 49        }
 50
 260551        _cachedSegment = segment;
 260552        _segmentDirection = _segmentLength == Fixed64.Zero ? Vector3d.Zero : segment.Normalized;
 260553        _segmentIsValid = true;
 260554        _calculateIntersections = calculateIntersectionPoints;
 260555    }
 56
 57    /// <summary>
 58    /// Checks whether a sphere collider overlaps the prepared segment.
 59    /// </summary>
 60    public bool CheckSphereOverlaps(LSSphereCollider sphereCollider, ref SwiftList<Vector3d> outputIntersectionPoints) =
 130761        CheckSphereOverlaps(
 130762            new FixedBoundSphere(sphereCollider.Center, sphereCollider.ScaledRadius),
 130763            ref outputIntersectionPoints);
 64
 65    /// <summary>
 66    /// Checks whether a sphere overlaps this worker's prepared ray segment.
 67    /// </summary>
 68    /// <param name="sphere">The sphere bound.</param>
 69    /// <param name="outputIntersectionPoints">
 70    /// Receives segment points reconstructed from exact physical intersection distances.
 71    /// </param>
 72    /// <returns><see langword="true"/> when the prepared segment overlaps the sphere.</returns>
 73    public bool CheckSphereOverlaps(
 74        FixedBoundSphere sphere,
 75        ref SwiftList<Vector3d> outputIntersectionPoints)
 76    {
 132077        if (!_segmentIsValid)
 278            return false;
 79
 131880        if (_cachedSegment.IsZero)
 381            return CheckPointInsideSphere(sphere, ref outputIntersectionPoints);
 82
 131583        var query = new FixedSegment(_cachedOrigin, _cachedEnd);
 131584        if (!query.TryGetSphereIntersectionDistanceInterval(
 131585                sphere,
 131586                Fixed64.Zero,
 131587                _segmentLength,
 131588                out Fixed64 entry,
 131589                out Fixed64 exit,
 131590                out bool startContained,
 131591                out bool endContainedStrict))
 92        {
 1393            return false;
 94        }
 95
 130296        if (!_calculateIntersections)
 197            return true;
 98
 130199        AddFiniteAxisIntersectionInterval(
 1301100            entry,
 1301101            exit,
 1301102            startContained,
 1301103            endContainedStrict,
 1301104            ref outputIntersectionPoints);
 1301105        return true;
 106    }
 107
 108    /// <summary>
 109    /// Checks whether a capsule collider overlaps the prepared segment.
 110    /// </summary>
 111    public bool CheckCapsuleOverlaps(LSCapsuleCollider capsuleCollider, ref SwiftList<Vector3d> outputIntersectionPoints
 112    {
 16113        if (!_segmentIsValid)
 1114            return false;
 115
 15116        var query = new FixedSegment(_cachedOrigin, _cachedEnd);
 15117        if (!query.TryGetCapsuleIntersectionDistanceInterval(
 15118                capsuleCollider.Center,
 15119                capsuleCollider.Rotation,
 15120                capsuleCollider.AxisLength,
 15121                capsuleCollider.ScaledRadius,
 15122                Fixed64.Zero,
 15123                _segmentLength,
 15124                out Fixed64 entry,
 15125                out Fixed64 exit,
 15126                out bool startContained,
 15127                out bool endContainedStrict))
 128        {
 4129            return false;
 130        }
 131
 11132        if (!_calculateIntersections)
 1133            return true;
 134
 10135        AddFiniteAxisIntersectionInterval(
 10136            entry,
 10137            exit,
 10138            startContained,
 10139            endContainedStrict,
 10140            ref outputIntersectionPoints);
 10141        return true;
 142    }
 143
 144    /// <summary>
 145    /// Checks whether a cylinder collider overlaps the prepared segment.
 146    /// </summary>
 147    public bool CheckCylinderOverlaps(LSCylinderCollider cylinderCollider, ref SwiftList<Vector3d> outputIntersectionPoi
 148    {
 29149        if (!_segmentIsValid)
 1150            return false;
 151
 28152        var query = new FixedSegment(_cachedOrigin, _cachedEnd);
 28153        if (!query.TryGetFiniteCylinderIntersectionDistanceInterval(
 28154                cylinderCollider.Center,
 28155                cylinderCollider.Rotation,
 28156                cylinderCollider.Height,
 28157                cylinderCollider.ScaledRadius,
 28158                Fixed64.Zero,
 28159                Fixed64.Zero,
 28160                _segmentLength,
 28161                out Fixed64 entry,
 28162                out Fixed64 exit,
 28163                out bool startContained,
 28164                out bool endContainedStrict))
 165        {
 7166            return false;
 167        }
 168
 21169        if (!_calculateIntersections)
 1170            return true;
 171
 20172        AddFiniteAxisIntersectionInterval(
 20173            entry,
 20174            exit,
 20175            startContained,
 20176            endContainedStrict,
 20177            ref outputIntersectionPoints);
 20178        return true;
 179    }
 180
 181    /// <summary>
 182    /// Checks whether a cone collider overlaps the prepared segment.
 183    /// </summary>
 184    public bool CheckConeOverlaps(LSConeCollider coneCollider, ref SwiftList<Vector3d> outputIntersectionPoints)
 185    {
 706186        if (!_segmentIsValid)
 1187            return false;
 188
 705189        var query = new FixedSegment(_cachedOrigin, _cachedEnd);
 705190        if (!query.TryGetCenteredFiniteConeIntersectionDistanceInterval(
 705191                coneCollider.Center,
 705192                coneCollider.Rotation,
 705193                coneCollider.Height,
 705194                coneCollider.ScaledRadius,
 705195                _segmentLength,
 705196                out Fixed64 entry,
 705197                out Fixed64 exit,
 705198                out bool startContained,
 705199                out bool endContainedStrict))
 200        {
 11201            return false;
 202        }
 203
 694204        if (!_calculateIntersections)
 3205            return true;
 206
 691207        AddFiniteAxisIntersectionInterval(
 691208            entry,
 691209            exit,
 691210            startContained,
 691211            endContainedStrict,
 691212            ref outputIntersectionPoints);
 213
 691214        return true;
 215    }
 216
 217    /// <summary>
 218    /// Checks whether a mesh collider overlaps the prepared segment.
 219    /// </summary>
 220    public bool CheckMeshOverlaps(LSMeshCollider meshCollider, ref SwiftList<Vector3d> outputIntersectionPoints)
 221    {
 400222        if (!_segmentIsValid)
 1223            return false;
 224
 399225        if (!meshCollider.Mesh.TryConvertWorldToScaledLocal(
 399226                _cachedOrigin,
 399227                out Vector3d localOrigin)
 399228            || !meshCollider.Mesh.TryConvertWorldToScaledLocal(
 399229                _cachedEnd,
 399230                out Vector3d localEnd))
 231        {
 2232            return false;
 233        }
 397234        Vector3d localSegment = localEnd - localOrigin;
 397235        Fixed64 localSegmentLengthSqr = localSegment.MagnitudeSquared;
 397236        Fixed64 localSegmentLength = localSegmentLengthSqr == Fixed64.Zero ? Fixed64.Zero : localSegment.Magnitude;
 397237        Vector3d localSegmentDirection = localSegmentLength == Fixed64.Zero ? Vector3d.Zero : localSegment.Normalized;
 238
 397239        _meshTriangleBuffer.FastClear();
 397240        meshCollider.Mesh.GetTrianglesInLocalBounds(CreateSegmentBounds(localOrigin, localEnd), _meshTriangleBuffer);
 397241        bool intersects = false;
 1878242        for (int i = 0; i < _meshTriangleBuffer.Count; i++)
 243        {
 543244            int triangleIndex = _meshTriangleBuffer[i];
 543245            meshCollider.Mesh.GetLocalTriangleVertices(triangleIndex, out Vector3d first, out Vector3d second, out Vecto
 543246            if (!TryAddLocalTriangleIntersection(
 543247                    meshCollider.Mesh,
 543248                    first,
 543249                    second,
 543250                    third,
 543251                    meshCollider.Mesh.GetScaledLocalFaceNormal(triangleIndex),
 543252                    localOrigin,
 543253                    localEnd,
 543254                    localSegmentDirection,
 543255                    localSegmentLength,
 543256                    localSegmentLengthSqr,
 543257                    ref outputIntersectionPoints))
 258            {
 259                continue;
 260            }
 261
 463262            intersects = true;
 463263            if (!_calculateIntersections)
 1264                return true;
 265        }
 266
 396267        return intersects;
 268    }
 269
 270    /// <summary>
 271    /// Checks whether a cuboid's axis-aligned bounds overlap the prepared segment.
 272    /// </summary>
 273    public bool CheckAABBoxOverlaps(LSCuboidCollider aabox, ref SwiftList<Vector3d> outputIntersectionPoints) =>
 108274         CheckAABBoxOverlaps(aabox.BoundsMin, aabox.BoundsMax, ref outputIntersectionPoints);
 275
 276    /// <summary>
 277    /// Checks whether an axis-aligned bounding box overlaps this worker's prepared ray segment.
 278    /// </summary>
 279    public bool CheckAABBoxOverlaps(Vector3d min, Vector3d max, ref SwiftList<Vector3d> outputIntersectionPoints)
 280    {
 126281        if (!_segmentIsValid)
 1282            return false;
 283
 125284        if (_cachedSegment.IsZero)
 11285            return CheckPointInsideBox(min, max, ref outputIntersectionPoints);
 286
 114287        if (!SweepBoundsUtility.TryClipSegment(
 114288            _cachedOrigin,
 114289            _segmentDirection,
 114290            _segmentLength,
 114291            min,
 114292            max,
 114293            out Fixed64 entry,
 114294            out Fixed64 exit))
 295        {
 12296            return false;
 297        }
 298
 102299        if (_calculateIntersections)
 300        {
 101301            outputIntersectionPoints.Add(_cachedOrigin + _segmentDirection * entry);
 101302            if (exit != entry)
 99303                outputIntersectionPoints.Add(_cachedOrigin + _segmentDirection * exit);
 304        }
 305
 102306        return true;
 307    }
 308
 309    /// <summary>
 310    /// Checks whether an oriented bounding box overlaps this worker's prepared ray segment.
 311    /// </summary>
 312    public bool CheckOBBoxOverlaps(LSCuboidCollider oobox, ref SwiftList<Vector3d> outputIntersectionPoints)
 313    {
 236314        if (!_segmentIsValid)
 1315            return false;
 316
 235317        var ray = new FixedRay(_cachedOrigin, _cachedSegment);
 235318        if (!oobox.OrientedBox.TryGetRayIntersectionInterval(
 235319                ray,
 235320                Fixed64.One,
 235321                out Fixed64 entry,
 235322                out Fixed64 exit))
 18323            return false;
 324
 217325        if (_calculateIntersections)
 326        {
 327            // The interval is clipped to this segment, so both parameters are
 328            // convex combinations of its representable endpoints.
 216329            _ = ray.TryGetPoint(entry, out Vector3d entryPoint);
 216330            Vector3d exitPoint = default;
 216331            if (exit != entry)
 7332                _ = ray.TryGetPoint(exit, out exitPoint);
 333
 216334            AddIntersectionPoint(entryPoint, ref outputIntersectionPoints);
 216335            if (exit != entry)
 7336                AddIntersectionPoint(exitPoint, ref outputIntersectionPoints);
 337        }
 338
 217339        return true;
 340    }
 341
 342    private bool TryAddLocalTriangleIntersection(
 343        PhysicsMesh mesh,
 344        Vector3d first,
 345        Vector3d second,
 346        Vector3d third,
 347        Vector3d normal,
 348        Vector3d localOrigin,
 349        Vector3d localEnd,
 350        Vector3d localSegmentDirection,
 351        Fixed64 localSegmentLength,
 352        Fixed64 localSegmentLengthSqr,
 353        ref SwiftList<Vector3d> outputIntersectionPoints)
 354    {
 543355        var triangle = new FixedTriangle(first, second, third);
 543356        if (localSegmentLengthSqr == Fixed64.Zero)
 357        {
 3358            if (Vector3d.Dot(localOrigin - first, normal).Abs() > Fixed64.Epsilon
 3359                || !triangle.ContainsProjection(localOrigin))
 360            {
 2361                return false;
 362            }
 363
 364            // localOrigin was obtained from this representable world endpoint.
 1365            _ = mesh.TryConvertScaledLocalToWorld(
 1366                localOrigin,
 1367                out Vector3d worldOrigin);
 368
 1369            AddIntersectionPoint(worldOrigin, ref outputIntersectionPoints);
 1370            return true;
 371        }
 372
 540373        Fixed64 denominator = Vector3d.Dot(normal, localSegmentDirection);
 540374        if (denominator.Abs() <= Fixed64.Epsilon)
 375        {
 155376            if (Vector3d.Dot(localOrigin - first, normal).Abs() > Fixed64.Epsilon)
 1377                return false;
 378
 154379            bool found = false;
 154380            if (triangle.ContainsProjection(localOrigin))
 381            {
 83382                _ = mesh.TryConvertScaledLocalToWorld(
 83383                    localOrigin,
 83384                    out Vector3d worldOrigin);
 385
 83386                AddIntersectionPoint(worldOrigin, ref outputIntersectionPoints);
 83387                found = true;
 388            }
 389
 154390            if (triangle.ContainsProjection(localEnd))
 391            {
 90392                _ = mesh.TryConvertScaledLocalToWorld(
 90393                    localEnd,
 90394                    out Vector3d worldEnd);
 395
 90396                AddIntersectionPoint(worldEnd, ref outputIntersectionPoints);
 90397                found = true;
 398            }
 399
 154400            return found;
 401        }
 402
 385403        Fixed64 distance = Vector3d.Dot(first - localOrigin, normal) / denominator;
 385404        if (distance < Fixed64.Zero || distance > localSegmentLength)
 2405            return false;
 406
 383407        Vector3d localPoint = localOrigin + localSegmentDirection * distance;
 383408        if (!triangle.ContainsProjection(localPoint))
 18409            return false;
 410
 411        // A point on the local segment maps to the same convex combination of
 412        // the already-representable world endpoints.
 365413        _ = mesh.TryConvertScaledLocalToWorld(
 365414            localPoint,
 365415            out Vector3d worldPoint);
 416
 365417        AddIntersectionPoint(worldPoint, ref outputIntersectionPoints);
 365418        return true;
 419    }
 420
 421    private void AddIntersectionPoint(Vector3d point, ref SwiftList<Vector3d> outputIntersectionPoints)
 422    {
 762423        if (!_calculateIntersections)
 1424            return;
 425
 1740426        for (int i = 0; i < outputIntersectionPoints.Count; i++)
 427        {
 236428            if (Vector3d.DistanceSquared(outputIntersectionPoints[i], point) <= Fixed64.Epsilon)
 127429                return;
 430        }
 431
 634432        outputIntersectionPoints.Add(point);
 634433    }
 434
 435    private static FixedBoundVolume CreateSegmentBounds(Vector3d origin, Vector3d end)
 436    {
 397437        Vector3d min = Vector3d.Min(origin, end);
 397438        Vector3d max = Vector3d.Max(origin, end);
 397439        return new FixedBoundVolume(min, max);
 440    }
 441
 442    private bool CheckPointInsideSphere(
 443        FixedBoundSphere sphere,
 444        ref SwiftList<Vector3d> outputIntersectionPoints)
 445    {
 3446        if (!sphere.Contains(_cachedOrigin))
 1447            return false;
 448
 2449        if (_calculateIntersections)
 1450            outputIntersectionPoints.Add(_cachedOrigin);
 451
 2452        return true;
 453    }
 454
 455    private void AddFiniteAxisIntersectionInterval(
 456        Fixed64 entry,
 457        Fixed64 exit,
 458        bool startContained,
 459        bool endInsideStrict,
 460        ref SwiftList<Vector3d> outputIntersectionPoints)
 461    {
 2022462        AddSegmentIntersectionPoint(entry, ref outputIntersectionPoints);
 2022463        if (!startContained
 2022464            && exit != entry
 2022465            && (exit < _segmentLength || !endInsideStrict))
 466        {
 1512467            AddSegmentIntersectionPoint(exit, ref outputIntersectionPoints);
 468        }
 469
 2022470    }
 471
 472    private void AddSegmentIntersectionPoint(
 473        Fixed64 distance,
 474        ref SwiftList<Vector3d> outputIntersectionPoints) =>
 3534475        outputIntersectionPoints.Add(new FixedSegment(_cachedOrigin, _cachedEnd)
 3534476            .GetPointAtDistance(distance, _segmentLength));
 477
 478    private bool CheckPointInsideBox(
 479        Vector3d min,
 480        Vector3d max,
 481        ref SwiftList<Vector3d> outputIntersectionPoints)
 482    {
 11483        if (_cachedOrigin.X < min.X || _cachedOrigin.X > max.X
 11484            || _cachedOrigin.Y < min.Y || _cachedOrigin.Y > max.Y
 11485            || _cachedOrigin.Z < min.Z || _cachedOrigin.Z > max.Z)
 486        {
 6487            return false;
 488        }
 489
 5490        if (_calculateIntersections)
 4491            outputIntersectionPoints.Add(_cachedOrigin);
 492
 5493        return true;
 494    }
 495
 496}

Methods/Properties

.ctor()
get_SegmentDirection()
PrepareSegmentCheck(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Boolean)
CheckSphereOverlaps(Gravitas.Colliders.LSSphereCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckSphereOverlaps(FixedMathSharp.Geometry.FixedBoundSphere,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckCapsuleOverlaps(Gravitas.Colliders.LSCapsuleCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckCylinderOverlaps(Gravitas.Colliders.LSCylinderCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckConeOverlaps(Gravitas.Colliders.LSConeCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckMeshOverlaps(Gravitas.Colliders.LSMeshCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckAABBoxOverlaps(Gravitas.Colliders.LSCuboidCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckAABBoxOverlaps(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckOBBoxOverlaps(Gravitas.Colliders.LSCuboidCollider,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
TryAddLocalTriangleIntersection(Gravitas.Colliders.PhysicsMesh,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
AddIntersectionPoint(FixedMathSharp.Vector3d,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CreateSegmentBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CheckPointInsideSphere(FixedMathSharp.Geometry.FixedBoundSphere,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
AddFiniteAxisIntersectionInterval(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
AddSegmentIntersectionPoint(FixedMathSharp.Fixed64,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)
CheckPointInsideBox(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,SwiftCollections.SwiftList`1<FixedMathSharp.Vector3d>&)