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

Это один из самых простых видов муаровых узоров, но их гораздо больше. Вот один, который я сделал из концентрических колец:

Да, мы собираемся узнать, как настроить P5 в современной среде разработки Javascript. Но самое интересное - это сделать эти выкройки самостоятельно. Итак, приступим к делу.

Настраивать

Если вы немного знаете о разработке Javascript, вы можете пропустить большую часть этого раздела. Для вас: знайте, что мы используем webpack-dev-server и Babel с ES6 stage 2. Нам также понадобится установленная библиотека P5. Если вы не понимаете, что я только что сказал: хорошие новости! Этот раздел для вас.

Я предполагаю, что у вас установлен Node и вы хоть немного можете использовать консоль. Если нет, следуйте этому руководству, чтобы установить Node / NPM и этот интерактивный курс CodeAcademy для терминала. Поверьте мне, даже если вы когда-либо планируете заниматься только творческим кодированием, это стоит ваших усилий.

Хорошо, приступим. Создайте папку для своего проекта. Затем перейдите к нему в консоли и запустите npm init. Вам зададут кучу вопросов. Заполните его как можно больше, но помните, что детали не имеют большого значения. Вы можете изменить их позже, а некоторые вещи, например точку входа, мы вообще не будем использовать.

Теперь мы можем использовать диспетчер пакетов узлов (NPM) для установки зависимостей нашего проекта. Нам нужно несколько: Webpack, webpack-dev-server, babel, babel-core, babel-loader, babel-preset-2015 и babel-preset-stage-2.

Давайте сначала установим их, а потом я объясню, что они делают:

npm install -s webpack webpack-dev-server babel babel-core babel-loader babel-preset-2015 babel-preset-stage-2

Хорошо, давайте рассмотрим их по одному.

Webpack, помимо прочего, представляет собой сборщик Javascript. Мы будем использовать его для объединения разных файлов Javascript в один. Это позволит нам раскрыть код в будущих статьях и упростить чтение и выполнение сценариев.

Webpack-dev-server - это веб-сервер с горячей перезагрузкой, который автоматически связывает код и запускает файл конфигурации вашего веб-пакета. По мере нашего развития это облегчит нашу жизнь. Вы также можете запустить Webpack в режиме прослушивания вручную с помощью webpack --colors --progress --watch, но использование сервера разработки удобнее и помогает бороться с проблемами CORS при локальной разработке.

Babel конвертирует кодовую базу из одного языка / языкового стандарта в другой. В этом случае он берет код, написанный на современном Javascript, и преобразует его в код, который может понять почти любой браузер. Babel - фантастический инструмент, который часто появляется при разработке Javascript. Например, при разработке React вы часто используете файлы .JSX. Эти файлы в основном представляют собой чистый Javascript, но в них встроен HTML. Весь код JSX можно преобразовать в чистый Javascript, но писать эквивалентный код было бы беспорядочно. Вот почему мы позволили Babel сделать это за нас.

И babel-preset-2015, и babel-preset-stage-2 - это определения Babel для ES6. Первый - это ES6 по годовому стандарту, а второй - для языковых функций, которые еще не были окончательно доработаны к моменту выхода стандарта ES6. В основном мы будем использовать if для оператора распространения (который выглядит как эллипс или…), который упрощает клонирование объектов данных. Если вы перейдете к разработке React, вы увидите много эллипсов.

Настройка проекта

Итак, у вас все установлено и готово. Есть несколько файлов, которые нам нужно настроить, прежде чем мы перейдем к разработке P5. Во-первых, давайте построим структуру. Вот как я настроил свой:

project
--+ index.HTML
--+ main.js
--+ package.json
--+ webpack.config.js

Достаточно просто. Если ваш проект растет, вы можете легко переместить код в каталог js.

Давайте заполним нашу HTML-страницу:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Moire Patterns</title>
        <link rel="stylesheet" href="./css.css">
    </head>
    <body>
        <div id="test"></div>
    </body>
    <script src="bundle.js"></script>
</html>

Опять же, просто. Вы могли заметить, что файл bundle.js на самом деле еще не существует. Это потому, что мы не будем его создавать. Скорее это будет выходной файл для Webpack. Кстати, нам нужно заполнить конфигурационный файл Webpack.

module.exports = {
  entry: "./main.js",
  output: {
      path: __dirname,
      filename: "bundle.js"
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['es2015', 'stage-2']
          }
        }
      }
    ]
  }
};

Хорошо, давайте не торопимся.

Entry - это главный файл Javascript вашего проекта. Я использую main.js, но вы можете называть его как хотите, пока он существует. Свойство output содержит объект с путем, который следует оставить как __dirname. Это просто дает вам локальный каталог для работы.

Имя файла - это имя связанного кода, который будет выводить Webpack. Это также то, на что мы будем ссылаться в index.html. Далее идут модуль и загрузчики. Модули - это связки кода, которые что-то делают. Думайте о них, как о пакетах Node, но только для Webpack.

Загрузчики обрабатывают чтение на разных языках. Мы заботимся о загрузке Babel, чтобы наш код переносился из современного Javascript во что-то, что может запускать даже Internet Explorer. Тест - это способ сравнить разные файлы, чтобы увидеть, какое правило следует применить. Обратите внимание на команду exclude: она останавливает тестирование файлов в каталоге node_modules, что может занять очень много времени. Наконец, мы предоставляем некоторые параметры для babel-loader, которые и используются. Это две предустановки: es2015 и stage-2. es2015 - это ES6, вместе со всеми языковыми функциями, добавленными с ним. Однако некоторые из этих функций не были полностью доработаны к моменту официального выпуска ES6, поэтому есть небольшое исправление под названием stage-2. Я в основном включил это, потому что оператор распространения безумно мощный. Вы увидите, что он используется, когда мы перейдем к кодированию, но знайте, что разработка на React широко использует его.

Последняя строчка. Мы собираемся добавить пользовательскую команду в файл package.json для запуска webpack-dev-server. Вы можете пропустить это и вместо этого запустить сервер напрямую, но полезно знать, как создавать команды. Откройте файл package.json и найдите объект scripts:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
}

Добавим строку для нашей команды.

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "echo 'Starting Webpack dev server! \n' && webpack-dev-server"
}

Итак, мы предоставляем свойство name, которое содержит строку. Эта строка интерпретируется консолью. Если вы попытаетесь ввести echo 'hello world!' в консоль, вы увидите, что консоль распечатывает hello world! Команда && сообщает консоль, чтобы также запустить следующую команду, которая в данном случае является выполнением webpack-dev-server.

Последняя вещь. Давайте добавим простой CSS, чтобы привязать холст к левому верхнему углу:

canvas{
    position: fixed;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
}

После этого мы, наконец, готовы разработать наш муаровый узор!

Шаблон

Хорошо, нам нужен базовый HTML для поддержки нашего скетча. Ничего особенного, просто классический шаблон:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Moire Patterns</title>
        <link rel="stylesheet" href="./css.css">
    </head>
    <body>

    </body>

</html>

Нам также нужна ссылка на наш связанный код CSS. Мы будем писать наши материалы в файле main.js, но нам нужно добавить файл bundle.js на сайт. Давайте сделаем это внизу:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Moire Patterns</title>
        <link rel="stylesheet" href="./css.css">
    </head>
    <body>

    </body>
    <script src="bundle.js"></script>
</html>

У нас также есть ссылка на еще не созданный файл CSS. Итак, давайте сделаем это и дадим ему один селектор, нацеленный на элемент холста:

canvas{
    position: fixed;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
}

Это обеспечит позиционирование холста в абсолютном углу независимо от того, что еще мы добавляем на страницу. Я использую фиксированный здесь, поэтому он всегда виден, но не стесняйтесь смешивать его, как считаете нужным.

Теперь давайте настроим наш файл main.js. Во-первых, импорт P5:

import * as p5 from './p5.js';

Затем создайте новый объект P5:

import * as p5 from './p5.js';
const P5 = new p5();

Сделанный таким образом, P5 будет ожидать, что функция будет передана. Итак, нам нужно создать ее, а затем передать ее новому объекту p5.

import * as p5 from './p5.js';
let s = (sk) => {    

}

const P5 = new p5(s);

Хорошо, теперь мы переходим к самой интересной части, делая наброски. Большая часть нашего развития будет происходить внутри созданной нами функции. Во время работы P5 проверяет наличие определенных функций, которые, если они есть, он будет вызывать. Вы можете предоставить множество различных функций (вы можете увидеть их все, перейдя к документации P5 и проверив категорию структуры), но мы будем работать с двумя, с которыми мы будем работать: setup и рисовать. Давайте создадим их сейчас.

let s = (sk) => {    
    sk.setup = () =>{

    }

    sk.draw = () =>{

    }
}

Настройка используется для создания вашего эскиза. Он запускается только один раз при запуске скрипта. Draw же вызывается много раз в секунду. При желании вы можете изменить скорость, вызвав sk.frameRate() и передав нужные кадры в секунду.

Хорошо, давайте создадим холст. Вы делаете это с помощью createCanvas (). Давайте добавим это сюда вместе с некоторыми параметрами:

sk.setup = () =>{
sk.createCanvas(window.innerWidth,window.innerHeight);
}

Две ссылки на окно получают размеры отображаемой страницы браузера. Вот почему мы присвоили ему абсолютное значение в CSS. Если вы хотите поместить этот холст как дочерний для другого элемента, вы можете связать с ним другую функцию: sk.createCanvas(window.innerWidth,window.innerHeight).parent('elementId');

Но нам это не нужно.

Теперь, если вы проверите свою страницу, вы увидите полноэкранный элемент холста. Потрясающие! Теперь мы можем нарисовать на нем что-нибудь. Конечно, вы все еще смотрите на белый экран. Давайте исправим это:

sk.setup = () =>{
    sk.createCanvas(window.innerWidth,window.innerHeight);
    sk.background(40);
}

И у нас есть предыстория. Рисовать на холсте действительно так просто. Если вы хотите нарисовать коробку, вы можете сделать sk.rect(x,y,width,height);. Если вы хотите нарисовать линию, вместо этого сделайте sk.line(x1,y1,x2,y2);. Рисование точки выполняется с помощью sk.point(x,y);.

Если бы вы попробовали их, то могли заметить, что линию и точку было трудно увидеть. их немного трудно увидеть. Они оба очень тонкие, и сочетание черного на темно-сером - не самое заметное сочетание. Так что позвольте исправить это:

sk.setup = () =>{
    sk.createCanvas(window.innerWidth,window.innerHeight);
    sk.background(40);
    sk.stroke(200);
    sk.strokeWeight(3);
}

Если вы протестируете его сейчас, вы увидите, что ситуация значительно улучшилась. Точка и линия становятся толще и светлее. Коробка, однако, по-прежнему заполнена белым. Вы можете исправить это с помощью sk.fill(0);, чтобы заполнить его черным.

В случае sk.fill(0); я передаю в функцию значение оттенков серого. Во многом так же, как CSS имеет сокращенные свойства, P5 тоже. Это эквивалентно передаче в функцию трех значений в виде значений красного, зеленого и синего, например sk.fill(0,0,0);. Если вам нужен другой цвет, вы можете передать значения каждому из них от 0 до 255. Четвертый аргумент может быть добавлен для обработки прозрачности.

Вы также можете изменить способ обработки цветов, если предпочитаете другие цветовые режимы:

//Puts the sketch into Hue, Saturation and Brightness mode
sk.colorMode(HSB);
sk.stroke(200,100,0);

Если вы запустите их, вы заметите, что первый желтоватый, а второй фиолетовый.

P5 также принимает значения цвета CSS в виде строк. Это означает, что вы можете вызвать sk.stroke('#a97'); для установки шестнадцатеричного значения или даже sk.stroke('rgba(255,204,0,0.5)');.

Смело выбирайте цвет для своего муарового узора!

Убрав цвета в сторону, давайте поработаем над первым муаровым узором. Мы сделаем это простым с помощью поля из случайных точек. Когда мы закончим, это будет выглядеть примерно так:

Чтобы сделать точку случайной, мы запускаем sk.point(window.innerWidth,window.innerHeight);. Если нам нужно больше одной точки, мы вставляем их в цикл:

for(let i=0;i<1000;i++){
    sk.point(window.innerWidth,window.innerHeight);
}

А теперь на экране тысяча случайных точек. довольно круто, правда? Однако это еще не муар. Нам нужно продублировать узор, а затем сместить его. При установке положения точек мы использовали случайные числа без начального числа. Из-за этого мы не сможем получить то же поле из точек, просто вызвав цикл снова. Так как же нам продублировать узор? Есть много способов сделать это. Мы могли бы изменить цикл for, чтобы поместить координату в массив, например:

let arr [];
for(let i=0;i<1000;i++){
    arr.push({x:window.innerWidth,y:window.innerHeight})
}

А затем для рендеринга мы могли бы сделать цикл forEach:

arr.forEach(el=>sk.point(el.x,el.y)); 

Что эквивалентно:

arr.forEach(function(el){
    sk.point(el.x,el.y);
});

Это позволило бы нам рисовать все наши точки столько раз, сколько захотим, и мы сделаем это в следующей статье. Но пока есть подход получше. В качестве примера предположим, что сначала мы хотим сдвинуть наши точки немного вправо:

arr.forEach(el=>sk.point(el.x,el.y));
arr.forEach(el=>sk.point(el.x+5,el.y));

Это работает. Но что, если мы хотим повернуть наши точки? Затем математика усложняется. Есть гораздо лучший способ справиться с этим: мы берем все точки и превращаем их в изображение. Затем вы можете манипулировать всеми точками, как если бы они были единым целым, используя инструменты, которые P5 уже предоставляет вам.

Давайте выбросим петли и начнем заново. Сначала мы собираемся создать рисунок:

let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);

Обратите внимание, что мы назвали это так же, как мы изначально создавали холст. Это потому, что есть много общего. Думайте о графике как о неотрисованных виртуальных холстах. Вы можете нарисовать их, установить их фон, цвет заливки, обводку и т. Д. Но они будут нарисованы только тогда, когда вы им скажете, в установленном вами положении.

Давайте изменим его так, чтобы наши точки отображались на графике, а не на холсте:

let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
for(let i=0;i<1000;i++){
    gfx.point(
        Math.random()
        *window.innerWidth,Math.random()
        *window.innerHeight
    );
}

Вы, наверное, заметили, что на экране сейчас ничего нет. Нам нужно это отрендерить. Мы можем сделать это с помощью функции image:

for(let i=0;i<1000;i++){
    gfx.point(
        Math.random()
        *window.innerWidth,Math.random()
        *window.innerHeight
    );
    ///The new lines
    sk.image(gfx,0,0);

Вот и все. У вас есть куча черных точек. Но разве они не должны быть легче этого? Помните: мы применили наши stroke и strokeWeight к холсту. Созданная нами графика действует как отдельный холст. Итак, чтобы стилизовать наши точки, нам нужно изменить то, что мы называем штрихом и strokeWeight, примерно так:

sk.setup = () =>{
    let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);

    sk.createCanvas(window.innerWidth,window.innerHeight)
    sk.background(40);
    //Changed stroke and strokeWeight reference from sk to gfx
    gfx.stroke(200);
    gfx.strokeWeight(3);

    for(let i=0;i<1000;i++){
        gfx.point(
            Math.random()
            *window.innerWidth,Math.random()
            *window.innerHeight
        );
    }
    sk.image(gfx,0,0);
}

И вот, у нас есть поле из точек.

Хорошо, а теперь самое интересное: давайте сделаем наш первый муаровый узор. Для этого нам нужна новая графика, которая точно соответствует нашему предыдущему. Мы можем сделать это, продублировав объект с помощью Object.assign (), например: Object.assign({},gfx);. Первый аргумент - это новый объект. Второй - это объект, который нужно скопировать в него. Давайте добавим это в наш код:

sk.setup = () =>{
    let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
    let gfx2;
    sk.createCanvas(window.innerWidth,window.innerHeight)
    sk.background(40);
    gfx.stroke(200);
    gfx.strokeWeight(3);

    for(let i=0;i<1000;i++){
        gfx.point(
            Math.random()
            *window.innerWidth,Math.random()
            *window.innerHeight
        );
    }
    //creating the cloned object
    gfx2 = {...gfx};
    sk.image(gfx,0,0);
    sk.image(gfx2,2,2);
}

Это дает вам что-то вроде этого:

Но вот в чем дело: в ES6 есть лучший способ дублировать объект, чем Object.assign. Помните оператор распространения, о котором мы говорили? Вот наш шанс его использовать. Измените эту строку на gfx2 = {...gfx};. Это также копирует объект в другой объект и дает тот же результат, только он короче и, в конечном итоге, более полезен. Spread также работает с массивами, так что имейте это в виду.

Вы могли заметить, что второй вызов изображения смещает позицию на 2 по осям X и Y. Это означает, что мы меняем происхождение изображения. Вот как мы смещаем эти точки. Это может быть немного более очевидным, если вы измените его на большее значение, не стесняйтесь экспериментировать.

Переменный объезд

Хорошо, краткий обзор того, как Javascript обрабатывает переменные. Вы могли заметить, что я переместил объявление переменной в верхнюю часть кода. Это не обязательно в Javascript, потому что Javascript - немного странный зверь.

Перед выполнением кода интерпретатор просматривает код и перемещает все объявления переменных вверх, как я делал это вручную. Затем браузер интерпретирует код построчно. Из-за этого переменные существуют до того, как вы их объявите, просто со значением undefined. Итак, это:

let foo = () =>{
    aFunction1();
    aFunction2();
    let bar = "Hey!";
}

становится это для браузера:

let foo = () =>{
    let bar = undefined;
    aFunction1();
    aFunction2();
    bar = "Hey!";
}

В большинстве случаев это не имеет значения. Но когда дело доходит до функций, это имеет значение. Объявление функции через function(){} и let x = function(){} (или let x = () => {}, если вы предпочитаете ES6) не идентичны. Попробуйте эти два фрагмента:

x();
function x(){
    console.log("it works!")
}

А также:

x();
let x = () =>{
    console.log("it works!")
}

Вы заметили, как первое работает, а второе - нет? Первый перемещает объявление функции наверх, а второй перемещает неопределенную переменную наверх, которая затем назначается в коде. Когда браузер ищет первую, появляется ссылка на уже отображенную функцию. Когда он ищет его во втором, он вместо этого видит переменную, которая еще не была назначена. Таким образом, вызов его как функции прерывается.

Вернуться к коду

Хорошо, мы действительно близки к тому, чтобы получить наш первый муаровый узор. Почему бы не повернуть графику вместо смещения по значению? В P5 это просто. Давайте вызовем l sk.rotate(1); перед рендерингом второго изображения.

Обратите внимание, что мы вызываем вращение эскиза, а не графики. Мы хотим повернуть все изображение, а не только его часть. Вызов gfx2.rotate(1); повернёт любые фигуры, нарисованные на этом графике, но не сам график.

Наш новый код выглядит так:

sk.setup = () =>{
    let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
    let gfx2;
    sk.createCanvas(window.innerWidth,window.innerHeight)
    sk.background(40);
    gfx.stroke(200);
    gfx.strokeWeight(3);
    gfx.line(0,0,window.innerWidth,0);
    for(let i=0;i<1000;i++){
        gfx.point(Math.random()*window.innerWidth, Math.random()*window.innerHeight);
    }

    gfx2 = {...gfx};
    sk.image(gfx,0,0);
    sk.rotate(1);
    sk.image(gfx2,0,0);
}

Но подождите, это выглядит не так. Если вы запустили это, вы должны увидеть проблему. На самом деле это не выглядит так, как будто точки повернуты:

Время диагностики. Давайте попробуем понять, что пошло не так, проведя линию в верхней части рисунка:

let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
let gfx2;
sk.createCanvas(window.innerWidth,window.innerHeight)
sk.background(40);
gfx.stroke(200);
gfx.strokeWeight(3);
//Diagnostic line, remove after troubleshooting
gfx.line(0,0,window.innerWidth,0);

Итак, у нас две большие проблемы. Во-первых, он слишком сильно вращается. Во-вторых, он вращается из угла. Давайте сначала исправим поворот.

Итак, вы, вероятно, знаете, что можете описать круг, используя градусы или радианы. Круг равен 360 градусам, но его также можно описать в радианах как 2π. Чтобы преобразовать их, знайте, что 1 радиан = 180 градусов / π. Это примерно 57,3 градуса, и, глядя на наш эскиз, похоже, что это примерно то, на сколько мы его повернули.

Хотя радианы невероятно полезны, для нас будет проще использовать поворот в градусах. Нам нужно изменить angleMode, в котором работает P5. Вызовите sk.angleMode(sk.DEGREES);. Обратите внимание, что sk.DEGREES просто возвращает строку «градусов». Вы можете подтвердить это с помощью console.log, если хотите. Это константа, включенная в P5. Вы будете видеть их время от времени.

Добавляя это в наш код, мы получаем:

sk.setup = () =>{
    let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
    let gfx2;

    sk.createCanvas(window.innerWidth,window.innerHeight);

    //added an angleMode
    sk.angleMode(sk.DEGREES);
    sk.background(40);
    gfx.stroke(200);
    gfx.strokeWeight(3);
    gfx.line(0,0,window.innerWidth,0);
    for(let i=0;i<1000;i++){
        gfx.point(Math.random()*window.innerWidth, Math.random()*window.innerHeight);
    }

    gfx2 = {...gfx};
    sk.image(gfx,0,0);
    sk.rotate(1);
    sk.image(gfx2,0,0);
}

Лучше, но все равно вращается из-за угла. Пора это исправить. Левый верхний угол изображения - это его исходная точка, из которой создается изображение, и все относительные точки отсчитываются относительно. На самом деле нам нужно, чтобы исходная точка находилась в центре изображения. Прямо над angleMode добавьте imageMode:

sk.imageMode(sk.CENTER);
sk.angleMode(sk.DEGREES);
sk.background(40);
gfx.stroke(200);
gfx.strokeWeight(3);

Мы также передаем константу в imageMode, как и в случае с angleMode. Вы можете работать с множеством констант. Вы можете увидеть их все в документации P5.

Запуск кода сейчас покажет, что точки больше не покрывают весь экран. У холста тоже есть исходная точка, откуда мы и рисуем изображение. Мы могли бы исправить это, переместив каждое изображение на половину экрана вправо, но давайте будем последовательными и вместо этого изменим место рисования с помощью translate:

sk.imageMode(sk.CENTER);
sk.angleMode(sk.DEGREES);
//Translate half a screen in X and Y
sk.translate(window.innerWidth/2,window.innerHeight/2);
sk.background(40);
gfx.stroke(200);
gfx.strokeWeight(3);

Вот и простой муаровый узор!

К концу ваш код должен выглядеть примерно так:

import * as p5 from './p5.js';
//a P5 moire pattern.
let s = (sk) => {
    let layers = [];

    // sk.translate(window.innerWidth/2,window.innerHeight/2);
    sk.setup = () =>{
        let gfx = sk.createGraphics(window.innerWidth,window.innerHeight);
        let gfx2;

        sk.createCanvas(window.innerWidth,window.innerHeight);
        sk.angleMode(sk.DEGREES);
        sk.imageMode(sk.CENTER);
        sk.translate(window.innerWidth/2,window.innerHeight/2);
        sk.background(40);
        gfx.stroke(200);
        gfx.strokeWeight(3);
        gfx.line(0,0,window.innerWidth,0);
        for(let i=0;i<1000;i++){
            gfx.point(Math.random()*window.innerWidth, Math.random()*window.innerHeight);
        }

        gfx2 = {...gfx};
        sk.image(gfx,0,0);
        sk.rotate(1);
        sk.image(gfx2,0,0);
    }

}
const P5 = new p5(s);

Это примерно такой же простой узор муара, как и есть, но он дает нам хороший материал для работы в следующих статьях. Нам еще предстоит многое обсудить. Вы заметили, что мы никогда ничего не добавляли в функцию draw? Мы собираемся заняться этим в ближайшее время, когда начнем рисовать объекты в соответствии со значениями, переданными из элементов пользовательского интерфейса. Но сначала я хочу создать более интересные паттерны, чем то, что вы видите здесь.

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