Поскольку бизнес Кунчи был медленным, у меня было немного свободного времени, чтобы заняться творческим кодированием. В поисках вдохновения я наткнулся на знаменитое искусство Сола Левитта и решил воссоздать его с помощью JavaScript.

К концу этого поста мы нарисуем произведение Сола Левитта Квадрат, разделенный по горизонтали и вертикали на четыре равные части на холсте HTML5, используя p5js, популярный инструмент для творческого кодирования. Итак, приступим!

Обратите внимание, что для понимания части кода рекомендуется базовое знакомство с p5js. Вы можете посетить их документацию или проверить канал YouTube The Coding Train для ознакомления.

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

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

Мы поймем, как рисовать прямоугольник на холсте HTML5 и, более конкретно, как рисовать линии между двумя краями прямоугольника.

Точка

Точка - это точное местоположение на поверхности или плоскости, обычно обозначаемое точкой. Если мы рассмотрим нашу 2D-плоскость, представленную осью X и Y, тогда в нашем коде мы можем использовать массив размера 2 для обозначения точки, например, const point = [x, y].

Линия

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

В P5 есть удобный метод line(x1, y1, x2, y2) для рисования линии между двумя точками на 2D-плоскости.

Мы создадим функцию-оболочку, которая в конечном итоге вызовет линейный метод p5.

function drawLine(pointA, pointB) {
  p5.line(pointA[0], pointA[1], pointB[0], pointB[1]);
}

Четырехугольник

Четырехугольник буквально означает 4 стороны. Более точное определение из Википедии объясняет это дальше: «четырехугольник - это многоугольник с четырьмя ребрами (или сторонами) и четырьмя вершинами или углами». Некоторые примеры включают прямоугольник, квадрат, ромб и т. Д.

Так что же отличает квадрат, прямоугольник и другие четырехугольники друг от друга? Это внутренний угол и длина сторон по сравнению друг с другом. Например, все стороны прямоугольника соединены под углом 90 градусов, что делает две параллельные стороны (ширину и высоту) равными, в то время как для квадрата все стороны равны, а внутренний угол всегда равен 90 градусам.

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

Рисование прямоугольника на холсте в JavaScript

Давайте сначала настроим наш холст и нарисуем базовый прямоугольник с помощью rect(x, y, width, height)method p5.

export default function Sketch(p5) {
  p5.setup = () => {
    p5.createCanvas(300, 300);
    p5.noFill();
    p5.noLoop();
  };
  p5.draw = () => {
    p5.background(0);
    p5.stroke(255);
    // leave 10% margin on all sides
    const margin = 0.1;
    const x = p5.width * margin;
    const y = p5.height * margin;
    // adjust width & height based on margin
    let w = p5.width - 2 * x;
    let h = p5.height - 2 * y;
    p5.rect(x, y, w, h);
  };
  function drawLine(pointA, pointB) {
    p5.line(pointA[0], pointA[1], pointB[0], pointB[1]);
  }
}

Этот код создаст холст размером 300 x 300 и прямоугольник (фактически квадрат), скорректированный по полям.

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

Давайте создадим функцию с именем Rectangle, которая может хранить значения всех координат вершин прямоугольника. Мы представим вершины как точки a, b, c, d. Каждая вершина является точкой, поэтому может быть представлена ​​как const a = [x,y];

function Rectangle(x, y, width, height) {
  return {
    coordinates,
    draw,
  }
  function coordinates() {
      // adjust width & height as our scale might not be at zero
    const w = x + width;
    const h = y + height;
    return {
      a: [x, y],
      b: [w, y],
      c: [w, h],
      d: [x, h],
    };
  }
  function draw() {
      p5.rect(x, y, width, height);
  }
}

Укажите на расстоянии K

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

P (x, y) = (x1 + k (x2 - x1), y1 + k (y2 - y1))
где k - дробная часть расстояния

Итак, чтобы найти координаты точки, расположенной точно в середине ребра AB (вершины a и b составляют ребро AB), мы должны использовать k как 1/2 или 0,5 .

Мы можем написать эквивалентный Javascript как -

function pointCoordinatesOnLine(pointA, pointB, k) {
  const x = pointA[0] + k * (pointB[0] - pointA[0]);
  const y = pointA[1] + k * (pointB[1] - pointA[1]);
  return [x, y];
}

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

function Rectangle(x, y, width, height) {
  return {
    centerCoordinates,
    ...
   }
  ...
  function centerCoordinates(midpoints = {}) {
    // represent center coordinates of a particular side
    const midpointMapping = {
      ab: 'p',
      bc: 'q',
      cd: 'r',
      da: 's',
      ba: 'p',
      cb: 'q',
      dc: 'r',
      ad: 's',
    };
    const calculateMidpoint = (point1, point2) => {
      let [kA, pointA] = point1;
      let [kB, pointB] = point2;
      const edge = `${kA}${kB}`;
      midpoints[midpointMapping[edge]] = pointCoordinatesOnLine(
        pointA,
        pointB,
        0.5
      );
      return point2;
    };
    const coordinatesArray = Object.entries(coordinates());
    const sideDA = [
      coordinatesArray[0],
      coordinatesArray[coordinatesArray.length - 1],
    ];
    [...coordinatesArray, ...sideDA].reduce((point1, point2) =>
      calculateMidpoint(point1, point2)
    );
    return midpoints;
  }
...
}

Давайте воссоздадим наш прямоугольник на холсте, но вместо этого воспользуемся нашим методом Rectangle.draw.

Затем мы добавляем линии к средним точкам, используя вычисленные координаты, и делим наш прямоугольник на четыре части. Вершины средней точки представлены в коде как p, q, r, s, o, и здесь o просто означает центральную вершину прямоугольника.

p5.draw = () => {
  ...
  const { a, b, c, d } = rectangle.coordinates();
  const { p, q, r, s } = rectangle.centerCoordinates();
  const o = [x + w / 2, y + h / 2];
  drawLine(p, r);
  drawLine(q, s);
...
}

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

Соединительные края

Последний шаг, который нам нужно выяснить, - как провести горизонтальные, вертикальные и диагональные линии между краями.

Собственно, у нас уже есть все, чтобы провести эти линии. У нас есть все координаты прямоугольника, и мы можем использовать наш метод pointCoordiatnatesOnLine, чтобы найти координаты с каждой стороны на определенном расстоянии в нашем прямоугольнике. Итак, давайте создадим новую функцию, которая поможет нарисовать несколько линий между двумя краями.

p5.draw = () => {
  ...
  drawLines([a, p], [s, o], 12);
}
...
// helper function to draw lines between two
// edges at every k distance
function drawLines(edge1, edge2, k) {
  const [pointA, pointB] = edge1;
  const [pointC, pointD] = edge2;
  for (let i = 0; i < k; ++i) {
    const [x1, y1] = pointCoordinatesOnLine(pointA, pointB, i / k);
    const [x2, y2] = pointCoordinatesOnLine(pointC, pointD, i / k);
    drawLine([x1, y1], [x2, y2]);
  }
}

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

Потрясающие! Мы приближаемся. Нарисуем остальные линии и закончим набросок.

p5.draw = () => {
  ...
  drawLines([a, p], [s, o], 18);
  drawLines([p, o], [b, q], 18);
  drawLines([s, o], [s, d], 13);
  drawLines([d, r], [o, r], 13);
  drawLines([q, c], [q, o], 13);
  drawLines([c, r], [o, r], 13);
}

Большой! Вот наш окончательный результат. Выглядит довольно близко к оригиналу.

Давайте добавим цвет фона и изменим цвет обводки, чтобы он соответствовал исходной картине.

Итак, это все! Это было забавное упражнение для меня, оно потребовало некоторого творческого мышления, и я уверен, что есть куда больше возможностей для улучшения. Честно говоря, самым сложным было добавить все на средний 😅.

Окончательный код доступен как github gist.

Намаскар 🙏🏾

Этот пост изначально был размещен на varun.io