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
206 207 208 209 |
# File 'lib/cf3.rb', line 206 def circle( = {}) get_shape_values() @app.ellipse(0, 0, size, size) end |
#defaults ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/cf3.rb', line 165 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.
70 71 72 73 74 75 76 |
# File 'lib/cf3.rb', line 70 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
219 220 221 222 223 224 225 226 227 |
# File 'lib/cf3.rb', line 219 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.
188 189 190 191 192 193 |
# File 'lib/cf3.rb', line 188 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.
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 106 107 |
# File 'lib/cf3.rb', line 80 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.
111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/cf3.rb', line 111 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.
181 182 183 184 185 |
# File 'lib/cf3.rb', line 181 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.
153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/cf3.rb', line 153 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.
140 141 142 143 |
# File 'lib/cf3.rb', line 140 def restore_context @values = @rewind_stack.pop @graphics.set_matrix @matrix_stack.pop end |
#rewind ⇒ Object
Rewinding goes back one step.
146 147 148 149 |
# File 'lib/cf3.rb', line 146 def rewind restore_context save_context end |
#save_context ⇒ Object
Saving the context means the values plus the coordinate matrix.
134 135 136 137 |
# File 'lib/cf3.rb', line 134 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 67 |
# 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 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.
126 127 128 129 130 131 |
# File 'lib/cf3.rb', line 126 def split( = nil) save_context (@values, ) if yield restore_context end |
#square(some_options = {}) ⇒ Object
Square, circle, ellipse and triangles are the primitive shapes
196 197 198 199 200 201 202 203 204 |
# File 'lib/cf3.rb', line 196 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
211 212 213 214 215 216 217 |
# File 'lib/cf3.rb', line 211 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 |