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

Showing posts with label pshape. Show all posts
Showing posts with label pshape. Show all posts

Tuesday, 18 February 2014

More explorations of shape primitives in ruby-processing (JRubyArt)

I have now created a experimental "shape" library class for ruby-processing see the development branch at JRubyArt. This very much work in progress, but the box class is somewhat influenced by toxiclibs "axis bounding box" AABB class. But for the idiosyncratic axis convention lo would correspond to back lower left, and hi to front upper right.
load_libraries :vecmath, :shape

attr_reader :my_shape

def setup
  size 200, 200, P3D
  camera(100.0, 100.0, 120.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
  my_box = Box.new
  my_box.scale(size: 100)
  #my_box.scale(x: 50, y: 30, z: 80)
  fill 100, 100, 100, 100
  @my_shape = create_shape(BOX, *my_box.dimension)
  my_shape.translate(*my_box.center)
end

def draw
  lights
  fill 200, 0, 0
  no_stroke
  sphere(50)
  shape(my_shape)
end

Thursday, 7 March 2013

Stochastic Menger using PShape api

NB: This sketch requires the development version of ruby-processing see here.

##########################
# StochasticMenger.rb
# (processing-2.0)
# author Martin Prout
##########################

load_library 'arcball'
import "arcball"

PTS = [-1, 0, 1]
MIN_SIZE = 50
X = 0
Y = 1
Z = 2

attr_reader :arcball, :menger

def setup
  size(640, 480, P3D)
  smooth(8)
  camera
  camera(width/2.0, height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, 1, 0)
  @arcball = ArcBall.new(0, 0, min(width - 20, height - 20) * 0.8)
  @menger = create_shape(GROUP)
  create_menger(0, 0, 0, height * 0.8)
end

def draw
  background(40, 40, 180)
  no_stroke
  lights
  update
  define_lights
  render
end

def render
  menger.set_ambient(50)
  menger.set_specular(150)
  shape(menger)
end

def create_menger(xx, yy, zz, sz)
  u = sz / 3.0
  if (sz < MIN_SIZE) # recursion limited by minimum cube size
    no_stroke
    menger.add_child(create_cube(xx, yy, zz, sz)) # create and add a cube  
  else
    PTS.each do |i|
      PTS.each do |j|
        PTS.each do |k|
          if ((i.abs + j.abs + k.abs) > 1)
            create_menger(xx + (i * u), yy + (j * u), zz + (k * u), u)
          end
        end
      end
    end
  end
end

def create_cube(xx, yy, zz, sz)
  dim = sz / 2.0
  chance = rand
  if chance < 0.8
    cube = create_shape(BOX, sz, sz, sz)
    cube.set_fill(color(0, 0, 219))
  else
    cube = create_shape(SPHERE, dim)
    cube.set_fill(color(224, 223, 219))
  end
  cube.translate(xx, yy, zz)
  return cube
end

def update
  theta, x, y, z = arcball.update
  rotate(theta, x, y, z)
end

def mouse_pressed
  arcball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  arcball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, 240, 0)
  directional_light(50, 50, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

def key_pressed
  case(key)
  when 'x'
    arcball.select_axis(X)
  when 'y'
    arcball.select_axis(Y)
  when 'z'
    arcball.select_axis(Z)
  end
end

def key_released
  arcball.select_axis(-1)
end


Tuesday, 1 January 2013

Changes to PShape api processing-2.0b8

I'm kind of assuming the recent api changes in svn version of processing-2.0 are going to stick around to at least processing-2.0b8 (I can see some sort of logic in them at any rate). Here is my retained menger sketch that uses PShape updated in line with the api changes (note to set attributes such as ambient, and specular outside of begin/end shape you now need to use the set prefix as I have done below):-
The method signature is also different, either set_attribute(col) where col is an int or processing color or set_attribute(idx, col) where idx is an index int (which I have yet to explore).
##########################
# RetainedMenger.rb
# (processing-2.0)
# author Martin Prout
##########################

load_library 'arcball'
import "arcball"

PTS = [-1, 0, 1]
MIN_SIZE = 20
X = 0
Y = 1
Z = 2

attr_reader :arcball, :menger

def setup
  size(640, 480, P3D)
  smooth(8)
  camera
  camera(width/2.0, height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, 1, 0)
  @arcball = ArcBall.new(0, 0, min(width - 20, height - 20) * 0.8)
  @menger = create_shape(GROUP)
  create_menger(0, 0, 0, height * 0.8)
end

def draw
  background(20, 20, 200)
  no_stroke
  lights
  update
  define_lights
  render
end

def render
  menger.set_ambient(50)
  menger.set_specular(30)
  shape(menger)
end

def create_menger(xx, yy, zz, sz)
  u = sz / 3.0
  if (sz < MIN_SIZE) # recursion limited by minimum cube size
    no_stroke
    menger.add_child(create_cube(xx, yy, zz, sz)) # create and add a cube  
  else
    PTS.each do |i|
      PTS.each do |j|
        PTS.each do |k|
          if ((abs(i) + abs(j) + abs(k)) > 1)
            create_menger(xx + (i * u), yy + (j * u), zz + (k * u), u)
          end
        end
      end
    end
  end
end

def create_cube(xx, yy, zz, sz)
  dim = sz / 2.0
  cube = create_shape
  cube.begin_shape(QUADS)
  # Front face
  cube.fill(255)
  cube.normal(0, 0, 1)
  cube.vertex(-dim + xx, -dim + yy, -dim + zz)
  cube.vertex(+dim + xx, -dim + yy, -dim + zz)
  cube.vertex(+dim + xx, +dim + yy, -dim + zz)
  cube.vertex(-dim + xx, +dim + yy, -dim + zz)

  # Back face

  cube.normal(0, 0, -1)
  cube.vertex(-dim + xx, -dim + yy, +dim + zz)
  cube.vertex(+dim + xx, -dim + yy, +dim + zz)
  cube.vertex(+dim + xx, +dim + yy, +dim + zz)
  cube.vertex(-dim + xx, +dim + yy, +dim + zz)

  # Left face

  cube.normal(1, 0, 0)
  cube.vertex(-dim + xx, -dim + yy, -dim + zz)
  cube.vertex(-dim + xx, -dim + yy, +dim + zz)
  cube.vertex(-dim + xx, +dim + yy, +dim + zz)
  cube.vertex(-dim + xx, +dim + yy, -dim + zz)

  # Right face

  cube.normal(-1, 0, 0)
  cube.vertex(+dim + xx, -dim + yy, -dim + zz)
  cube.vertex(+dim + xx, -dim + yy, +dim + zz)
  cube.vertex(+dim + xx, +dim + yy, +dim + zz)
  cube.vertex(+dim + xx, +dim + yy, -dim + zz)

  # Top face

  cube.normal(0, 1, 0)
  cube.vertex(-dim + xx, -dim + yy, -dim + zz)
  cube.vertex(+dim + xx, -dim + yy, -dim + zz)
  cube.vertex(+dim + xx, -dim + yy, +dim + zz)
  cube.vertex(-dim + xx, -dim + yy, +dim + zz)

  # Bottom face

  cube.normal(0, -1, 0)
  cube.vertex(-dim + xx, +dim + yy, -dim + zz)
  cube.vertex(+dim + xx, +dim + yy, -dim + zz)
  cube.vertex(+dim + xx, +dim + yy, +dim + zz)
  cube.vertex(-dim + xx, +dim + yy, +dim + zz)
  cube.end_shape
  return cube
end

def update
  theta, x, y, z = arcball.update
  rotate(theta, x, y, z)
end

def mouse_pressed
  arcball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  arcball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

def key_pressed
  case(key)
  when 'x'
    arcball.select_axis(X)
  when 'y'
    arcball.select_axis(Y)
  when 'z'
    arcball.select_axis(Z)
  end
end

def key_released
  arcball.select_axis(-1)
end

























  • Here is another api change was shape.texture(img)
  • now shape.set_texture(img)

# Planets, by Andres Colubri
#
# Sun and mercury textures from http://planetpixelemporium.com
# Star field picture from http://www.galacticimages.com/

attr_reader :starfield, :sun, :suntex, :planet1, :surftex1, :cloudtex, :planet2, :surftex2

def setup
  size(1024, 768, P3D)
  @starfield = load_image("starfield.jpg")
  @suntex = load_image("sun.jpg")
  @surftex1 = load_image("planet.jpg")
  @surftex2 = load_image("mercury.jpg")
  no_stroke
  fill(255)
  sphere_detail(40)
  @sun = create_shape(SPHERE, 150)
  @sun.set_texture(suntex)
  @planet1 = create_shape(SPHERE, 150)
  planet1.set_texture(surftex1)
  @planet2 = create_shape(SPHERE, 50)
  planet2.set_texture(surftex2)
end

def draw
  # Even we draw a full screen image after this, it is recommended to use
  # background to clear the screen anyways, otherwise A3D will think
  # you want to keep each drawn frame in the framebuffer, which results in 
  # slower rendering.
  background(0)
  # Disabling writing to the depth mask so the 
  # background image doesn't occludes any 3D object.
  hint(DISABLE_DEPTH_MASK)
  image(starfield, 0, 0, width, height)
  hint(ENABLE_DEPTH_MASK)
  push_matrix
  translate(width/2, height/2, -300)
  push_matrix
  rotate_y(PI * frame_count / 500)
  shape(sun)
  pop_matrix
  point_light(255,  255,  255,  0,  0,  0)
  rotate_y(PI * frame_count / 300)
  translate(0, 0, 300)
  shape(planet2)
  pop_matrix
  no_lights
  point_light(255,  255,  255,  0,  0,  -150)
  translate(0.75 * width,  0.6 * height,  50)
  shape(planet1)
end

Here is yet another api chage here need to use set_fill rather fill (further takes an int or processing color as input, there is an set_fill(index, color) variant I've yet too explore)
########################
# NB: Slow to get going 
# but, performs Ok after 
########################

BOX_SIZE = 20
MARGIN = BOX_SIZE * 2
DEPTH = 400
FINT = 3

attr_reader :box_fill, :grid, :fcount, :lastm, :frate

def setup
  size(640, 360, P3D)
  frame_rate(60)
  @fcount = 0
  @lastm = 0
  no_smooth
  no_stroke
  @grid = create_shape(GROUP)
  # Build grid using multiple translations 
  (-(DEPTH/2 + MARGIN) ... (DEPTH/2 - MARGIN)).step(BOX_SIZE) do |i|
    (-(height + MARGIN) ... (height - MARGIN)).step(BOX_SIZE) do |j|
      (-(width + MARGIN) ... (width - MARGIN)).step(BOX_SIZE) do |k|
        # Base fill color on counter values, abs function 
        # ensures values stay within legal range
        @box_fill = color(i.abs.to_i, j.abs.to_i, k.abs.to_i, 50)
        cube = create_shape(BOX, BOX_SIZE.to_f, BOX_SIZE.to_f, BOX_SIZE.to_f)
        cube.set_fill(box_fill)
        cube.translate(k, j, i)
        grid.add_child(cube)
      end
    end
  end
end

def draw
  background(255)
  hint(DISABLE_DEPTH_TEST)
  # Center and spin grid
  push_matrix()
  translate(width/2, height/2, -DEPTH)
  rotate_y(frame_count * 0.01)
  rotate_x(frame_count * 0.01)
  shape(grid)
  pop_matrix()
  hint(ENABLE_DEPTH_TEST)
  @fcount += 1
  m = millis()
  if (m - lastm > 1000 * FINT)
    @frate = fcount / FINT
    @fcount = 0
    @lastm = m
    puts("fps: #{frate}")
  end
  fill(0)
  text("fps: #{frate}", 10, 20)
end

ruby-processing (pre processing-2.0b8)

Just as I was ready to get Jeremy Ashkenas to pull my updated version of ruby-processing, Andres has gone and changed the PShape api. Here is my toxiclibs volumeutils example converted for the new api (well actually just reqd mesh_to_vbo.rb library).

The change to api is that beginning a custom shape is a two stage process:-
  1. shape = create_shape (no param) 
  2. shape.begin_shape (type) 
and to be more consistent (I assume) instead of shape.end we have shape.end_shape
Here I have created a mock processing-2.0b8 version (see here how to do it) and created a local branch of my ruby-processing fork to experiemnt with. The example:-

#
# This example implements a custom VolumetricSpace using an implicit function
# to calculate each voxel. This is slower than the default array or HashMap
# based implementations, but also has much less memory requirements and so might
# be an interesting and more viable approach for very highres voxel spaces
# (e.g. >32 million voxels). This implementation here also demonstrates how to
# achieve an upper boundary on the iso value (in addition to the one given and
# acting as lower threshold when computing the iso surface)
#
# Usage:
# move mouse to rotate camera
# -/=: zoom in/out
# l: apply laplacian mesh smooth
# 
#

# 
# Copyright (c) 2010 Karsten Schmidt & ruby-processing version Martin Prout 2013
# This sketch relies on a custom ruby-processing mesh_to_vbo library
# 
# This library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
# 
# http://creativecommons.org/licenses/LGPL/2.1/
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

load_libraries 'toxiclibscore', 'vbo', 'volumeutils'
include_package 'toxi.geom'
include_package 'toxi.geom.mesh'
include_package 'toxi.volume'
include_package 'toxi.processing'

RES = 64
ISO = 0.2
MAX_ISO = 0.66

attr_reader :mesh, :vbo, :curr_zoom, :implicit

def setup()
  size(720,720, P3D)
  @vbo = MeshToVBO.new(self)
  @curr_zoom = 1
  vol = EvaluatingVolume.new(Vec3D.new(400,400,400), RES, RES, RES, MAX_ISO)
  surface = HashIsoSurface.new(vol)
  @mesh = WETriangleMesh.new()
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
  no_stroke
  @implicit = vbo.meshToVBO(mesh, true)
end

def draw()
  background(0)
  lights
  define_lights
  translate(width / 2.0, height / 2.0, 0)
  rotate_x(mouse_y * -0.01)
  rotate_y(mouse_x * -0.01)
  scale(curr_zoom)
  shape(implicit)
end

def key_pressed()
  case key
  when '-'
    @curr_zoom -= 0.1
  when'='
    @curr_zoom += 0.1
  when 'l', 'L'
    LaplacianSmooth.new().filter(mesh, 1)
    @implicit = vbo.meshToVBO(mesh, true)
  when 's', 'S'
    save_frame("implicit.png")
  end
end

def define_lights
 ambient(20, 20, 20)
 ambient_light(50, 50, 50)
 point_light(30, 30, 30, 200, -150, 0)
 directional_light(0, 30, 50, 1, 0, 0)
 spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

class EvaluatingVolume < VolumetricSpace
  include Processing::Proxy
  include_package 'toxi.math'
  attr_reader :upper_bound, :lut
  FREQ = PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
    @lut=SinCosLUT.new()
  end

  def clear()
    # nothing to do here
  end

  def getVoxelAt(i)
    getVoxel(i % resX, (i % sliceRes) / resX, i / sliceRes)
  end

  def getVoxel(x, y, z)  # can't overload so we renamed
    val = 0
    if (x > 0 && x < resX1 && y > 0 && y < resY1 && z > 0 && z < resZ1)
      xx = x * 1.0 / resX - 0.5  # NB: careful about integer division !!!
      yy = y * 1.0 / resY - 0.5
      zz = z * 1.0 / resZ - 0.5
      #val = lut.sin(xx * FREQ) + lut.cos(yy * FREQ) + lut.sin(zz * FREQ)
      val = lut.cos(xx * FREQ) * lut.sin(yy* FREQ) + lut.cos(yy* FREQ) * lut.sin(zz* FREQ) + lut.cos(zz* FREQ)* lut.sin(xx* FREQ)
      if (val > upper_bound)
        val = 0
      end
    end
    return val
  end
end

The required library:-
############################################
# mesh_to_vbo.rb
# a ruby library to convert toxi.mesh object
# to vbo (PShape) written by Martin Prout
# make use of Processing::Proxy mixin
############################################
class MeshToVBO
  include Processing::Proxy

  attr_reader :parent

  def initialize(parent)
    @parent = parent
  end

  def meshToVBO(mesh, smth)
    retained = parent.createShape()
    retained.begin_shape(PShape::TRIANGLES)
    retained.enableStyle()
    retained.fill(222, 222, 222)
    retained.ambient(50)
    retained.shininess(10)
    retained.specular(50)
    if (smth)
      mesh.computeVertexNormals()
      mesh.getFaces.each do |f|
        retained.normal(f.a.normal.x, f.a.normal.y, f.a.normal.z)
        retained.vertex(f.a.x, f.a.y, f.a.z)
        retained.normal(f.b.normal.x, f.b.normal.y, f.b.normal.z)
        retained.vertex(f.b.x, f.b.y, f.b.z)
        retained.normal(f.c.normal.x, f.c.normal.y, f.c.normal.z)
        retained.vertex(f.c.x, f.c.y, f.c.z)
      end
    else
      mesh.getFaces.each do |f|
        retained.normal(f.normal.x, f.normal.y, f.normal.z)
        retained.vertex(f.a.x, f.a.y, f.a.z)
        retained.vertex(f.b.x, f.b.y, f.b.z)
        retained.vertex(f.c.x, f.c.y, f.c.z)
      end
    end
    retained.end_shape()
    return retained
  end

  # variant
  # input array of meshes, output an array of shapes
  def meshToRetained(mesh, smth)
    rshapes = []
    (0 ... mesh.length).each do |i|
      rshapes.push(meshToVBO(mesh[i], smth))
    end
    return rshapes
  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