Холст, сделайте изображение по кривой Безье

#html #animation #canvas #bezier

Вопрос:

Я хочу создать анимацию с изображением холста, следующего за полукругом. Сначала я попробовал с холстом arc , но нашел, что с ним все проще bezier curve .

введите описание изображения здесь

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

 var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');

var statue = new Image();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';

function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
  var x =
    Math.pow(1 - percent, 2) * startPt.x  
    2 * (1 - percent) * percent * controlPt.x  
    Math.pow(percent, 2) * endPt.x;
  var y =
    Math.pow(1 - percent, 2) * startPt.y  
    2 * (1 - percent) * percent * controlPt.y  
    Math.pow(percent, 2) * endPt.y;
  return { x: x, y: y };
}

const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };

var percent = 0;

statue.addEventListener('load', () => {
  animate();
});

function animate() {
  //console.log(percent);
  ctx.clearRect(0, 0, c.width, c.height);
  percent > 1 ? (percent = 0) : (percent  = 0.003);
  var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);

  ctx.drawImage(
    statue,
    0,
    0,
    statue.width,
    statue.height,
    point.x - 50,
    point.y - 50,
    100,
    100
  );
  //ctx.fillRect(point.x, point.y, 10, 10);
  requestAnimationFrame(animate);
} 
 <!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="600" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<script>

</script> 

</body>
</html> 

Как правильно это сделать?

Комментарии:

1. Можете ли вы объяснить, почему кривая Безье проще, потому что, как человек, который немного разбирается в кривых Безье: дуга окружности намного проще, чем кривая Безье.

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

3. за счет нелинейного движения по траектории и исчисления вместо тригонометрии, что является произвольной разницей (квадратичное вычисление сложнее и дороже, чем математика дуги)

Ответ №1:

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

Затем вам нужно будет использовать ctx.translate() и ctx.rotate() изменить матрицу преобразования вместо установки позиции в .drawImage() вызове. ( .setTransform() Вызов в начале метода анимации сбрасывает матрицу для каждого кадра.)

Я также добавил сюда эффект «луковой шкурки», чтобы движение было лучше видно.

 var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var statue = new Image();
statue.src = "https://i.ibb.co/3TvCH8n/liberty.png";

function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
  var x =
    Math.pow(1 - percent, 2) * startPt.x  
    2 * (1 - percent) * percent * controlPt.x  
    Math.pow(percent, 2) * endPt.x;
  var y =
    Math.pow(1 - percent, 2) * startPt.y  
    2 * (1 - percent) * percent * controlPt.y  
    Math.pow(percent, 2) * endPt.y;
  return { x: x, y: y };
}

const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };

var percent = 0;

statue.addEventListener("load", () => {
  ctx.clearRect(0, 0, c.width, c.height);
  animate();
});

function animate() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  // "Onion skin" effect so the last frame is slightly retained to better show the motion.
  ctx.fillStyle = "rgba(255,255,255,0.1)";
  ctx.fillRect(0, 0, c.width, c.height);
  
  percent = (percent   0.003) % 1;
  
  var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
  var lastPoint = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent - 0.003);
  var angle = Math.atan2(lastPoint.y - point.y, lastPoint.x - point.x);
  
  // Debug pointer line
  ctx.beginPath();
  ctx.moveTo(point.x, point.y);
  ctx.lineTo(point.x   Math.cos(angle) * 50, point.y   Math.sin(angle) * 50);
  ctx.stroke();
  
  // Actual drawing
  ctx.translate(point.x, point.y);
  ctx.rotate(angle);
  ctx.drawImage(statue, 0, 0, statue.width, statue.height, -50, -50, 100, 100);
  requestAnimationFrame(animate);
} 
 <canvas id="myCanvas" width="600" height="400" style="border:1px solid #d3d3d3;">
  

Комментарии:

1. Ничего не вычисляя и просто позволяя холсту выполнять всю тяжелую работу, рассказывая ему, как перевести()/повернуть (), еще приятнее: математика вообще не требуется.

Ответ №2:

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

Мы наблюдаем, что мы имеем дело с круговым путем, который имеет (300 600) в качестве начала координат, радиусом 500 и начальным углом (в радианах) 0,9272951769 и конечным углом (в радианах) 2,21429922, поэтому мы можем сдвинуть нашу систему координат так, чтобы (0,0) было «действительно» (300 600), а затем, когда мы поворачиваем систему координат вокруг этого нового (0,0) на любой угол, все, что нам нужно сделать, это нарисовать объекты в (радиус, 0)

Поскольку система координат вращается за нас, нам не нужно на самом деле вычислять какие-либо точки. Мы уже знаем, где мы хотим, чтобы все было: на расстоянии radius от источника.

 cvs.width = 600;
cvs.height = 200;
const ctx = cvs.getContext('2d');
const statue = new Image();
statue.onload = () => animate();
statue.src = 'https://i.ibb.co/3TvCH8n/liberty.png';
const sWidth = sHeight = 80;

// circle values matching your path:
const origin = {x: 300, y: 600 };
const radius = 500;
const start = 0.9272951769;
const end = 2.21429922;

// animation values
let step = 0;
const totalSteps = 120;
const stepSize = (end - start)/totalSteps;

// And our drawing function
function animate() {
  ctx.resetTransform();
  ctx.clearRect(0, 0, cvs.width, cvs.height);

  if (step === totalSteps) step = 0;
  const angle = start   step   * stepSize;

  // first, change the coordinate system so that we don't need
  // to compute *anything* to draw it in the right place:
  ctx.translate(origin.x, origin.y);
  ctx.rotate(-angle);
  
  // Then we draw the debug line:
  ctx.beginPath();
  ctx.moveTo(0,0);
  ctx.lineTo(radius, 0);
  ctx.stroke();
  
  // And then we draw the image. However, the image is upright
  // when (radius,0) lines up with the x-axis, which means it's
  // actually going to look rotated compared to our line. So:
  // again, we update the coordinate system to do the work for us.
  // we update it so that (radius,0) becomes (0,0), we then rotate
  // it a quarter turn, and then we draw our image at (0,0).
  ctx.translate(radius, 0);
  ctx.rotate(Math.PI/2);

  // of course, (0,0) is the image's top-left corner, so if we
  // want to center it, we can do one more translation:
  ctx.translate(-sWidth/2, -sHeight/2);
  ctx.drawImage(statue, 0, 0, sWidth, sHeight);

  // and move on to the next frame
  requestAnimationFrame(animate);
} 
 <canvas id="cvs"></canvas> 

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

И небольшой известный факт: все браузеры делают HTML-элементы с id атрибутом доступными для JS, используя этот идентификатор в качестве имени переменной. Итак , в данном случае у нас есть a <canvas id="cvs"> , что означает, что на стороне JS есть переменная, называемая cvs , которая указывает на наш элемент canvas.

Комментарии:

1. Хм, IIRC квадратичный путь Безье не может точно представлять круг?

2. Правильно, но первоначальный пост начинался с того, что они хотели полукруг, но затем использовали квадратичный Безье «потому что это было проще», чего просто нет, вместо этого все намного сложнее =)

3. Я подумал, что у ОП тоже была какая-то другая причина переключиться на кривую Безье. Но да, проследить дугу окружности было бы очень просто с небольшим количеством тригонометрии…

4. Я закончил использовать это решение после того, как закончил его полностью понимать (стоило мне намного больше, чем решение Безье…). Но поскольку @AKX ответил на мою проблему, я все равно должен оставить ее как «принятый ответ».

5. возможно, вы захотите изменить принятый ответ, но оба ответа основаны на одной и той же концепции: измените систему координат и нарисуйте вещи «на месте», а не сохраняйте систему координат на месте и меняйте место, где вещи рисуются =)