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

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

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

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

// 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 (Я собрал все это вместе, так что это грязно)

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

23

Решение

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

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

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);
}

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

7

Другие решения

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

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

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

2

Я добавил свои два цента в 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, так что, возможно, это нужно окончательно уладить ,

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

2

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

1
По вопросам рекламы [email protected]