xrvg 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,245 @@
1
+ # Some BezierBuilder implementations
2
+ # - old-fashioned SimpleBezier
3
+ # - useful BezierLevel
4
+ # - powerful Offset
5
+ # - interesting Ondulation
6
+ # - simple ClosureBezier
7
+
8
+ require 'bezierbuilders'
9
+
10
+ module XRVG
11
+ # = SimpleBezier
12
+ # == Content
13
+ # Simple Bezier interpolator, that builds a multipiece "regular" bezier curve from a list of points
14
+ # == Algo
15
+ # For each point triplet :
16
+ # - tangent vector of the middle point is the vector mean of vector from first to middle and vector frommiddle to last.
17
+ # First and last tangents are computed by symetry
18
+ # == Note
19
+ # FittingBezier is a better class for point bezier interpolation. However, this class is kept mainly for historical reasons.
20
+ class SimpleBezier < BezierBuilder
21
+ attribute :support, nil, Array
22
+
23
+ # BezierBuilder overloading: see SimpleBezier description for algorithm
24
+ def compute( )
25
+ points = @support
26
+ if points.length < 2
27
+ Kernel::raise("SimpleBezier support must have at least two points")
28
+ elsif points.length == 2
29
+ return LinearBezier.build( :support, points).data
30
+ end
31
+
32
+ result = Array.new
33
+ p1 = points[0]
34
+ v1 = V2D::O
35
+ cpiece = [:vector, p1, v1]
36
+
37
+ points.triplets do |p1, p2, p3|
38
+ v = ((p2 - p1)..( p3 - p2 )).middle * 1.0 / 3.0
39
+ result.push( cpiece + [p2, v.reverse] )
40
+ cpiece = [:vector, p2, v]
41
+ end
42
+
43
+ pr2 = points[-1]
44
+ vr2 = V2D::O
45
+ result.push( cpiece + [pr2, vr2] )
46
+
47
+ # compute first and last piece again, by symetry
48
+ piece0 = Bezier.single( *result[0] )
49
+ p1, v1, p2, v2 = piece0.pointlist(:vector)
50
+ pv = (p2 - p1)
51
+ angle = v2.angle - pv.angle
52
+ v1 = (-v2).rotate( -2.0 * angle )
53
+ result[0] = [:vector, p1, v1, p2, v2]
54
+
55
+ piecel = Bezier.single( *result[-1] )
56
+ p1, v1, p2, v2 = piecel.pointlist(:vector)
57
+ pv = (p2 - p1)
58
+ angle = v1.angle - pv.angle
59
+ v2 = (-v1).rotate( -2.0 * angle )
60
+ result[-1] = [:vector, p1, v1, p2, v2]
61
+
62
+ return result
63
+ end
64
+ end
65
+
66
+ #
67
+ # Interpolation extension with SimpleBezier
68
+ #
69
+ module Interpolation
70
+
71
+ def compute_simplebezier
72
+ points = self.samplelist.foreach(2).map { |index, value| V2D[index,value] }
73
+ @simplebezier = SimpleBezier[ :support, points ]
74
+ end
75
+
76
+ def getcurve
77
+ if not @simplebezier
78
+ self.compute_simplebezier
79
+ end
80
+ return @simplebezier
81
+ end
82
+
83
+ def simplebezier( dindex )
84
+ return self.getcurve.sample( dindex ).y
85
+ end
86
+
87
+ end
88
+
89
+
90
+ # = Offset bezier builder
91
+ # == Content
92
+ # Generic offset bezier builder.
93
+ # == Attributes
94
+ # attribute :support, nil, Curve
95
+ # attribute :abscissasampler, (0.0..1.0), Samplable
96
+ # attribute :ampl, 0.5, :samplable
97
+ # attribute :nsamples, 100
98
+ class Offset < FitBezierBuilder
99
+ attribute :support, nil, Curve
100
+ attribute :abscissasampler, (0.0..1.0), Samplable
101
+ attribute :ampl, 0.5, :samplable
102
+ attribute :nsamples, 100
103
+
104
+ # overload FitBezierBuilder.points to compute Offset points
105
+ #
106
+ # Algo: for each sample, compute point, normal and amp, and newpoint = point + normal.norm * ampl
107
+ def points
108
+ result = []
109
+ SyncS[self.abscissasampler, self.ampl].samples( self.nsamples) do |abscissa, amplsample|
110
+ frame = self.support.frame( abscissa )
111
+ result << frame.center + frame.vector.ortho.norm * amplsample
112
+ end
113
+ return result
114
+ end
115
+ end
116
+
117
+ # = Fuseau bezier builder
118
+ # == Content
119
+ # Just shortcut class for Offset with :ampl = (1.0..0.0)
120
+ # == Attributes
121
+ # attribute :maxwidth, 0.1
122
+ class Fuseau < Offset
123
+ attribute :maxwidth, 0.1
124
+
125
+ # overload Offset.ampl method by returning (self.maxwidth..0.0)
126
+ def ampl
127
+ return (self.maxwidth..0.0)
128
+ end
129
+ end
130
+
131
+
132
+
133
+ # = BezierLevel bezier builder
134
+ # == Content
135
+ # Compute "roller coaster" bezier curves
136
+ #
137
+ # Can be used as a x-progressing curve, that is as an interpolation curve
138
+ # == Attributes
139
+ # attribute :samplelist, [], Array
140
+ # :samplelist must contain pairs of cartesien coords [x1,y1,x2,y2,...], x between 0.0 and 1.0 (as for interpolator)
141
+ class BezierLevel < BezierBuilder
142
+ attribute :samplelist, [], Array
143
+
144
+ # Overload BezierBuilder build method
145
+ #
146
+ # Algo: simply interpolate [x,y] couples as V2D, with SimpleBezier bezier builder
147
+ def BezierLevel.build( *args )
148
+ builder = BezierLevel.new( *args )
149
+ points = []
150
+ builder.samplelist.foreach do |x,y|
151
+ points << V2D[x,y]
152
+ end
153
+ return SimpleBezier[ :support, points ]
154
+ end
155
+ end
156
+
157
+ # = ClosureBezier bezier builder
158
+ # == Content
159
+ # Simple bezier operator that take a list of beziers and produce a concatenate multipieces closed bezier curve.
160
+ # Missing segments are completed with lines
161
+ class ClosureBezier < BezierBuilder
162
+ attribute :bezierlist
163
+
164
+ # BezierBuilder compute overloading
165
+ def compute
166
+ result = []
167
+ result += self.bezierlist[0].pieces
168
+ self.bezierlist[1..-1].each do |bezier|
169
+ lastpoint = result[-1].lastpoint
170
+ newpoint = bezier.firstpoint
171
+ if not V2D.vequal?( lastpoint, newpoint )
172
+ result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
173
+ end
174
+ result += bezier.pieces
175
+ end
176
+ lastpoint = result[-1].lastpoint
177
+ newpoint = result[0].firstpoint
178
+ if not V2D.vequal?( lastpoint, newpoint )
179
+ result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
180
+ end
181
+ result = result.map {|piece| piece.data}
182
+ # Trace("result #{result.inspect}")
183
+ return result
184
+ end
185
+ end
186
+
187
+ # = Ondulation bezier builder
188
+ # == Content
189
+ # Generic ondulation bezier builder.
190
+ # == Attributes
191
+ # attribute :support, nil, Curve
192
+ # attribute :ampl, 0.5, :samplable
193
+ # attribute :abscissasampler, (0.0..1.0), Samplable
194
+ # attribute :freq, 10
195
+ # :support is a Curve
196
+ # :abscissas must be a Float Samplable, as (0.0..1.0).geo(3.0)
197
+ # :ampl can be a constant or a sampler
198
+ # :freq is the number of oscillations to be computed
199
+ class Ondulation < BezierBuilder
200
+ attribute :support, nil, Curve
201
+ attribute :ampl, 0.5, :samplable
202
+ attribute :abscissasampler, (0.0..1.0), Samplable
203
+ attribute :freq, 10
204
+
205
+ # atomic pattern computation
206
+ #
207
+ def compute_arc( abs1, abs2, amplitude, sens )
208
+ mabs = (abs1 + abs2)/2.0
209
+ p1, halfpoint, p2 = self.support.points( [abs1, mabs, abs2] )
210
+ # Trace("mabs #{mabs} abs1 #{abs1} abs2 #{abs2} halfpoint #{halfpoint.inspect} p1 #{p1.inspect} p2 #{p2.inspect}")
211
+ # Trace("normal #{@support.normal( mabs )}")
212
+ halfnormal = self.support.normal( mabs ).norm * ( sens * amplitude * (p2 - p1).length)
213
+ # Trace("halfnormal #{halfnormal.inspect}")
214
+ newpoint = halfpoint + halfnormal
215
+ tpoint = halfpoint + halfnormal * 3.0
216
+ t1 = (tpoint - p1 ) / 6.0
217
+ t2 = (tpoint - p2 ) / 6.0
218
+ # Trace("newpoint #{newpoint.inspect} p1 #{p1.inspect} (newpoint - p1) #{(newpoint - p1).inspect}")
219
+ halftangent = self.support.tangent( mabs ).norm * (newpoint - p1).length / 3.0
220
+ # halftangent = self.support.tangent( mabs ).norm * (p2 - p1).length / 3.0
221
+ return [[:vector, p1, t1, newpoint, -halftangent], [:vector, newpoint, halftangent, p2, t2]]
222
+ end
223
+
224
+ def compute_interpol( abs1, abs2, amplitude, sens )
225
+ arc = Bezier.multi( self.compute_arc( abs1, abs2, 1.0, sens ) )
226
+ subsupport = self.support.subbezier( abs1, abs2 )
227
+ return InterBezier[ :bezierlist, [0.0, subsupport, 1.0, arc] ].sample( amplitude ).data
228
+ end
229
+
230
+ # algo : for each abscissa, 0.0 of the curve (given the normal)
231
+ # and for each mean abscissa, :amp normal
232
+ def compute
233
+ abscissas = self.abscissasampler.samples( self.freq + 1 )
234
+ sens = 1.0
235
+ pieces = []
236
+ [abscissas.pairs, self.ampl.samples( self.freq )].forzip do |abspair, amplitude|
237
+ abs1, abs2 = abspair
238
+ pieces += self.compute_interpol( abs1, abs2, amplitude, sens )
239
+ sens *= -1.0
240
+ end
241
+ return pieces
242
+ end
243
+ end
244
+
245
+ end # XRVG