Break down of a C64 demo effect

So I recently made this little sinewave demo effect on the Commodore 64:

This generated a lot of lively discussion on twitter and /r/c64. A bunch of people were curious as to how it works and I promised to explain it on my blog.

The main idea is to use character mode with a custom character set. I sample y-coordinate every 8 pixels to produce a list of line segments [(0, y0), (8, y1), (16, y2), ..., (320, y40)]. In the below animation, you can see these line segments along with blue rectangles that signify which 8x8 character blocks intersect the line segments.

The goal then is to fill all the pixels below a line segment with light blue and everything above it with dark blue.

Here’s how you might code this up in Python:

Unfortunately using a polygon fill routine in C64 bitmap mode would be pretty slow. To make this run fast, I precompute the different ways a line segment can intersect 8x8 character blocks using an edge function rasterizer written in Python. Here’s an image that shows some combinations of a line hitting 3 vertically stacked 8x8 blocks:

Here’s Python code for the block rasterizer:

A Python renderer using the table generated construct_fill_table():

Note that in the above code, each entry in the fill_tbl 2d-array is a list 3 character bitmaps. A character bitmap is a 64 entry list of zeros and ones. To make use of this on the C64, you need to turn this data into two separate arrays: the actual character set and an array indexed by y0, y1 that returns a list of 3 character indices.

Here’s how these two tables look like. First the charset:

..and the character index table ($20 is the space character, ie., an empty block, $a0 is a fully filled 8x8 block):

    .byte $00, $a0, $a0, $a0
    .byte $02, $0e, $a0, $a0
    .byte $0f, $10, $a0, $a0
    .byte $1b, $24, $a0, $a0
... 252 more entries ...

If you’d run the C64 equivalent of the above Python routine on the default charset, you’d see something like this:

Since I have full control over the sine animation, I choose animation parameters that make the line segments behave “nicely”. Niceness is defined as follows: let (x0, y0) and (x0 + 8, y1) be the end points of a line segment. The segment behaves “nicely” if it satisfies the following condition: |y0 − y1| < c (where c is some small number, in my case 8). This is required in order to keep the fill_tbl at a reasonable size and to limit the number of unique 8x8 characters (since the hardware character set is only 256 entries).

That’s about all there is to it, I guess.