OpenGL.org 3Dsource.de: Infos rund um 3D-Computergrafik, OpenGL und VRML
space Telefon-Tarife space VDI - Arbeitskreis Fahrzeugtechnik space Beschallungsanlagen und Elektroakustik space Heise-News space

20 Picking und Selection (Mausposition)

20.010 Wie kann ich herausbekommen, welches Objekt der Benutzer grade mit der Maus angeklickt hat ?

OpenGL definiert einen GL_SELECTION Zeichenmodus für diese Anforderungen, es sind aber auch andere Wege möglich.

Eine Lösung ist, jedes Objekt in einer anderen Farbe zu zeichnen, den Farbwert an der Mausposition mittels glReadPixels() zu bestimmen und daraus Rückschlüsse auf das ausgewählte Objekt zu ziehen. Weitere Informationen findet man hier bzw. auch hier.

Eine zweite Idee wäre eine Art Verfolgerstrahl von der Mausposition, dessen Schnittstellen mit anderen Objekten berechnet werden könnte. OpenGL bietet hierfür keine direkte Unterstützung, man kann sich aber mal in der BSP FAQ umschauen.

Um den benötigten Positionsstrahl zu berechnen, kann man z.B. auf gluUnProject() zurückgreifen, beim ersten Aufruf mit winZ (0.0) für die vordere Clippingfläche, beim zweiten mit winZ (1.0) für die hintere. Die Subtraktion der abgefragten Werte ergibt dann den Richtungsvektor des Positionsstrahls, bezogen auf den Koordinatenursprung des Modellkoordinatensystems.

(Bei Unklarheiten bitte mal mit der Originalversion der FAQ vergleichen, bzw. ins Red Book schauen. Ich hab im Moment noch keine geeignete Anleitung parat ;-)

Man kann den Positionsstrahl aber auch im Augpunktkoordinatensystem berechnen und dann mit der Inversen der ModelView Matrix multiplizieren. Der Ursprung des Richtungsvektors ist wiederum (0,0,0), die Berechnung kann z.B. so erfolgen:

    aspect = double(window_width)/double(window_height);
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glFrustum(-near_height * aspect, near_height * aspect,
              -near_height, near_height,
               zNear, zFar );

Den Vektor des Positionsstrahls berechnet man wie folgt:

    int window_y = (window_height - mouse_y) - window_height/2;
    double norm_y = double(window_y)/double(window_height/2);
    int window_x = mouse_x - window_width/2;
    double norm_x = double(window_x)/double(window_width/2);

Die meisten Windows-Systeme geben die Bildschirmkoordinaten (und damit die Mausposition) mit dem Ursprung (0,0) in der oberen linken Ecke an, was im oberen Code berücksichtigt wurde. Ist der Viewport nicht genauso gross wie das aktuelle Fenster, muss die Ausdehnung des Viewports (Höhe und Y-Position) für window_y bzw. norm_y verwendet werden.

norm_x and norm_y bewegen sich zwischen -1.0 und +1.0. Damit lässt sich die Mausposition wie folgt ermitteln:

    float y = near_height * norm_y;
    float x = near_height * aspect * norm_x;

Der Vektor des Positionsstrahls ist jetzt (x, y, -zNear). Um diesen Vektor in Augpunktkoordinaten nun in das Modellkoordinatensystem umzurechnen, muss er noch mit der Inversen der zuletzt gesetzten ModelView Matrix der Szene multipliziert werden.

Da sich Koordinaten und Vektoren erst in der vierten (homogenen) Komponente unterscheiden, muss noch diese Erweiterung vorgenommen werden:

    float ray_pnt[4] = {0.f, 0.f, 0.f, 1.f};
    float ray_vec[4] = {x, y, -near_distance, 0.f};

Die homogene Komponente identifiziert die erste Angabe als Startpunkt (w = 1.0) und die zweite als Richtungsvektor (mit w = 0.0).

20.020 Was muss ich machen, um Selection nutzen zu können ?

Es ist ein Selection Buffer anzulegen:

    GLuint buffer[BUF_SIZE];
    glSelectBuffer (BUF_SIZE, buffer);

Jetzt kann Selection aktiviert, die Szene gezeichnet und der Selection Modus wieder beendet werden.

    GLint hits;

    glRenderMode(GL_SELECT);
    // ...Szene wie sonst auch zeichnen, wird aber nicht angezeigt !...
    hits = glRenderMode(GL_RENDER);

Der Aufruf von glRenderMode(GL_RENDER) beendet den Selection Modus und speichert die Anzahl der Treffer in der Variable hits. Jeder Treffer beinhaltet Informationen über die Objekte, die während des Selection-Zeichnungsdurchlaufs innerhalb des Viewports waren.

Das war die Grundidee. In der Praxis will man aber oft das Viewing Volume begrenzen. Dafür eignet sich die Funktion gluPickMatrix(), mit der das Viewing Volume auf einen bestimmten Bereich ausgehend von einer Position (X,Y) z.B. von Maus oder Cursor festlegen lässt.

Manchmal will man auch den Name Stack nutzen, um Bezeichnungen für bestimmte Objekte festzulegen. Sobald der Stack einmal mit aktiviert ist (glPush), kann man eine beliebige Anzahl an Namen auf den Stack laden. Normalerweise wird ein Name festgelegt und dann eine Gruppe von Objekten gezeichnet. Der Name Stack eignet sich auch für die Verwendung in hierarchischen Datenbanken.

Nach der Rückkehr in den Zeichenmodus (GL_RENDER) muss der Selection Buffer abgefragt werden. Dieser enthält dann Null oder mehr Treffer. Die Anzahl der Treffer wird beim Aufruf von glRenderMode(GL_RENDER) zurückgegeben. Jeder Treffer enthät folgende Informationen (unsigend int):

  • Anzahl der Namen, die passend zum Treffer auf dem Name Stack gespeichert sind
  • kleinster Tiefenwert (Z) der gefundenen Objekte (im Bereich 0 bis 232-1)
  • grässter Tiefenwert (Z) der gefundenen Objekte (im Bereich 0 bis 232-1)
  • Inhalt des Name Stacks (je ein Name pro Treffer)

Mit den ermittelten Tiefenwerten (Zmin, Zmax) und den zugehörigen X,Y-Werten (z.B. durch Mausklick herauszubekommen) kann man die genaue Objektposition bestimmen. Man kann z.B. die Z-Werte auf den Bereich 0.0 bis 1.0 skalieren und im Aufruf von gluUnProject() verwenden.

20.030 Warum arbeitet Selection bei mir nicht ?

Meistens liegt es an den folgenden zwei Ursachen:

Wurde die Y-Koordinate richtig ausgewertet ?
Die meisten Windows-Systeme (Microsoft Windows, X Windows) legen den Koordinatenursprung in die linke obere Bildschirmecke und zählen Y nach unten positiv (X nach rechts positiv), während OpenGL den Koordinatenursprung unten links positioniert (Y nach oben positiv und X nach rechts positiv). Damit muss der Y-Wert wie folgt umgerechnet werden: YOpenGL = Fensterhöhe - YWindows.

Wurden die Transformationen richtig durchgeführt ?
Wenn man z.B. gluPickMatrix() verwendet, sollte die Abfrage der Projektionsmatrix unmittelbar nach glLoadIdentity(), auf jeden Fall aber der Projektionstransformation (gluPerspective(), glOrtho()) erfolgen. Die Modelview-Matrix sollte dann die gleiche wie beim normalen Zeichnen sein.

20.040 Wie kann ich meinen Positionsalgorithmus debuggen ?

Eine gute Methode zum Debuggen von Picking- oder Selection-Code ist, die Funktion glRenderMode(GL_SELECT) einfach nicht aufzurufen (also auskommentieren). Damit wird keine Selection vorgenommen, sondern der Selection-Bereich gezeichnet. Dann kann man sehen, ob die erwarteten Objekte im Selektionsbereich liegen.

In Zusammenhang mit dieser Methode ist es auch sinnvoll, den Selection-Bereich zu vergrössern, um mehr zu erkennen.

20.050 Wie kann ich ein selektiertes Objekt hervorheben, ähnlich wie es mit PHIGS und PEX möglich ist ?

Es gibt mit OpenGL leider keine elegante Lösung.

Nachdem man das Objekt identifiert hat, was mit der Selection hervorgehoben werden muss, hat das eigene Programm die Kennzeichnung sicherzustellen. Man kann z.B. das selektierte Objekt mit einer anderen Farbe in den Front Buffer zeichnen. Damit das funktioniert, sollte man auf den Polygon Offset oder zumindest auf glDepthFunc(GL_EQUAL) zurückgreifen. So wird das selektierte Objekt einfach nochmal über die bestehende Szene drübergezeichnet. Zur besseren Erkennbarkeit kann es sinnvoll sein, nur im Drahtgittermodus zu zeichnen oder das Objekt mehrfach hintereinander mit verschiedenen Farben aufzurufen (das Objekt blinkt dann).

Seite durchsuchen nach:

Fragen oder Ideen an:
Thomas.Kern@3Dsource.de
Linux-Counter
(C) Thomas Kern