【问题标题】:C# Raycasting / Find closest rectangle from directionC# Raycasting / 从方向查找最近的矩形
【发布时间】:2019-03-26 21:04:09
【问题描述】:

所以,我正在尝试找到一种方法来找到我的 Point 将命中的第一个 Rectangle(从矩形列表中,全是 2D),进入一个特定的方向(在 C# 中),但是我似乎无法正确描述术语(如果有这样的事情,我发现最接近的是光线投射)。

我的目标是从一个特定的“点”开始(在这种情况下,星号- * 从下面的示例中看到),然后从那里选择一个特定的方向(左、右、下、上)(无角度)。所以假设我们选择 Down,那么它会命中的第一个 rect 是“I'M A RECT 2”,因此应该返回这个。

我已经知道矩形的所有位置和大小,所以我得到了这些信息。

我该怎么做?

     *       [I'M A RECT 1]


[I'M A RECT 2]  
               [I'M A RECT 3]
[I'M A RECT 4]

【问题讨论】:

  • 您可以只模拟光线投射,即使只有 4 个方向,在您的情况下也更容易。并找到由 4 条光线中的一条中最近点击中的第一个矩形(源自您的 Point 的点序列)。
  • @Xiaoy312 有意思,可以给我一些示例代码吗? :)
  • 您的坐标是 int 还是 float/double?
  • 浮动。它们是包含左侧、顶部、宽度和高度的矩形/边界框。 @Xiaoy312
  • @Xiaoy312 我在你的回答中添加了评论:)

标签: c# rectangles raycasting


【解决方案1】:

您可以检查矩形是否可以与源自该点的射线相交,然后计算与point的距离:

var point = new PointF(1.2f, 2.5f);
var rectangles = new RectangleF[]
{
    new RectangleF(1, 1, 1, 1),
    new RectangleF(3, 1, 1, 1),
    new RectangleF(5, 2, 1, 1),
};

var hit = rectangles
    .Select(x =>
    {
        if (IsBetween(point.X, x.Left, x.Left + x.Width))
            return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Top - x.Height, x.Top) as float? };
        else if (IsBetween(point.X, x.Top - x.Height, x.Top))
            return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Left, x.Left + x.Width) as float? };
        else return new { Rectangle = x, Distance = default(float?) };
    })
    .Where(x => x.Distance != null)
    .OrderBy(x => x.Distance)
    .FirstOrDefault()?.Rectangle;

bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));

编辑:

var hit = RayTest(point, rectangles, RayDirections.Right | RayDirections.Down) // or, try just `Down`
    .Where(x => x.Success)
    .OrderBy(x => x.Distance)
    .FirstOrDefault()?.Target;

[Flags]
public enum RayDirections { None = 0, Left = 1 << 0, Up = 1 << 1, Right = 1 << 2, Down = 1 << 3, All = (1 << 4) - 1 }
public class RayHit<T>
{
    public T Target { get; }
    public float? Distance { get; }
    public bool Success => Distance.HasValue;

    public RayHit(T target, float? distance)
    {
        this.Target = target;
        this.Distance = distance;
    }
}

public IEnumerable<RayHit<RectangleF>> RayTest(PointF point, IEnumerable<RectangleF> rectangles, RayDirections directions = RayDirections.All)
{
    if (directions == RayDirections.None)
        return Enumerable.Empty<RayHit<RectangleF>>();

    var ray = new
    {
        Horizontal = new
        {
            LBound = directions.HasFlag(RayDirections.Left) ? float.MinValue : point.X,
            UBound = directions.HasFlag(RayDirections.Right) ? float.MaxValue : point.X,
        },
        Vertical = new
        {
            LBound = directions.HasFlag(RayDirections.Down) ? float.MinValue : point.Y,
            UBound = directions.HasFlag(RayDirections.Up) ? float.MaxValue : point.Y,
        },
    };

    return rectangles
        .Select(x =>
        {
            float left = x.Left, right = x.Left + x.Width;
            float top = x.Top, bottom = x.Top - x.Height;

            if (IsBetween(point.X, left, right) && (IsBetween(top, ray.Vertical.LBound, ray.Vertical.UBound) || IsBetween(bottom, ray.Vertical.LBound, ray.Vertical.UBound)))
                return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, bottom, top) as float?);
            else if (IsBetween(point.X, bottom, top) && (IsBetween(left, ray.Horizontal.LBound, ray.Horizontal.UBound) || IsBetween(right, ray.Horizontal.LBound, ray.Horizontal.UBound)))
                return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, left, right) as float?);
            else return new RayHit<RectangleF>(x, default);
        });

    bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
    float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
}

注意:在这两个版本中,当点在矩形内时都会出现错误。计算的距离将是到最近边缘的距离,而不是 0 或负值。

【讨论】:

  • 嗯,你会如何指定一个方向?您的代码是有道理的,但是如果我只想根据特定方向获得“命中”(例如从点向下向下(或其他方向)(没有天使,只是垂直)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
  • 2019-02-21
  • 2010-09-05
相关资源
最近更新 更多