Geometry in motion

Part 1: Trig

Trig. Yes I know. It's a four-letter word. Nevertheless, I'll do my level best to show you that:

  • Trig is simpler than you think
  • Trig can do beautiful things to your multimedia screen.

This is a two-part article. In this first part, you'll be exploring the anatomy of triangles and circles, tinkering with Pythagoras' Theorem and discovering the secrets of the Sine and Cosine. Then you'll write a simple handler and adapt it to produce some gorgeous curves: circles and waves, spirograph patterns and string art.

There are a couple of movies to help you. One is a shocked Tutorial movie, with interactive diagrams. This is to help you to understand the concepts treated below. The other is an unprotected Practice movie which you can use as a starting point for investigations of your own. It contains well-behaved versions of all the scripts in the article. (Scripts in this article use hardcoded tight repeat loops, for concision).

In the second part, we'll look at how vector geometry can provide certain solutions faster than trig can. You'll be developing a simple game of pool which uses vectors to bounce balls off each other according to the laws of physics. Who said geometry was no fun?

Trig is simpler than you think

Trigonometry (to give it its Sunday name) means "the measurement of trigons". You won't find "trigon" in your dictionary, though you might find "polygon". A polygon is a geometrical figure with many angles. A trigon is a figure with three angles... more commonly known as a triangle. Trig is all about triangles.

Trig's favorite triangle is the right-angled triangle. The godfather of the right-angled triangle is Pythagoras, so we'll start with him.

If you don't remember what Pythagoras' Theorem was all about, go and check out the Shockwave Tutorial. It demonstrates in non-mathematical terms that: the area of the square drawn on the hypotenuse of a right-angled triangle is equal to the sum of the areas drawn on the other two sides.

Circles and triangles

Believe it or not, triangles have a lot in common with circles. Circles + math = . Pi is a weird number. Nobody knows exactly what its value is. If you ask Director, you'll get a close enough approximation. Type this in the Message window:

set the floatPrecision to 14
put pi()
-- 3.14159265358979

Where does this number come from? It's simply the ratio between the circumference of a circle and its diameter. There's a demonstration of this in the Tutorial movie.

So what does pi have to do with triangles?

Pi and angles

Imagine that each angle of a triangle is at the center of a circle. You can define the angle as being a certain proportion of the whole circle. But how do you measure that proportion? You can't use a straight ruler, because the edge of a circle is round. So you have to divide the edge of the circle up into equal parts.

The Babylonians divided the circle into 360 degrees. This was for two reasons: - They reckoned the year to have 360 days, plus five extra days, one for each of the known planets. 360 was thus a number that described the full circle of the year. - They did their arithmetic in base 60. This is an extraordinarily handy base, when it comes to calculating fractions. 360 is conveniently 6 times 60.

Modern math divides the circle into radians. There are 2 radians in one complete rotation. While you may be more familiar with measuring angles in degrees, you'll find that the radian is a very powerful unit. Director works in radians, so let's try to understand why.

Radians

When you draw a circle with a pair of compasses, you only need to know one thing: its radius. A curved section of the circumference of a circle is called an arc. To imagine a radian, simply imagine an arc exactly one radius long. If you draw two lines, one from each end of the arc to the center of the circle, the angle between the two lines is a radian. There's a demonstration of this in the Tutorial movie.

The circumference of a circle is, by definition, times its diameter. The diameter is twice the radius, so you could express the circumference as 2 times the radius, or 2r.

You need 2 radians to rotate once in a complete circle, and you need 2 radius lengths to draw the complete circumference.

In other words, each time you turn a line through an angle of one radian, each point on the line will move along an arc whose curved length is equal to its distance from the center of rotation. Obvious isn't it?

Sines and angles

There's another way of determining angles, a way that deals only with straight lines.

Imagine that you have a ladder that is exactly one ladder-length long. (That's a mathematician's way of saying that we will say that its length is 1 unit, so that we can conveniently simplify things).

If you lean the ladder against a wall, it will make a certain angle with the ground. You could measure that angle in degrees or radians, or any other unit of circular measurement. Or you could simply measure how high the top of the ladder is from the ground.

If you change the angle, you change the height of the ladder. If you change the height, you change the angle. So why can't we use the height of the ladder as a measure of the angle?

The answer is... you can. If you use sines. What is a sine? A sine is a proportion. A sine is the height of a right-angled triangle compared to the length of the hypotenuse. In our example, the ladder makes a certain angle with the ground. The sine of that angle is equal to the height of the ladder divided by its length.

Converting from angles to sines

You can measure straight lines in feet or meters. Maybe you still have a school ruler with inches on one side and centimeters on the other. Or you may have a table that allows you to convert from one unit of measurement to the other.

You could create a similar sort of conversion table with angles on one side and sine values on the other. The only difference will be that the values will not change in a linear fashion, since we're dealing with circles and not with straight lines.

Director has access to just such a table. It is stored in special logic circuits in the heart of the microprocessor. Unfortunately, Director takes a little time to read the table, since it is not linear. We'll see shortly how to use Lingo to build a simpler table which is faster than the built-in trig functions.

Cosines

Is there any other straight line we could measure in order to determine the angle?

What about the horizontal distance between the foot of the ladder and the foot of the wall? That would give us a result which is complementary to the sine. It's called the cosine.

An angle of zero corresponds to a ladder lying flat on the ground. The height of the ladder (the sine) is zero and the distance between the foot of the ladder and the wall (the cosine) is one. When the ladder is standing vertically against the wall, it makes an angle of /2 radians with the ground. In Lingo terms, sin (pi/2) = 1, and the cos (pi /2) = 0. When the ladder is at 45 (or /4 radians as you should now say), the sine and the cosine are equal. From Pythagoras' Theorem, you can calculate that they will both be 2 (or sqrt (2.0), for those who prefer their math in Lingo).

Screen coordinates

On your computer screen there are no triangles and no circles. All you have are beautifully square pixels, arranged in serried rows and columns. So to draw triangles and circles we have to work with coordinates (and not with compasses).

Any point on the screen has a loc or location. This is expressed by two figures, the locH and the locV, or horizontal and vertical location. By convention, pixels are counted from the top left corner of the screen. A pixel with a high locH value is near the right of the screen. A pixel with a high locV value is near the bottom.

When you did geometry in class, you probably used a different coordinate system. Instead of locH and locV, you'd have used "x" and "y". In that system, coordinates are measured from a central point on the page, known as the Origin: x is counted to the right (just as locH is), but y was counted upward, whereas locV is counted downward.

To emphasize this difference, my examples use the variables h and v, instead of the more usual x and y.

And in the "Sine and cosine" section of my Tutorial movie, I have respected this difference again. You will not see a ladder leaning up against a wall. Instead you will find a line leaning down and to the right.

Trig can do beautiful things to your multimedia screen

Hey, didn't I promise you that you'd be creating luscious curves? I haven't forgotten. Let's start by drawing a circle. Create a new movie, then place a small bitmap in sprite channel one. Type this into a Movie Script (OK, so it's hardcoded, but no-one's looking. By the way, I use 400 iterations just because that's how many it takes to fill in all the points on the circle Try it with other values if you like).

on circle1
  set the trails of sprite 1 to TRUE
  set center to point (160, 160)
  set radius to 100
  repeat with angle = 1 to 400
    set h to radius * sin (angle)
    set v to radius * cos (angle)
    set the loc of sprite 1 to center + point (h, v)
    updateStage
  end repeat
end circle1

Now type this into the Message Window:

circle1

To erase your circle, type this into the Message Window:

set the stageColor to the stageColor

The circle1 handler makes a circle appear on the stage, almost as if it were being screwed in. What is happening?

To count through a repeat loop, you need integers. I use the variable "angle". Each time angle is incremented, the bitmap is moved one radian around the circle. (Try using only six iterations: repeat with angle = 1 to 6. That will make the effect easier to see).

If you want the circle to be drawn in only continuous movement, you're going to have to use floating point numbers. Clear the trails from the stage, then try this:

on circle2
  set the trails of sprite 1 to TRUE
  set center to point (160, 160)
  set radius to 100
  set iterations to 400
  set adjust to iterations / (2 * pi())
  repeat with angle = 1 to iterations
    set h to radius * sin (angle / adjust)
    set v to radius * cos (angle / adjust)
    set the loc of sprite 1 to center + point (h, v)
    updateStage
  end repeat
end circle2

How is the circle actually being drawn? Each time you pass through the repeat loop, Director imagines the hypotenuse of a different triangle. Director measures how many pixels right and how many pixels down it should place the far end of the hypotenuse, and stamps an image of your bitmap at that point.

You draw a circle by tracing the tip of a series of triangles. As I said before: circles and triangles have lots in common. All the other handlers you'll meet in this article are simple variations on this theme.

Optimizing

Calculating sine and cosine values on each iteration wastes CPU cycles. Floating point math is slow. Let's treat both these problems now. Let's create a list to store all the trig values that we'll ever need for our handler:

on sineList numberOfPoints, scaleFactor
  set angleBetweenPoints to (2 * pi()) / numberOfPoints
  set sineList to []
  repeat with i = 1 to numberOfPoints
    append (sineList, integer (sin (angleBetweenPoints * i) * scaleFactor))
  end repeat
  return sineList
end

This returns a list of integers. All sine values are between - 1 and 1. The parameter scaleFactor is used to scale these values up so that they lie in a useful range. Suppose we want to create a sine table using degrees instead of radians: a good value for scaleFactor would be 10000, since it would give us a unique value for each degree. Try this in the Message Window:

put sineList (360, 10000)
-- [175, 349, 523, 698, 872, 1045, 1219, 1392, 1564, 1736, ...
    9877, 9903, 9925, 9945, 9962, 9976, 9986, 9994, 9998, 10000, ... ]  

I have edited the output to show only the values for 1 - 10 and for 81 - 90. You may like to compare the results with what you get on your pocket calculator. Each value in the list is 10000 times the real sine value.

Actually, powers of two are better than not powers of ten, because the microprocessor works slightly faster with powers of two. Using 8192 ( 2 to the power 13) gives us excellent results here. A value as low as 64 (2 to the power 6) still draws an acceptable circle.

When you come to plot the values on the screen, you divide by scaleFactor again, so as to revert to the normal trig values.

A similar handler can be written to return a list of cosine values.

Benchmark tests

Test the difference in calculation time between the circle2 handler and the following one (you'll find benchmark code in the Practice movie). Drawing trails on to the screen takes a heavy toll on the processor's time, so you may see little difference in the actual execution of the circle on the screen. Indeed, creating the lists then drawing the circle only once may even take a few extra ticks. The second and subsequent times the circle is drawn, however, calculation time is reduced by around 70%.

on circle3
  set the trails of sprite 1 to TRUE
  set center to point (160, 160)
  set radius to 100
  set iterations to 400
  set scaleFactor to 8192
  set sineList to sineList (iterations, scaleFactor)
  set cosList to cosList (iterations, scaleFactor)
  repeat with angle = 1 to iterations
    set h to radius * getAt (sineList, angle) / scaleFactor
    set v to radius * getAt (cosList, angle) / scaleFactor
    set the loc of sprite 1 to center + point (h, v)
    updateStage
  end repeat
end circle3

Epicycloids

What happens if you move the center of your circle while you're drawing it?

Here's a Movie Script that works like a spirograph. It moves the point tempCenter around a circle of a given radius. Sprite 1 is moved in a circle around the shifting tempCenter. The result is a four-leafed clover. I've used globals so that you can type changes into the Message Window and see what happens. You can test with floating point numbers as well as integers.

global hRadius, vRadius, center, adjust, counter, lengthRatio, angleRatio

on startMovie
  set the trails of sprite 1 to TRUE
  set hRadius to 60
  set vRadius to 60
  set center to point (the width of the rect of the stage / 2, ¬
the height of the rect of the stage / 2)
  set adjust to 1000 / (2 * pi())
  set counter to 0
  set lengthRatio to 2
  set angleRatio to 5
end

on idle
  set counter to counter + 1
  -- MOVE tempCenter
  set h to hRadius * sin (counter / adjust)
  set v to vRadius * cos (counter / adjust)
  set tempCenter to center + point (h, v)
  -- MOVE SPRITE 1 AROUND tempCenter
  set h to (hRadius * sin (angleRatio * counter / adjust)) / lengthRatio
  set v to (vRadius * cos (angleRatio * counter / adjust)) / lengthRatio
  set the loc of sprite 1 to tempCenter + point (h, v)
  updateStage
end idle

A more elaborate version of this, incorporating the scaled integer technique, appears in the Practice movie.

Ellipses

An ellipse is simply a circle with two different diameters. Or if you prefer, a circle is a particular type of ellipse where both horizontal and vertical diameters are the same. Actually, that's the way the above handler works. Both hRadius and vRadius are originally set to the same value. Try changing one or the other through the Message Window to see the effect.

Waves

To draw circles and ellipses, we've been plotting sin () against cos(). You can make a perfect wave form, by simply plotting sin() directly against your iterative counter. Here's a handler that owes a lot to our circle:

on sinewave
  set the trails of sprite 1 to TRUE
  set radius to 100
  set centerV to 160
  set iterations to 320
  set adjust to iterations / (2 * pi())
  repeat with angle = 1 to iterations
    set h to angle set v to radius * sin (angle / adjust) -- ¬
    -- + (radius * sin ((8 * angle) / adjust) / 4)
    set the loc of sprite 1 to point (h, centerV - v)
    updateStage
  end repeat
end sinewave

Harmonics

I've commented out part of one of the lines. Uncomment this (in two places), to see what happens. This gives a similar effect to changing lengthRatio and angleRatio in the spirograph: you add a small wave to a larger one. Try altering the hardcoded values to see what strange waves you can create. Or add even more harmonics: continue the line, adding more elements of the type I had commented out, each with different values. Be esoteric: call your creations Fourier curves.

Rotating lines

So far, we've simply been placing a bitmap image at the point of a series of imaginary triangles. Now we'll try twirling lines. For this we need a drawLine handler. You'll find this in a separate cast member in the Practice movie. Since it doesn't use trig, I won't quote it here. I'll just tip my hat in passing to Tom Collins who wrote the original version of it.

A line needs to have a start and an end point. If we keep one end point fixed, and move the other round in a circle, with trails off, we get a clock hand. Here's our familiar circle handler revisited:

on circle4
  set the member of sprite 1 to member "line\"
  set the lineSize of sprite 1 to 2
  set centerH to 160
  set centerV to 120
  set radius to 100
  set iterations to 400
  set adjust to iterations / (2 * pi())
  repeat with angle = 1 to iterations
    set h to radius * sin (angle / adjust)
    set v to radius * cos (angle / adjust)
    drawLine (sprite 1, centerH, centerV, centerH + h, centerV - v)
    updateStage
  end repeat
end circle4

String art

But if you're an artist and not a clock watcher, why keep one end fixed? In the Practice movie, you'll find a behavior entitled "String Art". There I make the v coordinate move more rapidly than the h coordinate. To do this, I multiply the angle used to calculate v by a factor I call hvRatio. I also calculate two points, at different places on the same curve. The variable pointAdvance determines how far round the curve the second point is. Then I draw a line between the two points. Here are the key lines of code:

on drawNext me
  set counter to counter + 1
  set the forecolor of sprite the spriteNum of me to counter mod 256
  set h1 to centerH + hRadius * sin (counter / adjust)
  set v1 to centerV + vRadius * cos (hvRatio * counter / adjust)
  set h2 to centerH + hRadius * sin ((counter + pointAdvance) / adjust)
  set v2 to centerV + vRadius * cos (hvRatio * (counter + pointAdvance) / adjust)
  drawLine the spriteNum of me, h1, v1, h2, v2
end drawNext

I change the color of the line at each iteration for a better visual effect.

Summary

With two simple trigonometric functions, sin() and cos(), you can create a whole series of curves. By combining these curves with each other and with straight lines, you can produce dazzling patterns. To save CPU cycles, you can create lists of sine and cosine values, and refer to them rather than the native Lingo functions. Next month we'll look at cases where vector geometry leads to handlers that run faster even than optimized trig.