Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

RedBook

.pdf
Скачиваний:
20
Добавлен:
11.06.2015
Размер:
7.43 Mб
Скачать

В течение тесселяции, возвратные функции вызываются в манере, похожей на ту, в

которой вы используете команды OpenGL glBegin(), glEdgeFlag*(), glVertex*() и glEnd(). Функция комбинирования (combine) используется для создания новых вершин в точках пересечения ребер. Функция ошибок (error) вызывается в процессе тесселяции только тогда, когда что-то идет не так, как должно.

Для каждого созданного объекта тесселяции функция GLU_TESS_BEGIN вызывается с одним из 4 возможных параметров: GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES или GL_LINE_LOOP. Когда тесселятор декомпозирует (разбивает) полигоны, алгоритм тесселяции решает, какой тип треугольного примитива более эффективен для использования. (Если активизировано свойство

GLU_TESS_BOUNDARY_ONLY, для визуализации используется GL_LINE_LOOP.)

Поскольку флаг ребра не имеет смысла в случаях GL_TRIANGLE_FAN или GL_TRIANGLE_STRIP, то, если существует заданная возвратная функция

GLU_TESS_EDGE_FLAG, активизирующая флаги ребра, функция GLU_TESS_BEGIN

вызывается только с аргументом GL_TRIANGLES. Функция GLU_TESS_EDGE_FLAG работает абсолютно аналогично вызову команды OpenGL glEdgeFlag*().

После вызова функции, ассоциированной с GLU_TESS_BEGIN и до вызова функции, ассоциированной с GLU_TESS_END, вызывается некоторая комбинация функций

GLU_TESS_VERTEX и GLU_TESS_EDGE_FLAG (обычно из-за обращений к функции gluTessVertex()). Ассоциированные флаги ребра и вершины интерпретируются точно таким же образом, как если бы они задавались между командами OpenGL glBegin() и glEnd().

Если что-то идет не так, возвратной функции ошибки передается номер ошибки GLU. Символьная строка, описывающая ошибку, извлекается с использованием функции gluErrorString().

Пример 11-1 демонстрирует часть кода файла tess.cpp, в котором создается объект тесселяции и регистрируется несколько функций обратного вызова.

Пример 11-1. Регистрация возвратных функций тесселяции: файл tess.cpp

#ifndef CALLBACK #define CALLBACK #endif

/* часть функции init() */ tobj=gluNewTess();

gluTessCallback(tobj,GLU_TESS_VERTEX,glVertex3dv); gluTessCallback(tobj,GLU_TESS_BEGIN,beginCallback); gluTessCallback(tobj,GLU_TESS_END,endCallback); gluTessCallback(tobj,GLU_TESS_ERROR,errorCallback);

/* возвратно вызываемые функции, зарегистрированные с помощью gluTessCallback() */

void CALLBACK beginCallback(GLenum which)

{

glBegin(which);

}

void CALLBACK endCallback(void)

{

glEnd();

}

void CALLBACK errorCallback(GLenum errorCode)

{

const GLubyte *estring;

estring=gluErrorString(errorCode);

fprintf(stderr, "Tesselation error: %s\n",estring); exit(0);

}

Замечание: Приведение типов возвратно вызываемых функций довольно сложно, особенно, если вы хотите создать код, который будет одинаково работать на платформах Microsoft Windows (95/98/NT) и UNIX. Чтобы верно работать на платформах Microsoft Windows, программам, использующим возвратно-вызываемые функции, таким как tess.cpp требуется наличие символа CALLBACK в объявлении функции. Трюк с использованием пустого определения для CALLBACK (как показано ниже) позволяет коду запускаться как на Microsoft Windows, так и в UNIX.

#ifndef CALLBACK #define CALLBACK #endif

void CALLBACK callbackFunction(..)

{

}

В примере 11-1, зарегистрированная функция для GLU_TESS_VERTEX, представляет собой просто команду glVertex3dv(), в которую передаются только координаты каждой вершины. Однако, если вы желаете задавать больше информации в каждой вершине, например, цветовые величины, вектор нормали или координаты текстуры, вам следует создать более сложную функцию обратного вызова. Пример 11-2 демонстрирует начало другого тесселируемого объекта далее в программе tess.cpp. Зарегистрированная функция vertexCallback() ожидает параметра, являющегося указателем на 6 чисел с плавающей точкой двойной точности: координаты x, y и z, а также значения цветовых компонент красного, зеленого и синего для каждой вершины.

Пример 11-2. Возвратно-вызываемые функции GLU_TESS_VERTEX и GLU_TESS_COMBINE

/* другая часть функции init() */ gluTessCallback(tobj,GLU_TESS_VERTEX,vertexCallback); gluTessCallback(tobj,GLU_TESS_BEGIN,beginCallback); gluTessCallback(tobj,GLU_TESS_END,endCallback); gluTessCallback(tobj,GLU_TESS_ERROR,errorCallback); gluTessCallback(tobj,GLU_TESS_COMBINE,combineCallback);

/* новые возвратно-вызываемые функции */ void CALLBACK vertexCallback(GLvoid* vertex)

{

const GLdouble* pointer;

pointer=(GLdouble*)vertex;

glColor3dv(pointer+3);

glVertex3dv(vertex);

}

void CALLBACK combineCallback(GLdouble coords[3], GLdouble* vertex_data[4],

GLfloat weight[4], GLdouble** dataOut)

{

GLdouble *vertex; int i;

vertex=(GLdouble*) malloc(6*sizeof(GLdouble)); vertex[0]=coords[0];

vertex[1]=coords[1];

vertex[2]=coords[2];

for(i=3;i<6;i++) vertex[i]=weight[0]*vertex_data[0][i]+

weight[1]*vertex_data[1][i]+ weight[2]*vertex_data[2][i]+ weight[3]*vertex_data[3][i];

*dataOut=vertex;

}

Пример 11-2 также демонстрирует использование функции GLU_TESS_CALLBACK. Каждый раз, когда алгоритм тесселяции, анализирующий входящий контур, обнаруживает пересечение и решает, что должна быть создана новая вершина, вызывается функция обратного вызова зарегистрированная для GLU_TESS_COMBINE. Эта функция также вызывается в случае, если алгоритм решает объединить две вершины, которые очень близки друг к другу. Новая вершина является линейной комбинацией до четырех существующих вершин, на которые в примере 11-2 ссылаются как на vertex_data[0..3]. Коэффициенты линейной комбинации передаются в weight[0..3] (чья сумма составляет 1.0). Аргумент coords задает положение новой вершины.

Зарегистрированная функция обратного вызова должна зарезервировать память для новой вершины, произвести взвешенную интерполяцию данных с использованием vertex_data и weight и возвратить указатель на новую вершину в аргументе data_out. combineCallback() в примере 11-2 интерполирует цветовую величину. Функция резервирует массив из 6-ти элементов, помещает x, y и z в первые три элемента, а взвешенное среднее цветовых величин RGB в следующие три элемента.

11.1.2.1 Данные, определенные пользователем

Может быть зарегистрировано шесть типов возвратно-вызываемых функций. Поскольку существует два варианта каждой возвратной функции, существует всего 12 функций. Для каждой функции существует один вариант с данными, определенными пользователем, и один вариант без них. Данные, определенные пользователем передаются приложением в функцию gluTessBeginPolygon(), а затем без изменений передаются всем возвратным функциям *DATA. С функцией GLU_TESS_BEGIN_DATA данные, определенные пользователем могут использоваться в качестве данных для одного полигона. Если для определенного типа возвратно-вызываемой функции вы зададите оба варианта, будет использоваться вариант с аргументом user_data. Таким образом, несмотря на то, что существует 12 возвратных функций, в каждый момент времени активными могут быть только 6.

Например, в примере 11-2 используется плавная закраска, и vertexCallback() задает RGB цвет для каждой вершины. Если вы хотите использовать плавную заливку и освещение, функция обратного вызова должна задавать вектор нормали для каждой вершины. Однако, если вы хотите использовать освещение и плоскую закраску, вы можете задать только одни вектор нормали для каждого полигона, а не для каждой вершины. В таком случае вы можете использовать функцию GLU_TESS_BEGIN_DATA и передать координаты вершины и нормаль к поверхности в аргументе user_data.

11.1.3 Свойства тесселяции

Перед тесселяцией и визуализацией вы можете использовать функцию gluTessProperty() для настройки некоторых свойств, влияющих на алгоритм

тесселяции. Наиболее важное и сложное из этих свойств правило оборота, определяющее, что считается «внутренним», а что «внешним».

void gluTessProperty (GLUtesselator* tessobj, GLenum property, GLdouble value);

Значение свойства property объекта тесселяции tessobj устанавливается в значение value. Аргумент property может принимать значения GLU_TESS_BOUNDARY_ONLY, GLU_TESS_TOLERANCE или GLU_TESS_WINDING_RULE. Если property равно GLU_TESS_BOUNDARY_ONLY, value может принимать значения GL_TRUE или GL_FALSE.

Если оно равно GL_TRUE полигоны не тесселируются на закрашенные полигоны рисуются только замкнутые ломаные, показывающие границу контуров, разделяющих интерьер и экстерьер полигона. Значение по умолчанию – GL_FALSE. (Смотрите описание gluTessNormal() для понимания того, как управлять направлением перекрытия контуров.) Если property равно GLU_TESS_TOLERANCE, value представляет собой дистанцию, используемую при вычислении того, являются ли две вершины близкими в достаточной степени, чтобы их можно было объединить с помощью возвратной функции GLU_TESS_COMBINE. Величина толерантности умножается на

максимальную разницу в координатах входящих вершин для определения максимальной дистанции, на которую может сместиться какой-либо фрагмент вследствие одной операции совмещения. Совмещение может не поддерживаться вашей реализацией OpenGL, а величина толерантности (терпимости) имеет только рекомендательный характер. Величина толерантности имеет нулевое значение по умолчанию. Свойство GLU_TESS_WINDING_RULE определяет, какие части полигона находятся внутри, а какие снаружи и не должны быть закрашены. value может принимать значения GLU_TESS_WINDING_ODD (значение по умолчанию), GLU_TESS_WINDING_NONZERO, GLU_TESS_WINDING_POSITIVE, GLU_TESS_WINDING_NEGATIVE или GLU_TESS_WINDING_ABS_GEQ_TWO.

11.1.3.1 Число оборотов и правило оборота

Для отдельного контура, число оборотов точки представляет собой знаковое целое число полных оборотов, которые мы совершаем вокруг этой точки, проходя вдоль контура (обороты против часовой стрелки добавляют 1 к этому числу, а обороты по часовой стрелке вычитают 1) . Когда существует несколько контуров, индивидуальные числа оборотов суммируются. Данная процедура ассоциирует знаковое число со знаком с каждой точкой плоскости. Отметьте, что число оборотов является одним и тем же для всех точек одной ограниченной области.

На рисунке 11-2 показано три набора контуров и числа оборотов для точек внутри контуров. В наборе слева все три контура идут против часовой стрелки, так что каждый контур добавляет единицу к числам оборотов точек, находящихся внутри него. В наборе посередине два внутренних контура имеют направление по часовой стрелке, так что число оборотов уменьшается и в итоге становится меньше 0.

Рисунок 11-2. Числа оборотов для простых контуров

Правило оборота классифицирует регион, как внутренний, если его число оборотов принадлежит к определенной категории (odd – нечетное, nonzero – ненулевое, positive

положительное, negative – отрицательное или abs – «абсолютная величина больше или равная 2»). Правила GLU_TESS_WINDING_ODD и GLU_TESS_WINDING_NONZERO

часто используются для определения интерьера. Правила позитивное, негативное и «абсолютная величина» имеют ограниченное применение при выполнении операций над CSG (computational solid geometry – плоская вычисляемая геометрия).

Рисунок 11-3 демонстрирует влияние различных правил оборота на визуализацию контуров. Темные области на рисунке являются внутренними.

Рисунок 11-3. Как правила оборота определяют интерьер

11.1.3.2 Использование правил оборота с CSG

Правила оборота GLU_TESS_WINDING_ODD и GLU_TESS_WINDING_NONZERO являются наиболее часто используемыми. Они работают в большинстве типичных случаев заливки.

Правила оборота были разработаны и для операций с CSG и позволяют легко находить объединение, разницу или пересечение (Булевские операции) нескольких контуров.

Вначале предположим, что каждый контур определен таким образом, что число оборотов для каждого внешнего региона равно 0, а для каждого внутреннего – 1. (То есть ни один из контуров не пересекает сам себя.) Далее считаем, что контуры, проходящие против часовой стрелки, обозначают границы полигонов, а контуры, идущие по часовой стрелке, – дыры. Контуры могут быть вложенными, но внутренний

контур должен иметь направление противоположное направлению содержащего его контура.

Если изначальные полигоны не удовлетворяют данным условиям, их можно привести к ним, выполним предварительную тесселяцию с активизированным параметром GLU_TESS_BOUNDARY_ONLY. Эта тесселяция возвратит список полигонов, удовлетворяющих описанным ограничениям. Если создать два объекта тесселяции, то

возвратные функции первого из них могут направлять информацию непосредственно на вход второго.

При наличии двух или более полигонов в описанной форме, операции CSG могут быть реализованы следующим образом:

UNION (объединение) – чтобы вычислить объединение нескольких контуров, нарисуйте все входящие контуры в виде одного полигона. Число оборотов каждой результирующей области это сумма чисел оборотов входящих полигонов, которые ее покрывают. Объединение может быть получено с помощью правил GLU_TESS_WINDING_NONZERO или

GLU_TESS_WINDING_POSITIVE. Заметьте, что в случае ненулевого правила, мы получим тот же результат, даже если обратим направление всех контуров.

INTERSECTION (пересечение) – его можно получить только для двух контуров за один раз. Нарисуйте один полигон с использованием двух контуров. Результат получается с помощью правила GLU_TESS_WINDING_ABS_GEQ_TWO.

DIFFERENCE (разница) – предположим, что вы хотите вычислить A diff (B union C union D). Нарисуйте единственный полигон, состоящий из неизмененного контура A, за которым следуют контуры B, C и D с обратным порядком вершин. Для получения результата используйте правило GL_TESS_WINDING_POSITIVE. (Если B, C и D являются результатом операции GLU_TESS_BOUNDARY_ONLY, то можно не изменять порядок вершин, а воспользоваться функцией gluTessNormal() для изменения знака поставляемой нормали.)

11.1.3.3 Другие функции для работы со свойствами тесселяции

Существует две функции, работающие совместно с gluTessProperty(). gluTessGetProperty() позволяет получать текущие значения свойств тесселяции. Если тесселятор используется для генерирования проволочных каркасов, а не закрашенных полигонов, функция gluTessNormal() позволяет определить направление оборота для тесселируемых полигонов.

void gluTessGetProperty (GLUtesselator* tessobj, GLenum property, GLdouble* value);

Возвращает текущее значение свойства property объекта тесселяции tessobj в переменной value. Возможные значения для аргументов property и value те же самые,

что и в функции gluTessGetProperty().

void gluTessNormal (GLUtesselator* tessobj, GLdouble x, GLdouble y, GLdouble z);

Задает вектор нормали для объекта тесселяции tessobj. Вектор нормали задает направление оборота для генерируемых полигонов. Перед тесселяцией все входные данные проецируются на плоскость перпендикулярную вектору нормали. После все

результирующие треугольники ориентируются против часовой стрелки с учетом нормали. (Направление по часовой стрелке может быть получено путем изменения знака задаваемой нормали.) Вектор нормали по умолчанию – (0, 0, 0).

Если у вас есть данные о положении и ориентации входных данных, использование gluTessNormal() может увеличить скорость тесселяции. Например, если вы знаете, что все полигоны лежат в плоскости xy, вызовите gluTessNormal(tessobj,0,0,1).

Вектор нормали по умолчанию (0, 0, 0), и его влияние не всегда очевидны. В подобном случае ожидается, что все входные данные лежат примерно в одной плоскости и плоскость охватывает все вершины независимо от того, как они на самом деле соединены. Знак нормали выбирается таким образом, чтобы сумма всех знаковых областей всех входных контуров была неотрицательной (там, где контур против часовой стрелки окружает положительную область). Заметьте, что если входные данные не лежат примерно в одной плоскости, проецирование перпендикулярно вычисленной нормали может существенно изменить геометрию.

11.1.4 Определение полигона

После того, как установлены все свойства тесселяции и зарегистрированы все функции обратного вызова, наступает время описывать вершины, составляющие входящие контуры, и тесселировать полигоны.

void gluTessBeginPolygon (GLUtesselator* tessobj, void* user_data); void gluTessEndPolygon (GLUtesselator* tessobj);

Открывают и завершают спецификацию полигона, который нужно тесселировать и ассоциируют с ним объект тесселяции tessobj. user_data указывает на данные определяемые пользователем, которые передаются всем связанным возвратным функциям GLU_TESS_*_DATA.

Вызовы gluTessBeginPolygon() и gluTessEndPolygon() обрамляют определение одного или более контуров. Когда вызывается gluTessEndPolygon(), выполняется алгоритм тесселяции, генерируются и визуализируются тесселированные полигоны. В

течение этого алгоритма используются связанные функции обратного вызова и установленные свойства тесселяции.

void gluTessBeginContour (GLUtesselator* tessobj); void gluTessEndContour (GLUtesselator* tessobj);

Открывают и завершают спецификацию замкнутого контура, являющегося частью полигона. Замкнутый контур определяется нулем или более обращениями к функции gluTessVertex(), задающей вершину. Последняя вершина каждого контура автоматически соединяется с первой.

На практике для осмысленного контура требуется как минимум 3 вершины.

void gluTessVertex (GLUtesselator* tessobj, GLdouble coords[3], void* vertex_data);

Задает одну вершину в текущем контуре для объекта тесселяции tessobj. coords содержит трехмерные координаты вершины, а vertex_data это указатель, отсылаемые функции обратного вызова GLU_TESS_VERTEX или GLU_TESS_VERTEX_DATA. Обычно, vertex_data содержит координаты вершины, нормали к поверхности, координаты текстуры, информацию о цвете или что-либо другое, нужное приложению.

В программе tess.cpp, часть которой приводится в примере 11-3, определяются два полигона. Первый полигон состоит из прямоугольного контура с треугольной дырой внутри, а второй представляет собой плавно закрашенную самопересекающуюся пятиконечную звезду. Для большей эффективности оба полигона сохраняются в списке отображения. Первый полигон состоит из двух контуров, причем внешний ориентирован против часовой стрелки, а внутренний дыра») – по часовой стрелке. Для второго полигона массив star содержит и координаты вершин и информацию о цвете, и функция обратного вызова vertexCallback() использует и то, и другое. Результат работы программы tess.cpp изображен на рисунке 11-4.

Рисунок 11-4. Результат работы тесселятора

Важно, чтобы каждая вершина находилась в своей области памяти, так как вершинные данные не копируются функцией gluTessVertex() сохраняется только указатель (vertex_data). Программа, которая использует одну и ту же память для нескольких вершин, может не получить ожидаемого результата.

Замечание: Может показаться, что в функции gluTessVertex() бессмысленно задавать координаты вершин дважды в аргументах coords и vertex_data. Однако иногда это необходимо coords ссылается только на координаты вершины, а vertex_data также хранит координаты, но может содержать и другую информацию для каждой вершины.

Пример 11-3. Определение полигона: файл tess.cpp

GLdouble rect[4][3]= { 50.0, 50.0, 0.0, 200.0,50.0,0.0, 200.0,200.0,0.0, 50.0,200.0,0.0 };

GLdouble tri[3][3]={ 75.0,75.0,0.0, 125.0,175.0,0.0, 175.0,75.0,0.0 };

GLdouble star[5][6]={ 250.0,50.0,0.0,1.0,0.0,1.0, 325.0,200.0,0.0,1.0,1.0,0.0, 400.0,50.0,0.0,0.0,1.0,1.0, 250.0,150.0,0.0,1.0,0.0,0.0, 400.0,150.0,0.0,0.0,1.0,0.0 };

startList=glGenLists(2);

tobj=gluNewTess(); gluTessCallback(tobj,GLU_TESS_VERTEX,glVertex3dv); gluTessCallback(tobj,GLU_TESS_BEGIN,beginCallback); gluTessCallback(tobj,GLU_TESS_END,endCallback); gluTessCallback(tobj,GLU_TESS_ERROR,errorCallback);

glNewList(startList,GL_COMPILE); glShadeModel(GL_FLAT); gluTessBeginPolygon(tobj,NULL);

gluTessBeginContour(tobj);

gluTessVertex(tobj,rect[0],rect[0]);

gluTessVertex(tobj,rect[1],rect[1]);

gluTessVertex(tobj,rect[2],rect[2]);

gluTessVertex(tobj,rect[3],rect[3]);

gluTessEndContour(tobj);

gluTessBeginContour(tobj);

gluTessVertex(tobj,tri[0],tri[0]);

gluTessVertex(tobj,tri[1],tri[1]);

gluTessVertex(tobj,tri[2],tri[2]);

gluTessEndContour(tobj);

gluTessEndPolygon(tobj);

glEndList();

gluTessCallback(tobj,GLU_TESS_VERTEX,vertexCallback); gluTessCallback(tobj,GLU_TESS_BEGIN,beginCallback); gluTessCallback(tobj,GLU_TESS_END,endCallback); gluTessCallback(tobj,GLU_TESS_ERROR,errorCallback); gluTessCallback(tobj,GLU_TESS_COMBINE,combineCallback);

glNewList(startList+1,GL_COMPILE); glShadeModel(GL_SMOOTH); gluTessProperty(tobj,GLU_TESS_WINDING_RULE,

GLU_TESS_WINDING_POSITIVE); gluTessBeginPolygon(tobj,NULL);

gluTessBeginContour(tobj);

gluTessVertex(tobj,star[0],star[0]);

gluTessVertex(tobj,star[1],star[1]);

gluTessVertex(tobj,star[2],star[2]);

gluTessVertex(tobj,star[3],star[3]);

gluTessVertex(tobj,star[4],star[4]);

gluTessEndContour(tobj);

gluTessEndPolygon(tobj);

glEndList();

11.1.5 Удаление объекта тесселяции

Если вы более не нуждаетесь в объекте тесселяции, вы можете удалить его и освободить всю связанную с ним память с помощью функции gluDeleteTess().

void gluDeleteTess (GLUtesselator* tessobj);

Удаляет указанный объект тесселяции tessobj и освобождает всю связанную с ним память.

11.1.6 Советы по увеличению быстродействия тесселяции

Для наилучшего быстродействия соблюдайте следующие правила.

Соседние файлы в предмете Компьютерная Графика