Книга: JavaScript. Подробное руководство, 6-е издание

21.4.1. Рисование линий и заливка многоугольников

21.4.1. Рисование линий и заливка многоугольников

Чтобы нарисовать прямые линии в элементе <canvas> и залить внутреннюю область замкнутой фигуры, образуемой этими линиями, необходимо сначала определить контур (path). Контур - это последовательность из одного или более фрагментов контура. Фрагмент контура - это последовательность из двух или более точек, соединенных прямыми линиями (или, как будет показано ниже, кривыми). Создается новый контур с помощью метода beginPath(). А фрагмент контура -с помощью метода moveTo(). После установки начальной точки фрагмента контура вызовом метода moveTo() можно соединить эту точку с новой точкой прямой линией, вызвав метод liпеТо(). Следующий фрагмент определяет контур, состоящий из двух прямых линий:

с.beginPath(); // Новый контур
c.moveTo(100, 100); // Новый фрагмент контура с начальной точкой (100,100)
c.lineTo(200, 200); // Добавить линию, соединяющую точки (100,100) и (200,200)
c.lineTo(100, 200); // Добавить линию, соединяющую точки (200,200) и (100,200)

Фрагмент выше просто определяет контур - он ничего не рисует. Чтобы фактически нарисовать две линии, следует вызвать метод stroked, а чтобы залить область, ограниченную этими линиями, следует вызвать метод fill():

с.fill(); // Залить область треугольника
с.stroked; // Нарисовать две стороны треугольника

Фрагмент выше (плюс некоторый дополнительный программный код, устанавливающий толщину линий и цвет заливки) воспроизводит рисунок, изображенный на рис. 21.5.


Рис. 21.5. Простой путь, нарисованный и залитый

Обратите внимание, что фрагмент контура, определяемый выше, является «открытым». Он содержит всего две прямые линии, и его конечная точка не совпадает с начальной точкой. То есть он образует незамкнутую область. Метод fill() выполняет заливку открытых фрагментов контуров, как если бы конечная и начальная точка фрагмента контура были соединены прямой линией. Именно поэтому пример выше выполняет заливку треугольной области, но рисует только две стороны этого треугольника.

Если бы потребовалось нарисовать все три стороны треугольника выше, можно было бы вызвать метод closePath(), чтобы соединить конечную и начальную точки фрагмента контура. (Можно было бы также вызвать метод lineTo(100,100), но в этом случае получились бы три прямые линии с общей начальной и конечной точками, не образующие в действительности замкнутый фрагмент контура. При рисовании толстыми линиями результат визуально выглядит лучше, если используется метод closePath().)

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

с.moveTo(300,100); // Новый фрагмент контура с начальной точкой (300,100);
с.lineТо(300,200); // Нарисовать вертикальную линию вниз до точки (300,200);

Если затем вызвать метод stroke(), получились бы две соединенные вместе стороны треугольника и не связанная с ними вертикальная линия.

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

Пример 21.4 содержит определение функции рисования правильных многоугольников и демонстрирует использование методов moveTo(), lineTo() и closePath() для определения фрагментов контура и методов fill() и stroke() для рисования контуров. Он воспроизводит рисунок, изображенный на рис. 21.6.


Пример 21.4. Рисование правильных многоугольников с помощью методов moveTo(), lineTo() и closePath()

// Определяет правильный многоугольник с п сторонами, вписанный в окружность с центром
// в точке (х,у) и радиусом r. Вершины многоугольника находятся на окружности,
// на равном удалении друг от друга. Первая вершина помещается в верхнюю точку
// окружности или со смещением на указанный угол angle. Поворот выполняется
// по часовой стрелке, если в последнем аргументе не передать значение true,
function polygon(c, n, x, y, r, angle, counterclockwise) {
  angle = angle || 0;
  counterclockwise = counterclockwise || false;
  c.moveTo(x + r*Math.sin(angle), // Новый фрагмент контура
       у - r*Math.cos(angle)); // Исп. тригонометрию для выч. координат
  var delta = 2*Math.PI/n; // Угловое расстояние между вершинами
  for(var і = 1; і < n; i++) { // Для каждой из оставшихся вершин
    angle += counterclockwise?-delta:delta; // Скорректировать угол
    c.lineTo(x + r*Math.sin(angle), // Линия к след, вершине
             у - r*Math.cos(angle));
  }
  с.closePath(); // Соединить первую вершину с последней
}
// Создать новый контур и добавить фрагменты контура, соответствующие многоугольникам-
с.beginPath();
polygon(c, 3, 50, 70, 50);                   // Треугольник
polygon(c, 4, 150, 60, 50, Math.PI/4);       // Квадрат
polygon(c, 5, 255, 55, 50);                  // Пятиугольник
polygon(c, 6, 365, 53, 50, Math.PI/6);       // Шестиугольник
polygon(c, 4, 365, 53, 20, Math.PI/4, true); // Квадрат в шестиугольнике
// Установить некоторые свойства, определяющие внешний вид рисунка
c.fillStyle = "ftссс";  // Светло-серый фон внутренних областей
с.strokeStyle = "#008"; // темно-синие контуры
с.lineWidth = 5;        // толщиной пять пикселов.
// Нарисовать все многоугольники (каждый создается в виде отдельного фрагмента контура)
// следующими вызовами
с.fill();   // Залить фигуры
c.stroke(); // И нарисовать контур

Обратите внимание, что в этом примере внутри шестиугольника рисуется квадрат. Квадрат и шестиугольник являются отдельными фрагментами контура, но они перекрываются. Когда это происходит (или когда один фрагмент контура пересекается с самим собой), элементу <canvas> приходится выяснять, какая область является внутренней для фрагмента контура, а какая - внешней. Для этого элемент <canvas> использует алгоритм, известный как «правило ненулевого числа оборотов» («nonzero winding rule»). В данном случае внутренняя область квадрата не заливается светло-серым цветом, потому что квадрат и шестиугольник рисовались в противоположных направлениях: вершины шестиугольника соединялись линиями в направлении по часовой стрелке, а вершины квадрата - против часовой стрелки. Если бы рисование квадрата также выполнялось в направлении по часовой стрелке, метод fill() залил бы внутреннюю область квадрата.

Правило ненулевого числа оборотов

Чтобы определить, находится ли точка Р внутри контура, используя правило правило ненулевого числа оборотов, представьте луч, исходящий из точки Р в любом направлении и уходящий в бесконечность (или, если говорить практическим языком, до некоторой точки за пределами фигуры, образуемой рассматриваемым контуром). Теперь инициализируйте счетчик нулевым значением и проследите за всеми пересечениями контура с лучом. Каждый раз, когда луч пересекает контур, рисуемый в направлении по часовой стрелке, увеличивайте счетчик на единицу. Каждый раз, когда луч пересекает контур, рисуемый в направлении против часовой стрелки, уменьшайте счетчик на единицу. Если после учета всех пересечений в счетчике получается значение, не равное нулю, следовательно, точка Р находится внутри контура. Если счетчик оказывается равным нулю, следовательно, точка Р находится снаружи.

***********************************************

Оглавление книги


Генерация: 1.796. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз