Я привык к решению этих проблем взаимодействия с пользователем несколько наивно (возможно, не математически оптимальным способом), но «достаточно хорошо», учитывая, что они не очень критичны по производительности (части взаимодействия с пользователем, не обязательно в результате изменения сцены).
Для неограниченного свободного перетаскивания объекта, метод, который вы используете описанный unproject, как правило, работает достаточно хорошо, чтобы часто дает около пиксельной идеально увлекая с небольшой подстройкой:
... вместо того, чтобы использовать glReadPixels
, чтобы попытаться экстракт глубины экрана, вы хотите, чтобы понятие геометрического объекта/сетки было выбрано пользователем и/или выбрано. Теперь просто проецируйте центральную точку этого объекта, чтобы получить глубину экрана. Затем вы можете перемещаться по экрану X/Y, сохраняя тот же Z, который вы получили от этой проекции, и unproject, чтобы получить результирующую дельту перевода из предыдущего центра в новый центр, чтобы преобразовать объект. Это также заставляет его «чувствовать», как будто вы перетаскиваете из центра объекта, который имеет тенденцию быть достаточно интуитивным.
Для автоматического сдерживания перетаскивания - быстрый способ обнаружить, что нужно сначала захватить «обычный экран просмотра». Быстрый способ (может заставить математиков хмуриться) использовать эти функции проекции/непроекции, к которым вы привыкли, - это непроектировать две точки в центре видового экрана в пространстве экранов (одно с близким значением z и одно с большим значением z) и получить единичный вектор между этими двумя точками. Теперь вы можете найти ось мира, ближайшую к этой норме, используя точечный продукт. Две другие мировые оси определяют мир, который мы хотим перетащить.
Затем становится проще использовать эти удобные функции непроекции, чтобы получить луч вдоль курсора мыши. После этого вы можете выполнять повторные пересечения лучей/плоскости, когда вы перетаскиваете курсор, чтобы вычислить вектор трансляции из дельта.
Для более гибких ограничений может понадобиться gizmo (ака-манипулятор, в основном 3D-виджет), чтобы пользователь мог указать, какое именно ограничение перетаскивания он хочет (планарный, осевой, без ограничений и т. Д.), На основе которых из гизмо он выбирает/тащит. Для ограничений по осям удобно использовать пересечение лучей/линий или линий/линий.
В соответствии с просьбой в комментариях, чтобы получить луч из окна просмотра (C++ - иш псевдокод):
// 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), и создаст/визуализирует точку пересечения между лучом и плоскостью и убедитесь, что это правильно. Возьмите его маленькими, терпеливыми малышами, расхаживая себя, и у вас будет плавный, уверенный прогресс. Постарайтесь сделать слишком много сразу, и вы можете очень обескураживать резкий прогресс, пытаясь выяснить, где вы поступили неправильно.
Большинство пакетов моделирования предоставляют вам возможность перетаскивать по плоскостям XY/XZ/YZ с помощью 3D-виджета и/или ярлыка для их выбора. Например. в блендере вы можете 'G',' Shift' + 'Z' перемещаться вдоль XY. Но по умолчанию, когда вы перетаскиваете его, он перемещается по произвольной плоскости, не ориентированной по оси, - по направлению движения камеры. Это имеет смысл, потому что у вас есть плоский монитор и 2D-мышь. Чтобы перетащить по произвольной плоскости, используйте тест пересечения лучевой плоскости (где луч - проецируемая мышь): http://stackoverflow.com/a/30506030/1888983 – jozxyqk
@jozxyqk да, вот что я сейчас думаю, я придумал два решения: один должен двигаться на поверхность, потому что мы знаем глубину поверхности. Второе решение состояло бы в том, чтобы перетащить вдоль трех плоскостей. Таким образом, пользователь будет иметь 4 варианта, перетащить на поверхность (или может следовать за поверхностью), XY, XZ и YZ. – furqan
Я просто думал о том, чтобы перетащить на плоскость в точку, перпендикулярную направлению просмотра, но, как вы говорите, перетаскивая по поверхности, используя обычный вектор точки ([eg] (http://blender.stackexchange.com/ вопросы/3413/это-возможно-к-перемещать-части-модели-по-выбранному-нормали)), звучит весьма полезно и что-то менее распространенное в моделерах. – jozxyqk