ЛАБОРАТОРНАЯ РАБОТА № 4
Двумерные  отсечения. Заполнения и заливки
 
1. Цель работы
 
1) Освоить методы программирования отсечений двумерных объектов с использованием алгоритма Коэна-Сазерленда.
2) Освоить программирование заполнений и заливок.
3) Разработать программу в среде визуального проектирования C++Builder, позволяющую демонстрировать отсечения различных частей двумерных фигур прямоугольным окном, а также закрашивание их внутренней области заданным алгоритмом.
4) Освоить применение объектов и классов совместно с конвейерной обработкой изображения фигур.
5) Освоить инициализацию и буксировку графических объектов методом Drag and Drop.
6) Освоить программирование анимации нескольких объектов с использованием системного таймера.
 
2. Постановка задачи
 
2.1. В проект, разработанный для лаб. работ №1, 2 и 3 добавить новый модуль с формой Form5.
2.2. На поле Form5 установить графическое окно и панель управления в соответствии с рисунком 7.
2.3. Разработать функции, позволяющие выполнять перенос на поле графического окна, с использованием метода Drag and Drop, геометрических объектов 4-х типов: фигуры 1-го типа, фигуры 2-го типа (согласно варианту из таблицы 1), окна отсечения и ловушки. Количество фигур 1-го и 2-го типов не ограничено, но не менее чем по десять штук. Количество окон отсечения и ловушек – не менее чем по две штуки. Размер и цвет каждой из фигур должны задаваться средствами регулирования, расположенными на панели управления.
2.4. В зоне графического окна должны наблюдаться следующие события:
1). Равномерное перемещение установленных фигур 1-го и 2-го типов по полю графического окна, в соответствии с заданными параметрами скорости и направления для каждой фигуры после нажатия на кнопку "Пуск";
2). Отражение фигур от стенок графического окна при достижении границ окна;
3) Поглощение (исчезновение) фигур, попавших в зону действия ловушек, произвольно расставленных на поле;
4) Отсечение всей фигуры или её части, находящейся вне области окон отсечения, с применением алгоритма Коэна-Сазерленда;
5) Заливка (заполнение) фигур 1-го и 2-го типов установленным цветом с использованием алгоритма, заданного в таблице 1;
6) Буксировка (перетаскивание) ловушек и окон отсечения указателем мыши в любое место графического окна;
7) Одновременное изменение скорости всех фигур;
8) Остановка движения фигур по команде "Стоп";
9) Очистка поля графического окна от всех объектов, установленных на поле графического окна, при нажатии на кнопку "Очистка".
 
Таблица 1
Вар.
Типы фигур
Алгоритм заполнения или заливки
1-ая фигура
2-ая фигура
1
прямоугольник
параллелограмм
простой алгоритм заполнения
2
прямоугольник
трапеция
построчный алгоритм  заполнения
3
прямоугольник
ромб
простой алгоритм заливки
4
прямоугольник
треугольник
построчный алгоритм заливки
5
параллелограмм
трапеция
простой алгоритм заполнения
6
параллелограмм
ромб
построчный алгоритм  заполнения
7
параллелограмм
треугольник
простой алгоритм заливки
8
Трапеция
ромб
построчный алгоритм заливки
9
Трапеция
треугольник
простой алгоритм заполнения
10
Ромб
треугольник
построчный алгоритм  заполнения
 
2.5. Процесс создания программного кода модуля, как и в предыдущих работах, состоит из двух этапов. Первый этап (пункты 2.1 и 2.2) выполняется во время лабораторного занятия. Второй этап (пункты 2.3 и 2.4) выполняется самостоятельно, дома.
2.6. Оформить отчет согласно правилам.

3.  Краткие теоретические сведения
 
3.1 Алгоритмы отсечения
 
Отсечение – это удаление той части изображения, которая не попадает в заданное окно на экране монитора. В качестве окна могут приниматься: окно дисплея, графическое окно программы, другие произвольные контуры. Необходимость выполнения отсечения связана с тем, что некоторые, в основном, простые дисплеи не допускают выхода изображения за пределы экрана, а также, в связи с широким использованием технологии просмотра окнами, требующим выполнения отсечения сцены по границам окна видимости, так как выход за пределы окна приводит к искажению картины.
Графическая задача отсечения в обобщенном виде заключается в вычислении точек пересечения отрезков, из которых составлено изображение, с ограничивающими линиями окна отсечения, определении новых координат концов для этих отрезков и прорисовки изображения с помощью этих новых отрезков.
Отсекаемые отрезки могут быть трех классов - целиком видимые, целиком невидимые и пересекающие окно. Очевидно, что принятие решения о видимости целиком или отбрасывании отрезка надо делать в первую очередь. По способу выбора решения об отбрасывании невидимого отрезка существует два основных типа алгоритмов отсечения - алгоритмы, использующие кодирование концов отрезка или всего отрезка и алгоритмы, использующие параметрическое представление отсекаемых отрезков и окна отсечения.
К алгоритмам первого типа (с кодированием концов отрезков) относятся - алгоритм Коэна-Сазерленда (Cohen-Sutherland, CS-алгоритм) и FC-алгоритм (Fast Clipping - алгоритм).
К алгоритмам второго типа (параметрическим) относятся - алгоритм Кируса-Бека (Curus-Beck, CB - алгоритм) и алгоритм Лианга-Барски (Liang-Barsky, LB-алгоритм).
Алгоритмы с кодированием применимы для прямоугольного окна, стороны которого параллельны осям координат, в то время как алгоритмы с параметрическим представлением применимы для произвольного окна.
Алгоритм Коэна-Сазерленда является стандартом де-факто алгоритмов отсечения линий и обладает одним из лучших быстродействием при компактной реализации.
FC-алгоритм - наиболее быстрый, но и чрезвычайно громоздкий.
Алгоритм Лианга-Барски применяется для отсечения прямоугольным окном с использованием параметрического представления. Быстродействие этого алгоритма сравнимо с быстродействием алгоритма Коэна-Сазерленда при большей компактности и наличии 3D и 4D реализаций.
Алгоритм Кируса-Бека использует параметрическое представление и позволяет отсекать произвольным выпуклым окном.
Программное исполнение отсечения достаточно медленный процесс, поэтому, в мощные дисплеи встраивается соответствующая аппаратура. Впервые аппаратура отсечения, использующая алгоритм отсечения делением отрезка пополам была реализована в устройстве Clipping Divider, в 1968 г.
Здесь мы рассмотрим программную реализацию алгоритма отсечения Коэна-Сазерленда.
 
3.1.1  Алгоритм двумерного отсечения Коэна-Сазерленда
 
Этот алгоритм позволяет быстро выявить отрезки, которые могут быть либо приняты, либо отброшены целиком. Вычисление пересечений требуется, когда отрезок не попадает ни в один из этих классов.

 
Рисунок 1 - Отсечение по методу Коэна-Сазерленда
 
Идея алгоритма состоит в следующем:
1) Окно отсечения и прилегающие к нему части плоскости вместе образуют 9 областей, которым присваиваются 4-х разрядные коды. (рисунок 1)
Две конечные точки отрезка получают 4-х разрядные коды, соответствующие областям, в которые они попали.
2) Определение положения отрезка, либо целиком внутри окна, либо целиком вне окна выполняется следующим образом:
- если коды концов отрезка: Код1=0 и Код2=0 то - отрезок целиком внутри окна, он принимается как видимый (отрезок AB);
- если (Код1 & Код2) ¹ 0 - то отрезок целиком вне окна, он принимается как невидимый (отрезок KL);
- если (Код1 & Код2) = 0 - то отрезок может быть частично видимым (отрезки CD, EF, GH) или целиком невидимым (отрезок IJ); для него нужно определить координаты пересечений со сторонами окна и для каждой полученной части определить тривиальную видимость или невидимость. При этом для отрезков CD и IJ потребуется вычисление одного пересечения, для остальных (EF и GH) - двух.
При расчете пересечения используется горизонтальность либо вертикальность сторон окна, что позволяет определить координату X или Y точки пересечения без вычислений.
При непосредственном использовании описанного выше способа отбора целиком видимого или целиком невидимого отрезка после расчета пересечения потребовалось бы вычисление кода расположения точки пересечения. Для примера рассмотрим отрезок CD. Точка пересечения обозначена как P. В силу того, что граница окна считается принадлежащей окну, то можно просто принять только часть отрезка PD, попавшую в окно. Часть же отрезка CP, на самом деле оказавшаяся вне окна, потребует дальнейшего рассмотрения, так как логическое И кодов точек C и P даст 0, т.е. отрезок CP нельзя просто отбросить. Для решения этой проблемы Коэн и Сазерленд предложили заменять конечную точку с ненулевым кодом конца на точку, лежащую на стороне окна, либо на ее продолжении.
В целом схема алгоритма Коэна-Сазерленда следующая:
1. Рассчитать коды конечных точек отсекаемого отрезка.
2. В цикле повторять пункты 2-6.
3. Если логическое И кодов конечных точек не равно 0, то отрезок целиком вне окна. Он отбрасывается и отсечение закончено.
4. Если оба кода равны 0, то отрезок целиком видим. Он принимается и отсечение закончено.
5. Если начальная точка внутри окна, то она меняется местами с конечной точкой.
6. Анализируется код начальной точки для определения стороны окна, с которой есть пересечение и выполняется расчет пересечения. При этом вычисленная точка пересечения заменяет начальную точку.
7. Определение нового кода начальной точки.
Пример реализации этой схемы приведен в [13].
 
3.2. Алгоритмы заполнения  и  заливки
 
Заполнение – это закраска внутренней части многоугольника в режиме сканирования строк. Многоугольник задается координатами его вершин.
Заливка – это закраска определенной области в режиме затравочного заполнения. Заливаемая область определяется по коду пикселов, расположенных внутри этой области. Если код пиксела отличается от кода так называемого затравочного пиксела, значит он находится снаружи заливаемой области.
 
Основное отличие заполнения многоугольника от заливки области состоит в следующем:
При заполнении многоугольника информация о предельных размерах части экрана, занятой многоугольником задается сразу, с помощью координат вершин заполняемой области.
При заливке области вначале надо прочитать текущий пиксел, затем определить по его цвету, принадлежит ли он заливаемой области и если принадлежит, то перекрасить.
 
3.2.1. Алгоритмы заполнения
 
Различают два вида заполнения:  простое и построчное.
 
Простой алгоритм заполнения.
Простой способ заполнения заключается в определении, принадлежит ли текущий пиксел внутренней части многоугольника. Если принадлежит, то пиксел заносится.
Определить принадлежность пиксела многоугольнику можно, например, подсчетом суммарного угла с вершиной на пикселе при обходе контура многоугольника. Если пиксел внутри, то угол будет равен 360, если вне - 0 (рис. 2).
 
Рисунок 2 - Определение принадлежности пиксела многоугольнику
 
Вычисление принадлежности должно производиться для всех пикселов экрана и так как большинство пикселов скорее всего вне многоугольников, то данный способ слишком расточителен. Объем лишних вычислений в некоторых случаях можно сократить использованием прямоугольной оболочки - минимального прямоугольника, объемлющего интересующий объект, но все равно вычислений будет много.
Другой метод определения принадлежности точки внутренней части многоугольника используется в алгоритме Кируса-Бека.
Алгоритм построчного заполнения.
Реально используются алгоритмы построчного заполнения, основанные на том, что соседние пикселы в строке скорее всего одинаковы и меняются только там где строка пересекается с ребром многоугольника. Это называется когерентностью растровых строк (строки сканирования Yi, Yi+1, Yi+2 на рис.3). При этом достаточно определить X-координаты пересечений строк сканирования с ребрами. Пары отсортированных точек пересечения задают интервалы заливки.

 
Рисунок 3 -. Построчное заполнение многоугольника
 
Кроме того, если какие-либо ребра пересекались i-й строкой, то они скорее всего будут пересекаться также и строкой i+1. (строки сканирования Yi и Yi+1 на рис.3). Это называется когерентностью ребер. При переходе к новой строке легко вычислить новую X-координату точки пересечения ребра, используя X-координату старой точки пересечения и тангенс угла наклона ребра:
 
Xi+1 = Xi + 1/k
 
Тангенс угла наклона ребра:               k = dy/dx,
так как  dy = 1,  то:                               1/k = dx
 
Смена же количества интервалов заливки происходит только тогда, когда в строке сканирования появляется вершина.
Учет когерентности строк и ребер позволяет построить для заполнения многоугольников различные высокоэффективные алгоритмы построчного сканирования. Для каждой строки сканирования рассматриваются только те ребра, которые пересекают строку. Они задаются списком активных ребер (САР). При переходе к следующей строке для пересекаемых ребер перевычисляются X-координаты пересечений. При появлении в строке сканирования вершин производится перестройка САР. Ребра, которые перестали пересекаться, удаляются из САР, а все новые ребра, пересекаемые строкой заносятся в него.
Общая схема алгоритма приведена в [13].

 
Рисунок 4 - Сравнение алгоритмов заполнения многоугольника
 
В [13] приведены две подпрограммы заполнения многоугольника - V_FP0 и V_FP1 [13]. Первая реализует данный (простейший) алгоритм. Эта программа вполне работоспособна, но генерирует двух и трехкратное занесение части пикселов. Это мало приемлемо для матричных или струйных принтеров.
В отличие от V_FP0, в программе V_FP1 используется более сложный алгоритм формирования САР, обеспечивающий практически полное отсутствие дублирований (рис.4).
 
3.2.2. Алгоритмы заливки
 
В алгоритмах заливки задается заливаемая (перекрашиваемая) область, код пиксела, которым будет выполняться заливка и начальная точка в области, начиная с которой начнется заливка.
Виды заливок:
- простой алгоритм заливки с затравкой;
- построчный алгоритм заливки с затравкой.
 
По способу задания области заливки делятся на два типа:
1) Гранично-определенные, задаваемые своей (замкнутой) границей такой, что коды пикселов границы отличны от кодов внутренней, перекрашиваемой части области. На коды пикселов внутренней части области налагаются два условия - они должны быть отличны от кода пикселов границы и кода пиксела перекраски. Если внутри гранично-определенной области имеется еще одна граница, нарисованная пикселами с тем же кодом, что и внешняя граница, то соответствующая часть области не должна перекрашиваться;
2) Внутренне-определенные, нарисованные одним определенным кодом пиксела. При заливке этот код заменяется на новый код закраски.
 
По способам доступа к соседним пикселам области делятся на 4-х и 8-ми связные.
В 4-х связных областях доступ к соседним пикселам осуществляется по четырем направлениям - горизонтально влево и вправо и в вертикально вверх и вниз.
 В 8-ми связных областях к этим направлениям добавляются еще 4 диагональных. Используя связность мы может, двигаясь от точки затравки, достичь и закрасить все пикселы области.
4-х связную область мы можем заполнить как 4-х, так и 8-ми связным алгоритмом. Обратное же неверно. Так, область на рис.5а мы можем заполнить любым алгоритмом, а область на рис.5б, состоящую из двух примыкающих 4-х связных областей можно заполнить только 8-ми связным алгоритмом.
С использованием связности областей и стека можно построить простые алгоритмы закраски как внутренне, так и гранично-определенной области.

 
Рисунок 5 - Связность областей и их границ
 
Простой алгоритм заливки.
Простой алгоритм заливки может быть выполнен двумя способами:
- в рекурсивной реализации;
- в итеративной реализации.
Алгоритм заливки в рекурсивной реализации гранично-определенной 4-х связной области выполняется в следующем порядке:
- определяется, является ли пиксел граничным или уже закрашенным;
- если нет, то пиксел перекрашивается, затем проверяются и если надо перекрашиваются 4 соседних пиксела.
Текст тестовой программы  V_FAB4R с использованием этого алгоритма приведен в [13].
Несмотря на простоту, рекурсивная реализация проигрывает итеративной в том, что требуется много памяти для сохранения вложенных вызовов.
Логика работы итеративного алгоритма заливки 4-х связной гранично-определенной области следующая:
1. Поместить координаты затравки в стек;
2. Если стек не пуст, извлечь из него координаты пиксела;
3. Перекрасить пиксел;
4. Для всех четырех соседних пикселов проверить, является ли он граничным или уже перекрашен;
5. Если нет, то занести его координаты в стек.
 
На рис.6а показан выбранный порядок перебора соседних пикселов, а на рис.6б соответствующий ему порядок закраски простой гранично-определенной области.
 
Рисунок 6 - Заливка 4-х связной области итеративным алгоритмом
 
Программа V_FAB4, реализующая данный алгоритм, приведена в [13].
Как уже отмечалось, очевидный недостаток алгоритмов непосредственно использующих связность закрашиваемой области - большие затраты памяти на стек, так как на каждый закрашенный пиксел в стеке по максимуму будет занесена информация о еще трех соседних. Кроме того, информация о некоторых пикселах может записываться в стек многократно. Это приведет не только к перерасходу памяти, но и потере быстродействия за счет многократной раскраски одного и того же пиксела. Значительно более экономен далее рассмотренный построчный алгоритм заливки.
Построчный алгоритм заливки.
Использует пространственную когерентность, при которой:
- пикселы в строке меняются только на границах;
- при перемещении к следующей строке размер заливаемой строки скорее всего или неизменен или меняется на 1 пиксел.
Таким образом, на каждый закрашиваемый фрагмент строки в стеке хранятся координаты только одного начального пиксела, что приводит к существенному уменьшению размера стека.
Последовательность работы алгоритма для гранично-определенной области следующая:
1. Координата затравки помещается в стек, затем до исчерпания стека выполняются пункты 2-4.
2. Координата очередной затравки извлекается из стека и выполняется максимально возможное закрашивание вправо и влево по строке с затравкой, т.е. пока не попадется граничный пиксел. Пусть это Хлев и Хправ, соответственно.
3. Анализируется строка ниже закрашиваемой в пределах от Хлев до Хправ и в ней находятся крайние правые пикселы всех незакрашенных фрагментов. Их координаты заносятся в стек.
4. То же самое проделывается для строки выше закрашиваемой.
В [13] приведена процедура V_FAST, реализующая рассмотренный алгоритм. 
 
4. Интерфейс программы
 
Начало работы программы лабораторной работы №4 аналогично предыдущим лабораторным работам. После нажатия на кнопку "Lab4" на главной форме Form1 должна открываться форма Form5, с установленными на ней графическим окном  FormPaint1 и панелью управления Panel1 (см. рис. 7).
На панели управления установлены следующие стандартные компоненты:
- элементы  TImage1TImage2TImage3TImage4,  для инициализации и переноса с их области на поле графического окна фигур 1-го и 2-го типов, ловушек и окон отсечения методом Drag and Drop.
- флажки CheckBox1 и CheckBox2, для включения алгоритма отсечения фигур 1-го и 2-го типов, не попавших в зону окон отсечения и для заливки фигур заданным цветом и по заданному алгоритму;
- движок TrackBar1 - для задания размера создаваемой фигуры;
- выпадающий список TComboBox1  устанавливает один из возможных цветов для фигуры;
- движки TrackBar2  и TrackBar3, предназначенные для задания параметров переноса L и m для каждой созданной фигуры. Эти параметры определяют направление движения и скорость фигур, зависящее от соотношения приращения перемещения по осям Х и У;
- движок TrackBar4 предназначен для одновременного изменения скорости движения всех фигур;
- кнопки "Пуск" (Button1), "Стоп" (Button2) и "Очистить" (Button3), предназначены для запуска и остановки движения фигур и очистки графического окна от всех созданных объектов;
- таймер Timer1 - предназначен для обеспечения анимации установленных фигур (невидимый режим).
 
 
Рисунок 7 – Окно формы Form5
 
На рисунке 7 приведен пример формы Form5, на поле графического окна которой установлены методом Drag and Drop несколько различных, независимых объектов. Каждый из этих объектов обладает своими собственными атрибутами и методами, такими как координаты, размеры, цвет, направление движения и другие. Здесь установлены семь фигуры треугольной и ромбовидной формы синего, красного, зеленого и черного цветов, две ловушки, раскрашенных полосами черного и коричневого цветов, и одно квадратное окно отсечения без закраски. Треугольные и ромбовидные фигуры самостоятельно движутся по разным направлениям, ловушки и окно отсечения неподвижны, но их можно буксировать указателем мыши в любое место. В задачу ловушек входит уничтожение любых фигур, попавших в них. В задачу окна отсечения входит обязанность отсекать те части фигур 1-го и 2-го типов, которые находятся вне его области. На приведенном рисунке отсечение фигур окном отсечения пока не включено. После включения флажка "Включить отсечение" все фигуры 1-го и 2-го типов должны исчезнуть кроме красного ромба, находящегося внутри окна отсечения.
 
Основное отличие данной программы от предыдущих лабораторных работ заключается в том, что здесь задействовано одновременно несколько графических объектов, самостоятельно движущихся по собственным траекториям. Поэтому, для реализации поставленной задачи все фигуры 1-го и 2-го типов должны быть описаны как объекты класса Figura. Ловушки и окна отсечения опишем как объекты класса Zona (все эти названия произвольные).
 
5. Структура программы
 
На рисунках 8 и 9 изображены структурные схемы программы и графических конвейеров.
Как видно из схемы, структура программы состоит из:
- 4-х конвейеров для фигур 1-го и 2-го типов, для ловушек и для окон отсечения;
- обработчиков мыши, реализующих создание объектов методом Drag and Drop и их буксировку;
- обработчиков кнопок "Пуск", "Стоп", "Очистка" и других компонентов.

 
 Рисунок 8 – Структурная схема программы

 
Рисунок 9 – Структурная схема конвейера фигур 1-го типа


 Рисунок 10 – Структурная схема конвейера фигур 2-го типа
 
На рисунках 9 и 10 изображены развернутые схемы конвейеров GrafKonv1() и GrafKonv2() , служащих для обработки координат фигур 1-го и 2-го типов и их прорисовки.
В обработку координат входят следующие действия:
- вычисление новых координат фигур после их перемещения в режиме анимации в функциях MoveFig;
- вычисление координат вершин фигур в функциях GenFig;
- вычисление координат отсеченных отрезков фигур в функциях OtsechF.
 
Работа с программой выполняется по следующему алгоритму:
1) Сначала, методом Drag and Drop, т. е. методом перетаскивания из окон Image1, Image2, Image3, Image4 на поле графического окна произвольным образом расставляются фигуры 1-го типа и 2-го типа, ловушки и окна отсечения. При этом, перед перетаскиванием, для каждого из объектов устанавливаются свои размеры, цвет, направление перемещения.
2) Затем нажимается кнопка "Пуск", для запуска движения фигур по своим траекториям. В состав обработчика кнопки "Пуск" входит вызов запуска таймера, работа которого в свою очередь обеспечивает непрерывные перерисовки графического окна. Как видно из схемы на рис. 8, обработчик графического окна FormPaint содержит вызовы (в цикле) графических конвейеров всех объектов, что обеспечивает пересчет координат и их перерисовку на каждом таймерном отсчете.
3) Кнопка "Стоп" выключает таймер, что позволяет приостановить движение фигур.
4) Буксировка ловушек и окон отсечения может быть реализована в любом режиме.


6. СОЗДАНИЕ ДОПОЛНИТЕЛЬНОГО МОДУЛЯ ПРОЕКТА
 
6.1. Подключение дополнительного модуля Form5 к проекту
 
Откройте свой проект с созданными в нем модулями Form1, Form2Form3 и Form4 и подключите дополнительный модуль с формой Form5 аналогично тому, как это было сделано в лабораторной работе №1.
 
6.2.  Компоновка формы Form5
 
Для доступа к конструктору Form5 выполните: View => Forms… В открывшемся окне View Form выберите Form5.
 
2) Установите панель Panel1 на форму  Form5, как показано на рисунке 7.
 
3) Установка компонентов TImage1TImage2TImage3TImage4.
В любом графическом редакторе (например, Paint) создайте небольшие изображения для обозначения двух заданных по варианту фигур, а также для ловушки и для окна отсечения и сохраните их в папке вашего проекта.
Разместите четыре элемента Image (вкладка Additional) на панели Panel1, как показано на рисунке 7. Выделите первый элемент и в окне Object Inspector  выполните следующие операции:
- два раза щелкните на строке Picture;
- нажмите на кнопку Load;
- выберите созданное изображение;
- нажмите на кнопку ОК;
- в поле DragMode выберите dmAutomatic;
- в поле DragCursor выберите crMultiDrag;
Аналогичные действия проведите для трех других изображений.
Над фигурами разместите соответствующие надписи.
 
5). Установите компоненты CheckBox1 и CheckBox2.
Нанесите необходимые надписи согласно рисунку 7.
 
4) Установите движки TrackBar1, TrackBar2, TrackBar3, TrackBar4 и настройте их следующим образом:
Настройки для TrackBar1 "Размер объекта":
- Max = 5;
- Min = 1;
- Position = 1;
- TickStyle = tsManual;
 
Настройки для TrackBar2 и TrackBar3 "Направление движения":
- Max = 10;
- Min = -10;
- Position = 1;
- TickStyle = tsManual;
 
Настройки для TrackBar4 "Скорость движения":
- Max = 10;
- Min = 0;
- Position = 1;
- TickStyle = tsManual;
 
Над движками разместите соответствующие надписи.
 
5) Создание ниспадающего списка TComboBox1
Разместите на панели элемент ComboBox1 (вкладка Standart), задайте ему значение поля Style равное csOwnerDrawFixed. Чтобы заполнить этот компонент названиями цветов, в обработчике события onCreate для формы Form5 необходимо добавить определенные команды, о чем будет указано ниже.
 
6) Установка элемента Timer.
На вкладке System выберите компонент Timer  и поместите его на панели Panel1, в любом удобном месте. Этот элемент виден только в проектировщике формы. Когда приложение запущено, этот элемент скрыт. Установите в настройках таймера параметр   interval = 100.
 
7) Установите кнопки  Button1 - "Пуск", Button2 - "Стоп", и Button3 - "Очистка".
 
На этом компоновка формы Form5 заканчивается, сохраните проект и запустите его на исполнение для проверки.
 
7. РАЗРАБОТКА ФАЙЛА РЕАЛИЗАЦИИ Unit5.cpp
 
7.1. Предварительные пояснения
 
Процесс разработки программного кода модуля разделим на четыре основные части:
- программирование статического режима;
- программирование динамического режима;
- программирование отсечения;
- программирование заполнения (заливки).
В статическом режиме необходимо разработать методы классов для всех создаваемых графических объектов. В этом режиме мы также создадим функции, позволяющие создавать объекты методом Drag and Drop и функции, с помощью которых можно будет буксировать (перемещать указателем мыши) ловушки и окна отсечения. Фигуры в этом режиме пока неподвижны и будут прорисованы в том месте графического поля, куда мы их опустим указателем мыши при перетаскивании из окон Image.
В динамическом режиме разрабатываются функции ответственные за анимационное перемещение фигур 1-го и 2-го типа и за их уничтожение при попадании какой-либо фигуры в ловушку. Здесь нам понадобится разработать обработчики событий для таймера и для всех кнопок.
 
7.2. Объявления переменных и функций
 
Все глобальные переменные, используемые в этой лабораторной работе, будем добавлять по ходу создания функций программы. Что касается объявления прототипов функций, то здесь они имеют следующий вид:
static void GrafKonvF1(int indx);
static void GrafKonvF2(int indx);
static void GrafKonvL(int indx);
static void GrafKonvO(int indx);
static void OtsechF1(int indx);
static void OtsechF2(int indx);
static void Otsechenie(float xn, float yn, float xk, float yk);
static int CodeVer(float xVer, float yVer);
static void Zalivka(TColor colzal, int x, int y);
 
Последовательность работы и взаимосвязь этих функций показана на рисунках 8, 9 и 10 (кроме функции Zalivka(), которая из-за нехватки места не обозначена).
Функция CodeVer() входит в состав функции Otsechenie(), поэтому на схемах также не указана.
 
7.3. Описания классов и их конструкторов
 
Для описания создаваемых объектов, создадим два класса: класс figura для фигур 1-го и 2-го типов и класс zona для описания ловушек и окна отсечения. Описания классов состоят из двух основных частей: описания атрибутов (переменных) и описания методов (функций).
Пример описания класса "figura":
 
//----------------Класс "Фигура"------------------------------------------------
class figura
{
 public:
    bool ini;                     // признак инициализации фигуры
    int xf,yf;                    // координаты центра фигуры
    int ll, mm;                  // параметры переноса
    int z;                          // масштаб фигуры
    int ind;                       // индекс фигуры
    TColor colFig;           // цвет фигуры
 
    int mF1[6][3];             // матрица текущих координат 1-ой фигуры
    int mF2[8][3];             // матрица текущих координат 2-ой фигуры
 
    int mF1ots[6][3];          // матрица коорд. 1-ой отсеченной фигуры
    int mF2ots[8][3];          // матрица коорд. 2-ой отсеченной фигуры
 
    void InitFig1(int indx);      // функция инициализации атрибутов 1-ой фигуры
    void InitFig2(int indx);      // функция инициализации атрибутов 2-ой фигуры
    void MoveFig1(int indx);   // функция преобразования координат 1-ой фигуры
    void MoveFig2(int indx);   // функция преобразования координат 2-ой фигуры
 
    void GenFig1(void);        // функция генерации матрицы 1-ой фигуры
    void GenFig2(void);        // функция генерации матрицы 2-ой фигуры
 
    void ShowFig1(int mF1[6][3]); // функция прорисовки 1-ой фигуры
    void ShowFig2(int mF1[8][3]); // функция прорисовки 2-ой фигуры
 
    void RemFig1(int indx);    // функция уничтожения 1-ой фигуры
    void RemFig2(int indx);    // функция уничтожения 2-ой фигуры
 
    figura(void);                      // конструктор класса
 
} fig1[10], fig2[10];              // объявления имен объектов класса: fig1[10] и fig2[10]
 
Как видно из примера, описание атрибутов класса выполнено без особых сложностей: всё описано в режиме public. Студенты могут самостоятельно определять степень доступа к атрибутам классов, но при этом надо не забывать, что здесь требуется достаточно хорошее понимание правил использования списков формальных параметров в методах классов.
Пример описания класса "zona":
 
//----------------Класс "Зона"------------------------------------------------
class zona
{
 public:
    bool ini;                          // признак инициализации объекта
    int Xz,Yz;                      // координаты центров ловушки и окна
    int z;                               // масштаб объекта
    int ind;                            // индекс объекта
    TColor colZ;                  // цвет объекта
 
    int mLov[4][3];               // матрица текущих координат ловушки
    int mOkno[4][3];            // матрица текущих координат окна
 
    void InitLov(int indx);      // функция инициализации атрибутов ловушки
    void InitOkno(int indx);   // функция инициализации окна отсечения
 
    void GenLov(void);           // генерация матрицы ловушки
    void GenOkno(void);        // генерация матрицы окна отсечения
 
    void ShowLov(void);          // функция прорисовки ловушки
    void ShowOkno(void);       // функция прорисовки окна отсечения
 
    zona(void);                         // конструктор класса
   
} lov[2], okno[2];   // объявления имен двух объектов класса: lov[2] и okno[2]
 
Как видно из приведенных примеров описания классов, мы объявили два массива: fig1[10] и  fig2[10] класса figura, в каждом из которых зарезервировано по десять объектов для описания атрибутов всех десяти фигур 1-го и 2-го типов. А также два массива lov[2]  и okno[2] класса zona, по два объекта в каждом.
Студенты, хорошо владеющие методами использования динамической памяти, могут применить для хранения атрибутов создаваемых объектов линейные списки вместо массивов fig1[10], fig2[10], lov[2] и okno[2]. Тогда количество создаваемых объектов можно будет не ограничивать.
Поместите приведенные выше описания классов в начальную часть файла реализации Unit5.cpp.
Ниже, под описанием конструктора формы Form5, созданного автоматически при проектировании формы, поместите описания конструкторов классов "figura" и "zona":
 
//...........Описание конструктора формы....................................
//-----------------------------------------------------------------------------
__fastcall TForm5::TForm5(TComponent* Owner)
        : TForm(Owner){}
 
//...........Описание конструкторов классов....................................
//-----------------------------------------------------------------------------
figura::figura(void){}
//-----------------------------------------------------------------------------
zona::zona(void){}
//-----------------------------------------------------------------------------
 
Конструкторы классов вообще-то можно использовать для начальной инициализации атрибутов классов. Однако в нашем примере мы будем использовать для инициализации, как и раньше, только функцию FormCreate.

7.4. Создание обработчика исходной инициализации FormCreate
 
Порядок создания обработчика FormCreate приведен в лабораторной работе №2. В текст его содержимого, кроме команд настройки графического буфера, необходимо добавить команды настройки компонента ComboBox1, отвечающего за выбор цвета фигур:
 
 W=ClientWidth-(Panel1->Width);              // Задаем ширину графич. окна на форме
 H=ClientHeight-10;                                     // Задаем высоту графич. окна на форме
 
 formBitmap4=new Graphics::TBitmap();  // Выделям динамич. память под буфер
 formBitmap4->Width= W;                          // Задаем ширину буфера (битмапа)
 formBitmap4->Height=H;                           // Задаем высоту буфера (битмапа)
 formBitmap4->Canvas->Brush->Color=clWhite; // Задаем цвет закраски битмапа
 
 ComboBox1->AddItem("Черный",0);
 ComboBox1->AddItem("Синий",0);
 ComboBox1->AddItem("Красный",0);
 ComboBox1->AddItem("Голубой",0);
 ComboBox1->AddItem("Коричневый",0);
 ComboBox1->AddItem("Зеленый",0);
 ComboBox1->AddItem("Желтый",0);
 ComboBox1->ItemIndex=1;                    // Задаем начальный цвет компонентасиний
 
 col=clBlue;                                               // Задаем начальный цвет фигур–синий
 
Переменные  WHformBitmap4  и col  объявите глобально (см. лаб.раб. №2).
Об остальных переменных, которые будут инициализированы в составе этой функции, будет сказано позднее.
 
7.5. Создание  обработчика события  FormPaint
 
Процесс создания этого обработчика аналогичен действиям, изложенным в лабораторной работе №2. Текст команд этой функции имеет следующий вид:
 
void __fastcall TForm5::FormPaint(TObject *Sender)
{
 formBitmap4->Canvas->FillRect(Rect(0,0,W,H));   //Забеливаем битмап
 
 for(i=0; i<10; i++)                                 // цикл вызова конвейеров фигур
 {if(fig1[i].ini==true) GrafKonvF1(i);    // вызываем конвейер фигур 1-го типа
 if(fig2[i].ini==true) GrafKonvF2(i);      // вызываем конвейер фигур 2-го типа
 }
 for(i=0; i<2; i++)                                  // цикл вызова конвейеров лов. и ок.отс.
 {if(lov[i].ini==trueGrafKonvL(i);      // вызываем конвейер ловушек
 if(okno[i].ini==true) GrafKonvO(i);     // вызываем конвейер окон отсечения
 }
 Canvas->Draw((Panel1->Width),5,formBitmap4);    //Перерисовываем битмап на форму
}
 
Отличие программного кода этой функции от предыдущих лабораторных работ заключается только в том, что здесь запускаются четыре конвейера, в режиме циклической обработки, по количеству создаваемых объектов. Прежде чем запускается очередной конвейер, выполняется проверка состояния параметра инициализации каждого объекта.
Запустите проект на исполнение, предварительно закомментировав вызовы всех, ещё не созданных функции (а именно - GrafKonv()) и убедитесь, что в окне проекта формы Form5, в его правой части, появился белый прямоугольник графического окна.
 
ПРОГРАММИРОВАНИЕ СТАТИЧЕСКОГО РЕЖИМА
 
7.6. Разработка функций графических конвейеров GrafKonv1() и GrafKonv2()
 
В задачу графических конвейеров GrafKonv() входит последовательный запуск функций, ответственных за вычисление координат и прорисовку фигур 1-го и 2-го типов. Перечислим эти функции для первого конвейера, но все нижесказанное можно отнести и ко второму конвейеру:
1) InitFig1(indx) - функция инициализации параметров создаваемой фигуры, а именно: местоположение на поле, размеры, цвет, направление движения. В этом конвейере данная функция прописывается временно (поэтому она выделена синим цветом), для осуществления первичной прорисовки фигур. Затем, когда будут созданы функции обработки мыши, инициализацию фигур будем производить в другом месте, в обработчике событий мыши, в момент перетаскивания фигуры методом Drag and Drop. Параметр indx определяет номер фигуры в массиве fig1[10];
2) RemFig1(indx) - функция уничтожения фигуры при попадании её в ловушку. Прежде, чем осуществлять дальнейшие преобразования координат фигуры, при её движении и затем прорисовку фигуры, необходимо сначала проверить, не попала ли она в ловушку. Для этого и создана эта функция. В статическом режиме ловушки не работают, поэтому, на этом этапе функция RemFig1(indx) временно закомментирована;
3) MoveFig1(indx) – функция преобразования координат фигур при их передвижении по полю графического окна. Прежде чем выполнять преобразование координат фигур, надо проверить нажатие на кнопку "Пуск". Для этого необходимо создать переменную pysk и присваивать ей значение true при каждом нажатии пользователя на кнопку "Пуск". А при нажатии на кнопку "Стоп" переменной pysk будем присваивать значение false, тогда графический конвейер будет пропускать вызов функции MoveFig;
4) GenFig1() – функция генерации матрицы координат фигуры mF1[6][3]. В момент создания конкретной фигуры, при её инициализации методом Drag and Drop функция InitFig1(indx) присваивает координаты центра фигуры её атрибутам  xf и yf. Функция GenFig1() по координатам центра фигуры производит расчет координат всех её вершин и заносит в матрицу mF1[6][3]. Обращаем ваше внимание на то, что размерность матрицы 6х3 для фигуры, изображающей треугольник не является ошибкой, хотя вроде бы надо писать 3х3. Удвоенное количество вершин в матрице координат связано с тем, что далее, при выполнении отсечения фигур мы будем использовать алгоритм Коэна-Сазерленда для отсечения отрезков. Поэтому, будет удобнее, чтобы в матрице координат содержались координаты вершин каждого из трех отрезков, т. е. координаты шести вершин. Существуют, как вы знаете, алгоритмы отсечения многоугольников (а не отрезков), вот там удваивать количество вершин не потребуется. Студенты могут самостоятельно выбирать, какой способ отсечения использовать;
5) ShowFig1(F1.mF1) - функция прорисовки фигуры. Через параметр F1.mF1  в функцию вводится матрица координат конкретной фигуры;
6) Zalivka(TColor colzal, int x, int y) – функция закраски фигур установленным цветом и заданным алгоритмом заполнения или заливки.
 
В целях сокращения длины команд при обращении к атрибутам и методам объектов применим макросы вида:
#define F1 fig1[indx]
#define F2 fig2[indx]
#define F3 lov[indx]
#define F4 okno[indx]
 
С учетом всех пояснений, программный код функции GrafKonv1() будет иметь вид:
 
void GrafKonvF1(intindx)
{
  F1.InitFig1(indx);                       // временная инициализация фигур
//F1.RemFig1(indx);                    // уничтожение фигур, попавших в ловушки
 
//if(Form5->Timer1->Enabled==true)  // если таймер включен, то
//   F1.MoveFig1(indx);                         // выполняем анимац. пересчет корд. фигур
 
  F1.GenFig1();                                      // генерация координат вершин фигур
 
//if(Form5->CheckBox1->Checked)     // если включен флажок отсечения, то
//  {<команды отсечения>                   // выполняем отсечение фигур
//    F1.ShowFig1(F1.mF1ots);             // выполняем прорисовку отсеченных фигур
//  }
//else                                                        //    иначе
    {F1.ShowFig1(F1.mF1);                     // выполняем прорисовку фигур и
//   if(Form5->CheckBox2->Checked)     // если включен флажок заливки, то
//      Zalivka(F1.colFig, F1.xf, F1.yf);     // выполняем заливку
    }
}
 
Здесь, в целях лучшего понимания работы этой функции прописан полный перечень всех необходимых команд (кроме команд отсечения). Однако в статическом режиме включены  только функции  функции   InitFig1() ,  GenFig1() , и ShowFig1(). Остальные функции:  RemFig()MoveFig(), <команды отсечения>, Zalivka()  - не работают, поэтому пока они закомментированы, до особых указаний, т.е. до перехода к программированию динамического режима.
Функция InitFig1() прописана временно, для обеспечения первичной прорисовки фигуры, до тех пор, пока не разработаны функции методов Drag and Drop.
Как видно из текста команд, программа способна выполнять что-либо одно: или отсечение, или заливку. Одновременное выполнение этих двух качеств – достаточно сложная задача и здесь не решается.
Переменную pysk объявите глобально и проинициализируйте в FormCreate в виде: pysk = false.
Создайте аналогичную функцию GrafKonv2() с необходимыми отличиями.
Создайте обработчики для компонентов CheckBox1 (включить отсечение) и CheckBox2 (включить заливку) и впишите в них только одну команду - FormPaint(0);.

 
7.7. Разработка функций графических конвейеров GrafKonvL() и GrafKonvO()
 
В задачу графических конвейеров GrafKonvL() и GrafKonvO() входит запуск функций, ответственных за вычисление координат и прорисовку ловушек и окон отсечения. В конвейер ловушек входят следующие функции:
 
void GrafKonvL(intindx)
{
 lov[indx].InitLov(indx);
 lov[indx].GenLov();
 lov[indx].ShowLov();
}
 
А в конвейер окон отсечения:
 
void GrafKonvO(int indx)
{
 okno[indx].InitOkno(indx);
 okno[indx].GenOkno();
 okno[indx].ShowOkno();
}
Здесь команды инициализации InitLov() и InitOkno() прописаны временно, для обеспечения предварительной прорисовки ловушек и окон отсечения без использования метода Drag and Drop.
При переходе к динамическому режиму их необходимо будет удалить.
 
7.8. Разработка методов классов
 
7.8.1. Функции инициализации атрибутов объектов: InitFig1, InitFig2, InitLov, InitOkno
 
Инициализация атрибутов создаваемых объектов первой и второй фигур, а также ловушек и окон отсечения должна производиться в момент осуществления события Drag and Drop, т е. при перетаскивании их на поле графического окна из области компонентов Image. При этом в атрибутах запоминаются исходное положение, направление траектории (параметры переноса), масштаб и цвет фигуры каждого объекта. Приводим пример описания функции инициализации для фигур 1-ого типа.
 
void figura::InitFig1(intindx)
{
 ini=true;                                                      // признак инициализации фигуры методом Drag&Drop
 xf=cX;                                                         // координаты центра фигуры
 yf=cY;
 ll=Form5->TrackBar2->Position;               // перенос по X
 mm=Form5->TrackBar3->Position;           // перенос по Y
 ind=indx;                                                      // индекс фигуры в массиве
 z=Form5->TrackBar1->Position;               // масштаб фигуры
 colFig=col;                                                   // цвет фигуры
}
 
Переменные  cXcYindx  объявите глобально как int. Переменную col  объявите как  TColor.
Конкретные значения для этих переменных задаются в составе функции, реализующей событие Drag and Drop, создание которой будет рассмотрено ниже.
Напоминаем, что доступ ко всем атрибутам класса, если он производится по полной схеме, выглядел бы так:

 fig1[indx].ini=true;                   // признак инициализации фигуры методом Drag&Drop
 fig1[indx].xf =cX;                    // координаты центра фигуры
  и т.д.

Или же, в укороченном виде, с учетом созданных макросов:
 
 F1.ini=true;                              // признак инициализации фигуры методом Drag&Drop
 F1.xf =cX;                                // координаты центра фигуры
 и т.д.
 
Здесь же, внутри описания метода класса, вызываемого для конкретного объекта класса по его имени, все идентификаторы данного класса можно опустить. Это распространяется и на другие методы классов, за исключением отдельных случаев, которые будут рассмотрены далее.
Создайте аналогичную функцию инициализации для 2-ой фигуры - InitFig2.

      Функция инициализации ловушек – InitLov  имеет вид:
 
void zona::InitLov(int indx)
{
 ini=true;                                  // признак инициализации ловушки методом Drag&Drop
 Xz=cX;                                   // координаты центра объекта
 Yz=cY;
 ind=indx;
 z=Form5->TrackBar1->Position;            // масштаб объекта
 colZ=col;                                                  // цвет объекта
}

      Функцию инициализации окон отсечения InitOkno создайте самостоятельно, она ничем не отличается от InitLov (см. описание класса zona).
Проинициализируйте исходные значения атрибутов инициализации для первых элементов массивов фигур, ловушек и окон отсечения в обработчике FormCreate в виде:
 
fig1[0].ini=false;
fig2[0].ini=false;
lov[0].ini=false;
okno[0].ini=false;
 
7.8.2 Функции генерации  матриц  объектов: GenFig1, GenFig2, GenLov, GenOkno
 
Назначение функций генерации – вычисление конкретных координат всех 4-х (или 3-х) вершин заданных фигур, с использованием координат центра фигуры xf и yf, полученных при её инициализации во время выполнения события Drag and Drop. В примере описания функции генерации матрицы координат, приведенном ниже, выполнен расчет координат для треугольника. Студентам необходимо переработать эти тексты команд, применительно к своим вариантам.
 
void figura::GenFig1(void)
{
 mF1[0][0]=xf-z*10;    mF1[0][1]=yf;             mF1[0][2]=1;
 mF1[1][0]=xf+z*10;   mF1[1][1]=yf-z*10;    mF1[1][2]=1;
 mF1[2][0]=xf+z*10;   mF1[2][1]=yf-z*10;    mF1[2][2]=1;
 mF1[3][0]=xf+z*10;   mF1[3][1]=yf+z*10;   mF1[3][2]=1;
 mF1[4][0]=xf+z*10;   mF1[4][1]=yf+z*10;   mF1[4][2]=1;
 mF1[5][0]=xf-z*10;    mF1[5][1]=yf;              mF1[5][2]=1;
}
Функцию генерации для 2-ой фигуры составьте самостоятельно.
Функции генерации ловушек и окон отсечения также составьте самостоятельно, при этом не забывайте, что координаты их вершин должны быть занесены в матрицы mLov[4][3]  и mOkno[4][3].
Уберите заглушки на вызовах этих функций в функциях GrafKonv и запустите проект для отладки.
 
7.8.3. Разработка функций прорисовки фигур  ShowFig1 и ShowFig2
 
Прорисовку фигур выполним обычным, уже известным нам способом:
 
void figura::ShowFig1(int mF1[6][3])
{
 FBC->Pen->Color=colFig;
 FBC->MoveTo(mF1[0][0], mF1[0][1]);
 FBC->LineTo(mF1[1][0], mF1[1][1]);
 FBC->MoveTo(mF1[2][0], mF1[2][1]);
 FBC->LineTo(mF1[3][0], mF1[3][1]);
 FBC->MoveTo(mF1[4][0], mF1[4][1]);
 FBC->LineTo(mF1[5][0], mF1[5][1]);
 FBC->Pen->Color=clBlack;
}
Аналогично разрабатывается функция прорисовки второй фигуры ShowFig2.
Здесь мы уже можем предварительно просмотреть результаты своего труда, так как все необходимые функции и команды разработаны. Но перед этим необходимо выполнить следующие действия:
- объявите макрос FBC так, как это было сделано в лаб. раб. №2, п. 7.9.;
- присвойте переменным xf, yf, xz, yz в функциях InitFig конкретные, произвольные значения, определяющие их начальные положения на поле графического окна, например, в виде:
 
для InitFig1:       xf=100; yf=100;
для InitFig2:       xf=200; yf=100;
для InitLov:        Xz=100; Yz=200;
для InitOkno:     Xz=200; Yz=200;
 
Команды инициализации координат объектов прописываются временно, затем, когда будут созданы функции, реализующие методы Drag and Drop, инициализацию этих параметров будем производить в обработчиках событий мыши. А переменные cX и cY в функциях Init можно будет возвратить на свое законное место;
- снимите заглушки с функций InitFig(), GenFig(), ShowFig(), в функциях GrafKonv1() и GrafKonv2(), если они там поставлены;
- создайте обработчик компонента ComboBox1 "Цвет фигуры", (см. п. 7.10).
После этого можно запустить проект на исполнение. В графическом окне мы увидим свои фигуры, расположенные в тех местах, которые мы задали с помощью координат  xf и yf в InitFig(). Цвет и размеры фигур будут соответствовать параметрам, установленным с помощью компонентов управления.
Необходимо учитывать, что все координаты объектов в этой работе задаются в пикселях, отсчитываемых от начала оконной системы координат, т.е. от верхнего левого угла графического окна.
 
7.8.4. Разработка функции прорисовки окна отсечения ShowOkno
 
Методика разработки этой функции практически ничем не отличается от предыдущей функции.
Для разнообразия прорисовку окна выполним с помощью стандартной функции Polyline. Текст программного кода функции будет иметь вид:
 
void zona::ShowOkno(void)
{
 FBC->Pen->Color=colZ;                       // Устанавливаем цвет пера
 TPoint points[]={TPoint(mOkno[0][0], mOkno[0][1]),
                               TPoint(mOkno[1][0], mOkno[1][1]),
                               TPoint(mOkno[2][0], mOkno[2][1]),
                               TPoint(mOkno[3][0], mOkno[3][1]),
                               TPoint(mOkno[0][0], mOkno[0][1])
                              };
 FBC->Polyline(points,4);
 FBC->Pen->Color=clBlack;
}
 
Для предварительного просмотра этого объекта выполните операции аналогичные тем, что и в предыдущем подразделе.
 
7.8.5. Разработка функции прорисовки ловушки ShowLov
 
Принципы разработки этой функции аналогичны п.7.8.4, за исключением добавлений, связанных с тем, что закраска ловушки выполняется квадратами, убывающими по величине. Здесь надо создать несколько вложенных полигонов и закрашивать их последовательно: сначала рисуется цветной полигон по исходным размерам, с использованием команды FBC->Polygon(…), затем внутри него рисуется вложенный белый полигон меньших размеров, затем внутри этого белого полигона рисуется опять цветной полигон еще меньших размеров и т.д. Заголовок функции имеет вид:
 
void zona::showLov(void)
 
Содержимое функции разработайте самостоятельно.
 
На этом завершается подготовительное программирование – разработка методов, входящих в состав описания классов Figura  и Zona и предварительный просмотр результатов их работы. Здесь пока не описаны два последних метода - функции уничтожения фигур:  RemFig1 и RemFig2, и перемещения фигур: MoveFig1() и MoveFig2(). Их описание будет приведено позже, в динамическом режиме.
 
7.9. Разработка обработчиков манипуляций с мышью
 
7.9.1. Перетаскивание объектов методом  Drag  and  Drop
 
Программная реализация процесса перетаскивания потребует создания трех обработчиков: DragOver, StartDrag и DragDrop.
Обработчик события OnDragOver проверяет, может ли компонент Form5 принять информацию от компонента Image (перетаскиваемый объект) и если может, задать для параметра Accept значение true. Начало процесса перетаскивания определяется свойством DragMode,  установленным для перетаскиваемых изображений Image.  Значение dmAutomatic определяет автоматическое начало процесса перетаскивания при нажатии пользователем кнопки мыши над компонентом.
Обработчик события ImageStartDrag предназначен для запоминания типа объекта в созданной специально для этого глобальной переменной tip. Всего необходимо создать четыре таких обработчика, для каждого компонента Image.
Обработчик события OnDragDrop обеспечивает прием информации от перетаскиваемого объекта. Это событие наступает, если после перетаскивания пользователь отпустил кнопку мыши над данным компонентом. В обработчик этого события передаются параметры координат курсора X  и Y  в момент отпускания кнопки мыши.
 
7.9.1.1. Обработчик события OnDragOver
 
Для создания этого обработчика откройте конструктор формы Form5. В окне  Object Inspector  для этого компонента перейдите на вкладку  Events и дважды щелкните по белому окошку строки OnDragOver. В тексте программы Unit5.cpp появится новая функция, в которую надо вписать всего одну строчку:
 
void __fastcall TForm5::FormDragOver(TObject *Sender, TObject *Source,
      int X, int Y, TDragState State, bool &Accept)
{
 Accept==true;             // разрешаем перетаскивание в объект Form
}
 
7.9.1.2. Обработчики событий Image1StartDragImage4StartDrag
 
Для создания обработчика Image1StartDrag  выделите компонент Image1 на форме Form5, и в окошке свойств этого компонента  Object Inspector  выберите вкладку  Events.  Дважды  щелкните  по белому окошку строки OnStartDrag , в окне Unit5.cpp появится новая функция, в которую надо вписать всего одну строчку:
 
void __fastcall TForm5::Image1StartDrag(TObject *Sender, TDragObject *&DragObject)
{tip=1;}
 
Функции Image2StartDrag, Image3StartDrag, Image4StartDrag для компонентов Image2, Image3 и Image4 создайте аналогичным образом. Внутри них надо вписать:    tip=2;,  tip=3;, и tip=4;
Переменную tip надо объявить глобально и проинициализировать в функции  FormCreate, присвоив ей нулевое значение.
 
7.9.1.3. Обработчик события  Drag and Drop
 
Для создания этого обработчика выделите компонент Form5, и в окошке свойств этого компонента  Object Inspector  на вкладке  Events  дважды щелкните по белому окошку строки OnDragDrop. В окне Unit5.cpp появится новая функция, в которую надо вписать нижеприведенный код.
 
void __fastcall TForm5::FormDragDrop(TObject *Sender, TObject *Source,
      int X, int Y)
{
 cX=X-(Panel1->Width);
 cY=Y;
 
 if(tip==1)
   if(nf1<10)
     {fig1[nf1].InitFig1(nf1);
      nf1++;
     }
 
 if(tip==2)
   if(nf2<10)
     {fig2[nf2].InitFig2(nf2);
      nf2++;
     }
 
 if(tip==3)
   if(nL<2)
     {lov[nL].InitLov(nL);
      nL++;
    }
 
 if(tip==4)
   if(nO<2)
     {okno[nO].InitOkno(nO);
      nO++;
     }
 
 FormPaint(0);
}
 
Здесь, в глобальных переменных cX и  cY мы запоминаем начальные координаты расположения вновь созданных объектов. Затем, отдельно для каждого типа объектов (фигур, ловушек или окон отсечения), запускаем функции, выполняющие их инициализацию.
Переменные  nf1, nf2, nL и nO служат счетчиками количества созданных объектов, так как мы не можем создавать более чем по 10 штук каждой из фигур и по 2 штуки ловушек или окон отсечения (это ограничено количеством элементов в объявленных массивах). Переменные nf1, nf2, nL, nO надо объявить глобально и проинициализировать в функции FormCreate, присвоив им нулевые значения.
Перед тем, как запустить программу на исполнение, удалите все временные команды, которые были установлены для предварительной прорисовки фигур (см. п. 7.6, 7.7, 7.8.3), а именно:
- вызовы функций InitFig1, InitFig2, InitLov, InitOkno в графических конвейерах;
- временные значения переменных xf, yf, xz, yz в функциях InitFig1, InitFig2, InitLov, InitOkno.
Теперь инициализация фигур будет выполняться по командам, находящихся в обработчике  Drag and Drop. После запуска проекта достаточно будет перетащить указателем мыши любой из объектов Image в зону графического окна.
 
7.9.2. Буксировка объектов указателем мыши
 
Программная реализация процесса буксировки потребует создания трех обработчиков: MouseDown, MouseMove и MouseUp.
Обработчик события OnMouseDown срабатывает при нажатии на кнопку мыши в пределах графического поля и определяет местоположение указателя мыши при начале буксировки.
Обработчик события OnMouseMove обеспечивает процесс перемещения буксируемого объекта.
Обработчик события OnMouseUp срабатывает на отпускание кнопки мыши.
 
7.9.2.1. Обработчик события MouseDown
 
Этот обработчик создается точно так же как и обработчик из п 7.9.1.3, но здесь на вкладке Events выбирается событие  OnMouseDown. Программный код этой функции имеет вид:
 
void __fastcall TForm5::FormMouseDown(TObject *Sender, TMouseButton Button,
      TShiftState Shift, int X, int Y)
{
 if(Button != mbLeft) return;
 X0=X;
 Y0=Y;
 move=true;
((TControl*)Sender)->BringToFront();
}
 
Сначала в этой функции проверяется, нажата ли именно левая кнопка мыши. Затем в переменные X0 и Y0 запоминаются координаты указателя мыши  X  и Y в этот момент времени. После этого задается режим буксировки – true. Переменные X0 и Y0 надо объявить глобально, как int. Переменную move типа bool надо объявить глобально и проинициализировать в функции FormCreate, присвоив ей значение false.

7.9.2.2. Обработчик события 
MouseMove
 
Этот обработчик создается точно так же, как и обработчик из п 7.9.1.3, но здесь на вкладке Events выбирается событие  OnMouseMove.
Здесь нужно выполнить следующие действия: организовать циклы для проверки каждого объекта, входящего в массивы ловушек и массивы окон отсечения. Внутри этих циклов сначала надо проверить, попал ли указатель мыши на площадку какой-либо ловушки или окна отсечения. Если попал – выполнить пересчет координат объекта, после чего можно вызвать команду перерисовки графического окна FormPaint(0). Шаг изменения положения объекта (координаты X0 и Y0) перевычисляется на каждом изменении события move. Программный код функции MouseMove имеет вид:
 
 
void __fastcall TForm5::FormMouseMove(TObject *Sender, TShiftState Shift,
      int X, int Y)
{
 if(move)
   {for(int i=0; i<2; i++)                                                                 //Проверка попадания указателя на поле ловушки
     if((((X-(Panel1->Width))>((lov[i].Xz)-lov[i].z*10))&&
         ((X-(Panel1->Width))<((lov[i].Xz)+lov[i].z*10)))&&
         ((Y>((lov[i].Yz)-lov[i].z*10))&&(Y<((lov[i].Yz)+lov[i].z*10))))
          {
           lov[i].Xz=lov[i].Xz+(X-X0);                                           //Пересчет координат ловушки
           lov[i].Yz=lov[i].Yz+(Y-Y0);
          }
    for(int j=0; j<2; j++)                                                                   //Проверка попадания указателя на поле окна отс.
     if((((X-(Panel1->Width))>((okno[j].Xz)-okno[j].z*20))&&
         ((X-(Panel1->Width))<((okno[j].Xz)+okno[j].z*20)))&&
         ((Y>((okno[j].Yz)-okno[j].z*20))&&(Y<((okno[j].Yz)+okno[j].z*20))))
          {
           okno[j].Xz=okno[j].Xz+(X-X0);                                     //Пересчет координат окна отсеч.
           okno[j].Yz=okno[j].Yz+(Y-Y0);
          }
 }
 X0=X;
 Y0=Y;
 FormPaint(0);
}
 
7.9.2.3. Обработчик события MouseUp
 
Этот обработчик создается точно так же как и обработчик из п 7.9.1.3, но здесь на вкладке Events выбирается событие  OnMouseUp. Программный код этой функции имеет вид:
 
void __fastcall TForm5::FormMouseUp(TObject *Sender, TMouseButton Button,
      TShiftState Shift, int X, int Y)
{
 move=false;
}
 
Запустите проект, перетащите на поле графического окна ловушки и окна отсечения и выполните, для проверки, буксировку любого из этих объектов указателем мыши.
 
7.10 Создание обработчиков событий стандартных компонентов
для настройки параметров объектов
 
7.10.1. Обработчик выпадающего списка ComboBox1 "Цвет фигуры"
 
Для этого обработчика необходимо вписать следующие команды:
 
void __fastcall TForm5::ComboBox1Change(TObject *Sender)
{
 if (ComboBox1->ItemIndex == 0) col=clBlack;       //задаем цвет
 if (ComboBox1->ItemIndex == 1) col=clBlue;
 if (ComboBox1->ItemIndex == 2) col=clRed;
 if (ComboBox1->ItemIndex == 3) col=clSkyBlue;
 if (ComboBox1->ItemIndex == 4) col=clMaroon;
 if (ComboBox1->ItemIndex == 5) col=clGreen;
 if (ComboBox1->ItemIndex == 6) col=clYellow;
}
 
7.10.2. Обработчик события для кнопки Button3 "Очистить"
 
В составе этой функции необходимо выключить признаки инициализации всех объектов, установить в исходное состояние счетчики созданных объектов, работающих в функциях перетаскивания и буксировки и в заключение вызвать перерисовку графического окна:
 
void __fastcall TForm5::Button3Click(TObject *Sender)
{
 for(int i=0; i<10; i++)
    {
     fig1[i].ini=false;
     fig2[i].ini=false;
    }
 for(int i=0; i<2; i++)
    {
     lov[i].ini=false;
     okno[i].ini=false;
    }
 nf1=0;
 nf2=0;
 nL=0;
 nO=0;
 tip=0;
 FormPaint(0);
}
Запустите проект и проверьте работу кнопки "Очистка" по удалению всех установленных графических объектов.
 
На этом программирование статического режима закончено. Здесь мы можем создавать фигуры, ловушки и окна отсечения методом Drag and Drop, выполнять буксировку ловушек и окон, устанавливать цвет и размеры для всех объектов, а также уничтожать созданные объекты. Все создаваемые фигуры 1-го и 2-го типов пока неподвижны, но на следующем этапе нашей работы мы реализуем и их анимацию.

ПРОГРАММИРОВАНИЕ ДИНАМИЧЕСКОГО РЕЖИМА
 
7.11. Предварительные пояснения
 
В динамическом режиме мы разработаем функции, обеспечивающие непрерывное перемещение фигур 1-го и 2-го типов по полю графического окна в заданном направлении и с заданной скоростью. Запуск движения фигур будем осуществлять по команде кнопки "Пуск". При этом будет запускаться таймер, обеспечивающий непрерывные перерисовки графического окна. Как мы знаем, при каждой перерисовке окна запускаются графические конвейеры, в составе которых производятся перерасчеты координат перемещаемых фигур с помощью функций MoveFig1 и MoveFig2, с последующей перерисовкой фигур. Остановку движения фигур будем производить по команде кнопки "Стоп", что приведет к остановке таймера.
В процессе своего движения фигуры могут попадать в ловушки, что должно приводить к их уничтожению. Для определения, попала ли фигура в ловушку, разработаем функции RemFig1 и RemFig2.
С учетом всех выше оговоренных требований перечень разрабатываемых функций будет иметь следующий вид:
- обработчик события кнопки "Пуск";
- обработчик события для таймера;
- обработчик события кнопки "Стоп";
- функции преобразования координат фигур - MoveFig1 и MoveFig2;
- функции уничтожения фигур, попавших в ловушки - RemFig1 и RemFig2.
 
7.12. Обработчик события для кнопки "Пуск"
 
Создадим обработчик события для кнопки "Пуск" (Button1) и впишем в него всего одну команду:
 
void __fastcall TForm5::Button1Click(TObject *Sender)
{
 Form5->Timer1->Enabled=true;                     // включаем таймер
}
 
7.13. Обработчик события Timer
 
Компонент Timer позволяет задавать интервалы времени между перерисовками всех объектов, установленных на поле графического окна. В результате у нас будет получаться анимационная картина перемещения фигур согласно заданному алгоритму их движения. Создайте обработчик для Timer (щелкните дважды по компоненту на конструкторе формы) и впишите в его состав команду перерисовки графического окна:
 
void __fastcall TForm5::Timer1Timer(TObject *Sender)
{
 FormPaint(0);
}
 
7.14. Обработчик события для кнопки "Стоп"
 
Создадим обработчик события для кнопки "Стоп" (Button2):
 
void __fastcall TForm5::Button2Click(TObject *Sender)
{
 Form5->Timer1->Enabled=false;                    //останавливаем таймер
}
 
7.15. Функции преобразования координат фигур MoveFig1 и MoveFig2
 
Назначение этих двух функций – в изменении координат центров фигур 1-го и 2-го типов на каждом шаге их перемещения. Скорость и направление перемещения фигур зависит от приращений по  X и Y, которые устанавливаются компонентами TrackBar2 и TrackBar3 ("Направление движения") изначально, в момент создания фигур методом Drag and Drop и затем запоминаются в атрибутах  ll  и mm каждой фигуры.
В предыдущих лабораторных работах за преобразования координат фигур отвечали функции Preobr() и Normir(). Здесь, в связи с тем, что преобразования осуществляются только по двум параметрам переноса  L  и  m, мы упростим объем работы и выполним изменения центральных координат фигур непосредственным присваиванием, без использования матричных преобразований.
Второе важное действие, которое необходимо выполнить в этих функциях, заключается в изменении направления движения фигур при их встрече с границей графического окна. Для этого необходимо выполнить проверку, выходят ли координаты конкретной фигуры за границы графического окна. Тогда параметры переноса (шаг перемещения) центральных координат фигуры должны изменять свой знак на противоположный. По сути, это преобразование относится к "отображению".
Пример программного кода функции MoveFig1 имеет вид:
 
void figura::MoveFig1(int indx)
{
 if(((F1.xf)>(W-F1.z*10))&&((F1.yf)<(H-F1.z*10)))  //Проверка на касание с
      F1.ll=-F1.ll;                                                               // правой стенкой, если "да"
                                                                                        // то меняем знак для "LL"
 if(((F1.xf)<(W-F1.z*10))&&((F1.yf)>(H-F1.z*10)))  //Аналогично для нижней
      F1.mm=-F1.mm;                                                       // стенки
 
 if(((F1.xf)<(0+F1.z*10))&&((F1.yf)<(H-F1.z*10)))  // Аналогично для левой
     F1.ll=-F1.ll;                                                               // стенки
 
 if(((F1.xf)<(W-F1.z*10))&&((F1.yf)<(0+F1.z*10))) // Аналогично для верхней
      F1.mm=-F1.mm;                                                      // стенки
 
      F1.xf+=F1.ll;                 // Вычисляем новые координаты центра фигуры
      F1.yf+=F1.mm;
}
 
Здесь в выражениях (W-F1.z*10) и  (H-F1.z*10) учитываются реальные отстояния стенок графического окна от края формы Form5 на расстояния W или H.
Функцию MoveFig2   разработайте самостоятельно.
Прежде чем запустить программу на исполнение для проверки, снимите заглушку с команды if(Form5->Timer1->Enabled==true) F1.MoveFig1(indx),  находящейся в графическом конвейере  GrafKonv1(). Аналогичное действие выполните для GrafKonv2().
Теперь можно запустить проект, перетащить на поле графического окна несколько фигур и проверить работоспособность кнопок по запуску и остановке движения фигур.
 
7.16. Разработка функций уничтожения фигур  remFig1 и remFig2
 
Здесь надо сформировать цикл с параметром цикла равным двум, по количеству ловушек. Затем в теле цикла выполнить проверку: вошли ли координаты центра конкретной фигуры в зону "интересов" каждой из ловушек. Если вошли, тогда надо переустановить атрибут инициализации этой фигуры в состояние false. Это приведет к тому, что при очередной перерисовке графического окна функция GrafKonv() конкретно для фигуры с этим индексом не будет запущена, так как перед этим обязательно проверяется значение атрибута инициализации каждой фигуры. Пример функции имеет вид:
 
void figura::RemFig1(int indx)
{
 for (int j=0; j<2; j++)
 {
    if((F1.ini==true)&&(lov[j].ini==true))
      if (((lov[j].Xz-z*10)<F1.xf)&&((lov[j].Xz+z*10)>F1.xf)&&
          ((lov[j].Yz-z*10)<F1.yf)&&((lov[j].Yz+z*10)>F1.yf))
            F1.ini=false;           // Выключаем атрибут инициализации фигуры
 }
}
Аналогично разрабатывается функция уничтожения второй фигуры.
Снимите заглушку с команды  F1.RemFig1(indx),  находящейся в графическом конвейере  GrafKonv1(). То же самое выполните для GrafKonv2().
 
На этом заканчивается программирование динамического режима. Запустите проект и установите несколько фигур и ловушек на поле графического окна. Убедитесь в том, что после нажатия на кнопку "Пуск" все созданные фигуры перемещаются согласно заданным направлениям, а ловушки соответствуют своему предназначению: уничтожают попавшие в них фигуры.
 
ПРОГРАММИРОВАНИЕ ОТСЕЧЕНИЯ
 
7.17. Предварительные замечания
 
В данной лабораторной работе будет использован алгоритм  Коэна-Сазерленда для отсечения отрезков, составляющих фигуры 1-го или 2-го типов. Чтобы выполнять отсечение замкнутой фигуры, состоящей из нескольких отрезков, необходимо вызывать функцию отсечения по-отдельности, последовательно для всех сторон фигуры.
Прежде чем выполнить конкретное отсечение какой-либо стороны фигуры необходимо осуществить подготовительные действия, связанные с тем, что функция отсечения одна, а отрезков отсечения много и окон отсечения также несколько.
Для работы функции отсечения необходимы следующие исходные данные:
- координаты левой верхней и правой нижней вершины конкретного окна отсечения;
- координаты начала и конца конкретного отсекаемого отрезка;
Координаты окна и координаты отрезка являются текущими переменными. Мы будем перезаписывать их содержимое заново перед каждым запуском алгоритма отсечения для конкретного отрезка. Поэтому объявим их глобально в виде:
 
float mOk[2][2];                                       // матрица координат окна
float xnOtr, ynOtr, xkOtr, ykOtr;           // координаты начала и конца отрезка
 
Кроме этого объявим глобально новые координаты текущего отрезка после отсечения. Их значения вычисляет функция отсечения Otsechenie():
 
float xnOts, ynOts, xkOts, ykOts;         // координаты отсеченного отрезка
 
Далее эти координаты будем использовать для прорисовки фигуры, находящейся внутри окна отсечения.
С учетом сказанного, общий алгоритм работы окон отсечения для какой-либо, текущей, фигуры будет состоять из следующих действий:
1) Берем для обработки первое окно отсечения и проверяем, создано ли оно. Если создано, тогда заполняем матрицу координат для этого окна - mOk[2][2];
2) Берем для обработки первый отрезок текущей фигуры и присваиваем конкретные значения координат его концов переменным xnOtr, ynOtr, xkOtr, ykOtr;
3) Запускаем функцию Otsechenie(), реализующую алгоритм Коэна-Сазерленда для этого отрезка;
4) Вводим значения координат xnOts, ynOts, xkOts, ykOts, полученные при работе функции отсечения, в матрицу координат отсеченной фигуры - mF1ots[6][3] (или mF2ots[8][3]);
5) Повторяем операции пунктов 2), 3) и 4) для второго отрезка фигуры, затем для третьего, для четвертого и т.д., по количеству сторон многоугольника, подлежащего отсечению;
Так как операции в пунктах 2), 3) и 4) будут вызываться столько раз, сколько сторон имеет конкретная фигура, поэтому, в целях экономии, создадим функции OtsechF1() и OtsechF2(), в состав которых и включим все необходимые вышеперечисленные команды.
6) Выполняем прорисовку отсеченной фигуры командой ShowFig1(F1.mF1ots);
7) Повторяем операции всех вышеперечисленных пунктов для второго окна отсечения, для третьего, если оно есть и т.д.
 
Операции, выполняемые в 1), 5), 6) и 7) пунктах внесем в состав функций GrafKonv1() и GrafKonv2() , в те части, которые были обозначены как <команды отсечения> (см. п. 7.6).
С учетом вышесказанного разработаем (или переработаем) следующие функции:
- функции OtsechF1() и OtsechF2();
- функцию Otsechenie();
- функцию CodeVer() для кодирования вершин отрезка;
- функции GrafKonv1() и GrafKonv2().
 
7.18. Корректировка функций GrafKonv1() и GrafKonv2()
 
Корректировка этих функции заключается в раскрытии содержимого <команды отсечения>. После корректировки GrafKonv1() будет иметь следующий вид:
 
void GrafKonvF1(int indx)
{
 F1.RemFig1(indx);
 if(pysk==true) F1.MoveFig1(indx);
 F1.GenFig1();
 
 if(Form5->CheckBox1->Checked)
    {if(okno[0].ini==true)         // проверяем, создано ли первое окно
       {
        mOk[0][0]=okno[0].mOkno[0][0];   mOk[0][1]=okno[0].mOkno[0][1];
        mOk[1][0]=okno[0].mOkno[2][0];   mOk[1][1]=okno[0].mOkno[2][1];
        //OtsechF1(indx);
        //F1.ShowFig1(F1.mF1ots);
       }
     if(okno[1].ini==true)         // проверяем, создано ли второе окно
       {
        mOk[0][0]=okno[1].mOkno[0][0];   mOk[0][1]=okno[1].mOkno[0][1];
        mOk[1][0]=okno[1].mOkno[2][0];   mOk[1][1]=okno[1].mOkno[2][1];
        //OtsechF1(indx);
        //F1.ShowFig1(F1.mF1ots);
      }}
 else
    {F1.ShowFig1(F1.mF1);
     //Zalivka(F1.colFig, F1.xf, F1.yf);
    }
}
Как видим, здесь последовательно выполняются пункты 1), 5), 6) и 7) общего алгоритма работы окон отсечения, рассмотренные в разделе 7.17.
Здесь закомментированы еще не созданные функции.
Создайте аналогичную функцию для GrafKonv2() и запустите проект для проверки.
 
7.19. Разработка функций OtsechF1() и OtsechF2()
 
Содержимое функции OtsechF1() в соответствии с пунктами 2), 3), 4) и 5), изложенными в п. 7.17 имеет следующий вид:
 
void OtsechF1(int indx)
{
 xnOtr=F1.mF1[0][0];       ynOtr=F1.mF1[0][1];
 xkOtr=F1.mF1[1][0];       ykOtr=F1.mF1[1][1];
 //Otsechenie(xnOtr, ynOtr, xkOtr, ykOtr);
 F1.mF1ots[0][0]=xnOts;    F1.mF1ots[0][1]=ynOts;
 F1.mF1ots[1][0]=xkOts;    F1.mF1ots[1][1]=ykOts;
 
 xnOtr=F1.mF1[2][0];       ynOtr=F1.mF1[2][1];
 xkOtr=F1.mF1[3][0];       ykOtr=F1.mF1[3][1];
 //Otsechenie(xnOtr, ynOtr, xkOtr, ykOtr);
 F1.mF1ots[2][0]=xnOts;    F1.mF1ots[2][1]=ynOts;
 F1.mF1ots[3][0]=xkOts;    F1.mF1ots[3][1]=ykOts;
 
 xnOtr=F1.mF1[4][0];       ynOtr=F1.mF1[4][1];
 xkOtr=F1.mF1[5][0];       ykOtr=F1.mF1[5][1];
 //Otsechenie(xnOtr, ynOtr, xkOtr, ykOtr);
 F1.mF1ots[4][0]=xnOts;     F1.mF1ots[4][1]=ynOts;
 F1.mF1ots[5][0]=xkOts;     F1.mF1ots[5][1]=ykOts;
}
 
Как видим, здесь сначала выполняется отсечение первого отрезка треугольной фигуры, затем второго и затем третьего. Вызовы еще не созданной функции Otsechenie() пока закомментированы.
Создайте аналогичную функцию для OtsechF2() и запустите проект для проверки.
 
7.20. Разработка алгоритма отсечения Коэна-Сазерленда
 
Приводим текст программного кода этого алгоритма:
 
void Otsechenie(float xn, float yn, float xk, float yk)
{
   int    visible=0;
   int    codN,codK,
          ii, s;                             // Рабочие переменные
   float dx, dy,                       // Приращения координат
          dxdy,dydx,                 // Наклоны отрезка к сторонам
          r;                                 // Рабочая переменная
 
   float x0, y0, x1, y1;
 
 xL=mOk[0][0];
 xR=mOk[1][0];
 yT=mOk[0][1];
 yB=mOk[1][1];
 
 x1= xk; y1= yk;
 x0= xk; y0= yk; codK=CodeVer(x0, y0);
 x0= xn; y0= yn; codN=CodeVer(x0, y0);
 
 dx=x1-x0;
 dy=y1-y0;
 if(dx!=0) dydx=dy/dx;
 else if(dy==0)
          if((codN==0)&&(codK==0))
             {xnOts=x0; ynOts=y0;
              xkOts=x1; ykOts=y1;
               return;}
          else return ;
 
 if(dy!=0) dxdy=dx/dy;
 visible=0;
 ii= 4;
 do{
     if(codN & codK) break;           // Целиком вне окна
     if((codN==0)&&(codK==0))
       {++visible; break;}                // Целиком внутри окна
        
     if(!codN)                                          // Если Pn внутри окна, то
       {s=codN; codN=codK; codK=s;   // перестить точки Pn, Pk и
        r=x0; x0=x1; x1=r;                         // их коды, чтобы Pn
        r=y0; y0=y1; y1=r;}                     // оказалась вне окна
 
                   // Теперь отрезок разделяется. Pn помещается в точку
                   // пересечения отрезка со стороной окна.
 
     if(codN & 1)                   // Пересечение с левой стороной
       {y0=y0+dydx* (xL-x0);
        x0=xL;}
     else
       if(codN & 2)                 // Пересечение с правой стороной
         {y0=y0+dydx* (xR-x0);
          x0=xR;}
       else
         if(codN & 4)              // Пересечение в нижней стороной
           {x0=x0+dxdy* (yB-y0);
            y0=yB;}
         else
           if(codN & 8)            // Пересечение с верхней стороной
             {x0= x0 + dxdy * (yT-y0);
              y0= yT;}
     codN= CodeVer(x0, y0);        // Перевычисление кода точки Pn
    }
 while(--ii >= 0);
 if(visible)
    {xnOts=x0; ynOts=y0;
     xkOts=x1; ykOts=y1;}
}
Переменные xL, xR, yT, yB объявите глобально как float.
 
7.21. Разработка функции CodeVer()
 
Функция CodeVer() вычисляет коды для отсекаемых отрезков и имеет вид:
 
static int CodeVer(float xVer, float yVer)
{
 int cod=0;
 if(xVer<xL) ++cod; else
 if(xVer>xR) cod+=2;
 if(yVer>yB) cod+=4; else
 if(yVer<yT) cod+=8;
 return cod;
}
 
На этом программирование функций отсечения закончено.
Снимите заглушки, установленные ранее в функциях GrafKonv1() и GrafKonv2(), в функциях OtsechF1() и OtsechF2() и запустите проект на исполнение. После включения флажка "Отсечение" все фигуры 1-го и 2-го типов, установленные на поле графического окна, должны исчезнуть, кроме тех, что будут находиться в этот момент во внутренних областях окон отсечения. Можно указателем мыши поперемещать окна отсечения и «обнаружить» остальные, невидимые на данный момент, движущиеся по полю графического окна фигуры в момент их попадания в область окон отсечения.
 
ПРОГРАММИРОВАНИЕ ЗАПОЛНЕНИЯ (ЗАЛИВКИ)
 
7.22. Разработка функции Zalivka()
 
В функциях GrafKonv1() и GrafKonv2() присутствует вызов функции Zalivka(), но только для режима, когда отсечение фигур выключено. Выполнение заливки с включенным режимом отсечения представляет гораздо более сложную задачу, т. к. форма отсеченной фигуры в разных положениях может изменяться. Поэтому, в данной лабораторной работе заполнение (заливку) фигур 1-го и 2-го типа разрешается выполнить только для режима без отсечения.
Программный код функции Zalivka() разработайте самостоятельно, в соответствии с вариантом задания.
Для отладки разработанной функции не забудьте снять заглушки с вызовов функции Zalivka().
 
 
8. Контрольные вопросы
 
1. Дайте определение, что такое отсечение изображения и в чем заключается его действие.
2. Назовите основные типы отсечений и назовите алгоритмы, которые их реализуют.
3. Назовите классы отсекаемых отрезков.
4. Перечислите алгоритмы отсечений с кодированием отрезков. В чем заключаются их достоинства и недостатки.
5.Перечислите алгоритмы с параметрическим представлением отрезков. В чем заключаются их достоинства и недостатки.
6. Напишите алгоритм Коэна–Сазерленда.
7. На какие два основных вида разделяются алгоритмы закрашивания графических объектов?
8. В чем суть алгоритмов заполнения, и на какие виды они разделяется?
9. В чем суть алгоритмов заливки, и на какие виды они разделяется?
   
10. В чем заключаются основные различия между алгоритмами заполнения и алгоритмами заливки?