Я создал демонстрацию с простой трехмерной демонстрацией от первого лица с использованием 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
Теперь я знаю, что числа с плавающей точкой являются лишь приблизительными значениями действительных чисел, поэтому я предполагаю, что эта неточность является просто результатом округления с плавающей точкой, хотя возможно, что я допустил ошибку где-то еще в коде. Я знаю, что пересечение отключено только на очень небольшую величину, но я все еще забочусь об этом, потому что я планирую использовать эти точки для определения вершин модели или уровня, привязывая их к произвольно ориентированной сетке, поэтому я на самом деле хочу эти точки должны быть на этой сетке, даже если они немного неточны. Это может быть ошибочным подходом — я действительно не знаю.
Итак, мой вопрос: эта неточность — просто округление с плавающей точкой на работе, или я ошибся где-то еще?
Если это просто округление с плавающей запятой, есть ли способ с этим справиться? Я пытался округлить значения векторов вращения и положения различными способами, что, очевидно, приводит к менее точной точке пересечения, но я все еще иногда получаю пересечения, которые не находятся на плоскости. Я прочитал ответ на аналогичный вопрос (Является ли этот код пересечения плоских лучей правильным?) что упоминает о сохранении больших размеров, но я не уверен точно, что это значит.
Извините, если этот вопрос был задан ранее — я искал, но я не увидел ничего такого, с чем у меня были проблемы. Спасибо!
Ваша математика кажется правильной, и это определенно похоже на ошибку округления. У меня сильное чувство, что именно эта линия:
GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);
Тем не менее, я не вижу другого метода для вычисления т. Вы можете проверить, теряете ли вы точность, используя «% .12f» (или более) в ваших выражениях printf. Другой способ определить виновника — попытаться выполнить ваши t-вычисления шаг за шагом и распечатать результаты по пути, чтобы увидеть, теряете ли вы где-то точность.
Вы пытались использовать двойную точность с плавающей точкой, если точность действительно так важна для вас?
Других решений пока нет …