├── lib ├── templates │ ├── application │ │ ├── Contents │ │ │ ├── PkgInfo │ │ │ ├── Resources │ │ │ │ └── sketch.icns │ │ │ ├── MacOS │ │ │ │ └── JavaApplicationStub │ │ │ └── Info.plist.erb │ │ ├── lib │ │ │ ├── MANIFEST.MF │ │ │ ├── library │ │ │ │ └── library.txt │ │ │ └── args.txt.erb │ │ ├── run.exe │ │ └── run.erb │ └── create │ │ ├── blank_sketch.rb.erb │ │ └── p3d_sketch.rb.erb ├── ruby-processing │ ├── version.rb │ ├── runners │ │ ├── run_app.rb │ │ ├── run.rb │ │ ├── live.rb │ │ ├── base.rb │ │ └── watch.rb │ ├── helpers │ │ ├── numeric.rb │ │ ├── range.rb │ │ ├── camel_string.rb │ │ └── string_extra.rb │ ├── config.rb │ ├── exporters │ │ ├── application_exporter.rb │ │ ├── creator.rb │ │ └── base_exporter.rb │ ├── library_loader.rb │ ├── helper_methods.rb │ ├── app.rb │ └── runner.rb └── ruby-processing.rb ├── .yardopts ├── Gemfile ├── .mvn ├── wrapper │ └── maven-wrapper.properties └── extensions.xml ├── test ├── sketches │ ├── graphics.rb │ ├── setup_ex.rb │ ├── vector.rb │ ├── basic.rb │ ├── arcball.rb │ ├── p2d.rb │ ├── p3d.rb │ ├── pdf.rb │ └── export_test.rb ├── README.md ├── deglut_spec_test.rb ├── aabb_spec_test.rb ├── helper_methods_test.rb ├── rp5_run_test.rb ├── test_map1d.rb ├── math_tool_test.rb └── vecmath_spec_test.rb ├── .travis.yml ├── library ├── fastmath │ └── fastmath.rb ├── video_event │ └── video_event.rb ├── library_proxy │ ├── library_proxy.rb │ └── README.md ├── vecmath │ └── vecmath.rb ├── file_chooser │ └── file_chooser.rb ├── control_panel │ └── control_panel.rb └── boids │ └── boids.rb ├── .gitignore ├── bin └── rp5 ├── src └── monkstone │ ├── vecmath │ ├── vec3 │ │ └── Vec3Library.java │ ├── vec2 │ │ ├── Vec2Library.java │ │ └── Vec2.java │ ├── JRender.java │ ├── AppRender.java │ └── ShapeRender.java │ ├── arcball │ ├── ArcballLibrary.java │ ├── WheelHandler.java │ ├── Constrain.java │ ├── Rarcball.java │ ├── Quaternion.java │ ├── Jvector.java │ └── Arcball.java │ ├── fastmath │ ├── DeglutLibrary.java │ └── Deglut.java │ ├── videoevent │ └── VideoInterface.java │ ├── MathToolLibrary.java │ ├── ColorUtil.java │ ├── core │ └── AbstractLibrary.java │ └── MathTool.java ├── Rakefile ├── LICENSE.md ├── CONTRIBUTING.md ├── pom.rb ├── ruby-processing.gemspec ├── vendors └── Rakefile ├── pom.xml ├── README.md └── CHANGELOG /lib/templates/application/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | - 3 | CONTRIBUTING.md 4 | LICENSE.md 5 | README.md 6 | -------------------------------------------------------------------------------- /lib/ruby-processing/version.rb: -------------------------------------------------------------------------------- 1 | module RubyProcessing 2 | VERSION = '2.7.1' 3 | end 4 | -------------------------------------------------------------------------------- /lib/ruby-processing/runners/run_app.rb: -------------------------------------------------------------------------------- 1 | require_relative 'base' 2 | 3 | Processing.run_app -------------------------------------------------------------------------------- /lib/ruby-processing/runners/run.rb: -------------------------------------------------------------------------------- 1 | require_relative 'base' 2 | 3 | Processing.load_and_run_sketch 4 | -------------------------------------------------------------------------------- /lib/templates/application/lib/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: JRuby 3 | Main-Class: org.jruby.Main -------------------------------------------------------------------------------- /lib/templates/application/lib/library/library.txt: -------------------------------------------------------------------------------- 1 | All of your included libraries will be safely tucked away in here. -------------------------------------------------------------------------------- /lib/templates/application/run.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/ruby-processing/HEAD/lib/templates/application/run.exe -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruby-processing.gemspec 4 | gemspec 5 | 6 | 7 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip -------------------------------------------------------------------------------- /lib/templates/create/blank_sketch.rb.erb: -------------------------------------------------------------------------------- 1 | def setup 2 | <%= "size #{@width}, #{@height}" if @with_size %> 3 | end 4 | 5 | def draw 6 | 7 | end 8 | -------------------------------------------------------------------------------- /lib/templates/create/p3d_sketch.rb.erb: -------------------------------------------------------------------------------- 1 | def setup 2 | <%= "size #{@width}, #{@height}, P3D" if @with_size %> 3 | end 4 | 5 | def draw 6 | 7 | end -------------------------------------------------------------------------------- /test/sketches/graphics.rb: -------------------------------------------------------------------------------- 1 | def setup 2 | size(100, 100, P3D) 3 | puts Java::Processing::opengl::PGraphicsOpenGL.OPENGL_VERSION 4 | exit 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: ruby 4 | 5 | rvm: 6 | - jruby-19mode 7 | - jruby-head 8 | 9 | jdk: 10 | - openjdk7 11 | 12 | -------------------------------------------------------------------------------- /lib/templates/application/Contents/Resources/sketch.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/ruby-processing/HEAD/lib/templates/application/Contents/Resources/sketch.icns -------------------------------------------------------------------------------- /lib/templates/application/lib/args.txt.erb: -------------------------------------------------------------------------------- 1 | set EXPORTED='true' 2 | org.jruby.Main lib/ruby-processing/runners/run.rb lib/<%= @main_file %> 3 | <%= @windows_class_path %> 4 | -------------------------------------------------------------------------------- /lib/templates/application/Contents/MacOS/JavaApplicationStub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jashkenas/ruby-processing/HEAD/lib/templates/application/Contents/MacOS/JavaApplicationStub -------------------------------------------------------------------------------- /library/fastmath/fastmath.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-processing' 2 | require "#{RP5_ROOT}/lib/rpextras" 3 | 4 | Java::MonkstoneFastmath::DeglutLibrary.load(JRuby.runtime) 5 | -------------------------------------------------------------------------------- /lib/ruby-processing/helpers/numeric.rb: -------------------------------------------------------------------------------- 1 | class Numeric #:nodoc: 2 | def degrees 3 | self * 180 / Math::PI 4 | end 5 | 6 | def radians 7 | self * Math::PI / 180 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/sketches/setup_ex.rb: -------------------------------------------------------------------------------- 1 | def setup 2 | size(300, 300) 3 | begin 4 | unknown_method() 5 | rescue NoMethodError => e 6 | puts e 7 | exit 8 | end 9 | end 10 | 11 | def draw 12 | end 13 | -------------------------------------------------------------------------------- /test/sketches/vector.rb: -------------------------------------------------------------------------------- 1 | load_library :vecmath 2 | 3 | def setup 4 | size(300, 300) 5 | a = Vec2D.new(1.0, 1.0) 6 | b = Vec2D.new(1.0, 1.0) 7 | (a == b) ? puts('ok') : puts('fail') 8 | exit 9 | end 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | .DS_Store 3 | *.sw* 4 | *~ 5 | *.pdf 6 | *.gem 7 | *.tgz 8 | *.jar 9 | *.zip 10 | Gemfile.lock 11 | .ruby-version 12 | .jrubyrc 13 | tmp 14 | vendors/*.tar.gz 15 | target 16 | MANIFEST.MF 17 | -------------------------------------------------------------------------------- /lib/ruby-processing/helpers/range.rb: -------------------------------------------------------------------------------- 1 | # Extend Range class to include clip (used to implement processing constrain) 2 | class Range #:nodoc: 3 | def clip(n) 4 | return n if cover?(n) 5 | (n < min) ? min : max 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.takari.polyglot 5 | polyglot-ruby 6 | 0.1.18 7 | 8 | 9 | -------------------------------------------------------------------------------- /bin/rp5: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | file = __FILE__ 4 | if test(?l, file) 5 | require "pathname" 6 | file = Pathname.new(file).realpath 7 | end 8 | 9 | require File.expand_path(File.dirname(file) + "/../lib/ruby-processing") 10 | Processing::Runner.execute 11 | -------------------------------------------------------------------------------- /test/sketches/basic.rb: -------------------------------------------------------------------------------- 1 | java_alias :background_int, :background, [Java::int] 2 | 3 | def setup 4 | size(300, 300) 5 | frame_rate(10) 6 | end 7 | 8 | def draw 9 | background_int 0 10 | if frame_count == 3 11 | puts 'ok' 12 | exit 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/sketches/arcball.rb: -------------------------------------------------------------------------------- 1 | load_library :vecmath 2 | 3 | def setup 4 | size(300, 300, P3D) 5 | ArcBall.init(self) 6 | frame_rate(10) 7 | end 8 | 9 | def draw 10 | background 39, 232, 51 11 | if frame_count == 3 12 | puts 'ok' 13 | exit 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/sketches/p2d.rb: -------------------------------------------------------------------------------- 1 | java_alias :background_int, :background, [Java::int] 2 | 3 | def setup 4 | size(300, 300, P2D) 5 | frame_rate(10) 6 | end 7 | 8 | def draw 9 | background_int 255 10 | if frame_count == 3 11 | puts 'ok' 12 | exit 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/templates/application/run.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | <%# 3 | bash script to run exported application on linux 4 | %> 5 | APPDIR=$(dirname "$0") 6 | export EXPORTED="true" 7 | cd "$APPDIR/lib" 8 | java -cp "<%= @linux_class_path %>" org.jruby.Main ruby-processing/runners/run.rb <%= @main_file %> 9 | -------------------------------------------------------------------------------- /test/sketches/p3d.rb: -------------------------------------------------------------------------------- 1 | java_alias :background_float_float_float, :background, [Java::float, Java::float, Java::float] 2 | 3 | def setup 4 | size(300, 300, P3D) 5 | frame_rate(10) 6 | end 7 | 8 | def draw 9 | background_float_float_float 39, 232, 51 10 | if frame_count == 3 11 | puts 'ok' 12 | exit 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /library/video_event/video_event.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-processing' 2 | require "#{RP5_ROOT}/lib/rpextras" 3 | 4 | class Processing::App 5 | include Java::MonkstoneVideoevent::VideoInterface 6 | def captureEvent(c) 7 | # satisfy implement abstract class 8 | end 9 | 10 | def movieEvent(m) 11 | # satisfy implement abstract class 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /library/library_proxy/library_proxy.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-processing' 2 | require "#{RP5_ROOT}/lib/rpextras" 3 | 4 | LibraryProxy = Java::MonkstoneCore::AbstractLibrary 5 | 6 | # classes that inherit from Library are expected to implement 7 | # the abstract methods of processing.core.AbstractLibrary 8 | # def pre... 9 | # def draw... 10 | # def post... 11 | # NOOP is fine... 12 | -------------------------------------------------------------------------------- /library/vecmath/vecmath.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../lib/ruby-processing' 2 | require "#{RP5_ROOT}/lib/rpextras" 3 | 4 | Java::MonkstoneArcball::ArcballLibrary.load(JRuby.runtime) 5 | Java::MonkstoneVecmathVec2::Vec2Library.load(JRuby.runtime) 6 | Java::MonkstoneVecmathVec3::Vec3Library.load(JRuby.runtime) 7 | 8 | AppRender ||= Java::MonkstoneVecmath::AppRender 9 | ShapeRender ||= Java::MonkstoneVecmath::ShapeRender 10 | -------------------------------------------------------------------------------- /lib/ruby-processing/runners/live.rb: -------------------------------------------------------------------------------- 1 | # A pry shell for live coding. 2 | # Will start with your sketch. 3 | require_relative 'base' 4 | Processing.load_and_run_sketch 5 | 6 | class PryException < StandardError 7 | end 8 | 9 | MESSAGE = "You need to 'jruby -S gem install pry' for 'live' mode" 10 | 11 | if Gem::Specification.find_all_by_name('pry').any? 12 | require 'pry' 13 | $app.pry 14 | else 15 | fail(PryException.new, MESSAGE) 16 | end 17 | -------------------------------------------------------------------------------- /lib/ruby-processing/config.rb: -------------------------------------------------------------------------------- 1 | require 'psych' 2 | 3 | module Processing 4 | 5 | if ENV['EXPORTED'].eql?('true') 6 | RP_CONFIG = { 'PROCESSING_ROOT' => RP5_ROOT, 'JRUBY' => 'false' } 7 | end 8 | unless defined? RP_CONFIG 9 | begin 10 | CONFIG_FILE_PATH = File.expand_path('~/.rp5rc') 11 | RP_CONFIG = (Psych.load_file(CONFIG_FILE_PATH)) 12 | rescue 13 | warn('WARNING: you need to set PROCESSING_ROOT in ~/.rp5rc') 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/sketches/pdf.rb: -------------------------------------------------------------------------------- 1 | # One Frame. 2 | # 3 | # Saves one PDF with the contents of the display window. 4 | # Because this example uses beginRecord, the image is shown 5 | # on the display window and is saved to the file. 6 | 7 | load_library 'pdf' 8 | include_package 'processing.pdf' 9 | 10 | def setup 11 | size(600, 600) 12 | begin_record(PDF, "line.pdf") 13 | background(255) 14 | stroke(0, 20) 15 | strokeWeight(20.0) 16 | line(200, 0, 400, height) 17 | end_record 18 | end 19 | 20 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | rbest/vim 2 | :syntax onsketches/sketches/ 3 | b 4 | ======== 5 | sketches/ 6 | OK strictly thesketches/ould dsketches/est objects, rather than the indirect tests on io here. These test make use of minitest capture_io (this seems to require real files rather than temp files?). Also there seems to be some problem with how the built in version minitest run so I've specified gem "minitest". The graphics test is designed to fail if your graphics setup does not supports opengl 3+, this may not be fatal, but as message states is probably suboptimal. 7 | sketches/ 8 | [gist]:https://gist.githusketches/kstone/6145906 9 | -------------------------------------------------------------------------------- /lib/ruby-processing.rb: -------------------------------------------------------------------------------- 1 | # Ruby-Processing is for Code Art. 2 | # Send suggestions, ideas, and hate-mail to jashkenas [at] gmail.com 3 | # Also, send samples and libraries. 4 | unless defined? RP5_ROOT 5 | $LOAD_PATH << File.expand_path(File.dirname(__FILE__)) 6 | RP5_ROOT = File.expand_path(File.dirname(__FILE__) + '/../') 7 | end 8 | 9 | SKETCH_ROOT ||= Dir.pwd 10 | 11 | require 'ruby-processing/version' 12 | require 'ruby-processing/helpers/numeric' 13 | require 'ruby-processing/helpers/range' 14 | 15 | # The top-level namespace, a home for all Ruby-Processing classes. 16 | module Processing 17 | require 'ruby-processing/runner' 18 | end 19 | -------------------------------------------------------------------------------- /lib/ruby-processing/helpers/camel_string.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | # Avoid the monkey patching of String for camelize 4 | class CamelString 5 | extend Forwardable 6 | def_delegators(:@string, *String.public_instance_methods(false)) 7 | def initialize(str = 'no_name') 8 | @string = (str.length > 60) ? 'long_name' : str 9 | end 10 | 11 | def camelize(first_letter_in_uppercase = true) 12 | if first_letter_in_uppercase 13 | @string.gsub(%r{\/(.?)}) { '::' + Regexp.last_match[1].upcase } 14 | .gsub(/(^|_)(.)/) { Regexp.last_match[2].upcase } 15 | else 16 | @string[0] + camelize[1..-1] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/vec3/Vec3Library.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath.vec3; 2 | 3 | import java.io.IOException; 4 | import org.jruby.Ruby; 5 | import org.jruby.runtime.load.Library; 6 | 7 | /** 8 | * 9 | * @author Martin Prout 10 | */ 11 | public class Vec3Library implements Library { 12 | 13 | /** 14 | * 15 | * @param runtime 16 | */ 17 | public static void load(final Ruby runtime) { 18 | Vec3.createVec3(runtime); 19 | } 20 | 21 | /** 22 | * 23 | * @param runtime 24 | * @param wrap 25 | * @throws IOException 26 | */ 27 | @Override 28 | public void load(final Ruby runtime, boolean wrap) throws IOException { 29 | load(runtime); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/monkstone/arcball/ArcballLibrary.java: -------------------------------------------------------------------------------- 1 | package monkstone.arcball; 2 | 3 | import java.io.IOException; 4 | import org.jruby.Ruby; 5 | import org.jruby.runtime.load.Library; 6 | 7 | /** 8 | * 9 | * @author Martin Prout 10 | */ 11 | public class ArcballLibrary implements Library { 12 | 13 | /** 14 | * 15 | * @param runtime 16 | */ 17 | 18 | public static void load(final Ruby runtime) { 19 | Rarcball.createArcBall(runtime); 20 | } 21 | 22 | /** 23 | * 24 | * @param runtime 25 | * @param wrap 26 | * @throws IOException 27 | */ 28 | @Override 29 | public void load(final Ruby runtime, boolean wrap) throws IOException { 30 | load(runtime); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/vec2/Vec2Library.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath.vec2; 2 | 3 | import java.io.IOException; 4 | import org.jruby.Ruby; 5 | import org.jruby.runtime.load.Library; 6 | 7 | 8 | /** 9 | * 10 | * @author Martin Prout 11 | */ 12 | public class Vec2Library implements Library{ 13 | 14 | /** 15 | * 16 | * @param runtime 17 | */ 18 | public static void load(final Ruby runtime) { 19 | Vec2.createVec2(runtime); 20 | } 21 | 22 | /** 23 | * 24 | * @param runtime 25 | * @param wrap 26 | * @throws IOException 27 | */ 28 | @Override 29 | public void load(final Ruby runtime, boolean wrap) throws IOException { 30 | load(runtime); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/monkstone/fastmath/DeglutLibrary.java: -------------------------------------------------------------------------------- 1 | package monkstone.fastmath; 2 | 3 | import java.io.IOException; 4 | import org.jruby.Ruby; 5 | import org.jruby.runtime.load.Library; 6 | 7 | 8 | /** 9 | * 10 | * @author Martin Prout 11 | */ 12 | public class DeglutLibrary implements Library { 13 | 14 | /** 15 | * 16 | * @param runtime 17 | */ 18 | public static void load(final Ruby runtime) { 19 | Deglut.createDeglut(runtime); 20 | } 21 | 22 | /** 23 | * 24 | * @param runtime 25 | * @param wrap 26 | * @throws IOException 27 | */ 28 | @Override 29 | public void load(final Ruby runtime, boolean wrap) throws IOException { 30 | load(runtime); 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /test/deglut_spec_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/rpextras' 7 | 8 | Java::MonkstoneFastmath::DeglutLibrary.new.load(JRuby.runtime, false) 9 | 10 | EPSILON ||= 1.0e-04 11 | TO_RADIAN = Math::PI / 180 12 | 13 | Dir.chdir(File.dirname(__FILE__)) 14 | 15 | class DeglutTest < Minitest::Test 16 | def test_cos_sin 17 | (-720..720).step(1) do |deg| 18 | sine = DegLut.sin(deg) 19 | deg_sin = Math.sin(deg * TO_RADIAN) 20 | assert_in_delta(sine, deg_sin, EPSILON) 21 | cosine = DegLut.cos(deg) 22 | deg_cos = Math.cos(deg * TO_RADIAN) 23 | assert_in_delta(cosine, deg_cos, EPSILON) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /src/monkstone/videoevent/VideoInterface.java: -------------------------------------------------------------------------------- 1 | package monkstone.videoevent; 2 | import processing.video.Movie; 3 | import processing.video.Capture; 4 | /** 5 | * This interface makes it easier/possible to use the reflection methods 6 | * from Movie and Capture classes in Processing::App in ruby-processing 7 | * @author Martin Prout 8 | */ 9 | public interface VideoInterface { 10 | /** 11 | * Used to implement reflection method in PApplet 12 | * @see processing.video.Movie 13 | * @param movie Movie 14 | */ 15 | public void movieEvent(Movie movie); 16 | /** 17 | * Used to implement reflection method in PApplet 18 | * @see processing.video.Capture 19 | * @param capture Capture 20 | */ 21 | public void captureEvent(Capture capture); 22 | } 23 | -------------------------------------------------------------------------------- /test/sketches/export_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'minitest/autorun' 3 | 4 | Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) 5 | 6 | Dir.chdir(File.dirname(__FILE__)) 7 | 8 | class Rp5Test < Minitest::Test 9 | 10 | def test_normal 11 | out, _err_ = capture_io do 12 | open('|../bin/rp5 app pdf.rb', 'r') do |io| 13 | while l = io.gets 14 | puts(l.chop) 15 | end 16 | end 17 | end 18 | assert_match(/ok/, out, 'Failed PDF sketch') 19 | end 20 | 21 | def test_p3d 22 | out, _err_ = capture_io do 23 | open('|../bin/rp5 app p3d.rb', 'r') do |io| 24 | while l = io.gets 25 | puts(l.chop) 26 | end 27 | end 28 | end 29 | assert_match(/ok/, out, 'Failed P3D sketch') 30 | end 31 | end -------------------------------------------------------------------------------- /lib/ruby-processing/helpers/string_extra.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | # Avoid the monkey patching of String for underscore/titleize/humanize 4 | class StringExtra 5 | extend Forwardable 6 | def_delegators(:@string, *String.public_instance_methods(false)) 7 | def initialize(str = 'no_name') 8 | @string = (str.length > 60) ? 'long_name' : str 9 | end 10 | 11 | def titleize 12 | gsub(/::/, '/') 13 | .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 14 | .gsub(/([a-z\d])([A-Z])/, '\1_\2') 15 | .tr('-', '_') 16 | .downcase 17 | .gsub(/_id$/, '') 18 | .gsub(/_/, ' ').capitalize 19 | .gsub(/\b([a-z])/) { Regexp.last_match[1].capitalize } 20 | end 21 | 22 | def humanize 23 | gsub(/_id$/, '').gsub(/_/, ' ').capitalize 24 | end 25 | 26 | def underscore 27 | gsub(/::/, '/') 28 | .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 29 | .gsub(/([a-z\d])([A-Z])/, '\1_\2') 30 | .tr('-', '_') 31 | .downcase 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/JRender.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath; 2 | 3 | /** 4 | * 5 | * @author Martin Prout 6 | */ 7 | public interface JRender { 8 | 9 | /** 10 | * 11 | * @param x 12 | * @param y 13 | */ 14 | public void vertex(double x, double y); 15 | 16 | /** 17 | * 18 | * @param x 19 | * @param y 20 | */ 21 | public void curveVertex(double x, double y); 22 | 23 | /** 24 | * 25 | * @param x 26 | * @param y 27 | * @param z 28 | */ 29 | public void vertex(double x, double y, double z); 30 | 31 | /** 32 | * 33 | * @param x 34 | * @param y 35 | * @param z 36 | * @param u 37 | * @param v 38 | */ 39 | public void vertex(double x, double y, double z, double u, double v); 40 | 41 | /** 42 | * 43 | * @param x 44 | * @param y 45 | * @param z 46 | */ 47 | public void curveVertex(double x, double y, double z); 48 | 49 | /** 50 | * 51 | * @param x 52 | * @param y 53 | * @param z 54 | */ 55 | public void normal(double x, double y, double z); 56 | } 57 | -------------------------------------------------------------------------------- /test/aabb_spec_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/rpextras' 7 | require_relative '../lib/ruby-processing/helpers/aabb' 8 | 9 | Java::MonkstoneVecmathVec2::Vec2Library.new.load(JRuby.runtime, false) 10 | Java::MonkstoneVecmathVec3::Vec3Library.new.load(JRuby.runtime, false) 11 | 12 | EPSILON ||= 1.0e-04 13 | 14 | Dir.chdir(File.dirname(__FILE__)) 15 | 16 | class MathToolTest < Minitest::Test 17 | def test_aabb_new 18 | x, y = 1.0000001, 1.01 19 | a = Vec2D.new(x, y) 20 | assert AaBb.new(center: Vec2D.new, extent: a).kind_of? AaBb 21 | x0, y0 = -4, -4 22 | a = Vec2D.new(x0, y0) 23 | b = a *= -1 24 | assert AaBb.from_min_max(min: a, max: b).kind_of? AaBb 25 | x, y = 1.0000001, 1.01 26 | a = AaBb.new(center: Vec2D.new, extent: Vec2D.new(x, y)) 27 | a.position(Vec2D.new(4, 6)) 28 | assert a.center == Vec2D.new(4, 6) 29 | x, y = 1.0000001, 1.01 30 | a = AaBb.new(center: Vec2D.new, extent: Vec2D.new(x, y)) 31 | a.position(Vec2D.new(4, 6)) { false } 32 | assert a.center == Vec2D.new 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /src/monkstone/MathToolLibrary.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this class is to load the MathTool into ruby-processing runtime 3 | * Copyright (C) 2015-16 Martin Prout. This code is free software; you can 4 | * redistribute it and/or modify it under the terms of the GNU Lesser General 5 | * Public License as published by the Free Software Foundation; either version 6 | * 2.1 of the License, or (at your option) any later version. 7 | * 8 | * Obtain a copy of the license at http://www.gnu.org/licenses/lgpl-2.1.html 9 | */ 10 | 11 | package monkstone; 12 | 13 | import java.io.IOException; 14 | import org.jruby.Ruby; 15 | import org.jruby.runtime.load.Library; 16 | 17 | 18 | /** 19 | * 20 | * @author Martin Prout 21 | */ 22 | public class MathToolLibrary implements Library{ 23 | 24 | /** 25 | * 26 | * @param runtime 27 | */ 28 | public static void load(final Ruby runtime) { 29 | MathTool.createMathTool(runtime); 30 | } 31 | 32 | /** 33 | * 34 | * @param runtime 35 | * @param wrap 36 | * @throws java.io.IOException 37 | */ 38 | @Override 39 | public void load(final Ruby runtime, boolean wrap) throws IOException { 40 | load(runtime); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/monkstone/arcball/WheelHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Martin Prout 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * http://creativecommons.org/licenses/LGPL/2.1/ 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | package monkstone.arcball; 22 | 23 | /** 24 | * @author Martin Prout 25 | * from a borrowed pattern seen in Jonathan Feinbergs Peasycam 26 | * when I was struggling with non functioning browser applet, 27 | * probably superfluous here. 28 | */ 29 | public interface WheelHandler { 30 | /** 31 | * 32 | * @param amount 33 | */ 34 | 35 | public void handleWheel(final int amount); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require_relative 'lib/ruby-processing/version' 2 | 3 | def create_manifest 4 | title = 'Implementation-Title: rpextras (java extension for ruby-processing)' 5 | version = format('Implementation-Version: %s', RubyProcessing::VERSION) 6 | file = File.open('MANIFEST.MF', 'w') do |f| 7 | f.puts(title) 8 | f.puts(version) 9 | end 10 | end 11 | 12 | task :default => [:init, :compile, :test, :gem] 13 | 14 | desc 'Create Manifest' 15 | task :init do 16 | create_manifest 17 | end 18 | 19 | desc 'Build gem' 20 | task :gem do 21 | sh 'gem build ruby-processing.gemspec' 22 | end 23 | 24 | desc 'Compile' 25 | task :compile do 26 | sh 'mvn package' 27 | sh 'mv target/rpextras.jar lib' 28 | end 29 | 30 | desc 'Test' 31 | task :test do 32 | sh 'jruby test/vecmath_spec_test.rb' 33 | sh 'jruby test/deglut_spec_test.rb' 34 | sh 'jruby test/math_tool_test.rb' 35 | home = File.expand_path('~') 36 | config = File.exist?(format('%s/.rp5rc', home)) 37 | if config 38 | sh 'jruby test/helper_methods_test.rb' 39 | ruby 'test/rp5_run_test.rb' 40 | else 41 | warn format('You should create %s/.rp5rc to run sketch tests', home) 42 | end 43 | end 44 | 45 | desc 'Clean' 46 | task :clean do 47 | Dir['./**/*.%w{jar gem}'].each do |path| 48 | puts format('Deleting %s ...', path) 49 | File.delete(path) 50 | end 51 | FileUtils.rm_rf('./tmp') 52 | end 53 | -------------------------------------------------------------------------------- /lib/ruby-processing/runners/base.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | 3 | SKETCH_PATH ||= ARGV.shift 4 | SKETCH_ROOT ||= File.dirname(SKETCH_PATH) 5 | 6 | # we can safely require app.rb as we are using a jruby runtime 7 | require_relative '../app' 8 | 9 | # More processing module 10 | module Processing 11 | # For use with "bare" sketches that don't want to define a class or methods 12 | BARE_WRAP = <<-EOS 13 | class Sketch < Processing::App 14 | %s 15 | end 16 | EOS 17 | 18 | NAKED_WRAP = <<-EOS 19 | class Sketch < Processing::App 20 | def setup 21 | size(DEFAULT_WIDTH, DEFAULT_HEIGHT) 22 | %s 23 | no_loop 24 | end 25 | end 26 | EOS 27 | 28 | # This method is the common entry point to run a sketch, bare or complete. 29 | 30 | def self.run_app 31 | load SKETCH_PATH 32 | Processing::App.sketch_class.new unless $app 33 | end 34 | 35 | def self.load_and_run_sketch 36 | source = read_sketch_source 37 | wrapped = !source.match(/^[^#]*< Processing::App/).nil? 38 | no_methods = source.match(/^[^#]*(def\s+setup|def\s+draw)/).nil? 39 | if wrapped 40 | run_app 41 | return 42 | end 43 | code = no_methods ? format(NAKED_WRAP, source) : format(BARE_WRAP, source) 44 | Object.class_eval(code, SKETCH_PATH, -1) 45 | Processing::App.sketch_class.new 46 | end 47 | 48 | # Read in the sketch source code. Needs to work both online and offline. 49 | def self.read_sketch_source 50 | File.read(SKETCH_PATH) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Ruby-Processing is released under the MIT License. 2 | You can do pretty much whatever you'd like with it. 3 | 4 | ___ 5 | 6 | Copyright (c) 2008-2014 omygawshkenas 7 | 8 | Permission is hereby granted, free of charge, 9 | to any person obtaining a copy of this software 10 | and associated documentation files (the "Software"), 11 | to deal in the Software without restriction, 12 | including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, 14 | and/or sell copies of the Software, and to permit 15 | persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission 19 | notice shall be included in all copies or substantial 20 | portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 23 | OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 24 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 27 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 29 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | ___ 33 | 34 | Ruby-Processing also distributes core components 35 | of both [JRuby][] and [Processing][] both of which are 36 | licensed under the GNU lesser public license 37 | 38 | [jruby]: http://www.jruby.org/ 39 | [processing]: http://www.processing.org/ 40 | -------------------------------------------------------------------------------- /src/monkstone/ColorUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this utility is to allow ruby-processing users to use an alternative 3 | * to processing.org color their sketches (to cope with ruby FixNumber vs java int) 4 | * Copyright (C) 2015-16 Martin Prout. This tool is free software; you can 5 | * redistribute it and/or modify it under the terms of the GNU Lesser General 6 | * Public License as published by the Free Software Foundation; either version 7 | * 2.1 of the License, or (at your option) any later version. 8 | * 9 | * Obtain a copy of the license at http://www.gnu.org/licenses/lgpl-2.1.html 10 | */ 11 | package monkstone; 12 | 13 | /** 14 | * 15 | * @author Martin Prout 16 | */ 17 | public class ColorUtil { 18 | 19 | /** 20 | * Returns hex long as a positive int unless greater than Integer.MAX_VALUE 21 | * else return the complement as a negative integer or something like that 22 | */ 23 | static final int hexLong(long hexlong) { 24 | long SPLIT = Integer.MAX_VALUE + 1; 25 | if (hexlong < SPLIT) { 26 | return (int) hexlong; 27 | } else { 28 | return (int) (hexlong - SPLIT * 2L); 29 | } 30 | } 31 | 32 | static public int colorString(String hexstring) { 33 | return java.awt.Color.decode(hexstring).getRGB(); 34 | } 35 | 36 | static public float colorLong(double hex) { 37 | return (float) hex; 38 | } 39 | 40 | static public int colorLong(long hexlong){ 41 | return hexLong(hexlong); 42 | } 43 | 44 | static public float colorDouble(double hex){ 45 | return (float)hex; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | In the spirit of [free software][free-sw], **everyone** is encouraged to help improve this project. 3 | 4 | Here are some ways *you* can contribute: 5 | 6 | * by reporting bugs or problems [here][] 7 | * by closing [issues][] 8 | * by proselytizing [JRubyArt][], it is the future we need more champions 9 | * by supporting [Processing.org][], nothing to do with us but we rely on them 10 | * by figuring out how we could clamp pbox2d and toxiclibs version to 0.4.2 and 0.4.0 11 | * by updating build to match [JRubyArt][] 12 | 13 | ## Submitting an Issue 14 | We use the [GitHub issue tracker][issues] to track bugs and features. Before 15 | submitting a bug report or feature request, check to make sure it has not 16 | already been submitted. When submitting a bug report, ideally include a [Gist][] 17 | that includes a stack trace and any details that may be necessary to reproduce 18 | the bug, including your gem version, Ruby version, and operating system. 19 | 20 | ## Submitting a Pull Request 21 | 1. [Fork the repository.][fork] 22 | 2. [Submit a pull request.][pr] 23 | 24 | [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html 25 | [here]: https://github.com/jashkenas/ruby-processing/issues 26 | [issues]: https://github.com/jashkenas/ruby-processing/issues 27 | [gist]: https://gist.github.com/ 28 | [fork]: http://help.github.com/fork-a-repo/ 29 | [pr]: http://help.github.com/send-pull-requests/ 30 | [processing.org]: http://processing.org/foundation/ 31 | [development branch]: https://github.com/ruby-processing/JRubyArt 32 | [contributing examples]: https://github.com/ruby-processing/Example-Sketches/blob/master/CONTRIBUTING.md 33 | [shoes/furoshoki]:https://github.com/shoes/furoshiki 34 | [JRubyArt]:https://github.com/ruby-processing/JRubyArt 35 | -------------------------------------------------------------------------------- /src/monkstone/arcball/Constrain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Martin Prout 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * http://creativecommons.org/licenses/LGPL/2.1/ 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | package monkstone.arcball; 22 | 23 | /** 24 | * 25 | * @author Martin Prout 26 | */ 27 | public enum Constrain { 28 | 29 | /** 30 | * Used to constrain arc-ball rotation about X axis 31 | */ 32 | 33 | XAXIS(0), 34 | /** 35 | * Used to constrain arc-ball rotation about Y axis 36 | */ 37 | YAXIS(1), 38 | /** 39 | * Used to constrain arc-ball rotation about Z axis 40 | */ 41 | ZAXIS(2), 42 | /** 43 | * Used for default no constrain arc-ball about any axis 44 | */ 45 | FREE(-1); 46 | private final int index; 47 | 48 | Constrain(int idx) { 49 | this.index = idx; 50 | } 51 | 52 | /** 53 | * Numeric value of constrained axis 54 | * @return index int 55 | */ 56 | public int index() { 57 | return index; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pom.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | project 'rp5extras', 'https://github.com/jashkenas/ruby-processing' do 3 | model_version '4.0.0' 4 | id 'ruby-processing:rp5extras', '2.7.1' 5 | packaging 'jar' 6 | description 'rp5extras for ruby-processing' 7 | organization 'ruby-processing', 'https://ruby-processing.github.io' 8 | developer 'monkstone' do 9 | name 'Martin Prout' 10 | email 'mamba2928@yahoo.co.uk' 11 | roles 'developer' 12 | end 13 | 14 | issue_management 'https://github.com/jashkenas/ruby-processing/issues', 'Github' 15 | 16 | source_control( 17 | url: 'https://github.com/jashkenas/ruby-processing', 18 | connection: 'scm:git:git://github.com/jashkenas/ruby-processing.git', 19 | developer_connection: 'scm:git:git@github.com/jashkenas/ruby-processing.git' 20 | ) 21 | 22 | properties( 23 | 'maven.compiler.source' => '1.7', 24 | 'project.build.sourceEncoding' => 'UTF-8', 25 | 'maven.compiler.target' => '1.7', 26 | 'polyglot.dump.pom' => 'pom.xml', 27 | 'processing.api' => 'http://processing.github.io/processing-javadocs/core/', 28 | 'jruby.api' => 'http://jruby.org/apidocs/' 29 | ) 30 | 31 | pom 'org.jruby:jruby:1.7.25' 32 | jar 'org.processing:core:2.2.1' 33 | jar 'org.processing:video:2.2.1' 34 | plugin_management do 35 | plugin :resources, '2.6' 36 | plugin :dependency, '2.10' 37 | plugin( 38 | :compiler, '3.5.1', 39 | 'source' => '1.7', 40 | 'target' => '1.7' 41 | ) 42 | plugin( 43 | :javadoc, '2.10.4', 44 | 'detectOfflineLinks' => 'false', 45 | 'links' => ['${processing.api}', '${jruby.api}'] 46 | ) 47 | plugin( 48 | :jar, '3.0.2', 49 | 'archive' => { 50 | 'manifestFile' => 'MANIFEST.MF' 51 | } 52 | ) 53 | end 54 | build do 55 | default_goal 'package' 56 | source_directory 'src' 57 | final_name 'rpextras' 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/AppRender.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath; 2 | 3 | import processing.core.PApplet; 4 | 5 | /** 6 | * 7 | * @author Martin Prout 8 | */ 9 | public class AppRender implements JRender { 10 | 11 | final PApplet app; 12 | 13 | /** 14 | * 15 | * @param app 16 | */ 17 | public AppRender(final PApplet app) { 18 | this.app = app; 19 | } 20 | 21 | /** 22 | * 23 | * @param x 24 | * @param y 25 | */ 26 | @Override 27 | public void vertex(double x, double y) { 28 | app.vertex((float) x, (float) y); 29 | } 30 | 31 | /** 32 | * 33 | * @param x 34 | * @param y 35 | */ 36 | @Override 37 | public void curveVertex(double x, double y) { 38 | app.curveVertex((float) x, (float) y); 39 | } 40 | 41 | /** 42 | * 43 | * @param x 44 | * @param y 45 | * @param z 46 | */ 47 | @Override 48 | public void vertex(double x, double y, double z) { 49 | app.vertex((float) x, (float) y, (float) z); 50 | } 51 | 52 | /** 53 | * 54 | * @param x 55 | * @param y 56 | * @param z 57 | */ 58 | @Override 59 | public void normal(double x, double y, double z) { 60 | app.normal((float) x, (float) y, (float) z); 61 | } 62 | 63 | /** 64 | * 65 | * @param x 66 | * @param y 67 | * @param z 68 | * @param u 69 | * @param v 70 | */ 71 | @Override 72 | public void vertex(double x, double y, double z, double u, double v) { 73 | app.vertex((float) x, (float) y, (float) z, (float) u, (float) v); 74 | } 75 | 76 | /** 77 | * 78 | * @param x 79 | * @param y 80 | * @param z 81 | */ 82 | @Override 83 | public void curveVertex(double x, double y, double z) { 84 | app.curveVertex((float) x, (float) y, (float) z); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /lib/templates/application/Contents/Info.plist.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | <%= @title %> 7 | CFBundleVersion 8 | 1.0 9 | CFBundleAllowMixedLocalizations 10 | true 11 | CFBundleExecutable 12 | JavaApplicationStub 13 | CFBundleDevelopmentRegion 14 | English 15 | CFBundlePackageType 16 | APPL 17 | CFBundleSignature 18 | ???? 19 | CFBundleInfoDictionaryVersion 20 | 6.0 21 | CFBundleIconFile 22 | sketch.icns 23 | CFBundleIdentifier 24 | org.ruby-processing.<%= @class_name %> 25 | Java 26 | 27 | VMOptions 28 | -Xms756M -Xmx756M 29 | MainClass 30 | org.jruby.Main 31 | WorkingDirectory 32 | $JAVAROOT 33 | Arguments 34 | ruby-processing/runners/run.rb <%= @main_file %> 35 | JVMVersion 36 | 1.7+ 37 | ClassPath 38 | <%= @class_path %> 39 | Properties 40 | 41 | apple.laf.useScreenMenuBar 42 | true 43 | apple.awt.showGrowBox 44 | false 45 | com.apple.smallTabs 46 | true 47 | apple.awt.Antialiasing 48 | false 49 | apple.awt.TextAntialiasing 50 | true 51 | com.apple.hwaccel 52 | true 53 | apple.awt.use-file-dialog-packages 54 | false 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/ShapeRender.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath; 2 | 3 | import processing.core.PShape; 4 | 5 | /** 6 | * 7 | * @author Martin Prout 8 | */ 9 | public class ShapeRender implements JRender { 10 | 11 | final PShape shape; 12 | 13 | /** 14 | * 15 | * @param shape 16 | */ 17 | public ShapeRender(final PShape shape) { 18 | this.shape = shape; 19 | 20 | } 21 | 22 | /** 23 | * 24 | * @param x 25 | * @param y 26 | */ 27 | @Override 28 | public void vertex(double x, double y) { 29 | shape.vertex((float) x, (float) y); 30 | } 31 | 32 | /** 33 | * 34 | * @param x 35 | * @param y 36 | */ 37 | @Override 38 | public void curveVertex(double x, double y) { 39 | throw new UnsupportedOperationException("Not implemented for this renderer"); 40 | } 41 | 42 | /** 43 | * 44 | * @param x 45 | * @param y 46 | * @param z 47 | */ 48 | @Override 49 | public void vertex(double x, double y, double z) { 50 | shape.vertex((float) x, (float) y, (float) z); 51 | } 52 | 53 | /** 54 | * 55 | * @param x 56 | * @param y 57 | * @param z 58 | */ 59 | @Override 60 | public void normal(double x, double y, double z) { 61 | shape.normal((float) x, (float) y, (float) z); 62 | } 63 | 64 | /** 65 | * 66 | * @param x 67 | * @param y 68 | * @param z 69 | * @param u 70 | * @param v 71 | */ 72 | @Override 73 | public void vertex(double x, double y, double z, double u, double v) { 74 | shape.vertex((float) x, (float) y, (float) z, (float) u, (float) v); 75 | } 76 | 77 | /** 78 | * 79 | * @param x 80 | * @param y 81 | * @param z 82 | */ 83 | @Override 84 | public void curveVertex(double x, double y, double z) { 85 | throw new UnsupportedOperationException("Not implemented for this renderer"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/helper_methods_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/ruby-processing/helper_methods' 7 | 8 | include Processing::HelperMethods 9 | 10 | EPSILON ||= 1.0e-04 11 | 12 | Java::Monkstone::MathToolLibrary.new.load(JRuby.runtime, false) 13 | 14 | include Processing::HelperMethods 15 | include Processing::MathTool 16 | 17 | EPSILON ||= 1.0e-04 18 | 19 | Dir.chdir(File.dirname(__FILE__)) 20 | 21 | class HelperMethodsTest < Minitest::Test 22 | def test_hex_color 23 | col_double = 0.5 24 | hexcolor = 0xFFCC6600 25 | dodgy_hexstring = '*56666' 26 | hexstring = '#CC6600' 27 | assert hex_color(col_double) == 0.5, 'double as a color' 28 | assert hex_color(hexcolor) == -3381760, 'hexadecimal fixnum color' 29 | assert hex_color(hexstring) == -3381760, 'hexadecimal string color' 30 | assert_raises(StandardError, 'Dodgy Hexstring') do 31 | hex_color(dodgy_hexstring) 32 | end 33 | end 34 | 35 | def test_dist 36 | ax, ay, bx, by = 0, 0, 1.0, 1.0 37 | assert dist(ax, ay, bx, by) == Math.sqrt(2), '2D distance' 38 | by = 0.0 39 | assert dist(ax, ay, bx, by) == 1.0, 'when y dimension is zero' 40 | ax, ay, bx, by = 0, 0, 0.0, 0.0 41 | assert dist(ax, ay, bx, by) == 0.0, 'when x and y dimension are zero' 42 | ax, ay, bx, by = 1, 1, -2.0, -3.0 43 | assert dist(ax, ay, bx, by) == 5, 'classic triangle dimensions' 44 | ax, ay, bx, by, cx, cy = -1, -1, 100, 2.0, 3.0, 100 45 | assert dist(ax, ay, bx, by, cx, cy) == 5, 'classic triangle dimensions' 46 | ax, ay, bx, by, cx, cy = 0, 0, -1.0, -1.0, 0, 0 47 | assert dist(ax, ay, bx, by, cx, cy) == Math.sqrt(2) 48 | ax, ay, bx, by, cx, cy = 0, 0, 0.0, 0.0, 0, 0 49 | assert dist(ax, ay, bx, by, cx, cy) == 0.0 50 | ax, ay, bx, by, cx, cy = 0, 0, 1.0, 0.0, 0, 0 51 | assert dist(ax, ay, bx, by, cx, cy) == 1.0, 'when x and z dimension are zero' 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /ruby-processing.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'ruby-processing/version' 5 | require 'rake' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "ruby-processing" 9 | spec.version = RubyProcessing::VERSION 10 | spec.authors = %w(Jeremy\ Ashkenas Peter\ Gassner\ Martin\ Stannard\ Andrew\ Nanton 11 | Marc\ Chung Peter\ Krenn Florian\ Jenett Andreas\ Haller 12 | Juris\ Galang Guillaume\ Pierronnet Martin\ Prout) 13 | spec.email = "jeremy@ashkenas.com" 14 | spec.description = <<-EOS 15 | Ruby-Processing is a ruby wrapper for the processing-2.0 art framework. 16 | This version supports processing-2.2.1, and uses jruby-complete-1.7.26 or an 17 | installed jruby as the glue between ruby and java. Use both processing 18 | libraries and ruby gems in your sketches. The "watch" mode, provides a 19 | nice REPL-ish way to work on your processing sketches. Features a polyglot 20 | maven build, opening the way to use/test latest jruby. 21 | EOS 22 | spec.summary = %q{Code as Art, Art as Code. Processing and Ruby are meant for each other.} 23 | spec.homepage = "http://wiki.github.com/jashkenas/ruby-processing" 24 | spec.post_install_message = %q{Use 'rp5 setup install' to install jruby-complete, and 'rp5 setup check' to check config.} 25 | spec.license = 'MIT' 26 | 27 | spec.files = FileList['bin/**/*', 'lib/**/*', 'library/**/*', 'samples/**/*', 'vendors/Rakefile'].exclude(/jar/).to_a 28 | spec.files << 'lib/rpextras.jar' 29 | 30 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 31 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 32 | spec.require_paths = ["lib"] 33 | spec.required_ruby_version = '>= 1.9.3' 34 | 35 | spec.add_development_dependency "bundler", "~> 1.10" 36 | spec.add_development_dependency "rake", "~> 10.4" 37 | spec.add_development_dependency "minitest", "~> 5.8" 38 | spec.requirements << 'A decent graphics card' 39 | spec.requirements << 'java runtime >= 1.7+' 40 | spec.requirements << 'processing = 2.2.1' 41 | end 42 | 43 | -------------------------------------------------------------------------------- /lib/ruby-processing/runners/watch.rb: -------------------------------------------------------------------------------- 1 | require_relative 'base' 2 | 3 | module Processing 4 | # A sketch loader, observer, and reloader, to tighten 5 | # the feedback between code and effect. 6 | class Watcher 7 | # Sic a new Processing::Watcher on the sketch 8 | WATCH_MESSAGE ||= <<-EOS 9 | Warning: 10 | To protect you from running watch mode in a top level 11 | directory with lots of nested ruby or GLSL files we 12 | limit the number of files to watch to %d. 13 | If you really want to watch %d files you should 14 | increase MAX_WATCH in ~/.rp5rc 15 | 16 | EOS 17 | SLEEP_TIME = 0.2 18 | def initialize 19 | reload_files_to_watch 20 | @time = Time.now 21 | start_watching 22 | end 23 | 24 | # Kicks off a thread to watch the sketch, reloading Ruby-Processing 25 | # and restarting the sketch whenever it changes. 26 | def start_watching 27 | start_runner 28 | Kernel.loop do 29 | if @files.find { |file| FileTest.exist?(file) && File.stat(file).mtime > @time } 30 | puts 'reloading sketch...' 31 | $app && $app.close 32 | @time = Time.now 33 | java.lang.System.gc 34 | start_runner 35 | reload_files_to_watch 36 | end 37 | sleep SLEEP_TIME 38 | end 39 | end 40 | 41 | # Convenience function to report errors when loading and running a sketch, 42 | # instead of having them eaten by the thread they are loaded in. 43 | def report_errors 44 | yield 45 | rescue Exception => e 46 | wformat = 'Exception occured while running sketch %s...' 47 | tformat = "Backtrace:\n\t%s" 48 | warn format(wformat, File.basename(SKETCH_PATH)) 49 | puts format(tformat, e.backtrace.join("\n\t")) 50 | end 51 | 52 | def start_runner 53 | @runner.kill if @runner && @runner.alive? 54 | @runner = Thread.start do 55 | report_errors do 56 | Processing.load_and_run_sketch 57 | end 58 | end 59 | end 60 | 61 | def reload_files_to_watch 62 | @files = Dir.glob(File.join(SKETCH_ROOT, '**/*.{rb,glsl}')) 63 | count = @files.length 64 | max_watch = RP_CONFIG.fetch('MAX_WATCH', 20) 65 | return unless count > max_watch 66 | warn format(WATCH_MESSAGE, max_watch, count) 67 | abort 68 | end 69 | end 70 | end 71 | 72 | Processing::Watcher.new 73 | -------------------------------------------------------------------------------- /library/file_chooser/file_chooser.rb: -------------------------------------------------------------------------------- 1 | # Here's a little library for using swing JFileChooser. 2 | # in ruby-processing, borrows heavily from control_panel 3 | 4 | module FileChooser 5 | ## 6 | # FileFilter is abstract, requires accept and getDescription 7 | ## 8 | 9 | require 'pathname' 10 | JXChooser = Java::javax::swing::JFileChooser 11 | JFile = Java::java::io::File 12 | System = Java::JavaLang::System 13 | 14 | class Filter < Java::javax::swing::filechooser::FileFilter 15 | attr_reader :description, :extensions 16 | def define(description, extensions) 17 | @description, @extensions = description, extensions 18 | end 19 | 20 | def accept(fobj) 21 | return true if extensions.include? File.extname(fobj.to_s).downcase 22 | return true if fobj.isDirectory 23 | end 24 | 25 | def getDescription 26 | description 27 | end 28 | end 29 | 30 | class RXChooser 31 | java_import javax.swing.UIManager 32 | require 'rbconfig' 33 | HOST = 'host_os' 34 | UHOME = 'user.home' 35 | UDIR = 'user.dir' 36 | OS = :unix 37 | case RbConfig::CONFIG[HOST] 38 | when /darwin/ then OS = :mac 39 | when /mswin|mingw/ then OS = :windows 40 | end 41 | 42 | def initialize 43 | javax.swing.UIManager.setLookAndFeel( 44 | javax.swing.UIManager.getSystemLookAndFeelClassName) 45 | @chooser = JXChooser.new 46 | end 47 | 48 | def set_filter(description, extensions) 49 | filter = FileChooser::Filter.new 50 | filter.define(description, extensions) 51 | @chooser.setFileFilter(filter) 52 | end 53 | 54 | def display 55 | if :windows == OS 56 | @chooser.setCurrentDirectory(JFile.new(System.getProperty(UDIR))) 57 | else 58 | @chooser.setCurrentDirectory(JFile.new(System.getProperty(UHOME))) 59 | end 60 | success = @chooser.show_open_dialog($app) 61 | if success == JXChooser::APPROVE_OPTION 62 | return Pathname.new(@chooser.get_selected_file.get_absolute_path).to_s 63 | else 64 | nil 65 | end 66 | end 67 | 68 | def dispose 69 | @chooser = nil 70 | end 71 | end 72 | 73 | module InstanceMethods 74 | def file_chooser 75 | @chooser = RXChooser.new 76 | return @chooser unless block_given? 77 | yield(@chooser) 78 | end 79 | end 80 | end 81 | 82 | Processing::App.send :include, FileChooser::InstanceMethods 83 | -------------------------------------------------------------------------------- /src/monkstone/arcball/Rarcball.java: -------------------------------------------------------------------------------- 1 | package monkstone.arcball; 2 | 3 | import org.jruby.Ruby; 4 | import org.jruby.RubyClass; 5 | import org.jruby.RubyModule; 6 | import org.jruby.RubyObject; 7 | import org.jruby.anno.JRubyClass; 8 | import org.jruby.anno.JRubyMethod; 9 | import org.jruby.runtime.Arity; 10 | import org.jruby.runtime.ThreadContext; 11 | import org.jruby.runtime.builtin.IRubyObject; 12 | import processing.core.PApplet; 13 | 14 | /** 15 | * 16 | * @author Martin Prout 17 | */ 18 | @JRubyClass(name = "ArcBall") 19 | public class Rarcball extends RubyObject { 20 | 21 | private static final long serialVersionUID = -8164248008668234947L; 22 | 23 | /** 24 | * 25 | * @param runtime 26 | */ 27 | public static void createArcBall(final Ruby runtime) { 28 | RubyModule processing = runtime.defineModule("Processing"); 29 | RubyModule arcBallModule = processing.defineModuleUnder("ArcBall"); 30 | arcBallModule.defineAnnotatedMethods(Rarcball.class); 31 | } 32 | 33 | /** 34 | * 35 | * @param runtime 36 | * @param metaClass 37 | */ 38 | public Rarcball(Ruby runtime, RubyClass metaClass) { 39 | super(runtime, metaClass); 40 | } 41 | 42 | /** 43 | * 44 | * @param context 45 | * @param self 46 | * @param args optional (no args jx = 0, jy = 0) 47 | */ 48 | @JRubyMethod(name = "init", meta = true, rest = true, required = 1, optional = 3) 49 | 50 | public static void init(ThreadContext context, IRubyObject self, IRubyObject args[]) { 51 | int count = Arity.checkArgumentCount(context.getRuntime(), args, 1, 4); 52 | if (count == 4) { 53 | PApplet parent = (PApplet) args[0].toJava(PApplet.class); 54 | double cx = (double) args[1].toJava(Double.class); 55 | double cy = (double) args[2].toJava(Double.class); 56 | double radius = (double) args[3].toJava(Double.class); 57 | new Arcball(parent, cx, cy, radius).setActive(true); 58 | } 59 | if (count == 3) { 60 | PApplet parent = (PApplet) args[0].toJava(PApplet.class); 61 | double cx = (double) args[1].toJava(Double.class); 62 | double cy = (double) args[2].toJava(Double.class); 63 | new Arcball(parent, cx, cy, parent.width * 0.8f).setActive(true); 64 | } 65 | if (count == 1) { 66 | PApplet parent = (PApplet) args[0].toJava(PApplet.class); 67 | new Arcball(parent).setActive(true); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/rp5_run_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'minitest/autorun' 3 | require 'minitest/pride' 4 | 5 | Dir.chdir(File.dirname(__FILE__)) 6 | 7 | class Rp5Test < Minitest::Test 8 | 9 | def test_normal 10 | out, _err_ = capture_io do 11 | open('|../bin/rp5 run sketches/basic.rb', 'r') do |io| 12 | while l = io.gets 13 | puts(l.chop) 14 | end 15 | end 16 | end 17 | assert_match(/ok/, out, 'Failed Basic Sketch') 18 | end 19 | 20 | def test_p2d 21 | out, _err_ = capture_io do 22 | open('|../bin/rp5 run sketches/p2d.rb', 'r') do |io| 23 | while l = io.gets 24 | puts(l.chop) 25 | end 26 | end 27 | end 28 | assert_match(/ok/, out, 'Failed P2D sketch') 29 | end 30 | 31 | def test_proc_root 32 | require 'psych' 33 | path = File.expand_path('~/.rp5rc') 34 | config = FileTest.exist?(path)? Psych.load_file(path) : {} 35 | root = config.empty? ? '' : config['PROCESSING_ROOT'] 36 | assert root =~ /processing/, 'You need to set your PROCESSING_ROOT in .rp5rc' 37 | end 38 | 39 | 40 | def test_p3d 41 | out, _err_ = capture_io do 42 | open('|../bin/rp5 run sketches/p3d.rb', 'r') do |io| 43 | while l = io.gets 44 | puts(l.chop) 45 | end 46 | end 47 | end 48 | assert_match(/ok/, out, 'Failed P3D sketch') 49 | end 50 | 51 | def test_graphics 52 | out, _err_ = capture_io do 53 | open('|../bin/rp5 run sketches/graphics.rb', 'r') do |io| 54 | while l = io.gets 55 | puts(l.chop) 56 | end 57 | end 58 | end 59 | assert out[0].to_i >= 3, "Graphics capability #{out} may be sub-optimal" 60 | end 61 | 62 | def test_setup_exception 63 | out, _err_ = capture_io do 64 | open('|../bin/rp5 run sketches/setup_ex.rb', 'r') do |io| 65 | while l = io.gets 66 | puts(l.chop) 67 | end 68 | end 69 | end 70 | assert out.index("undefined method `unknown_method'"), 'Failed to raise exception?' 71 | end 72 | 73 | def test_vector 74 | out, _err_ = capture_io do 75 | open('|../bin/rp5 run sketches/vector.rb', 'r') do |io| 76 | while l = io.gets 77 | puts(l.chop) 78 | end 79 | end 80 | end 81 | assert out.index(/ok/), 'Failed vector test' 82 | end 83 | 84 | def test_arcball 85 | out, _err_ = capture_io do 86 | open('|../bin/rp5 run sketches/arcball.rb', 'r') do |io| 87 | while l = io.gets 88 | puts(l.chop) 89 | end 90 | end 91 | end 92 | assert_match(/ok/, out, 'Failed arcball sketch') 93 | end 94 | end 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/test_map1d.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/rpextras' 7 | require_relative '../lib/ruby-processing/helper_methods' 8 | 9 | Java::Monkstone::MathToolLibrary.new.load(JRuby.runtime, false) 10 | 11 | include Processing::HelperMethods 12 | include Processing::MathTool 13 | 14 | EPSILON ||= 1.0e-04 15 | 16 | Dir.chdir(File.dirname(__FILE__)) 17 | 18 | class Rp5Test < Minitest::Test 19 | def test_map1d 20 | x = [0, 5, 7.5, 10] 21 | range1 = (0..10) 22 | range2 = (100..1) 23 | range3 = (0..10) 24 | range4 = (5..105) 25 | assert map1d(x[0], range1, range2) == 100, 'map to first' 26 | assert map1d(x[1], range1, range2) == 50.5, 'map to reversed intermediate' 27 | assert map1d(x[2], range3, range4) == 80.0, 'map to intermediate' 28 | assert map1d(x[3], range1, range2) == 1, 'map to last' 29 | end 30 | 31 | def test_p5map # as map1d except not using range input 32 | x = [0, 5, 7.5, 10] 33 | range1 = (0..10) 34 | range2 = (100..1) 35 | range3 = (0..10) 36 | range4 = (5..105) 37 | assert p5map(x[0], range1.first, range1.last, range2.first, range2.last) == 100 38 | assert p5map(x[1], range1.first, range1.last, range2.first, range2.last) == 50.5 39 | assert p5map(x[2], range3.first, range3.last, range4.first, range4.last) == 80.0 40 | assert p5map(x[3], range1.first, range1.last, range2.first, range2.last) == 1 41 | end 42 | 43 | def test_norm 44 | x = [10, 140, 210] 45 | start0, last0 = 30, 200 46 | start1, last1 = 0, 200 47 | assert norm(x[0], start0, last0) == -0.11764705882352941, 'unclamped map' 48 | assert norm(x[1], start1, last1) == 0.7, 'map to intermediate' 49 | assert norm(x[2], start1, last1) == 1.05, 'unclamped map' 50 | end 51 | 52 | def test_norm_strict 53 | x = [10, 140, 210] 54 | start0, last0 = 30, 200 55 | assert norm_strict(x[0], start0, last0) == 0, 'clamped map to 0..1.0' 56 | end 57 | 58 | def test_lerp # behaviour is deliberately different to processing which is unclamped 59 | x = [0.5, 0.8, 2.0] 60 | start0, last0 = 300, 200 61 | start1, last1 = 0, 200 62 | assert lerp(start0, last0, x[0]) == 250, 'produces a intermediate value of a reversed range' 63 | assert lerp(start1, last1, x[1]) == 160, 'lerps tp an intermediate value' 64 | assert lerp(start1, last1, x[2]) == 200, 'lerps to the last value of a range' 65 | end 66 | 67 | def test_constrain 68 | x = [15, 2_500, -2_500] 69 | start1, last1 = 0, 200 70 | assert constrain(x[0], start1, last1) == 15 71 | assert constrain(x[1], start1, last1) == 200 72 | assert constrain(x[2], start1, last1) == 0 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /vendors/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | 3 | WARNING = <<-EOS 4 | WARNING: you may not have wget installed, you could just download 5 | the correct version of jruby-complete to the vendors folder, and 6 | re-run rp5 setup install instead of installing wget. Some systems 7 | may also require 'sudo' access to install, NB: this is untested.... 8 | 9 | EOS 10 | 11 | JRUBYC_VERSION = '1.7.26' 12 | EXAMPLES = '1.8' 13 | HOME_DIR = ENV['HOME'] 14 | MAC_OR_LINUX = /linux|mac|darwin/ =~ RbConfig::CONFIG['host_os'] 15 | 16 | CLOBBER.include("jruby-complete-#{JRUBYC_VERSION}.jar") 17 | 18 | desc "download, and copy to ruby-processing" 19 | task :default => [:download, :copy_ruby] 20 | 21 | desc "download JRuby upstream sources" 22 | task :download => ["jruby-complete-#{JRUBYC_VERSION}.jar"] 23 | 24 | file "jruby-complete-#{JRUBYC_VERSION}.jar" do 25 | begin 26 | sh "wget https://s3.amazonaws.com/jruby.org/downloads/#{JRUBYC_VERSION}/jruby-complete-#{JRUBYC_VERSION}.jar" 27 | rescue 28 | warn(WARNING) 29 | end 30 | check_sha1("jruby-complete-#{JRUBYC_VERSION}.jar", 'c09885af02af34266ed929f94cedcf87cc965f46') 31 | end 32 | 33 | directory "../lib/ruby" 34 | 35 | desc "copy jruby-complete" 36 | task :copy_ruby => ["../lib/ruby"] do 37 | sh "cp -v jruby-complete-#{JRUBYC_VERSION}.jar ../lib/ruby/jruby-complete.jar" 38 | end 39 | 40 | def check_sha1(filename, expected_hash) 41 | require "digest/sha1" 42 | sha1 = Digest::SHA1.new 43 | File.open(filename, "r") do |f| 44 | while buf = f.read(4096) 45 | sha1.update(buf) 46 | end 47 | end 48 | if sha1.hexdigest != expected_hash 49 | raise "bad sha1 checksum for #{filename} (expected #{expected_hash} got #{sha1.hexdigest})" 50 | end 51 | end 52 | 53 | desc "download, and copy to ruby-processing" 54 | task :unpack_samples => [:download_examples, :copy_examples] 55 | 56 | desc 'download and copy examples to user home' 57 | task :download_examples 58 | file_name = (MAC_OR_LINUX.nil?) ? "#{EXAMPLES}.zip" : "#{EXAMPLES}.tar.gz" 59 | file file_name do 60 | begin 61 | if MAC_OR_LINUX.nil? 62 | sh "wget https://github.com/ruby-processing/Example-Sketches/archive/#{EXAMPLES}.zip" 63 | else 64 | sh "wget https://github.com/ruby-processing/Example-Sketches/archive/#{EXAMPLES}.tar.gz" 65 | end 66 | rescue 67 | warn(WARNING) 68 | end 69 | end 70 | 71 | desc "copy examples" 72 | task :copy_examples => file_name do 73 | if MAC_OR_LINUX.nil? 74 | sh "unzip #{EXAMPLES}.zip" 75 | else 76 | sh "tar xzvf #{EXAMPLES}.tar.gz" 77 | end 78 | sh "rm -r #{HOME_DIR}/rp_samples" if File.exist? "#{HOME_DIR}/rp_samples" 79 | sh "cp -r Example-Sketches-#{EXAMPLES} #{HOME_DIR}/rp_samples" 80 | sh "rm -r Example-Sketches-#{EXAMPLES}" 81 | end 82 | -------------------------------------------------------------------------------- /test/math_tool_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/rpextras' 7 | require_relative '../lib/ruby-processing/helper_methods' 8 | 9 | Java::Monkstone::MathToolLibrary.new.load(JRuby.runtime, false) 10 | 11 | include Processing::HelperMethods 12 | include Processing::MathTool 13 | 14 | EPSILON ||= 1.0e-04 15 | 16 | Dir.chdir(File.dirname(__FILE__)) 17 | 18 | class MathToolTest < Minitest::Test 19 | def test_map1d 20 | x = [0, 5, 7.5, 10] 21 | range1 = (0..10) 22 | range2 = (100..1) 23 | range3 = (0..10) 24 | range4 = (5..105) 25 | assert map1d(x[0], range1, range2) == 100, 'map to first' 26 | assert map1d(x[1], range1, range2) == 50.5, 'map to reversed intermediate' 27 | assert map1d(x[2], range3, range4) == 80.0, 'map to intermediate' 28 | assert map1d(x[3], range1, range2) == 1, 'map to last' 29 | end 30 | 31 | def test_p5map # as map1d except not using range input 32 | x = [0, 5, 7.5, 10] 33 | range1 = (0..10) 34 | range2 = (100..1) 35 | range3 = (0..10) 36 | range4 = (5..105) 37 | assert p5map(x[0], range1.first, range1.last, range2.first, range2.last) == 100 38 | assert p5map(x[1], range1.first, range1.last, range2.first, range2.last) == 50.5 39 | assert p5map(x[2], range3.first, range3.last, range4.first, range4.last) == 80.0 40 | assert p5map(x[3], range1.first, range1.last, range2.first, range2.last) == 1 41 | end 42 | 43 | def test_norm 44 | x = [10, 140, 210] 45 | start0, last0 = 30, 200 46 | start1, last1 = 0, 200 47 | assert norm(x[0], start0, last0) == -0.11764705882352941, 'unclamped map' 48 | assert norm(x[1], start1, last1) == 0.7, 'map to intermediate' 49 | assert norm(x[2], start1, last1) == 1.05, 'unclamped map' 50 | end 51 | 52 | def test_norm_strict 53 | x = [10, 140, 210] 54 | start0, last0 = 30, 200 55 | assert norm_strict(x[0], start0, last0) == 0, 'clamped map to 0..1.0' 56 | end 57 | 58 | def test_lerp # behaviour is deliberately different to processing which is unclamped 59 | x = [0.5, 0.8, 2.0] 60 | start0, last0 = 300, 200 61 | start1, last1 = 0, 200 62 | assert lerp(start0, last0, x[0]) == 250, 'produces a intermediate value of a reversed range' 63 | assert lerp(start1, last1, x[1]) == 160, 'lerps tp an intermediate value' 64 | assert lerp(start1, last1, x[2]) == 200, 'lerps to the last value of a range' 65 | end 66 | 67 | def test_constrain 68 | x = [15, 2_500, -2_500] 69 | start1, last1 = 0, 200 70 | assert constrain(x[0], start1, last1) == 15 71 | assert constrain(x[1], start1, last1) == 200 72 | assert constrain(x[2], start1, last1) == 0 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /src/monkstone/core/AbstractLibrary.java: -------------------------------------------------------------------------------- 1 | package monkstone.core; 2 | 3 | import static processing.core.PConstants.*; 4 | 5 | /** 6 | * The purpose of this class is to enable 7 | * access to processing pre, draw and post loops in 8 | * ruby-processing as a regular java library class. 9 | * Also included background, fill and stroke methods. 10 | * PConstants should also be available from static import 11 | * @author Martin Prout 12 | */ 13 | public abstract class AbstractLibrary { 14 | 15 | private final processing.core.PApplet app; 16 | 17 | /** 18 | * Useful accessors 19 | */ 20 | public int width, height; 21 | 22 | /** 23 | * 24 | * @param app PApplet 25 | */ 26 | public AbstractLibrary(processing.core.PApplet app) { 27 | this.app = app; 28 | this.width = app.width; 29 | this.height = app.height; 30 | setActive(true); 31 | } 32 | 33 | /** 34 | * Extending classes must implement this gives access to, by reflection, 35 | * processing PApplet pre loop (called before draw) 36 | */ 37 | public abstract void pre(); 38 | 39 | /** 40 | * Extending classes must implement this gives access to processing PApplet 41 | * draw loop 42 | */ 43 | public abstract void draw(); 44 | 45 | /** 46 | * Extending classes must implement this gives access to, by reflection, 47 | * processing PApplet post loop (called after draw) 48 | */ 49 | public abstract void post(); 50 | 51 | private void setActive(boolean active) { 52 | if (active) { 53 | this.app.registerMethod("pre", this); 54 | this.app.registerMethod("draw", this); 55 | this.app.registerMethod("post", this); 56 | this.app.registerMethod("dispose", this); 57 | } else { 58 | this.app.unregisterMethod("pre", this); 59 | this.app.unregisterMethod("draw", this); 60 | this.app.unregisterMethod("post", this); 61 | } 62 | } 63 | 64 | /** 65 | * Simple signature for background hides need to call app 66 | * @param col 67 | */ 68 | public void background(int col) { 69 | this.app.background(col); 70 | } 71 | 72 | /** 73 | * Simple signature for fill hides need to call app 74 | * @param col 75 | */ 76 | public void fill(int col) { 77 | this.app.fill(col); 78 | } 79 | 80 | /** 81 | * Simple signature for stroke hides need to call app 82 | * @param col 83 | */ 84 | public void stroke(int col) { 85 | this.app.stroke(col); 86 | } 87 | 88 | /** 89 | * Access applet if we must 90 | * @return app PApplet 91 | */ 92 | public processing.core.PApplet app() { 93 | return this.app; 94 | } 95 | 96 | /** 97 | * required for processing 98 | */ 99 | public void dispose() { 100 | setActive(false); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /library/library_proxy/README.md: -------------------------------------------------------------------------------- 1 | ### Using the LibraryProxy in your sketches 2 | In the sketch you should `load_library :library_proxy` and your library class should inherit 3 | from LibraryProxy and implement pre(), draw() and post() methods (can be empty method if not 4 | required). For simplicity initialize your `processing library` in the sketch `setup`. 5 | 6 | ### Example library 7 | 8 | ```ruby 9 | require 'forwardable' 10 | 11 | # A custom Array created using forwardable (that can also access the PApplet pre, 12 | # post and draw loops by extending our new LibraryProxy class. Also has access 13 | # to custom background(int), fill(int) and stroke(int) methods. 14 | class CustomArray < LibraryProxy 15 | extend Forwardable 16 | def_delegators(:@objs, :each, :<<) 17 | include Enumerable 18 | 19 | attr_reader :app 20 | 21 | # We must initialize class with the PApplet instance 22 | def initialize(app) 23 | @app = app 24 | @objs = [] 25 | end 26 | 27 | def add_object(mx, my, x, y, speed) 28 | self << Particle.new(x.to_i, y.to_i, mx, my, Sketch::UNIT, speed, 1, 1) 29 | end 30 | 31 | # Access the processing post loop (gets called after draw) 32 | def post 33 | each do |obj| 34 | update_x obj 35 | next unless obj.y >= Sketch::UNIT || obj.x <= 0 36 | obj.ydir *= -1 37 | obj.y += obj.ydir 38 | end 39 | end 40 | 41 | def update_x(obj) 42 | obj.x += obj.speed * obj.xdir 43 | return if (0..Sketch::UNIT).cover? obj.x 44 | obj.xdir *= -1 45 | obj.x += obj.xdir 46 | obj.y += obj.ydir 47 | end 48 | 49 | # We need this to fulfill the contract of implementing abstract methods of 50 | # LibraryProxy which is an alias for Java::ProcessingCore::AbstractLibrary 51 | def pre 52 | end 53 | 54 | # Access the processing draw loop here, using our custom background and fill 55 | # note: use of 'app' to access ellipse functionality as would otherwise be 56 | # required for background and fill 57 | def draw 58 | background(0) 59 | fill(255) 60 | each do |obj| 61 | app.ellipse(obj.mx + obj.x, obj.my + obj.y, 6, 6) 62 | end 63 | end 64 | end 65 | 66 | # The Particle object 67 | 68 | Particle = Struct.new(:x, :y, :mx, :my, :size, :speed, :xdir, :ydir) 69 | ``` 70 | ### Example sketch 71 | 72 | ```ruby 73 | # A minimalist sketch that demonstrates a possible approach to creating a custom 74 | # array of objects using forwardable. Also demonstrates how to use LibraryProxy. 75 | 76 | load_library :library_proxy # loads the JRubyArt LibraryProxy abstract class 77 | require_relative 'custom_array' # loads our custom 'library' class 78 | 79 | UNIT = 40 80 | 81 | def setup 82 | size 640, 360 83 | wide_count = width / UNIT 84 | height_count = height / UNIT 85 | custom_array = CustomArray.new(self) 86 | height_count.times do |i| 87 | wide_count.times do |j| 88 | custom_array.add_object(j * UNIT, i * UNIT, UNIT / 2, UNIT / 2, rand(0.05..0.8)) 89 | end 90 | end 91 | no_stroke 92 | end 93 | 94 | # does nothing here see custom_array.rb 95 | def draw 96 | end 97 | ``` -------------------------------------------------------------------------------- /src/monkstone/fastmath/Deglut.java: -------------------------------------------------------------------------------- 1 | package monkstone.fastmath; 2 | 3 | import org.jruby.Ruby; 4 | import org.jruby.RubyClass; 5 | import org.jruby.RubyModule; 6 | import org.jruby.RubyObject; 7 | import org.jruby.anno.JRubyClass; 8 | import org.jruby.anno.JRubyMethod; 9 | import org.jruby.runtime.ThreadContext; 10 | import org.jruby.runtime.builtin.IRubyObject; 11 | 12 | /** 13 | * 14 | * @author Martin Prout 15 | */ 16 | @JRubyClass(name = "DegLut") 17 | public class Deglut extends RubyObject { 18 | 19 | /** 20 | * Lookup table for degree cosine/sine, has a fixed precision 1.0 21 | * degrees Quite accurate but imprecise 22 | * 23 | * @author Martin Prout 24 | */ 25 | static final double[] SIN_DEG_LUT = new double[91]; 26 | /** 27 | * 28 | */ 29 | public static final double TO_RADIANS = Math.PI / 180; 30 | /** 31 | * 32 | */ 33 | private static boolean initialized = false; 34 | 35 | private final static int NINETY = 90; 36 | private final static int FULL = 360; 37 | private static final long serialVersionUID = -1466528933765940101L; 38 | 39 | /** 40 | * Initialise sin table with values (first quadrant only) 41 | */ 42 | public static final void initTable() { 43 | if (initialized == false) { 44 | for (int i = 0; i <= NINETY; i++) { 45 | SIN_DEG_LUT[i] = Math.sin(TO_RADIANS * i); 46 | } 47 | initialized = true; 48 | } 49 | } 50 | 51 | 52 | /** 53 | * 54 | * @param runtime 55 | */ 56 | 57 | public static void createDeglut(final Ruby runtime){ 58 | RubyModule deglutModule = runtime.defineModule("DegLut"); 59 | deglutModule.defineAnnotatedMethods(Deglut.class); 60 | Deglut.initTable(); 61 | } 62 | 63 | 64 | /** 65 | * 66 | * @param runtime 67 | * @param klass 68 | */ 69 | private Deglut(Ruby runtime, RubyClass klass) { 70 | super(runtime, klass); 71 | } 72 | 73 | /** 74 | * 75 | * @param context 76 | * @param klazz 77 | * @param other 78 | * @return sin float 79 | */ 80 | @JRubyMethod(name = "sin", meta = true) 81 | 82 | public static IRubyObject sin(ThreadContext context, IRubyObject klazz, IRubyObject other) { 83 | int thet = (Integer) other.toJava(Integer.class); 84 | while (thet < 0) { 85 | thet += FULL; // Needed because negative modulus plays badly in java 86 | } 87 | int theta = thet % FULL; 88 | int y = theta % NINETY; 89 | double result = (theta < NINETY) ? SIN_DEG_LUT[y] : (theta < 180) 90 | ? SIN_DEG_LUT[NINETY - y] : (theta < 270) 91 | ? -SIN_DEG_LUT[y] : -SIN_DEG_LUT[NINETY - y]; 92 | return context.getRuntime().newFloat(result); 93 | } 94 | 95 | /** 96 | * 97 | * @param context 98 | * @param klazz 99 | * @param other 100 | * @return cos float 101 | */ 102 | @JRubyMethod(name = "cos", meta = true) 103 | public static IRubyObject cos(ThreadContext context, IRubyObject klazz, IRubyObject other) { 104 | int thet = (Integer) other.toJava(Integer.class); 105 | while (thet < 0) { 106 | thet += FULL; // Needed because negative modulus plays badly in java 107 | } 108 | int theta = thet % FULL; 109 | int y = theta % NINETY; 110 | double result = (theta < NINETY) ? SIN_DEG_LUT[NINETY - y] : (theta < 180) 111 | ? -SIN_DEG_LUT[y] : (theta < 270) 112 | ? -SIN_DEG_LUT[NINETY - y] : SIN_DEG_LUT[y]; 113 | return context.getRuntime().newFloat(result); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/ruby-processing/exporters/application_exporter.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | require_relative 'base_exporter' 3 | 4 | module Processing 5 | 6 | # A utility class to export Ruby-Processing sketches as 7 | # Mac/Win/Nix Applications. 8 | class ApplicationExporter < BaseExporter 9 | 10 | USAGE = <<-EOS 11 | 12 | The application exporter will generate a Mac application for you. 13 | Usage: script/application 14 | Example: script/applet samples/jwishy.rb 15 | Probably won't work with Oracle Java on Mac 16 | 17 | EOS 18 | 19 | def export!(sketch) 20 | # Check to make sure that the main file exists 21 | @main_file_path, @main_file, @main_folder = *get_main_file(sketch) 22 | usage(@main_file_path && FileTest.exist?(@main_file_path)) 23 | 24 | extract_information 25 | 26 | compute_destination_name 27 | 28 | wipe_and_recreate_destination 29 | 30 | copy_over_necessary_files 31 | 32 | calculate_substitutions 33 | 34 | create_executables 35 | 36 | symlink_library_into_place 37 | end 38 | 39 | def compute_destination_name 40 | @dest = "#{@title}.app" 41 | end 42 | 43 | def copy_over_necessary_files 44 | @prefix = 'lib' 45 | cp_r(Dir["#{RP5_ROOT}/lib/templates/application/{*,**}"], @dest) 46 | @necessary_files = [@main_file_path] 47 | @necessary_files += Dir["#{RP_CONFIG['PROCESSING_ROOT']}/core/library/{*,**}"] 48 | @necessary_files += Dir["#{RP5_ROOT}/lib/{*,**}"] 49 | @necessary_files += @real_requires 50 | NECESSARY_FOLDERS.each do |folder| 51 | resource_path = File.join(@main_folder, folder) 52 | @necessary_files << resource_path if FileTest.exist?(resource_path) 53 | end 54 | @necessary_files.uniq! 55 | cp_r(@necessary_files, File.join(@dest, @prefix)) 56 | cp_r(@libraries, File.join(@dest, @prefix, 'library')) unless @libraries.empty? 57 | # Then move the icon 58 | potential_icon = Dir.glob(File.join(@dest, @prefix, 'data/*.icns'))[0] 59 | move(potential_icon, File.join(@dest, 'Contents/Resources/sketch.icns'), force: true) if potential_icon 60 | end 61 | 62 | def calculate_substitutions 63 | file_list = ['lib/ruby/jruby-complete.jar'] 64 | @class_path = file_list.map { |f| '$JAVAROOT/' + f.sub(@prefix + '/', '') }.join(':') 65 | @linux_class_path = '.:../lib/ruby/*:../lib/*:../lib/library/*' 66 | @windows_class_path = '.;../lib/ruby/*;../lib/*;../lib/library/*' 67 | end 68 | 69 | def create_executables 70 | render_erb_in_path_with_binding(@dest, binding, delete: true) 71 | rm Dir.glob(@dest + "/**/*.java") 72 | runnable = @dest + '/' + File.basename(@main_file, '.rb') 73 | move @dest + '/run', runnable 74 | move @dest + '/run.exe', "#{runnable}.exe" 75 | chmod 0755, runnable 76 | chmod 0755, "#{runnable}.exe" 77 | chmod 0755, File.join(@dest, 'Contents', 'MacOS', 'JavaApplicationStub') 78 | end 79 | 80 | def symlink_library_into_place 81 | cd @dest + '/Contents/Resources' 82 | # Poor ol' windows can't symlink. 83 | # TODO... 84 | win ||= RbConfig::CONFIG['host_os'].match(/mswin/i) 85 | win ||= RbConfig::CONFIG['host_os'].match(/windows/i) 86 | puts "\n[warning] Applications exported from Windows won't run on Macs...\n" if win 87 | ln_s('../../lib', 'Java') unless win 88 | end 89 | 90 | def usage(predicate) 91 | return if predicate 92 | puts USAGE 93 | exit 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /src/monkstone/arcball/Quaternion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Martin Prout 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * http://creativecommons.org/licenses/LGPL/2.1/ 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | package monkstone.arcball; 22 | 23 | /** 24 | * Based on a original sketch by Ariel Malka 25 | * Arcball quaternion idea by Ken Shoemake 26 | * http://dl.acm.org/citation.cfm?id=325242 27 | * A google of the quaternions term will find a 28 | * freely down-loadable article by Ken Shoemake. 29 | * @author Martin Prout 30 | */ 31 | public final class Quaternion { 32 | 33 | private double w, x, y, z; 34 | 35 | /** 36 | * 37 | */ 38 | public Quaternion() { 39 | reset(); 40 | } 41 | 42 | /** 43 | * 44 | * @param w 45 | * @param x 46 | * @param y 47 | * @param z 48 | */ 49 | public Quaternion(double w, double x, double y, double z) { 50 | this.w = w; 51 | this.x = x; 52 | this.y = y; 53 | this.z = z; 54 | } 55 | 56 | /** 57 | * 58 | */ 59 | public final void reset() { 60 | w = 1.0f; 61 | x = 0.0f; 62 | y = 0.0f; 63 | z = 0.0f; 64 | } 65 | 66 | /** 67 | * 68 | * @param w scalar 69 | * @param v custom Vector class 70 | */ 71 | public void set(double w, Jvector v) { 72 | this.w = w; 73 | x = v.x; 74 | y = v.y; 75 | z = v.z; 76 | } 77 | 78 | /** 79 | * 80 | * @param q 81 | */ 82 | public void set(Quaternion q) { 83 | w = q.w; 84 | x = q.x; 85 | y = q.y; 86 | z = q.z; 87 | } 88 | 89 | /** 90 | * 91 | * @param q1 92 | * @param q2 93 | * @return new Quaternion 94 | */ 95 | public static Quaternion mult(Quaternion q1, Quaternion q2) { 96 | double w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z; 97 | double x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y; 98 | double y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z; 99 | double z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x; 100 | return new Quaternion(w, x, y, z); 101 | } 102 | 103 | /** 104 | * Transform this Quaternion into an angle (radians) and an axis vector, about 105 | * which to rotate (avoids NaN by setting sa to 1.0F when sa < epsilon) 106 | * @return a new double[] where a0 = angle and a1 .. a3 are axis vector 107 | */ 108 | 109 | public double[] getValue() { 110 | double sa = Math.sqrt(1.0 - w * w); 111 | if (sa < processing.core.PConstants.EPSILON) { 112 | sa = 1.0f; 113 | } 114 | return new double[]{ Math.acos(w) * 2.0f, x / sa, y / sa, z / sa}; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 11 | 4.0.0 12 | ruby-processing 13 | rp5extras 14 | 2.7.1 15 | rp5extras 16 | rp5extras for ruby-processing 17 | https://github.com/jashkenas/ruby-processing 18 | 19 | ruby-processing 20 | https://ruby-processing.github.io 21 | 22 | 23 | 24 | monkstone 25 | Martin Prout 26 | mamba2928@yahoo.co.uk 27 | 28 | developer 29 | 30 | 31 | 32 | 33 | scm:git:git://github.com/jashkenas/ruby-processing.git 34 | scm:git:git@github.com/jashkenas/ruby-processing.git 35 | https://github.com/jashkenas/ruby-processing 36 | 37 | 38 | Github 39 | https://github.com/jashkenas/ruby-processing/issues 40 | 41 | 42 | http://processing.github.io/processing-javadocs/core/ 43 | pom.xml 44 | UTF-8 45 | 1.7 46 | http://jruby.org/apidocs/ 47 | 1.7 48 | 49 | 50 | 51 | org.jruby 52 | jruby 53 | 1.7.25 54 | pom 55 | 56 | 57 | org.processing 58 | core 59 | 2.2.1 60 | 61 | 62 | org.processing 63 | video 64 | 2.2.1 65 | 66 | 67 | 68 | src 69 | package 70 | rpextras 71 | 72 | 73 | 74 | maven-resources-plugin 75 | 2.6 76 | 77 | 78 | maven-dependency-plugin 79 | 2.10 80 | 81 | 82 | maven-compiler-plugin 83 | 3.5.1 84 | 85 | 1.7 86 | 1.7 87 | 88 | 89 | 90 | maven-javadoc-plugin 91 | 2.10.4 92 | 93 | false 94 | 95 | ${processing.api} 96 | ${jruby.api} 97 | 98 | 99 | 100 | 101 | maven-jar-plugin 102 | 3.0.2 103 | 104 | 105 | MANIFEST.MF 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /lib/ruby-processing/exporters/creator.rb: -------------------------------------------------------------------------------- 1 | BASIC = <<-CODE 2 | def setup 3 | size %s, %s 4 | end 5 | 6 | def draw 7 | 8 | end 9 | CODE 10 | 11 | BASIC_MODE = <<-CODE 12 | def setup 13 | size %s, %s, %s 14 | end 15 | 16 | def draw 17 | 18 | end 19 | CODE 20 | 21 | CLASS_BASIC = <<-CODE 22 | class %s < Processing::App 23 | def setup 24 | size %s, %s 25 | end 26 | 27 | def draw 28 | 29 | end 30 | end 31 | CODE 32 | 33 | CLASS_MODE = <<-CODE 34 | class %s < Processing::App 35 | def setup 36 | size %s, %s, %s 37 | end 38 | 39 | def draw 40 | 41 | end 42 | end 43 | CODE 44 | 45 | INNER = <<-CODE 46 | class %s 47 | include Processing::Proxy 48 | 49 | end 50 | CODE 51 | 52 | module Processing 53 | require_relative '../helpers/string_extra' 54 | require_relative '../helpers/camel_string' 55 | # Write file to disk 56 | class SketchWriter 57 | attr_reader :file 58 | def initialize(path) 59 | underscore = StringExtra.new(path).underscore 60 | @file = "#{File.dirname(path)}/#{underscore}.rb" 61 | end 62 | 63 | def save(template) 64 | File.open(file, 'w+') do |f| 65 | f.write(template) 66 | end 67 | end 68 | end 69 | 70 | # An abstract class providing common methods for real creators 71 | class Creator 72 | ALL_DIGITS = /\A\d+\Z/ 73 | 74 | def already_exist(path) 75 | underscore = StringExtra.new(path).underscore 76 | new_file = "#{File.dirname(path)}/#{underscore}.rb" 77 | return if !FileTest.exist?(path) && !FileTest.exist?(new_file) 78 | puts 'That file already exists!' 79 | exit 80 | end 81 | 82 | # Show the help/usage message for create. 83 | def usage 84 | puts <<-USAGE 85 | 86 | Usage: rp5 create 87 | mode can be P2D / P3D. 88 | Use --wrap for a sketch wrapped as a class 89 | Use --inner to generated a ruby version of 'java' Inner class 90 | Examples: rp5 create app 800 600 91 | rp5 create app 800 600 p3d --wrap 92 | rp5 create inner_class --inner 93 | 94 | USAGE 95 | end 96 | end 97 | 98 | # This class creates bare sketches, with an optional render mode 99 | class BasicSketch < Creator 100 | # Create a blank sketch, given a path. 101 | def basic_template 102 | format(BASIC, @width, @height) 103 | end 104 | 105 | def basic_template_mode 106 | format(BASIC_MODE, @width, @height, @mode) 107 | end 108 | 109 | def create!(path, args) 110 | return usage if /\?/ =~ path || /--help/ =~ path 111 | # Check to make sure that the main file doesn't exist already 112 | already_exist(path) 113 | main_file = File.basename(path, '.rb') # allow uneeded extension input 114 | writer = SketchWriter.new(main_file) 115 | @width = args[0] 116 | @height = args[1] 117 | @mode = args[2].upcase unless args[2].nil? 118 | template = @mode.nil? ? basic_template : basic_template_mode 119 | writer.save(template) 120 | end 121 | end 122 | 123 | # This class creates class wrapped sketches, with an optional render mode 124 | class ClassSketch < Creator 125 | def class_template 126 | format(CLASS_BASIC, @name, @width, @height) 127 | end 128 | 129 | def class_template_mode 130 | format(CLASS_MODE, @name, @width, @height, @mode) 131 | end 132 | # Create a class wrapped sketch, given a path. 133 | def create!(path, args) 134 | return usage if /\?/ =~ path || /--help/ =~ path 135 | main_file = File.basename(path, '.rb') # allow uneeded extension input 136 | # Check to make sure that the main file doesn't exist already 137 | already_exist(path) 138 | @name = CamelString.new(main_file).camelize 139 | writer = SketchWriter.new(main_file) 140 | @title = StringExtra.new(main_file).titleize 141 | @width, @height = args[0], args[1] 142 | @mode = args[2].upcase unless args[2].nil? 143 | template = @mode.nil? ? class_template : class_template_mode 144 | writer.save(template) 145 | end 146 | end 147 | 148 | # This class creates a pseudo 'java inner class' of the sketch 149 | class Inner < Creator 150 | def inner_class_template 151 | format(INNER, @name) 152 | end 153 | # Create a pseudo inner class, given a path. 154 | def create!(path, _args_) 155 | return usage if /\?/ =~ path || /--help/ =~ path 156 | main_file = File.basename(path, '.rb') # allow uneeded extension input 157 | # Check to make sure that the main file doesn't exist already 158 | already_exist(path) 159 | @name = main_file.camelize 160 | writer = SketchWriter.new(main_file) 161 | template = inner_class_template 162 | writer.save(template) 163 | end 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /src/monkstone/arcball/Jvector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-16 Martin Prout 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * http://creativecommons.org/licenses/LGPL/2.1/ 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | package monkstone.arcball; 21 | 22 | /** 23 | * 24 | * @author Martin Prout 25 | */ 26 | public final class Jvector { 27 | 28 | static final double EPSILON = 9.999999747378752E-5f; 29 | 30 | /** 31 | * 32 | */ 33 | public double x; 34 | 35 | /** 36 | * 37 | */ 38 | public double y; 39 | 40 | /** 41 | * 42 | */ 43 | public double z; 44 | 45 | /** 46 | * 47 | * @param x 48 | * @param y 49 | * @param z 50 | */ 51 | public Jvector(double x, double y, double z) { 52 | this.x = x; 53 | this.y = y; 54 | this.z = z; 55 | } 56 | 57 | /** 58 | * 59 | */ 60 | public Jvector() { 61 | this(0.0f, 0.0f, 0.0f); 62 | } 63 | 64 | /** 65 | * 66 | * @param vect 67 | */ 68 | public Jvector(Jvector vect) { 69 | this(vect.x, vect.y, vect.z); 70 | } 71 | 72 | /** 73 | * 74 | * @param other 75 | * @return subtracted vector 76 | */ 77 | public Jvector sub(Jvector other) { 78 | return new Jvector(this.x - other.x, this.y - other.y, this.z - other.z); 79 | } 80 | 81 | /** 82 | * 83 | * @param scalar 84 | * @return subtracted vector 85 | */ 86 | public Jvector mult(double scalar) { 87 | return new Jvector(this.x * scalar, this.y * scalar, this.z * scalar); 88 | } 89 | 90 | /** 91 | * 92 | * @return magnitude float 93 | */ 94 | public double mag() { 95 | return Math.sqrt(x * x + y * y + z * z); 96 | } 97 | 98 | /** 99 | * The usual normalize 100 | * 101 | * @return this Jvector 102 | */ 103 | public Jvector normalize() { 104 | double mag = Math.sqrt(x * x + y * y + z * z); 105 | this.x /= mag; 106 | this.y /= mag; 107 | this.z /= mag; 108 | return this; 109 | } 110 | 111 | /** 112 | * 113 | * @param other 114 | * @return new dot product 115 | */ 116 | public double dot(Jvector other) { 117 | return x * other.x + y * other.y + z * other.z; 118 | } 119 | 120 | /** 121 | * 122 | * @param other 123 | * @return new cross product 124 | */ 125 | public Jvector cross(Jvector other) { 126 | double xc = y * other.z - z * other.y; 127 | double yc = z * other.x - x * other.z; 128 | double zc = x * other.y - y * other.x; 129 | return new Jvector(xc, yc, zc); 130 | } 131 | 132 | /** 133 | * 134 | * @param other 135 | * @return boolean 136 | */ 137 | public boolean equals(Jvector other) { 138 | if (other instanceof Jvector) { 139 | 140 | if (Math.abs(this.x - other.x) > EPSILON) { 141 | return false; 142 | } 143 | if (Math.abs(this.y - other.y) > EPSILON) { 144 | return false; 145 | } 146 | return (Math.abs(this.z - other.z) > EPSILON); 147 | } 148 | return false; 149 | 150 | } 151 | 152 | /** 153 | * 154 | * @param obj 155 | * @return boolean 156 | */ 157 | @Override 158 | public boolean equals(Object obj) { 159 | if (obj == null) { 160 | return false; 161 | } 162 | if (getClass() != obj.getClass()) { 163 | return false; 164 | } 165 | final Jvector other = (Jvector) obj; 166 | if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { 167 | return false; 168 | } 169 | if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { 170 | return false; 171 | } 172 | return (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)); 173 | } 174 | 175 | /** 176 | * 177 | * @return has int 178 | */ 179 | @Override 180 | public int hashCode() { 181 | int hash = 7; 182 | hash = 97 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); 183 | hash = 97 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); 184 | hash = 97 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); 185 | return hash; 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /lib/ruby-processing/exporters/base_exporter.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'erb' 3 | require_relative '../library_loader' 4 | require_relative '../helpers/string_extra' 5 | 6 | module Processing 7 | # This base exporter implements some of the common 8 | # code-munging needed to generate apps/ blank sketches. 9 | class BaseExporter 10 | include FileUtils 11 | DEFAULT_DIMENSIONS = { 'width' => '100', 'height' => '100' } 12 | DEFAULT_DESCRIPTION = '' 13 | NECESSARY_FOLDERS = %w(data lib vendor) 14 | 15 | # Returns the filepath, basename, and directory name of the sketch. 16 | def get_main_file(file) 17 | @file = file 18 | return file, File.basename(file), File.dirname(file) 19 | end 20 | 21 | # Centralized method to read the source of the sketch and extract 22 | # all the juicy details. 23 | def extract_information 24 | # Extract information from main file 25 | @info = {} 26 | @info[:source_code] = source = read_source_code 27 | @info[:class_name] = extract_class_name(source) 28 | @info[:title] = extract_title(source) 29 | @info[:width] = extract_dimension(source, 'width') 30 | @info[:height] = extract_dimension(source, 'height') 31 | @info[:description] = extract_description(source) 32 | @info[:libraries] = extract_libraries(source) 33 | @info[:real_requires] = extract_real_requires(source) 34 | hash_to_ivars @info 35 | @info 36 | end 37 | 38 | # Searches the source for a class name. 39 | def extract_class_name(source) 40 | match = source.match(/(\w+)\s*<\s*Processing::App/) 41 | match ? match[1] : 'Sketch' 42 | end 43 | 44 | # Searches the source for a title. 45 | def extract_title(source) 46 | generated_title = StringExtra.new(File.basename(@file, '.rb')).titleize 47 | match = source.match(/#{@info[:class_name]}\.new.*?:title\s=>\s["'](.+?)["']/m) 48 | match ? match[1] : generated_title 49 | end 50 | 51 | # Searches the source for the width and height of the sketch. 52 | def extract_dimension(source, dimension) 53 | filter = /#{@info[:class_name]}\.new.*?:#{dimension}\s?=>\s?(\d+)/m 54 | match = source.match(filter) 55 | sz_match = source.match(/^[^#]*size\(?\s*(\d+)\s*,\s*(\d+)\s*\)?/) 56 | return match[1] if match 57 | return (dimension == 'width' ? sz_match[1] : sz_match[2]) if sz_match 58 | warn 'using default dimensions for export, please use constants integer'\ 59 | 'values in size() call instead of computed ones' 60 | DEFAULT_DIMENSIONS[dimension] 61 | end 62 | 63 | # Searches the source for a description of the sketch. 64 | def extract_description(source) 65 | match = source.match(/\A((\s*#(.*?)\n)+)[^#]/m) 66 | match ? match[1].gsub(/\s*#\s*/, "\n") : DEFAULT_DESCRIPTION 67 | end 68 | 69 | # Searches the source for any libraries that have been loaded. 70 | def extract_libraries(source) 71 | lines = source.split("\n") 72 | libs = lines.grep(/^[^#]*load_(?:java_|ruby_)?librar(?:y|ies)\s+(.+)/) do 73 | Regexp.last_match(1).split(/\s*,\s*/).map do |raw_library_name| 74 | raw_library_name.tr("\"':\r\n", '') 75 | end 76 | end.flatten 77 | lib_loader = LibraryLoader.new 78 | libs.map { |lib| lib_loader.get_library_paths(lib) }.flatten.compact 79 | end 80 | 81 | # Looks for all of the codes require or load commands, checks 82 | # to see if the file exists (that it's not a gem, or a standard lib) 83 | # and hands you back all the real ones. 84 | def extract_real_requires(source) 85 | code = source.dup 86 | requirements = [] 87 | partial_paths = [] 88 | Kernel.loop do 89 | matchdata = code.match( 90 | /^.*[^::\.\w](require_relative|require|load)\b.*$/ 91 | ) 92 | break unless matchdata 93 | line = matchdata[0].gsub('__FILE__', "'#{@main_file_path}'") 94 | req = /\b(require_relative|require|load)\b/ 95 | if req =~ line 96 | ln = line.gsub(req, '') 97 | partial_paths << ln 98 | where = "{#{local_dir}/,}{#{partial_paths.join(',')}}" 99 | where += '.{rb,jar}' unless line =~ /\.[^.]+$/ 100 | requirements += Dir[where] 101 | end 102 | code = matchdata.post_match 103 | end 104 | requirements 105 | end 106 | 107 | protected 108 | 109 | def read_source_code 110 | File.read(@main_file_path) 111 | end 112 | 113 | def local_dir 114 | File.dirname(@main_file_path) 115 | end 116 | 117 | def hash_to_ivars(hash) 118 | hash.each { |k, v| instance_variable_set("@#{k}", v) } 119 | end 120 | 121 | def wipe_and_recreate_destination 122 | remove_entry_secure @dest if FileTest.exist?(@dest) 123 | mkdir_p @dest 124 | end 125 | 126 | def render_erb_in_path_with_binding(path, some_binding, opts = {}) 127 | erbs = Dir.glob(path + "/**/*.erb") # double quotes required 128 | erbs.each do |erb| 129 | string = File.open(erb) { |f| f.read } 130 | rendered = render_erb_from_string_with_binding(string, some_binding) 131 | File.open(erb.sub('.erb', ''), 'w') { |f| f.print rendered } 132 | rm erb if opts[:delete] 133 | end 134 | end 135 | 136 | def render_erb_from_string_with_binding(erb, some_binding) 137 | ERB.new(erb, nil, '<>', 'rendered').result(some_binding) 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### __IMPORTANT: Ruby-processing is deprecated and unsupported__ ### 2 | 3 | Use the updated version [JRubyArt][jruby_art] or the standalone alternative [propane][], which target processing-3.5.3 and processing-4.0 (like jdk11+) respectively, and support ruby-2.5+ syntax. Works on same platforms as vanilla processing (windows, mac, linux) for Android see Yuki Morohoshi [ruboto-processing2][]. 4 | 5 | [Processing][] provides a tidy API, with a bunch of handy methods you can call 6 | from Ruby-Processing. Here's a smattering: 7 | 8 | `alpha`, `arc`, `background`, `blend`, `blue`, `ellipse`, `frame_rate`, `hue`, `lerp`, `load_image`, `load_pixels`, `mouse_pressed`, `noise`, `rect`, `saturation`, `shape`, `smooth`, `text_align`, `translate`, `triangle`, `vertex`... 9 | 10 | 11 | ## Installation 12 | We assume you have some version of ruby installed if not, there is a [guide to installing][] ruby on various platforms including windows. Or here is an [alternative install][] guide. 13 | 14 | MacOSX users please try out this new [method](https://github.com/jashkenas/ruby-processing/wiki/Installing-ruby-processing-on-the-mac) or see this [japanese][] guide. 15 | 16 | Ideally you should install [jruby](http://jruby.org/download), at the very least you will have at least ruby-1.9.3 installed. You should [download][] and install vanilla [processing-2.2.1](https://processing.org/download/) prior to installing this version of ruby-processing. You must also set the `PROCESSING_ROOT` in the .rp5rc yaml configuration file, the easiest way to do this is by running the [SetProcessingRoot.pde](https://gist.github.com/monkstone/7438749) sketch in the processing ide. 17 | 18 | Then install ruby-processing (from rubygems-org) in the usual way 19 | 20 | `gem install ruby-processing` some systems eg Ubuntu may require `sudo` access 21 | 22 | To install jruby-complete use our built in tool (relies on `wget` to download [jruby-complete-1.7.26](http://jruby.org/download)) 23 | 24 | since ruby-processing-2.5.0 `rp5 setup install` (was `install_jruby_complete`) 25 | 26 | If you haven't got `wget` just download jruby-complete-1.7.26 (for ruby-processing-2.7.1) to the vendors folder (then run above tool) 27 | 28 | The vendored jruby-complete is only required for application export, and running certain sketches (eg shader sketches see [wiki][]). 29 | 30 | ## Documentation 31 | 32 | See [Wiki][] 33 | 34 | See also [FAQs][], [Contributing][] and [Samples][] 35 | 36 | # Usage Example 37 | 38 | ```bash 39 | rp5 run my_sketch.rb 40 | ``` 41 | 42 | or if not using system jruby (and not `JRUBY: 'false'` in `~/.rp5rc`) 43 | 44 | ```bash 45 | rp5 --nojruby run my_sketch.rb 46 | ``` 47 | 48 | where a simple ``my_sketch.rb`` could look like this 49 | 50 | ```ruby 51 | def setup 52 | size 400, 400 53 | fill 255 54 | end 55 | 56 | def draw 57 | background 0 58 | ellipse mouse_x, mouse_y, 100, 100 59 | end 60 | ``` 61 | 62 | or a simple 3D sketch ``cube.rb`` features ArcBall from vecmath library 63 | 64 | ```ruby 65 | load_library :vecmath 66 | 67 | ############################ 68 | # Use mouse drag to rotate 69 | # the arcball. Use mousewheel 70 | # to zoom. Hold down x, y, z 71 | # to constrain rotation axis. 72 | ############################ 73 | 74 | def setup 75 | size(600, 600, P3D) 76 | smooth(8) 77 | ArcBall.init(self, 300, 300) 78 | fill 180 79 | end 80 | 81 | def draw 82 | background(50) 83 | box(300, 300, 300) 84 | end 85 | 86 | ``` 87 | See [samples][] for many more examples 88 | ___ 89 | 90 | ### Supported java version 91 | 92 | NB: you can't use jdk/jre installed by processing 93 | * Open jdk8 (latest version preferred, is the default linux install) 94 | * jdk8 from Oracle (latest version preferred, or required by Mac) 95 | * jdk7 should also work (typically ubuntu linux or some other linux distro) 96 | 97 | ### Supported ruby version 98 | 99 | This gem has been tested with the following ruby implementations 100 | 101 | * Ruby 1.9.3 102 | * Ruby 2.0.0 103 | * Ruby 2.1.2 104 | * Ruby 2.2.1 105 | * Ruby 2.3.0 106 | * [JRuby][] preferred use jruby-1.7.XX but also works with jruby-9.1.2.0 release 107 | 108 | ### Supported processing version 109 | 110 | * processing-2.2.1 (required) 111 | * for processing-3.0+ see [JRubyArt][jruby_art] or [propane][propane] 112 | 113 | ____ 114 | 115 | ### Ruby alternatives for processing convenience methods 116 | 117 | Many processing (convenience) methods make little sense in ruby (and many are no-longer implemented). See ruby alternatives for [details][]. 118 | ____ 119 | 120 | [License][] 121 | 122 | [license]:LICENSE.md 123 | [contributing]:CONTRIBUTING.md 124 | [jruby]:http://www.jruby.org/ 125 | [processing]: http://www.processing.org/ 126 | [download]:https://processing.org/download/ 127 | [samples]:https://github.com/ruby-processing/Example-Sketches 128 | [wiki]:http://github.com/jashkenas/ruby-processing/wikis/ 129 | [details]:https://github.com/jashkenas/ruby-processing/wiki/Replacing-processing-convenience-methods 130 | [FAQs]:http://github.com/jashkenas/ruby-processing/wikis/FAQs/ 131 | [release]:https://github.com/jashkenas/ruby-processing/releases/ 132 | [guide to installing]:https://www.ruby-lang.org/en/installation/ 133 | [alternative install]:http://tutorials.jumpstartlab.com/topics/environment/environment.html 134 | [fix]:https://github.com/jruby/jruby/issues/1917 135 | [japanese]:http://qiita.com/yohm13/items/f3f82f423b507cec1dcc 136 | [jruby_art]:https://ruby-processing.github.io/JRubyArt/ 137 | [ruboto-processing2]:https://github.com/hoshi-sano/ruboto-processing2 138 | [propane]:https://ruby-processing.github.io/propane/ 139 | -------------------------------------------------------------------------------- /library/control_panel/control_panel.rb: -------------------------------------------------------------------------------- 1 | # Here's a little library for quickly hooking up controls to sketches. 2 | # For messing with the parameters and such. 3 | # These controls will set instance variables on the sketches. 4 | 5 | # You can make sliders, checkboxes, buttons, and drop-down menus. 6 | # (optionally) pass the range and default value. 7 | 8 | module ControlPanel 9 | # class used to create slider elements for control_panel 10 | class Slider < javax.swing.JSlider 11 | def initialize(control_panel, name, range, initial_value, proc = nil) 12 | min = range.begin * 100 13 | max = ( 14 | (range.exclude_end? && range.begin.respond_to?(:succ)) ? 15 | range.max : range.end) * 100 16 | super(min, max) 17 | set_minor_tick_spacing((max - min).abs / 10) 18 | set_paint_ticks true 19 | # paint_labels = true 20 | set_preferred_size(java.awt.Dimension.new(190, 30)) 21 | label = control_panel.add_element(self, name) 22 | add_change_listener do 23 | update_label(label, name, value) 24 | $app.instance_variable_set("@#{name}", value) unless value.nil? 25 | proc.call(value) if proc 26 | end 27 | set_value(initial_value ? initial_value * 100 : min) 28 | fire_state_changed 29 | end 30 | 31 | def value 32 | get_value / 100.0 33 | end 34 | 35 | def update_label(label, name, value) 36 | value = value.to_s 37 | value << '0' if value.length < 4 38 | label.set_text "
#{name}: #{value}" 39 | end 40 | end 41 | 42 | # class used to combo_box menu elements for control_panel 43 | class Menu < javax.swing.JComboBox 44 | def initialize(control_panel, name, elements, initial_value, proc = nil) 45 | super(elements.to_java(:String)) 46 | set_preferred_size(java.awt.Dimension.new(190, 30)) 47 | control_panel.add_element(self, name) 48 | add_action_listener do 49 | $app.instance_variable_set("@#{name}", value) unless value.nil? 50 | proc.call(value) if proc 51 | end 52 | set_selected_index(initial_value ? elements.index(initial_value) : 0) 53 | end 54 | 55 | def value 56 | get_selected_item 57 | end 58 | end 59 | 60 | # Creates check-box elements for control_panel 61 | class Checkbox < javax.swing.JCheckBox 62 | def initialize(control_panel, name, proc = nil) 63 | @control_panel = control_panel 64 | super(name.to_s) 65 | set_preferred_size(java.awt.Dimension.new(190, 64)) 66 | set_horizontal_alignment javax.swing.SwingConstants::CENTER 67 | control_panel.add_element(self, name, false) 68 | add_action_listener do 69 | $app.instance_variable_set("@#{name}", value) unless value.nil? 70 | proc.call(value) if proc 71 | end 72 | end 73 | 74 | def value 75 | is_selected 76 | end 77 | end 78 | 79 | # Creates button elements for control_panel 80 | class Button < javax.swing.JButton 81 | def initialize(control_panel, name, proc = nil) 82 | super(name.to_s) 83 | set_preferred_size(java.awt.Dimension.new(170, 64)) 84 | control_panel.add_element(self, name, false, true) 85 | add_action_listener do 86 | $app.send(name.to_s) 87 | proc.call(value) if proc 88 | end 89 | end 90 | end 91 | 92 | # class used to contain control_panel elements 93 | class Panel < javax.swing.JFrame 94 | java_import javax.swing.UIManager 95 | 96 | attr_accessor :elements, :panel 97 | 98 | def initialize 99 | super() 100 | @elements = [] 101 | @panel = javax.swing.JPanel.new(java.awt.FlowLayout.new(1, 0, 0)) 102 | set_feel 103 | end 104 | 105 | def display 106 | add panel 107 | set_size 200, 30 + (64 * elements.size) 108 | set_default_close_operation javax.swing.JFrame::HIDE_ON_CLOSE 109 | set_resizable false 110 | set_location($app.width + 10, 0) unless $app.width + 10 > $app.displayWidth 111 | panel.visible = true 112 | end 113 | 114 | def add_element(element, name, has_label = true, _button_ = false) 115 | if has_label 116 | label = javax.swing.JLabel.new("
#{name}") 117 | panel.add label 118 | end 119 | elements << element 120 | panel.add element 121 | has_label ? label : nil 122 | end 123 | 124 | def remove 125 | remove_all 126 | dispose 127 | end 128 | 129 | def slider(name, range = 0..100, initial_value = nil, &block) 130 | Slider.new(self, name, range, initial_value, block || nil) 131 | end 132 | 133 | def menu(name, elements, initial_value = nil, &block) 134 | Menu.new(self, name, elements, initial_value, block || nil) 135 | end 136 | 137 | def checkbox(name, initial_value = nil, &block) 138 | checkbox = Checkbox.new(self, name, block || nil) 139 | checkbox.do_click if initial_value == true 140 | end 141 | 142 | def button(name, &block) 143 | Button.new(self, name, block || nil) 144 | end 145 | 146 | def look_feel(lf) 147 | set_feel(lf) 148 | end 149 | 150 | private 151 | 152 | def set_feel(lf = 'metal') 153 | lafinfo = javax.swing.UIManager.getInstalledLookAndFeels 154 | laf = lafinfo.select do |info| 155 | info.getName.eql? lf.capitalize 156 | end 157 | javax.swing.UIManager.setLookAndFeel(laf[0].getClassName) 158 | end 159 | end 160 | 161 | # instance methods module 162 | module InstanceMethods 163 | def control_panel 164 | @control_panel = ControlPanel::Panel.new unless @control_panel 165 | return @control_panel unless block_given? 166 | yield(@control_panel) 167 | @control_panel.display 168 | end 169 | end 170 | end 171 | 172 | Processing::App.send :include, ControlPanel::InstanceMethods 173 | -------------------------------------------------------------------------------- /lib/ruby-processing/library_loader.rb: -------------------------------------------------------------------------------- 1 | # The processing wrapper module 2 | require_relative '../ruby-processing' 3 | 4 | module Processing 5 | 6 | # Encapsulate library loader functionality as a class 7 | class LibraryLoader 8 | attr_reader :sketchbook_library_path 9 | 10 | def initialize 11 | @sketchbook_library_path = File.join(find_sketchbook_path, 'libraries') 12 | @loaded_libraries = Hash.new(false) 13 | end 14 | 15 | # Detect if a library has been loaded (for conditional loading) 16 | def library_loaded?(library_name) 17 | @loaded_libraries[library_name.to_sym] 18 | end 19 | 20 | # Load a list of Ruby or Java libraries (in that order) 21 | # Usage: load_libraries :opengl, :boids 22 | # 23 | # If a library is put into a 'library' folder next to the sketch it will 24 | # be used instead of the library that ships with Ruby-Processing. 25 | def load_libraries(*args) 26 | message = 'no such file to load -- %s' 27 | args.each do |lib| 28 | loaded = load_ruby_library(lib) || load_java_library(lib) 29 | fail(LoadError.new, format(message, lib)) unless loaded 30 | end 31 | end 32 | alias_method :load_library, :load_libraries 33 | 34 | # For pure ruby libraries. 35 | # The library should have an initialization ruby file 36 | # of the same name as the library folder. 37 | def load_ruby_library(library_name) 38 | library_name = library_name.to_sym 39 | return true if @loaded_libraries.include?(library_name) 40 | if ENV['EXPORTED'].eql?('true') 41 | begin 42 | return @loaded_libraries[library_name] = (require_relative "../library/#{library_name}") 43 | rescue LoadError 44 | return false 45 | end 46 | end 47 | path = get_library_paths(library_name, 'rb').first 48 | return false unless path 49 | @loaded_libraries[library_name] = (require path) 50 | end 51 | 52 | # HACK: For pure java libraries, such as the ones that are available 53 | # on this page: http://processing.org/reference/libraries/index.html 54 | # that include native code, we mess with the 'Java ClassLoader', so that 55 | # you don't have to futz with your PATH. But it's probably bad juju. 56 | def load_java_library(library_name) 57 | library_name = library_name.to_sym 58 | return true if @loaded_libraries.include?(library_name) 59 | jpath = get_library_directory_path(library_name, 'jar') 60 | jars = get_library_paths(library_name, 'jar') 61 | return false if jars.empty? 62 | jars.each { |jar| require jar } 63 | platform_specific_library_paths = get_platform_specific_library_paths(jpath) 64 | platform_specific_library_paths = platform_specific_library_paths.select do |ppath| 65 | FileTest.directory?(ppath) && !Dir.glob(File.join(ppath, '*.{so,dll,jnilib}')).empty? 66 | end 67 | unless platform_specific_library_paths.empty? 68 | platform_specific_library_paths << java.lang.System.getProperty('java.library.path') 69 | new_library_path = platform_specific_library_paths.join(java.io.File.pathSeparator) 70 | java.lang.System.setProperty('java.library.path', new_library_path) 71 | field = java.lang.Class.for_name('java.lang.ClassLoader').get_declared_field('sys_paths') 72 | if field 73 | field.accessible = true 74 | field.set(java.lang.Class.for_name('java.lang.System').get_class_loader, nil) 75 | end 76 | end 77 | @loaded_libraries[library_name] = true 78 | end 79 | 80 | def platform 81 | match = %w(mac linux windows).find do |os| 82 | java.lang.System.getProperty('os.name').downcase.index(os) 83 | end 84 | return 'other' unless match 85 | return match unless match =~ /mac/ 86 | 'macosx' 87 | end 88 | 89 | def get_platform_specific_library_paths(basename) 90 | bits = 'universal' # for MacOSX, but does this even work, or does Mac return '64'? 91 | if java.lang.System.getProperty('sun.arch.data.model') == '32' || 92 | java.lang.System.getProperty('java.vm.name').index('32') 93 | bits = '32' 94 | elsif java.lang.System.getProperty('sun.arch.data.model') == '64' || 95 | java.lang.System.getProperty('java.vm.name').index('64') 96 | bits = '64' unless platform =~ /macosx/ 97 | end 98 | [platform, platform + bits].map { |p| File.join(basename, p) } 99 | end 100 | 101 | def get_library_paths(library_name, extension = nil) 102 | dir = get_library_directory_path(library_name, extension) 103 | Dir.glob("#{dir}/*.{rb,jar}") 104 | end 105 | 106 | protected 107 | 108 | def get_library_directory_path(library_name, extension = nil) 109 | extensions = extension ? [extension] : %w(jar rb) 110 | extensions.each do |ext| 111 | ["#{SKETCH_ROOT}/library/#{library_name}", 112 | "#{Processing::RP_CONFIG['PROCESSING_ROOT']}/modes/java/libraries/#{library_name}/library", 113 | "#{RP5_ROOT}/library/#{library_name}/library", 114 | "#{RP5_ROOT}/library/#{library_name}", 115 | "#{@sketchbook_library_path}/#{library_name}/library" 116 | ].each do |jpath| 117 | if File.exist?(jpath) && !Dir.glob(format('%s/*.%s', jpath, ext)).empty? 118 | return jpath 119 | end 120 | end 121 | end 122 | nil 123 | end 124 | 125 | def find_sketchbook_path 126 | preferences_paths = [] 127 | sketchbook_paths = [] 128 | sketchbook_path = Processing::RP_CONFIG.fetch('sketchbook_path', false) 129 | return sketchbook_path if sketchbook_path 130 | ["'Application Data/Processing'", 'AppData/Roaming/Processing', 131 | 'Library/Processing', 'Documents/Processing', 132 | '.processing', 'sketchbook'].each do |prefix| 133 | spath = format('%s/%s', ENV['HOME'], prefix) 134 | pref_path = format('%s/preferences.txt', spath) 135 | preferences_paths << pref_path if FileTest.file?(pref_path) 136 | sketchbook_paths << spath if FileTest.directory?(spath) 137 | end 138 | return sketchbook_paths.first if preferences_paths.empty? 139 | lines = IO.readlines(preferences_paths.first) 140 | matchedline = lines.grep(/^sketchbook/).first 141 | matchedline[/=(.+)/].gsub('=', '') 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /library/boids/boids.rb: -------------------------------------------------------------------------------- 1 | # Boids -- after Tom de Smedt. 2 | # See his Python version: http://nodebox.net/code/index.php/Boids 3 | # This is an example of how a pure-Ruby library can work. 4 | # -- omygawshkenas 5 | 6 | class Boid 7 | attr_accessor :boids, :x, :y, :z, :vx, :vy, :vz, :is_perching, :perch_time 8 | 9 | def initialize(boids, x, y, z) 10 | @boids, @flock = boids, boids 11 | @x, @y, @z = x, y, z 12 | @vx, @vy, @vz = 0.0, 0.0, 0.0 13 | @is_perching = false 14 | @perch_time = 0.0 15 | end 16 | 17 | def cohesion(d = 100.0) 18 | # Boids gravitate towards the center of the flock, 19 | # Which is the averaged position of the rest of the boids. 20 | cvx, cvy, cvz = 0.0, 0.0, 0.0 21 | boids.reject { |bd| bd.equal? self }.each do |boid| 22 | cvx, cvy, cvz = cvx + boid.x, cvy + boid.y, cvz + boid.z 23 | end 24 | count = boids.length - 1.0 25 | cvx, cvy, cvz = cvx / count, cvy / count, cvz / count 26 | [(cvx - x) / d, (cvy - y) / d, (cvz - z) / d] 27 | end 28 | 29 | def separation(radius = 10.0) 30 | # Boids don't like to cuddle. 31 | svx, svy, svz = 0.0, 0.0, 0.0 32 | boids.reject { |bd| bd.equal? self }.each do |boid| 33 | dvx, dvy, dvz = x - boid.x, y - boid.y, z - boid.z 34 | svx += dvx if dvx.abs < radius 35 | svy += dvy if dvy.abs < radius 36 | svz += dvz if dvz.abs < radius 37 | end 38 | [svx, svy, svz] 39 | end 40 | 41 | def alignment(d = 5.0) 42 | # Boids like to fly at the speed of traffic. 43 | avx, avy, avz = 0.0, 0.0, 0.0 44 | boids.reject { |bd| bd.equal? self }.each do |boid| 45 | avx, avy, avz = avx + boid.vx, avy + boid.vy, avz + boid.vz 46 | end 47 | count = boids.length - 1.0 48 | avx, avy, avz = avx / count, avy / count, avz / count 49 | [(avx - vx) / d, (avy - vy) / d, (avz - vz) / d] 50 | end 51 | 52 | def limit(max = 30.0) 53 | # Tweet, Tweet! The boid police will bust you for breaking the speed limit. 54 | most = [@vx.abs, @vy.abs, @vz.abs].max 55 | return if most < max 56 | scale = max / most.to_f 57 | @vx *= scale 58 | @vy *= scale 59 | @vz *= scale 60 | end 61 | 62 | def angle 63 | a = (Math.atan(@vy / @vx) * (180.0 / Math::PI)) + 360.0 64 | a += 180.0 if @vx < 0.0 65 | a 66 | end 67 | 68 | def goal(gx, gy, gz, d = 50.0) 69 | # Them boids is hungry. 70 | [(gx - x) / d, (gy - y) / d, (gz - z) / d] 71 | end 72 | end 73 | 74 | require 'forwardable' 75 | 76 | class Boids 77 | include Enumerable 78 | extend Forwardable 79 | def_delegators(:@boids, :reject, :<<, :each, :shuffle!, :length, :next) 80 | 81 | attr_accessor :boids, :x, :y, :w, :h, 82 | :scattered, :has_goal, :flee 83 | 84 | attr_reader :scatter, :scatter_time, :scatter_i, 85 | :perch, :perch_y, :perch_t, :boids, 86 | :goal_x, :goal_y, :goal_z 87 | 88 | def initialize 89 | @boids = [] 90 | end 91 | 92 | def self.flock(n, x, y, w, h) 93 | Boids.new.setup(n, x, y, w, h) 94 | end 95 | 96 | def setup(n, x, y, w, h) 97 | n.times do 98 | dx, dy = rand(w), rand(h) 99 | z = rand(200.0) 100 | self << Boid.new(self, x + dx, y + dy, z) 101 | end 102 | @x, @y, @w, @h = x, y, w, h 103 | init 104 | self 105 | end 106 | 107 | def init 108 | @scattered = false 109 | @scatter = 0.005 110 | @scatter_time = 50.0 111 | @scatter_i = 0.0 112 | @perch = 1.0 # Lower this number to divebomb. 113 | @perch_y = h 114 | @perch_t = -> { rand(25..75.0) } 115 | @has_goal = false 116 | @flee = false 117 | @goal_x = @goal_y = @goal_z = 0.0 118 | end 119 | 120 | def scatter(chance = 0.005, frames = 50.0) 121 | @scatter = chance 122 | @scatter_time = frames 123 | end 124 | 125 | def no_scatter 126 | @scatter = 0.0 127 | end 128 | 129 | def perch(ground = nil, chance = 1.0, frames = nil) 130 | frames ||= -> { rand(25..75.0) } 131 | ground ||= h 132 | @perch, @perch_y, @perch_t = chance, ground, frames 133 | end 134 | 135 | def no_perch 136 | @perch = 0.0 137 | end 138 | 139 | def goal(gx, gy, gz, flee = false) 140 | @has_goal = true 141 | @flee = flee 142 | @goal_x, @goal_y, @goal_z = gx, gy, gz 143 | end 144 | 145 | def no_goal 146 | @has_goal = false 147 | end 148 | 149 | def constrain 150 | # Put them boids in a cage. 151 | dx, dy = w * 0.1, h * 0.1 152 | each do |b| 153 | b.vx += rand(dx) if b.x < x - dx 154 | b.vx += rand(dy) if b.y < y - dy 155 | b.vx -= rand(dx) if b.x > x + w + dx 156 | b.vy -= rand(dy) if b.y > y + h + dy 157 | b.vz += 10.0 if b.z < 0.0 158 | b.vz -= 10.0 if b.z > 100.0 159 | next unless b.y > @perch_y && rand < @perch 160 | b.y = @perch_y 161 | b.vy = -(b.vy.abs) * 0.2 162 | b.is_perching = true 163 | @perch_t.respond_to?(:call) ? b.perch_time = @perch_t.call : b.perch_time = @perch_t 164 | end 165 | end 166 | 167 | def update(opts = {}) # Just flutter, little boids ... just flutter away. 168 | options = { 169 | shuffled: true, # Shuffling keeps things flowing smooth. 170 | cohesion: 100.0, 171 | separation: 10.0, 172 | alignment: 5.0, 173 | goal: 20.0, 174 | limit: 30.0 175 | } 176 | options.merge! opts 177 | shuffle! if options[:shuffled] 178 | m1 = 1.0 # cohesion 179 | m2 = 1.0 # separation 180 | m3 = 1.0 # alignment 181 | m4 = 1.0 # goal 182 | @scattered = true if !(@scattered) && rand < @scatter 183 | if @scattered 184 | m1 = -m1 185 | m3 *= 0.25 186 | @scatter_i += 1.0 187 | end 188 | if @scatter_i >= @scatter_time 189 | @scattered = false 190 | @scatter_i = 0.0 191 | end 192 | m4 = 0.0 unless @has_goal 193 | m4 = -m4 if @flee 194 | each do |b| 195 | if b.is_perching 196 | if b.perch_time > 0.0 197 | b.perch_time -= 1.0 198 | next 199 | else 200 | b.is_perching = false 201 | end 202 | end 203 | vx1, vy1, vz1 = *b.cohesion(options[:cohesion]) 204 | vx2, vy2, vz2 = *b.separation(options[:separation]) 205 | vx3, vy3, vz3 = *b.alignment(options[:alignment]) 206 | vx4, vy4, vz4 = b.goal(@goal_x, @goal_y, @goal_z, options[:goal]) 207 | b.vx += m1 * vx1 + m2 * vx2 + m3 * vx3 + m4 * vx4 208 | b.vy += m1 * vy1 + m2 * vy2 + m3 * vy3 + m4 * vy4 209 | b.vz += m1 * vz1 + m2 * vz2 + m3 * vz3 + m4 * vz4 210 | b.limit(options[:limit]) 211 | b.x += b.vx 212 | b.y += b.vy 213 | b.z += b.vz 214 | end 215 | constrain 216 | end 217 | end 218 | -------------------------------------------------------------------------------- /lib/ruby-processing/helper_methods.rb: -------------------------------------------------------------------------------- 1 | # processing module wrapper 2 | require_relative '../rpextras' 3 | 4 | 5 | module Processing 6 | # Provides some convenience methods available in vanilla processing 7 | Java::Monkstone::MathToolLibrary.load(JRuby.runtime) 8 | module HelperMethods 9 | # processings epsilon may not be defined yet 10 | EPSILON ||= 1.0e-04 11 | # Nice block method to draw to a buffer. 12 | # You can optionally pass it a width, a height, and a renderer. 13 | # Takes care of starting and ending the draw for you. 14 | def buffer(buf_width = width, buf_height = height, renderer = @render_mode) 15 | buf = create_graphics(buf_width, buf_height, renderer) 16 | buf.begin_draw 17 | yield buf 18 | buf.end_draw 19 | buf 20 | end 21 | 22 | # A nice method to run a given block for a grid. 23 | # Lifted from action_coding/Nodebox. 24 | def grid(cols, rows, col_size = 1, row_size = 1) 25 | (0...cols * rows).map do |i| 26 | x = col_size * (i % cols) 27 | y = row_size * i.div(cols) 28 | yield x, y 29 | end 30 | end 31 | 32 | # lerp_color takes three or four arguments, in Java that's two 33 | # different methods, one regular and one static, so: 34 | def lerp_color(*args) 35 | args.length > 3 ? self.class.lerp_color(*args) : super(*args) 36 | end 37 | 38 | def color(*args) 39 | return super(*args) unless args.length == 1 40 | super(hex_color(args[0])) 41 | end 42 | 43 | # Overrides Processing convenience function thread, which takes a String 44 | # arg (for a function) to more rubylike version, takes a block... 45 | def thread(&block) 46 | if block_given? 47 | Thread.new(&block) 48 | else 49 | fail ArgumentError, 'thread must be called with a block', caller 50 | end 51 | end 52 | 53 | # explicitly provide 'processing.org' min instance method 54 | # to return a float:- a, b and c need to be floats 55 | 56 | def min(*args) 57 | args.min # { |a,b| a <=> b } optional block not reqd 58 | end 59 | 60 | # explicitly provide 'processing.org' max instance method 61 | # to return a float:- a, b and c need to be floats 62 | 63 | def max(*args) 64 | args.max # { |a, b| a <=> b } optional block not reqd 65 | end 66 | 67 | # explicitly provide 'processing.org' dist instance method 68 | def dist(*args) 69 | len = args.length 70 | if len == 4 71 | return dist2d(*args) 72 | elsif len == 6 73 | return dist3d(*args) 74 | end 75 | fail ArgumentError, 'takes 4 or 6 parameters' 76 | end 77 | 78 | # Uses PImage class method under hood 79 | def blend_color(c1, c2, mode) 80 | Java::ProcessingCore::PImage.blendColor(c1, c2, mode) 81 | end 82 | 83 | # There's just so many functions in Processing, 84 | # Here's a convenient way to look for them. 85 | def find_method(method_name) 86 | reg = Regexp.new("#{method_name}", true) 87 | methods.sort.select { |meth| reg.match(meth) } 88 | end 89 | 90 | # Proxy over a list of Java declared fields that have the same name as 91 | # some methods. Add to this list as needed. 92 | def proxy_java_fields 93 | fields = %w(sketchPath key frameRate frame mousePressed keyPressed) 94 | methods = fields.map { |field| java_class.declared_field(field) } 95 | @declared_fields = Hash[fields.zip(methods)] 96 | end 97 | 98 | class VersionError < StandardError 99 | end 100 | 101 | # By default, your sketch path is the folder that your sketch is in. 102 | # If you'd like to do something fancy, feel free. 103 | def set_sketch_path(spath = nil) 104 | field = @declared_fields['sketchPath'] 105 | begin 106 | field.set_value(java_self, spath || SKETCH_ROOT) 107 | rescue TypeError 108 | fail VersionError, 'Use JRubyArt for processing-3.0' 109 | end 110 | end 111 | 112 | # Fix java conversion problems getting the last key 113 | # If it's ASCII, return the character, otherwise the integer 114 | def key 115 | int = @declared_fields['key'].value(java_self) 116 | int < 256 ? int.chr : int 117 | end 118 | 119 | # Provide a convenient handle for the Java-space version of self. 120 | def java_self 121 | @java_self ||= to_java(Java::ProcessingCore::PApplet) 122 | end 123 | 124 | # Get the sketch path 125 | def sketch_path 126 | @declared_fields['sketchPath'].value(java_self) 127 | end 128 | 129 | # Fields that should be made accessible as under_scored. 130 | define_method(:mouse_x) { mouseX } 131 | 132 | define_method(:mouse_y) { mouseY } 133 | 134 | define_method(:pmouse_x) { pmouseX } 135 | 136 | define_method(:pmouse_y) { pmouseY } 137 | 138 | define_method(:frame_count) { frameCount } 139 | 140 | define_method(:mouse_button) { mouseButton } 141 | 142 | define_method(:key_code) { keyCode } 143 | 144 | # Ensure that load_strings returns a real Ruby array 145 | def load_strings(file_or_url) 146 | loadStrings(file_or_url).to_a 147 | end 148 | 149 | # Writes an array of strings to a file, one line per string. 150 | # This file is saved to the sketch's data folder 151 | def save_strings(filename, strings) 152 | saveStrings(filename, [strings].flatten.to_java(:String)) 153 | end 154 | 155 | # frame_rate needs to support reading and writing 156 | def frame_rate(fps = nil) 157 | return @declared_fields['frameRate'].value(java_self) unless fps 158 | super(fps) 159 | end 160 | 161 | # Is the mouse pressed for this frame? 162 | def mouse_pressed? 163 | @declared_fields['mousePressed'].value(java_self) 164 | end 165 | 166 | # Is a key pressed for this frame? 167 | def key_pressed? 168 | @declared_fields['keyPressed'].value(java_self) 169 | end 170 | 171 | private 172 | 173 | # parse single argument color int/double/String 174 | def hex_color(a) 175 | if a.is_a?(Fixnum) 176 | return Java::Monkstone::ColorUtil.colorLong(a) 177 | elsif a.is_a?(String) 178 | return Java::Monkstone::ColorUtil.colorString(a) if a =~ /#\h+/ 179 | fail StandardError, 'Dodgy Hexstring' 180 | end 181 | Java::Monkstone::ColorUtil.colorDouble(a) 182 | end 183 | 184 | def dist2d(*args) 185 | dx = args[0] - args[2] 186 | dy = args[1] - args[3] 187 | return 0 if dx.abs < EPSILON && dy.abs < EPSILON 188 | Math.hypot(dx, dy) 189 | end 190 | 191 | def dist3d(*args) 192 | dx = args[0] - args[3] 193 | dy = args[1] - args[4] 194 | dz = args[2] - args[5] 195 | return 0 if dx.abs < EPSILON && dy.abs < EPSILON && dz.abs < EPSILON 196 | Math.sqrt(dx * dx + dy * dy + dz * dz) 197 | end 198 | end 199 | end 200 | -------------------------------------------------------------------------------- /src/monkstone/MathTool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this tool is to allow ruby-processing users to use an alternative 3 | * to processing.org map, lerp and norm methods in their sketches 4 | * Copyright (C) 2015-16 Martin Prout. This tool is free software; you can 5 | * redistribute it and/or modify it under the terms of the GNU Lesser General 6 | * Public License as published by the Free Software Foundation; either version 7 | * 2.1 of the License, or (at your option) any later version. 8 | * 9 | * Obtain a copy of the license at http://www.gnu.org/licenses/lgpl-2.1.html 10 | */ 11 | package monkstone; 12 | 13 | import org.jruby.Ruby; 14 | import org.jruby.RubyClass; 15 | import org.jruby.RubyFloat; 16 | import org.jruby.RubyModule; 17 | import org.jruby.RubyObject; 18 | import org.jruby.RubyRange; 19 | import org.jruby.anno.JRubyMethod; 20 | import org.jruby.runtime.ThreadContext; 21 | import org.jruby.runtime.builtin.IRubyObject; 22 | 23 | /** 24 | * 25 | * @author MartinProut 26 | */ 27 | 28 | public class MathTool extends RubyObject { 29 | 30 | private static final long serialVersionUID = 4427564758225746633L; 31 | 32 | 33 | 34 | /** 35 | * 36 | * @param runtime 37 | */ 38 | public static void createMathTool(Ruby runtime) { 39 | RubyModule processing = runtime.defineModule("Processing"); 40 | RubyModule module = processing.defineModuleUnder("MathTool"); 41 | module.defineAnnotatedMethods(MathTool.class); 42 | } 43 | 44 | /** 45 | * 46 | * @param context JRuby runtime 47 | * @param recv self 48 | * @param args array of RubyRange (must be be numeric) 49 | * @return RubyFloat 50 | */ 51 | @JRubyMethod(name = "map1d", rest = true, module = true) 52 | public static IRubyObject mapOneD(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 53 | double value = (Double) args[0].toJava(Double.class); 54 | RubyRange r1 = (RubyRange) args[1]; 55 | RubyRange r2 = (RubyRange) args[2]; 56 | double first1 = (Double) r1.first(context).toJava(Double.class); 57 | double first2 = (Double) r2.first(context).toJava(Double.class); 58 | double last1 = (Double) r1.last(context).toJava(Double.class); 59 | double last2 = (Double) r2.last(context).toJava(Double.class); 60 | return mapMt(context, value, first1, last1, first2, last2); 61 | } 62 | 63 | /** 64 | * 65 | * @param context JRuby runtime 66 | * @param recv self 67 | * @param args array of RubyRange (must be be numeric) 68 | * @return RubyFloat 69 | */ 70 | @JRubyMethod(name = "constrained_map", rest = true, module = true) 71 | public static IRubyObject constrainedMap(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 72 | double value = (Double) args[0].toJava(Double.class); 73 | RubyRange r1 = (RubyRange) args[1]; 74 | RubyRange r2 = (RubyRange) args[2]; 75 | double first1 = (Double) r1.first(context).toJava(Double.class); 76 | double first2 = (Double) r2.first(context).toJava(Double.class); 77 | double last1 = (Double) r1.last(context).toJava(Double.class); 78 | double last2 = (Double) r2.last(context).toJava(Double.class); 79 | double max = Math.max(first1, last1); 80 | double min = Math.min(first1, last1); 81 | if (value < min) { 82 | value = min; 83 | } 84 | if (value > max) { 85 | value = max; 86 | } 87 | return mapMt(context, value, first1, last1, first2, last2); 88 | } 89 | 90 | 91 | /** 92 | * 93 | * @param context JRuby runtime 94 | * @param recv self 95 | * @param args floats as in processing map function 96 | * @return RubyFloat 97 | */ 98 | @JRubyMethod(name = {"p5map", "map"}, rest = true, module = true) 99 | public static IRubyObject mapProcessing(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 100 | double value = (Double) args[0].toJava(Double.class); 101 | double first1 = (Double) args[1].toJava(Double.class); 102 | double first2 = (Double) args[3].toJava(Double.class); 103 | double last1 = (Double) args[2].toJava(Double.class); 104 | double last2 = (Double) args[4].toJava(Double.class); 105 | return mapMt(context, value, first1, last1, first2, last2); 106 | } 107 | 108 | 109 | /** 110 | * A more correct version than processing.org version 111 | * @param context 112 | * @param recv 113 | * @param args args[2] should be between 0 and 1.0 if not returns start or stop 114 | * @return lerp value 115 | */ 116 | @JRubyMethod(name = "lerp", rest = true, module = true) 117 | public static IRubyObject lerpP(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 118 | double start = (Double) args[0].toJava(Double.class); 119 | double stop = (Double) args[1].toJava(Double.class); 120 | double amount = (Double) args[2].toJava(Double.class); 121 | if (amount <= 0) return args[0]; 122 | if (amount >= 1.0) return args[1]; 123 | return context.getRuntime().newFloat((1 - amount) * start + (stop * amount)); 124 | } 125 | 126 | 127 | /** 128 | * Identical to p5map(value, low, high, 0, 1). 129 | * Numbers outside of the range are not clamped to 0 and 1, 130 | * because out-of-range values are often intentional and useful. 131 | * @param context 132 | * @param recv 133 | * @param args 134 | * @return norm value 135 | */ 136 | @JRubyMethod(name = "norm", rest = true, module = true) 137 | public static IRubyObject normP(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 138 | double value = (Double) args[0].toJava(Double.class); 139 | double start = (Double) args[1].toJava(Double.class); 140 | double stop = (Double) args[2].toJava(Double.class); 141 | return mapMt(context, value, start, stop, 0, 1.0); 142 | } 143 | 144 | /** 145 | * Identical to p5map(value, low, high, 0, 1) but 'clamped'. 146 | * Numbers outside of the range are clamped to 0 and 1, 147 | * @param context 148 | * @param recv 149 | * @param args 150 | * @return strict normalized value ie 0..1.0 151 | */ 152 | @JRubyMethod(name = "norm_strict", rest = true, module = true) 153 | public static IRubyObject norm_strict(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 154 | Ruby ruby = context.runtime; 155 | double value = (Double) args[0].toJava(Double.class); 156 | double start = (Double) args[1].toJava(Double.class); 157 | double stop = (Double) args[2].toJava(Double.class); 158 | if (value <= start) { 159 | return new RubyFloat(ruby, 0); 160 | } else if (value >= stop) { 161 | return new RubyFloat(ruby, 1.0); 162 | } else { 163 | return mapMt(context, value, start, stop, 0, 1.0); 164 | } 165 | } 166 | 167 | static final RubyFloat mapMt(ThreadContext context, double value, double first1, double last1, double first2, double last2) { 168 | double result = first2 + (last2 - first2) * ((value - first1) / (last1 - first1)); 169 | return context.getRuntime().newFloat(result); 170 | } 171 | 172 | /** 173 | * Provides processing constrain method as a ruby module method 174 | * @param context 175 | * @param recv 176 | * @param args 177 | * @return original or limit values 178 | */ 179 | @JRubyMethod(name = "constrain", rest = true, module = true) 180 | public static IRubyObject constrainValue(ThreadContext context, IRubyObject recv, IRubyObject[] args) { 181 | RubyFloat value = args[0].convertToFloat(); 182 | RubyFloat start = args[1].convertToFloat(); 183 | RubyFloat stop = args[2].convertToFloat(); 184 | if (value.op_ge(context, start).isTrue() && value.op_le(context, stop).isTrue()) { 185 | return args[0]; 186 | } else if (value.op_ge(context, start).isTrue()) { 187 | return args[2]; 188 | } else { 189 | return args[1]; 190 | } 191 | } 192 | 193 | /** 194 | * 195 | * @param runtime 196 | * @param metaClass 197 | */ 198 | public MathTool(Ruby runtime, RubyClass metaClass) { 199 | super(runtime, metaClass); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /lib/ruby-processing/app.rb: -------------------------------------------------------------------------------- 1 | # version without embedded or online 2 | # This class is a thin wrapper around Processing's PApplet. 3 | # Most of the code here is for interfacing with Swing, 4 | # web applets, going fullscreen and so on. 5 | require 'java' 6 | require_relative 'helper_methods' 7 | require_relative 'helpers/string_extra' 8 | require_relative 'library_loader' 9 | require_relative 'config' 10 | 11 | 12 | 13 | module Processing 14 | # This is the main Ruby-Processing class, and is what you'll 15 | # inherit from when you create a sketch. This class can call 16 | # all of the methods available in Processing, and has two 17 | # mandatory methods, 'setup' and 'draw', both of which you 18 | # should define in your sketch. 'setup' will be called one 19 | # time when the sketch is first loaded, and 'draw' will be 20 | # called constantly, for every frame. 21 | Dir["#{RP_CONFIG["PROCESSING_ROOT"]}/core/library/\*.jar"].each do |jar| 22 | require jar unless jar =~ /native/ 23 | end 24 | Java::Monkstone::MathToolLibrary.load(JRuby.runtime) 25 | # Include some core processing classes that we'd like to use: 26 | include_package 'processing.core' 27 | 28 | # Watch the definition of these methods, to make sure 29 | # that Processing is able to call them during events. 30 | METHODS_TO_ALIAS ||= { 31 | mouse_pressed: :mousePressed, 32 | mouse_dragged: :mouseDragged, 33 | mouse_clicked: :mouseClicked, 34 | mouse_moved: :mouseMoved, 35 | mouse_released: :mouseReleased, 36 | key_pressed: :keyPressed, 37 | key_released: :keyReleased, 38 | key_typed: :keyTyped 39 | } 40 | # All sketches extend this class 41 | class App < PApplet 42 | include Math 43 | include HelperMethods 44 | include MathTool 45 | # Alias some methods for familiarity for Shoes coders. 46 | # attr_accessor :frame, :title 47 | alias_method :oval, :ellipse 48 | alias_method :stroke_width, :stroke_weight 49 | alias_method :rgb, :color 50 | alias_method :gray, :color 51 | 52 | def sketch_class 53 | self.class.sketch_class 54 | end 55 | 56 | # Keep track of what inherits from the Processing::App, because we're going 57 | # to want to instantiate one. 58 | def self.inherited(subclass) 59 | super(subclass) 60 | @sketch_class = subclass 61 | end 62 | 63 | class << self 64 | # Handy getters and setters on the class go here: 65 | attr_accessor :sketch_class, :library_loader 66 | 67 | def load_libraries(*args) 68 | library_loader ||= LibraryLoader.new 69 | library_loader.load_library(*args) 70 | end 71 | alias_method :load_library, :load_libraries 72 | 73 | def library_loaded?(library_name) 74 | library_loader.library_loaded?(library_name) 75 | end 76 | 77 | def load_ruby_library(*args) 78 | library_loader.load_ruby_library(*args) 79 | end 80 | 81 | def load_java_library(*args) 82 | library_loader.load_java_library(*args) 83 | end 84 | 85 | # When certain special methods get added to the sketch, we need to let 86 | # Processing call them by their expected Java names. 87 | def method_added(method_name) #:nodoc: 88 | return unless METHODS_TO_ALIAS.key?(method_name) 89 | alias_method METHODS_TO_ALIAS[method_name], method_name 90 | end 91 | end 92 | 93 | def library_loaded?(library_name) 94 | self.class.library_loaded?(library_name) 95 | end 96 | 97 | # It is 'NOT' usually necessary to directly pass options to a sketch, it 98 | # gets done automatically for you. Since processing-2.0 you should prefer 99 | # setting the sketch width and height and renderer using the size method, 100 | # in the sketch (as with vanilla processing), which should be the first 101 | # argument in setup. Sensible options to pass are x and y to locate sketch 102 | # on the screen, or full_screen: true (prefer new hash syntax) 103 | 104 | def initialize(options = {}) 105 | super() 106 | post_initialize(options) 107 | $app = self 108 | proxy_java_fields 109 | set_sketch_path # unless Processing.online? 110 | mix_proxy_into_inner_classes 111 | java.lang.Thread.default_uncaught_exception_handler = proc do 112 | |_thread_, exception| 113 | puts(exception.class.to_s) 114 | puts(exception.message) 115 | puts(exception.backtrace.map { |trace| "\t#{trace}" }) 116 | close 117 | end 118 | run_sketch(options) 119 | end 120 | 121 | def size(*args) 122 | w, h, mode = *args 123 | @width ||= w 124 | @height ||= h 125 | @render_mode ||= mode 126 | import_opengl if /opengl/ =~ mode 127 | super(*args) 128 | end 129 | 130 | def post_initialize(_args) 131 | nil 132 | end 133 | 134 | # Set the size if we set it before we start the animation thread. 135 | def start 136 | size(@width, @height) if @width && @height 137 | super() 138 | end 139 | 140 | # Provide a loggable string to represent this sketch. 141 | def inspect 142 | "#" 143 | end 144 | 145 | # Cleanly close and shutter a running sketch. 146 | def close 147 | control_panel.remove if respond_to?(:control_panel) 148 | dispose 149 | frame.dispose 150 | end 151 | 152 | private 153 | 154 | # Mix the Processing::Proxy into any inner classes defined for the 155 | # sketch, attempting to mimic the behavior of Java's inner classes. 156 | def mix_proxy_into_inner_classes 157 | klass = Processing::App.sketch_class 158 | klass.constants.each do |name| 159 | const = klass.const_get name 160 | next if const.class != Class || const.to_s.match(/^Java::/) 161 | const.class_eval('include Processing::Proxy') 162 | end 163 | end 164 | 165 | def import_opengl 166 | # Include processing opengl classes that we'd like to use: 167 | %w(FontTexture FrameBuffer LinePath LineStroker PGL 168 | PGraphics2D PGraphics3D PGraphicsOpenGL PShader 169 | PShapeOpenGL Texture).each do |klass| 170 | java_import "processing.opengl.#{klass}" 171 | end 172 | end 173 | 174 | def run_sketch(options = {}) 175 | args = [] 176 | @width, @height = options[:width], options[:height] 177 | if options[:full_screen] 178 | present = true 179 | args << '--full-screen' 180 | args << "--bgcolor=#{options[:bgcolor]}" if options[:bgcolor] 181 | end 182 | xc = Processing::RP_CONFIG['X_OFF'] ||= 0 183 | yc = Processing::RP_CONFIG['Y_OFF'] ||= 0 184 | x = options.fetch(:x, xc) 185 | y = options.fetch(:y, yc) 186 | args << "--location=#{x},#{y}" # important no spaces here 187 | string_extra = StringExtra.new(File.basename(SKETCH_PATH).sub(/(\.rb)$/, '')) 188 | title = options.fetch(:title, string_extra.titleize) 189 | args << title 190 | PApplet.run_sketch(args.to_java(:string), self) 191 | end 192 | end # Processing::App 193 | 194 | # This module will get automatically mixed in to any inner class of 195 | # a Processing::App, in order to mimic Java's inner classes, which have 196 | # unfettered access to the methods defined in the surrounding class. 197 | module Proxy 198 | include Math 199 | include MathTool 200 | # Generate a list of method names to proxy for inner classes. 201 | # Nothing camelCased, nothing __internal__, just the Processing API. 202 | def self.desired_method_names(inner_class) 203 | bad_method = /__/ # Internal JRuby methods. 204 | unwanted = PApplet.superclass.instance_methods + Object.instance_methods 205 | unwanted -= %w(width height cursor create_image background size resize) 206 | methods = Processing::App.public_instance_methods 207 | methods.reject do |m| 208 | unwanted.include?(m) || bad_method.match(m) || inner_class.method_defined?(m) 209 | end 210 | end 211 | 212 | # Proxy methods through to the sketch. 213 | def self.proxy_methods(inner_class) 214 | code = desired_method_names(inner_class).reduce('') do |rcode, method| 215 | rcode << <<-EOS 216 | def #{method}(*args, &block) # def rect(*args, &block) 217 | if block_given? # if block_given? 218 | $app.send :'#{method}', *args, &block # ... 219 | else # else 220 | $app.#{method} *args # $app.rect *args 221 | end # end 222 | end # end 223 | EOS 224 | end 225 | inner_class.class_eval(code) 226 | end 227 | 228 | # Proxy the sketch's constants on to the inner classes. 229 | def self.proxy_constants(inner_class) 230 | Processing::App.constants.each do |name| 231 | next if inner_class.const_defined?(name) 232 | inner_class.const_set(name, Processing::App.const_get(name)) 233 | end 234 | end 235 | 236 | # Don't do all of the work unless we have an inner class that needs it. 237 | def self.included(inner_class) 238 | proxy_methods(inner_class) 239 | proxy_constants(inner_class) 240 | end 241 | end # Processing::Proxy 242 | end # Processing 243 | -------------------------------------------------------------------------------- /src/monkstone/arcball/Arcball.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this library is to allow users to use ArcBall in processing 3 | * sketches Copyright (C) 2014 Martin Prout This library is free software; you 4 | * can redistribute it and/or modify it under the terms of the GNU Lesser 5 | * General Public License as published by the Free Software Foundation; either 6 | * version 2.1 of the License, or (at your option) any later version. 7 | * 8 | * Obtain a copy of the license at http://www.gnu.org/licenses/lgpl-2.1.html 9 | */ 10 | 11 | /* 12 | * CREDITS...Initially I found this arcball in a sketch by Ariel Malka, 13 | * only later did I find the Tom Carden processing tutorial example, so take your pick 14 | * 15 | * 1) Ariel Malka - June 23, 2003 http://www.chronotext.org 16 | * 17 | * 2) Simon Greenwold? 2003 (as reported 2006 by Tom Carden http://wiki.processing.org/w/Arcball) 18 | * 19 | * 3) Arcball concept invented by Ken Shoemake, published in his 1985 SIGGRAPH paper "Animating rotations with quaternion curves". 20 | * 21 | * 4) Somewhat modified by Martin Prout to support callbacks from processing sketch 22 | **/ 23 | package monkstone.arcball; 24 | 25 | import java.util.Objects; 26 | import processing.core.PApplet; 27 | import processing.event.KeyEvent; 28 | import processing.event.MouseEvent; 29 | 30 | /** 31 | * Supports the Arcball and MouseWheel zoom manipulation of objects in 32 | * processing 33 | * 34 | * @author Martin Prout 35 | */ 36 | public class Arcball { 37 | 38 | private double center_x; 39 | private double center_y; 40 | private double radius; 41 | private Jvector v_down; 42 | private Jvector v_drag; 43 | private Quaternion q_now; 44 | private Quaternion q_down; 45 | private Quaternion q_drag; 46 | private Jvector[] axisSet; 47 | private Constrain axis; 48 | private boolean isActive = false; 49 | private PApplet parent; 50 | private double zoom = 1.0f; 51 | private WheelHandler zoomWheelHandler; 52 | private boolean camera = false; 53 | float DEPTH = (float) (1 / (2 * Math.tan(Math.PI / 6))); 54 | 55 | /** 56 | * 57 | * @param parent PApplet 58 | * @param center_x double x coordinate of arcball center 59 | * @param center_y double y coordinate of arcball center 60 | * @param radius double radius of arcball 61 | */ 62 | public Arcball(PApplet parent, double center_x, double center_y, double radius) { 63 | this.zoomWheelHandler = new WheelHandler() { 64 | @Override 65 | public void handleWheel(int delta) { 66 | zoom += delta * 0.05; 67 | } 68 | }; 69 | this.parent = parent; 70 | this.center_x = center_x; 71 | this.center_y = center_y; 72 | this.radius = radius; 73 | this.v_down = new Jvector(); 74 | this.v_drag = new Jvector(); 75 | this.q_now = new Quaternion(); 76 | this.q_down = new Quaternion(); 77 | this.q_drag = new Quaternion(); 78 | this.axisSet = new Jvector[]{new Jvector(1.0f, 0.0f, 0.0f), new Jvector(0.0f, 1.0f, 0.0f), new Jvector(0.0f, 0.0f, 1.0f)}; 79 | this.axis = Constrain.FREE; // no constraints... 80 | } 81 | 82 | /** 83 | * Default centered arcball and half width or half height whichever smaller 84 | * 85 | * @param parent 86 | * 87 | */ 88 | public Arcball(PApplet parent) { 89 | // this(parent, parent.width / 2.0f, parent.height / 2.0f, Math.min(parent.width, parent.height) * 0.5f); 90 | this(parent, 0f, 0f, Math.min(parent.width, parent.height) * 0.8f); 91 | parent.camera(parent.width / 2.0f, parent.height / 2.0f, (parent.height * DEPTH), 0, 0, 0, 0, 1.0f, 0); 92 | camera = true; 93 | this.axis = Constrain.FREE; // no constraints... 94 | } 95 | 96 | /** 97 | * mouse event to register 98 | * 99 | * @param e 100 | */ 101 | public void mouseEvent(MouseEvent e) { 102 | int x = e.getX(); 103 | int y = e.getY(); 104 | switch (e.getAction()) { 105 | case (MouseEvent.PRESS): 106 | v_down = mouse2sphere(x, y); 107 | q_down.set(q_now); 108 | q_drag.reset(); 109 | break; 110 | case (MouseEvent.DRAG): 111 | v_drag = mouse2sphere(x, y); 112 | q_drag.set(v_down.dot(v_drag), v_down.cross(v_drag)); 113 | break; 114 | case (MouseEvent.WHEEL): 115 | if (zoomWheelHandler != null) { 116 | zoomWheelHandler.handleWheel(e.getCount()); 117 | } 118 | break; 119 | default: 120 | } 121 | } 122 | 123 | /** 124 | * key event to register 125 | * 126 | * @param e 127 | */ 128 | public void keyEvent(processing.event.KeyEvent e) { 129 | if (e.getAction() != KeyEvent.PRESS) { 130 | } else { 131 | switch (e.getKey()) { 132 | case 'x': 133 | constrain(Constrain.XAXIS); 134 | break; 135 | case 'y': 136 | constrain(Constrain.YAXIS); 137 | break; 138 | case 'z': 139 | constrain(Constrain.ZAXIS); 140 | break; 141 | } 142 | } 143 | if (e.getAction() == KeyEvent.RELEASE) { 144 | constrain(Constrain.FREE); 145 | } 146 | } 147 | 148 | /** 149 | * 150 | */ 151 | public void pre() { 152 | if (!camera) { 153 | parent.translate((float) center_x, (float) center_y); 154 | } 155 | update(); 156 | } 157 | 158 | /** 159 | * May or may not be required for use in Web Applet it works so why worry as 160 | * used by Jonathan Feinberg peasycam, and that works OK 161 | * 162 | * @param active 163 | */ 164 | public void setActive(boolean active) { 165 | if (active != isActive) { 166 | isActive = active; 167 | if (active) { 168 | this.parent.registerMethod("dispose", this); 169 | this.parent.registerMethod("pre", this); 170 | this.parent.registerMethod("mouseEvent", this); 171 | this.parent.registerMethod("keyEvent", this); 172 | 173 | } else { 174 | this.parent.unregisterMethod("pre", this); 175 | this.parent.unregisterMethod("mouseEvent", this); 176 | this.parent.unregisterMethod("keyEvent", this); 177 | } 178 | } 179 | } 180 | 181 | /** 182 | * Don't call this directly in sketch use reflection to call in eg in pre() 183 | */ 184 | private void update() { 185 | q_now = Quaternion.mult(q_drag, q_down); 186 | applyQuaternion2Matrix(q_now); 187 | parent.scale((float) zoom); 188 | } 189 | 190 | /** 191 | * Returns either the Jvector of mouse position mapped to a sphere or the 192 | * constrained version (when constrained to one axis) 193 | * 194 | * @param x 195 | * @param y 196 | * @return mouse coordinate mapped to unit sphere 197 | */ 198 | public Jvector mouse2sphere(double x, double y) { 199 | Jvector v = new Jvector((x - center_x) / radius, (y - center_y) / radius, 0); 200 | double mag_sq = v.x * v.x + v.y * v.y; 201 | if (mag_sq > 1.0) { 202 | v.normalize(); 203 | } else { 204 | v.z = Math.sqrt(1.0 - mag_sq); 205 | } 206 | if (axis != Constrain.FREE) { 207 | v = constrainVector(v, axisSet[axis.index()]); 208 | } 209 | return v; 210 | } 211 | 212 | /** 213 | * Returns the Jvector if the axis is constrained 214 | * 215 | * @param vector 216 | * @param axis 217 | * @return constrained vector 218 | */ 219 | public Jvector constrainVector(Jvector vector, Jvector axis) { 220 | Jvector res = vector.sub(axis.mult(axis.dot(vector))); 221 | return res.normalize(); // like Jvector res is changed 222 | } 223 | 224 | /** 225 | * Constrain rotation to this axis 226 | * 227 | * @param axis 228 | */ 229 | public void constrain(Constrain axis) { 230 | this.axis = axis; 231 | } 232 | 233 | /** 234 | * Rotate the parent sketch according to the quaternion 235 | * 236 | * @param q 237 | */ 238 | public void applyQuaternion2Matrix(Quaternion q) { 239 | // instead of transforming q into a matrix and applying it... 240 | double[] aa = q.getValue(); 241 | parent.rotate((float) aa[0], (float) aa[1], (float) aa[2], (float) aa[3]); 242 | } 243 | 244 | /** 245 | * A recommended inclusion for a processing library 246 | */ 247 | public void dispose() { 248 | setActive(false); 249 | } 250 | 251 | /** 252 | * 253 | * @param obj 254 | * @return java boolean 255 | */ 256 | @Override 257 | public boolean equals(Object obj) { 258 | if (obj == null) { 259 | return false; 260 | } 261 | if (getClass() != obj.getClass()) { 262 | return false; 263 | } 264 | final Arcball other = (Arcball) obj; 265 | if (Double.doubleToLongBits(this.center_x) != Double.doubleToLongBits(other.center_x)) { 266 | return false; 267 | } 268 | if (Double.doubleToLongBits(this.center_y) != Double.doubleToLongBits(other.center_y)) { 269 | return false; 270 | } 271 | if (Double.doubleToLongBits(this.radius) != Double.doubleToLongBits(other.radius)) { 272 | return false; 273 | } 274 | return Objects.equals(this.parent, other.parent); 275 | } 276 | 277 | /** 278 | * 279 | * @return has code int 280 | */ 281 | @Override 282 | public int hashCode() { 283 | long hash = 3; 284 | hash = 59 * hash + Double.doubleToLongBits(this.center_x); 285 | hash = 59 * hash + Double.doubleToLongBits(this.center_y); 286 | hash = 59 * hash + Double.doubleToLongBits(this.radius); 287 | hash = 59 * hash + Objects.hashCode(this.parent); 288 | return (int) hash; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /lib/ruby-processing/runner.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require 'fileutils' 3 | require 'rbconfig' 4 | require_relative '../ruby-processing/config' 5 | require_relative '../ruby-processing/version' 6 | 7 | module Processing 8 | 9 | # Utility class to handle the different commands that the 'rp5' command 10 | # offers. Able to run, watch, live, create, app, and unpack 11 | class Runner 12 | HELP_MESSAGE ||= <<-EOS 13 | Version: #{RubyProcessing::VERSION} 14 | 15 | Ruby-Processing is a little shim between Processing and JRuby that helps 16 | you create sketches of code art. 17 | 18 | Usage: 19 | rp5 [choice] path/to/sketch 20 | 21 | choice:- 22 | run: run sketch once 23 | watch: watch for changes on the file and relaunch it on the fly 24 | live: launch sketch and give an interactive IRB shell 25 | create [width height][mode][flag]: create a new sketch. 26 | app: create an application version of the sketch 27 | setup: check setup, install jruby-complete, unpack samples 28 | 29 | Common options: 30 | --nojruby: use jruby-complete in place of an installed version of jruby 31 | (Set [JRUBY: 'false'] in .rp5rc to make using jruby-complete default) 32 | 33 | Examples: 34 | rp5 setup unpack_samples 35 | rp5 run rp_samples/samples/contributed/jwishy.rb 36 | rp5 run-app rp_samples/samples/contributed/jwishy.rb 37 | rp5 create some_new_sketch 640 480 p3d (P3D mode example) 38 | rp5 create some_new_sketch 640 480 --wrap (a class wrapped default sketch) 39 | rp5 watch some_new_sketch.rb 40 | 41 | Everything Else: 42 | http://wiki.github.com/jashkenas/ruby-processing 43 | 44 | EOS 45 | 46 | WIN_PATTERNS = [ 47 | /bccwin/i, 48 | /cygwin/i, 49 | /djgpp/i, 50 | /ming/i, 51 | /mswin/i, 52 | /wince/i 53 | ] 54 | 55 | attr_reader :os 56 | 57 | # Start running a ruby-processing sketch from the passed-in arguments 58 | def self.execute 59 | runner = new 60 | runner.parse_options(ARGV) 61 | runner.execute! 62 | end 63 | 64 | # Dispatch central. 65 | def execute! 66 | case @options.action 67 | when 'run' then run(@options.path, @options.args) 68 | when 'run-app' then run_app(@options.path, @options.args) 69 | when 'watch' then watch(@options.path, @options.args) 70 | when 'live' then live(@options.path, @options.args) 71 | when 'create' then create(@options.path, @options.args) 72 | when 'app' then app(@options.path) 73 | when 'setup' then setup(@options.path) 74 | when /-v/ then show_version 75 | when /-h/ then show_help 76 | else 77 | show_help 78 | end 79 | end 80 | 81 | # Parse the command-line options. Keep it simple. 82 | def parse_options(args) 83 | @options = OpenStruct.new 84 | @options.wrap = !args.delete('--wrap').nil? 85 | @options.inner = !args.delete('--inner').nil? 86 | @options.jruby = !args.delete('--jruby').nil? 87 | @options.nojruby = !args.delete('--nojruby').nil? 88 | @options.action = args[0] || nil 89 | @options.path = args[1] || File.basename(Dir.pwd + '.rb') 90 | @options.args = args[2..-1] || [] 91 | end 92 | 93 | # Create a fresh Ruby-Processing sketch, with the necessary 94 | # boilerplate filled out. 95 | def create(sketch, args) 96 | require_relative '../ruby-processing/exporters/creator' 97 | return Processing::Inner.new.create!(sketch, args) if @options.inner 98 | return Processing::ClassSketch.new.create!(sketch, args) if @options.wrap 99 | Processing::BasicSketch.new.create!(sketch, args) 100 | end 101 | 102 | # Just simply run a ruby-processing sketch. 103 | def run(sketch, args) 104 | ensure_exists(sketch) 105 | spin_up('run.rb', sketch, args) 106 | end 107 | 108 | def run_app(sketch, args) 109 | ensure_exists(sketch) 110 | spin_up('run_app.rb', sketch, args) 111 | end 112 | 113 | # Run a sketch, keeping an eye on it's file, and reloading 114 | # whenever it changes. 115 | def watch(sketch, args) 116 | ensure_exists(sketch) 117 | spin_up('watch.rb', sketch, args) 118 | end 119 | 120 | # Run a sketch, opening its guts to IRB, letting you play with it. 121 | def live(sketch, args) 122 | ensure_exists(sketch) 123 | spin_up('live.rb', sketch, args) 124 | end 125 | 126 | # Generate a cross-platform application of a given Ruby-Processing sketch. 127 | def app(sketch) 128 | require_relative '../ruby-processing/exporters/application_exporter' 129 | Processing::ApplicationExporter.new.export!(sketch) 130 | end 131 | 132 | def setup(choice) 133 | proc_root = FileTest.exist?("#{ENV['HOME']}/.rp5rc") 134 | case choice 135 | when /check/ 136 | check(proc_root, FileTest.exist?("#{RP5_ROOT}/lib/ruby/jruby-complete.jar")) 137 | when /install/ 138 | install(proc_root) 139 | when /unpack_samples/ 140 | system "cd #{RP5_ROOT}/vendors && rake unpack_samples" 141 | else 142 | puts 'Usage: rp5 setup [check | install | unpack_samples]' 143 | end 144 | end 145 | 146 | def install(root_exist) 147 | system "cd #{RP5_ROOT}/vendors && rake" 148 | return if root_exist 149 | set_processing_root 150 | warn 'PROCESSING_ROOT set optimistically, run check to confirm' 151 | end 152 | 153 | def check(root_exist, installed) 154 | show_version 155 | root = ' PROCESSING_ROOT = Not Set!!!' unless root_exist 156 | root ||= " PROCESSING_ROOT = #{Processing::RP_CONFIG['PROCESSING_ROOT']}" 157 | jruby = Processing::RP_CONFIG['JRUBY'] 158 | x_off = Processing::RP_CONFIG['X_OFF'] 159 | y_off = Processing::RP_CONFIG['Y_OFF'] 160 | puts root 161 | puts " JRUBY = #{jruby}" unless jruby.nil? 162 | puts " X_OFF = #{x_off}" unless x_off.nil? 163 | puts " Y_OFF = #{y_off}" unless y_off.nil? 164 | puts " jruby-complete installed = #{installed}" 165 | end 166 | 167 | # Display the current version of Ruby-Processing. 168 | def show_version 169 | puts "Ruby-Processing version #{RubyProcessing::VERSION}" 170 | end 171 | 172 | # Show the standard help/usage message. 173 | def show_help 174 | puts HELP_MESSAGE 175 | end 176 | 177 | private 178 | 179 | # Trade in this Ruby instance for a JRuby instance, loading in a starter 180 | # script and passing it some arguments.Unless '--nojruby' is passed, the 181 | # installed version of jruby is used instead of our vendored jarred one 182 | # (which is required for some sketches eg shaders and for export). To use 183 | # jruby-complete by default set JRUBY: false in ~/.rp5rc config 184 | # (but that will make using other gems in your sketches hard....) 185 | def spin_up(starter_script, sketch, args) 186 | runner = "#{RP5_ROOT}/lib/ruby-processing/runners/#{starter_script}" 187 | warn('The --jruby flag is no longer required') if @options.jruby 188 | @options.nojruby = true if Processing::RP_CONFIG['JRUBY'] == 'false' 189 | java_args = discover_java_args(sketch) 190 | if @options.nojruby 191 | command = ['java', 192 | java_args, 193 | '-cp', 194 | jruby_complete, 195 | 'org.jruby.Main', 196 | runner, 197 | sketch, 198 | args].flatten 199 | else 200 | command = ['jruby', 201 | java_args, 202 | runner, 203 | sketch, 204 | args].flatten 205 | end 206 | exec(*command) 207 | # exec replaces the Ruby process with the JRuby one. 208 | end 209 | 210 | # If you need to pass in arguments to Java, such as the ones on this page: 211 | # http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/java.html 212 | # add them to a java_args.txt in your data directory next to your sketch. 213 | def discover_java_args(sketch) 214 | arg_file = "#{File.dirname(sketch)}/data/java_args.txt" 215 | args = [] 216 | args += dock_icon 217 | if FileTest.exist?(arg_file) 218 | args += File.read(arg_file).split(/\s+/) 219 | elsif Processing::RP_CONFIG['java_args'] 220 | args += Processing::RP_CONFIG['java_args'].split(/\s+/) 221 | end 222 | args.map! { |arg| "-J#{arg}" } unless @options.nojruby 223 | args 224 | end 225 | 226 | # NB: we really do require 'and' not '&&' to get message returned 227 | 228 | def ensure_exists(sketch) 229 | puts "Couldn't find: #{sketch}" and exit unless FileTest.exist?(sketch) 230 | end 231 | 232 | def jruby_complete 233 | rcomplete = File.join(RP5_ROOT, 'lib/ruby/jruby-complete.jar') 234 | return rcomplete if FileTest.exist?(rcomplete) 235 | warn "#{rcomplete} does not exist\nTry running `rp5 setup install`" 236 | exit 237 | end 238 | 239 | def host_os 240 | detect_os = RbConfig::CONFIG['host_os'] 241 | case detect_os 242 | when /mac|darwin/ then :mac 243 | when /linux/ then :linux 244 | when /solaris|bsd/ then :unix 245 | else 246 | WIN_PATTERNS.find { |r| detect_os =~ r } 247 | fail "unknown os: #{detect_os.inspect}" if Regexp.last_match.nil? 248 | :windows 249 | end 250 | end 251 | 252 | # Optimistically set processing root 253 | def set_processing_root 254 | require 'psych' 255 | @os ||= host_os 256 | data = {} 257 | path = File.expand_path("#{ENV['HOME']}/.rp5rc") 258 | if os == :mac 259 | data['PROCESSING_ROOT'] = '/Applications/Processing.app/Contents/Java' 260 | else 261 | root = "#{ENV['HOME']}/processing-2.2.1" 262 | data['PROCESSING_ROOT'] = root 263 | end 264 | data['JRUBY'] = true 265 | open(path, 'w:UTF-8') { |f| f.write(data.to_yaml) } 266 | end 267 | 268 | # On the Mac, we can display a fat, shiny ruby in the Dock. 269 | def dock_icon 270 | @os ||= host_os 271 | icon = [] 272 | if os == :mac 273 | icon << '-Xdock:name=Ruby-Processing' 274 | icon << "-Xdock:icon=#{RP5_ROOT}/lib/templates/application/Contents/Resources/sketch.icns" 275 | end 276 | icon 277 | end 278 | end # class Runner 279 | end # module Processing 280 | -------------------------------------------------------------------------------- /test/vecmath_spec_test.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' # don't use bundled minitest 2 | require 'java' 3 | require 'minitest/autorun' 4 | require 'minitest/pride' 5 | 6 | require_relative '../lib/rpextras' 7 | 8 | Java::MonkstoneVecmathVec2::Vec2Library.load(JRuby.runtime) 9 | Java::MonkstoneVecmathVec3::Vec3Library.load(JRuby.runtime) 10 | 11 | EPSILON = 1.0e-04 12 | 13 | Dir.chdir(File.dirname(__FILE__)) 14 | 15 | class VecmathTest < Minitest::Test 16 | def test_equals 17 | x, y = 1.0000001, 1.01 18 | a = Vec2D.new(x, y) 19 | assert_equal(a.to_a, [x, y], 'Failed to return Vec2D as and Array') 20 | end 21 | 22 | def test_not_equals 23 | a = Vec2D.new(3, 5) 24 | b = Vec2D.new(6, 7) 25 | refute_equal(a, b, 'Failed equals false') 26 | end 27 | 28 | def test_copy_equals 29 | x, y = 1.0000001, 1.01 30 | a = Vec2D.new(x, y) 31 | b = a.copy 32 | assert_equal(a.to_a, b.to_a, 'Failed deep copy') 33 | end 34 | 35 | def test_copy_not_equals 36 | x, y = 1.0000001, 1.01 37 | a = Vec2D.new(x, y) 38 | b = a.copy 39 | b *= 0 40 | refute_equal(a.to_a, b.to_a, 'Failed deep copy') 41 | end 42 | 43 | def test_equals_when_close 44 | a = Vec2D.new(3.0000000, 5.00000) 45 | b = Vec2D.new(3.0000000, 5.000001) 46 | assert_equal(a, b, 'Failed to return equal when v. close') 47 | end 48 | 49 | def test_sum 50 | a = Vec2D.new(3, 5) 51 | b = Vec2D.new(6, 7) 52 | c = Vec2D.new(9, 12) 53 | assert_equal(a + b, c, 'Failed to sum vectors') 54 | end 55 | 56 | def test_subtract 57 | a = Vec2D.new(3, 5) 58 | b = Vec2D.new(6, 7) 59 | c = Vec2D.new(-3, -2) 60 | assert_equal(a - b, c, 'Failed to subtract vectors') 61 | end 62 | 63 | def test_multiply 64 | a = Vec2D.new(3, 5) 65 | b = 2 66 | c = a * b 67 | d = Vec2D.new(6, 10) 68 | assert_equal(c, d, 'Failed to multiply vector by scalar') 69 | end 70 | 71 | def test_divide 72 | a = Vec2D.new(3, 5) 73 | b = 2 74 | c = Vec2D.new(1.5, 2.5) 75 | d = a / b 76 | assert_equal(c, d, 'Failed to divide vector by scalar') 77 | end 78 | 79 | def test_from_angle 80 | a = Vec2D.from_angle(Math::PI * 0.75) 81 | assert_equal(a, Vec2D.new(-1 * Math.sqrt(0.5), Math.sqrt(0.5)), 'Failed to create vector from angle') 82 | end 83 | 84 | def test_random 85 | a = Vec2D.random 86 | assert a.kind_of? Vec2D 87 | assert_in_delta(a.mag, 1.0, EPSILON) 88 | end 89 | 90 | def test_assign_value 91 | a = Vec2D.new(3, 5) 92 | a.x=23 93 | assert_equal(a.x, 23, 'Failed to assign x value') 94 | end 95 | 96 | def test_mag 97 | a = Vec2D.new(-3, -4) 98 | assert_equal(a.mag, 5, 'Failed to return magnitude of vector') 99 | end 100 | 101 | def test_mag_variant 102 | a = Vec2D.new(3.0, 2) 103 | b = Math.sqrt(3.0**2 + 2**2) 104 | assert_in_delta(a.mag, b, EPSILON, 'Failed to return magnitude of vector') 105 | end 106 | 107 | def test_mag_zero_one 108 | a = Vec2D.new(-1, 0) 109 | assert_equal(a.mag, 1, 'Failed to return magnitude of vector') 110 | end 111 | 112 | def test_dist 113 | a = Vec2D.new(3, 5) 114 | b = Vec2D.new(6, 7) 115 | assert_equal(a.dist(b), Math.sqrt(3.0**2 + 2**2), 'Failed to return distance between two vectors') 116 | end 117 | 118 | def test_lerp 119 | a = Vec2D.new(1, 1) 120 | b = Vec2D.new(3, 3) 121 | assert_equal(a.lerp(b, 0.5), Vec2D.new(2, 2), 'Failed to return lerp between two vectors') 122 | end 123 | 124 | def test_lerp_unclamped 125 | a = Vec2D.new(1, 1) 126 | b = Vec2D.new(3, 3) 127 | assert_equal(a.lerp(b, 5), Vec2D.new(11, 11), 'Failed to return lerp between two vectors') 128 | end 129 | 130 | def test_lerp! 131 | a = Vec2D.new(1, 1) 132 | b = Vec2D.new(3, 3) 133 | a.lerp!(b, 0.5) 134 | assert_equal(a, Vec2D.new(2, 2), 'Failed to return lerp! between two vectors') 135 | end 136 | 137 | def test_lerp_unclamped! 138 | a = Vec2D.new(1, 1) 139 | b = Vec2D.new(3, 3) 140 | a.lerp!(b, 5) 141 | assert_equal(a, Vec2D.new(11, 11), 'Failed to return lerp! between two vectors') 142 | end 143 | 144 | def test_set_mag 145 | a = Vec2D.new(1, 1) 146 | assert_equal(a.set_mag(Math.sqrt(32)), Vec2D.new(4, 4), 'Failed to set_mag vector') 147 | end 148 | 149 | def test_set_mag_block 150 | a = Vec2D.new(1, 1) 151 | assert_equal(a.set_mag(Math.sqrt(32)) { true }, Vec2D.new(4, 4), 'Failed to set_mag_block true vector') 152 | end 153 | 154 | def test_set_mag_block_false 155 | a = Vec2D.new(1, 1) 156 | assert_equal(a.set_mag(Math.sqrt(32)) { false }, Vec2D.new(1, 1), 'Failed to set_mag_block true vector') 157 | end 158 | 159 | def test_plus_assign 160 | a = Vec2D.new(3, 5) 161 | b = Vec2D.new(6, 7) 162 | a += b 163 | assert_equal(a, Vec2D.new(9, 12), 'Failed to += assign') 164 | end 165 | 166 | def test_normalize 167 | a = Vec2D.new(3, 5) 168 | b = a.normalize 169 | assert_in_delta(b.mag, 1, EPSILON, 'Failed to return a normalized vector') 170 | end 171 | 172 | def test_normalize! 173 | a = Vec2D.new(3, 5) 174 | a.normalize! 175 | assert_in_delta(a.mag, 1, EPSILON, 'Failed to return a normalized! vector') 176 | end 177 | 178 | def test_heading 179 | a = Vec2D.new(1, 1) 180 | assert_in_delta(a.heading, Math::PI / 4.0, EPSILON, 'Failed to return heading in radians') 181 | end 182 | 183 | def test_rotate 184 | x, y = 20, 10 185 | b = Vec2D.new(x, y) 186 | a = b.rotate(Math::PI / 2) 187 | assert_equal(a, Vec2D.new(-10, 20), 'Failed to rotate vector by scalar radians') 188 | end 189 | 190 | 191 | def test_inspect 192 | a = Vec2D.new(3, 2.000000000000001) 193 | assert_equal(a.inspect, 'Vec2D(x = 3.0000, y = 2.0000)') 194 | end 195 | 196 | def test_array_reduce 197 | array = [Vec2D.new(1, 2), Vec2D.new(10, 2), Vec2D.new(1, 2)] 198 | sum = array.reduce(Vec2D.new) { |c, d| c + d } 199 | assert_equal(sum, Vec2D.new(12, 6)) 200 | end 201 | 202 | def test_array_zip 203 | one = [Vec2D.new(1, 2), Vec2D.new(10, 2), Vec2D.new(1, 2)] 204 | two = [Vec2D.new(1, 2), Vec2D.new(10, 2), Vec2D.new(1, 2)] 205 | zipped = one.zip(two).flatten 206 | expected = [Vec2D.new(1, 2), Vec2D.new(1, 2), Vec2D.new(10, 2), Vec2D.new(10, 2), Vec2D.new(1, 2), Vec2D.new(1, 2)] 207 | assert_equal(zipped, expected) 208 | end 209 | 210 | def test_equals 211 | x, y, z = 1.0000001, 1.01, 0.0 212 | a = Vec3D.new(x, y) 213 | assert_equal(a.to_a, [x, y, z], 'Failed to return Vec3D as and Array') 214 | end 215 | 216 | def test_not_equals 217 | a = Vec3D.new(3, 5, 1) 218 | b = Vec3D.new(6, 7, 1) 219 | refute_equal(a, b, 'Failed equals false') 220 | end 221 | 222 | def test_copy_equals 223 | x, y, z = 1.0000001, 1.01, 1 224 | a = Vec3D.new(x, y, z) 225 | b = a.copy 226 | assert_equal(a.to_a, b.to_a, 'Failed deep copy') 227 | end 228 | 229 | def test_copy_not_equals 230 | x, y, z = 1.0000001, 1.01, 6.0 231 | a = Vec3D.new(x, y, z) 232 | b = a.copy 233 | b *= 0 234 | refute_equal(a.to_a, b.to_a, 'Failed deep copy') 235 | end 236 | 237 | def test_equals_when_close 238 | a = Vec3D.new(3.0000000, 5.00000, 2) 239 | b = Vec3D.new(3.0000000, 5.000001, 2) 240 | assert_equal(a, b, 'Failed to return equal when v. close') 241 | end 242 | 243 | def test_sum 244 | a = Vec3D.new(3, 5, 1) 245 | b = Vec3D.new(6, 7, 1) 246 | c = Vec3D.new(9, 12, 2) 247 | assert_equal(a + b, c, 'Failed to sum vectors') 248 | end 249 | 250 | def test_subtract 251 | a = Vec3D.new(3, 5, 0) 252 | b = Vec3D.new(6, 7, 1) 253 | c = Vec3D.new(-3, -2, -1) 254 | assert_equal(a - b, c, 'Failed to subtract vectors') 255 | end 256 | 257 | def test_multiply 258 | a = Vec3D.new(3, 5, 1) 259 | b = 2 260 | c = a * b 261 | d = Vec3D.new(6, 10, 2) 262 | assert_equal(c, d, 'Failed to multiply vector by scalar') 263 | end 264 | 265 | def test_divide 266 | a = Vec3D.new(3, 5, 4) 267 | b = 2 268 | c = Vec3D.new(1.5, 2.5, 2) 269 | d = a / b 270 | assert_equal(c, d, 'Failed to divide vector by scalar') 271 | end 272 | 273 | def test_random 274 | a = Vec3D.random 275 | assert a.kind_of? Vec3D 276 | assert_in_delta(a.mag, 1.0, EPSILON) 277 | end 278 | 279 | def test_assign_value 280 | a = Vec3D.new(3, 5) 281 | a.x=23 282 | assert_equal(a.x, 23, 'Failed to assign x value') 283 | end 284 | 285 | def test_mag 286 | a = Vec3D.new(-3, -4) 287 | assert_equal(a.mag, 5, 'Failed to return magnitude of vector') 288 | end 289 | 290 | def test_mag_variant 291 | a = Vec3D.new(3.0, 2) 292 | b = Math.sqrt(3.0**2 + 2**2) 293 | assert_in_delta(a.mag, b, EPSILON, 'Failed to return magnitude of vector') 294 | end 295 | 296 | def test_mag_zero_one 297 | a = Vec3D.new(-1, 0) 298 | assert_equal(a.mag, 1, 'Failed to return magnitude of vector') 299 | end 300 | 301 | def test_dist 302 | a = Vec3D.new(3, 5, 2) 303 | b = Vec3D.new(6, 7, 1) 304 | message = 'Failed to return distance between two vectors' 305 | assert_equal(a.dist(b), Math.sqrt(3.0**2 + 2**2 + 1), message) 306 | end 307 | 308 | def test_dist_squared 309 | a = Vec3D.new(3, 5, 2) 310 | b = Vec3D.new(6, 7, 1) 311 | message = 'Failed to return distance squared between two vectors' 312 | assert_equal(a.dist_squared(b), 3.0**2 + 2**2 + 1, message) 313 | end 314 | 315 | def test_dot 316 | a = Vec3D.new(10, 20, 0) 317 | b = Vec3D.new(60, 80, 0) 318 | assert_equal(a.dot(b), 2200.0, 'Failed to dot product') 319 | end 320 | 321 | def test_cross 322 | a = Vec3D.new(3, 5, 2) 323 | b = Vec3D.new(6, 7, 1) 324 | c = Vec3D.new(-9.0, 9.0, -9.0) 325 | assert_equal(a.cross(b), c, 'Failed cross product') 326 | end 327 | 328 | def test_set_mag 329 | a = Vec3D.new(1, 1) 330 | assert_equal(a.set_mag(Math.sqrt(32)), Vec3D.new(4, 4), 'Failed to set_mag vector') 331 | end 332 | 333 | def test_set_mag_block 334 | a = Vec3D.new(1, 1) 335 | assert_equal(a.set_mag(Math.sqrt(32)) { true }, Vec3D.new(4, 4), 'Failed to set_mag_block true vector') 336 | end 337 | 338 | def test_set_mag_block_false 339 | a = Vec3D.new(1, 1) 340 | assert_equal(a.set_mag(Math.sqrt(32)) { false }, Vec3D.new(1, 1), 'Failed to set_mag_block true vector') 341 | end 342 | 343 | def test_plus_assign 344 | a = Vec3D.new(3, 5) 345 | b = Vec3D.new(6, 7) 346 | a += b 347 | assert_equal(a, Vec3D.new(9, 12), 'Failed to += assign') 348 | end 349 | 350 | def test_normalize 351 | a = Vec3D.new(3, 5) 352 | b = a.normalize 353 | assert_in_delta(b.mag, 1, EPSILON, 'Failed to return a normalized vector') 354 | end 355 | 356 | def test_normalize! 357 | a = Vec3D.new(3, 5) 358 | a.normalize! 359 | assert_in_delta(a.mag, 1, EPSILON, 'Failed to return a normalized! vector') 360 | end 361 | 362 | def test_inspect 363 | a = Vec3D.new(3, 2.000000000000001, 1) 364 | assert_equal(a.inspect, 'Vec3D(x = 3.0000, y = 2.0000, z = 1.0000)') 365 | end 366 | 367 | def test_array_reduce 368 | array = [Vec3D.new(1, 2), Vec3D.new(10, 2), Vec3D.new(1, 2)] 369 | sum = array.reduce(Vec3D.new) { |c, d| c + d } 370 | assert_equal(sum, Vec3D.new(12, 6)) 371 | end 372 | 373 | def test_array_zip 374 | one = [Vec3D.new(1, 2), Vec3D.new(10, 2), Vec3D.new(1, 2)] 375 | two = [Vec3D.new(1, 2), Vec3D.new(10, 2), Vec3D.new(1, 2)] 376 | zipped = one.zip(two).flatten 377 | expected = [Vec3D.new(1, 2), Vec3D.new(1, 2), Vec3D.new(10, 2), Vec3D.new(10, 2), Vec3D.new(1, 2), Vec3D.new(1, 2)] 378 | assert_equal(zipped, expected) 379 | end 380 | 381 | def test_eql? 382 | a = Vec3D.new(3.0, 5.0, 0) 383 | b = Vec3D.new(3.0, 5.0, 0) 384 | assert(a.eql?(b)) 385 | end 386 | 387 | def test_not_eql? 388 | a = Vec3D.new(3.0, 5.0, 0) 389 | b = Vec3D.new(3.0, 5.000001, 0) 390 | refute(a.eql?(b)) 391 | end 392 | 393 | def test_equal? 394 | a = Vec3D.new(3.0, 5.0, 0) 395 | assert(a.equal?(a)) 396 | end 397 | 398 | def test_not_equal? 399 | a = Vec3D.new(3.0, 5.0, 0) 400 | b = Vec3D.new(3.0, 5.0, 0) 401 | refute(a.equal?(b)) 402 | end 403 | end 404 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v2.7.1 update to jruby-complete-1.7.26. 2 | 3 | v2.7.0 bump version in recognition of the 'new' run-app option see wiki Getting-Started 4 | 5 | 6 | v2.6.18 update to jruby-complete-1.7.25, to allow travis testing, anyone wishing to update to jruby-complete-9.0.5.0+ should try out `propane`. 7 | 8 | v2.6.17 move to a polyglot maven build and update to jruby-complete-1.7.24, to allow travis testing, should be easy to update to jruby-complete-9.0+ if required. Updating processing version is unecessary because JRubyArt does that. 9 | 10 | v2.6.16 update to jruby-complete-1.7.23 changed to static load for jruby extensions implement Vec2D.random and Vec3D.random 11 | * get rid of rspec as development requirement (all minitest now) this is expected to be the last release of ruby-processing 12 | 13 | v2.6.15 added guard against running 'watch' in top level directories, rescue error 14 | * when processing-3.0 is specified, and suggest updating to JRubyArt update to 15 | * jruby-complete-1.7.22 (this may be the final release of 1.7.XX series) 16 | * features deprecation of processing map in favor of p5map (or map1d) which 17 | * are now both jruby extensions, along with lerp and norm 18 | 19 | v2.6.14 revert to using 'require to load jars' because everything just worked 20 | * before, and sometimes just doesn't with java classpath loading. 21 | * jruby-9.0.0.0 has been fixed, so now we don't need compability changes... 22 | * use pry-java for live mode 23 | 24 | v2.6.13 update to jruby-complete-1.7.21 25 | * the perfomance of 1.7.XX series still exceeds jruby-9.0.0.0 26 | 27 | v2.6.12 Changes to make jruby and PApplet use same classloader 28 | * to make ruby-processing compatible with JRuby-9.0.0.0.rc1+ 29 | * getting ready for JRuby-9.0.0.0 release 30 | 31 | v2.6.11 Update examples sketches to version 1.6 32 | * Enhanced 'watch' mode now monitors '*.glsl' files... 33 | * Two new libraries to make it possible to use java reflection methods 34 | * video_event library makes possible use of 'capturEvent(c)' and 'movieEvent(m)' 35 | * library_proxy possible use of 'pre', 'draw' and 'post' in a ruby library. 36 | 37 | v2.6.10 Update to jruby-complete-1.7.20 38 | * Possibly the last 1.7 series releases prior to jruby-9.0.0.0? 39 | 40 | v2.6.9 Fix wrapped sketches (no-one complained, yet old hands prefer them?) 41 | * simplified the loading of files to monitor, in watch mode 42 | 43 | v2.6.8 Some more refactoring, mainly tidying up library_loader/app interface. 44 | * changes for Vec2D and Vec3D, added eql? (exact match) and dup (alias copy) 45 | 46 | v2.6.7 Update to use jruby-complete-1.7.19, replaced monkey patching of 47 | * String, by creating StringExtra and CamelString classes 48 | 49 | v2.6.6 Update to use jruby-complete-1.7.18 50 | 51 | v2.6.5 Update to use jruby-complete-1.7.16.2 52 | * Some more refactoring including helper_methods, and base runner 53 | * No need for erb when running bare sketches. For class wrapped sketches 54 | * make sure to call new, after all the only point in writing a class wrapped 55 | * sketch is to send runtime parameter (:title, :fullscreen, etc) 56 | 57 | v2.6.4 Update to use jruby-complete-1.7.16.1 58 | * Some general refactoring of app.rb, moved examples to 59 | * their own github repo. 60 | 61 | v2.6.3 Update to use jruby-complete-1.7.16 62 | * Avoid Vec2D hypot NaNN with guards, correct 63 | * --inner template, some more refactoring, full_screen is now only 64 | * available via a runtime arg (there is only one way to do it..) 65 | * now adding Range.clip (like numpy.clip) which is used to implement 66 | * processing constrain. Sometimes it will be better to use clip directly. 67 | 68 | v2.6.2 Update to use jruby-complete-1.7.15 69 | * X and Y screen offset for sketch, can be set in ~/.rp5rc 70 | * A post initialization hook has been included to allow custom parameter setting 71 | * More deprecated methods have been removed anyone wanting pow(x), sq(x), 72 | * radians(x) can easily make their own if they must. Prefer x**2, x * x and 73 | * x.radians in ruby-processing etc.. 74 | 75 | v2.6.1 The templates for the improved sketch creator, broke app export 76 | * fix means templates are now hard coded (functionality retained) 77 | * update to use jruby-complete-1.7.14. 78 | 79 | v2.6.0 Removal of eval hack, and deprecating / removing processing 80 | * convenience methods. New improved sketch creator, added templates 81 | * class wrapped --wrap, and "inner class mixin" --inner. 82 | 83 | v2.5.1 Some fixes, and remove eval hack from base_exporter 84 | * Since this release some processing convenience methods are 85 | * deprecated see wiki (includes second, minute, hour in favor 86 | * of ruby alternatives t = Time.now, t.hour, t.sec, t.min 87 | * and theta.radians should be used instead of radians(theta)) 88 | * These changes will be seen in 2.6.0 release, with removal of 89 | * yet another eval hack. 90 | 91 | v2.5.0 Some refactoring, and a new install procedure 92 | * Some re-factoring of both code and examples, including 93 | * replacing the overuse of __FILE__ with 'relative_require' 94 | * 'install_jruby_complete', replaced by 'rp5 setup install' 95 | * The vecmath library is now a compiled jruby extension 96 | * which includes an extremely simple ArcBall interface. 97 | * Introducing the fastmath library with DegLut tables for cos/sin. 98 | * Update to JRuby-1.7.13 99 | 100 | v2.4.4 Update to JRuby-1.7.12 101 | * Enhancement to Vec2D & Vec3D (preferred to PVector as 102 | * providing a more ruby-like interface), now provide a 103 | * conditional set_mag method, via optional &block. 104 | 105 | v2.4.3 Update to JRuby-1.7.11 106 | * Added an autorun demo Rakefile to some sample directories 107 | * Support utf-8 in sketches 108 | * Refactor and extend vecmath (updated drawolver to use Vec3D) 109 | 110 | v2.4.2 Update to JRuby-1.7.10 111 | * Revised suggestions for PROCESSING_ROOT on MacOSX 112 | 113 | v2.4.1 First release to return to rubygems since processing 1.0.11 114 | * Features a post-install download of jruby-complete (version 1.7.9) 115 | * Features use of jars from an installed version of vanilla processing, 116 | * on linux and windows use version 2.1.0 (later versions may also work). 117 | * For Mac, especially if you are using Mac "java" stick with version 2.0.3 118 | * Update gemspec to match modern expectations 119 | 120 | v2.4.0 Returning to rubygems distribution, by not including any jars 121 | * Use jars from an installed version of vanilla processing-2.0.3 (or version 2.1.0 linux and windows) 122 | * Require an installed jruby (with an optional jruby-complete-1.7.8 post 123 | * install) 124 | 125 | v2.3.1 Revert to processing-2.0.3 for MacOSX 126 | * Mac users may use Apple jvm java 6 127 | * Windows and Linux users need at least java 7 jre (java 8 does now work) 128 | 129 | v2.2.2 Update to JRuby-1.7.6 130 | * Merge vec.rb, quaternion.rb and arcball.rb into vecmath.rb, stricter path 131 | # requires ruby filename to match that of library 132 | 133 | v2.2.1 Replacing 'arcball' library with 'vecmath' library 134 | * Arcball functionality is retained (in vecmath library), Vec2D and Vec3D 135 | * have been added to 'vecmath' library, they provide a pure ruby alternative 136 | * to processings PVector class, and hence a more ruby like interface 137 | 138 | 139 | v2.2.0 Update to JRuby-1.7.5 140 | * Changed app.rb to only java_import used, core classes thus when mode JAVA2D 141 | * ie default mode do not java_import opengl classes, removed event classes 142 | * since we failed to address the directly 143 | 144 | v2.1.6 In anticipation of JRuby-1.7.5 145 | * Minor release to crystalize changes before JRuby-1.7.5 146 | * Rakefile tries to detect Windows OS & warn possibly missing 'wget' 147 | * Rubified and expanded Shiffmans advanced data examples 148 | 149 | v2.1.5 Update to processing-2.0.3 150 | * Minor changes to control_panel 151 | * Introducing file_chooser, deprecate select_input 152 | * Added display_width and display_height aliases 153 | 154 | v2.1.4 Improved build file 155 | * Build corrected to work on systems with directories containing spaces/etc 156 | * Control panel extra feature to allow setting of look and feel 157 | 158 | v2.1.3 Update to processing-2.0.2 159 | * Minor update to samples 160 | 161 | v2.1.2 Moved JRuby-Complete.jar (avoids classpath conflict) 162 | * Change to using external jruby as default, introduce --nojruby 163 | * flag to use provided jruby-complete 164 | * Tests revised to be more compatible with minitest ethos (capture_io) 165 | 166 | v2.1.1 Added Gemfile 167 | * Support bundler usage 168 | 169 | v2.1.0 gc-pruned ruby-processing-2.0 170 | * Since BFG tool was used for archive pruning 171 | * This repo is not compatible with forks of jashkenas prior to this release 172 | 173 | v2.0.1 First minor revision for ruby-processing-2.0 174 | * Changes for application export on Windows and linux 175 | * Added support for 'require_relative' on export 176 | 177 | v2.0.0 A major revision, now based on processing-2.0 and JRuby 1.7+ 178 | * Processing updated to processing-2.0.1 export to applet has disappeared, also P3D is 179 | * the new OPENGL (except Jogl2 instead of Jogl1.1) if you've got an old graphics card or even some new netbook 180 | * with onboard graphics you may have issues 181 | * http://forum.processing.org/one/topic/processing-2-0-won-t-run-on-netbooks-and-older-cheaper-hardware. 182 | * Processing-2.0 has its own event system (replacing java.awt.event), ruby-processing sketches will normally 183 | * use this event system. 184 | * JRuby upgraded to 1.7.4 (default is ruby 1.9 and 2.0 is possible with a switch) 185 | * NB: bare sketches replace class wrapped sketches see samples... 186 | * Samples have been extended to include vanilla processing library examples. 187 | * References to the 'online' variable have been removed (deprecated in processing-2.0 slated for removal) 188 | * test suite now uses MiniTest some old tests have been remove. Others that probably will fail anyway, are 189 | * temporarily marked as skip. 190 | * Samples now rely on ruby 1.9 (almost 2.0) and processing-2.0 191 | * Where possible examples have been 'fixed' to run with new version (backward compability is not possible) 192 | 193 | v1.0.11 Fixing broken stuffs... 194 | * JRuby upgraded to 1.6.5 195 | * applet export fixed 196 | * application export fixed 197 | 198 | v1.0.10 Solidifying before Processing 2.0 ... 199 | * JRuby upgraded to 1.6.4 200 | * Processing upgraded to 1.5.1 201 | * load_library now works for Ruby and Java libraries present in the libraries Processing sketchbook 202 | * test suite created 203 | * removed ruby-processing specific hex() and shape() methods in favor of Processing ones 204 | * added some missing methods from Processing: println(), min(), max(), abs(), binary(), nf*(), etc... 205 | * watcher: watch for *.rb files inside sketch directory 206 | * linux opengl bugs fixed 207 | * samples/peasy_cam/hilbert_fractal example now allow the possibility of changing the fractal depth and to more correctly centre the fractal 208 | * added configuration file in $HOME/.rp5rc to configure java_args and sketchbook_path 209 | 210 | v1.0.9 The Yearly Update... 211 | * JRuby upgraded to 1.4.0 final. 212 | * Fix to allow arguments to be passed to sketches. 213 | * Allow "shape" to be called with a block. 214 | * Added new examples, including Monkstone's 3D Anar library and Hilbert curve. 215 | 216 | v1.0.8 Polishing the Windows... 217 | * Windows Application exporting works again, merely by virtue of 218 | not cluttering up the classpath. 219 | * Safer Ruby Platform detection. 220 | 221 | v1.0.7 Stability... 222 | * Added preliminary support for embedding Ruby-Processing in the Processing 223 | IDE (see the ruby-processing-plugin project). 224 | * Added 'width' and 'height' as methods that should get proxied down 225 | to inner classes and classes that include the Processing::Proxy. 226 | * Fixed a padding bug that put tiny gray margins on Windows and Linux. 227 | * Updated JRuby to 1.2.0 final as well as the Processing libraries. 228 | * Got a little bit better at detecting full-screen support on Linux. 229 | * Fixed some applet and app exporting problems on Windows. 230 | * The Boids library had a speed limit fix that should make 'em less flighty. 231 | * Peter Krenn contributed a simple Pong example. 232 | 233 | v1.0.6 Inner Classes... 234 | * Java-style inner classes. Any inner class of a sketch will now have the 235 | Processing methods and constants proxied down for convenience. 236 | * Sketches with tiny sizes get displayed in a nicer fashion. 237 | * New Blue Logo: Ruby-Processing, literally. 238 | * Moumar contributed a patch for the control_panel library, allowing your 239 | sliders and buttons to have an initial value. 240 | 241 | v1.0.5 Spring Cleaning... 242 | * The "Learning Processing" examples are now a separate project, a 243 | long-merited change. They'll grow up on their own at 244 | http://github.com/jashkenas/learning-processing-with-ruby 245 | * The watcher is now a bit better about catching recoverable exceptions. 246 | * load_strings and save_strings methods have been added to Processing::App. 247 | * Fixing a permissions problem with applet/application exporting. 248 | 249 | v1.0.4 Bare is Beautiful... 250 | * Ruby-Processing now supports "bare" sketches, which are sketches that 251 | consist of only setup and draw methods, or sketches that contain no method 252 | definitions at all (implicitly wrapping them in a 'setup'). This works 253 | by pre-processing the code. 254 | * Initialization heavily tweaked so that size() works as in Processing, 255 | from within setup(), and so that you can call full_screen as a class method, 256 | in your class definition, to avoid the need for explicit sketch instantiation. 257 | * "rp5 create" has a "--bare" option. 258 | * Many samples now use the bare style, and more "Learning Processing" examples 259 | were contributed by Juris Galang. 260 | 261 | v1.0.3 Tweaks and Tuneups... 262 | * "rp5 watch" is now a bit more robust, and tries to reload every 263 | * file, global, and constant that it thinks it needs to. 264 | * Many, many examples have been contributed by Marc Chung, 265 | Peter Krenn, and Florian Jenett. 266 | * Andreas Haller contributed a patch that added Ruby-1.9 compatibility. 267 | * The render mode now defaults to JAVA2D, as does Processing. 268 | * "rp5 create" now informs you of the file it just created. 269 | * "key" now returns a character, if ASCII and the integer value otherwise, 270 | mirroring Processing's behavior. 271 | * Numbers now have the methods 'degrees' and 'radians', for ease. 272 | 273 | v1.0.2 Bugfixes and Java Args... 274 | * Application exporting, long plagued, should now be a little 275 | closer to rock-solid. If you need to pass command-line options 276 | to the JVM, add a java_args.txt file in your sketch's data 277 | folder that sets stack size, memory size, or whatever ails you. 278 | 279 | v1.0.1 Gemmin' it up. 280 | * The smallest version bump is the biggest change: 281 | Ruby-Processing has undergone a great refactor, kicked off by 282 | Peter Gassner's initial efforts to make a gem out of it. Now 283 | available as a real RubyGem. 284 | 285 | * Changes all around: The main interface to Ruby-Processing is now 286 | through the 'rp5' command. Try rp5 --help to get started. 287 | 288 | * has_slider has been superseded by control_panel, a more full- 289 | fledged library for controlling aspects of your sketch. Read 290 | how to use it on the wiki, or check out jwishy.rb 291 | 292 | v1.0. Ruby-Processing goes 1.0 with Processing 1.0 293 | * Processing updated to 1.0.1 (congrats to the Processing team), 294 | and JRuby updated to the latest trunk. Most sketches run a good 295 | bit faster now. 296 | 297 | * Ruby-Processing now comes with many default libraries: Boids, DXF, 298 | Javascript, Minim, Net, OpenGL, PDF, Serial, Slider, and Video 299 | are now included in the download. 300 | 301 | * has_slider moved out into an included ruby library. 302 | 303 | v0.9. Multi-platform Application export, live coding, and more. 304 | * Inspired by NodeBox, Ruby-Processing now sports the ability 305 | to have sliders control numeric variables in your sketches. 306 | If you're using an instance variable, say, @speed, to control 307 | the speed of your sketch. 308 | 309 | has_slider :speed 310 | 311 | Will bring up a panel alongside with a slider that controls 312 | the speed. It can take a range of values as an optional parameter. 313 | Check out and run jwishy.rb for an example. 314 | 315 | * Multi-platform app export! Exporting your Ruby-Processing 316 | apps will now create executable apps for Mac/Windows/Linux. 317 | 318 | * Live coding support. Now you can do script/live path/to/sketch.rb 319 | to open up an interactive session with your sketch available 320 | as $app. 321 | 322 | * Nick Sieger donated an additional sample. 323 | 324 | v0.8. Exporting Applications 325 | * Ruby-Processing can now export Mac applications! Running 326 | script/application my_sketch.rb will create MySketch.app, 327 | complete with all of its data and libraries. If you have 328 | a .icns file inside of your data folder, it will become 329 | the app's icon. 330 | 331 | * Added a mathematical Fern sample. It's a port of Luis 332 | Correia's java original, with algorithms from Wikipedia. 333 | 334 | * Sketches now have a library_loaded? method, so that you can 335 | check if a library has been started successfully, and 336 | conditionally enable things. (Good for OpenGL.) 337 | 338 | * The Boids library is now about 40% faster. It also comes with 339 | an example in library/boids/samples. 340 | 341 | * Specs have been started both for exporting and for Ruby- 342 | Processing itself. 343 | 344 | v0.7. Flocking Boids and OpenGL Applets 345 | * Thanks to MenTaLguY, once again, for work on the JRubyApplet, OpenGL 346 | is now a first-class citizen. If you're using OpenGL in your sketch, 347 | the applet exporter should just work. It has also been moved and 348 | renamed, so now you can use it like: 349 | 350 | script/applet my_sketch.rb 351 | 352 | * An app generator has been added for getting started. It'll give you 353 | a template for an empty Ruby-Processing sketch, with setup and draw 354 | methods and all that. Usage: 355 | 356 | script/generate my_sketch 800 600 357 | 358 | Will create a file called my_sketch.rb, with a title of "My Sketch", 359 | 800 pixels wide and 600 pixels tall. Width and height are optional. 360 | 361 | * Ruby-Processing now includes its first pure-Ruby library, a port 362 | of Tom de Smedt's "Boids", for algorithmic flocking. 363 | 364 | v0.6. Generating Applets 365 | * Now we're baking up some applet pie. The applet_tree script will 366 | take your Ruby-Processing sketch, export it as an applet, and 367 | generate an HTML page for you to post. It's way easier now than it 368 | would have been before. (thanks to MenTaLguY.) Use it like so: 369 | 370 | ./applet_tree my_sketch.rb 371 | 372 | But there are caveats: Applets don't work with native libraries, so 373 | no OpenGL. If you're requiring other files that aren't part of the 374 | standard Ruby distro, you'll need to include them as libraries, which 375 | means: Drop them in a folder inside of "library". Use 376 | load_ruby_library("folder_name") or load_java_library() to load 'em. 377 | These methods replace the previous load_library(). Ruby libs will 378 | load the .rb with the same name as the folder. Java libs will just 379 | load up all of the .jars in the folder. 380 | 381 | Demos — all of the standard samples are available as applets: 382 | http://fiercefrontiers.com/applets/jwishy/ 383 | http://fiercefrontiers.com/applets/tree/ 384 | http://fiercefrontiers.com/applets/circle_collision/ 385 | http://fiercefrontiers.com/applets/reflection/ 386 | 387 | 388 | v0.5. With Native Libraries 389 | * Ruby-Processing gets easy native library support. Now you can take 390 | Processing libraries, drop them in the library folder, and load them 391 | up like so (inside your sketch): 392 | 393 | load_library "opengl" 394 | 395 | It works by loading up all of the .jars in that folder, and setting 396 | the java.library.path to that folder, so that the native extensions 397 | can be found. 398 | 399 | * Full Screen OpenGL demo added, but you'll need to copy over the 400 | OpenGL library to use it. 401 | 402 | v0.4. Going Fullscreen 403 | * Ruby-Processing goes fullscreen. Just pass :full_screen => true 404 | into the options when you’re starting up your app. Like so: 405 | 406 | MyApp.new(:title => "MyApp", :full_screen => true) 407 | 408 | * Because Processing has just so many methods, you can now search 409 | through them: find_method "method_name" 410 | 411 | v0.3. First Real Release 412 | * Processing::App.current will give you a handle on the app. (Useful 413 | in jirb). 414 | * samples/jwishy.rb has some new hooks for live coding. 415 | * circle_collision and tree samples added (Joe Holt) 416 | -------------------------------------------------------------------------------- /src/monkstone/vecmath/vec2/Vec2.java: -------------------------------------------------------------------------------- 1 | package monkstone.vecmath.vec2; 2 | /* 3 | * Copyright (C) 2015-16 Martin Prout 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * 10 | * http://creativecommons.org/licenses/LGPL/2.1/ 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public 18 | * License along with this library; if not, write to the Free Software 19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | import org.jruby.Ruby; 23 | import org.jruby.RubyArray; 24 | import org.jruby.RubyBoolean; 25 | import org.jruby.RubyClass; 26 | import org.jruby.RubyObject; 27 | import org.jruby.anno.JRubyClass; 28 | import org.jruby.anno.JRubyMethod; 29 | import org.jruby.runtime.Arity; 30 | import org.jruby.runtime.Block; 31 | import org.jruby.runtime.ObjectAllocator; 32 | import org.jruby.runtime.ThreadContext; 33 | import org.jruby.runtime.builtin.IRubyObject; 34 | import monkstone.vecmath.JRender; 35 | 36 | /** 37 | * 38 | * @author Martin Prout 39 | */ 40 | @JRubyClass(name = "Vec2D") 41 | public class Vec2 extends RubyObject { 42 | 43 | static final double EPSILON = 9.999999747378752e-05; // matches processing.org EPSILON 44 | private static final long serialVersionUID = -7013225882277559392L; 45 | private double jx = 0; 46 | private double jy = 0; 47 | 48 | /** 49 | * 50 | * @param runtime 51 | */ 52 | public static void createVec2(final Ruby runtime) { 53 | RubyClass vec2Cls = runtime.defineClass("Vec2D", runtime.getObject(), new ObjectAllocator() { 54 | @Override 55 | public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) { 56 | return new Vec2(runtime, rubyClass); 57 | } 58 | }); 59 | vec2Cls.defineAnnotatedMethods(Vec2.class); 60 | 61 | } 62 | 63 | /** 64 | * 65 | * @param context 66 | * @param klazz 67 | * @param args optional (no args jx = 0, jy = 0) 68 | * @return new Vec2 object (ruby) 69 | */ 70 | @JRubyMethod(name = "new", meta = true, rest = true) 71 | public static final IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) { 72 | Vec2 vec2 = (Vec2) ((RubyClass) klazz).allocate(); 73 | vec2.init(context, args); 74 | return vec2; 75 | } 76 | 77 | /** 78 | * 79 | * @param runtime 80 | * @param klass 81 | */ 82 | public Vec2(Ruby runtime, RubyClass klass) { 83 | super(runtime, klass); 84 | } 85 | 86 | void init(ThreadContext context, IRubyObject[] args) { 87 | if (Arity.checkArgumentCount(context.getRuntime(), args, Arity.OPTIONAL.getValue(), 2) == 2) { 88 | jx = (Double) args[0].toJava(Double.class); 89 | jy = (Double) args[1].toJava(Double.class); 90 | } 91 | } 92 | 93 | /** 94 | * 95 | * @param context 96 | * @return jx float 97 | */ 98 | @JRubyMethod(name = "x") 99 | 100 | public IRubyObject getX(ThreadContext context) { 101 | return context.getRuntime().newFloat(jx); 102 | } 103 | 104 | /** 105 | * 106 | * @param context 107 | * @return jy float 108 | */ 109 | @JRubyMethod(name = "y") 110 | 111 | public IRubyObject getY(ThreadContext context) { 112 | return context.getRuntime().newFloat(jy); 113 | } 114 | 115 | /** 116 | * 117 | * @param context 118 | * @param other 119 | * @return jx float 120 | */ 121 | @JRubyMethod(name = "x=") 122 | 123 | public IRubyObject setX(ThreadContext context, IRubyObject other) { 124 | jx = (Double) other.toJava(Double.class); 125 | return other; 126 | } 127 | 128 | /** 129 | * 130 | * @param context 131 | * @param other 132 | * @return jy float 133 | */ 134 | @JRubyMethod(name = "y=") 135 | 136 | public IRubyObject setY(ThreadContext context, IRubyObject other) { 137 | jy = (Double) other.toJava(Double.class); 138 | return other; 139 | } 140 | 141 | /** 142 | * 143 | * @param context 144 | * @param other 145 | * @return hypotenuse float 146 | */ 147 | @JRubyMethod(name = "dist", required = 1) 148 | 149 | public IRubyObject dist(ThreadContext context, IRubyObject other) { 150 | Vec2 b = null; 151 | Ruby runtime = context.getRuntime(); 152 | if (other instanceof Vec2) { 153 | b = (Vec2) other.toJava(Vec2.class); 154 | } else { 155 | throw runtime.newTypeError("argument should be Vec2D"); 156 | } 157 | double result = Math.hypot((jx - b.jx), (jy - b.jy)); 158 | return runtime.newFloat(result); 159 | } 160 | 161 | /** 162 | * 163 | * @param context 164 | * @param other 165 | * @return cross product as a new Vec3D 166 | */ 167 | @JRubyMethod(name = "cross", required = 1) 168 | 169 | public IRubyObject cross(ThreadContext context, IRubyObject other) { 170 | Vec2 b = null; 171 | Ruby runtime = context.getRuntime(); 172 | if (other instanceof Vec2) { 173 | b = (Vec2) other.toJava(Vec2.class); 174 | } else { 175 | throw runtime.newTypeError("argument should be Vec2D"); 176 | } 177 | return runtime.newFloat(jx * b.jy - jy * b.jx); 178 | } 179 | 180 | /** 181 | * 182 | * @param context 183 | * @param other 184 | * @return do product as a float 185 | */ 186 | @JRubyMethod(name = "dot", required = 1) 187 | 188 | public IRubyObject dot(ThreadContext context, IRubyObject other) { 189 | Vec2 b = null; 190 | Ruby runtime = context.getRuntime(); 191 | if (other instanceof Vec2) { 192 | b = (Vec2) other.toJava(Vec2.class); 193 | } else { 194 | throw runtime.newTypeError("argument should be Vec2D"); 195 | } 196 | return runtime.newFloat(jx * b.jx + jy * b.jy); 197 | } 198 | 199 | /** 200 | * 201 | * @param context 202 | * @param other 203 | * @return new Vec2 object (ruby) 204 | */ 205 | @JRubyMethod(name = "+", required = 1) 206 | 207 | public IRubyObject op_plus(ThreadContext context, IRubyObject other) { 208 | Vec2 b = null; 209 | Ruby runtime = context.getRuntime(); 210 | if (other instanceof Vec2) { 211 | b = (Vec2) other.toJava(Vec2.class); 212 | } else { 213 | throw runtime.newTypeError("argument should be Vec2D"); 214 | } 215 | return Vec2.rbNew(context, other.getMetaClass(), new IRubyObject[]{ 216 | runtime.newFloat(jx + b.jx), 217 | runtime.newFloat(jy + b.jy)}); 218 | } 219 | 220 | /** 221 | * 222 | * @param context 223 | * @param other 224 | * @return new Vec2 object (ruby) 225 | */ 226 | @JRubyMethod(name = "-", required = 1) 227 | 228 | public IRubyObject op_minus(ThreadContext context, IRubyObject other) { 229 | Vec2 b = null; 230 | Ruby runtime = context.getRuntime(); 231 | if (other instanceof Vec2) { 232 | b = (Vec2) other.toJava(Vec2.class); 233 | } else { 234 | throw runtime.newTypeError("argument should be Vec2D"); 235 | } 236 | return Vec2.rbNew(context, other.getMetaClass(), new IRubyObject[]{ 237 | runtime.newFloat(jx - b.jx), 238 | runtime.newFloat(jy - b.jy)}); 239 | } 240 | 241 | /** 242 | * 243 | * @param context 244 | * @param other 245 | * @return new Vec2 object (ruby) 246 | */ 247 | @JRubyMethod(name = "*") 248 | 249 | public IRubyObject op_mul(ThreadContext context, IRubyObject other) { 250 | Ruby runtime = context.getRuntime(); 251 | double scalar = (Double) other.toJava(Double.class); 252 | return Vec2.rbNew(context, this.getMetaClass(), 253 | new IRubyObject[]{runtime.newFloat(jx * scalar), 254 | runtime.newFloat(jy * scalar)}); 255 | } 256 | 257 | /** 258 | * 259 | * @param context 260 | * @param other 261 | * @return new Vec2 object (ruby) 262 | */ 263 | @JRubyMethod(name = "/", required = 1) 264 | 265 | public IRubyObject op_div(ThreadContext context, IRubyObject other) { 266 | Ruby runtime = context.getRuntime(); 267 | double scalar = (Double) other.toJava(Double.class); 268 | if (Math.abs(scalar) < Vec2.EPSILON) { 269 | return this; 270 | } 271 | return Vec2.rbNew(context, this.getMetaClass(), new IRubyObject[]{ 272 | runtime.newFloat(jx / scalar), 273 | runtime.newFloat(jy / scalar)}); 274 | } 275 | 276 | /** 277 | * 278 | * @param context 279 | * @return angle radians as a float 280 | */ 281 | @JRubyMethod(name = "heading") 282 | public IRubyObject heading(ThreadContext context) { 283 | return context.getRuntime().newFloat(Math.atan2(jy, jx)); 284 | } 285 | 286 | /** 287 | * 288 | * @param context 289 | * @return magnitude float 290 | */ 291 | @JRubyMethod(name = "mag") 292 | 293 | public IRubyObject mag(ThreadContext context) { 294 | double result = 0; 295 | if (Math.abs(jx) > EPSILON && Math.abs(jy) > EPSILON) { 296 | result = Math.hypot(jx, jy); 297 | } 298 | else{ 299 | if (Math.abs(jy) > EPSILON) { 300 | result = Math.abs(jy); 301 | } 302 | if (Math.abs(jx) > EPSILON) { 303 | result = Math.abs(jx); 304 | } 305 | } 306 | return context.getRuntime().newFloat(result); 307 | } 308 | 309 | /** 310 | * Call yield if block given, do nothing if yield == false else set_mag to 311 | * given scalar 312 | * 313 | * @param context 314 | * @param scalar double value to set 315 | * @param block should return a boolean (optional) 316 | * @return this Vec2D with the new magnitude 317 | */ 318 | @JRubyMethod(name = "set_mag") 319 | 320 | public IRubyObject set_mag(ThreadContext context, IRubyObject scalar, Block block) { 321 | double new_mag = (Double) scalar.toJava(Double.class); 322 | if (block.isGiven()) { 323 | if (!(boolean) block.yield(context, scalar).toJava(Boolean.class)) { 324 | return this; 325 | } 326 | } 327 | double current = 0; 328 | if (Math.abs(jx) > EPSILON && Math.abs(jy) > EPSILON) { 329 | current = Math.hypot(jx, jy); 330 | } 331 | else{ 332 | if (Math.abs(jy) > EPSILON) { 333 | current = Math.abs(jy); 334 | } 335 | if (Math.abs(jx) > EPSILON) { 336 | current = Math.abs(jx); 337 | } 338 | } 339 | if (current > 0) { 340 | jx *= new_mag / current; 341 | jy *= new_mag / current; 342 | } 343 | return this; 344 | } 345 | 346 | /** 347 | * 348 | * @param context 349 | * @return this as a ruby object 350 | */ 351 | @JRubyMethod(name = "normalize!") 352 | 353 | public IRubyObject normalize_bang(ThreadContext context) { 354 | double mag = 0; 355 | if (Math.abs(jx) > EPSILON && Math.abs(jy) > EPSILON) { 356 | mag = Math.hypot(jx, jy); 357 | } 358 | else{ 359 | if (Math.abs(jx) > EPSILON) { 360 | mag = Math.abs(jx); 361 | } 362 | if (Math.abs(jy) > EPSILON) { 363 | mag = Math.abs(jy); 364 | } 365 | } 366 | if (mag > 0) { 367 | jx /= mag; 368 | jy /= mag; 369 | } 370 | return this; 371 | } 372 | 373 | /** 374 | * 375 | * @param context 376 | * @return new Vec2 object (ruby) 377 | */ 378 | @JRubyMethod(name = "normalize") 379 | 380 | public IRubyObject normalize(ThreadContext context) { 381 | double mag = 0; 382 | Ruby runtime = context.getRuntime(); 383 | if (Math.abs(jx) > EPSILON && Math.abs(jy) > EPSILON) { 384 | mag = Math.hypot(jx, jy); 385 | } 386 | else{ 387 | if (Math.abs(jx) > EPSILON) { 388 | mag = jx; 389 | } 390 | if (Math.abs(jy) > EPSILON) { 391 | mag = jy; 392 | } 393 | } 394 | if (mag < EPSILON) { 395 | mag = 1.0; 396 | } 397 | return Vec2.rbNew(context, this.getMetaClass(), new IRubyObject[]{ 398 | runtime.newFloat(jx / mag), 399 | runtime.newFloat(jy / mag)}); 400 | } 401 | 402 | /** 403 | * Example of a regular ruby class method Use Math rather than RadLut 404 | * here!!! 405 | * 406 | * @param context 407 | * @param klazz 408 | * @param other input angle in radians 409 | * @return new Vec2 object (ruby) 410 | */ 411 | @JRubyMethod(name = "from_angle", meta = true) 412 | public static IRubyObject from_angle(ThreadContext context, IRubyObject klazz, IRubyObject other) { 413 | Ruby runtime = context.getRuntime(); 414 | double scalar = (Double) other.toJava(Double.class); 415 | return Vec2.rbNew(context, klazz, new IRubyObject[]{ 416 | runtime.newFloat(Math.cos(scalar)), 417 | runtime.newFloat(Math.sin(scalar))}); 418 | } 419 | 420 | /** 421 | * 422 | * @param context 423 | * @param other 424 | * @return this Vec2 object rotated 425 | */ 426 | @JRubyMethod(name = "rotate!") 427 | public IRubyObject rotate_bang(ThreadContext context, IRubyObject other) { 428 | double theta = (Double) other.toJava(Double.class); 429 | double x = (jx * Math.cos(theta) - jy * Math.sin(theta)); 430 | double y = (jx * Math.sin(theta) + jy * Math.cos(theta)); 431 | jx = x; 432 | jy = y; 433 | return this; 434 | } 435 | 436 | 437 | 438 | /** 439 | * 440 | * @param context 441 | * @param other 442 | * @return a new Vec2 object rotated 443 | */ 444 | @JRubyMethod(name = "rotate") 445 | public IRubyObject rotate(ThreadContext context, IRubyObject other) { 446 | Ruby runtime = context.getRuntime(); 447 | double theta = (Double) other.toJava(Double.class); 448 | IRubyObject[] ary = new IRubyObject[]{ 449 | runtime.newFloat(jx * Math.cos(theta) - jy * Math.sin(theta)), 450 | runtime.newFloat(jx * Math.sin(theta) + jy * Math.cos(theta))}; 451 | return Vec2.rbNew(context, this.getMetaClass(), ary); 452 | } 453 | 454 | /** 455 | * 456 | * @param context 457 | * @param args 458 | * @return as a new Vec2 object (ruby) 459 | */ 460 | @JRubyMethod(name = "lerp", rest = true) 461 | public IRubyObject lerp(ThreadContext context, IRubyObject[] args) { 462 | Ruby runtime = context.getRuntime(); 463 | Arity.checkArgumentCount(runtime, args, 2, 2); 464 | Vec2 vec = (Vec2) args[0].toJava(Vec2.class); 465 | double scalar = (Double) args[1].toJava(Double.class); 466 | assert (scalar >= 0 && scalar < 1.0) : 467 | "Lerp value " + scalar + " out of range 0 .. 1.0"; 468 | return Vec2.rbNew(context, this.getMetaClass(), new IRubyObject[]{ 469 | runtime.newFloat(jx + (vec.jx - jx) * scalar), 470 | runtime.newFloat(jy + (vec.jy - jy) * scalar)}); 471 | } 472 | 473 | /** 474 | * 475 | * @param context 476 | * @param args 477 | * @return this 478 | */ 479 | @JRubyMethod(name = "lerp!", rest = true) 480 | public IRubyObject lerp_bang(ThreadContext context, IRubyObject[] args) { 481 | Arity.checkArgumentCount(context.getRuntime(), args, 2, 2); 482 | Vec2 vec = (Vec2) args[0].toJava(Vec2.class); 483 | double scalar = (Double) args[1].toJava(Double.class); 484 | assert (scalar >= 0 && scalar < 1.0) : 485 | "Lerp value " + scalar + " out of range 0 .. 1.0"; 486 | jx += (vec.jx - jx) * scalar; 487 | jy += (vec.jy - jy) * scalar; 488 | return this; 489 | } 490 | 491 | /** 492 | * 493 | * @param context 494 | * @param other 495 | * @return theta radians float 496 | */ 497 | @JRubyMethod(name = "angle_between") 498 | 499 | public IRubyObject angleBetween(ThreadContext context, IRubyObject other) { 500 | Vec2 vec = null; 501 | Ruby runtime = context.getRuntime(); 502 | if (other instanceof Vec2) { 503 | vec = (Vec2) other.toJava(Vec2.class); 504 | } else { 505 | throw runtime.newTypeError("argument should be Vec2D"); 506 | } 507 | return runtime.newFloat(Math.atan2(jx - vec.jx, jy - vec.jy)); 508 | } 509 | 510 | /** 511 | * Example of a regular ruby class method Use Math rather than RadLut 512 | * here!!! 513 | * 514 | * @param context 515 | * @param klazz 516 | * @return new Vec2 object (ruby) 517 | */ 518 | @JRubyMethod(name = "random", meta = true) 519 | public static IRubyObject random_direction(ThreadContext context, IRubyObject klazz) { 520 | Ruby runtime = context.getRuntime(); 521 | double angle = Math.random() * Math.PI * 2; 522 | return Vec2.rbNew(context, klazz, new IRubyObject[]{ 523 | runtime.newFloat(Math.cos(angle)), 524 | runtime.newFloat(Math.sin(angle))}); 525 | } 526 | 527 | /** 528 | * 529 | * @param context 530 | * @return new copy 531 | */ 532 | @JRubyMethod(name = {"copy", "dup"}) 533 | 534 | public IRubyObject copy(ThreadContext context) { 535 | Ruby runtime = context.runtime; 536 | return Vec2.rbNew(context, this.getMetaClass(), new IRubyObject[]{ 537 | runtime.newFloat(jx), 538 | runtime.newFloat(jy)}); 539 | } 540 | 541 | /** 542 | * 543 | * @param context 544 | * @return ruby array 545 | */ 546 | @JRubyMethod(name = "to_a") 547 | 548 | public IRubyObject toArray(ThreadContext context) { 549 | Ruby runtime = context.runtime; 550 | return RubyArray.newArray(context.getRuntime(), new IRubyObject[]{ 551 | runtime.newFloat(jx), 552 | runtime.newFloat(jy)}); 553 | } 554 | 555 | /** 556 | * 557 | * @param context 558 | * @param object 559 | */ 560 | @JRubyMethod(name = "to_vertex") 561 | 562 | public void toVertex(ThreadContext context, IRubyObject object) { 563 | JRender renderer = (JRender) object.toJava(JRender.class); 564 | renderer.vertex(jx, jy); 565 | } 566 | 567 | /** 568 | * 569 | * @param context 570 | * @param object 571 | */ 572 | @JRubyMethod(name = "to_curve_vertex") 573 | 574 | public void toCurveVertex(ThreadContext context, IRubyObject object) { 575 | JRender renderer = (JRender) object.toJava(JRender.class); 576 | renderer.curveVertex(jx, jy); 577 | } 578 | 579 | 580 | /** 581 | * For jruby-9000 we alias to inspect 582 | * @param context 583 | * @return custom to string (inspect) 584 | */ 585 | @JRubyMethod(name = {"to_s", "inspect"}) 586 | 587 | public IRubyObject to_s(ThreadContext context) { 588 | return context.getRuntime().newString(String.format("Vec2D(x = %4.4f, y = %4.4f)", jx, jy)); 589 | } 590 | 591 | /** 592 | * 593 | * @return hash int 594 | */ 595 | @Override 596 | public int hashCode() { 597 | int hash = 5; 598 | hash = 53 * hash + (int) (Double.doubleToLongBits(this.jx) ^ (Double.doubleToLongBits(this.jx) >>> 32)); 599 | hash = 53 * hash + (int) (Double.doubleToLongBits(this.jy) ^ (Double.doubleToLongBits(this.jy) >>> 32)); 600 | return hash; 601 | } 602 | 603 | /** 604 | * 605 | * @param obj 606 | * @return ruby boolean 607 | */ 608 | @Override 609 | public boolean equals(Object obj) { 610 | if (obj instanceof Vec2){ 611 | final Vec2 other = (Vec2) obj; 612 | if (!((Double)this.jx).equals(other.jx)) { 613 | return false; 614 | } 615 | return ((Double)this.jy).equals(other.jy); 616 | } 617 | return false; 618 | } 619 | 620 | /** 621 | * 622 | * @param context 623 | * @param other 624 | * @return ruby boolean 625 | */ 626 | @JRubyMethod(name = "eql?", required = 1) 627 | public IRubyObject eql_p(ThreadContext context, IRubyObject other) { 628 | Ruby runtime = context.getRuntime(); 629 | if (other instanceof Vec2){ 630 | Vec2 v = (Vec2) other.toJava(Vec2.class); 631 | if (!((Double)this.jx).equals(v.jx)) { 632 | return RubyBoolean.newBoolean(runtime, false); 633 | } 634 | return RubyBoolean.newBoolean(runtime, ((Double)this.jy).equals(v.jy)); 635 | } 636 | return RubyBoolean.newBoolean(runtime, false); 637 | } 638 | 639 | /** 640 | * 641 | * @param context 642 | * @param other 643 | * @return ruby boolean 644 | */ 645 | @JRubyMethod(name = "==", required = 1) 646 | 647 | @Override 648 | public IRubyObject op_equal(ThreadContext context, IRubyObject other) { 649 | Ruby runtime = context.getRuntime(); 650 | if (other instanceof Vec2) { 651 | Vec2 v = (Vec2) other.toJava(Vec2.class); 652 | double diff = jx - v.jx; 653 | if ((diff < 0 ? -diff : diff) > Vec2.EPSILON) { 654 | return RubyBoolean.newBoolean(runtime, false); 655 | } 656 | diff = jy - v.jy; 657 | boolean result = ((diff < 0 ? -diff : diff) < Vec2.EPSILON); 658 | return RubyBoolean.newBoolean(runtime, result); 659 | } 660 | return RubyBoolean.newBoolean(runtime, false); 661 | } 662 | } 663 | --------------------------------------------------------------------------------