Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Building a 3D Engine in Perl, Part 4
by Geoff Broadwell | Pages: 1, 2, 3, 4, 5, 6, 7, 8

Speaking OpenGL's Language

The bitmaps produced by read_font_file are simply packed bitstrings, in this case 11 bytes long (one byte per seven-pixel row). Before using them to render strings, the engine must first load these bitmaps into OpenGL. This happens in the main init_fonts routine:

sub init_fonts
{
    my $self  = shift;

    my %fonts = (
        numbers =& 'numbers-7x11.txt',
    );

    glPixelStore(GL_UNPACK_ALIGNMENT, 1);

    foreach my $font (keys %fonts) {
        my ($bitmaps, $w, $h) = 
            $self->read_font_file($fonts{$font});

        my @cps    = sort {$a <=& $b} keys %$bitmaps;
        my $max_cp = $cps[-1];
        my $base   = glGenLists($max_cp + 1);

        foreach my $codepoint (@cps) {
            glNewList($base + $codepoint, GL_COMPILE);
            glBitmap($w, $h, 0, 0, $w + 2, 0,
                     $bitmaps->{$codepoint});
            glEndList;
        }

        $self->{fonts}{$font}{base} = $base;
    }
}

init_fonts opens with a hash associating each known font with a font file; at the moment, only the numbers font is defined. The real work begins with the glPixelStore call, which tells OpenGL that the rows for all bitmaps are tightly packed (along one-byte boundaries) rather than being padded, so that each row begins at even two-, four-, or eight-byte memory locations.

The main font loop starts by calling read_font_file to load the bitmaps for the current font into memory. The next line sorts the codepoints into @cps, and the following line finds the maximum codepoint by simply taking the last one in @cps.

The glGenLists call allocates display lists for codepoints 0 through $max_cp, which will have numbers from $base through $base + $max_cp. For each codepoint defined by the font, the inner loop uses glNewList to start compiling the appropriate list, glBitmap to load the bitmap into OpenGL, and finally, glEndList to finish compiling the list.

The glBitmap call has six parameters aside from the bitmap data itself ($bitmaps->{$codepoint}). The first two are the width and height of the bitmap in pixels, which read_font_file conveniently provides. The next two define the origin for the bitmap, counted from the lower-left corner. Bitmap fonts use a non-zero origin for several purposes, generally when the glyph extends farther left or below the "normal" lower-left corner. This may be because the glyph has a descender (a part of the glyph that descends below the general line of text, as with the lowercase letters "p" and "y"), or perhaps because the font leans to the left. The simple code in init_fonts assumes none of these special cases apply and sets the origin to (0,0).

The last two parameters are the X and Y increments, the distances that OpenGL should move along the X and Y axes before drawing the next character. Left-to-right languages use fonts with positive X and zero Y increments; right-to-left languages use negative X and zero Y. Top-to-bottom languages use zero X and negative Y. The increments must include both the width/height of the character itself and any additional distance needed to provide proper spacing. In this case, the rendering will be left to right. I wanted two extra pixels for spacing, so I set the X increment to width plus two, and the Y increment to zero.

The last line of the outer loop simply saves the list base for the font to make it available later during rendering.

init calls init_fonts as usual, just after the call to init_time:

$self->init_fonts;

Text Rendering

The hard part is now done: parsing the font file and loading the bitmaps into OpenGL. The new draw_fps routine calculates and renders the frame rate:

sub draw_fps
{
    my $self   = shift;

    my $base   = $self->{fonts}{numbers}{base};
    my $d_time = $self->{world}{d_time} || 0.001;
    my $fps    = int(1 / $d_time);

    glColor(1, 1, 1);
    glRasterPos(10, 10, 0);
    glListBase($base);
    glCallListsScalar($fps);
}

The routine starts by retrieving the list base for the numbers font, retrieving the world time delta for this frame, and calculating the current frames per second as one frame in $d_time seconds. It takes a little care to make sure $d_time is non-zero, even if the engine is running so fast that it renders a frame in less than a millisecond (the precision of SDL time handling); otherwise, the $fps calculation would die with a divide-by-zero error.

Pages: 1, 2, 3, 4, 5, 6, 7, 8

Next Pagearrow