ブラウザ上でRubyでProcessingする

思い立ってProcessing Advent Calendar 2015に参加させていただきました。12/20担当、kitaoです。よろしくお願いします。

かつて、「Rubyを使って気軽にクリエイティブコーディングしたい!」という人がたくさんいるに違いないと確信し、Processing.rbを作ったりしたのですが、心残りなこととして、JavaのProcessingライブラリを利用していたため、ブラウザ上で簡単に公開できないという点がありました。

あれから時代は流れ、今はp5.jsOpalといったブラウザと相性の良いライブラリが大分実用的になって来ましたので、今回はブラウザ上でRubyで作成したProcessingのスケッチを動かすことにチャレンジしてみたいと思います。

Rubyで作成したスケッチをブラウザ上で動作させる

先に結論。

  • Rubyで作ったスケッチを無事ブラウザで動かすことができました
  • でも、手を抜くためにダーティハックを多用しています
  • まだ実用には耐えられないので、真似しないほうが良いです
  • そのうちきちんとしたライブラリにまとめるので、今回はお許しください

f:id:tkitao:20151219182559p:plain

実際の手順を説明します。

使用するライブラリ

今回は、RubyJavaScriptに変換できるOpalと、JavaScriptでProcessingのスケッチが作成できるp5.jsを利用します。

この2つのライブラリで、Rubyコード⇨JavaScriptコード⇨Processingスケッチと変換していく目論見です。

ちょっと脱線ですが、以前のp5.jsは3D機能が使えなかったのでProcessing.jsの方がいいかなと思っていたのですが、3Dに対応してぐっと使う気が高まりました。IDEさえ整備されれば今後はこちらが主流になりそうな気がします。

f:id:tkitao:20151219183043p:plain

Opalとp5.jsの読み込み

まずはhtmlでOpalとp5.jsを読み込みます。今回は事前にライブラリをダウンロードするのではなく、CDNを使ってネットから直接取得します。

htmlに以下のscriptタグを追加します。

<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.20/p5.js"></script>
<script src="http://cdn.opalrb.org/opal/current/opal.min.js"></script>
<script src="http://cdn.opalrb.org/opal/current/opal-parser.min.js"></script>
<script>Opal.load('opal-parser');</script>
<script type="text/ruby" src="./main.rb"></script>

p5.jsは普通に読み込んでいますが、Opalは、RubyからJavaScriptへの変換をブラウザ上で行わせるために、opal.min.jsに加えてopal-parser.min.jsも読み込んでいます。また、text/rubyタイプのスクリプトを自動変換させるために、次の行のOpal.load('opal-parser');opal-parserを起動させています。

最後の行でスケッチ本体であるmain.rbを読み込んでいます。タイプ指定がtext/rubyになっているところがポイントです。

Opalとp5.jsを接続する

これでRubyコードがブラウザ上で動作するようになったので、早速スケッチを作成したいところなのですが、残念ながらRubyからp5.jsの命令を呼ぶためには、接続用のコードを書かなければなりません。

OpalとJavaScriptの接続方法は、公式マニュアルのCompiled Ruby Codeの章に記載されています。ざっくり要約すると、

  • `(バッククォート)や%xで囲めばJavaScriptRubyの中に混在させられる
  • RubyJavaScriptを違和感なくつなぐには、各命令をラップするクラスが必要

といった感じです。

さて、RubyコードではなくなってしまうのでJavaScript混在は嫌ですが、各命令をラップする時間も(締め切り直前のため)ないので、困ってしまいました。仕方がないので黒魔術(ダーティハック)に手を出すことにします。暗黒プログラマ万歳!

以下、main.rb冒頭の黒魔術コードです。

module P5
  def self.method_missing(name, *args)
    %x{
      obj = window[name];
      if (typeof(obj) == 'function') {
        return window[name].apply(window, args);
      } else {
        return window[name];
      }
    }
  end

  %x{
    window.setup = function() { Opal.top.$setup(); };
    window.draw = function() { Opal.top.$draw(); };
  }
end

前半のmethod_missing定義部分では、未定義の名前が使用されたら関数であればJavaScriptのグローバル関数を実行し、変数であればグローバル変数を返すようにしています。こうすることで、P5.some_methodのような呼び出しがあると、JavaScriptsome_methodが自動で呼ばれるようになります。

後半の%xの部分では、JavaScriptsetup関数とdraw関数が呼ばれたら、Rubyのトップレベルで定義されたsetupメソッドdrawメソッドを呼び出すようにしています。

Rubyでスケッチを作成する

最後に動作確認用のサンプルを作成します。

ここでは手抜きをして、以前作成したスケッチをそのまま流用しました。p5.jsの命令は冒頭にP5.が付いていることに注意してください。

LINE_RADIUS = 8
LINE_SPEED = 3

def setup
  P5.createCanvas(480, 240)
  P5.background(96)
  P5.noStroke

  @x = @y = LINE_RADIUS
  @vx = @vy = LINE_SPEED
end

def draw
  P5.fill(96, 8)
  P5.rect(0, 0, P5.width, P5.height)

  @x += @vx
  @y += @vy

  @vx *= -1 if @x <= LINE_RADIUS || @x >= P5.width - LINE_RADIUS
  @vy *= -1 if @y <= LINE_RADIUS || @y >= P5.height - LINE_RADIUS

  P5.fill(255, 204, 0)
  P5.ellipse(@x, @y, LINE_RADIUS * 2, LINE_RADIUS * 2)
end

特に変わったコードは書いていないので、Rubyになじみのない方でも、Processingを知っていれば内容は理解できるのではないかと思います。

動作確認

実際の動作画面です。

f:id:tkitao:20151219184934p:plain

また、こちらで実際に動く様子が確認できます。

無事、Rubyで作成したスケッチをブラウザ上で動かすことができました。

まとめ

今回、かろうじてRubyからp5.jsを動かすことができましたが、未対応のコールバック等、色々穴があるので、やはりきちんとしたラッパーライブラリを作成しないと実用は難しいです。

年末にでも、正式なOpal向けp5ラッパーライブラリの作成にトライしてみようと思います。