xrvg 0.0.6 → 0.0.7

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/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
-