8.3 Trackball virtual
Em aplicações de visualização interativa de objetos 3D, é desejável que exista um controle de interação de rotação que permita rodar o objeto em torno de si mesmo de modo a visualizá-lo de qualquer direção.
Podemos implementar tal interação de rotação através de três widgets do tipo slider, cada um correspondendo a um ângulo de rotação em \(x\), \(y\) e \(z\) que pode ser controlado pelo usuário. Do ponto de vista da implementação, podemos usar esses ângulos para rodar o objeto através de uma matriz de modelo composta pelas seguintes matrizes de rotação
\[ \mathbf{M}_{\textrm{model}}=\mathbf{R}_z\mathbf{R}_y\mathbf{R}_x. \]
Nesse tipo de rotação, os ângulos em \(x\), \(y\) e \(z\) são chamados de ângulos de Euler. Dada qualquer orientação no espaço, existe uma sequência específica de ângulos de Euler que levam o objeto para a orientação desejada. As rotações não precisam ser na ordem \(xyz\). Qualquer permutação dos eixos pode ser utilizada, mas cada ordem produz ângulos de Euler diferentes para chegar a uma mesma orientação.
Embora seja certo que existam ângulos de Euler para chegar a qualquer orientação desejada, o usuário provavelmente terá dificuldades em determinar os valores dos sliders necessários para levar o objeto à orientação-alvo.
Existe uma forma mais intuitiva de rodar um objeto 3D em torno de si mesmo. Se tivermos um dispositivo de entrada do tipo trackball, a rotação do objeto pode corresponder diretamente à rotação do dispositivo. A figura 8.18 mostra um trackball típico, com eixos principais sobrepostos na foto.
Se o usuário rodar o trackball para frente ou para trás, na direção de \(\pm y\), obteremos uma rotação em torno de \(x\). Se rodar para os lados, na direção de \(\pm x\), obteremos uma rotação em torno de \(y\). Enfim, qualquer rotação do trackball equivale a uma rotação em torno de um eixo (não necessariamente os eixos principais).
Podemos simular a rotação livre de um trackball através de um mouse comum, usando a técnica de Virtual Sphere (Chen, Mountford, and Sellen 1988) ou Arcball (Shoemake 1992) (figura 8.19).
Para simplificar, vamos supor que \(\mathbf{M}_{\textrm{model}}=\mathbf{I}\), e que o objeto 3D está centralizado na origem.
Primeiramente, considere uma esfera unitária centralizada na origem, definida pela equação
\[x^2+y^2+z^2=1.\]
O hemisfério \(z>0\) pode ser mapeado para o espaço da janela como um círculo inscrito no viewport, como se a câmera estivesse olhando na direção de \(-z\) no espaço do mundo31. A figura 8.20 ilustra esse mapeamento.
Se o usuário clicar em alguma posição na janela, as coordenadas \((x_w,y_w)\) do cursor podem ser convertidas em coordenadas de um ponto \((x,y,0)\) no plano \(z=0\).
Se \((x,y,0)\) estiver dentro da esfera unitária, isto é, se
\[\sqrt{x^2+y^2}\leq1,\]
a coordenada \(z\) da projeção ortogonal do ponto \((x,y,z)\) sobre o hemisfério \(z>0\) pode ser calculada usando a equação da esfera:
\[ \begin{align} &x^2+y^2+z^2=1,\\ \\ &z=\sqrt{1-x^2-y^2}. \end{align} \]
Note que, se \(P=(x,y,z)\), o vetor \(\mathbf{v}=P-O\) é um vetor unitário.
Se o usuário manter o botão do mouse pressionado em uma posição \(P_1\) da janela e então arrastar o mouse para outra posição \(P_2\), podemos simular o movimento de girar o trackball virtual na direção de \(P_1\) a \(P_2\) (figura 8.21).
Após a projeção de \(P_1\) e \(P_2\) sobre o hemisfério, obtemos vetores unitários \(\mathbf{v}_1\) e \(\mathbf{v}_2\). O vetor normal ao plano formado entre os dois vetores,
\[ \mathbf{n}=\mathbf{v}_1 \times \mathbf{v}_2, \] é o eixo de rotação correspondente.
Uma vez que \(\mathbf{v}_1\) e \(\mathbf{v}_2\) são vetores unitários, o ângulo de rotação pode ser calculado a partir da propriedade do produto vetorial:
\[ |\sin \theta| = |\mathbf{v}_1 \times \mathbf{v}_2|=|\mathbf{n}|. \]
Assim, a movimentação do ponto \(P_1\) a \(P_2\) no espaço da janela representa uma rotação do objeto por um ângulo \(\theta\) em torno de \(\mathbf{n}\).
Na prática, os pontos \(P_1\) e \(P_2\) têm um ângulo muito próximo de zero, pois os pontos equivalem a eventos consecutivos de movimentação do mouse. O ponto \(P_2\) é a posição atual do mouse, e \(P_1\) é a posição do mouse no último evento de movimentação, ou último evento de pressionamento do botão do mouse. Para ângulos próximos de zero, \(\sin \theta \approx \theta\), e assim não precisamos calcular a função arco seno em
\[\theta= \sin^{-1}(|\mathbf{n}|).\]
Se um ponto \(P=(x,y,0)\) estiver fora da esfera unitária, isto é, se
\[\sqrt{x^2+y^2}>1,\]
podemos considerar como vetor resultante o vetor \(\mathbf{v}=P-O\) normalizado. O resultado é um vetor unitário sobre o plano \(z=0\).
Na biblioteca GLM, a matriz de rotação por um ângulo \(\theta\) em torno de um eixo arbitrário \(\mathbf{n}\) pode ser obtida com a função glm::rotate
, definida em glm/gtc/matrix_transform.hpp
:
::mat4 glm::rotate(glm::mat4 const& m, float angle, glm::vec3 const& axis);
glm::dmat4 glm::rotate(glm::dmat4 const& m, double angle, glm::dvec3 const& axis); glm
onde
m
é a matriz que será multiplicada à esquerda pela matriz de rotação (pode ser a matriz identidadeglm::mat4(1.0f)
ouglm::mat4()
);angle
é o ângulo de rotação, em radianos;axis
é o eixo de rotação.
A seguir, veremos como tal matriz é construída.
Na maioria das implementações do trackball virtual, o usuário também pode controlar a escala do objeto através do botão de rolagem (scroll wheel) do mouse. Faremos isso em nossa implementação na seção 8.4.
Rotação em torno de um eixo arbitrário
Vimos na seção 7.4 como calcular as matrizes de rotação \(\mathbf{R}_x\), \(\mathbf{R}_y\) e \(\mathbf{R}_z\) em torno dos eixos principais \(x\), \(y\) e \(z\).
Através da concatenação dessas rotações podemos obter a matriz de rotação em torno de um eixo arbitrário.
Suponha que \(\hat{\mathbf{u}}=(u_x, u_y, u_z)\) é um vetor unitário que define a direção do eixo de rotação por um ângulo \(\theta\). Como o vetor é unitário,
\[ u_x^2+u_y^2+u_z^2=1. \]
A matriz de rotação em torno de \(\hat{\mathbf{u}}\) pode ser obtida através da seguinte composição de rotações:
\[ \mathbf{R}=\mathbf{R}_x(-\theta_x)\mathbf{R}_y(-\theta_y)\mathbf{R}_z(\theta)\mathbf{R}_y(\theta_y)\mathbf{R}_x(\theta_x). \]
Lendo da direita para a esquerda, primeiro aplicamos uma rotação em \(x\) (por um ângulo \(\theta_x\)), seguida de uma rotação em \(y\) (por um ângulo \(\theta_y\)). Suponha que esses ângulos são tais que alinham o vetor \(\hat{\mathbf{u}}\) com o eixo \(z\). Então, após \(\mathbf{R}_y(\theta_y)\mathbf{R}_x(\theta_x)\), o vetor \(\hat{\mathbf{u}}\) aponta na direção do eixo \(z\).
Agora que \(\hat{\mathbf{u}}\) está alinhado com \(z\), a próxima rotação, \(\mathbf{R}_z(\theta)\), equivale a uma rotação em torno de \(\hat{\mathbf{u}}\), que é o que desejamos. As transformações restantes desfazem as duas primeiras, em ordem, fazendo com que \(\hat{\mathbf{u}}\) retorne para sua direção original.
Em resumo, a concatenação de transformações pode ser dividida nas seguintes etapas:
- Alinhamento de \(\hat{\mathbf{u}}\) com o eixo \(z\).
- Rotação em torno de \(\hat{\mathbf{u}}\) alinhado com \(z\).
- Retorno de \(\hat{\mathbf{u}}\) para sua direção original.
O resultado final será uma rotação em torno do eixo arbitrário \(\hat{\mathbf{u}}\).
Para determinar \(\theta_x\), observe as relações trigonométricas na figura 8.22.
O vetor \(\hat{\mathbf{u}}\) é composto de componentes \((u_x,u_y,u_z)\). Logo, \(u_z\) e \(u_y\) formam os catetos de um triângulo retângulo. A hipotenusa é
\[ d=\sqrt{u_y^2+u_z^2} \] e corresponde ao comprimento da projeção ortogonal de \(\hat{\mathbf{u}}\) sobre o plano \(x=0\). A rotação de \(\hat{\mathbf{u}}\) pelo ângulo \(\theta_x\) faz com que \(\hat{\mathbf{u}}\) incida sobre o plano \(y=0\).
Não é necessário calcular \(\theta_x\) explicitamente. A matriz \(\mathbf{R}_x(\theta_x)\) usa apenas senos e cossenos de \(\theta_x\). Logo, podemos construir a matriz de rotação apenas observando as relações entre os lados do triângulo retângulo:
\[ \mathbf{R}_x(\theta_x)= \begin{bmatrix} 1 & 0 & 0 & 0\\ 0 & u_z/d & -u_y/d & 0\\ 0 & u_y/d & u_z/d & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}. \]
Após a rotação por \(\theta_x\), podemos determinar \(\theta_y\). Observe a figura 8.23.
Novamente, temos um triângulo retângulo com catetos \(d\) e \(u_x\). A hipotenusa é \(1\) pois \(\hat{\mathbf{u}}\) é unitário.
A rotação por \(\theta_y\) faz com que o vetor projetado no plano \(y=0\) fique alinhado ao eixo \(z\), que é o que desejamos.
Também não precisamos calcular \(\theta_y\) explicitamente, pois os elementos da matriz são compostos apenas de senos e cossenos do ângulo. A matriz de rotação em torno de \(y\) terá o formato
\[ \mathbf{R}_y(\theta_y)= \begin{bmatrix} d & 0 & -u_x & 0\\ 0 & 1 & 0 & 0\\ u_x & 0 & d & 0\\ 0 & 0 & 0 & 1 \end{bmatrix}. \]
Uma vez que já sabemos como calcular \(\mathbf{R}_z(\theta)\), podemos multiplicar todas as matrizes para obter a rotação \(\mathbf{R}\) final.
Referências
Isso pode ser obtido com uma câmera LookAt posicionada ao longo de \(+z\) no espaço do mundo, olhando na direção de \(-z\).↩︎