using System; using System.Numerics; using MoonTools.Structs; namespace MoonTools.Bonk { public static class SweepTest { /// /// Performs a sweep test on and against rectangles. Returns the position 1 pixel before overlap occurs. /// /// /// A spatial hash. /// /// A transform by which to transform the IHasAABB2D. /// Given in world-space. /// public static SweepResult Test(SpatialHash2D spatialHash, Rectangle rectangle, Transform2D transform, Vector2 ray) where T : IEquatable { var transformedAABB = rectangle.TransformedAABB(transform); var sweepBox = SweepBox(transformedAABB, ray); var shortestDistance = float.MaxValue; var nearestID = default(T); Rectangle? nearestRectangle = null; Transform2D? nearestTransform = null; foreach (var (id, shape, shapeTransform) in spatialHash.Retrieve(sweepBox)) { Rectangle otherRectangle; Transform2D otherTransform; AABB2D otherTransformedAABB; if (shape is Rectangle) { otherRectangle = (Rectangle)shape; otherTransformedAABB = shape.TransformedAABB(shapeTransform); otherTransform = shapeTransform; } else if (shape is MultiShape2D multiShape && multiShape.IsSingleShape()) { Transform2D rectangleOffset; (otherRectangle, rectangleOffset) = multiShape.ShapeTransformPair(); otherTransform = shapeTransform.Compose(rectangleOffset); otherTransformedAABB = shape.TransformedAABB(otherTransform); } else { continue; } float xInvEntry, yInvEntry; if (ray.X > 0) { xInvEntry = otherTransformedAABB.Left - (transformedAABB.Right); } else { xInvEntry = (otherTransformedAABB.Right) - transformedAABB.Left; } if (ray.Y > 0) { yInvEntry = otherTransformedAABB.Top - (transformedAABB.Bottom); } else { yInvEntry = (otherTransformedAABB.Bottom) - transformedAABB.Top; } float xEntry, yEntry; if (ray.X == 0) { xEntry = float.MinValue; } else { xEntry = xInvEntry / ray.X; } if (ray.Y == 0) { yEntry = float.MinValue; } else { yEntry = yInvEntry / ray.Y; } var entryTime = Math.Max(xEntry, yEntry); if (entryTime >= 0 && entryTime <= 1) { if (entryTime < shortestDistance) { shortestDistance = entryTime; nearestID = id; nearestRectangle = otherRectangle; nearestTransform = shapeTransform; } } } if (nearestRectangle.HasValue) { var overlapPosition = ray * shortestDistance; var correctionX = -Math.Sign(ray.X); var correctionY = -Math.Sign(ray.Y); return new SweepResult(true, new Position2D((int)overlapPosition.X + correctionX, (int)overlapPosition.Y + correctionY), nearestID); } else { return SweepResult.False; } } public static SweepResult Test(SpatialHash2D spatialHash, Point point, Transform2D transform, Vector2 ray) where T : IEquatable { return Test(spatialHash, new Rectangle(0, 0, 0, 0), transform, ray); } private static AABB2D SweepBox(AABB2D aabb, Vector2 ray) { return new AABB2D( Math.Min(aabb.Min.X, aabb.Min.X + ray.X), Math.Min(aabb.Min.Y, aabb.Min.Y + ray.Y), Math.Max(aabb.Max.X, aabb.Max.X + ray.X), Math.Max(aabb.Max.Y, aabb.Max.Y + ray.Y) ); } } }