Banner

Jruby Applet: OpenGL running from Ruby in an Applet Tue Apr 15

 

Hooray for combining technologies, this is a shining example of such work. So basically I’ve managed to put JRuby in an applet and make the ruby actually run the OpenGL code directly. I didn’t want to bomb every visitor to this page with a java applet though, so I’ve made it a bit easier to deal with. If you want to see the applet in action, click here. I’ve put this up on google code, so you can download the source from them. Anyway – basically what I’m doing is making an applet, evaluating the ruby code specified by URL in the ‘evalruby’ parameter, instantiating a new Ruby class (in java) and using it. Specifically, the ruby file highlighted in evalruby must define the class ‘RubyGLListener’, which implements the interface javax.media.opengl.GLEventListener. Then you just ‘plug’ the GLEventListener into the applet’s canvas and voila! OGL JRuby Applet Launcher, at your service!

So here we basically have the hardest bit:

package com.sikanrong;
import org.jruby.Ruby;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.GlobalVariable;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.RubyInstanceConfig;
import java.io.*;
import java.net.URL;

import java.util.*;
import javax.swing.JApplet;
import java.lang.ClassLoader;
import javax.media.opengl.GLEventListener;

public class RubyGLWrapper{

    public GLEventListener getGLListener(){
        return (GLEventListener) new Object();
    }

    public static RubyGLWrapper getInterfaceFromScript(String contents){
       final Ruby runtime = Ruby.getDefaultInstance();

       runtime.evalScriptlet(contents);

       RubyGLWrapper ruby = null;
       final IRubyObject rawRuby = 
                       runtime.evalScriptlet("RubyGLListener.new");    

       ruby = (RubyGLWrapper) JavaEmbedUtils.rubyToJava(runtime, 
                          rawRuby, 
                          RubyGLWrapper.class);
           return ruby;
    }

}

This is the RubyGLWrapper class – which is responsible for returning the Ruby class but wrapped so we can use it freely in java. Note the ruby class extends this class, and the only method we can call from java is getGLListener. If I wanted to call more Ruby methods from the java I would have to define the method both in the ruby and then a method to overwrite in the Java class that it inherits from. Moving on, this is the Ruby class:

#!/usr/local/jruby/bin/jruby
require 'java'

include_class "com.sikanrong.RubyGLWrapper" 

include_class "javax.swing.JFrame" 
include_class "java.awt.BorderLayout" 
include_class "com.sun.opengl.util.Animator" 
include_class "javax.media.opengl.GL" 
include_class "javax.media.opengl.GLCanvas" 
include_class "javax.media.opengl.GLCapabilities" 
include_class "javax.media.opengl.GLAutoDrawable" 
include_class "javax.media.opengl.GLDrawableFactory" 
include_class "javax.media.opengl.GLEventListener" 
include_class "com.sun.opengl.util.GLUT" 

class RubyGLListener < RubyGLWrapper
  include GLEventListener
  def getGLListener
    self
  end

  def init(drawable)
     gl = drawable.getGL
    glut = GLUT.new

    gl.glShadeModel(GL::GL_SMOOTH);

    gl.glEnable(GL::GL_LIGHTING)
    gl.glEnable(GL::GL_LIGHT0)

    gl.glEnable(GL::GL_CULL_FACE)
  end

  def reshape(drawable, x, y, width, height)

  end

  def displayChanged(drawable, modeChanged, deviceChanged)

  end
  def display(drawable)
      gl = drawable.getGL();
      glut = GLUT.new
      gl.glRotated(0.15, 0, 1, 0);  
      gl.glRotated(0.15, 1, 1, 0);  
      gl.glClear(GL::GL_COLOR_BUFFER_BIT);
      gl.glColor4d(1,0,0,0);

      mat_specular = glFloatArray [1.0, 1.0, 1.0, 1.0]
      mat_shininess = glFloatArray [50.0]
      light_position = glFloatArray [1.0, 1.0, 1.0, 0.0]

      gl.glMaterialfv(GL::GL_FRONT, GL::GL_SPECULAR, mat_specular);  
      gl.glMaterialfv(GL::GL_FRONT, GL::GL_SHININESS, mat_shininess);
      gl.glLightfv(GL::GL_LIGHT0, GL::GL_POSITION, light_position);

      glut.glutSolidSphere 0.7, 20, 20
      gl.glFlush();
  end

  def glFloatArray(array)
    ret = java.nio.FloatBuffer.allocate(array.length)
    array.each do |n|
      ret.put(n)
    end
    ret
  end

end

..So elegant in it’s simplicity… Note that the class inherits from the RubyGLWrapper and extends the GLEventListener interface. Pretty cool right? So as you can see all the OGL instructions in the applet are nicely contained right here in ruby! The possibilities with this stuff is endless, being able to manipulate OpenGL in ruby is going to lead to some awesome projects! Namely if those projects end up in an applet.. Speaking of which, this is the applet code:

package com.sikanrong;
// External imports
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import com.sun.opengl.util.Animator;
import javax.media.opengl.GL;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLEventListener;

public class RubyGLApplet extends Applet
{
    GLCapabilities caps;
    GLCanvas canvas;
    StringBuffer buf; 

    public RubyGLApplet()
    {

        setLayout(new BorderLayout());

        setSize(400, 400);

        setVisible(true);
        caps = new GLCapabilities();
        caps.setDoubleBuffered(true);
        caps.setHardwareAccelerated(true);
        canvas = new GLCanvas(caps);

        add(canvas, BorderLayout.CENTER);

        Animator anim = new Animator(canvas);
        anim.start();
    }
    public void init(){

                //ignore the weird spacing here
                //it's just to fit it into this 
                //page's formatting
        RubyGLWrapper ruby = RubyGLWrapper.getInterfaceFromScript(
                     readFileFromURL(
                        getParameter("evalruby")));

        GLEventListener listen = ruby.getGLListener();

        canvas.addGLEventListener(listen);

    }

    public String readFileFromURL(String strUrl){
        String line;
        URL url = null;
        try{
          url = new URL(strUrl);
        }
        catch(MalformedURLException e){}
        StringBuffer strBuff = null;
        try{
          InputStream in = url.openStream();
          BufferedReader bf = new BufferedReader(new InputStreamReader(in));
          strBuff = new StringBuffer();
          while((line = bf.readLine()) != null){
            strBuff.append(line + "\n");
          }

        }
        catch(IOException e){
          e.printStackTrace();
        }
        return new String(strBuff);
    }

}

Which is pretty short, note in the init method we grab the ruby eval url and instantiate the RubyGLWrapper instance, and from that we get the interface to pull our newly instantiated RubyGLListener out and use it for the applet canvas (i.e. we just ‘plug’ it in!)

So, pitfalls and other points of interest:

  1. The jruby jar is HUGE and it makes the applet take forever to load. I’ve been nagging people on the JRuby forum to make a slimmed down build option for the JRuby jar to alleviate some of this, and I think soon they will
  2. I’m using the JNLP applet launcher to run this, which is awesome because it automatically downloads the native OGL stuff when needed for any OS/Architecture. That’s freakin pimp, because it means that once you’ve written the Ruby OGL code, it’s portable to any computer with a browser. Awesome!!
  3. I’d really like to use Pack200 too solve the size issue, and i just haven’t been able to get it to work, anybody that could point me in a good direction should

Where is this going?

Well the right answer is really “Anywhere”, but I’d really (really) like to make a 3d game-engine in JRuby that has applet portability capabilities. I’d like to do it in JRuby because it would provide freedom and a concise & clean way to write game code on a level that the world of game-programmers have never before seen. Think “Ruby-on-Game-rails”. I’m going to be incorporating the JBullet physics library – and seeing what cool syntatical stuff I can get ruby to do with significance to gaming (things like “x = Model.new(’/path/to/model.3ds’, ‘path/to/texture.jpg’, :physics=>true)”). Unfortunately that’s all going to take forever because I’m really doing all this stuff on the weekend. Cool beans though, right??!

“So what does the applet actually do so far?”

If you want to marvel in the fact that it works, sure, go ahead and give it a click. All it does though is show a spinning sphere with a light pointed at it rotating though, and after about 15 seconds it’s pretty boring. The cool part is it’s really acting as an applet Launcher for any ruby based OGL stuff, so if you just switch out the ‘evalruby’ file for another file, it’ll use that OGL code instead. So, although my ruby only spins a sphere, you can download the applet jar and start writing your own Ruby OGL code and using the very same applet to run it, without actually even having to re-compile any java code. THAT’S slick.

“Your applet is broken”

NO ITS NOT – it just takes forever-and-a-day to load, so sit back and forget about it, and one of these times when you check back it’ll actually have loaded and be working. You have to hit ‘Trust’ when the little dialogs come up asking if you want to give my applet permissions and stuff.


Comments:

Patrick Wright (Posted at: 05:01 19/04/08 From IP: 77.12.14.146) (Flag this Comment)

Well, while I'm waiting for the applet to load :), I'll pass this along--until Java 6 Update N comes out, you need to configure Apache or whatever your webserver is to be able to recognize requests for Pack200 files. Do a search for "Apache JNLP pack200" and you'll get some hits--it's a little bit of a hassle, but it works. For example: http://joust.kano.net/weblog/archive/2004/10/16/pack200-on-apache-web-server/, http://sixlegs.com/blog/java/pack200-apache.html, etc. Good luck with your experiments. If you want to see some cool Swing/JOGL mixing, check out Chris Campbell's blog, like http://weblogs.java.net/blog/campbell/archive/2006/10/easy_2d3d_mixin.html or http://weblogs.java.net/blog/campbell/archive/2007/01/java_2d_and_jog.html. The code is available for download. It's pretty verbose when written in Java and is something you could improve with some well-thought out J/Ruby wrapper API. Cheers Patrick

sikanrong (Posted at: 05:40 28/04/08 From IP: 84.77.64.181) http://sikanrong.com (Flag this Comment)

Nice!! thanks so much man!

Leave a Comment:

Name:
Email (not displayed):
Website (optional):
Captcha:
simple_captcha.jpg
(type the code from the image)
Comment:
Back To Entry List
Rss