package processing.vec2; import org.apache.commons.math3.util.FastMath; import org.jruby.Ruby; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyFloat; import org.jruby.RubyObject; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Arity; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; /** * * @author Martin Prout */ @JRubyClass(name = "Processing::Vec2") public class Vec2 extends RubyObject { static final double EPSILON = 1.0e-04; // matches processing.org EPSILON double jx = 0; double jy = 0; /** * * @param context * @param klazz * @param args optional (no args jx = 0, jy = 0) * @return new Vec2 object (ruby) */ @JRubyMethod(name = "new", meta = true, rest = true) public static IRubyObject rbNew(ThreadContext context, IRubyObject klazz, IRubyObject[] args) { Vec2 vec2 = (Vec2) ((RubyClass) klazz).allocate(); vec2.init(context, args); return vec2; } /** * * @param runtime * @param klass */ public Vec2(Ruby runtime, RubyClass klass) { super(runtime, klass); } void init(ThreadContext context, IRubyObject[] args) { if (Arity.checkArgumentCount(context.getRuntime(), args, Arity.OPTIONAL.getValue(), 2) == 2) { jx = (Double) args[0].toJava(Double.class); jy = (Double) args[1].toJava(Double.class); } } /** * * @param context * @return */ @JRubyMethod(name = "x") public RubyFloat getX(ThreadContext context) { return context.getRuntime().newFloat(jx); } /** * * @param context * @return */ @JRubyMethod(name = "y") public RubyFloat getY(ThreadContext context) { return context.getRuntime().newFloat(jy); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "set_x") public RubyFloat setX(ThreadContext context, IRubyObject other) { double scalar = (Double) other.toJava(Double.class); jx = scalar; return context.getRuntime().newFloat(jx); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "set_y") public RubyFloat setY(ThreadContext context, IRubyObject other) { double scalar = (Double) other.toJava(Double.class); jy = scalar; return context.getRuntime().newFloat(jy); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "dist") public IRubyObject dist(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); double result = FastMath.sqrt((jx - b.jx) * (jx - b.jx) + (jy - b.jy) * (jy - b.jy)); return context.getRuntime().newFloat(result); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "dist_squared") public IRubyObject dist_squared(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); double result = (jx - b.jx) * (jx - b.jx) + (jy - b.jy) * (jy - b.jy); return context.getRuntime().newFloat(result); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "cross") public IRubyObject cross(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); return context.getRuntime().newFloat(jx * b.jy - jy * b.jx); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "dot") public IRubyObject dot(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); return context.getRuntime().newFloat(jx * b.jx + jy * b.jy); } /** * * @param context * @param other * @return new Vec2 object (ruby) */ @JRubyMethod(name = "add") public IRubyObject add(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); RubyFloat[] input = {context.getRuntime().newFloat(jx + b.jx), context.getRuntime().newFloat(jy + b.jy)}; return Vec2.rbNew(context, other.getMetaClass(), input); } /** * * @param context * @param other * @return new Vec2 object (ruby) */ @JRubyMethod(name = "sub") public IRubyObject sub(ThreadContext context, IRubyObject other) { Vec2 b = (Vec2) other.toJava(Vec2.class); RubyFloat[] input = {context.getRuntime().newFloat(jx - b.jx), context.getRuntime().newFloat(jy - b.jy)}; return Vec2.rbNew(context, other.getMetaClass(), input); } /** * * @param context * @param other * @return new Vec2 object (ruby) */ @JRubyMethod(name = "mult") public IRubyObject mult(ThreadContext context, IRubyObject other) { double scalar = (Double) other.toJava(Double.class); RubyFloat[] input = {context.getRuntime().newFloat(jx * scalar), context.getRuntime().newFloat(jy * scalar)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * * @param context * @param other * @return new Vec2 object (ruby) */ @JRubyMethod(name = "div") public IRubyObject div(ThreadContext context, IRubyObject other) { double scalar = (Double) other.toJava(Double.class); if (FastMath.abs(scalar) < Vec2.EPSILON) { return this; } RubyFloat[] input = {context.getRuntime().newFloat(jx / scalar), context.getRuntime().newFloat(jy / scalar)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * * @param context * @return */ @JRubyMethod(name = "heading") public IRubyObject heading(ThreadContext context) { return context.getRuntime().newFloat(FastMath.atan2(-jy, jx) * -1.0); } /** * * @param context * @return */ @JRubyMethod(name = "mag_squared") public IRubyObject mag_squared(ThreadContext context) { return context.getRuntime().newFloat(jx * jx + jy * jy); } /** * * @param context * @return */ @JRubyMethod(name = "mag") public IRubyObject mag(ThreadContext context) { return context.getRuntime().newFloat(FastMath.hypot(jx, jy)); } /** * * @param context * @param scalar * @return * @todo have optional conditional block (evaluate to boolean) */ @JRubyMethod(name = "set_mag") public IRubyObject set_mag(ThreadContext context, IRubyObject scalar) { double new_mag = (Double) scalar.toJava(Double.class); double current = FastMath.sqrt(FastMath.pow(jx, 2) + FastMath.pow(jy, 2)); jx *= new_mag / current; jy *= new_mag / current; return this; } /** * * @param context * @return this as a ruby object */ @JRubyMethod(name = "normalize!") public IRubyObject normalize_bang(ThreadContext context) { double mag = FastMath.sqrt(FastMath.pow(jx, 2) + FastMath.pow(jy, 2)); jx /= mag; jy /= mag; return this; } /** * * @param context * @return new Vec2 object (ruby) */ @JRubyMethod(name = "normalize") public IRubyObject normalize(ThreadContext context) { double mag = FastMath.sqrt(FastMath.pow(jx, 2) + FastMath.pow(jy, 2)); RubyFloat[] input = {context.getRuntime().newFloat(jx / mag), context.getRuntime().newFloat(jy / mag)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * Example of a regular ruby class method * * @param context * @param klazz the klazz of object we are creating * @param other input angle in radians * @return new Vec2 object (ruby) */ @JRubyMethod(name = "from_angle", meta = true) public static IRubyObject from_angle(ThreadContext context, IRubyObject klazz, IRubyObject other) { final double scalar = (Double) other.toJava(Double.class); RubyFloat[] args = {context.getRuntime().newFloat(FastMath.cos(scalar)), context.getRuntime().newFloat(FastMath.sin(scalar))}; Vec2 vec2 = (Vec2) ((RubyClass) klazz).allocate(); vec2.init(context, args); return vec2; } /** * * @param context * @param other * @return this Vec2 object rotated */ @JRubyMethod(name = "rotate!") public IRubyObject rotate_bang(ThreadContext context, IRubyObject other) { double theta = (Double) other.toJava(Double.class); double x = (jx * FastMath.cos(theta) - jy * FastMath.sin(theta)); double y = (jx * FastMath.sin(theta) + jy * FastMath.cos(theta)); jx = x; jy = y; return this; } /** * * @param context * @param other * @return a new Vec2 object rotated */ @JRubyMethod(name = "rotate") public IRubyObject rotate(ThreadContext context, IRubyObject other) { double theta = (Double) other.toJava(Double.class); double x = (jx * FastMath.cos(theta) - jy * FastMath.sin(theta)); double y = (jx * FastMath.sin(theta) + jy * FastMath.cos(theta)); RubyFloat[] input = {context.getRuntime().newFloat(x), context.getRuntime().newFloat(y)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * * @param context * @param args * @return as a new Vec2 object (ruby) */ @JRubyMethod(name = "lerp", rest = true) public IRubyObject lerp(ThreadContext context, IRubyObject[] args) { Arity.checkArgumentCount(context.getRuntime(), args, 2, 2); Vec2 vec = (Vec2) args[0].toJava(Vec2.class); double scalar = (Double) args[1].toJava(Double.class); assert (scalar >= 0 && scalar < 1.0) : "Lerp value " + scalar + " out of range 0 .. 1.0"; double x0 = jx + (vec.jx - jx) * scalar; double y0 = jx + (vec.jx - jx) * scalar; RubyFloat[] input = {context.getRuntime().newFloat(x0), context.getRuntime().newFloat(y0)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * * @param context * @param args * @return this */ @JRubyMethod(name = "lerp!", rest = true) public IRubyObject lerp_bang(ThreadContext context, IRubyObject[] args) { Arity.checkArgumentCount(context.getRuntime(), args, 2, 2); Vec2 vec = (Vec2) args[0].toJava(Vec2.class); double scalar = (Double) args[1].toJava(Double.class); assert (scalar >= 0 && scalar < 1.0) : "Lerp value " + scalar + " out of range 0 .. 1.0"; double x0 = jx + (vec.jx - jx) * scalar; double y0 = jx + (vec.jx - jx) * scalar; jx = x0; jy = y0; return this; } /** * * @param context * @return */ @JRubyMethod(name = "copy") public IRubyObject copy(ThreadContext context) { double x0 = jx; double y0 = jy; RubyFloat[] input = {context.getRuntime().newFloat(x0), context.getRuntime().newFloat(y0)}; return Vec2.rbNew(context, this.getMetaClass(), input); } /** * * @param context * @return */ @JRubyMethod(name = "to_a") public IRubyObject toArray(ThreadContext context) { RubyFloat[] output = {context.getRuntime().newFloat(jx), context.getRuntime().newFloat(jy)}; return context.getRuntime().newArray(output); } /** * * @param context * @return */ @JRubyMethod(name = "to_s") public IRubyObject to_s(ThreadContext context) { return context.getRuntime().newString(String.format("Vec2(x = %4.4f, y = %4.4f)", jx, jy)); } /** * * @return */ @Override public int hashCode() { int hash = 5; hash = 53 * hash + (int) (Double.doubleToLongBits(this.jx) ^ (Double.doubleToLongBits(this.jx) >>> 32)); hash = 53 * hash + (int) (Double.doubleToLongBits(this.jy) ^ (Double.doubleToLongBits(this.jy) >>> 32)); return hash; } /** * * @param obj * @return */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Vec2 other = (Vec2) obj; if (Double.doubleToLongBits(this.jx) != Double.doubleToLongBits(other.jx)) { return false; } return (Double.doubleToLongBits(this.jy) == Double.doubleToLongBits(other.jy)); } /** * * @param context * @param other * @return */ @JRubyMethod(name = "almost_eql?") public IRubyObject almost_eql_p(ThreadContext context, IRubyObject other) { Vec2 v = (other instanceof Vec2) ? (Vec2) other.toJava(Vec2.class) : null; IRubyObject result = (v == null) ? RubyBoolean.newBoolean(context.getRuntime(), false) : (FastMath.abs(jx - v.jx) > Vec2.EPSILON) ? RubyBoolean.newBoolean(context.getRuntime(), false) : (FastMath.abs(jy - v.jy) > Vec2.EPSILON) ? RubyBoolean.newBoolean(context.getRuntime(), false) : RubyBoolean.newBoolean(context.getRuntime(), true); return result; // return false as default unless not null && values equal } }
Here is the Vec2Service class
package processing.vec2; import java.io.IOException; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.load.BasicLibraryService; public class Vec2Service implements BasicLibraryService { @Override public boolean basicLoad(final Ruby runtime) throws IOException { RubyModule processing = runtime.defineModule("Processing"); RubyModule vec2Module = processing.defineModuleUnder("Vec2"); RubyClass vec2 = vec2Module.defineClassUnder(Vec2.class.getSimpleName(), runtime.getObject(), VEC_ALLOCATOR); vec2.defineAnnotatedMethods(Vec2.class); return true; } private static final ObjectAllocator VEC_ALLOCATOR = new ObjectAllocator() { @Override public IRubyObject allocate(Ruby runtime, RubyClass klazz) { return new Vec2(runtime, klazz); } }; }
Here is the Rakefile jruby -S rake compile
# -*- ruby -*- require 'java' require 'rake/javaextensiontask' Rake::JavaExtensionTask.new('processing/vec2') do |ext| jars = FileList['lib/*.jar'] ext.classpath = jars.map {|x| File.expand_path x}.join ':' ext.name = 'processing/vecmath' ext.debug=true ext.source_version='1.7' ext.target_version='1.7' end
Here is vec2.rb
require_relative './vecmath' require 'processing/vec2/vec2' module Processing module Vec2 class Vec2 alias_method :*, :mult alias_method :/, :div alias_method :+, :add alias_method :-, :sub alias_method :==, :eql? end end end
Here are some sample usages:-
require 'java' require_relative '../lib/processing_vecmath' include Processing::Vec2 vec = Vec2.new(0, 0) vec1 = Vec2.new(0, 0) vec2 = Vec2.new(3.0, 4.0) vec3 =Vec2.new(1.00000000000, 1.000000000000) puts vec3.normalize puts "mult #{vec3}" puts "add #{vec2 + vec3}" puts "original #{vec2}" puts vec == vec1 puts vec2.almost_eql? Vec2.new(3.0, 4.0) puts vec.almost_eql? Vec2.new(0, 0) puts vec.dist(vec2) puts vec2.mag
Here is the output:-
Vec2(x = 0.7071, y = 0.7071) mult Vec2(x = 1.0000, y = 1.0000) add Vec2(x = 4.0000, y = 5.0000) original Vec2(x = 3.0000, y = 4.0000) true true true 5.0 5.0Interesting working in Netbeans the rake build script was unecessary, but size of compiled jar was much smaller with the script (however I had linked jruby-9000 in Netbeans). See repository on github for Vec3 and spec tests.
No comments:
Post a Comment