Как нарисовать шесть линий, симметрично расположенных по радиусу в Canvas?

Я пытаюсь создать приложение для рисования снежинок в Canvas, очень похожее на это: http://www.pumpkinpirate.info/ks/.

По сути, если вы рисуете точку, она должна повториться еще 5 раз в виде симметричного круга на холсте. Вот иллюстрация:

концепция снежинки

Я знаю, как заставить холст рисовать что-то несколько раз, но мои математические способности ограничены, и мне интересно, как рассчитать положение 5 скопированных точек по отношению к исходной.

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

Мне интересно, могу ли я сделать что-то похожее на то, что они делают здесь: Инвертировать координаты X и Y на холсте HTML5

Просто для фона я изначально начал с 6 обрезанных холстов, где 5 были повернуты и копировали первый, но я понял, что этот подход на самом деле не работает, отчасти потому, что холсты слегка перекрывают друг друга (но были и другие проблемы). слишком).

Вот код, который у меня есть до сих пор:

drawing = false;
startX = 0;
startY = 0;
imageData = null;

const canvas = document.getElementById("canvas");

canvas.addEventListener("mousedown", startPosition());
canvas.addEventListener("mouseup", endPosition());
canvas.addEventListener("mousemove", draw());

function startPosition(e) {
  const canvas = this;
  const ctx = canvas.getContext("2d");

  startX = e.clientX;
  startY = e.clientY;
  imageData = ctx.getImageData(0,0,canvas.width,canvas.height);
  drawing = true;
}

function endPosition(e) {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  ctx.lineWidth = 4;
  ctx.lineCap = "round";
  ctx.moveTo(startX, startY);
  ctx.lineTo(e.clientX, e.clientY);
  ctx.strokeStyle = '#ffffff';
  ctx.stroke();
  ctx.beginPath();

  drawing = false;
}
function draw(e) {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  if(!drawing) {
    return;
  }

  ctx.putImageData(imageData, 0, 0);
  ctx.lineWidth = 4;
  ctx.lineCap = "round";
  ctx.moveTo(startX, startY);
  ctx.lineTo(e.clientX, e.clientY);    
  ctx.strokeStyle = '#ffffff';
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(e.clientX, e.clientY);

  //draw line 2
  ctx.moveTo(startX - 50, startY - 50);
  ctx.lineTo(e.clientX - 50, e.clientY - 50);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(e.clientX - 50, e.clientY - 50);

  //draw line 3
  ctx.moveTo(startX - 100, startY - 100);
  ctx.lineTo(e.clientX - 100, e.clientY - 100);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(e.clientX - 100, e.clientY - 100);
}

person hmoore    schedule 06.01.2020    source источник
comment
Что касается математики: я думаю, вам нужно 12 эквивалентных баллов, а не 6. Посмотрите на зубцы на эта снежинка: каждая конечная точка имеет 12 копий: 6 справа и 6 слева от каждой ветки. Математически говоря, вам нужно 5 точек, сгенерированных поворотом на 60 градусов вокруг центра, и остальные 6 точек, сгенерированные из них с помощью отражения (в связанной снежинке представьте отражение относительно вертикальной оси, которое соответствует преобразованию (x,y) <-> (-x,y).   -  person Andras Deak    schedule 07.01.2020
comment
Если вы используете только 6 повернутых версий, вы получите такие вещи, как ="nofollow noreferrer">это, но с 6 ногами, а не с 3 как на картинке. Вам не нужны ступни только с одной стороны каждой ноги.   -  person Andras Deak    schedule 07.01.2020
comment
Спасибо, Андреас! Да я вижу, что вы говорите. Моим вдохновением для этого послужил генератор снежинок в адвент-календаре Жаки Лоусон, который состоит из 6 разделов, но каждый раздел отражает сам себя по мере того, как вы рисуете, так что в итоге у вас получается 12.   -  person hmoore    schedule 07.01.2020


Ответы (1)


Чтобы повернуть точку вокруг другой точки

const center = {x: 100, y: 100};   // Point to rotate around
const point = {x: ?, y: ?};        // Point to rotate
const rotate = (Math.PI * 2) / 5;  // Rotate 1/5th of 360


// Get the vector from center to point
const vx = point.x - center.x;
const vy = point.y - center.y;

// Get the transformation (2D uniform rotate)
const xAx = Math.cos(rotate);
const xAy = Math.sin(rotate); 

// Transform the vector (rotates) and translate back to center
const tx = vx * xAx - vy * xAy + center.x;
const ty = vx * xAy + vy * xAx + center.y;

Как функция

function rotate(point, center, rotate, result = {}) {
    const vx = point.x - center.x;
    const vy = point.y - center.y;
    const xAx = Math.cos(rotate);
    const xAy = Math.sin(rotate); 
    result.x = vx * xAx - vy * xAy + center.x;
    result.y = vx * xAy + vy * xAx + center.y;
    return result;
}

Чтобы создать 5 точек из 1, используя вышеуказанную функцию

const slices = 5; // number of rotations
const center = {x: 100, y: 100}; // center point

// Rotates point steps times around center
// Returns array of points
function rotateAll(point, steps, result = []) {
    const ang = Math.PI * 2 / steps;
    result.push(point);                      // Add first point
    for (let rot = 1; rot < steps; rot++) {  // Add remaining points
        result.push(rotate(point, center, rot * ang));
    }
    return result;
}

// Usage
const points = rotateAll({x: 10, y: 20}, slices);
person Blindman67    schedule 06.01.2020
comment
Вам нужно 5 поворотов с одной шестой окружности (PI/3), потому что есть шесть копий (но у вас уже есть исходная точка). - person Andras Deak; 07.01.2020
comment
@AndrasDeak Константа slices определяет количество баллов. Нижний фрагмент. - person Blindman67; 07.01.2020
comment
В верхнем у вас есть (Math.PI * 2) / 5; // Rotate 1/5th of 360, что не может быть правильным. И я не знаю JS (?), но мне кажется, что slices передается как steps в rotateAll, поэтому вы снова передаете 5, который переходит в const ang = Math.PI * 2 / steps;. Это по-прежнему будет означать поворот на 72 градуса, а не на 60 градусов. Вам нужно steps итераций цикла, но Math.PI * 2 / (steps + 1) в качестве угла поворота. - person Andras Deak; 07.01.2020
comment
@AndrasDeak Первое значение произвольное, может быть любым. Функция rotateAll возвращает массив с аргументом steps точек. Если значение steps равно 5, то 5 точек равномерно распределены по кругу, только последние 4 вращаются. - person Blindman67; 07.01.2020
comment
Ах, хорошо, ваш цикл начинается с 1, а не с 0. Тогда я понял, спасибо. В этом случае я бы просто изменил все пятерки на шестерки, потому что, похоже, именно об этом спрашивает ОП (см. их изображение и знания о снежинках в целом). - person Andras Deak; 07.01.2020
comment
Спасибо Blindman67! Это работает отлично. Теперь мне просто нужно выяснить, как отразить шесть точек, как предложил Андреас Дик. Вот код приложения для рисования снежинок: codepen.io/moorehannah/pen/VwYyERG (отказ от ответственности: по какой-то причине рисуемая точка не соответствует положению курсора на 100% в codepen, но это соответствует моей локальной среде, поэтому я не собираюсь беспокоиться об этом прямо сейчас) - person hmoore; 07.01.2020
comment
@hmoore Зеркальная функция (сокращенные имена аргументов: p указывает на зеркальное отображение, mX зеркальное отображение оси X, mY зеркальное отображение оси Y в виде логического значения, истинно для оси зеркального отображения, c является центром (указывает на зеркальное отражение), а r возвращает результат.) функция function mirrorPoint(p, mX, mY, c, r = {}) {r.x = (p.x - c.x) * (mX ? -1 : 1) + c.x; r.y = (p.y - c.y) * (mY ? -1 : 1) + c.y; return r;} Использование: для отражения по осям X и Y используйте const mirroredPoint mirrorPoint({x: 10, y: 20}, true, true, center); (center, как используется в ответе) - person Blindman67; 07.01.2020
comment
Круто, большое спасибо @Blindman67! Это прекрасно! codepen.io/moorehannah/pen/VwYyERG - person hmoore; 07.01.2020