Landscape Creator
Es ist zu lange her dass ich etwas über ein interessantes Programmierprojekt gepostet habe, daher tue ich dies jetzt. [Wie ich schon mal erwähnte][lastpost], arbeite ich an einem Leveleditor für mein neues Eisenbahnspiel, also ein Programm mit dem ich dafür eine Landschaft erstellen kann. Dies ist in den letzten beiden Wochen deutlich besser geworden, was man am besten in einem Screenshot sehen kann.
![Natürlich funktioniert das mit dem Screenshot nicht immer.][screenshot]
Was sieht man hier? Drei Werkzeuge (Berg machen, Tal machen und mit einer Farbe malen) funktionieren schon. Die beiden Werkzeuge, die die Geometrie verändern, tun dies noch nicht so glatt wie sie sollten, aber sie laufen. Das malen arbeitet direkt auf der Grafikkarte[^1], was nicht viel Vorteil bringt aber cool klingt. Rückgängig und Wiederherstellen geht beliebig oft, man kann darüber hinaus Dateien öffnen und speichern. Das malen von Texturen (z.B. echtes Gras) auf die Landschaft kommt einigermaßen voran, man hat zumindest schon das Feld, wo man sich eine Textur auswählen kann.
Bei dieser Arbeit habe ich ein paar interessante Sachen entdeckt, die ich hier mal erwähnen wollte. Das ganze ist allerdings sehr technisch.
Kein Garbage Collection
Garbage Collection, also dass der Speicher des Programms automatisch verwaltet wird, sollte man nie zusammen mit OpenGL einsetzen. Man kann schon, aber Apples OpenGL Profiler funktioniert nicht damit. Dieses Programm ist aber die beste Möglichkeit, herauszufinden, wieso Code nichts oder was falsches zeichnet, und ist meines Erachtens deutlich wichtiger als das bisschen Bequemlichkeit durch automatische Speicherverwaltung.
Core Data kann schwierig sein
Dies ist das erste Mal, dass ich Core Data einsetze, eine Technologie von Apple die Speichern, Öffnen und so weiter sowohl vereinfachen als auch erweitern soll. Es stellt sich heraus, dass es Sachen gibt, die Core Data mag und gut tut, Sachen, die etwas Arbeit brauchen und Sachen, die einfach nicht gehen, und mein Programm hat natürlich etwas aus allen dreien. Eine OpenGL-Textur in Core Data speichern, aber auf der GPU zu bearbeiten, ist zum Beispiel nicht sinnvoll möglich (aber auch zugegebenermaßen eine seltene Anwendung). Nun verwende ich ein Paketformat (d.h. eine Datei ist tatsächlich ein Ordner, sieht aber nicht so aus), in dem eine Core Data-Datei und Extradateien für Texturen und Höhenwerte enthalten sind. Das war nicht ganz einfach, aber Apple bietet da [sehr hilfreichen Beispielcode][NSPersistentDocumentFileWrappers].
OpenGL ist irgendwie Mist
OpenGL ist prima für viele Sachen, aber man kommt auch leicht in Ecken, die es nicht mehr mag. Ein schönes Beispiel ist der OpenGL Kontext, was man sich ungefähr als das Fenster in das man zeichnet plus die zugehörigen Resourcen vorstellen kann. Dieser Kontext schwebt irgendwo im Hintergrund, nur selten gesehen und verwendet, was prima funktioniert bis man zwei davon hat. Ich habe zwei pro offenem Dokument (der Bildbrowser für die Texturauswahl verwendet aus irgend einem Grund auch OpenGL) und theoretisch beliebig viele offene Dokumente, was dies ein durchaus interessantes Problem macht. In der Zeichenroutine ist der Kontext immer der richtige, aber wenn ich zwischendurch Parameter anpasse (z.B. Farbe, in der ich male), ist er das nicht zwingend, so dass ich häufig Umständen etwas völlig falsches ändere.
Man kann manuell den aktuellen Kontext ändern. Es muss nur jeder Code, der OpenGL aufruft, Zugriff auf den Kontext haben, diesen setzen und am Ende am besten den vorherigen wieder herstellen. Es geht aber auch mehr oder weniger einfacher.
Apple bietet [OpenGL Makro-Header][oglmacros], was gewissermaßen die normalen OpenGL-Funktionen sind, nur ist jetzt der Kontext eine Variable die ich explizit bereitstelle. Der Gedanke ist vor allem bessere Performance, damit OpenGL nicht zuerst nachsehen muss welcher Kontext aktuell ist, aber es hilft hier auch Kontextwechsel komplett einzusparen. Dafür muss die benötigte cgl_ctx
-Variable einfach nur eine Instanzvariable werden:
@interface SomeGLUsingCode : NSObject
{
CGLContextObj cgl_ctx;
/* ... */
}
- (id)initWithContext:(CGLContextObj)context;
@end
In der Implementation dann entsprechend setzen:
- (id)initWithContext:(CGLContextObj)context;
{
if (![super init]) return nil;
cgl_ctx = context;
...
return self;
}
Nun werden alle OpenGL-Aufrufe, die das Objekt macht, im richtigen Kontext gemacht. Es gibt aber ein Problem hier: Es funktioniert nur in selbstgeschriebenem Code, nicht mit Bibliotheken die selbst OpenGL aufrufen - insbesondere GLU versagt hier schon. Es ist aber prinzipiell legal, dies als Ersatz für Kontextwechsel zu verwenden, zumindest [verwendet offizieller Apple-Beispielcode es explizit zu diesem Zweck][oglmacrosample].
Die Zukunft
Als nächstes müsste dann das Texturzeichnen rein. Danach kommt direkt der Exporter für das iPhone, dann versuche ich die Landschaft auf dem Gerät zu zeichen. Danach werden Schienen und statische Objekte sehr interessant sein, vielleicht noch mit Zusatzcode um speziell viele Bäume effizient zu verwalten. Wasser kommt viel später, wenn mir langweilig ist.
Nebenbei, ich plane nicht diesen Leveleditor an andere zu geben - ich schließe es aber auch nicht aus, besonders falls ich ihn mal für [Hubschrauber][hubi] verwende. Ich garantiere aber, dass es nie eine Windows-Version geben wird, außer wenn eines der Cocoa-Zu-Windows-Projekte im Internet plötzlich über Nacht völlig genial wird.
[^1]: Tatsächlich sind ein paar Sachen beim Rückgängig machen nicht auf der Grafikkarte, aber das reine Zeichnen ist komplett dort.
Geschrieben am 10. Juni 2009 um 10:23
Björn
Torsten (admin)