OpenGL - rotace
V této krátké kapitole, podobně jako v té předešlé, naváži na kapitolu o perspektivě, tentokrát na téma rotace os. Bude to ještě jednodušší kapitola, protože se nenaučíte nic z OpenGL funkcí, ale jen jak využít linmath.h.
Události myši
Proberu postupně všechny tři způsoby rotace podle os. Všechny příklady mají společnou obsluhu události myší, viz events.h:
static double lastX, lastY;
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GLFW_TRUE);
return;
}
}
static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT) {
return;
}
if (action == GLFW_PRESS) {
rotationOn = true;
glfwGetCursorPos(window, &lastX, &lastY);
return;
}
else {
rotationOn = false;
}
}
static void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
double x, y;
if(!rotationOn) return;
x = ypos - lastY;
y = xpos - lastX;
lastX = xpos;
lastY = ypos;
double xAngle = degToRad(x/5.0);
double yAngle = degToRad(y/5.0);
updateAngles(xAngle, yAngle);
}
Odstranil jsem většinu obsluhy pro události klávesnice (nechal jsem jen zavírání okna stiskem ESC).
Ve funkci mouse_button_callback()
si zaznamenávám
souřadnice, kde uživatele stiskne levé tlačítko (kde začiná myší tahat).
Také si zaznamenám, zda je tlačítko stisknuté, nebo ne (rotationOn).
Funkce cursor_position_callback()
se opakovaně volá
při přesunu myši. Pokud je rotationOn TRUE,
tak spočítám vzdálenost od místa kde uživatel začal s tažením
a podle toho nastavím úhel vodorovného (yAngle) a svislého
(xAngle) pootočení.
Nakonec se zavolá funkce updateAngles(), která se postará o nastavení matic transformací. Tato funkce bude v každém příkladu dělat něco jiného.
Funkci cursor_position_callback()
je potřeba ještě
zaregistrovat (dělám to v funkci main()
):
Rotace kolem os x a y
Tento způsob rotace je velmi jednoduchý. Hlavičkový soubor
update-angles.h definuje funkci updateAngles()
takto:
Znaménka měním proto, aby se jehlan otáčel v tom správném směru. Externí proměnné xAngle a yAngle jsou pak definované v souboru main.c jako globální proměnné:
Ve funkci draw()
se tyto proměnné použijí pro otočení
matice kolem osy X a Y.
mat4x4 uMVMatrix, uPMatrix;
mat4x4_perspective(uPMatrix, degToRad(45.0), ((GLfloat) width)/height, 1.0, 100.0);
mat4x4_translate(uMVMatrix, 0.0, 0.0, -10.0);
mat4x4_rotate_Y(uMVMatrix, uMVMatrix, yAngle);
mat4x4_rotate_X(uMVMatrix, uMVMatrix, -xAngle);
glUniformMatrix4fv(variables.uMVMatrixLoc, 1, GL_FALSE, (const GLfloat *) uMVMatrix);
glUniformMatrix4fv(variables.uPMatrixLoc, 1, GL_FALSE, (const GLfloat *) uPMatrix);
//...
A to je k rotaci vše.
Rotační matice
Druhý příklad si definuje matici rotace rotationMatrix, do které si ukládá postupně každý pohyb myší.
Stěžejní částí příkladu je zase soubor update-angles.h:
void updateAngles(GLfloat _xAngle, GLfloat _yAngle) {
mat4x4 newRotationMatrix;
mat4x4_identity(newRotationMatrix);
mat4x4_rotate_X(newRotationMatrix, newRotationMatrix, _xAngle);
mat4x4_rotate_Y(newRotationMatrix, newRotationMatrix, -_yAngle);
mat4x4_mul(rotationMatrix, newRotationMatrix, rotationMatrix);
}
Funkce updateAngles()
nejdříve vytvoří transformační
matici, do které uloží rotace kolem os x a y.
Touto maticí pak vynásobí rotationMatrix, která si tak „pamatuje“ předchozí rotace a kumuluje celkovou rotaci.
V souboru main.c je pak definována globální proměnná rotationMatrix,
která se nedjříve inicializuje jako jednotková matice
(v main()
):
draw()
:
mat4x4 uMVMatrix, uPMatrix;
mat4x4_perspective(uPMatrix, degToRad(45.0), ((GLfloat) width)/height, 1.0, 100.0);
mat4x4_translate(uMVMatrix, 0.0, 0.0, -10.0);
mat4x4_mul(uMVMatrix, uMVMatrix, rotationMatrix);
glUniformMatrix4fv(variables.uMVMatrixLoc, 1, GL_FALSE, (const GLfloat *) uMVMatrix);
glUniformMatrix4fv(variables.uPMatrixLoc, 1, GL_FALSE, (const GLfloat *) uPMatrix);
//...
Nyní se objekty otáčejí v závislosti na svém předchozím pootočení, takže vypadá otáčení přirozeněji a je možné otočit i zelenou osu x.
Quaternions
Třetí animace využívá quaternionů, díky kterým dokáže stále rotovat podle „původních“ os x i y, ikdyž jsou (spolu s tělesem) už nějak natočené.
Soubor update-angles.h vypadá takto:
extern quat rotationQuat;
void updateAngles(GLfloat _xAngle, GLfloat _yAngle) {
quat xQuat, yQuat, tmpQuat;
vec3 axis = { 1,0,0 };
quat_rotate(xQuat, _xAngle, axis);
tmpQuat[0] = rotationQuat[0];
tmpQuat[1] = rotationQuat[1];
tmpQuat[2] = rotationQuat[2];
tmpQuat[3] = rotationQuat[3];
quat_mul(rotationQuat, tmpQuat, xQuat);
axis[0] = 0;
axis[1] = 1;
quat_rotate(yQuat, _yAngle, axis);
tmpQuat[0] = rotationQuat[0];
tmpQuat[1] = rotationQuat[1];
tmpQuat[2] = rotationQuat[2];
tmpQuat[3] = rotationQuat[3];
quat_mul(rotationQuat, tmpQuat, yQuat);
mat4x4_from_quat(rotationMatrix, rotationQuat);
}
Tentokrát se pro „zapamatování“ předchozího stavu používá
quaternion rotationQuat. Matice rotationMatrix
se vytváří z tohoto quaternionu pomocí funkce mat4x4_from_quat()
.
Funkce quat_rotate()
vytvoří quaternion, který bude rotovat
o úhel daný druhým argumentem, podle os, které jsou určené třetím argumentem.
Funkce quat_mul()
násobí quaterniony z druhého a třetího argumentu
a výsledek uloží do prvního. Aby to správně fungovalo, tak výsledný quaternion
nesmí být odkaz na stejné pole jako je třetí nebo čtvrtý quaternion.
Proto ve funkci updateAngles()
používám pomocnou proměnnou
tmpQuat.
Matice rotationMatrix se použije v main.c úplně stejně, jako v předchozím
příkladu. Jediná změna v main.c tak je inicializace quaternionu rotationQuat
ve funkci main()
:
mat4x4_identity(rotationMatrix);
quat_identity(rotationQuat);
//...
Matice rotationMatrix musí být také inicializována,
aby obsahovala rozumnou hodnotu, než uživatel tažením myši
poprvé zavolá updateAngles()
.
A tím je tato kapitola u konce. V příští kapitole se vám rozsvítí.