我希望确定光线和盒子之间的交点。该框由其最小3D坐标和最大3D坐标定义,并且光线由其原点和它指向的方向定义。
目前,我正在为盒子的每个面形成一个平面,我正在将光线与平面相交。如果光线与平面相交,那么我检查交叉点是否实际位于框的表面上。如果是这样,我检查它是否是此光线的最近交点,并返回最近的交点。
我检查平面交叉点是否在盒子表面上的方式是通过函数
bool PointOnBoxFace(R3Point point, R3Point corner1, R3Point corner2)
{
double min_x = min(corner1.X(), corner2.X());
double max_x = max(corner1.X(), corner2.X());
double min_y = min(corner1.Y(), corner2.Y());
double max_y = max(corner1.Y(), corner2.Y());
double min_z = min(corner1.Z(), corner2.Z());
double max_z = max(corner1.Z(), corner2.Z());
if(point.X() >= min_x && point.X() <= max_x &&
point.Y() >= min_y && point.Y() <= max_y &&
point.Z() >= min_z && point.Z() <= max_z)
return true;
return false;
}
其中corner1
是该框面的矩形的一角,corner2
是对角。我的实现大部分时间都在工作,但有时它给了我错误的交集。请看图片:
图像显示来自相机眼睛并撞击盒子表面的光线。其他光线是盒子表面的法线。可以看出,特别是一条光线(它实际上是看到的法线)从盒子的“背面”出来,而法线应该从盒子的顶部出来。这似乎很奇怪,因为有多个其他光线正确地击中了盒子的顶部。
我想知道我正在检查交叉点是否在盒子上的方式是正确的还是我应该使用其他算法。
感谢。
答案 0 :(得分:15)
通过epsilon增加效果实际上并不是一种很好的方法,因为你现在在你的盒子边缘有一个大小为epsilon的边框,光线可以穿过它。所以你将摆脱这个(相对常见的)奇怪的错误集,并最终得到另一组(更罕见的)奇怪的错误。
我假设你已经想象你的光线沿着它的矢量以某种速度行进并找到与每个平面相交的时间。因此,例如,如果您在x=x0
处与平面相交,并且您的光线从(rx,ry,rz)
开始朝向(0,0,0)
方向,则交叉时间为t = x0/rx
。如果t
为负数,则忽略它 - 你会走另一条路。如果t
为零,你必须决定如何处理这种特殊情况 - 如果你已经在飞机上,你会反弹它,还是通过它?您可能还希望将rx==0
作为特殊情况处理(以便您可以触及方框的边缘)。
无论如何,现在你确切地知道你撞到那架飞机的坐标:它们是(t*rx , t*ry , t*rz)
。现在您可以读出t*ry
和t*rz
是否在他们需要的矩形内(即沿着这些轴的立方体的最小值和最大值之间)。 你没有测试x坐标,因为你已经知道你已经点击它了再次,你必须决定是否/如何处理角落作为特殊情况。此外,现在您可以按时间对各种曲面进行碰撞,并选择第一个作为碰撞点。
这使您可以在不借助任意ε因子的情况下计算光线是否与您的立方体相交,以及使用浮点运算实现精确度。
所以你只需要三个函数,比如你已经得到的函数:一个用于测试你是否在yz
内点击x
,以及xz
和xy
对应的函数y
假设您分别点击了z
和#define X_FACE 0
#define Y_FACE 1
#define Z_FACE 2
#define MAX_FACE 4
// true if we hit a box face, false otherwise
bool hit_face(double uhit,double vhit,
double umin,double umax,double vmin,double vmax)
{
return (umin <= uhit && uhit <= umax && vmin <= vhit && vhit <= vmax);
}
// 0.0 if we missed, the time of impact otherwise
double hit_box(double rx,double ry, double rz,
double min_x,double min_y,double min_z,
double max_x,double max_y,double max_z)
{
double times[6];
bool hits[6];
int faces[6];
double t;
if (rx==0) { times[0] = times[1] = 0.0; }
else {
t = min_x/rx;
times[0] = t; faces[0] = X_FACE;
hits[0] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
t = max_x/rx;
times[1] = t; faces[1] = X_FACE + MAX_FACE;
hits[1] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
}
if (ry==0) { times[2] = times[3] = 0.0; }
else {
t = min_y/ry;
times[2] = t; faces[2] = Y_FACE;
hits[2] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
t = max_y/ry;
times[3] = t; faces[3] = Y_FACE + MAX_FACE;
hits[3] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
}
if (rz==0) { times[4] = times[5] = 0.0; }
else {
t = min_z/rz;
times[4] = t; faces[4] = Z_FACE;
hits[4] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
t = max_z/rz;
times[5] = t; faces[5] = Z_FACE + MAX_FACE;
hits[5] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
}
int first = 6;
t = 0.0;
for (int i=0 ; i<6 ; i++) {
if (times[i] > 0.0 && (times[i]<t || t==0.0)) {
first = i;
t = times[i];
}
}
if (first>5) return 0.0; // Found nothing
else return times[first]; // Probably want hits[first] and faces[first] also....
}
。
编辑:添加到(详细)的代码显示如何针对每个轴进行不同的测试:
i
(我只是键入了这个,没有编译它,所以要小心bug。)
(编辑:刚刚更正了first
- &gt; {{1}}。)
无论如何,重点是你分别处理这三个方向,并测试是否在(u,v)坐标中的右框内发生了撞击,其中(u,v)是(x,y) ,(x,z)或(y,z)取决于你击中的平面。
答案 1 :(得分:2)
PointOnBoxFace
应该是二维检查而不是三维检查。例如,如果您要对z = z_min
平面进行测试,那么您只需要将x
和y
与各自的边界进行比较。您已经发现z
坐标是正确的。当您“重新检查”第三个坐标时,浮点精度可能会使您绊倒。
例如,如果z_min
为1.0,则首先针对该平面进行测试。您会找到(x
,y
,0.999999999)的交叉点。现在,即使x
和y
在范围内,z
也不是很正确。
答案 2 :(得分:0)
代码看起来很好。尝试找到这个特定的光线并进行调试。
答案 3 :(得分:0)
编辑:忽略这个答案(请参阅下面的评论,我非常有说服力地证明了我的方式错误。)
您正在测试该点是否在体积内,但该点位于该体积的外围,因此您可能会发现它在体积外的“无限小”距离。尝试通过一些小的epsilon来增长盒子,例如:
double epsilon = 1e-10; // Depends the scale of things in your code.
double min_x = min(corner1.X(), corner2.X()) - epsilon;
double max_x = max(corner1.X(), corner2.X()) + epsilon;
double min_y = min(corner1.Y(), corner2.Y()) - epsilon;
...
从技术上讲,比较几乎相等数字的正确方法是将它们的位表示转换为整数并比较整数以获得一些小的偏移量,例如在C:
中#define EPSILON 10 /* some small int; tune to suit */
int almostequal(double a, double b) {
return llabs(*(long long*)&a - *(long long*)&b) < EPSILON;
}
当然,在C#中这并不容易,但也许不安全的语义可以达到同样的效果。 (感谢@Novox的评论,这让我有一个很好的page详细解释了这个技术。)
答案 4 :(得分:0)
难道光线最终会完全穿过盒子的边缘吗?浮点舍入误差可能会导致右侧和背面都错过它。