Получение тангажа и крена из матрицы без сингулярностей

Я работаю над симулятором движения с 2 степенями свободы (pitch & roll). Я читаю матрицу преобразования из игры, и мне нужно получить углы и отправить на оборудование для управления двигателями. Поскольку у углов Эйлера есть особенности, я не могу их использовать. Он ведет себя так:

когда это должно понравиться:

Я подготовил онлайн-пример, чтобы лучше показать проблему:

// Get euler angles from model matrix
var mat = model.matrix;
mat.transpose();

var e = new THREE.Euler();
e.setFromRotationMatrix(mat, 'XZY');
var v = e.toVector3();

var pitch = -v.z;
var roll  = -v.x;

http://jsfiddle.net/qajro0ny/3/

Насколько я понимаю, здесь есть две проблемы.

  1. На тренажере отсутствует ось рыскания.
  2. Даже если бы была ось рыскания, двигатели просто не ведут себя как компьютерная графика, т.е. им нужно время, чтобы добраться до целевой позиции.

Я читал о блокировке кардана и даже реализовал фильтр Эйлера, но это не сработало, как ожидалось. Большинство советов о блокировке кардана заключалось в использовании кватернионов, но я не могу управлять физическим двигателем с помощью кватерниона (или могу?).

Порядок осей здесь на самом деле не имеет значения, потому что его изменение только переместит сингулярность с одной оси на другую.

Мне нужно справиться с этим по-другому.

Я попытался умножить векторы осей на матрицу, а затем использовать крест и скалярное произведение, чтобы получить углы, но это тоже не удалось. Я думаю, что для правильного решения необходимо также задействовать перепроецирование оси, но я не мог этого понять. Но что-то мне подсказывает, что это правильный способ сделать это. Это было примерно так: http://jsfiddle.net/qajro0ny/53/

Потом мне пришла в голову другая идея. Я знаю предыдущую позицию, поэтому, возможно, сделаю следующее:

  1. Преобразовать матрицу в кватернион
  2. Вычислить разницу между текущим и предыдущим кватернионом
  3. Преобразуйте полученный кватернион в углы Эйлера
  4. Добавьте эти углы к статическим переменным тангажа, крена и рыскания.

Я попробовал и ... это сработало! Никаких особенностей в любом из направлений, идеальное вращение на 360 градусов по тангажу, крену и рысканью. Идеальное решение! Только вот ... это не так. Кадры не синхронизировались, поэтому через некоторое время углы стали отличаться от должных. Я думал о каком-то механизме синхронизации, но решил, что это неправильный путь.

Выглядело это так: http://jsfiddle.net/qajro0ny/52/

И та же логика, но напрямую с матрицами: http://jsfiddle.net/qajro0ny/54/

Я искал в Интернете повсюду, я прочитал десятки статей и других вопросов / сообщений, и я просто не могу поверить, что в моем случае ничего действительно не работает.

Я могу чего-то не понять или пропустить, поэтому вот все, что я нашел и попробовал:

Ссылки: http://pastebin.com/3G0dYLvu

Код: http://pastebin.com/PiZKwE2t (я собрал все вместе, так что все беспорядочно)

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


person AdrianEddy    schedule 27.06.2015    source источник


Ответы (4)


Если вы знаете, что матрица содержит только два поворота (в заданном порядке T = RZ * RX), вы можете сделать что-то вроде следующего:

Второе вращение не влияет на локальную ось абсцисс. Таким образом, вы можете рассчитать первый угол, используя только локальную ось абсцисс. Затем вы можете удалить это вращение из матрицы и вычислить оставшийся угол от любой из двух других осей:

function calculateAngles() {
    var mat = model.matrix;

    //calculates the angle from the local x-axis
    var pitch = Math.atan2(mat.elements[1], mat.elements[0]);

    //this matrix is used to remove the first rotation
    var invPitch = new THREE.Matrix4();
    invPitch.makeRotationZ(-pitch);

    //this matrix will only contain the roll rotation
    //  rollRot = RZ^-1 * T
    //          = RZ^-1 * RZ * RX
    //          = RX
    var rollRot = new THREE.Matrix4();
    rollRot.multiplyMatrices(invPitch, mat);

    //calculate the remaining angle from the local y-axis
    var roll  = Math.atan2(rollRot.elements[6], rollRot.elements[5]);

    updateSimAngles(pitch, roll);
}

Это, конечно, работает только в том случае, если данная матрица соответствует требованиям. Он не должен содержать третью ротацию. В противном случае вам, вероятно, потребуется найти решение нелинейным методом наименьших квадратов.

person Nico Schertler    schedule 28.06.2015
comment
Спасибо, но, к сожалению, моя матрица содержит третий поворот. Не могли бы вы подробнее рассказать о решении нелинейных наименьших квадратов? Я читал об алгоритме Левенберга – Марквардта, но понятия не имею, как его использовать для моей задачи. - person AdrianEddy; 29.06.2015
comment
Levenberg Marquardt, вероятно, хороший выбор. По сути, вы хотите найти минимизатор || T - RZ(pitch) * RX(roll) ||_2 над pitch и roll. Большинство библиотек позволяют более или менее напрямую подключить это определение. Но я не знаю для этого никакой библиотеки JavaScript. Кроме того, вы всегда получите переворачивание ориентации, если третье вращение станет относительно большим. Вы можете попытаться обойти это с помощью условий регуляризации (разница между последним и текущим решением). Но я не уверен, что это поможет. - person Nico Schertler; 29.06.2015

Ваша идея об использовании дельт вращения на самом деле звучит довольно многообещающе.

Я не совсем понимаю, что вы имеете в виду под «кадрами не синхронизировались». Я мог представить, что ваши вычисления с кватернионами могут быть не на 100% точными (вы используете плавающие точки?), Что приводит к небольшим ошибкам, которые складываются и, в конечном итоге, приводят к асинхронным движениям.

Дело в том, что вы должны использовать кватернионы единиц для представления вращений. Вы можете сделать это в теоретической модели, но если вы представляете кватернионы четырьмя числами с плавающей запятой, ваши кватернионы в большинстве случаев не будут единичными кватернионами, а будут только очень близкими (их норма 1+e для некоторых небольших - и, возможно, отрицательных - значение e). Это нормально, поскольку вы не заметите этих небольших различий, однако, если вы выполняете множество операций над своими кватернионами (что вы делаете, постоянно меняя модель и вычисляя дельты), эти небольшие ошибки будут накапливаться. Таким образом, вам необходимо постоянно перенормировать свои кватернионы, чтобы они были как можно ближе к единичным кватернионам, чтобы ваши вычисления - особенно преобразование обратно в углы Эйлера - оставались (почти) точными.

person H W    schedule 02.07.2015
comment
Даже если я работаю исключительно с 64-битными двойниками (включая исходную матрицу из игры) и нормализую кватернионы после каждого вычисления, углы все равно значительно отклоняются после пары минут постоянного движения. Читаю матрицу 50 раз в секунду. Сейчас я пытаюсь создать какое-то гибридное решение, используя кватернионы для отслеживания правильной ориентации, но вычисляя конечные углы из углов Эйлера. - person AdrianEddy; 02.07.2015
comment
Вы можете увидеть это в действии на этой скрипке: jsfiddle.net/qajro0ny/52. Может я где-то ошибся? Конечно, это всего лишь пример на JS, но поведение почти такое же в C ++. - person AdrianEddy; 03.07.2015

Я добавил свои два цента в http://jsfiddle.net/qajro0ny/58/, в основном применяя работы http://blogs.msdn.com/b/mikepelton/archive/2004/10/29/249501.aspx, на который я наткнулся несколько лет назад. В основном это сводится к

var a = THREE.Math.clamp(-mat.elements[6], -1, 1);
var xPitch = Math.asin(a);
var yYaw = 0;
var zRoll = 0;
if (Math.cos(xPitch) > 0.0001)
{
  zRoll = -Math.atan2(mat.elements[4], mat.elements[5]);
  yYaw = -Math.atan2(mat.elements[2], mat.elements[10]);
}
else
{
  zRoll = -Math.atan2(-mat.elements[1], mat.elements[0]);
}

Я заметил, что предполагаемое отображение рыскания, тангажа, крена по осям (y, x, z), используемое в левой системе координат DirectX, отличается от той, которую вы используете OpenGL / WebGL, поэтому, возможно, с этим нужно окончательно разобраться .

Надеюсь, это поможет.

person mkoertgen    schedule 07.07.2015

Я полагаю, что вы действительно могли бы управлять физическими двигателями с кватернионами. Помните, что кватернион представляет собой ось вращения (x, y, z) и угол. Я предполагаю, что у вас есть три мотора (для каждой оси), скорость вращения которых вы можете масштабировать. Теперь, масштабируя скорость вращения этих трех двигателей, вы можете установить конкретную ось вращения, а путем тщательного измерения времени вы можете установить определенный угол вращения. Это должно быть проще, чем преобразование в дельты углов Эйлера.

person dragn    schedule 07.07.2015
comment
Дело в том, что у меня нет третьего двигателя, не с с таким дизайном. Мне нужно как-то отменить третью ротацию. Но если бы я мог надежно отменить третье вращение, я мог бы легко вычислить углы Эйлера из этого. - person AdrianEddy; 07.07.2015
comment
В порядке. Итак, предположим, что у вас есть целевой кватернион - вращение, которое вы хотите смоделировать. И у вас есть два двигателя, которые вращают по некоторым осям. Вы можете определить два кватерниона, которые соответствуют этим известным осям, но с неизвестными углами. Тогда умножение этих кватернионов должно быть как можно ближе к целевому кватерниону, который может дать вам неизвестные углы ... - person dragn; 07.07.2015
comment
Хотя это подразумевает сложную математику ... :) - person dragn; 07.07.2015
comment
Интересная идея. Я займусь этим (хотя я не очень разбираюсь в сложной математике :)). Спасибо! - person AdrianEddy; 07.07.2015