beziercurve 0.8 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7550491650757d1ef4313a11191cb788974cae7
4
- data.tar.gz: 66f165775867bf78cec4db74658de8b7a9bce86c
3
+ metadata.gz: a6acfd138758503d0649eb27ad80333f8c772cf2
4
+ data.tar.gz: 8c97586296b5fa7c2c11c5bf67979c81dfc6ece2
5
5
  SHA512:
6
- metadata.gz: a03fad9c836c26f020f3f5b5e859f9f5268b411cc5583d7022e7cf89f975cfedd16611f3cf20736451fa3d23f2ccb1a308ccf1367cdf3d4d3a50631545bc1464
7
- data.tar.gz: a84249c4189caac7f6f80d93dceebab5e29e1a0068e3d6e6ddd370e7e3d491361085703ea70fcafb059ffdd1c8120b3b7aa1520bb33fdd7afc2e75b3323181c4
6
+ metadata.gz: 22d46c3b5d4b41421dda3f3170833a0685dbbd6ce30de4d8a7bd1cdea9d5d54073d9ea2a163b5f2b9e82d3ba83946346aac44e4602f012187149666de2d6371f
7
+ data.tar.gz: 5dcae4d88aab32ca8c8d12e64bde76f59eb9fc60942e4bebf31ab3e95b9c39c6393da2781bba4f334f0b3e04ee473b1607a33a7ef192afb11756a157f561f502
@@ -0,0 +1,36 @@
1
+ **Bézier Ruby Gem**
2
+ ==
3
+
4
+ A Ruby gem for creating and analyzing Bézier curves. Allows you to create a Bézier curve by its control points, get specific point on that curve or iterate the curve points by an Enumerator. Inspired by [A Primer on Bézier Curves](http://pomax.github.io/bezierinfo/).
5
+
6
+ History
7
+ -------
8
+
9
+ 0.7 - implemented the De Casteljau algorithm
10
+
11
+ 0.8 - defined minimum Ruby version (2.0.0)
12
+
13
+
14
+ Installation
15
+ ------------
16
+
17
+ > gem install beziercurve
18
+
19
+ Sample
20
+ ------------------
21
+
22
+ > require 'beziercurve'
23
+ > bez = Bezier::Curve.new(Bezier::ControlPoint.new(40, 250), Bezier::ControlPoint.new(50, 150), Bezier::ControlPoint.new(90, 220))
24
+ > puts bez.point_on_curve(0.2).x;
25
+ 45.2
26
+ > puts bez.point_on_curve(0.2).y;
27
+ 216.8
28
+
29
+ or a nicer way:
30
+
31
+ > bez = Bezier::Curve.new([40, 250], [50, 150], [90, 220])
32
+ > puts bez.point_on_curve(0.2).x;
33
+ 45.2
34
+ > puts bez.point_on_curve(0.2).y;
35
+ 216.8
36
+
@@ -1,30 +1,49 @@
1
1
  # Curve == series of ControlPoints
2
- module Bezier
3
2
 
3
+ module Bezier
4
4
  class ControlPoint
5
5
  attr_accessor :x, :y
6
+
7
+ # @param x [Numeric]
8
+ # @param y [Numeric]
9
+ #
10
+ # Creates a new control point for the Bézier curve
6
11
  def initialize(x,y)
7
12
  @x = x
8
13
  @y = y
9
14
  end
15
+
10
16
  def - (b)
11
- self.class.new(self.x - b.x, self.y - b.y)
12
- end
13
- def + (b)
14
- self.class.new(self.x + b.x, self.y + b.y)
17
+ self.class.new(self.x - b.x, self.y - b.y)
18
+ end
19
+ def + (b)
20
+ self.class.new(self.x + b.x, self.y + b.y)
21
+ end
22
+
23
+ # @param point [ControlPoint]
24
+ #
25
+ # @return [ControlPoint] A ControlPoint in a new position
26
+ # Moves a controlpoint with relative distance
27
+ def movepoint (point)
28
+ self.class.new(self.x + point.x, self.y + point.y)
15
29
  end
30
+
16
31
  def inspect
17
32
  return @x, @y
18
33
  end
19
- # @return [CurvePoint] Returns a ControlPoint, converted to CurvePoint (only naming differences).
34
+
35
+ # @return [CurvePoint] Returns a ControlPoint, converted to CurvePoint (this is only a naming difference).
20
36
  def to_curvepoint
21
37
  CurvePoint.new(self.x, self.y)
22
38
  end
23
- def to_a
39
+
40
+ # @return [Array]
41
+ # Returns the control point as an array => [x, y]. The Array is fit to be as argument for ControlPoint.new
42
+ def to_a
24
43
  [self.x, self.y]
25
44
  end
26
45
  end
27
-
46
+
28
47
  class CurvePoint < ControlPoint
29
48
  # @return [ControlPoint] point coordinates on the Bézier curve.
30
49
  def to_controlpoint
@@ -33,105 +52,163 @@ module Bezier
33
52
  end
34
53
 
35
54
  class Curve
36
- # returns hull control points
55
+ # defaults
56
+ DeCasteljau = :decasteljau
57
+ Bernstein = :bernstein
58
+
59
+ # this should have been instance variable in the first place, correcting '@@...'
60
+ @calculation_method = Bernstein
61
+
62
+ @@fact_memoize = Hash.new
63
+ @@binomial_memoize = Hash.new
64
+ @@pascaltriangle_memoize = Hash.new
65
+
66
+ # Ye' olde factorial function
67
+ #
68
+ # @param n [Fixnum]
69
+ # @example
70
+ # > fact(5)
71
+ def self.fact(n)
72
+ @@fact_memoize[n] ||= (1..n).reduce(:*)
73
+ end
74
+
75
+ # @param n [Fixnum]
76
+ # @param k [Fixnum]
77
+ # standard 'n choose k'
78
+ def self.binomial(n,k)
79
+ return 1 if n-k <= 0
80
+ return 1 if k <= 0
81
+ @@binomial_memoize[[n,k]] ||= fact(n) / ( fact(k) * fact( n - k ) )
82
+ end
83
+
84
+ # Returns the specified line from the Pascal triangle as an Array
85
+ # @return [Array] A line from the Pascal triangle
86
+ # @example
87
+ # > pascaltriangle(6)
88
+ def self.pascaltriangle(nth_line) # Classic Pascal triangle
89
+ @@pascaltriangle_memoize[nth_line] ||= (0..nth_line).map { |e| binomial(nth_line, e) }
90
+ end
91
+
92
+ # Returns the Bezier curve control points
93
+ #
94
+ # @return [Array<ControlPoints>]
95
+
37
96
  attr_accessor :controlpoints
38
97
 
39
- # @param controlpoints [Array<ControlPoints, Array(Fixnum, Fixnum)>] list of ControlPoints defining the hull for the Bézier curve. A point can be of class ControlPoint or an Array containig 2 Fixnums, which will be converted to ControlPoint.
40
- # @return [Curve] a Bézier curve object
98
+ # @param controlpoints [Array<ControlPoints>, Array<(Fixnum, Fixnum)>] list of ControlPoints defining the hull for the Bézier curve. A point can be of class ControlPoint or an Array containig 2 Numerics, which will be converted to ControlPoint.
99
+ # @return [Curve] Creates a new Bézier curve object. The minimum number of control points is currently 3.
41
100
  # @example
42
101
  # initialize(p1, p2, p3)
43
102
  # initialize(p1, [20, 30], p3)
44
103
  def initialize(*controlpoints)
104
+
45
105
  # need at least 3 control points
46
106
  # this constraint has to be lifted, to allow adding Curves together like a 1 point curve to a 3 point curve
47
107
  if controlpoints.size < 3
48
- raise ArgumentError, 'Cannot create Bézier curve with less than 3 control points'
108
+ raise ArgumentError, 'Cannot create curve with less than 3 control points'
49
109
  end
50
110
 
51
- @controlpoints = controlpoints.map { |e|
52
- if e.class == Array
53
- ControlPoint.new(*e[0..1]) # make sure ControlPoint.new gets no more than 2 arguments. 'e' should contain at least 2 elements here
54
- elsif e.class == ControlPoint
55
- e
111
+ @controlpoints = controlpoints.map { |point|
112
+ if point.class == Array
113
+ ControlPoint.new(*point[0..1]) # ControlPoint.new gets no more than 2 arguments, excess values are ignored
114
+ elsif point.class == ControlPoint
115
+ point
56
116
  else
57
117
  raise 'Control points should be type of ControlPoint or Array'
58
118
  end
59
119
  }
60
120
  end
61
121
 
62
- # @param point [ControlPoint] addition
122
+ # Adds a new control point to the Bezier curve as endpoint.
123
+ #
124
+ # @param [ControlPoint, Array] point
63
125
  def add(point)
64
- if point.class == ControlPoint
65
- @controlpoints << point
66
- else
67
- raise TypeError, 'Point should be type of ControlPoint'
68
- end
69
- end
70
-
71
- # @param t [CurvePoint]
72
- def point_on_curve(t)
73
-
74
- def point_on_hull(point1, point2, t) # making this method local
75
- if (point1.class != ControlPoint) or (point2.class != ControlPoint)
76
- raise TypeError, 'Both points should be type of ControlPoint'
77
- end
78
- new_x = (point1.x - point2.x) * t
79
- new_y = (point1.y - point2.y) * t
80
- return ControlPoint.new(new_x, new_y)
81
- end
82
-
83
- # imperatively ugly, but works, refactor later. point_on_curve and point_on_hull should be one method
84
- ary = @controlpoints
85
- return ary if ary.length <= 1 # zero or one element as argument, return unmodified
86
-
87
- while ary.length > 1
88
- temp = []
89
- 0.upto(ary.length-2) do |index|
90
- memoize1 = point_on_hull(ary[index], ary[index+1], t)
91
- temp << ary[index+0] - memoize1
92
- end
93
- ary = temp
94
- end
95
- return temp[0].to_curvepoint
126
+ @controlpoints << case point
127
+ when ControlPoint
128
+ point
129
+ when Array
130
+ ControlPoint.new(*point[0..1])
131
+ else
132
+ raise(TypeError, 'Point should be type of ControlPoint')
133
+ end
134
+
135
+ # if point.class == ControlPoint
136
+ # @controlpoints << point
137
+ # elsif point.class == Array
138
+ # @controlpoints << ControlPoint.new(*point[0..1])
139
+ # else
140
+ # raise TypeError, 'Point should be type of ControlPoint'
141
+ # end
96
142
  end
97
143
 
98
- def pascaltriangle(nth_line)
99
- # @param n [Fixnum] Ye' olde factorial function
100
- # @todo this is slow, should be rewritten
101
- # @return [Array] an array of the specified line from the Pascal triangle
102
- # @example
103
- # > fact(5)
104
- # >
105
- def fact(n)
106
- (1..n).reduce(:*)
107
- end
144
+ # @param [CurvePoint] t
145
+ def point_on_curve(t) # calculates the 'x,y' coordinates of a point on the curve, at the ratio 't' (0 <= t <= 1)
146
+ case
147
+ when @@calculation_method == DeCasteljau
148
+ poc_with_decasteljau(t)
149
+ when @@calculation_method == Bernstein
150
+ poc_with_bernstein(t)
151
+ else
152
+ poc_with_bernstein(t) # default
153
+ end
154
+ end
155
+ alias_method :poc, :point_on_curve
156
+
157
+ def poc_with_bernstein(t)
158
+ n = @controlpoints.size-1
159
+ sum = [0,0]
160
+ for k in 0..n
161
+ sum[0] += @controlpoints[k].x * self.class.binomial(n,k) * (1-t)**(n-k) * t**k
162
+ sum[1] += @controlpoints[k].y * self.class.binomial(n,k) * (1-t)**(n-k) * t**k
163
+ end
164
+ return ControlPoint.new(*sum)
165
+ #poc_with_decasteljau(t)
166
+ end
167
+
168
+ def point_on_hull(point1, point2, t) # just realized this was nested (geez), Jörg.W.Mittag would have cried. So it is moved out from poc_with_decasteljau()
169
+ if (point1.class != ControlPoint) or (point2.class != ControlPoint)
170
+ raise TypeError, 'Both points should be type of ControlPoint'
171
+ end
172
+ new_x = (1-t) * point1.x + t * point2.x
173
+ new_y = (1-t) * point1.y + t * point2.y
174
+ return ControlPoint.new(new_x, new_y)
175
+ end
176
+
177
+ def poc_with_decasteljau(t)
178
+ # imperatively ugly, but works, refactor later. point_on_curve and point_on_hull should be one method
179
+ ary = @controlpoints
180
+ return ary if ary.length <= 1 # zero or one element as argument, return unmodified
181
+
182
+ while ary.length > 1
183
+ temp = []
184
+ 0.upto(ary.length-2) do |index|
185
+ memoize1 = point_on_hull(ary[index], ary[index+1], t)
186
+ temp << ary[index+0] - memoize1
187
+ end
188
+ ary = temp
189
+ end
190
+ temp[0].to_curvepoint
191
+ end
108
192
 
109
- # @param n [Fixnum]
110
- # @param k [Fixnum]
111
- def binomial(n,k)
112
- return 1 if n-k <= 0
113
- return 1 if k <= 0
114
- fact(n) / ( fact(k) * fact( n - k ) )
193
+ def point_on_curve_binom(t)
194
+ coeffs = self.class.pascaltriangle(self.order)
195
+ coeffs.reduce do |memo, obj|
196
+ memo += t**obj * (1-t)** (n - obj)
115
197
  end
116
- (0..nth_line).map { |e| binomial(nth_line, e) }
117
198
  end
118
199
 
119
- def point_on_curve_binom(t)
120
-
121
- # locally scoped, we don't need it outside of point_on_curve_binom
122
200
 
123
- coeffs = pascaltriangle(self.order)
124
- coeffs.reduce { |memo, obj|
125
- memo += t**obj * (1-t)** (n - obj)
126
- }
201
+ def gnuplot_hull # was recently 'display_points'. just a helper, for quickly put ControlPoints to STDOUT in a gnuplottable format
202
+ @controlpoints.map{|point| [point.x, point.y] }
127
203
  end
128
204
 
129
- def display_points # just a helper, for quickly put ControlPoints to STDOUT in a gnuplottable format
130
- @controlpoints.map{|point| puts "#{point.x} #{point.y}"}
205
+ def gnuplot_points(precision)
131
206
  end
132
207
 
208
+ # returns a new Enumerator that iterates over the Bezier curve from [start_t] to 1 by [delta_t] steps.
133
209
  def enumerated(start_t, delta_t)
134
210
  Enumerator.new do |yielder|
211
+ #TODO only do the conversion if start_t is not Float, Fractional or Bigfloat
135
212
  point_position = start_t.to_f
136
213
  number = point_on_curve(point_position)
137
214
  loop do
@@ -143,9 +220,15 @@ module Bezier
143
220
  end
144
221
  end
145
222
 
223
+ # returns the order of the Bezier curve, aka the number of control points.
146
224
  def order
147
225
  @controlpoints.size
148
226
  end
149
227
  end
150
-
151
228
  end
229
+
230
+ # Bernstein calculation is finished, that is the default
231
+ # DeCasteljau still has bugs
232
+ # renamed display_points to gnuplot_hull (this displays Hull coordinates)
233
+ # created gnuplot_points (this displays Curve points, uses the enumerated method to iterate over the curve)
234
+ # TODO: make sure the original CurvePoint argument type won't get converted. If someone created a new Bezier instance with Bigfloat as CurvePoints, it should stay Bigfloat during the entire calculation
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+ require 'beziercurve'
3
+
4
+ class TestBezierCurve < Test::Unit::TestCase
5
+
6
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beziercurve
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.8'
4
+ version: 0.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Földes László
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-18 00:00:00.000000000 Z
11
+ date: 2016-04-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Creates a Bézier curve by its control points. Implemented with de Casteljau
14
14
  method.
@@ -17,8 +17,10 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - README.md
20
21
  - lib/beziercurve.rb
21
- homepage: http://devrandom.postr.hu
22
+ - test/test_beziercurve.rb
23
+ homepage: https://github.com/karatedog/beziercurve
22
24
  licenses:
23
25
  - MIT
24
26
  metadata: {}
@@ -38,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
38
40
  version: '0'
39
41
  requirements: []
40
42
  rubyforge_project:
41
- rubygems_version: 2.2.2
43
+ rubygems_version: 2.4.8
42
44
  signing_key:
43
45
  specification_version: 4
44
46
  summary: Create and analyze Bézier curves