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,87 @@
1
+ # File for ParametricLength module
2
+
3
+ require 'geometry2D'
4
+
5
+ module XRVG
6
+ # Utilitary module to provide length sampling frrom parameter one
7
+ #
8
+ # Use pointfromparam method to compute points
9
+ module ParametricLength
10
+
11
+ # abstract method to provide range to be sampled to compute samples for length interpolation
12
+ def parameter_range
13
+ raise NotImplementedError.new("#{self.class.name}#parameter_range is an abstract method.")
14
+ end
15
+
16
+ def pointfromparameter( parameter, container )
17
+ raise NotImplementedError.new("#{self.class.name}#pointfromparameter is an abstract method.")
18
+ end
19
+
20
+ ParametricLength::NSAMPLES = 129
21
+
22
+ # compute the length of the bezier curve defined by the points
23
+ #
24
+ # Algo :
25
+ #
26
+ # for the moment, just take a fix number of samples, and some it
27
+ def compute_length_interpolator() #:nodoc:
28
+ sum = 0.0
29
+ previous = nil
30
+ samplelist = [0.0, 0.0]
31
+ new = V2D[0.0,0.0]
32
+ previous = nil
33
+ self.parameter_range.samples( ParametricLength::NSAMPLES ) do |abs|
34
+ self.pointfromparameter( abs, new )
35
+ if previous
36
+ sum+= (new - previous).r
37
+ samplelist += [sum, abs]
38
+ else
39
+ previous = V2D[0.0,0.0]
40
+ end
41
+ previous.x = new.x
42
+ previous.y = new.y
43
+ end
44
+ @length = samplelist[-2]
45
+
46
+ length_interpolator = nil
47
+ if @length == 0.0
48
+ newsamplelist = [0.0,0.0,0.0,1.0]
49
+ invsamplelist = [0.0,0.0,1.0,0.0]
50
+ else
51
+ newsamplelist = []
52
+ invsamplelist = []
53
+ samplelist.foreach do |sum, abs|
54
+ newsamplelist += [sum / @length, abs ]
55
+ invsamplelist += [abs, sum / @length ]
56
+ end
57
+ samplelist = newsamplelist
58
+ end
59
+ @abs_interpolator = InterpolatorQuad.new( :samplelist, invsamplelist )
60
+ return InterpolatorQuad.new( :samplelist, samplelist )
61
+ end
62
+
63
+ def length_interpolator() #:nodoc:
64
+ if @length_interpolator == nil
65
+ @length_interpolator = self.compute_length_interpolator()
66
+ end
67
+ return @length_interpolator
68
+ end
69
+
70
+ def length
71
+ if @length == nil
72
+ self.compute_length()
73
+ end
74
+ return @length
75
+ end
76
+
77
+ def compute_length() #:nodoc:
78
+ self.length_interpolator()
79
+ return @length
80
+ end
81
+
82
+ def parameterfromlength( lvalue ) #:nodoc:
83
+ result = self.length_interpolator.interpolate( lvalue )
84
+ return result
85
+ end
86
+ end
87
+ end
data/lib/render.rb CHANGED
@@ -7,6 +7,7 @@ require 'color'
7
7
  require 'attributable'
8
8
  require 'style'
9
9
 
10
+ module XRVG
10
11
  # Render abstract class
11
12
  #
12
13
  # Is pretty useless for the moment
@@ -79,10 +80,8 @@ class SVGRender < Render
79
80
 
80
81
  def svg_template #:nodoc:
81
82
  return '<?xml version="1.0" standalone="no"?>
82
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
83
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
84
- <svg width="%SIZE%" height="%SIZE%" %VIEWBOX% version="1.1"
85
- xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
83
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
84
+ <svg width="%SIZE%" height="%SIZE%" %VIEWBOX% version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
86
85
  %DEFS%
87
86
  %BACKGROUND%
88
87
  %CONTENT%
@@ -110,10 +109,6 @@ class SVGRender < Render
110
109
  refresh_viewbox( object )
111
110
  end
112
111
 
113
- def adds (objects) #:nodoc:
114
- objects.each { |object| add( object )}
115
- end
116
-
117
112
  def render (object, style=nil) #:nodoc:
118
113
  owidth, oheight = object.size
119
114
 
@@ -268,5 +263,5 @@ class SVGRender < Render
268
263
  # Kernel.system( "ruby", "svg2png.rb", filename(), "2.0" )
269
264
  # Kernel.system( "i_view32", filename().subreplace( ".svg" => ".png" ), "/fs" )
270
265
  end
271
-
266
+ end
272
267
  end
data/lib/samplation.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # The base module is FloatFunctor, used by Samplable and Splittable
4
4
  #
5
5
 
6
+ module XRVG
6
7
  #
7
8
  # Base module to define float lists processing and computation.
8
9
  # = Design principle
@@ -26,11 +27,9 @@ module FloatFunctor
26
27
  #
27
28
  # is private ?
28
29
  def addfilter( newfilter )
29
- # Trace("Sampler addfilter method self #{self.inspect}")
30
30
  if not @subfilter
31
31
  @subfilter = newfilter
32
32
  else
33
- # Trace("Sampler addfilter recurse on subfilter #{@subfilter.inspect}")
34
33
  @subfilter.addfilter( newfilter )
35
34
  end
36
35
  return self
@@ -52,15 +51,13 @@ module FloatFunctor
52
51
 
53
52
  # hook for Array
54
53
  def compute( indata, type, &block )
55
- return self.apply( self.modify( indata ), type, &block )
54
+ return self.apply( self.modify( indata, type ), type, &block )
56
55
  end
57
56
 
58
57
  # hook for rand()
59
58
  def process( indata, type, &block )
60
59
  if not block
61
- outdata = self.compute( indata, type )
62
- # Trace("Samplable#trigger object #{self.inspect} indata #{indata.inspect} outdata #{outdata.inspect}")
63
- return outdata
60
+ return self.compute( indata, type )
64
61
  else
65
62
  self.compute( indata, type, &block )
66
63
  end
@@ -69,18 +66,16 @@ module FloatFunctor
69
66
  # recursive method to compose modifications.
70
67
  #
71
68
  # must not be overloaded
72
- def modify( inputs )
73
- # Trace("Samplable#modify object #{self.inspect} inputs #{inputs.inspect}")
69
+ def modify( inputs, type )
74
70
  if @subfilter
75
- inputs = @subfilter.modify( inputs )
71
+ inputs = @subfilter.modify( inputs, type )
76
72
  end
77
- result = self.transforms( inputs )
78
- # Trace("Samplable#filter object #{self.inspect} inputs #{inputs.inspect} result #{result.inspect}")
73
+ result = self.transforms( inputs, type )
79
74
  return result
80
75
  end
81
76
 
82
77
  # to be overloaded if needed
83
- def transforms( inputs )
78
+ def transforms( inputs, type )
84
79
  return inputs.map {|abs| self.transform( abs )}
85
80
  end
86
81
 
@@ -91,7 +86,6 @@ module FloatFunctor
91
86
 
92
87
  # default generator method
93
88
  def generate( nsamples )
94
- # Trace("Samplable#generate object #{self.inspect} nsamples #{nsamples}")
95
89
  return (0.0..1.0).generate( nsamples )
96
90
  end
97
91
 
@@ -114,7 +108,7 @@ module FloatFunctor
114
108
  def apply( data, type, &block )
115
109
  @applyhash = self.applyhash
116
110
  if not @applyhash.key? type
117
- Kernel::Raise("FloatFunctor::apply no regsitration for type #{type} and object #{object.inspect}")
111
+ Kernel::raise("FloatFunctor::apply no regsitration for type #{type} and object #{self.inspect}")
118
112
  else
119
113
  return self.send(@applyhash[type], data, &block )
120
114
  end
@@ -139,60 +133,15 @@ module FloatFunctor
139
133
  return self.addfilter( SortFilter.new )
140
134
  end
141
135
 
136
+ # alternate filter
137
+ def alternate()
138
+ return self.addfilter( AlternateFilter.new )
139
+ end
140
+
142
141
  # shortcut method to build a sampler from self and a block
143
142
  def filter(samplemethod=:sample,&block)
144
143
  return Filter.new( self, samplemethod, &block )
145
144
  end
146
-
147
-
148
- # -------------------------------------------------------------
149
- # old methods to be refactored
150
- # -------------------------------------------------------------
151
-
152
- # deprecated
153
- #
154
- # ratios sum must be equal to 1.0
155
- def multisamples( nsamples, ratios )
156
- ratiosum = ratios.sum
157
- samplesum = nsamples.sum
158
- ratios = ratios.map {|ratio| ratio / ratiosum}
159
-
160
- rratios = ratios
161
- index = 0
162
- ratios.each do |ratio|
163
- rratios[index] = ratio / nsamples[index]
164
- index += 1
165
- end
166
-
167
- sum = 0.0
168
- samples = [0.0]
169
- periodindex = 0
170
- while sum <= 1.0
171
- sum += rratios[ periodindex ]
172
- if sum > 1.0
173
- break
174
- end
175
- samples += [sum]
176
- periodindex += 1
177
- if periodindex >= rratios.length
178
- periodindex = 0.0
179
- end
180
- end
181
- return self.samples( samples )
182
- end
183
-
184
- # deprecated
185
- #
186
- # TODO : must add gauss parameters
187
- def randgauss()
188
- begin
189
- x1 = 2.0 * Kernel::rand - 1.0
190
- x2 = 2.0 * Kernel::rand - 1.0
191
- w = x1 * x1 + x2 * x2
192
- end while w >= 1.0
193
- w = Math.sqrt( ( -2.0 * Math.log( w ) ) / w )
194
- return 1.0/2.0 * ( 1.0 + x1 * w )
195
- end
196
145
 
197
146
  end
198
147
 
@@ -229,7 +178,8 @@ module Samplable
229
178
  #
230
179
  # is basically .samples([abs]).pop
231
180
  def sample( abs )
232
- return self.apply( self.modify( [abs] ), :sample ).pop
181
+ type = :sample
182
+ return self.apply( self.modify( [abs], type ), type ).pop
233
183
  end
234
184
 
235
185
  # alias for sample( 0.5 )
@@ -255,8 +205,10 @@ module Samplable
255
205
  if not block
256
206
  return inputs.map {|abs| self.apply_sample( abs ) }
257
207
  else
258
- container = self.apply_sample( inputs[0] ); yield container;
259
- inputs[1..-1].each {|abs| yield self.apply_sample( abs, container ) }
208
+ if inputs.length > 0
209
+ container = self.apply_sample( inputs[0] ); yield container;
210
+ inputs[1..-1].each {|abs| yield self.apply_sample( abs, container ) }
211
+ end
260
212
  end
261
213
  end
262
214
 
@@ -265,6 +217,19 @@ module Samplable
265
217
  return abs
266
218
  end
267
219
 
220
+ # method to transform any object into samplable object
221
+ #
222
+ # used to add Attribute type :samplable
223
+ def Samplable.build( value )
224
+ if value.is_a? Array
225
+ return Roller[*value]
226
+ elsif value.is_a? Samplable
227
+ return value
228
+ else
229
+ return Roller[value]
230
+ end
231
+ end
232
+
268
233
  # apply_register( :sample, :apply_samples )
269
234
 
270
235
  end
@@ -290,11 +255,11 @@ module Splittable
290
255
  if nsamples.is_a? Integer
291
256
  nsamples += 1
292
257
  if nsamples < 2
293
- Kernel::Raise("Samplable#split method needs at least two data, instead of #{nsamples}")
258
+ Kernel::raise("Samplable#split method needs at least two data, instead of #{nsamples}")
294
259
  end
295
260
  else
296
261
  if nsamples.size < 2
297
- Kernel::Raise("Samplable#split method needs at least two data, instead of #{nsamples.inspect}")
262
+ Kernel::raise("Samplable#split method needs at least two data, instead of #{nsamples.inspect}")
298
263
  end
299
264
  end
300
265
  return self.trigger( nsamples, :split, &block )
@@ -302,12 +267,18 @@ module Splittable
302
267
 
303
268
  # must not be overriden
304
269
  def split( abs1, abs2 )
305
- return self.apply( self.modify( [abs1,abs2] ), :split ).pop
270
+ type = :split
271
+ return self.apply( self.modify( [abs1,abs2], type ), type ).pop
306
272
  end
307
273
 
308
274
  # to be overloaded if needed
309
- def apply_splits( inputs )
310
- return inputs.pairs.map {|t1,t2| self.apply_split( t1, t2 )}
275
+ def apply_splits( inputs, &block )
276
+ result = inputs.pairs.map {|t1,t2| self.apply_split( t1, t2 )}
277
+ if not block
278
+ return result
279
+ else
280
+ result.each {|v| yield v}
281
+ end
311
282
  end
312
283
 
313
284
  # to be overloaded if needed
@@ -328,11 +299,6 @@ end
328
299
  class Filter
329
300
 
330
301
  include Samplable
331
-
332
- # identity functor
333
- def Filter.identity
334
- return Filter.new(nil,nil) {|x| x}
335
- end
336
302
 
337
303
  # to define a filter on "object" with method "samplemethod", or with a block (exclusive)
338
304
  #
@@ -362,6 +328,7 @@ class Filter
362
328
 
363
329
  end
364
330
 
331
+
365
332
  # RandomFilter class, to resample randomly
366
333
  class RandomFilter < Filter
367
334
  def initialize(*args) #:nodoc:
@@ -370,11 +337,13 @@ class RandomFilter < Filter
370
337
  def transform(abs) #:nodoc:
371
338
  return (0.0..1.0).rand
372
339
  end
373
-
374
- def transforms( inputs ) #:nodoc:
340
+
341
+ def transforms( inputs, type ) #:nodoc:
375
342
  result = inputs.map { |v| self.transform( v ) }
376
- result[0] = 0.0
377
- result[-1] = 1.0
343
+ if type == :split
344
+ result[0] = 0.0
345
+ result[-1] = 1.0
346
+ end
378
347
  return result
379
348
  end
380
349
  end
@@ -384,12 +353,29 @@ class SortFilter < Filter
384
353
  def initialize(*args) #:nodoc:
385
354
  end
386
355
 
387
- def transforms( inputs ) #:nodoc:
356
+ def transforms( inputs, type ) #:nodoc:
388
357
  return inputs.sort
389
358
  end
390
359
 
391
360
  end
392
361
 
362
+ # AlternateFilter, to inverse one value per two
363
+ class AlternateFilter < Filter
364
+ def initialize(range=nil) #:nodoc:
365
+ if range
366
+ self.addfilter( range )
367
+ end
368
+ end
369
+
370
+ def transforms( inputs, type ) #:nodoc:
371
+ result = []
372
+ inputs.foreach do |v1, v2|
373
+ result += [v1, -v2]
374
+ end
375
+ return result
376
+ end
377
+ end
378
+
393
379
  # Experimental class to add discrete processing in float processing chains
394
380
  # Roller["white","black"].samples(3) => ["white","black","white"]
395
381
  class Roller
@@ -418,5 +404,8 @@ class Roller
418
404
  index = (0.0..@items.size).rand.to_i
419
405
  return @items[index]
420
406
  end
407
+ end
421
408
 
409
+ require 'attributable'
410
+ Attribute.addtype( :samplable, Samplable.method("build") )
422
411
  end
data/lib/shape.rb CHANGED
@@ -9,6 +9,7 @@ require 'geometry2D'
9
9
  require 'utils'
10
10
  require 'attributable'
11
11
 
12
+ module XRVG
12
13
  # Shape abstract interface
13
14
  # = Intro
14
15
  # To provide a set of services a shape class must provide
@@ -21,7 +22,7 @@ class Shape
21
22
  #
22
23
  # not yet used
23
24
  def contour( *args )
24
- Kernel::raise("Shape::contour must be redefined in subclasses")
25
+ raise NotImplementedError.new("#{self.class.name}#contour is an abstract method.")
25
26
  end
26
27
 
27
28
  # must return the svg description of the shape
@@ -30,7 +31,7 @@ class Shape
30
31
  #
31
32
  # must be defined
32
33
  def svg()
33
- Kernel::raise("Shape::svg must be redefined in subclasses")
34
+ raise NotImplementedError.new("#{self.class.name}#svg is an abstract method.")
34
35
  end
35
36
 
36
37
  # must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
@@ -39,7 +40,7 @@ class Shape
39
40
  #
40
41
  # must be defined
41
42
  def viewbox()
42
- Kernel::raise("Shape::viewbox must be redefined in subclasses")
43
+ raise NotImplementedError.new("#{self.class.name}#viewbox is an abstract method.")
43
44
  end
44
45
 
45
46
  # compute size of the shape, from viewbox
@@ -48,9 +49,9 @@ class Shape
48
49
  return [xmax-xmin, ymax-ymin]
49
50
  end
50
51
 
51
- # must return the enclosing box of the shape, that is [xmin, ymin, xmax, ymax]
52
+ # return the default style for a Shape instance
52
53
  #
53
- # abstract
54
+ # is done on instance, because for Curve for example, strokewidth is proportional to length
54
55
  def default_style()
55
56
  return Style[:fill, Color.black ]
56
57
  end
@@ -67,7 +68,7 @@ end
67
68
 
68
69
  # Curve abstract interface
69
70
  # = Intro
70
- # To provide a set of services a curve class must provide
71
+ # To define a set of services a curve class must provide
71
72
  class Curve < Shape
72
73
  # must compute the point at curve abscissa
73
74
  #
@@ -75,7 +76,7 @@ class Curve < Shape
75
76
  #
76
77
  # must be defined
77
78
  def point( abscissa, container=nil )
78
- Kernel::raise("Curve::point must be redefined in subclasses")
79
+ raise NotImplementedError.new("#{self.class.name}#curve is an abstract method.")
79
80
  end
80
81
 
81
82
  # must compute the tangent at curve abscissa
@@ -84,7 +85,7 @@ class Curve < Shape
84
85
  #
85
86
  # must be defined
86
87
  def tangent( abscissa, container=nil )
87
- Kernel::raise("Curve::tangent must be redefined in subclasses")
88
+ raise NotImplementedError.new("#{self.class.name}#tangent is an abstract method.")
88
89
  end
89
90
 
90
91
  # must compute the acceleration at curve abscissa
@@ -93,7 +94,7 @@ class Curve < Shape
93
94
  #
94
95
  # must be defined
95
96
  def acc( abscissa, container=nil )
96
- Kernel::raise("Curve::acc must be redefined in subclasses")
97
+ raise NotImplementedError.new("#{self.class.name}#acc is an abstract method.")
97
98
  end
98
99
 
99
100
  # must return the length at abscissa, or total length if abscissa nil
@@ -102,7 +103,7 @@ class Curve < Shape
102
103
  #
103
104
  # must be defined
104
105
  def length(abscissa=nil)
105
- Kernel::raise("Curve::length must be redefined in subclasses")
106
+ raise NotImplementedError.new("#{self.class.name}#length is an abstract method.")
106
107
  end
107
108
 
108
109
  # default style of a curve, as stroked with stroke width 1% of length
@@ -127,7 +128,7 @@ class Curve < Shape
127
128
  end
128
129
  result = 0.0
129
130
  if not self.tangent0_length == 0.0
130
- result = (tangent.r - self.tangent0_length)
131
+ result = (tangent.r / self.tangent0_length)
131
132
  end
132
133
  return result
133
134
  end
@@ -157,7 +158,7 @@ class Curve < Shape
157
158
 
158
159
  # compute frame vector at abscissa t, that is [curve.point( t ), curve.tangent( t ) ]
159
160
  def framev( t )
160
- return [curve.point( t ), curve.tangent( t ) ]
161
+ return [self.point( t ), self.tangent( t ) ]
161
162
  end
162
163
 
163
164
  # compute frame at abscissa t
@@ -212,6 +213,7 @@ class Curve < Shape
212
213
 
213
214
  end
214
215
 
216
+
215
217
  # Line class
216
218
  # = Intro
217
219
  # Used to draw polylines and polygons
@@ -264,11 +266,13 @@ class Line < Curve
264
266
  return container
265
267
  end
266
268
 
267
- # compute line frame at abscissa
268
- #
269
- # for the moment, frame rotation is always 0.0, and scale always 1.0
270
- def frame (abscissa)
271
- return Frame[ :center, self.point( abscissa ), :vector, V2D[ 0.0, 0.0 ], :rotation, 0.0, :scale, 1.0 ]
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
272
276
  end
273
277
 
274
278
  # compute line tangent at abscissa
@@ -278,6 +282,13 @@ class Line < Curve
278
282
  return container
279
283
  end
280
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
+
281
292
  # compute viewbox of the line
282
293
  #
283
294
  # simply call V2D.viewbox on :points
@@ -289,7 +300,7 @@ class Line < Curve
289
300
  #
290
301
  # return a new line with every point of :points translated
291
302
  def translate( v )
292
- return Line[ :points, @points.map {|ext| ext.translate( v )} ]
303
+ return Line[ :points, @points.map {|ext| ext + v } ]
293
304
  end
294
305
 
295
306
  # reverse a line
@@ -365,7 +376,7 @@ class Circle < Curve
365
376
  end
366
377
 
367
378
  def rotate( angle )
368
- @initangle += angle
379
+ return Circle[:center, self.center, :radius, self.radius, :initangle, self.initangle + angle]
369
380
  end
370
381
 
371
382
  # svg description of the circle
@@ -386,14 +397,25 @@ class Circle < Curve
386
397
  end
387
398
 
388
399
  # compute tangent at abscissa
389
- #
390
- # TODO
391
- def tangent( abscissa )
392
- TODO
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
393
415
  end
394
416
 
417
+
395
418
  include Samplable
396
419
  alias apply_sample point
397
-
398
420
  end
399
-
421
+ end