2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 import attr
, base
, canvas
, color
, helper
, path
, style
, trafo
, unit
31 return (x
> 0) and 1 or (((x
== 0) and 1 or 0) - 1)
33 return (x
>= 0) and 1 or -1
35 def realpolyroots(coeffs
, epsilon
=1e-5): # {{{
36 # use Numeric to find the roots (via an equivalent eigenvalue problem)
37 import Numeric
, LinearAlgebra
40 # build the Matrix of the polynomial problem
41 mat
= Numeric
.zeros((N
, N
), Numeric
.Float
)
45 mat
[0][i
] = -coeffs
[i
+1]/coeffs
[0]
46 # find the eigenvalues of the matrix (== the zeros of the polynomial)
47 zeros
= [complex(zero
) for zero
in LinearAlgebra
.eigenvalues(mat
)]
48 # take only the real zeros
49 zeros
= [zero
.real
for zero
in zeros
if -epsilon
< zero
.imag
< epsilon
]
51 ## check if the zeros are really zeros!
55 # p += coeffs[i] * zero**(N-i)
56 # if abs(p) > epsilon:
57 # raise Exception("value %f instead of 0" % p)
62 def curvescontrols_from_endlines_pt(B
, tangent1
, tangent2
, r1
, r2
, softness
): # {{{
63 # calculates the parameters for two bezier curves connecting two lines (curvature=0)
64 # starting at B - r1*tangent1
65 # ending at B + r2*tangent2
68 # and two tangent vectors heading to and from B
69 # and two radii r1 and r2:
70 # All arguments must be in Points
71 # Returns the seven control points of the two bezier curves:
73 # - control points g1 and f1
75 # - control points f2 and g2
78 # make direction vectors d1: from B to A
80 d1
= -tangent1
[0] / math
.hypot(*tangent1
), -tangent1
[1] / math
.hypot(*tangent1
)
81 d2
= tangent2
[0] / math
.hypot(*tangent2
), tangent2
[1] / math
.hypot(*tangent2
)
83 # 0.3192 has turned out to be the maximum softness available
84 # for straight lines ;-)
86 g
= (15.0 * f
+ math
.sqrt(-15.0*f
*f
+ 24.0*f
))/12.0
88 # make the control points of the two bezier curves
89 f1
= B
[0] + f
* r1
* d1
[0], B
[1] + f
* r1
* d1
[1]
90 f2
= B
[0] + f
* r2
* d2
[0], B
[1] + f
* r2
* d2
[1]
91 g1
= B
[0] + g
* r1
* d1
[0], B
[1] + g
* r1
* d1
[1]
92 g2
= B
[0] + g
* r2
* d2
[0], B
[1] + g
* r2
* d2
[1]
93 d1
= B
[0] + r1
* d1
[0], B
[1] + r1
* d1
[1]
94 d2
= B
[0] + r2
* d2
[0], B
[1] + r2
* d2
[1]
95 e
= 0.5 * (f1
[0] + f2
[0]), 0.5 * (f1
[1] + f2
[1])
97 return (d1
, g1
, f1
, e
, f2
, g2
, d2
)
100 def curveparams_from_endpoints_pt(A
, B
, tangentA
, tangentB
, curvA
, curvB
, obeycurv
=0, epsilon
=1e-5): # {{{
101 # connects points A and B with a bezier curve that has
102 # prescribed tangents dirA, dirB and curvatures curA, curB at the end points.
104 # The sign of the tangent vectors is _always_ enforced. If the solution goes
105 # into the opposite direction, None will be returned.
107 # If obeycurv, the sign of the curvature will be enforced which may cause a
108 # strange solution to be found (or none at all).
110 # normalise the tangent vectors
111 # XXX we get numeric instabilities for ||dirA||==||dirB||==1
112 #norm = math.hypot(*tangentA)
114 #dirA = (tangentA[0] / norm, tangentA[1] / norm)
115 #norm = math.hypot(*tangentB)
117 #dirB = (tangentB[0] / norm, tangentB[1] / norm)
121 T
= dirA
[0] * dirB
[1] - dirA
[1] * dirB
[0]
122 D
= 3 * (dirA
[0] * (B
[1]-A
[1]) - dirA
[1] * (B
[0]-A
[0]))
123 E
= 3 * (dirB
[0] * (B
[1]-A
[1]) - dirB
[1] * (B
[0]-A
[0]))
124 # the variables: \dot X(0) = a * dirA
125 # \dot X(1) = b * dirB
128 # we may switch the sign of the curvatures if not obeycurv
129 # XXX are these formulae correct ??
130 # XXX improve the heuristic for choosing the sign of the curvature !!
132 curvA
= abs(curvA
) * sign1(D
)
133 curvB
= abs(curvB
) * sign1(E
)
135 # ask for some special cases where the equations decouple
136 if abs(T
) < epsilon
*epsilon
:
139 a
= math
.sqrt(abs(a
)) * sign1(a
)
141 b
= math
.sqrt(abs(b
)) * sign1(b
)
142 except ZeroDivisionError:
143 sys
.stderr
.write("*** PyX Warning: The connecting bezier is not uniquely determined. "
144 "The simple heuristic solution may not be optimal.\n")
145 a
= b
= 1.5 * math
.hypot(A
[0] - B
[0], A
[1] - B
[1])
147 if abs(curvA
) < epsilon
:
149 a
= - (E
+ b
*abs(b
)*curvB
*0.5) / T
150 elif abs(curvB
) < epsilon
:
152 b
= (D
- a
*abs(a
)*curvA
*0.5) / T
156 # else find a solution for the full problem
159 # we first try to find all the zeros of the polynomials for a or b (4th order)
160 # this needs Numeric and LinearAlgebra
161 # First try the equation for a
162 coeffs
= (0.125*curvA
*curvA
*curvB
, 0, -0.5*D
*curvA
*curvB
, T
**3, T
*T
*E
+ 0.5*curvB
*D
*D
)
163 cands
= [cand
for cand
in realpolyroots(coeffs
) if cand
> 0]
167 b
= (D
- 0.5*curvA
*a
*a
) / T
169 # then, try the equation for b
170 coeffs
= (0.125*curvB
*curvB
*curvA
, 0, 0.5*E
*curvA
*curvB
, T
**3, -T
*T
*D
+ 0.5*curvA
*E
*E
)
171 cands
= [cand
for cand
in realpolyroots(coeffs
) if cand
> 0]
174 a
= - (E
+ 0.5*curvB
*b
*b
) / T
179 # if the Numeric modules are not available:
180 # solve the coupled system by Newton iteration
181 # 0 = Ga(a,b) = 0.5 a |a| curvA + b * T - D
182 # 0 = Gb(a,b) = 0.5 b |b| curvB + a * T + E
183 # this system is equivalent to the geometric contraints:
184 # the curvature and the normal tangent vectors
185 # at parameters 0 and 1 are to be continuous
186 # the system is solved by 2-dim Newton-Iteration
187 # (a,b)^{i+1} = (a,b)^i - (DG)^{-1} (Ga(a^i,b^i), Gb(a^i,b^i))
190 while max(abs(Ga
),abs(Gb
)) > epsilon
:
191 detDG
= abs(a
*b
) * curvA
*curvB
- T
*T
192 invDG
= [[curvB
*abs(b
)/detDG
, -T
/detDG
], [-T
/detDG
, curvA
*abs(a
)/detDG
]]
194 Ga
= a
*abs(a
)*curvA
*0.5 + b
*T
- D
195 Gb
= b
*abs(b
)*curvB
*0.5 + a
*T
+ E
197 a
, b
= a
- 0.5*invDG
[0][0]*Ga
- 0.5*invDG
[0][1]*Gb
, b
- 0.5*invDG
[1][0]*Ga
- 0.5*invDG
[1][1]*Gb
200 return (abs(a
) / 3.0, abs(b
) / 3.0)
203 def curvecontrols_from_endpoints_pt(A
, B
, tangentA
, tangentB
, curvA
, curvB
, obeycurv
=0, epsilon
=1e-5): # {{{
204 a
, b
= curveparams_from_endpoints_pt(A
, B
, tangentA
, tangentB
, curvA
, curvB
, obeycurv
=obeycurv
, epsilon
=epsilon
)
205 # XXX respect the normalization from curveparams_from_endpoints_pt
206 return A
, (A
[0] + a
* tangentA
[0], A
[1] + a
* tangentA
[1]), (B
[0] - b
* tangentB
[0], B
[1] - b
* tangentB
[1]), B
210 class deformer(attr
.attr
):
212 def deform (self
, basepath
):
215 class cycloid(deformer
): # {{{
216 """Wraps a cycloid around a path.
218 The outcome looks like a metal spring with the originalpath as the axis.
219 radius: radius of the cycloid
220 loops: number of loops from beginning to end of the original path
221 skipfirst/skiplast: undeformed end lines of the original path
225 def __init__(self
, radius
=0.5*unit
.t_cm
, halfloops
=10,
226 skipfirst
=1*unit
.t_cm
, skiplast
=1*unit
.t_cm
, curvesperhloop
=3, sign
=1, turnangle
=45):
227 self
.skipfirst
= skipfirst
228 self
.skiplast
= skiplast
230 self
.halfloops
= halfloops
231 self
.curvesperhloop
= curvesperhloop
233 self
.turnangle
= turnangle
235 def __call__(self
, radius
=None, halfloops
=None,
236 skipfirst
=None, skiplast
=None, curvesperhloop
=None, sign
=None, turnangle
=None):
239 if halfloops
is None:
240 halfloops
= self
.halfloops
241 if skipfirst
is None:
242 skipfirst
= self
.skipfirst
244 skiplast
= self
.skiplast
245 if curvesperhloop
is None:
246 curvesperhloop
= self
.curvesperhloop
249 if turnangle
is None:
250 turnangle
= self
.turnangle
252 return cycloid(radius
=radius
, halfloops
=halfloops
, skipfirst
=skipfirst
, skiplast
=skiplast
,
253 curvesperhloop
=curvesperhloop
, sign
=sign
, turnangle
=turnangle
)
255 def deform(self
, abasepath
):
256 basepath
= abasepath
.normpath()
258 for sp
in basepath
.normsubpaths
:
259 if sp
== basepath
.normsubpaths
[0]:
260 cycloidpath
= self
.deformsubpath(sp
)
262 cycloidpath
.join(self
.deformsubpath(sp
))
266 def deformsubpath(self
, subpath
):
268 skipfirst
= abs(unit
.topt(self
.skipfirst
))
269 skiplast
= abs(unit
.topt(self
.skiplast
))
270 radius
= abs(unit
.topt(self
.radius
))
271 turnangle
= self
.turnangle
* math
.pi
/ 180.0
273 cosTurn
= math
.cos(turnangle
)
274 sinTurn
= math
.sin(turnangle
)
276 # make list of the lengths and parameters at points on subpath where we will add cycloid-points
277 totlength
= subpath
.arclen_pt()
278 if totlength
<= skipfirst
+ skiplast
+ 2*radius
*sinTurn
:
279 sys
.stderr
.write("*** PyX Warning: subpath is too short for deformation with cycloid -- skipping...\n")
280 return path
.normpath([subpath
])
282 # parametrisation is in rotation-angle around the basepath
283 # differences in length, angle ... between two basepoints
284 # and between basepoints and controlpoints
285 Dphi
= math
.pi
/ self
.curvesperhloop
286 phis
= [i
* Dphi
for i
in range(self
.halfloops
* self
.curvesperhloop
+ 1)]
287 DzDphi
= (totlength
- skipfirst
- skiplast
- 2*radius
*sinTurn
) * 1.0 / (self
.halfloops
* math
.pi
* cosTurn
)
288 Dz
= (totlength
- skipfirst
- skiplast
- 2*radius
*sinTurn
) * 1.0 / (self
.halfloops
* self
.curvesperhloop
* cosTurn
)
289 zs
= [i
* Dz
for i
in range(self
.halfloops
* self
.curvesperhloop
+ 1)]
290 # from path._arctobcurve:
291 # optimal relative distance along tangent for second and third control point
292 L
= 4 * radius
* (1 - math
.cos(Dphi
/2)) / (3 * math
.sin(Dphi
/2))
294 # Now the transformation of z into the turned coordinate system
295 Zs
= [ skipfirst
+ radius
*sinTurn
# here the coordinate z starts
296 - sinTurn
*radius
*math
.cos(phi
) + cosTurn
*DzDphi
*phi
# the transformed z-coordinate
298 params
= subpath
._arclentoparam
_pt
(Zs
)[0]
300 # get the positions of the splitpoints in the cycloid
302 for phi
, param
in zip(phis
, params
):
303 # the cycloid is a circle that is stretched along the subpath
304 # here are the points of that circle
305 basetrafo
= subpath
.trafo([param
])[0]
307 # The point on the cycloid, in the basepath's local coordinate system
308 baseZ
, baseY
= 0, radius
*math
.sin(phi
)
310 # The tangent there, also in local coords
311 tangentX
= -cosTurn
*radius
*math
.sin(phi
) + sinTurn
*DzDphi
312 tangentY
= radius
*math
.cos(phi
)
313 tangentZ
= sinTurn
*radius
*math
.sin(phi
) + DzDphi
*cosTurn
314 norm
= math
.sqrt(tangentX
*tangentX
+ tangentY
*tangentY
+ tangentZ
*tangentZ
)
315 tangentY
, tangentZ
= tangentY
/norm
, tangentZ
/norm
317 # Respect the curvature of the basepath for the cycloid's curvature
318 # XXX this is only a heuristic, not a "true" expression for
319 # the curvature in curved coordinate systems
320 pathradius
= subpath
.curveradius_pt([param
])[0]
321 if pathradius
is not None:
322 factor
= (pathradius
- baseY
) / pathradius
328 # The control points prior and after the point on the cycloid
329 preeZ
, preeY
= baseZ
- l
* tangentZ
, baseY
- l
* tangentY
330 postZ
, postY
= baseZ
+ l
* tangentZ
, baseY
+ l
* tangentY
332 # Now put everything at the proper place
333 points
.append(basetrafo
._apply
(preeZ
, self
.sign
* preeY
) +
334 basetrafo
._apply
(baseZ
, self
.sign
* baseY
) +
335 basetrafo
._apply
(postZ
, self
.sign
* postY
))
338 sys
.stderr
.write("*** PyX Warning: subpath is too short for deformation with cycloid -- skipping...\n")
339 return path
.normpath([subpath
])
341 # Build the path from the pointlist
342 # containing (control x 2, base x 2, control x 2)
343 if skipfirst
> subpath
.epsilon
:
344 newpath
= subpath
.split([0, params
[0], 1])[0]
345 newpath
.append(path
.normcurve_pt(*(points
[0][2:6] + points
[1][0:4])))
346 cycloidpath
= path
.normpath([newpath
])
348 cycloidpath
= path
.normpath([path
.normsubpath([path
.normcurve_pt(*(points
[0][2:6] + points
[1][0:4]))], 0)])
349 for i
in range(1, len(points
)-1):
350 cycloidpath
.normsubpaths
[-1].append(path
.normcurve_pt(*(points
[i
][2:6] + points
[i
+1][0:4])))
351 if skiplast
> subpath
.epsilon
:
352 cycloidpath
.join(path
.normpath([subpath
.split([0, params
[-1], 1])[-1]]))
358 class smoothed(deformer
): # {{{
360 """Bends corners in a path.
362 This decorator replaces corners in a path with bezier curves. There are two cases:
363 - If the corner lies between two lines, _two_ bezier curves will be used
364 that are highly optimized to look good (their curvature is to be zero at the ends
365 and has to have zero derivative in the middle).
366 Additionally, it can controlled by the softness-parameter.
367 - If the corner lies between curves then _one_ bezier is used that is (except in some
368 special cases) uniquely determined by the tangents and curvatures at its end-points.
369 In some cases it is necessary to use only the absolute value of the curvature to avoid a
370 cusp-shaped connection of the new bezier to the old path. In this case the use of
371 "obeycurv=0" allows the sign of the curvature to switch.
372 - The radius argument gives the arclength-distance of the corner to the points where the
373 old path is cut and the beziers are inserted.
374 - Path elements that are too short (shorter than the radius) are skipped
377 def __init__(self
, radius
, softness
=1, obeycurv
=0):
379 self
.softness
= softness
380 self
.obeycurv
= obeycurv
382 def __call__(self
, radius
=None, softness
=None, obeycurv
=None):
386 softness
= self
.softness
388 obeycurv
= self
.obeycurv
389 return smoothed(radius
=radius
, softness
=softness
, obeycurv
=obeycurv
)
391 def deform(self
, abasepath
):
392 basepath
= abasepath
.normpath()
393 smoothpath
= path
.path()
395 for sp
in basepath
.normsubpaths
:
396 smoothpath
+= self
.deformsubpath(sp
)
400 def deformsubpath(self
, normsubpath
):
402 radius
= unit
.topt(self
.radius
)
404 npitems
= normsubpath
.normsubpathitems
405 arclens
= [npitem
.arclen_pt(normsubpath
.epsilon
) for npitem
in npitems
]
407 # 1. Build up a list of all relevant normsubpathitems
408 # and the lengths where they will be cut (length with respect to the normsubpath)
411 for no
in range(len(arclens
)):
413 # a first selection criterion for skipping too short normsubpathitems
414 # the rest will queeze the radius
416 npitemnumbers
.append(no
)
418 sys
.stderr
.write("*** PyX Warning: smoothed is skipping a normsubpathitem that is too short\n")
420 # XXX: what happens, if 0 or -1 is skipped and path not closed?
422 # 2. Find the parameters, points,
423 # and calculate tangents and curvatures
424 params
, tangents
, curvatures
, points
= [], [], [], []
425 for no
in npitemnumbers
:
429 # find the parameter(s): either one or two
430 if no
is npitemnumbers
[0] and not normsubpath
.closed
:
431 pars
= npitem
._arclentoparam
_pt
([max(0, alen
- radius
)], normsubpath
.epsilon
)[0]
432 elif alen
> 2 * radius
:
433 pars
= npitem
._arclentoparam
_pt
([radius
, alen
- radius
], normsubpath
.epsilon
)[0]
435 pars
= npitem
._arclentoparam
_pt
([0.5 * alen
], normsubpath
.epsilon
)[0]
437 # find points, tangents and curvatures
440 # XXX: there is no trafo method for normsubpathitems?
441 thetrafo
= normsubpath
.trafo([par
+ no
])[0]
442 p
= thetrafo
._apply
(0,0)
443 # XXX thetrafo._apply(1,0) causes numeric instabilities in
444 # bezier_by_endpoints
445 t
= thetrafo
._apply
(100,0)
447 ts
.append((t
[0]-p
[0], t
[1]-p
[1]))
448 c
= npitem
.curveradius_pt([par
])[0]
449 if c
is None: cs
.append(0)
450 else: cs
.append(1.0/c
)
455 curvatures
.append(cs
)
457 # create empty path to collect pathitems
458 # this will be returned as normpath, later
459 smoothpath
= path
.path()
460 do_moveto
= 1 # we do not know yet where to moveto
461 # 3. First part of extra handling of closed paths
462 if not normsubpath
.closed
:
463 bpart
= npitems
[npitemnumbers
[0]].split([0, params
[0], 1])[0]
465 smoothpath
.append(path
.moveto_pt(*bpart
.atbegin_pt()))
467 if isinstance(bpart
, path
.normline_pt
):
468 smoothpath
.append(path
.lineto_pt(*bpart
.atend_pt()))
469 elif isinstance(bpart
, path
.normcurve_pt
):
470 smoothpath
.append(path
.curveto_pt(bpart
.x1_pt
, bpart
.y1_pt
, bpart
.x2_pt
, bpart
.y2_pt
, bpart
.x3_pt
, bpart
.y3_pt
))
473 # 4. Do the splitting for the first to the last element,
474 # a closed path must be closed later
475 for i
in range(len(npitemnumbers
)-1+(normsubpath
.closed
==1)):
476 this
= npitemnumbers
[i
]
477 next
= npitemnumbers
[(i
+1) % len(npitemnumbers
)]
478 thisnpitem
, nextnpitem
= npitems
[this
], npitems
[next
]
480 # split thisnpitem apart and take the middle peace
481 if len(points
[this
]) == 2:
482 mpart
= thisnpitem
.split([0] + params
[this
] + [1])[1]
484 smoothpath
.append(path
.moveto_pt(*mpart
.atbegin_pt()))
486 if isinstance(mpart
, path
.normline_pt
):
487 smoothpath
.append(path
.lineto_pt(*mpart
.atend_pt()))
488 elif isinstance(mpart
, path
.normcurve_pt
):
489 smoothpath
.append(path
.curveto_pt(mpart
.x1_pt
, mpart
.y1_pt
, mpart
.x2_pt
, mpart
.y2_pt
, mpart
.x3_pt
, mpart
.y3_pt
))
491 # add the curve(s) replacing the corner
492 if isinstance(thisnpitem
, path
.normline_pt
) and isinstance(nextnpitem
, path
.normline_pt
) \
493 and (next
-this
== 1 or (this
==0 and next
==len(npitems
)-1)):
494 d1
,g1
,f1
,e
,f2
,g2
,d2
= curvescontrols_from_endlines_pt(
495 thisnpitem
.atend_pt(), tangents
[this
][-1], tangents
[next
][0],
496 math
.hypot(points
[this
][-1][0] - thisnpitem
.atend_pt()[0], points
[this
][-1][1] - thisnpitem
.atend_pt()[1]),
497 math
.hypot(points
[next
][0][0] - nextnpitem
.atbegin_pt()[0], points
[next
][0][1] - nextnpitem
.atbegin_pt()[1]),
498 softness
=self
.softness
)
500 smoothpath
.append(path
.moveto_pt(*d1
))
502 smoothpath
.append(path
.curveto_pt(*(g1
+ f1
+ e
)))
503 smoothpath
.append(path
.curveto_pt(*(f2
+ g2
+ d2
)))
504 #for X in [d1,g1,f1,e,f2,g2,d2]:
505 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
507 A
,B
,C
,D
= curvecontrols_from_endpoints_pt(
508 points
[this
][-1], points
[next
][0],
509 tangents
[this
][-1], tangents
[next
][0],
510 curvatures
[this
][-1], curvatures
[next
][0],
511 obeycurv
=self
.obeycurv
, epsilon
=normsubpath
.epsilon
)
513 smoothpath
.append(path
.moveto_pt(*A
))
515 smoothpath
.append(path
.curveto_pt(*(B
+ C
+ D
)))
517 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
519 # 5. Second part of extra handling of closed paths
520 if normsubpath
.closed
:
522 smoothpath
.append(path
.moveto_pt(*dp
.strokepath
.atbegin()))
523 sys
.stderr
.write("*** PyXWarning: The whole subpath has been smoothed away -- sorry\n")
524 smoothpath
.append(path
.closepath())
526 epart
= npitems
[npitemnumbers
[-1]].split([0, params
[-1][0], 1])[-1]
528 smoothpath
.append(path
.moveto_pt(*epart
.atbegin_pt()))
530 if isinstance(epart
, path
.normline_pt
):
531 smoothpath
.append(path
.lineto_pt(*epart
.atend_pt()))
532 elif isinstance(epart
, path
.normcurve_pt
):
533 smoothpath
.append(path
.curveto_pt(epart
.x1_pt
, epart
.y1_pt
, epart
.x2_pt
, epart
.y2_pt
, epart
.x3_pt
, epart
.y3_pt
))
538 smoothed
.clear
= attr
.clearclass(smoothed
)
541 smoothed
.SHARP
= smoothed(radius
=_base
/math
.sqrt(64))
542 smoothed
.SHARp
= smoothed(radius
=_base
/math
.sqrt(32))
543 smoothed
.SHArp
= smoothed(radius
=_base
/math
.sqrt(16))
544 smoothed
.SHarp
= smoothed(radius
=_base
/math
.sqrt(8))
545 smoothed
.Sharp
= smoothed(radius
=_base
/math
.sqrt(4))
546 smoothed
.sharp
= smoothed(radius
=_base
/math
.sqrt(2))
547 smoothed
.normal
= smoothed(radius
=_base
)
548 smoothed
.round = smoothed(radius
=_base
*math
.sqrt(2))
549 smoothed
.Round
= smoothed(radius
=_base
*math
.sqrt(4))
550 smoothed
.ROund
= smoothed(radius
=_base
*math
.sqrt(8))
551 smoothed
.ROUnd
= smoothed(radius
=_base
*math
.sqrt(16))
552 smoothed
.ROUNd
= smoothed(radius
=_base
*math
.sqrt(32))
553 smoothed
.ROUND
= smoothed(radius
=_base
*math
.sqrt(64))