Tutorial Objektselektion

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Objektselektion

Info DGL.png Die hier vorgestellte Methode ist mit aktueller Hardware ein potentielles Performance-Problem. Als Alternative wird empfohlen Color Picking zu verwenden.

Einleitung

Manchmal ist es nötig, zu wissen, welches auf den Schirm gebrachte Objekt vom Benutzer angewählt wurde. Egal ob man es für einen Karteneditor benötigt, um zu erfahren, welche Wand angeklickt wurde, oder für ein Strategiespiel, um herauszufinden, welches Gebäude angewählt wurde, es ist sehr einfach zu implementieren. Da ich noch keine guten Tutorials über OpenGLs Selection-Modus (besonders in Delphi) gesehen habe und diesen für den Karteneditor der ZornGL Engine benötigte, habe ich mich dran gemacht, herauszufinden wie diese Technik zu nutzen ist und werde es Ihnen in diesem Tutorial zeigen.


Schritt 1 - Die Objektnamen auf den Name Stack legen

Das Wichtigste am Selection Modus ist der Name Stack. Dies ist der Platz, an dem OpenGL die "Namen" der Objekte (trotz der Bezeichnung "Name" handelt es sich um Integer Werte) ablegt, die später zur Erkennung des gewählten Objekts zurückgegeben werden. Wie mit allen anderen OpenGL Stacks kann man Namen auf den Stack legen. Obwohl man auch Namen von diesem Stack herunternehmen kann, wird man davon kaum Nutzen machen. Beachten Sie, dass alle Kommandos zur Manipulation des Name Stacks nur im Selection Modus funktionieren, dazu aber später mehr. Sehen Sie sich zuerst dieses kleine Beispiel und seine Beschreibung an:


const
 Sun   = 1;
 Mars  = 2;
 Earth = 3;
 Moon  = 4;

procedure TGLForm.DrawScene;
begin
glInitNames;
glPushName(0);
...
glLoadName(Sun);
glCallList(SunList);
...
glLoadName(Mars);
glCallList(MarsList);
...
end;


Die Nutzung des Selection Modus ist - wie Sie sehen können - sehr einfach. Zuerst müssen Sie den Name Stack leeren and dann muss mindestens ein Name auf den Stack gelegt werden. Wenn Sie dies nicht tun, wird der nächste glLoadName-Befehl keinen Namen auf den Stack legen, sondern einen GL_INVALID_OPERATION-Fehler auslösen.

Nachdem der Name Stack also ordnungsgemäß initialisiert wurde, laden wir für jedes Objekt in unserer Szene (oder besser alle Objekte, die der Nutzer auswählen kann) einen Namen. Nach dem Laden des Namens zeichnen Sie Ihr Objekt. Dabei macht es keinen Unterschied ob dies eine Displayliste ist oder durch glBegin und glEnd geschieht.


Schritt 2 - Die ausgewählten Objekte finden

Jetzt kommen wir zum Kern der Sache: Herauszufinden, welches Objekt unter dem Cursor liegt. Dies ist ein wenig komplizierter, aber sehen Sie sich dazu einfach folgende Prozedur und ihre Beschreibung an:


function TGLForm.GetSelectBufferHit : Integer;
var
 SelectBuffer : array[0..512] of TGLUInt;
 Viewport     : TGLArrayi4;
 Hits,i       : Integer;
 HitZValue    : TGLUInt;
 Hit          : TGLUInt;
begin
glGetIntegerv(GL_VIEWPORT, @viewport);
glSelectBuffer(512, @SelectBuffer);
glRenderMode(GL_SELECT);
glInitNames;
glPushName(0);
glMatrixMode(GL_PROJECTION);
glPushMatrix;
 glLoadIdentity;
 gluPickMatrix(mx, viewport[3]-my, 1.0, 1.0, viewport);
 gluPerspective(45.0, ClientWidth/ClientHeight, 0.1, 1000);
 DrawScene;
 glMatrixMode(GL_PROJECTION);
glPopMatrix;
Hits := glRenderMode(GL_RENDER);
Hit       := High(TGLUInt);
HitZValue := High(TGLUInt);
for i := 0 to Hits-1 do
 if SelectBuffer[(i*4)+1] < HitZValue then
  begin
  Hit       := SelectBuffer[(i*4)+3];
  HitZValue := SelectBuffer[(i*4)+1];
  end;
Result := Hit;
end;


Wenn Sie die Variablendeklaration dieser Funktion betrachten, werden Sie sehen, dass ein Array namens SelectBuffer benötigt wird. Dies ist der Platz an dem OpenGL alle Treffer mit ihren Z-Werten speichern wird. Ein weiterer Nachteil des Selection-Modus ist die Tatsache, dass die Anzahl der Treffer, die man bekommt nicht bekannt ist. Deshalb muss ein Array fester Größe genutzt werden, was jedoch aufgrund der Tatsache, dass dies lokal deklariert ist, keine Probleme macht.

Nach dem Sichern unseres Blickfeldes nutzen wir den Befehl glSelectBuffer(size : Integer; buffer : PGLUint) um OpenGL die Größe und die Adresse unseres SelectBuffers mitzuteilen.

Nun wechseln wir mit Hilfe des glRenderMode(mode : cardinal)-Befehls vom Rendermodus in den Selection-Modus, initialisieren den Name Stack und legen einen Namen auf ihn.

Danach ist es Zeit, eine neue Projekionsmatrix zu erstellen. Da wir wollen, dass der Nutzer ein Objekt durch einen Mausklick anwählen kann, erstellen wir mit gluPickMatrix(x, y, width, height: TGLdouble; viewport: TVector4i) eine neue Matrix, die an der Mausposition zentiert wird und 1x1 Pixel groß ist.

Nach einem erneuten Setzen des FOV, des Aspektratios- und des zFar-Wertes zeichnen wir unsere Szene erneut. Diesmal wird sie jedoch im Selection-Modus gezeichnet und alle den Name Stack betreffenden Befehle werden jetzt ausgeführt.

Nachdem wir unsere Projektionsmatrix wiederhergestellt haben, wechseln wir wieder in den Rendermodus. Aber diesmal gibt der glRender-Befehl die Anzahl der Objekttreffer zurück, die wir für die spätere Nutzung speichern.

Da wir jetzt also die Zahl der Treffer kennen, ist es sehr einfach, das vom Nutzer gewählte Objekt zu finden. Wie Sie wissen sollten, ist dies das Objekt mit dem niedrigsten Z-Wert (also das mit der geringsten Entfernung zum Betrachter). Um dieses zu finden muss man erst wissen, was sich im SelectionBuffer befindet. Der Puffer besteht aus Trefferrecords, der wiederum aus folgenden Teilen besteht:

  • Anzahl der Namen auf dem Stack zum Zeitpunkt des Treffers
  • Kleinste z-Koordinate aller Eckpunkte des gewählten Objekts
  • Größte z-Koordinate aller Eckpunkte des gewählten Objekts
  • Name des getroffenen Objekts

Die einzigen benötigten Einträge sind der Zweite und der Letzte. Auf diese kann mit SelectBuffer[(n*4)+1] und SelectBuffer[(n*4)+3] zugegriffen werden, wobei n der gerade bearbeitet Treffer ist. Genau dies tut obige Prozedur in der am Ende gestarteten Schleife. Wenn die z-Koordinate des Treffers niedriger ist als die gespeicherte, wird sie durch selbige ersetzt. Außerdem wird gleichzeitig der Name des Treffers gespeichert. Wenn die Schleife durchlaufen ist, haben wir also den Namen des Objektes, das sich unter dem Mauszeiger befand!


Das Beispielprogramm

Ich habe ein kleines Beispielprogramm geschrieben, das ein winziges Universum simuliert.Beim Klick auf einen der Planeten sehen Sie im unteren Panel dessen Namen.


Tutorial Selection selection01.jpg


Download

Das Selektionsdemo (inklusive Quellcode) herunterladen


Autor: Sascha Willems



Vorhergehendes Tutorial:
Tutorial Selection
Nächstes Tutorial:
Tutorial ColorPicking

Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com.
Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen.