Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Showing posts with label PVector. Show all posts
Showing posts with label PVector. Show all posts

Saturday, 24 May 2014

Extending ruby-processing with built in jruby extensions

A jruby extension need not be a gem (and in some ways extension as gems can be limiting with ruby-processing, as they do not work too easily with jruby-complete) instead it can become a custom or built in library. In the development branch of ruby-processing (fastmath fork) I have created two built in libraries as jruby extensions:-
  1. vecmath, as direct replacement for PVector and incorporation arcball functionality (uses jafama under hood)
  2. fastmath, incorporating degree precision deg/cos lookup table and a wrapper for some jafama functions

For the vecmath I took the advantage of working with java to create arcball functionality using processing reflection calls. I also chose to allow simple Vec3D to vertex and Vec3D to normal (this has got to be most efficient way since it avoids unecessary java to ruby, ruby to java conversions something missing in vanilla processing). Here is a sketch (original by Andrés Colubri) that uses the FastMath sin and cos functions (from jafama) and Vec3D to vertex and Vec3D to normal conversions.
# Trefoil, by Andres Colubri
# A parametric surface is textured procedurally
# by drawing on an offscreen PGraphics surface.

load_libraries :vecmath, :fastmath

attr_reader :pg, :trefoil

def setup
  size(1024, 768, P3D)

  texture_mode(NORMAL)
  noStroke

  # Creating offscreen surface for 3D rendering.
  @pg = create_graphics(32, 512, P3D)
  pg.begin_draw
  pg.background(0, 0)
  pg.noStroke
  pg.fill(255, 0, 0, 200)
  pg.end_draw

  # Saving trefoil surface into a PShape3D object
  @trefoil = create_trefoil(350, 60, 15, pg)
end

def draw
  background(0)

  pg.begin_draw
  pg.ellipse(rand(0.0 .. pg.width), rand(0.0 .. pg.height), 4, 4)
  pg.end_draw

  ambient(250, 250, 250)
  pointLight(255, 255, 255, 0, 0, 200)

  push_matrix
  translate(width/2, height/2, -200)
  rotate_x(frame_count * PI / 500)
  rotate_y(frame_count * PI / 500)
  shape(trefoil)
  pop_matrix
end

# Code to draw a trefoil knot surface, with normals and texture 
# coordinates.
# Adapted from the parametric equations example by Philip Rideout:
# http://iphone-3d-programming.labs.oreilly.com/ch03.html

# This function draws a trefoil knot surface as a triangle mesh derived
# from its parametric equation.
def create_trefoil(s, ny, nx, tex)

  obj = create_shape()
  obj.begin_shape(TRIANGLES)
  obj.texture(tex)

  (0 ... nx).each do |j|
    u0 = j.to_f / nx
    u1 = (j + 1).to_f / nx
    (0 ... ny).each do |i|
      v0 = i.to_f / ny
      v1 = (i + 1).to_f / ny

      p0 = eval_point(u0, v0)
      n0 = eval_normal(u0, v0)

      p1 = eval_point(u0, v1)
      n1 = eval_normal(u0, v1)

      p2 = eval_point(u1, v1)
      n2 = eval_normal(u1, v1)

      # Triangle p0-p1-p2      
      n0.shape_normal(obj)
      pa = p0 * s
      pa.shape_vertex(obj, u0, v0)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, u0, v1)
      n2.shape_normal(obj)
      pc = p2 * s
      pc.shape_vertex(obj, u1, v1)

      p1 = eval_point(u1, v0)
      n1 = eval_normal(u1, v0)

      # Triangle p0-p2-p1      
      n0.shape_normal(obj)
      pa.shape_vertex(obj, u0, v0)
      n2.shape_normal(obj)
      pc.shape_vertex(obj, u1, v1)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, u1, v0)
    end
  end
  obj.end_shape
  return obj
end

# Evaluates the surface normal corresponding to normalized 
# parameters (u, v)
def eval_normal(u, v)
  # Compute the tangents and their cross product.
  p = eval_point(u, v)
  tangU = eval_point(u + 0.01, v)
  tangV = eval_point(u, v + 0.01)
  tangU -= p
  tangV -= p

  normUV = tangV.cross(tangU)
  normUV.normalize!
  return normUV
end

# Evaluates the surface point corresponding to normalized 
# parameters (u, v)
def eval_point(u, v)
  a = 0.5
  b = 0.3
  c = 0.5
  d = 0.1
  s = TWO_PI * u
  t = (TWO_PI * (1 - v)) * 2

  r = a + b * FastMath.cos(1.5 * t)
  x = r * FastMath.cos(t)
  y = r * FastMath.sin(t)
  z = c * FastMath.sin(1.5 * t)

  dv = Vec3D.new
  dv.x = -1.5 * b * FastMath.sin(1.5 * t) * FastMath.cos(t) - (a + b * FastMath.cos(1.5 * t)) * FastMath.sin(t)
  dv.y = -1.5 * b * FastMath.sin(1.5 * t) * FastMath.sin(t) + (a + b * FastMath.cos(1.5 * t)) * FastMath.cos(t)
  dv.z = 1.5 * c * FastMath.cos(1.5 * t)

  q = dv
  q.normalize!
  qvn = Vec3D.new(q.y, -q.x, 0)
  qvn.normalize!
  ww = q.cross(qvn)

  pt = Vec3D.new
  pt.x = x + d * (qvn.x * FastMath.cos(s) + ww.x * FastMath.sin(s))
  pt.y = y + d * (qvn.y * FastMath.cos(s) + ww.y * FastMath.sin(s))
  pt.z = z + d * ww.z * FastMath.sin(s)
  return pt
end



For the curious the current version of ruby-processings jruby extensions are available here.

Tuesday, 18 February 2014

Nature of Code examples now ported to ruby

Thanks to Pierre-Pat, Shiffmans the nature of code examples have now been ported to ruby-processing, get them here. Naturally enough PVectors were retained in the examples (the code after all originally comes from Dan Shiffman). I am not a big fan of the PVector class (which I think is trying to do too much, which is why I created the vecmath library for ruby-processing) so here is one of examples that could easily use the Vec2D class instead of PVector:-
# The Nature of Code
# http://natureofcode.com
# NOC_1_11_motion101_acceleration_array

load_library :vecmath

class Mover
  TOP_SPEED = 6
  attr_reader :location, :velocity, :topspeed_squared

  def initialize(width, height)
    @location = Vec2D.new(rand(width/2), rand(height/2))
    @velocity = Vec2D.new(0, 0)
    @topspeed_squared = TOP_SPEED * TOP_SPEED
  end

  def update
    mouse = Vec2D.new(mouse_x, mouse_y)
    acceleration = mouse - location
    acceleration.normalize!
    acceleration *= 0.2

    @velocity += acceleration
    @velocity.set_mag(TOP_SPEED) if velocity.mag_squared > topspeed_squared
    @location += velocity
  end

  def display
    stroke(0)
    stroke_weight(2)
    fill(127)
    ellipse(location.x, location.y, 48, 48)
  end
end

def setup
  size(800, 200)
  @movers = Array.new(20) { Mover.new(width, height) }
end

def draw
  background(255)

  @movers.each do |mover|
    mover.update
    mover.display
  end
end

Saturday, 5 October 2013

A custom Vector library for ruby processing

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     # a value used by processing.org
  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

  # Modulus of vec. Also known as length, size or norm
  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

  # vanilla processing returns a Vector, rather than Scalar (defaults to 3D result when z = 0)
  def cross_product(vec)
    x * vec.y - y * vec.x
  end

  # Scalar product, also known as inner product or dot product
  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

  # Scalar product, also known as inner product or dot product
  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
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse = mouse - center # note need assign result to mouse

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse = mouse * 150 # note need assign the result to mouse

  translate(width/2,height/2)
  # Draw the resulting vector
  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).
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse -= center  # note we can assign result to mouse using -=

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse *= 150 # note we can assign result to mouse using *=

  translate(width/2,height/2)
  # Draw the resulting vector
  stroke(255)
  stroke_weight(4)
  line(0, 0, mouse.x, mouse.y)

end

Thursday, 15 August 2013

Ruby-processing-2.1.3 is released (features processing-2.0.2)

In recent weeks I've changed the default mode of ruby-processing from using the included jruby-complete by default to using an external (system installed) jruby. So the --jruby flag is now deprecated (it doesn't do anything) and is replaced by the --nojruby flag which causes the installed jruby-complete to be used instead of the system version. The jruby-complete is retained for the following uses:-

  1. To support application export
  2. To run certain sketches (mainly GLSL shader sketches) that wont run with installed jruby
  3. For people without an installed jruby (for whatever reason), make sure and use --nojruby flag.
Anyway I've also revisited, some of the samples with a view to introducing some more idiomatic ruby, and one thing I came up with was the idea to replace the heavyweight PVector class with a lightweight Struct alternative when the PVector class was being used as a repository for x, y, z values.
Vect = Struct.new(:x, :y, :z) do
  def add v
    self.x += v.x
    self.y += v.y
    self.z += v.z
  end
end

Followers

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2