Nebolo by super rotovať váš model len tak, iba použitím myši? S ArcBall (prekl.: Oblúková lopta, guľa :-) je to možné. Tento dokument je založený na mojej vlastnej implementácii a úvahách o pridávaní do vašich projektov. Moja implementácia ArcBall je založená na tej od Brettona Wadea, ktorá je založená na implementácii Kena Shoemakea zo série kníh Graphic Gems. Ja som to trosku upravil, opravil zopár chýb a urobil optimalizáciu pre naše účely.
ArcBall funguje tak, že mapuje súradnice kliknutí do okna priamo do súradníc ArcBall, akoby to bolo priamo pred vami.
Na dosiahnutie tohoto, najskôr jednoducho zmenšíme mierku súradníc myši z rozsahu 0..sirka, 0..vyska na rozsah -1..1, 1..-1 (Pamätajte, že prevrátime znamienko Y súradnice, aby sme dosiahli korektný výsledok v OpenGL). A to vyzerá asi takto:
MousePt.X = ((MousePt.X / ((Width - 1) / 2)) - 1);
MousePt.Y = -((MousePt.Y / ((Height - 1) / 2)) - 1);
Jediný dôvod prečo sme menili mierku súradníc je, aby sme si uľahčili matiku a šťastnou zhodou okolností to dovoľuje kompilátoru kód trošku optimalizovať.
Ďalej vyrátame dĺžku vektora a určíme, či je alebo nie je vnútri gule. Ak je, vrátime vektor z vnútra gule, inak normalizujeme bod a vrátime najbližší bod k vonkajšku gule.
Keď už máme oba vektory, vyrátame vektor kolmý na začiatočný a koncový vektor s uhlom, čím dostaneme quaternion. S týmto v rukách, máme dosť informácií na generovanie rotačnej matice a sme doma.
Trieda ArcBall bude mať nasledovný konštruktor. NewWidth a NewHeight sú rozmery okna.
ArcBall_t::ArcBall_t(GLfloat NewWidth, GLfloat NewHeight)
Keď užívateľ klikne myšou, vyráta sa počiatočný vektor podľa toho, kam klikol.
void ArcBall_t::click(const Point2fT* NewPt)
Keď užívateľ potiahne myšou (drag), koncový vektor sa aktualizuje cez procedúru drag a ak je poskytnutý aj výstupný quaternion (NewRot), tento je aktualizovaný výslednou maticou.
void ArcBall_t::drag(const Point2fT* NewPt, Quat4fT* NewRot)
Ak sa zmení veľkosť okna, jednoducho updatneme ArcBall.
void ArcBall_t::setBounds(GLfloat NewWidth, GLfloat NewHeight)
Vo vašich projektoch budete potrebovať zopár premenných:
Transformation je naša finálna transformácia - rotácia alebo aj posunutie. LastRot je posledná rotácia, ktorú sme zaznamenali od konca dragu. ThisRot je rotácia počas ťahania stlačenej myši. Všetko je inicializované na identitu.
Keď klikneme, začneme z identického stavu rotácie. Keď dragneme, počítame rotáciu od začiatočnej pozície kliku po bod ťahania. Aj keď používame túto implementáciu na rotovanie objektov na scéne, je dôležité poznamenať, že nerotujeme samotný ArcBall. Pretože chceme maž rastúce rotácie, musíme sa s nimi sami vysporiadať.
Tu prichádza na scénu LastRot a ThisRot. LastRot môžeme definovať ako "všetky rotácie až do teraz", ThisRot je "aktuálna rotácia". Vždy, keď sa začne rotácia, ThisRot sa modifikuje originálnou rotáciou. Potom sa aktualizuje ako výsledok Ono_Samo*LastRot (Potom sa upraví finálna transformácia). Keď drag skončí, LastRot nadobudne hodnoty ThisRot.
Ak by sme nezhromažďovali rotácie samotné, model by vyzeral, akoby sa priliepal (snapoval) na začiatok súradníc každý raz, keď klikneme. Napríklad, ak by sme rotovali okolo X-ovej osi o 90 stupňov, potom o 45 stupňov, chceli by sme 135 namiesto len posledných 45.
Pre zvyšné premenné (okrem isDragged) je všetko čo potrebujete aktualizovať ich v správnom čase, podľa vášho systému. ArcBall potrebuje, aby jeho hranice boli resetnuté zakaždým, čo sa zmení veľkosť okna. MousePt sa aktualizuje keď pohnete myšou, alebo stlačíte tlačítko. isClicked/isRClicked keď stlačíte ľavé/pravé tlačítko myši. isClicked použijeme pre zistenie kliknutí a dragnutí. isRClicked použijeme na resetnutie všetkých rotácií.
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
};
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áhnutí myší?
Zvyšné systémové updaty podľa NeHeGL/Windows vyzerajú asi takto:
void ReshapeGL (int width, int height)
{
// Konec funkce
ArcBall.setBounds((GLfloat)width, (GLfloat)height);// Nastaví hranice pro ArcBall
}
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 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;
// Pokračování funkce
}
Keď už tento kód máme, je na čase vysporiadať sa z klikacou logikou :-) Je to dosť samovysvetľujúce, ak už viete všetko nad tým.
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)// Pokud se ještě netáhne myší
{
if (isClicked)// První kliknutí
{
isDragging = true;// Příprava na draging
LastRot = ThisRot;// Nastavení minulé statické rotace na dynamickou
ArcBall.click(&MousePt);// Aktualizace startovního vektoru a příprava na dragging
}
}
else// Už se táhne myší
{
if (isClicked)// Tlačítko je stisknuto
{
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;
}
}
}
Toto sa postará o všetko. Teraz už len potrebujeme aplikovať transformáciu na naše modely a sme hotoví. Je to dosť jednoduché:
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í dynamické 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 hlubky
glPushMatrix();// Uložení matice
glMultMatrixf(Transform.M);// Aplikování dynamické transformace
glColor3f(1.0f,0.75f,0.75f);// Barva
gluSphere(quadratic,1.3f,20,20);// Vykreslení koule
glPopMatrix();// Obnovení matice
glFlush();// Flushnutí renderovací pipeline
}
Pridal som aj ukážku, ktorá demonštruje toto všetko. nemusíte používať moju matiku a funkcie, naopak, odporúčam spraviť si vlastné, ak si dosť veríte. Akokoľvek, všetko je dosť sebestačné a malo by fungovať. Keď už vidíte, aké to je jednoduché, mali by ste byť schopní použiť ArcBall vo vlastných projektoch!
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>
Whooaa! Tak toto je už moj druhý preklad, dúfam že sa vám páčil a pomohol vám, s otázkami sa obráťte na mňa alebo správcu webu (Woq). Teším sa na ďalšie preklady!