【问题标题】:Detecting light projections and intersections in 2D space using C#使用 C# 检测 2D 空间中的光投影和交叉点
【发布时间】:2012-07-30 21:47:21
【问题描述】:

光源是二维空间中位于单个坐标中的实体。

在不同的位置周围有多个光源,每个光源在 N、S、E、W、NW、NE、SW、SE 方向上发出 8 条光线。所有灯光的坐标都是已知的。

我需要计算网格内这些光线的所有交点。

long width = int.MaxValue; // 2D grid width.
long height = int.MaxValue * 3; // 2D grid height.
List<Point> lights = a bunch of randomly placed light sources.
List<Point> intersections = calculate all possible intersections.

for (int i=0; i < lights.Count - 1; i++)
{
    for (int j=i + 1; j < lights.Count; j++)
    {
        // How to compare using integers only?
        // If that is not possible, what is the fastest alternative?
    }
}

【问题讨论】:

  • 首先,您需要获取所有交叉点的列表还是只想知道有多少个交叉点?您所说的“可能”是什么意思,(不在网格上的交叉点)(可能是也可能不是要进一步调查的交叉点)。其次,what have you tried?
  • 我想获取网格内所有交叉点的列表。我们已经有了所有光源的坐标 (List&lt;Point&gt; lights)。至于我尝试过的,答案在提供的代码中;迭代。我不知道,问题的主题是 how 比较冒泡排序算法中的两个点,以确保我们覆盖所有 8 条光线。
  • @jonsca:这不是重复的。这也是我的问题!不同的上下文。
  • @RaheelKhan The other question has written out in words Now I need to iterate the lights' collection and determine if my location (position) is being hit by each.` 当我查看这个问题时,似乎您刚刚在这里用代码写了出来没有改变任何问题。如果在这种情况下的目标不同,则将您在此处代码的 cmets 中提出的观点移到问题的主体中,以便了解您的意图。一旦你将两者区分开来,它们将不再是重复的,而且选票也会逐渐消失。

标签: c# .net geometry 2d raytracing


【解决方案1】:

我的回答基于您对链接问题的评论:是否还有一种简单的方法可以确定两个给定点的对角线光线在哪些坐标处相互交叉?看起来您想要确定光源给出的光线的交点。

根据您已经描述的情况,水平/垂直情况很容易。两个源之间的点描述了交叉点。对角线的情况比较棘手,我认为最简单的方法就是计算线的交叉点。

您可以将每个对角线/反对角线描述为由矢量方程 ray = s + u * d 描述的一条线,其中 s 是光源的位置,d 是光线的方向([1, 1][1, -1][1, 0][0, 1])。每个源都有四个这样的方程,每个方向一个。现在,要找到对角线的交点,只需找到两个源的非平行线的交点(一对将是平行的,因此不能相交)。

抱歉,如果不清楚,我会尝试更新。

更新

作为一种简单的优化,当且仅当源之间的rectilinear distance (|x1 - x2| + |y1 - y2|) 是偶数时,光线才对角相交。我认为还有其他条件有助于简化您的案例。

这是一个推导,可以找到您需要的方程式。我们从两条射线开始:

ray1 = s1 + u1 * d1
ray2 = s2 + u2 * d2

在笛卡尔坐标中:

ray1x = s1x + u1 * d1x
ray1y = s1y + u1 * d1y
ray2x = s2x + u2 * d2x
ray2y = s2y + u2 * d2y

在路口,ray1x = ray2xray1y = ray2y

s1x + u1 * d1x = s2x + u2 * d2x
s1y + u1 * d1y = s2y + u2 * d2y

为方便起见,我们可以隔离并消除u2

u2 = (s1x - s2x + u1 * d1x) / d2x
u2 = (s1y - s2y + u1 * d1y) / d2y

(s1x - s2x + u1 * d1x) / d2x = (s1y - s2y + u1 * d1y) / d2y
(s1x - s2x + u1 * d1x) * d2y = (s1y - s2y + u1 * d1y) * d2x

然后求解u1

(s1x - s2x) * d2y + u1 * d1x * d2y = (s1y - s2y) * d2x + u1 * d1y * d2x
u1 * (d1x * d2y - d1y * d2x) = (s1y - s2y) * d2x - (s1x - s2x) * d2y

u1 = ((s1y - s2y) * d2x - (s1x - s2x) * d2y) / (d1x * d2y - d1y * d2x)

要查找u2,您可以只计算上述公式之一或使用:

u2 = ((s2y - s1y) * d1x - (s2x - s1x) * d1y) / (d2x * d1y - d2y * d1x)

所以你有它。在给定源位置s1s2 和射线方向d1d2 的情况下,求解u1u2 的两个方程。您只需将u1u2 值插入原始ray 方程,就可以得到一对的交点。在您的情况下,当且仅当 u1u2 是整数时才存在交集。在一种情况下,当方向为[1, 0][0, 1] 时,会发生除以零的情况,但这种情况很容易解决(源的非零坐标形成交叉点的坐标)。

【讨论】:

  • 谢谢。这很有帮助。您提到了其他有助于消除和优化的条件;你会怎么谷歌呢?我正在尝试提出简单的条件检查,涵盖所有对角线场景,不涉及遍历线上的每个点。
  • 对于这种特殊情况,我想你只需要考虑简单的条件来消除不必要的工作。我描述的线交点方法在恒定时间内找到交点,并且只需要很少的算术运算。这对您来说更容易,因为您的方向向量只是 [1, 1][1, -1]。我强烈推荐它而不是任何类型的循环。
【解决方案2】:

假设您有一个固定的坐标平面大小,并且您将对不同位置的光源进行多次这些计算,您可以做得比迭代每个点更好。

您可以创建四个布尔(或位)数组。

  1. 地平线
  2. 垂直
  3. DiagR
  4. DiagL

对于我们的每个光源,我们将它们“投影”到那些一维数组上。 (在图片中我只展示了两个投影)。

投影到 Horiz 和 Verti 上很简单。

在图中所示的 DiagR 数组上投影一个点 (x,y) 就像 x 加 y 一样简单。

现在您可以简单地遍历所有网格点,看看是否至少有 2 个投影设置为 true。

但我们可以做得更好,

例如,在示例中,我们可以从遍历 Verti 数组开始。

我们注意到 Verti[0] 设置为 true,现在我们想看看它是否与 Horiz、DiagR、DiagL 相交。

我们计算出要检查与 DiagR(我们图片中的另一个数组)的交集,我们只需要查看 DiagR[0]、DiagR[1]、DiagR[2] 和 DiagR[3] 是否为真,我们可以忽略该数组的其余部分。

Verti[0] 的光可以与水平的任何元素相交。

Verti[0] 的光只能在 DiagL 位置 0、1、2 和 3 处与 DiagL 相交。

继续 Verti[i] 的其余部分。

我们现在可以做类似的事情,从真正的 Horiz[i] 与 DiagR 和 DiagL 寻找交叉点。

最后,我们走过 DiagR 并寻找与 DiagL 的交叉点。

这将为您返回所有光线交点的列表,但其中还包括光源的点。

您可以忽略所有在有点源的地方出现的交叉点,或者使用一些巧妙的方法来解释这些点。

【讨论】:

  • 谢谢。我确实有一个固定的坐标平面大小,但这个大小是巨大的,网格无法保存在内存中。只有光源是已知的。由于网格太大,即使是射线点也无法保存在内存中。我只能握住几千个十字路口,围绕一个兴趣点。您会在这里推荐基于向量的方法吗?
  • 如果大小仅为 10,000 x 10,000,则此方法仅使用 4 个额外的长度为 10,000 10,000 和大约 20,000 20,000 的一维数组。您可以将它们制作成 BitArray 以使它们更小。您无需将完整的坐标平面存储在程序中的任何位置。
  • 但是,宗力的方法也有可比性,正如它所描述的,它具有非常相似的性质,将光源的垂直光线与对角线和水平线阵列进行比较,我们都没有方法将平行光线相互比较,(您可以修改 Zong Li 的方法以不检查由两个光源在一条线上产生的等效方程)。
  • 作为我方法的最后修改,您可以对一维数组使用 hashSets 并仅存储真实值。
  • @RaheelKhan 除此之外,因为我以前从未真正做过光线追踪,我不能说哪种方法会更好,我会假设我的方法,因为它使用数组查找按顺序对预先计算的值进行处理,而不是从成对的方程中求解交点。
【解决方案3】:

我已经从 here 中提取了数学,

好的,所以每个点都有 4 条“基本射线”,一条射线是在两点之间通过的无限线。

// A line in the form Ax+By=C from 2 points
public struct Ray
{
    public readonly float A;
    public readonly float B;
    public readonly float C;

    public Ray(PointF one, PointF two)
    {
       this.A = two.y - one.y;
       this.B = one.x - two.x;
       this.C = (this.A * one.x) + (this.B * two.x); 
    }
}

要获得红衣主教,我们可以扩展PointF

private readonly SizeF NS = new SizeF(0.0F, 1.0F);
private readonly SizeF EW = new SizeF(1.0F, 0.0F);
private readonly SizeF NESW = new SizeF(1.0F, 1.0F);
private readonly SizeF NWSE = new SizeF(-1.0F, 1.0F);

public static IEnumerable<Ray> GetCardinals(this PointF point)
{
    yield return new Ray(point + NS, point - NS);
    yield return new Ray(point + EW, point - EW);
    yield return new Ray(point + NESW, point - NESW);
    yield return new Ray(point + NWSE, point - NWSE);
}

我们可以找到两条射线的交点

static PointF Intersection(Ray one, Ray two)
{
    var delta = (one.A * two.B) - (two.A * one.B);

    if (delta == 0.0F)
    {
        //Lines are parallel
        return PointF.Empty;
    }
    else
    {
        var x = ((two.B * one.C) - (one.B * two.C)) / delta;
        var y = ((one.A * two.C) - (two.A * one.C)) / delta;
        return new PointF(x, y);
    }
}

所以,要得到两点基数的交点,

public static IEnumerable<PointF> GetCardinalIntersections(
    this PointF point,
    PointF other);
{
    return point.GetCardianls().SelectMany(other.GetCardinals(), Intersection)
        .Where(i => !i.IsEmpty());
}

然后启用,

public static IEnumerable<PointF> GetCardinalIntersections(
    this PointF point,
    IEnumerable<PointF> others);
{
    return others.SelectMany((o) => point.GetCardinalIntersections(o));
}

然后我们可以像这样使用这个功能。

var point = new PointF(1.0F, 1.0F);

var others = new [] { new PointF(2.0F, 5.0F), new PointF(-13.0F, 32.0F) };

var intersections = point.GetCardinalIntersections(others);

显然这里有很多迭代,我还没有编译或测试过它,但因为在它的核心,数学似乎相当有效,我对性能持乐观态度。

【讨论】:

  • 我知道这是一种很好的 OOP 实践,但我认为创建这么多对象只是为了执行线交叉不是一个好主意。
  • @ZongLi 是因为性能会变慢还是因为它会使用额外的内存?如果没有测试,我不相信任何一个都是重要的。但是,我确实移动了基数大小偏移的实例化。
  • 我认为在这种情况下没有必要进行诸如Ray对象之类的区分。我相信在这种情况下,目标代码只会混淆实际正在评估的一两个方程。
  • 我正在努力想象一个更简单的函数签名。我猜你是在建议float[][] Intersections(float x1, float y1, float x2, float y2)
  • 其实,没关系。我认为 OP 应该保留Ray 的想法并将其称为Vector,以坐标而不是标准形式表示。另外,我刚刚意识到您的代码中有一个小问题。由于我们正在研究出租车几何形状,因此两条非平行线不一定相交(请参阅我关于直线距离的回答)。
猜你喜欢
  • 1970-01-01
  • 2018-09-26
  • 2021-01-15
  • 1970-01-01
  • 2013-11-09
  • 2019-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多