xrvg 0.0.6 → 0.0.7

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.
@@ -1,210 +0,0 @@
1
- # BezierBuilder file
2
- # See:
3
- # - BezierBuilder
4
- # - SimilarMotifIterator
5
- # - AttributeMotifIterator
6
- # - FitBezierBuilder
7
-
8
- require 'bezier'
9
- require 'fitting'
10
-
11
- module XRVG
12
-
13
- # = BezierBuilder class
14
- # == Content
15
- # Abstract class to define prototype of a bezier factory
16
- #
17
- # Provides the notation
18
- # bezierresult = BezierBuilder[ :parameter1, arg1, :parameter2, arg2 ]
19
- # with bezierresult the computed Bezier object from the BezierBuilder algorithm
20
- class BezierBuilder
21
- include Attributable
22
-
23
- # Hook for subclassing : must return a list of raw pieces, to be provided to Bezier.multi
24
- def compute()
25
- raise NotImplementedError.new("#{self.class.name}#compute is an abstract method.")
26
- end
27
-
28
- # syntax sugar method to replace BezierBuilder.build notation with the simpler BezierBuilder[] one
29
- #
30
- # Note that BezierBuilder[] does not return a BezierBuilder object, but a Bezier one
31
- def BezierBuilder.[](*args)
32
- return self.build( *args )
33
- end
34
-
35
- # create the BezierBuilder, and build a Bezier.multi by calling BezierBuilder.compute
36
- def BezierBuilder.build( *args )
37
- builder = self.new( *args )
38
- return Bezier.multi( builder.compute )
39
- end
40
-
41
- # multipiece bezier operator to smooth tangents of piece junctions
42
- #
43
- # Algo:
44
- # - for each pair of pieces, take last and first vector
45
- # - compute the mean of these vectors
46
- # - for each vector, linearly interpolate given :factor between initial vector and mean
47
- # As a consequence, default :factor value means that by default, we take vector means, and this operator
48
- # do nothing if you set :factor to 0.0
49
- def BezierBuilder.lissage( bezier, factor=1.0 )
50
- result = [[:vector] + (bezier.pieces[0].pointlist(:vector))[0..1]]
51
- bezier.pieces.pairs do |piece1, piece2|
52
- p = piece1.lastpoint;# equal to piece2.firstpoint
53
- v1, v2 = [-piece1.lastvector, piece2.firstvector]
54
- mean = (v1..v2).mean
55
- newv1 = (v1..mean).sample( factor )
56
- newv2 = (v2..mean).sample( factor )
57
- result[-1] += [p,-newv1]
58
- result << [:vector, p, newv2]
59
- end
60
- result[-1] += (bezier.pieces[-1].pointlist(:vector))[-2..-1]
61
- return Bezier.multi( result )
62
- end
63
- end
64
-
65
-
66
- #-------------------------------------------------------------------------------
67
- # We can now build on these atomic motifs some motif iterators
68
- #-------------------------------------------------------------------------------
69
-
70
- # = Similar Motif Iterator
71
- # == Content
72
- # Take a bezier curve as :motif, and a "curvesampler" as a support with sampling description, and iterate motif on each pair computed by sampling
73
- # == Attributes
74
- # attribute :curvesampler, nil, Samplable
75
- # attribute :motif, nil, Bezier
76
- # attribute :nmotifs, 10
77
- # == Example
78
- # motif = PicBezier[ :support, [V2D::O, V2D::X], :height, -1.0 ]
79
- # curvesampler = bezier.geo( 3.0 )
80
- # bezier = SimilarMotifIterator[ :curvesampler, curvesampler, :motif, motif, :nmotifs, 10 ]
81
- # == TODO
82
- # Represents the basic operator to compute bezier fractal curves !!
83
- class SimilarMotifIterator < BezierBuilder
84
- attribute :curvesampler, nil, Samplable
85
- attribute :motif, nil, Bezier
86
- attribute :nmotifs, 10
87
-
88
- # BezierBuilder overloading
89
- #
90
- # Algo
91
- # - sample @nmotif+1 times @curvesampler to get @nmotifs point pairs
92
- # - foreach pair, compute new bezier by calling Bezier.similar on @motif
93
- def compute
94
- result = []
95
- self.curvesampler.samples( self.nmotifs + 1).pairs do |p1,p2|
96
- Trace("SimilarMotifIterator::compute p1 #{p1.inspect} p2 #{p2.inspect}")
97
- newbezier = self.motif.similar( (p1..p2) )
98
- result += newbezier.data
99
- end
100
- return result
101
- end
102
-
103
- end
104
-
105
- # = Attribute Motif Iterator
106
- # == Content
107
- # More advanced motif iterator than SimilarMotifIterator, and also more expensive, AttributeMotifIterator samples
108
- # a curvesampler and foreach pair build a new bezier motif, with varying attributes
109
- # == Attributes
110
- # attribute :curvesampler, nil, Splittable
111
- # attribute :motifclass
112
- # attribute :nmotifs, 10
113
- # attribute :attributes, [], Array
114
- # attribute :closed, false
115
- # :motifclass is a BezierBuilder class, as ArcBezier, PicBezier, or even AttributeMotifIterator...
116
- #
117
- # :attributes is of the form [attribute1, specification1, :attribute2, specification, ...] with
118
- # - attribute1, attribute2 attributes of the :motifclass
119
- # - specification can be :
120
- # - single value
121
- # - sampler
122
- #
123
- # :closed attribute state if each subbezier computed for each point pair must be closed with the corresponding subbezier of the :curvesampler
124
- # == Example
125
- # motif = PicBezier[ :support, [V2D::O, V2D::X], :height, -1.0 ]
126
- # curvesampler = bezier.geo( 3.0 )
127
- # result = AttributeMotifIterator[ :curvesampler, curvesampler, :motifclass, ArcBezier, :attributes, [:height, (-2.0..0.0).random], :nmotifs, 30, :closed, true ]
128
- # == WARNING
129
- # Only works with BezierMotif defined by two points
130
- class AttributeMotifIterator < BezierBuilder
131
- attribute :curvesampler, nil, Splittable
132
- attribute :motifclass
133
- attribute :nmotifs, 10
134
- attribute :attributes, [], Array
135
- attribute :closed, false
136
-
137
- # BezierBuilder overloading
138
- #
139
- # See AttributeMotifIterator class description for details
140
- def compute
141
- result = []
142
- attrvalues = []
143
- self.attributes.foreach do |name, spec|
144
- attrvalues += [name, Samplable.build( spec ).samples( self.nmotifs )]
145
- end
146
- self.curvesampler.splits( self.nmotifs ).each_with_index do |subbezier,index|
147
- pair = [subbezier.firstpoint, subbezier.lastpoint]
148
- p1, p2 = pair
149
- args = [:support, pair]
150
- attrvalues.foreach do |name, values|
151
- args += [name, values[index]]
152
- end
153
- newbezier = self.motifclass[ *args ]
154
- if self.closed
155
- newbezier = newbezier + subbezier.reverse
156
- end
157
- result += newbezier.data
158
- end
159
- return result
160
- end
161
-
162
- end
163
-
164
- # = FitBezierBuilder class
165
- # == Content
166
- # Build a bezier from a point list defined by :points by computing adaptative multipiece bezier fitting.
167
- #
168
- # While this class is by itself quite usefull, it can also be subclassed by overloading "points" method to
169
- # compute all sorts of curve (as Offset for example)
170
- # == Attributes
171
- # attribute :points, [], Array; # to be able to subclass FitBezierBuilder to compute points by diverse means
172
- # attribute :maxerror, 0.001
173
- # :maxerror attribute represents the bezier curve matching error (as explained in Fitting)
174
- class FitBezierBuilder < BezierBuilder
175
- attribute :points, [], Array; # to be able to subclass FitBezierBuilder to compute points by diverse means
176
- attribute :maxerror, 0.001
177
-
178
- def FitBezierBuilder.build( *args )
179
- builder = self.new( *args )
180
- return Fitting.adaptative_compute( builder.points, builder.maxerror )[0]
181
- end
182
-
183
- end
184
-
185
- # Extend Circle class for subcurve definition
186
- class Circle
187
- # return approximating bezier curve
188
- def bezier
189
- # following computation is too expensive
190
- # return FitBezierBuilder[ :points, self.samples( 20 ) ]
191
-
192
- # based on http://www.whizkidtech.redprince.net/bezier/circle/
193
- kappa = 0.5522847498
194
-
195
- beziers = []
196
- self.frames( (0.0..1.0).samples(5) ).pairs do |f1,f2|
197
- beziers << Bezier.vector( f1.center, f1.vector.norm * kappa * self.radius, f2.center, f2.vector.norm * kappa * (-self.radius) )
198
- end
199
-
200
- result = beziers[0]
201
- beziers[1..-1].each do |b|
202
- result = result + b
203
- end
204
-
205
- return result
206
- end
207
- end
208
-
209
-
210
- end # end XRVG
data/lib/beziermotifs.rb DELETED
@@ -1,121 +0,0 @@
1
- # See
2
- # - BezierMotif
3
- # - PicBezier
4
- # - ArcBezier
5
- # - LinearBezier
6
-
7
- require 'bezierbuilders'
8
-
9
- module XRVG
10
-
11
- # = BezierMotif class
12
- # == Content
13
- # Abstract class to define prototype of a motif bezier factory
14
- # Motif is localized by the :support attribute
15
- # == Attributes
16
- # attribute :support
17
- class BezierMotif < BezierBuilder
18
- include Attributable
19
- attribute :support
20
-
21
- def BezierMotif.build( *args )
22
- builder = self.new( *args )
23
- result = []
24
- builder.support.pairs do |p1,p2|
25
- builder.support = [p1,p2]
26
- result << Bezier.multi( builder.compute )
27
- end
28
- return result.sum
29
- end
30
-
31
- end
32
-
33
-
34
-
35
- # = PicBezier class
36
- # == Content
37
- # Build a curved "pic" bezier whose base is specified by two points, and whose shape is controlled by two attributes, :height and :curvature.
38
- # :height parameter is a factor of base length
39
- # == Attributes
40
- # attribute :height, 1.0
41
- # attribute :curvature, 1.0
42
- # == Example
43
- # pic = PicBezier[ :support, [V2D::O, V2D::X], :height, 1.0, :curvature, 2.0 ]
44
- class PicBezier < BezierMotif
45
- attribute :height, 1.0
46
- attribute :curvature, 1.0
47
-
48
- # BezierBuilder compute overloading.
49
- #
50
- # See code for algorithm
51
- def compute
52
- p1, p2 = self.support
53
- onethird = (1.0 / 3.0)
54
- p1top2 = p2 - p1
55
- pp1 = p1 + p1top2.ortho.*(@height)
56
- pp2 = pp1 + ( p1top2 * @curvature )
57
-
58
- p1topp2 = pp2 - p1
59
- pc1 = p1 + ( p1topp2 * onethird )
60
- pc2 = p2 + ( p1topp2 * onethird )
61
-
62
- p3 = p1 + ( p1top2 * @curvature )
63
- p4 = p1 + ( p1top2 * ( @curvature + 1.0 ) )
64
-
65
- pp1top2 = p3 - pp1
66
- pp1top3 = p4 - pp1
67
-
68
- pp1c1 = pp1 + ( pp1top2 * onethird )
69
- pp1c2 = pp1 + ( pp1top3 * onethird )
70
-
71
- return [[:raw, p1, pc1, pp1c1, pp1], [:raw, pp1, pp1c2, pc2, p2]]
72
- end
73
- end
74
-
75
- # = ArcBezier class
76
- # == Content
77
- # Build an "arc" bezier whose base is specified by two points, and whose shape is controlled by a :height attribute.
78
- # :height parameter is a factor of base length
79
- # == Attributes
80
- # attribute :height, 1.0
81
- # == Example
82
- # arc = ArcBezier[ :support, [V2D::O, V2D::X], :height, 1.0 ]
83
- class ArcBezier < BezierMotif
84
- attribute :height, 1.0
85
-
86
- # BezierBuilder compute overloading.
87
- #
88
- # See code for algorithm
89
- def compute
90
- p1, p2 = self.support
91
- v = (p2 - p1).ortho * @height
92
- return [[:vector, p1, v, p2, v]]
93
- end
94
- end
95
-
96
- # = LinearBezier class
97
- # == Content
98
- # Build an line bezier whose base is specified by two points.
99
- # == Attributes
100
- # None, apart from :support
101
- # == Example
102
- # line = LinearBezier[ :support, [V2D::O, V2D::X] ]
103
- class LinearBezier < BezierMotif
104
- attribute :support, [V2D::O, V2D::X]
105
-
106
- # BezierBuilder compute overloading.
107
- #
108
- # See code for algorithm
109
- def compute
110
- p1, p2 = self.support
111
- return [[:vector, p1, (p2-p1) * 1.0 / 3.0, p2, (p1 - p2) * 1.0 / 3.0]]
112
- end
113
-
114
- # Utilitary method to build a unit bezier line with angle
115
- def LinearBezier.buildwithangle( angle )
116
- return LinearBezier[ :support, [V2D::O, V2D::X.rotate( angle )]]
117
- end
118
-
119
- end
120
-
121
- end # end XRVG
data/lib/bezierspline.rb DELETED
@@ -1,235 +0,0 @@
1
- # +BezierSpline+ source
2
-
3
- require 'interpolation'
4
- require 'parametriclength'
5
-
6
- module XRVG
7
- # BezierSpline class
8
- #
9
- # Internal class to represent a single-piece cubic bezier curve, defined by four points or two point + two vectors.
10
- # You may never have to use this class. Prefer the use of +Bezier+ class
11
- class BezierSpline #:nodoc:
12
-
13
- def BezierSpline.[](*args)
14
- return BezierSpline.new( *args )
15
- end
16
-
17
- def initialize( type, v1, v2, v3, v4 )
18
- self.checktype( type )
19
- self.checkvalues( v1, v2, v3, v4 )
20
- self.initdata( type, v1, v2, v3, v4 )
21
- end
22
-
23
- def checkvalues( v1, v2, v3, v4 )
24
- [v1, v2, v3, v4].each do |v|
25
- if not (v.respond_to?(:x) || v.respond_to?(:y))
26
- Kernel::raise( "BezierSpline : init value #{v.inspect} does not respond to :x or :y" )
27
- end
28
- end
29
- end
30
-
31
- def checktype( type )
32
- if not type == :raw || type == :vector
33
- Kernel::raise( "BezierSpline : type #{type.inspect} is not :raw or :vector" )
34
- end
35
- end
36
-
37
- def data()
38
- return [:raw] + self.pointlist
39
- end
40
-
41
- def initdata( type, v1, v2, v3, v4 )
42
- @rawpointlist = nil
43
- @vectorpointlist = nil
44
- if type == :raw
45
- @rawpointlist = [v1, v2, v3, v4]
46
- else
47
- @vectorpointlist = [v1, v2, v3, v4]
48
- end
49
- end
50
-
51
- def compute_rawpointlist
52
- # Assert{ @vectorpointlist }
53
- @rawpointlist = [@vectorpointlist[0], @vectorpointlist[0] + @vectorpointlist[1], @vectorpointlist[2] + @vectorpointlist[3], @vectorpointlist[2]]
54
- end
55
-
56
- def compute_vectorpointlist
57
- # Assert{ @rawpointlist }
58
- @vectorpointlist = [@rawpointlist[0], @rawpointlist[1] - @rawpointlist[0], @rawpointlist[3], @rawpointlist[2] - @rawpointlist[3]]
59
- end
60
-
61
-
62
- def pointlist(type=:raw)
63
- self.checktype( type )
64
- if type == :raw
65
- if not @rawpointlist
66
- self.compute_rawpointlist
67
- end
68
- return @rawpointlist
69
- elsif type == :vector
70
- if not @vectorpointlist
71
- self.compute_vectorpointlist
72
- end
73
- return @vectorpointlist
74
- end
75
- end
76
-
77
- # shortcut method to get piece first point
78
- def firstpoint
79
- return self.pointlist()[0]
80
- end
81
-
82
- # shortcut method to get piece last point
83
- def lastpoint
84
- return self.pointlist()[-1]
85
- end
86
-
87
- # shortcut method to get piece first vector
88
- def firstvector
89
- return self.pointlist(:vector)[1]
90
- end
91
-
92
- # shortcut method to get piece last vector
93
- def lastvector
94
- return self.pointlist(:vector)[-1]
95
- end
96
-
97
- # -------------------------------------------------------------
98
- # bezier formula
99
- # -------------------------------------------------------------
100
-
101
- def compute_factors
102
- p1, p2, p3, p4 = self.pointlist
103
- @factors = [-(p1 - p2 * 3.0 + p3 * 3.0 - p4), (p1 - p2 * 2.0 + p3) * 3.0, (p2 - p1) * 3.0, p1]
104
- @tfactors = [@factors[0], @factors[1] * 2.0 / 3.0, @factors[2] / 3.0]
105
- @afactors = [@tfactors[0] * 2.0, @tfactors[1]]
106
- end
107
-
108
- # get point from the bezier curve
109
- # this method use the definition of the bezier curve
110
- def point( t, result=nil )
111
- t2 = t * t
112
- t3 = t2 * t
113
- if not result
114
- result = V2D[0.0,0.0]
115
- end
116
-
117
- if not @factors
118
- compute_factors
119
- end
120
-
121
- # decomposed because avoid to build useless V2D
122
- result.x = @factors[3].x + @factors[2].x * t + @factors[1].x * t2 + @factors[0].x * t3
123
- result.y = @factors[3].y + @factors[2].y * t + @factors[1].y * t2 + @factors[0].y * t3
124
-
125
- return result
126
- end
127
-
128
- # compute the bezier tangent vector
129
- #
130
- # Beware that what is actually computed here is 1/3 tangent !!
131
- def tangent( t, result=nil )
132
- t2 = t * t
133
- if not result
134
- result = V2D[0.0,0.0]
135
- end
136
-
137
- if not @factors
138
- compute_factors
139
- end
140
-
141
- # decomposed because avoid to build useless V2D
142
- result.x = @tfactors[2].x + @tfactors[1].x * t + @tfactors[0].x * t2
143
- result.y = @tfactors[2].y + @tfactors[1].y * t + @tfactors[0].y * t2
144
-
145
- return result
146
- end
147
-
148
- # compute the acc of bezier curve at t abscissa
149
- def acc( t, result=nil )
150
- if not result
151
- result = V2D[0.0,0.0]
152
- end
153
-
154
- if not @factors
155
- compute_factors
156
- end
157
-
158
- result.x = @afactors[1].x + @afactors[0].x * t
159
- result.y = @afactors[1].y + @afactors[0].y * t
160
-
161
- return result
162
- end
163
-
164
- # compute tangent vectors corresponding to the new subbezier
165
- # very strange method, but effective
166
- def subtangents (t1, t2 )
167
- v1 = self.tangent( t1 ) * (t2 - t1)
168
- v2 = self.tangent( t2 ) * (t1 - t2)
169
- return [v1, v2]
170
- end
171
-
172
- # compute a subpiece of the current bezier
173
- # t1 and t2 must correspond to the same atomic bezier
174
- def subpiece (t1, t2)
175
- tan1, tan2 = self.subtangents( t1, t2 )
176
- return BezierSpline.new( :vector, self.point( t1 ), tan1, self.point( t2 ), tan2 )
177
- end
178
-
179
- def reverse()
180
- return BezierSpline[ :raw, *self.pointlist().reverse ]
181
- end
182
-
183
- # simple translation operation : translate every point of the piece
184
- # return a new BezierSpline
185
- def translate( v )
186
- newpoints = self.pointlist.map {|point| point + v}
187
- return BezierSpline[ :raw, *newpoints ]
188
- end
189
-
190
- # simple rotation operation : rotate every point of the piece
191
- # return a new BezierSpline
192
- def rotate( angle, center )
193
- newpoints = self.pointlist.map {|point| center + (point-center).rotate( angle )}
194
- return BezierSpline[ :raw, *newpoints ]
195
- end
196
-
197
- # simple sym operation : sym every point of the piece
198
- # return a new BezierSpline
199
- def sym( center )
200
- newpoints = self.pointlist.map {|point| center.sym( point )}
201
- return BezierSpline[ :raw, *newpoints ]
202
- end
203
-
204
-
205
- # simple axe sym operation : sym every point of the piece
206
- # return a new BezierSpline
207
- def axesym( origin, v )
208
- newpoints = self.pointlist.map {|point| point.axesym( origin, v )}
209
- return BezierSpline[ :raw, *newpoints ]
210
- end
211
-
212
- def gdebug(render)
213
- p1, pc1, pc2, p2 = self.pointlist()
214
- v1 = pc1 - p1
215
- r1 = v1.r / 30.0
216
- v2 = pc2 - p2
217
- r2 = v2.r / 30.0
218
- render.add( Circle[ :center, p1, :radius, r1 ], Style[ :fill, "red" ])
219
- render.add( Circle[ :center, pc1, :radius, r1 ], Style[ :fill, "red" ])
220
- render.add( Line[ :points, [p1, pc1] ], Style[ :stroke, "red", :strokewidth, (r1 / 10.0) ])
221
- render.add( Circle[ :center, p2, :radius, r2 ], Style[ :fill, "red" ])
222
- render.add( Circle[ :center, pc2, :radius, r1 ], Style[ :fill, "red" ])
223
- render.add( Line[ :points, [p2, pc2] ], Style[ :stroke, "red", :strokewidth, (r2 / 10.0) ])
224
- end
225
-
226
- # length computation
227
- include ParametricLength
228
- def parameter_range
229
- return (0.0..1.0)
230
- end
231
-
232
- alias pointfromparameter point
233
-
234
- end
235
- end