Class: Processing::ContextFree

Inherits:
Object
  • Object
show all
Includes:
Proxy
Defined in:
lib/cf3.rb

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeContextFree

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

#appObject

Returns the value of attribute app.



7
8
9
# File 'lib/cf3.rb', line 7

def app
  @app
end

#heightObject

Returns the value of attribute height.



7
8
9
# File 'lib/cf3.rb', line 7

def height
  @height
end

#ruleObject

Returns the value of attribute rule.



7
8
9
# File 'lib/cf3.rb', line 7

def rule
  @rule
end

#widthObject

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(some_options = {})
  get_shape_values(some_options)
  @app.ellipse(0, 0, size, size)
end

#defaultsObject



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(some_options = {})
  options = get_shape_values(some_options)
  width = options[:w] || options[:size]
  height = options[:h] || options[:size]
  rot = some_options[:rotation]
  @app.rotate(rot) if rot
  @app.oval(options[:x] || 0, options[: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(some_options)
  old_ops = @values.dup
  merge_options(old_ops, some_options) unless some_options.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 merge_options(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_drawObject

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_contextObject

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

#rewindObject

Rewinding goes back one step.



146
147
148
149
# File 'lib/cf3.rb', line 146

def rewind
  restore_context
  save_context
end

#save_contextObject

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(options = nil)
  save_context
  merge_options(@values, options) if options
  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(some_options = {})
  options = get_shape_values(some_options)
  width = options[:w] || options[:size]
  height = options[:h] || options[:size]
  rot = options[: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(some_options = {})
  options = get_shape_values(some_options)
  rot = options[: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