Here's something I've been hacking on for the past two months, an OpenGL
scenegraph engine written mostly in Ruby with some critical sections
inlined in C. It's been a educational experience :)

Librend is a simple scenegraph engine on top of OpenGL, SDL, Imlib2,
GLEW and Cairo that aims to make it quick and easy to write apps that
mix 2D, 3D and vector graphics.

I'm dubious about its suitability for public consumption at this stage
but I'll release it anyhow :I

Comments and questions welcome!


Homepage: http://librend.rubyforge.org/
Screenshots: http://librend.rubyforge.org/screenshots/
API docs: http://librend.rubyforge.org/rdoc/
Downloads: http://rubyforge.org/frs/?group_id=716


>From the Rend module docs:

Librend implements a scenegraph 3D engine. The graph is a tree, and is
read from the root upwards.

You create Rend::Models and attach children to them. The models are then
drawn from root upwards, with the children using the properties of their
parents as the default.

Transforms cascade, so an object 4 levels down the tree has its base
position and rotation determined by applying all the positions and
rotations of its parents.

The scene root is given to the renderer, which draws the scenegraph to
the screen. There are no (well, shouldn be) rendering API specific
parts in the scene graph, so it quite API-independent. (Note: This is
not entirely true, there are some places which use API-specific
functions, e.g. the classes Sound and Shader and the current
unabstracted event model.)


Here an irb session showcasing setting up a scene and editing it on
the fly.

   require 'rend'

   # First, create the scene and some models.
   scene = Rend::Model.new
   ball = Rend::Sphere.new
   cube = Rend::Cube.new

   # Add the ball and the cube to the scene.
   scene.attach ball, cube

   # Set up the renderer and start running.
   renderer = Rend::Renderer.new
   renderer.scene = scene

   # Let's move the ball up...
   ball.position = [0, 2, 0]

   # ...and tweak the camera a bit.
   renderer.camera.fov = 60
   renderer.camera.looking_at[1] = 1
   renderer.camera.position[2] += 2

   # Wouldn't it be nice if the cube rotated?
   # Let's add a time handler to do just that.
   cube.add_time_handler{|o,t| o.rotation = [(t*100)%360, 1, 0, 1]}

   # Some light would be nice aswell.
   scene.lights = [Rend::Light.new(:position => [2, 5, 3])]

   # The camera should orbit our scene, right?
   renderer.camera.add_time_handler{|o,t|
     o.position = [4.0*Math.sin(t/2), 2.0, 4.0*Math.cos(t/2)]
   }

   # The cube rotation makes the orbiting look all weird,
   # let's take it out.
   cube.time_handlers.clear

   # The ball looks a bit crude, let's add some detail.
   ball.geometry.radial_detail = 150
   ball.geometry.axial_detail = 150

   # And make it shiny red!
   ball.material = Rend::Material.new
   ball.material.diffuse = [1, 0, 0, 1] # red diffuse
   ball.material.specular = [1, 1, 1, 1] # white specular
   ball.material.shininess = 150

   renderer.thread.kill # Oops, there goes our eventloop
   renderer.run # but we can restart it
   renderer.thread.join # and wait until someone exits the program