Анимация графика в javascript

Работаем с двумерной графикой в JavaScript

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

Хотите узнать, как эти алгоритмы реализуются в JS?

Равномерное движение и движение с ускорением

Для равномерного движения мы можем использовать следующий код:

Здесь x и y — это координаты объекта, vx и vy — скорость объекта по горизонтальной и вертикальной осям, соответственно, dt (time delta — дельта времени) — время между двумя отметками таймера, что в JS равняется двум вызовам requestAnimationFrame.

Например, если мы хотим переместить объект, находящийся в точке с координатами 150, 50, на юго-запад, мы можем сделать следующее (одна отметка таймера или один шаг):

x = 150 += -1 * 0.1 - > 149.9 y = 50 += 1 * 0.1 - > 50.1 

Равномерное движение — это скучно, поэтому давайте придадим нашему объекту ускорение:

Здесь ax и ay — это ускорение по осям x и y, соответственно. Мы используем ускорение для изменения скорости (vx/vy). Теперь, если мы возьмем предыдущий пример и добавим ускорение по оси x (на запад), то получим следующее:

vx = -1 += -1 * 0.1 - > -1.1 // vx += ax * dt vy = 1 += 0 * 0.1 - > 1 // vy += ay * dt x = 150 += -1.1 * 0.1 - > 149.89 // x += vx * dt; объект переместился дальше на -0.01 y = 50 += 1 * 0.1 - > 50.1 // y += vy * dt 

Гравитация

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

Читайте также:  Выберите основные элементы современного html документа

Вот что мы хотим получить:

Для начала вспомним несколько уравнений из старших классов.

Сила, приложенная к телу, рассчитывается по следующей формуле:
F = m * a… сила равна массе, умноженной на ускорение
a = F / m… из этого мы можем сделать вывод, что сила действует на объект с ускорением

Если мы применим это к двум взаимодействующим объектам, то получим следующее:

Выглядит сложно (по крайней мере, для меня), поэтому давайте разбираться. В данном уравнении |F| — это величина силы, которая одинакова для обоих объектов, но направлена в противоположные стороны. Объекты представлены массами m_1 и m_2. k — это гравитационная постоянная и r — расстояние между центрами масс объектов. Все еще непонятно? Вот иллюстрация:

Если мы хотим сделать что-то интересное, нам потребуется больше двух объектов.

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

  • сначала мы рассчитываем силы F_1 и F_2, используя предыдущую формулу:
  • затем переводим все в векторы:

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

function moveWithGravity(dt, o) < // o - массива объектов, с которыми мы работаем for (let o1 of o) < // нулевой счетчик (сумматор) сил каждого объекта o1.fx = 0 o1.fy = 0 >for (let [i, o1] of o.entries()) < // для каждой пары объектов for (let [j, o2] of o.entries()) < if (i < j) < // чтобы не делать одного и того же для той же пары дважды let dx = o2.x - o1.x // вычисляем расстояние между центрами объектов let dy = o2.y - o1.y let r = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) if (r < 1) < // чтобы избежать деления на 0 r = 1 >// вычисляем равнодействующую для этой пары; k = 1000 let f = (1000 * o1.m * o2.m) / Math.pow(r, 2) let fx = f * dx / r let fy = f * dy / r o1.fx += fx // сила первого объекта o1.fy += fy o2.fx -= fx // сила второго объекта в противоположной направлении o2.fy -= fy > > > for (let o1 of o) < // для каждого объекта обновляем. let ax = o1.fx / o1.m // ускорение let ay = o1.fy / o1.m o1.vx += ax * dt // скорость o1.vy += ay * dt o1.x += o1.vx * dt // позицию o1.y += o1.vy * dt >> 

Столкновение

Движущиеся тела иногда сталкиваются. От столкновения происходит либо выталкивание одних объектов другими, либо отскакивание одних объектов от других. Сначала поговорим о выталкивании:

Прежде всего, нам необходимо определить, что имело место столкновение:

class Collision < constructor(o1, o2, dx, dy, d) < this.o1 = o1 this.o2 = o2 this.dx = dx this.dy = dy this.d = d >> function checkCollision(o1, o2) < let dx = o2.x - o1.x let dy = o2.y - o1.y let d = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) if(d < o1.r + o2.r)< return < collisionInfo: new Collision(o1, o2, dx, dy, d), collided: true >> return < collisionInfo: null, collided: false >> 

Мы объявляем класс Collision, представляющий два столкнувшихся объекта. В функции checkCollision мы сначала вычисляем разницу между координатами x и y объектов, затем вычисляем их фактическое расстояние d. Если сумма радиусов объектов меньше, чем расстояние между ними, значит имело место столкновение этих объектов — возвращаем объект Collision.

Далее нам нужно определить направление смещения и его величину (магнитуду):
n_x = d_x / d… это вектор
n_y = d_y / d

s = r_1 + r_2 — d… это «величина» столкновения (см. картинку ниже)

В JS это может выглядеть так:

function resolveCollision(info) < // "info" - это объект Collision из предыдущего примера let nx = info.dx / info.d // вычисляем векторы let ny = info.dy / info.d let s = info.o1.r + info.o2.r - info.d // вычисляем глубину проникновения info.o1.x -= nx * s/2 // сдвигаем первый объект на половину величины столкновения info.o1.y -= ny * s/2 info.o2.x += nx * s/2 // сдвигаем второй объект в противоположную сторону info.o2.y += ny * s/2 >

Отскакивание

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

k = -2 * ((o2.vx — o1.vx) * nx + (o2.vy — o1.vy) * ny) / (1/o1.m + 1/o2.m)… *Магия*

Как мы можем использовать волшебную k? Мы знаем, в каком направлении будут двигаться объекты, но не знаем на какое расстояние. Это и есть k. Вот как вычисляется вектор (z), показывающий, куда должны переместиться объекты:

function resolveCollisionWithBounce(info) < let nx = info.dx / info.dy let ny = info.dy / info.d let s = info.o1.r + info.o2.r - info.d info.o1.x -= nx * s/2 info.o1.y -= ny * s/2 info.o2.x += nx * s/2 info.o2.y += ny * s/2 // магия. let k = -2 ((info.o2.vx - info.o1.vx) * nx + (info.o2.vy - info.o1.vy) * ny) / (1/info.o1.m + 1/info.o2.m) info.o1.vx -= k * nx / info.o1.m // то же самое, только добавили "k" и поменяли "s/2" на "m" info.o1.vy -= k * ny / info.o1.m info.o2.vx += k * nx / info.o2.m info.o2.vy += k * ny / info.o2.m >

Заключение

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

Источник

Анимация на JavaScript используя Canvas

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

Наиболее очевидный и распространенный вид анимации в компьютерной графике – это спрайтовая анимация.

Спрайт — графический объект в компьютерной графике, чаще всего растровое изображение, свободно перемещающееся по экрану.

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

Перемещение спрайта по экрану определяет изменение координат его положения с течением времени.

Т.е. необходима функция, которая срабатывает с определенным интервалом времени. И такая функция в JS есть, и не одна.

  • setTimeout позволяет вызвать функцию один раз через определённый интервал времени.
  • setInterval позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени.

setTimeout
Синтаксис:

let timerId = setTimeout ( func | code , [ delay ] , [ arg1 ] , [ arg2 ] , . )
Параметры:

func|code — Функция или строка кода для выполнения. Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется.

delay — Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0.

arg1, arg2… — Аргументы, передаваемые в функцию.

Отмена через clearTimeout
Вызов setTimeout возвращает «идентификатор таймера» timerId , который можно использовать для отмены дальнейшего выполнения.

let timerId = setTimeout(. ); clearTimeout(timerId);

setInterval
Метод setInterval имеет такой же синтаксис как setTimeout :

let timerId = setInterval ( func | code , [ delay ] , [ arg1 ] , [ arg2 ] , . )
Все аргументы имеют такое же значение. Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.

Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval ( timerId ) .

Пример анимации линейного спрайтового движения:

html> head> meta charset="utf-8" /> script src="test.js">script> head> body onload="init()"> canvas id="tutorial" width="500" height="500">canvas>br /> input type="button" onclick="start()" value="Пуск"> input type="button" onclick="stop()" value="Стоп"> body> html>
var x; var Idint; var ctx; function init(){ x=10; ctx = document.getElementById('tutorial').getContext('2d'); } function draw() { ctx.fillStyle = 'white'; ctx.fillRect(x,10,50,50); x=x+3; ctx.fillStyle = 'blue'; ctx.fillRect(x,10,50,50); } function start() { Idint = setInterval(draw, 100); } function stop() { clearInterval(Idint); }

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

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

  • Анимация выглядит более плавной, поскольку уровень кадров в секунду остается постоянной.
  • Процессор не перегружается задачами по рендерингу, а может обрабатывать и другие задачи во время рендеринга анимации. Вообще браузер может определить тот уровень кадров в секунду, который будет оптимален для задач, которые браузер выполняет одновременно с анимацией.

let requestId = requestAnimationFrame ( callback )
Такой вызов планирует запуск функции callback на ближайшее время, когда браузер сочтёт возможным осуществить анимацию.

Пример анимации линейного спрайтового движения:

var x; var Idint; var ctx; function init(){ x=10; ctx = document.getElementById('tutorial').getContext('2d'); } function draw() { if (x450) { ctx.fillStyle = 'white'; ctx.fillRect(x,10,50,50); x=x+3; ctx.fillStyle = 'blue'; ctx.fillRect(x,10,50,50); Idint = requestAnimationFrame(draw); } else {cancelAnimationFrame(Idint);} } function start() { Idint = requestAnimationFrame(draw); } function stop() { cancelAnimationFrame(Idint); }

Источник

Оцените статью