[lumen logo]


This is a simple narrative description of the spinner application included with the Lumen library as a demo.

The spinner demo has no command-line parameters, so you just invoke it by typing its name, like ./spinner if you're sitting in the bin directory, or bin/spinner if you're in the lumen directory. When run, spinner creates a black window with a slowly rotating red and yellow square in the middle. It should complete one full rotation every 15 seconds, since it advances the rotation one degree every frame, and should run at 24 frames per second. Terminate it by pressing any key, or closing the window.

The spinner demo is an enhancement of the sgi_simple demo, intended to show some additional features of Lumen. It's getting closer to what a real Lumen app might typically look like, though it's still very basic.

First off, creating a double-buffered rendering context (by allowing the default Animated => True in Lumen.Window.Create) is actually necessary here in order to get smooth animation. And as with all d-b contexts, we call Lumen.Window.Swap after we've finished our scene, so as to sort of "publish" it to the user.

Event-wise, spinner differs from sgi_simple by using Lumen.Events.Animate.Select_Event instead of the simpler Lumen.Events.Receive_Event. The main difference is that Receive_Events passes all events to a single handler procedure, whereas Select_Events passes them to different procedures according to the event type. So the call to Select_Events passes a short table of callbacks, each of which handles just one kind of event.

Of note here is that we attach the same procedure, Quit_Handler, to two different event types: Key_Press and Close_Window. In this case that makes sense, since both events just mean "quit". If, however, the app needed to do something with the event data, like for example look at the actual key pressed, then it should probably use two different callback procedures for the two different event types. Not that it must, though, since each callback still gets the entire event-data record as a parameter, so it could look at the event type and process each type differently. But the whole point of using Select_Events is so each procedure gets only one kind of event and thus doesn't have to waste time asking which kind it is. We just merged the two types here because neither requires any complicated processing.

In the paragraphs above, we sorta conflated the event-loop procedures (specifically Receive_Events and Select_Events) in Lumen.Events with those in Lumen.Events.Animate. That was on purpose, because the identically-named procedures behave pretty much identically, except the ones in Animate also call a draw-frame procedure, where the ones in Events don't.

Much of the rest of the app is similar to sgi_simple, with a few additions. First, instead of drawing a plain white square, it uses OpenGL's built-in smooth color blending to draw a fancy two-tone square, ooh la la! Second, it rotates the square before displaying it, which is a way to show that animation is actually happening.

And third, of course, is the animation mechanism itself. When calling the Lumen.Events.Animate version of Select_Events, we said "Here, call this procedure (New_Frame) 24 times every second." And in most cases, that's just what it does. If we had asked it to call New_Frame eighty bajillion times a second, the library might not be able to keep up, in which case it'll call the procedure as often as it can. If you want your draw-a-frame procedure to be called as frequently as possible, use the constant Flat_Out for the FPS value instead of using some arbitrary big number.

When it's called, New_Frame updates the square's rotation value, then calls Draw to re-draw it. Now, why didn't we just put that little rotation-update code at the top of Draw and use that procedure directly as my new-frame callback? Because Draw gets called in other places, like after an Exposed event. And we didn't want those calls to update the rotation, which would have made it "jerky"--sometimes it would happen on schedule, and sometimes it would happen when the user is fiddling with the window. To get the smooth stately rotation it has, we wanted to update the angle only when the new-frame time rolls around, once every 24th of a second. That's a good general rule for frame-based animation: Update your scene once per frame, and re-draw your scene when you need to, but don't mix the two processes.


Last Updated: 14 Dec 2011 09:32:32