ch02.md 9.4 KB

Глава 2. Рисование линий

Прямая — это ряд смежных точек. Мы ограничимся рассмотрением двух типов прямых:

  • горизонтальные линии
  • вертикальные линии

При построении этих линий точка за точкой меняется только одна координата, а другая остается неизменной. Точка — это объект, состоящий из координат x и y. Мы определяем и используем ее следующим образом:

  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).

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

  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 — проверку полноты перечисления.

Теперь мы можем дополнить предыдущий пример следующими строками:

  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 — вертикальную.

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

Этого можно добиться с помощью рекурсии.

  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)