Home       About Me       Contact       Downloads       Part 82    Part 84   

Part 83: New Source Code

June 8, 2013

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.

Finally, I realized I don't have to package a font at all. I have FreeType to generate character images and return spacing information. Under WebGL, I have the Javascript Canvas element which can render text and return a character at a time.

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.

Javascript Text

Fig 1: A character cell
FreeType gives me all the information I need on a character. For Javascript, it's more of a problem. What I want is a character image and three pairs of numbers (See Figure 1):
  • Horizontal and vertical advance. This is how much the current point moves when drawing the character. The vector from A to B in Figure 1. Vertical advance will be zero for European fonts.

  • Horizontal and vertical origin. This is the vector from A to C in Figure 1. It tells me where to place the character image in relation to the baseline of text.

  • Image width and height. These are the size of the character cell I'm drawing.

The Javascript Canvas element only returns horizontal advance. There's a spec that describes other information which might be in the TextMetric object, but neither Chrome or Firefox implement this. So at first, I thought I just couldn't use the Canvas as a source of text.

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'm not supporting any kind of kerning with this code. Kerning is where a pair of letters are moved closer than their standard advance distances. For example, in the word "Two", the "w" could be fit under the bar of the "T", which would look a bit nicer. The Javascript canvas text doesn't return any kerning information. FreeType does return it, but none of the standard Web fonts like "Georgia" or "Arial" have any kerning information.

The Cursor

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.)

Every PC game out there lets you hit ESC to get to the menu. Even if I coded my Javascript demo to use some other key, people will hit ESC by reflex and lose the pointer lock. That means they lose their shooter cursor and can't get it back.

I haven't decided what to do about this. If I handled a touch screen like a tablet, I wouldn't have a cursor at all. So perhaps the right approach is to code Javascript apps as if they had no mouse or keyboard, and the mouse was really a touch, dragging the view. We'll see.

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.

Also, my current crude GUI tells you to hit F1 to toggle help. The browsers all reserve this key themselves and open their own help. But they do pass the key to Javascript, so the help will be toggled when you dismiss the browser help pane and return to the demo. Or you can hit F2, which opens the debug console and dismisses help. Hit F2 again and the debug console will also disappear.







Home       About Me       Contact       Downloads       Part 82    Part 84   

blog comments powered by Disqus