http://mathling.com/art/meanders library module
http://mathling.com/art/meanders
Meanders: smooth meanders
Regular meander:
θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
θ = tangential angle, s=arc length
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s))
Looping meander()
dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s))
dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s ))
= Σ[i=1:N] d((A[i] sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] d((sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s )
Example: Plot a random smooth meander in upper left of canvas
let $box := box:box(0, 0, box:width($canvas) div 2, box:height($canvas) div 2): let
$path := meanders:meander($box, $randomizers, $parameters)
return draw:draw($path, map {"stroke":"blue","fill":"none","width":5})
Parameters:
Randomizers:
meanders.A.n: terms in the meander function; higher=more complex
meanders.A.initial: A1
meanders.A.ratio: A2/A1
meanders.A.fade: decay rate for A3...An
meanders.λ.initial: λ1
meanders.λ.ratio: λ2/λ1
meanders.λ.fade: decay rate for λ3...λn
meanders.extent: extent of curve to draw
meanders.rotation: rotation to apply to curve
Rendering parameters:
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Bleeding edge
Imports
http://mathling.com/art/componentsimport module namespace components="http://mathling.com/art/components" at "../art/components.xqy"http://mathling.com/type/distribution
import module namespace dist="http://mathling.com/type/distribution" at "../types/distributions.xqy"http://mathling.com/type/wrapper
import module namespace wrapper="http://mathling.com/type/wrapper" at "../types/wrapper.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/geometric/spline
import module namespace spline="http://mathling.com/geometric/spline" at "../geo/spline.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random" at "../core/random.xqy"http://mathling.com/art/core
import module namespace core="http://mathling.com/art/core" at "../art/core.xqy"http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/shape/paths
import module namespace paths="http://mathling.com/shape/paths" at "../shapes/paths.xqy"http://mathling.com/shape
import module namespace shapes="http://mathling.com/shape" at "../shapes/shapes.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"
Functions
Function: components
declare function components() as map(xs:string, map(xs:string,item()*))
declare function components() as map(xs:string, map(xs:string,item()*))
Standard callback: Subcomponent map.
Example. render=true() and mode="default" are the defaults if unspecified
map {
"example":
map {
"namespace": "http://mathling.com/art/example",
"render": true(),
"mode": "default"
}
}
Returns
- map(xs:string,map(xs:string,item()*))
declare function this:components() as map(xs:string, map(xs:string,item()*)) { components:expand( map { }, this:lookup#2 ) }
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()*)
Standard callback: Set of rendering parameters.
Params
- canvas as map(xs:string,item()*): drawing canvas
- algorithm-parameters as map(xs:string,item()*): set of algorithm parameters
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()*) { map { } }
Function: algorithm-mode-parameters
declare function algorithm-mode-parameters($mode as xs:string,
$resolution as xs:string,
$canvas as map(xs:string,item()*)) as map(xs:string,item()*)
declare function algorithm-mode-parameters($mode as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*)) as map(xs:string,item()*)
Standard callback: Set of algorithm parameters specific to a particular mode.
Params
- mode as xs:string: selected mode
- resolution as xs:string: defined resolution
- canvas as map(xs:string,item()*): drawing canvas
Returns
- map(xs:string,item()*)
declare function this:algorithm-mode-parameters( $mode as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*) ) as map(xs:string,item()*) { switch ($mode) case "default" return map {} default return errors:error("ML-BADPARMS", ("mode", $mode)) }
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()*)
Standard callback: Set of default algorithm parameters.
Params
- resolution as xs:string: defined resolution
- canvas as map(xs:string,item()*): drawing canvas
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()*) { map { } }
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()*)
Standard callback: Set of randomizers.
Params
- canvas as map(xs:string,item()*): drawing canvas
- parameters as map(xs:string,item()*): algorithm parameter bundle
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 { "meanders.A.n": dist:binomial(4, 30), "meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"), "meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"), "meanders.A.fade": dist:normal(1.0, 0.2)=>dist:min(0.3)=>dist:max(3.0)=>dist:cast("decimal"), "meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"), "meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"), "meanders.λ.fade": dist:normal(1.0, 0.5)=>dist:min(0.1)=>dist:max(3.0)=>dist:cast("decimal"), "meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"), "meanders.rotation": $rand:STD-DEGREES=>dist:cast("decimal") } }
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?
Standard callback: component colophon (string attached to signature).
Params
- parameters as map(xs:string,item()*): algorithm parameter bundle
Returns
- xs:string?
declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string? { "Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s); Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s); θ=tangential angle; plotted via Whewell equation " }
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()*))
Standard callback: component metadata (in addition to dump of randomizers
and parameters)
Params
- canvas as map(xs:string,item()*): drawing canvas
- randomizers as map(xs:string,item()*): active randomizers for component
- parameters as map(xs:string,item()*): active parameters for component
declare function this:metadata( $canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*) ) { () }
Function: meander
declare function meander($canvas as map(xs:string, item()*),
$randomizers as map(xs:string, item()*),
$parameters as map(xs:string, item()*))
declare function meander($canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*))
meander()
Construct a random smooth meander that fits the canvas.
$canvas: space for meander; it will be centered and scaled to this space
$randomizers: randomizers to use
$parameters: parameters to use
Params
- canvas as map(xs:string,item()*)
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
declare function this:meander( $canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*) ) { let $dynamics := core:random-parameters( core:random-parameter-map(( "meanders.A.n", "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade", "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade", "meanders.extent", "meanders.rotation" )), $randomizers ) let $n := core:parameter("meanders.A.n", $dynamics) let $A-initial := core:parameter("meanders.A.initial", $dynamics) let $A-ratio := core:parameter("meanders.A.ratio", $dynamics) let $A-fade := core:parameter("meanders.A.fade", $dynamics) let $λ-initial := core:parameter("meanders.λ.initial", $dynamics) let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics) let $λ-fade := core:parameter("meanders.λ.fade", $dynamics) let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2)) let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2)) let $extent := core:parameter("meanders.extent", $dynamics) let $rotation := core:parameter("meanders.rotation", $dynamics) let $center := box:center($canvas) let $radius := min((box:width($canvas), box:height($canvas))) div 2 let $n-points := util:round(box:diagonal($canvas) * 2) let $stroke := ( paths:meander( $center, $radius, $As, $λs, $n-points, $extent, false() )=>geom:decimal(1)=>spline:spline()=> geom:mutate(function($p) {$p=>point:as-dimension(2)})=> geom:rotate($rotation, $center)=> shapes:scale-and-center($canvas)=> path:with-edge-ts() ) return ( wrapper:wrapper( $stroke, $dynamics ) ) }
Function: looping-meander
declare function looping-meander($canvas as map(xs:string, item()*),
$randomizers as map(xs:string, item()*),
$parameters as map(xs:string, item()*))
declare function looping-meander($canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*))
looping-meander()
Construct a random smooth looping meander that fits the canvas.
$canvas: space for meander; it will be centered and scaled to this space
$randomizers: randomizers to use
$parameters: parameters to use
Params
- canvas as map(xs:string,item()*)
- randomizers as map(xs:string,item()*)
- parameters as map(xs:string,item()*)
declare function this:looping-meander( $canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*) ) { let $dynamics := core:random-parameters( core:random-parameter-map(( "meanders.A.n", "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade", "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade", "meanders.extent", "meanders.rotation" )), $randomizers ) let $n := core:parameter("meanders.A.n", $dynamics) let $A-initial := core:parameter("meanders.A.initial", $dynamics) let $A-ratio := core:parameter("meanders.A.ratio", $dynamics) let $A-fade := core:parameter("meanders.A.fade", $dynamics) let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics) let $λ-initial := core:parameter("meanders.λ.initial", $dynamics) let $λ-fade := core:parameter("meanders.λ.fade", $dynamics) let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2)) let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2)) let $extent := core:parameter("meanders.extent", $dynamics) let $rotation := core:parameter("meanders.rotation", $dynamics) let $center := box:center($canvas) let $radius := min((box:width($canvas), box:height($canvas))) div 2 let $n-points := util:round(box:diagonal($canvas) * 2) let $stroke := ( paths:looping-meander( $center, $radius, $As, $λs, $n-points, $extent, false() )=>geom:decimal(1)=>spline:spline()=> geom:mutate(function($p) {$p=>point:as-dimension(2)})=> geom:rotate($rotation, $center)=> shapes:scale-and-center($canvas)=> path:with-edge-ts() ) return ( wrapper:wrapper( $stroke, $dynamics ) ) }
Original Source Code
xquery version "3.1"; (:~ : Meanders: smooth meanders : Regular meander: : θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] ) : θ = tangential angle, s=arc length : For set of A,λ,φ; let's take φ[i]=0 : (x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s)) : : Looping meander() : dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] ) : For set of A,λ,φ; let's take φ[i]=0 : (x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s)) : dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s )) : = Σ[i=1:N] d((A[i] sin( (2π/λ[i])s ))) : = Σ[i=1:N] A[i] d((sin( (2π/λ[i])s ))) : = Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s ) : : Example: Plot a random smooth meander in upper left of canvas : : let $box := box:box(0, 0, box:width($canvas) div 2, box:height($canvas) div 2): let $path := meanders:meander($box, $randomizers, $parameters) : return draw:draw($path, map {"stroke":"blue","fill":"none","width":5}) : : Parameters: : : Randomizers: : meanders.A.n: terms in the meander function; higher=more complex : meanders.A.initial: A1 : meanders.A.ratio: A2/A1 : meanders.A.fade: decay rate for A3...An : meanders.λ.initial: λ1 : meanders.λ.ratio: λ2/λ1 : meanders.λ.fade: decay rate for λ3...λn : meanders.extent: extent of curve to draw : meanders.rotation: rotation to apply to curve : : Rendering parameters: : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since mmm 2023 : @custom:Status Bleeding edge :) module namespace this="http://mathling.com/art/meanders"; import module namespace core="http://mathling.com/art/core" at "../art/core.xqy"; import module namespace components="http://mathling.com/art/components" at "../art/components.xqy"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.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 wrapper="http://mathling.com/type/wrapper" at "../types/wrapper.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 spline="http://mathling.com/geometric/spline" at "../geo/spline.xqy"; import module namespace path="http://mathling.com/geometric/path" at "../geo/path.xqy"; import module namespace paths="http://mathling.com/shape/paths" at "../shapes/paths.xqy"; import module namespace shapes="http://mathling.com/shape" at "../shapes/shapes.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"; (:~ : In XQuery this is pointless. It is here for single-sourcing, because in : XSLT we have to make sure the lookup happens in the right context. So: : a bit of a hack, to be sure. :) declare %private function this:lookup($qname as xs:QName, $n as xs:integer) as function(*)? { function-lookup($qname, $n) }; (:~ : Standard callback: Subcomponent map. : Example. render=true() and mode="default" are the defaults if unspecified : map { : "example": : map { : "namespace": "http://mathling.com/art/example", : "render": true(), : "mode": "default" : } : } :) declare function this:components() as map(xs:string, map(xs:string,item()*)) { components:expand( map { }, this:lookup#2 ) }; (:~ : Standard callback: Set of rendering parameters. : @param $canvas: drawing canvas : @param $algorithm-parameters: set of algorithm parameters :) declare function this:rendering-parameters( $canvas as map(xs:string,item()*), $algorithm-parameters as map(xs:string,item()*) ) as map(xs:string,item()*) { map { } }; (:~ : Standard callback: Set of algorithm parameters specific to a particular mode. : @param $mode: selected mode : @param $resolution: defined resolution : @param $canvas: drawing canvas :) declare function this:algorithm-mode-parameters( $mode as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*) ) as map(xs:string,item()*) { switch ($mode) case "default" return map {} default return errors:error("ML-BADPARMS", ("mode", $mode)) }; (:~ : Standard callback: Set of default algorithm parameters. : @param $resolution: defined resolution : @param $canvas: drawing canvas :) declare function this:algorithm-parameters( $resolution as xs:string, $canvas as map(xs:string, item()*) ) as map(xs:string,item()*) { map { } }; (:~ : Standard callback: Set of randomizers. : @param $canvas: drawing canvas : @param $parameters: algorithm parameter bundle :) declare function this:randomizers( $canvas as map(xs:string,item()*), $parameters as map(xs:string,item()*) ) as map(xs:string,item()*) { map { "meanders.A.n": dist:binomial(4, 30), "meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"), "meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"), "meanders.A.fade": dist:normal(1.0, 0.2)=>dist:min(0.3)=>dist:max(3.0)=>dist:cast("decimal"), "meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"), "meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"), "meanders.λ.fade": dist:normal(1.0, 0.5)=>dist:min(0.1)=>dist:max(3.0)=>dist:cast("decimal"), "meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"), "meanders.rotation": $rand:STD-DEGREES=>dist:cast("decimal") } }; (:~ : Standard callback: component colophon (string attached to signature). : @param $parameters: algorithm parameter bundle :) declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string? { "Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s); Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s); θ=tangential angle; plotted via Whewell equation " }; (:~ : Standard callback: component metadata (in addition to dump of randomizers : and parameters) : : @param $canvas: drawing canvas : @param $randomizers: active randomizers for component : @param $parameters: active parameters for component :) declare function this:metadata( $canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*) ) { () }; (:======================================================================:) (:~ : meander() : Construct a random smooth meander that fits the canvas. : : $canvas: space for meander; it will be centered and scaled to this space : $randomizers: randomizers to use : $parameters: parameters to use : @return Scaled spline wrapped with dynamic parameters :) declare function this:meander( $canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*) ) { let $dynamics := core:random-parameters( core:random-parameter-map(( "meanders.A.n", "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade", "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade", "meanders.extent", "meanders.rotation" )), $randomizers ) let $n := core:parameter("meanders.A.n", $dynamics) let $A-initial := core:parameter("meanders.A.initial", $dynamics) let $A-ratio := core:parameter("meanders.A.ratio", $dynamics) let $A-fade := core:parameter("meanders.A.fade", $dynamics) let $λ-initial := core:parameter("meanders.λ.initial", $dynamics) let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics) let $λ-fade := core:parameter("meanders.λ.fade", $dynamics) let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2)) let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2)) let $extent := core:parameter("meanders.extent", $dynamics) let $rotation := core:parameter("meanders.rotation", $dynamics) let $center := box:center($canvas) let $radius := min((box:width($canvas), box:height($canvas))) div 2 let $n-points := util:round(box:diagonal($canvas) * 2) let $stroke := ( paths:meander( $center, $radius, $As, $λs, $n-points, $extent, false() )=>geom:decimal(1)=>spline:spline()=> geom:mutate(function($p) {$p=>point:as-dimension(2)})=> geom:rotate($rotation, $center)=> shapes:scale-and-center($canvas)=> path:with-edge-ts() ) return ( wrapper:wrapper( $stroke, $dynamics ) ) }; (:~ : looping-meander() : Construct a random smooth looping meander that fits the canvas. : : $canvas: space for meander; it will be centered and scaled to this space : $randomizers: randomizers to use : $parameters: parameters to use : @return Scaled spline wrapped with dynamic parameters :) declare function this:looping-meander( $canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*) ) { let $dynamics := core:random-parameters( core:random-parameter-map(( "meanders.A.n", "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade", "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade", "meanders.extent", "meanders.rotation" )), $randomizers ) let $n := core:parameter("meanders.A.n", $dynamics) let $A-initial := core:parameter("meanders.A.initial", $dynamics) let $A-ratio := core:parameter("meanders.A.ratio", $dynamics) let $A-fade := core:parameter("meanders.A.fade", $dynamics) let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics) let $λ-initial := core:parameter("meanders.λ.initial", $dynamics) let $λ-fade := core:parameter("meanders.λ.fade", $dynamics) let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2)) let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2)) let $extent := core:parameter("meanders.extent", $dynamics) let $rotation := core:parameter("meanders.rotation", $dynamics) let $center := box:center($canvas) let $radius := min((box:width($canvas), box:height($canvas))) div 2 let $n-points := util:round(box:diagonal($canvas) * 2) let $stroke := ( paths:looping-meander( $center, $radius, $As, $λs, $n-points, $extent, false() )=>geom:decimal(1)=>spline:spline()=> geom:mutate(function($p) {$p=>point:as-dimension(2)})=> geom:rotate($rotation, $center)=> shapes:scale-and-center($canvas)=> path:with-edge-ts() ) return ( wrapper:wrapper( $stroke, $dynamics ) ) };