我在 2D 平面上有两条射线,它们延伸到无穷远,但都有一个起点。它们都由一个起点和一个延伸到无穷远的射线方向上的向量来描述。我想知道两条射线是否相交,但我不需要知道它们在哪里相交(这是碰撞检测算法的一部分)。
到目前为止,我所看到的所有内容都描述了找到两条线或线段的交点。有没有快速的算法来解决这个问题?
我在 2D 平面上有两条射线,它们延伸到无穷远,但都有一个起点。它们都由一个起点和一个延伸到无穷远的射线方向上的向量来描述。我想知道两条射线是否相交,但我不需要知道它们在哪里相交(这是碰撞检测算法的一部分)。
到目前为止,我所看到的所有内容都描述了找到两条线或线段的交点。有没有快速的算法来解决这个问题?
我很抱歉不同意 Peter Walser 的回答。解方程在我的桌子上给出:
u = ((bs.y - as.y) * bd.x - (bs.x - as.x) * bd.y) / (bd.x * ad.y - bd.y * ad.x)
v = ((bs.y - as.y) * ad.x - (bs.x - as.x) * ad.y) / (bd.x * ad.y - bd.y * ad.x)
排除常用术语,这就是:
dx = bs.x - as.x
dy = bs.y - as.y
det = bd.x * ad.y - bd.y * ad.x
u = (dy * bd.x - dx * bd.y) / det
v = (dy * ad.x - dx * ad.y) / det
五次减法,六次乘法和两次除法。
如果只需要知道射线是否相交,u 和 v 的符号就足够了,这两个除法可以用 num*denom<0 或 (sign(num) != sign(denom)) 代替,这取决于什么在您的目标机器上更有效。
请注意,det==0 的罕见情况意味着光线不相交(另一种比较)。
给定:两条射线 a、b,起点(原矢量)as、bs 和方向矢量 ad、bd。
如果存在交点 p,则两条线相交:
p = as + ad * u
p = bs + bd * v
如果这个方程组有 u>=0 和 v>=0 的解(正方向使它们成为射线),则射线相交。
对于二维向量的 x/y 坐标,这意味着:
p.x = as.x + ad.x * u
p.y = as.y + ad.y * u
p.x = bs.x + bd.x * v
p.y = bs.y + bd.y * v
进一步的步骤:
as.x + ad.x * u = bs.x + bd.x * v
as.y + ad.y * u = bs.y + bd.y * v
解决 v:
v := (as.x + ad.x * u - bs.x) / bd.x
针对 u 插入和求解:
as.y + ad.y * u = bs.y + bd.y * ((as.x + ad.x * u - bs.x) / bd.x)
u := (as.y*bd.x + bd.y*bs.x - bs.y*bd.x - bd.y*as.x ) / (ad.x*bd.y - ad.y*bd.x)
计算 u,然后计算 v,如果两者都为正则射线相交,否则不相交。
一条射线可以用点的集合来表示A + Vt
,其中A
是起点,V
是表示射线方向的向量,t >= 0
是参数。因此,要确定两条射线是否相交,请执行以下操作:
bool DoRaysIntersect(Ray r1, Ray r2)
{
// Solve the following equations for t1 and t2:
// r1.A.x + r1.V.x * t1 == r2.A.x + r2.V.x * t2
// r1.A.y + r1.V.y * t1 == r2.A.y + r2.V.y * t2
if(no solution) // (e.g. parallel lines)
{
if(r1 == r2) // same ray?
return true;
else
return false; // parallel, non-intersecting
}
else // unique solution
{
if(t1 >= 0 && t2 >= 0)
return true;
else
return false; // they would intersect if they are lines, but they are not lines
}
}
根据此处的其他答案,我在尝试找到两条光线之间的交点时发现了这篇文章。以防万一其他人来到这里寻找相同的答案,这里有一个 TypeScript / JavaScript 的答案。
/**
* Get the intersection of two rays, with origin points p0 and p1, and direction vectors n0 and n1.
* @param p0 The origin point of the first ray
* @param n0 The direction vector of the first ray
* @param p1 The origin point of the second ray
* @param n1 The direction vector of the second ray
* @returns
*/
export function getRaysIntersection(
p0: number[],
n0: number[],
p1: number[],
n1: number[]
): number[] | undefined {
const dx = p1[0] - p0[0];
const dy = p1[1] - p0[1];
const det = n1[0] * n0[1] - n1[1] * n0[0];
const u = (dy * n1[0] - dx * n1[1]) / det;
const v = (dy * n0[0] - dx * n0[1]) / det;
if (u < 0 || v < 0) return undefined; // Might intersect as lines, but as rays.
const m0 = n0[1] / n0[0];
const m1 = n1[1] / n1[0];
const b0 = p0[1] - m0 * p0[0];
const b1 = p1[1] - m1 * p1[0];
const x = (b1 - b0) / (m0 - m1);
const y = m0 * x + b0;
return Number.isFinite(x) ? [x, y] : undefined;
}
此处演示:https ://codesandbox.io/s/intersection-of-two-rays-mcwst
GeomAlgorithms.com有一些非常棒的算法来处理 3D 中的线......不过一般来说,两条线在 3D 空间中相交的概率真的很低。
在 2D 中,您必须检查斜率。如果斜率不相等,则它们相交。如果斜率相等,如果它们上的一个点具有相同的 x 坐标或相同的 y 坐标,则它们相交。
线由点p和向量v表示:
line = p + a * v(对于所有 a)
光线是那条线的(正)一半:
ray = p + a * v(对于所有 a >= 0)
要确定两条线是否相交,请将它们设置为相等并求解:
交点发生在p 1 + a 1 * v 1 = p 2 + a 2 * v 2
(注意有两个未知数 a 1和 a 2以及两个方程,因为p和v是多维度)
求解1和2 - 如果它们都是非负数,则它们相交。如果一个是负数,它们就不会相交。
用于 Guntners 解决方案的 c++
bool RaysIntersection(const Point& as, const Point& ad, const Point& bs, const Point& bd, Point& result)
{
if (as == bs) {
result = as;
return true;
}
auto dx = bs.X - as.X;
auto dy = bs.Y - as.Y;
auto det = bd.X * ad.Y - bd.Y * ad.X;
if (det != 0) { // near parallel line will yield noisy results
double u = (dy * bd.X - dx * bd.Y) / (double)det;
double v = (dy * ad.X - dx * ad.Y) / (double)det;
if (u >= 0 && v >= 0) {
result = as + ad * u;
return true;
}
}
return false;
}
我只想检查两条射线是否相交。我将通过计算从两条射线创建的两个“三角形”的旋转方向来解决这个问题。它们并不是真正的三角形,但从数学的角度来看,如果我只想计算三角形的旋转,我只需要两个具有共同起点的向量,其余的都无所谓。
第一个三角形将由两个向量和一个起点组成。起点将是第一条射线的起点。第一个向量将是第一条光线的方向向量。第二个向量将是从第一条射线的起点到第二条射线的起点的向量。从这里我们取两个向量的叉积并注意符号。
我们对第二个三角形再次这样做。同样,起点是第二条射线的起点。第一个向量是第二条射线的方向,第二个向量是从第二条射线的起点到第一条射线的起点。我们再次取向量的叉积并注意符号。
现在我们简单地取这两个标志并检查它们是否相同。如果它们相同,我们就没有交集。如果它们不同,我们就有一个交叉点。而已!
这是一些伪代码:
sign1 = cross(vector1, point1 - point2)
sign2 = cross(vector2, point2 - point1)
if (sign1 * sign2 < 0) // If signs are mismatched, they will multiply to be negative
return intersection
结果是五次乘法,六次减法和一次比较。
如果线是无限长的,那么它们将永远相交,除非它们是平行的。要检查它们是否平行,请找到每条线的斜率并进行比较。斜率仅为 (y2-y1)/(x2-x1)。