stevebob.net

Framebuffers and Coffee

2012-11-11 15:03
This was drawn using a framebuffer. (Image from images.wikia.com)

Two discoveries I recently made that will help with my game engine project as well as make my life easier in general, are CoffeeScript and using HTML Canvas elements as framebuffers. The former is a programming language that adds some “niceness” to javascript. The latter is a technique in computer graphics that allows the rendering of an image to what is effectively an invisible screen for some further processing (or just storage) before sending it to the actual screen.

CoffeeScript

Here is the website. If you ever intend on writing more than 10 lines of javascript you should click on that link and learn some CoffeeScript.

It’s very transparent, in that most lines of javascript can be converted 1 to 1 into the corresponding CoffeeScript, so don’t worry about sacrificing functionality. It has (in my opinion) a far cleaner syntax than javascript. Pythonesque indenting is used to indicate scope, brackets aren’t required around control flow statements or function calls, there’s no need for semicolons to terminate or delimit lines.

Besides the syntactic pleasantries, there is an intuitive way of defining classes (which I guess is still just a syntactic thing if you want to get technical). The reason I highlight this point is it encourages you to write classes the “correct” way for most use cases. The way I write classes in javascript is:

function Animal(name) {

    this.name = name; // field

    this.sayName = function() { // method
        alert("My name is " + name);
    }
}

var bob = new Animal("bob");
bob.sayName(); // opens an alert box with "My name is bob"

That seem fairly intuitive (by javascript standards at least). I have a function that creates an object which I can access using object oriented semantics (fields and methods). The “class definition” and the constructor are kinda the same thing here.

Now here’s why it’s bad. Let’s say I go and create a large number of “Animal” objects. The code for the sayName method will be duplicated for every “Animal” instance I create. This means that there is memory wasted to store the duplicated code, and time wasted to copy the code each time I create a new “Animal”.

What I should have done is:

var Animal = (function() {

  function Animal() {}

  Animal.prototype.construtor = function(name) {
    this.name = name;
  };

  Animal.prototype.sayName = function() {
    return alert("My name is " + this.name);
  };

  return Animal;

})();

var bob = new Animal;

bob.sayName();

This works the way one would expect an “Animal” class to work. It can be accessed in the same ways as the previously defined one, but code is not duplicated. To be honest, I cheated a bit here and wrote it in CoffeeScript, compiled it to javascript and changed the output to look like code a person would write.

Here’s the CoffeeScript that generated the above code:

class Animal
  construtor: (@name) ->
  sayName: -> alert "My name is " + @name

bob = new Animal
bob.sayName()

The “->” denotes a function with an optional list of arguments to the left and the definition to the right. The constructor takes a name, and does nothing with it. The “@” denotes a field, so the passed in name is stored in a field, and accessed in the “sayName” method.

Canvas as a Framebuffer

A framebuffer is an array of pixels that you can’t necessarily see. It can be thought of as a regular screen (or canvas element), and all the same operations can be performed.

The reason this made me so happy when I found out about it is that I want to do something like this in my game engine. If the player is obscured from view, the thing obscuring them can be made transparent in a localised area. I got this idea from the original Fallout:

A screenshot from Fallout demonstrating cutaway walls (Image from steamaddicts.com)
fallout screenshot

Framebuffers make it really easy to do this. You just have one framebuffer on which you draw everything behind the player (and the player as well if you want). On a second framebuffer, draw everything in front of the player. Then you figure out the 2d screen coordinates where the player will be drawn. Then you create a transparent circle with that point as its centre on the second framebuffer (by reducing the alpha value of pixels). Then you just copy the framebuffers to the visible canvas, with the first one being drawn before the second one.

Another benefit of doing this is it can be used to optimize vector graphics. Most of the apps I wrote in the past use vector graphics in some way. It’s easy to just tell the canvas to draw a line between two points or fill in some polygon. Before the image can be drawn though, it must be rasterized (turned into an array of pixels). When vector graphics are used for animations, each frame must be rasterized before it’s drawn, which comes at a performance cost. This is justified usually, because the content is being dynamically generated, but for this game engine, if (say) a wall was stored using vector graphics, it’s going to look pretty much the same all the time. Rather than rasterizing the wall at each frame, I can rasterize it once when the program starts, and store the result on a framebuffer. Then, whenever I need to render the wall, I can just look at the framebuffer and copy pixels to the visible screen.

To test out this technique, and also to get the feel for accessing canvas elements from CoffeeScript, I made the mudkip demo at the top of the page. The ImageLoader helper class is for dynamically loading image files. The FrameBuffer helper class just wraps invisible canvas elements and takes care of giving it a unique name and actually creating the element at runtime. I’ve dumped all the code into the box below (it’s actually divided into files). Note that this uses jquery (which is all the dollar signs).

# effectively the main function
$ ->
  ImageLoader.loadAsync [
    "http://images.wikia.com/animalcrossing/images/a/ae/Mudkip.png"
  ], (images) ->
    # images is an array of Image objects passed to this callback   
    
    # connect to the main canvas
    canvas = document.getElementById 'screen'
    ctx = canvas.getContext '2d'

    # create a framebuffer
    fb = new FrameBuffer
    
    # draw a box
    fb.ctx.fillStyle="rgb(120, 170, 200)"
    fb.ctx.fillRect 2, 2, 212, 274
    fb.ctx.fill
    fb.ctx.lineWidth = 4
    fb.ctx.strokeStyle="rgb(50, 80, 200)"
    fb.ctx.strokeRect 2, 2, 212, 274
    ctx.stroke
   
    # draw some images on the framebuffer
    fb.ctx.drawImage images[0], 2, 2, 210, 272
    
    # copy the framebuffer onto the main canvas
    ctx.drawImage fb.canvas, 0, 0


class ImageLoader
  # srcArray: an array of strings representing the image sources
  # callback: a function to call on the array of Image objects once
  # they are loaded
  @loadAsync: (srcArray, callback) ->
    
    # create a blank image for each source
    images = srcArray.map -> new Image
    
    # maintain a count of how many images have loaded
    numLoaded = 0
    
    # load the images
    zipWith images, srcArray, (img, src) ->
      
      # when the image loads, increment the counter
      img.onload = -> numLoaded++

      # this actually loads the image
      img.src = src
    
    # this function sets itself to be called periodically to check
    # if all the images are loaded, and makes the callback with
    # the loaded images as its argument once they are loaded
    wait = (period, retries) ->
      if numLoaded == images.length
        return callback(images)

      if retries == 0
        return console.log "timeout loading images"

      console.log "."
      setTimeout wait, period, retries-1

    console.log "Loading images"
    wait 100, 20


class FrameBuffer
  constructor: ->
    
    # create the unique html id
    id = "framebuffer" + FrameBuffer.globalCount

    # create a new canvas element
    @canvasArr = ($ "", {id: id})
    @canvas = @canvasArr[0]
    @canvas.width = 300
    @canvas.height = 300

    # get the 2d context for drawing
    @ctx = @canvas.getContext '2d'

    # increment the count
    FrameBuffer.globalCount++

  # used to create unique names for frame buffers
  @globalCount: 0

zip = (xs, ys) ->
  return [] if not (xs.length and ys.length)
  rest = zip xs[1..], ys[1..]
  rest.unshift {_1: xs[0], _2: ys[0]}
  return rest

zipWith = (xs, ys, fn) ->
  (zip xs, ys).map (x) ->
    fn x._1, x._2