Как вращать объект вокруг мировых осей?

15

У меня есть Vector3, который имеет угол Эйлера для каждой оси.

Обычно, когда я хочу создать матрицу вращения, я буду использовать функции, такие как D3DXMatrixRotationX, передавая соответствующий угол из моего вектора вращения выше, и умножать матрицы (ZXY), чтобы создать общую матрицу вращения, которая используется для формирования полной матрицы преобразования объекта.

Однако этот метод будет производить набор поворотов в пространстве объектов. То есть передача вектора (90, 0, 90) в мой метод создаст вращение в мировом пространстве эффективно (90, 90, 0).

Есть ли способ всегда гарантировать, что каждый компонент моего вектора вращения приводит к вращению вокруг соответствующих осей, выровненных по пространству мира?

РЕДАКТИРОВАТЬ:

Это анимация происходящего в настоящее время - я хочу, чтобы способ вращался вокруг синих осей, а не красных.

Эйлеровы углы

РЕДАКТИРОВАТЬ 2:

Просто чтобы заметить, я не ищу решение, включающее углы Эйлера, а просто способ, которым я могу представить преобразование нескольких вращений вокруг мировых осей.

Syntac_
источник
Что плохого в том, чтобы просто вызывать функции difnet три раза и отфильтровывать ненужные части векторов (устанавливая их в 0 перед вызовом функции)? В противном случае я не уверен, чего вы пытаетесь достичь.
TravisG
Что отфильтровывать? Я вызываю 3 отдельные функции, а затем умножаю их для создания матрицы преобразования. Это архивирует местную ротацию, хотя.
Syntac_
Вы хотите углы Эйлера или вращение вокруг мировых осей? Обратите внимание, что по определению углов Эйлера (например, en.wikipedia.org/wiki/Euler_angles ) только угол альфа строго направлен вокруг мировой оси. Два других угла относятся к наклонным осям, которые не обязательно совпадают с мировыми осями.
DMGregory
1
Используя углы Эйлера, вы умножаете все три матрицы вращения перед тем, как применить их к вершине. Если M, N, O являются матрицами вращения, результатом операции будет MNO v. Я предложил применить каждую матрицу отдельно: v1 = O v0, затем v2 = N v1 и, наконец, v3 = M v2. Таким образом, каждый vi будет находиться в мировых координатах, и вам просто нужно использовать матрицу вращения для текущей оси в мировых координатах.
dsilva.vinicius
3
@ dsilva.vinicius Ваши разделенные преобразования точно такие же, как объединенные, или, иначе говоря, MNO v == M * (N * (O v))
GuyRT

Ответы:

1

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

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

Как отмечает Чарльз Битти , из-за того, что вращения не коммутируют, это не будет работать так, как ожидалось, если игрок не поворачивает объект в том же порядке, в котором eulerAnglesToMatrix()применяет вращения.

В частности, рассмотрим следующую последовательность вращений:

  1. повернуть объект на x градусов вокруг оси X;
  2. повернуть объект на y градусов вокруг оси Y;
  3. повернуть объект на - x градусов вокруг оси X;
  4. повернуть объект на - y градусов вокруг оси Y.

В наивном представлении угла Эйлера, как реализовано в псевдокоде выше, эти повороты будут отменены, и объект вернется к своей первоначальной ориентации. В реальном мире этого не происходит - если вы мне не верите, возьмите шестигранный кубик или кубик Рубика, пусть x = y = 90 °, и попробуйте сами!

Решение, как вы заметили в своем собственном ответе , состоит в том, чтобы сохранить ориентацию объекта в виде матрицы вращения (или кватерниона) и обновить эту матрицу на основе пользовательского ввода. То есть вместо псевдокода выше вы бы сделали что-то вроде этого:

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(Технически, так как любая матрица поворота или кватернионы может быть представлена в виде набора углов Эйлера, то есть их можно использовать для хранения ориентации объекта. Но физически правильное правило для объединения два последовательных поворотов, каждый из которых представлен в виде углов Эйлера, в одно вращение довольно сложно, и, по сути, сводится к преобразованию вращений в матрицы / кватернионы, умножению их, а затем преобразованию результата обратно в углы Эйлера.)

Илмари Каронен
источник
Да, вы правы, это было решение. Я чувствую, что это немного лучше, чем ответ concept3d, поскольку он создает впечатление, что кватернион необходим, но это не так. Пока я сохранял текущее вращение в виде матрицы, а не три угла Эйлера, все было в порядке.
Syntac_
15

Проблема с вращениями заключается в том, что большинство людей думают об этом с точки зрения углов Эйлера, поскольку их легко понять.

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

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

Это должно происходить с любыми 3 последовательными вращениями, даже при использовании кватернионов.

введите описание изображения здесь

Я хочу подчеркнуть тот факт, что кватернионы не являются решением для блокировки gimble. На самом деле блокировка будет всегда происходить, если вы представляли углы Эйлера с помощью кватернионов. Проблема не в представлении, проблема в 3 последовательных шагах.

Решение?

Решение для поворота вектора вокруг 3-х осей независимо состоит в объединении в одну ось и один угол, таким образом вы можете избавиться от шага, на котором вы должны выполнить последовательное умножение. Это будет эффективно переводить на:

Моя матрица вращения представляет результат вращения вокруг X, Y и Z.

а не эйлерова интерпретация

Моя матрица вращения представляет вращение вокруг X, затем Y, затем Z.

Чтобы прояснить это, я процитирую из теоремы Эйлера о вращении:

Согласно теореме Эйлера о вращении, любое вращение или последовательность вращений твердого тела или системы координат вокруг неподвижной точки эквивалентно одному вращению на заданный угол вокруг неподвижной оси (называемой осью Эйлера), которая проходит через неподвижную точку. Ось Эйлера обычно представлена ​​единичным вектором u →. Следовательно, любое вращение в трех измерениях можно представить как комбинацию вектора u → и скаляра θ. Кватернионы дают простой способ кодировать это представление оси-угла четырьмя числами и применять соответствующий поворот к вектору положения, представляющему точку относительно начала координат в R3.

Обратите внимание, что умножение 3 матриц всегда будет представлять 3 последовательных поворота.

Теперь для того, чтобы объединить вращения вокруг 3 осей, вам нужно получить одну ось и один угол, который представляет вращение вокруг X, Y, Z. Другими словами, вам нужно использовать представление оси / угла или кватерниона, чтобы избавиться от последовательных поворотов.

Обычно это делается, начиная с начальной ориентации (ориентацию можно рассматривать как угол оси), обычно представляемой как кватернион или угол оси, и затем изменяя эту ориентацию, чтобы представить ориентацию назначения. Например, вы начинаете с кватерона идентификации, а затем поворачиваетесь на разницу, чтобы достичь ориентации назначения. Таким образом, вы не теряете никакой степени свободы.

concept3d
источник
Отмечено как ответ, поскольку это кажется проницательным.
Syntac_
У меня возникли проблемы с выяснением того, что вы пытаетесь сказать этим ответом. Это просто «не хранить ориентацию объекта как углы Эйлера»? И если так, то почему бы просто не сказать так?
Ильмари Каронен,
@IlmariKaronen Можно было бы сформулировать более четко, но я думаю, что concept3d продвигает представление под углом; см. раздел 1.2.2 этого документа для связи между осевым углом и кватернионами. Представление осевого угла проще реализовать по указанным выше причинам, оно не страдает от блокировки карданного подвеса, и (по крайней мере для меня) это так же легко понять, как и углы Эйлера.
NauticalMile
@ concept3d, это очень интересно, и мне очень нравится твой ответ. Однако мне не хватает одной вещи: люди взаимодействуют с компьютером с помощью клавиатуры и мыши, если мы думаем о мышке, то мы говорим о дельтах мыши x и y. Как представить эти дельты x, y одним кватернионом, который мы можем использовать для генерации матрицы вращения, например, для изменения ориентации объекта?
Гманьо
@gmagno подход обычно состоит в том, чтобы проецировать движение мыши по объектам или сцене и вычислять дельты в этом пространстве, вы делаете это путем наведения луча и вычисления пересечения. Поиск лучей, проектирование и непроектирование, я грубо разбираюсь в деталях, так как не работал в CG уже много лет. надеюсь, это поможет.
concept3d
2

Переключение комбинации вращений из пространства объектов в мировое пространство тривиально: вам просто нужно изменить порядок, в котором применяются вращения.

В вашем случае вместо умножения матриц Z × X × Yвам просто нужно вычислить Y × X × Z.

Обоснование этому можно найти в Википедии: преобразование между внутренним и внешним вращением .

Сэм Хоцевар
источник
Если бы это было правдой, то следующее утверждение из вашего источника не было бы правдой, потому что вращения были бы другими: «Любое внешнее вращение эквивалентно внутреннему вращению на те же углы, но с инвертированным порядком элементных вращений, и наоборот. «.
Syntac_
1
Я не вижу здесь противоречия. И мой ответ, и это утверждение верны. И да, выполнение вращений в пространстве объектов и в мировом пространстве приводит к различным вращениям; в том-то и дело, не так ли?
Сэм Хоцевар
Это утверждение говорит о том, что изменение порядка всегда приведет к одному и тому же повороту. Если один ордер производит неправильное вращение, то другой ордер тоже будет означать, что это не решение.
Syntac_
1
Вы неправильно читаете. Изменение порядка не приводит к тому же повороту. Изменение порядка и переключение с внутренних вращений на внешние вращения приводит к тому же вращению.
Сэм Хоцевар
1
Я не думаю, что понимаю ваш вопрос. Ваш GIF показывает поворот примерно на 50 градусов вокруг Z(пространство объекта), затем на 50 градусов вокруг X(пространство объекта), затем на 45 градусов вокруг Y(пространство объекта). Это точно так же, как вращение на 45 градусов вокруг Y( мировое пространство ), затем на 50 градусов вокруг X( мировое пространство ), затем на 50 градусов вокруг Z( мировое пространство ).
Сэм Хочевар
1

Я предоставлю свое решение в качестве ответа, пока кто-нибудь не сможет объяснить, почему это работает.

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

Однако, чтобы сохранить его вокруг мировых осей, мне пришлось сохранить кватернион по всем кадрам и вращать объекты только с разницей в углах, т.е.

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(Многословно для читабельности)

Я думаю, что dsilva.vinicius пытался добраться до этой точки.

Syntac_
источник
1

Вам нужно будет хранить порядок поворотов.

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

Сохраните текущую матрицу вращения и предварительно умножьте каждый поворот по мере их поступления.

Чарльз Битти
источник
0

В дополнение к ответу @ concept3d вы можете использовать 3 матрицы внешнего вращения для вращения вокруг оси в мировых координатах. Цитата из Википедии :

Внешние вращения - это элементарные вращения, которые происходят вокруг осей фиксированной системы координат xyz. Система XYZ вращается, а xyz фиксируется. Начиная с XYZ, перекрывающего XYZ, можно использовать композицию из трех внешних вращений для достижения любой целевой ориентации для XYZ. Углы Эйлера или Тайта Брайана (α, β, γ) являются амплитудами этих элементарных вращений. Например, целевая ориентация может быть достигнута следующим образом:

XYZ-система вращается вокруг оси z на α. Ось X теперь находится под углом α относительно оси x.

XYZ-система снова вращается вокруг оси x на β. Ось Z теперь находится под углом β относительно оси z.

XYZ-система вращается в третий раз вокруг оси z на γ.

Матрицы вращения могут использоваться для представления последовательности внешних вращений. Например,

R = Z (γ) Y (β) X (α)

представляет собой композицию внешних вращений вокруг осей xyz, если она используется для предварительного умножения векторов столбцов, тогда как

R = X (α) Y (β) Z (γ)

представляет точно такую ​​же композицию, когда используется для пост-умножения векторов строк.

Так что вам нужно инвертировать порядок поворотов относительно того, что вы будете делать, используя внутренние (или локальные пространства) повороты. @Syntac попросил вращение zxy, поэтому мы должны сделать внешнее вращение yxz для достижения того же результата. Код ниже:

Объяснение значений матрицы здесь .

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

Этот код является дидактическим, а не оптимальным, поскольку вы можете использовать несколько матриц D3DXMATRIX.

dsilva.vinicius
источник
1
извини, это не правильно. матричное / векторное умножение ассоциативно. это точно так же, как комбинированное матричное умножение.
concept3d
Вы правы. Я смешал понятия внешних и внутренних вращений.
dsilva.vinicius
Я исправлю этот ответ.
dsilva.vinicius
Ответ сейчас исправлен.
dsilva.vinicius