paulnicholson-acrylic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +0 -0
- data/Rakefile +28 -0
- data/TODO +19 -0
- data/VERSION.yml +4 -0
- data/ext/image_surface_extensions/extconf.rb +11 -0
- data/ext/image_surface_extensions/native_image_surface_extensions.c +344 -0
- data/lib/acrylic.rb +6 -0
- data/lib/border_tools.rb +121 -0
- data/lib/cairo_tools.rb +200 -0
- data/lib/color.rb +149 -0
- data/lib/core_ext.rb +16 -0
- data/lib/curve.rb +120 -0
- data/lib/image_generator.rb +79 -0
- data/lib/image_surface_extensions.rb +14 -0
- data/lib/pascal.rb +32 -0
- data/lib/shape.rb +14 -0
- data/lib/text_box.rb +96 -0
- data/test/bump_map_test.rb +60 -0
- data/test/cairo_tools_test.rb +36 -0
- data/test/color_test.rb +71 -0
- data/test/surface.png +0 -0
- data/test/test_helper.rb +16 -0
- metadata +85 -0
data/lib/cairo_tools.rb
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_support'
|
|
3
|
+
require 'cairo'
|
|
4
|
+
require 'gdk_pixbuf2'
|
|
5
|
+
require 'color'
|
|
6
|
+
require 'text_box'
|
|
7
|
+
require 'image_surface_extensions'
|
|
8
|
+
require 'inline'
|
|
9
|
+
|
|
10
|
+
module CairoTools
|
|
11
|
+
include Color
|
|
12
|
+
attr_reader :surface, :cr, :canvas_height, :canvas_width, :top_margin, :right_margin, :bottom_margin, :left_margin
|
|
13
|
+
attr_accessor :preview
|
|
14
|
+
|
|
15
|
+
def generate_image(path, options)
|
|
16
|
+
# dummy context is useful sometimes
|
|
17
|
+
@surface = Cairo::ImageSurface.new(1, 1)
|
|
18
|
+
@cr = Cairo::Context.new(surface)
|
|
19
|
+
draw(*options)
|
|
20
|
+
cr.target.write_to_png(path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def dimensions(width, height)
|
|
24
|
+
@canvas_width, @canvas_height = width, height
|
|
25
|
+
@surface = Cairo::ImageSurface.new(width, height)
|
|
26
|
+
@cr = Cairo::Context.new(surface)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def margin(*rect)
|
|
30
|
+
rect = rect + rect if rect.length == 1
|
|
31
|
+
rect = rect + rect if rect.length == 2
|
|
32
|
+
@top_margin, @right_margin, @bottom_margin, @left_margin = rect
|
|
33
|
+
cr.matrix = Cairo::Matrix.identity.translate(left_margin, top_margin)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def width
|
|
37
|
+
canvas_width - right_margin - left_margin
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def height
|
|
41
|
+
canvas_height - top_margin - bottom_margin
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def transform(matrix, &block)
|
|
45
|
+
old_matrix = cr.matrix
|
|
46
|
+
cr.matrix = matrix
|
|
47
|
+
yield
|
|
48
|
+
cr.matrix = old_matrix
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# http://www.cairographics.org/cookbook/roundedrectangles/
|
|
52
|
+
def rounded_rectangle(x, y, w, h, radius_x=5, radius_y=radius_x)
|
|
53
|
+
arc_to_bezier = 0.55228475
|
|
54
|
+
radius_x = w / 2 if radius_x > w - radius_x
|
|
55
|
+
radius_y = h / 2 if radius_y > h - radius_y
|
|
56
|
+
c1 = arc_to_bezier * radius_x
|
|
57
|
+
c2 = arc_to_bezier * radius_y
|
|
58
|
+
|
|
59
|
+
cr.new_path
|
|
60
|
+
cr.move_to(x + radius_x, y)
|
|
61
|
+
cr.rel_line_to(w - 2 * radius_x, 0.0)
|
|
62
|
+
cr.rel_curve_to(c1, 0.0, radius_x, c2, radius_x, radius_y)
|
|
63
|
+
cr.rel_line_to(0, h - 2 * radius_y)
|
|
64
|
+
cr.rel_curve_to(0.0, c2, c1 - radius_x, radius_y, -radius_x, radius_y)
|
|
65
|
+
cr.rel_line_to(-w + 2 * radius_x, 0)
|
|
66
|
+
cr.rel_curve_to(-c1, 0, -radius_x, -c2, -radius_x, -radius_y)
|
|
67
|
+
cr.rel_line_to(0, -h + 2 * radius_y)
|
|
68
|
+
cr.rel_curve_to(0.0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y)
|
|
69
|
+
cr.close_path
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def circular_text(x, y, radius, font_size, text)
|
|
73
|
+
radians = proc {|text| cr.set_font_size(font_size); cr.text_extents(text).x_advance/radius}
|
|
74
|
+
blank = (2*Math::PI - radians[text])/2
|
|
75
|
+
start = blank + Math::PI/2
|
|
76
|
+
partial = ''
|
|
77
|
+
text.split(//).each do |letter|
|
|
78
|
+
theta = start + radians[partial]
|
|
79
|
+
cr.move_to(x+radius*Math.cos(theta), y+radius*Math.sin(theta))
|
|
80
|
+
cr.set_font_matrix Cairo::Matrix.identity.rotate(theta + Math::PI/2).scale(font_size, font_size)
|
|
81
|
+
cr.show_text letter
|
|
82
|
+
theta += radians[letter]
|
|
83
|
+
partial << letter
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def create_text_box(x, y, width=nil, height=nil, valign=:top)
|
|
88
|
+
TextBox.new(self, x, y, width, height, valign)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def draw_text_box(x, y, width=nil, height=nil, valign=:top)
|
|
92
|
+
tb = create_text_box(x, y, width, height, valign)
|
|
93
|
+
yield tb
|
|
94
|
+
tb.draw
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def set_color(color)
|
|
98
|
+
cr.set_source_rgba(*color.to_rgb.to_a)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def linear_gradient(x0, y0, x1, y1, *colors)
|
|
102
|
+
gradient(Cairo::LinearPattern.new(x0, y0, x1, y1), *colors)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def radial_gradient(cx0, cy0, r0, cx1, cy1, r1, *colors)
|
|
106
|
+
gradient(Cairo::RadialPattern.new(cx0, cy0, r0, cx1, cy1, r1), *colors)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def gradient(gradient, *colors)
|
|
110
|
+
colors.each_with_index do |color, i|
|
|
111
|
+
array = color.respond_to?(:to_rgb) ? color.to_rgb.to_a : color.to_a
|
|
112
|
+
gradient.add_color_stop(i.to_f/(colors.length - 1), *array)
|
|
113
|
+
end
|
|
114
|
+
cr.set_source(gradient)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def shadow(radius=3, alpha=1)
|
|
118
|
+
shadow_surface = Cairo::ImageSurface.new(@canvas_width, @canvas_height)
|
|
119
|
+
shadow_cr = Cairo::Context.new(shadow_surface)
|
|
120
|
+
shadow_cr.set_source_rgba(0, 0, 0, alpha)
|
|
121
|
+
shadow_cr.mask(Cairo::SurfacePattern.new(surface))
|
|
122
|
+
shadow_surface.blur(radius)
|
|
123
|
+
shadow_cr.set_source(Cairo::SurfacePattern.new(surface))
|
|
124
|
+
shadow_cr.paint
|
|
125
|
+
@surface = shadow_surface
|
|
126
|
+
@cr = shadow_cr
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def get_pixel(x, y)
|
|
130
|
+
@surface.get_pixel(x, y)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def load_image(path, x=0, y=0)
|
|
134
|
+
image = Gdk::Pixbuf.new(File.join(File.dirname($0), path))
|
|
135
|
+
cr.set_source_pixbuf(image)
|
|
136
|
+
cr.source.matrix = Cairo::Matrix.identity.translate(x, y)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def clip!
|
|
140
|
+
i = self.class.new
|
|
141
|
+
w, h = @canvas_width, @canvas_height
|
|
142
|
+
pattern = Cairo::SurfacePattern.new(@surface)
|
|
143
|
+
clip = cr.copy_path
|
|
144
|
+
i.instance_eval do
|
|
145
|
+
dimensions w, h
|
|
146
|
+
cr.append_path clip
|
|
147
|
+
cr.clip
|
|
148
|
+
cr.set_source(pattern)
|
|
149
|
+
cr.paint
|
|
150
|
+
end
|
|
151
|
+
dimensions w, h
|
|
152
|
+
cr.set_source(Cairo::SurfacePattern.new(i.surface))
|
|
153
|
+
cr.paint
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def transparent!(alpha)
|
|
157
|
+
i = self.class.new
|
|
158
|
+
w, h = @canvas_width, @canvas_height
|
|
159
|
+
pattern = Cairo::SurfacePattern.new(@surface)
|
|
160
|
+
i.instance_eval do
|
|
161
|
+
dimensions w, h
|
|
162
|
+
cr.set_source(pattern)
|
|
163
|
+
cr.paint_with_alpha(alpha)
|
|
164
|
+
end
|
|
165
|
+
@surface = i.surface
|
|
166
|
+
@cr = i.cr
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def draw_image(image, x=0, y=0, a=1)
|
|
170
|
+
i = self.class.new
|
|
171
|
+
i.instance_eval do
|
|
172
|
+
draw(image)
|
|
173
|
+
end
|
|
174
|
+
cr.set_source(Cairo::SurfacePattern.new(i.surface))
|
|
175
|
+
cr.source.matrix = Cairo::Matrix.identity.translate(-x, -y)
|
|
176
|
+
cr.paint_with_alpha(a)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def clouds
|
|
180
|
+
Cairo::ImageSurface.new(129, 129)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class Cairo::Context
|
|
185
|
+
inline(:C) do |builder|
|
|
186
|
+
builder.include '<stdlib.h>'
|
|
187
|
+
builder.include '<cairo.h>'
|
|
188
|
+
builder.include '<rb_cairo.h>'
|
|
189
|
+
builder.include '<intern.h>'
|
|
190
|
+
builder.add_compile_flags '`/opt/local/bin/pkg-config --cflags cairo`'
|
|
191
|
+
builder.add_compile_flags '-I/opt/local/lib/ruby/site_ruby/1.8/i686-darwin9/'
|
|
192
|
+
builder.add_compile_flags '-I/opt/local/lib/ruby/gems/1.8/gems/cairo-1.6.2/src'
|
|
193
|
+
builder.c %{
|
|
194
|
+
void paint_with_alpha(double alpha) {
|
|
195
|
+
cairo_t *cr = RVAL2CRCONTEXT(self);
|
|
196
|
+
cairo_paint_with_alpha(cr, alpha);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
end
|
data/lib/color.rb
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_support'
|
|
3
|
+
require 'core_ext'
|
|
4
|
+
|
|
5
|
+
module Color
|
|
6
|
+
def rgb(*args)
|
|
7
|
+
RGB.new(*args)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def hsl(*args)
|
|
11
|
+
HSL.new(*args)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Base
|
|
15
|
+
def self.inherited(subclass)
|
|
16
|
+
components = subclass.name.sub('Color::', '').downcase.split(//).map(&:to_sym)
|
|
17
|
+
components << :a
|
|
18
|
+
components.each_with_index do |component, i|
|
|
19
|
+
subclass.class_eval <<-"end;"
|
|
20
|
+
def #{component}(value=nil)
|
|
21
|
+
if value
|
|
22
|
+
result = self.class.new(@components)
|
|
23
|
+
result.#{component} = value
|
|
24
|
+
result
|
|
25
|
+
else
|
|
26
|
+
@components[#{i}]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
def #{component}=(x)
|
|
30
|
+
@components[#{i}] = constrain(x)
|
|
31
|
+
end
|
|
32
|
+
end;
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(*components)
|
|
37
|
+
if components[0].is_a?(String)
|
|
38
|
+
@components = []
|
|
39
|
+
self.css = components[0]
|
|
40
|
+
else
|
|
41
|
+
@components = components.flatten.map(&method(:constrain))
|
|
42
|
+
end
|
|
43
|
+
@components << 1.0 if @components.size == 3
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def primary_components
|
|
47
|
+
@components[0..-2]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_a
|
|
51
|
+
@components
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def -(other)
|
|
55
|
+
self + other.to_a.map(&:-@)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def +(other)
|
|
59
|
+
other_array = other.to_a
|
|
60
|
+
array = component_indicies.map {|i| @components[i] + (other_array[i] || 0)}
|
|
61
|
+
self.class.new(*array)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ==(other)
|
|
65
|
+
other.class == self.class && component_indicies.all? {|i| epsilon(@components[i] - other.to_a[i])}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
"<#{self.class.name}: #{@components.join(", ")}>"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
protected
|
|
73
|
+
def component_indicies
|
|
74
|
+
(0..@components.size - 1).to_a
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def epsilon(x)
|
|
78
|
+
x.abs <= 0.01
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def constrain(x)
|
|
82
|
+
x > 1.0 ? 1.0 : x < 0.0 ? 0.0 : x
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class RGB < Base
|
|
87
|
+
def css=(string)
|
|
88
|
+
string.gsub!('#', '')
|
|
89
|
+
self.r, self.g, self.b = string.split(//).in_groups_of(string.length/3).map {|a| a.join.to_i(16).to_f / (16 ** (string.length/3) - 1).to_f}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_hsl
|
|
93
|
+
max = primary_components.max
|
|
94
|
+
min = primary_components.min
|
|
95
|
+
compute_hue = proc do |numerator, degrees|
|
|
96
|
+
degrees(60) * (numerator / (max - min)) + degrees(degrees)
|
|
97
|
+
end
|
|
98
|
+
l = (max + min)/2.0
|
|
99
|
+
s = (epsilon(l) || epsilon(max - min)) ? 0 :
|
|
100
|
+
l <= 0.5 ? (max - min) / (max + min) :
|
|
101
|
+
(max - min) / (2 - (max + min))
|
|
102
|
+
h = epsilon(max - min) ? 0 :
|
|
103
|
+
max == r ? compute_hue[g - b, g >= b ? 0 : 360] :
|
|
104
|
+
max == g ? compute_hue[b - r, 120] :
|
|
105
|
+
compute_hue[r - g, 240]
|
|
106
|
+
HSL.new(h, s, l, a)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_css
|
|
110
|
+
"%02X" * 3 % @components.map {|c| c * 255}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def to_rgb
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
protected
|
|
118
|
+
def degrees(x)
|
|
119
|
+
x.to_f/360.0
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class HSL < Base
|
|
124
|
+
def to_rgb
|
|
125
|
+
return RGB.new(l, l, l, a) if epsilon(s)
|
|
126
|
+
q = l < 0.5 ? l * (1.0 + s) : (l + s) - l * s
|
|
127
|
+
p = 2 * l - q
|
|
128
|
+
tc = [h + 1.0/3.0, h, h - 1.0/3.0]
|
|
129
|
+
tc.map! do |x|
|
|
130
|
+
x < 0 ? x + 1.0 :
|
|
131
|
+
x > 1.0 ? x - 1.0 : x
|
|
132
|
+
end
|
|
133
|
+
rgb = tc.map do |tc|
|
|
134
|
+
tc < 1.0/6.0 ? p + ((q - p) * 6.0 * tc) :
|
|
135
|
+
tc < 3.0/6.0 ? q :
|
|
136
|
+
tc < 4.0/6.0 ? p + ((q - p) * 6.0 * (2.0/3.0 - tc)) : p
|
|
137
|
+
end
|
|
138
|
+
RGB.new(rgb + [a])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def to_css
|
|
142
|
+
to_rgb.to_css
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def css=(string)
|
|
146
|
+
self.h, self.s, self.l = RGB.new(string).to_hsl.to_a
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/core_ext.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class Symbol
|
|
2
|
+
def self.pattern(name, pattern, &formation)
|
|
3
|
+
define_method(name) {to_s.match(pattern) ? self : formation.call(self).to_sym}
|
|
4
|
+
define_method(name.question) {to_s.match(pattern)}
|
|
5
|
+
end
|
|
6
|
+
pattern(:question, /\?$/) {|s| "#{s}?"}
|
|
7
|
+
pattern(:bang, /!$/) {|s| "#{s}!"}
|
|
8
|
+
pattern(:writer, /=$/) {|s| "#{s}="}
|
|
9
|
+
pattern(:iv, /^@[^@]/) {|s| "@#{s}"}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Numeric
|
|
13
|
+
def deg
|
|
14
|
+
self / 180.0 * Math::PI
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/curve.rb
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
class Curve < Array
|
|
2
|
+
attr_accessor :cr
|
|
3
|
+
|
|
4
|
+
def initialize(cr)
|
|
5
|
+
@cr = cr
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def draw
|
|
9
|
+
draw_control_points(self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def draw_control_points(control_points)
|
|
13
|
+
cr.move_to(*control_points.first.point)
|
|
14
|
+
cx1, cy1 = control_points.first.leading_point
|
|
15
|
+
control_points[1..-1].each do |point|
|
|
16
|
+
cx2, cy2 = point.trailing_point
|
|
17
|
+
cr.curve_to(cx1, cy1, cx2, cy2, point.x, point.y)
|
|
18
|
+
cx1, cy1 = point.leading_point
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def offset_line(distance, flip)
|
|
23
|
+
offset_line = self.map do |p|
|
|
24
|
+
p.move_away_from_line(distance)
|
|
25
|
+
end
|
|
26
|
+
offset_line.each_with_index do |p, i|
|
|
27
|
+
p.d1 *= (p.distance(offset_line[i-1])/self[i].distance(self[i-1])) * 1.1 unless i == 0
|
|
28
|
+
p.d2 *= (p.distance(offset_line[i+1])/self[i].distance(self[i+1])) * 1.1 unless i + 1 == length
|
|
29
|
+
end
|
|
30
|
+
flip ? offset_line.map(&:flip).reverse : offset_line
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def draw_outline
|
|
34
|
+
there = offset_line(-2, false)
|
|
35
|
+
there.first.d1 = 5
|
|
36
|
+
back = offset_line(2, true)
|
|
37
|
+
back.last.d2 = 2
|
|
38
|
+
draw_control_points(there + back + [there.first])
|
|
39
|
+
cr.stroke
|
|
40
|
+
# cr.fill
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def debug
|
|
44
|
+
each do |point|
|
|
45
|
+
cr.circle(point.x, point.y, 3)
|
|
46
|
+
cr.fill
|
|
47
|
+
cr.line_width = 1
|
|
48
|
+
# cr.move_to(*point.trailing_point.map {|n| n + 2})
|
|
49
|
+
cr.move_to(*point.trailing_point)
|
|
50
|
+
cr.line_to(*point.leading_point)
|
|
51
|
+
cr.stroke
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def point(x, y, theta, d1, d2=d1)
|
|
56
|
+
p [x, y, theta, d1, d2]
|
|
57
|
+
push(ControlPoint.new(x, y, theta, d1, d2))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def spiral(center_x, center_y, theta_one, delta_theta, phi, &algorithm)
|
|
61
|
+
control_points_per_rotation = 8
|
|
62
|
+
control_points = (control_points_per_rotation * delta_theta.abs / Math::PI / 2).to_i
|
|
63
|
+
radians_between_points = delta_theta / (control_points-1)
|
|
64
|
+
(0...control_points).each do |i|
|
|
65
|
+
theta = theta_one + radians_between_points * i
|
|
66
|
+
radius = algorithm.call(theta)
|
|
67
|
+
x, y = center_x + radius * Math.cos(theta), center_y + radius * Math.sin(theta)
|
|
68
|
+
tangent = theta + phi
|
|
69
|
+
influence = radius * 0.285 * (delta_theta / delta_theta.abs)
|
|
70
|
+
if i == control_points - 1
|
|
71
|
+
point(x, y, tangent, influence, influence * 2)
|
|
72
|
+
elsif i == 0
|
|
73
|
+
point(x, y, tangent, influence * 3, influence)
|
|
74
|
+
else
|
|
75
|
+
point(x, y, tangent, influence)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def logarithmic_spiral(center_x, center_y, theta_one, delta_theta, a, b)
|
|
81
|
+
spiral(center_x, center_y, theta_one, delta_theta, Math.atan(1/b)) do |theta|
|
|
82
|
+
(a*Math::E)**(b*theta)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class ControlPoint
|
|
88
|
+
attr_accessor :x, :y, :theta, :d1, :d2
|
|
89
|
+
def initialize(x, y, theta, d1, d2=d1)
|
|
90
|
+
@x, @y, @theta, @d1, @d2 = x, y, theta, d1, d2
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def point
|
|
94
|
+
[x, y]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def distance(point)
|
|
98
|
+
Math.sqrt((x - point.x) ** 2 + (y - point.y) ** 2)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def trailing_point
|
|
102
|
+
[x - d1 * Math.cos(theta), y - d1 * Math.sin(theta)]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def leading_point
|
|
106
|
+
[x + d2 * Math.cos(theta), y + d2 * Math.sin(theta)]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def flip
|
|
110
|
+
ControlPoint.new(x, y, theta + 180.deg, d2, d1)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def move_away_from_line(delta)
|
|
114
|
+
ControlPoint.new(x + delta * Math.cos(theta - 90.deg) * d1/d1.abs, y + delta * Math.sin(theta - 90.deg) * d1/d1.abs, theta, d1, d2)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def move_along_line(delta)
|
|
118
|
+
ControlPoint.new(x + delta * Math.cos(theta) * d1/d1.abs, y + delta * Math.sin(theta) * d1/d1.abs, theta, d1, d2)
|
|
119
|
+
end
|
|
120
|
+
end
|