# Глава 2. Рисование линий Прямая — это ряд смежных точек. Мы ограничимся рассмотрением двух типов прямых: - горизонтальные линии - вертикальные линии При построении этих линий точка за точкой меняется только одна координата, а другая остается неизменной. Точка — это объект, состоящий из координат x и y. Мы определяем и используем ее следующим образом: ```nim import pixels type Point = object # 1 x: int # 2 y: int var p = Point(x: 5, y: 7) # 3 putPixel(p.x, p.y) # 4 ``` - 1 Defining a Point as an object. - 2 Coordinates x and y are integers. - 3 Variable p is an instance of a Point with coordinates (5, 7). - 4 We can access fields of an object with a dot. In this case, we access the x and y coordinates of the point p with p.x and p.y. - 1 Определение точки как объекта - 2 Координаты `x` и `y` — целые числа - 3 Переменная `p` является экземпляром класса `Point` с координатами (5, 7). - 4 Мы можем обращаться к полям объекта через точку. В данном случае мы обращаемся к координатам `x` и `y` точки `p` через `p.x` и `p.y`. ## 2.1. Проведение горизонтальных и вертикальных линий Линия может быть задана начальной и конечной точками или начальной точкой, длиной и направлением. Мы рассмотрим оба подхода. ### 2.1.1. Проведение линии с использованием одной точки, длины и направления Мы можем задать линию по её начальной точке, длине и направлению. Имея эту информацию, мы можем рисовать линию попиксельно. Длина линии — это положительное целое число (нуль — это просто точка, а не линия; отрицательное значение длины физически невозможно). Направление может быть горизонтальным или вертикальным. Это значит, что мы будем ограничиваться построением линий слева направо (указываем самую левую точку линии, а затем увеличиваем координату `x`) или сверху вниз (увеличиваем координату `y`). Для начала можно создать два похожих процесса: один для рисования горизонтальных линий, другой — для рисования вертикальных. ```nim import pixels type Point = object x: int y: int proc drawHorizontalLine(start: Point; length: Positive) = # 1 for delta in 0 .. length: # 2 putPixel(start.x + delta, start.y) # 3 proc drawVerticalLine(start: Point; length: Positive) = for delta in 0 .. length: putPixel(start.x, start.y + delta) # 4 let a = Point(x: 60, y: 40) drawHorizontalLine(a, 50) # 5 drawVerticalLine(a, 30) # 6 ``` - 1 Длина определяется как встроенный в **Nim** тип `Positive`, который является подтипом `int` и содержит только положительные числа. - 2 `p..q` — это (встроенный) итератор, который выполняет итерацию от `p` до `q` (оба конца включительно) в порядке возрастания, то есть для выполнения итерации `p` должно быть меньше `q`. В данном случае мы выполняем итерацию от нуля до `length`. - 3 Для горизонтальной линии координата `y` остается постоянной, и на каждом шаге итерации мы рисуем пиксель на один пиксель правее, чем на предыдущем шаге. - 4 Аналогичным образом для вертикальной линии координата `x` остается неизменной, а координата `y` увеличивается, чтобы нарисовать новый пиксель. - 5 Этот вызов позволит провести горизонтальную линию от (60, 40) до (110, 40). - 6 Этот вызов позволит провести вертикальную линию от (60, 40) до (60, 70). У нас может быть одна процедура `drawLine`, которая будет рисовать как горизонтальные, так и вертикальные линии в зависимости от заданного пользователем параметра. Для моделирования набора из ограниченного количества значений мы используем тип `enum`. Преимущества этого выбора станут очевидны позже, когда мы рассмотрим одну из особенностей языка **Nim** — проверку полноты перечисления. Теперь мы можем дополнить предыдущий пример следующими строками: ```nim type Direction = enum 1 Horizontal 2 Vertical proc drawLine(start: Point; length: Positive; direction: Direction) = 3 case direction 4 of Horizontal: drawHorizontalLine(start, length) 5 of Vertical: drawVerticalLine(start, length) drawLine(a, 50, Horizontal) 6 drawLine(a, 30, Vertical) ``` - 1 Направление — это перечисление (`enum`) с двумя возможными значениями. - 2 Значения направления: горизонтальное и вертикальное. - 3 Теперь у нас есть дополнительный параметр типа `Direction`. - 4 Это оператор `switch`. Он похож на оператор `if`, но более функционален. Мы не только получаем отдельные ветви для каждого варианта, но и проверяем полноту: мы не можем случайно пропустить какой-либо вариант. В частности, мы должны учесть все значения перечисления. - 5 Мы делегируем задачу по построению линий ранее определенным процессам. - 6 Будут нарисованы те же линии, что и в предыдущем примере. ### 2.1.2. Рисование линии с указанием начальной и конечной точек Если мы используем две точки для определения прямой, нам не нужно знать (или вычислять) длину прямой, а направление можно определить по расположению этих двух точек: если у них одинаковые координаты по оси `Y`, мы рисуем горизонтальную прямую, а если одинаковые координаты по оси `X` — вертикальную. Мы всегда будем начинать с точки с меньшим значением изменяемой координаты, но позволим пользователям указывать точки в любом порядке. Этого можно добиться с помощью рекурсии. ```nim proc drawHorizontalLine(a, b: Point) = # 1 if b.x < a.x: drawHorizontalLine(b, a) # 2 else: for x in a.x .. b.x: putPixel(x, a.y) # 3 proc drawVerticalLine(a, b: Point) = if b.y < a.y: drawVerticalLine(b, a) else: for y in a.y .. b.y: putPixel(a.x, y) let p = Point(x: 20, y: 20) q = Point(x: 50, y: 20) r = Point(x: 20, y: -10) drawHorizontalLine(p, q) # 4 drawVerticalLine(p, r) # 5 ``` - 1 Несмотря на то, что у нас уже есть процедура с таким же названием, переопределения не происходит: это разные процедуры, потому что у них разные параметры. - 2 Обратите внимание на обратный порядок аргументов. - 3 `x` — изменяющаяся переменная, а `y` остается постоянной. - 4 Проведите горизонтальную линию между точками (20, 20) и (50, 20). - 5 Поскольку `r.y` меньше, чем `p.y`, это, в свою очередь, приведёт к вызову функции `drawVerticalLine(r, p)` и нарисует вертикальную линию между точками (20, -10) и (20, 20)