glEnd()

advertisement
1
Оглавление
1. Постановка задачи. ................................................................................................................................. 3
2. Описание модели сцены. ....................................................................................................................... 4
2.1 Описание системы координат. ......................................................................................................... 4
2.2 Описание используемой проекции сцены. ..................................................................................... 4
2.3 Описание моделей объекта. ............................................................................................................ 7
3. Основные алгоритмы. ...........................................................................................................................11
3.1. Преобразование системы координат. ..........................................................................................11
3.2. Алгоритм z-buffer ............................................................................................................................13
3.3. Алгоритм вращения. ......................................................................................................................18
3.4. Алгоритм сдвига. ............................................................................................................................20
3.5. Алгоритм построения отражения в плоском зеркале.................................................................23
4. Описание интерфейса программы. .....................................................................................................28
5. Иллюстрации..........................................................................................................................................31
6. Список литературы. ...............................................................................................................................34
7. Текст программы. ..................................................................................................................................35
2
1. Постановка задачи.
Необходимо разработать программу, которая будет визуализировать 3-d сцену, в которой
находится объект, состоящий из двух деталей – основания и боковой детали,
располагающиеся на зеркале (рис.1). Проекция кабинетная. Система координат правая.
Так же необходимо обеспечить следующий функционал: вращение боковой детали вокруг
своей оси на 3600, вращение обоих деталей вокруг оси У на 3600, независимое растяжение
основания вдоль оси У и растяжение боковой детали вдоль оси Х. Обе детали
располагаются на зеркале, форма которого представлена на рис.2. Кроме того, нужно
реализовать автоматический режим – по умолчанию непрерывное, плавное, с
регулируемыми в реальном времени направлениями движения.
Рис. 1
Рис.2
Разработанная программа должна работать на ОС (операционной системе) Windows,
начиная с Windows XP и на более поздних версиях этой системы. Так же необходимо
обеспечить, чтобы программа работала на компьютере пользователя без каких-либо
дополнительных, установленных заранее, библиотек и расширений, то есть все
используемые программой библиотеки должны быть собраны вместе и идти «в
комплекте» с разрабатываемой программой.
3
2. Описание модели сцены.
Описание моделей объекта, системы координат с конкретными цифровыми значениями,
используемой проекции сцены.
2.1 Описание системы координат.
Используется правостроннняя система координат. Расположение осей координат
представлено на рис. 3
Рис. 3.
2.2 Описание используемой проекции сцены.
Для начала надо разобраться с тем, что мы понимаем и подразумеваем под словом
«проецирование», так как это напрямую связано с описанием используемой проекции.
Слово проецирование означает отображение трехмерного объекта на двумерную
картинную плоскость. Так же помним, что получение проекции основано на методе
трассировки лучей. Суть этого метода такова: берем некоторый центр проецирования
(обозначим его S) и проводим лучи через каждую точку объекта то того момента, пока эти
лучи не пересекутся с картинной плоскостью. Те следы, которые образуются точками
пересечения этих лучей с картинной плоскостью, образуют проекцию объекта.
Здесь стоит упомянуть, что любая проекция, а их за время развития человека придумано
очень много, содержит ошибку восстановления координат объекта-образа. Это связано с
тем, что проецирование не является аффинным преобразованием и принципиально не
обратимо. Тогда возникает закономерный вопрос, почему проекций так много, чем они
отличаются и для чего придуманы? Ответ очень прост, каждая проекция (точнее сказать
метод проецирования) служит для уменьшения той самой неопределенности восприятия
наиболее значимых свойств и некоторых параметров объектов за счет увеличения
неопределенности менее значимых свойств объекта.
4
Мы будем использовать кабинетную проекцию (еще одно название этой проекции косоугольная фронтальная диметрия). Объясним суть этой проекции. Начнем с того, что
вспомним общепринятую классификацию проекций. Основным моментом, который нас
интересует, является то, что все проекции делятся на параллельные и центральные. В
свою очередь, параллельные проекции делятся на косоугольные и ортогональные.
Кабинетная проекция относится к косоугольным проекциям. Косоугольное
проецирование отличается от ортогонального тем, что в нем для объемного видения
объект параллельных проецирующих лучей пускается не ортогонально картинной
плоскости. При этом сам объект и его объектная система координат остаются
неподвижными. Соответственно Sкос=S.
Теперь обратимся к рисунку 4.
Рис. 4
На этом рисунке мы видим, что вектор направления на проецирующий пучок S=[sx sy sz]
составляет с нормалью фронтальной картинной плоскости угол проецирования αf с
тангенсом
tg(αf)= (√(sx2+sy2 ))/sz.
Теперь нам необходимо найти параметрическую функцию луча, который проходит через
точку p=[x y z] в направлении –S: эта функция записывается так: p-St.
Таким образом, этот луч пересекает фронтальную картинную плоскость в точке
p’=[x’ y’ 0]. Теперь нам необходимо получить параметр пересечения tf и координаты
проекции точки p’ϵf из условия z’=z-sztf=0
параметр tf :
tf=z/sz
и, собственно, координаты проекции точки p’ϵf : x’= x-sx*z/sz, y’= y – sy*z/sz.
Теперь опишем косоугольное проецирование на фронтальную картинную плоскость.
Получаем, что в векторной форме p’=pKf косоугольное проецирование на фронтальную
картинную плоскость описывается следующей матрицей Kf :
5
, где cx=sx/sz, cy=sy/sz »
(формула 1)
Теперь нам необходимо получить коэффициенты осевых искажений, проецируя орты
объектной системы координат на фронтальную картинную плоскость.
Получим эти коэффициенты:
, отсюда mx=my=1, mz=√(cx2+cy2) = tg(αf) (формула 2)
При этом, углы ориентации проекций осей объектной системы координат на фронтальную
картинную плоскость определяются условиями:
γxy = 90°, tg(γyz) = -cx/cy = -sx/sy,
tg(γzx) = -cy/cx = -sy/sx.
(формула 3)
Теперь, когда мы описали суть косоугольной проекции, вернемся к самому основному –
описанию кабинетной проекции, так как нам необходимо использовать именно
кабинетную проекцию в работе. Будем использовать ранее полученные формулы 1, 2 и 3
для определения параметров mx , my и mz . При этом стоит знать, что кабинетная проекция
образуется при направлении источника S = [1 1 2√2]. По формулам (1-3) определяем ее
параметры mx = my = 1, mz = 0.5, γxy = 90°,
γyz = γzx =135°, λf = arctg(0.5) ≈ 26.6°, cx = cy = 1/2√2 и
,
Sкаб = [1 1 2√2].
Изобразим кабинетную проекцию на рисунке 5.
Рис. 5
6
2.3 Описание моделей объекта.
Объект состоит из двух деталей – основания и боковой детали, а так же зеркала,
находящегося непосредственно под ними. Основание находится на зеркале, то есть
нижняя грань основания лежит в плоскости зеркала. Зеркало имеет форму, изображенную
на рисунке 2. Для создания такой формы зеркала используется наложение двух полигонов
справа и слева соответственно с цветом фона поверх зеркала. Будем далее называть эти
полигоны соответственно: наложенный полигон слева (заштрихован на рисунке) и
наложенный полигон справа (закрашен черным цветом). Изобразим на рисунке эти
полигоны (рис. 6)
Рис. 6
Основание состоит из граней. Пронумерованные грани изображены на рис. 7. Номера
граней обведены в пятиугольник. Цифрами обозначены номера вершин.
Рис. 7
7
Грани основания:
1) Передняя грань обозначена цифрой 1, которая обведена в пятиугольник.
2) Правая грань обозначена цифрой 2, которая обведена в пятиугольник.
3) Задняя грань обозначена цифрой 3, которая обведена в пятиугольник.
4) Левая грань обозначена цифрой 4, которая обведена в пятиугольник.
5) Нижняя грань обозначена цифрой 5, которая обведена в пятиугольник.
6) Верхняя грань обозначена цифрой 6, которая обведена в пятиугольник.
Грани боковой детали:
1) Передняя грань обозначена цифрой 7, которая обведена в пятиугольник.
2) Правая грань обозначена цифрой 8, которая обведена в пятиугольник.
3) Задняя грань обозначена цифрой 9, которая обведена в пятиугольник.
4) Левая грань обозначена цифрой 10, которая обведена в пятиугольник.
5) Нижняя грань обозначена цифрой 11, которая обведена в пятиугольник.
6) Верхняя грань обозначена цифрой 12, которая обведена в пятиугольник.
Наложенные полигоны слева и справа.
Наложенный полигон слева обозначен цифрой 14 (цифра 14 обведена в пятиугольник).
Наложенный полигон справа обозначен цифрой 15 (цифра 15 обведена в пятиугольник).
Зеркало обозначено цифрой 13 (цифра 13 обведена в пятиугольник).
Каждая грань описана вершинами. Укажем из каких вершин состоит каждая грань.
Грани основания:
1) Передняя грань, обозначенная цифрой 1 в пятиугольнике, состоит из вершин: 1, 2, 6, 5.
2) Правая грань, обозначенная цифрой 2 в пятиугольнике, состоит из вершин: 2, 3, 7, 6.
3) Задняя грань, обозначенная цифрой 3 в пятиугольнике, состоит из вершин: 4, 3, 7,8.
4) Левая грань, обозначенная цифрой 4 в пятиугольнике, состоит из вершин: 1, 4, 8, 5.
5) Нижняя грань, обозначенная цифрой 5 в пятиугольнике, состоит из вершин: 1, 2, 3, 4.
6) Верхняя грань, обозначенная цифрой 6 в пятиугольнике, состоит из вершин: 5, 6, 7, 8.
8
Грани боковой детали:
1) Передняя грань, обозначенная цифрой 7 в пятиугольнике, состоит из вершин: 9, 10,
13,6.
2) Правая грань, обозначенная цифрой 8 в пятиугольнике, состоит из вершин: 10, 11,
14,13.
3) Задняя грань, обозначенная цифрой 9 в пятиугольнике, состоит из вершин: 12, 11, 14, 7.
4) Левая грань, обозначенная цифрой 10 в пятиугольнике, состоит из вершин: 9, 12, 7, 6.
5) Нижняя грань, обозначенная цифрой 11 в пятиугольнике, состоит из вершин: 9, 10, 11,
12.
6) Верхняя грань, обозначенная цифрой 12 в пятиугольнике, состоит из вершин: 6, 13, 14,
7.
Наложенный полигон слева обозначен цифрой 14 в пятиугольнике состоит из вершин:
15, 16, 17, 18.
Наложенный полигон справа обозначен цифрой 15 в пятиугольнике состоит из вершин:
19, 20, 21, 22.
Зеркало обозначено цифрой 13 (цифра 13 обведена в пятиугольник) состоит из вершин:
15, 20, 23, 24.
Координаты вершин (x,y,z) соответственно (рис. 8).
Рис. 8
1-ая вершина имеет координаты: (-0.5, 0, 0.5); 13-я вершина имеет координаты: (1.5, 2, 0.5)
9
2-ая вершина имеет координаты: (0.5, 0, 0.5); 14-я вершина имеет координаты: (1.5, 1.6, -0.5)
3-я вершина имеет координаты: (0.5, 0, -0.5); 15-я вершина имеет координаты: (-2.5, 0, 5)
4-ая вершина имеет координаты: (-0.5, 0, -0.5); 16-я вершина имеет координаты (-1.5, 0, 5)
5-я вершина имеет координаты: (-0.5, 2, 0.5); 17-я вершина имеет координаты: (-1.5, 0, 0)
6-я вершина имеет координаты: (0.5, 2, 0.5); 18-я вершина имеет координаты: (-2.5, 0, 0)
7-я вершина имеет координаты: (0.5, 2, -0.5); 19-я вершина имеет координаты: (1.5, 0, 5)
8-я вершина имеет координаты: (-0.5, 2, -0.5); 20-я вершина имеет координаты: (2.5, 0, 5)
9-я вершина имеет координаты: (0.5, 1.6, 0.5); 21-я вершина имеет координаты: (2.5, 0, 0)
10-я вершина имеет координаты: (1.5, 1.6, 0.5); 22-я вершина имеет координаты: (1.5, 0, 0)
11-я вершина имеет координаты: (1.5, 1.6, -0.5);23-я вершина имеет координаты: (2.5, 0, -3.5)
12-я вершина имеет координаты:(0.5, 1.6, -0.5);24-я вершина имеет координаты: (-2.5, 0, -3.5)
10
3. Основные алгоритмы.
3.1. Преобразование системы координат.
Сначала поясним, что такое геометрическое преобразование по своей сути. Под
геометрическим преобразованием понимаем отображение p’=f(p) точки pϵRn n-мерного
пространства образа в точку p’ϵRn’ n’ – мерного пространства прообраза. Отметим, что
геометрические преобразования делятся на нелинейные (как пример можно привести
отражение в кривом зеркале) и линейные. При этом линейное преобразование точки
будет описываться уравнением: p’=pA + B (где A и B - это матрицы преобразования,
причем AϵRn*n’, BϵR1*n’. При этом понимаем, что эти матрицы не зависят от координат
точки p. Из формулы p’=pA + B делаем следующий вывод – матрица B (по сути, она имеет
структуру вектора) задает направление переноса точки в пространстве. Здесь еще стоит
упомянуть по вырожденные и невырожденные преобразования. В зависимости от свойств
A (в формуле p’=pA + B) и размерности пространств n и n’ линейные преобразования
делятся на вырожденные (проективные) и невырожденные (аффинные). Невырожденным
преобразованиям присущи такие свойства как: квадратность и невырожденность матрицы
А (то есть n’=n). Из этого вытекает следующее – существование обратной матрицы А-1, и
эта матрица позволяет нам по точке прообраза p’ восстановить точку образа p по формуле:
p=(p’-B)A-1. В отличие от невырожденных преобразований, в проективных
преобразованиях (n’<n) не существует обратной матрицы A-1. Поэтому однозначное
восстановление образа по его проекции невозможно из-за потери информации об одной
или нескольких координатах.
Нам необходимо совершить преобразование координат из мировой системы (МСК) в
систему экранных координат. Визуально это действие можно представить на рисунке
(рис. 9).
Рис. 9
Чтобы решить эту задачу, воспользуемся методом парных точек. Этот метод основан
на формировании матрицы преобразования в n-мерном пространстве по n+1-ой паре
элементов образа и прообраза. При решении этим методом, первое, что необходимо
сделать – расчет масштабного коэффициента μэ и матрицы Cэ, где происходит
пропорциональное отображение на экранную систему координат (Xэ Yэ) прямоугольного
окна из мировой системы координат (X Y).
11
Вводим:
𝑦
−𝑦
𝜌 = 𝑚𝑎𝑥 𝑚𝑖𝑛
𝑥𝑚𝑎𝑥 −𝑥𝑚𝑖𝑛
Где 𝜌
и 𝜌э
𝜌э =
низ−вверх
право−лево
- коэффициенты прямоугольности.
Если окна пропорциональны, то их коэффициенты равны. При этом матрицу Cэ
можно вычислить методом парных точек при n=2, при этом находя матрицы {A,B} и C по
известному начальному положению объекта Р и известному конечному положению
объекта P′. Чтобы вычислить матрицы С ∈ R3×3 ,𝐴 ∈ R2×2 ,𝐵 ∈ R1×2 при n=2, необходимо
на плоскости XY выбрать в начальном и конечном состояниях фигуры по две тройки
неколлинеарных точек {p1, p2 , p3}, {p1′, p2′ , p3′}, или же по одной точке и по два
неколлинеарных вектора{o,V,W}, {o′,V ′,W′}
Выберем четыре пары точек (в мировой системе координат точки не должны быть
компланарны), с помощью которых будем осуществлять пространственное
преобразование:
p1 =[xmin ymin 0] → p1э =[лево низ 0],
p2 =[xmin ymax 0]→ p2э =[лево вверх 0],
p3 =[xmax ymax 0]→ p3э =[право вверх 0],
zо→O3 .
Если окна непропорциональны, методом разложения получаем матрицу Cэ.

Осуществляем с помощью матрицы перенос центра окна Рц в начало мировой
системы координат:
T1 =T ([−xц − yц 0]), где xц =0.5(xmin +xmax) , yц =0.5 (ymin + ymax).

Далее масштабируем окно по осям X и Y, инвертируем направление оси Y и
формально обнуляем матрицей Z-координату.
где diag — квадратная матрица 𝑀 ∈ Rn×n с диагональными элементами Mii и
недиагональными элементами Mij =0 ∀i≠ j .
12

Следующий шаг – перенос центра окна матрицей в точку Рэц экранной системы
координат:
T2 =T ([xцэ yцэ 0]), где xцэ =0.5(лево +право), yцэ =0.5(вверх+низ) .
В результате мы получим матрицу полного преобразования
С помощью коэффициентов полученной матрицы получаем прямые уравнения
пересчета координат точки из мировой системы координат в экранную систему координат
(координата zэ вектора p’ =p*Cэ при этом получается нулевой):
xэ =xцэ +μэ (x –xц) , yэ = yцэ −μэ (y – yц)
Таким образом, преобразование системы координат осуществляется успешно.
3.2. Алгоритм z-buffer
Объекты, которые мы имеем в нашей работе, нужно суметь корректно отобразить,
чтобы на мониторе мы увидели реалистичную и понятную картинку. Чтобы это сделать,
нам поможет метод визуализации поверхностей – алгоритм z-буфера, за авторством Э.
Кэтмула. Этот способ довольно прост и эффективен.



Логически метод работает так:
сначала перебираются пиксели, принадлежащие проекции поверхности
потом эти пиксели восстанавливаем по положению соответствующих пикселей на
самой поверхности
и, в итоге, записываем в видеопамять коды цветов тех точек, которые ближе к
наблюдателю
В этом методе мы используем z-координату, чтобы в конце концов узнать, какие
точки насколько отдалены от наблюдателя – отсюда и название метода.
ЭВМ должна определить какие z-координаты у каждой точки и отсортировать эти
координаты. Для этого нужно создать две матрицы:

во-первых, одна будет хранить z-координаты тех точек, которые к наблюдателю
самые ближайшие – видимые на экране. Это буфер глубины Z. Чтобы рассчитать
его объем, нужно умножить количество слов на объем слова. Количество
определяется размером окна вывода дисплея: ширина * высота, где ширина –
крайняя правая точка окна минус крайняя левая и плюс 1, а высота – крайняя
нижняя минус крайняя верхняя точка окна и плюс 1. А объем слова Nz будет
зависеть от того, сколько в нем будет разрядов. Их число определяется
13
количеством квантов, на которые мы разделим глубину Z, чем больше квантов –
тем точнее будет результат, и вычисляется разрядность так: 2^(Nz).

Вторая матрица – буфер, который хранит цвет каждой точки, окончательно
обреченной к выводу на дисплей. Такая матрица называется буфер кадра F. Объем
такого буфера – площадь окна вывода (рассчитывается как для предыдущего
буфера), умноженная на слово, содержащее информацию о цвете. Разрядность
такого слова NF зависит от количества квантов, на которые мы расслоили диапазон
цветов - 2^(NF). Чем больше квантов – тем больше цветов, но объемнее буфер.
Для наглядности и удобства к восприятию алгоритм работы z-буфера поясняется на
схеме (рис. 10).
Следует отметить, что все вычисления ведутся в экранной системе координат.
Необходимо составить список LP={Pk}, в который мы поместим данные обо всех
полигонах каждой поверхности. Для начала им можно присвоить элементам такого списка
случайные номера, к сортировке мы еще не подошли.
Перед тем, как начать обрабатывать полигоны, выполним инициализацию двух
буферов – глубины и кадра (рассмотренных выше), для этого положим в буфер кадра
картинку (в принципе, любую) – она будет видима при отсутствии перед ней объектов. В
буфер глубины занесем информацию о дальности этой картинки от наблюдателя. В
соответствии с названием, фон – картинка на заднем плане, поэтому располагаем его в
самой дальней от наблюдателя плоскости, в то место, дальше которого ни один объект,
который мы будем потом отображать, не сможет забраться. Часто такие координаты
устанавливаются в зависимости от того, какое максимальное число разрядной сетки
процессора ЭВМ (чтоб уж наверняка).
Визуализируем кадр. Для этого:



сначала выбираем из списка LP очередной полигон Pk.
Проецируем его на экран, отсекаем лишнее окном вывода (рис. 11а,б).
Получившаяся экранная проекция Pэ сканируется по точкам (рис. 11в).
Такой процесс называется растеризация. Иными словами – происходит разложение
в растр – в двухмерный массив пикселей, заполняющий контур проекции. Вообще,
пиксели для растеризации можно выбирать в любом порядке, но, в большинстве случаев,
растеризация идет слева направо по строчкам – от верхней до нижней. Таких растровых
алгоритмов придумано довольно много.
Из получившегося растра pэ = [i j 0] берутся пиксели по одному и для каждого из
них восстанавливается образ точки p = [x y z], по уравнению той самой поверхности, на
которой она лежит. Можно сказать, что из каждого экранного пикселя, которые мы
храним в массиве растра, в направлении объекта проводится вектор Hэ (специальный
служебный, для сканирования), который, когда пересечет поверхность грани, поможет
рассчитать до нее расстояние.
14
Рис. 10
Рис. 11
Вид вектора Hэ зависит от типа наблюдателя:


Если он дальний (рис. 11а), то будет фиксированным Hэ = S, таким образом он
соответствует заданной параллельной проекции;
В сцене с ближним наблюдателем в точке S (рис. 11б) вектор Hэ = S – pэ. Метод
расчета пересечения прямой c объектом выглядит так: r(Ѳ) = pэ + Hэ Ѳ и
определяется в зависимости от того, какая поверхность этот объект ограничивает;
15
В таком случае, для неявной модели f(p) = 0 необходимо решить скалярное
уравнение f(pэ +Hэ Ѳ) = 0 и сделать этот расчет относительно лучевого параметра Ѳ
(который дает нам точку p=r(Ѳ) ). Если для примера взять, допустим, плоскость, заданную
{o,N}, у которой НФ f(p) = (p-o)◦N, то после проведения расчетов получим p =
cross_line(pэ,Hэ,o,N);
Чтобы найти ту точку, в которой прямая будет пересекаться с параметрической
поверхностью p(t,τ), нужно решить векторное уравнение pэ + Нэ Ѳ = p(t,τ), из которого p
будет равна r(Ѳ).
Итак, у найденной точки p мы нашли дальность d по формуле di=|ci-S|. Теперь
необходимо проверить состояние (i,j)-го элемента (Zij) в нашем буфере глубины. Если d
получается меньше, чем Zij, то это значит, что точка p к наблюдателю располагается
ближе, чем известные точки (фон или ранее найденные точки) с такими же экранными
координатами [ i j]. В таком случае (нашли новую ближайшую точку), сохраним
состояние этой точки Zij = d в z-буфере. В свою очередь, в пиксель буфера экранного
кадра Fij запишем цветовой код c.
Вот так и получается, что из нескольких точек, нанизанных на (i,j)-ую
сканирующую прямую, наблюдатель увидит лишь самую ближайшую. В случае, если на
прямой не оказалось ни одной точки, то цвет пикселя останется фоновым.
Когда оба вышеописанных цикла наконец выполнятся, то что содержалось в итоге
в буфере кадра (цвета пикселей отображаемого окна) скопируется в окно вывода на экране
дисплея, начиная с верхней строчки и с самого левого пикселя в ней.
В итоге, главные преимущества алгоритма z-буфера – это простота и тот факт, что
поверхности можно обрабатывать в произвольном порядке. Это позволяет экономить
машинные ресурсы на специальную сортировку по глубине. Сложность вычислений в
результате будет линейно зависеть от количества объектов и площади экрана, которую
они своими проекциями покрывают. Если на нашей сцене объектов совсем немного и они
относительно простые (и особенно если доля фона на конечном изображении большая), то
метод z-буфера совершит свои дела довольно таки быстро.
По традиции, нужно перечислить недостатки описанного метода (куда же без них):
1) Большой объем памяти, который занимают два крупных буфера, тем больше, чем
больше графическое разрешении и площадь окна вывода. Соответственно, при большом
разрешении и крупном окне памяти требуется много.
2) Приходится совершать предварительную растровую развертку проекции поверхности, а
если эта самая поверхность непрямолинейна – задача становится сложнее.
3) Так как на экран поверхности выводятся в произвольном порядке, становится сложно
реализовать полупрозрачность.
А.Майерсо, широко известный в узких кругах, предложил способ нейтрализации
одного из недостатков - повышенного потребления памяти. Чтобы это потребление
16
уменьшить, по его словам, нужно сократить высоту буферов кадра и глубины до одной
строки экрана, то есть сделать значение высоты=1.
В результате получился метод построчного сканирования с z-буфером.
Получается, что однострочная развертка поверхности – это:


проекция на экране такой линии, которая образуется при сечении поверхности
плоскостью зрения П (которая проходит через крайние точки сканирующей строки
[left уэ],[right уэ])
точка S расположения ближнего наблюдателя (рис. 12,а).
Отметим, что плоскость зрения П почти всегда не ортогональна картинной плоскости.
В случае же наблюдателя, удаленного по вектору S, плоскость зрения проходит через
точку [left уэ] + S и, например, при S||z°, всегда ортогональна экрану (рис. 12,б).
Самое сложное – это расчет плоского сечения криволинейной поверхности, не
аппроксимированной полигональными гранями (рис. 12,а).
Рис. 12
Еще здесь приходится формировать множество точек, которые мы находим, когда
сканирующий луч (наш вспомогательный вектор Hэ) пересекается с кривой линией
сечения. Ведь только в результате сортировки получившегося массива мы сможем найти
ту самую заветную ближайшую к наблюдателю точку.
Вообще можно производить расчеты с помощью аналитических формул. Но такое
возможно только для узкого класса неплоских поверхностей и кривых линий, в основном,
квадратичного типа. Если же наши объекты не попадают в их компанию, придется
прибегать к услугам медленных численных методов решения нелинейных уравнений.
Плоская грань пересекается с плоскостью зрения по прямому отрезку, имеющему
не более одной точки пересечения с лучом, но все равно эти расчеты должны выполняться
в пространстве (рис. 12,б). Поэтому гораздо эффективнее будет сначала найти экранную
проекцию абриса поверхности или грани, а потом уже построчно решать на плоскости xэуэ
задачу внешнего отсечения горизонтального отрезка {[left уэ],[right уэ]} полученным
полигоном.
В результате, в сканирующей строке формируется список проекций отрезков
Ls={a1’b1’…an’bn’}. Далее начинается их попиксельная обработка с помощью Z- и Fбуферов строки (которые нужно предварительно инициализировать фоновыми
17
значениями глубины и цвета). Ну и когда, наконец, завершится перебор содержимого
списка Ls, буфер F отобразится в сканируемую строку.
3.3 Алгоритм вращения.
Теперь опишем алгоритм вращения. Сначала рассмотрим вращение произвольной точки
p=[x y]ϵR2 на плоскости xy на угол φ. Данное вращение описывается следующими
уравнениями (ниже представлена система уравнений):
x’ = rcos(α+ φ) = xcos(φ) – ysin(φ),
y’ = rsin(α+ φ) = xsin(φ) + ycos(φ),
так как x = rcos(α) и y = rsin(α). Углы α и φ обозначены на рисунке 13а.
Рис. 13а
Отсюда можно получить матрицы вращения в положительном (против часовой стрелки) и
отрицательном направлениях. Проделаем это:
Теперь разберемся с вращением в пространстве. При вращении точки в пространстве (для
примера назовем эту точку p, эта точка будет задана координатами: p=[x y z]ϵR3) вокруг
осей правой (именно правой) системы координат (а это значит, что положительные
направления вращений удовлетворяют нашему любимому правилу буравчика) матрицы
вращений получим из уравнений:
18
Здесь хотелось бы упомянуть тот факт, что буквально каждый первокурсник знает и
понимает (должен знать и понимать!) операции вращения, эти операции очень важные и
очень широко распространены, поэтому эти преобразования многие считают
элементарными, а не сложными.
Воспользуемся следующим уравнениями при вращении точки P имеющей координаты
(x,y,z) вокруг оси Y правой системы координат:
x’ =xcos(φy) + zsin(φy),
y’ = y,
z’ = - xsin(φy)+zсos(φy)
Таким образом, для поворота объекта (в нашем случае Основания и Боковой детали, См. в
пункте 7 “Текст программы”, страница 47 и стр.48) на заданный угол каждая координата
вершины объекта пересчитывается по соответствующей формуле.
Блок схема алгоритма вращения (рис. 13б)
19
Рис. 13б
С помощью данного алгоритма поворачиваем каждую точку объекта на необходимый
угол вокруг оси У. Это что касается вращения обоих деталей вокруг оси Y.
Теперь необходимо описать вращение боковой детали вокруг своей оси параллельно оси
X. Для этого необходимо сделать следующие действия: перенос всех точек, образующих
боковую деталь в начало координат, так чтобы собственная ось вращения детали
совпадала с осью X, затем нужно выполнить собственно вращение боковой детали на
заданный угол, а на последнем шаге переместить боковую деталь в исходное положение,
но уже в повернутом состоянии. См. в пункте 7 “Текст программы”, стр.48). При этом
будем пользоваться следующими уравнениями при вращении точки M имеющей
координаты (x,y,z) вокруг оси X правой системы координат:
x’ =x,
y’ = ycos(φx) - zsin(φx),
z’ = ysin(xx)+zсos(φx).
Таким образом, для поворота объекта (боковой детали) на заданный угол каждая
координата вершины объекта пересчитывается по соответствующей формуле.
3.4 Алгоритм сдвига.
Для того чтобы реализовать растяжение деталей (в рамках курсовой работы необходимо
растягивать основание и боковую деталь рис. 14а, и при этом боковая деталь или как мы
будем называть её еще – боковой отросток должен растягиваться вдоль оси Х, а наша
20
ножка (основание) должна растягиваться вдоль оси У) необходимо преобразовать
координаты соответствующих граней этих объектов. То есть нам необходимо
преобразовывать по четыре вершины у каждой детали.
Рис. 14а
Таким образом, нам нужно сдвигать каждую вершину соответствующей поверхности
(грани). Для бокового отростка изобразим на рисунке 14б эти четыре точки, которые надо
преобразовывать.
Рис. 14б
Для этого необходимо умножить на соответствующую матрицу каждую вершину грани и
получить измененные координаты этой вершины.
Рассмотрим сдвиг одной единственной точки в трехмерном пространстве.
Для начала обозначим эту точку K, а ее координаты (x,y,z.) Точка K сдвигается в
положение K' = (x', у', z'). (рис. 15)
21
Рис. 15
Будем делать сдвиг следующим образом: прибавлением расстояний mx,mу и mz к координатам точки K:
х' = х + mx,
у' = у + my,
z' = z + mz
Таким образом, нам необходимо знать оператор сдвига – M – это матрица размерности
4×4
Точки K и K' представляются четырехэлементными векторами-столбцами
K' =K×P
В итоге, получаем следующее:

1ый элемент равен х + mx

2ой элемент равен у + my

3ий элемент равен z + mz

4ый элемент равен 1
Представим блок-схему данного алгоритма на рис. 16
22
Рис. 16
В разработанной программе данный алгоритм применяется для растяжения боковой
детали вдоль оси X и для растяжения основания вдоль оси У (см. пункт 7 «Текст
программы» стр.46, 47 )
3.5. Алгоритм построения отражения в плоском зеркале.
Для начала нам необходима отрисовка частей основания и боковой детали, которые
необходимо отразить. При этом не забываем, что при отражении часть световой энергии
теряется. Это нам нужно для того, чтобы построить отражение в зеркале более
реалистичным (не с точки зрения геометрии, а с точки зрения реалистичности цвета
отраженной детали). Теперь выясним как это сделать. Это достаточно несложно, цвет
отраженных деталей (основания и боковой детали) будем вычислять по следующей
формуле:
Сотр = Cзерк×C
где Cзерк – цвет зеркала, С – отражаемый цвет.
(См. в пункте 7 «Текст программы», стр. 39 )
Отражение полученной сцены.
Опишем, как именно происходит отражение. Для этого разберем это на примере одной
точки. (для остальных все будет аналогично).
Опишем плоскость зеркала с помощью вектора n (нормали) и принадлежащей плоскости
точки p0. Значит, если точка p0 принадлежит плоскости, то точка p также принадлежит
этой плоскости в том случае, если вектор (p – p0) перпендикулярен вектору нормали
плоскости (рис. 17)
n×(p-p0)=0
23
в этой формуле × - скалярное произведение.
Рис. 17
Отметим, что при описании конкретной плоскости вектор нормали n и принадлежащая
плоскости точка p0 обычно фиксированы, и формула n×(p-p0)=0 записывается в виде:
n×p+r=0
где r = –n × p0, в этих формулах и ниже × - скалярное произведение.
Таким образом, получаем что если n × p + r = 0, то точка p принадлежит плоскости. Кроме
того, для построения отражения необходимо определить точку на плоскости, которая
ближайшая к какой-нибудь заданной точке (это является обязательным).Допустим, у нас
есть точка k, нам необходимо решить задачу нахождения точки q, которая принадлежит
плоскости ( , d) и причем q будет ближайшей к точке k.
Обратим внимание на то, что предполагается что вектор нормали плоскости
нормализован, это упрощает решение задачи. (рис. 18)
Рис. 18
Точка q плоскости ( , d) ближайшая к точке k. Обратим внимание на то, что кратчайшее
расстояние m от точки k до плоскости положительно, если точка k находится в
положительном полупространстве плоскости ( , d). Если же точка k находится за
плоскостью, то m < 0
Помним, что если вектор нормали плоскости n нормализован, то n × p + d является
кратчайшим расстоянием от плоскости до точки k.
После того как мы задали плоскость
точки k = (px, py, pz)
 k + r = 0 будем строить отражение
Вычислим точку отраженную k' = (k'x, k'y, k'z) см. рис 19
24
Рис. 19
На рис. 18 видно, что q = k+ (-m ), где m — это кратчайшее расстояние от точки k до
плоскости, которое одновременно является и кратчайшим расстоянием между
точками k и q.
Получаем следующее: отражение точки k' относительно плоскости
вычисляется так:
k'=k-2m
= k- 2( *k+r)
*k+r=0
= k-2(( *k) +r )
В матричном виде будет выглядеть преобразование k в точку k':
Использование буфера трафарета.
По заданию необходимо сделать зеркало Т-образной формы (рис.20)
Рис. 20
25
Следовательно, нам необходимо решить задачу отсечения. Под отсечением будем
понимать процедуру обнаружения и исключения из дальнейшего анализа таких элементов
сцены, которые не попадут в область видимости наблюдателя. Для создания такой формы
зеркала (рис. 6) используется наложение двух полигонов справа и слева соответственно с
цветом фона поверх зеркала. Для решения задачи отсечения в данной работе будем
использовать буфер трафарета. Он позволяет не визуализировать заданные области. Далее
объясним, как мы использовали буфер трафарета, разобьем все действия на несколько
шагов. Использование буфера трафарета пошагово:
1) Очистка буфера трафарета (заполнение нулями)
2) Заполняем единицами буфер трафарета в тех местах, где будет в дальнейшем
нарисовано отражение наших деталей (основания и боковой части) (рис. 21)
Рис. 21
3) Далее визуализируем отражение изображения основания и боковой детали. Перед этим
задаем проверку трафарета так, чтобы тест выполнялся успешно, в случае если в буфере
трафарета находится единица (видно по рис. 21)
4) Таким образом, у нас изображается отображение деталей (основания и боковой части)
только так, где соответствующему пикселю в буфере трафарета стоит 1. Но у нас единицы
в буфере трафарета находятся там, где должно быть отражение деталей, и т.е. только в
нашем зеркале будет нарисовано отражение.
См. пункт 7 «Текст программы» стр.38
Составим бок-схему алгоритма «Отражение» (рис. 22)
26
Рис. 22
27
4. Описание интерфейса программы.
В главном окне программы пользователю доступен просмотр сцены с помощью
графического интерфейса.
При запуске программы перед пользователем открывается следующее окно (рис. 23)
Рис. 23
Окно вывода изображения содержит объект (две детали и зеркало) и оси координат.
Черным цветом изображена ось Х, красным ось У, зеленым ось Z.
Справа от окна вывода изображение присутствует панель управления. Рассмотрим панель
управления объектом подробнее.
Первым элементом является тройка кнопок, с помощью которых пользователь может
запустить автоматическое вращение объектов сцены (рис. 24)
28
Рис. 24
При этом обе детали начинают вращаться вокруг оси У, боковая деталь при этом также
вращается вокруг своей оси. Кроме того, происходит растяжение обоих деталей. Данная
тройка кнопок создана для автоматической демонстрации возможностей программы. При
нажатии кнопки stop все движения прекращаются. Отметим, что одновременно
пользователю доступно нажатие только одной из трех кнопок.
Следующими элементами управления являются спиннеры – они позволяют изменять
скорость вращения деталей в описанном выше режиме (рис. 25)
Рис. 25
Как видно из рисунка, верхний спиннер отвечает за скорость вращения боковой детали
вокруг своей оси, а нижний – за скорость вращения обей деталей вокруг оси У.
Пользователю доступно изменение значения спиннеров после запуска автоматического
режима.
Далее рассмотрим элементы ручного управления. За растяжение деталей отвечают два
ползунка (рис. 26)
Рис. 26
Слева располагается ползунок, отвечающий за растяжение основания по оси У. Справа от
него, находится ползунок, отвечающий за растяжение боковой детали по оси Х. Отметим,
что пользователю доступны эти ползунки в автоматическом режиме.
29
Ниже располагаются еще 2 ползунка, отвечающие за вращение обоих деталей вокруг оси
У и за вращение боковой детали вокруг своей оси (рис. 27)
Рис. 27
Верхний ползунок отвечает за вращение боковой детали вокруг своей оси, а нижний – за
вращение обоих деталей вокруг оси У. Также стоит отметить, что пользователю доступны
эти ползунки в автоматическом режиме.
Слева внизу располагается кнопка выхода из программы, когда пользователю наскучило
смотреть на вращение деталей и отражение от них в зеркале (рис. 28)
Рис. 28
При нажатии на кнопу «Quit» программа завершает свою работу.
30
5. Иллюстрации
При запуске программы перед пользователем предстает следующее окно (рис. 29)
Рис. 29
При нажатии на кнопку вращение начинается вращение обоих деталей вокруг оси У, растяжение
обоих деталей – основания и боковой детали, а так же вращение боковой детали вокруг своей оси
(рис. 30)
Рис. 30
После небольшого промежутка времени, порядка 25 секунд получаем следующее (изображено на
рис. 31)
31
Рис. 31
Теперь посмотрим поворот деталей вокруг оси У. Так же для наглядности воспользуемся
ползунком растяжения основания вверх (рис. 32)
Рис. 32
32
В следующем примере повернем обе детали вокруг оси У примерно на 150° и опустим основание,
чтобы лучше разглядеть отражение в зеркале (рис. 33)
Рис. 33
А теперь повернем боковую деталь вокруг своей оси
Для этого используем соответствующий ползунок (рис. 34)
Рис. 34
33
6. Список литературы.
1) Е.А. Никулин. Компьютерная геометрия и алгоритмы машинной графики. «БХВПетербург», Санкт-Петербург, 2003.
2) Эйнджел Э. Интерактивная компьютерная графика. Вводный курс на базе OpenGL, 2-е
изд. – М.: Издательский лом “Вильямс”, 2001.
3) Майкл Ласло. Вычислительная геометрия и компьютерная графика на C++: Пер. с англ
– М.: Бином, 1997.
4) Ленджел Mathematics for 3D Game Programming and Computer Graphics, 2011, 3-е
издание
34
7. Текст программы.
Файл glwidget.cpp
#include "glwidget.h"
#include <GL/glu.h>
#include <QtGui>
#include <math.h>
double PI =3.141593;
// задаем положение источников света и цвет
GLfloat g_light0_pos[4] = { 0.0f, 15.0f, 0.0f, 1.0f };
GLfloat g_light1_pos[4] = { 0.0f, 15.0f, 0.0f, 1.0f };
GLfloat ambient1[]={0.4f,0.4f,0.4f};
GLdouble eqn1[4] = { 0.0, 0.0, 0.0, 0.0 };
// прямоугольное зеркало
double Zerk[4][3]={
{-2.5, 0, 5},{2.5, 0, 5},{2.5, 0, -3.5},{-2.5, 0, -3.5}
};
// заплатки
double ZapLeft[4][3]={
{-2.5, 0, 5},{-1.5, 0, 5},{-1.5, 0, 0},{-2.5, 0, 0},
};
double ZapRight[4][3]={
{1.5, 0, 5},{2.5, 0.1, 5},{2.5, 0, 0},{1.5, 0, 0},
};
// деталь 1
double Det[8][3]={
// x
y z
{-0.5, 0, 0.5},{0.5, 0, 0.5},{0.5, 0, -0.5},{-0.5, 0, -0.5},
{-0.5, 2, 0.5},{0.5, 2, 0.5},{0.5, 2, -0.5},{-0.5, 2, -0.5}
};
// вспомогательный массив для вращения
double Det11[8][3]={
// x
y z
{-0.5, 0, 0.5},{0.5, 0, 0.5},{0.5, 0, -0.5},{-0.5, 0, -0.5},
{-0.5, 2, 0.5},{0.5, 2, 0.5},{0.5, 2, -0.5},{-0.5, 2, -0.5}
35
};
// деталь 2
double Det2[8][3]={
{0.5, 1.6, 0.5},{1.5, 1.6, 0.5},{1.5, 1.6, -0.5},{0.5, 1.6, -0.5},
{0.5, 2, 0.5},{1.5, 2, 0.5},{1.5, 2, -0.5},{0.5, 2, -0.5}
}; //
double Det21[8][3]={
{0.5, 1.6, 0.5},{1.5, 1.6, 0.5},{1.5, 1.6, -0.5},{0.5, 1.6, -0.5},
{0.5, 2, 0.5},{1.5, 2, 0.5},{1.5, 2, -0.5},{0.5, 2, -0.5}
};
// инициализация таймера
GLWidget::GLWidget(QWidget* parent) : QGLWidget(parent)
{
xRot=-90; yRot=0; zRot=0;rot1=0;TimerSpeed=20;inc1=1;inc2=1;
timer1 = new QTimer(this);//создаем таймер.
QObject::connect(timer1,SIGNAL(timeout()),this,SLOT(timerAction()));
}
void GLWidget::initializeGL()
{
// задаем позиции источников света
glLightfv( GL_LIGHT0, GL_POSITION, g_light0_pos );
glLightfv( GL_LIGHT1, GL_POSITION, g_light1_pos );
glLightfv(GL_LIGHT1,GL_AMBIENT,ambient1);
// цвет фона (белый)
qglClearColor(Qt::white);
glShadeModel( GL_SMOOTH );
//активируем тест глубины
glEnable( GL_DEPTH_TEST );
glFrontFace( GL_CCW );
/*GL_CCW, что соответствует ориентации против часовой стрелки
упорядоченного набора вершин спроецированного полигона.*/
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glEnable( GL_LIGHTING ); // вкл освещение
glLightModeli( GL_FRONT, GL_TRUE );
36
glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glEnable( GL_COLOR_MATERIAL );
glEnable( GL_LIGHT0 ); // вкл первую лампочку
glEnable( GL_LIGHT1 ); // вкл вторую лампочку
}
void GLWidget::resizeGL(int nWidth, int nHeight)
{
// инициализация и задание проекции
glViewport(0, 0, (GLint)nWidth, (GLint)nHeight);
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glOrtho(0, 12, 0, 15, 1.0f, 150.0f);
// кабинетная проекция
GLdouble projOblique[] ={1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
-0.354, -0.354, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0};
glMultMatrixd(projOblique);
updateGL();
}
void GLWidget::paintGL()
{
GLint buffers = GL_NONE;
// получаем текущий буфер цвета
glGetIntegerv( GL_DRAW_BUFFER, &buffers );
glPushMatrix( );
//утановим значение очищения
glClearStencil( 0x0 );
//очищение буфера трафарета
glClear( GL_STENCIL_BUFFER_BIT );
//установим, чтобы всегда выполнялся тест трафарета
glStencilFunc(GL_ALWAYS, 0x1, 0x1 );
37
//устанавливаем режим модифицирования буфера
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
glDrawBuffer( GL_NONE );
// включаем тест трафарета
glEnable( GL_STENCIL_TEST );
// отрисовываем зеркало
glBegin( GL_QUADS );
mirror();
glEnd();
// снова вкл изображение в буффер
glDrawBuffer( (GLenum) buffers );
// делаем буфер шаблона
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
// очистим буфер глубины
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// рисуем изображение в зеркале
glPushMatrix();
/*трансформация glScalef(). переворот*/
glScalef( 1.0f, -1.0f, 1.0f );
// отрисовываем где значение буффера = 1
glStencilFunc( GL_EQUAL, 0x1, 0x1 );
// рисуем сцену
render();
// включаем плоскости отсечения
glDisable( GL_CLIP_PLANE0 );
glPopMatrix( );
// отключаем буффер трафарета
glDisable( GL_STENCIL_TEST );
glDrawBuffer( GL_NONE );
// отрисовываем зеркало в буффер глубины
// для предотвращения объекта зазеркалом
glBegin( GL_QUADS );
mirror();
glEnd();
38
glDrawBuffer( (GLenum) buffers );
// отрисовка сцены без теста трафарета
glPushMatrix( );
render();
glPopMatrix( );
// рисуем контур зеркала
glColor3f( 1.0f, 1.0f, 1.0f );
glBegin( GL_LINE_LOOP );
mirror();
glEnd( );
// зеркальный блеск
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glEnable( GL_BLEND );
glDepthMask( GL_FALSE ); // откл воз-ть изменения буфера глубины
glDepthFunc( GL_LEQUAL );
glDisable( GL_LIGHTING );
glColor4f( 0.7f, 0.7f, 0.99f, .2f ); // цвет зеркала
glBegin( GL_QUADS );
mirror();
glEnd( );
glDisable( GL_BLEND );
glDepthMask( GL_TRUE );
glDepthFunc( GL_LESS );
glEnable( GL_LIGHTING );
glPopMatrix( );
}
// функция отрисовки зеркало вместе с заплатками
void GLWidget::mirror()
{
Zerkalo();
Zaplatki();
}
void GLWidget::render( ) // функция рендера
{
39
// вкл источники света
glLightfv( GL_LIGHT0, GL_POSITION, g_light0_pos );
glLightfv( GL_LIGHT1, GL_POSITION, g_light1_pos );
glPushMatrix();
glColor3f( 1.0f, 1.0f, 1.0f ); // фоновое освещение
raschet(); // расчет кадра
Detal(); // отрисовка 1 детали
Detal2(); // отрисовка 2 детали
glPopMatrix();
}
// отрисовка зеркала
void GLWidget::Zerkalo()
{
glBegin(GL_QUADS);
glVertex3f(Zerk[0][0],Zerk[0][1],Zerk[0][2]);
glVertex3f(Zerk[1][0],Zerk[1][1],Zerk[1][2]);
glVertex3f(Zerk[2][0],Zerk[2][1],Zerk[2][2]);
glVertex3f(Zerk[3][0],Zerk[3][1],Zerk[3][2]);
glEnd();
}
//отрисовка заплаток
void GLWidget::Zaplatki()
{
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
glVertex3f(ZapLeft[0][0],ZapLeft[0][1],ZapLeft[0][2]);
glVertex3f(ZapLeft[1][0],ZapLeft[1][1],ZapLeft[1][2]);
glVertex3f(ZapLeft[2][0],ZapLeft[2][1],ZapLeft[2][2]);
glVertex3f(ZapLeft[3][0],ZapLeft[3][1],ZapLeft[3][2]);
glEnd();
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
glVertex3f(ZapRight[0][0],ZapRight[0][1],ZapRight[0][2]);
glVertex3f(ZapRight[1][0],ZapRight[1][1],ZapRight[1][2]);
40
glVertex3f(ZapRight[2][0],ZapRight[2][1],ZapRight[2][2]);
glVertex3f(ZapRight[3][0],ZapRight[3][1],ZapRight[3][2]);
glEnd();
// отрисовка контура зеркала
// торец впереди
glColor3f(0.7, 0.7, 1);
glBegin(GL_LINES); // рисовать линию
glVertex3f(-1.5,0.0,5); //Начальная точка
glVertex3f(1.5,0.0,5); //Конечная точка
glEnd;
// справа (идем против часовой стрелки)
glBegin(GL_LINES); //
glVertex3f(1.5,0.0,5); //Начальная точка
glVertex3f(1.5,0.0,0); //Конечная точка
glEnd;
glBegin(GL_LINES); //
glVertex3f(1.5,0.1,0); //Начальная точка
glVertex3f(2.5,0.1,0); //Конечная точка
glEnd;
glBegin(GL_LINES); //
glVertex3f(2.5,0.0,0); //Начальная точка
glVertex3f(2.5,0.0,-3.5); //Конечная точка
glEnd;
glBegin(GL_LINES); //
glVertex3f(2.5,0.0,-3.5); //Начальная точка
glVertex3f(-2.5,0.0,-3.5); //Конечная точка
glEnd;
glBegin(GL_LINES); //
glVertex3f(-2.5,0.0,-3.5); //Начальная точка
glVertex3f(-2.55,0.0,0); //Конечная точка
glEnd;
glBegin(GL_LINES); //
glVertex3f(-2.4,0.1,-0.05); //Начальная точка
glVertex3f(-1.5,0.05,-0.05); //Конечная точка
41
glEnd;
glBegin(GL_LINES); //
glVertex3f(-1.5,0.0,0); //Начальная точка
glVertex3f(-1.5,0.0,5); //Конечная точка
glEnd;
// отрисовка осей координат
glDisable(GL_DEPTH_TEST );
glLineWidth( 3.0f );
// ось x
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_LINES); //
glVertex3f(0,0,0); //Начальная точка
glVertex3f(4,0.14,0); //Конечная точка
glEnd;
//ось y
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_LINES); //
glVertex3f(0,0.0,0); //Начальная точка
glVertex3f(0,7,0); //Конечная точка
glEnd;
//ось z
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_LINES); //
glVertex3f(0,0.0,0); //Начальная точка
glVertex3f(0,0,7); //Конечная точка
glEnd;
glEnable( GL_DEPTH_TEST );
}
// отрисовка 1 детали - основания
void GLWidget::Detal() // ножка (слева)
{
// янтарный
glColor3f(1, 0.749, 0);
glBegin(GL_POLYGON);
42
glVertex3f(Det[0][0],Det[0][1],Det[0][2]);
glVertex3f(Det[4][0],Det[4][1],Det[4][2]);
glVertex3f(Det[7][0],Det[7][1],Det[7][2]);
glVertex3f(Det[3][0],Det[3][1],Det[3][2]);
glEnd();
//
glColor3f(0.2, 0.9, 0.3);// задняя грань
glBegin(GL_POLYGON);
glVertex3f(Det[3][0],Det[3][1],Det[3][2]);
glVertex3f(Det[7][0],Det[7][1],Det[7][2]);
glVertex3f(Det[6][0],Det[6][1],Det[6][2]);
glVertex3f(Det[2][0],Det[2][1],Det[2][2]);
glEnd();
// голубой
glColor3f(0, 0.749, 1);
glBegin(GL_POLYGON);
glVertex3f(Det[0][0],Det[0][1],Det[0][2]);
glVertex3f(Det[3][0],Det[3][1],Det[3][2]);
glVertex3f(Det[2][0],Det[2][1],Det[2][2]);
glVertex3f(Det[1][0],Det[1][1],Det[1][2]);
glEnd();
// золотой
glColor3f(1, 0.843, 0);
glBegin(GL_POLYGON);
glVertex3f(Det[1][0],Det[1][1],Det[1][2]);
glVertex3f(Det[5][0],Det[5][1],Det[5][2]);
glVertex3f(Det[6][0],Det[6][1],Det[6][2]);
glVertex3f(Det[2][0],Det[2][1],Det[2][2]);
glEnd();
// зеленое море
glColor3f(0.18, 0.545, 0.341); // передняя грань
glBegin(GL_POLYGON);
glVertex3f(Det[0][0],Det[0][1],Det[0][2]);
glVertex3f(Det[4][0],Det[4][1],Det[4][2]);
43
glVertex3f(Det[5][0],Det[5][1],Det[5][2]);
glVertex3f(Det[1][0],Det[1][1],Det[1][2]);
glEnd();
// серый шифер
glColor3f(0.439, 0.502, 0.565);
glBegin(GL_POLYGON);
glVertex3f(Det[4][0],Det[4][1],Det[4][2]);
glVertex3f(Det[7][0],Det[7][1],Det[7][2]);
glVertex3f(Det[6][0],Det[6][1],Det[6][2]);
glVertex3f(Det[5][0],Det[5][1],Det[5][2]);
glEnd();
}
// отрисовка боковой детали
void GLWidget::Detal2() // деталь справа
{
// томатный
glColor3f(1.0, 0.388, 0.278);
glBegin(GL_POLYGON);
glVertex3f(Det2[0][0],Det2[0][1],Det2[0][2]);
glVertex3f(Det2[4][0],Det2[4][1],Det2[4][2]);
glVertex3f(Det2[7][0],Det2[7][1],Det2[7][2]);
glVertex3f(Det2[3][0],Det2[3][1],Det2[3][2]);
glEnd();
// ванильный
glColor3f(0.953, 0.898, 0.67);
glBegin(GL_POLYGON);
glVertex3f(Det2[3][0],Det2[3][1],Det2[3][2]);
glVertex3f(Det2[7][0],Det2[7][1],Det2[7][2]);
glVertex3f(Det2[6][0],Det2[6][1],Det2[6][2]);
glVertex3f(Det2[2][0],Det2[2][1],Det2[2][2]);
glEnd();
// голубой
glColor3f(0, 0.749, 1);
glBegin(GL_POLYGON);
44
glVertex3f(Det2[0][0],Det2[0][1],Det2[0][2]);
glVertex3f(Det2[3][0],Det2[3][1],Det2[3][2]);
glVertex3f(Det2[2][0],Det2[2][1],Det2[2][2]);
glVertex3f(Det2[1][0],Det2[1][1],Det2[1][2]);
glEnd();
// малиновый
glColor3f(1,0 , 1);
glBegin(GL_POLYGON);
glVertex3f(Det2[1][0],Det2[1][1],Det2[1][2]);
glVertex3f(Det2[5][0],Det2[5][1],Det2[5][2]);
glVertex3f(Det2[6][0],Det2[6][1],Det2[6][2]);
glVertex3f(Det2[2][0],Det2[2][1],Det2[2][2]);
glEnd();
// передняя грань
glColor3f(0.99, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex3f(Det2[0][0],Det2[0][1],Det2[0][2]);
glVertex3f(Det2[4][0],Det2[4][1],Det2[4][2]);
glVertex3f(Det2[5][0],Det2[5][1],Det2[5][2]);
glVertex3f(Det2[1][0],Det2[1][1],Det2[1][2]);
glEnd();
// ярко-зеленый
glColor3f(0.4, 1, 0);
glBegin(GL_POLYGON);
glVertex3f(Det2[4][0],Det2[4][1],Det2[4][2]);
glVertex3f(Det2[7][0],Det2[7][1],Det2[7][2]);
glVertex3f(Det2[6][0],Det2[6][1],Det2[6][2]);
glVertex3f(Det2[5][0],Det2[5][1],Det2[5][2]);
glEnd();
}
// расчет положения всех объектов перед отрисовкой
void GLWidget::raschet() {
// расчет кадра
45
// считываем значение с 1го слайдера
float tmp=slider1;
if(len1<50)
tmp+=len1;
if(len1>=50)
{ len1--;
tmp+=len1;
}
tmp=tmp/100;
// растяжение 1 детали
Det[4][1]=2+tmp;
Det[5][1]=2+tmp;
Det[6][1]=2+tmp;
Det[7][1]=2+tmp;
Det11[4][1]=2+tmp;
Det11[5][1]=2+tmp;
Det11[6][1]=2+tmp;
Det11[7][1]=2+tmp;
// растяжение 2 детали
Det2[0][1]=1.6+tmp;
Det2[1][1]=1.6+tmp;
Det2[2][1]=1.6+tmp;
Det2[3][1]=1.6+tmp;
Det2[4][1]=2+tmp;
Det2[5][1]=2+tmp;
Det2[6][1]=2+tmp;
Det2[7][1]=2+tmp;
Det21[0][1]=1.6+tmp;
Det21[1][1]=1.6+tmp;
Det21[2][1]=1.6+tmp;
Det21[3][1]=1.6+tmp;
Det21[4][1]=2+tmp;
Det21[5][1]=2+tmp;
46
Det21[6][1]=2+tmp;
Det21[7][1]=2+tmp;
// теперь со второго слайдера и изменяем координаты
tmp=slider2;
// аналогично для 2 слайдера
// он отвечает за растяжение 2 детали
if(len2<50)
tmp+=len1;
if(len2>=50)
{ len2--;
tmp+=len2;
}
tmp=tmp/100;
Det2[1][0]=1.5+tmp;
Det2[2][0]=1.5+tmp;
Det2[5][0]=1.5+tmp;
Det2[6][0]=1.5+tmp;
Det21[1][0]=1.5+tmp;
Det21[2][0]=1.5+tmp;
Det21[5][0]=1.5+tmp;
Det21[6][0]=1.5+tmp;
tmp=slider3; // с 3го слайдера
// реализация вращения детали 2 вокруг
// своей оси
float centr;
float TempY,TempZ,y,z; //
//переводим градусы в радианы.
tmp=(PI/180)*tmp;
tmp+=rot1; //
for(int j=0; j<8; j++)
{
//тут возьмем val - это угол в градусах.
TempY=Det21[j][1],TempZ=Det21[j][2];//
centr=Det21[0][1]+0.2; // половина высоты детальки 2
47
TempY-=centr;//
y=TempY,z=TempZ;//
y=TempY*cos(tmp)+-TempZ*sin(tmp);//вращение
z=TempY*sin(tmp)+TempZ*cos(tmp);
y+=centr;//
Det2[j][1]=y,Det2[j][2]=z;
}
// вращение обоих деталей
tmp=slider4;
tmp+=rot2;
tmp=tmp*(-1);
glRotatef(tmp, 0, 1, 0);
}
// эти функци считывают значения с формы
// со всех четырех слайдеров и передают эти
// значения если они были изменены
// растяжение детали 1
void GLWidget::changeSlider(int val){
slider1=val;
updateGL();
}
// растяжение детали 2
void GLWidget::changeSlider2(int val){
slider2=val;
updateGL();
}
// вращение обоих деталей Det1 и Det2
void GLWidget::changeHSlider(int val){
slider3=val;
updateGL();
}
// вращение одной детали Det2 вокруг своей оси
void GLWidget::changeHSlider2(int val){
48
slider4=val;
updateGL();
}
void GLWidget::TimerSlot()
{
}
void GLWidget::timerAction()//то есть если таймер включен - эта функция
//постоянно вызывается, каждые 20 мc
{
float tmp;
tmp = ((float)inc1)/10;
if(timerflag==1) // если нажат 1 спиннер
{
rot1+=tmp;
rot2+=inc2;
}
if(timerflag==3) // если нажат 3 спиннер
{
rot1-=tmp;
rot2-=inc2;
}
len1+=1; // ножка растет
len2+=1; // дет 2 растет
updateGL();
}
// запускается если нажали на 1 радиокнопка
// радиокнопка - это кнопка ввиде черной точки
// на форме
void GLWidget::button1(bool sig)
{
if(sig) {
timerflag=1;
timer1->start(TimerSpeed);
49
}
else timer1->stop();
}
// запускается если нажали на 2 радиокнопка
void GLWidget::button2(bool sig)
{
if(sig) {
timerflag=2;
timer1->stop();
}
}
// запускается если нажали на 3 радиокнопка
void GLWidget::button3(bool sig)
{
if(sig) {
timerflag=3;
timer1->start(TimerSpeed);
}
else timer1->stop();
}
// отлавливаем событие - считываем значение спиннеров
// для увеличения/уменьшения скорости вращения
void GLWidget::spin1(int var)
{
inc1=var;
}
void GLWidget::spin2(int var)
{
inc2=var;
}
50
Файл glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QGLWidget>
#include <QTimer> //
#include <QObject>
class GLWidget : public QGLWidget
{
Q_OBJECT
private:
GLfloat xRot;
GLfloat yRot;
GLfloat zRot;
QTimer *timer1;
int timerflag;
protected:
void initializeGL();
void resizeGL(int nWidth, int nHeight);
void paintGL();
public:
//объявляем прототипы функций которые
// будут использоваться в glwidget.cpp
void Zerkalo(); // отрисовка зеркала
void Zaplatki(); // отрисовка заплаток
void Detal(); // отрисовка 1 детали
void Detal2();// отрисовка 2 детали
void render(); // функция рндера
void mirror(); // отрисовка зеркала
void raschet(); // расчет кадра
void TimerSlot(); // слот под таймер
float rot1; // для вращения обоих деталей
float rot2; // для вращения одной правой детали
float len1; // для растяжения ножки
51
float len2; // для растяжения детали 2
// глобальные переменные
int TimerSpeed; //интервал считывания с таймера
int inc1;
int inc2;
// считанные значения со слайдеров
float slider1;
float slider2;
float slider3;
float slider4;
explicit GLWidget(QWidget *parent = 0);
public slots:
// слоты под слайдеры
void changeSlider(int val); //слоты для сигналов
void changeSlider2(int val);
void changeHSlider(int val);
void changeHSlider2(int val);
void timerAction();
// 3 радиокноки
void button1(bool sig);
void button2(bool sig);
void button3(bool sig);
// 2 спинера
void spin1(int var);
void spin2(int var);
};
#endif // GLWIDGET_H
52
Related documents
Download