C#光线投射/从方向查找最近的矩形
C# Raycasting / Find closest rectangle from direction
所以,我试图找到一种方法来找到我的 Point 会命中,朝特定方向前进(在 C# 中),但是我似乎无法正确描述术语(如果有这样的东西,我发现最接近的东西是光线投射)。
我的目标是从一个特定的 "Point" 开始(在本例中是 asteterik- * 从下面的例子中可以看出),然后从那里选择一个特定的方向(左、右、下、上)(没有角度)。所以假设我们选择向下,那么它会命中的第一个矩形是 "I'M A RECT 2",因此应该 return 这个。
我已经知道矩形的所有位置和大小,所以我得到了这些信息。
我该怎么做?
* [I'M A RECT 1]
[I'M A RECT 2]
[I'M A RECT 3]
[I'M A RECT 4]
您可以检查矩形是否可以与源自该点的射线相交,然后计算与 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 或负值。
所以,我试图找到一种方法来找到我的 Point 会命中,朝特定方向前进(在 C# 中),但是我似乎无法正确描述术语(如果有这样的东西,我发现最接近的东西是光线投射)。
我的目标是从一个特定的 "Point" 开始(在本例中是 asteterik- * 从下面的例子中可以看出),然后从那里选择一个特定的方向(左、右、下、上)(没有角度)。所以假设我们选择向下,那么它会命中的第一个矩形是 "I'M A RECT 2",因此应该 return 这个。
我已经知道矩形的所有位置和大小,所以我得到了这些信息。
我该怎么做?
* [I'M A RECT 1]
[I'M A RECT 2]
[I'M A RECT 3]
[I'M A RECT 4]
您可以检查矩形是否可以与源自该点的射线相交,然后计算与 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 或负值。