使用C#检测2D空间中的光投影和交叉点

时间:2012-07-27 01:14:35

标签: c# .net geometry 2d raytracing

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

在不同位置周围有多个光源,每个光源在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?
    }
}

3 个答案:

答案 0 :(得分:2)

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

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

您可以将每个对角线/反对角线描述为矢量方程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)

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

答案 1 :(得分:1)

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

你可以创建四个bool(或位)数组。

  1. 卧式
  2. VERTI
  3. DIAGR
  4. DiagL
  5. 对于我们的每个光源,我们将它们“投射”到那些一维阵列上。 (在图片中我只展示了两个投影)。

    enter image description here

    投影到Horiz和Verti很简单。

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

    现在,您可以简单地遍历所有网格点,并查看其中至少有两个投影是否设置为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的交叉点。

    这将返回所有光线交点的列表,但也包括光源的点。

    您可以忽略所有有点源的交叉点,也可以使用一些独创性来解释这些点。

答案 2 :(得分:1)

我从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);

显然这里有很多迭代,我没有编译或测试过这个,但是从它的结尾看,数学似乎相当有效,我对性能持乐观态度。