Lektion 48
ArcBall Rotation Kontrolle
von Terence J. Grant (tjgrant@tatewake.com)
Wäre es nicht großartig, wenn Sie Ihr Modell rotieren lassen könnten, indem Sie nur die Maus verwenden? Mit einem ArcBall können Sie das machen. In diesem Dokument komme ich auf meine Implementation und Betrachtungen zurück, damit Sie sowas in Ihren eigenen Projekten einfügen können.
Meine Implementation der ArcBall Klasse basiert auf Bretton Wade's, welche wiederum auf Ken Shoemake's, aus der Graphic Gems Buchreihe, basiert. Wie dem auch sei. Ich habe ein paar Fehler behoben und einige Optimierungen für unser Vorhaben vorgenommen.
Der ArBall funktioniert so, dass die Klick-Koordinaten in einem Fenster direkt in di ArcBall Sphere Koordinaten gemapped werden, als ob es direkt vor Ihnen wäre.
Um das zu erreichen, skalieren wir einfach die Maus-Koordinaten herunter, in eine Spann von [0...width), [0...height) bis [-1...1], [1...-1] – (Behalten Sie im Hinterkopf, dass wir das Vorzeichen von Y ändern, so dass wir korrekte Ergebnisse in OpenGL erhalten.) Und das sieht wie folgt aus:
MousePt.X = ((MousePt.X / ((Width – 1) / 2)) – 1);
MousePt.Y = -((MousePt.Y / ((Height – 1) / 2)) – 1);
Der einzige Grund, warum wir die Koordinaten runter auf eine Spanne von [-1..1] skalieren, ist, dass die Mathematik dadurch einfacher wird; und durch einen glücklichen Zufall nimmt der Compiler auch einige kleine Optimierungen vor.
Als nächstes berechnen wir die Länge des Vektors und bestimmen, ob oder ob nicht, er innerhalb oder außerhalb der Sphere Grenzen liegt. Falls er innerhalb der Grenze der Sphere ist, geben wir einen Vektor aus dem Inneren der Sphere zurück, ansonsten normalisieren wir den Punkt und geben den am nächst gelegensten Punkt zum Äußeren der Sphere zurück.
Wenn wir erst einmal beide Vektoren haben, können wir mit einem Winkel einen zum Anfangs- und Endvektor senkrecht stehenden Vektor berechnen, welcher ein Quarternion sein wird. Damit haben wir genügend Informationen zusammen, um eine Rotations-Matrix zu erstellen.
Der ArcBall wird instanziiert durch den folgenden Konstruktor. NewWidth und NewHeight sind logischerweiße Breite und Höhe des Fensters.
ArcBall_t::ArcBall_t(GLfloat NewWidth, GLfloat NewHeight)
Wenn der Benutzer die Maus klickt, wird der Anfangsvektor basierend auf der Position, wo der Klick auftrat, berechnet.
void ArcBall_t::click(const Point2fT* NewPt)
Wenn der Benutzer die Maus zieht, wird der Endvektor aktualisiert und wenn ein Quaternion Ausgabe Parameter übergeben wird, wird dieser mit der resultierenden Roatation aktualisiert.
void ArcBall_t::drag(const Point2fT* NewPt, Quat4fT* NewRot)
Wenn sich die Fenstergröße ändert, aktualisieren wir den ArcBall einfach mit diesen Informationen:
void ArcBall_t::setBounds(GLfloat NewWidth, GLfloat NewHeight)
Wenn Sie das in Ihrem eigenen Projekt verwenden, werden Sie wohl eigen Member-Variablen benutzen.
// Finale Transformation
Matrix4fT Transform = { 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 = { 1.0f, 0.0f, 0.0f, // Letzte Rotation
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };
Matrix3fT ThisRot = { 1.0f, 0.0f, 0.0f, // DieseRotation
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f };
ArcBallT ArcBall(640.0f, 480.0f); // ArcBall Instanz
Point2fT MousePt; // aktueller Maus Punkt
bool isClicked = false; // wurde die Maus geklickt?
bool isRClicked = false; // wurde die rechte Maustaste geklickt?
bool isDragging = false; // wird die Maus (mit gehaltener Taste) gezogen?
Transform ist unsere endgültige Transformation, unsere Rotation und jede optionale Translation, die Sie vielleicht verwenden wollen. LastRot ist die letzte Roatation, die wir am Ende des Ziehens angewendet haben. ThisRot ist die Rotation, die wir erleben, während wir Ziehen. Alle werden mit der Einheitsmatrix initialisiert.
Wenn wir klicken, starten wir von einem Einheits-Rotations-Status. Wenn wir Ziehen, berechnen wir die Rotation vom ursprünglichen Klickpunkt bis zum 'loslassen' Punkt. Selbst wenn wir diese Informationen zum rotieren der Objekte auf dem Screen verwenden, ist es wichtig anzumerken, dass wir nicht den ArcBall selbst rotieren. Deshalb, um kummulative Rotation zu erhalten, müssen wir uns darum selbst kümmern.
Hier kommen dann LastRot und ThisRot ins Spiel. LastRot kann als "alle Rotationen bis jetzt" definiert werden, wobei ThisRot als "die aktuelle Rotation" definiert werden kann. Jedes Mal, wenn ein Ziehen gestartet wird, wird ThisRot um die originale Rotation geändert. Es wird dann mit dem Produkt von sich selbst * LastRot aktualisiert. (DAnn wird auch die endgültige Rotation aktualisiert). Wenn ein Ziehen erst einmal gestoppt wird, wird LastRot der Wert von ThisRot zurgewiesen.
Wenn wir die Rotation nicht von selbst akkumulieren, würde das Modell jedes Mal zum Ursprung zurückschnelle, wenn wir klicken würden. Wenn wir also um 90 Grad auf der X-Achse drehen würden, dann 45 Grad, würden wir eine 135 Grad Drehung erhalten wollen und nicht nur die letzten 45 Grad.
Für den Rest der Varialben (außer für isDragged) gilt, dass Sie diese nur zum richtigen Zeitpunkt aktualisieren müssen. ArcBall muss seine Grenzen resetten, wenn sich die Fenstergröße ändert. MousePt wird aktualisiert sobald sich die Maus bewegt oder wenn nur die Maustaste unten ist. isClicked / isRClicked jedes Mal wenn die linke / rechte Maustaste geklickt wird. isClicked wird verwendet um zu Klicks und Ziehen zu bestimmen. Wir verwenden isRClicked um alle Rotationen zurückzusetzen.
Der restlich Aktualisierungscode für NeHeGL/Windows sieht etwa so aus:
void ReshapeGL (int width, int height)
{
. . .
ArcBall.setBounds((GLfloat)width, (GLfloat)height); // aktualisier Mausgrenze für ArcBall
}
// bearbeite Fenster Nachrichten
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
. . .
// Maus basierte Nachrichten für ArcBall
case WM_MOUSEMOVE:
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: isClicked = false; break;
case WM_RBUTTONUP: isRClicked = false; break;
case WM_LBUTTONDOWN: isClicked = true; break;
case WM_RBUTTONDOWN: isRClicked = true; break;
. . .
}
Wenn wir einmal den Aktualisierungscode haben, wird es Zeit sich um die Klick-Logik zu kümmern. Das ist recht selbsterklärend, wenn Sie das bisherige gelesen haben.
if (isRClicked) // Wenn die rechte Maustaste geklickt wird, setze alle Rotationen zurück
{
// setze Rotation zurück
Matrix3fSetIdentity(&LastRot);
// Resette Rotation
Matrix3fSetIdentity(&ThisRot);
// Resette Rotation
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
}
if (!isDragging) // Kein Ziehen der Maus mit gehaltener Taste
{
if (isClicked) // erster Klick
{
isDragging = true; // bereite auf's Ziehen vor
LastRot = ThisRot; // Setze die letzte statische Rotation auf die letzte dynamische
ArcBall.click(&MousePt); // aktualisiere Anfangsvektor und bereite auf's Ziehen vor
}
}
else
{
if (isClicked) // Taste wird immer noch geklickt, also immer noch gezogen
{
Quat4fT ThisQuat;
ArcBall.drag(&MousePt, &ThisQuat); // aktualisiere Endvektor und ermittle Rotation als Quarternion
Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat); // konvertiere Quaternion in Matrix3fT
Matrix3fMulMatrix3f(&ThisRot, &LastRot); // akkumuliere letzte Rotation in die aktuelle
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); // Setze unsere endgültige Transformations Rotation von dieser
}
else // Es wird nicht länger gezogen
isDragging = false;
}
Das macht alles für uns, was wir brauchen. Nun müssen wir die Transformation nur noch auf unsere Modells übertragen und wir sind fertig. Das ist wirklich einfach:
glPushMatrix(); // bereite dynamische Transformation vor
glMultMatrixf(Transform.M); // wende dynamische Transformation an
glBegin(GL_TRIANGLES); // fange an das Modell zu zeichnen
. . .
glEnd(); // fertig mit dem Zeichnen des Modells
glPopMatrix(); // verwende keine dynamische Transformation mehr
Ich habe ein Beispiel hinzugefügt, welches alles obige demonstriert. Sie sind nicht gezwungen meine Mathe Art oder Funktionen zu verwenden; eigentlich würde ich Ihnen sogar raten, das Ihrem eigenen Mathe-System anzupassen, wenn Sie damit genügend vertraut sind. Wie dem auch sei, alles ist ansonsten in sich geschlossen und sollte von alleine arbeiten.
Nun, nachdem Sie gesehen haben, wie einfach das ist, sollten Sie Ihren eigenen ArcBall zu Ihren Projekten hinzufügen können. Habt Spass!
Terence J. Grant
* DOWNLOAD Visual C++ Code für diese Lektion.
* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Le Thanh Cong )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Victor Andrée )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by George Gensure )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by Brian Leair )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Joachim Rohde )
Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.