Обертати об'єкт навколо нерухомої осі


12

Я намагаюся дозволити користувачеві мого додатка обертати 3D-об’єкт, намальований в центрі екрана, перетягуючи пальцем по екрану. Горизонтальний рух на екрані означає обертання навколо нерухомої осі Y, а вертикальний рух означає обертання навколо осі X. Проблема, яка у мене виникає, полягає в тому, що якщо я просто дозволю обертання навколо однієї осі, об'єкт обертається нормально, але як тільки я ввожу друге обертання, об'єкт не обертається так, як очікувалося.

Ось картина того, що відбувається:

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

Синя вісь представляє мою нерухому вісь. Зобразіть екран із фіксованою синьою віссю. Це те, що я хочу, щоб об'єкт обертався по відношенню до. Те, що відбувається, - червоним кольором.

Ось що я знаю:

  1. Перше обертання навколо Y (0, 1, 0) змушує модель переміщатися з синього простору (називати цей простір A) в інший простір (називати цей простір B)
  2. Повторна спроба обертання за допомогою вектора (1, 0, 0) обертається навколо осі х у просторі B НЕ в просторі A, що я не маю на увазі робити.

Ось що я спробував, враховуючи те, що я (думаю) знаю (залишаючи W координату для стислості):

  1. Спочатку обертайтеся навколо Y (0, 1, 0), використовуючи кватерніон.
  2. Перетворіть обертання Y Кватерніона в матрицю.
  3. Помножте матрицю обертання Y на мою нерухому вісь x Vector (1, 0, 0), щоб отримати вісь X по відношенню до нового простору.
  4. Обертайтеся навколо цього нового X-вектора за допомогою кватерніона.

Ось код:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};
    float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();

    // multiply x axis by rotationY to put it in object space
    float[] xAxisObjectSpace = new float[4];
    multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);

    float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();

    float[] rotationMatrix = new float[16];
    multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
    return rotationMatrix;
  }

Це не працює, як я очікую. Обертання, здається, працює, але в якийсь момент горизонтальний рух не обертається навколо осі Y, воно, здається, обертається навколо осі Z.

Я не впевнений, чи моє розуміння невірно, чи щось інше спричиняє проблеми. У мене є деякі інші перетворення, які я роблю об'єкту, крім обертання. Я переміщую об’єкт до центру перед застосуванням обертання. Я обертаю його за допомогою матриці, повернутої з моєї функції вище, потім перекладаю її -2 у напрямку Z, щоб я міг бачити об’єкт. Я не думаю, що це псує мої ротації, але ось код для цього все одно:

private float[] getMvpMatrix() {
    // translates the object to where we can see it
    final float[] translationMatrix = new float[16];
    setIdentityM(translationMatrix, 0);
    translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);

    float[] rotationMatrix = rotationMatrix();

    // centers the object
    final float[] centeringMatrix = new float[16];
    setIdentityM(centeringMatrix, 0);
    float moveX = (extents.max[0] + extents.min[0]) / 2f;
    float moveY = (extents.max[1] + extents.min[1]) / 2f;
    float moveZ = (extents.max[2] + extents.min[2]) / 2f;
    translateM(centeringMatrix, 0, //
      -moveX, //
      -moveY, //
      -moveZ //
    );

    // apply the translations/rotations
    final float[] modelMatrix = new float[16];
    multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
    multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);

    final float[] mvpMatrix = new float[16];
    multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
    return mvpMatrix;
  }

Я затримався на цьому кілька днів. Допомога дуже цінується.

===================================================== ================

ОНОВЛЕННЯ:

Домогтися цього працювати в Єдності просто. Ось код, який обертає куб із центром у походження:

public class CubeController : MonoBehaviour {

    Vector3 xAxis = new Vector3 (1f, 0f, 0f);
    Vector3 yAxis = new Vector3 (0f, 1f, 0f);

    // Update is called once per frame
    void FixedUpdate () {
        float horizontal = Input.GetAxis ("Horizontal");
        float vertical = Input.GetAxis ("Vertical");

        transform.Rotate (xAxis, vertical, Space.World);
        transform.Rotate (yAxis, -horizontal, Space.World);
    }
}

Частина, яка змушує обертання вести себе так, як я очікую, - це Space.Worldпараметр Rotateфункції на перетворенні.

Якби я міг використовувати Unity, я б, на жаль, мав сам кодувати цю поведінку.


1
Моя відповідь тут gamedev.stackexchange.com/questions/67199/… могла б допомогти вам ..
concept3d

Я розумію концепцію, що стоїть за вашою відповіддю, але те, як реалізувати, мені уникає.
Крістофер Перрі

Якщо ви перевірили інші відповіді, синтаксична відповідь реалізує ідею, яку я пояснив.
concept3d

Ні, це не так, він робить кілька обертів щодо різних осей. Ви пропонуєте зробити одне обертання.
Крістофер Перрі

Відповіді:


3

Проблема, яка виникає у вас, називається gimble lock . Я думаю, що те, що ти шукаєш, називається обертанням аркболу . Математика для аркболу може бути складною.

Найпростіший спосіб зробити це пошук 2d вектора, перпендикулярного до пальця 2d на екрані.

Візьміть вектор і проектуйте його на камеру біля площини, щоб отримати 3d вектор у світовому просторі. Екранний простір до світового простору .

Потім створіть кватерніон з цим вектором і помножте його на ігровий об’єкт. Можливо, з якимсь перервою або лерпним переходом.

Редагувати:

Приклад єдності: У прикладі єдності внутрішній стан обертання ігрових об'єктів є четвертинником, а не матрицею. Метод transform.rotation генерує кватерніон на основі наданого вектора та кута та примножує цей кватерніон на кватерніон обертання ігрових об'єктів. Він створює лише матрицю обертання для візуалізації або фізики в більш пізній момент. Кватерніони є добавкою і уникають блокування приступів.

Ваш код повинен виглядати приблизно так:

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

Підручник з обертання ArcBall


Я не отримую карданного блокування, перше обертання переміщує вісь, а друге обертання засноване на переміщеній осі. Погляньте, будь ласка, на другий малюнок, який я надав.
Крістофер Перрі

Я сподіваюся, ви це зрозуміли. Коротко. Кватерніони можна множити разом, щоб застосувати обертання. Ви повинні генерувати матрицю лише в кінці всіх обчислень обертання. Також xQ * yQ не дорівнює yQ * xQ. Кватерніони не є комутативними, як сказав Крістофер Перрі.
Ентоні Раймондо

Я поклав все з мого коду тут . Я відчуваю, що я все пробував. Можливо, хтось інший погляд на це вловить мою помилку.
Крістофер Перрі

Я не прийняв це, алгоритм стеків обмінів автоматично призначив вам бали. : /
Крістофер Перрі

Мені шкода цієї несправедливості.
Ентоні Раймондо

3

Я зміг отримати очікувані обертання, обертаючи накопичену матрицю обертання.

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);

1

Ваше зображення відповідає вашому коду rotatationMatrix, обертаючи вісь x з попереднім обертанням y, ви отримуєте локальну вісь x, коли потім обертаєте об'єкт навколо, щоб отримати результат, який ви показуєте на своєму зображенні. Щоб обертання було логічним з точки зору користувачів, ви б хотіли обертати об'єкт, використовуючи натомість вісь світової координати.

Якщо ви хочете, щоб ваш користувач міг обертати ваш об'єкт багато разів, було б доцільно зберігати обертання в кватерніоні замість матриці, з часом багато обертів (і неточності з плаваючою комою) змусять матрицю виглядати все менше і менше матриця обертання, те ж саме відбувається в кватерніоні, звичайно, але саме нормалізація кватерніона повертає її до хорошого обертання.

Просто використовуйте кватерніон ідентичності як початкове значення, і кожен раз, коли користувач проведе екраном, ви обертаєте ваш кватерніон з Quaternion.fromAxisAngle(yAxis, -angleX)кодом. Завжди використовуючи (1,0,0,1) для x обертів і (0,1,0,1) для y обертання.

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

Оскільки ви не згадали про мову чи якийсь конкретний фреймворк, методи на Quaternion, звичайно, можуть називатися чимось інакше, і нормалізувати не потрібно так часто дзвонити, але оскільки обертання відбувається від того, хто користувач перебирає екран, вони не будуть сильно сповільнюють справи, і таким чином немає шансів, що кватерніон вислизає з кватерніона.


Я отримую саме таку поведінку, роблячи це.
Крістофер Перрі

1
@ChristopherPerry Спробуйте змінити порядок обертання: myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()вони не комутативні, тому порядок, який ви виконуєте, враховується. Додайте ще один коментар зі своєю рамкою / мовою, якщо це не спрацювало, і я ще більше копаюсь у ньому.
Даніель Карлссон

Це не працює, я маю таку ж поведінку.
Крістофер Перрі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.