Seer User Guide

Seer Tutorial

To get started with your own code, see how to download seer-template-project in Getting Started, or simply add scala files to the example directory of Seer and run them from the sbt example project.

SeerApp

The most basic program in Scala looks like this:

package my.package.name

object Main extends App {
    println("Hello World: " + args.mkString(" ")
}

But where is the main function? Our Main object inherits the App trait. There is a little bit of trickery in the App trait which is actually hiding the main entry point, and ends up executing the body of our Main object. Our object also inherits a member named args containing any command line arguments.

The most basic Seer program looks like this:

package my.package.name

import com.fishuyo.seer.SeerApp

object Main extends SeerApp {
    println("Goodnight Moon: " + args.mkString(" ")
}

Here we have switched out the App trait for SeerApp. SeerApp does a little more than just hide the main function. It is also hiding the initialization of a libGDX application, which handles the creation of a render window and and openGL context.

SeerApp does one more thing, it adds itself to the global SceneGraph. I will explain more about this later, but you can think of it like this: anything in the SceneGraph will be drawn and animated every frame, meaning having its draw() and animate(dt:Float) methods called, consequently anthing in the SceneGraph inherits the Animatable trait.

So lets draw something:

package my.package.name

import com.fishuyo.seer.SeerApp
import com.fishuyo.seer.graphics.Sphere

object Main extends SeerApp {
    println("Goodnight Moon: " + args.mkString(" ")

    val sphere:Model = Sphere()

    override def draw(){
        sphere.draw()
    }
}

Notice that we've imported another object called Sphere. Sphere generates Models containing a generated sphere Mesh. A Model is like an instance of a Mesh, with a Pose and Material, letting you change its position and orientation in space and also how it is rendered by the default Shader.

You'll notice that this looks like simply a white circle, lets change the material to see the effects of the default lighting.

package my.package.name

import com.fishuyo.seer.SeerApp
import com.fishuyo.seer.graphics._

object Main extends SeerApp {

    val sphere = Sphere()
    sphere.material = Material.specular
    sphere.material.color = RGB(1,0,0)

    override def draw(){
        sphere.draw()
    }
}

Here we give the model a specular material and set its color to red.

Lets make a few more objects, and animate them:

package my.package.name

import com.fishuyo.seer.SeerApp
import com.fishuyo.seer.graphics._

object Main extends SeerApp {

    val sphere = Sphere().translate(-2,0,-4)
    val cube = Cube().translate(0,2,-4)
    val tetra = Tetrahedron().translate(2,0,-4)
    val quad = Plane().translate(0,-2,-4)

    Shader.defaultMaterial = Material.specular
    Shader.defaultMaterial.color = RGB(1,0,0)

    override def draw(){
        sphere.draw()
        cube.draw()
        tetra.draw()
        quad.draw()
    }

    override def animate(dt:Float){
        sphere.rotate(0.01f,0f,0)
        cube.rotate(0.01f,0.02f,0)
        tetra.rotate(0.02f,0.01f,0)
        quad.rotate(0.01f,0.03f,0)
    }
}

Here notice the addition of the animate method, which is called every frame, causing the models to be rotated. Also, instead of setting the material for each model, we set the default material in the Shader object, which I'll explain later. We also see a few other built in Model generators, and some transformation functions on the Model class.

Now lets make something a little more interesting:

package my.package.name

import com.fishuyo.seer.SeerApp
import com.fishuyo.seer.graphics._

object Main extends SeerApp {

    val n = 30                  // number of models to make
    val s = 1.f / n             // scale factor based on n

    // generate a list of cubes using yield
    // collecting what is returned from the block in this case c
    val cubes = for(i <- 0 until n) yield {
        val c = Cube().scale(1,s,1).translate(0, i*s - .5f, 0)
        c.material = Material.specular
        c.material.color = HSV(i*s, 0.7f, 0.7f)
        c
    }

    override def draw(){
        FPS.print                   // print current fps
        cubes.foreach( _.draw )     // draw each cube
    }

    override def animate(dt:Float){
        // rotate each cube based on its index in the list
        cubes.zipWithIndex.foreach {
            case(cube, idx) => cube.rotate(0,(idx+1)*s/100,0)
        }
    }
}

Here we use Scala's for comprehension and yield to generate a list of Models. We then rotate each model every frame at a different rate corresponding to its index in the list.

Seer Script

Seer script is a way of having more immediate feedback as you are coding. Seer script is simply a way of monitoring a scala file and compiling it when it is modified. If it compiles sucessfully it will be automatically added to the SceneGraph and will start running. Everytime you make a change, the script is recompiled and re-run, replacing the previous Script in the SceneGraph.

Here is an example SeerApp using a ScalaScriptLoader:

package tutorial

import com.fishuyo.seer._
import dynamic.SeerScriptLoader

object Main extends SeerApp {
    val live = new SeerScriptLoader("scripts/live.scala")
}

That's it for the Main program, now make another scala file in a directory called "scripts" named "live.scala":

import com.fishuyo.seer._
import dynamic.SeerScript
import graphics._

object Script extends SeerScript {

    override def draw(){
        Sphere().draw()
    }

    override def animate(dt:Float){}
}

Script

A couple important things to note. First, this object extends SeerScript. Second, we return the object from our script in the final line. These are necessary for your Script to run properly within the running context.

Now, any changes you make to the live.scala file will be compiled on the fly and show up. If they don't, check the console to see if any compilation errors occured.

All for now, have fun!