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