Here's the library that allows these rule-systems to be used. It's probably the most complicated chunk
of code on the site, and may get a little hard to follow.
1
2
3
4
5
6
7
8 module Processing
9
10 class ContextFree
11 attr_accessor :rules, :app
12 STOP_SIZE = 1.5
13 AVAILABLE_OPTIONS = [:x, :y, :rotation, :size, :flip, :color, :hue, :saturation, :brightness]
14 HSB_ORDER = {:hue => 0, :saturation => 1, :brightness => 2}
15
16 def initialize()
17 @rules = {}
18 @finished = false
19 @rewind_stack = []
20 end
21
22
23 AVAILABLE_OPTIONS.each do |option_name|
24 define_method option_name do
25 @values[option_name]
26 end
27 end
28
29 def setup(some_hash)
30 @starting_values = some_hash
31 @starting_values[:stop_size] ||= STOP_SIZE
32 end
33
34
35 def create_method(name, &block)
36 self.class.send(:define_method, name, &block)
37 end
38
39
40
41
42
43
44
45 def rule(rule_name, prob=1, &proc)
46 @rules[rule_name] ||= {:procs => [], :total => 0}
47 total = @rules[rule_name][:total]
48 @rules[rule_name][:procs] << [(total...(prob+total)), proc]
49 @rules[rule_name][:total] += prob
50 unless ContextFree.method_defined? rule_name
51
52 create_method rule_name do |options|
53 merge_options(@values, options)
54 pick = determine_rule rule_name
55 @finished = true if @values[:size] < STOP_SIZE
56 unless @finished
57 get_ready_to_draw
58 pick[1].call(options)
59 end
60 end
61 end
62 end
63
64
65 def determine_rule(rule_name)
66 rule = @rules[rule_name]
67 chance = rand * rule[:total]
68 pick = @rules[rule_name][:procs].select {|the_proc| the_proc[0].include?(chance) }
69 return pick.flatten
70 end
71
72
73
74 def merge_options(old_ops, new_ops)
75 return unless new_ops
76
77 old_ops[:size] *= new_ops[:size] if new_ops[:size]
78 new_ops.each do |key, value|
79 case key
80 when :size
81 when :x, :y
82 old_ops[key] = value * old_ops[:size]
83 when :rotation
84 old_ops[key] = value * (Math::PI / 180.0) / 2
85 when :hue, :saturation, :brightness
86 adjusted = old_ops[:color].dup
87 adjusted[HSB_ORDER[key]] *= value
88 old_ops[:color] = adjusted
89 when :flip
90 old_ops[key] = !old_ops[key]
91 when :width, :height
92 old_ops[key] *= value
93 when :color
94 old_ops[key] = value
95 else
96 merge_unknown_key(key, value, old_ops)
97 end
98 end
99 end
100
101
102
103 def merge_unknown_key(key, value, old_ops)
104 key_s = key.to_s
105 if key_s.match(/^set/)
106 key_sym = key_s.sub('set_', '').to_sym
107 if key_s.match(/(brightness|hue|saturation)/)
108 adjusted = old_ops[:color].dup
109 adjusted[HSB_ORDER[key_sym]] = value
110 old_ops[:color] = adjusted
111 else
112 old_ops[key_sym] = value
113 end
114 end
115 end
116
117
118
119 def split(options=nil, &block)
120 save_context
121 merge_options(@values, options) if options
122 yield
123 restore_context
124 end
125
126 def save_context
127 @rewind_stack.push @values.dup
128 @app.push_matrix
129 end
130
131 def restore_context
132 @values = @rewind_stack.pop
133 @app.pop_matrix
134 end
135
136 def rewind
137 @finished = false
138 restore_context
139 save_context
140 end
141
142
143
144 def render(rule_name)
145 @values = {:x => 0, :y => 0,
146 :rotation => 0, :flip => false,
147 :size => 20, :width => 20, :height => 20,
148 :color => [0.5, 0.5, 0.5]}
149 @values.merge!(@starting_values)
150 @finished = false
151 @app = Processing::App.current
152 @app.reset_matrix
153 @app.no_stroke
154 @app.color_mode(App::HSB, 1.0)
155 @app.translate @values[:start_x], @values[:start_y]
156 begin
157 self.send(rule_name, {})
158 rescue SystemStackError
159 @finished = true
160 @app.reset_matrix
161 @app.fill 0
162 @app.rect 0, 0, @app.width, @app.height
163 end
164 end
165
166 def get_ready_to_draw
167 @app.translate(@values[:x], @values[:y])
168 sign = (@values[:flip] ? -1 : 1)
169 @app.rotate(sign * @values[:rotation])
170 end
171
172 def get_shape_values(some_options)
173 old_ops = @values.dup
174 merge_options(old_ops, some_options) if some_options
175 @app.fill *old_ops[:color]
176 return old_ops[:size], old_ops
177 end
178
179
180
181 def square(some_options=nil)
182 size, options = *get_shape_values(some_options)
183 @app.rect(-(size/2), -(size/2), size, size)
184 end
185
186 def circle(some_options=nil)
187 size, options = *get_shape_values(some_options)
188 @app.oval(-(size/2), -(size/2), size, size)
189 end
190
191 def oval(some_options=nil)
192 rot = some_options[:rotation]
193 @app.rotate(rot) if rot
194 size, options = *get_shape_values(some_options)
195 width = options[:width] || options[:size]
196 height = options[:height] || options[:size]
197 @app.oval(options[:x], options[:y], width, height)
198 @app.rotate(-rot) if rot
199 end
200 alias_method :ellipse, :oval
201
202 end
203
204
205
206 class App
207
208 def context_free(&block)
209 free = ContextFree.new
210 free.instance_eval &block
211 return free
212 end
213
214 end
215
216 end
217