У меня есть сфера (представляющая Землю), и я хочу, чтобы пользователь мог перемещать мышь, пока я отслеживаю точку под этим положением мыши на сфере.
Все отлично работает пока камера находится на разумной высоте от поверхности сферы (скажем, эквивалент по крайней мере нескольких сотен метров в реальной жизни).
Но если я увеличу масштаб слишком близко к поверхности сферы (скажем, 30 метров над поверхностью), то я начну наблюдать странное поведение: все точки, которые я рисую сейчас, начинают «привязываться» к некоторой предопределенной решетке в пространстве, и если я попытаюсь нарисовать несколько линий, которые пересекаются в точке на поверхности непосредственно под мышью, они вместо этого «привязываются» к некоторой соседней точке, нигде под курсором.
В частности, я использую следующий код для отображения точки из 3D в 2D и обратно:
double line_sphere_intersect(double const (&o)[3], double const (&d)[3], double const r)
{
double const
dd = d[0] * d[0] + d[1] * d[1] + d[2] * d[2],
od = o[0] * d[0] + o[1] * d[1] + o[2] * d[2],
oo = o[0] * o[0] + o[1] * o[1] + o[2] * o[2],
left = -od,
right = sqrt(od * od - dd * (oo - r * r)),
r1 = (left + right) / dd,
r2 = (left - right) / dd;
return ((r1 < 0) ^ (r1 < r2)) ? r1 : r2;
}
Point3D mouse_pos_to_coord(int x, int y)
{
GLdouble model[16]; glGetDoublev(GL_MODELVIEW_MATRIX, model);
GLdouble proj[16]; glGetDoublev(GL_proj_MATRIX, proj);
GLint view[4]; glGetIntegerv(GL_view, view);
y = view[3] - y; // invert y axis
GLdouble a[3]; if (!gluUnProject(x, y, 0 , model, proj, view, &a[0], &a[1], &a[2])) { throw "singular"; }
GLdouble b[3]; if (!gluUnProject(x, y, 1 - 1E-4, model, proj, view, &b[0], &b[1], &b[2])) { throw "singular"; }
for (size_t i = 0; i < sizeof(b) / sizeof(*b); ++i) { b[i] -= a[i]; }
double const t = line_sphere_intersect(a, b, earth_radius / 1000);
Point3D result = Point3D(t * b[0] + a[0], t * b[1] + a[1], t * b[2] + a[2]);
Point3D temp;
if (false /* changing this to 'true' changes things, see question */)
{
gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
gluUnProject(temp.X, temp.Y, 1 - 1E-4, model, proj, view, &result.X, &result.Y, &result.Z);
gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
}
return result;
}
со следующими матрицами:
glMatrixMode(GL_PROJECTION);
gluPerspective(
60, (double)viewport[2] / (double)viewport[3], pow(FLT_EPSILON, 0.9),
earth_radius_in_1000km * (0.5 + diag_dist / tune_factor / zoom_factor));
glMatrixMode(GL_MODELVIEW);
gluLookAt(eye.X, eye.Y, eye.Z, 0, 0, 0, 0, 1, 0);
где eye
текущее местоположение камеры над Землей.
(И да, я пользуюсь double
везде, так что это не должно быть проблемой точности с float
.)
Кроме того, я заметил, что если я изменю if (false)
в if (true)
в моем коде красные линии теперь, кажется, пересекаются прямо под курсором, что я нахожу сбивающим с толку. (Изменить: я не уверен, что сопоставленная точка все еще правильна, хотя … мне трудно сказать.)
Это подразумевает, что красные линии правильно пересекаются, когда соответствующая координата «Z» позиции 2D курсора (то есть относительно окна) составляет почти 1… но когда он вырождается примерно до 0.9
или ниже, тогда я начинаю видеть проблему «щелчка».
Я не понимаю, как или почему это влияет на что-либо, хотя. Почему координата Z влияет на такие вещи? Это нормально? Как мне исправить эту проблему?
Просто потому, что вы используете double
не означает, что у вас не будет проблем с точностью. Если вы используете большие числа, вы можете представлять небольшие дробные изменения менее точно. Википедия имеет достойное объяснение точности с плавающей запятой:
Малые значения, близкие к нулю, могут быть представлены с гораздо более высоким разрешением (например, одним фемтометром), чем большие, потому что для кодирования значительно больших значений необходимо выбирать больший масштаб (например, световые годы).
Вы можете попробовать длинный дубль в качестве эксперимента. Если вы попробуете это, и проблема решится или, по крайней мере, улучшится, вы поймете, что есть проблема с точностью.
Начните сообщать некоторые цифры, которые вы можете сравнить, а не полагаться на графическое представление. Это также устранит код рисования как источник проблем. Если числа выглядят правильно, то, вероятно, что-то не так с кодом рисования, а не с вычислениями пересечений.
Получите несколько модульных тестов для ваших функций, которые доказывают, что вы получаете числа, которые вы ожидаете в промежуточных точках.
Похоже, видеокарта внутренне усекается до 32-разрядных операций с плавающей запятой для рендеринга (но, возможно, я использую 64-разрядные операции с плавающей запятой для некоторых других вычислений, я не уверен).
Похоже, это верно как для моей карты Intel, так и для моей карты NVIDIA.
Перецентрирование системы координат вокруг центра карты, похоже, решает проблему.