Найти в Дзене
Сделай игру

Угол падения и угол отражения

Оглавление

Недавно посмотрел фильм про Соника и вспомнил, какой революционной игрой он казался когда-то: бег по кольцевой тропинке, прыжки и отскоки. И появился вопрос, а как в игре можно было бы реализовать углы отражения?

Угол падения, угол отражения
Угол падения, угол отражения

Уверен, что в самой игре были зафиксированы правила отражения (помните, там были такие круглые штуки, которые откидывали его в сторону под углом), однако мне стало интересно решить эту задачу в более общем виде: избыточное для 16 битной платформы, но для более современных - вполне рабочее для современного железа.

Общие сведения и теория

Сама идея отражения объекта от препятствия довольно старая. Взять, например, Арканоид: мяч отражается от поверхностей, но если присмотреться, набор возможных углов падения и отражения - фиксирован (вероятно, возможные углы падения и отражения - предопределены заранее).

Просто, чтобы напомнить, что за игра.
Просто, чтобы напомнить, что за игра.

Иными словами, идея отражения какого-то объекта - не нова. Но мы попробуем продвинуться чуть-чуть далее. Давайте сделаем некоторую универсальную модель отражения и воспользуемся правилом: угол падения равен углу отражения. Это, разумеется, не то, чтобы идеальное решение, однако его, если что, можно и изменить.

Математика вопроса

Падение и отражение, по сути, сводится к пересечению двух прямых. Неважно, какая траектория у отражаемого объекта - её можно свести к набору отрезков. Неважно, какая форма у отражающей поверхности - её, также, можно свести к набору отрезков.

Формула прямой - y = kx + b, где k - коэффициент наклона прямой, b - смещение. У нас, напомню, два отрезка - две прямые, ограниченные с двух сторон точками, координаты которых нам известны. Это позволяет найти коэффициенты (расчёты тут).

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

Следующий шаг - это найти угол падения. В этом нам поможет теорема косинусов. Фактически, мы просто получаем два треугольника с известными длинами сторон и надо просто выяснить падения.

И под конец - вычисления угла отражения. Тут я решил пойти по пути наименьшего сопротивления: падающий луч, чтобы отразиться, должен "заступить" за линию зоны отражения; поэтому мы просто повернём "залезший внутрь" отрезок. Угол поворота будет равен двум углам падения против часовой стрелки.

Это, полагаю, довольно сложно прочитать и представить, поэтому прилагаю рисунок, как это должно выглядеть.
Да, так себе рисунок, но идея, надеюсь, ясна
Да, так себе рисунок, но идея, надеюсь, ясна

Зелёным - выделены углы падения, оранжевым - угол отражения и равен двум углам падения. Тут, разумеется, можно поуправлять углом отражения, но пока остановимся на классике.

Проблема подхода и её преодоление

На самом деле в этой бочке мёда математического удовлетворения есть один нюанс, который всё ломает - это вертикальные и горизонтальные линии. Проблема в том, что формула y=kx+b для вертикальных и горизонтальных отрезков не работает, потому что для вертикальных линий k всегда равно 0; с горизонтальными всё ещё хуже. Это нетрудно определить: если x-координаты отрезка одинаковы - значит у нас вертикальная линия; если y-координаты одинаковы - значит горизонтальная.

Короче говоря, наши формулы прекращают работать. Поэтому мы пойдём на небольшую хитрость - намеренно введём небольшую ошибку: у второй координаты отрезка сместим "одинаковую" величину координат точки на какое-нибудь небольшое значение, скажем, 0.0000001. С точки зрения конечного результата - всё будет приемлемо, с точки зрения математики - формулы всегда будут работать, с точки зрения конечного результата - незначительное неверное отклонение создаст ошибку только на очень большом расстоянии, что для нашей задачи - несущественно.

Решение

Пришло время написать немного кода. Изначально я всё сделал на Rust, потому что мне хотелось опробовать подход с неизменными (иммутабельными) структурами, которые возвращают каждый раз новые структуры. Но когда дело дошло до визуализации, я вдруг понял, что масштаб задачи не сопоставим с хлопотами на сборку. Поэтому решил просто переписать всё на JS.

На таких данных тестировал всё
На таких данных тестировал всё

Однако, результат получился не совсем : неверно выглядят как угол отражения, так и длина отражённого отрезка.

Линии разных цветов, чтобы проще было понять что и где
Линии разных цветов, чтобы проще было понять что и где

Оранжевый - луч падения, серый - отражающая поверхность, фиолетовый - луч отражения. Причина такого результата почти всегда предопределена: где-то мы напутали в вычислении углов. Отладчик нам в помощь!

Исправленная версия
Исправленная версия

А проблема была не в вычислениях, а в тестовых данных; поправили данные - всё стало выглядеть хорошо.

Расширенное решение

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

Теперь отражение больше похоже на отражение
Теперь отражение больше похоже на отражение

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

Отслеживает перетягивание опорных точек
Отслеживает перетягивание опорных точек

Исходник тестовых данных немного усложнился.

Добавилась постоянная перерисовка экрана
Добавилась постоянная перерисовка экрана

Результат получился приемлемым.

Падение и отражение
Падение и отражение

Заключение

Как видите, задача несложная; можно много где использовать. Например, написать игру вроде Арканоида, но на стероидах.

Внимание, вопрос, к чему эта картинка?
Внимание, вопрос, к чему эта картинка?

Вас, наверное, удивляет, причём тут деревья, камни и прочее. Признаться, я и сам озадачен. Попросил Алису от Яндекса сгенерировать мне изображение с углами падения и отражения, но получил это. Если у вас получилось разгадать этот ребус от нейросетки, не откажите в любезности, поделитесь открытием.