The original screencast can be seen here http://www.prestonlee.com/2010/05/17/3d-osx-applications-with-ruby-processing-screencast/
If you are up and running with GitHub you may wish to download the code directly from Preston Lees GitHub (and modify the starfield.rb as below) otherwise follow instructions below:-
1. Install ruby processing (follow the instructions at http://wiki.github.com/jashkenas/ruby-processing/getting-started)
2. You should create a directory starfield, and create a sub directory data
3. Download Univers66.vlw.gz from http://github.com/preston/Starfield and put it in the data folder
4. Create starfield.rb, star.rb and asychronous_object.rb in the starfield directory here is the code:-
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# asynchronous_object.rb | |
# | |
# Copyright © 2009 Preston Lee. All rights reserved. | |
require 'thread' | |
class AsychronousObject | |
attr_reader :thread | |
attr_reader :update_frequency | |
attr_reader :last_updated | |
def initialize(freq) | |
@update_freqency = freq | |
# puts @update_freqency | |
@mutex = Mutex.new | |
start | |
end | |
def start | |
@last_updated = Time.now | |
@state = :active # or :inactive | |
# puts @mutex | |
@thread = Thread.new do | |
keep_going = true | |
while keep_going do | |
@mutex.synchronize do | |
# puts "going" | |
keep_going = false if @state == :inactive | |
end | |
if keep_going | |
now = Time.now | |
# puts "TL '#{@last_updated}' N #{now} F #{@update_freqency}" | |
update(@last_updated, now) | |
@last_updated = now | |
sleep @update_freqency | |
end | |
end | |
end | |
end | |
def update | |
puts "FAIL!" | |
raise "You need to implement this method!" | |
end | |
def activate | |
@mutex.synchronize do | |
case @state | |
when :active | |
# do nothing | |
when :inactive | |
start | |
end | |
end | |
end | |
def deactivate | |
@mutex.synchronize do | |
@state = :inactive | |
end | |
end | |
def join | |
@thread.join | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# star.rb | |
# | |
# Copyright © 2009 Preston Lee. All rights reserved. | |
require 'asynchronous_object' | |
class Star < AsychronousObject | |
attr_reader :x, :y, :z, :dxs, :dys, :dzs,:clipping_plane | |
DEFAULT_UPDATE_FREQUENCY = 0.20 # In seconds. | |
DEFAULT_CLIPPING_PLANE = 1800 # The universe is a giant cube, with each side being this long. | |
def initialize(freq = DEFAULT_UPDATE_FREQUENCY, clip = DEFAULT_CLIPPING_PLANE) | |
@clipping_plane = clip | |
@dxs = rand(20) - 10 # X-axis movement per wall second. | |
@dys = rand(20) - 10 # Y-axis movement per wall second. | |
@dzs = rand(200) * 2 # Z-axis movement per wall second. | |
set_random_position | |
super(freq) # We have to do this last since a +Thread+ will be created that needs our instance variables instantiated. | |
end | |
def set_random_position | |
@x = rand(clipping_plane * 2) - clipping_plane | |
@y = rand(clipping_plane * 2) - clipping_plane | |
@z = rand(clipping_plane * 2) - clipping_plane | |
end | |
def set_new_position | |
set_random_position | |
@z = -1 * clipping_plane | |
end | |
def update(last, now) | |
# Check if the star is getting too far away from the universe, and move it back to a reasonable starting point if so. | |
# puts "CLIP NIL" if @clipping_plane.nil? | |
set_new_position if x >= clipping_plane | |
set_new_position if y >= clipping_plane | |
set_new_position if z >= clipping_plane | |
# puts "Updating star position. '#{freq}'" | |
# Figure out the translation required along each axis for this time period. | |
dtime = now - last | |
dx = dxs * dtime | |
dy = dys * dtime | |
dz = dzs * dtime | |
# Move the star. | |
@x += dx | |
@y += dy | |
@z += dz | |
#puts "Moving to #{@x}, #{@y}, #{@z}" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright © 2009 Preston Lee. All rights reserved. | |
require 'star' | |
require 'asynchronous_object' | |
class StarfieldStar < Star | |
include Processing::Proxy | |
def draw | |
push_matrix | |
translate(@x, @y, @z) | |
box(8) | |
pop_matrix | |
end | |
end | |
class Starfield < Processing::App | |
load_library :opengl | |
NUM_STARS = 200; | |
CAMERA_SPEED = 20 # Pixels per wall second. | |
CAMERA_ROTATE_SPEED = 0.08 # Radians per wall second. | |
FRAME_RATE = 30 # Target frame per second. | |
attr_accessor :stars, :in_setup | |
full_screen | |
def setup | |
if in_setup == nil # guard against repeatedly setting up opengl? | |
library_loaded?(:opengl) ? setup_opengl : render_mode(P3D) | |
in_setup = true | |
end | |
frame_rate FRAME_RATE | |
@moved = false | |
@x = 0 | |
@y = 0 | |
@z = 0 | |
@rz = 0 | |
@mouse_last_x = nil | |
@mouse_last_y = nil | |
@active = true | |
@active_mutex = Mutex.new | |
@stars = [] | |
NUM_STARS.times do | |
@stars << StarfieldStar.new(1.0 / FRAME_RATE) | |
end | |
text_font load_font("Univers66.vlw.gz"), 10 | |
end | |
def draw | |
background 0 # Paint the entire screen solid black. | |
fill(255) | |
color(100,255,255) | |
sphere(100) | |
# text("Moving starfield demo aoenu hreouh rcohurcoeuh arochuoaentuhoe u.", 0, 0, 0) | |
if !@moved | |
push_matrix | |
scale(5.0, 5.0, 5.0) | |
# translate(0, 100, 0) | |
text("Multi-threaded starfield simulation.", 0, 80, 0) | |
text("Move: A, S, D, W, R, F, Arrow Keys", 0, 100, 0) | |
text("Roll: Z, C", 0, 110, 0) | |
text("Look: click and drag mouse.", 0, 120, 0) | |
text("Pause/Resume: TAB", 0, 130, 0) | |
text("PrestonLee.com", 0, 150, 0) | |
pop_matrix | |
else | |
# puts "MOVED" | |
end | |
@stars.each do |s| | |
push_matrix | |
s.draw | |
pop_matrix | |
end | |
move_camera_for_frame | |
# move_for_frame() | |
# scale(5.0, 5.0, 5.0) | |
# text('aoeuaoeuao euaoeuaoeu') | |
# translate(@x, @y, @z) | |
# rotate_z(@rz) | |
end | |
def setup_opengl | |
render_mode OPENGL | |
hint DISABLE_OPENGL_ERROR_REPORT | |
hint ENABLE_OPENGL_4X_SMOOTH | |
end | |
def mouse_released | |
@mouse_last_x = nil | |
@mouse_last_y = nil | |
end | |
def mouse_dragged | |
@moved = true | |
@mouse_last_x = mouse_x if @mouse_last_x.nil? | |
@mouse_last_y = mouse_y if @mouse_last_y.nil? | |
dx = @mouse_last_x - mouse_x | |
dy = @mouse_last_y - mouse_y | |
begin_camera | |
if dx != 0 | |
# puts "#{mouse_x} #{mouse_y}" | |
rotate_y radians(-dx) * 0.1 | |
end | |
if dy != 0 | |
rotate_x radians(dy) * 0.1 | |
end | |
end_camera | |
@mouse_last_x = mouse_x | |
@mouse_last_y = mouse_y | |
end | |
def key_pressed | |
@moved = true | |
# puts "KEY_PRESSED: #{key_code}" | |
handle_camera_change_start | |
handle_pause_and_resume | |
end | |
def handle_pause_and_resume | |
case key_code | |
when TAB: | |
@active_mutex.synchronize do | |
@stars.each do |s| | |
@active ? s.deactivate : s.activate | |
end | |
@active = !@active | |
end | |
end | |
end | |
def key_released | |
# puts "KEY_RELEASED: #{key_code}" | |
handle_camera_change_stop | |
end | |
def handle_camera_change_start | |
begin_camera | |
case key_code | |
when UP: | |
@camera_move_z = -1 | |
when DOWN, 's', 'o': | |
@camera_move_z = 1 | |
when LEFT: | |
@camera_move_x = -1 | |
when RIGHT: | |
@camera_move_x = 1 | |
end | |
case key | |
when 'w', ',': | |
@camera_move_z = -1 | |
when 's', 'o': | |
@camera_move_z = 1 | |
when 'a': | |
@camera_move_x = -1 | |
when 'd', 'e': | |
@camera_move_x = 1 | |
when 'r', 'p': | |
@camera_move_y = -1 | |
when 'f', 'u': | |
@camera_move_y = 1 | |
when 'z', ';': | |
@camera_rotate_z = -1 | |
when 'c', 'j': | |
@camera_rotate_z = 1 | |
end | |
end_camera | |
end | |
def handle_camera_change_stop | |
begin_camera | |
case key_code | |
when UP, DOWN, 'w', ',', 's', 'o': | |
@camera_move_z = 0 | |
when LEFT, RIGHT, 'a', 'd', 'e': | |
@camera_move_x = 0 | |
end | |
case key | |
when 'w', ',', 's', 'o': | |
@camera_move_z = 0 | |
when 'a', 'd', 'e': | |
@camera_move_x = 0 | |
when 'r', 'p', 'f', 'u': | |
@camera_move_y = 0 | |
when 'z', ';', 'c', 'j': | |
@camera_rotate_z = 0 | |
end | |
end_camera | |
end | |
def move_camera_for_frame | |
begin_camera | |
@dx = (@camera_move_x || 0) * CAMERA_SPEED | |
@dy = (@camera_move_y || 0) * CAMERA_SPEED | |
@dz = (@camera_move_z || 0) * CAMERA_SPEED | |
@drz = (@camera_rotate_z || 0) * CAMERA_ROTATE_SPEED | |
@x += @dx | |
@y += @dy | |
@z += @dz | |
@rz += @drz | |
translate(@dx, 0, 0) if !@camera_move_x.nil? && @camera_move_x != 0 | |
translate(0, @dy, 0) if !@camera_move_y.nil? && @camera_move_y != 0 | |
translate(0, 0, @dz) if !@camera_move_z.nil? && @camera_move_z != 0 | |
rotate_z(@drz) if !@camera_rotate_z.nil? && @camera_rotate_z != 0 | |
end_camera | |
end | |
end | |
5. In a terminal cd to the starfield directory and enter rp5 run starfield.rb
If you want export the app you will need to modify starfield.rb to require asychronous_object.rb.
Because there is currently an issue with resizing opengl applications with linux you will need to run the app in full screen
mode, use the escape key to quit full screen mode. If it hangs alt tab to another console pgrep java to get the offending process id
the issue kill -s KILL pid where pid is the process id.
Possible issues are:-
Your OS is hogging the graphics driver (solution turn down the level of eye candy)
You don't have a working 3D graphics driver (solution install a 3D graphics driver)
You are running a 64 bit operating system see my last post
Here is a screen-shot of the app running on Kubuntu, with the desktop eye candy turned down.
No comments:
Post a Comment