Landscape Creator
It’s been too long since I last posted about something fun programming, so today I’m going to post about something fun with programming again. [As you may recall][lastpost], I’ve recently posted about starting development on a level editor for my new railroad app. Over the past few weeks, it has gained a lot of traction, which can be best shown in a screenshot.
![Of course, a screenshot is not always an option.][screenshot]
What can you see here? Three basic tools (raise landscape, lower landscape and paint solid color) are already working. The two geometry tools are not working as flawlessly as they should, but they do work. Solid color painting happens completely on the GPU[^1] which is not a huge advantage but sure sounds cool. There is full undo and redo, unlimited other than by RAM. There is full saving and loading. Finally, texture painting is coming along, at least the screen for selecting a texture works.
Over this time, I’ve learnt some very interesting things, which I thought I might share. Note that this is going to be very technical.
Do not Garbage Collect
I think it cannot be stressed enough: If you use any OpenGL code, then do not use the Garbage collector you can use in Mac OS X 10.5 in your application. It actually works perfectly as long as your code is correct, but you can’t use the OpenGL Profiler that way. OpenGL Profiler is probably the best way to figure out what the hell your drawing code is doing and why, and it’s too valuable to give up for decadent luxury like automatic memory management.
Core Data can be tough
This is the first time I’m using Core Data at all. It’s a technology in Mac OS X to make saving and loading of documents both easier and more powerful and I hoped it would save me some work. As it turns out, there are things that Core Data really likes doing, and there are things that it take some work and there are things that are just not possible. Storing an OpenGL texture in Core Data while editing this on the GPU is admittedly a niche task, but it’s just something that will absolutely not work with it. Now, I’m using a package format (i.e. a folder that appears as a file to the user), storing some Core Data data as well as plain height field and texture data. Again, something that took some work, but Apple provides [some very helpful sample code][NSPersistentDocumentFileWrappers] here.
OpenGL actually kind of sucks
OpenGL is great for a lot of work, but it’s easy to get it out of it’s comfort zone as well. A fun thing are OpenGL contexts, which can be thought of as the areas you draw to plus associated resources. It’s very important to have one, but it tends to float in the background, harldy ever seen or interacted with. That works great until the very moment you have two. I have two per document (the image browser for the textures takes one too. Not sure why, but it does) and potentially unlimited documents. Inside the designated drawing routine, you can count on your context being the right one. Outside, not so much, leading to much fun when you draw something or set some state at a completely different point than you thought.
Of course, you can manually switch the context. You just have to make sure that every piece of code that wants to make OpenGL calls knows that context and sets it before doing anything — and, of course, sets the one previously used again after that. There is, however, an easier way.
Apple offers [OpenGL macro headers][oglmacros], which basically means normal OpenGL functions, except the context is now a variable you provide. The idea is that this saves some speed because it is no longer required to look up the correct context, but you can also use it to keep from switching to a different context completely. You simply add the required cgl_ctx
variable to your class like this:
@interface SomeGLUsingCode : NSObject
{
CGLContextObj cgl_ctx;
/* ... */
}
- (id)initWithContext:(CGLContextObj)context;
@end
And in the implementation, the first thing you do is set it:
- (id)initWithContext:(CGLContextObj)context;
{
if (![super init]) return nil;
cgl_ctx = context;
...
return self;
}
Now all OpenGL calls made from that object will go to the right context. One point, though: This only works if you call the OpenGL functions yourself, not if you use some library that calls OpenGL for you - notably, it already fails with GLU. However, it does seem that it’s legal to use this as a replacement for context switches, [Apple sample code uses it this way][oglmacrosample]. I just think it’s sad that there’s no more direct way to pass the context to an OpenGL function.
The Future
Now, next up is getting texture drawing in, then having an exporter for iPhone, then drawing it on the device. After that, rails and static objects, possibly special cases for trees and the like. Water might come later if I’m really bored.
By the way, I do not currently plan to make this level editor available to anyone but myself, although I won’t completely rule it out either, especially if I ever decide to make it work with maps for [Hubschrauber][hubi] as well. I do guarantee that there will not be a Windows version ever, unless one of the Cooca-To-Windows-Projects starts becoming truly amazing over night.
[^1]: Actually, old versions needed for undo are stored in the normal program. It makes a few things easier if I do it that way.
Written on June 10th, 2009 at 10:23 am
Björn
Torsten (admin)