«Физика для программистов» — как физтехи применяют её в приложениях. Бросок объекта под уголом к горизонту
Аннотация
Данная статья входит в цикл, освещающий задачи на моделирование физических процессов на факультете МТФИ ВШПИ.
Полученной задание выглядело достаточно скучно:
Мы решили, что хотим повеселиться. А то, что из этого вышло, вы узнаете в этой статье.
Первая реализация — Angry Ball
Проект демонстрирует принципы физики через интерактивную игру, где игроки бросают мяч в среде с линейной функцией трения от скорости.
Введение
Коэфицент вязкого трения — 2
Проект Angry Ball — это приложение Flutter, которое демонстрирует законы полета через интерактивную игру, где игроки бросают мяч в среде с линейной функцией трения от скорости. Проект разработан так, чтобы быть как развлекательным, так и образовательным, предоставляя возможность узнать о гравитации, дифференциальных уравнениях, мобильной разработке и игровых движках. Автор надеется, что эта игра может потенциально вызвать жгучий интерес в багажнике к дальнейшему обучению.
Related Work
Раскрутим клубок, объясняющий симуляцию. Какие силы влияют на мяч? Их две:
Чтобы не иметь дело с вектором скорости и оставить только числа, пусть:
С помощью второго закона Ньютона: , дифференциальное уравнение для мяча в воздухе будет:
Расстояние от начала координат и высота мяча от времени:
Нам нужен момент , когда:
Зависимость расстояния и высоты от времени
Построив эти 2 графика в Desmos, мы замечаем, что у есть только 2 решения. Первое — это и действительно из-за начальных условий дифференциального уравнения. Второй корень можно найти с помощью алгоритма тернарного поиска. Теперь, зная , программа вставляет значение его в функцию длины , чтобы получить координату , оценочное расстояние текущего броска. Полученное решение на практике дает погрешность не более 3–5%, однако настоящая точность не высчитывалась.
Flame?) Серьезно?
Коэфицент вязкого трения — 8
Отец спросил, хватит ли у меня смелости забабахать мобильное приложение. Так как с Unity я никогда не работал, а писать надо с 0, то взял за основу первый попавшийся движок. Им оказался Flame, новинка от команды Flutter, мультиплатформенного фреймворка от Гугла.
Настоящие мужчины используют указатели, ссылки, разбираются в State Management. Я же наследовал все классы от HasGameRef и все текущие параметры программы хранил обычными переменными в main классе. Не лучшая идея, но для маленького pet-project без дальнейшего будущего самое то. Быстро и незамысловато.
Итого, у меня было 2 класса для движков (сил действия на объект и сам объект, шарик), пару вспомогательных классов для математических расчетов и задания параметров окружения, в котором будет находится объект. Не то, чтобы все это было категорически необходимо для реализации, но если уж не хардкодить единицы длины (метр), то к делу надо подходить серьезно.
Вторая реализация — React
А что если на React?
Я задался таким вопросом, когда решил написать эту работу по физике. Мне хотелось попрактиковаться в React.
Первая часть
Первоначальной идеей было сделать сайт, в котором пользователь мог ввести параметры броска (угол наклона, стартовая скорость, масса обьекта, коэфицент вязкого трения, коэфицент лобового сопротивления), а сайт промоделировал бы полет этого объекта.
Так как на объект действует несколько сил, я пересчитывал скорость и положение шарика в каждый момент времени, а точнее каждую милисекунду. Я сделал setInterval на каждую милисекунду, но он дает погрешность, срабатывая неточно, поэтому каждый раз я брал время в момент срабатывания и вычитал из него время предыдущего срабатывания.
const updateTime = () => {
const date = new Date();
return (
date.getHours() * 60 * 60 * 1000 +
date.getMinutes() * 60 * 1000 +
date.getSeconds() * 1000 +
date.getMilliseconds()
);
};
Переменные, которые описывают положение и скорость объекта:
const [objectPositionX, setObjectPositionX] = useState(startPositionX);
const [objectPositionY, setObjectPositionY] = useState(startPositionY);
const [objectSpeedX, setObjectSpeedX] = useState(startSpeedX);
const [objectSpeedY, setObjectSpeedY] = useState(startSpeedY);
Начальный (нерабочий) вид пересчета: код
Реализован функционал остановки объекта при клике на область.
Проблема: пересчитывать переменные состояния каждую милисекунду не получится, так как setState и setInterval — асинхронные функции, и значение переменной не успевает измениться к следующему пересчету.
Решение: я добавил переменные без состояния, которые являются полными копиями исходных, теперь на них происходит пересчет в setInterval, а также по ним обновляются состояния исходных, но от их значений зависит положение обьекта на экране пользователя, а не следующий пересчет: код
Результат
Реализация этой части
Вторая часть
Сайт был бесполезным, поэтому я решил добавить работу с погрешностями. На самом деле каждую милисекунду оъект двигается по прямой:
Из-за этого пользователь получает данные о дальности и максимальной высоте с погрешностью. Я решил добавить пользователю возможность вводить требуемую погрешность, по которой сервис будет выдавать интервалы значений, которые пользователь может ввести в оставшиеся поля. Чтобы это посчитать, я делал бинарный поиск и выдавал подходящий интревал, по невведеному значению. Бинпоиск подходит, потому что погрешность растет, когда объект проводит мало времени в движении, то есть при маленькой скорости или сильном сопротивлении.
Чтобы посчитать корректные значения дальности и максимальной высоты при выбранных параметрах в бинпоиске, я брал выше частоту обновления. Мне не нужно отрисовывать это, поэтому поиск подходящих интервалов работал быстро.
Если пользователь захочет посчитать интервал для скорости, то он должен будет ввести все известные параметры, а мой сервис вернет интревал.
Результат
Код проекта
Итоги
Во время разработки я столкнулся с рядом неочевидных проблем, поэтому этот проект был полезен для меня. Если мне ещё придется писать что-то подобное, то я первым делом напишу нормальный движок, потому что с добавлением нового функционала количество кода слишком быстро росло.