xrvg 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/lib/shape.rb ADDED
@@ -0,0 +1,421 @@
1
+ # shape.rb file
2
+ # See
3
+ # - Shape interface
4
+ # - Curve interface
5
+ # - Line class
6
+ # - Circle class
7
+ require 'utils'
8
+ require 'frame'
9
+ require 'geometry2D'
10
+
11
+
12
+ module XRVG
13
+ # Shape abstract interface
14
+ # = Intro
15
+ # To provide a set of services a shape class must provide
16
+ class Shape
17
+ include Attributable
18
+
19
+ # must return the contour of the shape, of Curve type
20
+ #
21
+ # abstract
22
+ #
23
+ # not yet used
24
+ def contour( *args )
25
+ raise NotImplementedError.new("#{self.class.name}#contour is an abstract method.")
26
+ end
27
+
28
+ # must return the svg description of the shape
29
+ #
30
+ # abstract
31
+ #
32
+ # must be defined
33
+ def svg()
34
+ raise NotImplementedError.new("#{self.class.name}#svg is an abstract method.")
35
+ end
36
+
37
+ # must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
38
+ #
39
+ # abstract
40
+ #
41
+ # must be defined
42
+ def viewbox()
43
+ raise NotImplementedError.new("#{self.class.name}#viewbox is an abstract method.")
44
+ end
45
+
46
+ # compute size of the shape, from viewbox
47
+ def size()
48
+ xmin, ymin, xmax, ymax = self.viewbox
49
+ return [xmax-xmin, ymax-ymin]
50
+ end
51
+
52
+ # return the default style for a Shape instance
53
+ #
54
+ # is done on instance, because for Curve for example, strokewidth is proportional to length
55
+ def default_style()
56
+ return Style[:fill, Color.black ]
57
+ end
58
+
59
+ # compute the "surface" of the viewbox of the shape
60
+ #
61
+ # use size method
62
+ def surface
63
+ width, height = self.size
64
+ return width * height
65
+ end
66
+
67
+ end
68
+
69
+ # Curve abstract interface
70
+ # = Intro
71
+ # To define a set of services a curve class must provide
72
+ class Curve < Shape
73
+ # must compute the point at curve abscissa
74
+ #
75
+ # abstract
76
+ #
77
+ # must be defined
78
+ def point( abscissa, container=nil )
79
+ raise NotImplementedError.new("#{self.class.name}#curve is an abstract method.")
80
+ end
81
+
82
+ # must compute the tangent at curve abscissa
83
+ #
84
+ # abstract
85
+ #
86
+ # must be defined
87
+ def tangent( abscissa, container=nil )
88
+ raise NotImplementedError.new("#{self.class.name}#tangent is an abstract method.")
89
+ end
90
+
91
+ # must compute the acceleration at curve abscissa
92
+ #
93
+ # abstract
94
+ #
95
+ # must be defined
96
+ def acc( abscissa, container=nil )
97
+ raise NotImplementedError.new("#{self.class.name}#acc is an abstract method.")
98
+ end
99
+
100
+ # must return the length at abscissa, or total length if abscissa nil
101
+ #
102
+ # abstract
103
+ #
104
+ # must be defined
105
+ def length(abscissa=nil)
106
+ raise NotImplementedError.new("#{self.class.name}#length is an abstract method.")
107
+ end
108
+
109
+ # default style of a curve, as stroked with stroke width 1% of length
110
+ def default_style
111
+ return Style[ :stroke, Color.black, :strokewidth, self.length / 100.0 ]
112
+ end
113
+
114
+ # compute the rotation at curve abscissa, or directly from tangent (for frame computation speed up),
115
+ # as angle between tangent0 angle and tangent( abscissa ) (or tangent) angle
116
+ def rotation( abscissa, tangent=nil )
117
+ if not tangent
118
+ tangent = self.tangent( abscissa )
119
+ end
120
+ return (tangent.angle - self.tangent0_angle)
121
+ end
122
+
123
+ # must compute the scale at curve abscissa, or directly from tangent (for frame computation speed up)
124
+ # as ratio between tangent0 size and tangent( abscissa ) (or tangent) size
125
+ def scale( abscissa, tangent=nil )
126
+ if not tangent
127
+ tangent = self.tangent( abscissa )
128
+ end
129
+ result = 0.0
130
+ if not self.tangent0_length == 0.0
131
+ result = (tangent.r / self.tangent0_length)
132
+ end
133
+ return result
134
+ end
135
+
136
+ def tangent0
137
+ if not @tangent0
138
+ @tangent0 = self.tangent( 0.0 )
139
+ end
140
+ return @tangent0
141
+ end
142
+
143
+ # TODO : must be cached in vector
144
+ def tangent0_angle
145
+ if not @tangent0_angle
146
+ @tangent0_angle = self.tangent0.angle
147
+ end
148
+ return @tangent0_angle
149
+ end
150
+
151
+ # TODO : must be cached in vector
152
+ def tangent0_length
153
+ if not @tangent0_length
154
+ @tangent0_length = self.tangent0.r
155
+ end
156
+ return @tangent0_length
157
+ end
158
+
159
+ # compute frame vector at abscissa t, that is [curve.point( t ), curve.tangent( t ) ]
160
+ def framev( t )
161
+ return [self.point( t ), self.tangent( t ) ]
162
+ end
163
+
164
+ # compute frame at abscissa t
165
+ def frame( t )
166
+ point, tangent = self.framev( t )
167
+ return Frame[ :center, point, :vector, tangent, :rotation, self.rotation( nil, tangent ), :scale, self.scale( nil, tangent ) ]
168
+ end
169
+
170
+ # compute normal at abscissa t
171
+ #
172
+ # do tangent.ortho
173
+ def normal( t )
174
+ return self.tangent( t ).ortho
175
+ end
176
+
177
+ # compute normal acceleration at abscissa t
178
+ def acc_normal( t )
179
+ normal = self.normal( t ).norm
180
+ result = self.acc( t ).inner_product( normal )
181
+ return result
182
+ end
183
+
184
+ # compute curvature at abscissa t
185
+ def curvature( t )
186
+ acc_normal = self.acc_normal( t )
187
+ if acc_normal == 0.0
188
+ return 0.0
189
+ end
190
+ return 1.0 / (self.tangent( t ).r / acc_normal )
191
+ end
192
+
193
+ # shortcut method to map frames from abscissas
194
+ def frames (abscissas)
195
+ return abscissas.map { |abscissa| self.frame( abscissa ) }
196
+ end
197
+
198
+ # shortcut method to map points from abscissas
199
+ def points (abscissas)
200
+ result = abscissas.map { |abscissa| self.point( abscissa ) }
201
+ return result
202
+ end
203
+
204
+ # shortcut method to map tangents from abscissas
205
+ def tangents (abscissas)
206
+ return abscissas.map { |abscissa| self.tangent( abscissa ) }
207
+ end
208
+
209
+ # shortcut method, map of normal
210
+ def normals( indexes )
211
+ return indexes.map {|i| self.normal( i )}
212
+ end
213
+
214
+ end
215
+
216
+
217
+ # Line class
218
+ # = Intro
219
+ # Used to draw polylines and polygons
220
+ # = Attributes
221
+ # attribute :points, [V2D[0.0, 0.0], V2D[1.0, 1.0]]
222
+ # WARNING : getter "points" is not defined, because is defined as Curve.points( abscissa ) !!!
223
+ # = Example
224
+ # line = Line[ :points, [V2D::O, V2D::X] ]
225
+ class Line < Curve
226
+ attribute :points, [V2D[0.0, 0.0], V2D[1.0, 1.0]]
227
+
228
+ def initialize (*args) #:nodoc:
229
+ super( *args )
230
+ self.init_tangents
231
+ end
232
+
233
+ def init_tangents #:nodoc:
234
+ index = 0
235
+ @tangents = Array.new
236
+ @points.pairs { |p1, p2|
237
+ @tangents[ index ] = (p2-p1).norm
238
+ index += 1
239
+ }
240
+ end
241
+
242
+ # return the total length of the polyline
243
+ def length
244
+ if not @length
245
+ @length = 0.0
246
+ @points.pairs do |p1, p2|
247
+ @length += (p1 - p2).r
248
+ end
249
+ end
250
+ return @length
251
+ end
252
+
253
+ # compute line point at abscissa
254
+ # Line[ :points, [V2D::O, V2D::X] ].point( 0.3 ) => V2D[0.0,0.3]
255
+ def point (abscissa, container=nil)
256
+ container ||= V2D[]
257
+ piece1 = abscissa.to_int
258
+ if piece1 == @points.size - 1
259
+ container.xy = @points[-1]
260
+ else
261
+ abscissa -= piece1
262
+ cpoints = @points.slice( piece1, 2 )
263
+ container.xy = [(cpoints[0].x..cpoints[1].x).sample( abscissa ),
264
+ (cpoints[0].y..cpoints[1].y).sample( abscissa )]
265
+ end
266
+ return container
267
+ end
268
+
269
+ # redefining to discriminate between @points and map.point
270
+ def points(arg=nil)
271
+ if not arg
272
+ return @points
273
+ else
274
+ super(arg)
275
+ end
276
+ end
277
+
278
+ # compute line tangent at abscissa
279
+ def tangent (abscissa, container=nil)
280
+ container ||= V2D[]
281
+ container.xy = @tangents[abscissa.to_int]
282
+ return container
283
+ end
284
+
285
+ # acc V2D.O
286
+ def acc( abscissa, container=nil )
287
+ container ||= V2D[]
288
+ container.xy = [0.0,0.0]
289
+ return container
290
+ end
291
+
292
+ # compute viewbox of the line
293
+ #
294
+ # simply call V2D.viewbox on :points
295
+ def viewbox
296
+ return V2D.viewbox( @points )
297
+ end
298
+
299
+ # translate a line of v offset, v being a vector
300
+ #
301
+ # return a new line with every point of :points translated
302
+ def translate( v )
303
+ return Line[ :points, @points.map {|ext| ext + v } ]
304
+ end
305
+
306
+ # reverse a line
307
+ #
308
+ # return a new line with :points reversed
309
+ def reverse
310
+ return Line[ :points, @points.reverse ]
311
+ end
312
+
313
+ # return line svg description
314
+ def svg
315
+ path = "M #{points[0].x} #{points[0].y} "
316
+ @points[1..-1].each { |p|
317
+ path += "L #{p.x} #{p.y}"
318
+ }
319
+ return "<path d=\"" + path + "\"/>"
320
+ end
321
+
322
+ include Samplable
323
+ alias apply_sample point
324
+ end
325
+
326
+ # Circle class
327
+ # = Intro
328
+ # define a circle curve
329
+ # = Attributes
330
+ # attribute :center, V2D[0.0,0.0]
331
+ # attribute :radius, 1.0
332
+ # attribute :initangle, 0.0
333
+ # = Example
334
+ # c = Circle[ :center, V2D::O, :radius, 1.0 ] # equiv Circle[]
335
+ class Circle < Curve
336
+ attribute :center, V2D[0.0,0.0]
337
+ attribute :radius, 1.0
338
+ attribute :initangle, 0.0
339
+
340
+ # Circle builder from points diametraly opposed
341
+ # Circle.diameter( V2D[-1.0,0.0], V2D[1.0,0.0] ) == Circle[ :center, V2D::O, :radius, 1.0 ]
342
+ def Circle.diameter( p1, p2 )
343
+ initangle = ( p1 - p2 ).angle
344
+ return Circle[ :center, (p1 + p2)/2.0, :radius, (p1 - p2).r/2.0, :initangle, initangle ]
345
+ end
346
+
347
+ # compute length of the circle
348
+ def length
349
+ if not @length
350
+ @length = 2.0 * Math::PI * self.radius
351
+ end
352
+ return @length
353
+ end
354
+
355
+ # shortcut method to retun center.x
356
+ def cx
357
+ return self.center.x
358
+ end
359
+
360
+ # shortcut method to retun center.y
361
+ def cy
362
+ return self.center.y
363
+ end
364
+
365
+ # viewbox of the circle
366
+ def viewbox
367
+ return [ self.cx - self.radius,
368
+ self.cy - self.radius,
369
+ self.cx + self.radius,
370
+ self.cy + self.radius ]
371
+ end
372
+
373
+ # size of the circle
374
+ def size
375
+ return [ self.radius, self.radius ]
376
+ end
377
+
378
+ def rotate( angle )
379
+ return Circle[:center, self.center, :radius, self.radius, :initangle, self.initangle + angle]
380
+ end
381
+
382
+ # svg description of the circle
383
+ def svg
384
+ template = '<circle cx="%cx%" cy="%cy%" r="%r%"/>'
385
+ return template.subreplace( {"%cx%" => cx,
386
+ "%cy%" => cy,
387
+ "%r%" => radius} )
388
+ end
389
+
390
+ # compute point at abscissa
391
+ def point (abscissa, container=nil)
392
+ angle = Range::Angle.sample( abscissa ) + @initangle
393
+ container ||=V2D[]
394
+ container.x = self.cx + self.radius * Math.cos( angle )
395
+ container.y = self.cy + self.radius * Math.sin( angle )
396
+ return container
397
+ end
398
+
399
+ # compute tangent at abscissa
400
+ def tangent( abscissa, container=nil )
401
+ angle = Range::Angle.sample( abscissa ) + @initangle
402
+ container ||=V2D[]
403
+ container.x = -self.radius * Math.sin( angle )
404
+ container.y = self.radius * Math.cos( angle )
405
+ return container
406
+ end
407
+
408
+ # compute acc at abscissa
409
+ def acc( abscissa, container=nil )
410
+ angle = Range::Angle.sample( abscissa ) + @initangle
411
+ container ||=V2D[]
412
+ container.x = -self.radius * Math.cos( angle )
413
+ container.y = -self.radius * Math.sin( angle )
414
+ return container
415
+ end
416
+
417
+
418
+ include Samplable
419
+ alias apply_sample point
420
+ end
421
+ end
data/lib/spiral.rb ADDED
@@ -0,0 +1,89 @@
1
+ # File dedicated to Spiral curves
2
+ # - Spiral
3
+ # - SpiralParam
4
+ # - SpiralFrise
5
+
6
+ require 'shape'
7
+
8
+ module XRVG
9
+
10
+ # abstract class to define spiral types
11
+ #
12
+ # use compute_radius to compute nsamples reference points, before interpolating with SimpleBezier
13
+ class GSpiral < BezierBuilder
14
+ attribute :center, V2D::O, V2D
15
+ attribute :ext, V2D::O + V2D::X, V2D
16
+ attribute :curvature, 1.0
17
+ attribute :nsamples, 100
18
+ attribute :sens, 1.0
19
+
20
+ def compute_radius( r0, angle0, curvature, angle )
21
+ raise NotImplementedError.new("#{self.class.name}#compute_radius is an abstract method.")
22
+ end
23
+
24
+ def maxangle( r0, angle0, curvature )
25
+ raise NotImplementedError.new("#{self.class.name}#maxangle is an abstract method.")
26
+ end
27
+
28
+ def compute()
29
+ points = []
30
+ r0, angle0 = (@ext - @center).coords(:polar)
31
+ maxangle = self.maxangle( r0, angle0, @curvature )
32
+ SyncS[(r0..0.0), (angle0..self.sens * maxangle)].samples( self.nsamples ) do |radius, angle|
33
+ r = self.compute_radius( r0, angle0, @curvature, angle )
34
+ point = V2D.polar( r, angle )
35
+ points.push( @center + point )
36
+ end
37
+ return SimpleBezier.build( :support, points ).data
38
+ end
39
+ end
40
+
41
+ # at angle angle0, r = r0
42
+ # at angle angle0 + curvature, r = 0.0
43
+ class SpiralLinear < GSpiral
44
+
45
+ def maxangle( r0, angle0, curvature )
46
+ return (angle0 + curvature)
47
+ end
48
+
49
+ def compute_radius( r0, angle0, curvature, angle )
50
+ return r0 * (1.0 - ( 1.0 + (Math.exp( - ( angle - angle0) / curvature ) ) ) * ( angle - angle0 ) /curvature )
51
+ # return r0 * (1.0 - ( angle - angle0 ) /curvature )
52
+ end
53
+
54
+ end
55
+
56
+ # curvature is in number of tour before center % extremity
57
+ class SpiralLog < GSpiral
58
+
59
+ def nsamples
60
+ return (curvature * 15.0).to_i
61
+ end
62
+
63
+ def compute_radius( r0, angle0, curvature, angle )
64
+ return r0 * Math.exp( - self.sens * (angle - angle0) / curvature )
65
+ end
66
+
67
+ def maxangle( r0, angle0, curvature )
68
+ return angle0 - curvature * Math.log( 0.001 )
69
+ end
70
+
71
+ def SpiralLog.tangent( center, ext, curvature )
72
+ return (center - ext).rotate( -Math.atan( curvature ) )
73
+ end
74
+
75
+ def SpiralLog.fromtangent( tangent, ext, curvature, sens=1.0 )
76
+ radial = tangent.rotate( sens * Math.atan( curvature ) )
77
+ newcenter = ext + radial
78
+ # modelcenter = V2D::O
79
+ # radial = (modelcenter - ext)
80
+ # modeltangent = radial.rotate( -Math.atan( curvature ) )
81
+ # scalefactor = tangent.r / modeltangent.r
82
+ # angle = V2D.angle( modeltangent, tangent )
83
+ # newcenter = (modelcenter - ext).rotate( angle ) * scalefactor + ext
84
+ return SpiralLog[ :center, newcenter, :ext, ext, :curvature, curvature, :sens, sens ]
85
+ end
86
+ end
87
+
88
+ end #XRVG
89
+
data/lib/style.rb ADDED
@@ -0,0 +1,76 @@
1
+ #
2
+ # See +Style+
3
+ #
4
+ require 'attributable'
5
+ require 'utils'
6
+ require 'color'
7
+
8
+ module XRVG
9
+ #
10
+ # Style class
11
+ #
12
+ # Used to define the way an object has to be rendered.
13
+ # For the moment, only the following style attributes are really useful :
14
+ # - attribute :fill, "none", [String, Color, Gradient]
15
+ # - attribute :stroke, "none", [String, Color, Gradient]
16
+ # - attribute :strokewidth, 1.0
17
+ #
18
+ # For example :
19
+ # render.add( Circle[], Style[ :fill, Color.red ] )
20
+ # render.add( Circle[], Style[ :stroke, Color.red ] )
21
+ class Style
22
+ include Attributable
23
+ # attribute :opacity, 1.0
24
+ attribute :fill, "none", [String, Color, Gradient]
25
+ # attribute :fillopacity, 1.0
26
+ attribute :stroke, "none", [String, Color, Gradient]
27
+ attribute :strokewidth, 1.0
28
+ # attribute :strokeopacity, 1.0
29
+
30
+ def fillopacity()
31
+ if @fill.is_a? Color
32
+ return @fill.a
33
+ end
34
+ return 1.0
35
+ end
36
+
37
+ def strokeopacity()
38
+ if @stroke.is_a? Color
39
+ return @stroke.a
40
+ end
41
+ return 1.0
42
+ end
43
+
44
+
45
+ def svgfill
46
+ if fill.is_a? Color
47
+ return fill.svg
48
+ elsif fill.is_a? Gradient
49
+ return "%fillgradient%"
50
+ else
51
+ return fill
52
+ end
53
+ end
54
+
55
+ def svgstroke
56
+ if stroke.is_a? Color
57
+ return stroke.svg
58
+ elsif stroke.is_a? Gradient
59
+ return "%strokegradient%"
60
+ else
61
+ return stroke
62
+ end
63
+ end
64
+
65
+ def svgline
66
+ template = 'style="opacity:%opacity%;fill:%fill%;fill-opacity:%fillopacity%;stroke:%stroke%;stroke-width:%strokewidth%;stroke-opacity:%strokeopacity%"'
67
+
68
+ return template.subreplace( {"%opacity%" => 1.0,
69
+ "%fill%" => svgfill,
70
+ "%fillopacity%" => fillopacity,
71
+ "%stroke%" => svgstroke,
72
+ "%strokewidth%" => strokewidth,
73
+ "%strokeopacity%" => strokeopacity} )
74
+ end
75
+ end
76
+ end
data/lib/utils.rb CHANGED
@@ -277,7 +277,7 @@ class Range
277
277
  end
278
278
 
279
279
  #
280
- # Array extension to synchronize enumerations, and also provide other recurrent services
280
+ # Array extension (see SyncS for enumeration synchronisation)
281
281
  # See this[http://xrvg.rubyforge.org/RubyXRVGExtension.html] for presentation
282
282
  #
283
283
  class Array
@@ -476,22 +476,6 @@ class Array
476
476
  def shuffle
477
477
  return self.sort_by{ rand }
478
478
  end
479
-
480
- # -------------------------------------------------------------
481
- # Samplable interface include and overriding
482
- # -------------------------------------------------------------
483
- include XRVG::Samplable
484
- include XRVG::Splittable
485
-
486
- # FloatFunctor overloading to synchronize content sampling and splitting
487
- def compute( inputs, type, &block )
488
- return self.map {|v| v.compute( inputs, type )}.forzip(nil,&block)
489
- end
490
-
491
- def addfilter( newfilter )
492
- self.each {|v| v.addfilter( newfilter )}
493
- end
494
-
495
479
  end
496
480
 
497
481
  # -------------------------------------------------------------
data/lib/xrvg.rb ADDED
@@ -0,0 +1,47 @@
1
+ # This file has to be included if you want to start a XRVG script
2
+ #
3
+ # Please refer to README for XRVG introduction
4
+
5
+ # XRVG version (used in rakefile)
6
+ XRVG_VERSION = "0.0.8"
7
+
8
+ # XRVG namespace
9
+ module XRVG
10
+ end
11
+
12
+ # Standard Ruby extensions
13
+ require 'enumerator'
14
+
15
+ # XRVG Infrastructure
16
+ require 'trace'
17
+
18
+ # XRVG new mixins
19
+ require 'samplation'
20
+ require 'attributable'
21
+ require 'interpolation'
22
+ require 'parametriclength'
23
+
24
+ # XRVG base class extensions
25
+ require 'utils'
26
+ require 'geometry2D'
27
+ require 'intersection'
28
+
29
+ # XRVG base classes
30
+ require 'color'
31
+ require 'frame'
32
+ require 'shape'
33
+ require 'render'
34
+ require 'bezier'
35
+
36
+ # XRVG extensions
37
+ require 'fitting'
38
+ require 'bezierbuilders'
39
+ require 'beziermotifs'
40
+ require 'beziertools'
41
+ require 'interbezier'
42
+ require 'geovariety'
43
+ require 'spiral'
44
+ # require 'graph'
45
+
46
+
47
+
@@ -52,5 +52,24 @@ class AttributableTest < Test::Unit::TestCase
52
52
  assert_equal( [], obj1.a )
53
53
  end
54
54
 
55
+ def test_error1
56
+ assert_raise(RuntimeError) {AttrClass[:c, 1, :b, 1.0, :toto, 2]}
57
+ end
58
+
59
+ def test_error2
60
+ assert_raise(RuntimeError) {AttrClass[:c, 1, :b, 1, :a, 2]}
61
+ end
62
+
63
+ def test_error3
64
+ assert_raise(RuntimeError) {AttrClass[]}
65
+ end
66
+
67
+ # cannot be tested because cannot declare class in method
68
+ # class B;
69
+ # include XRVG::Attributable;
70
+ # assert_raise(RuntimeError) {attribute :c, nil, :toto}
71
+ # end
72
+ # end
73
+
55
74
  end
56
75
 
data/test/test_bezier.rb CHANGED
@@ -51,6 +51,13 @@ class BezierTest < Test::Unit::TestCase
51
51
  assert_raise(RuntimeError) {Bezier.raws( 1.0, V2D[1.0, 1.0], V2D[1.0, 0.0], V2D[2.0, 0.0] )}
52
52
  end
53
53
 
54
+ def test_builder4
55
+ b = Bezier.vectorreg( V2D::O, V2D[0.1,0.0], V2D::X, V2D[-0.1,0.0] )
56
+ c = LinearBezier[]
57
+ assert_equal( b.pointlist, c.pointlist )
58
+ end
59
+
60
+
54
61
  def test_piece_f
55
62
  piece = BezierSpline[:vector, V2D[0.0, 1.0], V2D[1.0, 1.0], V2D[0.0, 0.0], V2D[1.0, 0.0]]
56
63
  assert_equal( V2D[0.0, 1.0], piece.firstpoint )