xrvg 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bezier.rb DELETED
@@ -1,536 +0,0 @@
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
- # Basic Multi Bezier builder
71
- #
72
- # raw pieces must be an array of arrays of the form [type, p1, p2, p3, p4] as defined for single
73
- def Bezier.multi( rawpieces )
74
- return Bezier.new( :pieces, rawpieces.map {|piece| BezierSpline[*piece]} )
75
- end
76
-
77
- # "regular" Multi Bezier :raw specification
78
- #
79
- # args is a list of points and control points as [p1, pc1, p2, pc2, p3, pc3]
80
- #
81
- # Beware that
82
- # Bezier.raw( p1, pc1, pc2, p2 ) == Bezier.raws( p1, pc1, p2, p2 + (p2-pc2))
83
- def Bezier.raws( *args )
84
- rawpieces = []
85
- args.foreach(2).pairs do |pair1, pair2|
86
- p1, pc1 = pair1
87
- p2, pc2 = pair2
88
- rawpieces << [:raw, p1, pc1, p2+(p2-pc2), p2]
89
- end
90
- return Bezier.multi( rawpieces )
91
- end
92
-
93
- # "regular" Multi Bezier :vector specification
94
- #
95
- # args is a list of points and control points as [p1, v1, p2, v2, p3, v3]
96
- #
97
- # Beware that
98
- # Bezier.vector( p1, v1, p2, v2 ) == Bezier.vectors( p1, v1, p2, -v2)
99
- def Bezier.vectors( *args )
100
- rawpieces = []
101
- args.foreach(2).pairs do |pair1, pair2|
102
- p1, v1 = pair1
103
- p2, v2 = pair2
104
- rawpieces << [:vector, p1, v1, p2, -v2]
105
- end
106
- return Bezier.multi( rawpieces )
107
- end
108
-
109
-
110
-
111
-
112
- # bezier point, as
113
- # Bezier[:raw, V2D::O, V2D::O, V2D::O, V2D::O]
114
- O = Bezier.raw( V2D::O, V2D::O, V2D::O, V2D::O )
115
-
116
-
117
- # return piece of index "index",as BezierSpline object
118
- #
119
- # index can be
120
- # - integer : in that case, simple return @pieces[index]
121
- # - float : in that case, use second default argument
122
- # this method must actually be very rarely called, as usually
123
- # we want to compute something with index, and in that case we
124
- # want to delegate computation to a BezierSpline, with parameter
125
- # mapping parametermapping
126
- def piece( index, parametertype=:length )
127
- pieceindex = index
128
- if index.is_a? Float
129
- pieceindex, t = self.parametermapping( index, parametertype )
130
- end
131
- return @pieces[ pieceindex ]
132
- end
133
-
134
- # return number of pieces
135
- def piecenumber
136
- return @pieces.length
137
- end
138
-
139
- # -------------------------------------------------------------
140
- # curve interface
141
- # -------------------------------------------------------------
142
-
143
- # overload Curve::viewbox
144
- def viewbox
145
- return V2D.viewbox( self.pointlist() )
146
- end
147
-
148
-
149
- # -------------------------------------------------------------
150
- # piece shortcuts
151
- # -------------------------------------------------------------
152
-
153
- # generic method to return points list of a curve
154
- # b = Bezier[ :pieces, [[:raw, p1, pc1, pc2, p2], [:raw, p2, pc2b, pc3, p3]] ]
155
- # b.pointlist #=> equiv to b.pointlist(:raw)
156
- # b.pointlist(:raw) #=> [p1, pc1, pc2, p2, p2, pc2b, pc3, p3]
157
- # if you want to get a particular piece pointlist, do
158
- # b.piece( t ).pointlist(nil|:raw|:vector)
159
- # TODO : result must be cached by type
160
- def pointlist( type=:raw )
161
- result = []
162
- @pieces.each {|piece| result = result + piece.pointlist(type)}
163
- # Trace("Bezier.pointlist result #{result.inspect}")
164
- return result
165
- end
166
-
167
- # shortcut method to get curve first point
168
- def firstpoint
169
- return self.pointlist()[0]
170
- end
171
-
172
- # shortcut method to get curve last point
173
- def lastpoint
174
- return self.pointlist()[-1]
175
- end
176
-
177
- # shortcut method to build Bezier objects for each piece
178
- def beziers
179
- return self.pieces.map{ |piece| Bezier.single( *piece.data ) }
180
- end
181
-
182
- # shortcut method to retrieve a piece as an Bezier object
183
- def bezier( index )
184
- return Bezier.single( *self.piece( index ).data )
185
- end
186
-
187
- # shortcut method to retrieve data list of a bezier
188
- def data
189
- return self.pieces.map{ |piece| piece.data }
190
- end
191
-
192
-
193
- # -------------------------------------------------------------
194
- # piece delegation computation
195
- # -------------------------------------------------------------
196
-
197
- # with index (must be Float) and parametertype as inputs, must compute :
198
- # - the index of the piece on which the computation must take place
199
- # - the new parameter value corresponding to bezier computation input
200
- def parametermapping( index, parametertype=:length, side=:right ) #:nodoc:
201
- check_parametertype( parametertype )
202
- result = []
203
- if parametertype == :length
204
- index = (0.0..1.0).trim( index )
205
- result = length_parameter_mapping( index, side )
206
- else # no need to test parametertype value, as check_parametertype already do it
207
- index = (0.0..self.piecenumber.to_f).trim( index )
208
- pieceindex = index < self.piecenumber ? index.to_i : (index-1).to_i
209
- t = index - pieceindex
210
- result = [pieceindex, t]
211
- end
212
-
213
- return result
214
- end
215
-
216
- # utilitary method to factorize abscissa parameter type value checking
217
- def check_parametertype( parametertype ) #:nodoc:
218
- if !(parametertype == :parameter or parametertype == :length )
219
- Kernel::raise("Invalid parametertype value #{parametertype}")
220
- end
221
- end
222
-
223
- # -------------------------------------------------------------
224
- # bezier computations
225
- # -------------------------------------------------------------
226
-
227
- # compute a point at curviligne abscissa or parameter t
228
- #
229
- # curve method redefinition
230
- def point( t, container=nil, parametertype=:length )
231
- pieceindex, t = parametermapping( t, parametertype )
232
- return self.piece( pieceindex ).point( t, container )
233
- end
234
-
235
- # compute tangent at curviligne abscissa or parameter t
236
- #
237
- # curve method redefinition
238
- def tangent ( t, container=nil, parametertype=:length )
239
- pieceindex, t = parametermapping( t, parametertype )
240
- return self.piece( pieceindex ).tangent( t, container )
241
- end
242
-
243
- # compute acceleration at curviligne abscissa or parameter t
244
- #
245
- # curve method redefinition
246
- def acc( t, container=nil, parametertype=:length )
247
- pieceindex, t = parametermapping( t, parametertype )
248
- return self.piece( pieceindex ).acc( t, container )
249
- end
250
-
251
- # curve method redefinition to factorize parametermapping
252
- def frame( t, container=nil, parametertype=:length )
253
- pieceindex, t = parametermapping( t, parametertype )
254
- containerpoint = container ? container.center : nil
255
- containertangent = container ? container.vector : nil
256
- point = self.piece( pieceindex ).point( t, containerpoint )
257
- tangent = self.piece( pieceindex ).tangent( t, containertangent )
258
- rotation = self.rotation( nil, tangent )
259
- scale = self.scale( nil, tangent )
260
- result = container ? container : Frame[ :center, point, :vector, tangent, :rotation, rotation, :scale, scale ]
261
- return result
262
- end
263
-
264
- # -------------------------------------------------------------
265
- # subpiece computation
266
- # -------------------------------------------------------------
267
-
268
- # generalize Bezier method
269
- def subpieces (t1, t2) #:nodoc:
270
- # Trace("subpieces t1 #{t1} t2 #{t2}")
271
- pieceindex1, t1 = parametermapping( t1, :length, :left )
272
- pieceindex2, t2 = parametermapping( t2, :length, :right )
273
- # Trace("after translation pieceindex1 #{pieceindex1} t1 #{t1} pieceindex2 #{pieceindex2} t2 #{t2}")
274
- result = []
275
-
276
- if pieceindex1 == pieceindex2
277
- result = [self.piece( pieceindex1 ).subpiece( t1, t2 )]
278
- else
279
- result << self.piece( pieceindex1 ).subpiece( t1, 1.0 )
280
- if pieceindex1 + 1 != pieceindex2
281
- result += self.pieces[pieceindex1+1..pieceindex2-1]
282
- end
283
- result << self.piece( pieceindex2 ).subpiece( 0.0, t2 )
284
- end
285
- return result
286
- end
287
-
288
- # compute the sub curve between abscissa t1 and t2
289
- #
290
- # may result in a multi-pieces Bezier
291
- #
292
- # Note: special modulo effect to deal with closed bezier curve
293
- #
294
- # TODO: improve code (bas design)
295
- def subbezier( t1, t2)
296
- # return Bezier.new( :pieces, self.subpieces( t1, t2 ) )
297
- ranges = (0.0..1.0).modulos( (t1..t2) )
298
- # Trace("Bezier::subbezier t1 #{t1} t2 #{t2} ranges #{ranges.inspect}")
299
- pieces = []
300
- ranges.each do |range|
301
- range = range.sort
302
- pieces += self.subpieces( range.begin, range.end )
303
- end
304
-
305
- bezier = Bezier.new( :pieces, pieces )
306
- if t1 > t2
307
- bezier = bezier.reverse
308
- end
309
- return bezier
310
- end
311
-
312
- # -------------------------------------------------------------
313
- # reverse
314
- # -------------------------------------------------------------
315
-
316
- # return a new Bezier curve reversed from current one
317
- #
318
- # simply reverse BezierSpline pieces, both internally and in the :pieces list
319
- def reverse
320
- newpieces = @pieces.map {|piece| piece.reverse()}
321
- return Bezier.new( :pieces, newpieces.reverse )
322
- end
323
-
324
- # -------------------------------------------------------------
325
- # translation
326
- # -------------------------------------------------------------
327
-
328
- # translate the Bezier curve, by translating its points
329
- def translate( v )
330
- return Bezier.new( :pieces, @pieces.map { |piece| piece.translate( v ) } )
331
- end
332
-
333
- # rotate the Bezier curve, by rotating its points
334
- def rotate( angle, center=V2D::O )
335
- return Bezier.new( :pieces, @pieces.map { |piece| piece.rotate( angle, center ) } )
336
- end
337
-
338
- # central symetry
339
- def sym( center )
340
- return Bezier.new( :pieces, @pieces.map { |piece| piece.sym( center ) } )
341
- end
342
-
343
- # axis symetry
344
- def axesym( point, v )
345
- return Bezier.new( :pieces, @pieces.map { |piece| piece.axesym( point, v ) } )
346
- end
347
-
348
- # -------------------------------------------------------------
349
- # similar (transform is used for samplation)
350
- # see XRVG#33
351
- # -------------------------------------------------------------
352
-
353
- # "Similitude" geometric transformation
354
- #
355
- # See http://en.wikipedia.org/wiki/Similitude_%28geometry%29
356
- #
357
- # Similtude geometric transformation is (firspoint..lastpoint) -> pointrange
358
- #
359
- # TODO : method must be put in Curve interface
360
- def similar( pointrange )
361
- oldRepere = [self.firstpoint, self.lastpoint - self.firstpoint]
362
- newRepere = [pointrange.begin, pointrange.end - pointrange.begin]
363
- rotation = V2D.angle( newRepere[1], oldRepere[1] )
364
- if oldRepere[1].r == 0.0
365
- Kernel::raise("similar error : bezier is length 0")
366
- end
367
- scale = newRepere[1].r / oldRepere[1].r
368
- newpoints = []
369
- self.pointlist.each do |oldpoint|
370
- oldvector = oldpoint - oldRepere[0]
371
- newvector = oldvector.rotate( rotation ) * scale
372
- newpoints.push( newRepere[0] + newvector )
373
- end
374
- splines = []
375
- newpoints.foreach do |p1, p2, p3, p4|
376
- splines.push( BezierSpline[:raw, p1, p2, p3, p4] )
377
- end
378
- return Bezier[ :pieces, splines ]
379
- end
380
-
381
- # -------------------------------------------------------------
382
- # concatenation
383
- # -------------------------------------------------------------
384
-
385
- # Bezier curve concatenation
386
- def +( other )
387
- return Bezier.new( :pieces, self.pieces + other.pieces )
388
- end
389
-
390
- # -------------------------------------------------------------
391
- # svg
392
- # -------------------------------------------------------------
393
-
394
- # return the svg description of the curve
395
- #
396
- # if firstpoint == lastpoint, curve is considered as closed
397
- def svg()
398
- # Trace("Bezier::svg #{self.inspect}")
399
- path = ""
400
- previous = nil
401
- self.pieces().each do |piece|
402
- p1, pc1, pc2, p2 = piece.pointlist
403
- # Trace("previous #{previous.inspect} p1 #{p1.inspect}")
404
- if previous == nil or not (previous - p1).r <= 0.0000001
405
- # Trace("svg bezier not equal => M")
406
- path += "M #{p1.x},#{p1.y}"
407
- end
408
- previous = p2
409
- path += "C #{pc1.x},#{pc1.y} #{pc2.x},#{pc2.y} #{p2.x},#{p2.y}"
410
- end
411
-
412
- if self.firstpoint == self.lastpoint
413
- path += " z"
414
- end
415
-
416
- result = "<path d=\"#{path}\"/>"
417
- return result
418
- end
419
-
420
- # -------------------------------------------------------------
421
- # gdebug
422
- # -------------------------------------------------------------
423
-
424
- # Display Bezier curve decorated with points and control points
425
- def gdebug(render)
426
- self.pieces.each {|piece| piece.gdebug(render)}
427
- end
428
-
429
-
430
- # -------------------------------------------------------------
431
- # length computation
432
- # -------------------------------------------------------------
433
-
434
- # return the length of the bezier curve
435
- #
436
- # simply add pieces lengths
437
- def length
438
- if not @length
439
- @length = compute_length
440
- end
441
- return @length
442
- end
443
-
444
- # Note : lengthranges building should be more functional ...
445
- # must use an interpolator ?
446
- def compute_length #:nodoc:
447
- lengths = self.pieces.map {|piece| piece.length}
448
- # Trace("pieces #{self.pieces.inspect} lenghts #{lengths.inspect}")
449
- result = lengths.sum
450
- if result == 0.0
451
- lengths = [1.0]
452
- else
453
- psum = 0.0
454
- lengths = lengths.map {|v| psum += v/result}
455
- end
456
- lmin = 0.0
457
- @lengthranges = []
458
- lengths.each do |llength|
459
- # Trace("lmin #{lmin} llength #{llength}")
460
- @lengthranges << (lmin..llength)
461
- lmin = llength
462
- end
463
- @lengthranges[-1] = @lengthranges[-1].begin..1.0
464
- return result
465
- end
466
-
467
- # utilitary method for interbezier
468
- #
469
- # return list of piece lengths relatives to total bezier lengths, and cumulated
470
- # Bezier.new( :pieces, [piece1, piece2] ).piecelengths; => [0.0, piece1.length / bezier.length, 1.0]
471
- def piecelengths
472
- result = self.lengthranges.map {|range| range.begin}
473
- result << 1.0
474
- return result
475
- end
476
-
477
- # private method, to compute lengthranges if not existing
478
- def lengthranges #:nodoc:
479
- if not @lengthranges
480
- compute_length
481
- end
482
- return @lengthranges
483
- end
484
-
485
- def length_parameter_mapping( t, side ) #:nodoc:
486
- pieceindex = -1
487
- # Trace("self.lengthranges #{self.lengthranges.inspect}")
488
- self.lengthranges.each_with_index do |lrange,i|
489
- if lrange.include?( t )
490
- pieceindex = i
491
- t = lrange.abscissa( t )
492
- t = self.piece( i ).parameterfromlength( t )
493
- # Trace("pieceindex #{pieceindex} t #{t}")
494
- break
495
- end
496
- end
497
- if ((side == :left) and t.fequal?(1.0) and (pieceindex < self.piecenumber - 1))
498
- pieceindex += 1
499
- t = 0.0
500
- end
501
- if pieceindex == -1
502
- Kernel::raise("length_parameter_mapping error : t #{t} is not in length range #{self.lengthranges.inspect}")
503
- end
504
- return [pieceindex, t]
505
- end
506
-
507
-
508
-
509
- # -------------------------------------------------------------
510
- # sampler computation
511
- # -------------------------------------------------------------
512
- include Samplable
513
- include Splittable
514
-
515
- # filter, sampler methods
516
- #
517
- # just a shortcut to define easily specific sampler on bezier curve
518
- #
519
- # TODO : must be defined on Curve interface !!
520
- def filter(type=:point, &block)
521
- return super(type, &block).addfilter( (0.0..1.0) )
522
- end
523
-
524
- def apply_split( t1, t2 ) #:nodoc:
525
- return self.subbezier( t1, t2 )
526
- end
527
-
528
- alias apply_sample point
529
- # alias apply_split subbezier
530
- alias sampler filter
531
-
532
- # TODO : add generic bezier builder from points : must be adaptative !! (use Fitting)
533
- end
534
- end
535
-
536
-