CoffeeScript + Processing.js == Crazy Delicious

February 21st 2011
Kill Demo

Want to create cool visuals using CoffeeScript and Processing.js? Excellent, me too. Read on.

Particulars

In the end, this sketch weighs in under 70 lines of CoffeeScript, and was a breeze to write.

While it doesn’t do anything profound, it is pretty to look at and fun to tweak.

Follow this guide and soon, you too can have some pretty in-browser javascript-canvas visuals to play with.

Here’s the Demo (reload for new patterns) and the Source

Pregame

I’ll be using BaseJSApp to start. It’s a quick way to get the skeleton of an app up and running. If you go your own way with straight apache or something else, that’s cool too. Skip what doesn’t pertain to your setup and modify anything that needs changing.

Grab your friends

First, we need Processing.js. Get it here: processing-1.0.0.min.js and copy it to /js/libs/processing-1.0.0.min.js.

We won’t be pre-compiling our CoffeeScript with node. Instead we’ll be using the client-side compiler. Grab that here: coffee-script.js and copy it to /js/libs/coffee-script.js.

Now, create a new CoffeeScript that we can work on. I created mine at /js/coffee_draw.coffee

Some HTML

Put three new script tags in /index.html linking to Processing.js, the CoffeeScript compiler, and the .coffee file:

<!-- /index.html -->

  ...

  <!-- makes life easy -->
  <script src="/js/libs/jquery-1.5.min.js"></script>
  <script src="/js/libs/underscore-1.1.4.min.js"></script>

  <!-- add processing.js -->
  <script src="/js/libs/processing-1.0.0.min.js"></script>

  <!-- add the compiler: -->
  <script src="/js/libs/coffee-script.js"></script>

  <!-- and the coffeescript file we'll be playing with: -->
  <script type="text/coffeescript" src="/js/coffee_draw.coffee"></script>

  <!-- usually use these, but not today -->
  <!-- <script src="/js/plugins.js"></script> -->
  <!-- <script src="/js/script.js"></script> -->

  ...

  

If you’re using BaseJSApp or Sinatra as the webserver for this app, you’ll need to tell sinatra/rack how to handle .coffee files. To do this, add a text/coffeescript mime-type in your config.ru file:

# /config.ru

  require 'rubygems'
  require 'open-uri'
  require 'sinatra'

  # add this line:

  mime_type :coffee, "text/coffeescript"

  set :public, File.dirname(__FILE__) + '/public'

  ...
  

Pulses

Processing.js relies on canvas for its awesome drawing skills. First order of business is to create a canvas element for it to use:

<!-- /index.html -->

  ...

  <div id="main">

    <!-- add the canvas element -->
    <canvas id="processing"></canvas>
  </div>

  ...    

  

Easy.

And, we’re done with html/server stuff, it’s CoffeeScript + Processing.js time. Get excited.

a Quick note on Processing

You don’t need too much knowledge of Processing to follow this guide, but I’d like to point a couple things out that will make this easier.

Processing sketches typically heavily rely on the draw() method. draw() is the sketch’s main draw loop, and as such will be called many times per second (60, by default). All the interesting stuff we’ll be doing will be called out to from this method.

Another “default” method in Processing is setup(). This method gets called only once, at the very beginning. It’s typically used to set the size of the sketch, initial background color, and maybe some other initialization variables.

Structure

Here’s an outline of how we’ll structure our .coffee file:

 _________________________________________________

  |  coffee_draw: Our main sketch object/function  |
   _________________________________________________

   _________________________________________________ 

  | Bean: a class that we'll use in our main sketch |
   _________________________________________________ 

   _________________________________________________

  |       create a new Processing instance          |
  |         bind it to the canvas element           |
  |         with our coffee_draw object.            |
   _________________________________________________ 

  

Getting Real

And here’s the code:

# /js/coffee_draw.coffee

  # Our main sketch object:
  coffee_draw = (p5) ->  

    # processing's "init" method:

    p5.setup = () ->
      p5.size($(window).width(), $(window).height())
      p5.background(0)

    # where the fun stuff happens:
    p5.draw = () ->
      # to make sure everything's working

      # let's do a quick test:

      # set the value of the background equal
      # to the sketch's current frame count
      # and the whole canvas will pulse different colors
      p5.background(p5.frameCount)


  # a helper class that will come into play soon

  class Bean
    constructor: (p5, opts) ->  

  # wait for the DOM to be ready, 
  # create a processing instance...
  $(document).ready ->
    canvas = document.getElementById "processing"

    processing = new Processing(canvas, coffee_draw)
  

If you fire up a browser and go to http://localhost:3000 you should see the background pulse different colors (black to white, lots of shades of blue, etc…). Kinda cool, but not what we’re after. Onwards!

Ghost Doodles

We now have everything hooked up and a skeleton sketch down. It’s time to get fancy.

One of my favorite methods in the Processing API is noise(). To quote the documentation: “Perlin noise is a random sequence generator producing a more natural ordered, harmonic succession of numbers compared to the standard random() function.”

Translation: it gives you random numbers that are pretty.

Let’s use it to “randomly” control a “brush”:

# /js/coffee_draw.coffee


  # Our main sketch object:
  coffee_draw = (p5) ->  
  ...

    # got rid of the test code

    # lets make a "brush"
    p5.draw = () ->

      # noise() needs an "offset" argument
      # we'll tie it to the frame count
      x_off = p5.frameCount * 0.0005

      # we want y's offset to increase at the same rate
      # but be different (20 is arbitrary)
      y_off = x_off + 20

      # use noise(), the offset, and our sketch's dimensions
      # to get a "random" position for our "brush"

      x = p5.noise(x_off) * p5.width
      y = p5.noise(y_off) * p5.height

      # color our brush (red with 15% opacity)
      p5.stroke(255, 0, 0, 15)

      # draw at brush's current location (set above)
      p5.point(x, y)

  ...

  

Run that for a bit in the browser and you should have something like this:

Ghost Doodle

Ok, that’s kind of cool. But only kind of. We should take it to the next step.

Green Beans

Remember when we created that “Bean” class, and then promptly forgot about it? We’re going to use it now.

This “bean” object we’re about to create will behave similarly to the “brush” we just made, but it will move a little bit differently. Instead of relying on noise() to provide an arbitrary location on the canvas each frame, a “bean” will keep track of its position, velocity, and acceleration, and will use that information to draw. However, we will also use noise() to influence the direction of movement. Essentially a “bean” is a particle.

A “bean” should keep track of its:

  • x position
  • y position
  • x noise offset
  • y noise offset
  • velocity
  • acceleration

Additionally, every frame it should be able to update its position using these attributes. Let’s make that happen:

# /js/coffee_draw.coffee
  ...

  # a helper class that will come into play soon
  class Bean
    # when creating a new bean instance
    # we'll pass in a reference to processing

    # and an options object
    constructor: (@p5, opts) ->
      # set the initial values for bean's attributes
      @x = opts.x
      @y = opts.y

      @x_off = opts.x_off
      @y_off = opts.y_off

      @vel = opts.vel || 3

      @accel = opts.accel || -0.003

    # we'll call this once per frame, just like our main
    # object's draw() method
    draw: () ->

      # only do the following if we have positive velocity
      return unless @vel > 0

      # increment our noise offsets

      @x_off += 0.0007
      @y_off += 0.0007

      # adjust our velocity by our acceleration
      @vel += @accel

      # use noise, offsets and velocity 
      # to get a new position
      @x += @p5.noise(@x_off) * @vel - @vel/2

      @y += @p5.noise(@y_off) * @vel - @vel/2

      # set a color to draw with (3% opacity, green)

      @p5.stroke(0, 255, 0, 3)

      # draw a point
      @p5.point(@x, @y)

  

That’s great, but this Bean class won’t change our sketch until we start creating instances of them. Let’s do that now:

# /js/coffee_draw.coffee


  # Our main sketch object:
  coffee_draw = (p5) ->

    p5.setup = () ->

      p5.size $(window).width(), $(window).height()
      p5.background(0)

      # create an array to store our "beans"
      # (to be created below)
      @beans = []

    p5.draw = () ->

      x_off = p5.frameCount * 0.0005
      y_off = x_off + 20

      x = p5.noise(x_off) * p5.width
      y = p5.noise(y_off) * p5.height

      # we have beans now, we don't need to draw the brush 
      # p5.stroke(255, 0, 0, 15)
      # p5.point(x, y)

      # every 20 frames, we'll create a "bean"
      if p5.frameCount % 20 == 0
        # the new bean instance will be constructed
        # with the current attributes of our "brush"

        bean = new Bean(p5, {
          x: x
          y: y
          x_off: x_off
          y_off: y_off
        })
        # add our newly created bean to our array
        @beans.push(bean)

      # go through the list of "@beans" and draw them

      # <3 CoffeeScript
      bean.draw() for bean in @beans
  

Now we see something like the following:

Ghost Doodle2

Pretty cool, but I think we can do better =)

Color

I like how this is going, but I think that was a bit too much green. noise() has served us well so far, why stop now?

We’ll create a new method in our Bean class for setting the draw color:

# /js/coffee_draw.coffee
  ...
  class Bean
    constructor: (@p5, opts) ->
      ...

    draw: () ->

      ...
      @x += @p5.noise(@x_off) * @vel - @vel/2
      @y += @p5.noise(@y_off) * @vel - @vel/2

      # we'll be setting the color with the set_color() method
      # so delete or comment our previous call to stroke()
      # @p5.stroke(0, 255, 0, 3)

      # replacing it with a call to set_color():
      @set_color()

      @p5.point(@x, @y)

    set_color: () ->

      # we're primarily interested in changing the hue
      # of the draw color, therefore we make our lives
      # easier by setting the color mode to HSB
      @p5.colorMode(@p5.HSB, 360, 100, 100)

      # the hue will now be a function of our good friend

      # noise, and the average of the x and y offsets
      h = @p5.noise((@x_off+@y_off)/2)*360

      # change these according to taste

      s = 100
      b = 100
      a = 4

      # and set the stroke
      @p5.stroke(h, s, b, a)

  

While we’re at it, let’s make our brush plant these beans more frequently:

# /js/coffee_draw.coffee

  coffee_draw = (p5) ->

    p5.setup = () ->
      ...

    p5.draw = () ->

      ...
      # it used to every 20 frames, but let's try 8
      if p5.frameCount % 8 == 0
        bean = new Bean(p5, {
          x: x
          y: y
          x_off: x_off
          y_off: y_off
        })
        @beans.push(bean)

  

Sweet.

3

What next?

If you’ve made it this far, you should have something like the above picture running in your browser. But, what’s next?

Tweaks

First, there are a ton of tweakable values to play with. A lot of the numbers included here were purely arbitrary. In the last section we changed a 20 to an 8, but maybe it looks better at a 5. The colors seem to skew blue/green, maybe messing with the set_color() method needs some tweaking. We could change the x and y offsets that we create beans with, not to mention play with their velocity and acceleration values.

Enhancements

In a lot of my sketches I find myself adding a fade() method. Basically, every so many frames I’ll draw a screen-sized black rectangle over everything with a very low opacity ~7-15%. Older parts of the sketch will get increasingly covered by black. This prevents the canvas from getting too busy.

Mouse interaction could also be cool, and Processing makes it a snap. At any time you can get the coordinates of the user’s mouse by accessing mouseX and mouseY on the processing object. Use this instead of the “brush” to create beans, affect the color, or offset increments— possibilities are literally endless, and that’s before you even consider adding DOM elements and overlays.

Performance

One thing that should be done, but I won’t cover here is removing beans from @beans once they stop moving. Each frame we iterate over every bean that we’ve created, and this number continues to grow. To prevent this from getting out of hand, we can remove the “dead” ones from the array. Alternatively we can just set a maximum length to the @beans array and remove beans down to that limit.

That’s it. Have fun!

The Demo and the Source