Приём заказов:
Круглосуточно
Москва
ул. Никольская, д. 10.
Ежедневно 8:00–20:00
Звонок бесплатный

Альфа-смешение: алгоритм выполнения

Диплом777
Email: info@diplom777.ru
Phone: +7 (800) 707-84-52
Url:
Логотип сайта компании Диплом777
Никольская 10
Москва, RU 109012
Содержание

2

Оглавление

  • Введение
  • 1. Постановка задачи
  • 2. Описание используемых алгоритмов
  • 2.1 Преобразование координат
  • 2.2 Алгоритм удаления невидимых граней
  • 2.3 Определение лицевых и не лицевых граней с помощью вектора нормали
  • 2.4 Закраска трехмерного объекта с использованием простой модели освещения
  • 3. Описание программы
  • 3.1 Введенные типы данных и их предназначение
  • 3.2 Основные переменные и их предназначение
  • 3.3 Описание основных процедур и функций
  • 3.4 Алгоритм взаимодействия процедур и функций
  • 4. Тестирование программы
  • 4.1 Описание интерфейса
  • 4.2 Руководство по использованию программы
  • 4.3 Результат
  • Заключение
  • Список использованных источников
  • Приложения

Введение

Компьютерная графика — это область деятельности, в которой компьютеры используются в качестве инструмента создания изображений и обработки визуальной информации, полученной из реального мира. По способам задания изображений графику можно разделить на две основные категории: двумерная графика и трехмерная. Двумерная графика в свою очередь делится на векторную, растровую и фрактальную графику.

Векторная графика представляет изображение как набор геометрических примитивов. Обычно в качестве них выбираются точки, прямые, окружности, прямоугольники, а также как общий случай, кривые некоторого порядка. Объектам присваиваются некоторые атрибуты, например, толщина линий, цвет заполнения. Рисунок хранится как набор координат, векторов и других чисел, характеризующих набор примитивов. Изображение в векторном формате даёт простор для редактирования. Изображение может без потерь масштабироваться, поворачиваться, деформироваться и имитировать трехмерную графику.

Растровая графика всегда оперирует двумерным массивом (матрицей) пикселей. Каждому пикселю сопоставляется значение — яркости, цвета, прозрачности — или комбинация этих значений. Растровый образ имеет некоторое число строк и столбцов. Без особых потерь растровые изображения можно только лишь уменьшать, хотя некоторые детали изображения тогда исчезнут навсегда, что иначе в векторном представлении, при увеличении наблюдается пиксельная сетка. В растровом виде представимо любое изображение, однако этот способ хранения имеет свои недостатки: больший объём памяти, необходимый для работы с изображениями, потери при редактировании.

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

Трёхмерная графика оперирует с объектами в трёхмерном пространстве. Обычно результаты представляют собой плоскую картинку, проекцию. В трёхмерной компьютерной графике все объекты обычно представляются как набор поверхностей или частиц. Минимальную поверхность называют полигоном. В качестве полигона обычно выбирают треугольники. Любой полигон можно представить в виде набора из координат его вершин. Так, у треугольника будет 3 вершины. Координаты каждой вершины представляют собой вектор (x, y, z). Умножив вектор на соответствующую матрицу (поворота/сдвига/масштабирования), мы получим новый вектор. Сделав такое преобразование со всеми вершинами полигона, получим новый полигон, а преобразовав все полигоны, получим новый объект. В данной работе предстоит создать трехмерную сцену посредством Microsoft Visual Studio, а конкретнее с помощью программирования на языке высокого уровня C#.

альфа смешение алгоритм графика

1. Постановка задачи

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

Создаваемая сцена будет состоять из двух предметов: трехмерной модели граненого стакана и модель куба. Стакан будет исполнять роль прозрачного предмета, а куб — непрозрачного. Стакан состоит из 20 боковых граней и грани основания стакана, все грани должны быть прозрачными. Куб — стандартная геометрическая модель, имеющая 6 граней, выполненных в форме правильных прямоугольников, квадратов. Данная сцена должна отражать эффект прозрачности и эффект освещения.

Рассмотрим координаты вершин модели стакана (таблица 1).

Таблица 1 — Координаты вершин модели стакана

Номер вершины

Координата Х

Координата Y

Координата Z

0

-50

-50

0

1

-48

-50

-15

2

-40

-50

-30

3

-28

-50

-42

4

-15

-50

-48

5

0

-50

-50

6

15

-50

-48

7

28

-50

-42

8

40

-50

-30

9

48

-50

-15

10

50

-50

0

11

48

-50

15

12

40

-50

30

13

28

-50

42

14

15

-50

48

15

0

-50

50

16

-15

-50

48

17

-28

-50

42

18

-40

-50

30

19

-48

-50

15

20

-60

100

0

21

-58

100

-19

22

-48

100

-36

23

-35

100

-50

24

-18

100

-58

25

0

100

-60

26

18

100

-58

27

35

100

-50

28

48

100

-36

29

58

100

-19

30

60

100

0

31

58

100

19

32

48

100

36

33

35

100

50

34

18

100

58

35

0

100

60

36

-18

100

58

37

-35

100

50

38

-48

100

36

39

-58

100

19

В данном случае стакан представлен в цилиндра, с более широким верхом и менее широким основанием. Вершины от 0 по 19 составляют основание стакана, с помощью этого диапазона вершин строится грань основания стакана. Вершины от 20 до 39 составляют верхнюю грань стакана, но учитываю специфичную форму сосуда, грань из этих вершин строить не будем. В данном стакане предусмотрено 20 боковых граней, каждая грань строится по принципу соединения вершин в один полигон, например: одна из боковых граней имеет вершины 0, 20, 21, 1 (цикл: i, i + 20, i + 21, i + 1).

Рассмотрим координаты вершин куба с ребром 50 (таблица 2).

Таблица 2 — Координаты вершин куба

Номер вершины

Координата Х

Координата Y

Координата Z

Основание куба

40

-60

-50

-65

41

-60

-50

-115

42

-10

-50

-115

43

-10

-50

-65

Верхняя грань

44

-60

0

-65

45

-60

0

-115

46

-10

0

-115

47

-10

0

-65

Модель куба собирается по аналогичному принципу моделирования стакана, сначала формируем грань основания, потом верхнюю грань, а потом создаем боковые грани. Каждая грань куба задается 4 вершинами. Так как эти две модели образуют единую сцену, то координаты отсчитываются по единой системе, а также номера вершин задаются по порядку. В дальнейшем к разрабатываемым объектам сцены будет применено закрашивание граней, введение прозрачности и создание освещения всей сцены.

2. Описание используемых алгоритмов

2.1 Преобразование координат

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

// Видовые координаты:

View [i]. x = 20 * (int) Math. Round (Vertex [i]. x * (-Math. Sin (teta)) + Vertex [i]. y * Math. Cos (teta));

View [i]. y = 20 * (int) Math. Round (Vertex [i]. x * (-Math. Cos (phi) * Math. Cos (teta)) — Vertex [i]. y * (Math. Cos (phi) * Math. Sin (teta)) + Vertex [i]. z * Math. Sin (phi));

View [i]. z = — 20 * (int) Math. Round (Vertex [i]. x * (-Math. Sin (phi) * Math. Cos (teta)) — Vertex [i]. y * (Math. Sin (phi) * Math. Sin (teta)) — Vertex [i]. z * (Math. Cos (phi)) + R0);

// Перспективное преобразование

Perspective [i]. X = pictureBox1. Width / 2 + (int) Math. Round (D0 * (View [i]. x / View [i]. z));

Perspective [i]. Y = pictureBox1. Height / 2 + (int) Math. Round (D0 * (View [i]. y / View [i]. z));

// Получение экранных координат

Scrn [i]. X = (int) Perspective [i]. X + pictureBox1. Width / 2;

Scrn [i]. Y = (int) Perspective [i]. Y + pictureBox1. Height / 2;

Рисунок 1 — Получение экранных координат

Более подробный алгоритм можно показан в приложении А, где указан листинг программы.

2.2 Алгоритм удаления невидимых граней

Так как в двумерном пространстве строится трехмерная модель, то из-за этого возникает эффект перекрытия граней, то есть грани отрисовываются в одном и том же месте. Чтобы этого избежать, следует показывать только лицевые грани, а невидимые грани удалять. Для этого воспользуемся алгоритмом Ньюла-Ньюэла-Санча, иначе называющимся алгоритм художника. Идея алгоритма художника, состоит в том, что, используя упорядоченность граней по глубине, выводится с закраской грани, начиная с дальних граней. При этом грани, которые выводятся позже, закрывают собою невидимые части более дальних граней. Часть этого метода работает в пространстве объекта, а часть в пространстве изображения. Он также работает для параллельной проекции, то есть с учетом того, что произведено перспективное преобразование.

Метод состоит из трех основных шагов:

1. упорядочение всех полигонов в соответствии с их наибольшими z-координатами;

2. разрешение всех неопределенностей, которые возникают при перекрытии z-оболочек многоугольников;

3. прорисовка каждого полигона, производимая в порядке уменьшения их наибольшей z-координаты.

Для нахождения среднего значения по координате Z, необходимо сложить значения координаты Z каждой из вершин полигона и поделить эту сумму на количество вершин. Это и будет среднее значение координаты Z полигона. Далее создадим массив, содержащий все средние значения, и попарно будем сравнивать их. Если данное значение будет меньше предыдущего, то значит, что полигон находится ближе к наблюдателю, а значит, будет рисоваться в последнюю очередь. Таким образом, грани будут появляться в порядке уменьшения значения Z.

Данный алгоритм отличается чрезмерной простотой реализации, и, как следствие, недостаточно эффективен. В самом деле, если трехмерная сцена состоит из большого количества объектов, в среднем половина полигонов будет не видна в текущем кадре. А значит, их отображение на экране и не требуется. Недостаток «алгоритма художника» состоит в том, что отображаются все полигоны.

2.3 Определение лицевых и не лицевых граней с помощью вектора нормали

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

Идея алгоритма состоит в том, что по углу между направлением взгляда и нормалью к поверхности можно определить расположение граней, лицевая она или же нет. Нормаль к поверхности находится с помощью трех точек, так как через любые три точки можно провести плоскость. Координаты вектора нормали следующие: N (Nx, Ny, Nz). Три точки поверхности имеют следующие координаты: A (Ax, Ay, Az), B (Bx, By, Bz), C (Cx, Cy, Cz). Уравнение плоскости задается в виде:

Nx * X + Ny * Y + Nz * Z + d = 0.

В результате получим следующую систему уравнений:

Преобразуем данную систему в матрицу размером 4Х4.

Найдем определитель данной матрицы по первой строке. Так как данная система уравнений имеет нетривиальное решение, то определитель данной матрицы равен 0. В результате получаем:

Nx = , Ny = , Nz = .

Значение d для определения координат вектора не нужно. Итак, координаты вектора нормали имеют вид:

Nx = Ay (Bz — Cz) + By (Cz — Az) + Cy (Az — Bz);

Ny = Az (Bx — Cx) + Bz (Cx — Ax) + Cz (Ax — Bx);

Nz = Ax (By — Cy) + Bx (Cy — Ay) + Cx (Ay — By).

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

Для реализации данного алгоритма нам потребуется только координата Nz. Значение получим по формуле: Nz = Nz/ (Корень (Nx*Nx + Ny*Ny + Nz*Nz)).

Если Nz > 0, то грань не видна, если Nz < 0, то грань видна. Так как у нас на сцене имеется прозрачный объект, у которого должны прорисовываться все грани, то выполним оба условия на проверку видимости граней.

2.4 Закраска трехмерного объекта с использованием простой модели освещения

Так как расчет нормалей в нашей сцене мы уже реализовали, то добавить освещение к нашим объектам уже не составит больших проблем. Помимо освещенности будем одновременно закрашивать получившиеся поверхности объектов. Идея алгоритма состоит в следующем: будем закрашивать каждую грань объекта, умножив значение заданного цветового канала (A, R, G, B) на значение координаты Z вектора нормали. Цвет и настройки прозрачности у объектов разные, но система цветов одна, поэтому зададим предварительно значения альфа-канала для граней куба полностью непрозрачными — 255, а для граней стакана значения определим следующие: 80 для боковых граней, и 100 для основания стакана. Чем больше значение Nz будет приближаться к единице, тем меньше будет угол между нормалью и лучом света и тем соответственно больше будет интенсивность цвета.

Для закраски полигонов воспользуемся стандартной функцией закраски — FillPolygon. Данная функция заполняет внутреннюю часть полигона, определяемого массивом точек, один из двух параметров данной функции отвечает за цвет закраски. В итоге получаем простейшую, но эффектную систему освещения.

3. Описание программы

3.1 Введенные типы данных и их предназначение

В таблице 3 указаны все типы данных, используемые в программе.

Таблица 3 — Типы данных, используемые в программе

Тип данных

Предназначение

int

Используются для целочисленных вычислений

double

Используются для точных вычислений

bool

Используется для логических преобразований и операций

int []

Используется в качестве массивов, хранящих целые значения (номера полигонов, вершин)

double []

Используется в качестве массивов, хранящих вещественные числа, например массив, хранящий средние значения координаты Z

Структуры

Используются для описания координат вершин

Классы

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

3.2 Основные переменные и их предназначение

Основные переменные и их предназначение рассмотрены в таблице 4.

Таблица 4 — Основные переменные и их функции

Переменная

Тип данных

Функция

b

Класс Bitmap

Используется для работы с изображениями, определяемыми данными пикселей

g

Класс Graphics

Инкапсулирует поверхность рисования

image

Класс Image

Позволяет загрузить фоновое изображение для компонентов программы

Vertex

Point3D []

Определяет структуру координат вершин

View

Point3D []

Видовые координаты вершин

Scrn

Point []

Массив экранных координат

Perspective

Point []

Массив перспективных координат

NZ

double []

Массив, содержащий все значения координаты Z вектора нормали

Order

int []

Массив, задающий порядок отображения граней, порядковые номера полигонов

D

double []

Массив, хранящий среднее значение по координате Z плоскости

Teta, Phi

double

Переменные, хранящие значения углов

nx, ny, nz

double

Координаты вектора нормали

R0

int

Коэффициент масштабирования сцены

D0

int

Коэффициент перспективы сцены

mousePress

bool

Логическая переменная, отслеживающая нажатие на кнопку мыши

3.3 Описание основных процедур и функций

Рассмотрим основные функции и процедуры.

1. Процедура «ПостроитьСцену». Главная процедура, в которой происходит преобразование координат, создание полигонов, вычисление средних значений координаты Z, закрашивание поверхностей и создание модели освещения.

2. Функция «СортировкаГраней». Сортирует грани по глубине и заполняет массив упорядоченных граней для отображения.

3. Процедура «ВычислениеНормали». Вычисляет нормаль к поверхности и заполняет массив с координатой Z этой нормали.

4. Процедура «ПоказатьСцену». Данная процедура используется для перерисовки сцены.

5. Функция «Flag». Вспомогательный метод для реализации функции «СортировкаГраней». Определяет грани для сравнения.

3.4 Алгоритм взаимодействия процедур и функций

Рассмотрим алгоритм взаимодействия вышеописанных процедур и функций. Процедура «ПоказатьСцену» вызывается каждый раз при изменении изображения сцены, данная процедура содержит в своем теле основную процедуру программы «ПостроениеСцены». Процедура «ПостроениеСцены» помимо своих наборов действий состоит из функции: «СортировкаГраней» и функции «ВычислениеНормали». Функция «СортировкаГраней» включает в себе метод «Flag». Условная схема взаимодействия процедур и функций представлена на рисунке 2.

Рисунок 2 — Условная схема взаимодействия процедур и функций

4. Тестирование программы

4.1 Описание интерфейса

При запуске программы пользователь сразу видит сцену: стакан и куб перед ним (рисунок 3).

Рисунок 3 — Интерфейс программы

Программа имеет интуитивно понятный интерфейс, то, что было заявлено, то и получилось — два объекта: прозрачный объект (стакан) и непрозрачный (куб). А также надпись на самой форме «С# Alpha Blending» говорит о теме работы, альфа смешении. Окно приложения нельзя масштабировать, можно только закрывать и сворачивать. Заголовок окна приложения также дает понять о тематике приложения.

4.2 Руководство по использованию программы

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

Пользователь может изучать данные предметы путем их вращения или масштабирования. Обзор сцены выполняется либо с помощью мыши, либо с помощью стрелок клавиатуры. Система управления представлена в таблице 5.

Таблица 5 — Управления сценой

Элемент управления

Действие

Мышь

Зажатая ЛКМ/ПКМ

Обзор сцены со всех ракурсов

Вращение колесика

Масштабирование +/-

Нажатие ПКМ

Привидение к сходному размеру

Клавиатура

Вверх

Вращение сцены вертикально вверх

Вниз

Вращение сцены вертикально вниз

Влево

Вращение сцены в левую сторону

Вправо

Вращение сцены в правую сторону

Данное приложение работает без аварийных завершений, не вызывает исключений, не перегружает систему, не помечается антивирусными системами как потенциально опасное программное обеспечение.

Минимальные системные требования:

— операционная система Windows XP, Vista, 7 (совместимость сWindows 8 неизвестна);

— процессор с тактовой частотой 1 Ггц или выше;

— ОЗУ 512 Мб или выше;

— Видеопамять 128 Мб или выше;

— Клавиатура, мышь.

4.3 Результат

В результате выполнения программы, получаем следующие результаты, показанные на рисунках 4-8.

Рисунок 4 — Часть стакана скрыта непрозрачным кубом

Рисунок 5 — Видимость куба сквозь прозрачный стакан

Рисунок 6 — Куб находится позади стакана

Рисунок 7 — Пример масштабирования на уменьшение сцены

Рисунок 8 — Пример масштабирования на увеличение сцены

Заключение

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

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

Итогом работы стало приложение «C# Alpha Blending», наглядно показывающий принцип работы альфа-смешения, а также доказывающий возможность создания трехмерной сцены посредством программирования на языке высокого уровня C#.

Список использованных источников

1 Кузнецова Е.А., Лукошков М.М., Майоров И.С. Методические указания к выполнению лабораторных работ по дисциплине «Компьютерная геометрия и графика». [Текст] — Издательство: » Архангельский Государственный Технический Университет», г. Архангельск, 2006 г. — стр.36;

2 Википедия — Свободная Энциклопедия. [Электронный Ресурс] — Режим доступа: http://ru. wikipedia.org/, свободный;

3 Иванов В.П., Батраков А.С. Трехмерная компьютерная графика. [Текст] — Издательство: «Радио и связь», ISBN 5-256-01204-5, г. Москва, 1995 г. — стр.224;

4 Роджерс Д., Адамс Дж. Математические основы машинной графики. [Текст] — Издательство: «Мир», перевод с английского, ISBN 5-03-002143-4, г. Москва, 2001 г. — стр.604.

Приложения

Приложение А

Листинг основных компонентов

Листинг модуля Form1. cs

using System;

using System. Collections. Generic;

using System.componentModel;

using System. Data;

using System. Drawing;

using System. Linq;

using System. Text;

using System. Windows. Forms;

namespace WindowsFormsApplication1

{

// Структура координат

public struct Point3D

{

public double x;

public double y;

public double z;

public Point3D (double x, double y, double z)

{

this. x = x;

this. y = y;

this. z = z;

}

}

public partial class Form1: Form

{

Bitmap b;

Graphics g;

Image image;

Point3D [] Vertex = new Point3D [48]; // Массив координат вершин (x, y, z)

Point3D [] View = new Point3D [48]; // Массив видовых координат

Point [] Scrn = new Point [48]; // Массив экранных координат

Point [] Perspective = new Point [48]; // Массив перспективных координат

double [] NZ = new double [27]; // Массив нормалей для освещения

int [] Order = new int [27]; // Массив порядка отображения видимых граней

double [] D = new double [27]; // Массив, содержащий средние значения Z-граней

double Teta = Math. PI / 180; // Угол тета = 45 градусов

double Phi = Math. PI / 180; // Угол фи = 45 градусов

bool mousePress = false;

double sX = 0;

double sY = 0;

double nx, ny, nz;

int D0 = 500;

int R0 = 300;

public Form1 ()

{

InitializeComponent ();

this. MouseWheel += new MouseEventHandler (Form1_MouseWheel);

b = new Bitmap («Background. jpg»); // Экземпляр класса Bitmap

image = Image. FromFile («Background. jpg»); // Загружаем фоновое изображение

g = Graphics. FromImage (b); // Задаем поверхность для рисования

}

// Построение объектов сцены

void ПостроитьСцену ()

{

g. DrawImage (image, 0, 0);

for (int i = 0; i < 48; i++)

{

// Преобразование из мировых в видовые координаты

View [i]. x = 20 * (int) Math. Round (Vertex [i]. x * (-Math. Sin (Teta)) + Vertex [i]. y * Math. Cos (Teta));

View [i]. y = 20 * (int) Math. Round (Vertex [i]. x * (-Math. Cos (Phi) * Math. Cos (Teta)) — Vertex [i]. y * (Math. Cos (Phi) * Math. Sin (Teta)) + Vertex [i]. z * Math. Sin (Phi));

View [i]. z = — 20 * (int) Math. Round (Vertex [i]. x * (-Math. Sin (Phi) * Math. Cos (Teta)) — Vertex [i]. y * (Math. Sin (Phi) * Math. Sin (Teta)) — Vertex [i]. z * (Math. Cos (Phi)) + R0);

// Перспективное преобразование видовых координат

Perspective [i]. X = pictureBox1. Width / 2 + (int) Math. Round (D0 * (View [i]. x / View [i]. z));

Perspective [i]. Y = pictureBox1. Height / 2 + (int) Math. Round (D0 * (View [i]. y / View [i]. z));

if (View [i]. z == 0)

{

Perspective [i]. X = 0;

Perspective [i]. Y = 0;

}

else

{

Perspective [i]. X = (int) (Math. Round (D0 * (View [i]. x / View [i]. z)));

Perspective [i]. Y = (int) (Math. Round (D0 * (View [i]. y / View [i]. z)));

}

// Получение экранных координат

Scrn [i]. X = (int) Perspective [i]. X + pictureBox1. Width / 2;

Scrn [i]. Y = (int) Perspective [i]. Y + pictureBox1. Height / 2;

}

// Находим среднее значение по координате Z каждой грани

for (int i = 0; i < 19; i++)

{

D [i] = (View [i]. z + View [i + 20]. z + View [i + 21]. z + View [i + 1]. z) / 4;

}

// Конечная грань стакана

D [19] = (View [19]. z + View [39]. z + View [20]. z + View [0]. z) / 4;

// Грань — основание стакана

for (int i = 0; i < 19; i++)

{

D [20] += View [i]. z / 20;

}

// Грани ядовито-желтого куба

D [21] = (View [40]. z + View [41]. z + View [42]. z + View [43]. z) / 4;

D [22] = (View [40]. z + View [44]. z + View [47]. z + View [43]. z) / 4;

D [23] = (View [43]. z + View [47]. z + View [46]. z + View [42]. z) / 4;

D [24] = (View [42]. z + View [46]. z + View [45]. z + View [41]. z) / 4;

D [25] = (View [41]. z + View [45]. z + View [44]. z + View [40]. z) / 4;

D [26] = (View [44]. z + View [45]. z + View [46]. z + View [47]. z) / 4;

// Заполняем список граней

List<Point [] > Plane = new List<Point [] > ();

for (int i = 0; i < 19; i++)

{

Plane. Add (new Point [] { Scrn [i], Scrn [20 + i], Scrn [21 + i], Scrn [1 + i] });

}

Plane. Add (new Point [] { Scrn [19], Scrn [39], Scrn [20], Scrn [0] });

Plane. Add (new Point [] {Scrn [0], Scrn [1], Scrn [2], Scrn [3], Scrn [4], Scrn [5], Scrn [6], Scrn [7], Scrn [8], Scrn [9], Scrn [10], Scrn [11], Scrn [12], Scrn [13], Scrn [14], Scrn [15], Scrn [16], Scrn [17], Scrn [18], Scrn [19] });

// Куб ядовито-желтого цвета

Plane. Add (new Point [] { Scrn [40], Scrn [41], Scrn [42], Scrn [43] });

Plane. Add (new Point [] { Scrn [40], Scrn [44], Scrn [47], Scrn [43] });

Plane. Add (new Point [] { Scrn [43], Scrn [47], Scrn [46], Scrn [42] });

Plane. Add (new Point [] { Scrn [42], Scrn [46], Scrn [45], Scrn [41] });

Plane. Add (new Point [] { Scrn [41], Scrn [45], Scrn [44], Scrn [40] });

Plane. Add (new Point [] { Scrn [44], Scrn [45], Scrn [46], Scrn [47] });

// Список цветов для граней сцены

List<Color> PlaneColor = new List<Color> ()

{

Color. FromArgb (80, 208, 208, 208), // 1 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 2 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 3 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 4 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 5 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 6 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 7 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 8 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 9 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 10 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 11 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 12 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 13 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 14 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 15 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 16 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 17 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 18 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 19 Грань стакана

Color. FromArgb (80, 208, 208, 208), // 20 Грань стакана

Color. FromArgb (100, 102, 102, 102), // 21 — основание стакана

Color. FromArgb (255, 204, 255, 0), // Грань куба

Color. FromArgb (255, 204, 255, 0), // Грань куба

Color. FromArgb (255, 204, 255, 0), // Грань куба

Color. FromArgb (255, 204, 255, 0), // Грань куба

Color. FromArgb (255, 204, 255, 0), // Грань куба

Color. FromArgb (255, 204, 255, 0), // Грань куба

};

СортировкаГраней (27, 0); // Сортировка 27 граней по глубине, начиная с 0-ой грани

ВычислениеНормали (); // Вычисление нормали к плоскости по трем точкам

for (int i = 0; i < 27; i++) // Перебираем все грани

{

if (NZ [Order [i]] >= 0) // Если грань не видна

{

int A = (int) ( (double) PlaneColor [Order [i]]. A); // Прозрачность

int R = (int) ( (double) PlaneColor [Order [i]]. R * NZ [Order [i]]); // Красный цвет * интенсивность

int G = (int) ( (double) PlaneColor [Order [i]]. G * NZ [Order [i]]); // Зеленый цвет * интенсивность

int B = (int) ( (double) PlaneColor [Order [i]]. B * NZ [Order [i]]); // Синий цвет * интенсивность

Color Glass = Color. FromArgb (A, R, G, B); // Задаем цвет

g. FillPolygon (new SolidBrush (Glass), Plane [Order [i]]); // Закраска

}

if (NZ [Order [i]] <= 0) // Если грань видна

{

int A = (int) Math. Abs ( ( (double) PlaneColor [Order [i]]. A));

int R = (int) Math. Abs ( ( (double) PlaneColor [Order [i]]. R * NZ [Order [i]]));

int G = (int) Math. Abs ( ( (double) PlaneColor [Order [i]]. G * NZ [Order [i]]));

int B = (int) Math. Abs ( ( (double) PlaneColor [Order [i]]. B * NZ [Order [i]]));

Color Glass = Color. FromArgb (A, R, G, B);

g. FillPolygon (new SolidBrush (Glass), Plane [Order [i]]);

}

}

}

// Процедура нахождения нормалей к поверхностям

void ВычислениеНормали ()

{

// Получение нормалей к первым 19 граням стакана

for (int i = 0; i < 19; i++)

{

nx = View [i]. y * (View [i + 20]. z — View [i + 21]. z) + View [i + 20]. y * (View [i + 21]. z — View [i]. z) + View [i + 21]. y * (View [i]. z — View [i + 20]. z);

ny = View [i]. z * (View [i + 20]. x — View [i + 21]. x) + View [i + 20]. z * (View [i + 21]. x — View [i]. x) + View [i + 21]. z * (View [i]. x — View [i + 20]. x);

nz = View [i]. x * (View [i + 20]. y — View [i + 21]. y) + View [i + 20]. x * (View [i + 21]. y — View [i]. y) + View [i + 21]. x * (View [i]. y — View [i + 20]. y);

NZ [i] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

}

// Нормаль к 19-ой грани (не поддается циклу)

nx = View [19]. y * (View [39]. z — View [20]. z) + View [39]. y * (View [20]. z — View [19]. z) + View [20]. y * (View [19]. z — View [39]. z);

ny = View [19]. z * (View [39]. x — View [20]. x) + View [39]. z * (View [20]. x — View [19]. x) + View [20]. z * (View [19]. x — View [39]. x);

nz = View [19]. x * (View [39]. y — View [20]. y) + View [39]. x * (View [20]. y — View [19]. y) + View [20]. x * (View [19]. y — View [39]. y);

NZ [19] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// Нормаль к грани (основание стакана), грань имеет 10 вершин, взято 3 (0, 10, 19)

nx = View [0]. y * (View [10]. z — View [19]. z) + View [10]. y * (View [19]. z — View [0]. z) + View [19]. y * (View [0]. z — View [10]. z);

ny = View [0]. z * (View [10]. x — View [19]. x) + View [10]. z * (View [19]. x — View [0]. x) + View [19]. z * (View [0]. x — View [10]. x);

nz = View [0]. x * (View [10]. y — View [19]. y) + View [10]. x * (View [19]. y — View [0]. y) + View [19]. x * (View [0]. y — View [10]. y);

NZ [20] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 40, 41, 42

nx = View [40]. y * (View [41]. z — View [42]. z) + View [41]. y * (View [42]. z — View [40]. z) + View [42]. y * (View [40]. z — View [41]. z);

ny = View [40]. z * (View [41]. x — View [42]. x) + View [41]. z * (View [42]. x — View [40]. x) + View [42]. z * (View [40]. x — View [41]. x);

nz = View [40]. x * (View [41]. y — View [42]. y) + View [41]. x * (View [42]. y — View [40]. y) + View [42]. x * (View [40]. y — View [41]. y);

NZ [21] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 40, 44, 47

nx = View [40]. y * (View [44]. z — View [47]. z) + View [44]. y * (View [47]. z — View [40]. z) + View [47]. y * (View [40]. z — View [44]. z);

ny = View [40]. z * (View [44]. x — View [47]. x) + View [44]. z * (View [47]. x — View [40]. x) + View [47]. z * (View [40]. x — View [44]. x);

nz = View [40]. x * (View [44]. y — View [47]. y) + View [44]. x * (View [47]. y — View [40]. y) + View [47]. x * (View [40]. y — View [44]. y);

NZ [22] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 43, 47, 46

nx = View [43]. y * (View [47]. z — View [46]. z) + View [47]. y * (View [46]. z — View [43]. z) + View [46]. y * (View [43]. z — View [47]. z);

ny = View [43]. z * (View [47]. x — View [46]. x) + View [47]. z * (View [46]. x — View [43]. x) + View [46]. z * (View [43]. x — View [47]. x);

nz = View [43]. x * (View [47]. y — View [46]. y) + View [47]. x * (View [46]. y — View [43]. y) + View [46]. x * (View [43]. y — View [47]. y);

NZ [23] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 42, 46, 45

nx = View [42]. y * (View [46]. z — View [45]. z) + View [46]. y * (View [45]. z — View [42]. z) + View [45]. y * (View [42]. z — View [46]. z);

ny = View [42]. z * (View [46]. x — View [45]. x) + View [46]. z * (View [45]. x — View [42]. x) + View [45]. z * (View [42]. x — View [46]. x);

nz = View [42]. x * (View [46]. y — View [45]. y) + View [46]. x * (View [45]. y — View [42]. y) + View [45]. x * (View [42]. y — View [46]. y);

NZ [24] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 41, 45, 44

nx = View [41]. y * (View [45]. z — View [44]. z) + View [45]. y * (View [44]. z — View [41]. z) + View [44]. y * (View [41]. z — View [45]. z);

ny = View [41]. z * (View [45]. x — View [44]. x) + View [45]. z * (View [44]. x — View [41]. x) + View [44]. z * (View [41]. x — View [45]. x);

nz = View [41]. x * (View [45]. y — View [44]. y) + View [45]. x * (View [44]. y — View [41]. y) + View [44]. x * (View [41]. y — View [45]. y);

NZ [25] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

// 44, 45, 46

nx = View [44]. y * (View [45]. z — View [46]. z) + View [45]. y * (View [46]. z — View [44]. z) + View [46]. y * (View [44]. z — View [45]. z);

ny = View [44]. z * (View [45]. x — View [46]. x) + View [45]. z * (View [46]. x — View [44]. x) + View [46]. z * (View [44]. x — View [45]. x);

nz = View [44]. x * (View [45]. y — View [46]. y) + View [45]. x * (View [46]. y — View [44]. y) + View [46]. x * (View [44]. y — View [45]. y);

NZ [26] = nz / Math. Sqrt (nz * nz + ny * ny + nx * nx);

}

// Получить изображение куба

void ПоказатьСцену ()

{

ПостроитьСцену ();

pictureBox1. Image = b;

}

// Метод-флаг

bool Flag (int i, int PlaneNumber, int [] Order)

{

bool flag = true; // Флаг поднят

for (int j = 0; j < PlaneNumber; j++) // Перебираем грани по их номерам

if (Order [j] == i) flag = false; // Сравниваем номер грани в массиве порядка с номером данной грани

return flag; // Возвращаем значение флага

}

// Метод сортировки граней

void СортировкаГраней (int PlanesCount, int PlaneNumber)

{

double k = 1000; // Показатель глубины

for (int i = 0; i < 27; i++) // Перебираем все грани

{

if (Flag (i, PlaneNumber, Order)) //

if (D [i] < k) // Если ср. знач. по Z < показателя глубины

{ // то.

k = D [i]; // Показатель глубины равняется конкретному значению

Order [PlaneNumber] = i; // Заполнения массива по порядку расположения граней

}

}

PlanesCount — -; // Уменьшаем количество граней на 1

PlaneNumber ++; // Увеличиваем номер грани на 1

if (PlanesCount > 0) // Если количество граней > 0, вызываем рекурсию

СортировкаГраней (PlanesCount, PlaneNumber);

}

// События, обрабатываемые при загрузке формы приложения

private void Form1_Load (object sender, EventArgs e)

{

Teta = 89.549087;

Phi = 134.99817;

Vertex [0] = new Point3D (-50, — 50, 0); // A

Vertex [1] = new Point3D (-48, — 50, — 15); // B

Vertex [2] = new Point3D (-40, — 50, — 30); // C

Vertex [3] = new Point3D (-28, — 50, — 42); // D

Vertex [4] = new Point3D (-15, — 50, — 48); // E

Vertex [5] = new Point3D (0, — 50, — 50); // F

Vertex [6] = new Point3D (15, — 50, — 48); // G

Vertex [7] = new Point3D (28, — 50, — 42); // H

Vertex [8] = new Point3D (40, — 50, — 30); // I

Vertex [9] = new Point3D (48, — 50, — 15); // J

Vertex [10] = new Point3D (50, — 50, 0); // K

Vertex [11] = new Point3D (48, — 50, 15); // L

Vertex [12] = new Point3D (40, — 50, 30); // M

Vertex [13] = new Point3D (28, — 50, 42); // N

Vertex [14] = new Point3D (15, — 50, 48); // O

Vertex [15] = new Point3D (0, — 50, 50); // P

Vertex [16] = new Point3D (-15, — 50, 48); // R

Vertex [17] = new Point3D (-28, — 50, 42); // S

Vertex [18] = new Point3D (-40, — 50, 30); // T

Vertex [19] = new Point3D (-48, — 50, 15); // U

// Верхняя плоскость стакана

Vertex [20] = new Point3D (-60, 100, 0); // A-1

Vertex [21] = new Point3D (-58, 100, — 19); // B-1

Vertex [22] = new Point3D (-48, 100, — 36); // C-1

Vertex [23] = new Point3D (-35, 100, — 50); // D-1

Vertex [24] = new Point3D (-18, 100, — 58); // E-1

Vertex [25] = new Point3D (0, 100, — 60); // F-1

Vertex [26] = new Point3D (18, 100, — 58); // G-1

Vertex [27] = new Point3D (35, 100, — 50); // H-1

Vertex [28] = new Point3D (48, 100, — 36); // I-1

Vertex [29] = new Point3D (58, 100, — 19); // J-1

Vertex [30] = new Point3D (60, 100, 0); // K-11

Vertex [31] = new Point3D (58, 100, 19); // L-12

Vertex [32] = new Point3D (48, 100, 36); // M-13

Vertex [33] = new Point3D (35, 100, 50); // N-14

Vertex [34] = new Point3D (18, 100, 58); // O-15

Vertex [35] = new Point3D (0, 100, 60); // P-16

Vertex [36] = new Point3D (-18, 100, 58); // R-17

Vertex [37] = new Point3D (-35, 100, 50); // S-18

Vertex [38] = new Point3D (-48, 100, 36); // T-19

Vertex [39] = new Point3D (-58, 100, 19); // U-20

// Ядовито-желтый куб (а = 50)

// Нижняя грань

Vertex [40] = new Point3D (-60, — 50, — 65); // A

Vertex [41] = new Point3D (-60, — 50, — 115); // B

Vertex [42] = new Point3D (-10, — 50, — 115); // C

Vertex [43] = new Point3D (-10, — 50, — 65); // D

// Верхняя грань

Vertex [44] = new Point3D (-60, 0, — 65); // A-1

Vertex [45] = new Point3D (-60, 0, — 115); // B-1

Vertex [46] = new Point3D (-10, 0, — 115); // C-1

Vertex [47] = new Point3D (-10, 0, — 65); // D-1

ПоказатьСцену ();

}

// Управление сценой с помощью клавиш-стрелок

private void Form1_KeyDown (object sender, KeyEventArgs e)

{

if (e. KeyCode == Keys. Up)

{

Phi = Phi + Math. PI / 128;

ПоказатьСцену ();

}

if (e. KeyCode == Keys. Down)

{

Phi = Phi — Math. PI / 128;

ПоказатьСцену ();

}

if (e. KeyCode == Keys. Right)

{

Teta = Teta + Math. PI / 128;

ПоказатьСцену ();

}

if (e. KeyCode == Keys. Left)

{

Teta = Teta — Math. PI / 128;

ПоказатьСцену ();

}

}

// Управление сценой с помощью мыши

private void pictureBox1_MouseMove (object sender, MouseEventArgs e)

{

if (mousePress)

{

if (e. X < sX)

{

Teta = Teta — Math. PI / 128;

sX = e. X;

}

if (e. X > sX)

{

Teta = Teta + Math. PI / 128;

sX = e. X;

}

if (e. Y > sY)

{

Phi = Phi — Math. PI / 128;

sY = e. Y;

}

if (e. Y < sY)

{

Phi = Phi + Math. PI / 128;

sY = e. Y;

}

ПоказатьСцену ();

}

}

// Событие, вохникающее при отпускании мыши

private void pictureBox1_MouseUp (object sender, MouseEventArgs e)

{

mousePress = false;

}

// Событие, возникающее при нажатии мыши

private void pictureBox1_MouseDown (object sender, MouseEventArgs e)

{

mousePress = true;

if (e. Button == MouseButtons. Right)

{

R0 = 300;

ПоказатьСцену ();

}

}

// Событие при прокрутке колеса мыши

void Form1_MouseWheel (object sender, MouseEventArgs e)

{

if ( (e. Delta == 120) && (R0! = 150))

{

R0 — = 5;

ПоказатьСцену ();

}

else if ( (e. Delta == — 120) && (R0! = 800))

{

R0 += 5;

ПоказатьСцену ();

}

}

}

}

Picture of Леонид Федотов
Леонид Федотов
Окончил НИУ ВШЭ факультет компьютерных наук. Сам являюсь кандидатом наук. По специальности работаю 13 лет, за это время создал 8 научных статей и 2 диссертации. В компании подрабатываю в свободное от работы время уже более 5 лет. Нравится помогать школьникам и студентам в решении контрольных работ и написании курсовых проектов. Люблю свою профессию за то, что это направление с каждым годом становится все более востребованным и актуальным.