xrvg 0.0.3 → 0.0.4

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.
Files changed (59) hide show
  1. data/CHANGES +21 -0
  2. data/README +3 -3
  3. data/Rakefile +4 -4
  4. data/examples/bezierbasic.rb +1 -0
  5. data/examples/bezierbasicvector.rb +1 -0
  6. data/examples/foreach.rb +1 -0
  7. data/examples/geodash.rb +1 -0
  8. data/examples/geodash2.rb +1 -0
  9. data/examples/hellocrown.rb +1 -0
  10. data/examples/hellocrown2.rb +1 -0
  11. data/examples/hellocrownrecurse.rb +1 -0
  12. data/examples/helloworldcompact.rb +1 -0
  13. data/examples/helloworldexpanded.rb +1 -0
  14. data/examples/multibezierbasic.rb +1 -0
  15. data/examples/palette_circle.rb +1 -0
  16. data/examples/randomdash.rb +1 -0
  17. data/examples/range_examples.rb +1 -0
  18. data/examples/range_examples2.rb +1 -0
  19. data/examples/sample.rb +1 -0
  20. data/examples/simpledash.rb +1 -0
  21. data/examples/uplets.rb +1 -0
  22. data/lib/bezier.rb +21 -55
  23. data/lib/bezierbuilders.rb +194 -0
  24. data/lib/beziermotifs.rb +114 -0
  25. data/lib/bezierspline.rb +20 -75
  26. data/lib/beziertools.rb +211 -0
  27. data/lib/color.rb +26 -7
  28. data/lib/fitting.rb +203 -0
  29. data/lib/frame.rb +2 -1
  30. data/lib/geometry2D.rb +6 -5
  31. data/lib/interbezier.rb +87 -0
  32. data/lib/interpolation.rb +6 -5
  33. data/lib/parametriclength.rb +87 -0
  34. data/lib/render.rb +4 -9
  35. data/lib/samplation.rb +71 -82
  36. data/lib/shape.rb +47 -25
  37. data/lib/style.rb +2 -1
  38. data/lib/trace.rb +2 -0
  39. data/lib/utils.rb +111 -17
  40. data/lib/xrvg.rb +16 -6
  41. data/test/test_attributable.rb +34 -2
  42. data/test/test_bezier.rb +93 -2
  43. data/test/test_bezierbuilders.rb +92 -0
  44. data/test/test_beziertools.rb +97 -0
  45. data/test/test_color.rb +65 -24
  46. data/test/test_fitting.rb +47 -0
  47. data/test/test_frame.rb +7 -2
  48. data/test/test_geometry2D.rb +26 -7
  49. data/test/test_interbezier.rb +29 -0
  50. data/test/test_interpolation.rb +16 -1
  51. data/test/test_parametric_length.rb +15 -0
  52. data/test/test_render.rb +54 -6
  53. data/test/test_shape.rb +103 -10
  54. data/test/test_trace.rb +13 -0
  55. data/test/test_utils.rb +114 -12
  56. data/test/test_xrvg.rb +3 -0
  57. metadata +16 -5
  58. data/lib/assertion.rb +0 -14
  59. data/lib/attributable.rb +0 -152
@@ -0,0 +1,114 @@
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
+ end
21
+
22
+
23
+
24
+ # = PicBezier class
25
+ # == Content
26
+ # Build a curved "pic" bezier whose base is specified by two points, and whose shape is controlled by two attributes, :height and :curvature.
27
+ # :height parameter is a factor of base length
28
+ # == Attributes
29
+ # attribute :height, 1.0
30
+ # attribute :curvature, 1.0
31
+ # == Example
32
+ # pic = PicBezier[ :support, [V2D::O, V2D::X], :height, 1.0, :curvature, 2.0 ]
33
+ class PicBezier < BezierMotif
34
+ attribute :height, 1.0
35
+ attribute :curvature, 1.0
36
+
37
+ # BezierBuilder compute overloading.
38
+ #
39
+ # See code for algorithm
40
+ def compute
41
+ p1, p2 = self.support
42
+ onethird = (1.0 / 3.0)
43
+ p1top2 = p2 - p1
44
+ pp1 = p1 + p1top2.ortho.*(@height)
45
+ pp2 = pp1 + ( p1top2 * @curvature )
46
+
47
+ p1topp2 = pp2 - p1
48
+ pc1 = p1 + ( p1topp2 * onethird )
49
+ pc2 = p2 + ( p1topp2 * onethird )
50
+
51
+ p3 = p1 + ( p1top2 * @curvature )
52
+ p4 = p1 + ( p1top2 * ( @curvature + 1.0 ) )
53
+
54
+ pp1top2 = p3 - pp1
55
+ pp1top3 = p4 - pp1
56
+
57
+ pp1c1 = pp1 + ( pp1top2 * onethird )
58
+ pp1c2 = pp1 + ( pp1top3 * onethird )
59
+
60
+ return [[:raw, p1, pc1, pp1c1, pp1], [:raw, pp1, pp1c2, pc2, p2]]
61
+ end
62
+ end
63
+
64
+ # = ArcBezier class
65
+ # == Content
66
+ # Build an "arc" bezier whose base is specified by two points, and whose shape is controlled by a :height attribute.
67
+ # :height parameter is a factor of base length
68
+ # == Attributes
69
+ # attribute :height, 1.0
70
+ # == Example
71
+ # arc = ArcBezier[ :support, [V2D::O, V2D::X], :height, 1.0 ]
72
+ class ArcBezier < BezierMotif
73
+ attribute :height, 1.0
74
+
75
+ # BezierBuilder compute overloading.
76
+ #
77
+ # See code for algorithm
78
+ def compute
79
+ p1, p2 = self.support
80
+ v = (p2 - p1).ortho * @height
81
+ return [[:vector, p1, v, p2, v]]
82
+ end
83
+ end
84
+
85
+ # = LinearBezier class
86
+ # == Content
87
+ # Build an line bezier whose base is specified by two points.
88
+ # == Attributes
89
+ # None, apart from :support
90
+ # == Example
91
+ # line = LinearBezier[ :support, [V2D::O, V2D::X] ]
92
+ class LinearBezier < BezierMotif
93
+ attribute :support, [V2D::O, V2D::X]
94
+
95
+ # BezierBuilder compute overloading.
96
+ #
97
+ # See code for algorithm
98
+ def compute
99
+ p1, p2 = self.support
100
+ return [[:vector, p1, (p2-p1) * 1.0 / 3.0, p2, (p1 - p2) * 1.0 / 3.0]]
101
+ end
102
+
103
+ # Utilitary method to build a unit bezier line with angle
104
+ def LinearBezier.buildwithangle( angle )
105
+ return LinearBezier[ :support, [V2D::O, V2D::X.rotate( angle )]]
106
+ end
107
+
108
+ end
109
+
110
+ end # end XRVG
111
+
112
+
113
+
114
+
data/lib/bezierspline.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # +BezierSpline+ source
2
2
 
3
3
  require 'interpolation'
4
+ require 'parametriclength'
4
5
 
5
-
6
+ module XRVG
6
7
  # BezierSpline class
7
8
  #
8
9
  # Internal class to represent a single-piece cubic bezier curve, defined by four points or two point + two vectors.
@@ -42,7 +43,7 @@ class BezierSpline #:nodoc:
42
43
  @vectorpointlist = nil
43
44
  if type == :raw
44
45
  @rawpointlist = [v1, v2, v3, v4]
45
- elsif
46
+ else
46
47
  @vectorpointlist = [v1, v2, v3, v4]
47
48
  end
48
49
  end
@@ -73,16 +74,25 @@ class BezierSpline #:nodoc:
73
74
  end
74
75
  end
75
76
 
76
- # shortcut method to get curve first point
77
+ # shortcut method to get piece first point
77
78
  def firstpoint
78
79
  return self.pointlist()[0]
79
80
  end
80
81
 
81
- # shortcut method to get curve last point
82
+ # shortcut method to get piece last point
82
83
  def lastpoint
83
84
  return self.pointlist()[-1]
84
85
  end
85
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
86
96
 
87
97
  # -------------------------------------------------------------
88
98
  # bezier formula
@@ -191,78 +201,13 @@ class BezierSpline #:nodoc:
191
201
  render.add( Line[ :points, [p2, pc2] ], Style[ :stroke, "red", :strokewidth, (r2 / 10.0) ])
192
202
  end
193
203
 
194
- # -------------------------------------------------------------
195
- # length computation
196
- # -------------------------------------------------------------
197
-
198
-
199
- # compute the length of the bezier curve defined by the points
200
- #
201
- # Algo : recursively approximate curve by lines, until length variation is under some epsilon
202
- #
203
- # for the moment, just take a fix number of samples, and some it
204
- def compute_length_interpolator() #:nodoc:
205
- sum = 0.0
206
- previous = nil
207
- samplelist = [0.0, 0.0]
208
- new = V2D[0.0,0.0]
209
- previous = nil
210
- (0.0..1.0).samples( 129 ) do |abs|
211
- self.point( abs, new )
212
- if previous
213
- sum+= (new - previous).r
214
- samplelist += [sum, abs]
215
- else
216
- previous = V2D[0.0,0.0]
217
- end
218
- previous.x = new.x
219
- previous.y = new.y
220
- end
221
- @length = samplelist[-2]
222
-
223
- length_interpolator = nil
224
- if @length == 0.0
225
- newsamplelist = [0.0,0.0,0.0,1.0]
226
- invsamplelist = [0.0,0.0,1.0,0.0]
227
- else
228
- newsamplelist = []
229
- invsamplelist = []
230
- samplelist.foreach do |sum, abs|
231
- newsamplelist += [sum / @length, abs ]
232
- invsamplelist += [abs, sum / @length ]
233
- end
234
- samplelist = newsamplelist
235
- end
236
- @abs_interpolator = InterpolatorQuad.new( :samplelist, invsamplelist )
237
- return InterpolatorQuad.new( :samplelist, samplelist )
238
- end
239
-
240
- def length_interpolator() #:nodoc:
241
- if @length_interpolator == nil
242
- @length_interpolator = self.compute_length_interpolator()
243
- end
244
- return @length_interpolator
245
- end
246
-
247
- def length(t=nil)
248
- if @length == nil
249
- self.compute_length()
250
- end
251
- if not t
252
- return @length
253
- else
254
- return @abs_interpolator.interpolate( t )
255
- end
204
+ # length computation
205
+ include ParametricLength
206
+ def parameter_range
207
+ return (0.0..1.0)
256
208
  end
257
209
 
258
- def compute_length() #:nodoc:
259
- self.length_interpolator()
260
- return @length
261
- end
210
+ alias pointfromparameter point
262
211
 
263
- def parameterfromlength( lvalue ) #:nodoc:
264
- result = self.length_interpolator.interpolate( lvalue )
265
- return result
266
- end
267
212
  end
268
-
213
+ end
@@ -0,0 +1,211 @@
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
+ # = Offset bezier builder
68
+ # == Content
69
+ # Generic offset bezier builder.
70
+ # == Attributes
71
+ # attribute :support, nil, Curve
72
+ # attribute :abscissasampler, (0.0..1.0), Samplable
73
+ # attribute :ampl, 0.5, :samplable
74
+ # attribute :nsamples, 100
75
+ class Offset < FitBezierBuilder
76
+ attribute :support, nil, Curve
77
+ attribute :abscissasampler, (0.0..1.0), Samplable
78
+ attribute :ampl, 0.5, :samplable
79
+ attribute :nsamples, 100
80
+
81
+ # overload FitBezierBuilder.points to compute Offset points
82
+ #
83
+ # Algo: for each sample, compute point, normal and amp, and newpoint = point + normal.norm * ampl
84
+ def points
85
+ result = []
86
+ [self.abscissasampler, self.ampl].samples( self.nsamples) do |abscissa, amplsample|
87
+ frame = self.support.frame( abscissa )
88
+ result << frame.center + frame.vector.ortho.norm * amplsample
89
+ end
90
+ return result
91
+ end
92
+ end
93
+
94
+ # = Fuseau bezier builder
95
+ # == Content
96
+ # Just shortcut class for Offset with :ampl = (1.0..0.0)
97
+ # == Attributes
98
+ # attribute :maxwidth, 0.1
99
+ class Fuseau < Offset
100
+ attribute :maxwidth, 0.1
101
+
102
+ # overload Offset.ampl method by returning (self.maxwidth..0.0)
103
+ def ampl
104
+ return (self.maxwidth..0.0)
105
+ end
106
+ end
107
+
108
+
109
+
110
+ # = BezierLevel bezier builder
111
+ # == Content
112
+ # Compute "roller coaster" bezier curves
113
+ #
114
+ # Can be used as a x-progressing curve, that is as an interpolation curve
115
+ # == Attributes
116
+ # attribute :samplelist, [], Array
117
+ # :samplelist must contain pairs of cartesien coords [x1,y1,x2,y2,...], x between 0.0 and 1.0 (as for interpolator)
118
+ class BezierLevel < BezierBuilder
119
+ attribute :samplelist, [], Array
120
+
121
+ # Overload BezierBuilder build method
122
+ #
123
+ # Algo: simply interpolate [x,y] couples as V2D, with SimpleBezier bezier builder
124
+ def BezierLevel.build( *args )
125
+ builder = BezierLevel.new( *args )
126
+ points = []
127
+ builder.samplelist.foreach do |x,y|
128
+ points << V2D[x,y]
129
+ end
130
+ return SimpleBezier[ :support, points ]
131
+ end
132
+ end
133
+
134
+ # = ClosureBezier bezier builder
135
+ # == Content
136
+ # Simple bezier operator that take a list of beziers and produce a concatenate multipieces closed bezier curve.
137
+ # Missing segments are completed with lines
138
+ class ClosureBezier < BezierBuilder
139
+ attribute :bezierlist
140
+
141
+ # BezierBuilder compute overloading
142
+ def compute
143
+ result = []
144
+ result += self.bezierlist[0].pieces
145
+ self.bezierlist[1..-1].each do |bezier|
146
+ lastpoint = result[-1].lastpoint
147
+ newpoint = bezier.firstpoint
148
+ if not V2D.vequal?( lastpoint, newpoint )
149
+ result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
150
+ end
151
+ result += bezier.pieces
152
+ end
153
+ lastpoint = result[-1].lastpoint
154
+ newpoint = result[0].firstpoint
155
+ if not V2D.vequal?( lastpoint, newpoint )
156
+ result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
157
+ end
158
+ result = result.map {|piece| piece.data}
159
+ # Trace("result #{result.inspect}")
160
+ return result
161
+ end
162
+ end
163
+
164
+ # = Ondulation bezier builder
165
+ # == Content
166
+ # Generic ondulation bezier builder.
167
+ # == Attributes
168
+ # attribute :support, nil, Curve
169
+ # attribute :ampl, 0.5, :samplable
170
+ # attribute :abscissasampler, (0.0..1.0), Samplable
171
+ # attribute :freq, 10
172
+ # :support is a Curve
173
+ # :abscissas must be a Float Samplable, as (0.0..1.0).geo(3.0)
174
+ # :ampl can be a constant or a sampler
175
+ # :freq is the number of oscillations to be computed
176
+ class Ondulation < BezierBuilder
177
+ attribute :support, nil, Curve
178
+ attribute :ampl, 0.5, :samplable
179
+ attribute :abscissasampler, (0.0..1.0), Samplable
180
+ attribute :freq, 10
181
+
182
+
183
+ # algo : for each abscissa, 0.0 of the curve (given the normal)
184
+ # and for each mean abscissa, :amp normal
185
+ def compute
186
+ abscissas = self.abscissasampler.samples( self.freq + 1 )
187
+ sens = 1.0
188
+ pieces = []
189
+ [abscissas.pairs, self.ampl.samples( self.freq )].forzip do |abspair, amplitude|
190
+ abs1, abs2 = abspair
191
+ mabs = (abs1 + abs2)/2.0
192
+ p1, halfpoint, p2 = self.support.points( [abs1, mabs, abs2] )
193
+ # Trace("mabs #{mabs} abs1 #{abs1} abs2 #{abs2} halfpoint #{halfpoint.inspect} p1 #{p1.inspect} p2 #{p2.inspect}")
194
+ # Trace("normal #{@support.normal( mabs )}")
195
+ halfnormal = self.support.normal( mabs ).norm * ( sens * amplitude * (p2 - p1).length)
196
+ # Trace("halfnormal #{halfnormal.inspect}")
197
+ newpoint = halfpoint + halfnormal
198
+ tpoint = halfpoint + halfnormal * 3.0
199
+ t1 = (tpoint - p1 ) / 6.0
200
+ t2 = (tpoint - p2 ) / 6.0
201
+ # Trace("newpoint #{newpoint.inspect} p1 #{p1.inspect} (newpoint - p1) #{(newpoint - p1).inspect}")
202
+ halftangent = self.support.tangent( mabs ).norm * (newpoint - p1).length / 3.0
203
+ # halftangent = self.support.tangent( mabs ).norm * (p2 - p1).length / 3.0
204
+ pieces += [[:vector, p1, t1, newpoint, -halftangent], [:vector, newpoint, halftangent, p2, t2]]
205
+ sens *= -1.0
206
+ end
207
+ return pieces
208
+ end
209
+ end
210
+
211
+ end # XRVG