Мы немного поигрались с
некоммутирующими дуальными комплексными числами: сделали
анимацию паровоза, а потом
добавили "параллакс", когда разные части фона как будто бы расположены на разном отдалении от зрителя. Мне понравилось работать с такими числами, но каких-то неведомых "суперспособностей" данный формализм пока не дал, всё то же самое мы могли бы осуществить и без них.
Интерполяция - другое дело, тут нам очень помогает наличие простой "формулы Эйлера" для взятия экспоненты от некомм. дуал. компл. числа! Из неё можно вывести и обратную операцию: натуральный логарифм, а из них двоих получить взятие произвольной степени от числа, в том числе и корень степени N, т.е одно движение разбить на N одинаковых движений, композиция которых и переведёт объект из начальной в конечную точку.
Конечно, и от матрицы 3х3 можно взять "матричную экспоненту", сегодня с утра вспоминал, как это делается, вроде вывел матричную экспоненту от "генератора произвольного движения на плоскости", матрицы вида
но пока не понял, можно ли сколько-нибудь легко и обратную операцию, "логарифм", записать.
А пока сформулируем задачу интерполяции движений на плоскости и посмотрим, как это решается в некомм. дуал. компл. числах. И ещё, в кои-то веки, поковыряемся в них "ручками", без помощи компьютера. Именно эти выкладки я хотел выложить ещё месяц назад, но решил начать с паровоза!
При возне с паровозом мы уже заметили, что одними и теми же числами мы на самом деле описываем две совершенно разные сущности:
1. Векторы, или точки на плоскости. Обычно они бы представлялись просто двумя числами, здесь у них появилось две дополнительные компоненты. Одна (мнимая) позволяет разместить вектор на "другой плоскости", другая (скалярная/действительная) пока нами не использовалась, при движениях она попросту остаётся неизменной. Если с параллаксом мы играться не собираемся, то вектор (x;y) мы записываем как i+xj+yk, или (0;1;x;y).
2. Операторы движения: вращения, параллельные переносы или комбинация их двух. Обычно мы бы разделяли параллельный перенос (описываемый просто вектором смещения) и поворот (описываемый матрицей 2х2), либо объединили бы их в матрицу 3х3 (см. выше) и ввели "гомогенные координаты", где вектор всё же станет трёхмерным, но в компоненте Z будет лежать просто единичка. Наша запись чуть компактнее: 4 числа выражают произвольное движение на плоскости, для чего самый минимум понадобилось бы 3 числа (два смещения и угол поворота). Ещё и масштаб можно сюда добавить, но о нём поговорим попозже.
Сейчас, при рассмотрении интерполяции, мы вообще практически позабудем о точках/векторах, нас будут интересовать исключительно операторы движения. Ведь положение твёрдого тела на плоскости можно описать ровно таким оператором! Единичка будет означать, что объект расположен строго в начале координат, и оси координат также совпадают. В общем случае, мы будем описывать, как именно надо переместить объект из начала координат, чтобы он принял то положение и ориентацию, какое занимает сейчас.
Введём числа Sn (от слова Step), где n=0..N-1. Это положения объекта, причём начальное S0 и конечное SN-1 мы уже знаем, а промежуточные хотим найти.
А дальше мы можем описать две разных ситуации, "внешнее" и "внутреннее" движение. В первом случае наш объект на каждом шаге вращается ВОКРУГ НАЧАЛА СИСТЕМЫ КООРДИНАТ и ещё смещается АБСОЛЮТНО (например, всегда ВПРАВО). Чтобы описать такое движение, надо текущее положение умножать на оператор СЛЕВА. Получаем:
здесь ΔT - искомый оператор движения, описывающий один шажок. ext - сокращённо от external, т.е "внешнее". Ставлю ли я этот (ext) в верхний или нижний индекс - не ищите глубокого смысла. Где есть место - туда и запихиваю...
Либо мы можем описать, как объект вращается ВОКРУГ СВОЕЙ ОСИ, а ещё смещается куда-то В СВОЕЙ СИСТЕМЕ КООРДИНАТ. Грубо говоря, автомобиль с выкрученным рулём - на каждом шажке поворачивается на 5° и при этом уезжает условно вперёд. Для такого движения мы получаем:
Интуитивно два этих движения кажутся совершенно разными, и величина одного "шажка", ΔT, будет в этих двух случаях получаться разной, но шаги интерполяции окажутся ПОЛНОСТЬЮ ИДЕНТИЧНЫМИ! Изначально я это увидел при симуляции на компьютере, и лишь потом смог худо-бедно обосновать математически.
Показать это можно, вспомнив, что эти наши некоммутирующие дуальные комплексные числа - это вариант кватернионов с бесконечно большой разницей в масштабе осей X и Y/Z, и мы всё ещё можем любое движение на плоскости рассматривать как вращения (в т.ч бесконечно малые) на единичной сфере.
В главе "
кватернионы и спиноры, порядок поворотов" мы рассматривали ситуацию, когда ориентация объекта задавалась кватернионом Λ, и объект совершил поворот, который в его связанных осях описывается кватернионом ΔΜ. Чтобы описать поворот объекта в инерциальной системе координат, мы пересчитали ΔΜ в инерциальную систему:
(это можно было сделать, поскольку скалярная компонента, выражающая угол поворота, в такой операции остаётся неизменной, а векторная компонента поворачивается как любой другой вектор, т.е ось поворота остаётся неизменной, просто выраженной в другой системе координат)
и затем посчитали кватернион ориентации объекта после этого поворота:
(тут мы полагаем, что все рассмотренные кватернионы имеют единичную норму, т.е выражают только поворот, но не масштабирование)
Именно так мы вывели в своё время, что поворот, описываемый во "внешней", обычно инерциальной, системе координат, применяется с помощью умножения СЛЕВА, тогда как поворот в связанных осях применяется умножением СПРАВА.
Но давайте сделаем ещё один шажок, посмотрим, как выразится в инерциальной системе ещё один такой же поворот ΔΜ, при том, что ориентация объекта уже поменялась, и может показаться, что теперь этот поворот в инерциальной системе отсчёта запишется по-другому:
Но нет, кватернион остался тем же самым! То есть, последовательное умножение на один и тот же кватернион СПРАВА можно заменить на последовательное умножение на какой-то другой кватернион СЛЕВА, и наоборот. А учитывая, что начальное и конечное состояние при обоих описаниях одинаковое, то и все промежуточные шаги совпадут.
В общем-то, в случае поворотов это кажется практически очевидным: ведь сама ось поворота во время поворота остаётся неизменной, поэтому её достаточно пересчитать лишь однажды! А вот для произвольного движения на плоскости очевидность куда-то уходит...
Давайте проиллюстрируем эту "дуальность" на простейшем примере: мы начинаем с объекта, имеющего нулевое вращение (т.е его ось X сонаправлена оси X нашей системы координат) и координаты (100;0). Он задаётся числом Λ=1+50k, или (1;0;0;50). И мы хотим последовательно поворачивать его на 90 градусов против часовой стрелки, вокруг своей оси. Записываем оператор поворота:
Чтобы осуществить поворот вокруг своей оси, помножаем Λ на Tint СПРАВА:
По виду этого числа не сразу очевидно, что оно выражает. Давайте применим его к вектору i, или (0;1;0;0), выражающего начало координат на нашем "письменном столе". Сначала умножим i справа на число, сопряжённое к Λ1:
Теперь результат умножим слева на Λ1:
Здесь так и подмывает применить формулу сокращённого умножения, (a+b)(a-b)=a2-b2, но нельзя! Ведь оно выводится так:
И затем мы "с чистой совестью" сокращаем ba и ab. Только вот у нас числа некоммутирующие, поэтому, если так сделать - мы придём к ошибочному результату!
Так что будем раскрывать "ручками":
Четыре слагаемых мы выкинули, поскольку j2=k2=jk=kj=0, это ж дуальные числа! Также у нас ушла действительная компонента и компонента при k. И ещё не забываем поделить на 2: это деление мы сейчас временно убрали при выкладках для краткости. Вот результат:
Это соответствует точке на плоскости (100;0), то есть, как мы и хотели, объект остался на том же самом месте. И для полного успокоения совести давайте осуществим движение вектора i+j, или (0;1;1;0), который соответствует точке на плоскости (1;0). Исходя из линейности операций,
Первое слагаемое мы уже нашли, осталось найти второе. Действуем по тому же шаблону, но сейчас выйдет даже проще, т.к j при умножении на j или k будет тут же зануляться! Поехали:
Итак, "вектор" j превратился просто в k, тогда как i превратился в i+100j, поэтому итоговое значение: i+100j+k, или (0;1;100;1), что соответствует точки на плоскости (100;1). Всё верно: центр объекта отобразился в точке (100;0), тогда как точка (1;0), сидящая на объекте, отобразилась в (100;1), что и указывает - объект повернулся на 90° против часовой стрелки вокруг своей оси.
Теперь давайте повернём объект ЕЩЁ ОДИН РАЗ:
И снова преобразуем вектор i. Сначала помножим его справа на сопряжённое:
И слева на Λ2:
Как видим, центр объекта так и остался в точке (100;0), ровно так мы и хотели. И ещё преобразуем вектор j:
Если теперь взять вектор i+j, соответствующий точки на плоскости (1;0), он превращается в (99;0), и это снова вполне ожидаемо: объект теперь развёрнут на 180°, поэтому когда мы сместились В ОСЯХ ОБЪЕКТА на единичку "вправо", в абсолютных координатах это превратилось в сдвиг "влево".
Пока всё работает ровно так, как задумано. А теперь попытаемся то же действие, разворот на 90° против часовой стрелки ВОКРУГ СВОЕЙ ОСИ, изобразить как "внешнее движение", т.е разворот вокруг начала координат со смещением в абсолютных координатах.
Сделаем это "в лоб":
Снова нам не вполне понятно, что за движение выражает это число, поэтому применим его к вектору i:
Итак, точка на плоскости (0;0) переместилась в координаты (100;-100), т.е впридачу к повороту на 90 градусов вокруг начала координат, у нас присутствует параллельный перенос на 100 вправо и вниз.
Это имеет смысл: если мы поворачиваем объект с центром в (100;0) вокруг начала координат против часовой стрелки на 90 градусов, то его центр приобретает новые координаты (0;100). Если же к этому повороту добавить ещё и смещение на 100 вправо и 100 вниз, то мы вернёмся к исходным координатам (100;0)!
Напоследок убедимся в этом, домножив число Λ (исходное положение объекта на плоскости) СЛЕВА на Text:
Как видно, результат такого "внешнего движения" (поворот вокруг начала координат и смещение на 100 вправо, и на 100 вниз) полностью эквивалентен движению "внутреннему" (простое вращение вокруг своей оси), давая тот же самый результат! Сразу напомним: это справедливо лишь для ОДНОГО объекта - именно его можно двигать хоть так, хоть эдак. Если у нас объектов четыре штуки, то одно и то же число, которое опишет, к примеру, их поворот вокруг своей оси на 90 градусов, придётся под каждый из объектов преобразовывать по-своему, и получится ЧЕТЫРЕ РАЗНЫХ ЧИСЛА, которые опишут их движение, только на этот раз "внешнее". То же самое верно и в обратную сторону.
Мы, наконец-то, убедились, что интерполяция "внешними движениями" и "внутренними" - это одно и то же, и рассмотрим, наконец, а как это сделать.
Напомним: у нас есть числа S0 и SN-1, описывающие положение объекта на плоскости на шаге 0 и на шаге N-1, соответственно. Кроме того, мы считаем, что переход от шага к шагу описывается ВНЕШНИМ движением, задаваемым одним и тем же числом ΔT:
Чтобы найти ΔT, запишем эту формулу для n=N-1, где мы знаем результат, помножим обе части справа на (S0)-1, после чего левую и правую часть равенства поменяем местами:
(в кои-то веки мы можем применить операцию ДЕЛЕНИЯ некоммутирующих дуальных комплексных чисел! Когда оно записано как косая черта, то более-менее интуитивно, что умножение на обратную величину производится именно СПРАВА, раз уж она в этом выражении записывается правее! Именно поэтому мы в итоге предпочли "внешнее" движение "внутреннему": в противном случае обратная величина оказалась бы слева.)
Остаётся только извлечь корень (N-1)-й степени, но мы этот корень заменим на экспоненту и логарифм:
Ранее мы выписывали, чему равна экспонента от некоммутирующего дуального комплексного числа, правда, с нулевой действительной частью. Давайте для порядку выпишем самый общий случай:
Отсюда можно сообразить, как нам посчитать логарифм. Обозначим его аргумент: c+si+xj+yk. Такой выбор букв, поскольку первые две компоненты это обычно косинус (c) и синус (s), а последние две компоненты так или иначе выражают параллельный перенос. Сопоставим теперь с буквами выше:
Для начала находим общий множитель, exp(k0):
Теперь выражение можно отнормировать и найти первую компоненту логарифма:
Далее находим мнимую компоненту α:
Это мы находим "главную ветвь", в результате чего наш объект ни в коем случае не "закрутится" более чем на 1 оборот. Всё равно некомм. дуал. компл. числа, как и кватернионы, не могут хранить, сколько оборотов шла закрутка. Максимум, они отличат 360° от 0°, но от закрутки в 720° мы всё же вернёмся в исходное состояние.
И наконец, зная α, можно найти и B:
Мы всё же вводим функцию
Дело в том, что при отсутствии вращения (т.е при α=0) у нас будет получаться ситуация "0/0", которой быть не должно, тут должен сработать замечательный предел и получиться нормальная единичка.
Сейчас мы можем, наконец, громко подумать об "особом случае" α=π. Поскольку наши некомм. дуал. компл. числа оказались некоторой разновидностью кватернионов, и у них углы половинные, то "особый случай" соответствует полному обороту. Здесь у нас просто ОБЯЗАТЕЛЬНО должно быть x=y=0 в аргументе логарифма, иначе у нас получится деление на ноль! Выходит, что если объект совершал полный оборот "маленькими шажками", то он НЕ ИМЕЕТ ПРАВА ещё и куда-то сместиться!
Если рассмотреть "внутреннее движение", то физический смысл этого ограничения становится понятен. Грубо говоря, мы фиксируем у автомобиля руль в отклонённом состоянии, и начинаем ехать. Рано или поздно, сделав полный оборот, мы вернёмся ровно в ту же точку, откуда и стартовали. Диаметр круга, который мы описали, будет существенно разниться в зависимости от поворота руля (чем он "прямее", тем больше диаметр), но вернуться в начало мы попросту обязаны! Если же продолжить движение, мы можем описать и второй круг и снова вернуться в исходную точку, а потом и третий, и четвёртый! Половинные углы наших некоммутирующих дуальных комплексных чисел, в связке с функцией sinc(α), которая выползла в экспоненте, дадут именно такой эффект: если уж мы совершили целое число оборотов, отличное от нуля, значит, мы просто обязаны вернуться в исходную точку! А вот если ни одного оборота не сделано, то получаем вполне нормальное движение по прямой, без каких-либо ограничений.
Напомним: "основные свойства" экспоненты и логарифма у нас здесь, вообще говоря, не выполняются! То есть, экспонента от суммы не обязательно окажется равной произведению отдельных экспонент, равно как и логарифм от произведения не обязательно будет равен сумме логарифмов. Поэтому я предлагаю не использовать здесь запись экспоненты как ex, а исключительно как ФУНЦИЮ exp(x), определённую через ряд. Если используемые числа коммутируют, то можно доказать через бином Ньютона, что exp(x+y)=exp(x)exp(y), в противном случае равенство будет выполняться не всегда. Так что увидев под логарифмом частное от конечного и начального положения, не следует пытаться "упростить" выражение (сделав разность логарифмов), частное надо посчитать "честно", и уже потом брать логарифм, иначе не избежать проблем...
Вот мы и вывели это дело, ещё и показав, что интерполяция "внутренними" и "внешними" движениями оказывается в точности такой же.
Впрочем, в чистом виде такая интерполяция редко когда может пригодиться. В следующей части мы попробуем применить её на автомобиле, увидим, как он перестраивается из ряда в ряд, тупо ДВИГАЯСЬ ВБОК и поймём, что одну степень свободы надо убрать. Вот после этого получится интересный результат.