xrvg 0.0.7 → 0.0.8

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.
@@ -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