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 +4 -4
- data/README.md +36 -0
- data/lib/beziercurve.rb +160 -77
- data/test/test_beziercurve.rb +6 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6acfd138758503d0649eb27ad80333f8c772cf2
|
4
|
+
data.tar.gz: 8c97586296b5fa7c2c11c5bf67979c81dfc6ece2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22d46c3b5d4b41421dda3f3170833a0685dbbd6ce30de4d8a7bd1cdea9d5d54073d9ea2a163b5f2b9e82d3ba83946346aac44e4602f012187149666de2d6371f
|
7
|
+
data.tar.gz: 5dcae4d88aab32ca8c8d12e64bde76f59eb9fc60942e4bebf31ab3e95b9c39c6393da2781bba4f334f0b3e04ee473b1607a33a7ef192afb11756a157f561f502
|
data/README.md
ADDED
@@ -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
|
+
|
data/lib/beziercurve.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
108
|
+
raise ArgumentError, 'Cannot create curve with less than 3 control points'
|
49
109
|
end
|
50
110
|
|
51
|
-
@controlpoints = controlpoints.map { |
|
52
|
-
if
|
53
|
-
ControlPoint.new(*
|
54
|
-
elsif
|
55
|
-
|
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
|
-
#
|
122
|
+
# Adds a new control point to the Bezier curve as endpoint.
|
123
|
+
#
|
124
|
+
# @param [ControlPoint, Array] point
|
63
125
|
def add(point)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
124
|
-
|
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
|
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
|
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:
|
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:
|
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
|
-
|
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.
|
43
|
+
rubygems_version: 2.4.8
|
42
44
|
signing_key:
|
43
45
|
specification_version: 4
|
44
46
|
summary: Create and analyze Bézier curves
|