Пересечение плоскости луча: неточные результаты — ошибки округления?

Я создал демонстрацию с простой трехмерной демонстрацией от первого лица с использованием C ++ и OpenGL, и она, кажется, работает достаточно хорошо. Моя цель заключается в следующем: когда пользователь направляет камеру на плоскость и нажимает левую кнопку мыши, я хочу нарисовать пересечение луча, указывающего в направлении камеры с позиции игрока с этой плоскостью.

Итак, я начинаю с двух векторов, Vector position а также Vector rotationгде Vector — довольно стандартный трехмерный векторный класс:

class Vector
{
public:
GLfloat x, y, z;

Vector() {};

Vector(GLfloat x, GLfloat y, GLfloat z)
{
this->x = x;
this->y = y;
this->z = z;
}

GLfloat dot(const Vector &vector) const
{
return x * vector.x + y * vector.y + z * vector.z;
}

... etc ...

А также Plane pс плоскостью, являющейся простой структурой, хранящей нормаль плоскости, и d. Я скопировал эту структуру прямо из книги Кристера Эриксона «Обнаружение столкновений в реальном времени»:

struct Plane
{
Vector n; // Plane normal. Points x on the plane satisfy Dot(n,x) = d
float d; // d = dot(n,p) for a given point p on the plane
};

Для начала я беру position как начало луча, который я называю a, Я использую эту точку и rotation найти конец луча, b, Затем я использую алгоритм для нахождения пересечения луча и плоскости из той же книги. Я на самом деле реализовал тот же метод сам, но я использую код из книги прямо здесь, просто чтобы убедиться, что я ничего не испортил:

void pickPoint()
{
const float length = 100.0f;

// Points a and b
Vector a = State::position;
Vector b = a;

// Find point b of directed line ab
Vector radians(Math::rad(State::rotation.x), Math::rad(State::rotation.y), 0);
const float lengthYZ = Math::cos(radians.x) * length;

b.y -= Math::sin(radians.x) * length;
b.x += Math::sin(radians.y) * lengthYZ;
b.z -= Math::cos(radians.y) * lengthYZ;

// Compute the t value for the directed line ab intersecting the plane
Vector ab = b - a;

GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

printf("Plane normal: %f, %f, %f\n", p.n.x, p.n.y, p.n.z);
printf("Plane value d: %f\n", p.d);
printf("Rotation (degrees): %f, %f, %f\n", State::rotation.x, State::rotation.y, State::rotation.z);
printf("Rotation (radians): %f, %f, %f\n", radians.x, radians.y, radians.z);
printf("Point a: %f, %f, %f\n", a.x, a.y, a.z);
printf("Point b: %f, %f, %f\n", b.x, b.y, b.z);
printf("Expected length of ray: %f\n", length);
printf("Actual length of ray: %f\n", ab.length());
printf("Value t: %f\n", t);

// If t in [0..1] compute and return intersection point
if(t >= 0.0f && t <= 1.0f)
{
point = a + t * ab;
printf("Intersection: %f, %f, %f\n", point.x, point.y, point.z);
}
// Else no intersection
else
{
printf("No intersection found\n");
}

printf("\n\n");
}

Когда я отрисовываю эту точку с помощью OpenGL, она выглядит довольно близко к месту пересечения луча и плоскости. Но из распечатки фактических значений я обнаружил, что для определенных позиций и поворотов точка пересечения может быть отключена до 0,000004. Вот пример, где пересечение является неточным — я знаю, что точка пересечения НЕ находится на плоскости, потому что ее значение Y должно быть 0, а не 0,000002. Я мог бы также вставить его обратно в уравнение плоскости и получить неравенство:

Plane normal: 0.000000, 1.000000, 0.000000
Plane value d: 0.000000
Rotation (degrees): 70.100044, 1.899823, 0.000000
Rotation (radians): 1.223477, 0.033158, 0.000000
Point a: 20.818802, 27.240383, 15.124892
Point b: 21.947229, -66.788452, -18.894285
Expected length of ray: 100.000000
Actual length of ray: 100.000000
Value t: 0.289702
Intersection: 21.145710, 0.000002, 5.269455

Теперь я знаю, что числа с плавающей точкой являются лишь приблизительными значениями действительных чисел, поэтому я предполагаю, что эта неточность является просто результатом округления с плавающей точкой, хотя возможно, что я допустил ошибку где-то еще в коде. Я знаю, что пересечение отключено только на очень небольшую величину, но я все еще забочусь об этом, потому что я планирую использовать эти точки для определения вершин модели или уровня, привязывая их к произвольно ориентированной сетке, поэтому я на самом деле хочу эти точки должны быть на этой сетке, даже если они немного неточны. Это может быть ошибочным подходом — я действительно не знаю.

Итак, мой вопрос: эта неточность — просто округление с плавающей точкой на работе, или я ошибся где-то еще?

Если это просто округление с плавающей запятой, есть ли способ с этим справиться? Я пытался округлить значения векторов вращения и положения различными способами, что, очевидно, приводит к менее точной точке пересечения, но я все еще иногда получаю пересечения, которые не находятся на плоскости. Я прочитал ответ на аналогичный вопрос (Является ли этот код пересечения плоских лучей правильным?) что упоминает о сохранении больших размеров, но я не уверен точно, что это значит.

Извините, если этот вопрос был задан ранее — я искал, но я не увидел ничего такого, с чем у меня были проблемы. Спасибо!

2

Решение

Ваша математика кажется правильной, и это определенно похоже на ошибку округления. У меня сильное чувство, что именно эта линия:

GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

Тем не менее, я не вижу другого метода для вычисления т. Вы можете проверить, теряете ли вы точность, используя «% .12f» (или более) в ваших выражениях printf. Другой способ определить виновника — попытаться выполнить ваши t-вычисления шаг за шагом и распечатать результаты по пути, чтобы увидеть, теряете ли вы где-то точность.

Вы пытались использовать двойную точность с плавающей точкой, если точность действительно так важна для вас?

1

Другие решения

Других решений пока нет …