Here is the Vec2 class (I wanted to have Vec2D but that doesn't play too well with JRuby, well at least not for me), very loosely based on yokolets Fraction example.
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
@JRubyClass(name = "Processing::Vec2")
public class Vec2 extends RubyObject {
static final double EPSILON = 1.0e-04;
double jx = 0;
double jy = 0;
@param context
@param klazz
@param args
@return
@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
@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
@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
@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
@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
@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
@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
@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
@param other
@return
@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
@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
@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
@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
@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;
}
}
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
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.0
Interesting 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.