LENGTH = 10_000 SPEED = 10 load_library 'vecmath' attr_accessor :particles, :a, :b, :c, :d def setup size 512, 512, P3D ArcBall.init(self) frame_rate 30 stroke color(0, 0, 0, 33) stroke_weight 2 @a = 10 @b = 28 @c = 8 / 3 @d = 0.75 init_particles end def draw background(255) advance_particles(SPEED) draw_particles end def init_particles @particles = [] LENGTH.times do add_particle(Vec3D.new 1.5, -1.5, 1.5) # need to start somewhere end end def add_particle(p) step = Vec3D.new(a * (p.y - p.x), p.x * (b - p.z) - p.y, p.x * p.y - c * p.z) step *= (d / step.mag) particles << p + step end def advance_particles(count) (1..count).each do particles.shift add_particle(particles.last) end end def draw_particles scale(8) particles.each_cons(2) do |a, b| stroke(color((b.x - a.x) * 255, (b.y - a.y) * 255, (b.z - a.z) * 255, 88)) line(a.x, a.y, a.z - 30, b.x, b.y, b.z - 30) end end
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Tuesday, 23 December 2014
Lorenz Attractor Ruby-Processing
Here is a processing sketch that used a LinkedList (not needed in ruby version, an array is sufficient)
Labels:
arcball,
LinkedList,
lorenz attractor,
Vec3D
Thursday, 18 December 2014
Reading and writing to a csv file ruby-processing (version two)
I find it really surprising that one of most popular posts for ruby-processing is the original version here is an alternative slightly cleaner version:-
# # Loading Tabular Data # after Daniel Shiffman, by Martin Prout. # # This example demonstrates how to use CSV # to retrieve data from a CSV file and make objects # from that data. # # Here is what the CSV looks like: # # x,y,diameter,name # 160,103,43.19838,Happy # 372,137,52.42526,Sad # 273,235,61.14072,Joyous # 121,179,44.758068,Melancholy # require 'csv' load_library 'bubble' attr_reader :bubbles, :data def setup size(640, 360) load_data end def draw background(255) # Display all bubbles bubbles.run text_align(LEFT) fill(0) text('Click to add bubbles.', 10, height - 10) end def load_data # Load CSV file into an Array of Hash objects # headers: option indicates the file has a header row @bubbles = BubbleData.new CSV.foreach('data/data.csv', headers: true) do |row| x = row['x'].to_f y = row['y'].to_f d = row['diameter'].to_f n = row['name'] # Make a Bubble object out of the data read bubbles << Bubble.new(x, y, d, n) end end def mouse_pressed bubbles << Bubble.new(mouse_x, mouse_y, rand(40..80), 'blah') # If there are more than 10 bubbles delete the oldest bubble bubbles.shift if bubbles.size > 10 # Writing the csv data back to the same file, (also specify UTF-8 format) CSV.open('data/data.csv', 'w:UTF-8') do |csv| csv << %w(x y diameter name) # create csv headers bubbles.each do |bubble| csv << bubble.to_a # write back bubble data end end # And reloading it load_data end # A run module module Runnable def run each(&:display) each { |item| item.rollover(mouse_x, mouse_y) } end end # Enumerable class holds bubble data class BubbleData extend Forwardable def_delegators(:@bubbles, :each, :<<, :size, :shift) include Enumerable, Runnable def initialize @bubbles = [] end end
Labels:
csv,
ruby-processing,
saveTable,
UTF-8
Tuesday, 16 December 2014
Experimenting with jruby-complete-SNAPSHOT-9.0.0.0 and ruby processing
Anyone wishing to explore ruby-2.1+ syntax with ruby-processing should get hold of jruby-complete-SNAPSHOT-9.0.0.0.jar (compile it yourself) rename the jar to jruby-complete.jar and manually replace the jruby-complete.jar in the ruby-processing gem (lives in lib/ruby folder). Gems typically live in .gem/ruby/2.1.0/gems folder depending on your system unless you use rvm or other such nasty stuff. To ensure jruby-complete use add 'JRUBY: false' to your ~/.rp5rc settings (or use the --nojuby flag). Also use watch rather than run to run sketches (to avoid the failure in draw loop bug). Here is a sketch I posted earlier using 2.1 syntax, but here I explore using 'format' to present rationals....
After experimentation you can easily restore the original jruby-complete by running 'rp5 setup install' (no download required since we keep copy of the original in the vendors folder).
PS: this one of the sketches not affected by draw loop bug. May'be we don't need to wait too long for a preview version?
def setup size 640, 250 background 10 f = createFont("Arial", 24, true) third = 1 / 3r # since ruby 2.1.0 quarter = 1 / 4r add = format("%s + %s = %s", third, quarter, third + quarter) subtract = format("%s - %s = %s", third, quarter, third - quarter) multiply = format("%s * %s = %s", third, quarter, third * quarter) text_font(f, 24) fill(220) text("Math blackboard ruby-processing", 80, 50) text(add, 110, 100) text(subtract, 110, 150) text(multiply, 110, 200) end
PS: this one of the sketches not affected by draw loop bug. May'be we don't need to wait too long for a preview version?
@aselder Rough timeframe for a final is hopefully end of January. Will attempt a preview after MRI 2.2 is out.
— Charles Nutter (@headius) December 16, 2014
Monday, 24 November 2014
Radical Proxy experiment for JRubyArt
Here I create a custom (abstract) Proxy class as a java extension for JRubyArt, which when appropriately initialized, gives implementing classes access to processing pre(), draw() and post() loops. With example usage (by a custom Array class), note the empty draw loop.... It probably doesn't matter whether the particle gets updated in pre(), draw() or post() loops. See original vanilla processing code in action here as js version. Note how each module/particle has it's own copy of the update and draw code in those versions. Whereas in JRubyArt a single object, the custom array has this code, and the particle is just a simple Struct.
JRubyArt is a development branch of ruby-processing, that is currently going its own way (ie somewhat different implementation)
package processing.core; /** * * @author Martin Prout */ public abstract class Proxy { private final PApplet app; /** * Useful accessors */ public int width, height; /** * * @param app Applet */ public Proxy(PApplet app) { this.app = app; this.width = app.width; this.height = app.height; setActive(true); } /** * Extending classes must implement */ public abstract void pre(); /** * Extending classes must implement */ public abstract void draw(); /** * Extending classes must implement */ public abstract void post(); private void setActive(boolean active) { if (active) { this.app.registerMethod("pre", this); this.app.registerMethod("draw", this); this.app.registerMethod("post", this); this.app.registerMethod("dispose", this); } else { this.app.unregisterMethod("pre", this); this.app.unregisterMethod("draw", this); this.app.unregisterMethod("post", this); } } /** * Simple signature * @param col */ public void background(int col) { this.app.background(col); } /** * Simple signature * @param col */ public void fill(int col) { this.app.fill(col); } /** * Simple signature * @param col */ public void stroke(int col) { this.app.stroke(col); } /** * Access applet if we must * @return */ public PApplet app() { return this.app; } /** * required for processing */ public void dispose() { setActive(false); } }
# Demonstrates possible syntax for creating a custom array of objects. UNIT = 40 attr_reader :custom_array def setup size 640, 360 wide_count = width / UNIT height_count = height / UNIT @custom_array = CustomArray.new(self) height_count.times do |i| wide_count.times do |j| custom_array.add_object(j * UNIT, i * UNIT, UNIT / 2, UNIT / 2, rand(0.05..0.8)) end end no_stroke end def draw end # The Particle object Particle = Struct.new(:x, :y, :mx, :my, :size, :speed, :xdir, :ydir) require 'forwardable' # The custom Array (that can access pre, post and draw loops by reflection) # Also Proxy has access to background(int), fill(int) and stroke(int) class CustomArray < Java::ProcessingCore::Proxy extend Forwardable def_delegators(:@objs, :each, :<<) include Enumerable attr_reader :app def initialize(app) @app = app @objs = [] end def add_object(mx, my, x, y, speed) self << Particle.new(x.to_i, y.to_i, mx, my, UNIT, speed, 1, 1) end def post each do |obj| update_x obj next unless obj.y >= UNIT || obj.x <= 0 obj.ydir *= -1 obj.y += obj.ydir end end def update_x(obj) obj.x += obj.speed * obj.xdir return if (0..UNIT).cover? obj.x obj.xdir *= -1 obj.x += obj.xdir obj.y += obj.ydir end def pre end def draw background(0) fill(255) each do |obj| app.ellipse(obj.mx + obj.x, obj.my + obj.y, 6, 6) end end end
JRubyArt is a development branch of ruby-processing, that is currently going its own way (ie somewhat different implementation)
Labels:
draw,
jruby-art,
post,
pre,
processing,
proxy,
reflection
Thursday, 13 November 2014
Custom Contact Listener for pbox2d and ruby-processing
require 'pbox2d' require_relative 'lib/custom_listener' require_relative 'lib/particle' require_relative 'lib/boundary' attr_reader :box2d, :particles, :wall def setup size 400, 400 @box2d = Box2D.new(self) box2d.create_world box2d.add_listener(CustomListener.new) @particles = [] @wall = Boundary.new(box2d, width / 2, height - 5, width, 10) end def draw background(255) if (rand < 0.1) particles << Particle.new(box2d, rand(width), 20, rand(4..8)) end particles.each{ |p| p.display(self) } particles.reject!(&:done) wall.display(self) end
Custom Listener
class CustomListener include ContactListener def begin_contact(cp) # Get both fixtures f1 = cp.getFixtureA f2 = cp.getFixtureB # Get both bodies b1 = f1.getBody b2 = f2.getBody # Get our objects that reference these bodies o1 = b1.getUserData o2 = b2.getUserData return unless (o1.respond_to?(:change) && o2.respond_to?(:change)) o1.change o2.change end def end_contact(cp) end def pre_solve(cp, m) end def post_solve(cp, ci) end end
Boundary class
CENTER ||= Java::ProcessingCore::PConstants::CENTER class Boundary include PB attr_reader :box2d, :x, :y, :w, :h, :b def initialize(b2d, x, y, w, h) @box2d, @x, @y, @w, @h = b2d, x, y, w, h sd = PolygonShape.new box2dW = box2d.scale_to_world(w / 2) box2dH = box2d.scale_to_world(h / 2) # We're just a box sd.setAsBox(box2dW, box2dH); # Create the body bd = BodyDef.new bd.type = BodyType::STATIC; bd.position.set(box2d.processing_to_world(x,y)) @b = box2d.create_body(bd) # Attached the shape to the body using a Fixture b.create_fixture(sd, 1) b.setUserData(self) end # Draw the boundary, if it were at an angle we'd have to do something fancier def display(app) app.fill(0) app.stroke(0) app.rectMode(CENTER) app.rect(x,y,w,h) end endParticle class
class Particle include PB attr_accessor :body attr_reader :box2d, :radius, :col def initialize(b2d, x, y, r) @box2d, @x, @y, @radius = b2d, x, y, r # This function puts the particle in the Box2d world make_body(x, y, radius) @col = -5263441 body.setUserData(self) end # This function removes the particle from the box2d world def kill_body box2d.destroy_body(body) end # Change color when hit def change @col = -65536 # red end # Is the particle ready for deletion? def done # Let's find the screen position of the particle pos = box2d.body_coord(body) # Is it off the bottom of the screen? return false unless (pos.y > box2d.height + radius * 2) kill_body true end def display(app) # We look at each body and get its screen position pos = box2d.body_coord(body) # Get its angle of rotation a = body.get_angle app.push_matrix app.translate(pos.x, pos.y) app.rotate(a) app.fill(col) app.stroke(0) app.stroke_weight(1) app.ellipse(0, 0, radius * 2, radius * 2) # Let's add a line so we can see the rotation app.line(0, 0, radius, 0) app.pop_matrix end # Here's our function that adds the particle to the Box2D world def make_body(x, y, r) # Define a body bd = BodyDef.new # Set its position bd.position = box2d.processing_to_world(x, y) bd.type = BodyType::DYNAMIC @body = box2d.create_body(bd) # Make the body's shape a circle cs = CircleShape.new cs.m_radius = box2d.scale_to_world(r) fd = FixtureDef.new fd.shape = cs # Parameters that affect physics fd.density = 1 fd.friction = 0.01 fd.restitution = 0.3 # Attach fixture to body body.create_fixture(fd) body.set_angular_velocity(rand(-10.0..10)) end end
Labels:
contact listener,
pbox2d
Sunday, 2 November 2014
Using the pbox2d gem in JRubyArt
Things are little bit more complicated for JRubyArt (compared with ruby-processing) as I am currently refusing to use the Processing::Proxy sugar (that allows you in part to mimic the inner classes of vanilla processing) in JRubyArt as it is kind of bad practice to expose variables and methods willy nilly.
require 'jruby_art' require 'pbox2d' require_relative 'lib/custom_shape' class Polygons < Processing::App # Basic example of falling rectangles attr_reader :box2d, :boundaries, :polygons def setup size(640, 360) # Initialize box2d physics and create the world @box2d = Box2D.new(self) box2d.create_world # We are setting a custom gravity box2d.gravity(0, -20) # Create Arrays @polygons = [] @boundaries = [] # Add a bunch of fixed boundaries boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10, 0) boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10, 0) boundaries << Boundary.new(box2d, width - 5, height / 2, 10, height, 0) boundaries << Boundary.new(box2d, 5, height / 2, 10, height, 0) end def draw background(255) # Display all the boundaries boundaries.each { |wall| wall.display(self) } # Display all the polygons polygons.each { |poly| poly.display(self) } # polygons that leave the screen, we delete them # (note they have to be deleted from both the box2d world and our list polygons.reject!(&:done) end def mouse_pressed polygons << CustomShape.new(box2d, mouse_x, mouse_y, height) end end Polygons.new(title: 'Polygons')
CLOSE = Java::ProcessingCore::PConstants::CLOSE # these are just ints CENTER = Java::ProcessingCore::PConstants::CENTER class CustomShape include PB # We need to keep track of a Body and a width and height attr_reader :body, :box2d, :height # Constructor def initialize(b2d, x, y, height) # Add the box to the box2d world @box2d, @height = b2d, height make_body(PB::Vec2.new(x, y)) end # This function removes the particle from the box2d world def kill_body! box2d.destroy_body(body) end # Is the particle ready for deletion? def done # Let's find the screen position of the particle pos = box2d.body_coord(body) # Is it off the bottom of the screen? return false unless pos.y > height kill_body! true end # Drawing the box def display(app) # We look at each body and get its screen position pos = box2d.body_coord(body) # Get its angle of rotation a = body.get_angle f = body.get_fixture_list ps = f.get_shape app.rect_mode(CENTER) app.push_matrix app.translate(pos.x, pos.y) app.rotate(-a) app.fill(175) app.stroke(0) app.begin_shape # For every vertex, convert to pixel vector ps.get_vertex_count.times do |i| v = box2d.vector_to_processing(ps.get_vertex(i)) app.vertex(v.x, v.y) end app.end_shape(CLOSE) app.pop_matrix end # This function adds the rectangle to the box2d world def make_body(center) # Define a polygon (this is what we use for a rectangle) sd = PB::PolygonShape.new vertices = [] vertices << box2d.vector_to_world(PB::Vec2.new(-15, 25)) vertices << box2d.vector_to_world(PB::Vec2.new(15, 0)) vertices << box2d.vector_to_world(PB::Vec2.new(20, -15)) vertices << box2d.vector_to_world(PB::Vec2.new(-10, -10)) sd.set(vertices.to_java(Java::OrgJbox2dCommon::Vec2), vertices.length) # Define the body and make it from the shape bd = PB::BodyDef.new bd.type = PB::BodyType::DYNAMIC bd.position.set(box2d.processing_to_world(center)) @body = box2d.create_body(bd) body.create_fixture(sd, 1.0) # Give it some initial random velocity body.set_linear_velocity(Vec2.new(rand(-5.0..5), rand(2.0..5))) body.set_angular_velocity(rand(-5.0..5)) end end class Boundary include PB attr_reader :box2d, :b, :x, :y, :w, :h def initialize(b2d, x, y, w, h, a) @box2d, @x, @y, @w, @h = b2d, x, y, w, h # Define the polygon sd = PB::PolygonShape.new # Figure out the box2d coordinates box2d_w = box2d.scale_to_world(w / 2) box2d_h = box2d.scale_to_world(h / 2) # We're just a box sd.set_as_box(box2d_w, box2d_h) # Create the body bd = PB::BodyDef.new bd.type = PB::BodyType::STATIC bd.angle = a bd.position.set(box2d.processing_to_world(x, y)) @b = box2d.create_body(bd) # Attached the shape to the body using a Fixture b.create_fixture(sd, 1) end # Draw the boundary, it doesn't move so we don't have to ask the Body for location def display(app) app.fill(0) app.stroke(0) app.stroke_weight(1) app.rect_mode(CENTER) a = b.get_angle app.push_matrix app.translate(x, y) app.rotate(-a) app.rect(0, 0, w, h) app.pop_matrix end end
Labels:
example,
jbox2d,
JRubyArt,
pbox2d,
physics engine
Saturday, 1 November 2014
Creating a gem wrapper for jbox2d in ruby-processing
The mechanism for including Daniel Shiffmans PBox2D for processing into ruby-processing is unnecessarily complicated, and I had always thought there should be an easier way. So here it is jbox2d wrapped as a gem using some jruby sugar (and avoiding the nasty reflection nonsense that Dan used, although under the hood jruby may do something similar).
The ruby-processing sketch
The associated (local) library
The ruby-processing sketch
require 'pbox2d' require_relative 'lib/particle_system' attr_reader :box2d, :boundaries, :systems def setup size(400,300) @box2d = Box2D.new(self) box2d.create_world # We are setting a custom gravity box2d.gravity(0, -20) # Create Arrays @systems = [] @boundaries = [] # Add a bunch of fixed boundaries boundaries << Boundary.new(box2d, 50, 100, 300, 5, -0.3) boundaries << Boundary.new(box2d, 250, 175, 300, 5, 0.5) end def draw background(255) # Run all the particle systems if systems.size > 0 systems.each do |system| system.run system.add_particles(box2d, rand(0..2)) end end # Display all the boundaries boundaries.each(&:display) end def mouse_pressed # Add a new Particle System whenever the mouse is clicked systems << ParticleSystem.new(box2d, 0, mouse_x, mouse_y) end
The associated (local) library
require 'forwardable' module Runnable def run reject! { |item| item.done } each { |item| item.display } end end class ParticleSystem include Enumerable, Runnable extend Forwardable def_delegators(:@particles, :each, :reject!, :<<, :empty?) def_delegator(:@particles, :empty?, :dead?) attr_reader :x, :y def initialize(bd, num, x, y) @particles = [] # Initialize the Array @x, @y = x, y # Store the origin point num.times do self << Particle.new(bd, x, y) end end def add_particles(bd, n) n.times do self << Particle.new(bd, x, y) end end end # A Particle require 'pbox2d' class Particle include Processing::Proxy, PB TRAIL_SIZE = 6 # We need to keep track of a Body attr_reader :trail, :body, :box2d # Constructor def initialize(b2d, x, y) @box2d = b2d @trail = Array.new(TRAIL_SIZE, [x, y]) # Add the box to the box2d world # Here's a little trick, let's make a tiny tiny radius # This way we have collisions, but they don't overwhelm the system make_body(PB::Vec2.new(x, y), 0.2) end # This function removes the particle from the box2d world def kill_body box2d.destroy_body(body) end # Is the particle ready for deletion? def done # Let's find the screen position of the particle pos = box2d.body_coord(body) # Is it off the bottom of the screen? return false unless (pos.y > $app.height + 20) kill_body true end # Drawing the box def display # We look at each body and get its screen position pos = box2d.body_coord(body) # Keep track of a history of screen positions in an array (TRAIL_SIZE - 1).times do |i| trail[i] = trail[i + 1] end trail[TRAIL_SIZE - 1] = [pos.x, pos.y] # Draw particle as a trail begin_shape no_fill stroke_weight(2) stroke(0, 150) trail.each do |v| vertex(v[0], v[1]) end end_shape end # This function adds the rectangle to the box2d world def make_body(center, r) # Define and create the body bd = PB::BodyDef.new bd.type = PB::BodyType::DYNAMIC bd.position.set(box2d.processing_to_world(center)) @body = box2d.create_body(bd) # Give it some initial random velocity body.set_linear_velocity(PB::Vec2.new(rand(-1.0..1), rand(-1.0..1))) # Make the body's shape a circle cs = PB::CircleShape.new cs.m_radius = box2d.scale_to_world(r) fd = PB::FixtureDef.new fd.shape = cs fd.density = 1 fd.friction = 0 # Slippery when wet! fd.restitution = 0.5 # We could use this if we want to turn collisions off # cd.filter.groupIndex = -10 # Attach fixture to body body.create_fixture(fd) end end class Boundary include Processing::Proxy, PB attr_reader :box2d, :b, :x, :y, :w, :h def initialize(b2d, x, y, w, h, a) @box2d, @x, @y, @w, @h = b2d, x, y, w, h # Define the polygon sd = PB::PolygonShape.new # Figure out the box2d coordinates box2d_w = box2d.scale_to_world(w / 2) box2d_h = box2d.scale_to_world(h / 2) # We're just a box sd.set_as_box(box2d_w, box2d_h) # Create the body bd = PB::BodyDef.new bd.type = PB::BodyType::STATIC bd.angle = a bd.position.set(box2d.processing_to_world(x, y)) @b = box2d.create_body(bd) # Attached the shape to the body using a Fixture b.create_fixture(sd, 1) end # Draw the boundary, it doesn't move so we don't have to ask the Body for location def display fill(0) stroke(0) stroke_weight(1) rect_mode(CENTER) a = b.get_angle push_matrix translate(x, y) rotate(-a) rect(0, 0, w, h) pop_matrix end end
Labels:
gem,
jbox2d,
ruby-processing
Subscribe to:
Posts (Atom)
Followers
About Me
- monkstone
- I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2


