xrvg 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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