C # Raycasting / Поиск ближайшего прямоугольника по направлению

#c# #rectangles #raycasting

#c# #прямоугольники #raycasting

Вопрос:

Итак, я пытаюсь найти способ найти первый прямоугольник (из списка прямоугольников, все 2D), в который попадет моя точка, двигаясь в определенном направлении (в C #), однако, похоже, я не могу правильно описать терминологию (если для этого есть такая вещь, самое близкое, что я нашел, — raycasting).

Моя цель — начать с определенной «точки» (в данном случае с астетерика- *, видимого в примере ниже), и оттуда выбрать конкретное направление (влево, вправо, вниз, вверх) (без углов). Итак, допустим, мы выбираем Down , тогда первая строка, на которую он попадет, будет «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]
  

Комментарии:

1. Вы можете просто имитировать преобразование лучей, что в вашем случае проще, даже если существует только 4 направления. И найдите первый прямоугольник, на который попадает ближайшая точка в одном из 4 лучей (последовательность точек, исходящих из вашей точки ).

2. @Xiaoy312 Интересно, не могли бы вы привести мне какой-нибудь пример кода? 🙂

3. Ваши координаты в формате int или float / double?

4. Всплывает. Это прямоугольники / ограничивающие рамки, которые содержат левое, верхнее, ширину и высоту. @Xiaoy312

5. @Xiaoy312 Я добавил комментарий к вашему ответу 🙂

Ответ №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 amp;amp; 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) amp;amp; (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) amp;amp; (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 amp;amp; value <= uBound;
    float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
}
  

примечание: В обеих версиях есть ошибка, когда точка находится внутри прямоугольника. Вычисленное расстояние будет расстоянием до ближайшего края, а не 0 или отрицательным значением.

Комментарии:

1. Хм, как бы вы указали единственное направление, хотя? Ваш код имеет смысл, но если я только хочу получить «попадание» на основе определенного направления (например, от точки и вниз ВНИЗ (или в других направлениях) (без ангелов, просто прямая вертикаль)).