Каков наилучший способ перетаскивания 3D-точки с помощью мыши. Проблема не в комплектации, а в перетаскивании в трехмерном пространстве.
Я думаю, что есть два способа, один из них — получить координаты вида на мир с помощью gluUnProject и перевести трехмерную точку. Проблема в этом случае состоит в том, что это только мир на поверхностях со значением Depth (с glReadPixels), если мышь покидает поверхность, она выдает максимальные или минимальные значения глубины, основанные на компоненте winZ gluUnProject. И это не работает в некоторых случаях.
Второй способ — перетаскивание по плоскости XY, XZ, YZ с помощью GL_MODELVIEW_MATRIX.
Но проблема в этом случае, как мы узнаем, что мы находимся в плоскости XY или XZ или YZ? Как мы можем знать, что вид спереди трекбола находится в плоскости XY, и что, если мы хотим перетащить боковую плоскость, а не переднюю плоскость?
Итак, есть ли способ, который дает мне точную 2D-3D-координату, чтобы я мог легко перетаскивать 3D-точку в любом направлении, не принимая во внимание плоскость? Должно быть несколько способов, я видел 3D-программы, они имеют отличную функцию перетаскивания.
Я привык решать эти проблемы взаимодействия с пользователем несколько наивно (возможно, не математически оптимально), но «достаточно хорошо», учитывая, что они не очень критичны по производительности (части взаимодействия с пользователем, не обязательно являющиеся результатом изменений в сцена).
Для свободного перетаскивания объекта без ограничений метод, который вы описали с помощью unproject, имеет тенденцию работать довольно хорошо, часто давая почти идеальное перетаскивание с небольшим изменением:
… Вместо того, чтобы использовать glReadPixels
чтобы попытаться извлечь глубину экрана, вам нужно понятие геометрического объекта / сетки, когда пользователь выбирает и / или выбрал. Теперь просто спроецируйте центральную точку этого объекта, чтобы получить глубину экрана. Затем вы можете перемещаться по экрану X / Y, сохраняя тот же Z, который вы получили от этой проекции, и снимать с проекции, чтобы получить результирующую дельту перевода из предыдущего центра в новый центр для преобразования объекта. Это также заставляет вас «чувствовать», как будто вы тащите из центра объекта, что, как правило, интуитивно понятно.
Для перетаскивания с автоматическим ограничением, быстрый способ обнаружить это сначала захватить «нормальную плоскость обзора». Быстрый способ (может вызвать недовольство математиков) с помощью тех функций проекции / снятия проекций, к которым вы привыкли, — это отменить проецирование двух точек в центре области просмотра в экранном пространстве (одна со значением около z и одна со значением далеко z) и получить единичный вектор между этими двумя точками. Теперь вы можете найти мировую ось, ближайшую к этой нормали, используя скалярное произведение. Две другие мировые оси определяют мировую плоскость, по которой мы хотим потащиться.
Затем становится простым использование снова этих удобных функций проецирования, чтобы направить луч вдоль курсора мыши. После этого вы можете делать повторные пересечения луча / плоскости, перемещая курсор вокруг, чтобы вычислить вектор перевода из дельты.
Для более гибких ограничений может пригодиться гизмо (он же манипулятор, в основном 3D-виджет), так что пользователь может указать, какой тип ограничения перетаскивания он хочет (плоский, осевой, без ограничений и т. Д.), Основываясь на том, какие части гизмо он кирки / тащит. Для ограничений оси удобно пересечение луча / линии или линии / линии.
Как было запрошено в комментариях, чтобы получить луч из области просмотра (C ++ — ish псевдокод):
// Get a ray from the current cursor position (screen_x and screen_y).
const float near = 0.0f;
const float far = 1.0f;
Vec3 ray_org = unproject(Vec3(screen_x, screen_y, near));
Vec3 ray_dir = unproject(Vec3(screen_x, screen_y, far));
ray_dir -= ray_org;
// Normalize ray_dir (rsqrt should handle zero cases to avoid divide by zero).
const float rlen = rsqrt(ray_dir[0]*ray_dir[0] +
ray_dir[1]*ray_dir[1] +
ray_dir[2]*ray_dir[2]);
ray_dir[0] *= rlen;
ray_dir[1] *= rlen;
ray_dir[2] *= rlen;
Затем мы делаем пересечение луча / плоскости с лучом, полученным от курсора мыши, чтобы выяснить, где луч пересекает плоскость, когда пользователь начинает перетаскивать (пересечение даст нам трехмерную точку). После этого он просто переводит объект с помощью дельт между точками, собранными при повторном выполнении этого, когда пользователь перетаскивает мышь. Объект должен интуитивно следовать за мышью при перемещении вдоль плоского ограничения.
Перетаскивание оси в основном та же идея, но мы превращаем луч в линию и делаем пересечение линия / линия (линия мыши против линии для ограничения оси, что дает нам ближайшую точку, поскольку линии обычно не пересекаются идеально), возвращая нам трехмерную точку, из которой мы можем использовать дельты для перемещения объекта по ограниченной оси.
Обратите внимание, что существуют хитрые крайние случаи, связанные с ограничениями перетаскивания оси / плоскости. Например, если плоскость перпендикулярна плоскости обзора (или близко), она может отстреливать объект в бесконечность. Такой же тип случая существует с перетаскиванием оси вдоль линии, которая перпендикулярна, как попытка перетаскивания вдоль оси Z из передней области просмотра (плоскость просмотра X / Y). Поэтому стоит выявить те случаи, когда линия / плоскость перпендикулярна (или близка), и предотвратить перетаскивание в таких случаях, но это можно сделать после того, как базовая концепция заработает.
Еще одна хитрость, которую стоит отметить, чтобы улучшить то, как вещи «чувствуют» в некоторых случаях, — это скрыть курсор мыши. Например, с ограничениями оси курсор мыши может в конечном итоге стать очень удаленным от самой оси, и это может выглядеть / ощущаться странно. Итак, я видел ряд коммерческих пакетов, которые просто скрывают курсор мыши в этом случае, чтобы не выявить это расхождение между мышью и гизмо / дескриптором, и в результате он кажется более естественным. Когда пользователь отпускает кнопку мыши, курсор мыши перемещается в визуальный центр ручки. Обратите внимание, что вы не должны выполнять это перетаскивание скрытым курсором для планшетов (они немного исключение).
Этот материал, связанный с отбором, перетаскиванием и пересечением, может быть очень сложным для отладки, поэтому стоит заняться этим в babysteps. Установите для себя небольшие цели, например, просто нажмите кнопку мыши в окне просмотра где-нибудь, чтобы создать луч. Затем вы можете вращаться вокруг и убедиться, что луч был создан в правильном положении. Затем вы можете попробовать простой тест, чтобы увидеть, пересекает ли этот луч плоскость в мире (скажем, X / Y), и создать / визуализировать точку пересечения между лучом и плоскостью и убедиться, что это правильно. Возьмите его маленькими, терпеливыми бабистами, шагайте сами, и вы получите плавный, уверенный прогресс. Попробуйте сделать слишком много за один раз, и вы можете получить очень обескураживающий резкий прогресс, пытаясь выяснить, где вы ошиблись.
Это очень интересный фильм. Как и методы, которые вы описали с помощью цветовой маркировки, z-буфер также является методом. Там были похожие дискуссии на ту же тему здесь:
OpenGL — комплектация (самый быстрый способ)
Как получить координаты объекта из экранных координат?
Это также похоже на выбор объектов, который был полностью обсужден, включая их плюсы и минусы:
http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/
На самом деле мой ответ — нет единственного лучшего способа сделать это. Как вы также упомянули, у них есть свои плюсы и минусы. Лично мне нравится глюунпроект, так как он прост и с его результатами все в порядке. Кроме того, вы не можете перемещать вершину в любом направлении, используя пространство экрана, так как пространство экрана является 2D, и нет уникального способа отобразить его обратно в трехмерное пространство геометрии. Надеюсь, поможет :).