# menger.rb by Martin Prout
require 'ruby-processing'
load_library 'opengl'
# full_screen required for linux with OPENGL (a bug)
full_screen if java.lang.System.get_property('os.name') == "Linux"
ANGLE_STEP = Math::PI/180
DATA = [-1, 0, 1]
MIN_SIZE = 40 # controls recursion depth (20 is pushing it for me!!)
def setup
size(600, 600, OPENGL)
library_loaded?(:opengl) ? render_mode(OPENGL) : render_mode(P3D)
@start_t = Time.now.to_f
@data = [] # initialize empty array
cube(0, 0, 0, 300) # fill data array
@angle = 0
end
# Fill @data array with cube center coordinate
# and size data (this can be done at setup)
def cube x, y, z, sz
unless (sz <= MIN_SIZE) then
u = sz/3.0
DATA.each do |i|
DATA.each do |j|
DATA.each do |k|
cube(x+(i*u), y+(j*u), z+(k*u), u) unless (i.abs + j.abs + k.abs) == 1
end
end
end
end
@data.push [x, y, z, sz] unless sz > MIN_SIZE
end
# Render the cubes (data from @data) using
# translate and custom box function, uses ruby splat
def draw_cubes
@data.each do |point|
x, y, z, sz = *point
push_matrix
translate(x, y, z)
my_box(sz)
pop_matrix
end
end
def draw
background 0
ambient_light 155, 155, 155
directional_light 225, 225, 225, 1, 1, -1
@angle = (ANGLE_STEP + @angle) % (Math::PI * 2)
translate(width/2, height/2, 0)
rotate_x @angle
rotate_y @angle
rotate_z @angle
draw_cubes
end
def my_box sz
no_stroke
begin_shape QUADS
# +Z "front" face
fill 200, 0, 0
vertex -sz/2, -sz/2, sz/2
vertex sz/2, -sz/2, sz/2
vertex sz/2, sz/2, sz/2
vertex -sz/2, sz/2, sz/2
# -Z "back" face
vertex sz/2, -sz/2, -sz/2
vertex -sz/2, -sz/2, -sz/2
vertex -sz/2, sz/2, -sz/2
vertex sz/2, sz/2, -sz/2
# +Y "bottom" face
fill 0, 200, 0
vertex -sz/2, sz/2, sz/2
vertex sz/2, sz/2, sz/2
vertex sz/2, sz/2, -sz/2
vertex -sz/2, sz/2, -sz/2
# -Y "top" face
vertex -sz/2, -sz/2, -sz/2
vertex sz/2, -sz/2, -sz/2
vertex sz/2, -sz/2, sz/2
vertex -sz/2, -sz/2, sz/2
# +X "right" face
fill 0, 0, 200
vertex sz/2, -sz/2, sz/2
vertex sz/2, -sz/2, -sz/2
vertex sz/2, sz/2, -sz/2
vertex sz/2, sz/2, sz/2
# -X "left" face
vertex -sz/2, -sz/2, -sz/2
vertex -sz/2, -sz/2, sz/2
vertex -sz/2, sz/2, sz/2
vertex -sz/2, sz/2, -sz/2
end_shape
end
I would be interested to know how this sketch would work on Windows or Mac os, with a more powerful graphics card. The static screen shot below was taken with min size set to 40 on my linux box (33 is practical limit). P3D is not so good as you get to see the joins.
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Tuesday, 29 September 2009
Real Menger Sponge in Ruby Processing
Labels:
menger sponge,
ruby processing
Thursday, 24 September 2009
A Simple Rotating Cube using PVector and splat
require 'ruby-processing'
class RotatingCube < Processing::App
def setup
render_mode P3D
@data = [
PVector.new( -1, -1, 1), # 0 twice unit cube vertices
PVector.new( 1, -1, 1), # 1 use halves for unit cube
PVector.new( 1, 1, 1), # 2
PVector.new( -1, 1, 1), # 3
PVector.new( 1, -1, -1), # 4
PVector.new( -1, -1, -1), # 5
PVector.new( -1, 1, -1), # 6
PVector.new( 1, 1, -1), # 7
PVector.new( 1, -1, 1) # 8
]
frame_rate 60
@theta = 0
end
def draw
background 0
@theta += 0.005
ambient_light 255, 255, 255
directional_light 255, 255, 255, 1, -1, 0
translate width/2, height/2, 0
rotate_x @theta
rotate_y @theta
rotate_z @theta
pts = scale_vectors width/4
draw_cube pts
end
def draw_cube pts
stroke 0
begin_shape QUADS
fill 255, 0, 0
# +Z "front" face
vertex *pts[0].array # 0
vertex *pts[1].array # 1
vertex *pts[2].array # 2
vertex *pts[3].array # 3
# -Z "back" face
vertex *pts[4].array # 4
vertex *pts[5].array # 5
vertex *pts[6].array # 6
vertex *pts[7].array # 7
fill 0, 255, 0
# +Y "bottom" face
vertex *pts[3].array # 3
vertex *pts[2].array # 2
vertex *pts[7].array # 7
vertex *pts[6].array # 6
# -Y "top" face
vertex *pts[5].array # 5
vertex *pts[4].array # 4
vertex *pts[8].array # 8
vertex *pts[0].array # 0
fill 0, 0, 255
# +X "right" face
vertex *pts[8].array # 8
vertex *pts[4].array # 4
vertex *pts[7].array # 7
vertex *pts[2].array # 2
# -X "left" face
vertex *pts[5].array # 5
vertex *pts[0].array # 0
vertex *pts[3].array # 3
vertex *pts[6].array # 6
end_shape
end
def scale_vectors sz
points = Array.new
@data.each do |point|
points.push(PVector.mult(point, sz))
end
return points
end
end
RotatingCube.new :title => "Rotating Cube", :width => 300, :height => 300
Labels:
rotating cube,
ruby-processing
Wednesday, 23 September 2009
Tunnel a popular theme in context free
Here's another ruby-processing context-free DSL example:-
# tunnel.rb ruby-processing
load_library 'context_free'
def setup_the_tunnel
@tunnel = ContextFree.define do
rule :start do
body :brightness => 1.0
end
rule :body do
square :brightness => 0.6
square :size => 0.996, :brightness => 0.3
body :size => 0.994, :rotation => 0.12
end
end
end
def setup
size 500, 500
setup_the_tunnel
color_mode HSB, 360, 1, 1
smooth
draw_it
save_frame("tunnel.png")
end
def draw
# Do nothing.
end
def draw_it
background 0
@tunnel.render :start, :size => height, :stop_size => 2,
:start_x => width/2, :start_y => height/2
end
# tunnel.rb ruby-processing
load_library 'context_free'
def setup_the_tunnel
@tunnel = ContextFree.define do
rule :start do
body :brightness => 1.0
end
rule :body do
square :brightness => 0.6
square :size => 0.996, :brightness => 0.3
body :size => 0.994, :rotation => 0.12
end
end
end
def setup
size 500, 500
setup_the_tunnel
color_mode HSB, 360, 1, 1
smooth
draw_it
save_frame("tunnel.png")
end
def draw
# Do nothing.
end
def draw_it
background 0
@tunnel.render :start, :size => height, :stop_size => 2,
:start_x => width/2, :start_y => height/2
end
Labels:
cfdg,
DSL,
moire fringes,
ruby processing,
tunnel
Fake Menger Sponge
Here I have used a combination of context free art ('cfdg') and ruby-processing to produce a fake "Menger Sponge" here is the cfdg file used create a Sierpinksi carpet image.
startshape carpet
background{b 1 h 360 sat 1}
rule carpet{
patch{}
carpet{s .3333 y .3333}
carpet{s .3333 x .3333 y .3333}
carpet{s .3333 x .3333}
carpet{s .3333 x .3333 y -.3333}
carpet{s .3333 y -.3333}
carpet{s .3333 x -.3333 y -.3333}
carpet{s .3333 x -.3333}
carpet{s .3333 x -.3333 y .3333}
}
rule patch{
SQUARE{s .3333 b -1}
}
Here is the Sierpinski carpet used for the cube faces (needs to be put in data folder). Image has been cropped to remove a blank border.
require 'ruby-processing'
class FakeMenger < Processing::App
# load_library 'opengl' ## for opengl uncomment this and adjust render mode
# full_screen ## uncomment this for linux opengl or just if you like fullscreen
def setup
render_mode P3D ## OPENGL would be better if you're not on linux
@theta = 0.0
frame_rate 60
@img = load_image "menger.png" ## put image in data folder
texture_mode NORMALIZED
end
def draw
background 0
@theta += 0.01
ambient_light 255, 0, 0
directional_light 255, 0, 0, 1, -1, 0
translate width/2, height/2, 0
rotate_x @theta
rotate_y @theta
draw_fake_menger height/4
end
def draw_fake_menger sz
stroke 0
begin_shape QUADS
texture @img
# +Z "front" face
vertex -sz, -sz, sz, 0, 0
vertex sz, -sz, sz, sz, 0
vertex sz, sz, sz, sz, sz
vertex -sz, sz, sz, 0, sz
# -Z "back" face
vertex sz, -sz, -sz, 0, 0
vertex -sz, -sz, -sz, sz, 0
vertex -sz, sz, -sz, sz, sz
vertex sz, sz, -sz, 0, sz
# +Y "bottom" face
vertex -sz, sz, sz, 0, 0
vertex sz, sz, sz, sz, 0
vertex sz, sz, -sz, sz, sz
vertex -sz, sz, -sz, 0, sz
# -Y "top" face
vertex -sz, -sz, -sz, 0, 0
vertex sz, -sz, -sz, sz, 0
vertex sz, -sz, sz, sz, sz
vertex -sz, -sz, sz, 0, sz
# +X "right" face
vertex sz, -sz, sz, 0, 0
vertex sz, -sz, -sz, sz, 0
vertex sz, sz, -sz, sz, sz
vertex sz, sz, sz, 0, sz
# -X "left" face
vertex -sz, -sz, -sz, 0, 0
vertex -sz, -sz, sz, sz, 0
vertex -sz, sz, sz, sz, sz
vertex -sz, sz, -sz, 0, sz
end_shape
end
end
FakeMenger.new :title => "Fake Menger Sponge", :width => 300, :height => 300
# FakeMenger.new :title => "Fake Menger Sponge", :width => 1024, :height => 1024
If you are not on linux try using 'opengl' it should run smoother than with P3D, where the cube actually looks decidedly spongy. It actually works tolerably well using opengl with linux, but you need to set full_screen mode and you might as well beef up the size a bit. Just for a bit of fun you can replace the black on the image with transparency, then you a bit of a ghostly image. I have implemented the menger in ruby-processing, follow the link.
startshape carpet
background{b 1 h 360 sat 1}
rule carpet{
patch{}
carpet{s .3333 y .3333}
carpet{s .3333 x .3333 y .3333}
carpet{s .3333 x .3333}
carpet{s .3333 x .3333 y -.3333}
carpet{s .3333 y -.3333}
carpet{s .3333 x -.3333 y -.3333}
carpet{s .3333 x -.3333}
carpet{s .3333 x -.3333 y .3333}
}
rule patch{
SQUARE{s .3333 b -1}
}
Here is the Sierpinski carpet used for the cube faces (needs to be put in data folder). Image has been cropped to remove a blank border.
require 'ruby-processing'
class FakeMenger < Processing::App
# load_library 'opengl' ## for opengl uncomment this and adjust render mode
# full_screen ## uncomment this for linux opengl or just if you like fullscreen
def setup
render_mode P3D ## OPENGL would be better if you're not on linux
@theta = 0.0
frame_rate 60
@img = load_image "menger.png" ## put image in data folder
texture_mode NORMALIZED
end
def draw
background 0
@theta += 0.01
ambient_light 255, 0, 0
directional_light 255, 0, 0, 1, -1, 0
translate width/2, height/2, 0
rotate_x @theta
rotate_y @theta
draw_fake_menger height/4
end
def draw_fake_menger sz
stroke 0
begin_shape QUADS
texture @img
# +Z "front" face
vertex -sz, -sz, sz, 0, 0
vertex sz, -sz, sz, sz, 0
vertex sz, sz, sz, sz, sz
vertex -sz, sz, sz, 0, sz
# -Z "back" face
vertex sz, -sz, -sz, 0, 0
vertex -sz, -sz, -sz, sz, 0
vertex -sz, sz, -sz, sz, sz
vertex sz, sz, -sz, 0, sz
# +Y "bottom" face
vertex -sz, sz, sz, 0, 0
vertex sz, sz, sz, sz, 0
vertex sz, sz, -sz, sz, sz
vertex -sz, sz, -sz, 0, sz
# -Y "top" face
vertex -sz, -sz, -sz, 0, 0
vertex sz, -sz, -sz, sz, 0
vertex sz, -sz, sz, sz, sz
vertex -sz, -sz, sz, 0, sz
# +X "right" face
vertex sz, -sz, sz, 0, 0
vertex sz, -sz, -sz, sz, 0
vertex sz, sz, -sz, sz, sz
vertex sz, sz, sz, 0, sz
# -X "left" face
vertex -sz, -sz, -sz, 0, 0
vertex -sz, -sz, sz, sz, 0
vertex -sz, sz, sz, sz, sz
vertex -sz, sz, -sz, 0, sz
end_shape
end
end
FakeMenger.new :title => "Fake Menger Sponge", :width => 300, :height => 300
# FakeMenger.new :title => "Fake Menger Sponge", :width => 1024, :height => 1024
If you are not on linux try using 'opengl' it should run smoother than with P3D, where the cube actually looks decidedly spongy. It actually works tolerably well using opengl with linux, but you need to set full_screen mode and you might as well beef up the size a bit. Just for a bit of fun you can replace the black on the image with transparency, then you a bit of a ghostly image. I have implemented the menger in ruby-processing, follow the link.
Labels:
cfdg,
fake menger sponge,
ruby-processing
Saturday, 19 September 2009
Using Multidimensional Data Arrays
require 'ruby-processing'
class Sierpinski < Processing::App
def setup
size 800, 800
### data is an array of calculated coordinates for the Sierpinski triangle
data= [[[400.000000, -0.546875,387.500000, 21.103760,412.500000, 21.103760]],
[[387.500000, 21.103760,375.000000, 42.754395,400.000000, 42.754395]],
####################################################################
# Most data lines omitted
####################################################################
[[787.500000, 670.622803,775.000000, 692.273438,800.000000, 692.273438]]]
fill 0
background 255
no_stroke
data.each do |line|
line.each do |tri|
triangle(*tri) ## use 'splat' to de-array coordinates
end
end
end
end
There are some neat tricks you can use in ruby to read data from multidimensional arrays. The above code includes only a very partial amount of the data array, but the code illustrates how easy it is to extract the data from a multidimensional array in ruby. The "splat" prefix '*' strips the values out of the array, which can then be used directly to draw the triangle. This approach may be useful if there is performance need to avoid avoid recursion overhead. There's a lot of gibberish on the web about tail recursion optimization on the way for java 7 (and hence ruby 1.9? and may'be jruby so you won't need to worry about such optimization in the future, whenever that comes).
Labels:
jvm,
multidimensional array,
ruby processing,
ruby splat,
tail recursion
Friday, 18 September 2009
Another of Lazydogs Little Gems translated to ruby-processing
require 'ruby-processing'
load_library 'opengl'
########################################################
# Original code
# http://lazydog-bookfragments.blogspot.com/2009/05/mirror-of-confusion.html
# Original version written for java processing author 'lazydog'
# aka Ben Notorianni translated to ruby-processing by 'monkstone'
# aka Martin Prout
########################################################
#
# Dimensions of screen.
#
K_WIDTH = 800
K_HEIGHT = 600
#
# Dimensions of texture.
# For fast PCs reduce or remove the denominator.
#
K_TEXTURE_WIDTH = K_WIDTH/2
K_TEXTURE_HEIGHT = K_HEIGHT/2
# following currently required by linux? Otherwise use 'lazydogs' dimensions
#
if java.lang.System.get_property('os.name') == "Linux" then
full_screen
end
def setup
size(K_WIDTH, K_HEIGHT, OPENGL )
#
# P3D doesn't work well in this sketch!
#
library_loaded?(:opengl) ? render_mode(OPENGL) : render_mode(P3D)
@start_t = Time.now.to_f
color_mode RGB, 255
smooth
#
# @history used to store the current frame buffer as a texture.
#
@history = create_image( K_TEXTURE_WIDTH, K_TEXTURE_HEIGHT, RGB )
texture_mode( NORMALIZED )
end
def millis
(@start_t - Time.now.to_f) * 10**6
end
def draw
background( 255 )
translate( width/2, height/2 )
#
# Draw the ground - use a simple grid for detail.
#
# All numbers are fudged to make the grid and ground look about right.
#
hint(DISABLE_DEPTH_TEST)
no_stroke
fill( 240, 189, 180 )
rect( -width/2, 24, width/2, 200 )
fill( 200, 220, 255 )
rect( 0, 24, width, 200 )
stroke( 0 )
stroke_weight( 1.0 )
10.times do |i|
z = -2000 + i * 200
line( -width*10, 200, z, width* 10, 200, z )
x = i* 200
line( -x, 200, -2000, -x, 200, 200 )
line( x, 200, -2000, x, 200, 200 )
end
hint(ENABLE_DEPTH_TEST)
#
# Draw the mirror.
#
# The mirror is animated by rotating around the y-axis.
# The mirror is drawn using the last frame buffer as the reflection in the mirror.
# The view in the mirror is dimmed slightly by drawing an alpha rectangle over it.
# Also, the mirror is oultined to make it stand out a bit.
#
push_matrix()
# rotate_x( 0.03 * Math.sin( millis * 0.0023) )
rotate_y( 0.3 * Math.sin( millis * 0.001) )
translate( -width/2, -height/2, -200 )
no_stroke
begin_shape
texture( @history )
vertex( 0.0, 0.0, 0, 0 )
vertex( width, 0, 1, 0 )
vertex( width, height, 1, 1 )
vertex( 0, height, 0, 1 )
end_shape
fill( 0, 0, 0, 30 )
begin_shape
vertex( 0.0, 0.0 )
vertex( width, 0 )
vertex( width, height )
vertex( 0, height )
end_shape
stroke( 200, 200, 200 )
stroke_weight( 2 )
no_fill
begin_shape()
vertex( 0, 0 )
vertex( width, 0 )
vertex( width, height )
vertex( 0, height )
end_shape(CLOSE)
pop_matrix
#
# Draw the foreground scene.
#
translate( 200 * Math.sin( millis * 0.001 ), 200 * Math.sin( millis* 0.0013 ) )
lights
no_stroke
fill( 238, 80, 70 )
sphere( 100 )
#
# Use the current frame as the texture for the
# mirror in the next frame.
#
# if ( frame_count % 4 == 0 )
@history.copy( get, 0, 0, width, height, 0, 0, K_TEXTURE_WIDTH, K_TEXTURE_HEIGHT )
end
Labels:
opengl,
ruby processing
A More Complicated Sierpinski
Here is a more elegant use of the mathematics, the main credit to lazydog for figuring it all out, all I did was translate it to ruby-processing. If you just want to see a boring old sphere, remove the comment in front of the last recursive drawTriangle (in drawTriangle of course). If you want to see more amazing processing opengl apps check out lazydogs blog.
# elegant.rb
class ElegantBall < Processing::App
load_library :opengl
attr_reader :start_t
def setup()
library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
color_mode(RGB, 1)
end
def configure_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH # optional
hint DISABLE_OPENGL_ERROR_REPORT # optional
end
def draw()
#background(0.25)
background(0)
# Move the origin so that the scene is centered on the screen.
translate(width/2, height/2, 0.0)
# Set up the lighting.
setup_lights
# Rotate the local coordinate system.
smooth_rotation(5.0, 6.7, 7.3)
# Draw the inner object.
no_stroke()
fill(smooth_colour(10.0, 12.0, 7.0))
draw_icosahedron(5, 60.0, false)
# Rotate the local coordinate system again.
smooth_rotation(4.5, 3.7, 7.3)
# Draw the outer object.
stroke(0.2)
fill(smooth_colour(6.0, 9.2, 0.7))
draw_icosahedron(5, 200.0, true)
end
def setup_lights
ambient_light(0.025, 0.025, 0.025)
directional_light(0.2, 0.2, 0.2, -1, -1, -1)
spot_light(1.0, 1.0, 1.0, -200, 0, 300, 1, 0, -1, Math::PI/4, 20)
end
##
# Generate a vector whose components change smoothly over time in the range [ 0, 1 ].
# Each component uses a Math.sin() function to map the current time in milliseconds somewhere
# in the range [ 0, 1 ].A 'speed' factor is specified for each component.
#
def smooth_vector(s1, s2, s3)
mills = millis * 0.00003 ## Lazydogs factor
# mills = millis * 0.0000001 ## worked for me a bit slower!!
x = 0.5 * Math.sin(mills * s1) + 0.5
y = 0.5 * Math.sin(mills * s2) + 0.5
z = 0.5 * Math.sin(mills * s3) + 0.5
PVector.new(x, y, z)
end
##
# Generate a colour which smoothly changes over time.
# The speed of each component is controlled by the parameters s1, s2 and s3.
#
def smooth_colour(s1, s2, s3)
v = smooth_vector(s1, s2, s3)
color(v.x, v.y, v.z)
end
##
# Rotate the current coordinate system.
# Uses smooth_vector() to smoothly animate the rotation.
#
def smooth_rotation(s1, s2, s3)
r1 = smooth_vector(s1, s2, s3)
rotate_x(2.0 * Math::PI * r1.x)
rotate_y(2.0 * Math::PI * r1.y)
rotate_x(2.0 * Math::PI * r1.z)
end
##
# Draw an icosahedron defined by a radius r and recursive depth d.
# Geometry data will be saved into dst. If spherical is true then the icosahedron
# is projected onto the sphere with radius r.
#
def draw_icosahedron(depth, r, spherical)
# Calculate the vertex data for an icosahedron inscribed by a sphere radius 'r'.
# Use 4 Golden Ratio rectangles as the basis.
gr = (1.0 + Math.sqrt(5.0)) / 2.0
h = r / Math.sqrt(1.0 + gr * gr)
v =
[
PVector.new(0, -h, h*gr), PVector.new(0, -h, -h*gr), PVector.new(0, h, -h*gr), PVector.new(0, h, h*gr),
PVector.new(h, -h*gr, 0), PVector.new(h, h*gr, 0), PVector.new(-h, h*gr, 0), PVector.new(-h, -h*gr, 0),
PVector.new(-h*gr, 0, h), PVector.new(-h*gr, 0, -h), PVector.new(h*gr, 0, -h), PVector.new(h*gr, 0, h)
]
# Draw the 20 triangular faces of the icosahedron.
unless spherical then
r = 0.0
end
begin_shape(TRIANGLES)
draw_triangle(depth, r, v[0], v[7],v[4])
draw_triangle(depth, r, v[0], v[4], v[11])
draw_triangle(depth, r, v[0], v[11], v[3])
draw_triangle(depth, r, v[0], v[3], v[8])
draw_triangle(depth, r, v[0], v[8], v[7])
draw_triangle(depth, r, v[1], v[4], v[7])
draw_triangle(depth, r, v[1], v[10], v[4])
draw_triangle(depth, r, v[10], v[11], v[4])
draw_triangle(depth, r, v[11], v[5], v[10])
draw_triangle(depth, r, v[5], v[3], v[11])
draw_triangle(depth, r, v[3], v[6], v[5])
draw_triangle(depth, r, v[6], v[8], v[3])
draw_triangle(depth, r, v[8], v[9], v[6])
draw_triangle(depth, r, v[9], v[7], v[8])
draw_triangle(depth, r, v[7], v[1], v[9])
draw_triangle(depth, r, v[2], v[1], v[9])
draw_triangle(depth, r, v[2], v[10], v[1])
draw_triangle(depth, r, v[2], v[5], v[10])
draw_triangle(depth, r, v[2], v[6], v[5])
draw_triangle(depth, r, v[2], v[9], v[6])
end_shape()
end
##
# Draw a triangle either immediately or subdivide it first.
# If depth is 1 then draw the triangle otherwise subdivide first.
#
def draw_triangle(depth, r, p1, p2, p3)
if (depth == 1) then
vertex(p1.x, p1.y, p1.z)
vertex(p2.x, p2.y, p2.z)
vertex(p3.x, p3.y, p3.z)
else
# Calculate the mid points of this triangle.
v1 = PVector.mult(PVector.add(p1, p2), 0.5)
v2 = PVector.mult(PVector.add(p2, p3), 0.5)
v3 = PVector.mult(PVector.add(p3, p1), 0.5)
unless (r == 0.0) then
# Project the verticies out onto the sphere with radius r.
v1.normalize()
v1.mult(r)
v2.normalize()
v2.mult(r)
v3.normalize()
v3.mult(r)
end
## Generate the next level of detail
depth -= 1
draw_triangle(depth, r, p1, v1, v3)
draw_triangle(depth, r, v1, p2, v2)
draw_triangle(depth, r, v2, p3, v3)
# Uncomment out the next line to include the central part of the triangle.
# draw_triangle(depth, r, v1, v2, v3)
end
end
end
### Guard against current issues with Linux and opengl with ruby-processing (18 Sept 2009)
if java.lang.System.get_property('os.name') == "Linux" then
ElegantBall.new(:width => 800, :height => 800, :title => "Elegant Ball", :full_screen => true)
else
ElegantBall.new(:width => 800, :height => 800, :title => "Elegant Ball", :full_screen => false)
end
Currently there is an issue with resizing on linux with opengl and ruby-processing, so best to specify full screen (NB to run this script you will need ruby-processing follow the link in my blog header to find out how, to run the script:-
"rp5 run elegant_ball.rb". See screenshot of running sketch below:-
# elegant.rb
class ElegantBall < Processing::App
load_library :opengl
attr_reader :start_t
def setup()
library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
color_mode(RGB, 1)
end
def configure_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH # optional
hint DISABLE_OPENGL_ERROR_REPORT # optional
end
def draw()
#background(0.25)
background(0)
# Move the origin so that the scene is centered on the screen.
translate(width/2, height/2, 0.0)
# Set up the lighting.
setup_lights
# Rotate the local coordinate system.
smooth_rotation(5.0, 6.7, 7.3)
# Draw the inner object.
no_stroke()
fill(smooth_colour(10.0, 12.0, 7.0))
draw_icosahedron(5, 60.0, false)
# Rotate the local coordinate system again.
smooth_rotation(4.5, 3.7, 7.3)
# Draw the outer object.
stroke(0.2)
fill(smooth_colour(6.0, 9.2, 0.7))
draw_icosahedron(5, 200.0, true)
end
def setup_lights
ambient_light(0.025, 0.025, 0.025)
directional_light(0.2, 0.2, 0.2, -1, -1, -1)
spot_light(1.0, 1.0, 1.0, -200, 0, 300, 1, 0, -1, Math::PI/4, 20)
end
##
# Generate a vector whose components change smoothly over time in the range [ 0, 1 ].
# Each component uses a Math.sin() function to map the current time in milliseconds somewhere
# in the range [ 0, 1 ].A 'speed' factor is specified for each component.
#
def smooth_vector(s1, s2, s3)
mills = millis * 0.00003 ## Lazydogs factor
# mills = millis * 0.0000001 ## worked for me a bit slower!!
x = 0.5 * Math.sin(mills * s1) + 0.5
y = 0.5 * Math.sin(mills * s2) + 0.5
z = 0.5 * Math.sin(mills * s3) + 0.5
PVector.new(x, y, z)
end
##
# Generate a colour which smoothly changes over time.
# The speed of each component is controlled by the parameters s1, s2 and s3.
#
def smooth_colour(s1, s2, s3)
v = smooth_vector(s1, s2, s3)
color(v.x, v.y, v.z)
end
##
# Rotate the current coordinate system.
# Uses smooth_vector() to smoothly animate the rotation.
#
def smooth_rotation(s1, s2, s3)
r1 = smooth_vector(s1, s2, s3)
rotate_x(2.0 * Math::PI * r1.x)
rotate_y(2.0 * Math::PI * r1.y)
rotate_x(2.0 * Math::PI * r1.z)
end
##
# Draw an icosahedron defined by a radius r and recursive depth d.
# Geometry data will be saved into dst. If spherical is true then the icosahedron
# is projected onto the sphere with radius r.
#
def draw_icosahedron(depth, r, spherical)
# Calculate the vertex data for an icosahedron inscribed by a sphere radius 'r'.
# Use 4 Golden Ratio rectangles as the basis.
gr = (1.0 + Math.sqrt(5.0)) / 2.0
h = r / Math.sqrt(1.0 + gr * gr)
v =
[
PVector.new(0, -h, h*gr), PVector.new(0, -h, -h*gr), PVector.new(0, h, -h*gr), PVector.new(0, h, h*gr),
PVector.new(h, -h*gr, 0), PVector.new(h, h*gr, 0), PVector.new(-h, h*gr, 0), PVector.new(-h, -h*gr, 0),
PVector.new(-h*gr, 0, h), PVector.new(-h*gr, 0, -h), PVector.new(h*gr, 0, -h), PVector.new(h*gr, 0, h)
]
# Draw the 20 triangular faces of the icosahedron.
unless spherical then
r = 0.0
end
begin_shape(TRIANGLES)
draw_triangle(depth, r, v[0], v[7],v[4])
draw_triangle(depth, r, v[0], v[4], v[11])
draw_triangle(depth, r, v[0], v[11], v[3])
draw_triangle(depth, r, v[0], v[3], v[8])
draw_triangle(depth, r, v[0], v[8], v[7])
draw_triangle(depth, r, v[1], v[4], v[7])
draw_triangle(depth, r, v[1], v[10], v[4])
draw_triangle(depth, r, v[10], v[11], v[4])
draw_triangle(depth, r, v[11], v[5], v[10])
draw_triangle(depth, r, v[5], v[3], v[11])
draw_triangle(depth, r, v[3], v[6], v[5])
draw_triangle(depth, r, v[6], v[8], v[3])
draw_triangle(depth, r, v[8], v[9], v[6])
draw_triangle(depth, r, v[9], v[7], v[8])
draw_triangle(depth, r, v[7], v[1], v[9])
draw_triangle(depth, r, v[2], v[1], v[9])
draw_triangle(depth, r, v[2], v[10], v[1])
draw_triangle(depth, r, v[2], v[5], v[10])
draw_triangle(depth, r, v[2], v[6], v[5])
draw_triangle(depth, r, v[2], v[9], v[6])
end_shape()
end
##
# Draw a triangle either immediately or subdivide it first.
# If depth is 1 then draw the triangle otherwise subdivide first.
#
def draw_triangle(depth, r, p1, p2, p3)
if (depth == 1) then
vertex(p1.x, p1.y, p1.z)
vertex(p2.x, p2.y, p2.z)
vertex(p3.x, p3.y, p3.z)
else
# Calculate the mid points of this triangle.
v1 = PVector.mult(PVector.add(p1, p2), 0.5)
v2 = PVector.mult(PVector.add(p2, p3), 0.5)
v3 = PVector.mult(PVector.add(p3, p1), 0.5)
unless (r == 0.0) then
# Project the verticies out onto the sphere with radius r.
v1.normalize()
v1.mult(r)
v2.normalize()
v2.mult(r)
v3.normalize()
v3.mult(r)
end
## Generate the next level of detail
depth -= 1
draw_triangle(depth, r, p1, v1, v3)
draw_triangle(depth, r, v1, p2, v2)
draw_triangle(depth, r, v2, p3, v3)
# Uncomment out the next line to include the central part of the triangle.
# draw_triangle(depth, r, v1, v2, v3)
end
end
end
if java.lang.System.get_property('os.name') == "Linux" then
ElegantBall.new(:width => 800, :height => 800, :title => "Elegant Ball", :full_screen => true)
else
ElegantBall.new(:width => 800, :height => 800, :title => "Elegant Ball", :full_screen => false)
"rp5 run elegant_ball.rb". See screenshot of running sketch below:-
Labels:
opengl,
ruby-processing,
Sierpinski triangle
Thursday, 3 September 2009
An Advanced Pretzel
Just another idle context free rule set in ruby processing, whilst I try to figure out how to do a pentaflake.
load_library 'context_free'
def setup_the_spiral
@spiral = ContextFree.define do
rule :start do
split do
star :rotation => 0
rewind
star :rotation => 120
rewind
star :rotation => 240
end
end
rule :base do
triangle :hue => 0.5
base :rotation => 3, :size => 0.98, :x => 0.09
end
rule :star do
split do
base :brightness => 0.8, :rotation => 0
rewind
base :brightness => 0.8, :rotation => 40
rewind
base :brightness => 0.8, :rotation => 80
end
end
end
end
def setup
size 600, 600
setup_the_spiral
no_stroke
color_mode HSB, 1.0
smooth
draw_it
end
def draw
# Do nothing.
end
def draw_it
background 0.33, 0.25, 0.2
@spiral.render :start, :size => height/5, :color => [0.35, 0.4, 0.9, 0.55], :stop_size => 1,
:start_x => width/2, :start_y => height/2
end
load_library 'context_free'
def setup_the_spiral
@spiral = ContextFree.define do
rule :start do
split do
star :rotation => 0
rewind
star :rotation => 120
rewind
star :rotation => 240
end
end
rule :base do
triangle :hue => 0.5
base :rotation => 3, :size => 0.98, :x => 0.09
end
rule :star do
split do
base :brightness => 0.8, :rotation => 0
rewind
base :brightness => 0.8, :rotation => 40
rewind
base :brightness => 0.8, :rotation => 80
end
end
end
end
def setup
size 600, 600
setup_the_spiral
no_stroke
color_mode HSB, 1.0
smooth
draw_it
end
def draw
# Do nothing.
end
def draw_it
background 0.33, 0.25, 0.2
@spiral.render :start, :size => height/5, :color => [0.35, 0.4, 0.9, 0.55], :stop_size => 1,
:start_x => width/2, :start_y => height/2
end
Labels:
cfdg,
DSL,
ruby-processing
Tuesday, 1 September 2009
Non cfdg DSL Sierpinski triangle using ruby processing
A genuine Sierpinski triangle using ruby-processing no DSL required:-
# Sierpinski.rb by Martin Prout
require 'ruby-processing'
T_HEIGHT = Math.sqrt(3)/2 # use ruby Math
TOP_Y = 1/Math.sqrt(3)
BOT_Y = Math.sqrt(3)/6
TRIANGLE_SIZE = 800
def setup()
size(TRIANGLE_SIZE, (T_HEIGHT * TRIANGLE_SIZE))
smooth()
fill(255)
background(0)
no_stroke()
draw_sierpinski(width/2, height/1.5, TRIANGLE_SIZE)
save_frame("sierpinski.png")
end
def draw_sierpinski(cx, cy, sz)
if (sz < 5) then # Limit no of recursions on size
draw_triangle(cx, cy, sz) # Only draw terminals
no_loop()
else
cx0 = cx
cy0 = cy - BOT_Y * sz
cx1 = cx - sz/4
cy1 = cy + (BOT_Y/2) * sz
cx2 = cx + sz/4
cy2 = cy + (BOT_Y/2) * sz
draw_sierpinski(cx0, cy0, sz/2)
draw_sierpinski(cx1, cy1, sz/2)
draw_sierpinski(cx2, cy2, sz/2)
end
end
def draw_triangle(cx, cy, sz)
cx0 = cx
cy0 = cy - TOP_Y * sz
cx1 = cx - sz/2
cy1 = cy + BOT_Y * sz
cx2 = cx + sz/2
cy2 = cy + BOT_Y * sz
triangle(cx0, cy0, cx1, cy1, cx2, cy2)
end
# Sierpinski.rb by Martin Prout
require 'ruby-processing'
T_HEIGHT = Math.sqrt(3)/2 # use ruby Math
TOP_Y = 1/Math.sqrt(3)
BOT_Y = Math.sqrt(3)/6
TRIANGLE_SIZE = 800
def setup()
size(TRIANGLE_SIZE, (T_HEIGHT * TRIANGLE_SIZE))
smooth()
fill(255)
background(0)
no_stroke()
draw_sierpinski(width/2, height/1.5, TRIANGLE_SIZE)
save_frame("sierpinski.png")
end
def draw_sierpinski(cx, cy, sz)
if (sz < 5) then # Limit no of recursions on size
draw_triangle(cx, cy, sz) # Only draw terminals
no_loop()
else
cx0 = cx
cy0 = cy - BOT_Y * sz
cx1 = cx - sz/4
cy1 = cy + (BOT_Y/2) * sz
cx2 = cx + sz/4
cy2 = cy + (BOT_Y/2) * sz
draw_sierpinski(cx0, cy0, sz/2)
draw_sierpinski(cx1, cy1, sz/2)
draw_sierpinski(cx2, cy2, sz/2)
end
end
def draw_triangle(cx, cy, sz)
cx0 = cx
cy0 = cy - TOP_Y * sz
cx1 = cx - sz/2
cy1 = cy + BOT_Y * sz
cx2 = cx + sz/2
cy2 = cy + BOT_Y * sz
triangle(cx0, cy0, cx1, cy1, cx2, cy2)
end
Labels:
recursion,
ruby processing,
Sierpinski triangle
Subscribe to:
Posts (Atom)
Followers
Blog Archive
-
▼
2009
(50)
-
▼
September
(9)
- Real Menger Sponge in Ruby Processing
- A Simple Rotating Cube using PVector and splat
- Tunnel a popular theme in context free
- Fake Menger Sponge
- Using Multidimensional Data Arrays
- Another of Lazydogs Little Gems translated to ruby...
- A More Complicated Sierpinski
- An Advanced Pretzel
- Non cfdg DSL Sierpinski triangle using ruby proces...
-
▼
September
(9)
About Me
- monkstone
- I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2