Crickets Chirping

mon qui si, mon qui d’où

js.Processing with Rhino

with 4 comments

Processing.js: Processing in JavaScript

A few months ago JavaScript Ninja John Resig unleashed processing.js. Like many others I was shocked and amazed. I love to tinker with Processing, and I also do a fair amount of JavaScript work, so I was keenly interested.

There are some limitations to using the browser for the type of work people want to do with Processing though. Processing.js is even slower than Processing applets, and it’s not even a full implementation.

However, I am intrigued by the use of JavaScript to do Processing-like stuff.

js.Processing: JavaScript in Processing

The Processing language inherits some unfortunate (and personally annoying) features of Java since that’s what it compiles down to.

I actually prefer JavaScript to the Processing language because it’s loosely typed and has some other nifty features.

I’d like to be able to use all of Processing’s capabilities (not the crippled applet variety), AND program with JavaScript instead of Processing’s language.

cake.have().eat();

So today I created a .pde that loads Rhino, creates a JavaScript execution context, stuffs itself into that context, loads an external javascript file and runs it.

And js.Processing is born.

I added some functions to proxy the built-in Processing callbacks so you can write “draw(){}” and “mouseDragged(){}” in javascript and they do what you’d expect.

Here’s the main shell Processing sketch that runs the Javascript (listing below):


/**
 * Experiment to drive Processing with JavaScript.
 * Uses Rhino: https://developer.mozilla.org/en/Rhino_Overview
 * This does not currently work in an applet due to some Rhino issues
 * with class loading and optimization.
 *
 * This class itself isn't where you put the javascript.  .js files
 * should go in your processing project's code/ folder.  Load your .js
 * script explicitly in setup() below.  This example is hard-coded to
 * laod "guilloches.js", an example I ported from regular Processing
 * but you can set it to whatever file name you want.
 *
 * This class just forwards calls from Processing into your javascript code.
 *
 * To access Processing methods in javascript, prefix them with "p5." -
 * I'm looking at how to make those functions global, if that's possible.
 *
 * author: Sean McCullough banksean@gmail.com
 */
import org.mozilla.javascript.*;

Context cx;
Scriptable scope;
Function jsInit;
Function jsDraw;
Function jsMouseDragged;
Function jsMousePressed;
Function jsMouseReleased;
Function jsKeyPressed;
Function jsKeyReleased;

void setup() {
  cx = Context.enter();   

  // TODO: make this work so it can run in an applet.  Because optimization levels 0 and higher do classLoader voodoo,
  // the browser seems to object.  Setting it to -1, however, makes JavaAdapter no workie and that makes this exercise
  // even more useless.
  // cx.setOptimizationLevel(-1);

  scope = cx.initStandardObjects();
  size(400, 400);

  // String[] globalNames = {"log"};
  // TODO: make this work so global functions don't need to be prefixed with p5.whatever.
  // scope.defineFunctionProperties(globalNames, Class.forName("jstest.JSProcessing"), ScriptableObject.DONTENUM);

  Object wrappedThis = Context.javaToJS(this, scope);

  // p5 is the global "Processing" object available to your scripts.  Prefix any processing api calls with "p5." or they
  // won't work.
  ScriptableObject.putProperty(scope, "p5", wrappedThis);

  String srcStrings[] = loadStrings("guilloches.js");
  String src = "";
  for (int i=0; i<srcStrings.length; i++) {
    src += srcStrings[i] + "\n";
  }

  cx.evaluateString(scope, src, "<cmd>", 1, null);

  jsInit = getFunction("init");
  jsDraw = getFunction("draw");
  jsMouseDragged = getFunction("mouseDragged");
  jsMousePressed = getFunction("mousePressed");
  jsMouseReleased = getFunction("mouseReleased");

  jsKeyPressed = getFunction("keyPressed");
  jsKeyReleased = getFunction("keyReleased");

  jsInit.call(cx, scope, scope, null);
}

Function getFunction(String name) {
  Object o = scope.get(name, scope);
  if (o instanceof Function) { return (Function) o; }
  return null;
}

void draw() {
  callIfNotNull(jsDraw);
}

void mouseDragged() {
  callIfNotNull(jsMouseDragged);
}

void mousePressed() {
  callIfNotNull(jsMousePressed);
}

void mouseReleased() {
  callIfNotNull(jsMouseReleased);
}

void keyPressed() {
  callIfNotNull(jsKeyPressed);
}

void keyReleased() {
  callIfNotNull(jsKeyReleased);
}

void callIfNotNull(Function f) {
  if (f != null) {
    f.call(cx, scope, scope, null);
  }
}

public void log(String str) {
  System.out.println(str);
}

//////////////////// Begin overloaded global functions //////////////////////

public void stroke(String cr) {
  stroke((Integer)colors.get(cr));
}

public void fill(String s) {
  fill((Integer)colors.get(s));
}

HashMap colors = new HashMap();

/**
 * Rhino/Javascript don't support long types.  In processing, a color object is
 * represented by a long, so we have a problem.  This method proxies color() to
 * return a JS-safe reference to a processing color value.
 * See:
 * http://www.mozilla.org/rhino/apidocs/org/mozilla/javascript/FunctionObject.html
 * Also, it's called getColor() instead of color() because processing won't let
 * me override that method name.  Probably has something to do with it being a
 * reserved word as well as a method/class.  Should probably just replace this
 * with a hex string of the color values.
 */
public String getColor(int r, int g, int b, int a) {
  color c = color(r,g,b,a);
  colors.put("" + c, c);
  return "" + c;
}

Here’s what my Guilloches sketch looks like, re-implemented in js.Processing:


function init() {
  p5.smooth();
  p5.background(255);
  // NOTE: have to use getColor() instead of color()
  // because JavaScript is retarded about Longs, which color() returns.
  var c = p5.getColor(0, 0, 0, 64);
  p5.stroke(c);
  p5.frameRate(10);
  p5.strokeWeight(0.5);
  p5.strokeCap(p5.SQUARE);
}

var lastSet = false;
var lastX = 0;
var lastY = 0;

var dTheta = 0.0004 * p5.PI;
var theta = 0.0;

var R = 50.0;
var r = -0.25;
var p = 25.0;
var m = 1;
var n = 6.00;
var Q = 0; 

function mousePressed() {
  drawStuff();
}

function mouseDragged() {
  drawStuff();
}

function draw() {
  if (!lastSet) drawStuff();
}

function drawStuff() {
  var mx = 2.0*(p5.width/2.0-p5.mouseX)/p5.width;
  var my = 2.0*(p5.height/2.0-p5.mouseY)/p5.height;

  if (lastSet)
    Q = (50*Math.sqrt(mx*mx + my*my));

  var Rr = R + r;
  var rp = r + p;

  p5.background(255);

  var mTheta = m*theta;
  var mThetaRrr = m*theta*Rr/r;
  var nTheta = n*theta;
  var x;
  var y;
  var plotX;
  var plotY;

  for (var i=0; i<15000; i++) {
  theta += dTheta;

  mTheta = m*theta;
  mThetaRrr = m*theta*Rr/r;
  nTheta = n*theta;

  x = (Rr)*Math.cos(mTheta) +
	(rp)*Math.cos(mThetaRrr) +
	Q*Math.cos(nTheta);
  y = (Rr)*Math.sin(mTheta) +
	(rp)*Math.sin(mThetaRrr) +
	Q*Math.sin(nTheta);

  plotX = (p5.width/2 + (x/125)*p5.width/2);
  plotY = (p5.height/2 + (y/125)*p5.height/2);
  if (!lastSet) {
    lastSet = true;
    lastX = plotX;
    lastY = plotY;
  }

  p5.line(lastX, lastY, plotX, plotY);
  lastX = plotX;
  lastY = plotY;
  }
}

I’m not really taking advantage of JavaScript language features much here (besides not having to worry about variable types) but it proves the concept.

I haven’t done any formal performance tests, but this seems to run slower than the original.

Download the js.Processing Project .zip Here

The JavaScript source code is in the data/ folder (since it’s loaded as an external resource).

Of course I imagine there’s an easier way to go about this, especially if one were to actually modify the Processing source code itself to accommodate JavaScript/Rhino.

Written by banksean

December 7th, 2008 at 9:23 pm

Posted in Code,General,Processing

4 Responses to 'js.Processing with Rhino'

Subscribe to comments with RSS

  1. Good stuff. I’m using this to evaluate new Processing code while the sketch is running. Nice.

    jzzz

    17 Jun 09 at 5:41 pm

  2. Here is the fix so far, enjoy:
    (it would be really nice to get rid of the “p5” prefix in js!)

    ——- clip ——-

    Server serv;
    Client cl;

    void draw() {
    callIfNotNull(jsDraw);

    cl = serv.available();
    if(cl != null) {
    jsinput = cl.readString();
    try {
    cx.evaluateString(scope, jsinput, “”, 1, null);
    }
    catch(Exception e) {
    System.out.println(“Eval exception.”);
    }
    }
    }

    ——- clip ——-

    Then just send new js code to Server with Client. I made a Tool for this to select js code in a sketch, and hit cmd-. to evaluate it, SuperCollider style.

    best,
    j

    jzzz

    17 Jun 09 at 5:45 pm

  3. Got a link to more info on this project? It sounds pretty interesting.

    to get rid of ‘p5’ you can just use the Javascript ‘with’ keyword:

    with(p5) {
    background(255);
    stroke(getColor(0, 0, 0, 64));
    line(0, 0, 100, 100);
    }

    banksean

    17 Jun 09 at 6:23 pm

  4. Still working on it, hope to write it up soon. The Evaluate-tool simply picks selected text from current sketch and sends it to localhost on a fixed port. Following the above code that text is read with Server and evaluated on a js context. Thus you can execute new processing java code as well while the sketch is running.. it does add to the sketching factor.

    Here’s the Evaluate-tool:
    http://pinktwins.com/disk/p5/Evaluate.zip

    And I’m sorry to say that it calls a function that only exists in my version of processing, editor.clearConsole();. Remove this line from run() and recompile before trying to use it.

    Processing sketch for the Java host, modified from sketch on this page:

    http://pinktwins.com/disk/p5/PSJavascript.zip

    Processing sketch with javascript to try it out:

    http://pinktwins.com/disk/p5/js_code.zip

    So, install Evaluate-tool, open the sketches, run java host, select all text on js_code, and do “Tools -> Evaluate Selection”.
    (and fix the image path from js_code first).

    Apologies for lack of polish, this is what I could manage with the time I have now. And js_code makes no sense visually, it just shows that Processing calls can be made, and drawing can be built up dynamically.

    (Evaluate goes to Processing.app/Contents/Resources/Java/tools on a mac)

    jzzz

    26 Jun 09 at 2:02 pm

Leave a Reply