Class: Processing::ContextFree
Overview
A Context-Free library for JRubyArt, inspired by and based on context_free.rb by Jeremy Ashkenas. That in turn was inspired by contextfreeart.org
Constant Summary collapse
- AVAILABLE_OPTIONS =
%i[ x y w h rotation size flip color hue saturation brightness alpha ].freeze
- HSB_ORDER =
{ hue: 0, saturation: 1, brightness: 2, alpha: 3 }.freeze
- TRIANGLE_TOP =
-1 / Math.sqrt(3)
- TRIANGLE_BOTTOM =
Math.sqrt(3) / 6
- RADIANS =
(Math::PI / 180.0)
Instance Attribute Summary collapse
-
#app ⇒ Object
Returns the value of attribute app.
-
#height ⇒ Object
Returns the value of attribute height.
-
#rule ⇒ Object
Returns the value of attribute rule.
-
#width ⇒ Object
Returns the value of attribute width.
Class Method Summary collapse
-
.define(&block) ⇒ Object
Define a context-free system.
Instance Method Summary collapse
- #circle(some_options = {}) ⇒ Object
- #defaults ⇒ Object
-
#determine_rule(rule_name) ⇒ Object
Rule choice is random, based on the assigned probabilities.
- #ellipse(some_options = {}) ⇒ Object (also: #oval)
-
#get_shape_values(some_options) ⇒ Object
Compute the rendering parameters for drawing a shape.
-
#initialize ⇒ ContextFree
constructor
Initialize a bare ContextFree object with empty recursion stacks.
-
#merge_options(old_ops, new_ops) ⇒ Object
At each step of the way, any of the options may change, slightly.
-
#merge_unknown_key(key, value, old_ops) ⇒ Object
Using an unknown key let's you set arbitrary values, to keep track of for your own ends.
-
#prepare_to_draw ⇒ Object
Before actually drawing the next step, we need to move to the appropriate location.
-
#render(rule_name, starting_values = {}) ⇒ Object
Render the is method that kicks it all off, initializing the options and calling the first rule.
-
#restore_context ⇒ Object
Restore the values and the coordinate matrix as the recursion unwinds.
-
#rewind ⇒ Object
Rewinding goes back one step.
-
#save_context ⇒ Object
Saving the context means the values plus the coordinate matrix.
-
#shape(rule_name, prob = 1, &proc) ⇒ Object
Here's the first serious method: A Rule has an identifying name, a probability, and is associated with a block of code.
-
#split(options = nil) ⇒ Object
Doing a 'split' saves the context, and proceeds from there, allowing you to rewind to where you split from at any moment.
-
#square(some_options = {}) ⇒ Object
Square, circle, ellipse and triangles are the primitive shapes.
- #triangle(some_options = {}) ⇒ Object
Constructor Details
#initialize ⇒ ContextFree
Initialize a bare ContextFree object with empty recursion stacks.
25 26 27 28 29 30 31 32 33 |
# File 'lib/cf3.rb', line 25 def initialize @app = Processing.app @graphics = @app.g @width = @app.width @height = @app.height @rules = {} @rewind_stack = [] @matrix_stack = [] end |
Instance Attribute Details
#app ⇒ Object
Returns the value of attribute app.
7 8 9 |
# File 'lib/cf3.rb', line 7 def app @app end |
#height ⇒ Object
Returns the value of attribute height.
7 8 9 |
# File 'lib/cf3.rb', line 7 def height @height end |
#rule ⇒ Object
Returns the value of attribute rule.
7 8 9 |
# File 'lib/cf3.rb', line 7 def rule @rule end |
#width ⇒ Object
Returns the value of attribute width.
7 8 9 |
# File 'lib/cf3.rb', line 7 def width @width end |
Class Method Details
.define(&block) ⇒ Object
Define a context-free system. Use this method to create a ContextFree object. Call render() on it to make it draw.
18 19 20 21 22 |
# File 'lib/cf3.rb', line 18 def self.define(&block) cf = ContextFree.new cf.instance_eval(&block) cf end |
Instance Method Details
#circle(some_options = {}) ⇒ Object
204 205 206 207 |
# File 'lib/cf3.rb', line 204 def circle( = {}) get_shape_values() @app.ellipse(0, 0, size, size) end |
#defaults ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/cf3.rb', line 163 def defaults { x: 0, y: 0, rotation: 0, flip: false, size: 20, start_x: width / 2, start_y: height / 2, color: [180, 0.5, 0.5, 1], stop_size: 1.5 } end |
#determine_rule(rule_name) ⇒ Object
Rule choice is random, based on the assigned probabilities.
69 70 71 72 73 74 75 |
# File 'lib/cf3.rb', line 69 def determine_rule(rule_name) rule = @rules[rule_name] chance = rand(0.0..rule[:total]) @rules[rule_name][:procs].select do |the_proc| the_proc[0].include?(chance) end.flatten end |
#ellipse(some_options = {}) ⇒ Object Also known as: oval
217 218 219 220 221 222 223 224 225 |
# File 'lib/cf3.rb', line 217 def ellipse( = {}) = get_shape_values() width = [:w] || [:size] height = [:h] || [:size] rot = [:rotation] @app.rotate(rot) if rot @app.oval([:x] || 0, [:y] || 0, width, height) @app.rotate(-rot) if rot end |
#get_shape_values(some_options) ⇒ Object
Compute the rendering parameters for drawing a shape.
186 187 188 189 190 191 |
# File 'lib/cf3.rb', line 186 def get_shape_values() old_ops = @values.dup (old_ops, ) unless .empty? @app.fill(*old_ops[:color]) old_ops end |
#merge_options(old_ops, new_ops) ⇒ Object
At each step of the way, any of the options may change, slightly. Many of them have different strategies for being merged.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/cf3.rb', line 79 def (old_ops, new_ops) return unless new_ops # Do size first old_ops[:size] *= new_ops.fetch(:size, 1.0) new_ops.each do |key, value| case key # when :size when :x, :y old_ops[key] = value * old_ops.fetch(:size, 1.0) when :rotation old_ops[key] = value * RADIANS when :hue, :saturation, :brightness, :alpha adjusted = old_ops[:color].dup adjusted[HSB_ORDER[key]] *= value unless key == :hue adjusted[HSB_ORDER[key]] += value if key == :hue old_ops[:color] = adjusted when :flip old_ops[key] = !old_ops[key] when :w, :h old_ops[key] = value * old_ops.fetch(:size, 1.0) 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 |
#merge_unknown_key(key, value, old_ops) ⇒ Object
Using an unknown key let's you set arbitrary values, to keep track of for your own ends.
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/cf3.rb', line 109 def merge_unknown_key(key, value, old_ops) key_s = key.to_s return unless key_s =~ /^set/ key_sym = key_s.sub('set_', '').to_sym if key_s =~ /(brightness|hue|saturation)/ adjusted = old_ops[:color].dup adjusted[HSB_ORDER[key_sym]] = value old_ops[:color] = adjusted else old_ops[key_sym] = value end end |
#prepare_to_draw ⇒ Object
Before actually drawing the next step, we need to move to the appropriate location.
179 180 181 182 183 |
# File 'lib/cf3.rb', line 179 def prepare_to_draw @app.translate(@values.fetch(:x, 0), @values.fetch(:y, 0)) sign = (@values[:flip] ? -1 : 1) @app.rotate(sign * @values[:rotation]) end |
#render(rule_name, starting_values = {}) ⇒ Object
Render the is method that kicks it all off, initializing the options and calling the first rule.
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/cf3.rb', line 151 def render(rule_name, starting_values = {}) @values = defaults @values.merge!(starting_values) @app.reset_matrix @app.rect_mode CENTER @app.ellipse_mode CENTER @app.no_stroke @app.color_mode HSB, 360, 1.0, 1.0, 1.0 # match cfdg @app.translate @values.fetch(:start_x, 0), @values.fetch(:start_y, 0) send(rule_name, {}) end |
#restore_context ⇒ Object
Restore the values and the coordinate matrix as the recursion unwinds.
138 139 140 141 |
# File 'lib/cf3.rb', line 138 def restore_context @values = @rewind_stack.pop @graphics.set_matrix @matrix_stack.pop end |
#rewind ⇒ Object
Rewinding goes back one step.
144 145 146 147 |
# File 'lib/cf3.rb', line 144 def rewind restore_context save_context end |
#save_context ⇒ Object
Saving the context means the values plus the coordinate matrix.
132 133 134 135 |
# File 'lib/cf3.rb', line 132 def save_context @rewind_stack.push @values.dup @matrix_stack << @graphics.get_matrix end |
#shape(rule_name, prob = 1, &proc) ⇒ Object
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.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/cf3.rb', line 49 def shape(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 return if ContextFree.method_defined? rule_name self.instance_eval do eval <<-METH def #{rule_name}(options) merge_options(@values, options) pick = determine_rule(#{rule_name.inspect}) return if (@values[:size] - @values[:stop_size]) < 0 prepare_to_draw pick[1].call(options) end METH end end |
#split(options = nil) ⇒ Object
Doing a 'split' saves the context, and proceeds from there, allowing you to rewind to where you split from at any moment.
124 125 126 127 128 129 |
# File 'lib/cf3.rb', line 124 def split( = nil) save_context (@values, ) if yield restore_context end |
#square(some_options = {}) ⇒ Object
Square, circle, ellipse and triangles are the primitive shapes
194 195 196 197 198 199 200 201 202 |
# File 'lib/cf3.rb', line 194 def square( = {}) = get_shape_values() width = [:w] || [:size] height = [:h] || [:size] rot = [:rotation] @app.rotate(rot) if rot @app.rect(0, 0, width, height) @app.rotate(-rot) if rot end |
#triangle(some_options = {}) ⇒ Object
209 210 211 212 213 214 215 |
# File 'lib/cf3.rb', line 209 def triangle( = {}) = get_shape_values() rot = [:rotation] @app.rotate(rot) if rot @app.triangle(0, TRIANGLE_TOP * size, 0.5 * size, TRIANGLE_BOTTOM * size, -0.5 * size, TRIANGLE_BOTTOM * size) @app.rotate(-rot) if rot end |