Спирали и круговые узоры

Сегодня ты соединишь две вещи, которые уже умеешь по отдельности — цикл for и пару sin/cos — и получишь из них настоящую магию: спирали, мандалы и круговые узоры вокруг CodeChick.

Главная идея урока: точка на окружности задаётся углом и радиусом — x = cx + r * cos(угол), y = cy + r * sin(угол). Если в цикле крутить угол по кругу, получится кольцо; а если при этом ещё и плавно растить радиус, кольцо раскручивается в спираль.

Зачем тебе спирали

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

В уроке про цикл for и ряды ты научился ставить элементы в линию: меняешь одну переменную в цикле — и получаешь ряд кружков. А в уроке про sin и cos ты узнал, что эти две функции умеют гнать точку по окружности. Сегодня мы поженим эти два приёма. Цикл будет крутить угол, а sin и cos — превращать угол в координаты точки на круге. Стоит добавить к этому растущий радиус — и плоское кольцо оживёт спиралью.

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

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

Как угол и радиус рождают точку

Метафора: стрелка часов и выдвижная антенна

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

А теперь представь, что стрелка — это выдвижная антенна, которая, пока крутится, ещё и медленно выезжает наружу. Чем дальше повернулась — тем длиннее стала. Кончик такой стрелки уже не повторяет круг, а уходит по спирали всё дальше от центра. Вот и весь секрет: кольцо — это угол по кругу при постоянном радиусе, а спираль — то же самое, но радиус растёт вместе с углом.

Формула точки на окружности. Если центр круга — точка (cx, cy), радиус — r, а угол — a, то координаты точки на круге такие: x = cx + r * cos(a) и y = cy + r * sin(a). Косинус отвечает за горизонталь, синус — за вертикаль.

Не пугайся формулы — она короткая, и её достаточно один раз понять. cos(a) и sin(a) всегда дают число от -1 до 1: это как бы «доля» радиуса по горизонтали и по вертикали. Умножаем на r — получаем реальное смещение в пикселях. Прибавляем центр cx, cy — и точка встаёт там, где нужно, относительно центра холста, а не угла экрана.

Давай проверим формулу на пальцах, чтобы она перестала быть магией. Возьми угол a = 0 (точка смотрит вправо): cos(0) равен 1, sin(0) равен 0. Значит x = cx + r, y = cy — точка ровно справа от центра, на расстоянии радиуса. Теперь поверни на четверть круга вниз, a = HALF_PI: cos станет 0, sin станет 1, и точка окажется ровно под центром. Ещё четверть — точка слева, потом сверху, и круг замкнётся. То есть угол как стрелка компаса просто водит точку по ободу, а sin и cos сами пересчитывают «куда именно» в координаты холста. Не нужно ничего держать в голове — формула делает это за тебя.

Радианы, а не градусы

Важная деталь, на которой спотыкаются все новички: по умолчанию sin() и cos() в p5.js ждут угол в радианах, а не в привычных градусах. Полный круг — это не 360, а TWO_PI (примерно 6.28). Половина круга — PI, четверть — HALF_PI. Это просто другой «язык измерения угла», как сантиметры против дюймов.

Чтобы крутить точку по полному кругу, угол надо прогнать от 0 до TWO_PI. А если хочешь думать в градусах — поставь в начале angleMode(DEGREES), и тогда полный круг снова станет 360. В этом уроке мы будем работать в радианах, потому что так пишут большинство примеров в интернете — привыкай к ним.

Разбираем на примерах

Пример 1: кольцо из зёрнышек вокруг CodeChick

Начнём с самого простого узора — кольца. Хотим разложить 12 зёрнышек ровным кругом вокруг цыплёнка, будто он сидит в центре тарелки с кормом. Радиус постоянный, меняется только угол.

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(135, 206, 235);

  let cx = 200;   // центр круга — центр холста
  let cy = 200;
  let r = 120;    // радиус кольца, одинаковый для всех зёрен
  let count = 12; // сколько зёрнышек по кругу

  // CodeChick в центре
  fill(255, 209, 64);
  circle(cx, cy, 70);
  fill(255, 140, 30);
  triangle(cx + 26, cy - 5, cx + 48, cy, cx + 26, cy + 5);

  // раскладываем зёрна по кольцу
  fill(150, 90, 40);
  for (let i = 0; i < count; i = i + 1) {
    let a = (TWO_PI / count) * i;   // угол для i-го зерна
    let x = cx + r * cos(a);
    let y = cy + r * sin(a);
    circle(x, y, 16);
  }
}

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

Ключевая строка — let a = (TWO_PI / count) * i;. Она режет полный круг на count равных кусочков и для каждого зерна (i = 0, 1, 2, ...) берёт свой угол. Поменяй count на 6 — зёрен станет шесть, и они разойдутся пореже. Поставь 30 — получится плотный венок. Это и есть рецепт круговой расстановки: дели круг на сколько хочешь частей и ставь элемент на каждом шаге.

Пример 2: спираль из точек — раскручиваем радиус

Теперь самое интересное — превратим кольцо в спираль. Делаем ровно то же самое, но угол крутим дольше (несколько оборотов), а радиус с каждым шагом чуть-чуть растим. Антенна выезжает, пока крутится.

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(20, 20, 40);

  let cx = 200;
  let cy = 200;
  let points = 200;   // сколько точек в спирали

  fill(255, 209, 64);
  for (let i = 0; i < points; i = i + 1) {
    let a = 0.25 * i;       // угол растёт — много оборотов
    let r = 1.2 * i;        // радиус растёт вместе с углом
    let x = cx + r * cos(a);
    let y = cy + r * sin(a);
    circle(x, y, 6);
  }
}

Результат: на тёмно-синем фоне закручивается жёлтая спираль из 200 маленьких точек. У центра точки лежат плотно и близко, а к краям расходятся всё дальше и дальше — получается завиток, как у ракушки или хвоста кометы. Чем больше i, тем больше и угол (точка повернулась дальше), и радиус (точка уехала от центра дальше) — поэтому она не возвращается на круг, а уходит наружу по дуге.

Поиграй с двумя числами. Множитель угла 0.25 отвечает за закрученность: меньше число — витки реже и спираль распахнутее, больше — плотнее закручена. Множитель радиуса 1.2 отвечает за скорость убегания от центра: больше — точки быстрее разлетаются. Поменяй одно число — и спираль полностью преобразится. Именно так художники-генеративщики подбирают форму: крутят пару коэффициентов, пока не понравится.

Обрати внимание на одну тонкость: точки у нас все одинакового размера (6 пикселей), но у центра их так много на маленькой площади, что они почти сливаются, а у края разбегаются. Это не баг, а природа спирали: за один оборот точка с большим радиусом проходит больший путь, поэтому соседние точки расходятся сильнее. Если хочешь, чтобы плотность была ровной по всей длине, нужно, наоборот, замедлять рост угла к краю — но для красивого завитка это и не нужно, неравномерность как раз и придаёт ему живость. Запусти этот скетч и просто посмотри, как из горстки чисел рождается то, что в природе встречается в раковинах улиток, шишках и даже в том, как закручивается ураган на спутниковых снимках.

Пример 3: мандала-розетка из лепестков

Соединим оба приёма в красивый симметричный узор. Поставим по кругу не точки, а вытянутые лепестки, и каждый развернём «носом» от центра. Для разворота используем push()/pop() и поворот системы координат — так удобнее, чем считать углы каждого лепестка вручную.

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  background(255, 245, 230);

  let cx = 200;
  let cy = 200;
  let petals = 16;   // число лепестков

  for (let i = 0; i < petals; i = i + 1) {
    let a = (TWO_PI / petals) * i;
    push();                 // запоминаем систему координат
    translate(cx, cy);      // переносим центр в середину холста
    rotate(a);              // поворачиваем на угол лепестка
    fill(255, 140, 30, 180);
    ellipse(90, 0, 70, 26); // лепесток на расстоянии 90 от центра
    pop();                  // возвращаем систему координат назад
  }

  // CodeChick в самом центре розетки
  fill(255, 209, 64);
  circle(cx, cy, 60);
  fill(255, 140, 30);
  triangle(cx + 22, cy - 5, cx + 42, cy, cx + 22, cy + 5);
}

Результат: на кремовом фоне распускается симметричная оранжевая розетка из 16 полупрозрачных лепестков, развёрнутых от центра, как лепестки ромашки или солнечные лучи. В сердцевине сидит жёлтый CodeChick. Лепестки полупрозрачные (последний параметр fill — alpha 180), поэтому там, где они находят друг на друга у центра, цвет становится насыщеннее — получается живой переливчатый узор.

Тут работает та же круговая расстановка: a = (TWO_PI / petals) * i делит круг на 16 равных углов. Но вместо ручного расчёта x и y мы делаем хитрее: translate ставит центр в середину холста, rotate(a) крутит весь «лист бумаги» на нужный угол, и тогда лепесток, нарисованный просто справа на расстоянии 90, оказывается уже повёрнутым. push() и pop() тут обязательны: они сохраняют и возвращают систему координат, иначе повороты будут накапливаться и узор поедет. Это второй, более удобный способ круговой расстановки — особенно когда элементы надо ещё и развернуть.

Частые ошибки и подводные камни

  • Считают углы в градусах без angleMode. Если написать cos(90), ожидая прямой угол, получится ерунда: 90 радиан — это куча оборотов. По умолчанию p5.js считает в радианах. Либо крути угол от 0 до TWO_PI, либо в самом начале поставь angleMode(DEGREES) и думай в привычных 360.

  • Путают sin и cos местами. Запомни: cos — это горизонталь (x), sin — вертикаль (y). Если поменять их, узор всё равно нарисуется, но «повернётся» не так, как ты ждал, а смешанные оси дадут странные перекосы. Держи пару в порядке x = ...cos, y = ...sin.

  • Забывают про центр cx, cy. Если написать просто x = r * cos(a) без + cx, центр круга окажется в левом верхнем углу холста (точке 0,0), и половина узора убежит за край экрана. Всегда прибавляй координаты центра, вокруг которого хочешь крутить.

  • Делят круг на ноль или ставят count = 0. Если count станет нулём, TWO_PI / count сломается, а цикл не нарисует ни одного элемента. Проверяй, что число элементов в кольце — хотя бы 1, а лучше 3 и больше, иначе «кольца» не видно.

  • Забывают pop() после push(). В примере с лепестками каждый push() должен иметь свой pop(). Если забыть pop(), повороты и сдвиги накопятся от кадра к кадру, и узор начнёт уезжать и крутиться сам по себе. Правило простое: открыл push() — закрой pop() в том же блоке.

Мини-практика: спиральный венок вокруг CodeChick

Собери свой узор, который соединяет всё из урока — кольцо, спираль и центр с цыплёнком. План такой:

  1. Возьми за основу Пример 2 со спиралью из точек.
  2. Сделай так, чтобы точки меняли размер: пусть ближе к центру они мелкие, а к краю крупнее. Подсказка — размер можно привязать к i: например let d = 3 + i * 0.05; и рисуй circle(x, y, d);.
  3. Добавь цвет, плывущий по спирали: переведи скетч в режим colorMode(HSB) в setup(), а внутри цикла задавай тон от шага — fill(i % 360, 80, 100);. Получится спираль-радуга.
  4. Посади в самый центр CodeChick (жёлтый круг + оранжевый клюв), как в Примере 1, чтобы спираль закручивалась прямо из него.

Когда заработает, поэкспериментируй: поменяй множитель угла 0.25 на 0.1 и на 0.5 — почувствуй, как меняется закрученность. Попробуй два множителя радиуса в разных запусках. А ещё можно сделать двойную спираль: добавь второй цикл с углом a = 0.25 * i + PI (сдвиг на полкруга) — и из центра пойдут сразу два рукава, как у галактики.

Итоги

Сегодня ты освоил рецепт любого кругового узора:

  • Точка на круге задаётся центром, радиусом и углом: x = cx + r * cos(a), y = cy + r * sin(a). Косинус — горизонталь, синус — вертикаль.
  • Кольцо — это угол по кругу при постоянном радиусе. Дели круг TWO_PI на число элементов и ставь по одному на каждом шаге цикла.
  • Спираль — то же кольцо, но радиус растёт вместе с углом. Два множителя (для угла и для радиуса) задают закрученность и скорость убегания от центра.
  • Углы в p5.js по умолчанию в радианах (полный круг — TWO_PI); для разворота элементов удобно крутить саму систему координат через translate, rotate и пару push()/pop().

Теперь ты умеешь не просто ставить фигуры в ряд, а раскладывать их по кругу и закручивать в спирали — а это половина всей генеративной графики, которую ты видишь в интернете. В следующем уроке мы продолжим тему паттернов и научимся строить сетки и решётки из вложенных циклов: ряды и столбцы элементов, из которых складываются мозаики, шахматки и пиксельные узоры. Увидимся на следующей странице — и обязательно покрути сегодня пару чисел в спирали, это затягивает!

Проверьте себя
1. Как из угла a и радиуса r получить координаты точки на окружности с центром (cx, cy)?
Ax = cx + r * cos(a), y = cy + r * sin(a)
Bx = cx + r * sin(a), y = cy + r * tan(a)
Cx = r * a, y = cy * a
Dx = cx * cos(r), y = cy * sin(r)
2. Чем спираль отличается от ровного кольца при той же расстановке точек?
AВ спирали радиус постоянный, а угол не меняется
BВ спирали радиус растёт вместе с углом, поэтому точки уходят от центра
CСпираль рисуется без цикла for
DВ спирали используется только sin, без cos
3. В каких единицах sin() и cos() в p5.js ждут угол по умолчанию?
AВ градусах (0–360)
BВ пикселях
CВ радианах (полный круг — TWO_PI)
DВ процентах от оборота
4. Зачем в примере с лепестками нужна пара push() и pop()?
AЧтобы ускорить отрисовку цикла
BЧтобы сохранить и вернуть систему координат, иначе повороты накопятся
CЧтобы поменять цвет заливки
DЧтобы остановить функцию draw()
5. Чтобы разложить 8 элементов ровным кольцом, какой угол взять для i-го элемента?
Aa = TWO_PI * i * 8
Ba = (TWO_PI / 8) * i
Ca = 8 / i
Da = i (просто номер элемента)
6. Что произойдёт, если в формуле точки забыть прибавить центр и написать просто x = r * cos(a)?
AУзор станет крупнее в два раза
BЦентр круга окажется в левом верхнем углу холста (0,0), и часть узора убежит за край
CНичего не изменится
DЦикл перестанет работать