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