http://mathling.com/art/curl library module
http://mathling.com/art/curl
Curls: circle arc curls
Parameters:
curl.mode: what kinds of curls are we making?
Acts as a master switch for default parameter settings
smooth: fat-sinuous-curl
tangent-connected arcs, sliced by rotation from origin with translation
kinked: fat-kinked-curl
rotated from origin, but buggy in an interesting way
interpolated: fat-interpolated-curl
sliced by splines created via tropism function
smoke: smoky-curl
sliced by messing with radius and angle of minicurls
ribbon: ribbon
tangent-connected arc, sliced by rotation from random connection point
curl.scaling: basic sizing of curls
curl.generations: mean number of generations
curl.fickleness: standard deviation of number of generations (as fraction)
curl.fade: how size of curls fades with generation
curl.angle.min: minumum arc of curls
curl.angle.max: maximum arc of curls
curl.radius.min: minimum radius of curls
curl.n-slices: how many rendering slices to do
curl.slice.extent: extent of slices
curl.angle.extent: extent of angles of slices
curl.interpolation: how much to interpolate points in paths
curl.jitter.percent: how much to jitter interpolation
curl.gradients: gradient palettes to use in slicing
curl.opacity: base opacity of curl slices
curl.opacity.fade: fade of opacity across slices
curl.opacity.mode: mode to apply opacity fade
none: all slices have same opacity
symmetric: slices fade to edges
top: slices fade from first to last
bottom: slices fade from last to first
curl.colour.mode: colour selection mode
singular: every slice the same colour from gradient
multiple: every slice a random colour from gradient
centered: every slice a random colour centered on single random colour
sliced: colours sliced across gradient
curl.tropism.function: tropism function to use for slicing (interpolated)
Randomizers:
curl.angle: angle of extent for minicurls
curl.radius: radius of minicurls
curl.generations: how many minicurls
curl.colour: colouring for curl slices
Rendering parameters:
colours.{1-5}.gradient, populated from curl.gradients
stroke-opacity, defaults to empty to support slicing
Copyright© Mary Holstege 2020-2025
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Function Index
algorithm-mode-parameters($resolution as xs:string, $canvas as map(xs:string,item()*), $mode as xs:string, $reverse as xs:boolean) as map(xs:string,item()*)algorithm-mode-parameters($mode-and-reverse as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*)) as map(xs:string,item()*)algorithm-parameters($resolution as xs:string, $canvas as map(xs:string, item()*)) as map(xs:string,item()*)angle($minicurl as map(xs:string,item()*)) as xs:doublecolophon($parameters as map(xs:string,item()*)) as xs:string?component-map($render as xs:boolean, $mode as xs:string) as map(xs:string,item()*)component-map($render as xs:boolean) as map(xs:string,item()*)component-map() as map(xs:string,item()*)curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*) as map(xs:string,item()*)?describe($minicurls as map(xs:string,item()*)*) as xs:stringfat-interpolated-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*fat-kinked-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*fat-sinuous-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*initial-angle($minicurl as map(xs:string,item()*)) as xs:doublemetadata($canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*))minicurl($radius as xs:double, $angle as xs:double) as map(xs:string,item()*)minicurl($radius as xs:double, $angle as xs:double, $initial-angle as xs:double) as map(xs:string,item()*)minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*radius($minicurl as map(xs:string,item()*)) as xs:doublerandomizers($canvas as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)rendering-parameters($canvas as map(xs:string,item()*), $algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)ribbon($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*ribbon($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $pivot as map(xs:string,item()*), $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*sheet($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, (: minicurl* :) $pivot-path as map(xs:string,item()*), (: path :) $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*smoky-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*symmetric-minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*symmetric-minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*symmetric-minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*tropism($pt as map(xs:string,item()*), $origin as map(xs:string,item()*), $i as xs:integer, $d as xs:double)
Imports
http://mathling.com/type/curlimport module namespace curl="http://mathling.com/type/curl"
at "../types/curl.xqy"http://mathling.com/mathimport module namespace mmath="http://mathling.com/math"
at "../math/math.xqy"http://mathling.com/svg/gradientsimport module namespace gradient="http://mathling.com/svg/gradients"
at "../svg/gradients.xqy"http://mathling.com/type/distributionimport module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy"http://mathling.com/geometric/rectangleimport module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy"http://mathling.com/geometric/splineimport module namespace spline="http://mathling.com/geometric/spline"
at "../geo/spline.xqy"http://mathling.com/geometricimport module namespace geom="http://mathling.com/geometric"
at "../geo/euclidean.xqy"http://mathling.com/type/defrefimport module namespace defref="http://mathling.com/type/defref"
at "../types/defref.xqy"http://mathling.com/geometric/pointimport module namespace point="http://mathling.com/geometric/point"
at "../geo/point.xqy"http://mathling.com/core/randomimport module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy"http://mathling.com/art/coreimport module namespace core="http://mathling.com/art/core"
at "../art/core.xqy"http://mathling.com/geometric/pathimport module namespace path="http://mathling.com/geometric/path"
at "../geo/path.xqy"http://mathling.com/geometric/edgeimport module namespace edge="http://mathling.com/geometric/edge"
at "../geo/edge.xqy"http://mathling.com/core/utilitiesimport module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy"
Functions
Function: component-map
declare function component-map($render as xs:boolean,
$mode as xs:string) as map(xs:string,item()*)
declare function component-map($render as xs:boolean, $mode as xs:string) as map(xs:string,item()*)
Map to use in components:expand in caller.
Params
- render as xs:boolean
- mode as xs:string
Returns
- map(xs:string,item()*)
declare function this:component-map(
$render as xs:boolean,
$mode as xs:string
) as map(xs:string,item()*)
{
map {
"namespace": "http://mathling.com/art/curl",
"render": true(),
"mode": "default"
}
}
Function: component-map
declare function component-map($render as xs:boolean) as map(xs:string,item()*)
declare function component-map($render as xs:boolean) as map(xs:string,item()*)
Params
- render as xs:boolean
Returns
- map(xs:string,item()*)
declare function this:component-map(
$render as xs:boolean
) as map(xs:string,item()*)
{
this:component-map($render, "default")
}
Function: component-map
declare function component-map() as map(xs:string,item()*)
declare function component-map() as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:component-map(
) as map(xs:string,item()*)
{
this:component-map(true())
}
Function: rendering-parameters
declare function rendering-parameters($canvas as map(xs:string,item()*),
$algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare function rendering-parameters($canvas as map(xs:string,item()*), $algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- canvas as map(xs:string,item()*)
- algorithm-parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:rendering-parameters(
$canvas as map(xs:string,item()*),
$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let $all-gradients := core:parameter("curl.gradients", $algorithm-parameters)
let $mode := core:parameter("curl.mode", $algorithm-parameters)
return
map {
"meta-description": "Arc curls",
"colours.1.gradient": $all-gradients[1],
"colours.2.gradient": $all-gradients[2],
"colours.3.gradient": $all-gradients[3],
"colours.4.gradient": $all-gradients[4],
"colours.5.gradient": $all-gradients[5],
"stroke-opacity": ""
}
}
Function: algorithm-mode-parameters
declare function algorithm-mode-parameters($resolution as xs:string,
$canvas as map(xs:string,item()*),
$mode as xs:string,
$reverse as xs:boolean) as map(xs:string,item()*)
declare function algorithm-mode-parameters($resolution as xs:string, $canvas as map(xs:string,item()*), $mode as xs:string, $reverse as xs:boolean) as map(xs:string,item()*)
Params
- resolution as xs:string
- canvas as map(xs:string,item()*)
- mode as xs:string
- reverse as xs:boolean
Returns
- map(xs:string,item()*)
declare function this:algorithm-mode-parameters(
$resolution as xs:string,
$canvas as map(xs:string,item()*),
$mode as xs:string,
$reverse as xs:boolean
) as map(xs:string,item()*)
{
let $basic-scaling := box:width(core:canvas($resolution)) idiv 7
return
switch ($mode)
case "smooth" return
map {
"description": "Arc curls: smooth",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 10,
"curl.angle.extent": 10,
"curl.opacity": 1,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "interpolated" return
map {
"description": "Arc curls: interpolated",
"curl.mode": $mode,
"curl.scaling": 1.5 * $basic-scaling,
"curl.generations": 10,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("curl"),
"curl.n-slices": 50,
"curl.slice.extent": 75,
"curl.angle.extent": 20,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 10,
"curl.jitter.percent": 0
}
case "kinked" return
map {
"description": "Arc curls: kinked",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5, (: 0.2 :)
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 20,
"curl.angle.extent": 20,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "smoke" return
map {
"description": "Arc curls: smoke",
"curl.mode": $mode,
"curl.scaling": 0.1 * $basic-scaling,
"curl.generations": 12,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 30,
"curl.angle.max": 330,
"curl.gradients": ("lajolla"),
"curl.n-slices": 150,
"curl.slice.extent": 15,
"curl.angle.extent": 5,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "top",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "ribbon" return
map {
"description": "Arc curls: ribbon",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 10,
"curl.angle.extent": 10,
"curl.opacity": 1,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "symmetric",
"curl.colour.mode": "centered",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
default return map {}
}
Function: algorithm-mode-parameters
declare function algorithm-mode-parameters($mode-and-reverse as xs:string,
$resolution as xs:string,
$canvas as map(xs:string,item()*)) as map(xs:string,item()*)
declare function algorithm-mode-parameters($mode-and-reverse as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- mode-and-reverse as xs:string
- resolution as xs:string
- canvas as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:algorithm-mode-parameters(
$mode-and-reverse as xs:string,
$resolution as xs:string,
$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let $mode := substring-before($mode-and-reverse,'-')
let $reverse :=
if (substring-after($mode-and-reverse,'-')="reverse") then true() else false()
return this:algorithm-mode-parameters($resolution, $canvas, $mode, $reverse)
}
Function: algorithm-parameters
declare function algorithm-parameters($resolution as xs:string,
$canvas as map(xs:string, item()*)) as map(xs:string,item()*)
declare function algorithm-parameters($resolution as xs:string, $canvas as map(xs:string, item()*)) as map(xs:string,item()*)
Params
- resolution as xs:string
- canvas as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:algorithm-parameters(
$resolution as xs:string,
$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
this:algorithm-mode-parameters($resolution, $canvas, "smooth", false())
}
Function: randomizers
declare function randomizers($canvas as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare function randomizers($canvas as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)
Params
- canvas as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:randomizers(
$canvas as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"curl.angle":
dist:uniform(
core:parameter("curl.angle.min", $parameters),
core:parameter("curl.angle.max", $parameters)
)=>dist:cast("integer")
,
"curl.radius":
dist:normal(
core:parameter("curl.scaling", $parameters),
core:parameter("curl.scaling", $parameters) idiv 10
)=>dist:min(core:parameter("curl.radius.min", $parameters))
=>dist:truncation("ceiling")
=>dist:cast("integer")
,
"curl.generations":
let $generations := core:parameter("curl.generations", $parameters)
return
dist:normal(
$generations,
min((
1,
mmath:round(
$generations *
core:parameter("curl.fickleness", $parameters)
)
))
)=>dist:min(1)=>dist:cast("integer")
,
(: Note: this gets std/max adjusted for additional gradients if necessary :)
"curl.colour":
let $n := gradient:n-gradient-colours((core:parameter("curl.gradients", $parameters))[1])
return
dist:normal(rand:get-last#2, $n idiv 20)=>
dist:cast("integer")=>
dist:min(1)=>
dist:max($n)=>
dist:truncation("ceiling")
}
}
Function: colophon
declare function colophon($parameters as map(xs:string,item()*)) as xs:string?
declare function colophon($parameters as map(xs:string,item()*)) as xs:string?
Params
- parameters as map(xs:string,item()*)
Returns
- xs:string?
declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string?
{
"Arc curls created by stitching together circle arcs at their tangents"
}
Function: metadata
declare function metadata($canvas as map(xs:string,item()*),
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*))
declare function metadata($canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*))
Params
- canvas as map(xs:string,item()*)
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
declare function this:metadata(
$canvas as map(xs:string,item()*),
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
)
{
()
}
Function: minicurl
declare function minicurl($radius as xs:double, $angle as xs:double) as map(xs:string,item()*)
declare function minicurl($radius as xs:double, $angle as xs:double) as map(xs:string,item()*)
Params
- radius as xs:double
- angle as xs:double
Returns
- map(xs:string,item()*)
declare function this:minicurl($radius as xs:double, $angle as xs:double) as map(xs:string,item()*)
{
map {
"radius": $radius,
"angle": $angle
}
}
Function: minicurl
declare function minicurl($radius as xs:double, $angle as xs:double, $initial-angle as xs:double) as map(xs:string,item()*)
declare function minicurl($radius as xs:double, $angle as xs:double, $initial-angle as xs:double) as map(xs:string,item()*)
Params
- radius as xs:double
- angle as xs:double
- initial-angle as xs:double
Returns
- map(xs:string,item()*)
declare function this:minicurl($radius as xs:double, $angle as xs:double, $initial-angle as xs:double) as map(xs:string,item()*)
{
this:minicurl($radius, $angle)=>
map:put("initial-angle", $initial-angle)
}
Function: radius
declare function radius($minicurl as map(xs:string,item()*)) as xs:double
declare function radius($minicurl as map(xs:string,item()*)) as xs:double
Params
- minicurl as map(xs:string,item()*)
Returns
- xs:double
declare function this:radius($minicurl as map(xs:string,item()*)) as xs:double
{
$minicurl("radius")
}
Function: angle
declare function angle($minicurl as map(xs:string,item()*)) as xs:double
declare function angle($minicurl as map(xs:string,item()*)) as xs:double
Params
- minicurl as map(xs:string,item()*)
Returns
- xs:double
declare function this:angle($minicurl as map(xs:string,item()*)) as xs:double
{
$minicurl("angle")
}
Function: initial-angle
declare function initial-angle($minicurl as map(xs:string,item()*)) as xs:double
declare function initial-angle($minicurl as map(xs:string,item()*)) as xs:double
Params
- minicurl as map(xs:string,item()*)
Returns
- xs:double
declare function this:initial-angle($minicurl as map(xs:string,item()*)) as xs:double
{
($minicurl("initial-angle"),0)[1]
}
Function: describe
declare function describe($minicurls as map(xs:string,item()*)*) as xs:string
declare function describe($minicurls as map(xs:string,item()*)*) as xs:string
Params
- minicurls as map(xs:string,item()*)*
Returns
- xs:string
declare function this:describe($minicurls as map(xs:string,item()*)*) as xs:string
{
string-join(
for $mini in $minicurls return (
this:radius($mini)||"∠"||this:angle($mini)||
(if (this:initial-angle($mini)!=0)
then "+"||this:initial-angle($mini)
else "")
)," "
)
}
Function: minicurls
declare function minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- curl as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let $generations := $curl=>curl:generations()
let $initial-angle := $curl=>curl:initial-angle()
let $fade := $curl=>curl:fade()
let $radii := curl:radii($generations, $curl)
let $angles := curl:angles($generations, $curl)
for $i in 1 to $generations
let $radius :=
mmath:round(math:pow($fade,($i - 1)) * $radii[$i])
let $angle := mmath:round(math:pow($fade,($i - 1)) * $angles[$i])
return
if ($i=1)
then this:minicurl($radius, $angle, curl:initial-angle($curl))
else this:minicurl($radius, $angle)
}
Function: symmetric-minicurls
declare function symmetric-minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function symmetric-minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- curl as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:symmetric-minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let $generations := $curl=>curl:generations()
let $initial-angle := $curl=>curl:initial-angle()
let $fade := $curl=>curl:fade()
let $radii := curl:radii($generations, $curl)
let $angles := curl:angles($generations, $curl)
for $i in 1 to $generations
let $pow-fade := math:pow($fade,abs(($generations idiv 2) - ($i - 1)))
let $radius := mmath:round($pow-fade * $radii[$i])
let $angle := mmath:round($pow-fade * $angles[$i])
return
if ($i=1)
then this:minicurl($radius, $angle, curl:initial-angle($curl))
else this:minicurl($radius, $angle)
}
Function: minicurls
declare function minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
core:randomize("curl.angle", $randomizers),
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
}
Function: minicurls
declare function minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- initial-angle as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
$initial-angle,
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
}
Function: symmetric-minicurls
declare function symmetric-minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function symmetric-minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:symmetric-minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:symmetric-minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
core:randomize("curl.angle", $randomizers),
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
}
Function: symmetric-minicurls
declare function symmetric-minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function symmetric-minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- initial-angle as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:symmetric-minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:symmetric-minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
$initial-angle,
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
}
Function: curl
declare function curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*) as map(xs:string,item()*)?
declare function curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*) as map(xs:string,item()*)?
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
Returns
- map(xs:string,item()*)?
declare function this:curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*) as map(xs:string,item()*)?
{
let $edges :=
fold-left(1 to count($minicurls), (),
function($edges as map(xs:string,item()*)*, $i as xs:integer) {
$edges,
let $last := $edges[last()]
let $start := if (empty($edges)) then $origin else $last=>edge:end()
let $lω :=
if (empty($edges)) then $minicurls[$i]=>this:initial-angle()
else mmath:round(point:angle($last=>edge:arc-center(), $last=>edge:end()))
let $last-flipped :=
if (empty($edges)) then true() else $last=>edge:arc-flipped()
let $last-radius :=
if (empty($edges))
then 0
else $last=>edge:arc-radius()
let $last-center :=
if (empty($edges))
then $origin
else $last=>edge:arc-center()
let $α := $minicurls[$i]=>this:angle()
let $radius := $minicurls[$i]=>this:radius()
let $center :=
point:destination($last-center, $lω, $last-radius + $radius)
let $θ :=
mmath:remap-degrees(point:angle($center, $start))
let $end :=
if ($last-flipped)
then point:snap(point:destination($center, $θ + $α, $radius))
else point:snap(point:destination($center, $θ - $α, $radius))
return (
edge:arc($center, $radius, $start, $end, not($last-flipped), $α > 180)
)
}
)
where exists($edges)
return path:path($edges)
}
Function: fat-sinuous-curl
declare function fat-sinuous-curl($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function fat-sinuous-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:fat-sinuous-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $rotation := mmath:decimal($i * $angle-sep, 2)
let $translation := mmath:decimal($i * $slice-sep, 2)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"translate("||$translation||","||$translation||") scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($origin)||","||point:y($origin)||")"
}
)
)
}
Function: fat-kinked-curl
declare function fat-kinked-curl($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function fat-kinked-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:fat-kinked-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
for $i in 1 to $n-slices
let $edges :=
for $edge in $curl=>geom:edges()
let $ends := $edge=>edge:arc-ends()
let $start := geom:translate($ends[1], $i * $slice-sep, $i * $slice-sep)
let $end := geom:translate($ends[2], $i * $slice-sep, $i * $slice-sep)
return
edge:arc(
$edge=>edge:arc-center(),
$edge=>edge:arc-radius() + $i * $slice-sep,
$start,
$end,
$edge=>edge:arc-flipped(),
$edge=>edge:arc-large()
)
let $rotated := geom:rotate(path:path($edges), $i * $angle-sep, $origin)
let $scaled := geom:scale($rotated, $scale, $scale, $origin)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return $scaled=>geom:with-properties($properties)
}
Function: smoky-curl
declare function smoky-curl($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function smoky-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:smoky-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $angle-min := core:parameter("curl.angle.min", $parameters)
let $radius-min := core:parameter("curl.radius.min", $parameters)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
for $i in 1 to $n-slices
let $newcurls :=
for $minicurl in $minicurls
let $radius := rand:randomize(dist:normal($minicurl=>this:radius(), $i * $slice-sep)=>dist:min($radius-min))
let $angle := rand:randomize(dist:normal($minicurl=>this:angle(), $i * $slice-sep)=>dist:min($angle-min))
return this:minicurl($radius, $angle)
let $curl := this:curl($origin, $newcurls)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return $curl=>geom:with-properties($properties)
}
Function: tropism
declare function tropism($pt as map(xs:string,item()*), $origin as map(xs:string,item()*), $i as xs:integer, $d as xs:double)
declare function tropism($pt as map(xs:string,item()*), $origin as map(xs:string,item()*), $i as xs:integer, $d as xs:double)
Params
- pt as map(xs:string,item()*)
- origin as map(xs:string,item()*)
- i as xs:integer
- d as xs:double
declare function this:tropism($pt as map(xs:string,item()*), $origin as map(xs:string,item()*), $i as xs:integer, $d as xs:double)
{
let $θ := point:angle($origin, $pt)
return
point:snap(
geom:translate($pt,
$i * $d * math:cos(5*$θ) * math:cos(3*$θ),
$i * $d * math:sin(5*$θ) * math:cos(3*$θ)
)
)
}
Function: fat-interpolated-curl
declare function fat-interpolated-curl($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function fat-interpolated-curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:fat-interpolated-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $path :=
if ($curl-interpolation > 0) then (
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
) else (
$curl=>geom:points()
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $tropism := core:parameter("curl.tropism.function", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
return
fold-left(1 to $n-slices, spline:open-spline($path),
function($paths as map(xs:string,item()*)*, $i as xs:integer) {
$paths,
let $points :=
for $point in $path
return $tropism($point, $origin, $i, $slice-sep)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return spline:open-spline($points)=>geom:with-properties($properties)
}
)
}
Function: ribbon
declare function ribbon($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function ribbon($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:ribbon(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $pivot := rand:select-random($curl=>geom:points())
return
this:ribbon(
$origin,
$minicurls,
$pivot,
$scale,
$gradient-ix,
$fade-scale,
$randomizers,
$parameters
)
}
Function: ribbon
declare function ribbon($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$pivot as map(xs:string,item()*),
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function ribbon($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, $pivot as map(xs:string,item()*), $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- pivot as map(xs:string,item()*)
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:ribbon(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$pivot as map(xs:string,item()*),
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $rotation := ($i - 1) * $angle-sep
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($pivot)||","||point:y($pivot)||")"
}
)
)
}
Function: sheet
declare function sheet($origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*, (: minicurl* :)
$pivot-path as map(xs:string,item()*), (: path :)
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function sheet($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*, (: minicurl* :) $pivot-path as map(xs:string,item()*), (: path :) $scale as xs:double, $gradient-ix as xs:integer, $fade-scale as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- origin as map(xs:string,item()*)
- minicurls as map(xs:string,item()*)*
- pivot-path as map(xs:string,item()*)
- scale as xs:double
- gradient-ix as xs:integer
- fade-scale as xs:double
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:sheet(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*, (: minicurl* :)
$pivot-path as map(xs:string,item()*), (: path :)
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $pivot-points :=
let $n-edges := count($pivot-path=>geom:edges())
return
if ($n-edges <= $n-slices)
then geom:interpolate($pivot-path, 1 + ($n-slices idiv $n-edges))=>geom:points()
else $pivot-path=>geom:points()
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $pivot := $pivot-points[$i]
let $rotation := ($i - 1) * $angle-sep
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($pivot)||","||point:y($pivot)||")"
}
)
)
}
Original Source Code
xquery version "3.1";
(:~
: Curls: circle arc curls
:
: Parameters:
: curl.mode: what kinds of curls are we making?
: Acts as a master switch for default parameter settings
: smooth: fat-sinuous-curl
: tangent-connected arcs, sliced by rotation from origin with translation
: kinked: fat-kinked-curl
: rotated from origin, but buggy in an interesting way
: interpolated: fat-interpolated-curl
: sliced by splines created via tropism function
: smoke: smoky-curl
: sliced by messing with radius and angle of minicurls
: ribbon: ribbon
: tangent-connected arc, sliced by rotation from random connection point
: curl.scaling: basic sizing of curls
: curl.generations: mean number of generations
: curl.fickleness: standard deviation of number of generations (as fraction)
: curl.fade: how size of curls fades with generation
: curl.angle.min: minumum arc of curls
: curl.angle.max: maximum arc of curls
: curl.radius.min: minimum radius of curls
:
: curl.n-slices: how many rendering slices to do
: curl.slice.extent: extent of slices
: curl.angle.extent: extent of angles of slices
: curl.interpolation: how much to interpolate points in paths
: curl.jitter.percent: how much to jitter interpolation
:
: curl.gradients: gradient palettes to use in slicing
: curl.opacity: base opacity of curl slices
: curl.opacity.fade: fade of opacity across slices
: curl.opacity.mode: mode to apply opacity fade
: none: all slices have same opacity
: symmetric: slices fade to edges
: top: slices fade from first to last
: bottom: slices fade from last to first
: curl.colour.mode: colour selection mode
: singular: every slice the same colour from gradient
: multiple: every slice a random colour from gradient
: centered: every slice a random colour centered on single random colour
: sliced: colours sliced across gradient
:
: curl.tropism.function: tropism function to use for slicing (interpolated)
:
: Randomizers:
: curl.angle: angle of extent for minicurls
: curl.radius: radius of minicurls
: curl.generations: how many minicurls
: curl.colour: colouring for curl slices
:
: Rendering parameters:
: colours.{1-5}.gradient, populated from curl.gradients
: stroke-opacity, defaults to empty to support slicing
:
: Copyright© Mary Holstege 2020-2025
: CC-BY (https://creativecommons.org/licenses/by/4.0/)
: @custom:Status Stable
:)
module namespace this="http://mathling.com/art/curl";
import module namespace core="http://mathling.com/art/core"
at "../art/core.xqy";
import module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy";
import module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy";
import module namespace mmath="http://mathling.com/math"
at "../math/math.xqy";
import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy";
import module namespace geom="http://mathling.com/geometric"
at "../geo/euclidean.xqy";
import module namespace point="http://mathling.com/geometric/point"
at "../geo/point.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy";
import module namespace edge="http://mathling.com/geometric/edge"
at "../geo/edge.xqy";
import module namespace path="http://mathling.com/geometric/path"
at "../geo/path.xqy";
import module namespace spline="http://mathling.com/geometric/spline"
at "../geo/spline.xqy";
import module namespace curl="http://mathling.com/type/curl"
at "../types/curl.xqy";
import module namespace defref="http://mathling.com/type/defref"
at "../types/defref.xqy";
import module namespace gradient="http://mathling.com/svg/gradients"
at "../svg/gradients.xqy";
declare namespace svg="http://www.w3.org/2000/svg";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";
declare namespace art="http://mathling.com/art";
(:~
: Map to use in components:expand in caller.
:)
declare function this:component-map(
$render as xs:boolean,
$mode as xs:string
) as map(xs:string,item()*)
{
map {
"namespace": "http://mathling.com/art/curl",
"render": true(),
"mode": "default"
}
};
declare function this:component-map(
$render as xs:boolean
) as map(xs:string,item()*)
{
this:component-map($render, "default")
};
declare function this:component-map(
) as map(xs:string,item()*)
{
this:component-map(true())
};
declare function this:rendering-parameters(
$canvas as map(xs:string,item()*),
$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let $all-gradients := core:parameter("curl.gradients", $algorithm-parameters)
let $mode := core:parameter("curl.mode", $algorithm-parameters)
return
map {
"meta-description": "Arc curls",
"colours.1.gradient": $all-gradients[1],
"colours.2.gradient": $all-gradients[2],
"colours.3.gradient": $all-gradients[3],
"colours.4.gradient": $all-gradients[4],
"colours.5.gradient": $all-gradients[5],
"stroke-opacity": ""
}
};
declare function this:algorithm-mode-parameters(
$resolution as xs:string,
$canvas as map(xs:string,item()*),
$mode as xs:string,
$reverse as xs:boolean
) as map(xs:string,item()*)
{
let $basic-scaling := box:width(core:canvas($resolution)) idiv 7
return
switch ($mode)
case "smooth" return
map {
"description": "Arc curls: smooth",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 10,
"curl.angle.extent": 10,
"curl.opacity": 1,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "interpolated" return
map {
"description": "Arc curls: interpolated",
"curl.mode": $mode,
"curl.scaling": 1.5 * $basic-scaling,
"curl.generations": 10,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("curl"),
"curl.n-slices": 50,
"curl.slice.extent": 75,
"curl.angle.extent": 20,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 10,
"curl.jitter.percent": 0
}
case "kinked" return
map {
"description": "Arc curls: kinked",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5, (: 0.2 :)
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 20,
"curl.angle.extent": 20,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "none",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "smoke" return
map {
"description": "Arc curls: smoke",
"curl.mode": $mode,
"curl.scaling": 0.1 * $basic-scaling,
"curl.generations": 12,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 30,
"curl.angle.max": 330,
"curl.gradients": ("lajolla"),
"curl.n-slices": 150,
"curl.slice.extent": 15,
"curl.angle.extent": 5,
"curl.opacity": 0.6,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "top",
"curl.colour.mode": "sliced",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
case "ribbon" return
map {
"description": "Arc curls: ribbon",
"curl.mode": $mode,
"curl.scaling":
(: if ($reverse) then $basic-scaling * 0.1 else :) $basic-scaling
,
"curl.generations": if ($reverse) then 12 else 15,
"curl.fickleness": 0.5,
"curl.fade": if ($reverse) then 1.25 else 0.7,
"curl.radius.min": 10,
"curl.angle.min": 10,
"curl.angle.max": 30,
"curl.gradients": ("lajolla", "turbidity"),
"curl.n-slices": 200,
"curl.slice.extent": 10,
"curl.angle.extent": 10,
"curl.opacity": 1,
"curl.opacity.fade": 0.99,
"curl.opacity.mode": "symmetric",
"curl.colour.mode": "centered",
"curl.tropism.function": this:tropism#4,
"curl.interpolation": 0,
"curl.jitter.percent": 0
}
default return map {}
};
declare function this:algorithm-mode-parameters(
$mode-and-reverse as xs:string,
$resolution as xs:string,
$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let $mode := substring-before($mode-and-reverse,'-')
let $reverse :=
if (substring-after($mode-and-reverse,'-')="reverse") then true() else false()
return this:algorithm-mode-parameters($resolution, $canvas, $mode, $reverse)
};
declare function this:algorithm-parameters(
$resolution as xs:string,
$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
this:algorithm-mode-parameters($resolution, $canvas, "smooth", false())
};
declare function this:randomizers(
$canvas as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"curl.angle":
dist:uniform(
core:parameter("curl.angle.min", $parameters),
core:parameter("curl.angle.max", $parameters)
)=>dist:cast("integer")
,
"curl.radius":
dist:normal(
core:parameter("curl.scaling", $parameters),
core:parameter("curl.scaling", $parameters) idiv 10
)=>dist:min(core:parameter("curl.radius.min", $parameters))
=>dist:truncation("ceiling")
=>dist:cast("integer")
,
"curl.generations":
let $generations := core:parameter("curl.generations", $parameters)
return
dist:normal(
$generations,
min((
1,
mmath:round(
$generations *
core:parameter("curl.fickleness", $parameters)
)
))
)=>dist:min(1)=>dist:cast("integer")
,
(: Note: this gets std/max adjusted for additional gradients if necessary :)
"curl.colour":
let $n := gradient:n-gradient-colours((core:parameter("curl.gradients", $parameters))[1])
return
dist:normal(rand:get-last#2, $n idiv 20)=>
dist:cast("integer")=>
dist:min(1)=>
dist:max($n)=>
dist:truncation("ceiling")
}
};
declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string?
{
"Arc curls created by stitching together circle arcs at their tangents"
};
declare function this:metadata(
$canvas as map(xs:string,item()*),
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
)
{
()
};
declare function this:minicurl($radius as xs:double, $angle as xs:double) as map(xs:string,item()*)
{
map {
"radius": $radius,
"angle": $angle
}
};
declare function this:minicurl($radius as xs:double, $angle as xs:double, $initial-angle as xs:double) as map(xs:string,item()*)
{
this:minicurl($radius, $angle)=>
map:put("initial-angle", $initial-angle)
};
declare function this:radius($minicurl as map(xs:string,item()*)) as xs:double
{
$minicurl("radius")
};
declare function this:angle($minicurl as map(xs:string,item()*)) as xs:double
{
$minicurl("angle")
};
declare function this:initial-angle($minicurl as map(xs:string,item()*)) as xs:double
{
($minicurl("initial-angle"),0)[1]
};
declare function this:describe($minicurls as map(xs:string,item()*)*) as xs:string
{
string-join(
for $mini in $minicurls return (
this:radius($mini)||"∠"||this:angle($mini)||
(if (this:initial-angle($mini)!=0)
then "+"||this:initial-angle($mini)
else "")
)," "
)
};
declare function this:minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let $generations := $curl=>curl:generations()
let $initial-angle := $curl=>curl:initial-angle()
let $fade := $curl=>curl:fade()
let $radii := curl:radii($generations, $curl)
let $angles := curl:angles($generations, $curl)
for $i in 1 to $generations
let $radius :=
mmath:round(math:pow($fade,($i - 1)) * $radii[$i])
let $angle := mmath:round(math:pow($fade,($i - 1)) * $angles[$i])
return
if ($i=1)
then this:minicurl($radius, $angle, curl:initial-angle($curl))
else this:minicurl($radius, $angle)
};
(: Symmetrically faded, that is :)
declare function this:symmetric-minicurls($curl as map(xs:string,item()*)) as map(xs:string,item()*)*
{
let $generations := $curl=>curl:generations()
let $initial-angle := $curl=>curl:initial-angle()
let $fade := $curl=>curl:fade()
let $radii := curl:radii($generations, $curl)
let $angles := curl:angles($generations, $curl)
for $i in 1 to $generations
let $pow-fade := math:pow($fade,abs(($generations idiv 2) - ($i - 1)))
let $radius := mmath:round($pow-fade * $radii[$i])
let $angle := mmath:round($pow-fade * $angles[$i])
return
if ($i=1)
then this:minicurl($radius, $angle, curl:initial-angle($curl))
else this:minicurl($radius, $angle)
};
declare function this:minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
core:randomize("curl.angle", $randomizers),
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
};
declare function this:minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
$initial-angle,
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
};
declare function this:symmetric-minicurls($randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:symmetric-minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
core:randomize("curl.angle", $randomizers),
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
};
declare function this:symmetric-minicurls($initial-angle as xs:double, $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:symmetric-minicurls(
curl:curl(
core:randomize("curl.generations", $randomizers),
$initial-angle,
core:parameter("curl.fade", $parameters),
core:randomizer("curl.radius", $randomizers),
core:randomizer("curl.angle", $randomizers)
)
)
};
declare function this:curl($origin as map(xs:string,item()*), $minicurls as map(xs:string,item()*)*) as map(xs:string,item()*)?
{
let $edges :=
fold-left(1 to count($minicurls), (),
function($edges as map(xs:string,item()*)*, $i as xs:integer) {
$edges,
let $last := $edges[last()]
let $start := if (empty($edges)) then $origin else $last=>edge:end()
let $lω :=
if (empty($edges)) then $minicurls[$i]=>this:initial-angle()
else mmath:round(point:angle($last=>edge:arc-center(), $last=>edge:end()))
let $last-flipped :=
if (empty($edges)) then true() else $last=>edge:arc-flipped()
let $last-radius :=
if (empty($edges))
then 0
else $last=>edge:arc-radius()
let $last-center :=
if (empty($edges))
then $origin
else $last=>edge:arc-center()
let $α := $minicurls[$i]=>this:angle()
let $radius := $minicurls[$i]=>this:radius()
let $center :=
point:destination($last-center, $lω, $last-radius + $radius)
let $θ :=
mmath:remap-degrees(point:angle($center, $start))
let $end :=
if ($last-flipped)
then point:snap(point:destination($center, $θ + $α, $radius))
else point:snap(point:destination($center, $θ - $α, $radius))
return (
edge:arc($center, $radius, $start, $end, not($last-flipped), $α > 180)
)
}
)
where exists($edges)
return path:path($edges)
};
declare function this:fat-sinuous-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $rotation := mmath:decimal($i * $angle-sep, 2)
let $translation := mmath:decimal($i * $slice-sep, 2)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"translate("||$translation||","||$translation||") scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($origin)||","||point:y($origin)||")"
}
)
)
};
declare function this:fat-kinked-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
for $i in 1 to $n-slices
let $edges :=
for $edge in $curl=>geom:edges()
let $ends := $edge=>edge:arc-ends()
let $start := geom:translate($ends[1], $i * $slice-sep, $i * $slice-sep)
let $end := geom:translate($ends[2], $i * $slice-sep, $i * $slice-sep)
return
edge:arc(
$edge=>edge:arc-center(),
$edge=>edge:arc-radius() + $i * $slice-sep,
$start,
$end,
$edge=>edge:arc-flipped(),
$edge=>edge:arc-large()
)
let $rotated := geom:rotate(path:path($edges), $i * $angle-sep, $origin)
let $scaled := geom:scale($rotated, $scale, $scale, $origin)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return $scaled=>geom:with-properties($properties)
};
declare function this:smoky-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $angle-min := core:parameter("curl.angle.min", $parameters)
let $radius-min := core:parameter("curl.radius.min", $parameters)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
for $i in 1 to $n-slices
let $newcurls :=
for $minicurl in $minicurls
let $radius := rand:randomize(dist:normal($minicurl=>this:radius(), $i * $slice-sep)=>dist:min($radius-min))
let $angle := rand:randomize(dist:normal($minicurl=>this:angle(), $i * $slice-sep)=>dist:min($angle-min))
return this:minicurl($radius, $angle)
let $curl := this:curl($origin, $newcurls)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return $curl=>geom:with-properties($properties)
};
declare function this:tropism($pt as map(xs:string,item()*), $origin as map(xs:string,item()*), $i as xs:integer, $d as xs:double)
{
let $θ := point:angle($origin, $pt)
return
point:snap(
geom:translate($pt,
$i * $d * math:cos(5*$θ) * math:cos(3*$θ),
$i * $d * math:sin(5*$θ) * math:cos(3*$θ)
)
)
};
declare function this:fat-interpolated-curl(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $path :=
if ($curl-interpolation > 0) then (
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
) else (
$curl=>geom:points()
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := count(gradient:gradient-colours($gradient))
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $tropism := core:parameter("curl.tropism.function", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
return
fold-left(1 to $n-slices, spline:open-spline($path),
function($paths as map(xs:string,item()*)*, $i as xs:integer) {
$paths,
let $points :=
for $point in $path
return $tropism($point, $origin, $i, $slice-sep)
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
let $properties :=
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2)
}
return spline:open-spline($points)=>geom:with-properties($properties)
}
)
};
declare function this:ribbon(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $pivot := rand:select-random($curl=>geom:points())
return
this:ribbon(
$origin,
$minicurls,
$pivot,
$scale,
$gradient-ix,
$fade-scale,
$randomizers,
$parameters
)
};
declare function this:ribbon(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*,
$pivot as map(xs:string,item()*),
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $rotation := ($i - 1) * $angle-sep
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($pivot)||","||point:y($pivot)||")"
}
)
)
};
declare function this:sheet(
$origin as map(xs:string,item()*),
$minicurls as map(xs:string,item()*)*, (: minicurl* :)
$pivot-path as map(xs:string,item()*), (: path :)
$scale as xs:double,
$gradient-ix as xs:integer,
$fade-scale as xs:double,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
let $curl := this:curl($origin, $minicurls)
let $curl-interpolation := core:parameter("curl.interpolation", $parameters)
let $curl-jitter-percent := core:parameter("curl.jitter.percent", $parameters)
let $curl :=
if ($curl-interpolation > 0) then (
path:path(edge:to-edges(
if ($curl-jitter-percent > 0) then (
geom:interpolate-jittered($curl, $curl-interpolation, $curl-jitter-percent)
) else (
geom:interpolate($curl, $curl-interpolation)
)
))
) else (
$curl
)
let $all-gradients := core:parameter("curl.gradients", $parameters)
let $gradient-ix := mmath:modix($gradient-ix, count($all-gradients))
let $gradient := $all-gradients[$gradient-ix]
let $n-colours := gradient:n-gradient-colours($gradient)
let $_ := util:assert($n-colours!=0, "Missing gradient "||$gradient)
let $curl-opacity := core:parameter("curl.opacity", $parameters)
let $opacity-fade := core:parameter("curl.opacity.fade", $parameters)
let $opacity-mode := core:parameter("curl.opacity.mode", $parameters)
let $colour-mode := core:parameter("curl.colour.mode", $parameters)
let $n-slices := core:parameter("curl.n-slices", $parameters)
let $slice-extent := core:parameter("curl.slice.extent", $parameters)
let $angle-extent := core:parameter("curl.angle.extent", $parameters)
let $slice-sep := ($slice-extent * $fade-scale) div $n-slices
let $angle-sep := ($angle-extent * $fade-scale) div $n-slices
let $gradient-sep := $n-colours div $n-slices
let $single-stop := rand:select-random(1 to $n-colours)
let $centered-colour :=
if ($gradient-ix=1) then core:randomizer("curl.colour", $randomizers)
else (
let $n := gradient:n-gradient-colours($gradient)
return
if ($n=$n-colours) then core:randomizer("curl.colour", $randomizers)
else core:randomizer("curl.colour", $randomizers)=>dist:std($n idiv 20)=>dist:max($n)
)
let $pivot-points :=
let $n-edges := count($pivot-path=>geom:edges())
return
if ($n-edges <= $n-slices)
then geom:interpolate($pivot-path, 1 + ($n-slices idiv $n-edges))=>geom:points()
else $pivot-path=>geom:points()
let $id := rand:id("curl")
return (
defref:def($id, $curl),
for $i in 1 to $n-slices
let $pivot := $pivot-points[$i]
let $rotation := ($i - 1) * $angle-sep
let $opacity :=
switch ($opacity-mode)
case "symmetric" return $curl-opacity * $fade-scale * math:pow($opacity-fade, abs(($n-slices idiv 2) - $i))
case "bottom" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($i idiv 2))
case "top" return $curl-opacity * $fade-scale * math:pow($opacity-fade, ($n-slices - $i) idiv 2)
default return $curl-opacity * $fade-scale
let $stop :=
switch($colour-mode)
case "singular" return $single-stop
case "multiple" return rand:select-random(1 to $n-colours)
case "centered" return rand:randomize(1, $single-stop, $centered-colour)
case "sliced" return 1 + ceiling($gradient-sep*($i - 1))
default return 1 + ceiling($gradient-sep*($i - 1))
return
defref:ref($id,
map {
"gradient": $gradient-ix,
"stop": $stop,
"stroke-opacity": mmath:decimal($opacity,2),
"transform":
"scale("||$scale||","||$scale||") rotate("||$rotation||","||point:x($pivot)||","||point:y($pivot)||")"
}
)
)
};