Увеличение количества эпсилонами на самом деле не является хорошим способом сделать это, так как теперь у вас есть граница размера эпсилон на краю вашей коробки, через которую могут проходить лучи. Таким образом, вы избавитесь от этого (относительно распространенного) странного набора ошибок и в итоге получите еще один (более редкий) набор странных ошибок.
Я предполагаю, что вы уже представляете, что ваш луч движется с некоторой скоростью вдоль своего вектора, и находите время пересечения с каждой плоскостью. Так, например, если вы пересекаете плоскость в 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
-> first
.)
В любом случае, дело в том, что вы обрабатываете три направления по отдельности и проверяете, произошло ли воздействие в пределах правого поля в координатах (u, v), где (u, v) равны (x, y) , (x, z) или (y, z) в зависимости от того, в какую плоскость вы попали.