I recently got my copy of
Practical Object Oriented Design in Ruby by
Sandi Metz book, this got me thinking what could I could apply the ideas to in ruby-processing, and here is an early crack at it. Creating a custom (pure-ruby) vector library which can replace the hybrid RPVector (that extends PVector from processing) class of
a previous post. This is very much a first crack (but it works, only difference needed is
load_library :vec and use
normalize! instead of
normalize, much more ruby like I think) because I have ideas for extending the functionality along
some of these lines, however my sentiment is with toxi re simple made easy. Further if I use vanilla processing logic the cross_product method (modulus, dist etc) could all live in Vec, but I can easily defer that decision. I haven't at this stage made use
distance_squared externally, however this will be more efficient for testing boundary conditions of say a bouncing ball than regular
dist. Not shown here are the rspec tests that I've been using to ensure the code behaves, I am tempted to bundle this and a Quaternion class and possibly some others as a core ruby-processing library, since the operations of PVector are not at all ruby like, and many more people seem to come to ruby-processing from ruby, not the other way round sadly.
class Vec
attr_accessor :x, :y, :z
EPSILON = 9.999999747378752e-05
def initialize(x = 0 ,y = 0, z = 0)
@x, @y, @z = x, y, z
post_initialize
end
def post_initialize
nil
end
def ==(vec)
(x - vec.x).abs < EPSILON && (y - vec.y).abs < EPSILON && (z - vec.z).abs < EPSILON
end
end
class Vec2D < Vec
def modulus
Math.hypot(x, y)
end
def self.dist_squared(vec_a, vec_b)
(vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2
end
def self.dist(vec_a, vec_b)
Math.hypot(vec_a.x - vec_b.x, vec_a.y - vec_b.y)
end
def cross_product(vec)
x * vec.y - y * vec.x
end
def dot(vec)
x * vec.x + y * vec.y
end
def collinear_with?(vec)
cross_product(vec).abs < EPSILON
end
def +(vec)
Vec2D.new(x + vec.x, y + vec.y)
end
def -(vec)
Vec2D.new(x - vec.x, y - vec.y)
end
def *(scalar)
Vec2D.new(x * scalar, y * scalar)
end
def / (scalar)
Vec2D.new(x / scalar, y / scalar) unless scalar == 0
end
def normalize!
@x, @y = x / modulus, y / modulus
return self
end
alias :mag :modulus
end
class Vec3D < Vec
def modulus
Math.sqrt(x**2 + y**2 + z**2)
end
def self.dist_squared(vec_a, vec_b)
(vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2 + (vec_a.z - vec_b.z)**2
end
def self.dist(vec_a, vec_b)
Math.sqrt(self.dist_squared(vec_a, vec_b))
end
def cross_product(vec)
xc = y * vec.z - z * vec.y
yc = z * vec.x - x * vec.z
zc = x * vec.y - y * vec.x
Vec3D.new(xc, yc, zc)
end
def dot(vec)
x * vec.x + y * vec.y + z * vec.z
end
def collinear_with?(vec)
cross_product(vec) == Vec3D.new
end
def +(vec)
Vec3D.new(x + vec.x, y + vec.y, z + vec.z)
end
def -(vec)
Vec3D.new(x - vec.x, y - vec.y, z - vec.z)
end
def * (scalar)
Vec3D.new(x * scalar, y * scalar, z * scalar)
end
def / (scalar)
Vec3D.new(x / scalar, y / scalar, z / scalar) unless scalar.abs < EPSILON
end
def normalize!
@x, @y, @z = x / modulus, y / modulus, z / modulus
return self
end
alias :mag :modulus
end
A Little Test courtesy of Daniel Shiffman
load_library :vec
def setup
size(640,360)
end
def draw
background(0)
mouse = Vec2D.new(mouse_x, mouse_y)
center = Vec2D.new(width/2,height/2)
mouse = mouse - center
mouse.normalize!
mouse = mouse * 150
translate(width/2,height/2)
stroke(255)
stroke_weight(4)
line(0, 0, mouse.x, mouse.y)
end
Now interestingly the following also works so you can use the
+=, -=, /=, and *= assignment methods ( but you cannot override += etc as a methods unlike C++ )
So what this means is that in ruby += etc are just syntactic shortcuts, and not an operator in its own right (which is probably a good thing).
load_library :vec
def setup
size(640,360)
end
def draw
background(0)
mouse = Vec2D.new(mouse_x, mouse_y)
center = Vec2D.new(width/2,height/2)
mouse -= center
mouse.normalize!
mouse *= 150
translate(width/2,height/2)
stroke(255)
stroke_weight(4)
line(0, 0, mouse.x, mouse.y)
end