picrate1, 2
PiCrate provides a number of libraries that you can use out of the box, but which still need to be loaded to use them in you sketches see examples below:-
Boids library
1A built in pure ruby library
The original boids library was created by Jeremy Ashkenas to demonstrate a pure ‘ruby’ library for ruby processing, this updated version has the same goal. However it is updated to make use of Vec3D and Vec2D classes (picrate features) and keyword arguments (ruby-2.1). It also use forwardable. To see usage of now correctly implemented angle
(heading) see simple example.
See the full library code below:-
# frozen_string_literal: true
# Boids -- after Tom de Smedt.
# See his Python version: http://nodebox.net/code/index.php/Boids
# This is an example of how a pure-Ruby library can work. Original for
# ruby-processing Jeremy Ashkenas. Reworked, re-factored for picrate
# by Martin Prout, features forwardable, keyword args, Vec3D and Vec2D.
class Boid
attr_reader :boids
attr_accessor :vel, :pos, :is_perching, :perch_time
def initialize(boids, pos)
@boids, @flock = boids, boids
@pos = pos
@vel = Vec3D.new
@is_perching = false
@perch_time = 0.0
end
def cohesion(d:)
# Boids gravitate towards the center of the flock,
# Which is the averaged position of the rest of the boids.
vect = Vec3D.new
@boids.each do |boid|
vect += boid.pos unless boid == self
end
count = @boids.length - 1.0
vect /= count
(vect - pos) / d
end
def separation(radius:)
# Boids don't like to cuddle.
vect = Vec3D.new
@boids.each do |boid|
if boid != self
dv = pos - boid.pos
vect += dv if dv.mag < radius
end
end
vect
end
def alignment(d:)
# Boids like to fly at the speed of traffic.
vect = Vec3D.new
@boids.each do |boid|
vect += boid.vel if boid != self
end
count = @boids.length - 1.0
vect /= count
(vect - vel) / d
end
def limit(max:)
# Tweet, Tweet! The boid police will bust you for breaking the speed limit.
most = [vel.x.abs, vel.y.abs, vel.z.abs].max
return if most < max
scale = max / most.to_f
@vel *= scale
end
def angle
Vec2D.new(vel.x, vel.y).heading
end
def goal(target, d = 50.0)
# Them boids is hungry.
(target - pos) / d
end
end
require 'forwardable'
# The Boids class
class Boids
include Enumerable
extend Forwardable
def_delegators(:@boids, :reject, :<<, :each, :shuffle!, :length, :next)
attr_reader :has_goal, :perch, :perch_tm, :perch_y
def initialize
@boids = []
end
def self.flock(n:, x:, y:, w:, h:)
flock = Boids.new.setup(n, x, y, w, h)
flock.reset_goal(Vec3D.new(w / 2, h / 2, 0))
end
def setup(n, x, y, w, h)
n.times do
dx, dy = rand(w), rand(h)
z = rand(200.0)
self << Boid.new(self, Vec3D.new(x + dx, y + dy, z))
end
@x, @y, @w, @h = x, y, w, h
@scattered = false
@scatter = 0.005
@scatter_time = 50.0
@scatter_i = 0.0
@perch = 1.0 # Lower this number to divebomb.
@perch_y = h
@perch_tm = -> { 25.0 + rand(50.0) }
@has_goal = false
@flee = false
@goal = Vec3D.new
self
end
def scatter(chance = 0.005, frames = 50.0)
@scatter = chance
@scatter_time = frames
end
def no_scatter
@scatter = 0.0
end
def perch(ground = nil, chance = 1.0, frames = nil)
@perch_tm = frames.nil? ? -> { 25.0 + rand(50.0) } : frames
@perch_y = ground.nil? ? @h : ground
@perch = chance
end
def no_perch
@perch = 0.0
end
def reset_goal(target)
@has_goal = true
@flee = false
@goal = target
self
end
def goal(target:, flee:)
@has_goal = true
@flee = flee
@goal = target
self
end
def no_goal
@has_goal = false
end
def constrain
# Put them boids in a cage.
dx, dy = @w * 0.1, @h * 0.1
each do |b|
b.vel.x += rand(dx) if b.pos.x < @x - dx
b.vel.y += rand(dy) if b.pos.y < @y - dy
b.vel.x -= rand(dx) if b.pos.x > @x + @w + dx
b.vel.y -= rand(dy) if b.pos.y > @y + @h + dy
b.vel.z += 10.0 if b.pos.z < 0.0
b.vel.z -= 10.0 if b.pos.z > 100.0
next unless b.pos.y > perch_y && rand < perch
b.pos.y = perch_y
b.vel.y = b.vel.y.abs * -0.2
b.is_perching = true
b.perch_time = perch_tm.respond_to?(:call) ? perch_tm.call : perch_tm
end
end
def update(goal: 20.0, limit: 30.0, **args)
shuffled = args.fetch(:shuffled, true)
cohesion = args.fetch(:cohesion, 100)
separation = args.fetch(:separation, 10)
alignment = args.fetch(:alignment, 5.0)
# Just flutter, little boids ... just flutter away.
# Shuffling keeps things flowing smooth.
shuffle! if shuffled
m1 = 1.0 # cohesion
m2 = 1.0 # separation
m3 = 1.0 # alignment
m4 = 1.0 # goal
@scattered = true if !@scattered && rand < @scatter
if @scattered
m1 = -m1
m3 *= 0.25
@scatter_i += 1.0
end
if @scatter_i >= @scatter_time
@scattered = false
@scatter_i = 0.0
end
m4 = 0.0 unless has_goal
m4 = -m4 if @flee
each do |b|
if b.is_perching
if b.perch_time > 0.0
b.perch_time -= 1.0
next
else
b.is_perching = false
end
end
v1 = b.cohesion(d: cohesion)
v2 = b.separation(radius: separation)
v3 = b.alignment(d: alignment)
v4 = b.goal(@goal, goal)
# NB: vector must precede scalar in '*' operation below
b.vel += (v1 * m1 + v2 * m2 + v3 * m3 + v4 * m4)
b.limit(max: limit)
b.pos += b.vel
end
constrain
end
end
Here is the re-factored Flight Patterns Sketch:-
#!/usr/bin/env jruby -v -w
# Description:
# Flight Patterns is that ol' Euruko 2008 demo.
# Reworked version for PiCrate
# Usage:
# Drag mouse to steer 'invisible' flock attractor, use 'f' key to toggle flee
# Mouse 'click' to toggle 'sphere' or 'circle' display
require 'picrate'
class FlightPatterns < Processing::App
load_library :boids
attr_reader :flee, :radius
def settings
size 1024, 768, P3D
end
def setup
sketch_title 'Flight Patterns'
sphere_detail 8
color_mode RGB, 1.0
no_stroke
shininess 1.0
specular 0.3, 0.1, 0.1
emissive 0.03, 0.03, 0.1
@radius = 0.02 * height
@click = false
@flee = false
@flocks = (0..3).map { Boids.flock(n: 20, x: 0, y: 0, w: width, h: height) }
end
def mouse_pressed
@click = !@click
end
def key_pressed
return unless key == 'f'
@flee = !@flee
end
def draw
background 0.05
ambient_light 0.01, 0.01, 0.01
light_specular 0.4, 0.2, 0.2
point_light 1.0, 1.0, 1.0, mouse_x, mouse_y, 190
@flocks.each_with_index do |flock, i|
flock.goal(target: Vec3D.new(mouse_x, mouse_y, 0), flee: @flee)
flock.update(goal: 185, limit: 13.5)
flock.each do |boid|
r = (0.15 * boid.pos.z) + radius
case i
when 0 then fill 0.85, 0.65, 0.65
when 1 then fill 0.65, 0.85, 0.65
when 2 then fill 0.65, 0.65, 0.85
end
push_matrix
point_array = (boid.pos.to_a).map { |p| p - (r / 2.0) }
translate(*point_array)
@click ? sphere(r / 2) : ellipse(0, 0, r, r)
@click ? hint(ENABLE_DEPTH_TEST) : hint(DISABLE_DEPTH_TEST)
pop_matrix
end
end
end
end
FlightPatterns.new
Control Panel library
2A built in hybrid ruby/java library
Start by loading in the control_panel library, and then define your panel in setup like so:
#!/usr/bin/env jruby -v -W2
# frozen_string_literal: true
require 'picrate'
# Iconic ruby-processing example for PiCrate
class JWishy < Processing::App
load_library :control_panel
attr_reader :alpha, :back_color, :bluish, :hide, :magnitude, :panel
attr_reader :x_wiggle, :y_wiggle, :go_big, :shape
def settings
size 600, 600
end
def setup
sketch_title 'Wishy Worm'
control_panel do |c|
c.title 'Control Panel'
c.look_feel 'Nimbus'
c.slider :bluish, 0.0..1.0, 0.5
c.slider :alpha, 0.0..1.0, 0.5
c.checkbox :go_big, false
c.button :reset
c.menu :shape, %w[oval square triangle], 'oval'
@panel = c
end
@hide = false
@x_wiggle, @y_wiggle = 10.0, 0
@magnitude = 8.15
@back_color = [0.06, 0.03, 0.18]
color_mode RGB, 1
ellipse_mode CORNER
smooth
end
#....rest of code
def draw
# only make control_panel visible once, or again when hide is false
unless hide
@hide = true
panel.set_visible(hide)
end
#.... rest of draw
JWishy.new
See also penrose and bezier playground sketches. See ruby code here.
Video Event Library
2A built in hybrid ruby/java library
The video library should be installed using picrate --install video
The purpose of the video_event
library is to allow you to use the vanilla processing reflection methods captureEvent
and movieEvent
from the processing video
library. It is almost impossible to use vanilla processing reflection methods without this sort of wrapper.
A movie example:-
#!/usr/bin/env jruby -w
require 'picrate'
# Loop.
#
# Shows how to load and play a QuickTime movie file.
class Loop < Processing::App
load_libraries :video, :video_event
include_package 'processing.video'
attr_reader :movie
def setup
sketch_title 'Loop'
background(0)
# Load and play the video in a loop
@movie = Movie.new(self, data_path('transit.mov'))
movie.loop
end
def draw
image(movie, 0, 0, width, height)
end
# use camel case to match java reflect method
def movieEvent(m)
m.read
end
def settings
size 640, 360
end
end
Loop.new
A capture example-
#!/usr/bin/env jruby -w
require 'picrate'
class TestCapture < Processing::App
load_libraries :video, :video_event
include_package 'processing.video'
attr_reader :cam
def setup
sketch_title 'Test Capture'
cameras = Capture.list
fail 'There are no cameras available for capture.' if (cameras.length == 0)
p 'Matching cameras available:'
size_pattern = Regexp.new(format('%dx%d', width, height))
select = cameras.grep size_pattern # filter available cameras
select.uniq.map { |cam| p cam.strip }
fail 'There are no matching cameras.' if (select.length == 0)
start_capture(select[0])
end
def start_capture(cam_string)
# The camera can be initialized directly using an
# element from the array returned by list:
@cam = Capture.new(self, cam_string)
p format('Using camera %s', cam_string)
cam.start
end
def draw
image(cam, 0, 0, width, height)
# The following does the same, and is faster when just drawing the image
# without any additional resizing, transformations, or tint.
# set(0, 0, cam)
end
def captureEvent(c)
c.read
end
def settings
size 1280, 720, P2D
end
end
TestCapture.new
File Chooser Library
2A built in hybrid ruby/java library
Start by loading in the chooser library, the purpose of this library is to allow you to use the vanilla processing interface to the native file chooser
(it is almost impossible to use vanilla processing reflection methods without this sort of wrapper)
#!/usr/bin/env jruby -v -W2
require 'picrate'
###########
# Example Native File Chooser using vanilla processing
# select_input, and file_selected
###########
class SelectFile < Processing::App
load_library :file_chooser
def settings
size 200, 100
end
def setup
sketch_title 'Select File, native chooser'
# java_signature 'void selectInput(String, String)'
select_input('Select a File', 'file_selected')
end
# signature 'void file_selected(java.io.File file)'
def file_selected(file)
puts file.get_absolute_path unless file.nil?
end
end
SelectFile.new
See also these examples