介绍
通常,在开发游戏时,必须找到直线,线段,光线等的交点。本文中如何以最简单的方式实现这一点。
流行方法及其批评
也许许多人会记住从学校代数开始的一种方法-制作两条直线的方程式,将它们的右手边等价,找到x,然后将其代入直线方程式以找到y(更多详细信息在这里)。
但是,此方法在编写代码时变得非常麻烦(也许这就是为什么现在阅读本文),而且,它不是通用的:如果其中一条直线平行于Y轴,则在计算斜率时将得到除以零的误差,因此必须为这种情况注册代码;如果两条线是平行的,那么您也需要修改这种情况。这样的代码又长又丑。
为了找到更优雅的解决方案,我偶然发现了一些基于矢量乘法的有趣方法(habr.com/ru/post/267037)和射线投射(ru.wikipedia.org/wiki/Ray_casting#Concept)。但是我认为它们在计算上是不合理的复杂。因此,我向您介绍(和批评)我的方法。
我的方式
任务
给出了两个线段的坐标。您需要找出线段是否相交,如果相交,则在什么时候相交。为此,我们将编写一个函数。
决断
避免误解的图例:a-向量a,(y)-向量a在Y轴上的投影,{x1,y1}-向量a,由坐标x1,y1指定。
让我们以两个向量的形式表示我们的细分:{x2-x1; y2-y1}和b {x3-x4; y3-x4}。请注意,向量b的方向与预期相反。让我们介绍一个向量c {x3-x1; y3-y1}。注意a * k + b * n = c,其中k,n是一些系数。因此,我们获得了一个方程组:
a(x)* k + b(x)* n = c(x)
a(y)* k + b(y)* n = c(y)
我们的任务被简化为寻找这些系数(说实话,只找到其中之一就足够了)。
我建议将下式的两边乘以q = -a(x)/ a(y)。因此,在添加了两个方程式之后,我们立即摆脱了k。将n简化为求解普通线性方程。重要的是要注意,n可能没有解。
细心的读者会注意到,当(y)= 0时,我们会得到一个错误。让我们在找到(y)的阶段编写分支。这种情况甚至更简单,因为我们立即得到了一个未知数的方程。
我建议您尝试自行打印n,这样可以更清楚地了解下面代码中的内容。
知道n,就可以找到交点,为此,我们从点(x3,y3)的坐标中减去向量b * n
把它放在一起
float dot[2]; //
bool cross(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
float n;
if (y2 - y1 != 0) { // a(y)
float q = (x2 - x1) / (y1 - y2);
float sn = (x3 - x4) + (y3 - y4) * q; if (!sn) { return 0; } // c(x) + c(y)*q
float fn = (x3 - x1) + (y3 - y1) * q; // b(x) + b(y)*q
n = fn / sn;
}
else {
if (!(y3 - y4)) { return 0; } // b(y)
n = (y3 - y1) / (y3 - y4); // c(y)/b(y)
}
dot[0] = x3 + (x4 - x3) * n; // x3 + (-b(x))*n
dot[1] = y3 + (y4 - y3) * n; // y3 +(-b(y))*n
return 1;
}
如果线段的直线(即直线)相交,则此函数获取顶点的坐标并返回值1;否则,返回0。如果需要顶点的坐标,则可以从点[]数组中获取它们。
重要提示:当引入两条重合线时,该算法不会显示任何交集。该算法找到线段所在的线的交点,因此该点可能在线段之外(您必须在代码中检入)。
让我们应用函数:
int main() {
if (cross(1,1,7,2, 7,3,5,6)) {
std::cout << dot[0] << " " << dot[1] << std::endl;
}
else {
std::cout<<"Not cross!"<<std::endl;
}
return 0;
}
后记
尽管我在搜索问题过程中没有遇到过这种方法,而是自己开发了算法,但我并不假装自己是完全原创的(而且是正确的)。所以欢迎发表评论!