Повернуть объект вокруг неподвижной оси

12

Я пытаюсь позволить пользователю моего приложения вращать трехмерный объект, нарисованный в центре экрана, перетаскивая палец на экране. Горизонтальное движение на экране означает вращение вокруг фиксированной оси Y, а вертикальное движение означает вращение вокруг оси X. У меня проблема в том, что если я просто позволю вращение вокруг одной оси, объект вращается нормально, но как только я ввожу второе вращение, объект не вращается, как ожидалось.

Вот картина того, что происходит:

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

Синяя ось представляет мою фиксированную ось. Представьте себе экран с фиксированной синей осью. Это то, что я хочу, чтобы объект вращался относительно. То, что происходит, в красном.

Вот что я знаю:

  1. Первое вращение вокруг Y (0, 1, 0) заставляет модель двигаться из синего пространства (назовите это пространство A) в другое пространство (назовите это пространство B)
  2. Попытка повернуть снова, используя вектор (1, 0, 0), вращается вокруг оси x в пространстве B, а НЕ в пространстве A, что я не хочу делать.

Вот что я попробовал, учитывая то, что я (думаю) я знаю (опуская W координаты для краткости):

  1. Сначала поверните вокруг Y (0, 1, 0), используя кватернион.
  2. Преобразовать вращение Y Quaternion в Матрицу.
  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;
  }

Я застрял на этом в течение нескольких дней. Помощь очень ценится.

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

ОБНОВИТЬ:

Заставить это работать в Unity просто. Вот некоторый код, который вращает куб с центром в начале координат:

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
Я понимаю концепцию, лежащую в основе вашего ответа, но как реализовать это ускользает от меня.
Кристофер Перри
Если вы проверили другие ответы, syntac answer реализует идею, которую я объяснил.
concept3d
Нет, это не так, он делает несколько поворотов вокруг разных осей. Вы предлагаете сделать один оборот.
Кристофер Перри

Ответы:

3

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

Более простой способ сделать это - найти 2-мерный вектор, перпендикулярный 2-мерному смахиванию на экране.

Возьмите вектор и спроецируйте его на камеру около самолета, чтобы получить трехмерный вектор в мировом пространстве. Пространство экрана в мировое пространство .

Затем создайте кватернион с этим вектором и умножьте его на игровой объект. Вероятно, с каким-то переходом с хлебным пятном или без изменений.

Редактировать:

Пример Unity: В примере Unity внутренним состоянием вращения игровых объектов является кватернион, а не матрица. Метод 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 Rotation Opengl Учебное пособие

Энтони Раймондо
источник
У меня нет блокировки карданного подвеса, первое вращение перемещает ось, поэтому второе вращение основано на перемещенной оси. Пожалуйста, взгляните еще раз на картинку, которую я предоставил.
Кристофер Перри
Я надеюсь, что вы поняли это. Короче говоря. Кватернионы можно умножить вместе, чтобы применить вращение. Вы должны генерировать матрицу только в конце всех вычислений вращения. Также 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

Ваше изображение соответствует вашему коду RotateMatrix, вращая ось 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()они не коммутативны, поэтому учитывается порядок, в котором вы их делаете. Добавьте еще один комментарий с вашим фреймворком / языком, если это не сработало, и я углублюсь в это немного больше.
Даниэль Карлссон
Это тоже не работает, у меня такое же поведение.
Кристофер Перри
Я поместил ВСЕ свой код здесь
Кристофер Перри