Document 177943

advertisement
В предыдущем разделе была представлена концепция управления памятью в OSG. Если вам не знаком подсчет
ссылок памяти, то лучше посмотреть на реальные OSG примеры, для лучшего понимания. В этом разделе
представлена простая OSG программа, которая использует технику управления памятью описанную ранее, и
знакомит вас с построением графа сцены с помощью OSG классов содержащих геометрию. На первый взгляд
код может показаться громоздким, поскольку вы еще не знакомы со многими классами. Подробное объяснение
геометрических классов дается в комментариях в исходном коде.
Листинг 2-1 широко использует шаблонный класс ref_ptr<>, описанный в предыдущем разделе. Все выделения
памяти в Листинге 2-1 с подсчетом ссылок. Даже функция createSceneGraph() возвращает ref_ptr<>, созданного
графа сцены. (Проще говоря, код в Листинге 2-1 мог бы быть написан полностью с использованием обычных
C++ указателей, а возвращенный указатель сохранялся бы в ref_ptr<>. Тем не менее, является хорошей
практикой использование ref_ptr<> в вашем приложении, поскольку это автоматизирует освобождение памяти
даже в случае исключений или преждевременного выхода из функции. В примерах кода этой книги
используется ref_ptr<> для поддержки этой хорошей практики.)
Листинг 2-1
Построение простого графа сцены
Это отрывок из кода Простого примера идущего вместе с книгой. Функция createSceneGraph() определяет
геометрию для единственного прямоугольного примитива. Прямоугольник имеет разный цвет вершин, но
общую нормаль для всего примитива.
#include <osg/Geode>
#include <osg/Geometry>
osg::ref_ptr<osg::Node> createSceneGraph()
{
// Создать объект для хранения в нем геометрии.
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
// Создать массив для хранения четырех вершин.
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
geom->setVertexArray( v.get() );
v->push_back( osg::Vec3( -1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, 1.f ) );
v->push_back( osg::Vec3( -1.f, 0.f, 1.f ) );
// Создать массив их четырех цветов
osg::ref_ptr<osg::Vec4Array> c = new osg::Vec4Array;
geom->setColorArray( c.get() );
geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
c->push_back( osg::Vec4( 1.f, 0.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 1.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) );
c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) );
// Создать массив содержащий одну нормаль.
osg::ref_ptr<osg::Vec3Array> n = new osg::Vec3Array;
geom->setNormalArray( n.get() );
geom->setNormalBinding( osg::Geometry::BIND_OVERALL );
n->push_back( osg::Vec3( 0.f, -1.f, 0.f ) );
// Получить прямоугольник из четырех вершин из ранее подготовленных данных.
geom->addPrimitiveSet(new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) );
// Добавить Geometry (Drawable) в Geode и вернуть Geode.
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable( geom.get() );
return geode.get();
}
Код в Листинге 2-1 создает граф сцены с одним узлом. На рисунке 2-3 показан этот предельно простой граф
сцены. Этот один узел графа сцены имеет образовательную цель для новичков. Реальные графы сцен гораздо
сложнее.
Обратите внимание, что код в Листинге 2-1 определяет четыре вершины в плоскости y=0. Подобно OpenGL,
OSG не налагает ограничений на систему координат, используемую приложением. Тем не менее, по умолчанию
Рисунок 2-3
Листинг 2-1 графа сцены
В Листинге 2-1 создается граф сцены состоящий из одного Geode.
библиотека osgViewer использует мировую систему координат, которая ориентированна положительный x
вправо, положительный z вверх, и положительный y в экран. Это работает в большинстве приложений, в
которых земная поверхность находится в xy плоскости. Глава 3, Использование OpenSceneGraph в Вашем
Приложении, описывается как изменить систему мировых координат на отличную от по умолчанию. Код в
Листинге 2-1 использует ориентацию по умолчанию для отображения четырехугольника, обращенного к
наблюдателю.
В дополнение к созданному графу сцены, как показано в Листинге 2-1, вы так же захотите его отобразить или
анимировать. Примеры в этой главе используют приложение osgviewer для отображения графа сцены,
поскольку написание кода для отображения будет представлено только в Глава 3, Использование
OpenSceneGraph в Вашем Приложении. Для отображения графа сцены в приложении osgviewer, вам
необходимо записать его(граф сцены) на диск. В Листинге 2-2 показан код, который вызывает функцию из
Листинга 2-1, и записывает граф сцены на диск в .osg файл. После того как граф сцены записан на диск, вы
можете использовать osgviewer для просмотра того, как выглядит результат.
Листинг 2-2
Запись графа сцены на диск
В этом листинге показана точка входа main() примера Простой программы. Из main() вызывается
createSceneGraph() из Листинга 2-1, где создается граф сцены, затем этот граф сцены записывается на диск в
файл с именем “Simple.osg”.
#include <osg/ref_ptr>
#include <osgDB/Registry>
#include <osgDB/WriteFile>
#include <osg/Notify>
#include <iostream>
using std::endl;
osg::ref_ptr<osg::Node> createSceneGraph();
int main( int, char** )
{
osg::ref_ptr<osg::Node> root = createSceneGraph();
if (!root.valid())
osg::notify(osg::FATAL) << "Failed in createSceneGraph()." << endl;
bool result = osgDB::writeNodeFile(
*(root.get()), "Simple.osg" );
if ( !result )
osg::notify(osg::FATAL) << "Failed in osgDB::writeNodeFile()." << endl;
}
После вызова функции из Листинга 2-1 создающего граф сцены, код из Листинга 2-2 записывает его на диск в
виде файла с именем “Simple.osg”. Формат файла .osg это принадлежащий OSG текстовой ASCII формат
файла. Поскольку это ASCII файл, файлы .osg медленно загружаются да к тому же еще и велики, поэтому это
формат редко используется в промышленном коде. Тем не менее, очень полезно для отладки в процессе
разработки и быстрых демонстраций.
Код в Листинге 2-1 и 2-2 из Простого примера идущего вместе книгой. Если вы этого еще не сделали, скачайте
исходный код примера с Web сайта этой книги, откомпилируйте и запустите его. После запуска примера вы
найдете выходной файл Simple.osg в вашей рабочей директории. Для того чтобы посмотреть, как выглядит граф
сцены, используйте osgviewer:
osgviewer Simple.osg
osgviewer должен дать результат похожий представленному на Картинке 2-4. В Главе 1 описывается osgviewer
и как им пользоваться. Например, вы можете вращать отображаемую геометрию с помощью левой кнопки
мыши и приближать или удалять с помощью правой.
Код из Листинга 2-1 широко использует классы геометрии OSG. Далее дается на более высоком уровне
объяснение как пользоваться этими классами.
Рисунок 2-4
Простой пример графа сцены отображаемый в osgviewer’е
На картинке показан квадрат созданный в Листинге 2-1, а после записанный в файл .osg в коде Листинга 2-2 и
отображенный с помощью osgviewer’a.
2.2.1 Обзор Классов Геометрии
Код в Листинге 2-1 может сбивать с толку, но в сущности в нем выполняется только три операции.
1. Он создает массив вершин, нормалей и данные о цвете
2. Он создает объект osg::Geometry и добавляет в него массивы. Он так же добавляет объект
osg::DrawArrays для определения как рисовать данные.
3. Он создает узел графа сцены osg::Geode и добавляет объект Geometry в него.
В этом разделе рассматривается каждый из этих шагов подробно.
Классы Vector и Array
В OSG определен богатый набор классов для хранения векторных данных, таких как вершины, нормали, цвета и
текстурные координаты. osg::Vec3 это массив трехкомпонентных чисел с плавающей запятой; используйте его
для определения данных векторов и нормалей. Используйте osg::Vec4 для определения данных цвета и
osg::Vec2 для 2D текстурных координат. В дополнение к простому хранению эти классы обеспечивают полный
набор методов для расчета длины, векторного и скалярного произведения, сложения векторов и векторноматричного умножения.
В OSG определены шаблонные классы массивов для хранения объектов. Наиболее часто используемые
шаблонные классы массивов предназначены для хранения векторных данных. В связи с тем фактом, что они
часто используются OSG предоставляет определение типов для массивов векторных данных - osg::Vec2Array,
osg::Vec3Array, и osg::Vec4Array.
В Листинге 2-1 создаются отдельные трехкомпонентные вектора для каждой xyz вершины с помощью Vec3,
затем каждый Vec3 сохраняется в конец Vec3Array. Код использует Vec3Array похожим образом для
хранения xyz данных нормалей. В Листинге 2-1 используется Vec4 и Vec4Array для данных о цвете,
поскольку цвет определяется четырьмя компонентами (красный, зеленый, синий и альфа). Позже в этой главе
будет представлен код примера, который использует Vec2 и Vec2Array для хранения двухэлементных
текстурных координат.
Типы массивов наследуются от std::vector, поэтому они поддерживают метод push_back() для добавления
новых элементов, как показано в Листинге 2-1. Как дочерние классы от std::vector, классы массивов так же
поддерживают методы resize() и operator[]().
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
geom->setVertexArray( v.get() );
v->resize( 4 );
(*v)[ 0 ] = osg::Vec3( -1.f, 0.f, -1.f );
(*v)[ 1 ] = osg::Vec3( 1.f, 0.f, -1.f );
(*v)[ 2 ] = osg::Vec3( 1.f, 0.f, 1.f );
(*v)[ 3 ] = osg::Vec3( -1.f, 0.f, 1.f );
Рисуемые
OSG определяет класс osg::Drawable, для хранения данных отображения. Drawable это виртуальный базовый
класс, который не создается непосредственно. OSG предоставляет три производных класса от Drawable.



osg::DrawPixels—DrawPixels это надстройка вокруг команды glDrawPixels().
osg::ShapeDrawable—ShapeDrawable предоставляет доступ к нескольким предопределенным
геометрическим формам, таким как цилиндры и сферы.
osg::Geometry—Geometry это гибкий класс для хранения и отображения геометрии общего
назначения. Пример кода использует Geometry, который является наиболее часто используемым
производным классом.
Если вы уже знакомы с массивами вершин в OpenGL, класс Geometry вам будет легко использовать. Geometry
предоставляет интерфейс, который позволяет вашему приложению определять массивы данных вершин, как их
интерпретировать и отображать. Аналогично с определением данных массива вершин в OpenGL (с помощью
glVertexPointer() и glNormalPointer()) и отображением массива вершин (с помощью glDrawArrays() и
glDrawElements()). Код из Листинга 2-1 использует следующие методы Geometry:



setVertexArray(), setColorArray() и setNormalArray()—Эти методы аналогичны glVertexPointer(),
glColorPointer() и glNormalPointer() в OpenGL. Ваше приложение использует эти методы для
определения массива вершин, цвета, и данных нормалей. Каждый из методов setVertexArray() и
setNormalArray() принимает указатель на Vec3Array как параметр, и setColorArray() принимает
указатель на Vec4Array.
setColorBinding() and setNormalBinding()—Эти методы говорят Geometry как применять данные о
цвете и нормалях. Они принимают перечисляемый тип, определенный в классе Geometry, как
параметр. В Листинге 2-1 назначение цвета как osg::Geometry::BIND_PER_VERTEX означает, что цвет
у каждой вершины будет индивидуальным. Тем не менее, нормали определены как
osg::Geometry::BIND_OVERALL что означает применить одну нормаль на всю Geometry.
addPrimitiveSet()—Этот метод говорит как Geometry отображать свои данные. Он принимает
указатель на osg::PrimitiveSet как параметр. PrimitiveSet это виртуальный базовый класс который вы
не можете создать непосредственно. Ваш код должен добавлять множество PrimitiveSet объектов к
одной и той же Geometry.
Метод addPrimitiveSet() позволяет вашему приложению определять как OSG должен рисовать геометрические
данные хранимые в Geometry объекте. В Листинге 2-1 определен osg::DrawArrays объект. DrawArrays
наследуется от PrimitiveSet. Думайте об этом как о надстройке над командой glDrawArrays() для рисования
массива вершин. Другие дочерние классы от PrimitiveSet (DrawElementsUByte, DrawElementsUShort и
DrawElementsUInt) имитируют OpenGL команду glDrawElements(). OSG так же предоставляет класс
DrawArrayLengths, которому нет аналога в OpenGL. Функционально он схож с множеством вызовов
glDrawArrays() с различными диапазонами индексов и количеством элементов.
Наиболее часто используемый конструктор класса DrawArrays имеет следующие определение:
osg::DrawArrays::DrawArrays( GLenum mode, GLint first, GLsizei count );
mode это один из десяти типов примитивов OpenGL, таких как GL_POINTS, GL_LINES или
GL_TRIANGLE_STRIP. PrimitiveSet это базовый класс в котором определен эквивалент перечислению
OpenGL, например такой osg::PrimitiveSet::POINTS, и ваш код может использовать их так же.
first является индексом на первый элемент в массиве данных вершин которые OSG должно использовать для
отображения, и count это общее количество элементов которое OSG должно использовать. Например, если
данные вершин содержат 6 вершин и вы хотите отобразить полоску треугольников (triangle strip) из этих
вершин, то вы можете добавить следующий DrawArrays набор примитивов к вашей Geometry:
geom->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, 6 );
После добавления данных о вершинах, цвете, нормалях и DrawArrays наборе примитивов в Geometry объект,
код в Листинге 2-1 выполняет одну последнюю операцию с Geometry. Он добавляет ее к узлу графа сцены. В
следующем разделе описывается эта операция.
Как OSG Рисует
В то время как классы, производные от PrimitiveSet, обеспечивают функциональность схожую с
возможностями OpenGL для массивов вершин, не стоит полагаться что PrimitiveSet всегда
использует массивы вершин во внутренней реализации. В зависимости от настроек отображения,
OSG может использовать массивы вершин (с или без буферизации объектов), дисплейные списки
или даже glBegin()/glEnd() для отображения геометрии.
Объекты, наследуемые от Drawable (такие как Geometry) используют дисплейные списки по
умолчанию. Вы можете изменять это поведение вызвав osg::Drawable::setUseDisplayList( false ).
OSG перейдет на использование glBegin()/glEnd() в случае если вы определите атрибут
BIND_PER_PRIMITIVE, что вызовет установку атрибутов для каждого примитива (например для
каждого треугольника в режиме GL_TRIANGLES.)
Geodes
Класс osg::Geode в OSG является листовым узлом, который хранит геометрию для отображения. В Листинге 21 создается простейший граф сцены, какой только возможно—граф сцены, состоящий из единственного
листового узла. В конце Листинга 2-1 функция createSceneGraph() возвращает адрес этого Geode в виде
ref_ptr<> на osg::Node. Это допустимо в C++ коде, поскольку Geode является производным классом от Node.
(По определению в OSG все узлы графа сцены наследуются от Node.)
osg::Geode не имеет потомков, поскольку он является листовым узлом, но он может содержать геометрию.
Имя Geode это сокращение от “узел геометрии”—узел который содержит геометрию. Любая геометрия,
которую ваше приложение отображает, должно быть присоединено к Geode. Geode предоставляет метод
addDrawable() который позволяет вашему приложению присоединять геометрию.
Geode::addDrawable() принимает указатель на Drawable в качестве параметра. Как было написано в
предыдущем разделе, Drawable это виртуальный базовый класс от которого наследуется множество дочерних
классов, например такой как Geometry. Посмотрите в Листинге 2-1 пример того, как добавлять объект
Geometry в Geode с помощью addDrawable(). В коде эта операция выполняется в конце функции
createSceneGraph() прямо перед возвращением Geode.
2.3 Узел Group
Узлом групп в OSG является osg::Group, который позволяет вашему приложению добавлять в себя любое
количество дочерних узлов, которые в свою очередь так же могут быть узлами Group со своими потомками, как
показано на Рисунке 2-5. Group является базовым классом для многих полезных узлов, включая
osg::Transform, osg::LOD и osg::Switch, которые описываются далее в этом разделе.
Group наследуется от Referenced. В обычном случае только код ссылок дан в родители Group, таким образом,
если корневой узел графа сцены удален, это вызовет цепное удаление, гарантирующие отсутствие утечек
памяти.
На самом деле Group является сердцем OSG, поскольку он позволяет вашему приложению организовывать
данные в виду графа сцены. Сила объектов Group в их интерфейсе управления дочерними узлами. В Group так
же есть интерфейс управления родителями, который он наследует от базового класса, osg::Node. В этом разделе
дается обзор как дочернего, так и родительского интерфейсов.
Далее описываются дочерние и родительские интерфейсы. Этот раздел описывает три часто используемых
класса наследуемых от Group—это узлы Transform, LOD и Switch.
2.3.1 Дочерний Интерфейс
Класс Group определяет интерфейс для потомков и все узлы, которые наследуются от Group, наследуют этот
интерфейс. Большинство OSG узлов, которые вы используете, наследуются от Group (Geode является
исключением), поэтому в общем случае вы можете полагать, что большинство узлов поддерживают дочерний
интерфейс.
Рисунок 2-5
Узел Group
Показанные зеленым, узлы Group могут иметь множество потомков, которые в свою очередь так же могут
быть узлами Group со своими потомками.
Group хранит указатели на свои дочерние узлы в std::vector< ref_ptr<Node> >—в массиве ref_ptr<>
переменных указывающих на Node. Вы можете получить доступ к потомку по индексу, поскольку Group
используется как массив. Group использует ref_ptr<>, что позволяет работать системе управления памятью
OSG. Следующий отрывок кода показывает часть описания дочернего интерфейса объекта Group. Все классы в
пространстве имен osg.
class Group : public Node {
public:
...
// Добавит дочерний узел.
bool addChild( Node* child );
// Убрать дочерний узел. Если этот узел не является дочерним, ничего не делать
// и вернуть false.
bool removeChild( Node* child );
// Заменить дочерний узел на новый.
bool replaceChild( Node* origChild, Node* newChild );
// Вернуть количество детей.
unsigned int getNumChildren() const;
// Вернуть true если определенный узел является дочерним.
bool containsNode( const Node* node ) const;
...
};
Очень простой граф сцены может состоять из родительского узла Group с двумя потомками Geode. Вы можете
построить подобный граф сцены с помощью следующего кода.
osg::ref_ptr<osg::Group> group = new osg::Group;
osg::ref_ptr<osg::Geode> geode0 = new osg::Geode;
group->addChild( Geode0.get() );
osg::ref_ptr<osg::Geode> geode1 = new osg::Geode;
group->addChild( Geode1.get() );
Замете что Group использует ref_ptr<>, чтобы указывать на свои дочерние узлы. В этом примере group
принимает ссылки памяти на geode0 и geode1. Эта память остается занятой после того как geode0
и geode1выйдут из области видимости, и освободится после удаления group.
2.3.2 Родительский Интерфейс
Group наследует интерфейс для управления родителем от Node. Geode имеет такой же интерфейс, поскольку
он так же наследуется от Node. OSG позволяет узлам иметь множество родителей. Следующий отрывок кода
показывает часть описания родительского интерфейса объекта Node. Все классы в пространстве имен osg.
class Node : public Object {
public:
...
typedef std::vector<Group*> ParentList;
// Вернуть список всех родителей.
const ParentList& getParents() const;
// Вернуть указатель на родителя по заданному индексу.
Group* getParent( unsigned int I );
// Вернуть количество родителей
unsigned int getNumParents() const;
...
};
Node наследуется от osg::Object. Object это виртуальный базовый класс который ваше приложение не может
создавать непосредственно. Object предоставляет интерфейс для хранения и извлечения строки с именем,
определяет режим хранения данных как статический или динамически модифицируемый, и так же наследуется
от Referenced для поддержки управления памятью OSG. В Главе 3.2 Динамическая Модификация,
обсуждаются имена и интерфейс динамических данных более детально.
Множество Родителей
Когда вы добавляете
один и тот же узел как
дочерний к нескольким
узлам, дочерний узел
становится
обладателем
нескольких родителей,
как показано на
диаграмме. Вы можете
захотеть сделать так что
бы отобразить дочерний
узел множество раз без
необходимости
создания нескольких
копий. В Главе 2.4.3
Пример Кода Настройки
Состояния показывает
пример, как отображать
один и тот же Geode с
различными
трансформациями и
состоянием отображения.
Когда узел имеет множество родителей, OSG обходит узлы множество раз. Каждый из родителей
хранит свой собственный ref_ptr<> на своего потомка, таким образом, дочерний узел не будет
удален до тех пор, пока все родители не перестанут на него ссылаться.
Обратите внимание что osg::Node::ParentList это std::vector обычных C++ указателей. Поскольку Node
содержит адреса своих родителей, Node не нужно управление памятью OSG для ссылок на родителей. При
удалении родителя, родитель удаляет себя из всех списков дочерних узлов.
В обычном случае, когда узел имеет одного родителя (getNumParents() возвращает один), вызов getParent( 0 )
получает указатель на родителя.
Вы часто используете дочерний и родительские интерфейсы при построении и управлении графом сцены. Тем
не менее классы, наследуемые от Group, предоставляют дополнительную функциональность требуемую
многим приложениям. В следующих разделах описываются три из них.
2.3.4 Узел LOD
Используйте узел osg::LOD для отображения объектов с различными степенями детализации. LOD наследуется
от Group и следовательно наследует дочерний интерфейс. LOD так же позволяет вам задавать диапазон для
каждого потомка. Диапазон состоит из значений минимума и максимума. Эти значения представляют собой
расстояния по умолчанию, и LOD отображает потомка в случае если расстояние до наблюдателя попадает в
диапазон потомка. LOD потомки могут храниться в любом порядке и нет необходимости в сортировке по
расстоянию степени детализации.
На Рисунке 2-6 показан узел LOD с тремя потомками. Первый потомок это узел Group со своими потомками.
Когда расстояние до точки наблюдения попадет в диапазон первого потомка, OSG совершает обход его и его
потомков. Узел LOD действует аналогично по отношению ко второму и третьему потомкам. OSG может ничего
не отобразить, какой-нибудь или все потомки LOD, основываясь на их диапазонах.
osg::ref_ptr<osg::Geode> geode;
...
osg::ref_ptr<osg::LOD> lod = new osg::LOD;
// Отобразить geode в случае когда 0.f <= distance < 1000.f
lod->addChild( geode.get(), 0.f, 1000.f );
LOD отобразит множество потомков одновременно случае наложения их диапазонов.
osg::ref_ptr<osg::Geode> geode0, geode1;
// Инициализация Geode.
...
osg::ref_ptr<osg::LOD> lod = new osg::LOD;
// Display geode0 when 0.f <= distance < 1050.f
lod->addChild( geode0.get(), 0.f, 1050.f );
// Display geode1 when 950.f <= distance < 2000.f
lod->addChild( geode1.get(), 950.f, 2000.f );
// Result: display geode0 and geode1 when 950.f <= distance < 1050.f
Download