beziercurve 0.8 → 0.8.4

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.
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