I've updated the Downloads Page with the latest source code and versions of the demos. This is the first update since August. So embarrassing....
Most of the work last fall was on the summarized cubical landscape, which has not appeared in any of the demos (but I haven't given up on it!) The problem with releasing the demos was related to the GUI work.
Long time readers may remember that when I first did 2D graphics, I used Windows GDI to construct bitmaps in memory, and then turned those into overlay textures for the GUI. This only worked under Windows and was slow, but did the job.
Back in Part 46, I switched to using the FreeType library to draw characters. For the rest of 2D graphics, I implemented a very crude set of functions for lines and rectangles. That was enough to put together an ugly GUI library, and it worked on Windows, Linux and Mac.
Towards the end of the year, I wanted to finish off the GUI, and needed a richer set of 2D graphics primitives to draw nicer looking buttons, etc. I found a piece of code on the net that did antialiased complex polygon fill, and rebuilt the code on top of that.
Unfortunately, the polygon fill code had a tendency to overwrite random bits of memory in my demos. I don't know if I was using it incorrectly, if I broke it when integrating it, or if it was just broken from the start. In any case, I couldn't release any demos with that code in it, since they crashed all the time in odd ways. I also didn't feel like digging through a couple of thousand lines of very optimized C code to figure out what was wrong with it. Since there was no new function in the demos anyway, I let them sit.
Finally, in the last part, I tried to get the GUI code to work in the Emscripten versions by just compiling FreeType and the broken polygon fill code. It did work, but with the same glitches the C version had. It was also noticeably slow, and it required me to package font files with the demos, making them huge. To release any new demos, I needed to redo the 2D graphics library.
New 2D Graphics
The obvious way to do 2D, which people have recommended from the start, is to use OpenGL to render to a texture, then use that for the overlay. To handle text, you would create another big texture image with all the characters in a font and draw individual characters using triangles with that font texture.
There are a couple of reasons not to do this. First, you have to package the big font image with the demo. There will actually be a font image for each typeface and size you want to use, with variations for bold, italic, and bold-italic. That adds up fast.
Second, you'll be fixing the font size at compile time, with no way to adjust it for the users screen resolution (or, in my case, bad eyesight.) I didn't want either of those problems, so I kept rejecting this approach. I also didn't really want to code all my 2D graphics as triangles under OpenGL.
I recoded the text support to have an external source of characters, potentially different on each platform. It requests a character and then adds it to the font texture. Characters can be whatever style, size, boldness, etc. that I need. The single font texture mixes them all, and contains just the characters used by the application.
When the font texture gets full, I just force out any partially drawn strings, and empty the font texture. Remember that the desired result is the bits in the overlay texture. Once a character image is drawn there, the overlay is no longer dependent on the font texture. I can use this as a cache and just clear it whenever necessary.
Then I realized that I'm going to be discarding pixels under any blank areas of the character image. So it doesn't matter if the character cell is larger than it really should be. At that matters is that the character is there and the baseline is right. The rest I can fake.
The Canvas allows me to request a font size in pixels. That will be the distance from the top of a capital letter to the bottom of the tail of a 'g'. I can take a point size from my code and a screen resolution and convert that into a character size in pixels. I assume another 20% for spacing between the lines of text.
For each character, I know the horizontal advance from the TextMetric. I assume vertical advance of zero (no non-European fonts supported!) That gives me the A-B line. I reserve enough space to the left and right for an extreme italic overhang of the letter. I reserve enough height for a tall letter. Then I render the letter and save the entire area around it as the character image. The "C" point is based on my assumption of the worst case.
This works, with the problem that I don't really know the distance from the baseline to the top of a capital letter (called the ascender.) You'll see in the Emscripten demos that buttons and things are a little large, because I had to guess this number. If I get it wrong, the lines of text will run into one another, so I've overestimated it a bit.
If I do an Android port, I can use similar techniques there. In fact, if I wanted to drop FreeType, I could go back to using native text drawing (Windows, XLib or Cocoa) as a source of characters.
I have one last issue in the WebGL demos. I use the ESC key to toggle between a "desktop" cursor which can point anywhere and use the GUI, and a "shooter" cursor that's locked to the center of the screen. I can't do this correctly under WebGL.
There's a feature called "pointer lock" which is supposed to solve this problem. Unfortunately, there are issues. First, under Firefox, it only works if you are in fullscreen mode. Second, they pop up a little dialog telling you the page is locking your pointer. Third, they use ESC to break the lock. And once broken, the app cannot reaquire the lock (at least under Linux Chrome.)
For completeness, here are the Emscripten demos. The new source code checked in has makefiles for building these demos, but without a fix for the cursor problem, they are kind of hard to use.
These are the full C++ demos. They expect the cursor to be turned off and to get relative points from the mouse. If the browser cursor gets separated from the app cursor, just move out of the window and back in.
blog comments powered by Disqus