Lekce 48 - ArcBall rotace

Nebylo by skvělé otáčet modelem pomocí myši jednoduchým drag & drop? S ArcBall rotacemi je to možné. Moje implementace je založená na myšlenkách Brettona Wadea a Kena Shoemakea. Kód také obsahuje funkci pro rendering toroidu - kompletně i s normálami.

Verze ve slovenštině...

ArcBall funguje tak, že mapuje okenní souřadnice kliknutí přímo do souřadnic ArcBallu. Zmenší poměr souřadnic myši z rozsahu [0..šířka, 0..výška] na rozsah [-1..1, 1..-1]. Pamatujte si, že aby v OpenGL dosáhl korektního výsledku, musí převrátit znamínko y souřadnice. Vzorec vypadá takto:

MousePt.X = ((MousePt.X / ((Width - 1) / 2)) - 1);

MousePt.Y = -((MousePt.Y / ((Height - 1) / 2)) - 1);

Jediný důvod, proč jsme měnili měřítko souřadnic je, abychom zjednodušili matematiku, nicméně šťastnou shodou okolností to dovoluje kompilátoru kód trochu optimalizovat. Dále vypočítáme délku vektoru a určíme, jestli se nachází nebo nenachází uvnitř koule. Pokud ano, vrátíme vektor z jejího vnitřku, jinak normalizujeme bod a vrátíme nejbližší pozici k vnějšku koule. Poté, co máme oba vektory, získáme vektor současně kolmý na počáteční i koncový vektor, čímž dostaneme quaternion. S tímto v rukách máme dost informací na vygenerování rotační matice.

Konstruktoru třídy ArcBall budeme předávat rozměry okna.

ArcBall_t::ArcBall_t(GLfloat NewWidth, GLfloat NewHeight)

Když uživatel klikne myší, vypočítáme počáteční vektor podle toho, kam kliknul.

void ArcBall_t::click(const Point2fT* NewPt)

Když táhne myší (drag), aktualizujeme koncový vektor pomocí metody drag() a pokud je poskytnut i výstupní quaternion, aktualizujeme ho pomocí výsledné rotace.

void ArcBall_t::drag(const Point2fT* NewPt, Quat4fT* NewRot)

Při změně velikosti okna jednoduše aktualizujeme i rozměry ArcBallu.

void ArcBall_t::setBounds(GLfloat NewWidth, GLfloat NewHeight)

V projektu budeme potřebovat i několik dalších proměnných. Transformation je finální transformace, která určuje rotaci, ale také posunutí. LastRot představuje poslední zaznamenanou rotaci od konce dragu a ThisRot určuje rotaci v době táhnutí myší. Všechny tři na začátku inicializujeme na matici identity.

Při kliknutí se začíná z identického stavu rotace a když následně táhneme, rotace se počítá od pozice kliknutí až po bod táhnutí. I když na otáčení objektů ve scéně používáme tuto implementaci, je důležité poznamenat, že nerotujeme samotný ArcBall. S rostoucími (přírůstkovými) rotacemi se musíme vypořádat sami. To je úkol LastRot a ThisRot. LastRot si můžeme představit jako všechny rotace až do teď a ThisRot jako aktuální rotace. Vždy, když začne rotace, ThisRot se modifikuje pomocí originální rotace a potom se aktualizuje jako výsledek součinu s LastRot (a také se upraví konečná transformace). Po skončení dragu přiřadíme do LastRot hodnoty z ThisRot. Kdybychom neakumulovali rotace samotné, model by vypadal, jako by se při každém kliknutí přilepil na začátek souřadnic. Například při rotaci okolo osy x o 90 stupňů a potom o 45 stupňů, chceme získat 135 namísto posledních 45.

Matrix4fT Transform =// Finální transformace

{

1.0f, 0.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f, 0.0f,

0.0f, 0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 0.0f, 1.0f

};

Matrix3fT LastRot =// Minulá rotace

{

1.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 1.0f

};

Matrix3fT ThisRot =// Současná rotace

{

1.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 1.0f

};

Co se týče zbytku proměnných (kromě isDragged), všechno, co s nimi musíme udělat, je vždy je ve správný čas aktualizovat. ArcBall potřebuje, aby se jeho hranice při každé změně velikosti okna resetovaly. MousePt se aktualizuje při pohybu myší nebo stisknutí tlačítka a isClicked/isRClicked při stlačení levého/pravého tlačítka myši. Levé tlačítko slouží pro dragging a pravé pro resetování všech rotací do výchozího identity stavu.

ArcBallT ArcBall(640.0f, 480.0f);// Instance ArcBallu

Point2fT MousePt;// Pozice myši

bool isClicked = false;// Kliknuto myší?

bool isRClicked = false;// Kliknuto pravým tlačítkem myši?

bool isDragging = false;// Táhnuto myší?

Aktualizace proměnných vypadají takto:

// Konec ReshapeGL()

ArcBall.setBounds((GLfloat)width, (GLfloat)height);// Nastaví hranice pro ArcBall

// Funkce WindowProc() - ošetření zpráv myši

case WM_MOUSEMOVE:// Pohyb

MousePt.s.X = (GLfloat)LOWORD(lParam);

MousePt.s.Y = (GLfloat)HIWORD(lParam);

isClicked = (LOWORD(wParam) & MK_LBUTTON) ? true : false;

isRClicked = (LOWORD(wParam) & MK_RBUTTON) ? true : false;

break;

case WM_LBUTTONUP:// Uvolnění levého tlačítka

isClicked = false;

break;

case WM_RBUTTONUP:// Uvolnění pravého tlačítka

isRClicked = false;

break;

case WM_LBUTTONDOWN:// Kliknutí levým tlačítkem

isClicked = true;

break;

case WM_RBUTTONDOWN:// Kliknutí pravým tlačítkem

isRClicked = true;

break;

Máme-li toto všechno, je na čase vypořádat se s klikací logikou.

void Update(DWORD milliseconds)// Aktualizace scény

{

if (g_keys->keyDown [VK_ESCAPE] == TRUE)// Stisk ESC

{

TerminateApplication(g_window);// Ukončení programu

}

if (g_keys->keyDown [VK_F1] == TRUE)// Stisk F1

{

ToggleFullscreen(g_window);// Přepnutí do fullscreenu

}

if (isRClicked)// Kliknutí pravým tlačítkem - reset všech rotací

{

Matrix3fSetIdentity(&LastRot);

Matrix3fSetIdentity(&ThisRot);

Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);

}

if (!isDragging)// Netáhne se myší?

{

if (isClicked)// Kliknutí?

{

isDragging = true;// Příprava na dragging

LastRot = ThisRot;// Nastavení minulé statické rotace na tuto

ArcBall.click(&MousePt);// Aktualizace startovního vektoru a příprava na dragging

}

}

else// Už se táhne

{

if (isClicked)// Je ještě stisknuto tlačítko?

{

Quat4fT ThisQuat;

ArcBall.drag(&MousePt, &ThisQuat);// Aktualizace koncového vektoru a získání rotace jako quaternionu

Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat);// Konvertování quaternionu na Matrix3fT

Matrix3fMulMatrix3f(&ThisRot, &LastRot);// Akumulace minulé rotace do této

Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);// Nastavení koncové transformační rotace na tuto

}

else// Už není stisknuto

{

isDragging = false;// Konec draggingu

}

}

}

Teď už jenom potřebujeme aplikovat transformaci na naše modely a jsme hotovi.

void Draw(void)// Vykreslování

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smazání bufferů

glLoadIdentity();// Reset matice

glTranslatef(-1.5f, 0.0f, -6.0f);// Translace doleva a do hloubky

glPushMatrix();// Uložení matice

glMultMatrixf(Transform.M);// Aplikování transformace

glColor3f(0.75f, 0.75f, 1.0f);// Barva

Torus(0.30f, 1.00f);// Vykreslení toroidu (speciální funkce)

glPopMatrix();// Obnovení původní matice

glLoadIdentity();// Reset matice

glTranslatef(1.5f, 0.0f, -6.0f);// Translace doprava a do hloubky

glPushMatrix();// Uložení matice

glMultMatrixf(Transform.M);// Aplikování transformace

glColor3f(1.0f, 0.75f, 0.75f);// Barva

gluSphere(quadratic,1.3f,20,20);// Vykreslení koule

glPopMatrix();// Obnovení matice

glFlush();// Flushnutí renderovací pipeline

}

Přidal jsem i ukázku kompletního kódu, který toto všechno demonstruje. Nemusíte používat moji matematiku a funkce stojící na pozadí, naopak, pokud si věříte, doporučuji vytvořit si vlastní. Nicméně i s mými vzorci a výpočty by všechno mělo bez problémů fungovat.

napsal: Terence J. Grant <tjgrant (zavináč) tatewake.com>
do slovenštiny přeložil: Pavel Hradský - PcMaster <pcmaster (zavináč) stonline.sk>
do češtiny přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>

Zdrojové kódy

Lekce 48

<<< Lekce 47