Lesson 3: Parameters, Lux expressions, basic animations

View the full source.

Most of the time we want our graphical elements to be dynamic in some way. For example, they might be animated in time, or responding to user input. So far, all examples we have seen have static data. In this lesson, you will create a simple dynamic actor.

The simplest way to create actors that change with time is to make the vertex positions in an actor depend on the time since the page loaded. Naively, we might think of creating a new actor with new model positions at every clock tick, since that is what we have seen until now. This is a bad idea in general: creating new actors involves a number of WebGL calls that are slow.

A better idea is to tell Lux that the vertex position in your actor should depend on some small number of parameters, which are created ahead of time and changed as necessary to reflect the changing state of the world. As these parameters change, so does the actor's appearance. Importantly, changing a parameter is fast.

Lux parameters can be created, but for this example, we will use a parameter predefined by Lux, namely now:

var angle = Lux.now().mul(50).radians();

The notation used above to express the value stored in angle is a little different than you might have expected. To explain it, we need to get into a little more detail about Lux.

Lux expressions are GPU computations are Javascript values

One of the fundamental features in Lux is the ability to manipulate expressions which represent computations to be performed on the GPU. When you program in WebGL directly, these computations are specified by vertex and fragment shaders. These are written in a separate programming language, compiled and called mostly as a black box, as far as Javascript is concerned. This separation of two worlds makes writing software that uses shaders awkward. In Lux, on the other hand, we insist that every GPU computation be expressed as a Javascript value. Notice that Lux is not precomputing values for the GPU ahead of time; rather, Lux lets you build expressions which it can later compile into GPU computations. Let's now look at how to build these types of expressions.

The variable now contains a value that represents the number of seconds elapsed since the context was created. This value can be used to specify the color of a vertex, where the red component of the square's color would be alternating between zero and one depending on the amount of elapsed time:

Lux.actor({
    model: square_model, 
    appearance: { 
        position: square_model.vertex,
        color: Shade.vec(now.sin().add(1).div(2), 0.5, 0.5, 1)}});

Notice that the variable now does not store a number, but rather an object from which you can build new objects. You can see that the resulting expression denotes one half plus half the sine of the parameter, but it is important to notice that (Math.sin(now)+1)/2 would not work! The moral of the story is that these expressions can be combined to build more complicated expressions which denote interesting computations. This is what's happening in the assignment to angle as well. If s is the number of elapsed seconds, then now represents the value s, now.mul(50) represents the value 50*s, and now.mul(50).radians(), in turn, represents the value 50*s*(pi/180).

We will use angle to rotate the vertex positions before translating them. Notice that Shade.translation and Shade.rotation behave like functions, just like camera does. In the following example, Shade.rotation(angle, Shade.vec(1, 0, 0)) denotes a transformation, that when applied to a vertex v, will return an expression denoting a vertex rotated angle radians around the vector [1,0,0]:

Lux.Scene.add(Lux.actor({
    model: square, 
    appearance: {
        position: camera(
            Shade.translation( 1.5, 0, -6)(
                Shade.rotation(angle, Shade.vec(1, 0, 0))(
                    square.vertex),
        color: Shade.vec(now.sin().add(1).div(2), 0, 0, 1)}}));

As you can see, Lux expressions are built either by taking a known expression and calling some method on it, or by building it from scratch from the Shade object.

Lux currently has no way of knowing that your scene is dynamic and should be regularly redrawn. So you tell it explicitly, like so:

Lux.Scene.animate();

Back to the index.