Skip to content

Instantly share code, notes, and snippets.

@monkstone
Created September 9, 2010 06:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save monkstone/571495 to your computer and use it in GitHub Desktop.
Save monkstone/571495 to your computer and use it in GitHub Desktop.
Experimental 3D context free dsl, in ruby processing
# A colorful animation of default.es
# the StructureSynth default script with wheel zoom feature
load_libraries :test_free
full_screen
attr_reader :xrot, :yrot, :zrot, :wheel, :col
def setup_the_spiral
@spiral = ContextFree.define do
rule :default do
split do
R1 :brightness => 1
rewind
R2 :brightness => 1
end
end
rule :R1 do
sphere :brightness => 0.8
R1 :size => 0.99, :x => 0.25, :rz => 6, :ry => 6
end
rule :R2 do
sphere :brightness => 0.8
R2 :size => 0.99, :x => -0.25, :rz => 6, :ry => 6
end
end
end
def setup
render_mode P3D
@wheel = JWheelListener.new(-50, -20) # initialize listener with start_z and maximum values
self.add_mouse_wheel_listener(wheel) # register the mouse listener
smooth
@xrot = 0.01
@yrot = 0
@zrot = 0
@col = 0
setup_the_spiral
end
def draw
background 0
setup_lights col
specular col, 1, 1
emissive 0.05
shininess 10
smooth_rotation(6.7, 7.3)
smooth_color(6.0)
@spiral.render :default, :start_x => 0, :start_y => 0, :start_z => wheel.zoom, :size => height/350,
:stop_size => 0.2, :color => [0, 0.8, 0.8], :rx => xrot, :ry => yrot, :rz => zrot
end
##
# Generate a gradient value that changes smoothly over time in the range [ 0, 1 ].
# The Math.sin() function is used to map the current time in milliseconds somewhere
# in the range [ 0, 1 ]. A 'speed' factor is specified by the parameters s1.
#
def smooth_gradient(s1)
mills = millis * 0.00003
gradient = 0.5 * Math.sin(mills * s1) + 0.5
end
##
# Rotate the current coordinate system.
# Uses smooth_gradient() to smoothly animate the rotation.
#
def smooth_rotation(s1, s2)
@yrot = 2.0 * Math::PI * smooth_gradient(s1)
@zrot = 2.0 * Math::PI * smooth_gradient(s2)
end
##
# Generate a 'hue' value which smoothly changes over time.
# The speed of change is controlled by the parameter s1.
#
def smooth_color(s1)
@col = smooth_gradient(s1)
end
def setup_lights(col)
directional_light(col, 0.8, 0.8, -0.5, 0.5, -1)
point_light(col, 0.8, 0.8, -0.5, 0.5, -1)
ambient_light(col, 0.8, 0.8, 1, 1, 1)
end
# A Context-Free library for Ruby-Processing, inspired by
# contextfreeart.org and StructureSynth
# Built on Jeremy Ashkenas context free DSL script
# now extended to provide mouse wheel zoom class
module Processing
class ContextFree
include Processing::Proxy
attr_accessor :rules, :app, :xr, :yr, :zr
AVAILABLE_OPTIONS = [:x, :y, :z, :rx, :ry, :rz, :size, :color, :hue, :saturation, :brightness, :alpha]
HSB_ORDER = {:hue => 0, :saturation => 1, :brightness => 2, :alpha => 3}
# Define a context-free system. Use this method to create a ContextFree
# object. Call render() on it to make it draw.
def self.define(&block)
cf = ContextFree.new
cf.instance_eval &block
cf
end
# Initialize a bare ContextFree object with empty recursion stacks.
def initialize
@app = $app
@graphics = $app.g
@finished = false
@rules = {}
@rewind_stack = []
@matrix_stack = []
@xr = 0
@yr = 0
@zr = 0
end
# Create an accessor for the current value of every option. We use a values
# object so that all the state can be saved and restored as a unit.
AVAILABLE_OPTIONS.each do |option_name|
define_method option_name do
@values[option_name]
end
end
# Here's the first serious method: A Rule has an
# identifying name, a probability, and is associated with
# a block of code. These code blocks are saved, and indexed
# by name in a hash, to be run later, when needed.
# The method then dynamically defines a method of the same
# name here, in order to determine which rule to run.
def rule(rule_name, prob=1, &proc)
@rules[rule_name] ||= {:procs => [], :total => 0}
total = @rules[rule_name][:total]
@rules[rule_name][:procs] << [(total...(prob+total)), proc]
@rules[rule_name][:total] += prob
unless ContextFree.method_defined? rule_name
self.class.class_eval do
eval <<-METH
def #{rule_name}(options)
merge_options(@values, options)
pick = determine_rule(#{rule_name.inspect})
@finished = true if @values[:size] < @values[:stop_size]
unless @finished
get_ready_to_draw
pick[1].call(options)
end
end
METH
end
end
end
# Rule choice is random, based on the assigned probabilities.
def determine_rule(rule_name)
rule = @rules[rule_name]
chance = rand * rule[:total]
pick = @rules[rule_name][:procs].select {|the_proc| the_proc[0].include?(chance) }
return pick.flatten
end
# At each step of the way, any of the options may change, slightly.
# Many of them have different strategies for being merged.
def merge_options(old_ops, new_ops)
return unless new_ops
# Do size first
old_ops[:size] *= new_ops[:size] if new_ops[:size]
new_ops.each do |key, value|
case key
when :size
when :x, :y, :z
old_ops[key] = value * old_ops[:size]
when :rz, :ry, :rx
old_ops[key] = value * (Math::PI / 180.0)
when :hue, :saturation, :brightness, :alpha
adjusted = old_ops[:color].dup
adjusted[HSB_ORDER[key]] *= value
old_ops[:color] = adjusted
when :width, :height
old_ops[key] *= value
when :color
old_ops[key] = value
else # Used a key that we don't know about or trying to set
merge_unknown_key(key, value, old_ops)
end
end
end
# Using an unknown key let's you set arbitrary values,
# to keep track of for your own ends.
def merge_unknown_key(key, value, old_ops)
key_s = key.to_s
if key_s.match(/^set/)
key_sym = key_s.sub('set_', '').to_sym
if key_s.match(/(brightness|hue|saturation|alpha)/)
adjusted = old_ops[:color].dup
adjusted[HSB_ORDER[key_sym]] = value
old_ops[:color] = adjusted
else
old_ops[key_sym] = value
end
end
end
# Doing a 'split' saves the context, and proceeds from there,
# allowing you to rewind to where you split from at any moment.
def split(options=nil, &block)
save_context
merge_options(@values, options) if options
yield
restore_context
end
# Saving the context means the values plus the coordinate matrix.
def save_context
@rewind_stack.push @values.dup
@matrix_stack << @graphics.get_matrix
end
# Restore the values and the coordinate matrix as the recursion unwinds.
def restore_context
@values = @rewind_stack.pop
@graphics.set_matrix @matrix_stack.pop
end
# Rewinding goes back one step.
def rewind
@finished = false
restore_context
save_context
end
# Render the is method that kicks it all off, initializing the options
# and calling the first rule.
def render(rule_name, starting_values={})
@values = {:x => 0, :y => 0, :z => 0,
:rz => 0, :ry => 0, :rx => 0,
:size => 1, :width => 1, :height => 1,
:start_x => 0, :start_y => 0, :start_z => 0,
:color => [0.5, 0.5, 0.5, 1],
:stop_size => 1.5}
@values.merge!(starting_values)
@finished = false
@app.reset_matrix
@app.no_stroke
@app.color_mode HSB, 1.0
@app.translate @values[:start_x], @values[:start_y], @values[:start_z]
self.send(rule_name, {})
end
# Before actually drawing the next step, we need to move to the appropriate
# location.
def get_ready_to_draw
@app.translate(@values[:x], @values[:y], @values[:z])
@app.rotate_x(@values[:rx])
@app.rotate_y(@values[:ry])
@app.rotate_z(@values[:rz])
end
# Compute the rendering parameters for drawing a shape.
def get_shape_values(some_options)
old_ops = @values.dup
merge_options(old_ops, some_options) if some_options
@app.fill *old_ops[:color]
return old_ops[:size], old_ops
end
# The primitive shapes are sphere and cube
def cube(some_options=nil)
size, options = *get_shape_values(some_options)
rotz = options[:rz]
roty = options[:ry]
rotx = options[:rx]
@app.rotate_x rotx unless rotx.nil?
@app.rotate_y roty unless roty.nil?
@app.rotate_z rotz unless rotz.nil?
@app.translate(options[:x] * size, options[:y] * size , options[:z] * size)
@app.box(size)
@app.rotate_z(-1 * rotz) unless rotz.nil? # unwind rotations in an ordered way
@app.rotate_y(-1 * roty) unless roty.nil?
@app.rotate_x(-1 * rotx) unless rotx.nil?
end
def sphere(some_options=nil)
size, options = *get_shape_values(some_options)
rotz = options[:rz]
roty = options[:ry]
rotx = options[:rx]
@app.rotate_x rotx unless rotx.nil?
@app.rotate_y roty unless roty.nil?
@app.rotate_z rotz unless rotz.nil?
@app.translate(options[:x] * size, options[:y] * size, options[:z] * size)
@app.sphere_detail 10
@app.sphere(size)
@app.rotate_z(-1 * rotz) unless rotz.nil? # unwind rotations in an ordered way
@app.rotate_y(-1 * roty) unless roty.nil?
@app.rotate_x(-1 * rotx) unless rotx.nil?
end
end
# JWheelListener class enables the use mouse wheel to say zoom sketch
class JWheelListener
include java.awt.event.MouseWheelListener
attr_reader :zoom, :max
# zoom is the initial value (for say z_start)
# limit range of zoom with max
def initialize(zoom, max)
@zoom = zoom
@max = max
end
def mouse_wheel_moved(e)
increment = e.get_wheel_rotation * 5 # increment/decrement
@zoom = ((zoom < max)||(increment < 0))? zoom + increment : zoom
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment