Scheme is a programming language invented by Gerald J. Sussman and Guy L. Steel Jr. in 1975. Scheme is based on another language – Lisp, which dates back to the fifties. It is a high level language, which means it is biased towards human, rather than machine understanding. The fluxus scratchpad embeds a Scheme interpreter (it can run Scheme programs) and the fluxus modules extend the Scheme language with commands for 3D computer graphics.
This chapter gives a very basic introduction to Scheme programming, and a fast path to working with fluxus – enough to get you started without prior programming experience, but I don’t explain the details very well. For general scheme learning, I heartily recommend the following books (two of which have the complete text on-line):
The Little Schemer Daniel P. Friedman and Matthias Felleisen
Structure and Interpretation of Computer Programs Harold Abelson and Gerald Jay Sussman with Julie Sussman Online: http://mitpress.mit.edu/sicp/full-text/book/book.html
We’ll start by going through some language basics, which are easiest done in the fluxus scratchpad using the console mode – launch fluxus and press ctrl 0 to switch to console mode.
Scheme as calculator
Languages like Scheme are composed of two things – operators (things which do things) and values which operators operate upon. Operators are always specified first in Scheme, so to add 1 and 2, we do the following:
fluxus> (+ 1 2) 3
This looks pretty odd to begin with, and takes some getting used to, but it means the language has less rules and makes things easier later on. It also has some other benefits, in that to add 3 numbers we can simply do:
fluxus> (+ 1 2 3) 6
It is common to “nest” the brackets inside one another, for example:
fluxus> (+ 1 (* 2 3)) 7
If we want to specify values and give them names we can use the Scheme command “define”:
fluxus> (define size 2) fluxus> size 2 fluxus> (* size 2) 4
Naming is arguably the most important part of programming, and is the simplest form of what is termed “abstraction” - which means to separate the details (e.g. The value 2) from the meaning – size. This is not important as far as the machine is concerned, but it makes all the difference to you and other people reading code you have written. In this example, we only have to specify the value of size once, after that all uses of it in the code can refer to it by name – making the code much easier to understand and maintain.
Naming values is very useful, but we can also name operations (or collections of them) to make the code simpler for us:
fluxus> (define (square x) (* x x)) fluxus> (square 10) 100 fluxus> (square 2) 4
Look at this definition carefully, there are several things to take into account. Firstly we can describe the procedure definition in English as: To (define (square of x) (multiply x by itself)) The “x” is called an argument to the procedure, and like the size define above – it’s name doesn’t matter to the machine, so:
fluxus> (define (square apple) (* apple apple))
Will perform exactly the same work. Again, it is important to name these arguments so they actually make some sort of sense, otherwise you end up very confused. Now we are abstracting operations (or behaviour), rather than values, and this can be seen as adding to the vocabulary of the Scheme language with our own words, so we now have a square procedure, we can use it to make other procedures:
fluxus> (define (sum-of-squares x y) (+ (square x) (square y))) fluxus> (sum-of-squares 10 2) 104
The newline and white space tab after the define above is just a text formatting convention, and means that you can visually separate the description and it’s argument from the internals (or body) of the procedure. Scheme doesn’t care about white space in it’s code, again it’s all about making it readable to us.
Making some shapes
Now we know enough to make some shapes with fluxus. To start with, leave the console by pressing ctrl-1 – you can go back at any time by pressing ctrl-0. Fluxus is now in script editing mode. You can write a script, execute it by pressing F5, edit it further, press F5 again... this is the normal way fluxus is used.
Enter this script:
(define (render) (draw-cube)) (every-frame (render))
Then press F5, you should see a cube on the screen, drag the mouse around the fluxus window, and you should be able to move the camera – left mouse for rotate, middle for zoom, right for translate.
This script defines a procedure that draws a cube, and calls it every frame – resulting in a static cube.
You can change the colour of the cube like so:
(define (render) (colour (vector 0 0.5 1)) (draw-cube)) (every-frame (render))
The colour command sets the current colour, and takes a single input – a vector. Vectors are used a lot in fluxus to represent positions and directions in 3D space, and colours – which are treated as triplets of red green and blue. So in this case, the cube should turn a light blue colour.
Add a scale command to your script:
(define (render) (scale (vector 0.5 0.5 0.5)) (colour (vector 0 0.5 1)) (draw-cube)) (every-frame (render))
Now your cube should get smaller. This might be difficult to tell, as you don’t have anything to compare it with, so we can add another cube like so:
(define (render) (colour (vector 1 0 0)) (draw-cube) (translate (vector 2 0 0)) (scale (vector 0.5 0.5 0.5)) (colour (vector 0 0.5 1)) (draw-cube)) (every-frame (render))
Now you should see two cubes, a red one, then the blue one, moved to one side (by the translate procedure) and scaled to half the size of the red one.
(define (render) (colour (vector 1 0 0)) (draw-cube) (translate (vector 2 0 0)) (scale (vector 0.5 0.5 0.5)) (rotate (vector 0 45 0)) (colour (vector 0 0.5 1)) (draw-cube)) (every-frame (render))
For completeness, I added a rotate procedure, to twist the blue cube 45 degrees.
To do more interesting things, we will write a procedure to draw a row of cubes. This is done by recursion, where a procedure can call itself, and keep a record of how many times it’s called itself, and end after so many iterations.
In order to stop calling our self as a procedure, we need to take a decision – we use cond for decisions.
(define (draw-row count) (cond ((not (zero? count)) (draw-cube) (translate (vector 1.1 0 0)) (draw-row (- count 1)))))
(every-frame (draw-row 10))
Be careful with the brackets – the fluxus editor should help you by highlighting the region each bracket corresponds to. Run this script and you should see a row of 10 cubes. You can build a lot out of the concepts in this script, so take some time over this bit.
Cond is used to ask questions, and it can ask as many as you like – it checks them in order and does the first one which is true. In the script above, we are only asking one question, (not (zero? Count)) – if this is true, if count is anything other than zero, we will draw a cube, move a bit and then call our self again. Importantly, the next time we call draw-row, we do so with one taken off count. If count is 0, we don’t do anything at all – the procedure exits without doing anything.
So to put it together, draw-row is called with count as 10 by every-frame. We enter the draw-row function, and ask a question – is count 0? No – so carry on, draw a cube, move a bit, call draw-row again with count as 9. Enter draw-row again, is count 0? No, and so on. After a while we call draw-row with count as 0, nothing happens – and all the other functions exit. We have drawn 10 cubes.
Recursion is a very powerful idea, and it’s very well suited to visuals and concepts like self similarity. It is also nice for quickly making very complex graphics with scripts not very much bigger than this one.
Well, now you’ve got through that part, we can quite quickly take this script and make it move.
(define (draw-row count) (cond ((not (zero? count)) (draw-cube) (rotate (vector 0 0 (* 45 (sin (time))))) (translate (vector 1.1 0 0)) (draw-row (- count 1))))) (every-frame (draw-row 10))
time is a procedure which returns the time in seconds since fluxus started running. Sin converts this into a sine wave, and the multiplication is used to scale it up to rotate in the range of -45 to +45 degrees (as sin only returns values between -1 and +1). Your row of cubes should be bending up and down. Try changing the number of cubes from 10, and the range of movement by changing the 45.
To give you something more visually interesting, this script calls itself twice – which results in an animating tree shape.
(define (draw-row count) (cond ((not (zero? count)) (translate (vector 2 0 0)) (draw-cube) (rotate (vector (* 10 (sin (time))) 0 0)) (with-state (rotate (vector 0 25 0)) (draw-row (- count 1))) (with-state (rotate (vector 0 -25 0)) (draw-row (- count 1)))))) (every-frame (draw-row 10))
For an explanation of with-state, see the next section.
Comments in scheme are denoted by the ; character:
; this is a comment
Everything after the ; up to the end of the line are ignored by the interpreter.
Using #; you can also comment out expressions in scheme easily, for example:
(with-state (colour (vector 1 0 0)) (draw-torus)) (translate (vector 0 1 0)) #;(with-state (colour (vector 0 1 0)) (draw-torus))
Stops the interpreter from executing the second (with-state) expression, thus stopping it drawing the green torus.
Let is used to store temporary results. An example is needed:
(define (animate) (with-state (translate (vector (sin (time)) (cos (time)) 0)) (draw-sphere)) (with-state (translate (vmul (vector (sin (time)) (cos (time)) 0) 3)) (draw-sphere))) (every-frame (animate))
This script draws two spheres orbiting the origin of the world. You may notice that there is some calculation which is being carried out twice - the (sin (time)) and the (cos (time)). It would be simpler and faster if we could calculate this once and store it to use again. One way of doing this is as follows:
(define x 0) (define y 0) (define (animate) (set! x (sin (time))) (set! y (cos (time))) (with-state (translate (vector x y 0)) (draw-sphere)) (with-state (translate (vmul (vector x y 0) 3)) (draw-sphere))) (every-frame (animate))
Which is better - but x and y are globally defined and could be used and changed somewhere else in the code, causing confusion. A better way is by using let:
(define (animate) (let ((x (sin (time))) (y (cos (time)))) (with-state (translate (vector x y 0)) (draw-sphere)) (with-state (translate (vmul (vector x y 0) 3)) (draw-sphere))))
This specifically restricts the use of x and y to inside the area defined by the outer let brackets. Lets can also be nested inside of each other for when you need to store a value which is dependant on another value, you can use let* to help you out here:
(define (animate) (let ((t (* (time) 2)) ; t is set here (x (sin t)) ; we can use t here (y (cos t))) ; and here (with-state (translate (vector x y 0)) (draw-sphere)) (with-state (translate (vmul (vector x y 0) 3)) (draw-sphere)))) (every-frame (animate))
Lambda is a strange name, but it's use is fairly straightforward. Where as normally in order to make a function you need to give it a name, and then use it:
(define (square x) (* x x))
(display (square 10))(newline)
Lambda allows you to specify a function at the point where it's used:
(display ((lambda (x) (* x x)) 10))(newline)
This looks a bit confusing, but all we've done is replace square with (lambda (x) (* x x)). The reason this is useful is that for small specialised functions which won't be needed to be used anywhere else it can become very cumbersome and cluttered to have to define them all, and give them all names.