Я пытаюсь позволить пользователю моего приложения вращать трехмерный объект, нарисованный в центре экрана, перетаскивая палец на экране. Горизонтальное движение на экране означает вращение вокруг фиксированной оси Y, а вертикальное движение означает вращение вокруг оси X. У меня проблема в том, что если я просто позволю вращение вокруг одной оси, объект вращается нормально, но как только я ввожу второе вращение, объект не вращается, как ожидалось.
Вот картина того, что происходит:
Синяя ось представляет мою фиксированную ось. Представьте себе экран с фиксированной синей осью. Это то, что я хочу, чтобы объект вращался относительно. То, что происходит, в красном.
Вот что я знаю:
- Первое вращение вокруг Y (0, 1, 0) заставляет модель двигаться из синего пространства (назовите это пространство A) в другое пространство (назовите это пространство B)
- Попытка повернуть снова, используя вектор (1, 0, 0), вращается вокруг оси x в пространстве B, а НЕ в пространстве A, что я не хочу делать.
Вот что я попробовал, учитывая то, что я (думаю) я знаю (опуская W координаты для краткости):
- Сначала поверните вокруг Y (0, 1, 0), используя кватернион.
- Преобразовать вращение Y Quaternion в Матрицу.
- Умножьте матрицу вращения Y на мою фиксированную ось x Vector (1, 0, 0), чтобы получить ось X относительно нового пространства.
- Поверните вокруг этого нового вектора 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, я бы, к сожалению, сам должен был написать код этого поведения.
Ответы:
Проблема, которую вы испытываете, называется gimble lock . Я думаю, то, что вы хотите сделать, называется вращением арбалета . Математика для аркбола может быть довольно сложной.
Более простой способ сделать это - найти 2-мерный вектор, перпендикулярный 2-мерному смахиванию на экране.
Возьмите вектор и спроецируйте его на камеру около самолета, чтобы получить трехмерный вектор в мировом пространстве. Пространство экрана в мировое пространство .
Затем создайте кватернион с этим вектором и умножьте его на игровой объект. Вероятно, с каким-то переходом с хлебным пятном или без изменений.
Редактировать:
Пример Unity: В примере Unity внутренним состоянием вращения игровых объектов является кватернион, а не матрица. Метод transform.rotation генерирует кватернион на основе заданного вектора и угла и умножает этот кватернион на кватернион вращения игровых объектов. Он только генерирует матрицу вращения для рендеринга или физики на более позднем этапе. Кватернионы являются аддитивными и избегают запираемости.
Ваш код должен выглядеть примерно так:
ArcBall Rotation Opengl Учебное пособие
источник
Мне удалось получить ожидаемые вращения, вращая накопленную матрицу вращения.
источник
Ваше изображение соответствует вашему коду RotateMatrix, вращая ось X с вашим предыдущим вращением Y, вы получаете локальную ось X, а затем поворачиваете объект вокруг, чтобы получить результат, который вы показываете на своем изображении. Чтобы вращение было логичным с точки зрения пользователя, вам нужно повернуть объект, используя вместо этого мировую координатную ось.
Если вы хотите, чтобы ваш пользователь мог вращать ваш объект много раз, то имеет смысл хранить вращение в кватернионе вместо матрицы, с течением времени множественные вращения (и неточности с плавающей запятой) приведут к тому, что матрица будет выглядеть все меньше и меньше с матрицей вращения, то же самое происходит в кватернионе, конечно, но простая нормализация кватерниона возвращает его к хорошему вращению.
Просто используйте идентификационный кватернион в качестве начального значения, и каждый раз, когда пользователь проводит по экрану, вы поворачиваете кватернион с помощью
Quaternion.fromAxisAngle(yAxis, -angleX)
кода. Всегда используйте (1,0,0,1) для вращения x и (0,1,0,1) для вращения y.Поскольку вы не упомянули язык или какой-либо конкретный каркас, методы в Quaternion, конечно, могут называться как-то по-другому, и нормализация не обязательна для того, чтобы вызывать это часто, но поскольку вращение происходит от пользователя, проводящего по экрану, они не сильно замедляет ход событий и, таким образом, нет шансов, что кватернион ускользнет от единичного кватерниона.
источник
myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()
они не коммутативны, поэтому учитывается порядок, в котором вы их делаете. Добавьте еще один комментарий с вашим фреймворком / языком, если это не сработало, и я углублюсь в это немного больше.