Parameter = Struct.new(:cx, :cy, :ch, :cw) # required for future RATIO = 1.336 TMAX = 5_000 attr_reader :current, :default # required for future work def setup size(534, 400, P2D) @default = Parameter.new(-0.53 - (1.4 * RATIO), -1.4, 2.8, 2.8 * RATIO) @current = default load_pixels no_fill stroke(255) create_mandelbrot(width, height) no_loop end def create_mandelbrot(w, h) y_zero = ->(y, param) { y * param.ch / height + param.cy } x_zero = ->(x, param) { x * param.cw / width + param.cx } h.times do |j| y0 = y_zero.call(j, current) w.times do |k| x0 = x_zero.call(k, current) x, y = 0, 0 xsqr, ysqr = 0.0, 0.0 t = 0 while t < TMAX && (xsqr + ysqr < 4.0) y = x * y y += y + y0 x = xsqr - ysqr + x0 xsqr = x * x ysqr = y * y t += 1 end if t < TMAX smth = t - Math.log(xsqr + ysqr) pixels[k + j * width] = Java::JavaAwt::Color.HSBtoRGB(smth / 500, 0.8, 0.9) else pixels[k + j * width] = 0 end end end update_pixels end
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Thursday, 25 December 2014
Towards a zoom-able mandelbrot ruby-processing
Tuesday, 23 December 2014
Lorenz Attractor Ruby-Processing
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
Thursday, 18 December 2014
Reading and writing to a csv file ruby-processing (version two)
# # 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
Tuesday, 16 December 2014
Experimenting with jruby-complete-SNAPSHOT-9.0.0.0 and ruby processing
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
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)
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
Sunday, 2 November 2014
Using the pbox2d gem in JRubyArt
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
Saturday, 1 November 2014
Creating a gem wrapper for jbox2d in ruby-processing
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
Wednesday, 29 October 2014
Toxiclibs (or any other processing library) as a rubygem
require 'toxiclibs' include Toxi # # <p>GrayScottToneMap shows how to use the ColorGradient & ToneMap classes of the # colorutils package to create a tone map for rendering the results of # the Gray-Scott reaction-diffusion.</p> # # <p><strong>Usage:</strong><ul> # <li>click + drag mouse to draw dots used as simulation seed</li> # <li>press any key to reset</li> # </ul></p> # # # Copyright (c) 2010 Karsten Schmidt # # This demo & library is free software you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation either # version 2.1 of the License, or (at your option) any later version. # # http://creativecommons.org/licenses/LGPL/2.1/ # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # NUM_ITERATIONS = 10 attr_reader :gs, :tone_map def setup size(256,256) @gs= Toxi::GrayScott.new width,height, false @gs.set_coefficients 0.021, 0.076, 0.12, 0.06 # create a color gradient for 256 values grad = Toxi::ColorGradient.new # NamedColors are preset colors, but any TColor can be added # see javadocs for list of names: # http://toxiclibs.org/docs/colorutils/toxi/color/NamedColor.html # NB: use '::' in place of '.' here for these java constants grad.add_color_at(0, Toxi::NamedColor::BLACK) grad.add_color_at(128, Toxi::NamedColor::RED) grad.add_color_at(192, Toxi::NamedColor::YELLOW) grad.add_color_at(255, Toxi::NamedColor::WHITE) # this gradient is used to map simulation values to colors # the first 2 parameters define the min/max values of the # input range (Gray-Scott produces values in the interval of 0.0 - 0.5) # setting the max = 0.33 increases the contrast @tone_map = Toxi::ToneMap.new 0, 0.33, grad end def draw @gs.set_rect(mouse_x, mouse_y, 20, 20) if mouse_pressed? load_pixels # update the simulation a few time steps NUM_ITERATIONS.times { @gs.update(1) } # read out the V result array # and use tone map to render colours gs.v.length.times do |i| pixels[i]=tone_map.getARGBToneFor(gs.v[i]) # NB: don't camel case convert here end update_pixels end def key_pressed @gs.reset end
Here is another example running a JRubyArt sketch from netbeans.
Monday, 27 October 2014
For the next version of ruby-processing examples will not be included in the gem.
Example-Sketches
Many of the vanilla processing example sketches have been translated to ruby-processing, and they are mainly written as 'bare' sketches (ie not class wrapped) in keeping with the original processing. At runtime these sketches the get wrapped into a Sketch class. Should you prefer you can still write class wrapped sketches, these will work equally well with ruby processing. Certain sketches must be run with JRuby-Complete (load_image
and shader
sketches), this is some java permissions thing with jruby. You should also checkout the Nature of Code Examples in ruby and for the beginner Learning Processing with Ruby.
Partial Catalogue (for the lazy)
Saturday, 25 October 2014
Sketch featuring Vec2D to_curve_vertex (JRubyArt)
######## # Soft Body by Ira Greenberg # Softbody dynamic simulation using curve_vertex # and curve_tightness (and new Vec2D to_curve_vertex) ######## require 'jruby_art' # SoftBody class wrapped JRubyArt sketch features Vec2D :to_curve_vertex class SoftBody < Processing::App attr_reader :accel, :center, :frequency, :radius, :rot_angle attr_reader :organic_constant, :nodes, :renderer, :angle, :node_start SPRINGING = 0.0009 DAMPING = 0.98 NODES = 5 def setup size 640, 360 @renderer = AppRender.new(self) init_node no_stroke frame_rate 30 end def init_node @accel = Vec2D.new @center = Vec2D.new(width / 2, height / 2) @radius = 45 @organic_constant = 1 @rot_angle = -90 @nodes = (0...NODES).map { Vec2D.new } @frequency = (0...NODES).map { rand(5..12) } @angle = Array.new(NODES, 0) end def draw fill(0, 100) rect(0, 0, width, height) update draw_shape move_shape end def draw_shape curve_tightness(organic_constant) fill 255 begin_shape nodes.each { |vec| vec.to_curve_vertex(renderer) } nodes.take(NODES - 1).each { |vec| vec.to_curve_vertex(renderer) } end_shape(Java::ProcessingCore::PConstants::CLOSE) end def update delta = Vec2D.new(mouse_x - center.x, mouse_y - center.y) delta *= SPRINGING @accel += delta @center += accel @accel *= DAMPING @organic_constant = 1 - (((accel.x).abs + (accel.y).abs) * 0.1) @node_start = create_start end def create_start (0...NODES).map do |n| Vec2D.new( center.x + DegLut.cos(n * (360 / NODES)) * radius, center.y + DegLut.sin(n * (360 / NODES)) * radius ) end end def move_shape (0...NODES).each do |i| nodes[i] = Vec2D.new( node_start[i].x + DegLut.sin(angle[i]) * (accel.x * 2), node_start[i].y + DegLut.sin(angle[i]) * (accel.y * 2) ) angle[i] = frequency[i] + angle[i] end end end SoftBody.new(title: 'Soft Body')
Tuesday, 14 October 2014
More progress with JRubyArt (Alternative ruby-processing implementation)
ruby-processing | JRubyArt |
---|---|
rp5 setup install | k9 setup install |
rp5 --nojruby run sketch.rb | k9 run sketch.rb |
rp5 run sketch.rb | jruby sketch.rb |
NB: Regular ruby-processing can be made to always run with vendored jruby-complete in ~/.rp5rc config file, but default is to use an installed jruby. MRI ruby can be used start sketches with run mode, but jruby is used to actually run the sketches.
Sunday, 12 October 2014
Transducers in ruby-processing
require 'transducers' class GameOfLife < Processing::App T = Transducers def random_data T.transduce(T.map{ rand(1000) < ALIVE_START }, :<<, [], 0..row * column) end end
Monday, 6 October 2014
Vec3D to shape vertex in JRubyArt
# Trefoil, by Andres Colubri # A parametric surface is textured procedurally # by drawing on an offscreen PGraphics surface. # # Features (Vec3D).to_normal(renderer) and (Vec3D).to_vertex_uv(renderer, u, v) # see line 55 for inititialization of renderer where obj is an instance of PShape # renderer = ShapeRender.new(obj) require 'jruby_art' class Trefoil < Processing::AppGL attr_reader :pg, :trefoil def setup size(1024, 768, P3D) texture_mode(NORMAL) no_stroke # Creating offscreen surface for 3D rendering. @pg = create_graphics(32, 512, P3D) pg.begin_draw pg.background(0, 0) pg.noStroke pg.fill(255, 0, 0, 200) pg.end_draw # Saving trefoil surface into a PShape3D object @trefoil = create_trefoil(350, 60, 15, pg) end def draw background(0) pg.begin_draw pg.ellipse(rand(0.0..pg.width), rand(0.0..pg.height), 4, 4) pg.end_draw ambient(250, 250, 250) pointLight(255, 255, 255, 0, 0, 200) push_matrix translate(width/2, height/2, -200) rotate_x(frame_count * PI / 500) rotate_y(frame_count * PI / 500) shape(trefoil) pop_matrix end # Code to draw a trefoil knot surface, with normals and texture # coordinates. Makes of the Vec3D Render interface (uses ShapeRender here). # Adapted from the parametric equations example by Philip Rideout: # http://iphone-3d-programming.labs.oreilly.com/ch03.html # This function draws a trefoil knot surface as a triangle mesh derived # from its parametric equation. def create_trefoil(s, ny, nx, tex) obj = create_shape() obj.begin_shape(TRIANGLES) obj.texture(tex) renderer = ShapeRender.new(obj) (0 ... nx).each do |j| u0 = j.to_f / nx u1 = (j + 1).to_f / nx (0 ... ny).each do |i| v0 = i.to_f / ny v1 = (i + 1).to_f / ny p0 = eval_point(u0, v0) n0 = eval_normal(u0, v0) p1 = eval_point(u0, v1) n1 = eval_normal(u0, v1) p2 = eval_point(u1, v1) n2 = eval_normal(u1, v1) # Triangle p0-p1-p2 n0.to_normal(renderer) (p0 * s).to_vertex_uv(renderer, u0, v0) n1.to_normal(renderer) (p1 * s).to_vertex_uv(renderer, u0, v1) n2.to_normal(renderer) (p2 * s).to_vertex_uv(renderer, u1, v1) p1 = eval_point(u1, v0) n1 = eval_normal(u1, v0) # Triangle p0-p2-p1 n0.to_normal(renderer) (p0 * s).to_vertex_uv(renderer, u0, v0) n2.to_normal(renderer) (p2 * s).to_vertex_uv(renderer, u1, v1) n1.to_normal(renderer) (p1 * s).to_vertex_uv(renderer, u1, v0) end end obj.end_shape return obj end # Evaluates the surface normal corresponding to normalized # parameters (u, v) def eval_normal(u, v) # Compute the tangents and their cross product. p = eval_point(u, v) tang_u = eval_point(u + 0.01, v) tang_v = eval_point(u, v + 0.01) tang_u -= p tang_v.cross(tang_u).normalize! # it is easy to chain Vec3D operations end # Evaluates the surface point corresponding to normalized # parameters (u, v) def eval_point(u, v) a = 0.5 b = 0.3 c = 0.5 d = 0.1 s = TAU * u t = (TAU * (1 - v)) * 2 sint = Math.sin(t) cost = Math.cos(t) sint15 = Math.sin(1.5 * t) cost15 = Math.cos(1.5 * t) r = a + b * cost15 x = r * cost y = r * sint z = c * sint15 dv = Vec3D.new( -1.5 * b * sint15 * cost - y, -1.5 * b * sint15 * sint + x, 1.5 * c * cost15) q = dv.normalize # regular normalize creates a new Vec3D for us qvn = Vec3D.new(q.y, -q.x, 0).normalize! # chained Vec3D operations ww = q.cross(qvn) coss = Math.cos(s) sins = Math.sin(s) Vec3D.new( x + d * (qvn.x * coss + ww.x * sins), y + d * (qvn.y * coss + ww.y * sins), z + d * ww.z * sins) end end Trefoil.new(title: 'Trefoil', fullscreen: true, bgcolor: '#000000')
Followers
Blog Archive
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