xrvg 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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