Готовый перевод Ogre3D Intermediate Tutorials / Промежуточные туториалы Ogre3D: Intermediate Tutorial 4: Volume Selection and Basic Manual Objects
ВВЕДЕНИЕ
В этом уроке мы рассмотрим, как сделать выделение некоторого объема сцены.
Идея состоит в том, что когда вы нажимаете кнопку мыши и протягиваете ее через экран, область выделения будет отслежена белым прямоугольником.
Когда вы отпустите кнопку мыши, все объекты, находящиеся в этой области, будут выделены.
Для достижения этого мы изучим возможности использования двух объектов: ManualObject (для создания выделяющего прямоугольника) и PlaneBoundedVolumeListSceneQuery.
Обратите внимание, что мы рассмотрим только некоторые базовые аспекты использования ManualObject, это только введение в него, а не урок, как создавать завершенные 3D объекты с его помощью.
Мы рассмотрим только то, что мы будем использовать.
ПРЕДВАРИТЕЛЬНЫЕ УСЛОВИЯ
Убедитесь, что на данный момент вы имеете каркас базовых уроков (Basic Tutorial Framework).
Затем убедитесь, что заголовочный файл и файл реализации вашего приложения выглядят как-то так:
Удостоверьтесь, что этот код компилируется, прежде чем продолжать.
Когда вы запустите его, вы должны увидеть, что вы можете двигать курсором мыши, но кроме этого приложение на данный момент ничего не делает.
Для выхода нажмите Esc.
ОБЪЕКТЫ КЛАССА MANUALOBJECT
КРАТКИЙ ЭКСКУРС В 3D ОБЪЕКТЫ
Прежде, чем мы окунемся в непосредственно создание сетки, вероятно, было бы полезно немного поговорить о том, что такое сетка и из чего она состоит.
Хотя это - грубое упрощение, сетка состоит примерно из двух частей: буфера вершин и буфера индексов.
Буферы вершин определяют точки в трехмерном пространстве.
Каждый элемент в буфере вершин определяется несколькими атрибутами, которые вы можете задать.
Единственный атрибут, который вы должны установить - позиция вершины.
Кроме того, есть множество необязательных свойств, которые вы можете задать, такие как цвет вершины, текстурные координаты и так далее.
Какие из из них вы будете использовать зависит от того, что вы захотите сделать с сеткой.
Индексные буферы "соединяют точки" путем указания точек из вершинного буфера.
Каждые три индекса, указанные в индексном буфере, определяют один треугольник, рисуемый GPU.
Порядок, в котором вы указываете вершины в индексном буфере, сообщает графической карте, в какую сторону будет обращен треугольник.
Треугольник, рисуемый против часовой стрелки, обращен к вам, а рисуемый по часовой стрелке - от вас.
Обычно отрисовывается только передняя сторона треугольника, так что важно быть уверенным, что ваши треугольники образованы правильным образом.
Хотя все сетки имеют вершинный буфер, не все содержат буфер индексов.
Например, у сетки, которую мы хотим создать, не будет индексного буфера, так как нам требуется пустой прямоугольник (в противоположность закрашенному прямоугольнику).
Наконец, обратите внимание на то, что вершинные и индексные буферы обычно хранятся в собственной памяти видеокарты, поэтому ваше ПО может просто отправить карте один простой, дискретный набор команд, чтобы сказать ей использовать эти предопределенные буферы для отрисовки всей 3D сетки за один проход.
ВВЕДЕНИЕ
Существуют два способа создания вашей собственной сетки в Ogre.
Первый вариант - унаследоваться от SimpleRenderable и предоставить через него вершинные и индексные буферы напрямую.
Это - самый прямой путь для создания сетки, но также и самый непонятный.
Фрагмент кода Генерация сетки (Generating A Mesh) показывает пример его использования.
Чтобы упростить задачу, Ogre предоставляет намного более лучший интерфейс, называемый ManualObject, который позволяет вам использовать некоторые простые функции для определения сетки вместо занесения "сырых" данных в объекты буферов.
Вместо закидывания позиции, цвета и т.д. в буфер, вы просто вызываете функции position() и colour().
В этом уроке нам необходимо создать белый прямоугольник, который будет отображаться, когда мы будем перетаскивать мышь для выделения объектов.
В Ogre действительно нет класса, который мы могли бы использовать для отображения двумерного прямоугольника.
Мы должны будем придумать способ сделать это самостоятельно.
Мы могли бы использовать оверлей и изменять его размер для отображения прямоугольника выделения, но здесь проблема в том, что изображение, которое используется для прямоугольника выделения, может растянуться за пределы формы и выглядеть неуклюже.
Вместо этого мы генерируем очень простую 2D сетку, которая будет выполнять роль нашего прямоугольника выделения.
ОБЛАСТЬ ВЫДЕЛЕНИЯ
Теперь мы можем обработать всю функциональность рамки выделения в нашем учебном приложении, но это только приведет к беспорядку, который нам совсем не нужен.
Это очень хорошая практика - разделять отдельные объекты в отдельные классы, потому что она делает вещи более ясными и легкими для использования.
Поэтому вначале для рамки выделения мы сделаем отдельный заголовочный файл и файл реализации, называемые SelectionBox.h и SelectionBox.cpp соответственно, и мы просто объявим этот класс как и любой другой; кроме того, он будет расширять Ogre::ManualObject.
Так что приведите ваши файлы .h и .cpp к чему-то, подобное этому.
КОД
Когда мы создаем прямоугольник выделения, мы должны опираться на то, что он будет отрисовываться в 2D-пространстве.
Мы также должны позаботиться о том, чтобы он отрисовывался тогда же, когда и оверлеи Ogre: таким образом он будет находиться поверх всех других объектов на экране.
Сделать это на самом деле очень легко.
Найдите конструктор SelectionBox и добавьте туда следующий код:
Первая функция устанавливает очередь визуализации для объекта такой же, как у очереди оверлеев.
Следующие две функции устанавливают единичные матрицы для матриц проекции и вида.
Матрицы проекции и вида используются многими системами визуализации (такими, как OpenGL и DirectX) для определения того, где объекты располагаются в мире.
Так как Ogre абстрагирует нас от этого, мы не будем вникать в какие-либо детали о том, что это за матрицы или что они делают.
Вместо этого все, что вы должны знать - установив единичные матрицы для матриц проекции и вида, как мы имеем здесь, мы по сути создадим 2D объект (прим. пер.: вынужден не согласится с автором, так как без знания, что же это за матрицы и что они делают, в 3D графике далеко не уедешь. Поэтому, если вы этого не знаете, бегом хотя бы на википедию).
При определении этого объекта система координат немного меняется.
Мы больше не имеем дела с осью Z (если вас спросят про ось Z, установите значение в -1).
Теперь мы имеем новую систему координат, с осями X и Y от -1 до 1 включительно (т.е., в математической нотации, [-1; 1]).
Наконец, мы устанавливаем флаги запроса для объекта в 0, что предотвращает включение прямоугольника выделения в результаты запроса (о запросах ниже).
Теперь, когда объект настроен, нам нужно, собственно, создать прямоугольник.
У нас имеется одно маленькое препятствие, прежде чем мы начнем. Мы собираемся вызывать эту функцию с координатами мыши.
Так-то оно так, но мы получим число в диапазоне [0; 1] для x и у-координат, а нам нужно преобразовать его в число в диапазоне [-1; 1].
Чтобы еще больше усложнить задачу, координата y также должна быть инвертирована.
Курсор мыши в CEGUI определяет верхнюю границу экрана как 0, а нижнюю - как 1.
В новой системе координат верх экрана это +1, а низ это -1.
К счастью, несколько быстрых преобразований позаботятся об этой проблеме.
Найдите функцию setCorners() и добавьте туда следующее:
Теперь позиции представлены в новой системе координат.
Далее мы должны собственно создать объект.
Чтобы сделать это, сперва вызовем метод begin().
Он принимает два параметра, имя материала, используемого этой секцией объекта и операцию визуализации, используемую для его рисования.
Так как мы не помещаем текстуру на него, мы оставим материал пустым (прим. пер.: в оригинале используется пустая строка "". Однако правильнее передать объект StringUtil::BLANK, который специально для этого и предназначен).
Второй параметр - операция визуализации RenderOperation.
Мы можем отрисовать сетку, используя точки, линии или треугольники.
Мы бы использовали треугольники, если бы отрисовывали полную сетку, но так как мы хотим пустой прямоугольник, мы будем использовать полосу линий.
Полоса линий подразумевает рисование линии до каждой вершины от предыдущей определенной вами вершины.
Так что для создания нашего прямоугольника мы должны определить 5 точек (первая и последняя одинаковы для замыкания прямоугольника):
Обратите внимание, что поскольку мы будем вызывать этот код много раз, мы добавили вызов clear() в начало для удаления предыдущего прямоугольника, прежде чем перерисовывать его.
При определении объекта ManualObject вы можете вызывать begin()/end() несколько раз для создания нескольких частей сеток (sub-mesh) (которые могут иметь различные материалы/операции визуализации).
Заметьте, что мы также устанавливаем параметр Z в -1, как как мы пытаемся определить 2D объект, который не использует эту ось.
Установка его в -1 гарантирует, что мы не окажемся выше, или позади, камеры при отрисовке.
Последняя вещь, которую мы должны сделать, это установка ограничивающего параллелепипеда для этого объекта.
Многие менеджеры сцен отбрасывают объекты, которые находятся вне экрана.
Даже при том, что мы в сущности создали 2D объект, Ogre все же 3D-движок и обрабатывает наш 2D объект, как будто он находится в 3D пространстве.
Это означает, что если мы создадим этот объект и присоединим его к узлу сцены (как мы и сделаем в следующем разделе), он пропадет из виду, как только мы отвернемся.
Чтобы исправить это, мы устанавливаем ограничивающий параллелепипед объекта в бесконечность, таким образом камера всегда будет внутри него:
Убедитесь, что вы добавили этот код после вызова clear().
Каждый раз при вызове ManualObject::clear() ограничивающий параллелепипед сбрасывается, поэтому будьте осторожны, если вы создаете другой ManualObject, который часто очищается, потому что ограничивающий параллелепипед надо устанавливать каждый раз, когда вы пересоздаете его.
Это все, что нам нужно было сделать в классе SelectionBox.
Перед тем, как продолжать, убедитесь, что код компилируется, однако ничего нового программа пока делать не будет
ВЫДЕЛЕНИЕ В ПРОСТРАНСТВЕ
НАСТРОЙКА
Прежде, чем мы перейдем к коду выделения, мы должны настроить несколько вещей.
Прежде всего включите SelectionBox.h в наш файл IntermediateTutorial4.h, чтобы мы могли использовать его; также мы должны объявить переменную-указатель для рамки выделения, поместив ее в раздел protected класса IntermediateTutorial4.
Теперь, когда все настроено, мы должны создать экземпляр класса SelectionBox и попросить менеджер сцены создать для нас запрос из объема.
Добавьте следующий код в конец createScene(), так как в этом случае мы точно знаем, что менеджер сцены был инициализирован.
Во-вторых, мы должны убедиться, что слушатель кадров подчищает после себя, когда мы закончим.
Добавьте следующий код в ~IntermediateTutorial4:
Заметьте, что мы предоставляем менеджеру сцены подчистить запрос за нас вместо прямого его удаления.
ОБРАБОТЧИКИ МЫШИ
Возможность, которую мы пытаемся реализовать - выделение объема.
Это означает, что когда пользователь нажимает кнопку мыши и проводит курсор вдоль экрана, будет рисоваться прямоугольник.
Когда кнопку мыши отпустят, все объекты, находящиеся в прямоугольнике, будут выделены.
Первое, что нам нужно, это знать, когда нажата мышь.
Мы должны сохранить начальную позицию и сделать SelectionBox видимым.
Найдите функцию mousePressed и добавьте в нее следующий код:
Обратите внимание, что мы используем координаты x и y от CEGUI::MouseCursor, а не от координат мыши OIS-а.
Это потому, что иногда OIS думает, что мышь находится в другом месте, чем ее отображает CEGUI (прим. пер.: если это баг, то он уже мог быть исправлен, поэтому не поленитесь и проверьте).
Чтобы убедиться, что мы синхронизированы с тем, что видит пользователь, мы используем координаты мыши, предоставляемые CEGUI.
Следующее, что мы должны сделать - остановить отображение прямоугольника выделения и выполнить запрос на выделение, когда пользователь отпускает кнопку мыши.
Добавьте следующий код в mouseReleased:
Ко всему прочему, каждый раз при движении мыши нужно обновлять координаты углов прямоугольника:
Мы корректируем вектор mStop каждый раз при движении мыши, поэтому мы можем просто использовать его в функции-члене setCorners().
Наконец, мы инициализируем mSelecting значением false в конструкторе, так как в начале мы еще не имеем никакого выделения. Замените конструктор IntermediateTutorial4 следующим кодом:
Скомпилируйте и запустите приложение. Теперь вы можете рисовать прямоугольник с помощью мыши.
PLANEBOUNDEDVOLUMELISTSCENEQUERY
Теперь, когда рамка выделения отображается корректно, нам нужно выполнить само объемное выделение.
Найдите функцию performSelection() и добавьте следующий код:
В этом куске кода мы заносим параметры векторов в переменные left, right, top и bottom.
Выражение if гарантирует, что меньшие значения окажутся слева и сверху.
(Если прямоугольник рисуется "наоборот", т.е. из нижнего-правого к верхнему-левому углу, то мы выполняем этот обмен.)
Далее, мы должны проверить и посмотреть, насколько большую область охватывает прямоугольник.
Если прямоугольник очень маленький, наш метод создания ограниченного плоскостью объема не поможет и мы закончим тем, что выберем слишком много или слишком мало объектов.
Если прямоугольник будет меньше, чем определенный процент от экрана, мы просто выйдем из функции без выполнения выделения.
Порог 0.0001 для отмены запроса выбран произвольно, вы должны поиграться с этим значением в своей собственной программе.
Также, в реальном приложении вы вероятно должны найти центр прямоугольника и выполнить стандартный запрос RaySceneQuery вместо игнорирования попытки выделения.
Теперь мы добрались до сути функции и должны выполнить требуемый запрос.
Запросы PlaneBoundedVolumeQuerie используют плоскости для выделения области и выборки любых находящихся в ней объектов.
Для этого примера мы создадим область, отгороженную пятью плоскостями, смотрящими вовнутрь.
Для создания этих плоскостей из нашего прямоугольника мы создаем 4 луча, по одному на каждый угол.
Как только мы получим эти четыре луча, мы возьмем точки на лучах для создания плоскостей:
Теперь создадим плоскости.
Обратите внимание, что мы выбираем точки в 100 единицах от начала луча.
Это значение выбрано произвольно, просто для красоты. Мы могли бы использовать 2 вместо 100.
Единственная точка, которая имеет здесь значение, это определяющая переднюю плоскость, которая начинается в 3 единицах перед камерой.
Эти плоскости теперь определяют "открытый параллелепипед", который простирается в бесконечность перед камерой.
Вы можете думать о прямоугольнике, который мы нарисовали мышью, как о конечной точке параллелепипеда прямо перед камерой.
Теперь, когда мы имеет плоскости, мы должны выполнить запрос:
Наконец, мы должны обработать результаты запроса.
Вначале мы снимаем выделение со всех объектов, выделенных до этого, затем мы выделяем все объекты, которые были найдены запросом.
Функции deselectObjects() и selectObject() уже были написаны для вас, так как мы рассмотрели их в предыдущем уроке:
Это все, что мы должны были сделать для запроса.
Обратите внимание, что мы также можем использовать флаги запроса на запросах объема, даже при том, что мы не использовали их в этом уроке.
Смотрите предыдущий урок, чтобы узнать, как использовать флаги запроса.
Теперь, пока не забыли, нам нужно заполнить функции selectObject() и deselectObjects().
Скомпилируйте и запустите приложение.
Теперь вы можете выделять объекты в некотором объеме сцены!
ПОСЛЕДНЕЕ ЗАМЕЧАНИЕ ОБ ОГРАНИЧИВАЮЩИХ ПАРАЛЛЕЛЕПИПЕДАХ
Как вы заметили в этом уроке и в предыдущих двух уроках, выделение в Ogre зависит от ограничивающего параллелепипеда рассматриваемых объектов, а не от их сетки.
Это означает, что RaySceneQuery и PlaneBoundedVolumeQuery всегда будут принимать все, что в действительности попало под запрос.
Существуют способы выполнения выделения по лучу с точностью до пикселя (который вы должны использовать, чтобы узнать, действительно ли поразил стрелок в FPS свою цель) и объемное выделение, которое дает вам достаточно точные результаты и приемлемую скорость.
К сожалению, это выходит за рамки данного урока.
Изучите урок Трассировка лучей на полигональном уровне (Raycasting to the polygon level), чтобы узнать, как это делается в чистом Ogre.
Если вы интегрировали Ogre с физический библиотекой, такой как OgreNewt, она также должна предоставлять методы, которые сделают это за вас.
Вы изучили лишь малую часть того, что предоставляют запросы по лучу и в объеме, но все же и это немало.
Выполнение выделения, основанного на геометрии очень трудоемко и может значительно снизить ваше количество кадров в секунду, если вы попытаетесь проверять им все в сцене.
На самом деле, наиболее общий способ выполнить "истинное" выделение мышью - это сначала выполнить запрос Ogre (такой, как RaySceneQuery), а затем индивидуально протестировать каждое пересечение, возвращенное запросом с использованием физического движка, который фактически производит проверки геометрии сетки в попытке определить, попали мы в неё или просто очень близко прошли.
Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется
Переведено на Нотабеноиде
http://notabenoid.org/book/20063/65476
Переводчики: Mingun, proVIDec
Настройки
Готово:
