Opengl: удержание вектора вверх камеры Arcball на оси Y

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

Я смог реализовать Arcball в OpenGL с использованием C ++ и Qt. Но я не могу понять, как сохранить его направленный вверх вектор. Я был в состоянии держать это время от времени моим кодом ниже:

void ArcCamera::setPos (Vector3 np)
{
Vector3 up(0, 1, 0);

Position = np;
ViewDir = (ViewPoint - Position); ViewDir.normalize();

RightVector =  ViewDir ^ up; RightVector.normalize();

UpVector = RightVector ^ ViewDir;  UpVector.normalize();
}

Это работает до тех пор, пока положение не станет равным 90 градусам, затем правый вектор изменится и все будет инвертировано.

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

void CCamera::setRot (QQuaternion q)
{
tot = tot * q;

Position  = tot.rotatedVector(PositionOriginal);

UpVector = tot.rotatedVector(UpVectorOriginal);
UpVector.normalize();

RightVector  = tot.rotatedVector(RightVectorOriginal);
RightVector.normalize();
}

QQuaternion q генерируется из пары ось-угол, полученной из перетаскивания мышью. Я уверен, что это сделано правильно. Само вращение в порядке, оно просто не поддерживает ориентацию.

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

2

Решение

Причина, по которой это не работает, заключается в том, что манипуляции с камерой Maya в области просмотра не используют интерфейс arcball. Что вы хотите сделать, это Команда падения майя. Лучший ресурс, который я нашел, чтобы объяснить это этот документ из класса компьютерной графики профессора Орра.

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

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

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

translate(pivotPosition) * rotate(angleX, angleY, angleZ) * translate(-pivotPosition)

Мы можем использовать это для вычисления азимутального компонента вращения, который является вращением вокруг мировой оси Y:

azimuthRotation = translate(pivotPosition) * rotateY(angleY) * translate(-pivotPosition)

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

elevationRotation = translate(worldToViewMatrix * pivotPosition) * rotateX(angleX) * translate(worldToViewMatrix * -pivotPosition)

Затем мы можем получить новую матрицу вида с помощью:

newWorldToViewMatrix = elevationRotation * worldToViewMatrix * azimuthRotation

Теперь, когда у нас есть новая матрица worldToView, нам осталось извлечь новое положение и ориентацию в мировом пространстве из матрицы вида. Для этого нам нужна матрица viewToWorld, которая является обратной к матрице worldToView.

newOrientation = transpose(mat3(newWorldToViewMatrix))
newPosition = -((newOrientation * newWorldToViewMatrix).column(3))

На данный момент у нас есть отдельные элементы. Если ваша камера настроена так, что вы сохраняете кватернион только для своей ориентации, вам просто нужно сделать матрицу вращения -> кватернионное преобразование. Конечно, Maya собирается преобразовать в углы Эйлера для отображения в окне канала, которое будет зависеть от порядка вращения камеры (обратите внимание, что математика для акробатики не меняется при изменении порядка вращения, как способ вращения). матрица -> преобразование углов Эйлера).

Вот пример реализации в Python:

#!/usr/bin/env python

import numpy as np
from math import *

def translate(amount):
'Make a translation matrix, to move by `amount`'
t = np.matrix(np.eye(4))
t[3] = amount.T
t[3, 3] = 1
return t.T

def rotateX(amount):
'Make a rotation matrix, that rotates around the X axis by `amount` rads'
c = cos(amount)
s = sin(amount)

return np.matrix([
[1, 0, 0, 0],
[0, c,-s, 0],
[0, s, c, 0],
[0, 0, 0, 1],
])

def rotateY(amount):
'Make a rotation matrix, that rotates around the Y axis by `amount` rads'
c = cos(amount)
s = sin(amount)
return np.matrix([
[c, 0, s, 0],
[0, 1, 0, 0],
[-s, 0, c, 0],
[0, 0, 0, 1],
])

def rotateZ(amount):
'Make a rotation matrix, that rotates around the Z axis by `amount` rads'
c = cos(amount)
s = sin(amount)
return np.matrix([
[c,-s, 0, 0],
[s, c, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
])

def rotate(x, y, z, pivot):
'Make a XYZ rotation matrix, with `pivot` as the center of the rotation'
m = rotateX(x) * rotateY(y) * rotateZ(z)

I = np.matrix(np.eye(4))
t = (I-m) * pivot
m[0, 3] = t[0, 0]
m[1, 3] = t[1, 0]
m[2, 3] = t[2, 0]

return m

def eulerAnglesZYX(matrix):
'Extract the Euler angles from an ZYX rotation matrix'
x = atan2(-matrix[1, 2], matrix[2, 2])
cy = sqrt(1 - matrix[0, 2]**2)
y = atan2(matrix[0, 2], cy)
sx = sin(x)
cx = cos(x)
sz = cx * matrix[1, 0] + sx * matrix[2, 0]
cz = cx * matrix[1, 1] + sx * matrix[2, 1]
z = atan2(sz, cz)
return np.array((x, y, z),)

def eulerAnglesXYZ(matrix):
'Extract the Euler angles from an XYZ rotation matrix'
z = atan2(matrix[1, 0], matrix[0, 0])
cy = sqrt(1 - matrix[2, 0]**2)
y = atan2(-matrix[2, 0], cy)
sz = sin(z)
cz = cos(z)
sx = sz * matrix[0, 2] - cz * matrix[1, 2]
cx = cz * matrix[1, 1] - sz * matrix[0, 1]
x = atan2(sx, cx)
return np.array((x, y, z),)

class Camera(object):
def __init__(self, worldPos, rx, ry, rz, coi):
# Initialize the camera orientation.  In this case the original
# orientation is built from XYZ Euler angles.  orientation is the top
# 3x3 XYZ rotation matrix for the view-to-world matrix, and can more
# easily be thought of as the world space orientation.
self.orientation = \
(rotateZ(rz) * rotateY(ry) * rotateX(rx))

# position is a point in world space for the camera.
self.position = worldPos

# Construct the world-to-view matrix, which is the inverse of the
# view-to-world matrix.
self.view = self.orientation.T * translate(-self.position)

# coi is the "center of interest".  It defines a point that is coi
# units in front of the camera, which is the pivot for the tumble
# operation.
self.coi = coi

def tumble(self, azimuth, elevation):
'''Tumble the camera around the center of interest.

Azimuth is the number of radians to rotate around the world-space Y axis.
Elevation is the number of radians to rotate around the view-space X axis.
'''
# Find the world space pivot point.  This is the view position in world
# space minus the view direction vector scaled by the center of
# interest distance.
pivotPos = self.position - (self.coi * self.orientation.T[2]).T

# Construct the azimuth and elevation transformation matrices
azimuthMatrix = rotate(0, -azimuth, 0, pivotPos)
elevationMatrix = rotate(elevation, 0, 0, self.view * pivotPos)

# Get the new view matrix
self.view = elevationMatrix * self.view * azimuthMatrix

# Extract the orientation from the new view matrix
self.orientation = np.matrix(self.view).T
self.orientation.T[3] = [0, 0, 0, 1]

# Now extract the new view position
negEye = self.orientation * self.view
self.position = -(negEye.T[3]).T
self.position[3, 0] = 1

np.set_printoptions(precision=3)

pos = np.matrix([[5.321, 5.866, 4.383, 1]]).T
orientation = radians(-60), radians(40), 0
coi = 1

camera = Camera(pos, *orientation, coi=coi)
print 'Initial attributes:'
print np.round(np.degrees(eulerAnglesXYZ(camera.orientation)), 3)
print np.round(camera.position, 3)
print 'Attributes after tumbling:'
camera.tumble(azimuth=radians(-40), elevation=radians(-60))
print np.round(np.degrees(eulerAnglesXYZ(camera.orientation)), 3)
print np.round(camera.position, 3)
3

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

Следите за своими взглядами и правыми векторами с самого начала и обновляйте их с помощью матрицы вращения. Затем вычислите свой вектор роста.

0