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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Доброго времени суток, друзья!

Представляю Вашему вниманию перевод статьи Martin Heinz «Implementing 2D Physics in JavaScript».

Давайте немного развлечемся, создавая двухмерные симуляции и визуализации в JS.

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

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



Примеры можно посмотреть здесь.

Исходный код находится здесь.

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


Начнем с движения.

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

function move(dt) {
    x += vx * dt
    y += vy * dt
}

Здесь 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

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

function move(dt) {
    vx += ax * dt
    vy += ay * dt
    x += vx * dt
    y += vy * dt
}

Здесь 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

Гравитация


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

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



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

Сила, приложенная к телу, рассчитывается по следующей формуле:
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 [i, 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 реализуются физические явления и процессы.
Источник: https://habr.com/ru/post/487962/


Интересные статьи

Интересные статьи

Мой опыт разработки игры «Змейка» на Brython «Погоди, что?» – думаю, большинство читателей отреагирует на заголовок именно так. В смысле «просто использовать Python в браузе...
Меня зовут Павел Пархоменко, я ML-разработчик. В этой статье я хотел бы рассказать об устройстве сервиса Яндекс.Дзен и поделиться техническими улучшениями, внедрение которых позволило увеличить к...
Сидя за столом, вы используете мышку и клавиатуру, сидя на диване — скорей всего тачпад ноутбука или тачскрин планшета. Возможно, вы даже используете Leap Motion и управляете компьютером с помо...
Сравнивать CRM системы – дело неблагодарное. Очень уж сильно они отличаются в целях создания, реализации, в деталях.
Привет, Хаброжители! Хотите сделать отличный подарок ребёнку, желающему научиться программировать, или научить взрослого, далёкого от мира кодов? Тогда книга-героиня нашего поста Вам подойдет. Э...