xrvg 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +10 -2
- data/examples/arcrecurse2.rb +1 -1
- data/examples/gradientgeo.rb +1 -1
- data/examples/hellocrown2.rb +1 -1
- data/examples/palette_circle.rb +1 -1
- data/examples/sample.rb +1 -1
- data/lib/bezier.rb +588 -0
- data/lib/bezierbuilders.rb +210 -0
- data/lib/beziermotifs.rb +121 -0
- data/lib/bezierspline.rb +235 -0
- data/lib/beziertools.rb +245 -0
- data/lib/color.rb +567 -0
- data/lib/fitting.rb +203 -0
- data/lib/frame.rb +33 -0
- data/lib/geovariety.rb +128 -0
- data/lib/interbezier.rb +87 -0
- data/lib/intersection.rb +89 -0
- data/lib/render.rb +266 -0
- data/lib/samplation.rb +44 -1
- data/lib/shape.rb +421 -0
- data/lib/spiral.rb +89 -0
- data/lib/style.rb +76 -0
- data/lib/utils.rb +1 -17
- data/lib/xrvg.rb +47 -0
- data/test/test_attributable.rb +19 -0
- data/test/test_bezier.rb +7 -0
- data/test/test_color.rb +36 -1
- data/test/test_intersection.rb +42 -0
- data/test/test_sample.rb +56 -5
- data/test/test_spiral.rb +8 -0
- data/test/test_utils.rb +1 -32
- metadata +19 -2
data/lib/beziertools.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# Some BezierBuilder implementations
|
2
|
+
# - old-fashioned SimpleBezier
|
3
|
+
# - useful BezierLevel
|
4
|
+
# - powerful Offset
|
5
|
+
# - interesting Ondulation
|
6
|
+
# - simple ClosureBezier
|
7
|
+
|
8
|
+
require 'bezierbuilders'
|
9
|
+
|
10
|
+
module XRVG
|
11
|
+
# = SimpleBezier
|
12
|
+
# == Content
|
13
|
+
# Simple Bezier interpolator, that builds a multipiece "regular" bezier curve from a list of points
|
14
|
+
# == Algo
|
15
|
+
# For each point triplet :
|
16
|
+
# - tangent vector of the middle point is the vector mean of vector from first to middle and vector frommiddle to last.
|
17
|
+
# First and last tangents are computed by symetry
|
18
|
+
# == Note
|
19
|
+
# FittingBezier is a better class for point bezier interpolation. However, this class is kept mainly for historical reasons.
|
20
|
+
class SimpleBezier < BezierBuilder
|
21
|
+
attribute :support, nil, Array
|
22
|
+
|
23
|
+
# BezierBuilder overloading: see SimpleBezier description for algorithm
|
24
|
+
def compute( )
|
25
|
+
points = @support
|
26
|
+
if points.length < 2
|
27
|
+
Kernel::raise("SimpleBezier support must have at least two points")
|
28
|
+
elsif points.length == 2
|
29
|
+
return LinearBezier.build( :support, points).data
|
30
|
+
end
|
31
|
+
|
32
|
+
result = Array.new
|
33
|
+
p1 = points[0]
|
34
|
+
v1 = V2D::O
|
35
|
+
cpiece = [:vector, p1, v1]
|
36
|
+
|
37
|
+
points.triplets do |p1, p2, p3|
|
38
|
+
v = ((p2 - p1)..( p3 - p2 )).middle * 1.0 / 3.0
|
39
|
+
result.push( cpiece + [p2, v.reverse] )
|
40
|
+
cpiece = [:vector, p2, v]
|
41
|
+
end
|
42
|
+
|
43
|
+
pr2 = points[-1]
|
44
|
+
vr2 = V2D::O
|
45
|
+
result.push( cpiece + [pr2, vr2] )
|
46
|
+
|
47
|
+
# compute first and last piece again, by symetry
|
48
|
+
piece0 = Bezier.single( *result[0] )
|
49
|
+
p1, v1, p2, v2 = piece0.pointlist(:vector)
|
50
|
+
pv = (p2 - p1)
|
51
|
+
angle = v2.angle - pv.angle
|
52
|
+
v1 = (-v2).rotate( -2.0 * angle )
|
53
|
+
result[0] = [:vector, p1, v1, p2, v2]
|
54
|
+
|
55
|
+
piecel = Bezier.single( *result[-1] )
|
56
|
+
p1, v1, p2, v2 = piecel.pointlist(:vector)
|
57
|
+
pv = (p2 - p1)
|
58
|
+
angle = v1.angle - pv.angle
|
59
|
+
v2 = (-v1).rotate( -2.0 * angle )
|
60
|
+
result[-1] = [:vector, p1, v1, p2, v2]
|
61
|
+
|
62
|
+
return result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Interpolation extension with SimpleBezier
|
68
|
+
#
|
69
|
+
module Interpolation
|
70
|
+
|
71
|
+
def compute_simplebezier
|
72
|
+
points = self.samplelist.foreach(2).map { |index, value| V2D[index,value] }
|
73
|
+
@simplebezier = SimpleBezier[ :support, points ]
|
74
|
+
end
|
75
|
+
|
76
|
+
def getcurve
|
77
|
+
if not @simplebezier
|
78
|
+
self.compute_simplebezier
|
79
|
+
end
|
80
|
+
return @simplebezier
|
81
|
+
end
|
82
|
+
|
83
|
+
def simplebezier( dindex )
|
84
|
+
return self.getcurve.sample( dindex ).y
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# = Offset bezier builder
|
91
|
+
# == Content
|
92
|
+
# Generic offset bezier builder.
|
93
|
+
# == Attributes
|
94
|
+
# attribute :support, nil, Curve
|
95
|
+
# attribute :abscissasampler, (0.0..1.0), Samplable
|
96
|
+
# attribute :ampl, 0.5, :samplable
|
97
|
+
# attribute :nsamples, 100
|
98
|
+
class Offset < FitBezierBuilder
|
99
|
+
attribute :support, nil, Curve
|
100
|
+
attribute :abscissasampler, (0.0..1.0), Samplable
|
101
|
+
attribute :ampl, 0.5, :samplable
|
102
|
+
attribute :nsamples, 100
|
103
|
+
|
104
|
+
# overload FitBezierBuilder.points to compute Offset points
|
105
|
+
#
|
106
|
+
# Algo: for each sample, compute point, normal and amp, and newpoint = point + normal.norm * ampl
|
107
|
+
def points
|
108
|
+
result = []
|
109
|
+
SyncS[self.abscissasampler, self.ampl].samples( self.nsamples) do |abscissa, amplsample|
|
110
|
+
frame = self.support.frame( abscissa )
|
111
|
+
result << frame.center + frame.vector.ortho.norm * amplsample
|
112
|
+
end
|
113
|
+
return result
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# = Fuseau bezier builder
|
118
|
+
# == Content
|
119
|
+
# Just shortcut class for Offset with :ampl = (1.0..0.0)
|
120
|
+
# == Attributes
|
121
|
+
# attribute :maxwidth, 0.1
|
122
|
+
class Fuseau < Offset
|
123
|
+
attribute :maxwidth, 0.1
|
124
|
+
|
125
|
+
# overload Offset.ampl method by returning (self.maxwidth..0.0)
|
126
|
+
def ampl
|
127
|
+
return (self.maxwidth..0.0)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
# = BezierLevel bezier builder
|
134
|
+
# == Content
|
135
|
+
# Compute "roller coaster" bezier curves
|
136
|
+
#
|
137
|
+
# Can be used as a x-progressing curve, that is as an interpolation curve
|
138
|
+
# == Attributes
|
139
|
+
# attribute :samplelist, [], Array
|
140
|
+
# :samplelist must contain pairs of cartesien coords [x1,y1,x2,y2,...], x between 0.0 and 1.0 (as for interpolator)
|
141
|
+
class BezierLevel < BezierBuilder
|
142
|
+
attribute :samplelist, [], Array
|
143
|
+
|
144
|
+
# Overload BezierBuilder build method
|
145
|
+
#
|
146
|
+
# Algo: simply interpolate [x,y] couples as V2D, with SimpleBezier bezier builder
|
147
|
+
def BezierLevel.build( *args )
|
148
|
+
builder = BezierLevel.new( *args )
|
149
|
+
points = []
|
150
|
+
builder.samplelist.foreach do |x,y|
|
151
|
+
points << V2D[x,y]
|
152
|
+
end
|
153
|
+
return SimpleBezier[ :support, points ]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# = ClosureBezier bezier builder
|
158
|
+
# == Content
|
159
|
+
# Simple bezier operator that take a list of beziers and produce a concatenate multipieces closed bezier curve.
|
160
|
+
# Missing segments are completed with lines
|
161
|
+
class ClosureBezier < BezierBuilder
|
162
|
+
attribute :bezierlist
|
163
|
+
|
164
|
+
# BezierBuilder compute overloading
|
165
|
+
def compute
|
166
|
+
result = []
|
167
|
+
result += self.bezierlist[0].pieces
|
168
|
+
self.bezierlist[1..-1].each do |bezier|
|
169
|
+
lastpoint = result[-1].lastpoint
|
170
|
+
newpoint = bezier.firstpoint
|
171
|
+
if not V2D.vequal?( lastpoint, newpoint )
|
172
|
+
result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
|
173
|
+
end
|
174
|
+
result += bezier.pieces
|
175
|
+
end
|
176
|
+
lastpoint = result[-1].lastpoint
|
177
|
+
newpoint = result[0].firstpoint
|
178
|
+
if not V2D.vequal?( lastpoint, newpoint )
|
179
|
+
result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
|
180
|
+
end
|
181
|
+
result = result.map {|piece| piece.data}
|
182
|
+
# Trace("result #{result.inspect}")
|
183
|
+
return result
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# = Ondulation bezier builder
|
188
|
+
# == Content
|
189
|
+
# Generic ondulation bezier builder.
|
190
|
+
# == Attributes
|
191
|
+
# attribute :support, nil, Curve
|
192
|
+
# attribute :ampl, 0.5, :samplable
|
193
|
+
# attribute :abscissasampler, (0.0..1.0), Samplable
|
194
|
+
# attribute :freq, 10
|
195
|
+
# :support is a Curve
|
196
|
+
# :abscissas must be a Float Samplable, as (0.0..1.0).geo(3.0)
|
197
|
+
# :ampl can be a constant or a sampler
|
198
|
+
# :freq is the number of oscillations to be computed
|
199
|
+
class Ondulation < BezierBuilder
|
200
|
+
attribute :support, nil, Curve
|
201
|
+
attribute :ampl, 0.5, :samplable
|
202
|
+
attribute :abscissasampler, (0.0..1.0), Samplable
|
203
|
+
attribute :freq, 10
|
204
|
+
|
205
|
+
# atomic pattern computation
|
206
|
+
#
|
207
|
+
def compute_arc( abs1, abs2, amplitude, sens )
|
208
|
+
mabs = (abs1 + abs2)/2.0
|
209
|
+
p1, halfpoint, p2 = self.support.points( [abs1, mabs, abs2] )
|
210
|
+
# Trace("mabs #{mabs} abs1 #{abs1} abs2 #{abs2} halfpoint #{halfpoint.inspect} p1 #{p1.inspect} p2 #{p2.inspect}")
|
211
|
+
# Trace("normal #{@support.normal( mabs )}")
|
212
|
+
halfnormal = self.support.normal( mabs ).norm * ( sens * amplitude * (p2 - p1).length)
|
213
|
+
# Trace("halfnormal #{halfnormal.inspect}")
|
214
|
+
newpoint = halfpoint + halfnormal
|
215
|
+
tpoint = halfpoint + halfnormal * 3.0
|
216
|
+
t1 = (tpoint - p1 ) / 6.0
|
217
|
+
t2 = (tpoint - p2 ) / 6.0
|
218
|
+
# Trace("newpoint #{newpoint.inspect} p1 #{p1.inspect} (newpoint - p1) #{(newpoint - p1).inspect}")
|
219
|
+
halftangent = self.support.tangent( mabs ).norm * (newpoint - p1).length / 3.0
|
220
|
+
# halftangent = self.support.tangent( mabs ).norm * (p2 - p1).length / 3.0
|
221
|
+
return [[:vector, p1, t1, newpoint, -halftangent], [:vector, newpoint, halftangent, p2, t2]]
|
222
|
+
end
|
223
|
+
|
224
|
+
def compute_interpol( abs1, abs2, amplitude, sens )
|
225
|
+
arc = Bezier.multi( self.compute_arc( abs1, abs2, 1.0, sens ) )
|
226
|
+
subsupport = self.support.subbezier( abs1, abs2 )
|
227
|
+
return InterBezier[ :bezierlist, [0.0, subsupport, 1.0, arc] ].sample( amplitude ).data
|
228
|
+
end
|
229
|
+
|
230
|
+
# algo : for each abscissa, 0.0 of the curve (given the normal)
|
231
|
+
# and for each mean abscissa, :amp normal
|
232
|
+
def compute
|
233
|
+
abscissas = self.abscissasampler.samples( self.freq + 1 )
|
234
|
+
sens = 1.0
|
235
|
+
pieces = []
|
236
|
+
[abscissas.pairs, self.ampl.samples( self.freq )].forzip do |abspair, amplitude|
|
237
|
+
abs1, abs2 = abspair
|
238
|
+
pieces += self.compute_interpol( abs1, abs2, amplitude, sens )
|
239
|
+
sens *= -1.0
|
240
|
+
end
|
241
|
+
return pieces
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end # XRVG
|