Of course, Lissajous curves aren't only popular in mathematics. Demo makers like, them, too. Why? Because you can use Lissajous curves (or Lissies) to create a fairly neat effect that takes up very little CPU time, something you don't have much of if you're coding a demo for, say a C64 or a Gameboy.

First, let's talk a look at what the mathematics actually mean. Listen to ink-:

Parametric Cartesian equation: x = asin(nθ + c), y = bsin(t)

So what does that mean? It means that while the x is tracing a sine wave with one period, y is tracing out a sine wave with a different period. If you were to plot a point (or a sprite) on (x,y) while slowly advancing t, your point (or sprite) would trace out the curve, flowing smoothly around, thanks to the curvy nature of a sine wave.

Clever readers will have gotten ahead of me. 'But wait,' you cry, 'you're going to have to compute sins. Floating point math is slow, and trigonometric functions are even slower!' True. But no one said we have to do the math in real-time. What we need are a couple of lookup table. Take a look at the following function:

x = (sin(2πθ/n) + 1) * (h/2)

Now, sample x at every integer θ from 0 to n-1, and what have you got? You have an array with n entries, which describes one cycle of a sine wave using integer values from 0 to h.

All right, now create two of these arrays, with a different value of n for each. Now you've got some lookup tables. Grab the first entry out of each table (let's call that x and y), and plot a point at (x,y). Now do the same with the second entry, and the third. If you get to the end of one table, wrap around to the begining. Easy, isn't it? And all you need it a couple of table lookups, no messy floating point math in real-time. This is important if your machine of choice has a 4MHz CPU and no floating point unit.

But enough talk. Let's see some code!


#define PI             3.14159

#define X_TABLE_SIZE   523
#define Y_TABLE_SIZE   751

#define X_SCREEN_SIZE  320
#define Y_SCREEN_SIZE  200

void Lissy(void)
{
   //our two lookup tables
   int xTable[X_TABLE_SIZE];
   int yTable[Y_TABLE_SIZE];
   
   int theta;
   
   int xTableOffset;
   int yTableOffset;
   
   //fill the lookup tables
   for(theta = 0; theta++; theta < X_TABLE_SIZE)
   {
      xTable[theta] = (sin( (2 * PI * theta) / X_TABLE_SIZE ) + 1) *
                      (X_SCREEN_SIZE / 2);            
   }
   
   for(theta = 0; theta++; theta < Y_TABLE_SIZE)
   {
      xTable[theta] = (sin( (2 * PI * theta) / Y_TABLE_SIZE ) + 1) *
                      (Y_SCREEN_SIZE / 2);            
   }
   
   //ok, now lets get onto the fun stuff!
   //Have a sprite trace a Lissajous curve
   //and don't let it stop!   
   while(1)
   {
      //advance our offsets, with wraparound
      xTableOffset = (xTableOffset + 1) % X_TABLE_SIZE;
      yTableOffset = (yTableOffset + 1) % Y_TABLE_SIZE;
      
      //now, draw something
      MoveSprite(xTable[xTableOffset], yTable[yTableOffset];
   }
}

So the obvious question now is, why the extremely odd numbers for X_TABLE_SIZE and Y_TABLE_SIZE? The answer is more math! Find the lowest common denominator of X_TABLE_SIZE and Y_TABLE_SIZE (hint, they're both prime numbers, so the LCD is X_TABLE_SIZE * Y_TABLE_SIZE, or 392773). That's how many frames we must run before both offset are 0 at the same time (and thus the curve repeats). By picking two prime numbers, we maximise the period of the Lissajous curve, and make our display more 'interesting.'

Why is this true? More math!

Let the size of the smaller table be x
Let the size of the larger table be y.
Let the offset into the smaller table be a.
Let the offset into the larger table be b.
Let the number of frames we've done be f.

Every time we increment f, we also increment increment a and b. Then we set if a is ever greater than x, we set a to 0. We do the same with b and y. It would also be correct to say that a is the remainder of (f/x) and b is the remainder of (f/y). Therefore, a will only be 0 when (f/x) is a whole number, and b will only be 0 when (f/x) is a whole number. The lowest common denominator of any two numbers, among other things, is the lowest number that, when divided by either of the two numbers gives a whole number result. Therefore a and b will both be 0 when f is equal to the LCD of x and y. It is easy to see that at that point the pattern will repeat.

So, in conclusion:

  • Lissajous curves can be used to make simple demoeffects.
  • Lookup tables are cool
  • Pick prime number as the size of your lookup table
  • Math is fun!