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.
data/CHANGES CHANGED
@@ -1,12 +1,20 @@
1
1
  = CHANGES
2
2
 
3
- == 0.0.8 (2008.08.26) (Future)
3
+ == 0.0.8 (2008.08.26)
4
+ === Content
5
+ - unitary tests completion
6
+ - new spiral builder from tangent (spiral refactoring to be committed soon...)
4
7
  - INCOMPATIBLE modifs:
5
8
  - Array is no longer SAMPLABLE => you must remplace "[].geo(5.0).samples" by "SyncS[].geo( 5.0 )"
6
9
  this has been done to avoid conflicts between Array and Samplation interface, namely .rand method,
7
10
  but also to clean semantic of the operation
11
+ and to provide shortcuts (as automatic Roller for arrays to be samples in a synchronized way...)
8
12
  and to be able to upgrade SyncS operations (with .split operator,...)
9
- == 0.0.7 (2008.08.24) (New)
13
+ === TODO
14
+ - tutorials need to be updated with the INCOMPATIBLE modif
15
+ - spiral class must be considered as curve, to redefine point and tangent computation, and to provide access to parameters
16
+
17
+ == 0.0.7 (2008.08.24)
10
18
  === Content
11
19
  - Add color services (hsv, hsl, ...)
12
20
  - Add intersection services (Beta for the moment, must be consolidated)
@@ -26,7 +26,7 @@ roots = [ArcBezier[ :support, [V2D::O, V2D::X]]]
26
26
  palette = Palette[ :colorlist, [ 0.0, Color.black,
27
27
  1.0, Color.blue]]
28
28
  niter = 6
29
- [(0.0..1.0),(0.01..0.001),palette].samples( niter ) do |time,width,color|
29
+ SyncS[(0.0..1.0),(0.01..0.001),palette].samples( niter ) do |time,width,color|
30
30
  time = (niter * time).to_i
31
31
  style.stroke = color
32
32
  style.strokewidth = width
@@ -13,7 +13,7 @@ palette = Palette[ :colorlist, [ 0.0, Color.black,
13
13
  0.5, Color.blue,
14
14
  1.0, Color.white]]
15
15
  style = Style.new( :strokewidth, 0.01 )
16
- [gbezier, palette].samples( 10 ) do |bezier, color|
16
+ SyncS[gbezier, palette].samples( 10 ) do |bezier, color|
17
17
  style.fill = color
18
18
  style.stroke = color
19
19
  render.add( bezier, style )
@@ -2,7 +2,7 @@ require 'xrvg'
2
2
  include XRVG
3
3
 
4
4
  render = SVGRender[ :filename, "hellocrown2.svg" ]
5
- [Circle[], (0.2..0.1)].samples( 10 ) do |point, radius|
5
+ SyncS[Circle[], (0.2..0.1)].samples( 10 ) do |point, radius|
6
6
  render.add( Circle[:center, point, :radius, radius ], Style[ :fill, Color.blue( 0.5 ) ] )
7
7
  end
8
8
  render.end
@@ -4,7 +4,7 @@ include XRVG
4
4
  render = SVGRender[ :filename, "palette_circle.svg", :background, "white" ]
5
5
 
6
6
  palette = Palette[ :colorlist, [ 0.0, Color.black, 1.0 , Color.blue] ]
7
- [Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
7
+ SyncS[Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
8
8
  render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
9
9
  end
10
10
 
data/examples/sample.rb CHANGED
@@ -6,7 +6,7 @@ render = SVGRender[ :filename, "sample.svg" ]
6
6
  palette = Palette[ :colorlist, [ 0.0, Color.blue, 0.25, Color.orange,
7
7
  0.5, Color.yellow, 0.75, Color.green,
8
8
  1.0, Color.blue] ]
9
- [Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
9
+ SyncS[Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
10
10
  render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
11
11
  end
12
12
 
data/lib/bezier.rb ADDED
@@ -0,0 +1,588 @@
1
+ # +Bezier+ source
2
+ #
3
+
4
+ require 'bezierspline'
5
+ require 'shape'
6
+
7
+ module XRVG
8
+ # = Base class for cubic bezier curves
9
+ # == Basics
10
+ # See http://en.wikipedia.org/wiki/B%C3%A9zier_curve
11
+ # == Examples
12
+ # Basically, a Bezier curve is a multi-pieces cubic bezier curve. As a first example, you can create bezier curves as follows :
13
+ # b = Bezier[ :pieces, [[:raw, p1, pc1, pc2, p2]] ]; # raw description, as SVG
14
+ # b = Bezier[ :pieces, [[:vector, p1, v1, p2, v2]] ]; # more "symetrical" description.
15
+ # For more extensive description, see http://xrvg.rubyforge.org/XRVGBezierCurve.html
16
+ # == Discussion
17
+ # In XRVG, bezier curves must be also viewed as a way to create smooth and non linear interpolation between values (see +Interpolation+)
18
+ #
19
+ # Other point : to run along a bezier curve, you can use two different parametrization :
20
+ # - the curve generic one, that is "curviligne" abscissa, that is length
21
+ # - the bezier parameter, as bezier curves are parametrized curves. For multi-pieces curve, by extension, parameter goes from one integer value to the next one
22
+ # As Bezier class provides several methods with a parameter input, it is necessary to specify with parameter type you want to use ! For example,
23
+ # to compute a point from a bezier curve, Bezier class defines the point method as follows :
24
+ # def point( t, parametertype=:length )
25
+ # This is a general declaration : every method with a parameter input will propose such a kind of interface :
26
+ # - t as Float parameter value
27
+ # - parametertype, by default :length, that can have two values, :length or :parameter. :parameter is kept because is far faster than other indexation.
28
+ # == Attributes
29
+ # attribute :pieces
30
+ class Bezier < Curve
31
+ attribute :pieces
32
+
33
+ # -------------------------------------------------------------
34
+ # builders
35
+ # -------------------------------------------------------------
36
+
37
+ # Initialize with the Attributable format
38
+ #
39
+ #
40
+ # Licit formats :
41
+ # b = Bezier.new( :pieces, [BezierSpline[:raw, p1, pc1, pc2, p2]] )
42
+ # b = Bezier[ :pieces, [BezierSpline[:vector, p1, v1, p2, v2]] ]
43
+ # However, prefer the use of the following builders
44
+ # b = Bezier.vector( p1, v1, p2, v2 )
45
+ # b = Bezier.raw( p1, pc1, pc2, p2 )
46
+ # b = Bezier.single( :raw, p1, pc1, pc2, p2 )
47
+ # b = Bezier.multi( [[:raw, p1, pc1, pc2, p2], [:raw, p1, pc1, pc2, p2]] )
48
+ # The two last syntaxes are provided as shortcuts, as used quite frequently, and must be used instead of :pieces attributable builder
49
+ def Bezier.[]( *args )
50
+ self.new( *args )
51
+ end
52
+
53
+ # Uni Bezier builder
54
+ #
55
+ # type can be :raw or :vector
56
+ def Bezier.single( type, p1, p2, p3, p4 )
57
+ return Bezier.new( :pieces, [BezierSpline[type, p1, p2, p3, p4]] )
58
+ end
59
+
60
+ # Uni Bezier builder in :raw format
61
+ def Bezier.raw( p1, pc1, pc2, p2 )
62
+ return Bezier.single( :raw, p1, pc1, pc2, p2 )
63
+ end
64
+
65
+ # Uni Bezier builder in :vector format
66
+ def Bezier.vector( p1, v1, p2, v2 )
67
+ return Bezier.single( :vector, p1, v1, p2, v2 )
68
+ end
69
+
70
+ # Uni Bezier builder in :vector format, with vector length normalized at 1/3 dist between points
71
+ def Bezier.vectorreg( p1, v1, p2, v2 )
72
+ d = (p1 - p2).r
73
+ v1 = v1.norm * d/3.0
74
+ v2 = v2.norm * d/3.0
75
+ return Bezier.vector( p1, v1, p2, v2 )
76
+ end
77
+
78
+
79
+ # Basic Multi Bezier builder
80
+ #
81
+ # raw pieces must be an array of arrays of the form [type, p1, p2, p3, p4] as defined for single
82
+ def Bezier.multi( rawpieces )
83
+ return Bezier.new( :pieces, rawpieces.map {|piece| BezierSpline[*piece]} )
84
+ end
85
+
86
+ # "regular" Multi Bezier :raw specification
87
+ #
88
+ # args is a list of points and control points as [p1, pc1, p2, pc2, p3, pc3]
89
+ #
90
+ # Beware that
91
+ # Bezier.raw( p1, pc1, pc2, p2 ) == Bezier.raws( p1, pc1, p2, p2 + (p2-pc2))
92
+ def Bezier.raws( *args )
93
+ rawpieces = []
94
+ args.foreach(2).pairs do |pair1, pair2|
95
+ p1, pc1 = pair1
96
+ p2, pc2 = pair2
97
+ rawpieces << [:raw, p1, pc1, p2+(p2-pc2), p2]
98
+ end
99
+ return Bezier.multi( rawpieces )
100
+ end
101
+
102
+ # "regular" Multi Bezier :vector specification
103
+ #
104
+ # args is a list of points and control points as [p1, v1, p2, v2, p3, v3]
105
+ #
106
+ # Beware that
107
+ # Bezier.vector( p1, v1, p2, v2 ) == Bezier.vectors( p1, v1, p2, -v2)
108
+ def Bezier.vectors( *args )
109
+ rawpieces = []
110
+ args.foreach(2).pairs do |pair1, pair2|
111
+ p1, v1 = pair1
112
+ p2, v2 = pair2
113
+ rawpieces << [:vector, p1, v1, p2, -v2]
114
+ end
115
+ return Bezier.multi( rawpieces )
116
+ end
117
+
118
+
119
+
120
+
121
+ # bezier point, as
122
+ # Bezier[:raw, V2D::O, V2D::O, V2D::O, V2D::O]
123
+ O = Bezier.raw( V2D::O, V2D::O, V2D::O, V2D::O )
124
+
125
+
126
+ # return piece of index "index",as BezierSpline object
127
+ #
128
+ # index can be
129
+ # - integer : in that case, simple return @pieces[index]
130
+ # - float : in that case, use second default argument
131
+ # this method must actually be very rarely called, as usually
132
+ # we want to compute something with index, and in that case we
133
+ # want to delegate computation to a BezierSpline, with parameter
134
+ # mapping parametermapping
135
+ def piece( index, parametertype=:length )
136
+ pieceindex = index
137
+ if index.is_a? Float
138
+ pieceindex, t = self.parametermapping( index, parametertype )
139
+ end
140
+ return @pieces[ pieceindex ]
141
+ end
142
+
143
+ # return number of pieces
144
+ def piecenumber
145
+ return @pieces.length
146
+ end
147
+
148
+ # -------------------------------------------------------------
149
+ # curve interface
150
+ # -------------------------------------------------------------
151
+
152
+ # overload Curve::viewbox
153
+ def viewbox
154
+ return V2D.viewbox( self.pointlist() )
155
+ end
156
+
157
+
158
+ # -------------------------------------------------------------
159
+ # piece shortcuts
160
+ # -------------------------------------------------------------
161
+
162
+ # generic method to return points list of a curve
163
+ # b = Bezier[ :pieces, [[:raw, p1, pc1, pc2, p2], [:raw, p2, pc2b, pc3, p3]] ]
164
+ # b.pointlist #=> equiv to b.pointlist(:raw)
165
+ # b.pointlist(:raw) #=> [p1, pc1, pc2, p2, p2, pc2b, pc3, p3]
166
+ # if you want to get a particular piece pointlist, do
167
+ # b.piece( t ).pointlist(nil|:raw|:vector)
168
+ # TODO : result must be cached by type
169
+ def pointlist( type=:raw )
170
+ result = []
171
+ @pieces.each {|piece| result = result + piece.pointlist(type)}
172
+ # Trace("Bezier.pointlist result #{result.inspect}")
173
+ return result
174
+ end
175
+
176
+ # shortcut method to get curve first point
177
+ def firstpoint
178
+ return self.pointlist()[0]
179
+ end
180
+
181
+ # shortcut method to get curve last point
182
+ def lastpoint
183
+ return self.pointlist()[-1]
184
+ end
185
+
186
+ # shortcut method to build Bezier objects for each piece
187
+ def beziers
188
+ return self.pieces.map{ |piece| Bezier.single( *piece.data ) }
189
+ end
190
+
191
+ # shortcut method to retrieve a piece as an Bezier object
192
+ def bezier( index )
193
+ return Bezier.single( *self.piece( index ).data )
194
+ end
195
+
196
+ # shortcut method to retrieve data list of a bezier
197
+ def data
198
+ return self.pieces.map{ |piece| piece.data }
199
+ end
200
+
201
+
202
+ # -------------------------------------------------------------
203
+ # piece delegation computation
204
+ # -------------------------------------------------------------
205
+
206
+ # with index (must be Float) and parametertype as inputs, must compute :
207
+ # - the index of the piece on which the computation must take place
208
+ # - the new parameter value corresponding to bezier computation input
209
+ def parametermapping( index, parametertype=:length, side=:right ) #:nodoc:
210
+ check_parametertype( parametertype )
211
+ result = []
212
+ if parametertype == :length
213
+ index = (0.0..1.0).trim( index )
214
+ result = length_parameter_mapping( index, side )
215
+ else # no need to test parametertype value, as check_parametertype already do it
216
+ index = (0.0..self.piecenumber.to_f).trim( index )
217
+ pieceindex = index < self.piecenumber ? index.to_i : (index-1).to_i
218
+ t = index - pieceindex
219
+ result = [pieceindex, t]
220
+ end
221
+
222
+ return result
223
+ end
224
+
225
+ # utilitary method to factorize abscissa parameter type value checking
226
+ def check_parametertype( parametertype ) #:nodoc:
227
+ if !(parametertype == :parameter or parametertype == :length )
228
+ Kernel::raise("Invalid parametertype value #{parametertype}")
229
+ end
230
+ end
231
+
232
+ # -------------------------------------------------------------
233
+ # bezier computations
234
+ # -------------------------------------------------------------
235
+
236
+ # compute a point at curviligne abscissa or parameter t
237
+ #
238
+ # curve method redefinition
239
+ def point( t, container=nil, parametertype=:length )
240
+ pieceindex, t = parametermapping( t, parametertype )
241
+ return self.piece( pieceindex ).point( t, container )
242
+ end
243
+
244
+ # compute tangent at curviligne abscissa or parameter t
245
+ #
246
+ # curve method redefinition
247
+ def tangent ( t, container=nil, parametertype=:length )
248
+ pieceindex, t = parametermapping( t, parametertype )
249
+ return self.piece( pieceindex ).tangent( t, container )
250
+ end
251
+
252
+ # compute acceleration at curviligne abscissa or parameter t
253
+ #
254
+ # curve method redefinition
255
+ def acc( t, container=nil, parametertype=:length )
256
+ pieceindex, t = parametermapping( t, parametertype )
257
+ return self.piece( pieceindex ).acc( t, container )
258
+ end
259
+
260
+ # curve method redefinition to factorize parametermapping
261
+ def frame( t, container=nil, parametertype=:length )
262
+ pieceindex, t = parametermapping( t, parametertype )
263
+ containerpoint = container ? container.center : nil
264
+ containertangent = container ? container.vector : nil
265
+ point = self.piece( pieceindex ).point( t, containerpoint )
266
+ tangent = self.piece( pieceindex ).tangent( t, containertangent )
267
+ rotation = self.rotation( nil, tangent )
268
+ scale = self.scale( nil, tangent )
269
+ result = container ? container : Frame[ :center, point, :vector, tangent, :rotation, rotation, :scale, scale ]
270
+ return result
271
+ end
272
+
273
+ # -------------------------------------------------------------
274
+ # subpiece computation
275
+ # -------------------------------------------------------------
276
+
277
+ # generalize Bezier method
278
+ def subpieces (t1, t2) #:nodoc:
279
+ # Trace("subpieces t1 #{t1} t2 #{t2}")
280
+ pieceindex1, t1 = parametermapping( t1, :length, :left )
281
+ pieceindex2, t2 = parametermapping( t2, :length, :right )
282
+ # Trace("after translation pieceindex1 #{pieceindex1} t1 #{t1} pieceindex2 #{pieceindex2} t2 #{t2}")
283
+ result = []
284
+
285
+ if pieceindex1 == pieceindex2
286
+ result = [self.piece( pieceindex1 ).subpiece( t1, t2 )]
287
+ else
288
+ result << self.piece( pieceindex1 ).subpiece( t1, 1.0 )
289
+ if pieceindex1 + 1 != pieceindex2
290
+ result += self.pieces[pieceindex1+1..pieceindex2-1]
291
+ end
292
+ result << self.piece( pieceindex2 ).subpiece( 0.0, t2 )
293
+ end
294
+ return result
295
+ end
296
+
297
+ # compute the sub curve between abscissa t1 and t2
298
+ #
299
+ # may result in a multi-pieces Bezier
300
+ #
301
+ # Note: special modulo effect to deal with closed bezier curve
302
+ #
303
+ # TODO: improve code (bas design)
304
+ def subbezier( t1, t2)
305
+ # return Bezier.new( :pieces, self.subpieces( t1, t2 ) )
306
+ ranges = (0.0..1.0).modulos( (t1..t2) )
307
+ # Trace("Bezier::subbezier t1 #{t1} t2 #{t2} ranges #{ranges.inspect}")
308
+ pieces = []
309
+ ranges.each do |range|
310
+ range = range.sort
311
+ pieces += self.subpieces( range.begin, range.end )
312
+ end
313
+
314
+ bezier = Bezier.new( :pieces, pieces )
315
+ if t1 > t2
316
+ bezier = bezier.reverse
317
+ end
318
+ return bezier
319
+ end
320
+
321
+ # -------------------------------------------------------------
322
+ # reverse
323
+ # -------------------------------------------------------------
324
+
325
+ # return a new Bezier curve reversed from current one
326
+ #
327
+ # simply reverse BezierSpline pieces, both internally and in the :pieces list
328
+ def reverse
329
+ newpieces = @pieces.map {|piece| piece.reverse()}
330
+ return Bezier.new( :pieces, newpieces.reverse )
331
+ end
332
+
333
+ # -------------------------------------------------------------
334
+ # translation
335
+ # -------------------------------------------------------------
336
+
337
+ # translate the Bezier curve, by translating its points
338
+ def translate( v )
339
+ return Bezier.new( :pieces, @pieces.map { |piece| piece.translate( v ) } )
340
+ end
341
+
342
+ # rotate the Bezier curve, by rotating its points
343
+ def rotate( angle, center=V2D::O )
344
+ return Bezier.new( :pieces, @pieces.map { |piece| piece.rotate( angle, center ) } )
345
+ end
346
+
347
+ # central symetry
348
+ def sym( center )
349
+ return Bezier.new( :pieces, @pieces.map { |piece| piece.sym( center ) } )
350
+ end
351
+
352
+ # axis symetry
353
+ def axesym( point, v )
354
+ return Bezier.new( :pieces, @pieces.map { |piece| piece.axesym( point, v ) } )
355
+ end
356
+
357
+ # -------------------------------------------------------------
358
+ # similar (transform is used for samplation)
359
+ # see XRVG#33
360
+ # -------------------------------------------------------------
361
+
362
+ # "Similitude" geometric transformation
363
+ #
364
+ # See http://en.wikipedia.org/wiki/Similitude_%28geometry%29
365
+ #
366
+ # Similtude geometric transformation is (firspoint..lastpoint) -> pointrange
367
+ #
368
+ # TODO : method must be put in Curve interface
369
+ def similar( pointrange )
370
+ oldRepere = [self.firstpoint, self.lastpoint - self.firstpoint]
371
+ newRepere = [pointrange.begin, pointrange.end - pointrange.begin]
372
+ rotation = V2D.angle( newRepere[1], oldRepere[1] )
373
+ if oldRepere[1].r == 0.0
374
+ Kernel::raise("similar error : bezier is length 0")
375
+ end
376
+ scale = newRepere[1].r / oldRepere[1].r
377
+ newpoints = []
378
+ self.pointlist.each do |oldpoint|
379
+ oldvector = oldpoint - oldRepere[0]
380
+ newvector = oldvector.rotate( rotation ) * scale
381
+ newpoints.push( newRepere[0] + newvector )
382
+ end
383
+ splines = []
384
+ newpoints.foreach do |p1, p2, p3, p4|
385
+ splines.push( BezierSpline[:raw, p1, p2, p3, p4] )
386
+ end
387
+ return Bezier[ :pieces, splines ]
388
+ end
389
+
390
+ # -------------------------------------------------------------
391
+ # concatenation
392
+ # -------------------------------------------------------------
393
+
394
+ # Bezier curve concatenation
395
+ def +( other )
396
+ return Bezier.new( :pieces, self.pieces + other.pieces )
397
+ end
398
+
399
+ # -------------------------------------------------------------
400
+ # svg
401
+ # -------------------------------------------------------------
402
+
403
+ # return the svg description of the curve
404
+ #
405
+ # if firstpoint == lastpoint, curve is considered as closed
406
+ def svg()
407
+ # Trace("Bezier::svg #{self.inspect}")
408
+ path = ""
409
+ previous = nil
410
+ self.pieces().each do |piece|
411
+ p1, pc1, pc2, p2 = piece.pointlist
412
+ # Trace("previous #{previous.inspect} p1 #{p1.inspect}")
413
+ if previous == nil or not (previous - p1).r <= 0.0000001
414
+ # Trace("svg bezier not equal => M")
415
+ path += "M #{p1.x},#{p1.y}"
416
+ end
417
+ previous = p2
418
+ path += "C #{pc1.x},#{pc1.y} #{pc2.x},#{pc2.y} #{p2.x},#{p2.y}"
419
+ end
420
+
421
+ if self.firstpoint == self.lastpoint
422
+ path += " z"
423
+ end
424
+
425
+ result = "<path d=\"#{path}\"/>"
426
+ return result
427
+ end
428
+
429
+ # -------------------------------------------------------------
430
+ # gdebug
431
+ # -------------------------------------------------------------
432
+
433
+ # Display Bezier curve decorated with points and control points
434
+ def gdebug(render)
435
+ self.pieces.each {|piece| piece.gdebug(render)}
436
+ end
437
+
438
+
439
+ # -------------------------------------------------------------
440
+ # length computation
441
+ # -------------------------------------------------------------
442
+
443
+ # return the length of the bezier curve
444
+ #
445
+ # simply add pieces lengths
446
+ def length
447
+ if not @length
448
+ @length = compute_length
449
+ end
450
+ return @length
451
+ end
452
+
453
+ # Note : lengthranges building should be more functional ...
454
+ # must use an interpolator ?
455
+ def compute_length #:nodoc:
456
+ lengths = self.pieces.map {|piece| piece.length}
457
+ # Trace("pieces #{self.pieces.inspect} lenghts #{lengths.inspect}")
458
+ result = lengths.sum
459
+ if result == 0.0
460
+ lengths = [1.0]
461
+ else
462
+ psum = 0.0
463
+ lengths = lengths.map {|v| psum += v/result}
464
+ end
465
+ lmin = 0.0
466
+ @lengthranges = []
467
+ lengths.each do |llength|
468
+ # Trace("lmin #{lmin} llength #{llength}")
469
+ @lengthranges << (lmin..llength)
470
+ lmin = llength
471
+ end
472
+ @lengthranges[-1] = @lengthranges[-1].begin..1.0
473
+ return result
474
+ end
475
+
476
+ # utilitary method for interbezier
477
+ #
478
+ # return list of piece lengths relatives to total bezier lengths, and cumulated
479
+ # Bezier.new( :pieces, [piece1, piece2] ).piecelengths; => [0.0, piece1.length / bezier.length, 1.0]
480
+ def piecelengths
481
+ result = self.lengthranges.map {|range| range.begin}
482
+ result << 1.0
483
+ return result
484
+ end
485
+
486
+ # private method, to compute lengthranges if not existing
487
+ def lengthranges #:nodoc:
488
+ if not @lengthranges
489
+ compute_length
490
+ end
491
+ return @lengthranges
492
+ end
493
+
494
+ def length_parameter_mapping( t, side ) #:nodoc:
495
+ pieceindex = -1
496
+ # Trace("self.lengthranges #{self.lengthranges.inspect}")
497
+ self.lengthranges.each_with_index do |lrange,i|
498
+ if lrange.include?( t )
499
+ pieceindex = i
500
+ t = lrange.abscissa( t )
501
+ t = self.piece( i ).parameterfromlength( t )
502
+ # Trace("pieceindex #{pieceindex} t #{t}")
503
+ break
504
+ end
505
+ end
506
+ if ((side == :left) and t.fequal?(1.0) and (pieceindex < self.piecenumber - 1))
507
+ pieceindex += 1
508
+ t = 0.0
509
+ end
510
+ if pieceindex == -1
511
+ Kernel::raise("length_parameter_mapping error : t #{t} is not in length range #{self.lengthranges.inspect}")
512
+ end
513
+ return [pieceindex, t]
514
+ end
515
+
516
+
517
+ # return a list of bezier params [a1,a2, b1, b2 ...] stating intersection points for each curve
518
+ # Algo:
519
+ # - compute segment approximations of 2 curves, by keeping parameter values
520
+ # - for the moment, brute algorithm: try to match every segment of one curve with every of the other
521
+ # - for every match, dychotomie to get precise parameter value
522
+ def Bezier.intersections( c1, c2 )
523
+ samples = Range.O.samples( 100 )
524
+
525
+ # sample curves
526
+ vs = []
527
+ [c1,c2].each do |c|
528
+ subvs = []
529
+ samples.each do |t|
530
+ subvs << [t, c.point( t )]
531
+ end
532
+ vs << subvs
533
+ end
534
+
535
+ # detect segment intersections and keep corresponding coords
536
+ rawresult = []
537
+ vs[0].pairs do |s01, s02|
538
+ vs[1].pairs do |s11, s12|
539
+ inter = V2DS[s01[1], s02[1]].intersection( V2DS[s11[1], s12[1]] )
540
+ if inter
541
+ # compute distances rapport
542
+ r0 = (inter - s01[1]).r / (s02[1] - s01[1]).r
543
+ r1 = (inter - s11[1]).r / (s12[1] - s11[1]).r
544
+ rawresult += [s01[0], s02[0], r0, s11[0], s12[0], r1]
545
+ end
546
+ end
547
+ end
548
+
549
+ Trace("rawresult #{rawresult.inspect}")
550
+
551
+ # compute accurately coords
552
+ # for the moment, just return mean
553
+ result = []
554
+ rawresult.foreach do |t1, t2, r12, t3, t4, r34|
555
+ result += [(t1..t2).sample( r12 ), (t3..t4).sample( r34 )]
556
+ end
557
+
558
+ return result
559
+ end
560
+
561
+ # -------------------------------------------------------------
562
+ # sampler computation
563
+ # -------------------------------------------------------------
564
+ include Samplable
565
+ include Splittable
566
+
567
+ # filter, sampler methods
568
+ #
569
+ # just a shortcut to define easily specific sampler on bezier curve
570
+ #
571
+ # TODO : must be defined on Curve interface !!
572
+ def filter(type=:point, &block)
573
+ return super(type, &block).addfilter( (0.0..1.0) )
574
+ end
575
+
576
+ def apply_split( t1, t2 ) #:nodoc:
577
+ return self.subbezier( t1, t2 )
578
+ end
579
+
580
+ alias apply_sample point
581
+ # alias apply_split subbezier
582
+ alias sampler filter
583
+
584
+ # TODO : add generic bezier builder from points : must be adaptative !! (use Fitting)
585
+ end
586
+ end
587
+
588
+