Параллакс при помощи некомм. дуал. компл. чисел

топ 100 блогов nabbla122.08.2024 В прошлый раз мы получили анимированный паровоз, используя некоммутирующие дуальные комплексные числа. Теперь добавим фон, причём лежащий в нескольких "планах", один совсем вдали (облачка), другой поближе (верхушки деревьев), Солнце бесконечно далеко, а столбы и вовсе ПЕРЕД ПАРОВОЗОМ.

Параллакс при помощи некомм. дуал. компл. чисел


Реализовать это на удивление просто, ОДНУ ЦИФЕРКУ ПОМЕНЯТЬ!

Под катом также разъяснено, как "ставить камеру на объект", чтобы паровоз вполне себе ехал, но оставался в центре кадра. И, как водится, некоторое количество укуренных гифок.


Напомним: эти числа - частный случай кватернионов, только масштаб по осям Y/Z бесконечно отличается от масштаба по оси X и по действительным числам (скалярам). Мы представили, будто начало координат поместили в центр Земли, ось X выходит из центра и "протыкает" нашу рабочую плоскость, расположенную на "письменном столе". Все объекты на плоскости имеют координату X, равную единице (что соответствует радиусу Земли), а координаты Y,Z отсчитываются в миллиметрах или сантиметрах, и далеко мы не уходим, из-за чего повороты вокруг осей Y, Z могут быть лишь совсем ничтожными, и мы их вообще не воспримем как повороты, для нас это будет поступательное движение по осям Z или Y. Таким немудрёным способом мы "обменяли" произвольные повороты в пространстве на произвольные движения (и поступательные, и вращательные) на плоскости!

Именно поэтому точки (xn, yn), загруженные из SVG-файла, мы превращали в неком. дуал. компл. числа i + jxn + kyn, или (0; 1; xn, yn). Так мы помещали все эти рисунки себе на "письменный стол". Ещё мы имели право поставить произвольную скалярную компоненту, она не оказывает влияния на дальнейшее, она вообще в теории не должна изменяться при любых движениях (т.е поворотах и параллельных переносах), но на неё может повлиять масштабирование. Как это использовать - я пока не придумал. Произвольные данные немного боязно сюда класть, т.к при реализации "в лоб" они участвуют в вычислениях и по законам математики должны остаться неизменными, но при работе с плавающей точкой либо с фиксированной точкой, но с округлением промежуточных результатов, это не так...

Пока будем придерживаться этого принципа и добавим железную дорогу. Она вообще преобразовываться изначально не должна, т.к "неподвижна". Поэтому прямо в SVG установлю ей координаты "внизу кадра", а паровоз чуть-чуть приподниму. Дорога вполне себе конечна, простираясь лишь на 2048 пикселей по горизонтали. Вообще, можно было бы придумать некую "закольцовку", но пока делаем "честно". В одном месте проскакивает стык с накладкой, но без болтов с гайками - их пока лениво было описывать.

Отрисуем это хозяйство: координаты "железной дороги" не трогаем, координаты точек паровоза преобразуем с помощью числа engine, первого ведущего колеса - с помощью engine*wheel1, сцепного дышла - с помощью engine*wheel2*connector, и так далее:

Параллакс при помощи некомм. дуал. компл. чисел


Паровоз буксует на месте... Оно и понятно: мы же и не задали его движения! Число engine, выражающее его положение в пространстве (перемещения и повороты) пока что оставалось неизменным. Исправим это, задав ему поступательное движение. Если мы каждый кадр поворачивали колесо радиусом 73 пикселя на 3 градуса, значит, за тот же кадр паровоз должен сдвигаться вправо на 3,8 пикселя. Поэтому оператор перемещения будет иметь вид locomotion = 1 + (3.8/2)k. На каждом кадре будем делать следующее:

engine = engine * locomotion;

Посмотрим теперь:
Параллакс при помощи некомм. дуал. компл. чисел


Ага, поехал, и тут же скрылся из виду...

А мы теперь хотим, чтобы паровоз оставался неподвижен и перемещал весь мир вокруг себя. Этого добиться очень легко. По сути, все наши итоговые координаты надо теперь домножить слева на Conj(engine), т.е на число, сопряжённое (Conjunction) к числу engine. Такое сопряжённое число выражает обратное движение. Если исходное выражало перемещение паровоза на 100 метров вправо, то сопряжённое будет выражать перемещение на те же 100 метров влево.

При этом, по определению, Conj(engine) * engine = engine * Conj(engine) = 1, что и понятно, два движения полностью друг друга компенсируют. Так-то в некомм. дуал. компл. число можно ещё добавить МАСШТАБИРОВАНИЕ, вот тогда вместо взятия сопряженной величины придётся находить обратную, т.е сопряжённое делить на квадрат нормы. С масштабированием поиграемся чуть попозже.

А поэтому нам нет нужды считать Conj(engine)*engine*wheel1, чтобы пересчитывать точки первого колеса. Два левых числа сократятся, оставив попросту wheel1.

Получится, что железную дорогу мы отрисуем, преобразуя точки числом Conj(engine), точки паровоза мы не преобразуем, точки колеса мы преобразуем числом wheel1, и так далее. Везде, где слева стоял engine, его не будет, а где его не было, появится Conj(engine).

Посмотрим, что получается:
Параллакс при помощи некомм. дуал. компл. чисел


Ага, паровоз в центре мира, потому как центром мира мы определили левый верхний угол... Что ж, значит, добавим ещё одно число, EngineViewport = 1 - 240j + 256k, или (1; 0; -240; 256). В начальный момент оно совпадает с engine, но затем engine начнёт меняться, а вот EngineViewport останется неизменным, он указывает "окошечко" (определённое в координатах паровоза, то есть как бы "приваренное" к паровозу), через которое мы наблюдаем за миром.

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

- железную дорогу преобразуем через EngineViewport*Conj(engine),
- паровоз преобразуем через EngineViewport,
- первое ведущее колесо преобразуем через EngineViewport*wheel1,
- поршневое дышло преобразуем через EngineViewport*wheel2*rod,
и так далее. Вот что получается:

Параллакс при помощи некомм. дуал. компл. чисел


Фух, пошли дела кое-как... Паровоз катится по рельсам, и мы видим, как он проезжает стык. Гифка довольно короткая, 240 кадров, т.е ровно два оборота колеса, затем всё закольцовывается.

Теперь добавим облачка, сначала обычным образом, т.е координаты точек, образующих облака, будут иметь вид i+xnj+ynk, или (0;1;xn;yn). Преобразовывать их будем точно также, как железную дорогу, никаких новых чисел нам и не понадобится. Получаем:
Параллакс при помощи некомм. дуал. компл. чисел


Облака уплыли влево с той же скоростью, будто они гвоздями приколочены к рельсам. Не особо "реалистично". Сделаем простейшую вещь: вместо i поставим 0,1i. Т.е теперь все координаты облаков будут иметь вид 0,1i+xnj+ynk, или (0;0,1;xn;yn). Результат:
Параллакс при помощи некомм. дуал. компл. чисел


Если мы расположили свою рабочую плоскость (где паровоз и рельсы) на расстоянии ЕДИНИЦА от центра, то облака мы расположили на расстоянии в 10 раз меньше. Поворот вокруг оси X (то, что и мы воспринимаем как поворот на плоскости) будет работать в точности также, а вот повороты вокруг осей Y,Z, которые мы воспринимаем как сдвиги, будут иметь в 10 раз меньший эффект!

Давайте ещё добавим некий силуэт на горизонте, то ли горы, то ли леса, в общем, какой-то рельеф. И он будет ползти чуть быстрее, т.к мы его поставим на расстоянии 0,2:
Параллакс при помощи некомм. дуал. компл. чисел


Ещё добавим солнышко "на бесконечность", что в нашем случае означает: В НУЛЕ! Так оно вообще не будет двигаться при поступательном движении паровоза. Конечно, такие неподвижные картинки можно и без всех этих премудростей на экран выводить, но чуть ниже мы поймём разницу. А пока вот:
Параллакс при помощи некомм. дуал. компл. чисел


Ещё одна деталь - и я успокоюсь. СТОЛБЫ! Причём те, что расположены ближе к нам, чем паровоз. Для них поставим расстояние 1,5. Небольшое преувеличение, но для пущего эффекта почему бы нет:
Параллакс при помощи некомм. дуал. компл. чисел


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

Теперь, как обычно, давайте поиздеваемся, и "приделаем гоупрошку" к сцепному дышлу. Для этого все выражения, перед EngineViewport, будем домножать не на Conj(engine), как ранее, а на Conj(engine*wheel2*connector), или, что то же самое, на Conj(connector)*Conj(wheel2)*Conj(engine).И ещё, вместо engineViewport у нас будет ConnectorViewport, чтобы уместиться в экран поудобнее. Итого:

- железную дорогу, облака, солнышко, горизонт и столбы преобразуем через ConnectorViewport*Conj(connector)*Conj(wheel2)*Conj(engine),
- паровоз преобразуем через ConnectorViewport*Conj(connector)*Conj(wheel2). Переменная engine сократится,
- первое ведущее колесо преобразуем через ConnectorViewport*Conj(connector)*Conj(wheel2)*wheel1,
- второе ведущее колесо преобразуем через ConnectorViewport*Conj(connector). Переменные engine и wheel2 сократятся,
- само сцепное дышло преобразуем через ConnectorViewport, и всё тут! Вот от того оно и станет неподвижным.
- поршневое дышло преобразуем через ConnectorViewport*Conj(connector)*rod.

Параллакс при помощи некомм. дуал. компл. чисел


И напоследок прицепимся к поршневому дышлу! Можно было бы ещё и к колесу, но это, блин, и голова закружиться может. Итак:

Параллакс при помощи некомм. дуал. компл. чисел


Вот и солнышко пришло в движение! И в целом мы увидели, как все эти облачка, горизонт и прочее поворачиваются единообразно, что является вполне реалистичным поведением. Как мы знаем, горизонт заваливается совершенно замечательно, каким бы бесконечно отдалённым он ни был!


Обновлённая версия программы лежит всё там же, https://hub.mos.ru/nabbla/noncommdualcomplexdemo
Ну и старая версия никуда не подевалась, это ж гит!

Как видим, один из "багов" некомм. дуал. компл. чисел (необходимость выставить "волшебное значение", единичку в мнимой компоненте незнамо зачем) оказалась хорошей такой, добротной фичей. Но возможности данного формализма мы до сих пор не исчерпали. Как минимум, хочу в дальнейшем показать интерполяцию движений и "оператор масштабирования". Продолжение следует!

Оставить комментарий

Архив записей в блогах:
https://youtu.be/S-yKQ5Q-_rs Кто-нибудь, кроме меня, ещё помнит этот фильм? Сегодня подруга прислала пронзительную песню из "Маленьких трагедий". Смотрю - автор Шнитке. Ассоциативно вспомнилась его тема полета из 'Сказка странствий'. Это самый глубокий и трагичный фильм из тех, что мне ...
Совершенно неожиданное пополнение в рубрику- Абрахам Маслоу. Ну тот, который пирамида Маслоу, все же её знают? "Незадолго до рождения Абрахама его родители, спасаясь от еврейских погромов, уехали из южной губернии Российской империи в США. Фамилия его отца – Маслов, но он решил ...
Вот это фото - сделали поляки. Жители Беловеже Подляского воеводства. Они наперегонки снимали, как белорусские птички деловито рассекают польские пространства. А представляете, какой там шум стоял, если на фото запечатлен так низко пролетающий белорусский вертолет! ...
K конфликтам в соцсетях я отношусь, как к любым обычным бытовым конфликтам, пытаясь понять, чем же я людей разозлил, сам того не желая. В своё время я почувствовал огромное облегчение, что это моё отношение разделяет и такой уважаемый мною мыслитель, как Джордан Питерсон. Мне трудно ...
С праздником вас, дорогие фрэнды. Приходите сегодня на ...